核心功能: - 记账CRUD(支出/收入/分类/备注/日期) - 统计分析(概览/分类占比/每日趋势) - 预算管理(按月设置/进度条/超支提醒) - 数据导出CSV 安全与认证: - HMAC-SHA256签名token认证 - 用户数据隔离 - 输入验证与错误处理 - CORS配置 前端优化: - 骨架屏加载 - 账单按日期分组 - 预算页面重构(快捷预设+Numpad) - SvgIcon组件(H5+微信双端适配) - 下拉刷新 后端优化: - 共享日期工具函数 - 数据库连接池优化 - 健康检查端点 - 优雅关闭处理 技术栈: - 前端:Uni-app (Vue 3 + Pinia) - 后端:Node.js + Express + MySQL
1639 lines
60 KiB
HTML
1639 lines
60 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
|
<title>小菜记账 — UI 精细稿</title>
|
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&family=Nunito:wght@400;500;600;700&family=Caveat:wght@400;600&display=swap" rel="stylesheet">
|
|
<style>
|
|
/* ==================== RESET ==================== */
|
|
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
html { -webkit-tap-highlight-color: transparent; }
|
|
svg { flex-shrink: 0; }
|
|
|
|
/* ==================== DESIGN TOKENS ==================== */
|
|
:root {
|
|
--primary: #FF8C69;
|
|
--primary-light: #FFB89A;
|
|
--primary-dark: #E67355;
|
|
--secondary: #6C9BCF;
|
|
--accent: #FFD166;
|
|
--bg: #FFF8F0;
|
|
--surface: #FFFFFF;
|
|
--surface-warm: #FFF0E6;
|
|
--text: #2D1B1B;
|
|
--text-sec: #8B7E7E;
|
|
--text-muted: #BFB3B3;
|
|
--success: #7BC67E;
|
|
--warning: #FFB347;
|
|
--danger: #FF6B6B;
|
|
--border: #F0E0D6;
|
|
|
|
--shadow-clay: 4px 4px 12px rgba(45,27,27,0.08), inset -2px -2px 6px rgba(45,27,27,0.04);
|
|
--shadow-clay-hover: 6px 6px 16px rgba(45,27,27,0.12), inset -2px -2px 6px rgba(45,27,27,0.04);
|
|
--shadow-clay-pressed: inset 2px 2px 6px rgba(45,27,27,0.1);
|
|
--shadow-clay-float: 8px 8px 24px rgba(45,27,27,0.1), inset -3px -3px 8px rgba(45,27,27,0.05);
|
|
--shadow-bottom: 0 -2px 12px rgba(45,27,27,0.06);
|
|
|
|
--font-title: 'Fredoka', 'PingFang SC', sans-serif;
|
|
--font-body: 'Nunito', 'PingFang SC', sans-serif;
|
|
--font-hand: 'Caveat', cursive;
|
|
|
|
--nav-h: 56px;
|
|
--safe-b: 34px;
|
|
--page-h: 812px;
|
|
--page-w: 375px;
|
|
}
|
|
|
|
/* ==================== PHONE FRAME ==================== */
|
|
body {
|
|
background: #E8DDD4;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: flex-start;
|
|
min-height: 100vh;
|
|
padding: 32px 16px;
|
|
font-family: var(--font-body);
|
|
color: var(--text);
|
|
}
|
|
|
|
.phone-frame {
|
|
width: var(--page-w);
|
|
height: var(--page-h);
|
|
background: var(--bg);
|
|
border-radius: 40px;
|
|
overflow: hidden;
|
|
position: relative;
|
|
box-shadow:
|
|
0 20px 60px rgba(45,27,27,0.15),
|
|
0 4px 16px rgba(45,27,27,0.1),
|
|
inset 0 0 0 2px rgba(255,255,255,0.3);
|
|
}
|
|
|
|
/* Status bar */
|
|
.status-bar {
|
|
height: 44px;
|
|
padding: 12px 24px 0;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-family: var(--font-title);
|
|
font-size: 14px;
|
|
font-weight: 600;
|
|
color: var(--text);
|
|
}
|
|
.status-bar .time { font-variant-numeric: tabular-nums; }
|
|
.status-bar .icons { display: flex; gap: 6px; }
|
|
.status-bar .icons svg { width: 16px; height: 16px; }
|
|
|
|
/* Pages container */
|
|
.pages-wrap {
|
|
height: calc(var(--page-h) - 44px);
|
|
position: relative;
|
|
}
|
|
.page {
|
|
position: absolute;
|
|
inset: 0;
|
|
opacity: 0;
|
|
pointer-events: none;
|
|
transition: opacity 0.25s ease, transform 0.25s ease;
|
|
transform: translateY(8px);
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
-webkit-overflow-scrolling: touch;
|
|
padding-bottom: calc(var(--nav-h) + var(--safe-b) + 16px);
|
|
}
|
|
.page::-webkit-scrollbar { display: none; }
|
|
.page.active {
|
|
opacity: 1;
|
|
pointer-events: auto;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
/* ==================== SHARED COMPONENTS ==================== */
|
|
|
|
/* Clay Card */
|
|
.clay-card {
|
|
background: var(--surface);
|
|
border-radius: 20px;
|
|
border: 2px solid var(--border);
|
|
box-shadow: var(--shadow-clay);
|
|
}
|
|
|
|
/* Bottom Nav */
|
|
.bottom-nav {
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: calc(var(--nav-h) + var(--safe-b));
|
|
padding-bottom: var(--safe-b);
|
|
background: var(--surface);
|
|
box-shadow: var(--shadow-bottom);
|
|
border-radius: 20px 20px 0 0;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-around;
|
|
z-index: 100;
|
|
}
|
|
.nav-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 2px;
|
|
cursor: pointer;
|
|
-webkit-tap-highlight-color: transparent;
|
|
min-width: 44px;
|
|
min-height: 44px;
|
|
justify-content: center;
|
|
}
|
|
.nav-item svg { width: 24px; height: 24px; stroke: var(--text-muted); fill: none; stroke-width: 2; transition: 0.2s; }
|
|
.nav-item span { font: 500 10px/1 var(--font-body); color: var(--text-muted); transition: 0.2s; }
|
|
.nav-item.active svg { stroke: var(--primary); fill: var(--primary); fill-opacity: 0.15; }
|
|
.nav-item.active span { color: var(--primary); font-weight: 600; }
|
|
|
|
.nav-add {
|
|
width: 52px; height: 52px;
|
|
border-radius: 50%;
|
|
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
|
|
display: flex; align-items: center; justify-content: center;
|
|
box-shadow: 0 4px 16px rgba(255,140,105,0.4);
|
|
margin-top: -12px;
|
|
cursor: pointer;
|
|
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
|
}
|
|
.nav-add:active { transform: scale(0.93); box-shadow: 0 2px 8px rgba(255,140,105,0.3); }
|
|
.nav-add svg { width: 28px; height: 28px; stroke: #fff; fill: none; stroke-width: 2.5; }
|
|
|
|
/* ==================== PAGE 1: HOME ==================== */
|
|
.home-header {
|
|
padding: 8px 20px 16px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
.home-header .avatar {
|
|
width: 36px; height: 36px;
|
|
border-radius: 50%;
|
|
border: 2px solid var(--border);
|
|
background: var(--surface-warm);
|
|
display: flex; align-items: center; justify-content: center;
|
|
overflow: hidden;
|
|
}
|
|
.home-header .avatar svg { width: 20px; height: 20px; stroke: var(--text-sec); }
|
|
.home-header .greeting {
|
|
font: 600 18px/1.3 var(--font-title);
|
|
color: var(--text);
|
|
}
|
|
.home-header .bell {
|
|
width: 36px; height: 36px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
cursor: pointer;
|
|
}
|
|
.home-header .bell svg { width: 22px; height: 22px; stroke: var(--text-sec); }
|
|
|
|
/* Overview Card */
|
|
.overview-card {
|
|
margin: 0 20px;
|
|
padding: 24px;
|
|
}
|
|
.overview-card .label {
|
|
font: 400 14px/1 var(--font-body);
|
|
color: var(--text-sec);
|
|
margin-bottom: 6px;
|
|
}
|
|
.overview-card .amount {
|
|
font: 600 36px/1.2 var(--font-title);
|
|
color: var(--text);
|
|
font-variant-numeric: tabular-nums;
|
|
letter-spacing: 0.5px;
|
|
}
|
|
.overview-card .amount .currency {
|
|
font-size: 22px;
|
|
font-weight: 500;
|
|
color: var(--text-sec);
|
|
margin-right: 2px;
|
|
}
|
|
|
|
/* Budget bar */
|
|
.budget-bar-wrap { margin-top: 16px; }
|
|
.budget-bar {
|
|
height: 8px; border-radius: 4px;
|
|
background: var(--border);
|
|
overflow: hidden;
|
|
}
|
|
.budget-bar .fill {
|
|
height: 100%; border-radius: 4px;
|
|
background: linear-gradient(90deg, var(--primary), var(--primary-light));
|
|
width: 65%;
|
|
transition: width 0.6s ease-out;
|
|
}
|
|
.budget-info {
|
|
display: flex; justify-content: space-between;
|
|
margin-top: 8px;
|
|
font: 400 12px/1 var(--font-body);
|
|
color: var(--text-sec);
|
|
}
|
|
.budget-info .pct {
|
|
font: 500 14px/1 var(--font-title);
|
|
color: var(--primary);
|
|
}
|
|
|
|
/* Mini stats */
|
|
.mini-stats {
|
|
display: flex; gap: 12px; margin-top: 20px;
|
|
}
|
|
.mini-stat {
|
|
flex: 1;
|
|
padding: 12px;
|
|
border-radius: 12px;
|
|
background: var(--bg);
|
|
text-align: center;
|
|
}
|
|
.mini-stat .label {
|
|
font: 400 12px/1 var(--font-body);
|
|
color: var(--text-sec);
|
|
margin-bottom: 4px;
|
|
}
|
|
.mini-stat .value {
|
|
font: 600 18px/1.2 var(--font-title);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
.mini-stat .value.income { color: var(--success); }
|
|
.mini-stat .value.expense { color: var(--danger); }
|
|
|
|
/* Quick actions */
|
|
.quick-actions {
|
|
display: flex; gap: 12px;
|
|
padding: 0 20px;
|
|
margin-top: 20px;
|
|
}
|
|
.quick-btn {
|
|
flex: 1;
|
|
height: 72px;
|
|
border-radius: 16px;
|
|
background: var(--surface);
|
|
border: 2px solid var(--border);
|
|
box-shadow: var(--shadow-clay);
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 4px;
|
|
cursor: pointer;
|
|
transition: transform 0.15s ease, box-shadow 0.15s ease;
|
|
}
|
|
.quick-btn:active {
|
|
transform: scale(0.97);
|
|
box-shadow: var(--shadow-clay-pressed);
|
|
}
|
|
.quick-btn svg { width: 26px; height: 26px; stroke-width: 2; }
|
|
.quick-btn span { font: 500 12px/1 var(--font-body); color: var(--text); }
|
|
.quick-btn.income svg { stroke: var(--success); }
|
|
.quick-btn.expense svg { stroke: var(--danger); }
|
|
.quick-btn.transfer svg { stroke: var(--secondary); }
|
|
|
|
/* Transactions */
|
|
.section-header {
|
|
display: flex; justify-content: space-between; align-items: center;
|
|
padding: 24px 20px 12px;
|
|
}
|
|
.section-header .title {
|
|
font: 500 14px/1 var(--font-body);
|
|
color: var(--text-sec);
|
|
}
|
|
.section-header .more {
|
|
font: 400 12px/1 var(--font-body);
|
|
color: var(--primary);
|
|
cursor: pointer;
|
|
}
|
|
|
|
.tx-date {
|
|
padding: 8px 20px;
|
|
font: 500 13px/1 var(--font-body);
|
|
color: var(--text-sec);
|
|
}
|
|
.tx-date:not(:first-child) { margin-top: 8px; }
|
|
|
|
.tx-item {
|
|
display: flex; align-items: center; gap: 12px;
|
|
padding: 14px 20px;
|
|
cursor: pointer;
|
|
transition: background 0.15s;
|
|
}
|
|
.tx-item:active { background: var(--surface-warm); }
|
|
.tx-item .icon-wrap {
|
|
width: 40px; height: 40px;
|
|
border-radius: 12px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
.tx-item .icon-wrap svg { width: 20px; height: 20px; stroke-width: 2; }
|
|
.tx-item .info { flex: 1; min-width: 0; }
|
|
.tx-item .info .name {
|
|
font: 500 14px/1.3 var(--font-body);
|
|
color: var(--text);
|
|
white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
|
|
}
|
|
.tx-item .info .meta {
|
|
font: 400 12px/1.3 var(--font-body);
|
|
color: var(--text-sec);
|
|
margin-top: 2px;
|
|
}
|
|
.tx-item .amount {
|
|
font: 600 16px/1 var(--font-title);
|
|
font-variant-numeric: tabular-nums;
|
|
white-space: nowrap;
|
|
}
|
|
.tx-item .amount.expense { color: var(--text); }
|
|
.tx-item .amount.income { color: var(--success); }
|
|
|
|
.tx-divider {
|
|
height: 1px;
|
|
background: var(--border);
|
|
margin: 0 20px;
|
|
}
|
|
|
|
/* ==================== PAGE 2: ADD RECORD ==================== */
|
|
.add-header {
|
|
padding: 8px 20px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
}
|
|
.add-header .close {
|
|
width: 32px; height: 32px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
cursor: pointer;
|
|
}
|
|
.add-header .close svg { width: 20px; height: 20px; stroke: var(--text-sec); }
|
|
.add-header .title { font: 500 16px/1 var(--font-body); color: var(--text); }
|
|
.add-header .save {
|
|
font: 600 14px/1 var(--font-body);
|
|
color: var(--primary);
|
|
cursor: pointer;
|
|
}
|
|
|
|
/* Toggle */
|
|
.type-toggle-wrap { display: flex; justify-content: center; margin: 12px 0; }
|
|
.type-toggle {
|
|
display: inline-flex;
|
|
background: var(--surface-warm);
|
|
border-radius: 12px;
|
|
padding: 4px;
|
|
position: relative;
|
|
}
|
|
.type-toggle .tab {
|
|
width: 80px; height: 36px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
font: 400 14px/1 var(--font-body);
|
|
color: var(--text-sec);
|
|
cursor: pointer;
|
|
border-radius: 10px;
|
|
position: relative;
|
|
z-index: 1;
|
|
transition: color 0.2s;
|
|
}
|
|
.type-toggle .tab.active {
|
|
color: var(--primary);
|
|
font-weight: 600;
|
|
}
|
|
.type-toggle .slider {
|
|
position: absolute;
|
|
top: 4px; left: 4px;
|
|
width: 80px; height: 36px;
|
|
background: var(--surface);
|
|
border-radius: 10px;
|
|
box-shadow: 2px 2px 8px rgba(45,27,27,0.08);
|
|
transition: transform 0.2s ease;
|
|
}
|
|
.type-toggle.income .slider { transform: translateX(80px); }
|
|
|
|
/* Amount input */
|
|
.amount-display {
|
|
text-align: center;
|
|
padding: 32px 20px 24px;
|
|
}
|
|
.amount-display .currency {
|
|
font: 500 24px/1 var(--font-title);
|
|
color: var(--text-muted);
|
|
margin-right: 4px;
|
|
}
|
|
.amount-display .digits {
|
|
font: 700 48px/1.2 var(--font-title);
|
|
color: var(--text);
|
|
font-variant-numeric: tabular-nums;
|
|
letter-spacing: 1px;
|
|
display: inline;
|
|
}
|
|
.amount-display .cursor {
|
|
display: inline-block;
|
|
width: 2px; height: 40px;
|
|
background: var(--primary);
|
|
margin-left: 2px;
|
|
vertical-align: middle;
|
|
animation: blink 1s step-end infinite;
|
|
}
|
|
@keyframes blink { 50% { opacity: 0; } }
|
|
|
|
/* Category grid */
|
|
.category-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 12px;
|
|
padding: 0 20px;
|
|
max-height: 240px;
|
|
overflow-y: auto;
|
|
}
|
|
.category-grid::-webkit-scrollbar { display: none; }
|
|
.cat-item {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 4px;
|
|
padding: 10px 0;
|
|
cursor: pointer;
|
|
transition: transform 0.1s;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
.cat-item:active { transform: scale(0.95); }
|
|
.cat-item .cat-icon {
|
|
width: 44px; height: 44px;
|
|
border-radius: 14px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
transition: border-color 0.2s, background 0.2s;
|
|
border: 2px solid transparent;
|
|
}
|
|
.cat-item .cat-icon svg { width: 22px; height: 22px; stroke-width: 2; transition: transform 0.2s; }
|
|
.cat-item .cat-name {
|
|
font: 400 11px/1 var(--font-body);
|
|
color: var(--text-sec);
|
|
transition: color 0.2s;
|
|
}
|
|
.cat-item.selected .cat-icon {
|
|
border-color: var(--primary);
|
|
background: #FFE8E0;
|
|
}
|
|
.cat-item.selected .cat-icon svg { transform: scale(1.1); }
|
|
.cat-item.selected .cat-name { color: var(--primary); font-weight: 600; }
|
|
|
|
/* Extra fields */
|
|
.extra-fields {
|
|
padding: 16px 20px;
|
|
display: flex; flex-direction: column; gap: 8px;
|
|
}
|
|
.extra-row {
|
|
height: 48px;
|
|
background: var(--bg);
|
|
border-radius: 12px;
|
|
padding: 0 16px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
cursor: pointer;
|
|
}
|
|
.extra-row .left {
|
|
display: flex; align-items: center; gap: 8px;
|
|
}
|
|
.extra-row .left svg { width: 18px; height: 18px; stroke: var(--text-sec); }
|
|
.extra-row .left span { font: 400 14px/1 var(--font-body); color: var(--text); }
|
|
.extra-row > svg { width: 16px; height: 16px; stroke: var(--text-muted); flex-shrink: 0; }
|
|
|
|
/* Numpad */
|
|
.numpad {
|
|
padding: 16px 16px 0;
|
|
display: grid;
|
|
grid-template-columns: repeat(4, 1fr);
|
|
gap: 8px;
|
|
}
|
|
.numpad-key {
|
|
height: 48px;
|
|
border-radius: 12px;
|
|
background: var(--bg);
|
|
display: flex; align-items: center; justify-content: center;
|
|
font: 500 22px/1 var(--font-title);
|
|
color: var(--text);
|
|
cursor: pointer;
|
|
transition: background 0.1s;
|
|
user-select: none;
|
|
-webkit-tap-highlight-color: transparent;
|
|
}
|
|
.numpad-key:active { background: var(--border); }
|
|
.numpad-key.fn {
|
|
background: #FFE8E0;
|
|
color: var(--primary);
|
|
}
|
|
.numpad-key.fn:active { background: #FFD0C0; }
|
|
.numpad-key.fn svg { width: 22px; height: 22px; stroke: var(--primary); }
|
|
.numpad-key.eq {
|
|
background: var(--primary);
|
|
color: #fff;
|
|
}
|
|
.numpad-key.eq:active { background: var(--primary-dark); }
|
|
|
|
.submit-btn {
|
|
margin: 12px 16px;
|
|
height: 52px;
|
|
border-radius: 16px;
|
|
background: linear-gradient(135deg, var(--primary), var(--primary-dark));
|
|
display: flex; align-items: center; justify-content: center;
|
|
font: 600 18px/1 var(--font-body);
|
|
color: #fff;
|
|
box-shadow: 0 4px 16px rgba(255,140,105,0.3);
|
|
cursor: pointer;
|
|
transition: transform 0.15s, box-shadow 0.15s;
|
|
gap: 8px;
|
|
}
|
|
.submit-btn:active { transform: scale(0.97); box-shadow: 0 2px 8px rgba(255,140,105,0.2); }
|
|
.submit-btn svg { width: 20px; height: 20px; stroke: #fff; }
|
|
|
|
/* ==================== PAGE 3: STATS ==================== */
|
|
.stats-header {
|
|
padding: 8px 20px 12px;
|
|
text-align: center;
|
|
font: 600 18px/1.3 var(--font-title);
|
|
color: var(--text);
|
|
}
|
|
|
|
.month-picker {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 24px;
|
|
padding: 8px 20px;
|
|
}
|
|
.month-picker .arrow {
|
|
width: 44px; height: 44px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
cursor: pointer;
|
|
}
|
|
.month-picker .arrow svg { width: 22px; height: 22px; stroke: var(--text-sec); }
|
|
.month-picker .month-text {
|
|
font: 500 16px/1 var(--font-title);
|
|
color: var(--text);
|
|
}
|
|
|
|
/* Stats overview */
|
|
.stats-overview {
|
|
margin: 16px 20px 0;
|
|
padding: 20px;
|
|
display: grid;
|
|
grid-template-columns: repeat(3, 1fr);
|
|
text-align: center;
|
|
}
|
|
.stats-overview .so-item .label {
|
|
font: 400 12px/1 var(--font-body);
|
|
color: var(--text-sec);
|
|
margin-bottom: 6px;
|
|
}
|
|
.stats-overview .so-item .value {
|
|
font: 600 20px/1.2 var(--font-title);
|
|
color: var(--text);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* Donut chart area */
|
|
.chart-card {
|
|
margin: 16px 20px 0;
|
|
padding: 20px;
|
|
}
|
|
.chart-card .chart-title {
|
|
font: 500 16px/1 var(--font-body);
|
|
color: var(--text);
|
|
margin-bottom: 16px;
|
|
}
|
|
.donut-wrap {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
}
|
|
.donut-svg { width: 180px; height: 180px; }
|
|
.donut-center {
|
|
text-align: center;
|
|
margin-top: -110px;
|
|
margin-bottom: 30px;
|
|
pointer-events: none;
|
|
}
|
|
.donut-center .total-label {
|
|
font: 400 12px/1 var(--font-body);
|
|
color: var(--text-sec);
|
|
}
|
|
.donut-center .total-value {
|
|
font: 600 20px/1.3 var(--font-title);
|
|
color: var(--text);
|
|
}
|
|
.chart-legend {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
justify-content: center;
|
|
gap: 12px 20px;
|
|
margin-top: 8px;
|
|
}
|
|
.legend-item {
|
|
display: flex; align-items: center; gap: 6px;
|
|
}
|
|
.legend-dot {
|
|
width: 10px; height: 10px;
|
|
border-radius: 50%;
|
|
}
|
|
.legend-item span {
|
|
font: 400 12px/1 var(--font-body);
|
|
color: var(--text-sec);
|
|
}
|
|
.legend-item .pct {
|
|
font: 500 12px/1 var(--font-title);
|
|
color: var(--text);
|
|
}
|
|
|
|
/* Line chart */
|
|
.line-chart-wrap { position: relative; }
|
|
.line-chart-svg { width: 100%; height: 160px; }
|
|
|
|
/* Rank list */
|
|
.rank-list { padding: 0 0 4px; }
|
|
.rank-item {
|
|
display: flex; align-items: center; gap: 12px;
|
|
margin-bottom: 16px;
|
|
}
|
|
.rank-item:last-child { margin-bottom: 0; }
|
|
.rank-num {
|
|
width: 24px;
|
|
font: 600 20px/1 var(--font-title);
|
|
text-align: center;
|
|
}
|
|
.rank-num.n1 { color: var(--primary); }
|
|
.rank-num.n2 { color: var(--accent); }
|
|
.rank-num.n3 { color: var(--secondary); }
|
|
.rank-icon {
|
|
width: 36px; height: 36px;
|
|
border-radius: 10px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
.rank-icon svg { width: 18px; height: 18px; stroke-width: 2; }
|
|
.rank-info { flex: 1; min-width: 0; }
|
|
.rank-info .name {
|
|
font: 500 14px/1 var(--font-body);
|
|
color: var(--text);
|
|
}
|
|
.rank-bar {
|
|
height: 6px; border-radius: 3px;
|
|
background: var(--border);
|
|
margin-top: 6px;
|
|
overflow: hidden;
|
|
}
|
|
.rank-bar .fill {
|
|
height: 100%; border-radius: 3px;
|
|
transition: width 0.6s ease-out;
|
|
}
|
|
.rank-info .meta {
|
|
font: 400 12px/1 var(--font-body);
|
|
color: var(--text-sec);
|
|
margin-top: 4px;
|
|
}
|
|
.rank-amount {
|
|
font: 600 16px/1 var(--font-title);
|
|
color: var(--text);
|
|
font-variant-numeric: tabular-nums;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
/* ==================== PAGE 4: PROFILE ==================== */
|
|
.profile-card {
|
|
margin: 0 20px;
|
|
padding: 24px;
|
|
background: linear-gradient(135deg, var(--primary), var(--primary-light));
|
|
border-radius: 20px;
|
|
box-shadow: var(--shadow-clay-float);
|
|
border: none;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
}
|
|
.profile-card .p-avatar {
|
|
width: 64px; height: 64px;
|
|
border-radius: 50%;
|
|
border: 3px solid rgba(255,255,255,0.5);
|
|
background: rgba(255,255,255,0.25);
|
|
display: flex; align-items: center; justify-content: center;
|
|
flex-shrink: 0;
|
|
}
|
|
.profile-card .p-avatar svg { width: 32px; height: 32px; stroke: #fff; stroke-width: 1.5; }
|
|
.profile-card .p-info .name {
|
|
font: 600 20px/1.3 var(--font-title);
|
|
color: #fff;
|
|
}
|
|
.profile-card .p-info .tagline {
|
|
font: 400 14px/1.3 var(--font-body);
|
|
color: rgba(255,255,255,0.8);
|
|
margin-top: 2px;
|
|
}
|
|
.profile-card .p-info .streak {
|
|
font: 400 12px/1 var(--font-body);
|
|
color: rgba(255,255,255,0.65);
|
|
margin-top: 8px;
|
|
}
|
|
|
|
/* Quick stats row */
|
|
.quick-stats {
|
|
display: flex; gap: 12px;
|
|
padding: 0 20px;
|
|
margin-top: 16px;
|
|
}
|
|
.qs-card {
|
|
flex: 1;
|
|
padding: 16px 12px;
|
|
background: var(--surface);
|
|
border-radius: 16px;
|
|
border: 2px solid var(--border);
|
|
box-shadow: var(--shadow-clay);
|
|
text-align: center;
|
|
}
|
|
.qs-card .label {
|
|
font: 400 12px/1 var(--font-body);
|
|
color: var(--text-sec);
|
|
margin-bottom: 4px;
|
|
}
|
|
.qs-card .value {
|
|
font: 600 16px/1.2 var(--font-title);
|
|
color: var(--text);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* Menu list */
|
|
.menu-section { margin: 20px 20px 0; }
|
|
.menu-card {
|
|
background: var(--surface);
|
|
border-radius: 20px;
|
|
border: 2px solid var(--border);
|
|
box-shadow: var(--shadow-clay);
|
|
overflow: hidden;
|
|
}
|
|
.menu-item {
|
|
height: 56px;
|
|
padding: 0 20px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
cursor: pointer;
|
|
transition: background 0.15s;
|
|
border-bottom: 1px solid var(--border);
|
|
}
|
|
.menu-item:last-child { border-bottom: none; }
|
|
.menu-item:active { background: var(--bg); }
|
|
.menu-item .left {
|
|
display: flex; align-items: center; gap: 12px;
|
|
}
|
|
.menu-item .left .mi-icon {
|
|
width: 32px; height: 32px;
|
|
border-radius: 8px;
|
|
display: flex; align-items: center; justify-content: center;
|
|
}
|
|
.menu-item .left .mi-icon svg { width: 18px; height: 18px; stroke-width: 2; }
|
|
.menu-item .left .mi-label {
|
|
font: 500 14px/1 var(--font-body);
|
|
color: var(--text);
|
|
}
|
|
.menu-item > svg { width: 16px; height: 16px; stroke: var(--text-muted); flex-shrink: 0; }
|
|
|
|
/* Toggle switch */
|
|
.toggle-switch {
|
|
width: 48px; height: 28px;
|
|
border-radius: 14px;
|
|
background: var(--border);
|
|
position: relative;
|
|
cursor: pointer;
|
|
transition: background 0.2s;
|
|
}
|
|
.toggle-switch.on { background: var(--primary); }
|
|
.toggle-switch .knob {
|
|
width: 22px; height: 22px;
|
|
border-radius: 50%;
|
|
background: #fff;
|
|
position: absolute;
|
|
top: 3px; left: 3px;
|
|
transition: transform 0.2s;
|
|
box-shadow: 1px 1px 4px rgba(0,0,0,0.1);
|
|
}
|
|
.toggle-switch.on .knob { transform: translateX(20px); }
|
|
|
|
/* ==================== ANIMATIONS ==================== */
|
|
@keyframes fadeUp {
|
|
from { opacity: 0; transform: translateY(12px); }
|
|
to { opacity: 1; transform: translateY(0); }
|
|
}
|
|
.stagger > * {
|
|
animation: fadeUp 0.3s ease-out backwards;
|
|
}
|
|
.stagger > *:nth-child(1) { animation-delay: 0ms; }
|
|
.stagger > *:nth-child(2) { animation-delay: 50ms; }
|
|
.stagger > *:nth-child(3) { animation-delay: 100ms; }
|
|
.stagger > *:nth-child(4) { animation-delay: 150ms; }
|
|
.stagger > *:nth-child(5) { animation-delay: 200ms; }
|
|
.stagger > *:nth-child(6) { animation-delay: 250ms; }
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.stagger > * { animation: none; }
|
|
.page { transition: none; }
|
|
}
|
|
|
|
/* ==================== TABS (stats) ==================== */
|
|
.stats-tabs {
|
|
display: flex;
|
|
justify-content: center;
|
|
margin: 0 20px;
|
|
}
|
|
.stats-tab-bar {
|
|
display: inline-flex;
|
|
background: var(--surface-warm);
|
|
border-radius: 12px;
|
|
padding: 4px;
|
|
}
|
|
.stats-tab-bar .stab {
|
|
padding: 8px 20px;
|
|
border-radius: 10px;
|
|
font: 400 14px/1 var(--font-body);
|
|
color: var(--text-sec);
|
|
cursor: pointer;
|
|
transition: all 0.2s;
|
|
}
|
|
.stats-tab-bar .stab.active {
|
|
background: var(--surface);
|
|
color: var(--primary);
|
|
font-weight: 600;
|
|
box-shadow: 2px 2px 8px rgba(45,27,27,0.06);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="phone-frame">
|
|
<!-- Status Bar -->
|
|
<div class="status-bar">
|
|
<span class="time">9:41</span>
|
|
<span class="icons">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 9l4-4 4 4"/><path d="M5 5v14"/><path d="M11 19h4a2 2 0 002-2V7a2 2 0 00-2-2h-4"/><path d="M11 19l-4-4 4-4"/></svg>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M6.7 6.7L1 12l5.7 5.3"/><path d="M17.3 6.7L23 12l-5.7 5.3"/><path d="M14 4l-4 16"/></svg>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="1" y="6" width="18" height="12" rx="2"/><path d="M23 10v4"/></svg>
|
|
</span>
|
|
</div>
|
|
|
|
<div class="pages-wrap">
|
|
|
|
<!-- ==================== PAGE 1: HOME ==================== -->
|
|
<div class="page active" id="page-home">
|
|
<!-- Header -->
|
|
<div class="home-header">
|
|
<div class="avatar">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="8" r="4"/><path d="M6 21v-2a4 4 0 014-4h4a4 4 0 014 4v2"/></svg>
|
|
</div>
|
|
<div class="greeting">早上好,小菜</div>
|
|
<div class="bell">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 01-3.46 0"/></svg>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Overview Card -->
|
|
<div class="overview-card clay-card">
|
|
<div class="label">本月支出</div>
|
|
<div class="amount"><span class="currency">¥</span>3,280.00</div>
|
|
<div class="budget-bar-wrap">
|
|
<div class="budget-bar"><div class="fill"></div></div>
|
|
<div class="budget-info">
|
|
<span>预算 ¥5,000 · 剩余 ¥1,720</span>
|
|
<span class="pct">65%</span>
|
|
</div>
|
|
</div>
|
|
<div class="mini-stats">
|
|
<div class="mini-stat">
|
|
<div class="label">收入</div>
|
|
<div class="value income">+¥8,000</div>
|
|
</div>
|
|
<div class="mini-stat">
|
|
<div class="label">支出</div>
|
|
<div class="value expense">-¥3,280</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Quick Actions -->
|
|
<div class="quick-actions stagger">
|
|
<div class="quick-btn income">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><polyline points="23 6 13.5 15.5 8.5 10.5 1 18"/><polyline points="17 6 23 6 23 12"/></svg>
|
|
<span>记收入</span>
|
|
</div>
|
|
<div class="quick-btn expense">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><polyline points="23 18 13.5 8.5 8.5 13.5 1 6"/><polyline points="17 18 23 18 23 12"/></svg>
|
|
<span>记支出</span>
|
|
</div>
|
|
<div class="quick-btn transfer">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M17 1l4 4-4 4"/><path d="M3 11V9a4 4 0 014-4h14"/><path d="M7 23l-4-4 4-4"/><path d="M21 13v2a4 4 0 01-4 4H3"/></svg>
|
|
<span>转账</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Transactions -->
|
|
<div class="section-header">
|
|
<span class="title">近期账单</span>
|
|
<span class="more">查看全部</span>
|
|
</div>
|
|
|
|
<div class="stagger">
|
|
<div class="tx-date">今天</div>
|
|
|
|
<div class="tx-item">
|
|
<div class="icon-wrap" style="background:#FFF0E6">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FF8C69"><path d="M18 8h1a4 4 0 010 8h-1"/><path d="M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/><line x1="14" y1="1" x2="14" y2="4"/></svg>
|
|
</div>
|
|
<div class="info">
|
|
<div class="name">午餐</div>
|
|
<div class="meta">餐饮 · 12:30</div>
|
|
</div>
|
|
<div class="amount expense">-¥35.00</div>
|
|
</div>
|
|
<div class="tx-divider"></div>
|
|
|
|
<div class="tx-item">
|
|
<div class="icon-wrap" style="background:#FFF0E6">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FF8C69"><path d="M18 8h1a4 4 0 010 8h-1"/><path d="M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/><line x1="14" y1="1" x2="14" y2="4"/></svg>
|
|
</div>
|
|
<div class="info">
|
|
<div class="name">咖啡</div>
|
|
<div class="meta">餐饮 · 09:15</div>
|
|
</div>
|
|
<div class="amount expense">-¥18.00</div>
|
|
</div>
|
|
<div class="tx-divider"></div>
|
|
|
|
<div class="tx-item">
|
|
<div class="icon-wrap" style="background:#E8F5E9">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#7BC67E"><rect x="2" y="5" width="20" height="14" rx="2"/><line x1="2" y1="10" x2="22" y2="10"/></svg>
|
|
</div>
|
|
<div class="info">
|
|
<div class="name">地铁充值</div>
|
|
<div class="meta">交通 · 08:45</div>
|
|
</div>
|
|
<div class="amount expense">-¥50.00</div>
|
|
</div>
|
|
|
|
<div class="tx-date">昨天</div>
|
|
|
|
<div class="tx-item">
|
|
<div class="icon-wrap" style="background:#E8F5E9">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#7BC67E"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/></svg>
|
|
</div>
|
|
<div class="info">
|
|
<div class="name">工资</div>
|
|
<div class="meta">收入 · 10:00</div>
|
|
</div>
|
|
<div class="amount income">+¥8,000.00</div>
|
|
</div>
|
|
<div class="tx-divider"></div>
|
|
|
|
<div class="tx-item">
|
|
<div class="icon-wrap" style="background:#E3F2FD">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#6C9BCF"><rect x="1" y="3" width="15" height="13"/><polygon points="16 8 20 8 23 11 23 16 16 16 16 8"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></svg>
|
|
</div>
|
|
<div class="info">
|
|
<div class="name">快递</div>
|
|
<div class="meta">交通 · 15:42</div>
|
|
</div>
|
|
<div class="amount expense">-¥28.00</div>
|
|
</div>
|
|
<div class="tx-divider"></div>
|
|
|
|
<div class="tx-item">
|
|
<div class="icon-wrap" style="background:#FFF3E0">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FFB347"><path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 01-8 0"/></svg>
|
|
</div>
|
|
<div class="info">
|
|
<div class="name">超市采购</div>
|
|
<div class="meta">购物 · 18:20</div>
|
|
</div>
|
|
<div class="amount expense">-¥156.00</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== PAGE 2: ADD ==================== -->
|
|
<div class="page" id="page-add">
|
|
<div class="add-header">
|
|
<div class="close">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
|
|
</div>
|
|
<span class="title">记一笔</span>
|
|
<span class="save">保存</span>
|
|
</div>
|
|
|
|
<div class="type-toggle-wrap">
|
|
<div class="type-toggle" id="typeToggle">
|
|
<div class="slider"></div>
|
|
<div class="tab active" data-type="expense" onclick="switchType('expense')">支出</div>
|
|
<div class="tab" data-type="income" onclick="switchType('income')">收入</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="amount-display">
|
|
<span class="currency">¥</span>
|
|
<span class="digits">35.00</span>
|
|
<span class="cursor"></span>
|
|
</div>
|
|
|
|
<div class="category-grid stagger">
|
|
<div class="cat-item selected" onclick="selectCat(this)">
|
|
<div class="cat-icon" style="background:#FFF0E6">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FF8C69"><path d="M18 8h1a4 4 0 010 8h-1"/><path d="M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/><line x1="14" y1="1" x2="14" y2="4"/></svg>
|
|
</div>
|
|
<span class="cat-name">餐饮</span>
|
|
</div>
|
|
<div class="cat-item" onclick="selectCat(this)">
|
|
<div class="cat-icon" style="background:#FFF0E6">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FF8C69"><path d="M18 8h1a4 4 0 010 8h-1"/><path d="M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/></svg>
|
|
</div>
|
|
<span class="cat-name">饮品</span>
|
|
</div>
|
|
<div class="cat-item" onclick="selectCat(this)">
|
|
<div class="cat-icon" style="background:#FFF3E0">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FFB347"><path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 01-8 0"/></svg>
|
|
</div>
|
|
<span class="cat-name">购物</span>
|
|
</div>
|
|
<div class="cat-item" onclick="selectCat(this)">
|
|
<div class="cat-icon" style="background:#E3F2FD">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#6C9BCF"><rect x="1" y="3" width="15" height="13"/><polygon points="16 8 20 8 23 11 23 16 16 16 16 8"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></svg>
|
|
</div>
|
|
<span class="cat-name">交通</span>
|
|
</div>
|
|
<div class="cat-item" onclick="selectCat(this)">
|
|
<div class="cat-icon" style="background:#E8F5E9">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#7BC67E"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
|
</div>
|
|
<span class="cat-name">住房</span>
|
|
</div>
|
|
<div class="cat-item" onclick="selectCat(this)">
|
|
<div class="cat-icon" style="background:#FCE4EC">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FF6B6B"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>
|
|
</div>
|
|
<span class="cat-name">医疗</span>
|
|
</div>
|
|
<div class="cat-item" onclick="selectCat(this)">
|
|
<div class="cat-icon" style="background:#E8EAF6">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#6C9BCF"><path d="M2 3h6a4 4 0 014 4v14a3 3 0 00-3-3H2z"/><path d="M22 3h-6a4 4 0 00-4 4v14a3 3 0 013-3h7z"/></svg>
|
|
</div>
|
|
<span class="cat-name">教育</span>
|
|
</div>
|
|
<div class="cat-item" onclick="selectCat(this)">
|
|
<div class="cat-icon" style="background:#F3E5F5">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#B39DDB"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/></svg>
|
|
</div>
|
|
<span class="cat-name">娱乐</span>
|
|
</div>
|
|
<div class="cat-item" onclick="selectCat(this)">
|
|
<div class="cat-icon" style="background:#FFF8E1">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FFD166"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg>
|
|
</div>
|
|
<span class="cat-name">服饰</span>
|
|
</div>
|
|
<div class="cat-item" onclick="selectCat(this)">
|
|
<div class="cat-icon" style="background:#FCE4EC">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FF8C69"><circle cx="12" cy="12" r="10"/><path d="M8 14s1.5 2 4 2 4-2 4-2"/><line x1="9" y1="9" x2="9.01" y2="9"/><line x1="15" y1="9" x2="15.01" y2="9"/></svg>
|
|
</div>
|
|
<span class="cat-name">美容</span>
|
|
</div>
|
|
<div class="cat-item" onclick="selectCat(this)">
|
|
<div class="cat-icon" style="background:#E3F2FD">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#6C9BCF"><rect x="5" y="2" width="14" height="20" rx="2" ry="2"/><line x1="12" y1="18" x2="12.01" y2="18"/></svg>
|
|
</div>
|
|
<span class="cat-name">通讯</span>
|
|
</div>
|
|
<div class="cat-item" onclick="selectCat(this)">
|
|
<div class="cat-icon" style="background:#F5F5F5">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#BFB3B3"><circle cx="12" cy="12" r="1"/><circle cx="19" cy="12" r="1"/><circle cx="5" cy="12" r="1"/></svg>
|
|
</div>
|
|
<span class="cat-name">更多</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="extra-fields">
|
|
<div class="extra-row">
|
|
<div class="left">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
|
|
<span>今天 (5月27日)</span>
|
|
</div>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
|
</div>
|
|
<div class="extra-row">
|
|
<div class="left">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 00-2 2v14a2 2 0 002 2h14a2 2 0 002-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 013 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
|
|
<span>添加备注...</span>
|
|
</div>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="numpad">
|
|
<div class="numpad-key">1</div>
|
|
<div class="numpad-key">2</div>
|
|
<div class="numpad-key">3</div>
|
|
<div class="numpad-key fn">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>
|
|
</div>
|
|
<div class="numpad-key">4</div>
|
|
<div class="numpad-key">5</div>
|
|
<div class="numpad-key">6</div>
|
|
<div class="numpad-key fn">+</div>
|
|
<div class="numpad-key">7</div>
|
|
<div class="numpad-key">8</div>
|
|
<div class="numpad-key">9</div>
|
|
<div class="numpad-key fn">-</div>
|
|
<div class="numpad-key">.</div>
|
|
<div class="numpad-key">0</div>
|
|
<div class="numpad-key fn">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 4H8l-7 8 7 8h13a2 2 0 002-2V6a2 2 0 00-2-2z"/><line x1="18" y1="9" x2="12" y2="15"/><line x1="12" y1="9" x2="18" y2="15"/></svg>
|
|
</div>
|
|
<div class="numpad-key eq">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="submit-btn">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>
|
|
记好了
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== PAGE 3: STATS ==================== -->
|
|
<div class="page" id="page-stats">
|
|
<div class="stats-header">统计分析</div>
|
|
|
|
<div class="month-picker">
|
|
<div class="arrow">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>
|
|
</div>
|
|
<span class="month-text">2026年5月</span>
|
|
<div class="arrow">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="stats-tabs">
|
|
<div class="stats-tab-bar">
|
|
<div class="stab active">支出</div>
|
|
<div class="stab">收入</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Overview -->
|
|
<div class="stats-overview clay-card">
|
|
<div class="so-item">
|
|
<div class="label">总支出</div>
|
|
<div class="value">¥3,280</div>
|
|
</div>
|
|
<div class="so-item">
|
|
<div class="label">日均</div>
|
|
<div class="value">¥122</div>
|
|
</div>
|
|
<div class="so-item">
|
|
<div class="label">笔数</div>
|
|
<div class="value">28笔</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Donut Chart -->
|
|
<div class="chart-card clay-card">
|
|
<div class="chart-title">分类占比</div>
|
|
<div class="donut-wrap">
|
|
<svg class="donut-svg" viewBox="0 0 200 200">
|
|
<!-- Donut segments -->
|
|
<circle cx="100" cy="100" r="70" fill="none" stroke="#FF8C69" stroke-width="28"
|
|
stroke-dasharray="167.6 272.4" stroke-dashoffset="0"
|
|
transform="rotate(-90 100 100)" stroke-linecap="round"/>
|
|
<circle cx="100" cy="100" r="70" fill="none" stroke="#FFD166" stroke-width="28"
|
|
stroke-dasharray="96.8 343.2" stroke-dashoffset="-167.6"
|
|
transform="rotate(-90 100 100)" stroke-linecap="round"/>
|
|
<circle cx="100" cy="100" r="70" fill="none" stroke="#6C9BCF" stroke-width="28"
|
|
stroke-dasharray="66 374" stroke-dashoffset="-264.4"
|
|
transform="rotate(-90 100 100)" stroke-linecap="round"/>
|
|
<circle cx="100" cy="100" r="70" fill="none" stroke="#7BC67E" stroke-width="28"
|
|
stroke-dasharray="52.8 387.2" stroke-dashoffset="-330.4"
|
|
transform="rotate(-90 100 100)" stroke-linecap="round"/>
|
|
<circle cx="100" cy="100" r="70" fill="none" stroke="#BFB3B3" stroke-width="28"
|
|
stroke-dasharray="56.4 383.6" stroke-dashoffset="-383.2"
|
|
transform="rotate(-90 100 100)" stroke-linecap="round"/>
|
|
</svg>
|
|
<div class="donut-center">
|
|
<div class="total-label">总支出</div>
|
|
<div class="total-value">¥3,280</div>
|
|
</div>
|
|
</div>
|
|
<div class="chart-legend">
|
|
<div class="legend-item"><div class="legend-dot" style="background:#FF8C69"></div><span>餐饮</span><span class="pct">38%</span></div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:#FFD166"></div><span>购物</span><span class="pct">22%</span></div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:#6C9BCF"></div><span>交通</span><span class="pct">15%</span></div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:#7BC67E"></div><span>住房</span><span class="pct">12%</span></div>
|
|
<div class="legend-item"><div class="legend-dot" style="background:#BFB3B3"></div><span>其他</span><span class="pct">13%</span></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Line Chart -->
|
|
<div class="chart-card clay-card">
|
|
<div class="chart-title">每日趋势</div>
|
|
<div class="line-chart-wrap">
|
|
<svg class="line-chart-svg" viewBox="0 0 300 160">
|
|
<!-- Grid lines -->
|
|
<line x1="30" y1="10" x2="30" y2="140" stroke="#F0E0D6" stroke-width="1"/>
|
|
<line x1="30" y1="140" x2="290" y2="140" stroke="#F0E0D6" stroke-width="1"/>
|
|
<line x1="30" y1="105" x2="290" y2="105" stroke="#F0E0D6" stroke-width="1" stroke-dasharray="4"/>
|
|
<line x1="30" y1="70" x2="290" y2="70" stroke="#F0E0D6" stroke-width="1" stroke-dasharray="4"/>
|
|
<line x1="30" y1="35" x2="290" y2="35" stroke="#F0E0D6" stroke-width="1" stroke-dasharray="4"/>
|
|
<!-- Y labels -->
|
|
<text x="25" y="144" text-anchor="end" fill="#BFB3B3" font-size="10" font-family="Fredoka">0</text>
|
|
<text x="25" y="109" text-anchor="end" fill="#BFB3B3" font-size="10" font-family="Fredoka">50</text>
|
|
<text x="25" y="74" text-anchor="end" fill="#BFB3B3" font-size="10" font-family="Fredoka">100</text>
|
|
<text x="25" y="39" text-anchor="end" fill="#BFB3B3" font-size="10" font-family="Fredoka">150</text>
|
|
<!-- Area fill -->
|
|
<path d="M45,100 L80,85 L115,95 L150,60 L185,70 L220,40 L255,55 L275,50 L275,140 L45,140 Z"
|
|
fill="url(#areaGrad)" opacity="0.3"/>
|
|
<!-- Line -->
|
|
<path d="M45,100 L80,85 L115,95 L150,60 L185,70 L220,40 L255,55 L275,50"
|
|
fill="none" stroke="#FF8C69" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
<!-- Dots -->
|
|
<circle cx="45" cy="100" r="4" fill="#FF8C69" stroke="#fff" stroke-width="2"/>
|
|
<circle cx="80" cy="85" r="4" fill="#FF8C69" stroke="#fff" stroke-width="2"/>
|
|
<circle cx="115" cy="95" r="4" fill="#FF8C69" stroke="#fff" stroke-width="2"/>
|
|
<circle cx="150" cy="60" r="4" fill="#FF8C69" stroke="#fff" stroke-width="2"/>
|
|
<circle cx="185" cy="70" r="4" fill="#FF8C69" stroke="#fff" stroke-width="2"/>
|
|
<circle cx="220" cy="40" r="5" fill="#FF8C69" stroke="#fff" stroke-width="2"/>
|
|
<circle cx="255" cy="55" r="4" fill="#FF8C69" stroke="#fff" stroke-width="2"/>
|
|
<circle cx="275" cy="50" r="4" fill="#FF8C69" stroke="#fff" stroke-width="2"/>
|
|
<!-- X labels -->
|
|
<text x="45" y="155" text-anchor="middle" fill="#BFB3B3" font-size="10" font-family="Fredoka">1</text>
|
|
<text x="80" y="155" text-anchor="middle" fill="#BFB3B3" font-size="10" font-family="Fredoka">5</text>
|
|
<text x="115" y="155" text-anchor="middle" fill="#BFB3B3" font-size="10" font-family="Fredoka">10</text>
|
|
<text x="150" y="155" text-anchor="middle" fill="#BFB3B3" font-size="10" font-family="Fredoka">15</text>
|
|
<text x="185" y="155" text-anchor="middle" fill="#BFB3B3" font-size="10" font-family="Fredoka">20</text>
|
|
<text x="220" y="155" text-anchor="middle" fill="#FF8C69" font-size="10" font-family="Fredoka" font-weight="600">25</text>
|
|
<text x="255" y="155" text-anchor="middle" fill="#BFB3B3" font-size="10" font-family="Fredoka">27</text>
|
|
<text x="275" y="155" text-anchor="middle" fill="#BFB3B3" font-size="10" font-family="Fredoka">30</text>
|
|
<defs>
|
|
<linearGradient id="areaGrad" x1="0" y1="0" x2="0" y2="1">
|
|
<stop offset="0%" stop-color="#FF8C69" stop-opacity="0.3"/>
|
|
<stop offset="100%" stop-color="#FF8C69" stop-opacity="0"/>
|
|
</linearGradient>
|
|
</defs>
|
|
</svg>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Rank -->
|
|
<div class="chart-card clay-card" style="margin-bottom:20px">
|
|
<div class="chart-title">分类排行</div>
|
|
<div class="rank-list stagger">
|
|
<div class="rank-item">
|
|
<div class="rank-num n1">1</div>
|
|
<div class="rank-icon" style="background:#FFF0E6">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FF8C69"><path d="M18 8h1a4 4 0 010 8h-1"/><path d="M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/><line x1="14" y1="1" x2="14" y2="4"/></svg>
|
|
</div>
|
|
<div class="rank-info">
|
|
<div class="name">餐饮</div>
|
|
<div class="rank-bar"><div class="fill" style="width:100%;background:#FF8C69"></div></div>
|
|
<div class="meta">12笔 · 38%</div>
|
|
</div>
|
|
<div class="rank-amount">¥1,246</div>
|
|
</div>
|
|
<div class="rank-item">
|
|
<div class="rank-num n2">2</div>
|
|
<div class="rank-icon" style="background:#FFF3E0">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FFB347"><path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 01-8 0"/></svg>
|
|
</div>
|
|
<div class="rank-info">
|
|
<div class="name">购物</div>
|
|
<div class="rank-bar"><div class="fill" style="width:58%;background:#FFD166"></div></div>
|
|
<div class="meta">5笔 · 22%</div>
|
|
</div>
|
|
<div class="rank-amount">¥722</div>
|
|
</div>
|
|
<div class="rank-item">
|
|
<div class="rank-num n3">3</div>
|
|
<div class="rank-icon" style="background:#E3F2FD">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#6C9BCF"><rect x="1" y="3" width="15" height="13"/><polygon points="16 8 20 8 23 11 23 16 16 16 16 8"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></svg>
|
|
</div>
|
|
<div class="rank-info">
|
|
<div class="name">交通</div>
|
|
<div class="rank-bar"><div class="fill" style="width:39%;background:#6C9BCF"></div></div>
|
|
<div class="meta">8笔 · 15%</div>
|
|
</div>
|
|
<div class="rank-amount">¥492</div>
|
|
</div>
|
|
<div class="rank-item">
|
|
<div class="rank-num">4</div>
|
|
<div class="rank-icon" style="background:#E8F5E9">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#7BC67E"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
|
</div>
|
|
<div class="rank-info">
|
|
<div class="name">住房</div>
|
|
<div class="rank-bar"><div class="fill" style="width:32%;background:#7BC67E"></div></div>
|
|
<div class="meta">2笔 · 12%</div>
|
|
</div>
|
|
<div class="rank-amount">¥394</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== PAGE 4: BILLS ==================== -->
|
|
<div class="page" id="page-bills">
|
|
<div class="stats-header">全部账单</div>
|
|
|
|
<div class="stats-tabs" style="margin-bottom:12px">
|
|
<div class="stats-tab-bar">
|
|
<div class="stab active">全部</div>
|
|
<div class="stab">支出</div>
|
|
<div class="stab">收入</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tx-date">今天 · 5月27日</div>
|
|
<div class="stagger">
|
|
<div class="tx-item">
|
|
<div class="icon-wrap" style="background:#FFF0E6">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FF8C69"><path d="M18 8h1a4 4 0 010 8h-1"/><path d="M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/><line x1="14" y1="1" x2="14" y2="4"/></svg>
|
|
</div>
|
|
<div class="info">
|
|
<div class="name">午餐</div>
|
|
<div class="meta">餐饮 · 12:30</div>
|
|
</div>
|
|
<div class="amount expense">-¥35.00</div>
|
|
</div>
|
|
<div class="tx-divider"></div>
|
|
<div class="tx-item">
|
|
<div class="icon-wrap" style="background:#FFF0E6">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FF8C69"><path d="M18 8h1a4 4 0 010 8h-1"/><path d="M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/><line x1="10" y1="1" x2="10" y2="4"/></svg>
|
|
</div>
|
|
<div class="info">
|
|
<div class="name">咖啡</div>
|
|
<div class="meta">餐饮 · 09:15</div>
|
|
</div>
|
|
<div class="amount expense">-¥18.00</div>
|
|
</div>
|
|
<div class="tx-divider"></div>
|
|
<div class="tx-item">
|
|
<div class="icon-wrap" style="background:#E3F2FD">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#6C9BCF"><rect x="1" y="3" width="15" height="13"/><polygon points="16 8 20 8 23 11 23 16 16 16 16 8"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></svg>
|
|
</div>
|
|
<div class="info">
|
|
<div class="name">地铁充值</div>
|
|
<div class="meta">交通 · 08:45</div>
|
|
</div>
|
|
<div class="amount expense">-¥50.00</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tx-date" style="margin-top:8px">昨天 · 5月26日</div>
|
|
<div class="stagger">
|
|
<div class="tx-item">
|
|
<div class="icon-wrap" style="background:#E8F5E9">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#7BC67E"><line x1="12" y1="1" x2="12" y2="23"/><path d="M17 5H9.5a3.5 3.5 0 000 7h5a3.5 3.5 0 010 7H6"/></svg>
|
|
</div>
|
|
<div class="info">
|
|
<div class="name">工资</div>
|
|
<div class="meta">收入 · 10:00</div>
|
|
</div>
|
|
<div class="amount income">+¥8,000.00</div>
|
|
</div>
|
|
<div class="tx-divider"></div>
|
|
<div class="tx-item">
|
|
<div class="icon-wrap" style="background:#E3F2FD">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#6C9BCF"><rect x="1" y="3" width="15" height="13"/><polygon points="16 8 20 8 23 11 23 16 16 16 16 8"/><circle cx="5.5" cy="18.5" r="2.5"/><circle cx="18.5" cy="18.5" r="2.5"/></svg>
|
|
</div>
|
|
<div class="info">
|
|
<div class="name">快递</div>
|
|
<div class="meta">交通 · 15:42</div>
|
|
</div>
|
|
<div class="amount expense">-¥28.00</div>
|
|
</div>
|
|
<div class="tx-divider"></div>
|
|
<div class="tx-item">
|
|
<div class="icon-wrap" style="background:#FFF3E0">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FFB347"><path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 01-8 0"/></svg>
|
|
</div>
|
|
<div class="info">
|
|
<div class="name">超市采购</div>
|
|
<div class="meta">购物 · 18:20</div>
|
|
</div>
|
|
<div class="amount expense">-¥156.00</div>
|
|
</div>
|
|
<div class="tx-divider"></div>
|
|
<div class="tx-item">
|
|
<div class="icon-wrap" style="background:#FFF0E6">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FF8C69"><path d="M18 8h1a4 4 0 010 8h-1"/><path d="M2 8h16v9a4 4 0 01-4 4H6a4 4 0 01-4-4V8z"/><line x1="6" y1="1" x2="6" y2="4"/></svg>
|
|
</div>
|
|
<div class="info">
|
|
<div class="name">下午茶</div>
|
|
<div class="meta">餐饮 · 14:30</div>
|
|
</div>
|
|
<div class="amount expense">-¥25.00</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="tx-date" style="margin-top:8px">5月25日 · 周六</div>
|
|
<div class="stagger">
|
|
<div class="tx-item">
|
|
<div class="icon-wrap" style="background:#FFF3E0">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FFB347"><path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/><line x1="3" y1="6" x2="21" y2="6"/><path d="M16 10a4 4 0 01-8 0"/></svg>
|
|
</div>
|
|
<div class="info">
|
|
<div class="name">网购衣服</div>
|
|
<div class="meta">购物 · 20:15</div>
|
|
</div>
|
|
<div class="amount expense">-¥299.00</div>
|
|
</div>
|
|
<div class="tx-divider"></div>
|
|
<div class="tx-item">
|
|
<div class="icon-wrap" style="background:#E3F2FD">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#6C9BCF"><polygon points="23 7 16 12 23 17 23 7"/><rect x="1" y="5" width="15" height="14" rx="2" ry="2"/></svg>
|
|
</div>
|
|
<div class="info">
|
|
<div class="name">电影票</div>
|
|
<div class="meta">娱乐 · 19:00</div>
|
|
</div>
|
|
<div class="amount expense">-¥78.00</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== PAGE 5: PROFILE ==================== -->
|
|
<div class="page" id="page-profile">
|
|
<div class="stats-header">我的</div>
|
|
|
|
<div class="profile-card">
|
|
<div class="p-avatar">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="8" r="4"/><path d="M6 21v-2a4 4 0 014-4h4a4 4 0 014 4v2"/></svg>
|
|
</div>
|
|
<div class="p-info">
|
|
<div class="name">小菜</div>
|
|
<div class="tagline">记账小能手</div>
|
|
<div class="streak">已记账 128 天 · 累计 526 笔</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="quick-stats stagger">
|
|
<div class="qs-card">
|
|
<div class="label">本月</div>
|
|
<div class="value">¥3,280</div>
|
|
</div>
|
|
<div class="qs-card">
|
|
<div class="label">本季</div>
|
|
<div class="value">¥9,840</div>
|
|
</div>
|
|
<div class="qs-card">
|
|
<div class="label">本年</div>
|
|
<div class="value">¥38,500</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="menu-section stagger">
|
|
<div class="menu-card">
|
|
<div class="menu-item">
|
|
<div class="left">
|
|
<div class="mi-icon" style="background:#FFF0E6">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FF8C69"><circle cx="12" cy="12" r="10"/><path d="M16 8h-6a2 2 0 000 4h4a2 2 0 010 4H8"/><path d="M12 6v2m0 8v2"/></svg>
|
|
</div>
|
|
<span class="mi-label">预算设置</span>
|
|
</div>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
|
</div>
|
|
<div class="menu-item">
|
|
<div class="left">
|
|
<div class="mi-icon" style="background:#FFF0E6">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FF8C69"><path d="M20.59 13.41l-7.17 7.17a2 2 0 01-2.83 0L2 12V2h10l8.59 8.59a2 2 0 010 2.82z"/><line x1="7" y1="7" x2="7.01" y2="7"/></svg>
|
|
</div>
|
|
<span class="mi-label">分类管理</span>
|
|
</div>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
|
</div>
|
|
<div class="menu-item">
|
|
<div class="left">
|
|
<div class="mi-icon" style="background:#E3F2FD">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#6C9BCF"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
|
</div>
|
|
<span class="mi-label">数据导出</span>
|
|
</div>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
|
</div>
|
|
<div class="menu-item">
|
|
<div class="left">
|
|
<div class="mi-icon" style="background:#E8F5E9">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#7BC67E"><polyline points="23 4 23 10 17 10"/><path d="M20.49 15a9 9 0 11-2.12-9.36L23 10"/></svg>
|
|
</div>
|
|
<span class="mi-label">数据备份</span>
|
|
</div>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="menu-section stagger">
|
|
<div class="menu-card">
|
|
<div class="menu-item">
|
|
<div class="left">
|
|
<div class="mi-icon" style="background:#F3E5F5">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#B39DDB"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/></svg>
|
|
</div>
|
|
<span class="mi-label">深色模式</span>
|
|
</div>
|
|
<div class="toggle-switch" onclick="this.classList.toggle('on')"><div class="knob"></div></div>
|
|
</div>
|
|
<div class="menu-item">
|
|
<div class="left">
|
|
<div class="mi-icon" style="background:#FFF3E0">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#FFB347"><path d="M18 8A6 6 0 006 8c0 7-3 9-3 9h18s-3-2-3-9"/><path d="M13.73 21a2 2 0 01-3.46 0"/></svg>
|
|
</div>
|
|
<span class="mi-label">记账提醒</span>
|
|
</div>
|
|
<div class="toggle-switch on" onclick="this.classList.toggle('on')"><div class="knob"></div></div>
|
|
</div>
|
|
<div class="menu-item">
|
|
<div class="left">
|
|
<div class="mi-icon" style="background:#E3F2FD">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#6C9BCF"><circle cx="12" cy="12" r="10"/><line x1="2" y1="12" x2="22" y2="12"/><path d="M12 2a15.3 15.3 0 014 10 15.3 15.3 0 01-4 10 15.3 15.3 0 01-4-10 15.3 15.3 0 014-10z"/></svg>
|
|
</div>
|
|
<span class="mi-label">语言</span>
|
|
</div>
|
|
<span style="font:400 13px/1 var(--font-body);color:var(--text-sec);display:flex;align-items:center;gap:4px">
|
|
中文
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
|
</span>
|
|
</div>
|
|
<div class="menu-item">
|
|
<div class="left">
|
|
<div class="mi-icon" style="background:#F5F5F5">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="#BFB3B3"><circle cx="12" cy="12" r="10"/><line x1="12" y1="16" x2="12" y2="12"/><line x1="12" y1="8" x2="12.01" y2="8"/></svg>
|
|
</div>
|
|
<span class="mi-label">关于小菜</span>
|
|
</div>
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ==================== BOTTOM NAV ==================== -->
|
|
<div class="bottom-nav">
|
|
<div class="nav-item active" data-page="home" onclick="switchPage('home')">
|
|
<svg viewBox="0 0 24 24"><path d="M3 9l9-7 9 7v11a2 2 0 01-2 2H5a2 2 0 01-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
|
|
<span>首页</span>
|
|
</div>
|
|
<div class="nav-item" data-page="stats" onclick="switchPage('stats')">
|
|
<svg viewBox="0 0 24 24"><path d="M18 20V10"/><path d="M12 20V4"/><path d="M6 20v-6"/></svg>
|
|
<span>统计</span>
|
|
</div>
|
|
<div class="nav-add" onclick="switchPage('add')">
|
|
<svg viewBox="0 0 24 24"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
|
|
</div>
|
|
<div class="nav-item" data-page="bills" onclick="switchPage('bills')">
|
|
<svg viewBox="0 0 24 24"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/></svg>
|
|
<span>账单</span>
|
|
</div>
|
|
<div class="nav-item" data-page="profile" onclick="switchPage('profile')">
|
|
<svg viewBox="0 0 24 24"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
|
<span>我的</span>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
function switchPage(page) {
|
|
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
|
document.getElementById('page-' + page).classList.add('active');
|
|
document.querySelectorAll('.nav-item').forEach(n => n.classList.remove('active'));
|
|
const activeNav = document.querySelector('.nav-item[data-page="' + page + '"]');
|
|
if (activeNav) activeNav.classList.add('active');
|
|
}
|
|
|
|
function switchType(type) {
|
|
const toggle = document.getElementById('typeToggle');
|
|
const tabs = toggle.querySelectorAll('.tab');
|
|
tabs.forEach(t => t.classList.remove('active'));
|
|
if (type === 'income') {
|
|
toggle.classList.add('income');
|
|
tabs[1].classList.add('active');
|
|
} else {
|
|
toggle.classList.remove('income');
|
|
tabs[0].classList.add('active');
|
|
}
|
|
}
|
|
|
|
function selectCat(el) {
|
|
document.querySelectorAll('.cat-item').forEach(c => c.classList.remove('selected'));
|
|
el.classList.add('selected');
|
|
}
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|