CleanupPlanContentController.php 16 KB

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