zlwu 4 mesiacov pred
rodič
commit
2f217111ac
50 zmenil súbory, kde vykonal 2188 pridanie a 260 odobranie
  1. 80 0
      backend/scripts/reset_process_categories.py
  2. 202 0
      backend/scripts/seed_processes.py
  3. 54 0
      docs/my-material-usage-tab-demos/01-current-baseline.html
  4. 58 0
      docs/my-material-usage-tab-demos/02-full-width-track.html
  5. 68 0
      docs/my-material-usage-tab-demos/03-icon-cards.html
  6. 51 0
      docs/my-material-usage-tab-demos/04-left-underline.html
  7. 52 0
      docs/my-material-usage-tab-demos/05-inline-header.html
  8. 58 0
      docs/my-material-usage-tab-demos/06-pill-with-hint.html
  9. 52 0
      docs/my-material-usage-tab-demos/07-bar-accent.html
  10. 123 0
      docs/my-material-usage-tab-demos/_demo-shell.js
  11. 726 0
      docs/my-material-usage-tab-demos/_shared.css
  12. 77 0
      docs/my-material-usage-tab-demos/index.html
  13. 7 7
      web-admin/graphify-out/.graphify_extract.json
  14. 1 1
      web-admin/graphify-out/GRAPH_REPORT.md
  15. 10 10
      web-admin/graphify-out/manifest.json
  16. 4 1
      web-admin/src/App.vue
  17. 4 1
      web-admin/src/api/material-usage.ts
  18. 139 0
      web-admin/src/assets/styles/core/app.scss
  19. 0 0
      web-admin/src/graphify-out/cache/ast/021ac45cbaf04b17ea17ac90f9651c31b1074afc7384878627ad93c248c74202.json
  20. 1 0
      web-admin/src/graphify-out/cache/ast/18a5f3b0cded4431bb43e4952a42bf842c9f8ef2f954a08337f91d304cbfbf63.json
  21. 1 0
      web-admin/src/graphify-out/cache/ast/195ceef3fa9fc30908886f48acda49ee96003c2f9e9101b7e830f9ae8576f3f6.json
  22. 1 0
      web-admin/src/graphify-out/cache/ast/2990d6021bb3098b3f06030bd47efb8a97a728629b4b865b86b2a39526ef739c.json
  23. 1 0
      web-admin/src/graphify-out/cache/ast/422e18aaf73c8116f7a508857e18caef8b3f78f87f88dc79228453e9e287b8d3.json
  24. 0 0
      web-admin/src/graphify-out/cache/ast/591fee48dae891f848b36ff28d4c82abda99fec9472a6caab067f45e6691a409.json
  25. 1 0
      web-admin/src/graphify-out/cache/ast/94134ffbe39ad939427fb924de3700926ac0727583ad88dc46cf46a68caba438.json
  26. 1 0
      web-admin/src/graphify-out/cache/ast/9e16682a821819196f321f7f474753bb34308d9926a2438ff92bba899fb6e403.json
  27. 1 0
      web-admin/src/graphify-out/cache/ast/c9f57eb08209ebac085325fa49702d2da83ea4cdd49afac729a6356f527091d3.json
  28. 1 0
      web-admin/src/graphify-out/cache/ast/d1a37e024f217848981c2ef23af67fa6b56463a6a1f502c40ffaa40deefef33a.json
  29. 0 0
      web-admin/src/graphify-out/cache/ast/e2498813516768e7f555269cef080facde799a10eb36ccf5ae07ca0b8c06ba93.json
  30. 1 1
      web-admin/src/views/cell-storage/my-material-usage/index.vue
  31. 1 1
      web-admin/src/views/cell-storage/payment-confirm/index.vue
  32. 1 1
      web-admin/src/views/pilot-service/cell-product-type/cell-product-kanban.scss
  33. 1 1
      web-admin/src/views/pilot-service/cell-product-type/index.vue
  34. 1 1
      web-admin/src/views/pilot-service/clinical-payment-confirm/index.vue
  35. 1 1
      web-admin/src/views/production/cleanroom-garment/index.vue
  36. 4 4
      web-admin/src/views/production/my-production-material-usage/index.vue
  37. 1 1
      web-admin/src/views/production/my-production-task/index.vue
  38. 2 2
      web-admin/src/views/production/output-type/index.vue
  39. 131 53
      web-admin/src/views/production/process-category/OutputTypeTab.vue
  40. 250 166
      web-admin/src/views/production/process-category/index.vue
  41. 8 0
      web-admin/src/views/production/process-category/process-category-kanban.scss
  42. 2 0
      web-admin/src/views/production/process/ProcessRouteTab.vue
  43. 3 1
      web-admin/src/views/production/process/index.vue
  44. 1 1
      web-admin/src/views/production/production-material/index.vue
  45. 1 1
      web-admin/src/views/production/production-work-order/SampleReceptionDialog.vue
  46. 1 1
      web-admin/src/views/production/production-work-order/index.vue
  47. 1 1
      web-admin/src/views/quality-inspection/my-qc-material-usage/index.vue
  48. 1 1
      web-admin/src/views/quality-inspection/my-qc-task/index.vue
  49. 1 1
      web-admin/src/views/quality-inspection/qc-work-order/index.vue
  50. 1 1
      web-admin/src/views/research-donation/sample-info/index.vue

+ 80 - 0
backend/scripts/reset_process_categories.py

@@ -0,0 +1,80 @@
+#!/usr/bin/env python3
+"""重置工艺工序分类(biz_process_category):清空指定租户数据后插入 5 条初始数据。
+
+用法(在 backend 目录):
+  source venv/bin/activate
+  python scripts/reset_process_categories.py
+  python scripts/reset_process_categories.py --tenant-no zkxw
+
+注意:biz_process / biz_process_route_step / biz_production_task_step 等表
+通过 category_id 引用本表,重置后上述关联 id 会失效(字段可空,不会阻断脚本)。
+"""
+from __future__ import annotations
+
+import argparse
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+if str(ROOT) not in sys.path:
+    sys.path.insert(0, str(ROOT))
+
+from sqlalchemy import text
+
+from app.core.database import SessionLocal, engine
+from app.crud import process_category as process_category_svc
+
+ADMIN_ID = "d3000000000000000000000000000001"
+
+# (category_code, category_name, order_num, remark)
+PROCESS_CATEGORIES = [
+    ("PC-XBYP", "细胞培养", 1, "细胞扩增与培养相关工序"),
+    ("PC-YBJS", "样本接收", 2, "样本接收、登记与初检"),
+    ("PC-ZJJC", "质检检测", 3, "过程质控与放行检测"),
+    ("PC-DCGL", "冻存管理", 4, "冻存、复苏与出入库"),
+    ("PC-BZZF", "包装发运", 5, "成品包装与发运"),
+]
+
+
+def reset_process_categories(tenant_no: str = "zkxw", user_id: str = ADMIN_ID) -> None:
+    with engine.begin() as conn:
+        result = conn.execute(
+            text("DELETE FROM `biz_process_category` WHERE `tenant_no` = :tenant_no"),
+            {"tenant_no": tenant_no},
+        )
+        deleted = result.rowcount or 0
+
+    db = SessionLocal()
+    created = 0
+    try:
+        for code, name, order_num, remark in PROCESS_CATEGORIES:
+            process_category_svc.create_process_category(
+                db,
+                category_code=code,
+                category_name=name,
+                tenant_no=tenant_no,
+                order_num=order_num,
+                remark=remark,
+                user_id=user_id,
+            )
+            created += 1
+        print(
+            f"工艺工序分类重置完成(租户 {tenant_no}):"
+            f"删除 {deleted} 条,新增 {created} 条。"
+        )
+        for code, name, _, _ in PROCESS_CATEGORIES:
+            print(f"  - {code} / {name}")
+    finally:
+        db.close()
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser(description="重置 biz_process_category 表数据")
+    parser.add_argument("--tenant-no", default="zkxw", help="租户编号,默认 zkxw")
+    parser.add_argument("--user-id", default=ADMIN_ID, help="create_by / update_by 用户 id")
+    args = parser.parse_args()
+    reset_process_categories(tenant_no=args.tenant_no, user_id=args.user_id)
+
+
+if __name__ == "__main__":
+    main()

+ 202 - 0
backend/scripts/seed_processes.py

@@ -0,0 +1,202 @@
+#!/usr/bin/env python3
+"""批量初始化工艺库测试数据(biz_process),已存在工序代码则跳过。
+
+用法(在 backend 目录):
+  source venv/bin/activate
+  python scripts/seed_processes.py
+  python scripts/seed_processes.py --tenant-no zkxw
+
+依赖:biz_process_category 中已有对应分类编码(可先运行 reset_process_categories.py)。
+"""
+from __future__ import annotations
+
+import argparse
+import sys
+from pathlib import Path
+from typing import Dict, Optional
+
+ROOT = Path(__file__).resolve().parents[1]
+if str(ROOT) not in sys.path:
+    sys.path.insert(0, str(ROOT))
+
+from app.core.database import SessionLocal
+from app.crud import process as process_svc
+from app.models.models import BizProcessCategory
+
+ADMIN_ID = "d3000000000000000000000000000001"
+
+# (process_code, process_name, category_code, description, equipment_name, estimated_duration_min, order_num, remark)
+SAMPLE_PROCESSES = [
+    (
+        "PR-YBJS-001",
+        "样本接收登记",
+        "PC-YBJS",
+        "核对样本信息、登记条码并分配接收批次",
+        "样本接收台",
+        "30",
+        1,
+        "样本接收首道工序",
+    ),
+    (
+        "PR-YBJS-002",
+        "样本初检",
+        "PC-YBJS",
+        "外观检查、温度记录及初筛不合格样本隔离",
+        "生物安全柜",
+        "45",
+        2,
+        None,
+    ),
+    (
+        "PR-XBYP-001",
+        "外周血分离",
+        "PC-XBYP",
+        "密度梯度离心分离单个核细胞",
+        "台式离心机",
+        "60",
+        3,
+        None,
+    ),
+    (
+        "PR-XBYP-002",
+        "细胞培养扩增",
+        "PC-XBYP",
+        "接种至培养体系并进行扩增培养",
+        "CO2培养箱",
+        "480",
+        4,
+        "长周期工序",
+    ),
+    (
+        "PR-XBYP-003",
+        "换液传代",
+        "PC-XBYP",
+        "更换培养基并按密度传代",
+        "生物安全柜",
+        "90",
+        5,
+        None,
+    ),
+    (
+        "PR-ZJJC-001",
+        "细胞活性检测",
+        "PC-ZJJC",
+        "台盼蓝染色法检测细胞活率",
+        "倒置显微镜",
+        "30",
+        6,
+        None,
+    ),
+    (
+        "PR-ZJJC-002",
+        "支原体检测",
+        "PC-ZJJC",
+        "PCR法检测支原体污染",
+        "PCR仪",
+        "180",
+        7,
+        None,
+    ),
+    (
+        "PR-DCGL-001",
+        "程序降温冻存",
+        "PC-DCGL",
+        "按程序降温曲线完成细胞冻存",
+        "程序降温仪",
+        "150",
+        8,
+        None,
+    ),
+    (
+        "PR-DCGL-002",
+        "冻存管入库",
+        "PC-DCGL",
+        "扫码登记并转入液氮罐指定位置",
+        "扫码枪",
+        "20",
+        9,
+        None,
+    ),
+    (
+        "PR-BZZF-001",
+        "成品包装发运",
+        "PC-BZZF",
+        "成品核对、冷链包装及发运交接",
+        "包装台",
+        "45",
+        10,
+        "末道工序",
+    ),
+]
+
+
+def _load_category_map(db, tenant_no: str) -> Dict[str, str]:
+    rows = (
+        db.query(BizProcessCategory)
+        .filter(BizProcessCategory.tenant_no == tenant_no, BizProcessCategory.del_flag == 0)
+        .all()
+    )
+    return {row.category_code: row.id for row in rows}
+
+
+def seed(tenant_no: str = "zkxw", user_id: str = ADMIN_ID) -> None:
+    db = SessionLocal()
+    created = 0
+    skipped = 0
+    missing_categories: set[str] = set()
+    try:
+        category_map = _load_category_map(db, tenant_no)
+        for (
+            code,
+            name,
+            category_code,
+            description,
+            equipment_name,
+            estimated_duration,
+            order_num,
+            remark,
+        ) in SAMPLE_PROCESSES:
+            if process_svc.process_code_exists(db, code, tenant_no):
+                skipped += 1
+                continue
+            category_id: Optional[str] = category_map.get(category_code)
+            if category_code and not category_id:
+                missing_categories.add(category_code)
+            process_svc.create_process(
+                db,
+                process_code=code,
+                process_name=name,
+                category_id=category_id,
+                description=description,
+                equipment_name=equipment_name,
+                estimated_duration=estimated_duration,
+                tenant_no=tenant_no,
+                order_num=order_num,
+                remark=remark,
+                user_id=user_id,
+            )
+            created += 1
+        print(
+            f"工艺库测试数据初始化完成(租户 {tenant_no}):"
+            f"新增 {created} 条,跳过 {skipped} 条(工序代码已存在)。"
+        )
+        if missing_categories:
+            print(
+                "以下工序分类编码未找到,对应工艺已写入但 category_id 为空:"
+                f" {', '.join(sorted(missing_categories))}"
+            )
+            print("可先执行:python scripts/reset_process_categories.py")
+    finally:
+        db.close()
+
+
+def main() -> None:
+    parser = argparse.ArgumentParser(description="初始化 biz_process 测试数据")
+    parser.add_argument("--tenant-no", default="zkxw", help="租户编号,默认 zkxw")
+    parser.add_argument("--user-id", default=ADMIN_ID, help="create_by / update_by 用户 id")
+    args = parser.parse_args()
+    seed(tenant_no=args.tenant_no, user_id=args.user_id)
+
+
+if __name__ == "__main__":
+    main()

