Link the workspace UI demo and implementation plan from the miniprogram UI spec for the unified mobile workbench. Co-authored-by: Cursor <cursoragent@cursor.com>
3677 lines
107 KiB
HTML
3677 lines
107 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
|
||
<title>中科兴皖 · 移动端 UI Demo(Vant)</title>
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vant@4.9.21/lib/index.css" />
|
||
<style>
|
||
:root {
|
||
--brand-green: #0a9f6d;
|
||
--brand-green-dark: #087f57;
|
||
--brand-green-soft: #e9f9f2;
|
||
--barley-yellow: #d6a343;
|
||
--barley-yellow-soft: #fff7e8;
|
||
--bg-page: #f5f8f7;
|
||
--surface: #ffffff;
|
||
--text-primary: #1f2a26;
|
||
--text-muted: #6b7b74;
|
||
--border: #dcebe4;
|
||
--danger: #d14343;
|
||
--warning: #cf8a19;
|
||
--success: #189b65;
|
||
--radius-card: 14px;
|
||
--radius-lg: 20px;
|
||
--shadow-soft: 0 4px 16px rgba(8, 127, 87, 0.08);
|
||
|
||
--van-primary-color: #0a9f6d;
|
||
--van-success-color: #189b65;
|
||
--van-warning-color: #cf8a19;
|
||
--van-danger-color: #d14343;
|
||
--van-border-color: #dcebe4;
|
||
--van-background: #f5f8f7;
|
||
--van-background-2: #ffffff;
|
||
--van-text-color: #1f2a26;
|
||
--van-text-color-2: #6b7b74;
|
||
--van-button-primary-background: #0a9f6d;
|
||
--van-button-primary-border-color: #0a9f6d;
|
||
--van-button-radius: 10px;
|
||
--van-cell-group-inset-padding: 12px;
|
||
--van-cell-group-inset-radius: 14px;
|
||
--van-dialog-confirm-button-text-color: #0a9f6d;
|
||
--van-nav-bar-icon-color: #ffffff;
|
||
--van-nav-bar-text-color: #ffffff;
|
||
--van-nav-bar-title-text-color: #ffffff;
|
||
--van-nav-bar-background: transparent;
|
||
--van-tabbar-item-active-color: #0a9f6d;
|
||
--van-tag-radius: 6px;
|
||
}
|
||
|
||
* { box-sizing: border-box; }
|
||
html, body {
|
||
margin: 0;
|
||
padding: 0;
|
||
min-height: 100%;
|
||
font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", sans-serif;
|
||
color: var(--text-primary);
|
||
background:
|
||
radial-gradient(900px 420px at 10% -10%, rgba(10, 159, 109, 0.14), transparent 55%),
|
||
radial-gradient(700px 380px at 90% 0%, rgba(214, 163, 67, 0.12), transparent 50%),
|
||
linear-gradient(180deg, #eef6f2 0%, #f7faf9 40%, #f3f6f5 100%);
|
||
}
|
||
|
||
body {
|
||
display: flex;
|
||
justify-content: center;
|
||
padding: 24px 16px 40px;
|
||
}
|
||
|
||
#app {
|
||
width: 100%;
|
||
max-width: 1120px;
|
||
display: grid;
|
||
grid-template-columns: 280px 1fr;
|
||
gap: 24px;
|
||
align-items: start;
|
||
}
|
||
|
||
.doc-panel {
|
||
position: sticky;
|
||
top: 24px;
|
||
background: rgba(255, 255, 255, 0.92);
|
||
border: 1px solid var(--border);
|
||
border-radius: 18px;
|
||
padding: 20px 18px;
|
||
box-shadow: var(--shadow-soft);
|
||
backdrop-filter: blur(8px);
|
||
}
|
||
|
||
.doc-panel h1 {
|
||
margin: 0 0 6px;
|
||
font-size: 20px;
|
||
line-height: 1.3;
|
||
color: var(--brand-green-dark);
|
||
}
|
||
|
||
.doc-panel .sub {
|
||
margin: 0 0 16px;
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.doc-panel .section-title {
|
||
margin: 16px 0 8px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
color: var(--text-muted);
|
||
letter-spacing: 0.04em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.screen-nav {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.screen-nav button {
|
||
appearance: none;
|
||
border: 1px solid var(--border);
|
||
background: #fff;
|
||
border-radius: 12px;
|
||
padding: 12px 14px;
|
||
text-align: left;
|
||
cursor: pointer;
|
||
font-size: 14px;
|
||
color: var(--text-primary);
|
||
transition: border-color .2s, background .2s, color .2s;
|
||
}
|
||
|
||
.screen-nav button:hover {
|
||
border-color: #a7e0c8;
|
||
background: var(--brand-green-soft);
|
||
}
|
||
|
||
.screen-nav button.active {
|
||
border-color: var(--brand-green);
|
||
background: var(--brand-green-soft);
|
||
color: var(--brand-green-dark);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.token-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.token-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
background: #fff;
|
||
border: 1px solid var(--border);
|
||
border-radius: 999px;
|
||
padding: 4px 8px 4px 4px;
|
||
}
|
||
|
||
.token-chip i {
|
||
width: 14px;
|
||
height: 14px;
|
||
border-radius: 50%;
|
||
display: block;
|
||
}
|
||
|
||
.notes {
|
||
margin: 12px 0 0;
|
||
padding: 0;
|
||
list-style: none;
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
line-height: 1.55;
|
||
}
|
||
|
||
.notes li {
|
||
position: relative;
|
||
padding-left: 12px;
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.notes li::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0;
|
||
top: 7px;
|
||
width: 5px;
|
||
height: 5px;
|
||
border-radius: 50%;
|
||
background: var(--brand-green);
|
||
}
|
||
|
||
.phone-stage {
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
.phone {
|
||
width: 390px;
|
||
height: 844px;
|
||
background: #111;
|
||
border-radius: 42px;
|
||
padding: 12px;
|
||
box-shadow:
|
||
0 24px 60px rgba(15, 40, 30, 0.18),
|
||
inset 0 0 0 1px rgba(255, 255, 255, 0.08);
|
||
position: relative;
|
||
}
|
||
|
||
.phone::before {
|
||
content: "";
|
||
position: absolute;
|
||
top: 18px;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 118px;
|
||
height: 28px;
|
||
background: #0a0a0a;
|
||
border-radius: 20px;
|
||
z-index: 5;
|
||
}
|
||
|
||
.phone-screen {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 32px;
|
||
overflow: hidden;
|
||
background: var(--bg-page);
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.screen-scroll {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
padding-bottom: 16px;
|
||
}
|
||
|
||
/* ========== 首页 ========== */
|
||
.home-header {
|
||
background: linear-gradient(145deg, var(--brand-green-dark), var(--brand-green));
|
||
color: #fff;
|
||
padding: 54px 16px 28px;
|
||
border-radius: 0 0 var(--radius-lg) var(--radius-lg);
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.home-header::after {
|
||
content: "";
|
||
position: absolute;
|
||
right: -40px;
|
||
top: -30px;
|
||
width: 160px;
|
||
height: 160px;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.08);
|
||
}
|
||
|
||
.home-header .brand {
|
||
font-size: 12px;
|
||
opacity: 0.88;
|
||
letter-spacing: 0.04em;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.home-header h2 {
|
||
margin: 0;
|
||
font-size: 22px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
.home-header .meta {
|
||
margin-top: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
font-size: 12px;
|
||
opacity: 0.92;
|
||
}
|
||
|
||
.home-header .meta .dot {
|
||
width: 4px;
|
||
height: 4px;
|
||
border-radius: 50%;
|
||
background: rgba(255,255,255,.7);
|
||
}
|
||
|
||
.home-body {
|
||
padding: 14px 12px 0;
|
||
}
|
||
|
||
.panel {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-card);
|
||
box-shadow: var(--shadow-soft);
|
||
padding: 14px 12px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.panel-hd {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 12px;
|
||
padding: 0 4px;
|
||
}
|
||
|
||
.panel-hd h3 {
|
||
margin: 0;
|
||
font-size: 15px;
|
||
font-weight: 650;
|
||
}
|
||
|
||
.panel-hd .hint {
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.menu-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 10px 6px;
|
||
}
|
||
|
||
.menu-card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
gap: 8px;
|
||
min-height: 84px;
|
||
padding: 10px 4px 6px;
|
||
border: none;
|
||
background: transparent;
|
||
cursor: pointer;
|
||
border-radius: 12px;
|
||
transition: background .18s, transform .18s;
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
|
||
.menu-card:active {
|
||
background: var(--brand-green-soft);
|
||
transform: scale(0.97);
|
||
}
|
||
|
||
.menu-icon {
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 14px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--brand-green-soft);
|
||
color: var(--brand-green-dark);
|
||
font-size: 24px;
|
||
box-shadow: inset 0 0 0 1px rgba(10, 159, 109, 0.08);
|
||
}
|
||
|
||
.menu-icon.tone-yellow {
|
||
background: var(--barley-yellow-soft);
|
||
color: #a67b20;
|
||
}
|
||
|
||
.menu-icon.tone-slate {
|
||
background: #eef2f1;
|
||
color: #3d524a;
|
||
}
|
||
|
||
.menu-title {
|
||
font-size: 12px;
|
||
line-height: 1.25;
|
||
color: var(--text-primary);
|
||
text-align: center;
|
||
max-width: 72px;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.perm-banner {
|
||
display: flex;
|
||
gap: 8px;
|
||
align-items: flex-start;
|
||
padding: 10px 12px;
|
||
border-radius: 12px;
|
||
background: var(--barley-yellow-soft);
|
||
border: 1px solid #f0e0b8;
|
||
font-size: 12px;
|
||
color: #8a6520;
|
||
margin-bottom: 12px;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
/* ========== 待办摘要 ========== */
|
||
.todo-panel {
|
||
padding: 14px 12px 12px;
|
||
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: 8px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.todo-stat {
|
||
position: relative;
|
||
border: none;
|
||
border-radius: 12px;
|
||
padding: 12px 10px 10px;
|
||
text-align: left;
|
||
cursor: pointer;
|
||
background: #fff;
|
||
box-shadow: 0 2px 10px rgba(8, 127, 87, 0.06);
|
||
border: 1px solid rgba(220, 235, 228, 0.9);
|
||
transition: transform .18s, box-shadow .18s;
|
||
-webkit-tap-highlight-color: transparent;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.todo-stat:active {
|
||
transform: scale(0.97);
|
||
}
|
||
|
||
.todo-stat::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 3px;
|
||
border-radius: 3px 0 0 3px;
|
||
}
|
||
|
||
.todo-stat.tone-warn::before { background: var(--warning); }
|
||
.todo-stat.tone-pay::before { background: var(--barley-yellow); }
|
||
.todo-stat.tone-danger::before { background: var(--danger); }
|
||
|
||
.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: 22px;
|
||
font-weight: 700;
|
||
line-height: 1.1;
|
||
letter-spacing: -0.02em;
|
||
color: var(--text-primary);
|
||
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); }
|
||
|
||
.todo-stat .label {
|
||
display: block;
|
||
margin-top: 4px;
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.todo-stat .stat-icon {
|
||
position: absolute;
|
||
right: 8px;
|
||
top: 8px;
|
||
width: 26px;
|
||
height: 26px;
|
||
border-radius: 8px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 14px;
|
||
opacity: 0.9;
|
||
}
|
||
|
||
.todo-stat.tone-warn .stat-icon {
|
||
background: rgba(207, 138, 25, 0.12);
|
||
color: var(--warning);
|
||
}
|
||
|
||
.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);
|
||
}
|
||
|
||
.todo-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.todo-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
width: 100%;
|
||
padding: 10px 10px;
|
||
border: 1px solid var(--border);
|
||
border-radius: 12px;
|
||
background: #fff;
|
||
cursor: pointer;
|
||
text-align: left;
|
||
transition: background .15s, border-color .15s;
|
||
}
|
||
|
||
.todo-item:active {
|
||
background: var(--brand-green-soft);
|
||
border-color: #b7e5d0;
|
||
}
|
||
|
||
.todo-item .ti-icon {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.todo-item .ti-icon.i-approve {
|
||
background: #fff3e0;
|
||
color: var(--warning);
|
||
}
|
||
|
||
.todo-item .ti-icon.i-pay {
|
||
background: var(--barley-yellow-soft);
|
||
color: #a67b20;
|
||
}
|
||
|
||
.todo-item .ti-icon.i-alert {
|
||
background: #fdecec;
|
||
color: var(--danger);
|
||
}
|
||
|
||
.todo-item .ti-body {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.todo-item .ti-title {
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--text-primary);
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.todo-item .ti-desc {
|
||
margin-top: 2px;
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.todo-item .ti-right {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
gap: 4px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.todo-item .ti-time {
|
||
font-size: 10px;
|
||
color: #9aada4;
|
||
}
|
||
|
||
/* ========== 列表 ========== */
|
||
.page-nav {
|
||
background: linear-gradient(145deg, var(--brand-green-dark), var(--brand-green));
|
||
padding: 44px 8px 10px;
|
||
}
|
||
|
||
.page-nav .van-nav-bar {
|
||
background: transparent;
|
||
}
|
||
|
||
.page-nav .van-hairline--bottom::after {
|
||
display: none;
|
||
}
|
||
|
||
.list-page {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
background:
|
||
radial-gradient(420px 220px at 100% 0%, rgba(214, 163, 67, 0.16), transparent 55%),
|
||
radial-gradient(380px 240px at 0% 30%, rgba(10, 159, 109, 0.10), transparent 50%),
|
||
linear-gradient(180deg, #e8f5ef 0%, #f3f7f5 38%, #f7f9f8 100%);
|
||
}
|
||
|
||
.list-page .screen-scroll {
|
||
background: transparent;
|
||
}
|
||
|
||
.list-hero {
|
||
background:
|
||
radial-gradient(220px 140px at 92% 18%, rgba(255, 255, 255, 0.18), transparent 60%),
|
||
linear-gradient(155deg, #066647 0%, #0a9f6d 48%, #12b37a 100%);
|
||
padding: 44px 14px 22px;
|
||
border-radius: 0 0 28px 28px;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.list-hero::after {
|
||
content: "";
|
||
position: absolute;
|
||
width: 180px;
|
||
height: 180px;
|
||
right: -50px;
|
||
bottom: -70px;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.08);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.list-hero .van-nav-bar {
|
||
background: transparent;
|
||
margin: 0 -6px;
|
||
}
|
||
|
||
.list-hero .van-hairline--bottom::after {
|
||
display: none;
|
||
}
|
||
|
||
.list-hero-copy {
|
||
position: relative;
|
||
z-index: 1;
|
||
margin: 4px 2px 14px;
|
||
color: #fff;
|
||
}
|
||
|
||
.list-hero-copy .eyebrow {
|
||
font-size: 11px;
|
||
letter-spacing: 0.08em;
|
||
opacity: 0.82;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.list-hero-copy h2 {
|
||
margin: 0;
|
||
font-size: 22px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.01em;
|
||
}
|
||
|
||
.list-hero-copy .subline {
|
||
margin-top: 6px;
|
||
font-size: 12px;
|
||
opacity: 0.88;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.list-hero-copy .subline em {
|
||
font-style: normal;
|
||
font-weight: 700;
|
||
background: rgba(255, 255, 255, 0.18);
|
||
border-radius: 999px;
|
||
padding: 1px 8px;
|
||
}
|
||
|
||
.list-search {
|
||
position: relative;
|
||
z-index: 1;
|
||
}
|
||
|
||
.list-search .van-search {
|
||
padding: 0;
|
||
}
|
||
|
||
.list-search .van-search__content {
|
||
background: rgba(255, 255, 255, 0.96);
|
||
border-radius: 14px;
|
||
box-shadow: 0 8px 24px rgba(4, 70, 48, 0.18);
|
||
}
|
||
|
||
.list-filter-dock {
|
||
margin: -14px 12px 0;
|
||
position: relative;
|
||
z-index: 3;
|
||
background: rgba(255, 255, 255, 0.92);
|
||
border: 1px solid rgba(220, 235, 228, 0.95);
|
||
border-radius: 18px;
|
||
padding: 10px;
|
||
box-shadow: 0 10px 28px rgba(15, 55, 40, 0.08);
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.seg-tabs {
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 4px;
|
||
padding: 4px;
|
||
border-radius: 14px;
|
||
background: #eef6f2;
|
||
}
|
||
|
||
.seg-tab {
|
||
border: none;
|
||
background: transparent;
|
||
border-radius: 11px;
|
||
padding: 8px 4px;
|
||
cursor: pointer;
|
||
color: #6b7b74;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 2px;
|
||
transition: all .2s;
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
|
||
.seg-tab .lab {
|
||
font-size: 12px;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.seg-tab .count {
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
font-variant-numeric: tabular-nums;
|
||
color: #9aada4;
|
||
}
|
||
|
||
.seg-tab.active {
|
||
background: #fff;
|
||
color: var(--brand-green-dark);
|
||
box-shadow: 0 4px 12px rgba(8, 127, 87, 0.14);
|
||
font-weight: 650;
|
||
}
|
||
|
||
.seg-tab.active .count {
|
||
color: var(--brand-green);
|
||
}
|
||
|
||
.list-wrap {
|
||
padding: 16px 12px 0;
|
||
}
|
||
|
||
.biz-card {
|
||
position: relative;
|
||
border: none;
|
||
border-radius: 20px;
|
||
padding: 0;
|
||
margin-bottom: 14px;
|
||
overflow: hidden;
|
||
box-shadow: 0 10px 28px rgba(20, 55, 40, 0.08);
|
||
transition: transform .16s, box-shadow .16s;
|
||
isolation: isolate;
|
||
}
|
||
|
||
.biz-card:active {
|
||
transform: translateY(1px) scale(0.995);
|
||
box-shadow: 0 6px 16px rgba(20, 55, 40, 0.07);
|
||
}
|
||
|
||
.biz-card::before {
|
||
content: "";
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 0;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.biz-card::after {
|
||
content: "";
|
||
position: absolute;
|
||
width: 140px;
|
||
height: 140px;
|
||
right: -36px;
|
||
top: -48px;
|
||
border-radius: 50%;
|
||
z-index: 0;
|
||
pointer-events: none;
|
||
opacity: 0.55;
|
||
}
|
||
|
||
.biz-card.status-warning {
|
||
background: linear-gradient(145deg, #fff9ef 0%, #ffffff 46%, #fffaf3 100%);
|
||
}
|
||
.biz-card.status-warning::before {
|
||
background: linear-gradient(90deg, rgba(214, 163, 67, 0.22), transparent 28%);
|
||
}
|
||
.biz-card.status-warning::after {
|
||
background: radial-gradient(circle, rgba(214, 163, 67, 0.28), transparent 68%);
|
||
}
|
||
|
||
.biz-card.status-primary {
|
||
background: linear-gradient(145deg, #eaf8f1 0%, #ffffff 48%, #f3fbf7 100%);
|
||
}
|
||
.biz-card.status-primary::before {
|
||
background: linear-gradient(90deg, rgba(10, 159, 109, 0.18), transparent 28%);
|
||
}
|
||
.biz-card.status-primary::after {
|
||
background: radial-gradient(circle, rgba(10, 159, 109, 0.22), transparent 68%);
|
||
}
|
||
|
||
.biz-card.status-danger {
|
||
background: linear-gradient(145deg, #fff1f1 0%, #ffffff 48%, #fff7f7 100%);
|
||
}
|
||
.biz-card.status-danger::before {
|
||
background: linear-gradient(90deg, rgba(209, 67, 67, 0.16), transparent 28%);
|
||
}
|
||
.biz-card.status-danger::after {
|
||
background: radial-gradient(circle, rgba(209, 67, 67, 0.18), transparent 68%);
|
||
}
|
||
|
||
.biz-card.status-success {
|
||
background: linear-gradient(145deg, #eef8f3 0%, #ffffff 50%, #f6fbf8 100%);
|
||
}
|
||
.biz-card.status-success::before {
|
||
background: linear-gradient(90deg, rgba(24, 155, 101, 0.16), transparent 28%);
|
||
}
|
||
.biz-card.status-success::after {
|
||
background: radial-gradient(circle, rgba(24, 155, 101, 0.18), transparent 68%);
|
||
}
|
||
|
||
.biz-card .card-inner {
|
||
position: relative;
|
||
z-index: 1;
|
||
padding: 14px 14px 12px;
|
||
}
|
||
|
||
.biz-card .row-top {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 10px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.biz-card .type-icon {
|
||
width: 44px;
|
||
height: 44px;
|
||
border-radius: 14px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
font-size: 22px;
|
||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.65);
|
||
}
|
||
|
||
.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);
|
||
color: #fff;
|
||
}
|
||
|
||
.biz-card.status-danger .type-icon {
|
||
background: linear-gradient(145deg, #ffb4b4, #e85a5a);
|
||
color: #fff;
|
||
}
|
||
|
||
.biz-card.status-success .type-icon {
|
||
background: linear-gradient(145deg, #b8efd4, #189b65);
|
||
color: #fff;
|
||
}
|
||
|
||
.biz-card .title-wrap {
|
||
flex: 1;
|
||
min-width: 0;
|
||
padding-top: 1px;
|
||
}
|
||
|
||
.biz-card .type-chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
font-size: 10px;
|
||
font-weight: 650;
|
||
letter-spacing: 0.04em;
|
||
padding: 2px 7px;
|
||
border-radius: 999px;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.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: 15px;
|
||
font-weight: 700;
|
||
line-height: 1.35;
|
||
color: #173128;
|
||
letter-spacing: -0.01em;
|
||
}
|
||
|
||
.biz-card .status-pill {
|
||
flex-shrink: 0;
|
||
margin-top: 2px;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
padding: 4px 9px;
|
||
border-radius: 999px;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
.biz-card.status-warning .status-pill {
|
||
background: #fff;
|
||
color: #a67b20;
|
||
box-shadow: 0 2px 8px rgba(214, 163, 67, 0.22);
|
||
}
|
||
|
||
.biz-card.status-primary .status-pill {
|
||
background: #0a9f6d;
|
||
color: #fff;
|
||
box-shadow: 0 4px 12px rgba(10, 159, 109, 0.28);
|
||
}
|
||
|
||
.biz-card.status-danger .status-pill {
|
||
background: #d14343;
|
||
color: #fff;
|
||
box-shadow: 0 4px 12px rgba(209, 67, 67, 0.25);
|
||
}
|
||
|
||
.biz-card.status-success .status-pill {
|
||
background: rgba(24, 155, 101, 0.12);
|
||
color: #147a50;
|
||
}
|
||
|
||
.biz-card .body-row {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.biz-card .desc-box {
|
||
flex: 1;
|
||
margin: 0;
|
||
padding: 10px 11px;
|
||
border-radius: 12px;
|
||
background: rgba(255, 255, 255, 0.72);
|
||
border: 1px solid rgba(255, 255, 255, 0.9);
|
||
font-size: 12px;
|
||
color: #3f564c;
|
||
line-height: 1.5;
|
||
backdrop-filter: blur(2px);
|
||
}
|
||
|
||
.biz-card .side-metric {
|
||
width: 86px;
|
||
flex-shrink: 0;
|
||
border-radius: 12px;
|
||
padding: 8px 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: flex-end;
|
||
text-align: right;
|
||
background: rgba(255, 255, 255, 0.65);
|
||
border: 1px solid rgba(255, 255, 255, 0.95);
|
||
}
|
||
|
||
.biz-card .side-metric .amt-label {
|
||
font-size: 10px;
|
||
color: #8aa198;
|
||
}
|
||
|
||
.biz-card .side-metric .amt {
|
||
margin-top: 2px;
|
||
font-size: 15px;
|
||
font-weight: 800;
|
||
color: #173128;
|
||
letter-spacing: -0.02em;
|
||
font-variant-numeric: tabular-nums;
|
||
line-height: 1.15;
|
||
}
|
||
|
||
.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: 6px;
|
||
font-size: 10px;
|
||
color: #8aa198;
|
||
}
|
||
|
||
.biz-card .meta-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.biz-card .meta-row .mi {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
font-size: 11px;
|
||
color: #5d746a;
|
||
background: rgba(255, 255, 255, 0.7);
|
||
border-radius: 999px;
|
||
padding: 3px 8px;
|
||
}
|
||
|
||
.biz-card .meta-row .van-icon {
|
||
font-size: 12px;
|
||
color: #7f968c;
|
||
}
|
||
|
||
.biz-card .code-pill {
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||
font-size: 10px;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
.biz-card .footer {
|
||
margin-top: 12px;
|
||
padding-top: 10px;
|
||
border-top: 1px dashed rgba(140, 170, 155, 0.28);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.biz-card .footer .left-hint {
|
||
font-size: 11px;
|
||
color: #7f968c;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
|
||
.biz-card .footer .left-hint i {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
display: inline-block;
|
||
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: 8px;
|
||
}
|
||
|
||
.biz-card .footer .van-button {
|
||
min-width: 64px;
|
||
border-radius: 999px;
|
||
height: 30px;
|
||
}
|
||
|
||
.biz-card .footer .van-button--primary {
|
||
background: linear-gradient(135deg, #12b37a, #087f57);
|
||
border: none;
|
||
box-shadow: 0 4px 12px rgba(8, 127, 87, 0.28);
|
||
}
|
||
|
||
.list-end {
|
||
padding: 8px 0 28px;
|
||
text-align: center;
|
||
font-size: 11px;
|
||
color: #8aa198;
|
||
letter-spacing: 0.08em;
|
||
}
|
||
|
||
.list-end span {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.list-end span::before,
|
||
.list-end span::after {
|
||
content: "";
|
||
width: 28px;
|
||
height: 1px;
|
||
background: linear-gradient(90deg, transparent, #c5d8ce);
|
||
}
|
||
|
||
.list-end span::after {
|
||
background: linear-gradient(90deg, #c5d8ce, transparent);
|
||
}
|
||
|
||
.list-empty-pad {
|
||
padding: 40px 0;
|
||
}
|
||
|
||
/* ========== 详情 ========== */
|
||
.detail-page {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
background:
|
||
radial-gradient(380px 200px at 100% 8%, rgba(214, 163, 67, 0.14), transparent 55%),
|
||
linear-gradient(180deg, #e8f5ef 0%, #f4f7f6 42%, #f7f9f8 100%);
|
||
}
|
||
|
||
.detail-page .screen-scroll {
|
||
background: transparent;
|
||
padding-bottom: 88px;
|
||
}
|
||
|
||
.detail-hero {
|
||
position: relative;
|
||
overflow: hidden;
|
||
padding: 44px 14px 28px;
|
||
border-radius: 0 0 28px 28px;
|
||
color: #fff;
|
||
}
|
||
|
||
.detail-hero.status-warning {
|
||
background:
|
||
radial-gradient(220px 140px at 90% 10%, rgba(255, 255, 255, 0.2), transparent 60%),
|
||
linear-gradient(155deg, #a67b20 0%, #d6a343 45%, #e8c06a 100%);
|
||
}
|
||
|
||
.detail-hero.status-primary {
|
||
background:
|
||
radial-gradient(220px 140px at 90% 10%, rgba(255, 255, 255, 0.18), transparent 60%),
|
||
linear-gradient(155deg, #066647 0%, #0a9f6d 48%, #12b37a 100%);
|
||
}
|
||
|
||
.detail-hero.status-danger {
|
||
background:
|
||
radial-gradient(220px 140px at 90% 10%, rgba(255, 255, 255, 0.16), transparent 60%),
|
||
linear-gradient(155deg, #9c2f2f 0%, #d14343 50%, #e36a6a 100%);
|
||
}
|
||
|
||
.detail-hero.status-success {
|
||
background:
|
||
radial-gradient(220px 140px at 90% 10%, rgba(255, 255, 255, 0.16), transparent 60%),
|
||
linear-gradient(155deg, #0f6b48 0%, #189b65 52%, #3bb883 100%);
|
||
}
|
||
|
||
.detail-hero::after {
|
||
content: "";
|
||
position: absolute;
|
||
width: 180px;
|
||
height: 180px;
|
||
right: -48px;
|
||
bottom: -70px;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.1);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.detail-hero .van-nav-bar {
|
||
background: transparent;
|
||
margin: 0 -6px;
|
||
}
|
||
|
||
.detail-hero .van-hairline--bottom::after {
|
||
display: none;
|
||
}
|
||
|
||
.detail-hero-body {
|
||
position: relative;
|
||
z-index: 1;
|
||
margin-top: 6px;
|
||
}
|
||
|
||
.detail-hero .type-line {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.detail-hero .type-chip {
|
||
font-size: 11px;
|
||
font-weight: 650;
|
||
letter-spacing: 0.04em;
|
||
padding: 3px 9px;
|
||
border-radius: 999px;
|
||
background: rgba(255, 255, 255, 0.2);
|
||
color: #fff;
|
||
}
|
||
|
||
.detail-hero .status-chip {
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
padding: 3px 9px;
|
||
border-radius: 999px;
|
||
background: #fff;
|
||
color: #173128;
|
||
}
|
||
|
||
.detail-hero.status-warning .status-chip { color: #8f6718; }
|
||
.detail-hero.status-primary .status-chip { color: #087f57; }
|
||
.detail-hero.status-danger .status-chip { color: #b13535; }
|
||
.detail-hero.status-success .status-chip { color: #147a50; }
|
||
|
||
.detail-hero h2 {
|
||
margin: 0;
|
||
font-size: 20px;
|
||
font-weight: 750;
|
||
line-height: 1.35;
|
||
letter-spacing: -0.01em;
|
||
}
|
||
|
||
.detail-hero .code {
|
||
margin-top: 8px;
|
||
font-size: 12px;
|
||
opacity: 0.92;
|
||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
.detail-metric-dock {
|
||
margin: -18px 12px 0;
|
||
position: relative;
|
||
z-index: 3;
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 8px;
|
||
padding: 12px;
|
||
border-radius: 18px;
|
||
background: rgba(255, 255, 255, 0.94);
|
||
border: 1px solid rgba(220, 235, 228, 0.95);
|
||
box-shadow: 0 12px 30px rgba(15, 55, 40, 0.1);
|
||
backdrop-filter: blur(10px);
|
||
}
|
||
|
||
.detail-metric {
|
||
text-align: center;
|
||
padding: 4px 2px;
|
||
}
|
||
|
||
.detail-metric .lab {
|
||
display: block;
|
||
font-size: 10px;
|
||
color: #8aa198;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.detail-metric .val {
|
||
display: block;
|
||
font-size: 14px;
|
||
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: 16px 12px 0;
|
||
}
|
||
|
||
.detail-section {
|
||
background: #fff;
|
||
border-radius: 18px;
|
||
border: 1px solid rgba(220, 235, 228, 0.9);
|
||
box-shadow: 0 8px 22px rgba(20, 55, 40, 0.05);
|
||
padding: 14px 14px 12px;
|
||
margin-bottom: 12px;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.detail-section .sec-hd {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.detail-section .sec-hd h3 {
|
||
margin: 0;
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #173128;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.detail-section .sec-hd h3::before {
|
||
content: "";
|
||
width: 4px;
|
||
height: 14px;
|
||
border-radius: 999px;
|
||
background: linear-gradient(180deg, #12b37a, #087f57);
|
||
}
|
||
|
||
.detail-section .sec-hd .more {
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--brand-green);
|
||
font-size: 12px;
|
||
cursor: pointer;
|
||
padding: 0;
|
||
}
|
||
|
||
.kv-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.kv-row {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 12px;
|
||
font-size: 13px;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.kv-row .k {
|
||
width: 72px;
|
||
flex-shrink: 0;
|
||
color: #8aa198;
|
||
}
|
||
|
||
.kv-row .v {
|
||
flex: 1;
|
||
color: #173128;
|
||
font-weight: 550;
|
||
word-break: break-all;
|
||
}
|
||
|
||
.detail-note {
|
||
margin: 0;
|
||
padding: 10px 12px;
|
||
border-radius: 12px;
|
||
background: linear-gradient(135deg, #fff8ec, #fffdf8);
|
||
border: 1px solid #f0e0b8;
|
||
color: #8a6520;
|
||
font-size: 12px;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* 详情 · 流转记录 */
|
||
.flow-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0;
|
||
position: relative;
|
||
}
|
||
|
||
.flow-item {
|
||
position: relative;
|
||
display: flex;
|
||
gap: 12px;
|
||
padding-bottom: 14px;
|
||
}
|
||
|
||
.flow-item:last-child {
|
||
padding-bottom: 0;
|
||
}
|
||
|
||
.flow-item:not(:last-child) .flow-rail::after {
|
||
content: "";
|
||
position: absolute;
|
||
left: 50%;
|
||
top: 36px;
|
||
bottom: -14px;
|
||
width: 2px;
|
||
transform: translateX(-50%);
|
||
background: linear-gradient(180deg, rgba(10, 159, 109, 0.45), rgba(220, 235, 228, 0.9));
|
||
border-radius: 2px;
|
||
}
|
||
|
||
.flow-rail {
|
||
position: relative;
|
||
width: 36px;
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
justify-content: center;
|
||
}
|
||
|
||
.flow-dot {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 16px;
|
||
color: #fff;
|
||
box-shadow: 0 6px 14px 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 6px 14px rgba(214, 163, 67, 0.28);
|
||
}
|
||
|
||
.flow-item.danger .flow-dot {
|
||
background: linear-gradient(145deg, #ef7a7a, #d14343);
|
||
box-shadow: 0 6px 14px rgba(209, 67, 67, 0.25);
|
||
}
|
||
|
||
.flow-card {
|
||
flex: 1;
|
||
min-width: 0;
|
||
border-radius: 14px;
|
||
padding: 11px 12px;
|
||
background: linear-gradient(160deg, #f7fbf9 0%, #ffffff 55%);
|
||
border: 1px solid #e4eee9;
|
||
box-shadow: 0 4px 12px 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: 8px;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.flow-card .flow-title {
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
color: #173128;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.flow-card .flow-badge {
|
||
flex-shrink: 0;
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
padding: 2px 7px;
|
||
border-radius: 999px;
|
||
letter-spacing: 0.02em;
|
||
}
|
||
|
||
.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: 12px;
|
||
color: #5d746a;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
.flow-card .flow-meta {
|
||
margin-top: 8px;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
.flow-card .flow-meta .chip {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
font-size: 11px;
|
||
color: #6b7b74;
|
||
background: rgba(255, 255, 255, 0.9);
|
||
border: 1px solid #e8f0ec;
|
||
border-radius: 999px;
|
||
padding: 2px 8px;
|
||
}
|
||
|
||
.flow-card .flow-meta .van-icon {
|
||
font-size: 12px;
|
||
color: #8aa198;
|
||
}
|
||
|
||
.pay-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
padding: 10px 12px;
|
||
border-radius: 12px;
|
||
background: #f7faf9;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.pay-row:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.pay-row .left .name {
|
||
font-size: 13px;
|
||
font-weight: 650;
|
||
color: #173128;
|
||
}
|
||
|
||
.pay-row .left .sub {
|
||
margin-top: 2px;
|
||
font-size: 11px;
|
||
color: #8aa198;
|
||
}
|
||
|
||
.pay-row .right {
|
||
text-align: right;
|
||
}
|
||
|
||
.pay-row .right .amt {
|
||
font-size: 14px;
|
||
font-weight: 750;
|
||
color: #9a7424;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
/* 详情 · 附件 */
|
||
.d-attach-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.d-attach-upload {
|
||
width: 100%;
|
||
border: 1.5px dashed #9ad9be;
|
||
border-radius: 14px;
|
||
background:
|
||
linear-gradient(135deg, rgba(233, 249, 242, 0.95), rgba(255, 255, 255, 0.95)),
|
||
#e9f9f2;
|
||
padding: 14px 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
cursor: pointer;
|
||
text-align: left;
|
||
transition: transform .15s, border-color .15s;
|
||
}
|
||
|
||
.d-attach-upload:active {
|
||
transform: scale(0.99);
|
||
border-color: #0a9f6d;
|
||
}
|
||
|
||
.d-attach-upload .up-icon {
|
||
width: 44px;
|
||
height: 44px;
|
||
border-radius: 12px;
|
||
background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
|
||
color: #fff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 22px;
|
||
box-shadow: 0 6px 14px 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: 13px;
|
||
font-weight: 700;
|
||
color: #087f57;
|
||
}
|
||
|
||
.d-attach-upload .up-sub {
|
||
margin-top: 2px;
|
||
font-size: 11px;
|
||
color: #6b7b74;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.d-attach-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 12px;
|
||
border-radius: 14px;
|
||
background: #fff;
|
||
border: 1px solid #e4eee9;
|
||
box-shadow: 0 4px 14px rgba(20, 55, 40, 0.05);
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.d-attach-card::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 3px;
|
||
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: 46px;
|
||
height: 46px;
|
||
border-radius: 13px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 1px;
|
||
flex-shrink: 0;
|
||
color: #fff;
|
||
font-weight: 800;
|
||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.2);
|
||
}
|
||
|
||
.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: 10px;
|
||
letter-spacing: 0.04em;
|
||
line-height: 1;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.d-attach-card .file-icon .van-icon {
|
||
font-size: 16px;
|
||
opacity: 0.95;
|
||
}
|
||
|
||
.d-attach-card .file-body {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.d-attach-card .file-name {
|
||
font-size: 13px;
|
||
font-weight: 650;
|
||
color: #173128;
|
||
line-height: 1.35;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.d-attach-card .file-meta {
|
||
margin-top: 4px;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
font-size: 11px;
|
||
color: #8aa198;
|
||
}
|
||
|
||
.d-attach-card .file-ops {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.d-attach-card .file-ops button {
|
||
border: none;
|
||
cursor: pointer;
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 16px;
|
||
transition: transform .12s;
|
||
}
|
||
|
||
.d-attach-card .file-ops button:active {
|
||
transform: scale(0.94);
|
||
}
|
||
|
||
.d-attach-card .file-ops .btn-preview {
|
||
background: var(--brand-green-soft);
|
||
color: var(--brand-green-dark);
|
||
}
|
||
|
||
.d-attach-card .file-ops .btn-download {
|
||
background: #f0f5f3;
|
||
color: #4d635a;
|
||
}
|
||
|
||
.d-attach-card .file-ops .icon-download {
|
||
width: 14px;
|
||
height: 14px;
|
||
display: block;
|
||
}
|
||
|
||
.detail-footer {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
display: flex;
|
||
gap: 10px;
|
||
padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
|
||
background: rgba(255, 255, 255, 0.96);
|
||
border-top: 1px solid var(--border);
|
||
backdrop-filter: blur(10px);
|
||
z-index: 4;
|
||
}
|
||
|
||
.detail-footer .van-button {
|
||
flex: 1;
|
||
height: 44px;
|
||
border-radius: 999px;
|
||
}
|
||
|
||
.detail-footer .van-button--primary {
|
||
background: linear-gradient(135deg, #12b37a, #087f57);
|
||
border: none;
|
||
box-shadow: 0 6px 16px rgba(8, 127, 87, 0.28);
|
||
}
|
||
|
||
/* ========== 表单 ========== */
|
||
.form-page {
|
||
padding-bottom: 88px;
|
||
}
|
||
|
||
.form-section-title {
|
||
margin: 14px 16px 8px;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.form-tip {
|
||
margin: 8px 16px 0;
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.form-footer {
|
||
position: absolute;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
display: flex;
|
||
gap: 10px;
|
||
padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
|
||
background: rgba(255, 255, 255, 0.96);
|
||
border-top: 1px solid var(--border);
|
||
backdrop-filter: blur(8px);
|
||
z-index: 3;
|
||
}
|
||
|
||
.form-footer .van-button {
|
||
flex: 1;
|
||
height: 44px;
|
||
}
|
||
|
||
/* ========== 组件展示 ========== */
|
||
.comps-page {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
min-height: 0;
|
||
background:
|
||
radial-gradient(400px 220px at 100% 0%, rgba(214, 163, 67, 0.14), transparent 55%),
|
||
radial-gradient(360px 200px at 0% 20%, rgba(10, 159, 109, 0.1), transparent 50%),
|
||
linear-gradient(180deg, #e8f5ef 0%, #f4f7f6 40%, #f7f9f8 100%);
|
||
}
|
||
|
||
.comps-page .screen-scroll {
|
||
background: transparent;
|
||
}
|
||
|
||
.comps-hero {
|
||
background:
|
||
radial-gradient(220px 140px at 90% 15%, rgba(255, 255, 255, 0.18), transparent 60%),
|
||
linear-gradient(155deg, #066647 0%, #0a9f6d 48%, #12b37a 100%);
|
||
padding: 44px 14px 22px;
|
||
border-radius: 0 0 28px 28px;
|
||
color: #fff;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.comps-hero::after {
|
||
content: "";
|
||
position: absolute;
|
||
width: 160px;
|
||
height: 160px;
|
||
right: -40px;
|
||
bottom: -60px;
|
||
border-radius: 50%;
|
||
background: rgba(255, 255, 255, 0.08);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.comps-hero .van-nav-bar {
|
||
background: transparent;
|
||
margin: 0 -6px;
|
||
}
|
||
|
||
.comps-hero .van-hairline--bottom::after {
|
||
display: none;
|
||
}
|
||
|
||
.comps-hero-copy {
|
||
position: relative;
|
||
z-index: 1;
|
||
margin: 4px 2px 0;
|
||
}
|
||
|
||
.comps-hero-copy .eyebrow {
|
||
font-size: 11px;
|
||
letter-spacing: 0.08em;
|
||
opacity: 0.82;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.comps-hero-copy h2 {
|
||
margin: 0;
|
||
font-size: 22px;
|
||
font-weight: 750;
|
||
}
|
||
|
||
.comps-hero-copy .subline {
|
||
margin-top: 6px;
|
||
font-size: 12px;
|
||
opacity: 0.9;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.comp-section {
|
||
padding: 14px 12px 28px;
|
||
}
|
||
|
||
.comp-block {
|
||
position: relative;
|
||
background: #fff;
|
||
border: 1px solid rgba(220, 235, 228, 0.95);
|
||
border-radius: 18px;
|
||
padding: 0;
|
||
margin-bottom: 12px;
|
||
box-shadow: 0 8px 22px rgba(20, 55, 40, 0.05);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.comp-block .comp-hd {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 12px 14px;
|
||
background: linear-gradient(135deg, #f3faf6 0%, #ffffff 70%);
|
||
border-bottom: 1px solid #eef4f1;
|
||
}
|
||
|
||
.comp-block .comp-hd .hd-icon {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 16px;
|
||
color: #fff;
|
||
background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
|
||
box-shadow: 0 4px 10px rgba(10, 159, 109, 0.22);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.comp-block .comp-hd .hd-icon.tone-amber {
|
||
background: linear-gradient(145deg, #ecc06a, #d6a343);
|
||
box-shadow: 0 4px 10px rgba(214, 163, 67, 0.25);
|
||
}
|
||
|
||
.comp-block .comp-hd .hd-icon.tone-rose {
|
||
background: linear-gradient(145deg, #f07171, #d14343);
|
||
box-shadow: 0 4px 10px rgba(209, 67, 67, 0.22);
|
||
}
|
||
|
||
.comp-block .comp-hd .hd-icon.tone-slate {
|
||
background: linear-gradient(145deg, #8aa198, #5d746a);
|
||
box-shadow: 0 4px 10px rgba(93, 116, 106, 0.2);
|
||
}
|
||
|
||
.comp-block .comp-hd h4 {
|
||
margin: 0;
|
||
font-size: 14px;
|
||
font-weight: 750;
|
||
color: #173128;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.comp-block .comp-hd .hd-sub {
|
||
margin-top: 2px;
|
||
font-size: 11px;
|
||
color: #8aa198;
|
||
}
|
||
|
||
.comp-block .comp-bd {
|
||
padding: 14px;
|
||
}
|
||
|
||
.comp-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
align-items: center;
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.comp-row:last-child { margin-bottom: 0; }
|
||
|
||
.label-muted {
|
||
font-size: 11px;
|
||
color: #8aa198;
|
||
width: 100%;
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.comps-page .van-button--primary {
|
||
background: linear-gradient(135deg, #12b37a, #087f57);
|
||
border: none;
|
||
box-shadow: 0 4px 12px rgba(8, 127, 87, 0.22);
|
||
}
|
||
|
||
.comps-page .van-button--primary.van-button--plain {
|
||
background: #fff;
|
||
box-shadow: none;
|
||
color: #0a9f6d;
|
||
border-color: #0a9f6d;
|
||
}
|
||
|
||
.comps-page .van-button--round {
|
||
border-radius: 999px;
|
||
}
|
||
|
||
.tag-grid {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.demo-tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
padding: 5px 10px;
|
||
border-radius: 999px;
|
||
font-size: 12px;
|
||
font-weight: 650;
|
||
}
|
||
|
||
.demo-tag i {
|
||
width: 6px;
|
||
height: 6px;
|
||
border-radius: 50%;
|
||
background: currentColor;
|
||
}
|
||
|
||
.demo-tag.t-warn {
|
||
background: #fff3e0;
|
||
color: #a67b20;
|
||
}
|
||
|
||
.demo-tag.t-primary {
|
||
background: #0a9f6d;
|
||
color: #fff;
|
||
}
|
||
|
||
.demo-tag.t-success {
|
||
background: rgba(24, 155, 101, 0.12);
|
||
color: #147a50;
|
||
}
|
||
|
||
.demo-tag.t-danger {
|
||
background: #fdecec;
|
||
color: #d14343;
|
||
}
|
||
|
||
.demo-tag.t-muted {
|
||
background: #eef2f1;
|
||
color: #6b7b74;
|
||
}
|
||
|
||
.comp-cell-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
}
|
||
|
||
.comp-cell-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 12px 12px;
|
||
border-radius: 14px;
|
||
background: linear-gradient(160deg, #f7fbf9 0%, #ffffff 55%);
|
||
border: 1px solid #e4eee9;
|
||
box-shadow: 0 4px 12px rgba(20, 55, 40, 0.04);
|
||
cursor: pointer;
|
||
text-align: left;
|
||
width: 100%;
|
||
transition: transform .15s, border-color .15s;
|
||
}
|
||
|
||
.comp-cell-item:active {
|
||
transform: scale(0.99);
|
||
border-color: #b7e5d0;
|
||
}
|
||
|
||
.comp-cell-item .ci-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 18px;
|
||
color: #fff;
|
||
flex-shrink: 0;
|
||
background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
|
||
box-shadow: 0 4px 10px rgba(10, 159, 109, 0.2);
|
||
}
|
||
|
||
.comp-cell-item .ci-icon.tone-amber {
|
||
background: linear-gradient(145deg, #ecc06a, #d6a343);
|
||
box-shadow: 0 4px 10px rgba(214, 163, 67, 0.22);
|
||
}
|
||
|
||
.comp-cell-item .ci-icon.tone-slate {
|
||
background: linear-gradient(145deg, #8aa198, #5d746a);
|
||
box-shadow: 0 4px 10px rgba(93, 116, 106, 0.18);
|
||
}
|
||
|
||
.comp-cell-item .ci-body {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.comp-cell-item .ci-title {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: #173128;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.comp-cell-item .ci-label {
|
||
margin-top: 2px;
|
||
font-size: 11px;
|
||
color: #8aa198;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
.comp-cell-item .ci-right {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
flex-shrink: 0;
|
||
color: #8aa198;
|
||
font-size: 13px;
|
||
font-weight: 550;
|
||
}
|
||
|
||
.comp-cell-item .ci-right .val {
|
||
color: #4d635a;
|
||
}
|
||
|
||
.comp-feedback-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 8px;
|
||
}
|
||
|
||
.comp-feedback-grid .fb-btn {
|
||
border: 1px solid #e4eee9;
|
||
background: linear-gradient(160deg, #f7fbf9, #fff);
|
||
border-radius: 14px;
|
||
padding: 14px 8px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 8px;
|
||
cursor: pointer;
|
||
color: #173128;
|
||
font-size: 12px;
|
||
font-weight: 650;
|
||
transition: transform .15s;
|
||
}
|
||
|
||
.comp-feedback-grid .fb-btn:active {
|
||
transform: scale(0.97);
|
||
}
|
||
|
||
.comp-feedback-grid .fb-btn .fb-icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 12px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 20px;
|
||
color: #fff;
|
||
}
|
||
|
||
.comp-feedback-grid .fb-btn.fb-toast .fb-icon {
|
||
background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
|
||
}
|
||
|
||
.comp-feedback-grid .fb-btn.fb-dialog .fb-icon {
|
||
background: linear-gradient(145deg, #ecc06a, #d6a343);
|
||
}
|
||
|
||
.comp-feedback-grid .fb-btn.fb-sheet .fb-icon {
|
||
background: linear-gradient(145deg, #8aa198, #5d746a);
|
||
}
|
||
|
||
.comp-steps-wrap {
|
||
padding: 8px 4px 0;
|
||
border-radius: 12px;
|
||
background: #f7faf9;
|
||
}
|
||
|
||
.comp-seg {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 4px;
|
||
padding: 4px;
|
||
border-radius: 14px;
|
||
background: #eef6f2;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.comp-seg button {
|
||
border: none;
|
||
background: transparent;
|
||
border-radius: 11px;
|
||
padding: 8px 4px;
|
||
font-size: 12px;
|
||
color: #6b7b74;
|
||
cursor: pointer;
|
||
}
|
||
|
||
.comp-seg button.active {
|
||
background: #fff;
|
||
color: #087f57;
|
||
font-weight: 700;
|
||
box-shadow: 0 4px 12px rgba(8, 127, 87, 0.12);
|
||
}
|
||
|
||
.comp-notice-stack {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 12px;
|
||
}
|
||
|
||
.comp-notice-bar {
|
||
border-radius: 14px;
|
||
overflow: hidden;
|
||
border: 1px solid #c7e6d6;
|
||
box-shadow: 0 4px 12px rgba(10, 159, 109, 0.08);
|
||
min-height: 44px;
|
||
}
|
||
|
||
.comp-empty-box {
|
||
border-radius: 16px;
|
||
background: linear-gradient(165deg, #eef9f3 0%, #ffffff 55%);
|
||
border: 1.5px dashed #b7e5d0;
|
||
padding: 28px 16px 24px;
|
||
min-height: 220px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
.comp-empty-box .van-empty {
|
||
padding: 0;
|
||
}
|
||
|
||
.comp-empty-box .van-empty__description {
|
||
margin-top: 12px;
|
||
color: #5d746a;
|
||
font-size: 13px;
|
||
}
|
||
|
||
.comp-empty-box .empty-tip {
|
||
margin-top: 4px;
|
||
font-size: 11px;
|
||
color: #8aa198;
|
||
text-align: center;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.comp-loading-box {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 12px;
|
||
padding: 28px 16px;
|
||
min-height: 120px;
|
||
border-radius: 16px;
|
||
background: linear-gradient(165deg, #f3faf6 0%, #ffffff 60%);
|
||
border: 1px solid #e4eee9;
|
||
color: #087f57;
|
||
font-size: 13px;
|
||
font-weight: 650;
|
||
box-shadow: 0 4px 12px rgba(20, 55, 40, 0.04);
|
||
}
|
||
|
||
.comp-loading-box .load-sub {
|
||
font-size: 11px;
|
||
font-weight: 500;
|
||
color: #8aa198;
|
||
}
|
||
|
||
.comps-page .comp-attach-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
width: 100%;
|
||
}
|
||
|
||
.comps-page .comp-upload-tile {
|
||
width: 100%;
|
||
border: 1.5px dashed #9ad9be;
|
||
border-radius: 16px;
|
||
background:
|
||
radial-gradient(120px 80px at 12% 30%, rgba(46, 207, 143, 0.16), transparent 70%),
|
||
linear-gradient(145deg, #e9f9f2 0%, #ffffff 55%);
|
||
padding: 16px 14px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
cursor: pointer;
|
||
text-align: left;
|
||
transition: transform .15s, border-color .15s, box-shadow .15s;
|
||
box-shadow: 0 6px 16px rgba(10, 159, 109, 0.08);
|
||
}
|
||
|
||
.comps-page .comp-upload-tile:active {
|
||
transform: scale(0.99);
|
||
border-color: #0a9f6d;
|
||
box-shadow: 0 4px 12px rgba(10, 159, 109, 0.12);
|
||
}
|
||
|
||
.comps-page .comp-upload-tile .up-icon {
|
||
width: 48px;
|
||
height: 48px;
|
||
border-radius: 14px;
|
||
background: linear-gradient(145deg, #2ecf8f, #0a9f6d);
|
||
color: #fff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 24px;
|
||
box-shadow: 0 6px 14px rgba(10, 159, 109, 0.28);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.comps-page .comp-upload-tile .up-text {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.comps-page .comp-upload-tile .up-title {
|
||
display: block;
|
||
font-size: 14px;
|
||
font-weight: 750;
|
||
color: #087f57;
|
||
}
|
||
|
||
.comps-page .comp-upload-tile .up-sub {
|
||
display: block;
|
||
margin-top: 3px;
|
||
font-size: 11px;
|
||
color: #6b7b74;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.comps-page .comp-file-card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 12px;
|
||
border-radius: 14px;
|
||
background: #fff;
|
||
border: 1px solid #e4eee9;
|
||
box-shadow: 0 4px 14px rgba(20, 55, 40, 0.05);
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.comps-page .comp-file-card::before {
|
||
content: "";
|
||
position: absolute;
|
||
left: 0;
|
||
top: 0;
|
||
bottom: 0;
|
||
width: 3px;
|
||
background: #3b82f6;
|
||
}
|
||
|
||
.comps-page .comp-file-card .file-icon {
|
||
width: 46px;
|
||
height: 46px;
|
||
border-radius: 13px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 1px;
|
||
flex-shrink: 0;
|
||
color: #fff;
|
||
font-weight: 800;
|
||
background: linear-gradient(145deg, #6ea8ff, #3b82f6);
|
||
}
|
||
|
||
.comps-page .comp-file-card .file-icon .ext {
|
||
font-size: 10px;
|
||
letter-spacing: 0.04em;
|
||
line-height: 1;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
.comps-page .comp-file-card .file-icon .van-icon {
|
||
font-size: 16px;
|
||
}
|
||
|
||
.comps-page .comp-file-card .file-body {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.comps-page .comp-file-card .file-name {
|
||
font-size: 13px;
|
||
font-weight: 650;
|
||
color: #173128;
|
||
line-height: 1.35;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.comps-page .comp-file-card .file-meta {
|
||
margin-top: 4px;
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
font-size: 11px;
|
||
color: #8aa198;
|
||
}
|
||
|
||
.comps-page .comp-file-card .file-ops {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.comps-page .comp-file-card .file-ops button {
|
||
border: none;
|
||
cursor: pointer;
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 16px;
|
||
}
|
||
|
||
.comps-page .comp-file-card .file-ops .btn-preview {
|
||
background: var(--brand-green-soft);
|
||
color: var(--brand-green-dark);
|
||
}
|
||
|
||
.comps-page .comp-file-card .file-ops .btn-del {
|
||
background: #fdecec;
|
||
color: #d14343;
|
||
}
|
||
|
||
/* 表单页等仍可能用到的旧附件样式 */
|
||
.upload-card {
|
||
width: 88px;
|
||
height: 88px;
|
||
border: 1.5px dashed #9ad9be;
|
||
border-radius: 14px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
color: #087f57;
|
||
background: linear-gradient(145deg, #e9f9f2, #fff);
|
||
font-size: 11px;
|
||
font-weight: 650;
|
||
}
|
||
|
||
.attach-card {
|
||
width: 140px;
|
||
min-height: 88px;
|
||
border: 1px solid #e4eee9;
|
||
border-radius: 14px;
|
||
padding: 10px 12px;
|
||
background: #fff;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: space-between;
|
||
font-size: 11px;
|
||
}
|
||
|
||
.attach-card .name {
|
||
color: #173128;
|
||
font-weight: 650;
|
||
word-break: break-all;
|
||
line-height: 1.35;
|
||
}
|
||
|
||
.attach-card .ops {
|
||
display: flex;
|
||
gap: 10px;
|
||
color: #0a9f6d;
|
||
font-weight: 650;
|
||
}
|
||
|
||
.demo-badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
padding: 2px 8px;
|
||
border-radius: 999px;
|
||
font-size: 11px;
|
||
background: var(--brand-green-soft);
|
||
color: var(--brand-green-dark);
|
||
}
|
||
|
||
@media (max-width: 900px) {
|
||
#app {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
.doc-panel {
|
||
position: static;
|
||
}
|
||
.phone {
|
||
width: min(390px, 100%);
|
||
height: min(844px, calc(100vh - 120px));
|
||
min-height: 640px;
|
||
}
|
||
}
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
* { transition: none !important; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div id="app">
|
||
<aside class="doc-panel">
|
||
<h1>移动端 UI Demo</h1>
|
||
<p class="sub">
|
||
Vant 4 + 三墙 Token(#0a9f6d)。首页为权限菜单卡片;列表 / 新增编辑 / 组件风格可在下方切换预览。
|
||
</p>
|
||
|
||
<div class="section-title">页面预览</div>
|
||
<div class="screen-nav">
|
||
<button
|
||
v-for="s in screens"
|
||
:key="s.key"
|
||
:class="{ active: screen === s.key }"
|
||
type="button"
|
||
@click="goScreen(s.key)"
|
||
>{{ s.label }}</button>
|
||
</div>
|
||
|
||
<div class="section-title">设计 Token</div>
|
||
<div class="token-row">
|
||
<span class="token-chip"><i style="background:#0a9f6d"></i>#0a9f6d</span>
|
||
<span class="token-chip"><i style="background:#087f57"></i>#087f57</span>
|
||
<span class="token-chip"><i style="background:#e9f9f2"></i>#e9f9f2</span>
|
||
<span class="token-chip"><i style="background:#d6a343"></i>#d6a343</span>
|
||
<span class="token-chip"><i style="background:#f5f8f7"></i>#f5f8f7</span>
|
||
</div>
|
||
|
||
<div class="section-title">权限说明</div>
|
||
<ul class="notes">
|
||
<li>菜单来自「菜单管理 → 移动端菜单」树</li>
|
||
<li>仅展示当前登录人角色/职位拥有的菜单权限</li>
|
||
<li>卡片:上 Icon、下标题;建议 4 列宫格</li>
|
||
<li>本 Demo 用右侧开关模拟「无某菜单权限」</li>
|
||
</ul>
|
||
|
||
<div class="section-title" v-if="screen === 'home'">权限模拟</div>
|
||
<div v-if="screen === 'home'" style="display:flex;flex-direction:column;gap:8px;">
|
||
<van-checkbox
|
||
v-for="m in allMenus"
|
||
:key="m.id"
|
||
v-model="grantedMap[m.id]"
|
||
shape="square"
|
||
icon-size="16px"
|
||
>{{ m.title }}</van-checkbox>
|
||
</div>
|
||
</aside>
|
||
|
||
<div class="phone-stage">
|
||
<div class="phone">
|
||
<div class="phone-screen">
|
||
|
||
<!-- ===== 首页 ===== -->
|
||
<template v-if="screen === 'home'">
|
||
<div class="screen-scroll">
|
||
<header class="home-header">
|
||
<div class="brand">中科兴皖 · 移动工作台</div>
|
||
<h2>你好,{{ userName }}</h2>
|
||
<div class="meta">
|
||
<span>{{ roleName }}</span>
|
||
<span class="dot"></span>
|
||
<span>{{ deptName }}</span>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="home-body">
|
||
<div class="perm-banner">
|
||
菜单卡片按角色权限动态渲染;无权限的功能入口不会出现在宫格中。
|
||
</div>
|
||
|
||
<section class="panel">
|
||
<div class="panel-hd">
|
||
<h3>功能菜单</h3>
|
||
<span class="hint">共 {{ visibleMenus.length }} 项</span>
|
||
</div>
|
||
<div v-if="visibleMenus.length" class="menu-grid">
|
||
<button
|
||
v-for="m in visibleMenus"
|
||
:key="m.id"
|
||
type="button"
|
||
class="menu-card"
|
||
@click="onMenuTap(m)"
|
||
>
|
||
<div class="menu-icon" :class="m.tone">
|
||
<van-icon :name="m.icon" />
|
||
</div>
|
||
<span class="menu-title">{{ m.title }}</span>
|
||
</button>
|
||
</div>
|
||
<div v-else class="list-empty-pad">
|
||
<van-empty description="暂无可用菜单权限" image-size="80" />
|
||
</div>
|
||
</section>
|
||
|
||
<section class="panel todo-panel">
|
||
<div class="panel-hd">
|
||
<h3>待办摘要</h3>
|
||
<button type="button" class="demo-badge" style="border:none;cursor:pointer;" @click="screen = 'list'">
|
||
查看全部
|
||
<van-icon name="arrow" style="font-size:11px;" />
|
||
</button>
|
||
</div>
|
||
|
||
<div class="todo-stats">
|
||
<button type="button" class="todo-stat tone-warn" @click="goListTab(2)">
|
||
<span class="stat-icon"><van-icon name="passed" /></span>
|
||
<span class="num">{{ todoStats.approve }}</span>
|
||
<span class="label">待审批</span>
|
||
</button>
|
||
<button type="button" class="todo-stat tone-pay" @click="goListTab(2)">
|
||
<span class="stat-icon"><van-icon name="balance-pay" /></span>
|
||
<span class="num">{{ todoStats.pay }}</span>
|
||
<span class="label">待确认缴费</span>
|
||
</button>
|
||
<button type="button" class="todo-stat tone-danger" @click="goListTab(0)">
|
||
<span class="stat-icon"><van-icon name="warning-o" /></span>
|
||
<span class="num">{{ todoStats.alert }}</span>
|
||
<span class="label">异常提醒</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="todo-list">
|
||
<button
|
||
v-for="t in todoItems"
|
||
:key="t.id"
|
||
type="button"
|
||
class="todo-item"
|
||
@click="screen = 'list'"
|
||
>
|
||
<div class="ti-icon" :class="t.iconClass">
|
||
<van-icon :name="t.icon" />
|
||
</div>
|
||
<div class="ti-body">
|
||
<div class="ti-title">{{ t.title }}</div>
|
||
<div class="ti-desc">{{ t.desc }}</div>
|
||
</div>
|
||
<div class="ti-right">
|
||
<van-tag :type="t.tagType" plain size="medium">{{ t.tag }}</van-tag>
|
||
<span class="ti-time">{{ t.time }}</span>
|
||
</div>
|
||
</button>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ===== 列表 ===== -->
|
||
<template v-else-if="screen === 'list'">
|
||
<div class="list-page">
|
||
<div class="screen-scroll">
|
||
<header class="list-hero">
|
||
<van-nav-bar left-arrow @click-left="screen = 'home'">
|
||
<template #title>
|
||
<span style="font-weight:600;">业务列表</span>
|
||
</template>
|
||
<template #right>
|
||
<van-icon name="plus" size="20" @click="openForm('create')" />
|
||
</template>
|
||
</van-nav-bar>
|
||
<div class="list-hero-copy">
|
||
<div class="eyebrow">WORKSPACE</div>
|
||
<h2>今日待办一览</h2>
|
||
<div class="subline">
|
||
<span>当前展示</span>
|
||
<em>{{ filteredList.length }} 条</em>
|
||
<span v-if="keyword">· 已筛选</span>
|
||
</div>
|
||
</div>
|
||
<div class="list-search">
|
||
<van-search
|
||
v-model="keyword"
|
||
shape="round"
|
||
background="transparent"
|
||
placeholder="搜索单号 / 名称 / 负责人"
|
||
/>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="list-filter-dock">
|
||
<div class="seg-tabs">
|
||
<button
|
||
v-for="(tab, idx) in listTabs"
|
||
:key="tab.key"
|
||
type="button"
|
||
class="seg-tab"
|
||
:class="{ active: listTab === idx }"
|
||
@click="listTab = idx"
|
||
>
|
||
<span class="lab">{{ tab.label }}</span>
|
||
<span class="count">{{ tabCount(idx) }}</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="list-wrap">
|
||
<article
|
||
v-for="item in filteredList"
|
||
:key="item.id"
|
||
class="biz-card"
|
||
:class="'status-' + item.tagType"
|
||
>
|
||
<div class="card-inner">
|
||
<div class="row-top">
|
||
<div class="type-icon">
|
||
<van-icon :name="item.icon" />
|
||
</div>
|
||
<div class="title-wrap">
|
||
<div class="type-chip">{{ item.typeLabel }}</div>
|
||
<div class="title">{{ item.title }}</div>
|
||
</div>
|
||
<span class="status-pill">{{ item.status }}</span>
|
||
</div>
|
||
|
||
<div class="body-row">
|
||
<p class="desc-box">{{ item.desc }}</p>
|
||
<div class="side-metric">
|
||
<template v-if="item.amount">
|
||
<span class="amt-label">金额</span>
|
||
<span class="amt">¥{{ item.amount }}</span>
|
||
</template>
|
||
<template v-else>
|
||
<span class="amt-label">进度</span>
|
||
<span class="amt" style="font-size:13px;">{{ item.progress || '—' }}</span>
|
||
</template>
|
||
<span class="time">{{ item.time }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="meta-row">
|
||
<span class="mi code-pill">{{ item.code }}</span>
|
||
<span class="mi"><van-icon name="manager-o" />{{ item.owner }}</span>
|
||
</div>
|
||
|
||
<div class="footer">
|
||
<span class="left-hint"><i></i>{{ item.hint || '点击操作处理' }}</span>
|
||
<div class="ops">
|
||
<van-button size="small" plain hairline type="default" @click="openDetail(item)">查看</van-button>
|
||
<van-button size="small" type="primary" @click="openForm('edit', item)">处理</van-button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</article>
|
||
|
||
<div v-if="!filteredList.length" class="list-empty-pad">
|
||
<van-empty description="暂无数据" />
|
||
</div>
|
||
|
||
<div v-else class="list-end"><span>已经到底了</span></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ===== 详情 ===== -->
|
||
<template v-else-if="screen === 'detail'">
|
||
<div class="detail-page" v-if="detail">
|
||
<div class="screen-scroll">
|
||
<header class="detail-hero" :class="'status-' + detail.tagType">
|
||
<van-nav-bar left-arrow @click-left="screen = 'list'">
|
||
<template #title>
|
||
<span style="font-weight:600;">业务详情</span>
|
||
</template>
|
||
<template #right>
|
||
<van-icon name="ellipsis" size="20" @click="showAction = true" />
|
||
</template>
|
||
</van-nav-bar>
|
||
<div class="detail-hero-body">
|
||
<div class="type-line">
|
||
<span class="type-chip">{{ detail.typeLabel }}</span>
|
||
<span class="status-chip">{{ detail.status }}</span>
|
||
</div>
|
||
<h2>{{ detail.title }}</h2>
|
||
<div class="code">{{ detail.code }}</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="detail-metric-dock">
|
||
<div class="detail-metric">
|
||
<span class="lab">{{ detail.amount ? '金额' : '进度' }}</span>
|
||
<span class="val" :class="{ money: !!detail.amount }">
|
||
{{ detail.amount ? ('¥' + detail.amount) : (detail.progress || '—') }}
|
||
</span>
|
||
</div>
|
||
<div class="detail-metric">
|
||
<span class="lab">负责人</span>
|
||
<span class="val">{{ detail.owner }}</span>
|
||
</div>
|
||
<div class="detail-metric">
|
||
<span class="lab">更新时间</span>
|
||
<span class="val">{{ detail.time }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-body">
|
||
<section class="detail-section">
|
||
<div class="sec-hd"><h3>基础信息</h3></div>
|
||
<div class="kv-list">
|
||
<div class="kv-row"><span class="k">业务类型</span><span class="v">{{ detail.typeLabel }}</span></div>
|
||
<div class="kv-row"><span class="k">所属部门</span><span class="v">{{ detail.dept || deptName }}</span></div>
|
||
<div class="kv-row"><span class="k">客户/对象</span><span class="v">{{ detail.customer || '—' }}</span></div>
|
||
<div class="kv-row"><span class="k">创建时间</span><span class="v">{{ detail.createdAt || '—' }}</span></div>
|
||
</div>
|
||
<p class="detail-note" style="margin-top:12px;">{{ detail.desc }}</p>
|
||
</section>
|
||
|
||
<section class="detail-section">
|
||
<div class="sec-hd"><h3>业务进度</h3></div>
|
||
<van-steps :active="detail.stepActive" active-color="#0a9f6d" inactive-color="#dcebe4">
|
||
<van-step v-for="s in detail.steps" :key="s">{{ s }}</van-step>
|
||
</van-steps>
|
||
</section>
|
||
|
||
<section class="detail-section" v-if="detail.payments && detail.payments.length">
|
||
<div class="sec-hd">
|
||
<h3>缴费明细</h3>
|
||
<button type="button" class="more" @click="toast('查看全部缴费')">全部</button>
|
||
</div>
|
||
<div
|
||
v-for="p in detail.payments"
|
||
:key="p.id"
|
||
class="pay-row"
|
||
>
|
||
<div class="left">
|
||
<div class="name">{{ p.name }}</div>
|
||
<div class="sub">{{ p.sub }}</div>
|
||
</div>
|
||
<div class="right">
|
||
<div class="amt">¥{{ p.amount }}</div>
|
||
<van-tag :type="p.tagType" plain size="medium" style="margin-top:4px;">{{ p.status }}</van-tag>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="detail-section">
|
||
<div class="sec-hd">
|
||
<h3>附件</h3>
|
||
<span style="font-size:11px;color:#8aa198;">{{ detail.attachments.length }} 个文件</span>
|
||
</div>
|
||
<div class="d-attach-list">
|
||
<button type="button" class="d-attach-upload" @click="toast('上传附件')">
|
||
<span class="up-icon"><van-icon name="plus" /></span>
|
||
<span class="up-text">
|
||
<span class="up-title">添加附件</span>
|
||
<span class="up-sub">支持 PDF / 图片 / Office,可多选上传</span>
|
||
</span>
|
||
<van-icon name="arrow" color="#0a9f6d" />
|
||
</button>
|
||
|
||
<div
|
||
v-for="a in detail.attachments"
|
||
:key="a.name"
|
||
class="d-attach-card"
|
||
:class="fileTypeClass(a.name)"
|
||
>
|
||
<div class="file-icon">
|
||
<van-icon :name="fileTypeIcon(a.name)" />
|
||
<span class="ext">{{ fileExt(a.name) }}</span>
|
||
</div>
|
||
<div class="file-body">
|
||
<div class="file-name">{{ a.name }}</div>
|
||
<div class="file-meta">
|
||
<span>{{ a.size || '—' }}</span>
|
||
<span>{{ a.time || '—' }}</span>
|
||
</div>
|
||
</div>
|
||
<div class="file-ops">
|
||
<button type="button" class="btn-preview" title="预览" @click="toast('预览 ' + a.name)">
|
||
<van-icon name="eye-o" />
|
||
</button>
|
||
<button type="button" class="btn-download" title="下载" @click="toast('下载 ' + a.name)">
|
||
<svg class="icon-download" viewBox="0 0 24 24" aria-hidden="true">
|
||
<path fill="currentColor" d="M12 3a1 1 0 0 1 1 1v8.59l2.3-2.3a1 1 0 1 1 1.4 1.42l-4 4a1 1 0 0 1-1.4 0l-4-4a1 1 0 1 1 1.4-1.42L11 12.59V4a1 1 0 0 1 1-1Z"/>
|
||
<path fill="currentColor" d="M5 15a1 1 0 0 1 1 1v2h12v-2a1 1 0 1 1 2 0v2a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-2a1 1 0 0 1 1-1Z"/>
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="detail-section">
|
||
<div class="sec-hd">
|
||
<h3>流转记录</h3>
|
||
<span style="font-size:11px;color:#8aa198;">{{ detail.logs.length }} 步</span>
|
||
</div>
|
||
<div class="flow-list">
|
||
<div
|
||
v-for="(log, idx) in detail.logs"
|
||
:key="idx"
|
||
class="flow-item"
|
||
:class="log.tone"
|
||
>
|
||
<div class="flow-rail">
|
||
<div class="flow-dot">
|
||
<van-icon :name="log.icon || 'checked'" />
|
||
</div>
|
||
</div>
|
||
<div class="flow-card">
|
||
<div class="flow-top">
|
||
<div class="flow-title">{{ log.title }}</div>
|
||
<span class="flow-badge">{{ log.badge || (log.tone === 'warn' ? '进行中' : '已完成') }}</span>
|
||
</div>
|
||
<div class="flow-desc">{{ log.desc }}</div>
|
||
<div class="flow-meta">
|
||
<span class="chip" v-if="log.actor">
|
||
<van-icon name="manager-o" />{{ log.actor }}
|
||
</span>
|
||
<span class="chip">
|
||
<van-icon name="clock-o" />{{ log.time }}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="detail-footer">
|
||
<van-button plain hairline type="default" @click="screen = 'list'">返回</van-button>
|
||
<van-button type="primary" @click="openForm('edit', detail)">编辑处理</van-button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- ===== 新增 / 编辑 ===== -->
|
||
<template v-else-if="screen === 'form'">
|
||
<div class="page-nav">
|
||
<van-nav-bar
|
||
:title="formMode === 'create' ? '新增业务' : '编辑业务'"
|
||
left-arrow
|
||
@click-left="screen = 'list'"
|
||
/>
|
||
</div>
|
||
<div class="screen-scroll form-page">
|
||
<div class="form-section-title">基础信息</div>
|
||
<van-form @submit="onSubmit">
|
||
<van-cell-group inset>
|
||
<van-field
|
||
v-model="form.name"
|
||
name="name"
|
||
label="名称"
|
||
placeholder="请输入名称"
|
||
:rules="[{ required: true, message: '请填写名称' }]"
|
||
/>
|
||
<van-field
|
||
v-model="form.code"
|
||
name="code"
|
||
label="业务单号"
|
||
placeholder="系统自动生成或手工录入"
|
||
/>
|
||
<van-field
|
||
v-model="form.typeLabel"
|
||
is-link
|
||
readonly
|
||
name="type"
|
||
label="类型"
|
||
placeholder="请选择类型"
|
||
@click="showTypePicker = true"
|
||
/>
|
||
<van-field name="urgent" label="加急">
|
||
<template #input>
|
||
<van-switch v-model="form.urgent" size="20px" active-color="#0a9f6d" />
|
||
</template>
|
||
</van-field>
|
||
</van-cell-group>
|
||
|
||
<div class="form-section-title">业务详情</div>
|
||
<van-cell-group inset>
|
||
<van-field
|
||
v-model="form.owner"
|
||
name="owner"
|
||
label="负责人"
|
||
placeholder="请输入负责人"
|
||
/>
|
||
<van-field
|
||
v-model="form.date"
|
||
is-link
|
||
readonly
|
||
name="date"
|
||
label="业务日期"
|
||
placeholder="选择日期"
|
||
@click="showDate = true"
|
||
/>
|
||
<van-field
|
||
v-model="form.amount"
|
||
type="number"
|
||
name="amount"
|
||
label="金额"
|
||
placeholder="0.00"
|
||
>
|
||
<template #right-icon>元</template>
|
||
</van-field>
|
||
<van-field
|
||
v-model="form.remark"
|
||
rows="3"
|
||
autosize
|
||
type="textarea"
|
||
maxlength="200"
|
||
name="remark"
|
||
label="备注"
|
||
placeholder="选填,最多 200 字"
|
||
show-word-limit
|
||
/>
|
||
</van-cell-group>
|
||
|
||
<div class="form-section-title">附件</div>
|
||
<van-cell-group inset>
|
||
<van-cell title="协议附件" label="支持多附件,逗号分隔存储">
|
||
<template #value>
|
||
<div style="display:flex;gap:8px;justify-content:flex-end;">
|
||
<div class="upload-card">
|
||
<van-icon name="plus" size="20" />
|
||
<span>上传</span>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</van-cell>
|
||
<van-cell>
|
||
<div style="display:flex;gap:8px;flex-wrap:wrap;">
|
||
<div class="attach-card">
|
||
<div class="name">协议扫描件.pdf</div>
|
||
<div class="ops">
|
||
<span>预览</span>
|
||
<span>删除</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</van-cell>
|
||
</van-cell-group>
|
||
|
||
<p class="form-tip">表单:label 在左、控件在右(Vant Field 默认);必填项校验失败时 Toast / 字段红字提示。底栏固定「取消 / 保存」。</p>
|
||
|
||
<div class="form-footer">
|
||
<van-button plain hairline type="default" @click="screen = 'list'">取消</van-button>
|
||
<van-button round block type="primary" native-type="submit">保存</van-button>
|
||
</div>
|
||
</van-form>
|
||
</div>
|
||
|
||
<van-popup v-model:show="showTypePicker" position="bottom" round>
|
||
<van-picker
|
||
:columns="typeColumns"
|
||
@confirm="onTypeConfirm"
|
||
@cancel="showTypePicker = false"
|
||
/>
|
||
</van-popup>
|
||
|
||
<van-calendar
|
||
v-model:show="showDate"
|
||
@confirm="onDateConfirm"
|
||
color="#0a9f6d"
|
||
/>
|
||
</template>
|
||
|
||
<!-- ===== 组件风格 ===== -->
|
||
<template v-else>
|
||
<div class="comps-page">
|
||
<div class="screen-scroll">
|
||
<header class="comps-hero">
|
||
<van-nav-bar left-arrow @click-left="screen = 'home'">
|
||
<template #title>
|
||
<span style="font-weight:600;">组件风格</span>
|
||
</template>
|
||
</van-nav-bar>
|
||
<div class="comps-hero-copy">
|
||
<div class="eyebrow">DESIGN SYSTEM</div>
|
||
<h2>组件合集</h2>
|
||
<div class="subline">按钮 · 状态 · 反馈 · 步骤 · 附件等基础样式规范</div>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="comp-section">
|
||
<section class="comp-block">
|
||
<div class="comp-hd">
|
||
<div class="hd-icon"><van-icon name="font-o" /></div>
|
||
<div>
|
||
<h4>按钮 Button</h4>
|
||
<div class="hd-sub">每组仅一个实心主色行动</div>
|
||
</div>
|
||
</div>
|
||
<div class="comp-bd">
|
||
<div class="comp-row">
|
||
<span class="label-muted">常用尺寸</span>
|
||
<van-button type="primary" size="small" round>主按钮</van-button>
|
||
<van-button type="primary" size="small" plain hairline round>描边主色</van-button>
|
||
<van-button type="default" size="small" plain hairline round>次要</van-button>
|
||
<van-button type="danger" size="small" plain hairline round>危险</van-button>
|
||
</div>
|
||
<div class="comp-row">
|
||
<van-button type="primary" block round style="height:44px;">通栏主按钮(44px)</van-button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="comp-block status-demo">
|
||
<div class="comp-hd">
|
||
<div class="hd-icon tone-amber"><van-icon name="label-o" /></div>
|
||
<div>
|
||
<h4>状态 Tag</h4>
|
||
<div class="hd-sub">语义色:待处理 / 进行中 / 完成 / 异常</div>
|
||
</div>
|
||
</div>
|
||
<div class="comp-bd">
|
||
<div class="tag-grid">
|
||
<span class="demo-tag t-warn"><i></i>待处理</span>
|
||
<span class="demo-tag t-primary"><i></i>进行中</span>
|
||
<span class="demo-tag t-success"><i></i>已完成</span>
|
||
<span class="demo-tag t-danger"><i></i>异常</span>
|
||
<span class="demo-tag t-muted"><i></i>已作废</span>
|
||
</div>
|
||
<div class="comp-row" style="margin-top:12px;">
|
||
<span class="label-muted">Vant Tag 兼容</span>
|
||
<van-tag type="warning" plain round>待处理</van-tag>
|
||
<van-tag type="primary" round>进行中</van-tag>
|
||
<van-tag type="success" plain round>已完成</van-tag>
|
||
<van-tag type="danger" plain round>异常</van-tag>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="comp-block">
|
||
<div class="comp-hd">
|
||
<div class="hd-icon tone-slate"><van-icon name="orders-o" /></div>
|
||
<div>
|
||
<h4>单元格 Cell</h4>
|
||
<div class="hd-sub">分组列表与跳转行</div>
|
||
</div>
|
||
</div>
|
||
<div class="comp-bd">
|
||
<div class="comp-cell-list">
|
||
<button type="button" class="comp-cell-item" @click="toast('查看内容')">
|
||
<span class="ci-icon"><van-icon name="orders-o" /></span>
|
||
<span class="ci-body">
|
||
<span class="ci-title">标题</span>
|
||
<span class="ci-label">基础信息展示</span>
|
||
</span>
|
||
<span class="ci-right"><span class="val">内容</span></span>
|
||
</button>
|
||
<button type="button" class="comp-cell-item" @click="toast('跳转详情')">
|
||
<span class="ci-icon tone-amber"><van-icon name="guide-o" /></span>
|
||
<span class="ci-body">
|
||
<span class="ci-title">可跳转</span>
|
||
<span class="ci-label">点击进入下一页</span>
|
||
</span>
|
||
<span class="ci-right">
|
||
<span class="val">详情</span>
|
||
<van-icon name="arrow" />
|
||
</span>
|
||
</button>
|
||
<button type="button" class="comp-cell-item" @click="toast('状态:启用')">
|
||
<span class="ci-icon tone-slate"><van-icon name="manager-o" /></span>
|
||
<span class="ci-body">
|
||
<span class="ci-title">带标签</span>
|
||
<span class="ci-label">辅助说明文字</span>
|
||
</span>
|
||
<span class="ci-right">
|
||
<span class="demo-tag t-success" style="padding:3px 8px;font-size:11px;"><i></i>启用</span>
|
||
</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="comp-block">
|
||
<div class="comp-hd">
|
||
<div class="hd-icon"><van-icon name="volume-o" /></div>
|
||
<div>
|
||
<h4>通知 / 空态 / 加载</h4>
|
||
<div class="hd-sub">提示条与占位反馈</div>
|
||
</div>
|
||
</div>
|
||
<div class="comp-bd">
|
||
<div class="comp-notice-stack">
|
||
<div class="comp-notice-bar">
|
||
<van-notice-bar
|
||
left-icon="volume-o"
|
||
text="移动端菜单在「菜单管理 → 移动端菜单」下配置,按角色授权后展示。"
|
||
background="#e9f9f2"
|
||
color="#087f57"
|
||
/>
|
||
</div>
|
||
<div class="comp-empty-box">
|
||
<van-empty description="暂无相关数据" image-size="110" />
|
||
<div class="empty-tip">可下拉刷新,或点击上方按钮新增一条记录</div>
|
||
</div>
|
||
<div class="comp-loading-box">
|
||
<van-loading type="spinner" color="#0a9f6d" size="28px" />
|
||
<div>内容加载中…</div>
|
||
<div class="load-sub">正在拉取最新业务数据</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="comp-block">
|
||
<div class="comp-hd">
|
||
<div class="hd-icon tone-amber"><van-icon name="comment-o" /></div>
|
||
<div>
|
||
<h4>反馈 Feedback</h4>
|
||
<div class="hd-sub">Toast / Dialog / ActionSheet</div>
|
||
</div>
|
||
</div>
|
||
<div class="comp-bd">
|
||
<div class="comp-feedback-grid">
|
||
<button type="button" class="fb-btn fb-toast" @click="toast('操作成功')">
|
||
<span class="fb-icon"><van-icon name="comment-o" /></span>
|
||
Toast
|
||
</button>
|
||
<button type="button" class="fb-btn fb-dialog" @click="showDialog">
|
||
<span class="fb-icon"><van-icon name="info-o" /></span>
|
||
Dialog
|
||
</button>
|
||
<button type="button" class="fb-btn fb-sheet" @click="showAction = true">
|
||
<span class="fb-icon"><van-icon name="wap-nav" /></span>
|
||
Sheet
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="comp-block">
|
||
<div class="comp-hd">
|
||
<div class="hd-icon"><van-icon name="cluster-o" /></div>
|
||
<div>
|
||
<h4>分段 / 步骤</h4>
|
||
<div class="hd-sub">筛选分段与流程进度</div>
|
||
</div>
|
||
</div>
|
||
<div class="comp-bd">
|
||
<div class="comp-seg">
|
||
<button type="button" :class="{ active: compTab === 0 }" @click="compTab = 0">资料</button>
|
||
<button type="button" :class="{ active: compTab === 1 }" @click="compTab = 1">进度</button>
|
||
<button type="button" :class="{ active: compTab === 2 }" @click="compTab = 2">附件</button>
|
||
</div>
|
||
<div class="comp-steps-wrap">
|
||
<van-steps :active="compTab === 2 ? 2 : 1" active-color="#0a9f6d" inactive-color="#dcebe4">
|
||
<van-step>提交</van-step>
|
||
<van-step>审批中</van-step>
|
||
<van-step>完成</van-step>
|
||
</van-steps>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<section class="comp-block">
|
||
<div class="comp-hd">
|
||
<div class="hd-icon tone-rose"><van-icon name="description" /></div>
|
||
<div>
|
||
<h4>附件卡片</h4>
|
||
<div class="hd-sub">上传入口与文件摘要卡</div>
|
||
</div>
|
||
</div>
|
||
<div class="comp-bd">
|
||
<div class="comp-attach-list">
|
||
<button type="button" class="comp-upload-tile" @click="toast('上传附件')">
|
||
<span class="up-icon"><van-icon name="plus" /></span>
|
||
<span class="up-text">
|
||
<span class="up-title">添加附件</span>
|
||
<span class="up-sub">支持 PDF / 图片 / Office,点按即可上传</span>
|
||
</span>
|
||
<van-icon name="arrow" color="#0a9f6d" />
|
||
</button>
|
||
<div class="comp-file-card">
|
||
<div class="file-icon">
|
||
<van-icon name="records" />
|
||
<span class="ext">DOCX</span>
|
||
</div>
|
||
<div class="file-body">
|
||
<div class="file-name">质检报告.docx</div>
|
||
<div class="file-meta">
|
||
<span>1.2 MB</span>
|
||
<span>08-10 10:20</span>
|
||
</div>
|
||
</div>
|
||
<div class="file-ops">
|
||
<button type="button" class="btn-preview" @click="toast('预览')">
|
||
<van-icon name="eye-o" />
|
||
</button>
|
||
<button type="button" class="btn-del" @click="toast('删除')">
|
||
<van-icon name="delete-o" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<van-action-sheet
|
||
v-model:show="showAction"
|
||
:actions="actions"
|
||
cancel-text="取消"
|
||
close-on-click-action
|
||
@select="onActionSelect"
|
||
/>
|
||
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script src="https://cdn.jsdelivr.net/npm/vue@3.5.13/dist/vue.global.prod.js"></script>
|
||
<script src="https://cdn.jsdelivr.net/npm/vant@4.9.21/lib/vant.min.js"></script>
|
||
<script>
|
||
const { createApp, computed, reactive, ref } = Vue
|
||
|
||
createApp({
|
||
setup() {
|
||
const screen = ref('home')
|
||
const screens = [
|
||
{ key: 'home', label: '1. 首页 · 权限菜单卡片' },
|
||
{ key: 'list', label: '2. 列表界面' },
|
||
{ key: 'detail', label: '3. 详情界面' },
|
||
{ key: 'form', label: '4. 新增 / 编辑表单' },
|
||
{ key: 'comps', label: '5. 组件风格合集' }
|
||
]
|
||
|
||
const userName = '张三'
|
||
const roleName = '业务专员'
|
||
const deptName = '样本运营部'
|
||
|
||
const allMenus = [
|
||
{ id: 'm1', title: '待办审批', icon: 'todo-list-o', tone: '', path: 'list' },
|
||
{ id: 'm2', title: '采集协议', icon: 'orders-o', tone: '', path: 'list' },
|
||
{ id: 'm3', title: '缴费确认', icon: 'balance-pay', tone: 'tone-yellow', path: 'list' },
|
||
{ id: 'm4', title: '样本接收', icon: 'logistics', tone: '', path: 'list' },
|
||
{ id: 'm5', title: '质检工单', icon: 'certificate', tone: 'tone-slate', path: 'list' },
|
||
{ id: 'm6', title: '生产任务', icon: 'cluster-o', tone: '', path: 'list' },
|
||
{ id: 'm7', title: '库位出库', icon: 'send-gift-o', tone: 'tone-yellow', path: 'list' },
|
||
{ id: 'm8', title: '我的', icon: 'user-o', tone: 'tone-slate', path: 'comps' }
|
||
]
|
||
|
||
const grantedMap = reactive({
|
||
m1: true, m2: true, m3: true, m4: true,
|
||
m5: true, m6: false, m7: true, m8: true
|
||
})
|
||
|
||
const visibleMenus = computed(() =>
|
||
allMenus.filter((m) => grantedMap[m.id])
|
||
)
|
||
|
||
const todoStats = { approve: 3, pay: 1, alert: 1 }
|
||
const todoItems = [
|
||
{
|
||
id: 't1',
|
||
title: '采集协议审批',
|
||
desc: '王女士 · 分期首期待复核',
|
||
tag: '待审批',
|
||
tagType: 'warning',
|
||
time: '09:20',
|
||
icon: 'todo-list-o',
|
||
iconClass: 'i-approve'
|
||
},
|
||
{
|
||
id: 't2',
|
||
title: '缴费确认',
|
||
desc: 'XY20260810001 · ¥12,800',
|
||
tag: '待确认',
|
||
tagType: 'warning',
|
||
time: '昨天',
|
||
icon: 'balance-pay',
|
||
iconClass: 'i-pay'
|
||
},
|
||
{
|
||
id: 't3',
|
||
title: '质检异常',
|
||
desc: 'QC-8821 · 结论待录入',
|
||
tag: '异常',
|
||
tagType: 'danger',
|
||
time: '08-08',
|
||
icon: 'warning-o',
|
||
iconClass: 'i-alert'
|
||
}
|
||
]
|
||
|
||
const keyword = ref('')
|
||
const listTab = ref(0)
|
||
const listTabs = [
|
||
{ key: 'all', label: '全部' },
|
||
{ key: 'doing', label: '进行中' },
|
||
{ key: 'pending', label: '待处理' },
|
||
{ key: 'done', label: '已完成' }
|
||
]
|
||
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-pay', 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: '已完成' }
|
||
]
|
||
}
|
||
]
|
||
|
||
const detail = ref(null)
|
||
|
||
function openDetail(item) {
|
||
detail.value = item || listData[0]
|
||
screen.value = 'detail'
|
||
}
|
||
|
||
function goScreen(key) {
|
||
if (key === 'detail' && !detail.value) detail.value = listData[0]
|
||
screen.value = key
|
||
}
|
||
|
||
function fileExt(name) {
|
||
const i = (name || '').lastIndexOf('.')
|
||
return i >= 0 ? name.slice(i + 1).slice(0, 4) : 'FILE'
|
||
}
|
||
|
||
function fileTypeClass(name) {
|
||
const ext = fileExt(name).toLowerCase()
|
||
if (ext === 'pdf') return 'ft-pdf'
|
||
if (['jpg', 'jpeg', 'png', 'gif', 'webp'].includes(ext)) return 'ft-img'
|
||
if (['xls', 'xlsx', 'csv'].includes(ext)) return 'ft-xls'
|
||
if (['doc', 'docx'].includes(ext)) return 'ft-doc'
|
||
return 'ft-other'
|
||
}
|
||
|
||
function fileTypeIcon(name) {
|
||
const cls = fileTypeClass(name)
|
||
if (cls === 'ft-pdf') return 'description'
|
||
if (cls === 'ft-img') return 'photo-o'
|
||
if (cls === 'ft-xls') return 'bar-chart-o'
|
||
if (cls === 'ft-doc') return 'records'
|
||
return 'bag-o'
|
||
}
|
||
|
||
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
|
||
}
|
||
|
||
function tabCount(idx) {
|
||
return rowsByTab(idx).length
|
||
}
|
||
|
||
const filteredList = computed(() => {
|
||
const kw = keyword.value.trim()
|
||
let rows = rowsByTab(listTab.value)
|
||
if (!kw) return rows
|
||
return rows.filter((r) =>
|
||
r.title.includes(kw) || r.code.includes(kw) || r.owner.includes(kw)
|
||
)
|
||
})
|
||
|
||
function goListTab(idx) {
|
||
listTab.value = idx
|
||
screen.value = 'list'
|
||
}
|
||
|
||
const formMode = ref('create')
|
||
const form = reactive({
|
||
name: '',
|
||
code: '',
|
||
typeLabel: '',
|
||
urgent: false,
|
||
owner: '',
|
||
date: '',
|
||
amount: '',
|
||
remark: ''
|
||
})
|
||
const showTypePicker = ref(false)
|
||
const showDate = ref(false)
|
||
const typeColumns = [
|
||
{ text: '采集协议', value: 'protocol' },
|
||
{ text: '样本接收', value: 'sample' },
|
||
{ text: '质检工单', value: 'qc' },
|
||
{ text: '出库申请', value: 'outbound' }
|
||
]
|
||
|
||
function resetForm() {
|
||
form.name = ''
|
||
form.code = ''
|
||
form.typeLabel = ''
|
||
form.urgent = false
|
||
form.owner = userName
|
||
form.date = ''
|
||
form.amount = ''
|
||
form.remark = ''
|
||
}
|
||
|
||
function openForm(mode, item) {
|
||
formMode.value = mode
|
||
if (mode === 'edit' && item) {
|
||
form.name = item.title
|
||
form.code = item.code
|
||
form.typeLabel = '采集协议'
|
||
form.urgent = item.tagType === 'danger'
|
||
form.owner = item.owner
|
||
form.date = '2026-08-10'
|
||
form.amount = '12800'
|
||
form.remark = item.desc
|
||
} else {
|
||
resetForm()
|
||
}
|
||
screen.value = 'form'
|
||
}
|
||
|
||
function onTypeConfirm({ selectedOptions }) {
|
||
form.typeLabel = selectedOptions[0]?.text || ''
|
||
showTypePicker.value = false
|
||
}
|
||
|
||
function onDateConfirm(val) {
|
||
const d = val
|
||
const y = d.getFullYear()
|
||
const m = String(d.getMonth() + 1).padStart(2, '0')
|
||
const day = String(d.getDate()).padStart(2, '0')
|
||
form.date = `${y}-${m}-${day}`
|
||
showDate.value = false
|
||
}
|
||
|
||
function onSubmit() {
|
||
vant.showToast({ message: formMode.value === 'create' ? '已保存(新增)' : '已保存(编辑)', type: 'success' })
|
||
screen.value = 'list'
|
||
}
|
||
|
||
function onMenuTap(m) {
|
||
if (m.path === 'list') screen.value = 'list'
|
||
else if (m.path === 'comps') screen.value = 'comps'
|
||
else vant.showToast(m.title)
|
||
}
|
||
|
||
function toast(msg) {
|
||
vant.showToast(msg)
|
||
}
|
||
|
||
function showDialog() {
|
||
vant.showConfirmDialog({
|
||
title: '确认提交',
|
||
message: '提交后将进入审批流程,是否继续?',
|
||
confirmButtonText: '确认',
|
||
confirmButtonColor: '#0a9f6d'
|
||
}).then(() => {
|
||
vant.showToast('已确认')
|
||
}).catch(() => {})
|
||
}
|
||
|
||
const showAction = ref(false)
|
||
const actions = [
|
||
{ name: '查看', color: '#1f2a26' },
|
||
{ name: '编辑', color: '#0a9f6d' },
|
||
{ name: '作废', color: '#d14343' }
|
||
]
|
||
const compTab = ref(0)
|
||
|
||
function onActionSelect(action) {
|
||
vant.showToast(action.name)
|
||
}
|
||
|
||
return {
|
||
screen, screens, goScreen, userName, roleName, deptName,
|
||
allMenus, grantedMap, visibleMenus,
|
||
todoStats, todoItems, goListTab,
|
||
keyword, listTab, listTabs, tabCount, filteredList,
|
||
detail, openDetail, fileExt, fileTypeClass, fileTypeIcon,
|
||
formMode, form, showTypePicker, showDate, typeColumns,
|
||
openForm, onTypeConfirm, onDateConfirm, onSubmit, onMenuTap,
|
||
toast, showDialog, showAction, actions, onActionSelect, compTab
|
||
}
|
||
}
|
||
}).use(vant).mount('#app')
|
||
</script>
|
||
</body>
|
||
</html>
|