MilliSim/.trae/documents/about-us-module-and-reset.md

12 KiB
Raw Permalink Blame History

关于我们模块 + 数据库重置 + COS 清理 实施计划

Context背景与目标

用户提出三项需求:

  1. 数据库重置:清空所有业务数据,仅保留 admin 账户,重置后保留种子数据(分类/平台/标签/示例项目/轮播图/合作伙伴由 DbSeeder 自动补回)。
  2. COS 资源全删:清空腾讯云 COS 存储桶内所有应用上传的文件(保留 backups/ 下的数据库备份)。
  3. 关于我们模块
    • 前台 About 页去掉"联系我们"模块
    • 后台新增"关于我们"管理模块:控制前台是否显示该页 + 全部内容可编辑(内联所见即所得,编辑样式与前台一致)

当前 About 页 100% 硬编码(About.vue),无任何后端存储。本计划从零构建内容存储 + 后台内联编辑 + 前台动态渲染。

关键设计决策(经用户确认 + 架构验证)

  1. 存储方式:全部 About 内容作为单个 JSON 字符串存入 SystemSettings 表Key=AboutContent),与现有 PlatformName/ShowPartners 等配置一致。避免新建表 + 改 seeder 补丁的复杂度。
  2. 编辑交互:使用无边框 el-input/el-textareaautosize通过 hover/focus 样式提示可编辑性。不使用 contenteditableVue 响应性陷阱:光标跳动)。
  3. 数组排序:上下箭头按钮,不引入拖拽库。
  4. ShowAbout 控制:隐藏导航链接 + 路由守卫拦截直接访问(重定向到首页)。
  5. 重置安全:要求输入 "RESET" 确认 + 重置前自动创建一次数据库备份 + 操作日志记录。
  6. COS 清理范围仅删除已知上传前缀avatars/banners/logos/projects/webgl/packages/common/icons/videos/categories/platforms/partners保留 backups/
  7. AboutContent 不进 PublicSettingsDtopublic-settings 仅加 showAbout(小布尔值,供导航/路由用AboutContent 由独立端点加载,仅 About 页消费。

实施步骤

阶段 A后端基础设施

A1. FileStorageService 增加批量删除能力

文件d:\TraeProject\MilliSim4\backend\MilliSim.Api\Services\Infrastructure\FileStorageService.cs

  • 接口 IFileStorageService 新增:
    • Task<List<string>> ListAllObjectsAsync() — 分页列出桶内所有对象 key每页 1000循环至结束
    • Task<int> DeleteObjectsAsync(IEnumerable<string> keys) — 批量删除(DeleteMultiObjectRequest,每批最多 1000
  • 实现:复用私有 GetStorageConfigAsync() + GetCosXml(config)Local 模式则遍历 wwwroot/uploads 目录删除文件
  • 不暴露到控制器单独使用,仅供 factory-reset 调用

A2. AboutContent DTO + 端点

文件d:\TraeProject\MilliSim4\backend\MilliSim.Api\Common\Dtos\Dtos.cs

新增 AboutContentDto

public class AboutContentDto
{
    public string HeroTag { get; set; } = "关于我们";
    public string HeroTitle { get; set; } = "";
    public string HeroDesc { get; set; } = "";
    public string IntroTitle { get; set; } = "公司简介";
    public List<string> IntroParagraphs { get; set; } = new();
    public List<AboutStatDto> Stats { get; set; } = new();
    public string MissionTitle { get; set; } = "使命与愿景";
    public List<AboutMissionCardDto> MissionCards { get; set; } = new();
    public string TimelineTitle { get; set; } = "发展历程";
    public List<AboutTimelineItemDto> Timeline { get; set; } = new();
}
public class AboutStatDto { public string Number { get; set; } = ""; public string Label { get; set; } = ""; }
public class AboutMissionCardDto { public string Title { get; set; } = ""; public string Icon { get; set; } = "rocket"; public string Desc { get; set; } = ""; }
public class AboutTimelineItemDto { public string Year { get; set; } = ""; public string Title { get; set; } = ""; public string Desc { get; set; } = ""; }

public class AboutSettingsDto
{
    public bool ShowAbout { get; set; } = true;
    public AboutContentDto Content { get; set; } = new();
}

PublicSettingsDto 仅新增 public bool ShowAbout { get; set; } = true;(不加 AboutContent

A3. SystemService 关于我们读写

文件d:\TraeProject\MilliSim4\backend\MilliSim.Api\Services\SystemService.cs

  • GetAboutSettingsAsync() → 读 SystemSettings 中 ShowAbout + AboutContentJSON 反序列化),返回 AboutSettingsDto
  • UpdateAboutSettingsAsync(AboutSettingsDto dto) → 序列化 Content 为 JSON 存入 AboutContent keyShowAbout 存 bool 字符串
  • GetPublicSettingsAsync() 中加入 ShowAbout 读取

A4. SystemController 端点

文件d:\TraeProject\MilliSim4\backend\MilliSim.Api\Controllers\SystemController.cs

  • GET /api/system/about[AllowAnonymous] 返回 AboutSettingsDto(前台 About 页消费)
  • PUT /api/system/about[Authorize(Policy="Admin")] 保存

A5. 工厂重置端点

文件d:\TraeProject\MilliSim4\backend\MilliSim.Api\Services\SystemService.cs + SystemController.cs

  • POST /api/system/factory-reset[Authorize(Policy="Admin")]body 含 ConfirmText(必须等于 "RESET"
  • 逻辑:
    1. 校验 ConfirmText == "RESET",否则返回 400
    2. 先调 CreateBackupAsync() 创建备份(保留安全网)
    3. SET FOREIGN_KEY_CHECKS=0
    4. 按依赖顺序 DELETE FROM 所有 21 张表Users 仅删 Username != 'admin',并清空 admin 的 Avatar/UpdatedAt 字段)
    5. SET FOREIGN_KEY_CHECKS=1
    6. DbSeeder.SeedAsync(db) 重新补种子admin 已存在跳过;分类/平台/标签/项目/轮播图/合作伙伴/设置按"为空才补"逻辑重新填入AboutContent/ShowAbout 默认值也会被补入)
    7. _fileStorage.ListAllObjectsAsync() 列出所有对象,过滤掉 backups/ 前缀,调 DeleteObjectsAsync() 批量删除
    8. 记操作日志
    9. 返回 { TablesCleared, ObjectsDeleted, BackupId }

A6. DbSeeder 补种子

文件d:\TraeProject\MilliSim4\backend\MilliSim.Api\Data\DbSeeder.cs

  • SeedAsync 的 SystemSettings 补种子段加入:
    • ShowAbout = "true"
    • AboutContent = 默认 JSON当前 About.vue 的硬编码内容序列化)
  • 仅在 key 不存在时插入(与现有逻辑一致)

