CleanupPlanContentController.php 15 KB

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