/* ============================================================ 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; }