Files
g82tt 68be41e7a2 初始提交:浏览器首页 MyHomePage 全栈项目
# 项目概述
个人浏览器首页导航应用,支持书签分类管理、搜索引擎快捷搜索、
必应每日壁纸轮播、前后端分离部署,适配 1Panel 服务器(Docker 模式)。

# 技术栈
- 前端:Vue 3 + TypeScript + Vite + Pinia + Capacitor(Android 打包)
- 后端:.NET 8 + SqlSugar(多数据库) + SQLite/MySQL + Swashbuckle
- 部署:1Panel 应用商店自定义应用(Docker Compose 模式)

# 项目结构
- backend/    .NET 8 API 后端(8 个 Controller + 15 个 Service)
- frontend/   Vue 3 前端(19 个组件 + 9 个 API 模块 + 5 个 Store)
- docker/     Docker 部署文件(后端镜像 + Nginx 反代)
- docs/       部署手册(1Panel 实战版)
- scripts/    E2E 测试脚本

# 已实现功能
- 书签管理:增删改查 + 树形分类 + 拖拽排序 + 主色自适应
- 搜索引擎:8 个内置引擎 + 自定义引擎 + favicon 自动抓取
- 必应壁纸:每日轮播 + 多分辨率自动选择 + 1.6MP 质量优先
- 全局设置:主题/行为/数据/工具 4 分类 + 跨设备同步
- 文件上传:图标/书签/通用(容器持久化 + 跨域 URL 拼接)
- 同步:基于变更日志的设备间数据同步
- 跨域部署:前后端分离 + runtime config.json 无需重新编译

# 进度记录
- 已完成 P0~P52 共 53 个开发节点(详细见 说明文档.md)
- 当前版本:v1.0 部署就绪

# 部署文档
- README.md:项目说明 + 快速开始
- 说明文档.md:完整开发进度(中文)
- docs/DEPLOY.md:1Panel 部署手册(Docker 模式)
2026-07-05 05:09:56 +08:00

86 lines
3.6 KiB
C#

using MyHomePage.Api.Common;
using MyHomePage.Api.Models.Dtos;
using MyHomePage.Api.Models.Entities;
using SqlSugar;
namespace MyHomePage.Api.Services;
/// <inheritdoc />
public class SettingService : ISettingService
{
private const int DefaultId = 1;
private static readonly HashSet<string> AllowedThemeModes = new(StringComparer.OrdinalIgnoreCase) { "dark", "light", "auto" };
private static readonly HashSet<string> AllowedBackgroundTypes = new(StringComparer.OrdinalIgnoreCase) { "preset", "custom", "solid" };
// ===== P34 360 壁纸切换间隔合法值(分钟)=====
private static readonly HashSet<int> AllowedWallpaperIntervals = new() { 0, 1, 5, 15, 30, 60 };
private readonly ISqlSugarClient _db;
private readonly SyncLogHelper _sync;
public SettingService(ISqlSugarClient db, SyncLogHelper sync)
{
_db = db;
_sync = sync;
}
/// <inheritdoc />
public async Task<SettingDto> GetAsync()
{
var entity = await _db.Queryable<Setting>().InSingleAsync(DefaultId);
if (entity is null)
{
// 兜底:写入默认值
entity = new Setting { Id = DefaultId };
await _db.Insertable(entity).ExecuteCommandAsync();
}
return ToDto(entity);
}
/// <inheritdoc />
public async Task<SettingDto> UpdateAsync(SettingUpdateRequest request)
{
var entity = await _db.Queryable<Setting>().InSingleAsync(DefaultId);
if (entity is null)
{
entity = new Setting { Id = DefaultId };
await _db.Insertable(entity).ExecuteCommandAsync();
}
if (!string.IsNullOrEmpty(request.ThemeMode))
{
if (!AllowedThemeModes.Contains(request.ThemeMode)) throw new BusinessException("不支持的主题模式", 400);
entity.ThemeMode = request.ThemeMode;
}
if (!string.IsNullOrEmpty(request.AccentColor))
{
if (request.AccentColor.Length > 16) throw new BusinessException("主色调格式错误", 400);
entity.AccentColor = request.AccentColor;
}
if (request.BackgroundImage is not null) entity.BackgroundImage = request.BackgroundImage;
if (!string.IsNullOrEmpty(request.BackgroundType))
{
if (!AllowedBackgroundTypes.Contains(request.BackgroundType)) throw new BusinessException("不支持的背景类型", 400);
entity.BackgroundType = request.BackgroundType;
}
if (request.OpenLinksInNewTab.HasValue) entity.OpenLinksInNewTab = request.OpenLinksInNewTab.Value ? 1 : 0;
if (request.OpenSearchInNewTab.HasValue) entity.OpenSearchInNewTab = request.OpenSearchInNewTab.Value ? 1 : 0;
// ===== P34 360 壁纸模式 =====
if (request.WallpaperEnabled.HasValue) entity.WallpaperEnabled = request.WallpaperEnabled.Value ? 1 : 0;
if (request.WallpaperCategoryId is not null) entity.WallpaperCategoryId = request.WallpaperCategoryId;
if (request.WallpaperInterval.HasValue)
{
if (!AllowedWallpaperIntervals.Contains(request.WallpaperInterval.Value))
throw new BusinessException("不支持的壁纸切换间隔(允许:0/1/5/15/30/60 分钟)", 400);
entity.WallpaperInterval = request.WallpaperInterval.Value;
}
entity.UpdatedAt = DateTime.UtcNow;
await _db.Updateable(entity).ExecuteCommandAsync();
await _sync.WriteAsync("setting", entity.Id, "update");
return ToDto(entity);
}
private static SettingDto ToDto(Setting s) => SettingDto.FromEntity(s);}