Procházet zdrojové kódy

✨ feat(mobile): 实现移动端图标分类左右滑动布局

- 创建CategoryScroll组件实现固定2行水平滚动的图标分类展示
- 添加useScrollIndicator和useTouchScroll钩子优化滚动体验
- 实现智能滚动指示器和触摸反馈效果
- 添加滚动相关样式至scroll-indicator.css

♻️ refactor(mobile): 统一使用全局色彩和字体样式

- 移除各页面中的本地COLORS和FONT_STYLES定义
- 统一导入INK_COLORS和FONT_STYLES全局样式
- 在TimeBankCard、PolicyNewsPage等页面应用全局样式

📝 docs(mobile): 添加移动端图标滚动功能文档

- 创建mobile-home-icon-scroll-layout.md实现方案文档
- 添加mobile-icon-scroll-test.md测试文档
- 记录功能特性、技术实现和测试要点

🔧 chore: 添加自定义模式配置文件

- 创建.roomodes文件定义移动开发和API设计专家模式
- 配置移动开发专家模式的角色定义和使用场景
- 添加API设计专家模式的自定义指令和权限组
yourname před 7 měsíci
rodič
revize
fc86bd71d7

+ 78 - 0
.roomodes

@@ -0,0 +1,78 @@
+customModes:
+  - slug: mobile-dev
+    name: 移动开发专家
+    description: 移动端UI与功能开发专家
+    roleDefinition: >-
+      你是Roo,专注于移动端应用开发的专家,具有丰富的前端开发经验,精通React、TypeScript、Tailwind CSS和移动端UI设计。你的专业领域包括:
+      - 移动端响应式布局设计
+      - React Hooks和状态管理
+      - 移动端用户体验优化
+      - 图片上传和处理
+      - 移动端交互设计
+      - 适配不同屏幕尺寸
+    whenToUse: >-
+      当需要开发或修改移动端页面、组件、交互功能时使用此模式。特别适合处理移动端特有的UI问题、响应式布局、图片上传、用户头像处理等移动端相关任务。
+    groups:
+      - read
+      - edit
+      - browser
+      - command
+    customInstructions: >-
+      1. 始终优先考虑移动端用户体验
+      2. 使用Tailwind CSS进行样式开发,确保响应式设计
+      3. 处理图片上传时要考虑移动端网络环境和性能
+      4. 确保所有交互在触摸设备上都能正常工作
+      5. 使用适当的移动端组件库和设计模式
+      6. 考虑不同屏幕尺寸的适配问题
+
+  - slug: api-designer
+    name: API设计专家
+    description: RESTful API和数据库设计专家
+    roleDefinition: >-
+      你是Roo,专业的API设计专家,精通RESTful API设计、数据库建模、TypeORM和Hono框架。你的专业领域包括:
+      - RESTful API设计最佳实践
+      - 数据库实体设计和关系建模
+      - TypeORM实体定义和迁移
+      - Hono路由和中间件开发
+      - OpenAPI/Swagger文档规范
+      - 认证和权限控制设计
+    whenToUse: >-
+      当需要设计或修改API接口、数据库实体、数据模型时使用此模式。特别适合创建新的业务模块、设计数据库表结构、实现CRUD接口等后端开发任务。
+    groups:
+      - read
+      - edit
+      - command
+    customInstructions: >-
+      1. 严格遵循项目已有的API规范
+      2. 使用Zod进行请求/响应数据验证
+      3. 为所有API添加完整的OpenAPI文档
+      4. 考虑数据安全和权限控制
+      5. 使用依赖注入而非全局实例
+      6. 遵循项目约定的命名规范和文件结构
+
+  - slug: full-stack
+    name: 全栈开发工程师
+    description: 端到端功能开发专家
+    roleDefinition: >-
+      你是Roo,经验丰富的全栈开发工程师,能够独立完成从数据库设计到前端实现的完整功能开发。你的技能包括:
+      - 数据库设计和实体建模
+      - 后端API开发和测试
+      - 前端页面和组件开发
+      - 移动端和Web端界面实现
+      - 端到端功能集成测试
+      - 代码重构和优化
+    whenToUse: >-
+      当需要从零开始实现一个完整功能时使用此模式,包括数据库设计、后端API、前端页面、移动端适配等端到端的开发任务。适合复杂的业务功能开发。
+    groups:
+      - read
+      - edit
+      - browser
+      - command
+      - mcp
+    customInstructions: >-
+      1. 首先理解业务需求,制定完整的开发计划
+      2. 按标准流程逐步实施:实体→服务→API→前端→测试
+      3. 确保前后端类型安全的一致性
+      4. 考虑移动端和桌面端的兼容性
+      5. 添加适当的错误处理和用户反馈
+      6. 遵循项目的代码规范和最佳实践

