diff --git a/src/main/java/com/yunhai/userhome/config/WidgetCatalogSeeder.java b/src/main/java/com/yunhai/userhome/config/WidgetCatalogSeeder.java
new file mode 100644
index 0000000..95a27c6
--- /dev/null
+++ b/src/main/java/com/yunhai/userhome/config/WidgetCatalogSeeder.java
@@ -0,0 +1,75 @@
+package com.yunhai.userhome.config;
+
+import com.yunhai.userhome.entity.WidgetCatalog;
+import com.yunhai.userhome.repository.WidgetCatalogRepository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.CommandLineRunner;
+import org.springframework.stereotype.Component;
+
+import java.util.Date;
+
+/**
+ * 小组件内置目录行启动 seeding(幂等)。
+ *
+ *
背景:种子 INSERT 原先只在 {@code resources/sql/widget_catalog.sql} 里,代码无 seeding,
+ * 新环境首次启动时候选池恒为空。此处补一条启动期兜底:库为空才写入 3 条内置目录行。
+ *
+ * 三条种子与 {@code resources/sql/widget_catalog.sql} 用同一份常量(code / name / comp_type / scope_type / enabled),
+ * 仅补「目录行」,不补内容(内置组件内容在前端工程);{@code WidgetDataService} 白名单不动。
+ *
+ * @author chenx
+ * @date 2026-10-11
+ */
+@Component
+public class WidgetCatalogSeeder implements CommandLineRunner {
+
+ private static final Logger log = LoggerFactory.getLogger(WidgetCatalogSeeder.class);
+
+ /** {code, name, sort}:与 resources/sql/widget_catalog.sql 的 INSERT 一一对应 */
+ private static final String[][] SEEDS = {
+ {"evaluate", "听评课", "1"},
+ {"syllabus", "课程表", "2"},
+ {"weekplan", "一周安排", "3"},
+ };
+
+ private static final int COMP_TYPE_BUILTIN = 1;
+
+ private static final int SCOPE_ALL = 0;
+
+ private static final int ENABLED = 1;
+
+ @Autowired
+ private WidgetCatalogRepository widgetCatalogRepository;
+
+ @Override
+ public void run(String... args) {
+ try {
+ // 幂等:库非空即整体跳过,避免管理员下线过的内置组件在重启后被重新种回
+ if (widgetCatalogRepository.count() > 0) {
+ log.info("[widget] widget_catalog 非空,跳过内置目录 seeding");
+ return;
+ }
+ for (String[] seed : SEEDS) {
+ String code = seed[0];
+ if (widgetCatalogRepository.findByCode(code).isPresent()) {
+ continue;
+ }
+ WidgetCatalog catalog = new WidgetCatalog();
+ catalog.setCode(code);
+ catalog.setName(seed[1]);
+ catalog.setCompType(COMP_TYPE_BUILTIN);
+ catalog.setScopeType(SCOPE_ALL);
+ catalog.setSort(Integer.valueOf(seed[2]));
+ catalog.setEnabled(ENABLED);
+ catalog.setUpdateTime(new Date());
+ widgetCatalogRepository.save(catalog);
+ log.info("[widget] seeding 内置目录行:{}", code);
+ }
+ } catch (Exception e) {
+ // seeding 失败不阻断应用启动(候选池另有管理端入口补录)
+ log.error("[widget] 内置目录 seeding 失败,跳过", e);
+ }
+ }
+}
diff --git a/src/main/java/com/yunhai/userhome/controller/api/AppSettingController.java b/src/main/java/com/yunhai/userhome/controller/api/AppSettingController.java
deleted file mode 100644
index 7dbdd43..0000000
--- a/src/main/java/com/yunhai/userhome/controller/api/AppSettingController.java
+++ /dev/null
@@ -1,243 +0,0 @@
-package com.yunhai.userhome.controller.api;
-
-import com.yunhai.platform.apistd.BizException;
-import com.yunhai.platform.utils.exception.ErrorCode;
-import com.yunhai.platform.utils.model.BoaUser;
-import com.yunhai.userhome.config.Config;
-import com.yunhai.userhome.entity.AppCategory;
-import com.yunhai.userhome.entity.UserCurrentRole;
-import com.yunhai.userhome.service.IAppCategoryService;
-import com.yunhai.userhome.service.IUserCurrentRoleService;
-import com.yunhai.userhome.utils.FileUtil;
-import com.yunhai.userhome.utils.OtherUtil;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.beans.factory.annotation.Value;
-import org.springframework.core.io.InputStreamResource;
-import org.springframework.data.domain.Page;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.MediaType;
-import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
-import org.springframework.web.multipart.MultipartFile;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Optional;
-import java.util.UUID;
-
-/**
- * 应用设置控制器(自 systemsetting AppSettingController + SysController 迁移,1:1 对齐)。
- *
- * 与原版差异点:写操作(应用 CRUD / UpdateResDate / 模板上传保存)校验调用者
- * 为超管(type==1)或校管理员(roleId∈{2,5,7})——原版任何登录用户可写,因新入口仅管理员可见,收紧。
- * 查询(分页列表 / FindAppCategory / GetTempList / downloadTemp)登录即可。
- */
-@RestController
-@RequestMapping(Config.API_PATH)
-public class AppSettingController {
-
- @Value("${FILE_PATH:./upload/}")
- private String FILE_PATH;
-
- @Value("${FILE_WEB_PATH:}")
- private String FILE_WEB_PATH;
-
- @Autowired
- private IAppCategoryService appCategoryService;
-
- @Autowired
- private IUserCurrentRoleService userCurrentRoleService;
-
- // ==================== 应用类别 CRUD ====================
-
- /** 新增/编辑应用(JSON body,1:1 对齐原 SaveAppCategory) */
- @PostMapping(value = "SaveAppCategory", produces = "application/json")
- public void saveAppCategory(BoaUser authUserWithDept, @RequestBody AppCategory appCategory) {
- checkAdmin(authUserWithDept);
- if (appCategory.getCDate() == null) appCategory.setCDate(new Date());
- if (appCategory.getResDate() == null) appCategory.setResDate(new Date());
- appCategory.setCId(authUserWithDept.getId());
- appCategoryService.save(appCategory);
- }
-
- @PostMapping(value = "DelAppCategory", produces = "application/json")
- public void delAppCategory(BoaUser authUserWithDept, Integer id) {
- checkAdmin(authUserWithDept);
- appCategoryService.deleteById(id);
- }
-
- @GetMapping(value = "GetAppCategoryList")
- public Map getAppCategoryList(BoaUser authUserWithDept, Integer page, Integer count,
- @RequestParam(value = "roleType") Integer roleType,
- @RequestParam(value = "name", required = false) String name) {
- Page data = appCategoryService.findPageList(page, count, roleType, name);
- Map map = new HashMap();
- map.put("total", data.getTotalElements());
- map.put("list", data.getContent());
- return map;
- }
-
- @GetMapping(value = "FindAppCategory")
- public Optional findAppCategory(BoaUser authUserWithDept, Integer id) {
- return appCategoryService.findById(id);
- }
-
- @PostMapping(value = "UpdateResDate", produces = "application/json")
- public void updateResDate(BoaUser authUserWithDept, Integer id) {
- checkAdmin(authUserWithDept);
- appCategoryService.updateResDate(id);
- }
-
- // ==================== 模板管理(对齐原 SysController) ====================
-
- /** 上传模板压缩包(zip/rar),解压覆盖到 tempUpload/<appModId>/,删除压缩包 */
- @PostMapping(value = "tempUpload", produces = "application/json")
- public void tempUpload(BoaUser authUserWithDept, @RequestParam("file") MultipartFile file, Integer appModId) {
- checkAdmin(authUserWithDept);
- if (file == null || file.isEmpty()) {
- throw new BizException(ErrorCode.CUSTOM, "请选择上传文件");
- }
- String originalName = file.getOriginalFilename();
- String ext = originalName == null ? "" : originalName.substring(originalName.lastIndexOf(".")).toLowerCase(Locale.ROOT);
- if (!".zip".equals(ext) && !".rar".equals(ext)) {
- throw new BizException(ErrorCode.CUSTOM, "仅支持 zip/rar 格式");
- }
- // 不走图片 5MB 校验(模板包允许更大,依赖 multipart 配置),存到 tempUpload 子目录
- String uploadPath = FILE_PATH + "/tempUpload/";
- File fl = new File(uploadPath);
- if (!fl.exists()) fl.mkdirs();
- String fileName = FileUtil.uploadFile(file, uploadPath);
- String filePath = "/upload/tempUpload/" + fileName;
-
- File unzipFiles = new File(FILE_PATH + "/tempUpload/" + appModId);
- if (!unzipFiles.exists()) unzipFiles.mkdirs();
- else FileUtil.delAllFile(unzipFiles.getPath());
- File zipFile = new File(filePath.replace("/upload", FILE_PATH));
- try {
- FileUtil.zipDecompress(zipFile, unzipFiles);
- } catch (Exception e) {
- throw new BizException(ErrorCode.CUSTOM, e.getMessage());
- } finally {
- zipFile.delete();
- }
- }
-
- /** 模板文件列表(name + 访问 path) */
- @GetMapping(value = "GetTempList")
- public List