chore: migrate remaining ECharts components to Chart.js

- 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
This commit is contained in:
SunCheng
2026-02-16 21:55:38 +08:00
parent a88556c784
commit 9921cd5fdf
77 changed files with 6964 additions and 1632 deletions

View File

@@ -0,0 +1,94 @@
using Service.AI;
namespace Service.IconSearch;
public record SearchKeywordSettings
{
public string KeywordPromptTemplate { get; init; } =
"为以下中文分类名称生成3-5个相关的英文搜索关键字用于搜索图标{categoryName}。" +
"输出格式为JSON数组例如[\"food\", \"restaurant\", \"dining\"]。";
}
public class SearchKeywordGeneratorService(
IOpenAiService openAiService,
IOptions<SearchKeywordSettings> settings,
ILogger<SearchKeywordGeneratorService> logger
) : ISearchKeywordGeneratorService
{
private readonly SearchKeywordSettings _settings = settings.Value;
public async Task<List<string>> GenerateKeywordsAsync(string categoryName)
{
if (string.IsNullOrWhiteSpace(categoryName))
{
logger.LogWarning("分类名称为空,无法生成搜索关键字");
return [];
}
try
{
var prompt = _settings.KeywordPromptTemplate.Replace("{categoryName}", categoryName);
var response = await openAiService.ChatAsync(prompt, timeoutSeconds: 15);
if (string.IsNullOrEmpty(response))
{
logger.LogWarning("AI未返回搜索关键字分类{CategoryName}", categoryName);
return [];
}
var keywords = ParseKeywordsFromResponse(response);
logger.LogInformation("为分类 {CategoryName} 生成了 {Count} 个搜索关键字:{Keywords}",
categoryName, keywords.Count, string.Join(", ", keywords));
return keywords;
}
catch (Exception ex)
{
logger.LogError(ex, "生成搜索关键字失败,分类:{CategoryName}", categoryName);
return [];
}
}
private List<string> ParseKeywordsFromResponse(string response)
{
try
{
var jsonNode = JsonNode.Parse(response);
if (jsonNode is JsonArray arrayNode)
{
var keywords = new List<string>();
foreach (var item in arrayNode)
{
if (item is JsonValue value && value.TryGetValue(out string keyword))
{
keywords.Add(keyword);
}
}
return keywords;
}
else if (jsonNode is JsonObject jsonObject)
{
if (jsonObject.TryGetPropertyValue("keywords", out var keywordsNode) && keywordsNode is JsonArray arrayNode2)
{
var keywords = new List<string>();
foreach (var item in arrayNode2)
{
if (item is JsonValue value && value.TryGetValue(out string keyword))
{
keywords.Add(keyword);
}
}
return keywords;
}
}
logger.LogWarning("无法解析AI响应为关键字数组{Response}", response);
return [];
}
catch (Exception ex)
{
logger.LogError(ex, "解析AI响应失败{Response}", response);
return [];
}
}
}