|
|
@@ -0,0 +1,125 @@
|
|
|
+<?php
|
|
|
+
|
|
|
+namespace App\Services;
|
|
|
+
|
|
|
+use Illuminate\Support\Facades\DB;
|
|
|
+use Illuminate\Support\Facades\Schema;
|
|
|
+use Illuminate\Support\Facades\Validator;
|
|
|
+use Illuminate\Validation\ValidationException;
|
|
|
+
|
|
|
+final class MenuBadgeAdminService
|
|
|
+{
|
|
|
+ private function ready(): void
|
|
|
+ {
|
|
|
+ if (!Schema::hasTable('menu_badge_configs')) {
|
|
|
+ throw ValidationException::withMessages(['config' => '菜单角标配置未初始化,请先完成系统更新']);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private function badgeTexts(array $texts): array
|
|
|
+ {
|
|
|
+ $rules = [];
|
|
|
+ $messages = [];
|
|
|
+ foreach (['zh' => '中文', 'en' => '英文', 'vi' => '越南文'] as $lang => $label) {
|
|
|
+ $rules[$lang] = ['required', 'array:text'];
|
|
|
+ $rules[$lang . '.text'] = ['required', 'string', 'max:100'];
|
|
|
+ $messages[$lang . '.required'] = $label . '角标不能为空';
|
|
|
+ $messages[$lang . '.text.required'] = $label . '角标不能为空';
|
|
|
+ }
|
|
|
+ $result = Validator::make($texts, $rules, $messages)->validate();
|
|
|
+ foreach ($result as $lang => &$text) {
|
|
|
+ if (preg_replace('/[\s\p{Z}\x{200B}\x{FEFF}]+/u', '', $text['text']) === '') {
|
|
|
+ throw ValidationException::withMessages(['badge_translations.' . $lang . '.text' => '角标不能为空']);
|
|
|
+ }
|
|
|
+ $text['text'] = trim($text['text']);
|
|
|
+ }
|
|
|
+ unset($text);
|
|
|
+
|
|
|
+ return $result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private function row(object $row): array
|
|
|
+ {
|
|
|
+ return [
|
|
|
+ 'id' => (int) $row->id,
|
|
|
+ 'menu_text' => (string) $row->menu_text,
|
|
|
+ 'badge_translations' => json_decode((string) $row->badge_translations, true) ?: [
|
|
|
+ 'zh' => ['text' => ''], 'en' => ['text' => ''], 'vi' => ['text' => ''],
|
|
|
+ ],
|
|
|
+ 'sort' => (int) $row->sort,
|
|
|
+ 'created_at' => (string) $row->created_at,
|
|
|
+ 'updated_at' => (string) $row->updated_at,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+
|
|
|
+ public function listing(): array
|
|
|
+ {
|
|
|
+ $this->ready();
|
|
|
+ $rows = DB::table('menu_badge_configs')->orderBy('sort')->orderBy('id')->get();
|
|
|
+
|
|
|
+ return ['total' => $rows->count(), 'data' => $rows->map(fn ($row) => $this->row($row))->all()];
|
|
|
+ }
|
|
|
+
|
|
|
+ public function save(array $input): array
|
|
|
+ {
|
|
|
+ $this->ready();
|
|
|
+ $data = Validator::make($input, [
|
|
|
+ 'id' => ['nullable', 'integer', 'min:1'],
|
|
|
+ 'menu_text' => ['required', 'string', 'max:200'],
|
|
|
+ 'badge_translations' => ['required', 'array:zh,en,vi'],
|
|
|
+ 'sort' => ['nullable', 'integer', 'min:0', 'max:9999'],
|
|
|
+ ])->validate();
|
|
|
+ $data['menu_text'] = trim($data['menu_text']);
|
|
|
+ if ($data['menu_text'] === '') {
|
|
|
+ throw ValidationException::withMessages(['menu_text' => '菜单文本不能为空']);
|
|
|
+ }
|
|
|
+ $data['badge_translations'] = $this->badgeTexts($data['badge_translations']);
|
|
|
+ $sort = array_key_exists('sort', $data) && $data['sort'] !== null ? (int) $data['sort'] : null;
|
|
|
+
|
|
|
+ return DB::transaction(function () use ($data, $sort) {
|
|
|
+ $now = now()->format('Y-m-d H:i:s');
|
|
|
+ $payload = [
|
|
|
+ 'menu_text' => $data['menu_text'],
|
|
|
+ 'badge_translations' => json_encode($data['badge_translations'], JSON_UNESCAPED_UNICODE | JSON_THROW_ON_ERROR),
|
|
|
+ 'updated_at' => $now,
|
|
|
+ ];
|
|
|
+ if (!empty($data['id'])) {
|
|
|
+ $before = DB::table('menu_badge_configs')->where('id', $data['id'])->lockForUpdate()->first();
|
|
|
+ if (!$before) {
|
|
|
+ throw ValidationException::withMessages(['id' => '配置不存在']);
|
|
|
+ }
|
|
|
+ if ($sort !== null) {
|
|
|
+ $payload['sort'] = $sort;
|
|
|
+ }
|
|
|
+ DB::table('menu_badge_configs')->where('id', $data['id'])->update($payload);
|
|
|
+ OperationAuditService::record('menu_badge_config', (int) $data['id'], 'update', $this->row($before), $data);
|
|
|
+ $id = (int) $data['id'];
|
|
|
+ } else {
|
|
|
+ $payload['sort'] = $sort ?? (int) DB::table('menu_badge_configs')->max('sort') + 1;
|
|
|
+ $payload['created_at'] = $now;
|
|
|
+ $id = (int) DB::table('menu_badge_configs')->insertGetId($payload);
|
|
|
+ OperationAuditService::record('menu_badge_config', $id, 'create', [], $data + ['id' => $id]);
|
|
|
+ }
|
|
|
+ $saved = DB::table('menu_badge_configs')->where('id', $id)->first();
|
|
|
+
|
|
|
+ return $this->row($saved);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ public function delete(array $input): array
|
|
|
+ {
|
|
|
+ $this->ready();
|
|
|
+ $data = Validator::make($input, ['id' => ['required', 'integer', 'min:1']])->validate();
|
|
|
+
|
|
|
+ return DB::transaction(function () use ($data) {
|
|
|
+ $before = DB::table('menu_badge_configs')->where('id', $data['id'])->lockForUpdate()->first();
|
|
|
+ if (!$before) {
|
|
|
+ throw ValidationException::withMessages(['id' => '配置不存在']);
|
|
|
+ }
|
|
|
+ DB::table('menu_badge_configs')->where('id', $data['id'])->delete();
|
|
|
+ OperationAuditService::record('menu_badge_config', (int) $data['id'], 'delete', $this->row($before), []);
|
|
|
+
|
|
|
+ return ['id' => (int) $data['id']];
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|