117 lines
3.7 KiB
PHP
117 lines
3.7 KiB
PHP
<?php
|
|
declare(strict_types=1);
|
|
|
|
namespace app\smt\service;
|
|
|
|
use app\smt\model\AchievementLevel;
|
|
use app\smt\model\AchievementTheme;
|
|
|
|
class AchievementService
|
|
{
|
|
public function listActiveThemes(): array
|
|
{
|
|
$themes = AchievementTheme::where('is_active', 1)
|
|
->whereNull('deleted_at')
|
|
->order('sort_order', 'asc')
|
|
->order('id', 'asc')
|
|
->select()
|
|
->all();
|
|
|
|
if (empty($themes)) {
|
|
return [];
|
|
}
|
|
|
|
$themeIds = array_map(static function ($item) {
|
|
return (int) $item->id;
|
|
}, $themes);
|
|
|
|
$levels = AchievementLevel::whereIn('theme_id', $themeIds)
|
|
->whereNull('deleted_at')
|
|
->order('required_days', 'asc')
|
|
->order('sort_order', 'asc')
|
|
->select()
|
|
->all();
|
|
|
|
$groupedLevels = [];
|
|
foreach ($levels as $level) {
|
|
$groupedLevels[(int) $level->theme_id][] = [
|
|
'id' => (int) $level->id,
|
|
'theme_id' => (int) $level->theme_id,
|
|
'name' => (string) $level->name,
|
|
'icon' => (string) $level->icon,
|
|
'required_days' => (int) $level->required_days,
|
|
'sort_order' => (int) $level->sort_order,
|
|
];
|
|
}
|
|
|
|
return array_map(static function ($theme) use ($groupedLevels) {
|
|
return [
|
|
'id' => (int) $theme->id,
|
|
'name' => (string) $theme->name,
|
|
'key' => (string) $theme->key,
|
|
'icon' => (string) $theme->icon,
|
|
'sort_order' => (int) $theme->sort_order,
|
|
'is_active' => (bool) $theme->is_active,
|
|
'levels' => $groupedLevels[(int) $theme->id] ?? [],
|
|
];
|
|
}, $themes);
|
|
}
|
|
|
|
public function getUserAchievement(int $themeId, int $days): ?array
|
|
{
|
|
$theme = AchievementTheme::where('id', $themeId)->whereNull('deleted_at')->find();
|
|
if (!$theme) {
|
|
return null;
|
|
}
|
|
|
|
$levels = AchievementLevel::where('theme_id', $themeId)
|
|
->whereNull('deleted_at')
|
|
->order('required_days', 'asc')
|
|
->order('sort_order', 'asc')
|
|
->select()
|
|
->all();
|
|
|
|
$current = null;
|
|
$next = null;
|
|
foreach ($levels as $index => $level) {
|
|
if ($days >= (int) $level->required_days) {
|
|
$current = $level;
|
|
$next = $levels[$index + 1] ?? null;
|
|
}
|
|
}
|
|
|
|
$progress = 0.0;
|
|
if ($current && $next) {
|
|
$rangeTotal = (int) $next->required_days - (int) $current->required_days;
|
|
if ($rangeTotal > 0) {
|
|
$progress = max(0, min(1, ($days - (int) $current->required_days) / $rangeTotal));
|
|
}
|
|
} elseif ($current) {
|
|
$progress = 1.0;
|
|
}
|
|
|
|
return [
|
|
'theme_id' => (int) $theme->id,
|
|
'theme_name' => (string) $theme->name,
|
|
'theme_key' => (string) $theme->key,
|
|
'theme_icon' => (string) $theme->icon,
|
|
'days' => max(0, $days),
|
|
'current' => $current ? $this->formatLevel($current) : null,
|
|
'next' => $next ? $this->formatLevel($next) : null,
|
|
'progress' => $progress,
|
|
];
|
|
}
|
|
|
|
private function formatLevel(AchievementLevel $level): array
|
|
{
|
|
return [
|
|
'id' => (int) $level->id,
|
|
'theme_id' => (int) $level->theme_id,
|
|
'name' => (string) $level->name,
|
|
'icon' => (string) $level->icon,
|
|
'required_days' => (int) $level->required_days,
|
|
'sort_order' => (int) $level->sort_order,
|
|
];
|
|
}
|
|
}
|