+ 54 - 0
docs/my-material-usage-tab-demos/01-current-baseline.html

@@ -0,0 +1,54 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>01 现状基线 · 我的物料领用记录 Tab Demo</title>
+    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
+    <link rel="stylesheet" href="_shared.css" />
+    <style>
+      .btn { height: 36px; padding: 0 14px; border-radius: 8px; font-size: 13px; font-weight: 500; border: 1px solid var(--border); background: var(--surface); color: var(--text); cursor: default; }
+      .btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
+    </style>
+  </head>
+  <body>
+    <header class="demo-banner">
+      <div>
+        <h1><span class="demo-badge">01 现状基线</span> 我的物料领用记录 · Tab 样式 Demo</h1>
+        <p>居中胶囊 Tab,与当前 <code>panel-pill-tabs</code> 线上一致 · 点击下方 Tab 可切换</p>
+      </div>
+      <div class="demo-banner__actions">
+        <a href="index.html">← 选型索引</a>
+        <a href="02-full-width-track.html">02 全宽滑轨 →</a>
+      </div>
+    </header>
+
+    <div class="page-wrap">
+      <section class="panel" id="panel-root">
+        <div class="panel-top"><h2>我的物料领用记录</h2></div>
+        <div class="panel-tab-zone">
+          <nav class="tabs-01" role="tablist">
+            <div class="tabs-01__track">
+              <button type="button" class="tab-btn is-active" data-tab="usage" role="tab">领用记录</button>
+              <button type="button" class="tab-btn" data-tab="consumption" role="tab">消耗明细</button>
+            </div>
+          </nav>
+        </div>
+        <div class="tab-panel is-active" data-panel="usage" id="content"></div>
+      </section>
+      <p class="style-note">
+        <strong>问题:</strong>居中放置、字号偏小、灰底与白底对比弱,首次进入容易被忽略。
+        <div class="pros-cons">
+          <div class="pro">✓ 与全站 panel-pill-tabs 风格统一</div>
+          <div class="con">✗ 双 Tab 场景下辨识度不足,用户不易发现可点击</div>
+        </div>
+      </p>
+    </div>
+
+    <script src="_demo-shell.js"></script>
+    <script>
+      document.getElementById('content').innerHTML = mockContent('usage')
+      initPanelTabs('panel-root')
+    </script>
+  </body>
+</html>

+ 58 - 0
docs/my-material-usage-tab-demos/02-full-width-track.html

@@ -0,0 +1,58 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>02 全宽双轨滑块 · 我的物料领用记录 Tab Demo</title>
+    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
+    <link rel="stylesheet" href="_shared.css" />
+    <style>
+      .btn { height: 36px; padding: 0 14px; border-radius: 8px; font-size: 13px; font-weight: 500; border: 1px solid var(--border); background: var(--surface); color: var(--text); cursor: default; }
+      .btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
+    </style>
+  </head>
+  <body>
+    <header class="demo-banner">
+      <div>
+        <h1><span class="demo-badge">02 全宽双轨滑块</span> 我的物料领用记录 · Tab 样式 Demo</h1>
+        <p>50/50 等分全宽轨道,白色滑块随 Tab 平滑移动 · 点击区域最大</p>
+      </div>
+      <div class="demo-banner__actions">
+        <a href="index.html">← 选型索引</a>
+        <a href="01-current-baseline.html">01 基线</a>
+        <a href="03-icon-cards.html">03 图标卡片 →</a>
+      </div>
+    </header>
+
+    <div class="page-wrap">
+      <section class="panel" id="panel-root">
+        <div class="panel-top"><h2>我的物料领用记录</h2></div>
+        <div class="panel-tab-zone">
+          <div class="tabs-02" data-active="0">
+            <div class="tabs-02-inner">
+              <div class="tabs-02-slider"></div>
+              <div class="tabs-02-track" role="tablist">
+                <button type="button" class="tab-btn is-active" data-tab="usage" role="tab">领用记录</button>
+                <button type="button" class="tab-btn" data-tab="consumption" role="tab">消耗明细</button>
+              </div>
+            </div>
+          </div>
+        </div>
+        <div class="tab-panel is-active" data-panel="usage" id="content"></div>
+      </section>
+      <p class="style-note">
+        <strong>强烈推荐:</strong>双 Tab 场景下全宽等分最协调,滑块动画提供明确的「选中」反馈,用户一眼就能看出这是可切换的控制器。
+        <div class="pros-cons">
+          <div class="pro">✓ 点击区域大、视觉权重高、动画反馈好</div>
+          <div class="con">✗ 占用约 48px 纵向空间,与三 Tab 以上场景需调整 grid</div>
+        </div>
+      </p>
+    </div>
+
+    <script src="_demo-shell.js"></script>
+    <script>
+      document.getElementById('content').innerHTML = mockContent('usage')
+      initPanelTabs('panel-root')
+    </script>
+  </body>
+</html>

+ 68 - 0
docs/my-material-usage-tab-demos/03-icon-cards.html

@@ -0,0 +1,68 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>03 图标信息卡片 · 我的物料领用记录 Tab Demo</title>
+    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
+    <link rel="stylesheet" href="_shared.css" />
+    <style>
+      .btn { height: 36px; padding: 0 14px; border-radius: 8px; font-size: 13px; font-weight: 500; border: 1px solid var(--border); background: var(--surface); color: var(--text); cursor: default; }
+      .btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
+    </style>
+  </head>
+  <body>
+    <header class="demo-banner">
+      <div>
+        <h1><span class="demo-badge">03 图标信息卡片</span> 我的物料领用记录 · Tab 样式 Demo</h1>
+        <p>图标 + 标题 + 副标题说明,双列大卡片 · 业务含义一目了然</p>
+      </div>
+      <div class="demo-banner__actions">
+        <a href="index.html">← 选型索引</a>
+        <a href="02-full-width-track.html">02 全宽滑轨</a>
+      </div>
+    </header>
+
+    <div class="page-wrap">
+      <section class="panel" id="panel-root">
+        <div class="panel-top"><h2>我的物料领用记录</h2></div>
+        <div class="panel-tab-zone">
+          <nav class="tabs-03" role="tablist">
+            <button type="button" class="tab-btn is-active" data-tab="usage" role="tab">
+              <span class="tab-icon" aria-hidden="true">
+                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><path d="M14 2v6h6"/><path d="M16 13H8"/><path d="M16 17H8"/><path d="M10 9H8"/></svg>
+              </span>
+              <span class="tab-text">
+                <span class="tab-title">领用记录</span>
+                <span class="tab-desc">查看本人出库、任务使用等变动</span>
+              </span>
+            </button>
+            <button type="button" class="tab-btn" data-tab="consumption" role="tab">
+              <span class="tab-icon" aria-hidden="true">
+                <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/></svg>
+              </span>
+              <span class="tab-text">
+                <span class="tab-title">消耗明细</span>
+                <span class="tab-desc">配制消耗的子物料明细</span>
+              </span>
+            </button>
+          </nav>
+        </div>
+        <div class="tab-panel is-active" data-panel="usage" id="content"></div>
+      </section>
+      <p class="style-note">
+        <strong>推荐:</strong>副标题帮助用户理解两个 Tab 的区别,图标增强模块识别度。落地时图标可用 Element Plus Icons(如 Document / DataLine)。
+        <div class="pros-cons">
+          <div class="pro">✓ 信息量大、可发现性最强、适合业务含义不易区分的场景</div>
+          <div class="con">✗ 占用约 72px 纵向空间,横向在小屏需考虑换行</div>
+        </div>
+      </p>
+    </div>
+
+    <script src="_demo-shell.js"></script>
+    <script>
+      document.getElementById('content').innerHTML = mockContent('usage')
+      initPanelTabs('panel-root')
+    </script>
+  </body>
+</html>

+ 51 - 0
docs/my-material-usage-tab-demos/04-left-underline.html

@@ -0,0 +1,51 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>04 左对齐下划线 · 我的物料领用记录 Tab Demo</title>
+    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
+    <link rel="stylesheet" href="_shared.css" />
+    <style>
+      .btn { height: 36px; padding: 0 14px; border-radius: 8px; font-size: 13px; font-weight: 500; border: 1px solid var(--border); background: var(--surface); color: var(--text); cursor: default; }
+      .btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
+    </style>
+  </head>
+  <body>
+    <header class="demo-banner">
+      <div>
+        <h1><span class="demo-badge">04 左对齐下划线</span> 我的物料领用记录 · Tab 样式 Demo</h1>
+        <p>加大字号 + 左对齐 + 绿色底边指示 · 经典 Tab 模式</p>
+      </div>
+      <div class="demo-banner__actions">
+        <a href="index.html">← 选型索引</a>
+      </div>
+    </header>
+
+    <div class="page-wrap">
+      <section class="panel" id="panel-root">
+        <div class="panel-top"><h2>我的物料领用记录</h2></div>
+        <div class="panel-tab-zone">
+          <nav class="tabs-04" role="tablist">
+            <button type="button" class="tab-btn is-active" data-tab="usage" role="tab">领用记录</button>
+            <button type="button" class="tab-btn" data-tab="consumption" role="tab">消耗明细</button>
+          </nav>
+        </div>
+        <div class="tab-panel is-active" data-panel="usage" id="content"></div>
+      </section>
+      <p class="style-note">
+        <strong>经典方案:</strong>左对齐 + 15px 字号 + 3px 绿色底边,符合用户对 Tab 的认知习惯。改动适中,可与 Element Plus 默认 tabs 风格接近。
+        <div class="pros-cons">
+          <div class="pro">✓ 用户认知成本低、实现简单、纵向占用适中</div>
+          <div class="con">✗ 相比全宽滑轨/图标卡片,视觉冲击力略弱</div>
+        </div>
+      </p>
+    </div>
+
+    <script src="_demo-shell.js"></script>
+    <script>
+      document.getElementById('content').innerHTML = mockContent('usage')
+      initPanelTabs('panel-root')
+    </script>
+  </body>
+</html>

+ 52 - 0
docs/my-material-usage-tab-demos/05-inline-header.html

@@ -0,0 +1,52 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>05 标题行内嵌 Tab · 我的物料领用记录 Tab Demo</title>
+    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
+    <link rel="stylesheet" href="_shared.css" />
+    <style>
+      .btn { height: 36px; padding: 0 14px; border-radius: 8px; font-size: 13px; font-weight: 500; border: 1px solid var(--border); background: var(--surface); color: var(--text); cursor: default; }
+      .btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
+      .panel-top--inline h2 { margin: 0; }
+    </style>
+  </head>
+  <body>
+    <header class="demo-banner">
+      <div>
+        <h1><span class="demo-badge">05 标题行内嵌 Tab</span> 我的物料领用记录 · Tab 样式 Demo</h1>
+        <p>Tab 放在标题右侧同一行 · 节省纵向空间</p>
+      </div>
+      <div class="demo-banner__actions">
+        <a href="index.html">← 选型索引</a>
+      </div>
+    </header>
+
+    <div class="page-wrap">
+      <section class="panel" id="panel-root">
+        <div class="panel-top panel-top--inline">
+          <h2 class="title-bar-heading">我的物料领用记录</h2>
+          <nav class="tabs-05" role="tablist">
+            <button type="button" class="tab-btn is-active" data-tab="usage" role="tab">领用记录</button>
+            <button type="button" class="tab-btn" data-tab="consumption" role="tab">消耗明细</button>
+          </nav>
+        </div>
+        <div class="tab-panel is-active" data-panel="usage" id="content"></div>
+      </section>
+      <p class="style-note">
+        <strong>紧凑方案:</strong>Tab 与标题同行,形成「页面名 + 视图切换」的组合,减少 Tab 被筛选区「淹没」的风险。
+        <div class="pros-cons">
+          <div class="pro">✓ 不额外占用纵向空间、Tab 与标题关联紧密</div>
+          <div class="con">✗ 窄屏下可能换行;标题较长时右侧空间紧张</div>
+        </div>
+      </p>
+    </div>
+
+    <script src="_demo-shell.js"></script>
+    <script>
+      document.getElementById('content').innerHTML = mockContent('usage')
+      initPanelTabs('panel-root')
+    </script>
+  </body>
+</html>

+ 58 - 0
docs/my-material-usage-tab-demos/06-pill-with-hint.html

