CleanupPlanContentController.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. <?php
  2. namespace App\Module\Cleanup\AdminControllers;
  3. use App\Module\Cleanup\Models\CleanupPlanContent;
  4. use App\Module\Cleanup\Models\CleanupPlan;
  5. use App\Module\Cleanup\Models\CleanupConfig;
  6. use App\Module\Cleanup\Repositories\CleanupPlanContentRepository;
  7. use App\Module\Cleanup\Enums\CLEANUP_TYPE;
  8. use App\Module\Cleanup\Logics\ModelScannerLogic;
  9. use UCore\DcatAdmin\AdminController;
  10. use Dcat\Admin\Form;
  11. use Dcat\Admin\Grid;
  12. use Dcat\Admin\Show;
  13. use Dcat\Admin\Layout\Content;
  14. use Spatie\RouteAttributes\Attributes\Resource;
  15. /**
  16. * 计划内容管理控制器
  17. *
  18. * 路由:/admin/cleanup/plan-contents
  19. */
  20. #[Resource('cleanup/plan-contents', names: 'dcat.admin.cleanup.plan-contents')]
  21. class CleanupPlanContentController extends AdminController
  22. {
  23. /**
  24. * 页面标题
  25. */
  26. protected $title = '计划内容管理';
  27. /**
  28. * 数据仓库
  29. */
  30. protected function repository()
  31. {
  32. return CleanupPlanContentRepository::class;
  33. }
  34. /**
  35. * 列表页面
  36. */
  37. protected function grid(): Grid
  38. {
  39. return Grid::make(new CleanupPlanContentRepository(), function (Grid $grid) {
  40. // 基础设置
  41. $grid->column('id', 'ID')->sortable();
  42. // 计划信息
  43. $grid->column('plan.plan_name', '所属计划')->sortable();
  44. // Model/表信息
  45. $grid->column('model_class', 'Model类')->display(function ($value) {
  46. if (!empty($value)) {
  47. $className = class_basename($value);
  48. return "<span class='label label-success'>{$className}</span>";
  49. }
  50. return '<span class="label label-warning">未设置</span>';
  51. })->sortable();
  52. $grid->column('table_name', '表名')->display(function ($value) {
  53. if (!empty($this->model_class)) {
  54. try {
  55. $model = new $this->model_class();
  56. $actualTable = $model->getTable();
  57. return "<span class='text-muted'>{$actualTable}</span>";
  58. } catch (\Exception $e) {
  59. return "<span class='text-danger'>{$value} (Model错误)</span>";
  60. }
  61. } else {
  62. return "<span class='text-warning'>{$value} (旧数据)</span>";
  63. }
  64. })->sortable();
  65. // 清理类型
  66. $grid->column('cleanup_type', '清理类型')->using([
  67. 1 => '清空表',
  68. 2 => '删除所有',
  69. 3 => '按时间删除',
  70. 4 => '按用户删除',
  71. 5 => '按条件删除',
  72. ])->label([
  73. 1 => 'danger',
  74. 2 => 'warning',
  75. 3 => 'info',
  76. 4 => 'primary',
  77. 5 => 'secondary',
  78. ])->sortable();
  79. // 配置信息
  80. $grid->column('priority', '优先级')->sortable();
  81. $grid->column('batch_size', '批处理大小')->display(function ($value) {
  82. return number_format($value);
  83. })->sortable();
  84. // 状态
  85. $grid->column('is_enabled', '启用状态')->using([1 => '启用', 0 => '禁用'])->label([
  86. 1 => 'success',
  87. 0 => 'danger',
  88. ])->sortable();
  89. $grid->column('backup_enabled', '备份启用')->using([1 => '启用', 0 => '禁用'])->label([
  90. 1 => 'success',
  91. 0 => 'danger',
  92. ])->sortable();
  93. // 条件描述
  94. $grid->column('conditions_description', '清理条件')->display(function () {
  95. return $this->conditions_description;
  96. });
  97. // 时间
  98. $grid->column('created_at', '创建时间')->sortable();
  99. $grid->column('updated_at', '更新时间')->sortable();
  100. // 筛选器
  101. $grid->filter(function (Grid\Filter $filter) {
  102. // 按计划筛选
  103. $plans = CleanupPlan::pluck('plan_name', 'id')->toArray();
  104. $filter->equal('plan_id', '所属计划')->select($plans);
  105. // Model类筛选
  106. $filter->like('model_class', 'Model类');
  107. // 表名筛选
  108. $filter->like('table_name', '表名');
  109. // 按清理类型筛选
  110. $filter->equal('cleanup_type', '清理类型')->select([
  111. 1 => '清空表',
  112. 2 => '删除所有',
  113. 3 => '按时间删除',
  114. 4 => '按用户删除',
  115. 5 => '按条件删除',
  116. ]);
  117. // 按状态筛选
  118. $filter->equal('is_enabled', '启用状态')->select([
  119. 1 => '启用',
  120. 0 => '禁用',
  121. ]);
  122. $filter->equal('backup_enabled', '备份启用')->select([
  123. 1 => '启用',
  124. 0 => '禁用',
  125. ]);
  126. // 按表名搜索
  127. $filter->like('table_name', '表名');
  128. // 按优先级范围
  129. $filter->between('priority', '优先级');
  130. });
  131. // 批量操作
  132. $grid->batchActions([
  133. new \App\Module\Cleanup\AdminControllers\Actions\BatchEnableAction(),
  134. new \App\Module\Cleanup\AdminControllers\Actions\BatchDisableAction(),
  135. new \App\Module\Cleanup\AdminControllers\Actions\BatchMigrateToModelAction(),
  136. ]);
  137. // 行操作
  138. $grid->actions(function (Grid\Displayers\Actions $actions) {
  139. $actions->append(new \App\Module\Cleanup\AdminControllers\Actions\EditPlanContentAction());
  140. $actions->append(new \App\Module\Cleanup\AdminControllers\Actions\DeletePlanContentAction());
  141. $actions->append(new \App\Module\Cleanup\AdminControllers\Actions\TestCleanupAction());
  142. $actions->append(new \App\Module\Cleanup\AdminControllers\Actions\MigrateToModelAction());
  143. });
  144. // 设置每页显示数量
  145. $grid->paginate(20);
  146. });
  147. }
  148. /**
  149. * 详情页面
  150. */
  151. protected function detail($id): Show
  152. {
  153. return Show::make($id, new CleanupPlanContentRepository(), function (Show $show) {
  154. $show->field('id', 'ID');
  155. // 关联信息
  156. $show->field('plan.plan_name', '所属计划');
  157. // Model/表信息
  158. $show->field('model_class', 'Model类')->as(function ($value) {
  159. if (!empty($value)) {
  160. return $value;
  161. } else {
  162. return '未设置(旧数据)';
  163. }
  164. });
  165. $show->field('table_name', '表名')->as(function ($value) {
  166. if (!empty($this->model_class)) {
  167. try {
  168. $model = new $this->model_class();
  169. return $model->getTable() . ' (从Model获取)';
  170. } catch (\Exception $e) {
  171. return $value . ' (Model错误: ' . $e->getMessage() . ')';
  172. }
  173. } else {
  174. return $value . ' (旧数据)';
  175. }
  176. });
  177. // 清理配置
  178. $show->field('cleanup_type', '清理类型')->using([
  179. 1 => '清空表',
  180. 2 => '删除所有',
  181. 3 => '按时间删除',
  182. 4 => '按用户删除',
  183. 5 => '按条件删除',
  184. ]);
  185. $show->field('conditions', '清理条件')->json();
  186. $show->field('conditions_description', '条件描述');
  187. // 执行配置
  188. $show->field('priority', '优先级');
  189. $show->field('batch_size', '批处理大小');
  190. // 状态配置
  191. $show->field('is_enabled', '启用状态')->using([1 => '启用', 0 => '禁用']);
  192. $show->field('backup_enabled', '备份启用')->using([1 => '启用', 0 => '禁用']);
  193. // 备注和时间
  194. $show->field('notes', '备注说明');
  195. $show->field('created_at', '创建时间');
  196. $show->field('updated_at', '更新时间');
  197. });
  198. }
  199. /**
  200. * 创建/编辑表单
  201. */
  202. protected function form(): Form
  203. {
  204. return Form::make(new CleanupPlanContentRepository(), function (Form $form) {
  205. $form->display('id', 'ID');
  206. // 基础信息
  207. $form->select('plan_id', '所属计划')
  208. ->options(CleanupPlan::pluck('plan_name', 'id')->toArray())
  209. ->required();
  210. // Model类选择(推荐使用)
  211. $availableModels = $this->getAvailableModels();
  212. $form->select('model_class', 'Model类')
  213. ->options($availableModels)
  214. ->help('选择要清理的Model类(推荐使用)');
  215. // 表名选择(兼容旧数据)
  216. $availableTables = CleanupConfig::whereNull('model_class')
  217. ->orWhere('model_class', '')
  218. ->pluck('table_name', 'table_name')
  219. ->toArray();
  220. if (!empty($availableTables)) {
  221. $form->select('table_name', '表名(兼容旧数据)')
  222. ->options($availableTables)
  223. ->help('仅用于兼容旧数据,建议使用Model类选择');
  224. }
  225. // 清理类型
  226. $form->select('cleanup_type', '清理类型')
  227. ->options([
  228. 1 => '清空表',
  229. 2 => '删除所有',
  230. 3 => '按时间删除',
  231. 4 => '按用户删除',
  232. 5 => '按条件删除',
  233. ])
  234. ->required()
  235. ->help('选择清理方式');
  236. // 清理条件(JSON配置)
  237. $form->keyValue('conditions', '清理条件')
  238. ->help('JSON格式的清理条件配置,根据清理类型设置相应参数');
  239. // 执行配置
  240. $form->number('priority', '优先级')
  241. ->default(100)
  242. ->min(1)
  243. ->max(999)
  244. ->help('数字越小优先级越高');
  245. $form->number('batch_size', '批处理大小')
  246. ->default(1000)
  247. ->min(100)
  248. ->max(10000)
  249. ->help('每批处理的记录数量');
  250. // 状态配置
  251. $form->switch('is_enabled', '启用状态')->default(1);
  252. $form->switch('backup_enabled', '备份启用')->default(1);
  253. // 备注
  254. $form->textarea('notes', '备注说明')
  255. ->help('对此清理配置的说明');
  256. // 表单验证
  257. $form->saving(function (Form $form) {
  258. $modelClass = $form->input('model_class');
  259. $tableName = $form->input('table_name');
  260. // 验证至少选择了Model类或表名
  261. if (empty($modelClass) && empty($tableName)) {
  262. return $form->response()->error('请至少选择Model类或表名');
  263. }
  264. // 如果选择了Model类,验证Model是否存在
  265. if (!empty($modelClass)) {
  266. if (!class_exists($modelClass)) {
  267. return $form->response()->error('选择的Model类不存在:' . $modelClass);
  268. }
  269. // 自动设置表名
  270. try {
  271. $modelInstance = new $modelClass();
  272. $form->input('table_name', $modelInstance->getTable());
  273. } catch (\Exception $e) {
  274. return $form->response()->error('Model类实例化失败:' . $e->getMessage());
  275. }
  276. }
  277. });
  278. // 时间字段
  279. $form->display('created_at', '创建时间');
  280. $form->display('updated_at', '更新时间');
  281. // 添加JavaScript增强用户体验
  282. $form->html('
  283. <script>
  284. $(document).ready(function() {
  285. // 监听Model类选择变化
  286. $(document).on("change", "select[name=model_class]", function() {
  287. var modelClass = $(this).val();
  288. if (modelClass) {
  289. // 显示提示信息
  290. var className = modelClass.split("\\\\").pop();
  291. var moduleName = modelClass.match(/App\\\\Module\\\\([^\\\\]+)\\\\/);
  292. moduleName = moduleName ? moduleName[1] : "Unknown";
  293. // 创建提示信息
  294. var infoHtml = "<div class=\"alert alert-info\">" +
  295. "<strong>已选择Model:</strong>" + className + "<br>" +
  296. "<strong>所属模块:</strong>" + moduleName + "<br>" +
  297. "<strong>建议:</strong>使用Model类可以利用Laravel的所有特性,如软删除、事件等" +
  298. "</div>";
  299. // 移除旧的提示信息
  300. $(".model-info-alert").remove();
  301. // 添加新的提示信息
  302. $(this).closest(".form-group").after("<div class=\"model-info-alert\">" + infoHtml + "</div>");
  303. } else {
  304. $(".model-info-alert").remove();
  305. }
  306. });
  307. // 页面加载时如果已有选择,显示提示
  308. var initialModel = $("select[name=model_class]").val();
  309. if (initialModel) {
  310. $("select[name=model_class]").trigger("change");
  311. }
  312. });
  313. </script>
  314. ', '用户体验增强');
  315. });
  316. }
  317. /**
  318. * 获取可用的Model类列表
  319. */
  320. private function getAvailableModels(): array
  321. {
  322. // 从CleanupConfig中获取已配置的Model类
  323. $configuredModels = CleanupConfig::whereNotNull('model_class')
  324. ->where('model_class', '!=', '')
  325. ->pluck('model_class', 'model_class')
  326. ->toArray();
  327. // 格式化显示名称
  328. $formattedModels = [];
  329. foreach ($configuredModels as $modelClass) {
  330. $className = class_basename($modelClass);
  331. $moduleName = $this->extractModuleName($modelClass);
  332. $formattedModels[$modelClass] = "{$moduleName} - {$className}";
  333. }
  334. // 按模块和类名排序
  335. asort($formattedModels);
  336. return $formattedModels;
  337. }
  338. /**
  339. * 从Model类名中提取模块名
  340. */
  341. private function extractModuleName(string $modelClass): string
  342. {
  343. if (preg_match('/App\\\\Module\\\\([^\\\\]+)\\\\Models\\\\/', $modelClass, $matches)) {
  344. return $matches[1];
  345. }
  346. return 'Unknown';
  347. }
  348. }