# Uniapp模块迭代计划 - 下一阶段优化 > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** 实现首页搜索功能,完善组件测试覆盖率,提升用户体验 **Architecture:** 基于uni-app + Vue 3 + TypeScript架构,实现跨平台搜索功能,增强组件可测试性 **Tech Stack:** uni-app, Vue 3.5, TypeScript 5.x, Pinia, Playwright, Vitest --- ## 背景 根据全系统功能对接综合评估,Uniapp模块存在以下待优化事项: - 首页搜索功能入口存在但未实现(测试失败) - 组件测试覆盖率可进一步提升 - 搜索服务已实现但未与UI集成 --- ## Task 1: 实现首页搜索功能入口 **Files:** - Modify: `src/pages/index/index.vue` - Modify: `src/components/SearchConditionPanel/index.vue` - Test: `src/__tests__/components/SearchConditionPanel.test.ts` **Step 1: 编写搜索功能测试** ```typescript // src/__tests__/components/SearchConditionPanel.test.ts import { mount } from '@vue/test-utils'; import SearchConditionPanel from '@/components/SearchConditionPanel/index.vue'; describe('SearchConditionPanel', () => { it('应该显示搜索输入框', () => { const wrapper = mount(SearchConditionPanel); expect(wrapper.find('[data-testid="search-input"]').exists()).toBe(true); }); it('点击搜索按钮应触发搜索事件', async () => { const wrapper = mount(SearchConditionPanel); const searchInput = wrapper.find('[data-testid="search-input"]'); await searchInput.setValue('测试搜索'); const searchButton = wrapper.find('[data-testid="search-button"]'); await searchButton.trigger('click'); expect(wrapper.emitted('search')).toBeTruthy(); expect(wrapper.emitted('search')![0]).toEqual(['测试搜索']); }); it('空搜索应显示提示信息', async () => { const wrapper = mount(SearchConditionPanel); const searchButton = wrapper.find('[data-testid="search-button"]'); await searchButton.trigger('click'); expect(wrapper.find('.search-hint').exists()).toBe(true); }); }); ``` **Step 2: 运行测试验证失败** Run: `cd everything-is-suitable-uniapp && npm run test -- SearchConditionPanel` Expected: FAIL (功能未实现) **Step 3: 更新首页添加搜索入口** ```vue ``` **Step 4: 完善搜索条件面板组件** ```vue ``` **Step 5: 运行测试验证通过** Run: `cd everything-is-suitable-uniapp && npm run test -- SearchConditionPanel` Expected: PASS **Step 6: Commit** ```bash git add src/pages/index/index.vue git add src/components/SearchConditionPanel/index.vue git add src/__tests__/components/SearchConditionPanel.test.ts git commit -m "feat(uniapp): implement search functionality on index page" ``` --- ## Task 2: 集成搜索服务 **Files:** - Modify: `src/pages/almanac-search/index.vue` - Modify: `src/services/searchService.ts` - Test: `src/__tests__/services/searchService.test.ts` **Step 1: 编写搜索服务集成测试** ```typescript // src/__tests__/services/searchService.test.ts import { searchService } from '@/services/searchService'; describe('searchService', () => { it('应该返回搜索结果', async () => { const result = await searchService.search('黄历'); expect(result).toBeDefined(); expect(result.items).toBeInstanceOf(Array); }); it('空关键词应返回空结果', async () => { const result = await searchService.search(''); expect(result.items).toHaveLength(0); }); it('应该支持分页', async () => { const result = await searchService.search('黄历', { page: 1, pageSize: 10 }); expect(result.pagination).toBeDefined(); expect(result.pagination.page).toBe(1); }); }); ``` **Step 2: 运行测试验证** Run: `cd everything-is-suitable-uniapp && npm run test -- searchService` Expected: PASS (服务已实现) **Step 3: 更新搜索结果页面** ```vue ``` **Step 4: Commit** ```bash git add src/pages/almanac-search/index.vue git add src/__tests__/services/searchService.test.ts git commit -m "feat(uniapp): integrate search service with search result page" ``` --- ## Task 3: 完善组件测试覆盖率 **Files:** - Create: `src/__tests__/components/AlmanacCard.test.ts` - Create: `src/__tests__/components/CalendarCard.test.ts` - Create: `src/__tests__/components/LoadingIndicator.test.ts` **Step 1: 编写老黄历卡片组件测试** ```typescript // src/__tests__/components/AlmanacCard.test.ts import { mount } from '@vue/test-utils'; import AlmanacCard from '@/components/AlmanacCard/AlmanacCard.vue'; describe('AlmanacCard', () => { const mockData = { date: '2024-01-01', lunarDate: '腊月初一', suit: ['嫁娶', '出行'], avoid: ['动土', '安葬'], zodiac: '龙' }; it('应该正确渲染日期信息', () => { const wrapper = mount(AlmanacCard, { props: { data: mockData } }); expect(wrapper.text()).toContain('2024-01-01'); expect(wrapper.text()).toContain('腊月初一'); }); it('应该显示宜忌信息', () => { const wrapper = mount(AlmanacCard, { props: { data: mockData } }); expect(wrapper.text()).toContain('嫁娶'); expect(wrapper.text()).toContain('动土'); }); it('应该显示生肖信息', () => { const wrapper = mount(AlmanacCard, { props: { data: mockData } }); expect(wrapper.text()).toContain('龙'); }); it('点击应触发事件', async () => { const wrapper = mount(AlmanacCard, { props: { data: mockData } }); await wrapper.trigger('click'); expect(wrapper.emitted('click')).toBeTruthy(); }); }); ``` **Step 2: 编写日历卡片组件测试** ```typescript // src/__tests__/components/CalendarCard.test.ts import { mount } from '@vue/test-utils'; import CalendarCard from '@/components/CalendarCard/CalendarCard.vue'; describe('CalendarCard', () => { const mockDate = new Date('2024-01-15'); it('应该正确渲染日期', () => { const wrapper = mount(CalendarCard, { props: { date: mockDate } }); expect(wrapper.text()).toContain('15'); }); it('应该显示农历信息', () => { const wrapper = mount(CalendarCard, { props: { date: mockDate, lunarInfo: '腊月初五' } }); expect(wrapper.text()).toContain('腊月初五'); }); it('今天应该有特殊样式', () => { const today = new Date(); const wrapper = mount(CalendarCard, { props: { date: today, isToday: true } }); expect(wrapper.classes()).toContain('is-today'); }); it('选中状态应有特殊样式', async () => { const wrapper = mount(CalendarCard, { props: { date: mockDate, selected: false } }); expect(wrapper.classes()).not.toContain('is-selected'); await wrapper.setProps({ selected: true }); expect(wrapper.classes()).toContain('is-selected'); }); }); ``` **Step 3: 编写加载指示器组件测试** ```typescript // src/__tests__/components/LoadingIndicator.test.ts import { mount } from '@vue/test-utils'; import LoadingIndicator from '@/components/LoadingIndicator/index.vue'; describe('LoadingIndicator', () => { it('默认应该显示加载动画', () => { const wrapper = mount(LoadingIndicator); expect(wrapper.find('.loading-spinner').exists()).toBe(true); }); it('应该支持不同尺寸', () => { const wrapper = mount(LoadingIndicator, { props: { size: 'large' } }); expect(wrapper.classes()).toContain('size-large'); }); it('应该支持自定义提示文字', () => { const wrapper = mount(LoadingIndicator, { props: { text: '加载中...' } }); expect(wrapper.text()).toContain('加载中...'); }); it('隐藏时不应渲染', () => { const wrapper = mount(LoadingIndicator, { props: { visible: false } }); expect(wrapper.find('.loading-indicator').exists()).toBe(false); }); }); ``` **Step 4: 运行所有组件测试** Run: `cd everything-is-suitable-uniapp && npm run test` Expected: PASS **Step 5: Commit** ```bash git add src/__tests__/components/AlmanacCard.test.ts git add src/__tests__/components/CalendarCard.test.ts git add src/__tests__/components/LoadingIndicator.test.ts git commit -m "test(uniapp): add comprehensive component tests for better coverage" ``` --- ## Task 4: 添加E2E测试 **Files:** - Create: `e2e/search.spec.ts` - Modify: `playwright.config.ts` **Step 1: 编写搜索功能E2E测试** ```typescript // e2e/search.spec.ts import { test, expect } from '@playwright/test'; test.describe('搜索功能', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); }); test('首页应显示搜索入口', async ({ page }) => { await expect(page.locator('.search-bar')).toBeVisible(); }); test('点击搜索入口应打开搜索面板', async ({ page }) => { await page.click('.search-bar'); await expect(page.locator('.search-panel')).toBeVisible(); }); test('输入关键词并搜索应跳转到搜索结果页', async ({ page }) => { await page.click('.search-bar'); await page.fill('[data-testid="search-input"]', '黄历'); await page.click('[data-testid="search-button"]'); await expect(page).toHaveURL(/almanac-search/); await expect(page.locator('.result-list')).toBeVisible(); }); test('搜索历史应正确显示', async ({ page }) => { await page.click('.search-bar'); await page.fill('[data-testid="search-input"]', '测试搜索'); await page.click('[data-testid="search-button"]'); // 返回首页再次打开搜索 await page.goBack(); await page.click('.search-bar'); await expect(page.locator('.history-tag').first()).toContainText('测试搜索'); }); test('空搜索应显示提示', async ({ page }) => { await page.click('.search-bar'); await page.click('[data-testid="search-button"]'); await expect(page.locator('.search-hint')).toBeVisible(); }); }); ``` **Step 2: 运行E2E测试** Run: `cd everything-is-suitable-uniapp && npx playwright test search.spec.ts` Expected: PASS **Step 3: Commit** ```bash git add e2e/search.spec.ts git commit -m "test(uniapp): add E2E tests for search functionality" ``` --- ## Task 5: 优化性能和用户体验 **Files:** - Modify: `src/components/SearchConditionPanel/index.vue` - Create: `src/composables/useDebounce.ts` **Step 1: 添加防抖功能** ```typescript // src/composables/useDebounce.ts import { ref, watch } from 'vue'; export function useDebounce(value: Ref, delay: number = 300): Ref { const debouncedValue = ref(value.value) as Ref; let timeout: ReturnType; watch(value, (newValue) => { clearTimeout(timeout); timeout = setTimeout(() => { debouncedValue.value = newValue; }, delay); }); return debouncedValue; } ``` **Step 2: 在搜索组件中应用防抖** ```vue ``` **Step 3: Commit** ```bash git add src/composables/useDebounce.ts git add src/components/SearchConditionPanel/index.vue git commit -m "perf(uniapp): add debounce for search input to improve UX" ``` --- ## 验收标准 - [ ] 首页搜索功能入口正常工作 - [ ] 搜索结果页面正确显示搜索结果 - [ ] 组件测试覆盖率 >= 80% - [ ] E2E测试全部通过 - [ ] 搜索响应时间 < 500ms - [ ] 用户操作流程符合设计规范 --- ## 风险与依赖 | 风险 | 影响 | 缓解措施 | |------|------|---------| | 搜索服务依赖后端API | 高 | 确保API模块端点路径一致 | | 跨平台兼容性问题 | 中 | 充分测试H5、小程序、App | | 性能问题 | 低 | 添加防抖、缓存优化 |