diff --git a/mobile-h5/components/AttachList.vue b/mobile-h5/components/AttachList.vue
new file mode 100644
index 00000000..ae2f0ab7
--- /dev/null
+++ b/mobile-h5/components/AttachList.vue
@@ -0,0 +1,75 @@
+
+
+
+
+
+ 添加附件
+ 支持 PDF / 图片 / Office,可多选上传
+
+
+
+
+
+
+
+ {{ fileExt(a.name) }}
+
+
+ {{ a.name }}
+
+ {{ a.size || '—' }}
+ {{ a.time || '—' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile-h5/components/BizListCard.vue b/mobile-h5/components/BizListCard.vue
new file mode 100644
index 00000000..d43c686c
--- /dev/null
+++ b/mobile-h5/components/BizListCard.vue
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+ {{ item.typeLabel }}
+ {{ item.title }}
+
+ {{ item.status }}
+
+
+
+ {{ item.desc }}
+
+
+ 金额
+ ¥{{ item.amount }}
+
+
+ 进度
+ {{ item.progress || '—' }}
+
+ {{ item.time }}
+
+
+
+
+ {{ item.code }}
+ {{ item.owner }}
+
+
+
+
+
+
+
+
diff --git a/mobile-h5/components/FlowTimeline.vue b/mobile-h5/components/FlowTimeline.vue
new file mode 100644
index 00000000..261c4c11
--- /dev/null
+++ b/mobile-h5/components/FlowTimeline.vue
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
+
+
+ {{ log.title }}
+ {{
+ log.badge || (log.tone === 'warn' ? '进行中' : '已完成')
+ }}
+
+ {{ log.desc }}
+
+
+
+ {{ log.actor }}
+
+
+
+ {{ log.time }}
+
+
+
+
+
+
+
+
diff --git a/mobile-h5/components/MenuGrid.vue b/mobile-h5/components/MenuGrid.vue
new file mode 100644
index 00000000..fd9c4416
--- /dev/null
+++ b/mobile-h5/components/MenuGrid.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/mobile-h5/components/TodoPanel.vue b/mobile-h5/components/TodoPanel.vue
new file mode 100644
index 00000000..a684e0b8
--- /dev/null
+++ b/mobile-h5/components/TodoPanel.vue
@@ -0,0 +1,61 @@
+
+
+
+ 待办摘要
+ 查看全部 ›
+
+
+
+
+
+ {{ stats.approve }}
+ 待审批
+
+
+
+ {{ stats.pay }}
+ 待确认缴费
+
+
+
+ {{ stats.alert }}
+ 异常提醒
+
+
+
+
+
+
+
+
+ {{ t.title }}
+ {{ t.desc }}
+
+
+ {{ t.tag }}
+ {{ t.time }}
+
+
+
+
+
+
+
+
diff --git a/mobile-h5/pages/demo/comps.vue b/mobile-h5/pages/demo/comps.vue
new file mode 100644
index 00000000..2df26d55
--- /dev/null
+++ b/mobile-h5/pages/demo/comps.vue
@@ -0,0 +1,113 @@
+
+
+
+
+
+ DESIGN SYSTEM
+ 组件合集
+ 按钮 · 状态 · 反馈 · 附件等基础样式
+
+
+
+
+
+ 按钮 Button
+
+ 主按钮
+ 描边
+ 次要
+ 危险
+
+
+ 通栏主按钮(44px)
+
+
+
+
+ 状态 Tag
+
+ 待处理
+ 进行中
+ 已完成
+ 异常
+
+
+
+
+ 通知 / 空态 / 加载
+
+
+
+
+
+ 加载中
+
+
+
+
+ 反馈
+
+
+ Toast
+
+
+ Dialog
+
+
+
+
+
+ 附件卡片
+
+
+
+
+
+
+
+
+
diff --git a/mobile-h5/pages/demo/detail.vue b/mobile-h5/pages/demo/detail.vue
new file mode 100644
index 00000000..9dd22f07
--- /dev/null
+++ b/mobile-h5/pages/demo/detail.vue
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+ {{ detail.typeLabel }}
+ {{ detail.status }}
+
+ {{ detail.title }}
+ {{ detail.code }}
+
+
+
+
+
+ {{ detail.amount ? '金额' : '进度' }}
+
+ {{ detail.amount ? '¥' + detail.amount : detail.progress || '—' }}
+
+
+
+ 负责人
+ {{ detail.owner }}
+
+
+ 更新时间
+ {{ detail.time }}
+
+
+
+
+
+ 基础信息
+
+ 业务类型{{ detail.typeLabel }}
+ 所属部门{{ detail.dept }}
+ 客户/对象{{ detail.customer }}
+ 创建时间{{ detail.createdAt }}
+
+ {{ detail.desc }}
+
+
+
+ 业务进度
+
+ {{ s }}
+
+
+
+
+
+ 缴费明细
+ 全部
+
+
+
+ {{ p.name }}
+ {{ p.sub }}
+
+
+ ¥{{ p.amount }}
+ {{ p.status }}
+
+
+
+
+
+
+ 附件
+ {{ (detail.attachments || []).length }} 个文件
+
+
+
+
+
+
+ 流转记录
+ {{ (detail.logs || []).length }} 步
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile-h5/pages/demo/form.vue b/mobile-h5/pages/demo/form.vue
new file mode 100644
index 00000000..df4b644a
--- /dev/null
+++ b/mobile-h5/pages/demo/form.vue
@@ -0,0 +1,164 @@
+
+
+
+
+
+
+ 基础信息
+
+ (form.name = e.detail)"
+ />
+ (form.code = e.detail)"
+ />
+
+
+ (form.urgent = e.detail)"
+ />
+
+
+
+ 业务详情
+
+ (form.owner = e.detail)"
+ />
+ (form.date = e.detail)"
+ />
+ (form.amount = e.detail)"
+ />
+ (form.remark = e.detail)"
+ />
+
+
+ 必填项校验失败时 Toast 提示。底栏固定「取消 / 保存」。
+
+
+
+
+
+
+
+
diff --git a/mobile-h5/pages/demo/list.vue b/mobile-h5/pages/demo/list.vue
new file mode 100644
index 00000000..b67039fa
--- /dev/null
+++ b/mobile-h5/pages/demo/list.vue
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+ WORKSPACE
+ 今日待办一览
+
+ 当前展示
+ {{ list.length }} 条
+ · 已筛选
+
+
+
+
+
+
+
+
+
+
+ {{ tab.label }}
+ {{ tabCount(idx) }}
+
+
+
+
+
+
+
+ 已经到底了
+
+
+
+
+
diff --git a/mobile-h5/pages/index/index.vue b/mobile-h5/pages/index/index.vue
new file mode 100644
index 00000000..0e457153
--- /dev/null
+++ b/mobile-h5/pages/index/index.vue
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+ 菜单卡片按角色权限动态渲染;无权限的功能入口不会出现在宫格中。
+
+
+
+
+ 功能菜单
+ 共 {{ menus.length }} 项
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mobile-h5/styles/components/attach.scss b/mobile-h5/styles/components/attach.scss
index 710ddc7d..fed82544 100644
--- a/mobile-h5/styles/components/attach.scss
+++ b/mobile-h5/styles/components/attach.scss
@@ -1 +1,185 @@
-// Task 4: attachment styles (.d-attach-list, .d-attach-upload, .d-attach-card, file type classes)
+.d-attach-list {
+ display: flex;
+ flex-direction: column;
+ gap: 20rpx;
+}
+
+.d-attach-upload {
+ width: 100%;
+ border: 3rpx dashed #9ad9be;
+ border-radius: 28rpx;
+ background: linear-gradient(135deg, rgba(233, 249, 242, 0.95), rgba(255, 255, 255, 0.95));
+ padding: 28rpx 24rpx;
+ display: flex;
+ align-items: center;
+ gap: 24rpx;
+}
+
+.d-attach-upload:active {
+ border-color: #0a9f6d;
+}
+
+.d-attach-upload .up-icon {
+ width: 88rpx;
+ height: 88rpx;
+ border-radius: 24rpx;
+ background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
+ color: #fff;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 44rpx;
+ box-shadow: 0 12rpx 28rpx rgba(10, 159, 109, 0.28);
+ flex-shrink: 0;
+}
+
+.d-attach-upload .up-text {
+ flex: 1;
+ min-width: 0;
+}
+
+.d-attach-upload .up-title {
+ font-size: 26rpx;
+ font-weight: 700;
+ color: #087f57;
+}
+
+.d-attach-upload .up-sub {
+ margin-top: 4rpx;
+ font-size: 22rpx;
+ color: #6b7b74;
+ line-height: 1.4;
+}
+
+.d-attach-card {
+ display: flex;
+ align-items: center;
+ gap: 24rpx;
+ padding: 24rpx;
+ border-radius: 28rpx;
+ background: #fff;
+ border: 1rpx solid #e4eee9;
+ box-shadow: 0 8rpx 28rpx rgba(20, 55, 40, 0.05);
+ position: relative;
+ overflow: hidden;
+}
+
+.d-attach-card::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: 6rpx;
+ background: var(--file-accent, #0a9f6d);
+}
+
+.d-attach-card.ft-pdf {
+ --file-accent: #d14343;
+}
+.d-attach-card.ft-img {
+ --file-accent: #0a9f6d;
+}
+.d-attach-card.ft-xls {
+ --file-accent: #189b65;
+}
+.d-attach-card.ft-doc {
+ --file-accent: #3b82f6;
+}
+.d-attach-card.ft-other {
+ --file-accent: #d6a343;
+}
+
+.d-attach-card .file-icon {
+ width: 92rpx;
+ height: 92rpx;
+ border-radius: 26rpx;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 2rpx;
+ flex-shrink: 0;
+ color: #fff;
+ font-weight: 800;
+}
+
+.d-attach-card.ft-pdf .file-icon {
+ background: linear-gradient(145deg, #f07171, #d14343);
+}
+.d-attach-card.ft-img .file-icon {
+ background: linear-gradient(145deg, #3dca96, #0a9f6d);
+}
+.d-attach-card.ft-xls .file-icon {
+ background: linear-gradient(145deg, #4fbf8a, #189b65);
+}
+.d-attach-card.ft-doc .file-icon {
+ background: linear-gradient(145deg, #6ea8ff, #3b82f6);
+}
+.d-attach-card.ft-other .file-icon {
+ background: linear-gradient(145deg, #e4bc6a, #d6a343);
+}
+
+.d-attach-card .file-icon .ext {
+ font-size: 20rpx;
+ letter-spacing: 0.04em;
+ line-height: 1;
+ text-transform: uppercase;
+}
+
+.d-attach-card .file-body {
+ flex: 1;
+ min-width: 0;
+}
+
+.d-attach-card .file-name {
+ font-size: 26rpx;
+ font-weight: 650;
+ color: #173128;
+ line-height: 1.35;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.d-attach-card .file-meta {
+ margin-top: 8rpx;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16rpx;
+ font-size: 22rpx;
+ color: #8aa198;
+}
+
+.d-attach-card .file-ops {
+ display: flex;
+ flex-direction: column;
+ gap: 12rpx;
+ flex-shrink: 0;
+}
+
+.d-attach-card .file-ops .op-btn {
+ width: 64rpx;
+ height: 64rpx;
+ border-radius: 20rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 32rpx;
+}
+
+.d-attach-card .file-ops .btn-preview {
+ background: var(--brand-green-soft, #e9f9f2);
+ color: var(--brand-green-dark, #087f57);
+}
+
+.d-attach-card .file-ops .btn-download {
+ background: #f0f5f3;
+ color: #4d635a;
+}
+
+.d-attach-card .file-ops .icon-download {
+ width: 28rpx;
+ height: 28rpx;
+ display: block;
+}
diff --git a/mobile-h5/styles/components/detail.scss b/mobile-h5/styles/components/detail.scss
index 7215d023..beb9211e 100644
--- a/mobile-h5/styles/components/detail.scss
+++ b/mobile-h5/styles/components/detail.scss
@@ -1 +1,241 @@
-// Task 4: detail page styles (.detail-page, .detail-hero, .detail-metric-dock, .detail-section, .detail-footer)
+@import '../mixins.scss';
+
+.detail-page {
+ min-height: 100vh;
+ background:
+ radial-gradient(760rpx 400rpx at 100% 8%, rgba(214, 163, 67, 0.14), transparent 55%),
+ linear-gradient(180deg, #e8f5ef 0%, #f4f7f6 42%, #f7f9f8 100%);
+ padding-bottom: 176rpx;
+}
+
+.detail-hero {
+ position: relative;
+ overflow: hidden;
+ padding: 88rpx 28rpx 56rpx;
+ border-radius: 0 0 56rpx 56rpx;
+ color: #fff;
+}
+
+.detail-hero.status-warning {
+ background: linear-gradient(155deg, #a67b20 0%, #d6a343 45%, #e8c06a 100%);
+}
+.detail-hero.status-primary {
+ @include hero-green;
+ border-radius: 0 0 56rpx 56rpx;
+}
+.detail-hero.status-danger {
+ background: linear-gradient(155deg, #9c2f2f 0%, #d14343 50%, #e36a6a 100%);
+}
+.detail-hero.status-success {
+ background: linear-gradient(155deg, #0f6b48 0%, #189b65 52%, #3bb883 100%);
+}
+
+.detail-hero-body {
+ position: relative;
+ z-index: 1;
+ margin-top: 12rpx;
+}
+
+.detail-hero .type-line {
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+ margin-bottom: 20rpx;
+}
+
+.detail-hero .type-chip,
+.detail-hero .status-chip {
+ font-size: 22rpx;
+ font-weight: 650;
+ padding: 6rpx 18rpx;
+ border-radius: 999rpx;
+}
+
+.detail-hero .type-chip {
+ background: rgba(255, 255, 255, 0.2);
+ color: #fff;
+}
+
+.detail-hero .status-chip {
+ background: #fff;
+ color: #173128;
+}
+
+.detail-hero .hero-title {
+ font-size: 40rpx;
+ font-weight: 750;
+ line-height: 1.35;
+}
+
+.detail-hero .code {
+ margin-top: 16rpx;
+ font-size: 24rpx;
+ opacity: 0.92;
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
+}
+
+.detail-metric-dock {
+ margin: -36rpx 24rpx 0;
+ position: relative;
+ z-index: 3;
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 16rpx;
+ padding: 24rpx;
+ border-radius: 36rpx;
+ background: rgba(255, 255, 255, 0.94);
+ border: 1rpx solid rgba(220, 235, 228, 0.95);
+ box-shadow: 0 24rpx 60rpx rgba(15, 55, 40, 0.1);
+}
+
+.detail-metric {
+ text-align: center;
+ padding: 8rpx 4rpx;
+}
+
+.detail-metric .lab {
+ display: block;
+ font-size: 20rpx;
+ color: #8aa198;
+ margin-bottom: 8rpx;
+}
+
+.detail-metric .val {
+ display: block;
+ font-size: 28rpx;
+ font-weight: 750;
+ color: #173128;
+ line-height: 1.25;
+ word-break: break-all;
+}
+
+.detail-metric .val.money {
+ color: #9a7424;
+ font-variant-numeric: tabular-nums;
+}
+
+.detail-body {
+ padding: 32rpx 24rpx 0;
+}
+
+.detail-section {
+ @include surface-card;
+ border-radius: 36rpx;
+ padding: 28rpx 28rpx 24rpx;
+ margin-bottom: 24rpx;
+}
+
+.detail-section .sec-hd {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 24rpx;
+}
+
+.detail-section .sec-hd .sec-title {
+ font-size: 28rpx;
+ font-weight: 700;
+ color: #173128;
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+}
+
+.detail-section .sec-hd .sec-title::before {
+ content: '';
+ width: 8rpx;
+ height: 28rpx;
+ border-radius: 999rpx;
+ background: linear-gradient(180deg, #12b37a, #087f57);
+}
+
+.detail-section .sec-hd .sec-more {
+ font-size: 24rpx;
+ color: var(--brand-green, #0a9f6d);
+}
+
+.kv-list {
+ display: flex;
+ flex-direction: column;
+ gap: 20rpx;
+}
+
+.kv-row {
+ display: flex;
+ align-items: flex-start;
+ gap: 24rpx;
+ font-size: 26rpx;
+ line-height: 1.45;
+}
+
+.kv-row .k {
+ width: 144rpx;
+ flex-shrink: 0;
+ color: #8aa198;
+}
+
+.kv-row .v {
+ flex: 1;
+ color: #173128;
+ font-weight: 550;
+ word-break: break-all;
+}
+
+.detail-note {
+ margin-top: 24rpx;
+ padding: 20rpx 24rpx;
+ border-radius: 24rpx;
+ background: linear-gradient(135deg, #fff8ec, #fffdf8);
+ border: 1rpx solid #f0e0b8;
+ color: #8a6520;
+ font-size: 24rpx;
+ line-height: 1.5;
+}
+
+.pay-row {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 20rpx;
+ padding: 20rpx 24rpx;
+ border-radius: 24rpx;
+ background: #f7faf9;
+ margin-bottom: 16rpx;
+}
+
+.pay-row .name {
+ font-size: 26rpx;
+ font-weight: 650;
+ color: #173128;
+}
+
+.pay-row .sub {
+ margin-top: 4rpx;
+ font-size: 22rpx;
+ color: #8aa198;
+}
+
+.pay-row .amt {
+ font-size: 28rpx;
+ font-weight: 750;
+ color: #9a7424;
+ font-variant-numeric: tabular-nums;
+ text-align: right;
+}
+
+.detail-footer {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ gap: 20rpx;
+ padding: 24rpx 28rpx calc(24rpx + env(safe-area-inset-bottom));
+ background: rgba(255, 255, 255, 0.96);
+ border-top: 1rpx solid var(--border, #dcebe4);
+ z-index: 20;
+}
+
+.detail-footer .btn-flex {
+ flex: 1;
+}
diff --git a/mobile-h5/styles/components/flow.scss b/mobile-h5/styles/components/flow.scss
index 0e801396..20652837 100644
--- a/mobile-h5/styles/components/flow.scss
+++ b/mobile-h5/styles/components/flow.scss
@@ -1 +1,142 @@
-// Task 4: flow timeline styles (.flow-list, .flow-item, .flow-dot, .flow-card)
+.flow-list {
+ display: flex;
+ flex-direction: column;
+}
+
+.flow-item {
+ position: relative;
+ display: flex;
+ gap: 24rpx;
+ padding-bottom: 28rpx;
+}
+
+.flow-item:last-child {
+ padding-bottom: 0;
+}
+
+.flow-item:not(:last-child) .flow-rail::after {
+ content: '';
+ position: absolute;
+ left: 50%;
+ top: 72rpx;
+ bottom: -28rpx;
+ width: 4rpx;
+ transform: translateX(-50%);
+ background: linear-gradient(180deg, rgba(10, 159, 109, 0.45), rgba(220, 235, 228, 0.9));
+ border-radius: 4rpx;
+}
+
+.flow-rail {
+ position: relative;
+ width: 72rpx;
+ flex-shrink: 0;
+ display: flex;
+ justify-content: center;
+}
+
+.flow-dot {
+ width: 72rpx;
+ height: 72rpx;
+ border-radius: 24rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 32rpx;
+ color: #fff;
+ box-shadow: 0 12rpx 28rpx rgba(8, 127, 87, 0.2);
+ position: relative;
+ z-index: 1;
+}
+
+.flow-item.done .flow-dot {
+ background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
+}
+.flow-item.warn .flow-dot {
+ background: linear-gradient(145deg, #ecc06a, #d6a343);
+ box-shadow: 0 12rpx 28rpx rgba(214, 163, 67, 0.28);
+}
+.flow-item.danger .flow-dot {
+ background: linear-gradient(145deg, #ef7a7a, #d14343);
+ box-shadow: 0 12rpx 28rpx rgba(209, 67, 67, 0.25);
+}
+
+.flow-card {
+ flex: 1;
+ min-width: 0;
+ border-radius: 28rpx;
+ padding: 22rpx 24rpx;
+ background: linear-gradient(160deg, #f7fbf9 0%, #ffffff 55%);
+ border: 1rpx solid #e4eee9;
+ box-shadow: 0 8rpx 24rpx rgba(20, 55, 40, 0.04);
+}
+
+.flow-item.warn .flow-card {
+ background: linear-gradient(160deg, #fff9ef 0%, #ffffff 60%);
+ border-color: #f0e0b8;
+}
+.flow-item.danger .flow-card {
+ background: linear-gradient(160deg, #fff4f4 0%, #ffffff 60%);
+ border-color: #f3d0d0;
+}
+
+.flow-card .flow-top {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 16rpx;
+ margin-bottom: 8rpx;
+}
+
+.flow-card .flow-title {
+ font-size: 26rpx;
+ font-weight: 700;
+ color: #173128;
+ line-height: 1.35;
+}
+
+.flow-card .flow-badge {
+ flex-shrink: 0;
+ font-size: 20rpx;
+ font-weight: 700;
+ padding: 4rpx 14rpx;
+ border-radius: 999rpx;
+}
+
+.flow-item.done .flow-badge {
+ background: rgba(10, 159, 109, 0.12);
+ color: #087f57;
+}
+.flow-item.warn .flow-badge {
+ background: rgba(214, 163, 67, 0.16);
+ color: #8f6718;
+}
+.flow-item.danger .flow-badge {
+ background: rgba(209, 67, 67, 0.12);
+ color: #b13535;
+}
+
+.flow-card .flow-desc {
+ font-size: 24rpx;
+ color: #5d746a;
+ line-height: 1.5;
+}
+
+.flow-card .flow-meta {
+ margin-top: 16rpx;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16rpx;
+ align-items: center;
+}
+
+.flow-card .flow-meta .chip {
+ display: inline-flex;
+ align-items: center;
+ gap: 8rpx;
+ font-size: 22rpx;
+ color: #6b7b74;
+ background: rgba(255, 255, 255, 0.9);
+ border: 1rpx solid #e8f0ec;
+ border-radius: 999rpx;
+ padding: 4rpx 16rpx;
+}
diff --git a/mobile-h5/styles/components/form-shell.scss b/mobile-h5/styles/components/form-shell.scss
index 52ec05e1..d1be8665 100644
--- a/mobile-h5/styles/components/form-shell.scss
+++ b/mobile-h5/styles/components/form-shell.scss
@@ -1 +1,116 @@
-// Task 4: form shell styles (.form-page, .form-section-title, .form-footer)
+@import '../mixins.scss';
+
+.form-page {
+ min-height: 100vh;
+ background: var(--bg-page, #f5f8f7);
+ padding-bottom: 176rpx;
+}
+
+.form-nav {
+ @include hero-green;
+ padding: 88rpx 16rpx 20rpx;
+}
+
+.form-section-title {
+ margin: 28rpx 32rpx 16rpx;
+ font-size: 26rpx;
+ font-weight: 600;
+ color: var(--text-muted, #6b7b74);
+}
+
+.form-tip {
+ margin: 16rpx 32rpx 0;
+ font-size: 22rpx;
+ color: var(--text-muted, #6b7b74);
+ line-height: 1.45;
+}
+
+.form-footer {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ display: flex;
+ gap: 20rpx;
+ padding: 24rpx 28rpx calc(24rpx + env(safe-area-inset-bottom));
+ background: rgba(255, 255, 255, 0.96);
+ border-top: 1rpx solid var(--border, #dcebe4);
+ z-index: 20;
+}
+
+.form-footer .btn-flex {
+ flex: 1;
+}
+
+.home-header {
+ @include hero-green;
+ padding: 108rpx 32rpx 56rpx;
+}
+
+.home-header .brand {
+ font-size: 24rpx;
+ opacity: 0.88;
+ letter-spacing: 0.04em;
+ margin-bottom: 16rpx;
+}
+
+.home-header .hello {
+ font-size: 44rpx;
+ font-weight: 700;
+}
+
+.home-header .meta {
+ margin-top: 20rpx;
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+ font-size: 24rpx;
+ opacity: 0.92;
+}
+
+.home-header .meta .dot {
+ width: 8rpx;
+ height: 8rpx;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.7);
+}
+
+.home-body {
+ padding: 28rpx 24rpx 48rpx;
+}
+
+.panel {
+ @include surface-card;
+ padding: 28rpx 24rpx;
+ margin-bottom: 24rpx;
+}
+
+.panel-hd {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 24rpx;
+ padding: 0 8rpx;
+}
+
+.panel-hd .panel-title {
+ font-size: 30rpx;
+ font-weight: 650;
+ color: var(--text-primary, #1f2a26);
+}
+
+.panel-hd .hint {
+ font-size: 22rpx;
+ color: var(--text-muted, #6b7b74);
+}
+
+.perm-banner {
+ padding: 20rpx 24rpx;
+ border-radius: 24rpx;
+ background: var(--barley-yellow-soft, #fff7e8);
+ border: 1rpx solid #f0e0b8;
+ font-size: 24rpx;
+ color: #8a6520;
+ margin-bottom: 24rpx;
+ line-height: 1.45;
+}
diff --git a/mobile-h5/styles/components/list.scss b/mobile-h5/styles/components/list.scss
index d1799715..27af28f0 100644
--- a/mobile-h5/styles/components/list.scss
+++ b/mobile-h5/styles/components/list.scss
@@ -1 +1,374 @@
-// Task 4: list page styles (.list-hero, .list-filter-dock, .seg-tabs, .biz-card, status variants)
+@import '../mixins.scss';
+
+.list-hero {
+ @include hero-green;
+ padding: 88rpx 28rpx 44rpx;
+}
+
+.list-hero::after {
+ content: '';
+ position: absolute;
+ width: 360rpx;
+ height: 360rpx;
+ right: -100rpx;
+ bottom: -140rpx;
+ border-radius: 50%;
+ background: rgba(255, 255, 255, 0.08);
+ pointer-events: none;
+}
+
+.list-hero-copy {
+ position: relative;
+ z-index: 1;
+ margin: 8rpx 4rpx 28rpx;
+ color: #fff;
+}
+
+.list-hero-copy .eyebrow {
+ font-size: 22rpx;
+ letter-spacing: 0.08em;
+ opacity: 0.82;
+ margin-bottom: 8rpx;
+}
+
+.list-hero-copy .title {
+ font-size: 44rpx;
+ font-weight: 700;
+}
+
+.list-hero-copy .subline {
+ margin-top: 12rpx;
+ font-size: 24rpx;
+ opacity: 0.88;
+ display: flex;
+ align-items: center;
+ gap: 16rpx;
+}
+
+.list-hero-copy .subline .em {
+ font-style: normal;
+ font-weight: 700;
+ background: rgba(255, 255, 255, 0.18);
+ border-radius: 999rpx;
+ padding: 2rpx 16rpx;
+}
+
+.list-search {
+ position: relative;
+ z-index: 1;
+}
+
+.list-filter-dock {
+ margin: -28rpx 24rpx 0;
+ position: relative;
+ z-index: 3;
+ background: rgba(255, 255, 255, 0.92);
+ border: 1rpx solid rgba(220, 235, 228, 0.95);
+ border-radius: 36rpx;
+ padding: 20rpx;
+ box-shadow: 0 20rpx 56rpx rgba(15, 55, 40, 0.08);
+}
+
+.seg-tabs {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 8rpx;
+ padding: 8rpx;
+ border-radius: 28rpx;
+ background: #eef6f2;
+}
+
+.seg-tab {
+ border-radius: 22rpx;
+ padding: 16rpx 8rpx;
+ color: #6b7b74;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 4rpx;
+ font-size: 24rpx;
+}
+
+.seg-tab .count {
+ font-size: 22rpx;
+ font-weight: 700;
+ color: #9aada4;
+ font-variant-numeric: tabular-nums;
+}
+
+.seg-tab.active {
+ background: #fff;
+ color: var(--brand-green-dark, #087f57);
+ font-weight: 650;
+ box-shadow: 0 8rpx 24rpx rgba(8, 127, 87, 0.14);
+}
+
+.seg-tab.active .count {
+ color: var(--brand-green, #0a9f6d);
+}
+
+.list-wrap {
+ padding: 32rpx 24rpx 0;
+}
+
+.biz-card {
+ position: relative;
+ border-radius: 40rpx;
+ margin-bottom: 28rpx;
+ overflow: hidden;
+ box-shadow: 0 20rpx 56rpx rgba(20, 55, 40, 0.08);
+ isolation: isolate;
+}
+
+.biz-card.status-warning {
+ background: linear-gradient(145deg, #fff9ef 0%, #ffffff 46%, #fffaf3 100%);
+}
+.biz-card.status-primary {
+ background: linear-gradient(145deg, #eaf8f1 0%, #ffffff 48%, #f3fbf7 100%);
+}
+.biz-card.status-danger {
+ background: linear-gradient(145deg, #fff1f1 0%, #ffffff 48%, #fff7f7 100%);
+}
+.biz-card.status-success {
+ background: linear-gradient(145deg, #eef8f3 0%, #ffffff 50%, #f6fbf8 100%);
+}
+
+.biz-card .card-inner {
+ position: relative;
+ z-index: 1;
+ padding: 28rpx 28rpx 24rpx;
+}
+
+.biz-card .row-top {
+ display: flex;
+ align-items: flex-start;
+ gap: 20rpx;
+ margin-bottom: 24rpx;
+}
+
+.biz-card .type-icon {
+ width: 88rpx;
+ height: 88rpx;
+ border-radius: 28rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ font-size: 44rpx;
+ color: #fff;
+}
+
+.biz-card.status-warning .type-icon {
+ background: linear-gradient(145deg, #ffe8b8, #f6d089);
+ color: #7a5610;
+}
+.biz-card.status-primary .type-icon {
+ background: linear-gradient(145deg, #8fe0bf, #0a9f6d);
+}
+.biz-card.status-danger .type-icon {
+ background: linear-gradient(145deg, #ffb4b4, #e85a5a);
+}
+.biz-card.status-success .type-icon {
+ background: linear-gradient(145deg, #b8efd4, #189b65);
+}
+
+.biz-card .title-wrap {
+ flex: 1;
+ min-width: 0;
+}
+
+.biz-card .type-chip {
+ display: inline-flex;
+ font-size: 20rpx;
+ font-weight: 650;
+ letter-spacing: 0.04em;
+ padding: 4rpx 14rpx;
+ border-radius: 999rpx;
+ margin-bottom: 10rpx;
+}
+
+.biz-card.status-warning .type-chip {
+ background: rgba(214, 163, 67, 0.16);
+ color: #8f6718;
+}
+.biz-card.status-primary .type-chip {
+ background: rgba(10, 159, 109, 0.12);
+ color: #087f57;
+}
+.biz-card.status-danger .type-chip {
+ background: rgba(209, 67, 67, 0.12);
+ color: #b13535;
+}
+.biz-card.status-success .type-chip {
+ background: rgba(24, 155, 101, 0.12);
+ color: #147a50;
+}
+
+.biz-card .title {
+ font-size: 30rpx;
+ font-weight: 700;
+ line-height: 1.35;
+ color: #173128;
+}
+
+.biz-card .status-pill {
+ flex-shrink: 0;
+ font-size: 22rpx;
+ font-weight: 700;
+ padding: 8rpx 18rpx;
+ border-radius: 999rpx;
+}
+
+.biz-card.status-warning .status-pill {
+ background: #fff;
+ color: #a67b20;
+ box-shadow: 0 4rpx 16rpx rgba(214, 163, 67, 0.22);
+}
+.biz-card.status-primary .status-pill {
+ background: #0a9f6d;
+ color: #fff;
+}
+.biz-card.status-danger .status-pill {
+ background: #d14343;
+ color: #fff;
+}
+.biz-card.status-success .status-pill {
+ background: rgba(24, 155, 101, 0.12);
+ color: #147a50;
+}
+
+.biz-card .body-row {
+ display: flex;
+ gap: 24rpx;
+ align-items: stretch;
+}
+
+.biz-card .desc-box {
+ flex: 1;
+ margin: 0;
+ padding: 20rpx 22rpx;
+ border-radius: 24rpx;
+ background: rgba(255, 255, 255, 0.72);
+ border: 1rpx solid rgba(255, 255, 255, 0.9);
+ font-size: 24rpx;
+ color: #3f564c;
+ line-height: 1.5;
+}
+
+.biz-card .side-metric {
+ width: 172rpx;
+ flex-shrink: 0;
+ border-radius: 24rpx;
+ padding: 16rpx;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: flex-end;
+ text-align: right;
+ background: rgba(255, 255, 255, 0.65);
+ border: 1rpx solid rgba(255, 255, 255, 0.95);
+}
+
+.biz-card .side-metric .amt-label {
+ font-size: 20rpx;
+ color: #8aa198;
+}
+
+.biz-card .side-metric .amt {
+ margin-top: 4rpx;
+ font-size: 30rpx;
+ font-weight: 800;
+ color: #173128;
+ font-variant-numeric: tabular-nums;
+}
+
+.biz-card.status-warning .side-metric .amt {
+ color: #9a7424;
+}
+.biz-card.status-primary .side-metric .amt {
+ color: #087f57;
+}
+.biz-card.status-danger .side-metric .amt {
+ color: #b13535;
+}
+
+.biz-card .side-metric .time {
+ margin-top: 12rpx;
+ font-size: 20rpx;
+ color: #8aa198;
+}
+
+.biz-card .meta-row {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 16rpx;
+ margin-top: 24rpx;
+}
+
+.biz-card .meta-row .mi {
+ display: inline-flex;
+ align-items: center;
+ gap: 8rpx;
+ font-size: 22rpx;
+ color: #5d746a;
+ background: rgba(255, 255, 255, 0.7);
+ border-radius: 999rpx;
+ padding: 6rpx 16rpx;
+}
+
+.biz-card .code-pill {
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
+ font-size: 20rpx;
+}
+
+.biz-card .footer {
+ margin-top: 24rpx;
+ padding-top: 20rpx;
+ border-top: 1rpx dashed rgba(140, 170, 155, 0.28);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 16rpx;
+}
+
+.biz-card .footer .left-hint {
+ font-size: 22rpx;
+ color: #7f968c;
+ display: inline-flex;
+ align-items: center;
+ gap: 8rpx;
+}
+
+.biz-card .footer .left-hint .dot {
+ width: 12rpx;
+ height: 12rpx;
+ border-radius: 50%;
+ background: currentColor;
+ opacity: 0.75;
+}
+
+.biz-card.status-warning .footer .left-hint {
+ color: #a67b20;
+}
+.biz-card.status-primary .footer .left-hint {
+ color: #0a9f6d;
+}
+.biz-card.status-danger .footer .left-hint {
+ color: #d14343;
+}
+.biz-card.status-success .footer .left-hint {
+ color: #189b65;
+}
+
+.biz-card .footer .ops {
+ display: flex;
+ gap: 16rpx;
+}
+
+.list-end {
+ padding: 16rpx 0 56rpx;
+ text-align: center;
+ font-size: 22rpx;
+ color: #8aa198;
+ letter-spacing: 0.08em;
+}
diff --git a/mobile-h5/styles/components/menu-grid.scss b/mobile-h5/styles/components/menu-grid.scss
index bae6d08c..d5f6a4f7 100644
--- a/mobile-h5/styles/components/menu-grid.scss
+++ b/mobile-h5/styles/components/menu-grid.scss
@@ -1 +1,54 @@
-// Task 4: menu-grid styles (.menu-grid, .menu-card, .menu-icon, .menu-title)
+@import '../mixins.scss';
+
+.menu-grid {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ gap: 20rpx 12rpx;
+}
+
+.menu-card {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: flex-start;
+ gap: 16rpx;
+ min-height: 168rpx;
+ padding: 20rpx 8rpx 12rpx;
+ border-radius: 24rpx;
+}
+
+.menu-card:active {
+ background: var(--brand-green-soft, #e9f9f2);
+}
+
+.menu-icon {
+ width: 96rpx;
+ height: 96rpx;
+ border-radius: 28rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background: var(--brand-green-soft, #e9f9f2);
+ color: var(--brand-green-dark, #087f57);
+ font-size: 48rpx;
+ box-shadow: inset 0 0 0 1rpx rgba(10, 159, 109, 0.08);
+}
+
+.menu-icon.tone-yellow {
+ background: var(--barley-yellow-soft, #fff7e8);
+ color: #a67b20;
+}
+
+.menu-icon.tone-slate {
+ background: #eef2f1;
+ color: #3d524a;
+}
+
+.menu-title {
+ font-size: 24rpx;
+ line-height: 1.25;
+ color: var(--text-primary, #1f2a26);
+ text-align: center;
+ max-width: 144rpx;
+ word-break: break-all;
+}
diff --git a/mobile-h5/styles/components/todo.scss b/mobile-h5/styles/components/todo.scss
index 9b30bb01..50d2ac24 100644
--- a/mobile-h5/styles/components/todo.scss
+++ b/mobile-h5/styles/components/todo.scss
@@ -1 +1,184 @@
-// Task 4: todo panel styles (.todo-panel, .todo-stats, .todo-stat, .todo-list, .todo-item)
+.todo-panel {
+ padding: 28rpx 24rpx 24rpx;
+ background: linear-gradient(165deg, rgba(233, 249, 242, 0.95) 0%, #fff 42%), #fff;
+}
+
+.todo-stats {
+ display: grid;
+ grid-template-columns: repeat(3, 1fr);
+ gap: 16rpx;
+ margin-bottom: 24rpx;
+}
+
+.todo-stat {
+ position: relative;
+ border-radius: 24rpx;
+ padding: 24rpx 20rpx 20rpx;
+ text-align: left;
+ background: #fff;
+ box-shadow: 0 4rpx 20rpx rgba(8, 127, 87, 0.06);
+ border: 1rpx solid rgba(220, 235, 228, 0.9);
+ overflow: hidden;
+}
+
+.todo-stat::before {
+ content: '';
+ position: absolute;
+ left: 0;
+ top: 0;
+ bottom: 0;
+ width: 6rpx;
+ border-radius: 6rpx 0 0 6rpx;
+}
+
+.todo-stat.tone-warn::before {
+ background: var(--warning, #cf8a19);
+}
+.todo-stat.tone-pay::before {
+ background: var(--barley-yellow, #d6a343);
+}
+.todo-stat.tone-danger::before {
+ background: var(--danger, #d14343);
+}
+
+.todo-stat.tone-warn {
+ background: linear-gradient(180deg, #fffaf0 0%, #fff 70%);
+}
+.todo-stat.tone-pay {
+ background: linear-gradient(180deg, #fff8ec 0%, #fff 70%);
+}
+.todo-stat.tone-danger {
+ background: linear-gradient(180deg, #fff5f5 0%, #fff 70%);
+}
+
+.todo-stat .num {
+ display: block;
+ font-size: 44rpx;
+ font-weight: 700;
+ line-height: 1.1;
+ color: var(--text-primary, #1f2a26);
+ font-variant-numeric: tabular-nums;
+}
+
+.todo-stat.tone-warn .num {
+ color: #b77412;
+}
+.todo-stat.tone-pay .num {
+ color: #9a7424;
+}
+.todo-stat.tone-danger .num {
+ color: var(--danger, #d14343);
+}
+
+.todo-stat .label {
+ display: block;
+ margin-top: 8rpx;
+ font-size: 22rpx;
+ color: var(--text-muted, #6b7b74);
+}
+
+.todo-stat .stat-icon {
+ position: absolute;
+ right: 16rpx;
+ top: 16rpx;
+ width: 52rpx;
+ height: 52rpx;
+ border-radius: 16rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 28rpx;
+}
+
+.todo-stat.tone-warn .stat-icon {
+ background: rgba(207, 138, 25, 0.12);
+ color: var(--warning, #cf8a19);
+}
+.todo-stat.tone-pay .stat-icon {
+ background: rgba(214, 163, 67, 0.16);
+ color: #a67b20;
+}
+.todo-stat.tone-danger .stat-icon {
+ background: rgba(209, 67, 67, 0.12);
+ color: var(--danger, #d14343);
+}
+
+.todo-list {
+ display: flex;
+ flex-direction: column;
+ gap: 16rpx;
+}
+
+.todo-item {
+ display: flex;
+ align-items: center;
+ gap: 20rpx;
+ width: 100%;
+ padding: 20rpx;
+ border: 1rpx solid var(--border, #dcebe4);
+ border-radius: 24rpx;
+ background: #fff;
+}
+
+.todo-item:active {
+ background: var(--brand-green-soft, #e9f9f2);
+ border-color: #b7e5d0;
+}
+
+.todo-item .ti-icon {
+ width: 72rpx;
+ height: 72rpx;
+ border-radius: 20rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ flex-shrink: 0;
+ font-size: 36rpx;
+}
+
+.todo-item .ti-icon.i-approve {
+ background: #fff3e0;
+ color: var(--warning, #cf8a19);
+}
+.todo-item .ti-icon.i-pay {
+ background: var(--barley-yellow-soft, #fff7e8);
+ color: #a67b20;
+}
+.todo-item .ti-icon.i-alert {
+ background: #fdecec;
+ color: var(--danger, #d14343);
+}
+
+.todo-item .ti-body {
+ flex: 1;
+ min-width: 0;
+}
+
+.todo-item .ti-title {
+ font-size: 26rpx;
+ font-weight: 600;
+ color: var(--text-primary, #1f2a26);
+ line-height: 1.3;
+}
+
+.todo-item .ti-desc {
+ margin-top: 4rpx;
+ font-size: 22rpx;
+ color: var(--text-muted, #6b7b74);
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.todo-item .ti-right {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-end;
+ gap: 8rpx;
+ flex-shrink: 0;
+}
+
+.todo-item .ti-time {
+ font-size: 20rpx;
+ color: #9aada4;
+}
diff --git a/mobile-h5/utils/biz-mock.js b/mobile-h5/utils/biz-mock.js
new file mode 100644
index 00000000..2a2a0180
--- /dev/null
+++ b/mobile-h5/utils/biz-mock.js
@@ -0,0 +1,262 @@
+const listData = [
+ {
+ id: 1,
+ title: '脐带血采集协议-王女士',
+ code: 'XY20260810001',
+ typeLabel: '采集协议',
+ icon: 'orders-o',
+ owner: '李四',
+ time: '08-10 09:20',
+ amount: '12,800',
+ progress: '',
+ desc: '分期缴费 · 待财务确认首期款项',
+ status: '待处理',
+ tagType: 'warning',
+ hint: '需尽快确认',
+ customer: '王女士',
+ dept: '样本运营部',
+ createdAt: '2026-08-08 14:20',
+ stepActive: 1,
+ steps: ['建档', '缴费确认', '样本采集', '入库'],
+ payments: [
+ {
+ id: 'p1',
+ name: '第 1 期(首期)',
+ sub: '应缴 08-10 · 微信转账',
+ amount: '6,400',
+ status: '待确认',
+ tagType: 'warning'
+ },
+ {
+ id: 'p2',
+ name: '第 2 期',
+ sub: '应缴 09-10 · 未到期',
+ amount: '6,400',
+ status: '待缴费',
+ tagType: 'primary'
+ }
+ ],
+ attachments: [
+ { name: '协议扫描件.pdf', size: '2.4 MB', time: '08-08 14:18' },
+ { name: '身份证明.jpg', size: '860 KB', time: '08-08 14:19' }
+ ],
+ logs: [
+ {
+ title: '提交审批',
+ desc: '提交采集协议,等待财务确认首期',
+ time: '08-08 14:22',
+ tone: 'done',
+ actor: '李四',
+ icon: 'edit',
+ badge: '已完成'
+ },
+ {
+ title: '流转至财务',
+ desc: '系统自动分配至财务确认岗',
+ time: '08-08 14:23',
+ tone: 'done',
+ actor: '系统',
+ icon: 'cluster-o',
+ badge: '已完成'
+ },
+ {
+ title: '待确认缴费',
+ desc: '首期 ¥6,400 等待财务核对附件',
+ time: '08-10 09:20',
+ tone: 'warn',
+ actor: '财务岗',
+ icon: 'balance-list-o',
+ badge: '进行中'
+ }
+ ]
+ },
+ {
+ id: 2,
+ title: '样本接收单-批次 A12',
+ code: 'YS20260809018',
+ typeLabel: '样本接收',
+ icon: 'logistics',
+ owner: '王五',
+ time: '08-09 16:40',
+ amount: '',
+ progress: '在途',
+ desc: '冷链在途 · 预计今日 18:00 前到达库房',
+ status: '进行中',
+ tagType: 'primary',
+ hint: '跟踪物流',
+ customer: '批次 A12',
+ dept: '仓储物流部',
+ createdAt: '2026-08-09 09:10',
+ stepActive: 2,
+ steps: ['发运', '在途', '到库签收', '入库'],
+ payments: [],
+ attachments: [{ name: '冷链温度记录.pdf', size: '1.1 MB', time: '08-09 11:28' }],
+ logs: [
+ {
+ title: '创建接收单',
+ desc: '创建样本接收单并绑定批次 A12',
+ time: '08-09 09:10',
+ tone: 'done',
+ actor: '王五',
+ icon: 'orders-o',
+ badge: '已完成'
+ },
+ {
+ title: '冷链发出',
+ desc: '承运方已取件,全程温控开启',
+ time: '08-09 11:30',
+ tone: 'done',
+ actor: '承运方',
+ icon: 'logistics',
+ badge: '已完成'
+ },
+ {
+ title: '运输中',
+ desc: '当前位置:城东中转站',
+ time: '08-09 16:40',
+ tone: 'warn',
+ actor: '物流跟踪',
+ icon: 'location-o',
+ badge: '进行中'
+ }
+ ]
+ },
+ {
+ id: 3,
+ title: '质检工单-QC-8821',
+ code: 'ZJ20260808007',
+ typeLabel: '质检工单',
+ icon: 'certificate',
+ owner: '赵六',
+ time: '08-08 11:05',
+ amount: '',
+ progress: '阻塞',
+ desc: '检测结论待录入,阻塞下游入库流程',
+ status: '异常',
+ tagType: 'danger',
+ hint: '优先处理',
+ customer: '样本 S-8821',
+ dept: '质检中心',
+ createdAt: '2026-08-07 16:40',
+ stepActive: 1,
+ steps: ['取样', '检测', '结论录入', '关闭'],
+ payments: [],
+ attachments: [{ name: '初检原始数据.xlsx', size: '428 KB', time: '08-08 10:48' }],
+ logs: [
+ {
+ title: '工单创建',
+ desc: '系统根据接收单自动生成质检工单',
+ time: '08-07 16:40',
+ tone: 'done',
+ actor: '系统',
+ icon: 'add-o',
+ badge: '已完成'
+ },
+ {
+ title: '检测完成',
+ desc: '完成实验操作,待录入结论',
+ time: '08-08 10:50',
+ tone: 'done',
+ actor: '赵六',
+ icon: 'certificate',
+ badge: '已完成'
+ },
+ {
+ title: '异常挂起',
+ desc: '结论超时未录入,已阻塞入库',
+ time: '08-08 11:05',
+ tone: 'danger',
+ actor: '质控规则',
+ icon: 'warning-o',
+ badge: '异常'
+ }
+ ]
+ },
+ {
+ id: 4,
+ title: '出库申请-库位 B-03',
+ code: 'CK20260807002',
+ typeLabel: '库位出库',
+ icon: 'send-gift-o',
+ owner: '张三',
+ time: '08-07 14:22',
+ amount: '',
+ progress: '完结',
+ desc: '复核已完成,可归档关闭',
+ status: '已完成',
+ tagType: 'success',
+ hint: '已完结',
+ customer: '库位 B-03',
+ dept: '仓储物流部',
+ createdAt: '2026-08-06 10:00',
+ stepActive: 3,
+ steps: ['申请', '复核', '出库', '归档'],
+ payments: [],
+ attachments: [{ name: '出库单签收.pdf', size: '980 KB', time: '08-07 14:18' }],
+ logs: [
+ {
+ title: '提交申请',
+ desc: '发起出库申请,指定库位 B-03',
+ time: '08-06 10:00',
+ tone: 'done',
+ actor: '张三',
+ icon: 'edit',
+ badge: '已完成'
+ },
+ {
+ title: '复核通过',
+ desc: '库管复核通过,允许出库',
+ time: '08-07 11:10',
+ tone: 'done',
+ actor: '库管',
+ icon: 'passed',
+ badge: '已完成'
+ },
+ {
+ title: '出库完成',
+ desc: '实物已交接,单据归档',
+ time: '08-07 14:22',
+ tone: 'done',
+ actor: '张三',
+ icon: 'checked',
+ badge: '已完成'
+ }
+ ]
+ }
+]
+
+export function getList() {
+ return listData.slice()
+}
+
+export function getById(id) {
+ const n = Number(id)
+ return listData.find((r) => r.id === n) || listData[0]
+}
+
+function rowsByTab(idx) {
+ if (idx === 1) return listData.filter((r) => r.status === '进行中')
+ if (idx === 2) return listData.filter((r) => r.status === '待处理')
+ if (idx === 3) return listData.filter((r) => r.status === '已完成')
+ return listData
+}
+
+export function tabCount(idx) {
+ return rowsByTab(idx).length
+}
+
+export function filterByTab(tabIndex, keyword) {
+ const kw = (keyword || '').trim()
+ let rows = rowsByTab(tabIndex)
+ if (!kw) return rows
+ return rows.filter(
+ (r) => r.title.includes(kw) || r.code.includes(kw) || r.owner.includes(kw)
+ )
+}
+
+export const LIST_TABS = [
+ { key: 'all', label: '全部' },
+ { key: 'doing', label: '进行中' },
+ { key: 'pending', label: '待处理' },
+ { key: 'done', label: '已完成' }
+]
diff --git a/mobile-h5/utils/menu-mock.js b/mobile-h5/utils/menu-mock.js
new file mode 100644
index 00000000..c26a8662
--- /dev/null
+++ b/mobile-h5/utils/menu-mock.js
@@ -0,0 +1,19 @@
+/** 模拟「菜单管理 → 移动端菜单」授权结果 */
+export const ALL_MOBILE_MENUS = [
+ { id: 'm1', title: '待办审批', icon: 'todo-list-o', path: '/pages/demo/list' },
+ { id: 'm2', title: '采集协议', icon: 'orders-o', path: '/pages/demo/list' },
+ { id: 'm3', title: '缴费确认', icon: 'balance-list-o', path: '/pages/demo/list' },
+ { id: 'm4', title: '样本接收', icon: 'logistics', path: '/pages/demo/list' },
+ { id: 'm5', title: '质检工单', icon: 'certificate', path: '/pages/demo/list' },
+ { id: 'm6', title: '生产任务', icon: 'cluster-o', path: '/pages/demo/list' },
+ { id: 'm7', title: '库位出库', icon: 'send-gift-o', path: '/pages/demo/list' },
+ { id: 'm8', title: '组件示例', icon: 'apps-o', path: '/pages/demo/comps' }
+]
+
+/** @param {string[]} grantedIds */
+export function filterMenusByPermission(grantedIds) {
+ const set = new Set(grantedIds || [])
+ return ALL_MOBILE_MENUS.filter((m) => set.has(m.id))
+}
+
+export const DEFAULT_GRANTED_IDS = ['m1', 'm2', 'm3', 'm4', 'm5', 'm7', 'm8']