2187 lines
47 KiB
CSS
2187 lines
47 KiB
CSS
/* ============================================================
|
||
BX-Mobile Global Styles — V1 Fresh Mint 清新薄荷
|
||
Directly from v1-fresh-mint.html design spec
|
||
============================================================ */
|
||
|
||
/* ----------------------------------------------------------
|
||
1. CSS VARIABLES (Design Tokens)
|
||
---------------------------------------------------------- */
|
||
:root {
|
||
--primary: #00c98d;
|
||
--primary-dark: #00a874;
|
||
--primary-darker: #008f62;
|
||
--primary-bg: #eafff6;
|
||
--primary-light: #f0fdf9;
|
||
|
||
--success: #10b981;
|
||
--success-bright: #34d399;
|
||
--success-bg: #ecfdf5;
|
||
--success-border: #a7f3d0;
|
||
|
||
--warning: #f59e0b;
|
||
--warning-bright: #fbbf24;
|
||
--warning-bg: #fffbeb;
|
||
--warning-border: #fcd34d;
|
||
|
||
--danger: #ef4444;
|
||
--danger-bright: #f87171;
|
||
--danger-bg: #fef2f2;
|
||
--danger-border: #fecaca;
|
||
|
||
--info: #3b82f6;
|
||
--info-bright: #60a5fa;
|
||
--info-bg: #eff6ff;
|
||
--info-border: #93c5fd;
|
||
|
||
--processing: #6366f1;
|
||
--processing-bright: #818cf8;
|
||
--processing-bg: #eef2ff;
|
||
--processing-border: #a5b4fc;
|
||
|
||
--text: #1a1a2e;
|
||
--text-sec: #64748b;
|
||
--text-muted: #94a3b8;
|
||
|
||
--bg: #f8fafc;
|
||
--surface: #fff;
|
||
--border: #e2e8f0;
|
||
|
||
--shadow: 0 2px 12px rgba(0, 0, 0, .06);
|
||
--shadow-md: 0 4px 16px rgba(0, 0, 0, .08);
|
||
--shadow-lg: 0 8px 24px rgba(0, 0, 0, .1);
|
||
|
||
--r: 12px;
|
||
--rs: 8px;
|
||
--rl: 16px;
|
||
|
||
--font: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||
|
||
/* Vant 组件默认 --van-primary-color 指向蓝色;对齐薄荷主色,避免 Tab/指示条/开关等发蓝 */
|
||
--van-primary-color: var(--primary);
|
||
--van-tab-active-text-color: var(--primary);
|
||
}
|
||
|
||
/* ----------------------------------------------------------
|
||
2. GLOBAL RESET
|
||
---------------------------------------------------------- */
|
||
* { box-sizing: border-box; margin: 0; padding: 0 }
|
||
|
||
body {
|
||
font-family: var(--font);
|
||
font-size: 14px;
|
||
color: var(--text);
|
||
background: var(--bg);
|
||
line-height: 1.5;
|
||
-webkit-tap-highlight-color: transparent;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
a { color: inherit; text-decoration: none }
|
||
|
||
#app { min-height: 100vh }
|
||
|
||
/* ----------------------------------------------------------
|
||
3. PAGE STRUCTURE
|
||
---------------------------------------------------------- */
|
||
.page {
|
||
max-width: 430px;
|
||
margin: 0 auto;
|
||
min-height: 100vh;
|
||
padding-bottom: 80px;
|
||
/* 列表空态占位:顶栏 + Tab 等;长筛选页用 .page--empty-chrome-tall 等覆盖 */
|
||
--empty-fill-chrome: 100px;
|
||
}
|
||
|
||
.page.page--empty-chrome-medium {
|
||
--empty-fill-chrome: 150px;
|
||
}
|
||
|
||
.page.page--empty-chrome-tall {
|
||
--empty-fill-chrome: 230px;
|
||
}
|
||
|
||
.page.page--empty-chrome-wf {
|
||
--empty-fill-chrome: 280px;
|
||
}
|
||
|
||
.page-padded {
|
||
padding: 0 16px;
|
||
}
|
||
|
||
/* 顶栏(sticky topbar)与紧随的主内容区间距 5px:新建/表单/详情等统一 */
|
||
.page > .topbar + .page-padded {
|
||
padding-top: 5px;
|
||
}
|
||
|
||
/* 首屏/整页内容区加载:顶栏下方水平垂直居中(详情、工作流配置等复用) */
|
||
.page-body-loading {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
min-height: calc(100vh - 140px);
|
||
min-height: calc(100dvh - 140px);
|
||
box-sizing: border-box;
|
||
padding: 24px 16px 80px;
|
||
}
|
||
|
||
/* 弹窗、分区列表等内嵌区域加载:固定占位高度内居中 */
|
||
.page-body-loading--embed {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 100%;
|
||
min-height: 200px;
|
||
box-sizing: border-box;
|
||
padding: 24px 16px;
|
||
}
|
||
|
||
.page-body-loading .van-loading,
|
||
.page-body-loading--embed .van-loading {
|
||
color: var(--primary);
|
||
}
|
||
|
||
/* 底部弹窗:标题区距弹窗顶 16px,与下方表单卡间距 5px */
|
||
.popup-sheet-title-wrap {
|
||
padding: 16px 16px 0;
|
||
}
|
||
|
||
.popup-sheet-title-wrap + .form-card {
|
||
margin-top: 5px;
|
||
}
|
||
|
||
.popup-sheet-title-wrap + div:not(.form-card) {
|
||
margin-top: 5px;
|
||
}
|
||
|
||
/* van-dialog:标题栏与插槽内容区顶部留白 16px */
|
||
.dialog-form-body {
|
||
padding: 16px 16px 16px;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
color: var(--text-sec);
|
||
letter-spacing: .04em;
|
||
padding: 20px 16px 10px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
|
||
.section-title::before {
|
||
content: '';
|
||
width: 3px;
|
||
height: 14px;
|
||
background: var(--primary);
|
||
border-radius: 2px;
|
||
}
|
||
|
||
/* 外层已含左右 padding 的弹窗滚动区:区块标题与 KV/附件/流程正文左缘对齐 */
|
||
.popup-sheet-body .section-title {
|
||
padding-left: 0;
|
||
padding-right: 0;
|
||
}
|
||
|
||
/* .popup-sheet-title-wrap 已含左右 padding:标题与下方 form-card(margin 0 16px)左缘对齐 */
|
||
.popup-sheet-title-wrap .section-title {
|
||
padding-left: 0;
|
||
padding-right: 0;
|
||
}
|
||
|
||
/* ----------------------------------------------------------
|
||
4. TOPBAR (replaces van-nav-bar)
|
||
---------------------------------------------------------- */
|
||
.topbar {
|
||
position: sticky;
|
||
top: 0;
|
||
z-index: 50;
|
||
display: grid;
|
||
grid-template-columns: 1fr auto 1fr;
|
||
align-items: center;
|
||
height: calc(58px + env(safe-area-inset-top));
|
||
padding: env(safe-area-inset-top) 12px 0;
|
||
background: rgba(255, 255, 255, .84);
|
||
backdrop-filter: saturate(180%) blur(14px);
|
||
-webkit-backdrop-filter: saturate(180%) blur(14px);
|
||
box-shadow: 0 1px 0 rgba(217, 232, 226, .85), 0 8px 20px rgba(15, 23, 42, .06);
|
||
}
|
||
|
||
.topbar::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: 0; right: 0; top: 0;
|
||
height: calc(58px + env(safe-area-inset-top));
|
||
background: linear-gradient(135deg, rgba(16, 185, 129, .10), rgba(236, 253, 245, .55) 45%, rgba(255, 255, 255, 0) 100%);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.topbar > * { position: relative }
|
||
|
||
.topbar-left {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.topbar-back {
|
||
width: 40px; height: 40px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--text);
|
||
border-radius: 10px;
|
||
cursor: pointer;
|
||
transition: .15s;
|
||
}
|
||
|
||
.topbar-back svg { width: 22px; height: 22px; stroke-linecap: round; stroke-linejoin: round }
|
||
|
||
.topbar-back:active {
|
||
transform: scale(.96);
|
||
opacity: .75;
|
||
}
|
||
|
||
.topbar-title {
|
||
justify-self: center;
|
||
text-align: center;
|
||
font-size: 17px;
|
||
font-weight: 800;
|
||
letter-spacing: .01em;
|
||
max-width: min(52vw, 220px);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.topbar-right {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 4px;
|
||
min-width: 0;
|
||
}
|
||
|
||
.topbar-btn {
|
||
width: 34px; height: 34px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
border: 1px solid rgba(217, 232, 226, .9);
|
||
background: rgba(255, 255, 255, .75);
|
||
color: var(--text-sec);
|
||
border-radius: 10px;
|
||
cursor: pointer;
|
||
transition: .15s;
|
||
}
|
||
|
||
.topbar-btn svg { width: 19px; height: 19px; stroke-linecap: round; stroke-linejoin: round }
|
||
|
||
.topbar-btn:active {
|
||
transform: scale(.96);
|
||
background: var(--primary-bg);
|
||
color: var(--primary-dark);
|
||
border-color: rgba(16, 185, 129, .35);
|
||
}
|
||
|
||
/* ----------------------------------------------------------
|
||
5. SEARCH BOX (replaces van-search)
|
||
---------------------------------------------------------- */
|
||
.search-wrap {
|
||
padding: 12px 16px;
|
||
background: var(--surface);
|
||
}
|
||
|
||
/* 搜索条与白卡片列表之间的留白(与首条卡片间距约 5px) */
|
||
.search-wrap + .page-padded {
|
||
padding-top: 5px;
|
||
}
|
||
|
||
.search-box {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
background: var(--bg);
|
||
border: 1.5px solid var(--border);
|
||
border-radius: 20px;
|
||
padding: 0 14px;
|
||
height: 40px;
|
||
transition: .2s;
|
||
}
|
||
|
||
.search-box:focus-within {
|
||
border-color: var(--primary);
|
||
background: #fff;
|
||
box-shadow: 0 0 0 3px rgba(0, 201, 141, .12);
|
||
}
|
||
|
||
.search-box svg { width: 18px; height: 18px; color: var(--text-muted); flex-shrink: 0 }
|
||
|
||
.search-box input {
|
||
flex: 1;
|
||
border: none;
|
||
outline: none;
|
||
background: none;
|
||
font-size: 14px;
|
||
color: var(--text);
|
||
font-family: var(--font);
|
||
}
|
||
|
||
.search-box input::placeholder { color: var(--text-muted) }
|
||
|
||
.search-box__clear {
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 28px;
|
||
height: 28px;
|
||
margin: 0 -2px 0 0;
|
||
padding: 0;
|
||
border: none;
|
||
background: transparent;
|
||
border-radius: 8px;
|
||
color: var(--text-muted);
|
||
cursor: pointer;
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
|
||
.search-box__clear:active {
|
||
background: var(--primary-bg);
|
||
color: var(--text-sec);
|
||
}
|
||
|
||
.search-box__clear svg {
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
|
||
.search-filter {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
padding: 8px 0 0;
|
||
}
|
||
|
||
/* 状态 chip 较多时横向滑动(列表页与「我的申请」共用) */
|
||
.search-filter--scroll {
|
||
flex-wrap: nowrap;
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
scrollbar-width: none;
|
||
padding-bottom: 4px;
|
||
}
|
||
|
||
.search-filter--scroll::-webkit-scrollbar {
|
||
display: none;
|
||
}
|
||
|
||
.filter-tag {
|
||
padding: 4px 12px;
|
||
border-radius: 14px;
|
||
font-size: 12px;
|
||
font-weight: 500;
|
||
border: 1px solid var(--border);
|
||
color: var(--text-sec);
|
||
background: var(--surface);
|
||
cursor: pointer;
|
||
transition: .15s;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.filter-tag.active {
|
||
background: var(--primary-bg);
|
||
border-color: var(--primary);
|
||
color: var(--primary-dark);
|
||
}
|
||
|
||
.filter-tag:active { transform: scale(.95) }
|
||
|
||
/* ----------------------------------------------------------
|
||
6. STAT CARDS (horizontal scroll)
|
||
---------------------------------------------------------- */
|
||
.stat-scroll {
|
||
display: flex;
|
||
gap: 10px;
|
||
padding: 0 16px 4px;
|
||
overflow-x: auto;
|
||
-webkit-overflow-scrolling: touch;
|
||
scrollbar-width: none;
|
||
}
|
||
|
||
.stat-scroll::-webkit-scrollbar { display: none }
|
||
|
||
.stat-card {
|
||
flex-shrink: 0;
|
||
width: 100px;
|
||
padding: 14px 12px;
|
||
border-radius: var(--r);
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 6px;
|
||
box-shadow: var(--shadow);
|
||
cursor: pointer;
|
||
transition: .2s;
|
||
}
|
||
|
||
.stat-card:active { transform: scale(.96) }
|
||
|
||
.stat-card.active {
|
||
background: var(--primary-bg);
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
.stat-card .num {
|
||
font-size: 22px;
|
||
font-weight: 800;
|
||
color: var(--primary);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.stat-card .label {
|
||
font-size: 11px;
|
||
color: var(--text-sec);
|
||
font-weight: 500;
|
||
}
|
||
|
||
.stat-card.warn .num { color: var(--danger) }
|
||
|
||
.stat-card.warn.active {
|
||
background: #fef2f2;
|
||
border-color: var(--danger);
|
||
}
|
||
|
||
/* ----------------------------------------------------------
|
||
7. EXPENSE LIST CARD
|
||
---------------------------------------------------------- */
|
||
.expense-card {
|
||
background: var(--surface);
|
||
border-radius: var(--r);
|
||
padding: 16px;
|
||
margin-bottom: 10px;
|
||
border: 1px solid var(--border);
|
||
box-shadow: var(--shadow);
|
||
cursor: pointer;
|
||
transition: .15s;
|
||
}
|
||
|
||
.expense-card:active {
|
||
transform: scale(.985);
|
||
box-shadow: 0 1px 6px rgba(0, 0, 0, .04);
|
||
}
|
||
|
||
.ec-top {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
gap: 10px;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.ec-type {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 6px;
|
||
min-width: 0;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
line-height: 1.3;
|
||
}
|
||
|
||
.ec-top .status-tag {
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
/* 列表/详情状态徽标(与 StatusTag 组件统一为 tag pill) */
|
||
.status-tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 2px 10px;
|
||
border-radius: 10px;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
border: 1px solid var(--border);
|
||
white-space: nowrap;
|
||
line-height: 1.25;
|
||
background: var(--bg);
|
||
color: var(--text-sec);
|
||
}
|
||
|
||
.status-tag.success {
|
||
background: var(--success-bg);
|
||
color: var(--success);
|
||
border-color: var(--success-border);
|
||
}
|
||
|
||
.status-tag.danger {
|
||
background: var(--danger-bg);
|
||
color: var(--danger);
|
||
border-color: var(--danger-border);
|
||
}
|
||
|
||
.status-tag.warning {
|
||
background: var(--warning-bg);
|
||
color: var(--warning);
|
||
border-color: var(--warning-border);
|
||
}
|
||
|
||
.status-tag.info {
|
||
background: var(--info-bg);
|
||
color: var(--info);
|
||
border-color: var(--info-border);
|
||
}
|
||
|
||
.status-tag.muted {
|
||
background: #f1f5f9;
|
||
color: #64748b;
|
||
border-color: rgba(100, 116, 139, .15);
|
||
}
|
||
|
||
.status-tag.processing {
|
||
background: var(--processing-bg);
|
||
color: var(--processing);
|
||
border-color: var(--processing-border);
|
||
}
|
||
|
||
.status-tag.s-draft { background: #f1f5f9; color: #64748b; border-color: rgba(100, 116, 139, .15) }
|
||
.status-tag.s-pending {
|
||
background: var(--warning-bg);
|
||
color: var(--warning);
|
||
border-color: var(--warning-border);
|
||
}
|
||
.status-tag.s-approved { background: var(--primary-bg); color: var(--primary-dark); border-color: rgba(0, 201, 141, .2) }
|
||
.status-tag.s-rejected { background: #fef2f2; color: #ef4444; border-color: rgba(239, 68, 68, .2) }
|
||
.status-tag.s-withdrawn { background: #fffbeb; color: #d97706; border-color: rgba(245, 158, 11, .25) }
|
||
.status-tag.s-issued { background: #eef2ff; color: #6366f1; border-color: rgba(99, 102, 241, .25) }
|
||
.status-tag.s-paid { background: var(--primary-bg); color: var(--primary-dark); border-color: rgba(0, 201, 141, .2) }
|
||
.status-tag.s-processed { background: var(--primary-bg); color: var(--primary-dark); border-color: rgba(0, 201, 141, .2) }
|
||
.status-tag.s-offset { background: #fffbeb; color: #d97706; border-color: rgba(245, 158, 11, .25) }
|
||
|
||
.ec-amount {
|
||
font-size: 24px;
|
||
font-weight: 800;
|
||
color: var(--primary-dark);
|
||
margin-bottom: 6px;
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.ec-amount::before {
|
||
content: '¥';
|
||
font-size: 14px;
|
||
margin-right: 2px;
|
||
}
|
||
|
||
.ec-meta {
|
||
display: flex;
|
||
gap: 12px;
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.ec-bottom {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
gap: 10px;
|
||
margin-top: 10px;
|
||
padding-top: 10px;
|
||
border-top: 1px dashed var(--border);
|
||
}
|
||
|
||
.ec-desc {
|
||
font-size: 13px;
|
||
color: var(--text-sec);
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.ec-arrow { color: var(--text-muted); flex-shrink: 0 }
|
||
|
||
/* ----------------------------------------------------------
|
||
8. BUTTONS (replaces van-button in custom layouts)
|
||
---------------------------------------------------------- */
|
||
/*
|
||
* 语义归类(全项目统一,全部为实心,无描边型主按钮):
|
||
* - btn-primary 薄荷绿实心:保存、提交、通过、登录、发放、查看、编辑、去处理、
|
||
* + 新增明细行、新增子项/子菜单 等(同语义跨页必须同类名)
|
||
* - btn-secondary 取消、上移/下移、分配用户/分配菜单(中性灰实心)
|
||
* - btn-danger 删除、驳回、退出登录(红实心)
|
||
* - btn-warning 冲抵、禁用(橙实心)
|
||
* 尺寸:默认 40px 高;列表/卡片内配合 .btn-sm(与 .op-actions 内 32px 对齐)
|
||
*/
|
||
.btn-row {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
align-items: center;
|
||
}
|
||
|
||
.btn {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
height: 40px;
|
||
padding: 0 20px;
|
||
border-radius: var(--rs);
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
font-family: var(--font);
|
||
cursor: pointer;
|
||
border: 1px solid transparent;
|
||
transition: .15s;
|
||
-webkit-user-select: none;
|
||
}
|
||
|
||
.btn:disabled {
|
||
opacity: 0.55;
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
.btn:disabled:active {
|
||
transform: none;
|
||
}
|
||
|
||
.btn:active:not(:disabled) { transform: scale(.96) }
|
||
|
||
/* 全局按钮:实心填充 */
|
||
.btn-primary {
|
||
background: var(--primary);
|
||
color: #fff;
|
||
border-color: var(--primary);
|
||
}
|
||
|
||
.btn-primary:active:not(:disabled) {
|
||
background: var(--primary-dark);
|
||
border-color: var(--primary-dark);
|
||
}
|
||
|
||
.btn-secondary {
|
||
background: var(--text-sec);
|
||
color: #fff;
|
||
border-color: var(--text-sec);
|
||
}
|
||
|
||
.btn-secondary:active:not(:disabled) {
|
||
background: #475569;
|
||
border-color: #475569;
|
||
}
|
||
|
||
.btn-danger {
|
||
background: var(--danger);
|
||
color: #fff;
|
||
border-color: var(--danger);
|
||
}
|
||
|
||
.btn-danger:active:not(:disabled) {
|
||
background: #dc2626;
|
||
border-color: #dc2626;
|
||
}
|
||
|
||
.btn-warning,
|
||
.btn-warning-outline {
|
||
background: var(--warning);
|
||
color: #fff;
|
||
border-color: var(--warning);
|
||
}
|
||
|
||
.btn-warning:active:not(:disabled),
|
||
.btn-warning-outline:active:not(:disabled) {
|
||
background: #d97706;
|
||
border-color: #d97706;
|
||
}
|
||
|
||
.btn-disabled {
|
||
background: #e2e8f0;
|
||
color: #94a3b8;
|
||
cursor: not-allowed;
|
||
border-color: #e2e8f0;
|
||
}
|
||
|
||
.btn-disabled:active { transform: none }
|
||
|
||
.btn-loading {
|
||
position: relative;
|
||
color: transparent !important;
|
||
}
|
||
|
||
.btn-loading::after {
|
||
content: '';
|
||
position: absolute;
|
||
width: 18px; height: 18px;
|
||
border: 2px solid rgba(255, 255, 255, .35);
|
||
border-top-color: #fff;
|
||
border-radius: 50%;
|
||
animation: spin .6s linear infinite;
|
||
}
|
||
|
||
.btn-danger.btn-loading::after {
|
||
border-color: rgba(255, 255, 255, .35);
|
||
border-top-color: #fff;
|
||
}
|
||
|
||
.btn-secondary.btn-loading::after {
|
||
border-color: rgba(255, 255, 255, .35);
|
||
border-top-color: #fff;
|
||
}
|
||
|
||
.btn-warning.btn-loading::after,
|
||
.btn-warning-outline.btn-loading::after {
|
||
border-color: rgba(255, 255, 255, .35);
|
||
border-top-color: #fff;
|
||
}
|
||
|
||
.btn-block { width: 100% }
|
||
|
||
.btn-sm {
|
||
height: 32px;
|
||
padding: 0 12px;
|
||
font-size: 12px;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.btn-link {
|
||
background: transparent;
|
||
border-color: transparent;
|
||
color: var(--primary-dark);
|
||
padding: 0 6px;
|
||
}
|
||
|
||
.btn-link:active:not(:disabled) {
|
||
background: var(--primary-bg);
|
||
border-color: rgba(16, 185, 129, .25);
|
||
}
|
||
|
||
@keyframes spin { to { transform: rotate(360deg) } }
|
||
|
||
/* ----------------------------------------------------------
|
||
9. FORM SYSTEM (replaces van-field)
|
||
---------------------------------------------------------- */
|
||
.form-card {
|
||
background: var(--surface);
|
||
border-radius: var(--r);
|
||
padding: 16px;
|
||
border: 1px solid var(--border);
|
||
}
|
||
|
||
.form-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
.form-header h3 {
|
||
font-size: 16px;
|
||
font-weight: 700;
|
||
}
|
||
|
||
.form-badge {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
background: var(--primary-bg);
|
||
color: var(--primary-dark);
|
||
}
|
||
|
||
.field { margin-bottom: 14px }
|
||
|
||
.field-label {
|
||
display: block;
|
||
font-size: 13px;
|
||
font-weight: 600;
|
||
color: var(--text);
|
||
margin-bottom: 6px;
|
||
}
|
||
|
||
.field-label .req { color: var(--danger); margin-left: 2px }
|
||
|
||
.form-input {
|
||
width: 100%;
|
||
height: 42px;
|
||
padding: 0 12px;
|
||
border: 1.5px solid var(--border);
|
||
border-radius: var(--rs);
|
||
font-size: 14px;
|
||
font-family: var(--font);
|
||
color: var(--text);
|
||
background: var(--surface);
|
||
transition: .2s;
|
||
outline: none;
|
||
}
|
||
|
||
.form-input:focus {
|
||
border-color: var(--primary);
|
||
box-shadow: 0 0 0 3px rgba(0, 201, 141, .1);
|
||
}
|
||
|
||
.form-input:disabled {
|
||
background: var(--bg);
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.form-input.error {
|
||
border-color: var(--danger);
|
||
box-shadow: 0 0 0 3px rgba(239, 68, 68, .1);
|
||
}
|
||
|
||
.form-input-icon { position: relative }
|
||
|
||
.form-input-icon .form-input { padding-right: 36px }
|
||
|
||
.form-input-icon svg {
|
||
position: absolute;
|
||
right: 10px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 18px;
|
||
height: 18px;
|
||
color: var(--text-muted);
|
||
}
|
||
|
||
.form-input-icon .form-input-pwd-toggle {
|
||
position: absolute;
|
||
right: 4px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
width: 32px;
|
||
height: 32px;
|
||
margin: 0;
|
||
padding: 0;
|
||
border: none;
|
||
background: transparent;
|
||
color: var(--text-muted);
|
||
cursor: pointer;
|
||
border-radius: 8px;
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
|
||
.form-input-icon .form-input-pwd-toggle:active {
|
||
background: rgba(15, 23, 42, 0.06);
|
||
}
|
||
|
||
.form-input-icon .form-input-pwd-toggle svg {
|
||
position: static;
|
||
transform: none;
|
||
right: auto;
|
||
top: auto;
|
||
width: 16px;
|
||
height: 16px;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.form-textarea {
|
||
width: 100%;
|
||
min-height: 80px;
|
||
padding: 10px 12px;
|
||
border: 1.5px solid var(--border);
|
||
border-radius: var(--rs);
|
||
font-size: 14px;
|
||
font-family: var(--font);
|
||
color: var(--text);
|
||
resize: vertical;
|
||
outline: none;
|
||
transition: .2s;
|
||
}
|
||
|
||
.form-textarea:focus {
|
||
border-color: var(--primary);
|
||
box-shadow: 0 0 0 3px rgba(0, 201, 141, .1);
|
||
}
|
||
|
||
.form-textarea.error {
|
||
border-color: var(--danger);
|
||
box-shadow: 0 0 0 3px rgba(239, 68, 68, .1);
|
||
}
|
||
|
||
.form-textarea-counter {
|
||
text-align: right;
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.field-error {
|
||
font-size: 12px;
|
||
color: var(--danger);
|
||
margin-top: 4px;
|
||
}
|
||
|
||
.form-input-prefix { position: relative }
|
||
|
||
.form-input-prefix .form-input { padding-left: 28px }
|
||
|
||
.form-input-prefix::before {
|
||
content: '¥';
|
||
position: absolute;
|
||
left: 12px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
color: var(--primary);
|
||
}
|
||
|
||
.edit-highlight .form-input {
|
||
background: var(--primary-light);
|
||
}
|
||
|
||
/* ----------------------------------------------------------
|
||
10. DETAIL PAGE
|
||
---------------------------------------------------------- */
|
||
.detail-hero {
|
||
background: linear-gradient(135deg, var(--primary-bg), var(--primary-light));
|
||
border-radius: var(--r);
|
||
padding: 20px;
|
||
text-align: center;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.detail-amount {
|
||
font-size: 32px;
|
||
font-weight: 800;
|
||
color: var(--primary-dark);
|
||
font-variant-numeric: tabular-nums;
|
||
}
|
||
|
||
.detail-amount::before {
|
||
content: '¥';
|
||
font-size: 16px;
|
||
margin-right: 4px;
|
||
}
|
||
|
||
.detail-status {
|
||
display: inline-block;
|
||
margin-top: 8px;
|
||
font-size: 12px;
|
||
font-weight: 600;
|
||
padding: 3px 12px;
|
||
border-radius: 10px;
|
||
background: var(--primary);
|
||
color: #fff;
|
||
}
|
||
|
||
.detail-grid {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 12px;
|
||
margin-bottom: 12px;
|
||
}
|
||
|
||
.detail-item {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--rs);
|
||
padding: 10px 12px;
|
||
}
|
||
|
||
.detail-item .di-label {
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
margin-bottom: 2px;
|
||
}
|
||
|
||
.detail-item .di-value {
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
color: var(--text);
|
||
word-break: break-all;
|
||
}
|
||
|
||
.detail-item.full { grid-column: 1 / -1 }
|
||
|
||
/* ----------------------------------------------------------
|
||
11. TIMELINE (approval flow)
|
||
---------------------------------------------------------- */
|
||
.timeline {
|
||
padding-left: 20px;
|
||
border-left: 2px solid var(--border);
|
||
margin: 12px 0;
|
||
}
|
||
|
||
.tl-step {
|
||
position: relative;
|
||
padding: 0 0 20px 16px;
|
||
}
|
||
|
||
.tl-step:last-child { padding-bottom: 0 }
|
||
|
||
.tl-step::before {
|
||
content: '';
|
||
position: absolute;
|
||
left: -25px;
|
||
top: 4px;
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
background: var(--border);
|
||
border: 2px solid var(--surface);
|
||
}
|
||
|
||
.tl-step.done::before {
|
||
background: var(--primary);
|
||
box-shadow: 0 0 0 2px var(--primary-bg);
|
||
}
|
||
|
||
.tl-step.current::before {
|
||
background: var(--info);
|
||
box-shadow: 0 0 0 3px rgba(59, 130, 246, .2);
|
||
animation: pulse-dot 1.5s infinite;
|
||
}
|
||
|
||
.tl-step.rejected::before {
|
||
background: var(--danger);
|
||
box-shadow: 0 0 0 2px rgba(239, 68, 68, .15);
|
||
}
|
||
|
||
@keyframes pulse-dot {
|
||
0%, 100% { box-shadow: 0 0 0 3px rgba(59, 130, 246, .2) }
|
||
50% { box-shadow: 0 0 0 6px rgba(59, 130, 246, .08) }
|
||
}
|
||
|
||
.tl-title { font-size: 13px; font-weight: 600 }
|
||
|
||
.tl-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px }
|
||
|
||
/* ----------------------------------------------------------
|
||
12. UPLOAD ZONE
|
||
---------------------------------------------------------- */
|
||
.upload-zone {
|
||
border: 2px dashed var(--border);
|
||
border-radius: var(--r);
|
||
padding: 24px;
|
||
text-align: center;
|
||
background: var(--bg);
|
||
cursor: pointer;
|
||
transition: .2s;
|
||
}
|
||
|
||
.upload-zone:active {
|
||
border-color: var(--primary);
|
||
background: var(--primary-bg);
|
||
}
|
||
|
||
.upload-zone svg { width: 36px; height: 36px; color: var(--text-muted); margin-bottom: 6px }
|
||
|
||
.upload-zone p { font-size: 13px; color: var(--text-sec) }
|
||
|
||
.upload-zone .hint { font-size: 11px; color: var(--text-muted); margin-top: 4px }
|
||
|
||
/* van-uploader 插槽:仅「上传附件」实心按钮,不占大块区域 */
|
||
.uploader-slot-btn .van-uploader {
|
||
display: inline-block;
|
||
}
|
||
|
||
.uploader-slot-btn .van-uploader__wrapper {
|
||
display: inline-flex;
|
||
}
|
||
|
||
/* ----------------------------------------------------------
|
||
13. ATTACHMENT GRID
|
||
---------------------------------------------------------- */
|
||
.att-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
gap: 8px;
|
||
}
|
||
|
||
.att-img {
|
||
position: relative;
|
||
aspect-ratio: 1;
|
||
border-radius: var(--rs);
|
||
overflow: hidden;
|
||
border: 1px solid var(--border);
|
||
background: var(--bg);
|
||
}
|
||
|
||
.att-img img { width: 100%; height: 100%; object-fit: cover }
|
||
|
||
/* 附件上传中:本地缩略图 + 半透明遮罩,避免「选完突然出现」 */
|
||
.att-img.is-uploading {
|
||
pointer-events: none;
|
||
}
|
||
|
||
.att-upload-mask {
|
||
position: absolute;
|
||
inset: 0;
|
||
z-index: 2;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 6px;
|
||
background: rgba(15, 23, 42, 0.48);
|
||
color: #fff;
|
||
}
|
||
|
||
.att-upload-mask__text {
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.02em;
|
||
line-height: 1;
|
||
}
|
||
|
||
.att-img .del {
|
||
position: absolute;
|
||
top: 4px;
|
||
right: 4px;
|
||
width: 20px;
|
||
height: 20px;
|
||
border-radius: 50%;
|
||
background: rgba(0, 0, 0, .5);
|
||
color: #fff;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 12px;
|
||
border: none;
|
||
cursor: pointer;
|
||
line-height: 1;
|
||
}
|
||
|
||
.att-file {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 12px;
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--rs);
|
||
}
|
||
|
||
.att-file-icon {
|
||
width: 36px;
|
||
height: 36px;
|
||
border-radius: 6px;
|
||
background: var(--primary-bg);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.att-file-icon svg { width: 20px; height: 20px; color: var(--primary-dark) }
|
||
|
||
.att-file-info { flex: 1; min-width: 0 }
|
||
|
||
.att-file-name {
|
||
font-size: 13px;
|
||
font-weight: 500;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.att-file-size { font-size: 11px; color: var(--text-muted) }
|
||
|
||
/* ----------------------------------------------------------
|
||
14. FORM CONTROLS
|
||
---------------------------------------------------------- */
|
||
.ctrl-group { margin-bottom: 16px }
|
||
|
||
.ctrl-label { font-size: 13px; font-weight: 600; margin-bottom: 8px }
|
||
|
||
.radio-group, .check-group {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
}
|
||
|
||
.radio-item, .check-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
font-size: 13px;
|
||
cursor: pointer;
|
||
padding: 6px 12px;
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--rs);
|
||
transition: .15s;
|
||
}
|
||
|
||
.radio-item:active, .check-item:active { background: var(--primary-bg) }
|
||
|
||
.radio-item.selected, .check-item.selected {
|
||
border-color: var(--primary);
|
||
background: var(--primary-bg);
|
||
color: var(--primary-dark);
|
||
font-weight: 600;
|
||
}
|
||
|
||
/* ----------------------------------------------------------
|
||
15. EMPTY STATE
|
||
---------------------------------------------------------- */
|
||
.empty-state {
|
||
text-align: center;
|
||
padding: 40px 20px;
|
||
}
|
||
|
||
.empty-state svg {
|
||
width: 80px;
|
||
height: 80px;
|
||
color: var(--text-muted);
|
||
margin-bottom: 12px;
|
||
opacity: .5;
|
||
}
|
||
|
||
.empty-state p {
|
||
font-size: 15px;
|
||
color: var(--text-muted);
|
||
margin-bottom: 16px;
|
||
}
|
||
|
||
/* 列表无数据:视口剩余区域水平垂直居中(--empty-fill-chrome 由 .page 修饰符控制) */
|
||
.empty-state.empty-state--fill {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-sizing: border-box;
|
||
min-height: calc(100dvh - var(--empty-fill-chrome, 100px) - env(safe-area-inset-top) - env(safe-area-inset-bottom));
|
||
min-height: calc(100vh - var(--empty-fill-chrome, 100px) - env(safe-area-inset-top) - env(safe-area-inset-bottom));
|
||
padding: 24px 20px 80px;
|
||
margin: 0 auto;
|
||
max-width: 100%;
|
||
}
|
||
|
||
/* 弹窗/卡片分区内的无数据 */
|
||
.empty-state.empty-state--embed {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-sizing: border-box;
|
||
min-height: 160px;
|
||
padding: 24px 16px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.empty-state.empty-state--fill > .van-icon:first-child,
|
||
.empty-state.empty-state--embed > .van-icon:first-child {
|
||
opacity: 0.55;
|
||
margin-bottom: 14px;
|
||
}
|
||
|
||
.empty-state.empty-state--fill p:last-child,
|
||
.empty-state.empty-state--embed p:last-child {
|
||
margin-bottom: 0;
|
||
line-height: 1.5;
|
||
}
|
||
|
||
/* ----------------------------------------------------------
|
||
16. TAGS & BADGES
|
||
---------------------------------------------------------- */
|
||
.tag {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
padding: 2px 10px;
|
||
border-radius: 10px;
|
||
font-size: 11px;
|
||
font-weight: 600;
|
||
border: 1px solid transparent;
|
||
}
|
||
|
||
.tag-green { background: var(--primary-bg); color: var(--primary-dark); border-color: rgba(0, 201, 141, .2) }
|
||
.tag-blue { background: #eff6ff; color: #3b82f6; border-color: rgba(59, 130, 246, .2) }
|
||
/** 报销等业务类型徽标(非状态):避免与「审批中」等蓝色混淆 */
|
||
.tag-amber { background: var(--warning-bg); color: var(--warning); border-color: var(--warning-border) }
|
||
.tag-neutral { background: #f1f5f9; color: #64748b; border-color: rgba(100, 116, 139, .15) }
|
||
.tag-red { background: #fef2f2; color: #ef4444; border-color: rgba(239, 68, 68, .2) }
|
||
.tag-orange { background: #fffbeb; color: #d97706; border-color: rgba(245, 158, 11, .2) }
|
||
.tag-gray { background: #f1f5f9; color: #64748b; border-color: rgba(100, 116, 139, .15) }
|
||
|
||
/* detail / approve 等区域可与 .tag 混用;列表优先 .status-tag */
|
||
|
||
.badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 18px;
|
||
height: 18px;
|
||
padding: 0 5px;
|
||
border-radius: 9px;
|
||
background: var(--danger);
|
||
color: #fff;
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
}
|
||
|
||
.badge-dot {
|
||
width: 8px;
|
||
height: 8px;
|
||
padding: 0;
|
||
min-width: 8px;
|
||
border-radius: 50%;
|
||
}
|
||
|
||
/* ----------------------------------------------------------
|
||
17. FLOW CARD (approval)
|
||
---------------------------------------------------------- */
|
||
.flow-card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--r);
|
||
padding: 16px;
|
||
}
|
||
|
||
.flow-step {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 12px;
|
||
padding-bottom: 16px;
|
||
position: relative;
|
||
}
|
||
|
||
.flow-step:last-child { padding-bottom: 0 }
|
||
|
||
.flow-step:not(:last-child)::after {
|
||
content: '';
|
||
position: absolute;
|
||
left: 15px;
|
||
top: 32px;
|
||
bottom: 0;
|
||
width: 2px;
|
||
background: var(--border);
|
||
}
|
||
|
||
.flow-avatar {
|
||
width: 32px;
|
||
height: 32px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 12px;
|
||
font-weight: 700;
|
||
color: #fff;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.flow-avatar.av-done { background: var(--primary) }
|
||
.flow-avatar.av-current { background: var(--warning); animation: pulse-dot 1.5s infinite }
|
||
.flow-avatar.av-reject { background: var(--danger) }
|
||
.flow-avatar.av-pending { background: var(--border); color: var(--text-muted) }
|
||
|
||
.flow-info { flex: 1 }
|
||
|
||
.flow-name { font-size: 13px; font-weight: 600 }
|
||
.flow-role { font-size: 11px; color: var(--text-muted) }
|
||
.flow-time { font-size: 11px; color: var(--text-muted); margin-top: 2px }
|
||
.flow-status { font-size: 11px; font-weight: 600; margin-left: auto; flex-shrink: 0 }
|
||
|
||
/* ----------------------------------------------------------
|
||
18. OP-CARD (card with action buttons)
|
||
---------------------------------------------------------- */
|
||
.card-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
}
|
||
|
||
.op-card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--r);
|
||
box-shadow: var(--shadow);
|
||
padding: 14px;
|
||
cursor: pointer;
|
||
transition: .15s;
|
||
}
|
||
|
||
.op-card:active {
|
||
transform: scale(.988);
|
||
box-shadow: 0 1px 8px rgba(0, 0, 0, .04);
|
||
}
|
||
|
||
.op-card-head {
|
||
display: flex;
|
||
align-items: flex-start;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
}
|
||
|
||
.op-card-title {
|
||
font-size: 15px;
|
||
font-weight: 900;
|
||
line-height: 1.2;
|
||
}
|
||
|
||
.op-card-sub {
|
||
margin-top: 4px;
|
||
font-size: 12px;
|
||
color: var(--text-muted);
|
||
display: flex;
|
||
gap: 10px;
|
||
flex-wrap: wrap;
|
||
}
|
||
|
||
.op-card-amount {
|
||
font-size: 20px;
|
||
font-weight: 900;
|
||
color: var(--primary-dark);
|
||
font-variant-numeric: tabular-nums;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.op-card-amount::before {
|
||
content: '¥';
|
||
font-size: 12px;
|
||
margin-right: 2px;
|
||
}
|
||
|
||
.op-card-body {
|
||
margin-top: 10px;
|
||
padding-top: 10px;
|
||
border-top: 1px dashed var(--border);
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
|
||
.op-card-body > .op-actions:only-child {
|
||
margin-left: auto;
|
||
}
|
||
|
||
.op-card-desc {
|
||
font-size: 13px;
|
||
color: var(--text-sec);
|
||
flex: 1;
|
||
min-width: 0;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.op-card-desc--wrap {
|
||
white-space: normal;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
word-break: break-word;
|
||
}
|
||
|
||
/* 列表卡片内多字段对照(op-card / expense-card 通用) */
|
||
.list-card-kv {
|
||
margin-top: 10px;
|
||
padding: 10px 12px;
|
||
border-radius: var(--rs);
|
||
background: var(--bg);
|
||
border: 1px solid var(--border);
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 6px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.list-card-kv__row {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
gap: 10px;
|
||
line-height: 1.45;
|
||
}
|
||
|
||
.list-card-kv__k {
|
||
flex-shrink: 0;
|
||
color: var(--text-muted);
|
||
font-size: 11px;
|
||
max-width: 42%;
|
||
}
|
||
|
||
.list-card-kv__v {
|
||
flex: 1;
|
||
min-width: 0;
|
||
text-align: right;
|
||
color: var(--text);
|
||
font-weight: 500;
|
||
word-break: break-word;
|
||
}
|
||
|
||
/* 详情/弹窗内无数据提示 */
|
||
.detail-section-empty {
|
||
margin: 0;
|
||
padding: 12px;
|
||
font-size: 13px;
|
||
color: var(--text-muted);
|
||
text-align: center;
|
||
background: var(--bg);
|
||
border-radius: var(--rs);
|
||
border: 1px dashed var(--border);
|
||
}
|
||
|
||
.op-card-amount-label {
|
||
margin-top: 8px;
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
font-weight: 600;
|
||
}
|
||
|
||
.op-card-reason {
|
||
margin-top: 8px;
|
||
font-size: 13px;
|
||
color: var(--text-sec);
|
||
line-height: 1.45;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 3;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
word-break: break-word;
|
||
}
|
||
|
||
.op-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.op-actions .btn {
|
||
height: 32px;
|
||
padding: 0 12px;
|
||
font-size: 12px;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
.op-actions .btn svg { width: 14px; height: 14px }
|
||
|
||
/* ----------------------------------------------------------
|
||
19. HOME HERO (首页顶部)
|
||
---------------------------------------------------------- */
|
||
.home-hero {
|
||
background: linear-gradient(135deg, #10b981 0%, #34d399 55%, #6ee7b7 100%);
|
||
color: #fff;
|
||
border-radius: 14px;
|
||
padding: 16px;
|
||
box-shadow: 0 10px 24px rgba(16, 185, 129, .25);
|
||
}
|
||
|
||
.home-hero-main { min-width: 0 }
|
||
|
||
.home-hero .hello { font-size: 12px; opacity: .9 }
|
||
|
||
.home-hero .name { font-size: 20px; font-weight: 900; margin-top: 4px }
|
||
|
||
.home-hero-stats-row {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 10px;
|
||
margin-top: 10px;
|
||
}
|
||
|
||
.home-hero-stats-row .meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 8px;
|
||
align-items: center;
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
|
||
.home-hero .chip {
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
padding: 4px 10px;
|
||
border-radius: 999px;
|
||
background: rgba(255, 255, 255, .2);
|
||
border: 1px solid rgba(255, 255, 255, .35);
|
||
}
|
||
|
||
.home-hero-logout {
|
||
flex-shrink: 0;
|
||
align-self: center;
|
||
margin: 0;
|
||
padding: 0;
|
||
border: none;
|
||
background: transparent;
|
||
font: inherit;
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
color: inherit;
|
||
opacity: .9;
|
||
cursor: pointer;
|
||
line-height: 1.2;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.home-hero-logout:active {
|
||
opacity: .75;
|
||
}
|
||
|
||
/* 首页:分组标题与菜单卡片文案使用主色文字(深色),与正文一致 */
|
||
.home-page .section-title {
|
||
color: var(--text);
|
||
}
|
||
|
||
/* ----------------------------------------------------------
|
||
20. MENU GRID
|
||
---------------------------------------------------------- */
|
||
.menu-grid {
|
||
margin-top: 12px;
|
||
display: grid;
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 10px;
|
||
}
|
||
|
||
.menu-item {
|
||
position: relative;
|
||
border: none;
|
||
background: var(--surface);
|
||
border-radius: var(--r);
|
||
padding: 10px 8px 9px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
gap: 5px;
|
||
cursor: pointer;
|
||
box-shadow: var(--shadow);
|
||
transition: background .15s, box-shadow .15s, transform .15s;
|
||
}
|
||
|
||
.menu-item:active {
|
||
transform: scale(.96);
|
||
background: var(--primary-light);
|
||
box-shadow: 0 1px 6px rgba(0, 0, 0, .05);
|
||
}
|
||
|
||
.menu-item .icon {
|
||
width: 40px;
|
||
height: 40px;
|
||
border-radius: 11px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
background: var(--primary-bg);
|
||
color: var(--primary-dark);
|
||
border: none;
|
||
}
|
||
|
||
.menu-item .txt {
|
||
font-size: 13px;
|
||
font-weight: 700;
|
||
color: var(--text);
|
||
align-self: stretch;
|
||
text-align: center;
|
||
line-height: 1.35;
|
||
white-space: pre-line;
|
||
}
|
||
|
||
.menu-item.hot .icon {
|
||
background: #eff6ff;
|
||
color: var(--info);
|
||
}
|
||
|
||
.menu-item.warn .icon {
|
||
background: #fffbeb;
|
||
color: #d97706;
|
||
}
|
||
|
||
.menu-item .msg-dot {
|
||
position: absolute;
|
||
top: 8px;
|
||
right: 10px;
|
||
width: 8px;
|
||
height: 8px;
|
||
border-radius: 50%;
|
||
background: var(--danger);
|
||
box-shadow: 0 0 0 2px var(--surface);
|
||
}
|
||
|
||
.menu-item .msg-badge {
|
||
position: absolute;
|
||
top: 4px;
|
||
right: 4px;
|
||
z-index: 1;
|
||
display: inline-flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 16px;
|
||
height: 16px;
|
||
padding: 0 4px;
|
||
font-size: 10px;
|
||
font-weight: 700;
|
||
font-variant-numeric: tabular-nums;
|
||
line-height: 1;
|
||
color: #fff;
|
||
pointer-events: none;
|
||
background: #dc2626;
|
||
border-radius: 8px;
|
||
box-shadow: 0 0 0 2px var(--surface);
|
||
}
|
||
|
||
/* ----------------------------------------------------------
|
||
21. TODO CARD
|
||
---------------------------------------------------------- */
|
||
.todo-card {
|
||
background: transparent;
|
||
border: none;
|
||
border-radius: 12px;
|
||
padding: 12px 0;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.todo-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.todo-head--badge-only {
|
||
justify-content: flex-end;
|
||
}
|
||
|
||
.todo-head .title {
|
||
font-size: 11px;
|
||
font-weight: 800;
|
||
color: var(--text);
|
||
}
|
||
|
||
.todo-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
padding: 9px 0;
|
||
border-top: 1px dashed rgba(226, 232, 240, .9);
|
||
}
|
||
|
||
.todo-item:first-of-type { border-top: none; padding-top: 0 }
|
||
|
||
.todo-item .left { min-width: 0 }
|
||
|
||
.todo-item .name {
|
||
font-size: 11px;
|
||
font-weight: 700;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.todo-item .meta { font-size: 11px; color: var(--text-muted); margin-top: 2px }
|
||
|
||
/* 待办「去处理」与列表卡片内按钮同高同圆角 */
|
||
.todo-item .btn.btn-sm {
|
||
height: 32px;
|
||
border-radius: 8px;
|
||
}
|
||
|
||
/* ----------------------------------------------------------
|
||
22. LOGIN PAGE
|
||
---------------------------------------------------------- */
|
||
.login-page {
|
||
min-height: 100vh;
|
||
min-height: 100dvh;
|
||
background: linear-gradient(180deg, #ecfdf5 0%, #f8fafc 42%, #f8fafc 100%);
|
||
padding: calc(16px + env(safe-area-inset-top, 0px)) 16px calc(16px + env(safe-area-inset-bottom, 0px));
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.login-page-inner {
|
||
width: 100%;
|
||
max-width: 400px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: stretch;
|
||
}
|
||
|
||
.login-brand-block {
|
||
text-align: center;
|
||
margin-bottom: 22px;
|
||
}
|
||
|
||
.brand-logo-img {
|
||
display: block;
|
||
width: 100%;
|
||
max-width: min(180px, 48vw);
|
||
height: auto;
|
||
margin: 0 auto 12px;
|
||
object-fit: contain;
|
||
}
|
||
|
||
.login-brand-block .brand-sub {
|
||
font-size: 22px;
|
||
font-weight: 700;
|
||
letter-spacing: 0.02em;
|
||
color: var(--text);
|
||
margin-top: 2px;
|
||
}
|
||
|
||
.login-card {
|
||
margin: 0;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
background: transparent;
|
||
border: none;
|
||
border-radius: 12px;
|
||
padding: 14px;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.login-title { font-size: 16px; font-weight: 900; margin-bottom: 12px }
|
||
|
||
.login-input {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
height: 42px;
|
||
padding: 0 12px;
|
||
border: 1.5px solid var(--border);
|
||
border-radius: 10px;
|
||
background: var(--surface);
|
||
margin-bottom: 10px;
|
||
}
|
||
|
||
.login-input:focus-within {
|
||
border-color: var(--primary);
|
||
box-shadow: 0 0 0 3px rgba(16, 185, 129, .12);
|
||
}
|
||
|
||
.login-input.error {
|
||
border-color: var(--danger);
|
||
box-shadow: 0 0 0 3px rgba(239, 68, 68, .12);
|
||
}
|
||
|
||
.login-input svg { width: 17px; height: 17px; color: var(--text-muted); flex-shrink: 0 }
|
||
|
||
.login-input input {
|
||
border: none;
|
||
outline: none;
|
||
background: none;
|
||
flex: 1;
|
||
font-size: 14px;
|
||
color: var(--text);
|
||
min-width: 0;
|
||
}
|
||
|
||
/* 密码行:右侧眼睛图标叠在输入区内,避免单独一列的割裂感 */
|
||
.login-input--password {
|
||
position: relative;
|
||
}
|
||
|
||
.login-input--password input {
|
||
padding-right: 38px;
|
||
}
|
||
|
||
.login-eye {
|
||
position: absolute;
|
||
right: 8px;
|
||
top: 50%;
|
||
transform: translateY(-50%);
|
||
width: 32px;
|
||
height: 32px;
|
||
margin: 0;
|
||
padding: 0;
|
||
border: none;
|
||
background: transparent;
|
||
border-radius: 6px;
|
||
color: var(--text-muted);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
-webkit-tap-highlight-color: transparent;
|
||
}
|
||
|
||
.login-eye svg { width: 16px; height: 16px }
|
||
|
||
.login-eye:active {
|
||
color: var(--primary-dark);
|
||
}
|
||
|
||
.login-card .btn-primary.btn-block {
|
||
margin-top: 12px;
|
||
}
|
||
|
||
.login-error-msg {
|
||
margin-top: -4px;
|
||
margin-bottom: 8px;
|
||
font-size: 12px;
|
||
color: var(--danger);
|
||
display: none;
|
||
}
|
||
|
||
.login-error-msg.show { display: block }
|
||
|
||
/* ----------------------------------------------------------
|
||
23. BOTTOM ACTION BAR
|
||
---------------------------------------------------------- */
|
||
.bottom-action-bar {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
right: 0;
|
||
padding: 12px 16px;
|
||
padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
|
||
background: var(--surface);
|
||
box-shadow: 0 -2px 12px rgba(0, 0, 0, .06);
|
||
z-index: 100;
|
||
display: flex;
|
||
gap: 10px;
|
||
}
|
||
|
||
/* 列表/管理页:右下角新增(圆形主色 + 白加号) */
|
||
.fab-add {
|
||
position: fixed;
|
||
right: 16px;
|
||
bottom: calc(20px + env(safe-area-inset-bottom, 0px));
|
||
z-index: 120;
|
||
width: 48px;
|
||
height: 48px;
|
||
padding: 0;
|
||
border: none;
|
||
border-radius: 50%;
|
||
background: var(--primary);
|
||
color: #fff;
|
||
box-shadow: 0 3px 12px rgba(0, 201, 141, .38);
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
cursor: pointer;
|
||
-webkit-tap-highlight-color: transparent;
|
||
transition: transform .15s, opacity .15s;
|
||
}
|
||
|
||
.fab-add svg {
|
||
width: 22px;
|
||
height: 22px;
|
||
stroke: currentColor;
|
||
stroke-width: 2.25;
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.fab-add:active {
|
||
transform: scale(.94);
|
||
opacity: .92;
|
||
}
|
||
|
||
/* ----------------------------------------------------------
|
||
24. PICKER TRIGGER (expense item)
|
||
---------------------------------------------------------- */
|
||
.picker-trigger {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 12px;
|
||
border: 1.5px solid var(--border);
|
||
border-radius: var(--r);
|
||
background: var(--surface);
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, .04);
|
||
cursor: pointer;
|
||
}
|
||
|
||
.picker-trigger:active {
|
||
background: var(--primary-bg);
|
||
border-color: rgba(16, 185, 129, .35);
|
||
}
|
||
|
||
.picker-trigger .left { flex: 1; min-width: 0 }
|
||
.picker-trigger .label { font-size: 11px; color: var(--text-muted); margin-bottom: 2px }
|
||
.picker-trigger .value {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.picker-trigger .ph { color: var(--text-muted); font-weight: 600 }
|
||
.picker-trigger .arrow { color: var(--text-muted); flex-shrink: 0 }
|
||
|
||
/* ----------------------------------------------------------
|
||
25. TREE (menu/expense item tree)
|
||
---------------------------------------------------------- */
|
||
.tree-card {
|
||
background: var(--surface);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--r);
|
||
box-shadow: var(--shadow);
|
||
overflow: hidden;
|
||
}
|
||
|
||
.tree-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
padding: 12px 14px;
|
||
background: linear-gradient(135deg, var(--primary-bg), #fff);
|
||
}
|
||
|
||
.tree-head .title { font-size: 14px; font-weight: 800; color: var(--text) }
|
||
.tree-head .meta { font-size: 12px; color: var(--text-sec) }
|
||
|
||
.tree { padding: 6px 0 }
|
||
|
||
.tree-node {
|
||
position: relative;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
padding: 10px 14px;
|
||
cursor: pointer;
|
||
-webkit-user-select: none;
|
||
}
|
||
|
||
.tree-node:active { background: var(--primary-bg) }
|
||
|
||
.tree-node + .tree-node { border-top: 1px solid rgba(226, 232, 240, .7) }
|
||
|
||
.tree-node .twisty {
|
||
width: 22px; height: 22px; border-radius: 7px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
background: rgba(16, 185, 129, .10);
|
||
color: var(--primary-dark);
|
||
flex-shrink: 0;
|
||
transition: .15s;
|
||
}
|
||
|
||
.tree-node.leaf .twisty { opacity: .35; background: transparent }
|
||
|
||
.tree-node .twisty svg { width: 14px; height: 14px; transition: .15s }
|
||
|
||
.tree-node.expanded > .twisty svg { transform: rotate(90deg) }
|
||
|
||
.tree-node .icon {
|
||
width: 30px; height: 30px; border-radius: 10px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
background: var(--bg);
|
||
border: 1px solid rgba(226, 232, 240, .9);
|
||
color: var(--text-sec);
|
||
flex-shrink: 0;
|
||
}
|
||
|
||
.tree-node .main { flex: 1; min-width: 0 }
|
||
|
||
.tree-node .name {
|
||
font-size: 14px;
|
||
font-weight: 700;
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.tree-node .sub {
|
||
margin-top: 2px;
|
||
font-size: 11px;
|
||
color: var(--text-muted);
|
||
white-space: nowrap;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
}
|
||
|
||
.tree-node .right { display: flex; align-items: center; gap: 8px; flex-shrink: 0 }
|
||
|
||
.tree-count {
|
||
font-size: 11px;
|
||
font-weight: 800;
|
||
color: var(--primary-dark);
|
||
background: var(--primary-bg);
|
||
border: 1px solid rgba(16, 185, 129, .22);
|
||
padding: 2px 8px;
|
||
border-radius: 999px;
|
||
}
|
||
|
||
.tree-selected {
|
||
outline: 2px solid rgba(16, 185, 129, .22);
|
||
outline-offset: -2px;
|
||
background: rgba(236, 253, 245, .55);
|
||
}
|
||
|
||
/* ----------------------------------------------------------
|
||
26. VANT MINIMAL OVERRIDES (functional only)
|
||
---------------------------------------------------------- */
|
||
/* Vant Toast 的根节点同时是 .van-popup + .van-toast;vant 中 .van-popup 白底若后加载/同特异度会盖住 toast 深色底 → 白底白字。
|
||
* 全项目凡 showToast / showSuccessToast 等均走此组件;用 !important + 字面 fallback 保证任意页面可读。
|
||
* (依赖 main.ts 中在 vant.css 之后引入本文件。) */
|
||
.van-popup.van-toast {
|
||
background: var(--van-toast-background, rgba(0, 0, 0, 0.78)) !important;
|
||
color: var(--van-toast-text-color, #fff) !important;
|
||
}
|
||
|
||
.van-popup.van-toast .van-toast__text {
|
||
color: inherit !important;
|
||
}
|
||
|
||
.van-popup.van-toast .van-toast__icon,
|
||
.van-popup.van-toast .van-icon {
|
||
color: inherit !important;
|
||
}
|
||
|
||
.van-popup.van-toast .van-toast__loading {
|
||
color: var(--van-toast-loading-icon-color, #fff) !important;
|
||
}
|
||
|
||
.van-popup.van-toast .van-loading__spinner .van-loading__dot {
|
||
background: currentColor !important;
|
||
}
|
||
|
||
.van-popup--bottom {
|
||
border-radius: 14px 14px 0 0 !important;
|
||
}
|
||
|
||
.van-overlay {
|
||
background: rgba(0, 0, 0, .42) !important;
|
||
}
|
||
|
||
.van-tabs__line {
|
||
background: var(--primary) !important;
|
||
background-color: var(--primary) !important;
|
||
border-radius: 2px !important;
|
||
height: 3px !important;
|
||
}
|
||
|
||
.van-tab--active {
|
||
color: var(--primary) !important;
|
||
font-weight: 600 !important;
|
||
}
|
||
|
||
/* 与变量链一致,防止个别主题下 Tab 文字仍回落到蓝 */
|
||
.van-tabs__nav .van-tab--active {
|
||
color: var(--primary) !important;
|
||
}
|
||
|
||
.van-loading__spinner .van-loading__dot {
|
||
background: var(--primary) !important;
|
||
}
|
||
|
||
.van-pull-refresh__head {
|
||
color: var(--primary) !important;
|
||
}
|
||
|
||
.van-switch--on {
|
||
background: var(--primary) !important;
|
||
}
|
||
|
||
.van-dialog {
|
||
border-radius: 14px !important;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* 勿对 .van-dialog__confirm 使用 color !important,否则会盖住 Vant 的 confirmButtonColor(删除确认需红色) */
|
||
.van-dialog__confirm {
|
||
font-weight: 600;
|
||
}
|
||
|
||
.van-notice-bar {
|
||
background: var(--primary-bg) !important;
|
||
color: var(--primary-dark) !important;
|
||
}
|