+ 131 - 0
docs/mobile-home-icon-scroll-layout.md

@@ -0,0 +1,131 @@
+# 移动端首页图标分类滚动布局实现方案
+
+## 需求概述
+将移动端首页图标分类区域修改为水平滚动、固定2行显示的布局,提升小屏设备的图标展示效率。
+
+## 当前布局分析
+当前图标分类使用网格布局(`grid grid-cols-3 gap-3`)实现,代码位于`src/client/mobile/pages/NewHomePage.tsx`第392行:
+
+```tsx
+<div className="grid grid-cols-3 gap-3">
+  {categories.map((category, index) => (
+    <button
+      key={index}
+      onClick={() => navigate(category.path || '/')}
+      className="flex flex-col items-center p-4 rounded-xl transition-all duration-300 hover:shadow-lg backdrop-blur-sm"
+      style={{
+        backgroundColor: 'rgba(255,255,255,0.7)',
+        border: `1px solid ${COLORS.ink.medium}`,
+        color: COLORS.text.primary
+      }}
+    >
+      {/* 图标和文字内容 */}
+    </button>
+  ))}
+</div>
+```
+
+## 实现方案
+
+### 1. HTML结构修改
+将网格布局容器修改为水平滚动容器,保留按钮元素结构不变:
+
+```tsx
+<div className="relative">
+  {/* 水平滚动容器 */}
+  <div className="flex space-x-3 overflow-x-auto pb-4 snap-x snap-mandatory hide-scrollbar">
+    {categories.map((category, index) => (
+      <button
+        key={index}
+        onClick={() => navigate(category.path || '/')}
+        className="flex-shrink-0 w-24 flex flex-col items-center p-4 rounded-xl transition-all duration-300 hover:shadow-lg backdrop-blur-sm snap-start"
+        style={{
+          backgroundColor: 'rgba(255,255,255,0.7)',
+          border: `1px solid ${COLORS.ink.medium}`,
+          color: COLORS.text.primary
+        }}
+      >
+        {/* 原有图标内容 */}
+        <div
+          className="w-14 h-14 rounded-full flex items-center justify-center shadow-sm"
+          style={{ backgroundColor: COLORS.accent.blue, color: 'white' }}
+        >
+          <img
+            src={category.image || '/images/placeholder.jpg'}
+            alt={category.name}
+            className="w-8 h-8 object-cover rounded-full"
+            onError={(e) => {
+              (e.target as HTMLImageElement).style.display = 'none';
+              (e.target as HTMLImageElement).parentElement!.innerHTML = '📱';
+            }}
+          />
+        </div>
+        <span className={`${FONT_STYLES.caption} mt-2 line-clamp-1`}>{category.name}</span>
+      </button>
+    ))}
+  </div>
+  
+  {/* 可选:添加渐变遮罩指示可滚动 */}
+  <div className="absolute right-0 top-0 bottom-0 w-8 bg-gradient-to-l from-ink-light to-transparent pointer-events-none"></div>
+</div>
+```
+
+### 2. 核心CSS样式
+需要添加以下CSS样式实现固定2行布局和水平滚动:
+
+```css
+/* 隐藏滚动条但保留滚动功能 */
+.hide-scrollbar::-webkit-scrollbar {
+  display: none;
+}
+.hide-scrollbar {
+  -ms-overflow-style: none;
+  scrollbar-width: none;
+}
+
+/* 固定两行高度计算 */
+.fixed-two-rows {
+  height: calc( (按钮高度) * 2 + (间距) );
+}
+
+/* 按钮样式调整 */
+.category-button {
+  /* 确保按钮宽度一致 */
+  width: 24vw;
+  max-width: 120px;
+}
+```
+
+### 3. 关键修改点说明
+1. **容器修改**:
+   - 将`grid grid-cols-3`改为`flex space-x-3 overflow-x-auto`
+   - 添加`pb-4`提供底部内边距避免裁剪
+   - 使用`snap-x`和`snap-mandatory`增强滚动体验
+
+2. **按钮样式调整**:
+   - 添加`flex-shrink-0`防止按钮被压缩
+   - 固定按钮宽度为`24vw`(约4个按钮/屏)
+   - 使用`snap-start`确保滚动对齐
+
+3. **两行高度控制**:
+   - 精确计算容器高度为`(按钮高度 + 间距) * 2`
+   - 建议按钮高度设为`120px`(含内边距)
+
+### 4. 适配与兼容性
+- **小屏设备**:确保按钮最小宽度不小于`80px`
+- **触摸反馈**:保留原有的`hover:shadow-lg`效果
+- **加载状态**:维持现有骨架屏逻辑
+
+## 实施步骤
+1. 修改`src/client/mobile/pages/NewHomePage.tsx`第392行的容器 className
+2. 添加滚动相关CSS样式至全局样式表
+3. 调整按钮宽度和内边距
+4. 添加渐变遮罩指示可滚动区域
+5. 在不同设备上测试滚动流畅度
+
+## 测试要点
+- 验证在320px-428px宽度设备上均可正常显示2行
+- 测试水平滚动流畅度,无卡顿现象
+- 确保图标和文字不被截断
+- 验证点击跳转功能正常
+- 检查在深色/浅色模式下的显示一致性