阶段 B前端 — 关于我们动态化 + 去掉联系模块

B1. 类型 + API + Store

文件

  • d:\TraeProject\MilliSim4\frontend\src\types\index.ts:加 AboutContent / AboutStat / AboutMissionCard / AboutTimelineItem 接口;PublicSettingsshowAbout: boolean
  • d:\TraeProject\MilliSim4\frontend\src\api\index.tssystemApi.getAbout() GET /system/aboutsystemApi.updateAbout(payload) PUTsystemApi.factoryReset(confirmText) POST
  • d:\TraeProject\MilliSim4\frontend\src\stores\system.tssettingsshowAbout(默认 trueaboutContent ref + loadAbout() 方法About 页 onMounted 调用)

B2. About.vue 动态化

文件d:\TraeProject\MilliSim4\frontend\src\views\frontend\About.vue

  • 去掉"联系我们"section原 L101-130+ 相关 CTA
  • scriptonMountedsystemStore.loadAbout() 获取内容;用 computed 映射 store 中的 aboutContent
  • template所有硬编码文本替换为 {{ about.heroTag }} 等绑定timeline 用 v-for="item in about.timeline"
  • 无数据时显示骨架屏/loading

B3. 路由 + 导航守卫

文件

  • d:\TraeProject\MilliSim4\frontend\src\router\index.ts/about 路由 meta.requiresAbout: truebeforeEach 守卫中 if (to.meta.requiresAbout && !systemStore.settings.showAbout) return '/',并先 await systemStore.ensureLoaded()
  • d:\TraeProject\MilliSim4\frontend\src\layouts\FrontendLayout.vuenav 菜单"关于我们"项加 v-if="systemStore.settings.showAbout"footer 链接同理

