- Migrated 4 components from ECharts to Chart.js: * MonthlyExpenseCard.vue (折线图) * DailyTrendChart.vue (双系列折线图) * ExpenseCategoryCard.vue (环形图) * BudgetChartAnalysis.vue (仪表盘 + 多种图表) - Removed all ECharts imports and environment variable switches - Unified all charts to use BaseChart.vue component - Build verified: pnpm build success ✓ - No echarts imports remaining ✓ Refs: openspec/changes/migrate-remaining-echarts-to-chartjs
267 lines
7.0 KiB
Markdown
267 lines
7.0 KiB
Markdown
# PROJECT KNOWLEDGE BASE - EmailBill
|
||
|
||
**Generated:** 2026-02-10
|
||
**Commit:** 3e18283
|
||
**Branch:** main
|
||
|
||
## OVERVIEW
|
||
Full-stack budget tracking app with .NET 10 backend and Vue 3 frontend.
|
||
|
||
## Project Structure
|
||
|
||
```
|
||
EmailBill/
|
||
├── Common/ # Shared utilities and abstractions
|
||
├── Entity/ # Database entities (FreeSql ORM)
|
||
├── Repository/ # Data access layer
|
||
├── Service/ # Business logic layer
|
||
├── Application/ # Application layer (业务编排、DTO转换)
|
||
├── WebApi/ # ASP.NET Core Web API
|
||
├── WebApi.Test/ # Backend tests (xUnit)
|
||
├── Web/ # Vue 3 frontend (Vite + Vant UI)
|
||
└── .doc/ # Project documentation archive
|
||
```
|
||
|
||
## WHERE TO LOOK
|
||
| Task | Location | Notes |
|
||
|------|----------|-------|
|
||
| Entity definitions | Entity/ | BaseEntity pattern, FreeSql attributes |
|
||
| Data access | Repository/ | BaseRepository, GlobalUsings |
|
||
| Business logic | Service/ | Jobs, Email services, App settings |
|
||
| Application orchestration | Application/ | DTO 转换、业务编排、接口门面 |
|
||
| Icon search integration | Service/IconSearch/ | Iconify API, AI keyword generation |
|
||
| API endpoints | WebApi/Controllers/ | DTO patterns, REST controllers |
|
||
| Frontend views | Web/src/views/ | Vue composition API |
|
||
| Icon components | Web/src/components/ | Icon.vue, IconPicker.vue |
|
||
| API clients | Web/src/api/ | Axios-based HTTP clients |
|
||
| Tests | WebApi.Test/ | xUnit + NSubstitute + FluentAssertions |
|
||
| Documentation archive | .doc/ | Technical docs, migration guides |
|
||
|
||
## Build & Test Commands
|
||
|
||
### Backend (.NET 10)
|
||
```bash
|
||
# Build and run
|
||
dotnet build EmailBill.sln
|
||
dotnet run --project WebApi/WebApi.csproj
|
||
|
||
# Run all tests
|
||
dotnet test WebApi.Test/WebApi.Test.csproj
|
||
|
||
# Run single test class
|
||
dotnet test --filter "FullyQualifiedName~BudgetStatsTest"
|
||
|
||
# Run single test method
|
||
dotnet test --filter "FullyQualifiedName~BudgetStatsTest.GetCategoryStats_月度_Test"
|
||
|
||
# Clean
|
||
dotnet clean EmailBill.sln
|
||
```
|
||
|
||
### Frontend (Vue 3)
|
||
```bash
|
||
cd Web
|
||
|
||
# Setup and dev
|
||
pnpm install
|
||
pnpm dev
|
||
|
||
# Build and preview
|
||
pnpm build
|
||
pnpm preview
|
||
|
||
# Lint and format
|
||
pnpm lint # ESLint with auto-fix
|
||
pnpm format # Prettier formatting
|
||
```
|
||
|
||
## C# Code Style
|
||
|
||
**Namespaces & Imports:**
|
||
- File-scoped namespaces: `namespace Entity;`
|
||
- Global usings in `Common/GlobalUsings.cs`
|
||
- Sort using statements alphabetically
|
||
|
||
**Naming:**
|
||
- Classes/Methods: `PascalCase`
|
||
- Interfaces: `IPascalCase`
|
||
- Private fields: `_camelCase`
|
||
- Parameters/locals: `camelCase`
|
||
|
||
**Entities:**
|
||
- Inherit from `BaseEntity`
|
||
- Use `[Column]` attributes for FreeSql ORM
|
||
- IDs via Snowflake: `YitIdHelper.NextId()`
|
||
- Use XML docs (`///`) for public APIs
|
||
- **Chinese comments for business logic** (per `.github/csharpe.prompt.md`)
|
||
|
||
**Best Practices:**
|
||
- Use modern C# syntax (records, pattern matching, nullable types)
|
||
- Use `IDateTimeProvider` instead of `DateTime.Now` for testability
|
||
- Avoid deep nesting, keep code flat and readable
|
||
- Reuse utilities from `Common` project
|
||
|
||
**Example:**
|
||
```csharp
|
||
namespace Entity;
|
||
|
||
/// <summary>
|
||
/// 实体基类
|
||
/// </summary>
|
||
public abstract class BaseEntity
|
||
{
|
||
[Column(IsPrimary = true)]
|
||
public long Id { get; set; } = YitIdHelper.NextId();
|
||
|
||
public DateTime CreateTime { get; set; } = DateTime.Now;
|
||
}
|
||
```
|
||
|
||
## Vue/TypeScript Style
|
||
|
||
**Component Structure:**
|
||
```vue
|
||
<template>
|
||
<van-config-provider :theme="theme">
|
||
<div class="component-name">
|
||
<!-- Content -->
|
||
</div>
|
||
</van-config-provider>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import { ref } from 'vue'
|
||
import { useMessageStore } from '@/stores/message'
|
||
|
||
const messageStore = useMessageStore()
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.component-name {
|
||
padding: 16px;
|
||
}
|
||
</style>
|
||
```
|
||
|
||
**Rules:**
|
||
- Composition API with `<script setup lang="ts">`
|
||
- Import order: Vue APIs → external libs → internal modules
|
||
- Use `@/` alias for absolute imports, avoid `../../../`
|
||
- Vant UI components: `<van-*>`
|
||
- Pinia for state, Vue Router for navigation
|
||
- SCSS with BEM naming, mobile-first design
|
||
|
||
**ESLint Rules (see `Web/eslint.config.js`):**
|
||
- 2-space indentation
|
||
- Single quotes, no semicolons
|
||
- `const` over `let`, no `var`
|
||
- Always use `===` (strict equality)
|
||
- `space-before-function-paren: 'always'`
|
||
- Max 1 empty line between blocks
|
||
- Vue: multi-word component names disabled
|
||
|
||
**Prettier Rules (see `Web/.prettierrc.json`):**
|
||
- Single quotes, no semicolons
|
||
- Trailing commas: none
|
||
- Print width: 100 chars
|
||
|
||
**Chart.js Usage (替代 ECharts):**
|
||
- 使用 `chart.js` (v4.5+) + `vue-chartjs` (v5.3+) 进行图表渲染
|
||
- 通用组件:`@/components/Charts/BaseChart.vue`
|
||
- 主题配置:`@/composables/useChartTheme.ts`(自动适配 Vant 暗色模式)
|
||
- 工具函数:`@/utils/chartHelpers.ts`(格式化、颜色、数据抽样)
|
||
- 仪表盘插件:`@/plugins/chartjs-gauge-plugin.ts`(Doughnut + 中心文本)
|
||
- 图表类型:line, bar, pie, doughnut
|
||
- 特性:支持响应式、触控交互、prefers-reduced-motion
|
||
|
||
**Example:**
|
||
```vue
|
||
<template>
|
||
<BaseChart
|
||
type="line"
|
||
:data="chartData"
|
||
:options="chartOptions"
|
||
/>
|
||
</template>
|
||
|
||
<script setup>
|
||
import BaseChart from '@/components/Charts/BaseChart.vue'
|
||
import { useChartTheme } from '@/composables/useChartTheme'
|
||
|
||
const { getChartOptions } = useChartTheme()
|
||
|
||
const chartData = {
|
||
labels: ['1月', '2月', '3月'],
|
||
datasets: [{
|
||
label: '支出',
|
||
data: [100, 200, 150],
|
||
borderColor: '#ff6b6b',
|
||
backgroundColor: 'rgba(255, 107, 107, 0.1)'
|
||
}]
|
||
}
|
||
|
||
const chartOptions = getChartOptions({
|
||
plugins: {
|
||
legend: { display: false }
|
||
}
|
||
})
|
||
</script>
|
||
```
|
||
|
||
## Testing
|
||
|
||
**Backend (xUnit + NSubstitute + FluentAssertions):**
|
||
```csharp
|
||
public class BudgetStatsTest : BaseTest
|
||
{
|
||
private readonly IBudgetRepository _repo = Substitute.For<IBudgetRepository>();
|
||
|
||
[Fact]
|
||
public async Task GetCategoryStats_月度_Test()
|
||
{
|
||
// Arrange
|
||
_repo.GetAllAsync().Returns(testData);
|
||
|
||
// Act
|
||
var result = await _service.GetCategoryStatsAsync(category, date);
|
||
|
||
// Assert
|
||
result.Month.Limit.Should().Be(2500);
|
||
}
|
||
}
|
||
```
|
||
- Arrange-Act-Assert pattern
|
||
- Constructor injection for dependencies
|
||
- Use Chinese test method names for domain clarity
|
||
|
||
**Frontend:**
|
||
- Vue Test Utils for components
|
||
- axios-mock-adapter for API mocking
|
||
|
||
## Development Workflow
|
||
|
||
1. **Before committing backend:** `dotnet test`
|
||
2. **Before committing frontend:** `pnpm lint && pnpm build`
|
||
3. **Database migrations:** Use FreeSql (check `Repository/`)
|
||
4. **API docs:** Scalar OpenAPI viewer
|
||
|
||
## Environment
|
||
|
||
**Required:**
|
||
- .NET 10 SDK
|
||
- Node.js 20.19+ or 22.12+
|
||
- pnpm
|
||
|
||
**Database:** SQLite (embedded)
|
||
|
||
**Config:**
|
||
- Backend: `appsettings.json`
|
||
- Frontend: `.env.development` / `.env.production`
|
||
|
||
## Critical Guidelines (from `.github/csharpe.prompt.md`)
|
||
|
||
- 优先使用新C#语法 (Use modern C# syntax)
|
||
- 优先使用中文注释 (Prefer Chinese comments for business logic)
|
||
- 优先复用已有方法 (Reuse existing methods)
|
||
- 不要深嵌套代码 (Avoid deep nesting)
|
||
- 保持代码简洁易读 (Keep code clean and readable) |