@@ -0,0 +1,58 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>06 左对齐胶囊 + 引导提示 · 我的物料领用记录 Tab Demo</title>
+    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
+    <link rel="stylesheet" href="_shared.css" />
+    <style>
+      .btn { height: 36px; padding: 0 14px; border-radius: 8px; font-size: 13px; font-weight: 500; border: 1px solid var(--border); background: var(--surface); color: var(--text); cursor: default; }
+      .btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
+    </style>
+  </head>
+  <body>
+    <header class="demo-banner">
+      <div>
+        <h1><span class="demo-badge">06 左对齐胶囊 + 引导</span> 我的物料领用记录 · Tab 样式 Demo</h1>
+        <p>保留胶囊风格但左对齐加大,上方加引导文案 · 渐进改良方案</p>
+      </div>
+      <div class="demo-banner__actions">
+        <a href="index.html">← 选型索引</a>
+        <a href="01-current-baseline.html">01 基线对比</a>
+      </div>
+    </header>
+
+    <div class="page-wrap">
+      <section class="panel" id="panel-root">
+        <div class="panel-top"><h2>我的物料领用记录</h2></div>
+        <div class="panel-tab-zone">
+          <div class="tab-zone-06">
+            <div class="tab-zone-06__hint">
+              <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M7 16V4m0 0L3 8m4-4l4 4"/><path d="M17 8v12m0 0l4-4m-4 4l-4-4"/></svg>
+              <span>点击下方切换查看不同记录类型</span>
+            </div>
+            <nav class="tabs-06" role="tablist">
+              <button type="button" class="tab-btn is-active" data-tab="usage" role="tab">领用记录</button>
+              <button type="button" class="tab-btn" data-tab="consumption" role="tab">消耗明细</button>
+            </nav>
+          </div>
+        </div>
+        <div class="tab-panel is-active" data-panel="usage" id="content"></div>
+      </section>
+      <p class="style-note">
+        <strong>渐进改良:</strong>在现有胶囊风格基础上左对齐、加大按钮尺寸,并增加一行引导提示。改动最小,适合不想大改布局的场景。
+        <div class="pros-cons">
+          <div class="pro">✓ 改动量小、与全站胶囊风格兼容、引导文案降低学习成本</div>
+          <div class="con">✗ 引导文案可能显得冗余(用户熟悉后可考虑隐藏)</div>
+        </div>
+      </p>
+    </div>
+
+    <script src="_demo-shell.js"></script>
+    <script>
+      document.getElementById('content').innerHTML = mockContent('usage')
+      initPanelTabs('panel-root')
+    </script>
+  </body>
+</html>

+ 52 - 0
docs/my-material-usage-tab-demos/07-bar-accent.html