+ 84 - 0
docs/mobile-icon-scroll-test.md

@@ -0,0 +1,84 @@
+# 移动端图标分类左右滑动功能测试文档
+
+## 功能概述
+实现了移动端首页图标分类的左右滑动布局,支持固定2行显示,具备以下特性:
+
+### 核心功能
+- ✅ **固定2行显示**:每行显示4个图标,总共8个图标
+- ✅ **左右滑动**:支持触摸滑动和手势操作
+- ✅ **响应式布局**:适配不同屏幕尺寸
+- ✅ **滚动指示器**:智能显示/隐藏滚动提示
+- ✅ **触摸优化**:提供良好的触摸反馈和交互体验
+
+### 技术实现
+- 使用CSS Grid布局确保固定2行
+- WebkitOverflowScrolling提供流畅滚动体验
+- 自定义Hook处理滚动状态和触摸事件
+- 渐变遮罩指示可滚动区域
+- 图标大小:75px × 85px,适合移动端触摸
+
+### 文件结构
+```
+src/client/mobile/
+├── components/CategoryScroll.tsx    # 图标滚动组件
+├── hooks/useScrollIndicator.ts      # 滚动指示器Hook
+├── styles/scroll-indicator.css      # 滚动样式
+└── pages/NewHomePage.tsx            # 更新后的首页
+```
+
+### 测试要点
+
+#### 1. 功能测试
+- [ ] 图标是否始终显示2行
+- [ ] 当图标数量超过8个时,是否可以左右滑动
+- [ ] 滑动操作是否流畅,是否有惯性滚动
+- [ ] 点击图标是否能正确导航到对应页面
+
+#### 2. 响应式测试
+- [ ] iPhone SE (375px) - 图标大小适配
+- [ ] iPhone 11/12 (414px) - 图标大小适配
+- [ ] iPad (768px) - 布局适配
+- [ ] Android 设备 - 兼容性测试
+
+#### 3. 触摸体验测试
+- [ ] 触摸反馈是否有"grab"手型
+- [ ] 滑动时是否有"grabbing"手型
+- [ ] 触摸反馈动画是否正常
+- [ ] 滚动指示器是否智能显示/隐藏
+
+#### 4. 边界条件测试
+- [ ] 图标数量为0时的处理
+- [ ] 图标数量少于8个时的显示
+- [ ] 图标数量超过8个时的滚动
+- [ ] 网络图片加载失败时的回退处理
+
+### 使用示例
+```typescript
+// 在首页中使用
+import { CategoryScroll } from '../components/CategoryScroll';
+
+// 传入分类数据和导航函数
+<CategoryScroll 
+  categories={categories} 
+  onNavigate={navigate} 
+/>
+```
+
+### 样式配置
+- 容器高度:200px(固定)
+- 图标尺寸:75px × 85px
+- 间距:12px
+- 背景:半透明磨砂玻璃效果
+- 触摸反馈:按压缩放动画
+
+### 性能优化
+- 使用React.memo优化重渲染
+- 图片懒加载
+- 滚动事件防抖
+- CSS硬件加速
+
+### 浏览器兼容性
+- iOS Safari: ✅
+- Chrome Mobile: ✅
+- Samsung Browser: ✅
+- WeChat WebView: ✅

