测试覆盖率
All checks were successful
Docker Build & Deploy / Build Docker Image (push) Successful in 27s
Docker Build & Deploy / Deploy to Production (push) Successful in 9s
Docker Build & Deploy / Cleanup Dangling Images (push) Successful in 2s
Docker Build & Deploy / WeChat Notification (push) Successful in 2s

This commit is contained in:
SunCheng
2026-01-28 17:00:58 +08:00
parent 3ed9cf5ebd
commit e93c3d6bae
30 changed files with 2492 additions and 227 deletions

View File

@@ -0,0 +1,80 @@
# Decisions - Statistics Year Selection Enhancement
## [2026-01-28] Architecture Decisions
### Frontend Implementation Strategy
#### 1. Date Picker Mode Toggle
- Add a toggle switch in the date picker popup to switch between "按月" (month) and "按年" (year) modes
- When "按年" selected: use `columns-type="['year']"`
- When "按月" selected: use `columns-type="['year', 'month']` (current behavior)
#### 2. State Management
- Add `dateSelectionMode` ref: `'month'` | `'year'`
- When year-only mode: set `currentMonth = 0` to indicate full year
- Keep `currentYear` as integer (unchanged)
- Update `selectedDate` array dynamically based on mode:
- Year mode: `['YYYY']`
- Month mode: `['YYYY', 'MM']`
#### 3. Display Logic
- Nav bar title: `currentYear年` when `currentMonth === 0`, else `currentYear年currentMonth月`
- Chart titles: Update to reflect year or year-month scope
#### 4. API Calls
- Pass `month: currentMonth.value || 0` to all API calls
- Backend will handle month=0 as year-only query
### Backend Implementation Strategy
#### 1. Repository Layer Change
**File**: `Repository/TransactionRecordRepository.cs`
**Method**: `BuildQuery()` lines 81-86
```csharp
if (year.HasValue)
{
if (month.HasValue && month.Value > 0)
{
// Specific month
var dateStart = new DateTime(year.Value, month.Value, 1);
var dateEnd = dateStart.AddMonths(1);
query = query.Where(t => t.OccurredAt >= dateStart && t.OccurredAt < dateEnd);
}
else
{
// Entire year
var dateStart = new DateTime(year.Value, 1, 1);
var dateEnd = new DateTime(year.Value + 1, 1, 1);
query = query.Where(t => t.OccurredAt >= dateStart && t.OccurredAt < dateEnd);
}
}
```
#### 2. Service Layer
- No changes needed - services already pass month parameter to repository
- Services will receive month=0 for year-only queries
#### 3. API Controller
- No changes needed - already accepts year/month parameters
### Testing Strategy
#### Backend Tests
- Test year-only query returns all transactions for that year
- Test month-specific query still works
- Test edge cases: year boundaries, leap years
#### Frontend Tests
- Test toggle switches picker mode correctly
- Test year selection updates state and fetches data
- Test display updates correctly for year vs year-month
### User Experience Flow
1. User clicks date picker in nav bar
2. Popup opens with toggle: "按月 | 按年"
3. User selects mode (default: 按月 for backward compatibility)
4. User selects date(s) and confirms
5. Statistics refresh with new scope
6. Display updates to show scope (year or year-month)

View File

@@ -0,0 +1,27 @@
# Issues - Statistics Year Selection Enhancement
## [2026-01-28] Backend Repository Limitation
### Issue
`TransactionRecordRepository.BuildQuery()` requires both year AND month parameters to be present. Year-only queries (month=null or month=0) are not supported.
### Impact
- Cannot query full-year statistics from the frontend
- Current implementation only supports month-level granularity
- All statistics endpoints rely on `QueryAsync(year, month, ...)`
### Solution
Modify `BuildQuery()` method in `Repository/TransactionRecordRepository.cs` to support:
1. Year-only queries (when year provided, month is null or 0)
2. Month-specific queries (when both year and month provided - current behavior)
### Implementation Location
- File: `Repository/TransactionRecordRepository.cs`
- Method: `BuildQuery()` lines 81-86
- Also need to update service layer to handle month=0 or null
### Testing Requirements
- Test year-only query returns all transactions for that year
- Test month-specific query still works as before
- Test edge cases: leap years, year boundaries
- Verify all statistics endpoints work with year-only mode

View File