@@ -0,0 +1,52 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>07 竖条强调 · 我的物料领用记录 Tab Demo</title>
+    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />
+    <link rel="stylesheet" href="_shared.css" />
+    <style>
+      .btn { height: 36px; padding: 0 14px; border-radius: 8px; font-size: 13px; font-weight: 500; border: 1px solid var(--border); background: var(--surface); color: var(--text); cursor: default; }
+      .btn-primary { background: var(--primary); border-color: var(--primary); color: #fff; }
+    </style>
+  </head>
+  <body>
+    <header class="demo-banner">
+      <div>
+        <h1><span class="demo-badge">07 竖条强调</span> 我的物料领用记录 · Tab 样式 Demo</h1>
+        <p>激活 Tab 左侧主色竖条 + 浅绿渐变底 · 与标题竖条视觉统一</p>
+      </div>
+      <div class="demo-banner__actions">
+        <a href="index.html">← 选型索引</a>
+        <a href="04-left-underline.html">04 下划线</a>
+      </div>
+    </header>
+
+    <div class="page-wrap">
+      <section class="panel" id="panel-root">
+        <div class="panel-top"><h2>我的物料领用记录</h2></div>
+        <div class="panel-tab-zone">
+          <nav class="tabs-07" role="tablist">
+            <button type="button" class="tab-btn is-active" data-tab="usage" role="tab">领用记录</button>
+            <button type="button" class="tab-btn" data-tab="consumption" role="tab">消耗明细</button>
+          </nav>
+        </div>
+        <div class="tab-panel is-active" data-panel="usage" id="content"></div>
+      </section>
+      <p class="style-note">
+        <strong>推荐:</strong>与 Fresh Mint「标题前竖条」同一视觉语言,品牌感连贯;左对齐 + 浅绿底让激活态更醒目。
+        <div class="pros-cons">
+          <div class="pro">✓ 与全站标题竖条统一、左对齐辨识度高、适合作为 panel-tabs 升级方向</div>
+          <div class="con">✗ 需新增 CSS class,与现有 panel-pill-tabs 结构不同</div>
+        </div>
+      </p>
+    </div>
+
+    <script src="_demo-shell.js"></script>
+    <script>
+      document.getElementById('content').innerHTML = mockContent('usage')
+      initPanelTabs('panel-root')
+    </script>
+  </body>
+</html>

+ 123 - 0
docs/my-material-usage-tab-demos/_demo-shell.js

@@ -0,0 +1,123 @@
+/** 我的物料领用记录 · 双 Tab Demo 通用交互 */
+function initPanelTabs(rootId) {
+  const root = document.getElementById(rootId)
+  if (!root) return
+
+  const tabBtns = root.querySelectorAll('[data-tab]')
+  const panels = root.querySelectorAll('[data-panel]')
+  const track = root.querySelector('.tabs-02')
+  const singleContent = root.querySelector('#content')
+
+  function activate(name, index) {
+    tabBtns.forEach((btn) => {
+      btn.classList.toggle('is-active', btn.dataset.tab === name)
+    })
+    panels.forEach((panel) => {
+      panel.classList.toggle('is-active', panel.dataset.panel === name)
+    })
+    if (track) {
+      track.dataset.active = String(index)
+    }
+    if (singleContent && typeof mockContent === 'function') {
+      singleContent.innerHTML = mockContent(name)
+    }
+  }
+
+  tabBtns.forEach((btn, index) => {
+    btn.addEventListener('click', () => activate(btn.dataset.tab, index))
+  })
+}
+
+function mockContent(tabName) {
+  if (tabName === 'consumption') {
+    return `
+      <div class="mock-content">
+        <div class="filter-block">
+          <div class="filter-grid">
+            <div class="filter-item">
+              <label>关键词</label>
+              <div class="fake-input">物料编码/名称/批次号</div>
+            </div>
+            <div class="filter-item">
+              <label>物料类型</label>
+              <div class="fake-input">全部</div>
+            </div>
+            <div class="filter-item">
+              <label>时间范围</label>
+              <div class="fake-input">开始日期 至 结束日期</div>
+            </div>
+          </div>
+          <div class="filter-actions-row">
+            <span class="filter-hint">查看配制消耗的子物料明细,关联配制单可跳转查看。</span>
+            <div class="filter-actions-end">
+              <button class="btn">重置</button>
+              <button class="btn btn-primary">搜索</button>
+            </div>
+          </div>
+        </div>
+        <div class="list-body">
+          <div class="fake-table">
+            <div class="fake-table-head" style="grid-template-columns: 48px 1fr 1fr 1fr 1fr 1.2fr">
+              <span>序号</span><span>物料编码</span><span>物料名称</span><span>消耗数量</span><span>配制单号</span><span>消耗时间</span>
+            </div>
+            <div class="fake-table-row" style="grid-template-columns: 48px 1fr 1fr 1fr 1fr 1.2fr">
+              <span>1</span><span>WL-001</span><span>DMEM培养基</span><span>2 瓶</span><span>PZ-20260610-003</span><span>2026-06-10 14:30</span>
+            </div>
+            <div class="fake-table-row" style="grid-template-columns: 48px 1fr 1fr 1fr 1fr 1.2fr">
+              <span>2</span><span>WL-012</span><span>胎牛血清</span><span>0.5 瓶</span><span>PZ-20260609-001</span><span>2026-06-09 09:15</span>
+            </div>
+          </div>
+        </div>
+      </div>
+    `
+  }
+
+  return `
+    <div class="mock-content">
+      <div class="filter-block">
+        <div class="filter-grid">
+          <div class="filter-item">
+            <label>关键词</label>
+            <div class="fake-input">物料编码/名称/入库编号/批号/货号</div>
+          </div>
+          <div class="filter-item">
+            <label>物料筛选</label>
+            <div class="fake-input">输入物料名称检索</div>
+          </div>
+          <div class="filter-item">
+            <label>物料类型</label>
+            <div class="fake-input">全部</div>
+          </div>
+          <div class="filter-item">
+            <label>变动类型</label>
+            <div class="fake-input">全部</div>
+          </div>
+          <div class="filter-item">
+            <label>时间范围</label>
+            <div class="fake-input">开始日期 至 结束日期</div>
+          </div>
+        </div>
+        <div class="filter-actions-row">
+          <span class="filter-hint">查看本人物料领用记录,可按物料、批号、类型与时间范围筛选。</span>
+          <div class="filter-actions-end">
+            <button class="btn">重置</button>
+            <button class="btn btn-primary">搜索</button>
+          </div>
+        </div>
+      </div>
+      <div class="list-body">
+        <div class="fake-table">
+          <div class="fake-table-head" style="grid-template-columns: 48px 1fr 1.2fr 1fr 1fr 1fr">
+            <span>序号</span><span>物料编码</span><span>物料名称</span><span>变动类型</span><span>数量</span><span>领用时间</span>
+          </div>
+          <div class="fake-table-row" style="grid-template-columns: 48px 1fr 1.2fr 1fr 1fr 1fr">
+            <span>1</span><span>WL-001</span><span>DMEM培养基</span><span>出库</span><span>5 瓶</span><span>2026-06-08 10:20</span>
+          </div>
+          <div class="fake-table-row" style="grid-template-columns: 48px 1fr 1.2fr 1fr 1fr 1fr">
+            <span>2</span><span>WL-012</span><span>胎牛血清</span><span>任务使用</span><span>1 瓶</span><span>2026-06-07 16:45</span>
+          </div>
+        </div>
+      </div>
+    </div>
+  `
+}

+ 726 - 0
docs/my-material-usage-tab-demos/_shared.css

@@ -0,0 +1,726 @@
+/* 我的物料领用记录 · 双 Tab 样式 Demo 共享样式(Fresh Mint Token) */
+:root {
+  --primary: #10b981;
+  --primary-dark: #059669;
+  --primary-bg: #ecfdf5;
+  --text: #1e293b;
+  --text-muted: #64748b;
+  --border: #e2e8f0;
+  --bg-page: #f8fafc;
+  --surface: #fff;
+  --radius: 10px;
+  --radius-sm: 8px;
+}
+
+* {
+  box-sizing: border-box;
+}
+
+body {
+  margin: 0;
+  min-height: 100vh;
+  font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
+  font-size: 14px;
+  line-height: 1.5;
+  color: var(--text);
+  background: var(--bg-page);
+}
+
+/* ── 顶栏说明 ── */
+.demo-banner {
+  position: sticky;
+  top: 0;
+  z-index: 200;
+  padding: 12px 24px;
+  background: var(--surface);
+  border-bottom: 1px solid var(--border);
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 16px;
+  flex-wrap: wrap;
+  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.04);
+}
+
+.demo-banner h1 {
+  margin: 0;
+  font-size: 15px;
+  font-weight: 600;
+}
+
+.demo-banner p {
+  margin: 4px 0 0;
+  font-size: 12px;
+  color: var(--text-muted);
+}
+
+.demo-banner__actions {
+  display: flex;
+  gap: 8px;
+  flex-wrap: wrap;
+  align-items: center;
+}
+
+.demo-banner a {
+  padding: 6px 12px;
+  border-radius: 6px;
+  font-size: 12px;
+  font-weight: 500;
+  text-decoration: none;
+  color: var(--text-muted);
+  border: 1px solid var(--border);
+  background: var(--surface);
+}
+
+.demo-banner a:hover {
+  border-color: var(--primary);
+  color: var(--primary-dark);
+}
+
+.demo-banner a.is-current {
+  background: var(--primary-bg);
+  border-color: var(--primary);
+  color: var(--primary-dark);
+}
+
+.demo-badge {
+  display: inline-block;
+  padding: 2px 8px;
+  border-radius: 6px;
+  font-size: 11px;
+  font-weight: 600;
+  background: var(--primary-bg);
+  color: var(--primary-dark);
+}
+
+/* ── 页面骨架 ── */
+.page-wrap {
+  padding: 24px;
+  max-width: 1280px;
+  margin: 0 auto;
+}
+
+.panel {
+  background: var(--surface);
+  border: 1px solid var(--border);
+  border-radius: var(--radius);
+  overflow: hidden;
+}
+
+.panel-top {
+  padding: 16px 24px 0;
+}
+
+.panel-top h2 {
+  margin: 0;
+  font-size: 16px;
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  gap: 10px;
+}
+
+.panel-top h2::before {
+  content: '';
+  width: 4px;
+  height: 16px;
+  border-radius: 2px;
+  background: var(--primary);
+  flex-shrink: 0;
+}
+
+.panel-tab-zone {
+  padding: 0 24px;
+}
+
+.mock-content {
+  border-top: 1px solid var(--border);
+}
+
+.filter-block {
+  padding: 16px 24px;
+}
+
+.filter-grid {
+  display: grid;
+  grid-template-columns: repeat(3, minmax(0, 1fr));
+  gap: 12px 16px;
+}
+
+.filter-item label {
+  display: block;
+  font-size: 11px;
+  font-weight: 500;
+  color: var(--text-muted);
+  margin-bottom: 6px;
+}
+
+.filter-item .fake-input {
+  height: 36px;
+  border: 1px solid var(--border);
+  border-radius: var(--radius-sm);
+  padding: 0 12px;
+  background: var(--surface);
+  color: var(--text-muted);
+  font-size: 13px;
+  display: flex;
+  align-items: center;
+}
+
+.filter-actions-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 16px;
+  margin-top: 12px;
+  flex-wrap: wrap;
+}
+
+.filter-hint {
+  font-size: 12px;
+  color: var(--text-muted);
+}
+
+.filter-actions-end {
+  display: flex;
+  gap: 8px;
+}
+
+.list-body {
+  padding: 0 24px 24px;
+}
+
+.fake-table {
+  border: 1px solid var(--border);
+  border-radius: var(--radius-sm);
+  overflow: hidden;
+}
+
+.fake-table-head,
+.fake-table-row {
+  display: grid;
+  gap: 8px;
+  padding: 10px 16px;
+  font-size: 13px;
+  align-items: center;
+}
+
+.fake-table-head {
+  background: #f1f5f9;
+  font-weight: 600;
+  color: var(--text-muted);
+  font-size: 12px;
+}
+
+.fake-table-row {
+  border-top: 1px solid var(--border);
+  color: var(--text);
+}
+
+.fake-table-row:nth-child(even) {
+  background: #fafafa;
+}
+
+.tab-panel {
+  display: none;
+}
+
+.tab-panel.is-active {
+  display: block;
+}
+
+.style-note {
+  margin: 24px 0 0;
+  padding: 14px 18px;
+  background: #fffbeb;
+  border: 1px solid #fde68a;
+  border-radius: var(--radius-sm);
+  font-size: 13px;
+  color: #92400e;
+}
+
+.style-note strong {
+  color: #78350f;
+}
+
+.pros-cons {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 12px;
+  margin-top: 12px;
+}
+
+.pros-cons div {
+  padding: 10px 12px;
+  border-radius: var(--radius-sm);
+  font-size: 12px;
+  line-height: 1.5;
+}
+
+.pros-cons .pro {
+  background: var(--primary-bg);
+  color: var(--primary-dark);
+}
+
+.pros-cons .con {
+  background: #f8fafc;
+  color: var(--text-muted);
+}
+
+/* ══════════════════════════════════════════
+   01 · 现状基线(居中胶囊,与线上一致)
+   ══════════════════════════════════════════ */
+.tabs-01 {
+  display: flex;
+  justify-content: center;
+  margin: 12px 0 4px;
+}
+
+.tabs-01__track {
+  display: inline-flex;
+  gap: 4px;
+  padding: 4px;
+  background: #f1f5f9;
+  border: 1px solid var(--border);
+  border-radius: 8px;
+}
+
+.tabs-01 .tab-btn {
+  min-width: 108px;
+  padding: 8px 20px;
+  border: none;
+  border-radius: 6px;
+  background: transparent;
+  font-size: 13px;
+  font-weight: 500;
+  color: var(--text-muted);
+  cursor: pointer;
+  transition: background 0.15s, color 0.15s, box-shadow 0.15s;
+}
+
+.tabs-01 .tab-btn:hover:not(.is-active) {
+  color: var(--text);
+  background: rgba(255, 255, 255, 0.6);
+}
+
+.tabs-01 .tab-btn.is-active {
+  background: #fff;
+  color: var(--primary-dark);
+  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);
+  font-weight: 600;
+}
+
+/* ══════════════════════════════════════════
+   02 · 全宽双轨滑块(50/50 等分)
+   ══════════════════════════════════════════ */
+.tabs-02 {
+  margin: 16px 0 8px;
+}
+
+.tabs-02-inner {
+  position: relative;
+}
+
+.tabs-02-track {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  background: #f1f5f9;
+  border-radius: var(--radius-sm);
+  padding: 4px;
+  border: 1px solid var(--border);
+}
+
+.tabs-02 .tab-btn {
+  padding: 12px 16px;
+  border: none;
+  border-radius: 6px;
+  background: transparent;
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--text-muted);
+  cursor: pointer;
+  text-align: center;
+  z-index: 1;
+  transition: color 0.2s;
+}
+
+.tabs-02 .tab-btn.is-active {
+  color: var(--primary-dark);
+  font-weight: 600;
+}
+
+.tabs-02-slider {
+  position: absolute;
+  top: 4px;
+  left: 4px;
+  width: calc((100% - 8px) / 2);
+  height: calc(100% - 8px);
+  background: var(--surface);
+  border-radius: 6px;
+  box-shadow: 0 1px 4px rgba(15, 23, 42, 0.1);
+  transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
+  pointer-events: none;
+}
+
+.tabs-02[data-active='1'] .tabs-02-slider {
+  transform: translateX(100%);
+}
+
+/* ══════════════════════════════════════════
+   03 · 图标信息卡片(双列大按钮)
+   ══════════════════════════════════════════ */
+.tabs-03 {
+  display: grid;
+  grid-template-columns: 1fr 1fr;
+  gap: 12px;
+  margin: 16px 0 8px;
+}
+
+.tabs-03 .tab-btn {
+  display: flex;
+  align-items: center;
+  gap: 14px;
+  padding: 14px 18px;
+  border: 1px solid var(--border);
+  border-radius: var(--radius-sm);
+  background: var(--surface);
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--text-muted);
+  cursor: pointer;
+  text-align: left;
+  transition: border-color 0.15s, background 0.15s, color 0.15s, box-shadow 0.15s;
+}
+
+.tabs-03 .tab-icon {
+  width: 40px;
+  height: 40px;
+  border-radius: 10px;
+  background: #f1f5f9;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  color: var(--text-muted);
+  transition: background 0.15s, color 0.15s;
+}
+
+.tabs-03 .tab-text {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+}
+
+.tabs-03 .tab-title {
+  font-weight: 600;
+  font-size: 14px;
+}
+
+.tabs-03 .tab-desc {
+  font-size: 12px;
+  font-weight: 400;
+  color: var(--text-muted);
+}
+
+.tabs-03 .tab-btn:hover:not(.is-active) {
+  border-color: #a7f3d0;
+  background: #f8fafc;
+}
+
+.tabs-03 .tab-btn.is-active {
+  border-color: var(--primary);
+  background: var(--primary-bg);
+  color: var(--primary-dark);
+  box-shadow: 0 2px 8px rgba(16, 185, 129, 0.12);
+}
+
+.tabs-03 .tab-btn.is-active .tab-icon {
+  background: var(--primary);
+  color: #fff;
+}
+
+.tabs-03 .tab-btn.is-active .tab-desc {
+  color: var(--primary-dark);
+  opacity: 0.85;
+}
+
+/* ══════════════════════════════════════════
+   04 · 左对齐下划线(加大字号)
+   ══════════════════════════════════════════ */
+.tabs-04 {
+  display: flex;
+  gap: 0;
+  margin: 12px 0 0;
+  border-bottom: 2px solid var(--border);
+}
+
+.tabs-04 .tab-btn {
+  padding: 14px 28px;
+  margin-bottom: -2px;
+  border: none;
+  background: transparent;
+  font-size: 15px;
+  font-weight: 500;
+  color: var(--text-muted);
+  cursor: pointer;
+  border-bottom: 3px solid transparent;
+  transition: color 0.15s, border-color 0.15s;
+}
+
+.tabs-04 .tab-btn:hover:not(.is-active) {
+  color: var(--text);
+  background: #f8fafc;
+}
+
+.tabs-04 .tab-btn.is-active {
+  color: var(--primary-dark);
+  font-weight: 600;
+  border-bottom-color: var(--primary);
+}
+
+/* ══════════════════════════════════════════
+   05 · 标题行内嵌 Tab(标题右侧)
+   ══════════════════════════════════════════ */
+.panel-top--inline {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 24px;
+  padding-bottom: 16px;
+  border-bottom: 1px solid var(--border);
+  flex-wrap: wrap;
+}
+
+.tabs-05 {
+  display: inline-flex;
+  gap: 4px;
+  padding: 4px;
+  background: #f1f5f9;
+  border: 1px solid var(--border);
+  border-radius: 8px;
+  flex-shrink: 0;
+}
+
+.tabs-05 .tab-btn {
+  min-width: 96px;
+  padding: 8px 18px;
+  border: none;
+  border-radius: 6px;
+  background: transparent;
+  font-size: 13px;
+  font-weight: 500;
+  color: var(--text-muted);
+  cursor: pointer;
+  transition: background 0.15s, color 0.15s, box-shadow 0.15s;
+}
+
+.tabs-05 .tab-btn.is-active {
+  background: #fff;
+  color: var(--primary-dark);
+  box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);
+  font-weight: 600;
+}
+
+/* ══════════════════════════════════════════
+   06 · 左对齐胶囊 + 引导提示
+   ══════════════════════════════════════════ */
+.tab-zone-06 {
+  margin: 12px 0 4px;
+}
+
+.tab-zone-06__hint {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 10px;
+  font-size: 12px;
+  color: var(--text-muted);
+}
+
+.tab-zone-06__hint svg {
+  flex-shrink: 0;
+  color: var(--primary);
+}
+
+.tabs-06 {
+  display: inline-flex;
+  gap: 4px;
+  padding: 5px;
+  background: #f1f5f9;
+  border: 1px solid var(--border);
+  border-radius: 10px;
+}
+
+.tabs-06 .tab-btn {
+  min-width: 140px;
+  padding: 10px 24px;
+  border: none;
+  border-radius: 8px;
+  background: transparent;
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--text-muted);
+  cursor: pointer;
+  transition: background 0.15s, color 0.15s, box-shadow 0.15s;
+}
+
+.tabs-06 .tab-btn:hover:not(.is-active) {
+  color: var(--text);
+  background: rgba(255, 255, 255, 0.7);
+}
+
+.tabs-06 .tab-btn.is-active {
+  background: #fff;
+  color: var(--primary-dark);
+  box-shadow: 0 2px 6px rgba(15, 23, 42, 0.1);
+  font-weight: 600;
+}
+
+/* ══════════════════════════════════════════
+   07 · 竖条强调 + 浅绿底(与标题竖条统一)
+   ══════════════════════════════════════════ */
+.tabs-07 {
+  display: flex;
+  gap: 0;
+  margin: 12px 0 0;
+  border-bottom: 1px solid var(--border);
+}
+
+.tabs-07 .tab-btn {
+  padding: 14px 28px;
+  border: none;
+  background: transparent;
+  font-size: 14px;
+  font-weight: 500;
+  color: var(--text-muted);
+  cursor: pointer;
+  position: relative;
+  transition: color 0.15s, background 0.15s;
+}
+
+.tabs-07 .tab-btn::before {
+  content: '';
+  position: absolute;
+  left: 16px;
+  top: 50%;
+  transform: translateY(-50%);
+  width: 3px;
+  height: 0;
+  border-radius: 2px;
+  background: var(--primary);
+  transition: height 0.15s;
+}
+
+.tabs-07 .tab-btn.is-active::before {
+  height: 18px;
+}
+
+.tabs-07 .tab-btn:hover:not(.is-active) {
+  color: var(--text);
+  background: #f8fafc;
+}
+
+.tabs-07 .tab-btn.is-active {
+  color: var(--primary-dark);
+  font-weight: 600;
+  background: linear-gradient(180deg, var(--primary-bg) 0%, transparent 100%);
+}
+
+/* ── 索引页 ── */
+.index-hero {
+  max-width: 960px;
+  margin: 0 auto;
+  padding: 32px 24px 16px;
+}
+
+.index-hero h1 {
+  margin: 0 0 8px;
+  font-size: 22px;
+}
+
+.index-hero p {
+  margin: 0;
+  color: var(--text-muted);
+  max-width: 720px;
+  line-height: 1.6;
+}
+
+.index-problem {
+  margin-top: 16px;
+  padding: 12px 16px;
+  background: #fef2f2;
+  border: 1px solid #fecaca;
+  border-radius: var(--radius-sm);
+  font-size: 13px;
+  color: #991b1b;
+  max-width: 720px;
+}
+
+.index-grid {
+  max-width: 960px;
+  margin: 0 auto;
+  padding: 16px 24px 48px;
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+  gap: 16px;
+}
+
+.index-card {
+  background: var(--surface);
+  border: 1px solid var(--border);
+  border-radius: 12px;
+  padding: 18px;
+  text-decoration: none;
+  color: inherit;
+  transition: border-color 0.2s, box-shadow 0.2s;
+}
+
+.index-card:hover {
+  border-color: #a7f3d0;
+  box-shadow: 0 4px 12px rgba(16, 185, 129, 0.08);
+}
+
+.index-card h2 {
+  margin: 0 0 6px;
+  font-size: 15px;
+  font-weight: 600;
+  color: var(--primary-dark);
+}
+
+.index-card p {
+  margin: 0;
+  font-size: 13px;
+  color: var(--text-muted);
+  line-height: 1.5;
+}
+
+.index-card .tag {
+  display: inline-block;
+  margin-top: 12px;
+  padding: 2px 8px;
+  border-radius: 6px;
+  font-size: 11px;
+  font-weight: 600;
+  background: #f1f5f9;
+  color: var(--text-muted);
+}
+
+.index-card .tag--rec {
+  background: var(--primary-bg);
+  color: var(--primary-dark);
+}
+
+.index-card .tag--baseline {
+  background: #fef3c7;
+  color: #92400e;
+}
+
+.preview-strip {
+  margin-top: 14px;
+  padding: 10px;
+  background: #f8fafc;
+  border-radius: 8px;
+  border: 1px solid var(--border);
+  font-size: 12px;
+  color: var(--text-muted);
+}