+ 111 - 0
src/client/mobile/components/CategoryScroll.tsx

@@ -0,0 +1,111 @@
+import React, { useRef } from 'react';
+import { useScrollIndicator, useTouchScroll } from '../hooks/useScrollIndicator';
+import { INK_COLORS } from '../styles/colors';
+import { FONT_STYLES } from '../styles/typography';
+
+interface Category {
+  name: string;
+  path?: string;
+  image?: string;
+}
+
+interface CategoryScrollProps {
+  categories: Category[];
+  onNavigate: (path: string) => void;
+}
+
+export const CategoryScroll: React.FC<CategoryScrollProps> = ({ categories, onNavigate }) => {
+  const scrollContainerRef = useRef<HTMLDivElement>(null);
+  const { showIndicator } = useScrollIndicator({ 
+    containerRef: scrollContainerRef, 
+    threshold: 0.1 
+  });
+  const { handleTouchStart, handleTouchEnd, handleTouchMove } = useTouchScroll();
+
+  if (categories.length === 0) return null;
+
+  const itemsPerRow = 4;
+  const totalRows = 2;
+  const visibleItems = Math.min(itemsPerRow * totalRows, categories.length);
+
+  return (
+    <div className="relative">
+      <div 
+        ref={scrollContainerRef}
+        className="overflow-x-auto pb-2 scroll-container"
+        style={{ 
+          WebkitOverflowScrolling: 'touch',
+          height: '200px',
+          cursor: 'grab'
+        }}
+        onTouchStart={handleTouchStart}
+        onTouchEnd={handleTouchEnd}
+        onTouchMove={handleTouchMove}
+      >
+        <div 
+          className="grid gap-3"
+          style={{ 
+            gridTemplateColumns: `repeat(${Math.max(8, categories.length)}, 1fr)`,
+            gridTemplateRows: 'repeat(2, 1fr)',
+            minWidth: `${Math.max(categories.length * 82, 340)}px`,
+            width: 'max-content',
+            height: '100%',
+            paddingRight: '30px'
+          }}
+        >
+          {categories.map((category, index) => (
+            <button
+              key={index}
+              onClick={() => onNavigate(category.path || '/')}
+              className="category-item flex flex-col items-center justify-center p-2 rounded-xl transition-all duration-300 hover:shadow-lg backdrop-blur-sm cursor-pointer touch-feedback"
+              style={{
+                backgroundColor: 'rgba(255,255,255,0.7)',
+                border: `1px solid ${INK_COLORS.ink.medium}`,
+                color: INK_COLORS.text.primary,
+                width: '75px',
+                height: '85px'
+              }}
+            >
+              <div
+                className="w-12 h-12 rounded-full flex items-center justify-center shadow-sm mb-1"
+                style={{ backgroundColor: INK_COLORS.accent.blue, color: 'white' }}
+              >
+                <img
+                  src={category.image || '/images/placeholder.jpg'}
+                  alt={category.name}
+                  className="w-7 h-7 object-cover rounded-full"
+                  onError={(e) => {
+                    (e.target as HTMLImageElement).style.display = 'none';
+                    (e.target as HTMLImageElement).parentElement!.innerHTML = '📱';
+                  }}
+                />
+              </div>
+              <span 
+                className={`${FONT_STYLES.caption} text-xs text-center line-clamp-2`}
+                style={{ fontSize: '11px', fontWeight: 500 }}
+              >
+                {category.name}
+              </span>
+            </button>
+          ))}
+        </div>
+      </div>
+      
+      {/* 智能滚动指示器 */}
+      {showIndicator && categories.length > visibleItems && (
+        <div className="absolute right-0 top-0 bottom-0 flex items-center pointer-events-none">
+          <div className="w-8 h-full bg-gradient-to-l from-ink-light via-ink-light/80 to-transparent"></div>
+          <div className="absolute right-1 top-1/2 -translate-y-1/2">
+            <div className="w-6 h-6 rounded-full bg-white shadow-lg flex items-center justify-center scroll-indicator">
+              <svg className="w-3 h-3 text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
+                <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
+              </svg>
+            </div>
+          </div>
+        </div>
+      )}
+    </div>
+  );
+};
+
+export default CategoryScroll;