@@ -0,0 +1,181 @@
# Learnings - Statistics Year Selection Enhancement
## [2026-01-28] Initial Analysis
### Current Implementation
- **File**: `Web/src/views/StatisticsView.vue`
- **Current picker**: `columns-type="['year', 'month']` (year-month only)
- **State variables**:
- `currentYear` - integer year
- `currentMonth` - integer month (1-12)
- `selectedDate` - array `['YYYY', 'MM']` for picker
- **API calls**: All endpoints use `{ year, month }` parameters
### Vant UI Year-Only Pattern
- **Key prop**: `columns-type="['year']"`
- **Picker value**: Single-element array `['YYYY']`
- **Confirmation**: `selectedValues[0]` contains year string
### Implementation Strategy
1. Add UI toggle to switch between year-month and year-only modes
2. When year-only selected, set `currentMonth = 0` or null to indicate full year
3. Backend API already supports year-only queries (when month=0 or null)
4. Update display logic to show "YYYY年" vs "YYYY年MM月"
### API Compatibility - CRITICAL FINDING
- **Backend limitation**: `TransactionRecordRepository.BuildQuery()` (lines 81-86) requires BOTH year AND month
- Current logic: `if (year.HasValue && month.HasValue)` - year-only queries are NOT supported
- **Must modify repository** to support year-only queries:
- When year provided but month is null/0: query entire year (Jan 1 to Dec 31)
- When both year and month provided: query specific month (current behavior)
- All statistics endpoints use `QueryAsync(year, month, ...)` pattern
### Required Backend Changes
**File**: `Repository/TransactionRecordRepository.cs`
**Method**: `BuildQuery()` lines 81-86
**Change**: Modify year/month filtering logic to support year-only queries
```csharp
// Current (line 81-86):
if (year.HasValue && month.HasValue)
{
var dateStart = new DateTime(year.Value, month.Value, 1);
var dateEnd = dateStart.AddMonths(1);
query = query.Where(t => t.OccurredAt >= dateStart && t.OccurredAt < dateEnd);
}
// Needed:
if (year.HasValue)
{
if (month.HasValue && month.Value > 0)
{
// Specific month
var dateStart = new DateTime(year.Value, month.Value, 1);
var dateEnd = dateStart.AddMonths(1);
query = query.Where(t => t.OccurredAt >= dateStart && t.OccurredAt < dateEnd);
}
else
{
// Entire year
var dateStart = new DateTime(year.Value, 1, 1);
var dateEnd = new DateTime(year.Value + 1, 1, 1);
query = query.Where(t => t.OccurredAt >= dateStart && t.OccurredAt < dateEnd);
}
}
```
### Existing Patterns
- BudgetView.vue uses same year-month picker pattern
- Dayjs used for all date formatting: `dayjs().format('YYYY-MM-DD')`
- Date picker values always arrays for Vant UI
## [2026-01-28] Repository BuildQuery() Enhancement
### Implementation Completed
- **File Modified**: `Repository/TransactionRecordRepository.cs` lines 81-94
- **Change**: Updated year/month filtering logic to support year-only queries
### Logic Changes
```csharp
// Old: Required both year AND month
if (year.HasValue && month.HasValue) { ... }
// New: Support year-only queries
if (year.HasValue)
{
if (month.HasValue && month.Value > 0)
{
// 查询指定年月
var dateStart = new DateTime(year.Value, month.Value, 1);
var dateEnd = dateStart.AddMonths(1);
query = query.Where(t => t.OccurredAt >= dateStart && t.OccurredAt < dateEnd);
}
else
{
// 查询整年数据1月1日到下年1月1日
var dateStart = new DateTime(year.Value, 1, 1);
var dateEnd = new DateTime(year.Value + 1, 1, 1);
query = query.Where(t => t.OccurredAt >= dateStart && t.OccurredAt < dateEnd);
}
}
```
### Behavior
- **Month-specific** (month.HasValue && month.Value > 0): Query from 1st of month to 1st of next month
- **Year-only** (month is null or 0): Query from Jan 1 to Jan 1 of next year
- **No year provided**: No date filtering applied
### Verification
- All 14 tests pass: `dotnet test WebApi.Test/WebApi.Test.csproj`
- No breaking changes to existing functionality
- Chinese comments added for business logic clarity
### Key Pattern
- Use `month.Value > 0` check to distinguish year-only (0/null) from month-specific (1-12)
- Date range is exclusive on upper bound (`< dateEnd`) to avoid including boundary dates
## [2026-01-28] Frontend Year-Only Selection Implementation
### Changes Made
**File**: `Web/src/views/StatisticsView.vue`
#### 1. Nav Bar Title Display (Line 12)
- Updated to show "YYYY年" when `currentMonth === 0`
- Shows "YYYY年MM月" when month is selected
- Template: `{{ currentMonth === 0 ? \`${currentYear}年\` : \`${currentYear}年${currentMonth}月\` }}`
#### 2. Date Picker Popup (Lines 268-289)
- Added toggle switch using `van-tabs` component
- Two modes: "按月" (month) and "按年" (year)
- Tabs positioned above the date picker
- Dynamic `columns-type` based on selection mode:
- Year mode: `['year']`
- Month mode: `['year', 'month']`
#### 3. State Management (Line 347)
- Added `dateSelectionMode` ref: `'month'` | `'year'`
- Default: `'month'` for backward compatibility
- `currentMonth` set to `0` when year-only selected
#### 4. Confirmation Handler (Lines 532-544)
- Updated to handle both year-only and year-month modes
- When year mode: `newMonth = 0`
- When month mode: `newMonth = parseInt(selectedValues[1])`
#### 5. API Calls (All Statistics Endpoints)
- Updated all API calls to use `month: currentMonth.value || 0`
- Ensures backend receives `0` for year-only queries
- Modified functions:
- `fetchMonthlyData()` (line 574)
- `fetchCategoryData()` (lines 592, 610, 626)
- `fetchDailyData()` (line 649)
- `fetchBalanceData()` (line 672)
- `loadCategoryBills()` (line 1146)
#### 6. Mode Switching Watcher (Lines 1355-1366)
- Added `watch(dateSelectionMode)` to update `selectedDate` array
- When switching to year mode: `selectedDate = [year.toString()]`
- When switching to month mode: `selectedDate = [year, month]`
#### 7. Styling (Lines 1690-1705)
- Added `.date-picker-header` styles for tabs
- Clean, minimal design matching Vant UI conventions
- Proper spacing and background colors
### Vant UI Patterns Used
- **van-tabs**: For mode switching toggle
- **van-date-picker**: Dynamic `columns-type` prop
- **van-popup**: Container for picker and tabs
- Composition API with `watch` for reactive updates
### User Experience
1. Click nav bar date → popup opens with "按月" default
2. Switch to "按年" → picker shows only year column
3. Select year and confirm → `currentMonth = 0`
4. Nav bar shows "2025年" instead of "2025年1月"
5. All statistics refresh with year-only data
### Verification
- Build succeeds: `cd Web && pnpm build`
- No TypeScript errors
- No breaking changes to existing functionality
- Backward compatible with month-only selection