MenuBadgeAdminService.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. <?php
  2. namespace App\Services;
  3. use Illuminate\Support\Facades\DB;
  4. use Illuminate\Support\Facades\Schema;
  5. use Illuminate\Support\Facades\Validator;
  6. use Illuminate\Validation\ValidationException;
  7. final class MenuBadgeAdminService
  8. {
  9. private function ready(): void
  10. {
  11. if (!Schema::hasTable('menu_badge_configs')) {
  12. throw ValidationException::withMessages(['config' => '菜单角标配置未初始化,请先完成系统更新']);
  13. }
  14. }
  15. private function badgeTexts(array $texts): array
  16. {
  17. $rules = [];
  18. $messages = [];
  19. foreach (['zh' => '中文', 'en' => '英文', 'vi' => '越南文'] as $lang => $label) {
  20. $rules[$lang] = ['required', 'array:text'];
  21. $rules[$lang . '.text'] = ['required', 'string', 'max:100'];
  22. $messages[$lang . '.required'] = $label . '角标不能为空';
  23. $messages[$lang . '.text.required'] = $label . '角标不能为空';
  24. }
  25. $result = Validator::make($texts, $rules, $messages)->validate();
  26. foreach ($result as $lang => &$text) {
  27. if (preg_replace('/[\s\p{Z}\x{200B}\x{FEFF}]+/u', '', $text['text']) === '') {
  28. throw ValidationException::withMessages(['badge_translations.' . $lang . '.text' => '角标不能为空']);
  29. }
  30. $text['text'] = trim($text['text']);
  31. }
  32. unset($text);
  33. return $result;
  34. }
  35. private function row(object $row): array
  36. {
  37. return [
  38. 'id' => (int) $row->id,
  39. 'menu_text' => (string) $row->menu_text,
  40. 'badge_translations' => json_decode((string) $row->badge_translations, true) ?: [
  41. 'zh' => ['text' => ''], 'en' => ['text' => ''], 'vi' => ['text' => ''],
  42. ],
  43. 'sort' => (int) $row->sort,
  44. 'status' => (int) ($row->status ?? 1),
  45. 'created_at' => (string) $row->created_at,
  46. 'updated_at' => (string) $row->updated_at,
  47. ];
  48. }
  49. public function listing(): array
  50. {
  51. $this->ready();
  52. $rows = DB::table('menu_badge_configs')->orderBy('sort')->orderBy('id')->get();
  53. return ['total' => $rows->count(), 'data' => $rows->map(fn ($row) => $this->row($row))->all()];
  54. }
  55. public function save(array $input): array
  56. {
  57. $this->ready();
  58. $data = Validator::make($input, [
  59. 'id' => ['nullable', 'integer', 'min:1'],
  60. 'menu_text' => ['required', 'string', 'max:200'],
  61. 'badge_translations' => ['required', 'array:zh,en,vi'],
  62. 'sort' => ['nullable', 'integer', 'min:0', 'max:9999'],
  63. 'status' => ['nullable', 'integer', 'in:0,1'],
  64. ])->validate();
  65. $data['menu_text'] = trim($data['menu_text']);
  66. if ($data['menu_text'] === '') {
  67. throw ValidationException::withMessages(['menu_text' => '菜单文本不能为空']);
  68. }
  69. $data['badge_translations'] = $this->badgeTexts($data['badge_translations']);
  70. $sort = array_key_exists('sort', $data) && $data['sort'] !== null ? (int) $data['sort'] : null;
  71. $status = array_key_exists('status', $data) && $data['status'] !== null ? (int) $data['status'] : null;
  72. return DB::transaction(function () use ($data, $sort, $status) {
  73. $now = now()->format('Y-m-d H:i:s');
  74. $payload = [
  75. 'menu_text' => $data['menu_text'],
  76. 'badge_translations' => json_encode($data['badge_translations'], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR),
  77. 'updated_at' => $now,
  78. ];
  79. if (!empty($data['id'])) {
  80. $before = DB::table('menu_badge_configs')->where('id', $data['id'])->lockForUpdate()->first();
  81. if (!$before) {
  82. throw ValidationException::withMessages(['id' => '配置不存在']);
  83. }
  84. if ($sort !== null) {
  85. $payload['sort'] = $sort;
  86. }
  87. if ($status !== null) {
  88. $payload['status'] = $status;
  89. }
  90. DB::table('menu_badge_configs')->where('id', $data['id'])->update($payload);
  91. OperationAuditService::record('menu_badge_config', (int) $data['id'], 'update', $this->row($before), $data);
  92. $id = (int) $data['id'];
  93. } else {
  94. $payload['sort'] = $sort ?? (int) DB::table('menu_badge_configs')->max('sort') + 1;
  95. $payload['status'] = $status ?? 1;
  96. $payload['created_at'] = $now;
  97. $id = (int) DB::table('menu_badge_configs')->insertGetId($payload);
  98. OperationAuditService::record('menu_badge_config', $id, 'create', [], $data + ['id' => $id]);
  99. }
  100. $saved = DB::table('menu_badge_configs')->where('id', $id)->first();
  101. return $this->row($saved);
  102. });
  103. }
  104. public function delete(array $input): array
  105. {
  106. $this->ready();
  107. $data = Validator::make($input, ['id' => ['required', 'integer', 'min:1']])->validate();
  108. return DB::transaction(function () use ($data) {
  109. $before = DB::table('menu_badge_configs')->where('id', $data['id'])->lockForUpdate()->first();
  110. if (!$before) {
  111. throw ValidationException::withMessages(['id' => '配置不存在']);
  112. }
  113. DB::table('menu_badge_configs')->where('id', $data['id'])->delete();
  114. OperationAuditService::record('menu_badge_config', (int) $data['id'], 'delete', $this->row($before), []);
  115. return ['id' => (int) $data['id']];
  116. });
  117. }
  118. public function status(array $input): array
  119. {
  120. $this->ready();
  121. $data = Validator::make($input, [
  122. 'id' => ['required', 'integer', 'min:1'],
  123. 'status' => ['required', 'integer', 'in:0,1'],
  124. ])->validate();
  125. return DB::transaction(function () use ($data) {
  126. $before = DB::table('menu_badge_configs')->where('id', $data['id'])->lockForUpdate()->first();
  127. if (!$before) {
  128. throw ValidationException::withMessages(['id' => '配置不存在']);
  129. }
  130. if ((int) $data['status'] === 1) {
  131. $this->badgeTexts(json_decode((string) $before->badge_translations, true) ?: []);
  132. }
  133. DB::table('menu_badge_configs')->where('id', $data['id'])->update([
  134. 'status' => (int) $data['status'],
  135. 'updated_at' => now()->format('Y-m-d H:i:s'),
  136. ]);
  137. OperationAuditService::record('menu_badge_config', (int) $data['id'], 'status', $this->row($before), $data);
  138. $saved = DB::table('menu_badge_configs')->where('id', $data['id'])->first();
  139. return $this->row($saved);
  140. });
  141. }
  142. }