# 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
请输入搜索关键词
搜索历史
{{ item }}
```
**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 |
| 性能问题 | 低 | 添加防抖、缓存优化 |