阶段 C后台 — 关于我们内联编辑页

C1. 新建 AboutSettings.vue

文件d:\TraeProject\MilliSim4\frontend\src\views\admin\AboutSettings.vue(新建)

布局完全复刻前台 About.vue 结构,但所有文字改为可编辑:

  • 顶部工具栏:ShowAbout el-switch + "保存" el-buttonloading 状态)
  • Hero 区3 个无边框 el-inputtag/title/descinherit 前台字体样式
  • 公司简介:el-textarea :autosize × N 段可增删4 个统计卡片number + label 各一输入),每卡右上角 hover 显示删除按钮 + 上下移按钮
  • 使命与愿景3 张卡片title + icon 选择 + desc textarea增删/排序
  • 发展历程时间线项year + title + desc增删/排序
  • 编辑态样式:.editable:hover { outline: 1px dashed var(--color-primary); } .editable:focus { outline: 2px solid var(--color-primary); background: var(--color-primary-light-9); }
  • 数组操作按钮hover 时浮现"上移/下移/删除""添加"按钮在列表末尾
  • onMountedsystemApi.getAbout() 加载;保存调 systemApi.updateAbout();保存后 systemStore.load() 刷新前台状态
  • 图标选择用 el-select 下拉(预设 SvgIcon 列表rocket/globe/heart/target/users/star 等)

C2. 路由 + 菜单

文件

  • d:\TraeProject\MilliSim4\frontend\src\router\index.ts:加 /admin/about-settings 路由admin auth
  • d:\TraeProject\MilliSim4\frontend\src\layouts\AdminLayout.vue:侧栏菜单加入"关于我们"项icon: infobuilding

阶段 D后台 — 工厂重置入口

D1. Settings.vue 危险操作卡片

文件d:\TraeProject\MilliSim4\frontend\src\views\admin\Settings.vue

  • 在页面末尾加"危险操作"卡片:
    • 标题 + 警告说明("将清空所有业务数据并删除 COS 资源,仅保留 admin 账户,操作前会自动创建备份"
    • el-input 要求输入 "RESET" 才能激活按钮
    • el-button danger "恢复出厂设置"disabled 直到输入正确)
    • 二次确认弹窗 showActionConfirm
    • systemApi.factoryReset(confirmText);成功后 ElMessage.success + 提示重新登录admin 密码不变)

阶段 E编译验证 + 重启

  1. 杀掉后端进程PID 13788/22680dotnet build → 重启后端
  2. 前端 Vite HMR 自动加载,确认无编译错误
  3. 浏览器测试场景

验证步骤

后端验证

  1. dotnet build 0 错误 0 警告
  2. 启动后端,确认 PendingUploads + 新增设置 keyAboutContent/ShowAbout建表/补种子成功(查日志)
  3. Swagger 确认新端点存在GET/PUT /api/system/about、POST /api/system/factory-reset

前端验证

  1. Vite HMR 无编译错误
  2. 访问 /about 确认内容从后端加载(非硬编码)
  3. 关闭 ShowAbout 后导航不显示"关于我们",直接访问 /about 重定向到首页
  4. 后台 /admin/about-settings 编辑任意文字 → 保存 → 刷新前台确认生效
  5. 数组增删/排序正常
  6. 后台"恢复出厂设置"输入 RESET → 执行 → 确认数据库清空 + COS 清空 + admin 可登录 + 种子数据恢复

假设与决策

  1. 不删 admin 账户DbSeeder 对 admin 用户按 username 幂等,重置后保留原密码 admin123
  2. 保留 backups/ 前缀:工厂重置不删 backups/ 下的数据库备份文件,作为最后安全网。
  3. 重置前自动备份:调用现有 CreateBackupAsync,即使重置失败也有回滚依据。
  4. AboutContent 默认值:取当前 About.vue 硬编码内容作为默认 JSON保证未配置时前台显示不变。
  5. 不引入拖拽库:数组排序用上下箭头按钮,与现有 SortOrder 字段约定一致。
  6. PublicSettingsDto 不含 AboutContent:避免每次应用启动加载大 JSONAboutContent 由 About 页独立加载。
  7. factory-reset 同步删 COSDB 清空后立即删 COS保证一致性COS 删除失败不回滚 DBDB 已是干净状态COS 残留可手动清理,记录日志)。