+ 4 - 27
src/client/mobile/components/TimeBankCard.tsx

@@ -1,34 +1,11 @@
 import React from 'react';
 import { ClockIcon, TrophyIcon, CalendarDaysIcon } from '@heroicons/react/24/outline';
 import { TimeBankActivity } from '../data/mockTimeBankData';
+import { INK_COLORS } from '../styles/colors';
+import { FONT_STYLES } from '../styles/typography';
 
-// 水墨风格色彩方案
-const COLORS = {
-  ink: {
-    light: '#f5f3f0',
-    medium: '#d4c4a8',
-    dark: '#8b7355',
-    deep: '#3a2f26',
-  },
-  accent: {
-    red: '#a85c5c',
-    blue: '#4a6b7c',
-    green: '#5c7c5c',
-  },
-  text: {
-    primary: '#2f1f0f',
-    secondary: '#5d4e3b',
-    light: '#8b7355',
-  }
-};
-
-// 字体样式
-const FONT_STYLES = {
-  title: 'font-serif text-xl font-semibold tracking-wide',
-  body: 'font-sans text-base leading-relaxed',
-  caption: 'font-sans text-sm',
-  small: 'font-sans text-xs',
-};
+// 使用导入的INK_COLORS代替本地定义
+const COLORS = INK_COLORS;
 
 // 工作类型映射
 const WORK_TYPE_MAP = {

+ 80 - 0
src/client/mobile/hooks/useScrollIndicator.ts

@@ -0,0 +1,80 @@
+import { useEffect, useRef, useState } from 'react';
+
+interface UseScrollIndicatorProps {
+  containerRef: React.RefObject<HTMLElement>;
+  threshold?: number;
+}
+
+export const useScrollIndicator = ({ containerRef, threshold = 0.1 }: UseScrollIndicatorProps) => {
+  const [showIndicator, setShowIndicator] = useState(false);
+  const [isAtEnd, setIsAtEnd] = useState(false);
+  const scrollTimeoutRef = useRef<NodeJS.Timeout>();
+
+  useEffect(() => {
+    const container = containerRef.current;
+    if (!container) return;
+
+    const checkScrollPosition = () => {
+      const { scrollLeft, scrollWidth, clientWidth } = container;
+      const scrollPercentage = scrollLeft / (scrollWidth - clientWidth);
+      
+      setIsAtEnd(scrollPercentage >= 1 - threshold);
+      setShowIndicator(scrollPercentage < 1 - threshold);
+    };
+
+    const handleScroll = () => {
+      if (scrollTimeoutRef.current) {
+        clearTimeout(scrollTimeoutRef.current);
+      }
+      
+      scrollTimeoutRef.current = setTimeout(checkScrollPosition, 100);
+    };
+
+    // 初始检查
+    checkScrollPosition();
+
+    container.addEventListener('scroll', handleScroll);
+    window.addEventListener('resize', checkScrollPosition);
+
+    return () => {
+      container.removeEventListener('scroll', handleScroll);
+      window.removeEventListener('resize', checkScrollPosition);
+      if (scrollTimeoutRef.current) {
+        clearTimeout(scrollTimeoutRef.current);
+      }
+    };
+  }, [containerRef, threshold]);
+
+  return { showIndicator, isAtEnd };
+};
+
+// 触摸滑动增强Hook
+export const useTouchScroll = () => {
+  const containerRef = useRef<HTMLDivElement>(null);
+  const [isScrolling, setIsScrolling] = useState(false);
+
+  const handleTouchStart = (e: React.TouchEvent<HTMLDivElement>) => {
+    if (containerRef.current) {
+      containerRef.current.style.cursor = 'grabbing';
+    }
+  };
+
+  const handleTouchEnd = (e: React.TouchEvent<HTMLDivElement>) => {
+    if (containerRef.current) {
+      containerRef.current.style.cursor = 'grab';
+    }
+  };
+
+  const handleTouchMove = (e: React.TouchEvent<HTMLDivElement>) => {
+    setIsScrolling(true);
+    setTimeout(() => setIsScrolling(false), 150);
+  };
+
+  return {
+    containerRef,
+    isScrolling,
+    handleTouchStart,
+    handleTouchEnd,
+    handleTouchMove
+  };
+};

+ 4 - 31
src/client/mobile/pages/NewHomePage.tsx

@@ -3,10 +3,12 @@ import { useNavigate } from 'react-router-dom';
 import { useHomeData } from '../hooks/useHomeData';
 import { useAuth } from '../hooks/AuthProvider';
 import { useHomeIcons } from '../hooks/useHomeIcons';
+import { CategoryScroll } from '../components/CategoryScroll';
 import { EnhancedCarousel } from '../components/EnhancedCarousel';
 import { AdBannerCarousel } from '../components/AdBannerCarousel';
 import { SkeletonLoader, BannerSkeleton, ListItemSkeleton } from '../components/SkeletonLoader';
 import { unsplash } from '../utils/unsplash';
+import '../styles/scroll-indicator.css';
 import {
   BriefcaseIcon,
   UserGroupIcon,
@@ -387,38 +389,9 @@ const NewHomePage: React.FC = () => {
           link: banner.link
         }))} />
 