+ 77 - 0
docs/my-material-usage-tab-demos/index.html

@@ -0,0 +1,77 @@
+<!doctype html>
+<html lang="zh-CN">
+  <head>
+    <meta charset="UTF-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>我的物料领用记录 · 双 Tab 样式选型</title>
+    <link
+      href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap"
+      rel="stylesheet"
+    />
+    <link rel="stylesheet" href="_shared.css" />
+  </head>
+  <body>
+    <div class="index-hero">
+      <h1>我的物料领用记录 · 双 Tab 样式 Demo</h1>
+      <p>
+        针对【生产管理 → 我的物料领用记录】页面中【领用记录】【消耗明细】两个 Tab
+        不够醒目、首次进入不易发现可点击的问题,提供 7 套可选样式。色值对齐 Fresh Mint(<code>#10b981</code>)。
+      </p>
+      <div class="index-problem">
+        <strong>现状问题:</strong>当前使用居中胶囊 Tab(<code>panel-pill-tabs</code>),字号 13px、灰底轨道,
+        夹在标题与筛选区之间视觉权重低,用户容易把它当成静态标签而非可切换的视图。
+      </div>
+    </div>
+
+    <div class="index-grid">
+      <a class="index-card" href="01-current-baseline.html">
+        <h2>01 · 现状基线</h2>
+        <p>与线上一致的居中胶囊 Tab,作为对比参照。</p>
+        <span class="tag tag--baseline">当前线上</span>
+        <div class="preview-strip">   [ 领用记录 | 消耗明细 ]</div>
+      </a>
+
+      <a class="index-card" href="02-full-width-track.html">
+        <h2>02 · 全宽双轨滑块</h2>
+        <p>50/50 等分全宽轨道,白色滑块平滑移动。点击区域最大,辨识度最高。</p>
+        <span class="tag tag--rec">强烈推荐</span>
+        <div class="preview-strip">┌────────领用记录────────┬────────消耗明细────────┐</div>
+      </a>
+
+      <a class="index-card" href="03-icon-cards.html">
+        <h2>03 · 图标信息卡片</h2>
+        <p>图标 + 标题 + 副标题说明,双列大卡片。业务含义一目了然。</p>
+        <span class="tag tag--rec">推荐</span>
+        <div class="preview-strip">[📋 领用记录] [📊 消耗明细]</div>
+      </a>
+
+      <a class="index-card" href="04-left-underline.html">
+        <h2>04 · 左对齐下划线</h2>
+        <p>加大字号(15px)+ 左对齐 + 绿色底边指示。经典 Tab 模式,改动适中。</p>
+        <span class="tag">经典</span>
+        <div class="preview-strip">领用记录 消耗明细 ———</div>
+      </a>
+
+      <a class="index-card" href="05-inline-header.html">
+        <h2>05 · 标题行内嵌 Tab</h2>
+        <p>Tab 放在标题右侧同一行,节省纵向空间,与标题形成视觉组合。</p>
+        <span class="tag">紧凑</span>
+        <div class="preview-strip">| 我的物料领用记录    [ 领用记录 | 消耗明细 ]</div>
+      </a>
+
+      <a class="index-card" href="06-pill-with-hint.html">
+        <h2>06 · 左对齐胶囊 + 引导提示</h2>
+        <p>保留胶囊风格但左对齐、加大尺寸,上方加一行「切换视图」引导文案。</p>
+        <span class="tag">渐进改良</span>
+        <div class="preview-strip">↕ 点击下方切换查看不同记录 [ 领用记录 | 消耗明细 ]</div>
+      </a>
+
+      <a class="index-card" href="07-bar-accent.html">
+        <h2>07 · 竖条强调 + 浅绿底</h2>
+        <p>激活 Tab 左侧主色竖条 + 浅绿渐变底,与列表标题竖条视觉统一。</p>
+        <span class="tag tag--rec">推荐</span>
+        <div class="preview-strip">|领用记录 消耗明细</div>
+      </a>
+    </div>
+  </body>
+</html>

+ 7 - 7
web-admin/graphify-out/.graphify_extract.json

@@ -12990,21 +12990,21 @@
       "label": "if()",
       "file_type": "code",
       "source_file": "views/production/process/ProcessRouteTab.vue",
-      "source_location": "L1471"
+      "source_location": "L1473"
     },
     {
       "id": "process_processroutetab_get",
       "label": "get()",
       "file_type": "code",
       "source_file": "views/production/process/ProcessRouteTab.vue",
-      "source_location": "L1680"
+      "source_location": "L1682"
     },
     {
       "id": "process_processroutetab_set",
       "label": "set()",
       "file_type": "code",
       "source_file": "views/production/process/ProcessRouteTab.vue",
-      "source_location": "L1690"
+      "source_location": "L1692"
     },
     {
       "id": "views_production_process_category_index_vue",
@@ -38471,7 +38471,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/process/ProcessRouteTab.vue",
-      "source_location": "L1471",
+      "source_location": "L1473",
       "weight": 1.0
     },
     {
@@ -38480,7 +38480,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/process/ProcessRouteTab.vue",
-      "source_location": "L1591",
+      "source_location": "L1593",
       "weight": 1.0
     },
     {
@@ -38489,7 +38489,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/process/ProcessRouteTab.vue",
-      "source_location": "L1680",
+      "source_location": "L1682",
       "weight": 1.0
     },
     {
@@ -38498,7 +38498,7 @@
       "relation": "contains",
       "confidence": "EXTRACTED",
       "source_file": "views/production/process/ProcessRouteTab.vue",
-      "source_location": "L1690",
+      "source_location": "L1692",
       "weight": 1.0
     },
     {

+ 1 - 1
web-admin/graphify-out/GRAPH_REPORT.md

@@ -1,7 +1,7 @@
 # Graph Report - src  (2026-06-15)
 
 ## Corpus Check
-- Large corpus: 729 files · ~509,555 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
+- Large corpus: 729 files · ~509,887 words. Semantic extraction will be expensive (many Claude tokens). Consider running on a subfolder, or use --no-semantic to run AST-only.
 
 ## Summary
 - 2373 nodes · 3234 edges · 50 communities detected

+ 10 - 10
web-admin/graphify-out/manifest.json

@@ -17,8 +17,8 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/common/index.ts": 1781272556.3597305,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/directive/directive.d.ts": 1781272556.3601494,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/api/api.d.ts": 1781272556.3606477,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1781511787.87359,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1781510903.3424032,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts": 1781517220.3789933,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/auto-imports.d.ts": 1781513914.3254542,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/store/index.ts": 1781272556.364766,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/router/index.ts": 1781272556.3652694,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/locales/index.ts": 1781272556.3657222,
@@ -228,7 +228,7 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/production-work-order.ts": 1781337826.360091,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-task-result.ts": 1781272556.6589992,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/protocol-sample-collection.ts": 1781272556.659353,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/material-usage.ts": 1781503317.6165462,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/material-usage.ts": 1781517319.838243,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/qc-third-party-company.ts": 1781415389.1483624,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/collection-protocol.ts": 1781272556.6603115,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/api/chinaRegion.ts": 1781272556.6606348,
@@ -323,7 +323,7 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/dept/index.vue": 1781272556.7969062,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/dict/index.vue": 1781272556.7987096,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/system/leave/index.vue": 1781272556.7999136,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/output-type/index.vue": 1781272556.800664,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/output-type/index.vue": 1781515464.8433418,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/index.vue": 1781435058.7246752,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/SourceChainGraph.vue": 1781272556.804776,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/ChainLinkEdge.vue": 1781272556.8054333,
@@ -333,7 +333,7 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/ChainNodeCard.vue": 1781272556.8073688,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/SampleCustomerViewDialog.vue": 1781272556.8079684,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/cell-product/source-chain/chainToFlow.ts": 1781272556.808583,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/my-production-material-usage/index.vue": 1781502903.1109588,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/my-production-material-usage/index.vue": 1781517868.2039423,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-route/index.vue": 1781272556.8092766,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/sop/index.vue": 1781272556.8117788,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-task/index.vue": 1781272556.812188,
@@ -345,7 +345,7 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskInputEnhancedTable.vue": 1781272556.8264909,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/DispatchQcWorkOrderDialog.vue": 1781272556.8274932,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/ProductionWorkOrderViewDialogHost.vue": 1781447171.0946243,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/SampleReceptionDialog.vue": 1781496401.5875661,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/SampleReceptionDialog.vue": 1781515183.5041866,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/TaskExecutionDialog.vue": 1781435123.291646,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/useProductionWorkOrderViewDialog.ts": 1781447123.1291173,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/production-work-order/EditTaskInfoDialog.vue": 1781272556.829831,
@@ -368,10 +368,10 @@
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/sample-reception-completion/index.vue": 1781272556.844081,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/my-production-task/index.vue": 1781496526.058609,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/equipment/index.vue": 1781333185.7518337,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process/index.vue": 1781428200.5972042,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process/ProcessRouteTab.vue": 1781434724.8148994,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-category/index.vue": 1781428200.6930432,
-  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-category/OutputTypeTab.vue": 1781272556.8543935,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process/index.vue": 1781516298.8619206,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process/ProcessRouteTab.vue": 1781516299.031787,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-category/index.vue": 1781515447.833402,
+  "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-category/OutputTypeTab.vue": 1781515476.20538,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/index/index.vue": 1781272556.8548205,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/index.vue": 1781428530.1744778,
   "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/quality-inspection/qc-work-order/QcWorkOrderSingleCard.vue": 1781444865.1974108,

+ 4 - 1
web-admin/src/App.vue

@@ -80,12 +80,15 @@
     [/(接收|检查项|样本|编码)/, 'auto-title-file'],
     [/(审核|记录)/, 'auto-title-file'],
     [/(客户|公司|机构|医院)/, 'auto-title-user'],
+    [/(洁净服)/, 'auto-title-garment'],
     [/(采集|地区|洁净|库位)/, 'auto-title-location'],
     [/(预约)/, 'auto-title-operation'],
     [/(工单)/, 'auto-title-receipt'],
     [/(流程|待办|已办|申请|请假|执行|计划)/, 'auto-title-operation'],
-    [/(耗材|试剂|设备)/, 'auto-title-box'],
+    [/(耗材|试剂|设备|物料库)/, 'auto-title-box'],
     [/(路线)/, 'auto-title-route'],
+    [/(工艺库)/, 'auto-title-process'],
+    [/(工艺基础)/, 'auto-title-category'],
     [/(工艺)/, 'auto-title-settings'],
     [/(规格|价格|体系|渠道|项目)/, 'auto-title-category'],
     [/(类型)/, 'auto-title-category'],

+ 4 - 1
web-admin/src/api/material-usage.ts

@@ -71,5 +71,8 @@ export function fetchMyMaterialUsageList(params: {
   startTime?: string
   endTime?: string
 }): Promise<{ list: MaterialUsage[]; total: number }> {
-  return request.get('/web/admin/my-material-usages', { params })
+  return request.get<PageList<MaterialUsage>>({
+    url: '/web/admin/my-material-usages',
+    params
+  })
 }

+ 139 - 0
web-admin/src/assets/styles/core/app.scss

@@ -426,6 +426,10 @@ body {
     margin: 12px 24px 4px;
   }
 
+  &--start:not(.el-tabs) {
+    justify-content: flex-start;
+  }
+
   &__track {
     display: inline-flex;
     gap: 4px;
@@ -568,6 +572,119 @@ body {
   }
 }
 
+// 面板级左对齐下划线 Tab(对齐 docs/my-material-usage-tab-demos/04-left-underline)
+.panel-underline-tabs {
+  &:not(.el-tabs) {
+    margin: 12px 24px 0;
+    border-bottom: 2px solid var(--mint-border, #e2e8f0);
+  }
+
+  &__track {
+    display: flex;
+    gap: 0;
+  }
+
+  &__btn {
+    padding: 14px 28px;
+    margin-bottom: -2px;
+    border: none;
+    background: transparent;
+    font-size: 15px;
+    font-weight: 500;
+    line-height: 1.4;
+    color: var(--mint-text-muted, #64748b);
+    cursor: pointer;
+    border-bottom: 3px solid transparent;
+    transition:
+      color 0.15s,
+      border-color 0.15s,
+      background 0.15s;
+
+    &:hover:not(.is-active) {
+      color: var(--mint-text, #1e293b);
+      background: #f8fafc;
+    }
+
+    &.is-active {
+      color: var(--primary-dark, #059669);
+      font-weight: 600;
+      border-bottom-color: var(--primary, #10b981);
+    }
+  }
+
+  // el-tabs 结构
+  &.el-tabs {
+    > .el-tabs__header {
+      display: flex;
+      justify-content: flex-start;
+      margin: 12px 24px 0;
+      padding: 0;
+      border: none;
+      border-bottom: 2px solid var(--mint-border, #e2e8f0);
+    }
+
+    .el-tabs__nav-wrap {
+      flex: 0 1 auto;
+
+      &::after {
+        display: none;
+      }
+    }
+
+    .el-tabs__nav-scroll {
+      display: flex;
+      justify-content: flex-start;
+    }
+
+    .el-tabs__nav {
+      float: none;
+      gap: 0;
+      padding: 0;
+      border: none;
+      background: transparent;
+    }
+
+    .el-tabs__active-bar {
+      display: none;
+    }
+
+    .el-tabs__item {
+      display: inline-flex !important;
+      align-items: center !important;
+      justify-content: center !important;
+      box-sizing: border-box;
+      height: auto !important;
+      margin: 0 0 -2px !important;
+      padding: 14px 28px !important;
+      border: none !important;
+      border-bottom: 3px solid transparent !important;
+      border-radius: 0;
+      background: transparent;
+      font-size: 15px;
+      font-weight: 500;
+      line-height: 1.4;
+      text-align: center !important;
+      color: var(--mint-text-muted, #64748b);
+      transition:
+        color 0.15s,
+        border-color 0.15s,
+        background 0.15s;
+
+      &:hover:not(.is-active) {
+        color: var(--mint-text, #1e293b);
+        background: #f8fafc;
+      }
+
+      &.is-active {
+        color: var(--primary-dark, #059669);
+        font-weight: 600;
+        border-bottom-color: var(--primary, #10b981) !important;
+        background: transparent;
+      }
+    }
+  }
+}
+
 .page-container
   :is(
     .panel-top h2,
@@ -722,6 +839,17 @@ body {
 .console-container .panel-top h2.auto-title-location {
   --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12 21s-7-4.5-7-11a7 7 0 1 1 14 0c0 6.5-7 11-7 11z'/%3E%3Ccircle cx='12' cy='10' r='2.5'/%3E%3C/svg%3E");
 }
+.page-container
+  :is(
+    .panel-top h2,
+    .panel-top h3,
+    .storage-content-header h3,
+    .storage-aside__head h2,
+    .user-dept-aside__head h2
+  ).auto-title-garment,
+.console-container .panel-top h2.auto-title-garment {
+  --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20.38 3.46 16 2a4 4 0 0 1-8 0L3.62 3.46a2 2 0 0 0-1.34 2.23l.58 3.47a1 1 0 0 0 .99.84H6v10c0 1.1.9 2 2 2h8a2 2 0 0 0 2-2V10h2.15a1 1 0 0 0 .99-.84l.58-3.47a2 2 0 0 0-1.34-2.23z'/%3E%3C/svg%3E");
+}
 .page-container
   :is(
     .panel-top h2,
@@ -744,6 +872,17 @@ body {
 .console-container .panel-top h2.auto-title-route {
   --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='6' cy='19' r='3'/%3E%3Cpath d='M9 19h8.5a3.5 3.5 0 0 0 0-7h-11a3.5 3.5 0 0 1 0-7H15'/%3E%3Ccircle cx='18' cy='5' r='3'/%3E%3C/svg%3E");
 }
+.page-container
+  :is(
+    .panel-top h2,
+    .panel-top h3,
+    .storage-content-header h3,
+    .storage-aside__head h2,
+    .user-dept-aside__head h2
+  ).auto-title-process,
+.console-container .panel-top h2.auto-title-process {
+  --panel-title-icon-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='black' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M12.83 2.18a2 2 0 0 0-1.66 0L2.6 6.08a1 1 0 0 0 0 1.83l8.58 3.91a2 2 0 0 0 1.66 0l8.58-3.9a1 1 0 0 0 0-1.83Z'/%3E%3Cpath d='m22 12.65-9.17 4.16a2 2 0 0 1-1.66 0L2 12.65'/%3E%3Cpath d='m22 17.65-9.17 4.16a2 2 0 0 1-1.66 0L2 17.65'/%3E%3C/svg%3E");
+}
 .page-container
   :is(
     .panel-top h2,

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
web-admin/src/graphify-out/cache/ast/021ac45cbaf04b17ea17ac90f9651c31b1074afc7384878627ad93c248c74202.json


+ 1 - 0
web-admin/src/graphify-out/cache/ast/18a5f3b0cded4431bb43e4952a42bf842c9f8ef2f954a08337f91d304cbfbf63.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_process_category_outputtypetab_vue", "label": "OutputTypeTab.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-category/OutputTypeTab.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
web-admin/src/graphify-out/cache/ast/195ceef3fa9fc30908886f48acda49ee96003c2f9e9101b7e830f9ae8576f3f6.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_process_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
web-admin/src/graphify-out/cache/ast/2990d6021bb3098b3f06030bd47efb8a97a728629b4b865b86b2a39526ef739c.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_my_production_material_usage_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/my-production-material-usage/index.vue", "source_location": "L1"}, {"id": "my_production_material_usage_index_handletabchange", "label": "handleTabChange()", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/my-production-material-usage/index.vue", "source_location": "L47"}], "edges": [{"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_my_production_material_usage_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_material_productionmaterialusagetab_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/my-production-material-usage/index.vue", "source_location": "L35", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_my_production_material_usage_index_vue", "target": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_production_material_materialconsumptiontab_vue", "relation": "imports_from", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/my-production-material-usage/index.vue", "source_location": "L36", "weight": 1.0}, {"source": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_my_production_material_usage_index_vue", "target": "my_production_material_usage_index_handletabchange", "relation": "contains", "confidence": "EXTRACTED", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/my-production-material-usage/index.vue", "source_location": "L47", "weight": 1.0}], "raw_calls": []}

+ 1 - 0
web-admin/src/graphify-out/cache/ast/422e18aaf73c8116f7a508857e18caef8b3f78f87f88dc79228453e9e287b8d3.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_types_import_components_d_ts", "label": "components.d.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts", "source_location": "L1"}], "edges": [], "raw_calls": []}

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
web-admin/src/graphify-out/cache/ast/591fee48dae891f848b36ff28d4c82abda99fec9472a6caab067f45e6691a409.json


+ 1 - 0
web-admin/src/graphify-out/cache/ast/94134ffbe39ad939427fb924de3700926ac0727583ad88dc46cf46a68caba438.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_process_category_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-category/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
web-admin/src/graphify-out/cache/ast/9e16682a821819196f321f7f474753bb34308d9926a2438ff92bba899fb6e403.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_types_import_components_d_ts", "label": "components.d.ts", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/types/import/components.d.ts", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
web-admin/src/graphify-out/cache/ast/c9f57eb08209ebac085325fa49702d2da83ea4cdd49afac729a6356f527091d3.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_output_type_index_vue", "label": "index.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/output-type/index.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

+ 1 - 0
web-admin/src/graphify-out/cache/ast/d1a37e024f217848981c2ef23af67fa6b56463a6a1f502c40ffaa40deefef33a.json

@@ -0,0 +1 @@
+{"nodes": [{"id": "users_wuzilong_work_00code_zkxw_web_admin_src_views_production_process_category_outputtypetab_vue", "label": "OutputTypeTab.vue", "file_type": "code", "source_file": "/Users/wuzilong/work/00code/zkxw/web-admin/src/views/production/process-category/OutputTypeTab.vue", "source_location": "L1"}], "edges": [], "raw_calls": []}

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
web-admin/src/graphify-out/cache/ast/e2498813516768e7f555269cef080facde799a10eb36ccf5ae07ca0b8c06ba93.json


+ 1 - 1
web-admin/src/views/cell-storage/my-material-usage/index.vue

@@ -3,7 +3,7 @@
   <div class="page-container">
     <section class="panel">
       <div class="panel-top">
-        <h2 class="title-bar-heading">我的物料领用记录</h2>
+        <h2>我的物料领用记录</h2>
       </div>
       <MaterialUsageTab mine-only />
     </section>

+ 1 - 1
web-admin/src/views/cell-storage/payment-confirm/index.vue

@@ -4,7 +4,7 @@
   <div class="page-container">
     <section class="panel">
       <div class="panel-top">
-        <h2 class="title-bar-heading">缴退费财务确认</h2>
+        <h2>缴退费财务确认</h2>
       </div>
 
       <div class="panel-pill-tabs panel-pill-tabs--start">

+ 1 - 1
web-admin/src/views/pilot-service/cell-product-type/cell-product-kanban.scss

@@ -42,7 +42,7 @@
   font-size: 14px;
   font-weight: 600;
   line-height: 1.4;
-  color: var(--mint-text, #1e293b);
+  color: var(--primary, #10b981);
   word-break: break-word;
   display: -webkit-box;
   -webkit-line-clamp: 2;

+ 1 - 1
web-admin/src/views/pilot-service/cell-product-type/index.vue

@@ -4,7 +4,7 @@
   <div class="page-container cell-product-kanban-page">
     <section class="panel">
       <div class="panel-top">
-        <h2 class="title-bar-heading">细胞产品基础信息维护</h2>
+        <h2>细胞产品基础信息维护</h2>
       </div>
 
       <section class="cell-product-stats">

+ 1 - 1
web-admin/src/views/pilot-service/clinical-payment-confirm/index.vue

@@ -4,7 +4,7 @@
   <div class="page-container">
     <section class="panel">
       <div class="panel-top">
-        <h2 class="title-bar-heading">缴退费财务确认</h2>
+        <h2>缴退费财务确认</h2>
       </div>
 
       <div class="panel-pill-tabs panel-pill-tabs--start">

+ 1 - 1
web-admin/src/views/production/cleanroom-garment/index.vue

@@ -4,7 +4,7 @@
   <div class="page-container">
     <section class="panel">
       <div class="panel-top">
-        <h2 class="title-bar-heading">洁净服管理</h2>
+        <h2>洁净服管理</h2>
       </div>
 
       <div class="filter-block">

+ 4 - 4
web-admin/src/views/production/my-production-material-usage/index.vue

@@ -3,17 +3,17 @@
   <div class="page-container">
     <section class="panel">
       <div class="panel-top">
-        <h2 class="title-bar-heading">我的物料领用记录</h2>
+        <h2>我的物料领用记录</h2>
       </div>
 
-      <div class="panel-pill-tabs">
-        <div class="panel-pill-tabs__track" role="tablist">
+      <div class="panel-underline-tabs">
+        <div class="panel-underline-tabs__track" role="tablist">
           <button
             v-for="tab in TABS"
             :key="tab.name"
             type="button"
             role="tab"
-            class="panel-pill-tabs__btn"
+            class="panel-underline-tabs__btn"
             :class="{ 'is-active': activeTab === tab.name }"
             :aria-selected="activeTab === tab.name"
             @click="handleTabChange(tab.name)"

+ 1 - 1
web-admin/src/views/production/my-production-task/index.vue

@@ -4,7 +4,7 @@
   <div class="page-container">
     <section class="panel">
       <div class="panel-top">
-        <h2 class="title-bar-heading">我的生产任务</h2>
+        <h2>我的生产任务</h2>
       </div>
 
       <div class="panel-pill-tabs">

+ 2 - 2
web-admin/src/views/production/output-type/index.vue

@@ -1,4 +1,4 @@
-<!-- 旧「工艺产出物类型」菜单入口:重定向至工艺基础信息维护 · 产出物类型 Tab -->
+<!-- 旧「工艺产出物类型」菜单入口:重定向至工艺基础信息维护 -->
 <template>
   <div />
 </template>
@@ -12,7 +12,7 @@
   onMounted(() => {
     router.replace({
       name: 'ProductionProcessCategory',
-      query: { ...route.query, tab: 'output-type' }
+      query: { ...route.query }
     })
   })
 </script>

+ 131 - 53
web-admin/src/views/production/process-category/OutputTypeTab.vue

@@ -1,7 +1,49 @@
-<!-- 工艺基础信息维护 · 产出物类型 Tab -->
+<!-- 工艺基础信息维护 · 产出物类型 -->
 <!-- eslint-disable vue/no-v-model-argument -->
 <template>
-  <div class="output-type-tab">
+  <div :class="layout === 'kanban' ? 'kanban-col-body' : 'output-type-tab'">
+    <template v-if="layout === 'kanban'">
+      <div class="kanban-col-search">
+        <el-input v-model="searchForm.keyword" placeholder="类型编码/名称" clearable />
+      </div>
+      <div class="kanban-list" v-loading="loading">
+        <article
+          v-for="row in tableData"
+          :key="row.id"
+          class="entity-card"
+          @dblclick="handleView(row)"
+        >
+          <div class="entity-card-del" @click.stop>
+            <el-button class="btn-op-item" title="删除" @click="handleDelete(row)">
+              <el-icon><Delete /></el-icon>
+            </el-button>
+          </div>
+          <div class="entity-card-body">
+            <h3 class="entity-card-title">{{ row.typeName }}</h3>
+            <div class="entity-card-meta">{{ row.typeCode }}</div>
+            <div v-if="row.remark" class="entity-card-tags">
+              <span class="tag muted">{{ row.remark }}</span>
+            </div>
+            <div class="entity-card-ft" @click.stop>
+              <div class="entity-card-ft-actions">
+                <el-button class="btn-op-item" @click="handleView(row)">
+                  <el-icon><View /></el-icon>查看
+                </el-button>
+                <el-button class="btn-op-item" @click="handleEdit(row)">
+                  <el-icon><Edit /></el-icon>编辑
+                </el-button>
+              </div>
+            </div>
+          </div>
+        </article>
+        <div v-if="!loading && !tableData.length" class="kanban-empty">
+          <span>暂无产出物类型</span>
+          <span class="kanban-empty__hint">点击列头「新增」添加产出物类型</span>
+        </div>
+      </div>
+    </template>
+
+    <template v-else>
       <div class="filter-block">
         <el-form :model="searchForm" class="filter-form" @submit.prevent>
           <div class="filter-grid">
@@ -95,7 +137,10 @@
                 <el-button class="btn-op-item" @click="handleView(row)">
                   <el-icon><View /></el-icon>查看
                 </el-button>
-                <el-dropdown trigger="hover" @command="(cmd: string) => handleDropdownCommand(cmd, row)">
+                <el-dropdown
+                  trigger="hover"
+                  @command="(cmd: string) => handleDropdownCommand(cmd, row)"
+                >
                   <el-button class="btn-op-item">
                     <el-icon><More /></el-icon>
                   </el-button>
@@ -126,57 +171,58 @@
           />
         </div>
       </div>
+    </template>
 
-      <el-dialog
-        v-model="dialogVisible"
-        :title="dialogTitle"
-        width="560px"
-        class="dialog-form-layout"
-        align-center
-        destroy-on-close
-        :close-on-click-modal="false"
+    <el-dialog
+      v-model="dialogVisible"
+      :title="dialogTitle"
+      width="560px"
+      class="dialog-form-layout"
+      align-center
+      destroy-on-close
+      :close-on-click-modal="false"
+    >
+      <el-form
+        ref="formRef"
+        :model="formData"
+        :rules="formRules"
+        label-position="top"
+        class="output-type-dialog-form"
+        scroll-to-error
+        :scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
       >
-        <el-form
-          ref="formRef"
-          :model="formData"
-          :rules="formRules"
-          label-position="top"
-          class="output-type-dialog-form"
-          scroll-to-error
-          :scroll-into-view-options="{ behavior: 'smooth', block: 'center' }"
-        >
-          <div class="form-grid-2">
-            <el-form-item label="类型编码" prop="typeCode">
-              <el-input v-model="formData.typeCode" placeholder="请输入类型编码" />
-            </el-form-item>
-            <el-form-item label="类型名称" prop="typeName">
-              <el-input v-model="formData.typeName" placeholder="请输入类型名称" />
-            </el-form-item>
-            <el-form-item label="备注" class="form-col-span-2">
-              <el-input v-model="formData.remark" placeholder="可选" />
-            </el-form-item>
-          </div>
-        </el-form>
-        <template #footer>
-          <el-button class="btn-ghost" @click="dialogVisible = false">
-            <el-icon><Close /></el-icon>
-            取消
-          </el-button>
-          <el-button class="btn-main" type="primary" @click="handleSubmit" :loading="submitLoading">
-            <el-icon><Check /></el-icon>
-            确定
-          </el-button>
-        </template>
-      </el-dialog>
-
-      <SystemViewDialog
-        v-model:visible="viewVisible"
-        :title="viewRow?.typeName || '类型详情'"
-        :icon="ScaleToOriginal"
-        :code="viewRow?.typeCode"
-        code-label="类型编码"
-        :sections="viewSections"
-      />
+        <div class="form-grid-2">
+          <el-form-item label="类型编码" prop="typeCode">
+            <el-input v-model="formData.typeCode" placeholder="请输入类型编码" />
+          </el-form-item>
+          <el-form-item label="类型名称" prop="typeName">
+            <el-input v-model="formData.typeName" placeholder="请输入类型名称" />
+          </el-form-item>
+          <el-form-item label="备注" class="form-col-span-2">
+            <el-input v-model="formData.remark" placeholder="可选" />
+          </el-form-item>
+        </div>
+      </el-form>
+      <template #footer>
+        <el-button class="btn-ghost" @click="dialogVisible = false">
+          <el-icon><Close /></el-icon>
+          取消
+        </el-button>
+        <el-button class="btn-main" type="primary" @click="handleSubmit" :loading="submitLoading">
+          <el-icon><Check /></el-icon>
+          确定
+        </el-button>
+      </template>
+    </el-dialog>
+
+    <SystemViewDialog
+      v-model:visible="viewVisible"
+      :title="viewRow?.typeName || '类型详情'"
+      :icon="ScaleToOriginal"
+      :code="viewRow?.typeCode"
+      code-label="类型编码"
+      :sections="viewSections"
+    />
   </div>
 </template>
 
@@ -206,6 +252,11 @@
 
   defineOptions({ name: 'OutputTypeTab' })
 
+  const props = withDefaults(defineProps<{ layout?: 'table' | 'kanban' }>(), { layout: 'table' })
+  const emit = defineEmits<{ (e: 'meta-change', payload: { total: number }): void }>()
+
+  const KANBAN_PAGE_SIZE = 100
+
   const searchForm = reactive<{ keyword: string }>({ keyword: '' })
   const tableData = ref<any[]>([])
   const selectedRows = ref<any[]>([])
@@ -260,6 +311,7 @@
       })
       tableData.value = res.list || []
       pagination.total = res.total ?? 0
+      emit('meta-change', { total: pagination.total })
     } finally {
       loading.value = false
     }
@@ -364,7 +416,7 @@
       await loadData()
     }, fullscreenLoadingText.batchDelete)
   }
-  // 输入检索条件时自动搜索(防抖 300ms)
+
   watchDebounced(
     searchForm,
     () => {
@@ -374,12 +426,38 @@
     { debounce: 300, deep: true }
   )
 
+  defineExpose({ handleAdd, loadData })
+
   onMounted(() => {
+    if (props.layout === 'kanban') {
+      pagination.pageSize = KANBAN_PAGE_SIZE
+    }
     loadData()
   })
 </script>
 
 <style scoped lang="scss">
+  .kanban-col-body {
+    display: flex;
+    flex-direction: column;
+    flex: 1;
+    min-height: 0;
+  }
+
+  .kanban-col-search {
+    padding: 8px 10px 0;
+    flex-shrink: 0;
+  }
+
+  .kanban-list {
+    padding: 10px;
+    overflow-y: auto;
+    flex: 1;
+    min-height: 0;
+  }
+
+  @import './process-category-kanban.scss';
+
   .output-type-dialog-form {
     :deep(.el-form-item) {
       margin-bottom: 0;

+ 250 - 166
web-admin/src/views/production/process-category/index.vue

@@ -1,135 +1,111 @@
-<!-- 工艺基础信息维护(工序分类 + 产出物类型) -->
+<!-- 工艺基础信息维护 · 看板模式(工艺工序分类 | 工艺产出物类型) -->
 <!-- eslint-disable vue/no-v-model-argument -->
 <template>
-  <div class="page-container">
+  <div class="page-container process-base-kanban-page">
     <section class="panel">
       <div class="panel-top">
         <h2>工艺基础信息维护</h2>
       </div>
 
-      <el-tabs v-model="activeTab" class="panel-pill-tabs">
-        <el-tab-pane label="工艺工序分类" name="category">
-      <div class="filter-block">
-        <el-form :model="searchForm" class="filter-form" @submit.prevent>
-          <div class="filter-grid">
-            <el-form-item label="关键词">
-              <el-input v-model="searchForm.keyword" placeholder="分类编码/名称" clearable />
-            </el-form-item>
+      <section class="process-base-stats">
+        <div class="stat-card">
+          <div class="stat-icon stat-icon--svg">
+            <el-icon><FolderOpened /></el-icon>
           </div>
-          <div class="filter-actions-row">
-            <span class="filter-hint">按编码、名称模糊查询。</span>
-            <div class="filter-actions-end">
-              <el-button class="btn-ghost" @click="handleReset"
-                ><el-icon><RefreshLeft /></el-icon>重置</el-button
-              >
-              <el-button class="btn-main" type="primary" @click="handleSearch"
-                ><el-icon><Search /></el-icon>搜索</el-button
-              >
-            </div>
+          <div class="stat-main">
+            <div class="stat-k">工艺工序分类</div>
+            <div class="stat-v">{{ categoryTotal }}</div>
           </div>
-        </el-form>
-      </div>
-
-      <div class="list-toolbar">
-        <div class="list-toolbar-row">
-          <div class="list-toolbar-selected">
-            已选 <span class="sel-num">{{ selectedRows.length }}</span> 项
+        </div>
+        <div class="stat-card">
+          <div class="stat-icon stat-icon--svg">
+            <el-icon><Box /></el-icon>
+          </div>
+          <div class="stat-main">
+            <div class="stat-k">工艺产出物类型</div>
+            <div class="stat-v">{{ outputTypeTotal }}</div>
           </div>
-          <el-button class="btn-main" type="primary" @click="handleAdd">
-            <el-icon><Plus /></el-icon>
-            新增分类
-          </el-button>
-          <el-button
-            class="btn-ghost"
-            type="danger"
-            :disabled="selectedRows.length === 0"
-            @click="handleBatchDelete"
-          >
-            批量删除
-          </el-button>
         </div>
-      </div>
+      </section>
 
-      <div class="list-body list-wrap">
-        <el-table
-          class="list-table"
-          :data="tableData"
-          v-loading="loading"
-          stripe
-          row-key="id"
-          @selection-change="handleSelectionChange"
-          @row-dblclick="handleView"
-        >
-          <el-table-column type="selection" min-width="55" align="center" header-align="center" />
-          <el-table-column
-            type="index"
-            label="序号"
-            min-width="68"
-            align="center"
-            header-align="center"
-            :index="(index) => (pagination.page - 1) * pagination.pageSize + index + 1"
-          />
-          <el-table-column
-            prop="categoryCode"
-            label="分类编码"
-            min-width="150"
-            align="center"
-            header-align="center"
-          />
-          <el-table-column
-            prop="categoryName"
-            label="分类名称"
-            min-width="150"
-            align="center"
-            header-align="center"
-          />
-          <el-table-column
-            prop="createTime"
-            label="创建时间"
-            min-width="180"
-            align="center"
-            header-align="center"
-          />
-          <el-table-column
-            label="操作"
-            min-width="120"
-            fixed="right"
-            align="center"
-            header-align="center"
-          >
-            <template #default="{ row }">
-              <div style="display: flex; justify-content: center; gap: 8px">
-                <el-button class="btn-op-item" @click="handleView(row)">
-                  <el-icon><View /></el-icon>查看
+      <div class="kanban">
+        <!-- 左列:工艺工序分类 -->
+        <div class="kanban-col">
+          <div class="kanban-col-hd">
+            <span class="kanban-col-title">
+              工艺工序分类
+              <span class="kanban-count">{{ categoryTotal }}</span>
+            </span>
+            <el-button class="btn-main kanban-add-btn" type="primary" @click="handleAdd">
+              <el-icon><Plus /></el-icon>
+              新增
+            </el-button>
+          </div>
+          <div class="kanban-col-search">
+            <el-input
+              v-model="searchForm.keyword"
+              placeholder="分类编码/名称"
+              clearable
+              @keyup.enter="handleSearch"
+            />
+          </div>
+          <div class="kanban-list" v-loading="loading">
+            <article
+              v-for="row in tableData"
+              :key="row.id"
+              class="entity-card"
+              @dblclick="handleView(row)"
+            >
+              <div class="entity-card-del" @click.stop>
+                <el-button class="btn-op-item" title="删除" @click="handleDelete(row)">
+                  <el-icon><Delete /></el-icon>
                 </el-button>
-                <el-dropdown trigger="hover" @command="(cmd: string) => handleDropdownCommand(cmd, row)">
-                  <el-button class="btn-op-item">
-                    <el-icon><More /></el-icon>
-                  </el-button>
-                  <template #dropdown>
-                    <el-dropdown-menu>
-                      <el-dropdown-item command="edit">
-                        <el-icon><Edit /></el-icon>编辑
-                      </el-dropdown-item>
-                      <el-dropdown-item command="delete" divided>
-                        <el-icon><Delete /></el-icon>删除
-                      </el-dropdown-item>
-                    </el-dropdown-menu>
-                  </template>
-                </el-dropdown>
               </div>
-            </template>
-          </el-table-column>
-        </el-table>
-        <div class="pagination list-pager-bar">
-          <el-pagination
-            :current-page="pagination.page"
-            :page-size="pagination.pageSize"
-            :total="pagination.total"
-            :page-sizes="[10, 20, 50]"
-            layout="total, sizes, prev, pager, next"
-            @size-change="(val) => ((pagination.page = 1), (pagination.pageSize = val), loadData())"
-            @current-change="(val) => ((pagination.page = val), loadData())"
+              <div class="entity-card-body">
+                <h3 class="entity-card-title">{{ row.categoryName }}</h3>
+                <div class="entity-card-meta">{{ row.categoryCode }}</div>
+                <div v-if="row.remark" class="entity-card-tags">
+                  <span class="tag muted">{{ row.remark }}</span>
+                </div>
+                <div class="entity-card-ft" @click.stop>
+                  <div class="entity-card-ft-actions">
+                    <el-button class="btn-op-item" @click="handleView(row)">
+                      <el-icon><View /></el-icon>查看
+                    </el-button>
+                    <el-button class="btn-op-item" @click="handleEdit(row)">
+                      <el-icon><Edit /></el-icon>编辑
+                    </el-button>
+                  </div>
+                </div>
+              </div>
+            </article>
+            <div v-if="!loading && !tableData.length" class="kanban-empty">
+              <span>暂无工序分类</span>
+              <span class="kanban-empty__hint">点击右上角「新增」添加工序分类</span>
+            </div>
+          </div>
+        </div>
+
+        <!-- 右列:工艺产出物类型 -->
+        <div class="kanban-col">
+          <div class="kanban-col-hd">
+            <span class="kanban-col-title">
+              工艺产出物类型
+              <span class="kanban-count">{{ outputTypeTotal }}</span>
+            </span>
+            <el-button
+              class="btn-main kanban-add-btn"
+              type="primary"
+              @click="outputTypeTabRef?.handleAdd()"
+            >
+              <el-icon><Plus /></el-icon>
+              新增
+            </el-button>
+          </div>
+          <OutputTypeTab
+            ref="outputTypeTabRef"
+            layout="kanban"
+            @meta-change="onOutputTypeMetaChange"
           />
         </div>
       </div>
@@ -184,12 +160,6 @@
         code-label="分类编码"
         :sections="viewSections"
       />
-        </el-tab-pane>
-
-        <el-tab-pane label="工艺产出物类型" name="output-type" lazy>
-          <OutputTypeTab />
-        </el-tab-pane>
-      </el-tabs>
     </section>
   </div>
 </template>
@@ -204,10 +174,9 @@
     View,
     Edit,
     Delete,
-    More,
     ScaleToOriginal,
-    RefreshLeft,
-    Search
+    FolderOpened,
+    Box
   } from '@element-plus/icons-vue'
   import SystemViewDialog from '@/components/business/system-view-dialog/index.vue'
   import { withFullscreenLoading, fullscreenLoadingText } from '@/utils/fullscreen-loading'
@@ -221,13 +190,16 @@
 
   defineOptions({ name: 'ProductionProcessCategory' })
 
-  const route = useRoute()
-  const activeTab = ref('category')
+  const KANBAN_PAGE_SIZE = 100
+
+  const outputTypeTabRef = ref<InstanceType<typeof OutputTypeTab> | null>(null)
+  const categoryTotal = ref(0)
+  const outputTypeTotal = ref(0)
+
   const searchForm = reactive<{ keyword: string }>({ keyword: '' })
   const tableData = ref<any[]>([])
-  const selectedRows = ref<any[]>([])
   const loading = ref(false)
-  const pagination = reactive({ page: 1, pageSize: 10, total: 0 })
+  const pagination = reactive({ page: 1, pageSize: KANBAN_PAGE_SIZE, total: 0 })
   const dialogVisible = ref(false)
   const dialogTitle = ref('新增分类')
   const isEdit = ref(false)
@@ -248,10 +220,6 @@
     categoryName: [{ required: true, message: '请输入分类名称', trigger: 'blur' }]
   }
 
-  const handleSelectionChange = (rows: any[]) => {
-    selectedRows.value = rows
-  }
-
   const viewSections = computed(() => {
     if (!viewRow.value) return []
     const row = viewRow.value
@@ -267,6 +235,10 @@
     ]
   })
 
+  function onOutputTypeMetaChange(payload: { total: number }) {
+    outputTypeTotal.value = payload.total
+  }
+
   async function loadData() {
     loading.value = true
     try {
@@ -277,6 +249,7 @@
       })
       tableData.value = res.list || []
       pagination.total = res.total ?? 0
+      categoryTotal.value = pagination.total
     } finally {
       loading.value = false
     }
@@ -287,12 +260,6 @@
     loadData()
   }
 
-  function handleReset() {
-    searchForm.keyword = ''
-    pagination.page = 1
-    loadData()
-  }
-
   function resetForm() {
     formData.categoryCode = ''
     formData.categoryName = ''
@@ -348,11 +315,6 @@
     viewVisible.value = true
   }
 
-  function handleDropdownCommand(cmd: string, row: any) {
-    if (cmd === 'edit') handleEdit(row)
-    else if (cmd === 'delete') handleDelete(row)
-  }
-
   async function handleDelete(row: any) {
     try {
       await ElMessageBox.confirm('确定要删除该分类吗?', '提示', { type: 'warning' })
@@ -366,22 +328,6 @@
     }, fullscreenLoadingText.delete)
   }
 
-  async function handleBatchDelete() {
-    const ids = selectedRows.value.map((r) => r.id)
-    if (!ids.length) return
-    await ElMessageBox.confirm(`确定删除选中的 ${ids.length} 个分类吗?`, '提示', {
-      type: 'warning'
-    })
-    await withFullscreenLoading(async () => {
-      for (const id of ids) {
-        await fetchDeleteProcessCategory(id)
-      }
-      ElMessage.success('批量删除成功')
-      selectedRows.value = []
-      await loadData()
-    }, fullscreenLoadingText.batchDelete)
-  }
-  // 输入检索条件时自动搜索(防抖 300ms)
   watchDebounced(
     searchForm,
     () => {
@@ -392,18 +338,156 @@
   )
 
   onMounted(() => {
-    const tab = route.query.tab
-    if (tab === 'output-type') {
-      activeTab.value = 'output-type'
-    }
     loadData()
   })
 </script>
 
 <style scoped lang="scss">
+  .process-base-stats {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    gap: 12px;
+    padding: 16px 16px 0;
+  }
+
+  .stat-card {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    min-height: 72px;
+    padding: 12px 16px;
+    border: 1px solid var(--mint-border, #d9e8e2);
+    border-radius: 10px;
+    background: var(--surface, #fff);
+    box-shadow: 0 1px 3px rgb(0 0 0 / 5%);
+  }
+
+  .stat-icon {
+    flex-shrink: 0;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 40px;
+    height: 40px;
+
+    &--svg {
+      border-radius: 10px;
+      background: var(--primary-bg, #ecfdf5);
+      color: var(--primary-dark, #059669);
+      font-size: 22px;
+    }
+  }
+
+  .stat-main {
+    display: flex;
+    flex: 1;
+    align-items: center;
+    justify-content: space-between;
+    gap: 12px;
+    min-width: 0;
+  }
+
+  .stat-k {
+    font-size: 14px;
+    font-weight: 600;
+    color: var(--mint-text, #1e293b);
+  }
+
+  .stat-v {
+    font-size: 22px;
+    font-weight: 700;
+    color: var(--primary-dark, #059669);
+    line-height: 1.1;
+  }
+
+  .kanban {
+    display: grid;
+    grid-template-columns: repeat(2, 1fr);
+    gap: 12px;
+    padding: 16px;
+    align-items: stretch;
+  }
+
+  .kanban-col {
+    min-width: 0;
+    background: var(--table-head, #f8fafc);
+    border: 1px solid var(--mint-border, #d9e8e2);
+    border-radius: 10px;
+    display: flex;
+    flex-direction: column;
+    min-height: calc(100vh - 280px);
+    max-height: calc(100vh - 200px);
+  }
+
+  .kanban-col-hd {
+    padding: 10px 12px;
+    border-bottom: 1px solid var(--mint-border, #d9e8e2);
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 8px;
+    flex-shrink: 0;
+  }
+
+  .kanban-col-title {
+    font-size: 13px;
+    font-weight: 600;
+    color: var(--mint-text, #1e293b);
+    display: inline-flex;
+    align-items: center;
+    gap: 8px;
+    min-width: 0;
+  }
+
+  .kanban-count {
+    font-size: 11px;
+    font-weight: 700;
+    padding: 2px 8px;
+    border-radius: 999px;
+    background: var(--surface, #fff);
+    color: var(--mint-text-muted, #64748b);
+    border: 1px solid var(--mint-border, #d9e8e2);
+  }
+
+  .kanban-add-btn {
+    flex-shrink: 0;
+    height: 32px;
+    padding: 0 10px;
+    font-size: 13px;
+  }
+
+  .kanban-col-search {
+    padding: 8px 10px 0;
+    flex-shrink: 0;
+  }
+
+  .kanban-list {
+    padding: 10px;
+    overflow-y: auto;
+    flex: 1;
+    min-height: 0;
+  }
+
+  @import './process-category-kanban.scss';
+
   .process-category-dialog-form {
     :deep(.el-form-item) {
       margin-bottom: 0;
     }
   }
+
+  @media (max-width: 1100px) {
+    .kanban {
+      grid-template-columns: 1fr;
+    }
+
+    .kanban-col {
+      min-height: 320px;
+      max-height: 420px;
+    }
+
+    .process-base-stats {
+      grid-template-columns: 1fr;
+    }
+  }
 </style>

+ 8 - 0
web-admin/src/views/production/process-category/process-category-kanban.scss

@@ -0,0 +1,8 @@
+@import '../../pilot-service/cell-product-type/cell-product-kanban.scss';
+
+.kanban-col-body {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  min-height: 0;
+}

+ 2 - 0
web-admin/src/views/production/process/ProcessRouteTab.vue

@@ -264,6 +264,8 @@
         width="90vw"
         class="dialog-form-layout route-config-dialog"
         align-center
+        append-to-body
+        modal-class="protocol-dialog-mask"
         destroy-on-close
         :close-on-click-modal="false"
       >

+ 3 - 1
web-admin/src/views/production/process/index.vue

@@ -6,7 +6,7 @@
         <h2>工艺库管理</h2>
       </div>
 
-      <el-tabs v-model="activeTab" class="panel-pill-tabs">
+      <el-tabs v-model="activeTab" class="panel-underline-tabs">
         <el-tab-pane label="工艺列表" name="list">
       <div class="filter-block">
         <el-form :model="searchForm" class="filter-form" @submit.prevent>
@@ -153,6 +153,8 @@
         width="1200px"
         class="dialog-form-layout"
         align-center
+        append-to-body
+        modal-class="protocol-dialog-mask"
         destroy-on-close
         :close-on-click-modal="false"
       >

+ 1 - 1
web-admin/src/views/production/production-material/index.vue

@@ -4,7 +4,7 @@
   <div class="page-container">
     <section class="panel">
       <div class="panel-top">
-        <h2 class="title-bar-heading">生产物料库</h2>
+        <h2>生产物料库</h2>
       </div>
 
       <div class="panel-pill-tabs">

+ 1 - 1
web-admin/src/views/production/production-work-order/SampleReceptionDialog.vue

@@ -277,10 +277,10 @@
     fetchSampleReceptionDetail,
     fetchSampleReceptionUpdate,
     fetchSampleReceptionSubmit,
-    fetchSampleReceptionCheckItemsActive,
     fetchAllocateLabStorageCodes,
     type SampleReceptionCheckResult
   } from '@/api/sample-reception'
+  import { fetchSampleReceptionCheckItemsActive } from '@/api/sample-reception-check-item'
   import { fetchUserOptions } from '@/api/admin'
   import { useUserStore } from '@/store/modules/user'
 

+ 1 - 1
web-admin/src/views/production/production-work-order/index.vue

@@ -4,7 +4,7 @@
   <div class="page-container">
     <section class="panel">
       <div class="panel-top">
-        <h2 class="title-bar-heading">生产工单</h2>
+        <h2>生产工单</h2>
       </div>
 
       <div class="panel-pill-tabs">

+ 1 - 1
web-admin/src/views/quality-inspection/my-qc-material-usage/index.vue

@@ -3,7 +3,7 @@
   <div class="page-container">
     <section class="panel">
       <div class="panel-top">
-        <h2 class="title-bar-heading">我的物料领用记录</h2>
+        <h2>我的物料领用记录</h2>
       </div>
       <QcMaterialUsageTab mine-only />
     </section>

+ 1 - 1
web-admin/src/views/quality-inspection/my-qc-task/index.vue

@@ -4,7 +4,7 @@
   <div class="page-container">
     <section class="panel">
       <div class="panel-top">
-        <h2 class="title-bar-heading">我的质检任务</h2>
+        <h2>我的质检任务</h2>
       </div>
 
       <div class="panel-pill-tabs">

+ 1 - 1
web-admin/src/views/quality-inspection/qc-work-order/index.vue

@@ -4,7 +4,7 @@
   <div class="page-container">
     <section class="panel">
       <div class="panel-top">
-        <h2 class="title-bar-heading">质检工单</h2>
+        <h2>质检工单</h2>
       </div>
 
       <div class="panel-pill-tabs">

+ 1 - 1
web-admin/src/views/research-donation/sample-info/index.vue

@@ -3,7 +3,7 @@
   <div class="page-container">
     <section class="panel">
       <div class="panel-top">
-        <h2 class="title-bar-heading">样本信息管理</h2>
+        <h2>样本信息管理</h2>
       </div>
       <DonationSampleCollectionInfoTab />
     </section>

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov