Penny Lens 移动端设计系统

2025年1月27日
8 分钟阅读
作者:Penny Lens Team

设计系统

🎨 设计理念

现代简约的设计理念,注重用户体验和移动端优化,为移动端财务管理提供直观、高效的界面设计。

🌈 色彩系统

主色系 - 金黄色 #ffda44

/* 主品牌色 - 金黄色 */
--primary-color: #ffda44;
--primary-50: #fffdf0;
--primary-100: #fff8d6;
--primary-200: #fff0ad;
--primary-300: #ffe584;
--primary-400: #ffda44; /* 主色 */
--primary-500: #ffd700;
--primary-600: #e6c200;
--primary-700: #ccad00;
--primary-800: #b39900;
--primary-900: #998000;
 
/* 渐变色 */
--primary-gradient: linear-gradient(135deg, #ffda44 0%, #ffd700 100%);
--primary-gradient-dark: linear-gradient(135deg, #e6c200 0%, #ccad00 100%);

功能色系

/* 页面主题色 */
--dashboard-blue: #3b82f6;     /* 首页 - 仪表盘 */
--transaction-green: #10b981;  /* 交易记录 - 交易 */
--wallet-purple: #8b5cf6;      /* 资产管理 - 钱包 */
--budget-blue: #3b82f6;        /* 预算管理 - 资金 */
--subscription-orange: #f59e0b; /* 订阅管理 - 日历 */
--category-cyan: #06b6d4;      /* 分类管理 - 标签 */
 
/* 语义色 */
--success: #10b981;   /* 成功/收入 */
--warning: #f59e0b;   /* 警告 */
--error: #ef4444;     /* 错误/支出 */
--info: #3b82f6;      /* 信息 */
--neutral: #6b7280;   /* 中性 */

中性色系

/* 灰色调 */
--gray-50: #f9fafb;
--gray-100: #f3f4f6;
--gray-200: #e5e7eb;
--gray-300: #d1d5db;
--gray-400: #9ca3af;
--gray-500: #6b7280;
--gray-600: #4b5563;
--gray-700: #374151;
--gray-800: #1f2937;
--gray-900: #111827;

深色模式

/* 深色背景 */
--dark-bg-primary: #0f172a;
--dark-bg-secondary: #1e293b;
--dark-bg-tertiary: #334155;
 
/* 深色文本 */
--dark-text-primary: #f8fafc;
--dark-text-secondary: #cbd5e1;
--dark-text-tertiary: #94a3b8;

📐 布局规范

页面结构模板

<template>
  <div class="page-container">
    <!-- 1. 页面头部 -->
    <div class="page-header">
      <div class="header-content">
        <div class="header-left">
          <div class="header-icon">
            <span class="i-{icon} text-white text-xl" />
          </div>
          <div class="header-text">
            <h2 class="page-title">页面标题</h2>
            <p class="page-subtitle">页面副标题</p>
          </div>
        </div>
        <div class="header-right">
          <button class="btn-primary">
            <span class="i-{icon} mr-1" />
            主要操作
          </button>
        </div>
      </div>
    </div>
 
    <!-- 2. 页面内容 -->
    <div class="page-content">
      <!-- 内容区域 -->
    </div>
  </div>
</template>

网格系统

/* 统计卡片布局 */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
  gap: 12px;
}
 
/* 响应式列数 */
.responsive-grid {
  display: grid;
  grid-template-columns: repeat(1, 1fr); /* 默认 1 列 */
  gap: 12px;
}
 
@media (min-width: 640px) {
  .responsive-grid {
    grid-template-columns: repeat(2, 1fr); /* sm: 2 列 */
  }
}
 
@media (min-width: 768px) {
  .responsive-grid {
    grid-template-columns: repeat(3, 1fr); /* md: 3 列 */
  }
}

🧩 组件规范

统计卡片 (StatCard)

<template>
  <div class="stat-card">
    <div class="card-header">
      <div class="card-icon">
        <span class="i-{icon} text-white text-base" />
      </div>
      <div class="card-info">
        <h3 class="card-title">{{ title }}</h3>
        <p class="card-subtitle">{{ subtitle }}</p>
      </div>
    </div>
    <div class="card-content">
      <div class="card-value">
        {{ prefix }}{{ formatValue(value) }}
      </div>
      <div class="card-footnote">
        {{ footnote }}
      </div>
    </div>
  </div>
</template>
 
<style scoped>
.stat-card {
  @apply bg-white rounded-xl p-3 shadow-sm border border-gray-100;
}
 
.card-header {
  @apply flex items-center gap-2 mb-2;
}
 
.card-icon {
  @apply w-8 h-8 rounded-lg bg-gradient-to-br flex items-center justify-center;
}
 
.card-info {
  @apply flex-1 min-w-0;
}
 
.card-title {
  @apply text-sm font-semibold text-gray-800 truncate;
}
 
.card-subtitle {
  @apply text-xs text-gray-500 truncate;
}
 
.card-content {
  @apply text-right;
}
 
.card-value {
  @apply text-lg font-bold text-gray-900;
}
 
.card-footnote {
  @apply text-xs text-gray-500;
}
</style>

列表组件

<template>
  <div class="list-container">
    <div class="list-item" v-for="item in items" :key="item.id">
      <div class="item-left">
        <div class="item-icon">
          <span class="i-{item.icon} text-white text-sm" />
        </div>
        <div class="item-content">
          <h4 class="item-title">{{ item.title }}</h4>
          <p class="item-subtitle">{{ item.subtitle }}</p>
        </div>
      </div>
      <div class="item-right">
        <div class="item-value">{{ item.value }}</div>
        <div class="item-action">
          <span class="i-{icon} text-gray-400 text-sm" />
        </div>
      </div>
    </div>
  </div>
</template>
 
<style scoped>
.list-container {
  @apply bg-white rounded-xl shadow-sm border border-gray-100;
}
 
.list-item {
  @apply flex items-center justify-between p-3 border-b border-gray-100 last:border-b-0;
}
 
.item-left {
  @apply flex items-center gap-3 flex-1 min-w-0;
}
 
.item-icon {
  @apply w-8 h-8 rounded-lg bg-gradient-to-br flex items-center justify-center;
}
 
.item-content {
  @apply flex-1 min-w-0;
}
 
.item-title {
  @apply text-sm font-semibold text-gray-800 truncate;
}
 
.item-subtitle {
  @apply text-xs text-gray-500 truncate;
}
 
.item-right {
  @apply flex items-center gap-2;
}
 
.item-value {
  @apply text-sm font-semibold text-gray-900;
}
 
.item-action {
  @apply flex items-center;
}
</style>

表单组件

<template>
  <div class="form-container">
    <div class="form-item" v-for="field in fields" :key="field.key">
      <label class="form-label">{{ field.label }}</label>
      <div class="form-input">
        <input
          v-model="formData[field.key]"
          :type="field.type"
          :placeholder="field.placeholder"
          class="input-field"
        />
      </div>
    </div>
    <div class="form-actions">
      <button class="btn-secondary" @click="handleCancel">取消</button>
      <button class="btn-primary" @click="handleSubmit">确定</button>
    </div>
  </div>
</template>
 
<style scoped>
.form-container {
  @apply bg-white rounded-xl p-4 shadow-sm border border-gray-100;
}
 
.form-item {
  @apply mb-4 last:mb-0;
}
 
.form-label {
  @apply block text-sm font-semibold text-gray-700 mb-2;
}
 
.form-input {
  @apply relative;
}
 
.input-field {
  @apply w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent;
}
 
.form-actions {
  @apply flex gap-3 mt-6;
}
 
.btn-primary {
  @apply flex-1 bg-blue-500 text-white py-2 px-4 rounded-lg font-semibold;
}
 
.btn-secondary {
  @apply flex-1 bg-gray-100 text-gray-700 py-2 px-4 rounded-lg font-semibold;
}
</style>

📝 字体规范

字体大小层级

/* 页面标题 */
.text-page-title {
  @apply text-xl font-bold text-gray-800;
}
 
/* 卡片标题 */
.text-card-title {
  @apply text-sm font-semibold text-gray-800;
}
 
/* 列表标题 */
.text-list-title {
  @apply text-sm font-semibold text-gray-800;
}
 
/* 正文内容 */
.text-body {
  @apply text-sm text-gray-600;
}
 
/* 辅助文字 */
.text-caption {
  @apply text-xs text-gray-500;
}
 
/* 数值显示 */
.text-value {
  @apply text-base font-bold text-gray-900;
}
 
/* 小数值 */
.text-value-small {
  @apply text-sm font-semibold text-gray-800;
}

文本处理

/* 不换行 */
.text-nowrap {
  @apply whitespace-nowrap;
}
 
/* 截断 */
.text-truncate {
  @apply truncate;
}
 
/* 多行截断 */
.text-clamp-2 {
  @apply overflow-hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

📏 间距系统

基础间距

/* 内边距 */
.p-xs { padding: 4px; }
.p-sm { padding: 8px; }
.p-md { padding: 12px; }
.p-lg { padding: 16px; }
.p-xl { padding: 20px; }
.p-2xl { padding: 24px; }
 
/* 外边距 */
.m-xs { margin: 4px; }
.m-sm { margin: 8px; }
.m-md { margin: 12px; }
.m-lg { margin: 16px; }
.m-xl { margin: 20px; }
.m-2xl { margin: 24px; }
 
/* 间距 */
.gap-xs { gap: 4px; }
.gap-sm { gap: 8px; }
.gap-md { gap: 12px; }
.gap-lg { gap: 16px; }
.gap-xl { gap: 20px; }

组件间距

/* 页面级间距 */
.page-spacing {
  @apply px-4 py-6;
}
 
/* 卡片间距 */
.card-spacing {
  @apply p-3;
}
 
/* 列表间距 */
.list-spacing {
  @apply p-3;
}
 
/* 表单间距 */
.form-spacing {
  @apply p-4;
}
 
/* 网格间距 */
.grid-spacing {
  @apply gap-3;
}

✨ 动画效果

页面动画

/* 页面进入动画 */
.page-enter {
  animation: slideInUp 0.3s ease-out;
}
 
@keyframes slideInUp {
  from {
    transform: translateY(20px);
    opacity: 0;
  }
  to {
    transform: translateY(0);
    opacity: 1;
  }
}
 
/* 页面退出动画 */
.page-leave {
  animation: slideOutDown 0.3s ease-in;
}
 
@keyframes slideOutDown {
  from {
    transform: translateY(0);
    opacity: 1;
  }
  to {
    transform: translateY(20px);
    opacity: 0;
  }
}

交互动画

/* 按钮悬停 */
.btn-hover {
  @apply transition-all duration-200;
}
 
.btn-hover:hover {
  @apply transform scale-105;
}
 
/* 卡片悬停 */
.card-hover {
  @apply transition-all duration-300;
}
 
.card-hover:hover {
  @apply transform -translate-y-1 shadow-md;
}
 
/* 列表项悬停 */
.list-item-hover {
  @apply transition-all duration-200;
}
 
.list-item-hover:hover {
  @apply bg-gray-50;
}

📱 响应式设计

断点系统

/* 移动端 */
@media (max-width: 639px) {
  .mobile-only { display: block; }
  .desktop-only { display: none; }
}
 
/* 平板端 */
@media (min-width: 640px) and (max-width: 1023px) {
  .tablet-only { display: block; }
}
 
/* 桌面端 */
@media (min-width: 1024px) {
  .desktop-only { display: block; }
  .mobile-only { display: none; }
}

网格响应式

/* 统计卡片 */
.stats-responsive {
  display: grid;
  grid-template-columns: repeat(1, 1fr); /* 默认 1 列 */
  gap: 12px;
}
 
@media (min-width: 640px) {
  .stats-responsive {
    grid-template-columns: repeat(2, 1fr); /* sm: 2 列 */
  }
}
 
@media (min-width: 768px) {
  .stats-responsive {
    grid-template-columns: repeat(3, 1fr); /* md: 3 列 */
  }
}

🎯 主色系使用规范

1. 主色 #ffda44 使用场景

/* 主要按钮 */
.btn-primary {
  @apply bg-[#ffda44] text-gray-900 font-semibold;
}
 
/* 重要提示 */
.alert-primary {
  @apply bg-[#fffdf0] border-[#ffda44] text-gray-800;
}
 
/* 高亮文本 */
.text-highlight {
  @apply text-[#ffda44] font-semibold;
}
 
/* 进度条 */
.progress-primary {
  @apply bg-[#ffda44];
}
 
/* 徽章 */
.badge-primary {
  @apply bg-[#ffda44] text-gray-900;
}

2. 渐变色使用

/* 卡片头部 */
.card-header-primary {
  background: linear-gradient(135deg, #ffda44 0%, #ffd700 100%);
}
 
/* 按钮渐变 */
.btn-gradient-primary {
  background: linear-gradient(135deg, #ffda44 0%, #ffd700 100%);
}
 
/* 图标背景 */
.icon-bg-primary {
  background: linear-gradient(135deg, #ffda44 0%, #ffd700 100%);
}

3. 深色模式适配

/* 深色模式下的主色 */
.dark .btn-primary {
  @apply bg-[#e6c200] text-gray-900;
}
 
.dark .alert-primary {
  @apply bg-[#332a00] border-[#e6c200] text-[#ffda44];
}
 
.dark .text-highlight {
  @apply text-[#e6c200];
}

📋 设计原则

1. 统一性原则

  • 所有页面使用统一的设计语言
  • 保持视觉风格的一致性
  • 组件复用最大化

2. 移动端优先

  • 移动端优先设计
  • 触摸友好的交互
  • 简洁的操作流程
  • 快速响应

3. 可读性原则

  • 确保文本清晰易读
  • 合理的对比度
  • 避免文本换行
  • 合适的字体大小

4. 响应式原则

  • 适配各种屏幕尺寸
  • 移动端优先设计
  • 渐进式增强
  • 性能优化

🚀 最佳实践

1. 性能优化

  • 使用 v-memo 缓存复杂计算
  • 合理使用 v-showv-if
  • 图片懒加载
  • 组件按需加载

2. 用户体验

  • 加载状态提示
  • 错误边界处理
  • 操作反馈及时
  • 触摸友好的交互

3. 可维护性

  • 组件职责单一
  • 样式模块化
  • 类型定义完整
  • 文档注释清晰

4. 可扩展性

  • 主题色可配置
  • 组件参数灵活
  • 样式可覆盖
  • 功能可扩展

📱 平台适配

微信小程序

/* 微信小程序特有样式 */
.wechat-specific {
  /* 微信小程序特有样式 */
}

支付宝小程序

/* 支付宝小程序特有样式 */
.alipay-specific {
  /* 支付宝小程序特有样式 */
}

H5

/* H5 特有样式 */
.h5-specific {
  /* H5 特有样式 */
}

App

/* App 特有样式 */
.app-specific {
  /* App 特有样式 */
}

重要提醒:

  1. 始终遵循设计系统规范
  2. 确保移动端兼容性
  3. 保持视觉一致性
  4. 优化用户体验
  5. 遵循响应式设计原则