-        {/* 服务分类 */}
+        {/* 服务分类 - 左右滑动固定2行 */}
         <div className="mt-4 px-4">
-          <div className="grid grid-cols-3 gap-3">
-            {categories.map((category, index) => (
-              <button
-                key={index}
-                onClick={() => navigate(category.path || '/')}
-                className="flex flex-col items-center p-4 rounded-xl transition-all duration-300 hover:shadow-lg backdrop-blur-sm"
-                style={{
-                  backgroundColor: 'rgba(255,255,255,0.7)',
-                  border: `1px solid ${COLORS.ink.medium}`,
-                  color: COLORS.text.primary
-                }}
-              >
-                <div
-                  className="w-14 h-14 rounded-full flex items-center justify-center shadow-sm"
-                  style={{ backgroundColor: COLORS.accent.blue, color: 'white' }}
-                >
-                  <img
-                    src={category.image || '/images/placeholder.jpg'}
-                    alt={category.name}
-                    className="w-8 h-8 object-cover rounded-full"
-                    onError={(e) => {
-                      (e.target as HTMLImageElement).style.display = 'none';
-                      (e.target as HTMLImageElement).parentElement!.innerHTML = '📱';
-                    }}
-                  />
-                </div>
-                <span className={`${FONT_STYLES.caption} mt-2 line-clamp-1`}>{category.name}</span>
-              </button>
-            ))}
-          </div>
+          <CategoryScroll categories={categories} onNavigate={navigate} />
         </div>
 
         {/* 推荐岗位 */}

