diff --git a/client/src/App.vue b/client/src/App.vue
index 0e94386..d81a597 100644
--- a/client/src/App.vue
+++ b/client/src/App.vue
@@ -49,10 +49,15 @@ onLaunch(() => {
diff --git a/client/src/pages/add/index.vue b/client/src/pages/add/index.vue
index 37aed76..846bac5 100644
--- a/client/src/pages/add/index.vue
+++ b/client/src/pages/add/index.vue
@@ -216,51 +216,44 @@ function goBack() {
diff --git a/client/src/pages/admin/users.vue b/client/src/pages/admin/users.vue
index bb0b7c1..130413a 100644
--- a/client/src/pages/admin/users.vue
+++ b/client/src/pages/admin/users.vue
@@ -162,86 +162,77 @@ onReachBottom(() => {
diff --git a/client/src/pages/bills/filter-panel.vue b/client/src/pages/bills/filter-panel.vue
index 217b386..5ae41bc 100644
--- a/client/src/pages/bills/filter-panel.vue
+++ b/client/src/pages/bills/filter-panel.vue
@@ -290,7 +290,6 @@ async function handleDeleteSaved(id: number) {
border-radius: 40rpx 40rpx 0 0;
display: flex;
flex-direction: column;
- box-sizing: border-box;
overflow: hidden;
}
@@ -332,13 +331,11 @@ async function handleDeleteSaved(id: number) {
padding: 24rpx 40rpx;
max-height: 55vh;
overflow-x: hidden;
- box-sizing: border-box;
}
.filter-section {
margin-bottom: 32rpx;
width: 100%;
- box-sizing: border-box;
}
.section-label {
@@ -354,7 +351,6 @@ async function handleDeleteSaved(id: number) {
flex-wrap: wrap;
gap: 12rpx;
width: 100%;
- box-sizing: border-box;
}
.chip {
@@ -388,7 +384,6 @@ async function handleDeleteSaved(id: number) {
align-items: center;
gap: 16rpx;
width: 100%;
- box-sizing: border-box;
}
.date-picker {
@@ -420,7 +415,6 @@ async function handleDeleteSaved(id: number) {
align-items: center;
gap: 16rpx;
width: 100%;
- box-sizing: border-box;
}
.amount-input {
@@ -440,7 +434,6 @@ async function handleDeleteSaved(id: number) {
}
.keyword-input {
- width: 100%;
height: 72rpx;
padding: 0 20rpx;
background: #fff8f0;
diff --git a/client/src/pages/bills/index.vue b/client/src/pages/bills/index.vue
index 9a65606..ad08f4c 100644
--- a/client/src/pages/bills/index.vue
+++ b/client/src/pages/bills/index.vue
@@ -245,60 +245,36 @@ onPullDownRefresh(() => {
diff --git a/client/src/pages/budget/index.vue b/client/src/pages/budget/index.vue
index 628e86b..50c13ee 100644
--- a/client/src/pages/budget/index.vue
+++ b/client/src/pages/budget/index.vue
@@ -180,46 +180,41 @@ function goBack() {
diff --git a/client/src/pages/profile-edit/index.vue b/client/src/pages/profile-edit/index.vue
index 53bb887..3f15d9f 100644
--- a/client/src/pages/profile-edit/index.vue
+++ b/client/src/pages/profile-edit/index.vue
@@ -105,43 +105,34 @@ async function saveProfile() {
diff --git a/client/src/pages/profile/index.vue b/client/src/pages/profile/index.vue
index b4d6fb2..4581f95 100644
--- a/client/src/pages/profile/index.vue
+++ b/client/src/pages/profile/index.vue
@@ -357,41 +357,38 @@ async function exportData() {
diff --git a/client/src/pages/stats/index.vue b/client/src/pages/stats/index.vue
index 60d86b0..13322c2 100644
--- a/client/src/pages/stats/index.vue
+++ b/client/src/pages/stats/index.vue
@@ -313,46 +313,36 @@ function getBarWidth(amount: number) {
diff --git a/client/src/styles/mixins.scss b/client/src/styles/mixins.scss
new file mode 100644
index 0000000..c8933bd
--- /dev/null
+++ b/client/src/styles/mixins.scss
@@ -0,0 +1,238 @@
+/* ===== 公共 Mixins ===== */
+
+// 页面基础样式
+@mixin page-base {
+ min-height: 100vh;
+ background: $bg;
+ padding-bottom: 180rpx;
+}
+
+// 粘性头部
+@mixin sticky-header {
+ position: sticky;
+ top: 0;
+ z-index: 100;
+ background: $bg;
+}
+
+// 状态栏
+@mixin status-bar {
+ background: $bg;
+}
+
+// 卡片
+@mixin card($radius: $radius-2xl) {
+ background: $surface;
+ border-radius: $radius;
+ border: 2rpx solid $border;
+ box-shadow: $shadow-md;
+}
+
+// 导航栏
+@mixin nav-bar {
+ display: flex;
+ align-items: center;
+ padding: $space-sm $space-xl $space-md;
+}
+
+// 返回按钮
+@mixin nav-back {
+ width: 88rpx;
+ height: 88rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ &:active { opacity: 0.6; }
+}
+
+// 页面标题
+@mixin page-title {
+ display: block;
+ text-align: center;
+ font-size: $font-2xl;
+ font-weight: 600;
+ color: $text;
+ padding: $space-sm 0 $space-md;
+}
+
+// 状态空/错误/加载
+@mixin state-box {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: $space-sm;
+ padding: 120rpx 0;
+}
+
+@mixin state-text {
+ font-size: $font-lg;
+ color: $text-muted;
+}
+
+// Tabs
+@mixin tabs-wrap {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: $space-sm;
+}
+
+@mixin tabs {
+ display: inline-flex;
+ background: $surface-warm;
+ border-radius: $radius-lg;
+ padding: $space-xs;
+}
+
+@mixin tab {
+ padding: $space-sm $space-xl;
+ border-radius: $radius-lg;
+ font-size: $font-lg;
+ color: $text-sec;
+ transition: all $transition-normal;
+
+ &.active {
+ background: $surface;
+ color: $primary;
+ font-weight: 600;
+ box-shadow: $shadow-sm;
+ }
+}
+
+// 弹窗
+@mixin modal-mask {
+ position: fixed;
+ top: 0; left: 0; right: 0; bottom: 0;
+ background: rgba(0, 0, 0, 0.4);
+ z-index: 200;
+ display: flex;
+ align-items: flex-end;
+}
+
+@mixin bottom-modal {
+ width: 100%;
+ max-height: 80vh;
+ background: $surface;
+ border-radius: $radius-2xl $radius-2xl 0 0;
+ display: flex;
+ flex-direction: column;
+}
+
+@mixin modal-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ padding: $space-lg $space-xl $space-md;
+ border-bottom: 2rpx solid $border;
+}
+
+@mixin modal-title {
+ font-size: $font-xl;
+ font-weight: 600;
+ color: $text;
+}
+
+@mixin modal-close {
+ width: 56rpx;
+ height: 56rpx;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ background: $border;
+ &:active { background: darken($border, 5%); }
+}
+
+// 列表项
+@mixin list-item {
+ display: flex;
+ align-items: center;
+ gap: $space-md;
+ padding: $space-lg $space-lg;
+ background: $surface;
+ border-radius: $radius-xl;
+ border: 2rpx solid $border;
+ box-shadow: $shadow-md;
+ margin-bottom: $space-sm;
+}
+
+// 输入框
+@mixin input {
+ width: 100%;
+ height: 80rpx;
+ padding: 0 $space-md;
+ background: $bg;
+ border-radius: $radius-lg;
+ border: 2rpx solid $border;
+ font-size: $font-lg;
+ color: $text;
+}
+
+// 按钮
+@mixin btn-primary {
+ height: 80rpx;
+ padding: 0 $space-lg;
+ background: linear-gradient(135deg, $primary, $primary-dark);
+ border-radius: $radius-lg;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ &:active { opacity: 0.8; }
+}
+
+@mixin btn-primary-text {
+ font-size: $font-lg;
+ font-weight: 600;
+ color: $surface;
+ white-space: nowrap;
+}
+
+@mixin btn-ghost {
+ padding: $space-sm $space-md;
+ background: $surface-warm;
+ border-radius: $radius-md;
+ &:active { background: $border; }
+}
+
+@mixin btn-ghost-text {
+ font-size: $font-md;
+ color: $primary;
+}
+
+// Shimmer 动画
+@mixin shimmer {
+ background: linear-gradient(90deg, $border 25%, $surface-warm 50%, $border 75%);
+ background-size: 200% 100%;
+ animation: shimmer 1.5s infinite;
+}
+
+@keyframes shimmer {
+ 0% { background-position: 200% 0; }
+ 100% { background-position: -200% 0; }
+}
+
+// Flex 工具
+@mixin flex-center {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
+
+@mixin flex-between {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+// 文本溢出
+@mixin text-ellipsis {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+// 安全区域
+@mixin safe-bottom {
+ padding-bottom: constant(safe-area-inset-bottom);
+ padding-bottom: env(safe-area-inset-bottom);
+}
diff --git a/client/src/uni.scss b/client/src/uni.scss
index 9f887c5..8ec32f3 100644
--- a/client/src/uni.scss
+++ b/client/src/uni.scss
@@ -1,31 +1,69 @@
-/* Design Tokens */
+/* ===== Design Tokens ===== */
+
+// 颜色系统
$primary: #FF8C69;
-$primary-light: #FFB89A;
+$primary-light: #FFE8E0;
$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;
+$danger-light: #FFE8E8;
+
$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);
+// 间距系统
+$space-xs: 8rpx;
+$space-sm: 16rpx;
+$space-md: 24rpx;
+$space-lg: 32rpx;
+$space-xl: 40rpx;
+$space-2xl: 48rpx;
-$radius-sm: 8rpx;
-$radius-md: 12rpx;
-$radius-lg: 16rpx;
-$radius-xl: 20rpx;
+// 圆角系统
+$radius-xs: 8rpx;
+$radius-sm: 12rpx;
+$radius-md: 16rpx;
+$radius-lg: 20rpx;
+$radius-xl: 28rpx;
+$radius-2xl: 40rpx;
$radius-full: 9999rpx;
-/* uCharts theme */
+// 字体系统
+$font-xs: 20rpx;
+$font-sm: 22rpx;
+$font-md: 24rpx;
+$font-base: 26rpx;
+$font-lg: 28rpx;
+$font-xl: 32rpx;
+$font-2xl: 36rpx;
+$font-3xl: 40rpx;
+$font-4xl: 56rpx;
+
+// 阴影系统
+$shadow-sm: 2rpx 2rpx 8rpx rgba(45, 27, 27, 0.06);
+$shadow-md: 4rpx 4rpx 12rpx rgba(45, 27, 27, 0.08);
+$shadow-lg: 8rpx 8rpx 24rpx rgba(45, 27, 27, 0.12);
+$shadow-clay: $shadow-md, inset -2rpx -2rpx 6rpx rgba(45, 27, 27, 0.04);
+$shadow-clay-hover: 6rpx 6rpx 16rpx rgba(45, 27, 27, 0.12), inset -2rpx -2rpx 6rpx rgba(45, 27, 27, 0.04);
+$shadow-clay-pressed: inset 2rpx 2rpx 6rpx rgba(45, 27, 27, 0.1);
+
+// 动画
+$transition-fast: 0.15s ease;
+$transition-normal: 0.2s ease;
+$transition-slow: 0.3s ease;
+
+/* ===== uCharts theme ===== */
page {
--u-charts-color-0: #FF8C69;
--u-charts-color-1: #FFD166;