'菜单角标配置未初始化,请先完成系统更新']); } } 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, 'status' => (int) ($row->status ?? 1), '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'], 'status' => ['nullable', 'integer', 'in:0,1'], ])->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; $status = array_key_exists('status', $data) && $data['status'] !== null ? (int) $data['status'] : null; return DB::transaction(function () use ($data, $sort, $status) { $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; } if ($status !== null) { $payload['status'] = $status; } 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['status'] = $status ?? 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']]; }); } public function status(array $input): array { $this->ready(); $data = Validator::make($input, [ 'id' => ['required', 'integer', 'min:1'], 'status' => ['required', 'integer', 'in:0,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' => '配置不存在']); } if ((int) $data['status'] === 1) { $this->badgeTexts(json_decode((string) $before->badge_translations, true) ?: []); } DB::table('menu_badge_configs')->where('id', $data['id'])->update([ 'status' => (int) $data['status'], 'updated_at' => now()->format('Y-m-d H:i:s'), ]); OperationAuditService::record('menu_badge_config', (int) $data['id'], 'status', $this->row($before), $data); $saved = DB::table('menu_badge_configs')->where('id', $data['id'])->first(); return $this->row($saved); }); } }