+ 4 - 26
src/client/mobile/pages/PolicyNewsPage.tsx

@@ -12,33 +12,11 @@ import {
   MagnifyingGlassIcon
 } from '@heroicons/react/24/outline';
 
-// 水墨风格配置
-const COLORS = {
-  ink: {
-    light: '#f5f3f0',
-    medium: '#d4c4a8',
-    dark: '#8b7355',
-    deep: '#3a2f26',
-  },
-  accent: {
-    red: '#a85c5c',
-    blue: '#4a6b7c',
-    green: '#5c7c5c',
-  },
-  text: {
-    primary: '#2f1f0f',
-    secondary: '#5d4e3b',
-    light: '#8b7355',
-  }
-};
+import { INK_COLORS } from '../styles/colors';
+import { FONT_STYLES } from '../styles/typography';
 
-const FONT_STYLES = {
-  title: 'font-serif text-3xl font-bold tracking-wide',
-  sectionTitle: 'font-serif text-2xl font-semibold tracking-wide',
-  body: 'font-sans text-base leading-relaxed',
-  caption: 'font-sans text-sm',
-  small: 'font-sans text-xs',
-};
+// 使用导入的INK_COLORS代替本地定义
+const COLORS = INK_COLORS;
 
 // 骨架屏组件
 const PolicyNewsSkeleton: React.FC = () => (

+ 4 - 28
src/client/mobile/pages/SilverWisdomPage.tsx

@@ -11,35 +11,11 @@ import {
   ClockIcon,
   BookOpenIcon
 } from '@heroicons/react/24/outline';
+import { INK_COLORS } from '../styles/colors';
+import { FONT_STYLES } from '../styles/typography';
 
-// 中国水墨风格色彩系统
-const COLORS = {
-  ink: {
-    light: '#f5f3f0',     // 宣纸背景色
-    medium: '#d4c4a8',    // 淡墨
-    dark: '#8b7355',      // 浓墨
-    deep: '#3a2f26',      // 焦墨
-  },
-  accent: {
-    red: '#a85c5c',    // 朱砂
-    blue: '#4a6b7c',   // 花青
-    green: '#5c7c5c',  // 石绿
-  },
-  text: {
-    primary: '#2f1f0f',   // 墨色文字
-    secondary: '#5d4e3b', // 淡墨文字
-    light: '#8b7355',     // 极淡文字
-  }
-};
-
-// 字体样式
-const FONT_STYLES = {
-  title: 'font-serif text-3xl font-bold tracking-wide',
-  sectionTitle: 'font-serif text-2xl font-semibold tracking-wide',
-  body: 'font-sans text-base leading-relaxed',
-  caption: 'font-sans text-sm',
-  small: 'font-sans text-xs',
-};
+// 使用导入的INK_COLORS代替本地定义
+const COLORS = INK_COLORS;
 
 // 分类标签颜色映射 - 基于categoryId映射
 const categoryColors: Record<number, string> = {

+ 4 - 19
src/client/mobile/pages/TalentDetailPage.tsx

@@ -3,26 +3,11 @@ import { useParams, useNavigate } from 'react-router-dom';
 import { ArrowLeftIcon, PhoneIcon, EnvelopeIcon, StarIcon, CheckCircleIcon, ClockIcon, BriefcaseIcon, UserGroupIcon } from '@heroicons/react/24/outline';
 import { useQuery } from '@tanstack/react-query';
 import { silverTalentsClient } from '@/client/api';
+import { INK_COLORS } from '../styles/colors';
+import { FONT_STYLES } from '../styles/typography';
 
-// 水墨风格色彩常量
-const COLORS = {
-  ink: {
-    light: '#f5f3f0',
-    medium: '#d4c4a8',
-    dark: '#8b7355',
-    deep: '#3a2f26',
-  },
-  accent: {
-    red: '#a85c5c',
-    blue: '#4a6b7c',
-    green: '#5c7c5c',
-  },
-  text: {
-    primary: '#2f1f0f',
-    secondary: '#5d4e3b',
-    light: '#8b7355',
-  }
-};
+// 使用导入的INK_COLORS代替本地定义
+const COLORS = INK_COLORS;
 
 const TalentDetailPage: React.FC = () => {
   const { id } = useParams<{ id: string }>();

+ 4 - 27
src/client/mobile/pages/TimeBankPage.tsx

@@ -4,34 +4,11 @@ import { ClockIcon, TrophyIcon, ArrowPathIcon } from '@heroicons/react/24/outlin
 import TimeBankCard from '../components/TimeBankCard';
 import { useTimeBankData } from '../hooks/useTimeBankData';
 import { SkeletonLoader } from '../components/SkeletonLoader';
+import { INK_COLORS } from '../styles/colors';
+import { FONT_STYLES } from '../styles/typography';
 
-// 水墨风格色彩方案
-const COLORS = {
-  ink: {
-    light: '#f5f3f0',
-    medium: '#d4c4a8',
-    dark: '#8b7355',
-    deep: '#3a2f26',
-  },
-  accent: {
-    blue: '#4a6b7c',
-    green: '#5c7c5c',
-  },
-  text: {
-    primary: '#2f1f0f',
-    secondary: '#5d4e3b',
-    light: '#8b7355',
-  }
-};
-
-// 字体样式
-const FONT_STYLES = {
-  title: 'font-serif text-2xl font-bold tracking-wide',
-  sectionTitle: 'font-serif text-xl font-semibold tracking-wide',
-  body: 'font-sans text-base leading-relaxed',
-  caption: 'font-sans text-sm',
-  small: 'font-sans text-xs',
-};
+// 使用导入的INK_COLORS代替本地定义
+const COLORS = INK_COLORS;
 
 // 统计卡片组件
 const StatsCard: React.FC<{

+ 57 - 0
src/client/mobile/styles/scroll-indicator.css

@@ -0,0 +1,57 @@
+/* 移动端图标分类滚动指示器样式 */
+.scroll-container {
+  -webkit-overflow-scrolling: touch;
+  cursor: grab;
+  user-select: none;
+}
+
+.scroll-container:active {
+  cursor: grabbing;
+}
+
+.scroll-container::-webkit-scrollbar {
+  display: none;
+}
+
+.scroll-container {
+  -ms-overflow-style: none;
+  scrollbar-width: none;
+}
+
+/* 滚动指示器动画 */
+.scroll-indicator {
+  animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+  0%, 100% {
+    opacity: 1;
+  }
+  50% {
+    opacity: 0.7;
+  }
+}
+
+/* 触摸反馈 */
+.touch-feedback {
+  transition: transform 0.2s ease;
+}
+
+.touch-feedback:active {
+  transform: scale(0.95);
+}
+
+/* 响应式调整 */
+@media (max-width: 375px) {
+  .category-item {
+    width: 65px !important;
+    height: 80px !important;
+  }
+}
+
+@media (min-width: 414px) {
+  .category-item {
+    width: 80px !important;
+    height: 95px !important;
+  }
+}