改为独立座驾模块

This commit is contained in:
pllx
2026-04-30 09:55:20 +08:00
parent 3c95478097
commit 181cc6a0b0
22 changed files with 886 additions and 216 deletions
@@ -84,6 +84,10 @@
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.shop.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '🛒 商店管理' !!}
</a>
<a href="{{ route('admin.rides.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.rides.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
🚘 座驾管理
</a>
<a href="{{ route('admin.marriages.index') }}"
class="block px-4 py-3 rounded-md transition {{ request()->routeIs('admin.marriages.*') ? 'bg-indigo-600 font-bold' : 'hover:bg-white/10' }}">
{!! '💒 婚姻管理' !!}
+270
View File
@@ -0,0 +1,270 @@
{{--
文件功能:后台座驾独立管理页面
支持查看、新增、编辑、上下架切换、删除座驾,以及配置价格、使用天数和欢迎语。
--}}
@extends('admin.layouts.app')
@section('title', '🚘 座驾管理')
@section('content')
@php require resource_path('views/admin/partials/list-theme.php'); @endphp
@php
$isSuperAdmin = Auth::id() === 1;
@endphp
<div x-data="{
showForm: false,
editing: null,
form: {
name: '',
slug: 'ride_',
effect_key: '',
icon: '🚘',
description: '',
price: 1000,
duration_days: 7,
welcome_message: '',
sort_order: 80,
is_active: true,
},
openCreate() {
this.editing = null;
this.form = {
name: '',
slug: 'ride_',
effect_key: '',
icon: '🚘',
description: '',
price: 1000,
duration_days: 7,
welcome_message: '',
sort_order: 80,
is_active: true,
};
this.showForm = true;
this.$nextTick(() => this.$refs.nameInput?.focus());
},
openEdit(ride) {
this.editing = ride;
this.form = {
name: ride.name,
slug: ride.slug,
effect_key: ride.effect_key,
icon: ride.icon,
description: ride.description || '',
price: ride.price,
duration_days: ride.duration_days || 7,
welcome_message: ride.welcome_message || '',
sort_order: ride.sort_order,
is_active: ride.is_active,
};
this.showForm = true;
this.$nextTick(() => this.$refs.nameInput?.focus());
},
closeForm() {
this.showForm = false;
this.editing = null;
}
}">
<div class="{{ $adminListHeaderCardClass }} mb-6">
<div class="flex items-center justify-between">
<div>
<h2 class="{{ $adminListHeaderTitleClass }}">聊天室座驾列表</h2>
<p class="{{ $adminListHeaderSubtitleClass }}">单独管理座驾价格、使用天数、入场欢迎语和全屏特效 key。</p>
</div>
@if ($isSuperAdmin)
<button type="button" @click="openCreate()" class="{{ $adminListPrimaryButtonClass }}">
+ 新增座驾
</button>
@endif
</div>
</div>
<div class="{{ $adminListCardClass }}">
<table class="{{ $adminListTableClass }}">
<thead class="{{ $adminListTableHeadRowClass }}">
<tr>
<th class="{{ $adminListTableHeadCellClass }}">座驾</th>
<th class="{{ $adminListTableHeadCellClass }}">特效 Key</th>
<th class="{{ $adminListTableHeadCellClass }} text-right">价格</th>
<th class="{{ $adminListTableHeadCellClass }} text-center">使用天数</th>
<th class="{{ $adminListTableHeadCellClass }} text-center">排序</th>
<th class="{{ $adminListTableHeadCellClass }} text-center">状态</th>
<th class="{{ $adminListTableHeadCellClass }} text-center">操作</th>
</tr>
</thead>
<tbody class="{{ $adminListTableBodyClass }}">
@forelse ($rides as $ride)
<tr class="{{ $adminListTableRowClass }} {{ $ride->is_active ? '' : 'opacity-50' }}">
<td class="px-4 py-3">
<div class="flex items-center gap-3">
<span class="text-2xl leading-none">{{ $ride->icon }}</span>
<div>
<p class="{{ $adminListPrimaryTextClass }}">{{ $ride->name }}</p>
<p class="{{ $adminListSecondaryTextClass }} font-mono">{{ $ride->slug }}</p>
@if ($ride->description)
<p class="mt-0.5 max-w-xs truncate text-xs text-gray-500" title="{{ $ride->description }}">
{{ $ride->description }}
</p>
@endif
</div>
</div>
</td>
<td class="px-4 py-3 font-mono text-xs text-gray-600">{{ $ride->effect_key }}</td>
<td class="px-4 py-3 text-right font-mono font-bold text-amber-600">
{{ number_format($ride->price) }}
</td>
<td class="px-4 py-3 text-center text-xs text-gray-500">{{ $ride->duration_days }} </td>
<td class="px-4 py-3 text-center font-mono text-xs text-gray-400">{{ $ride->sort_order }}</td>
<td class="px-4 py-3 text-center">
<form method="POST" action="{{ route('admin.rides.toggle', $ride) }}" class="inline">
@csrf @method('PATCH')
<button type="submit"
class="rounded-full px-2.5 py-1 text-xs font-bold transition {{ $ride->is_active ? 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200' : 'bg-gray-100 text-gray-500 hover:bg-gray-200' }}">
{{ $ride->is_active ? '上架中' : '已下架' }}
</button>
</form>
</td>
<td class="px-4 py-3">
<div class="flex items-center justify-center gap-2">
<button
@click="openEdit({{ json_encode([
'id' => $ride->id,
'name' => $ride->name,
'slug' => $ride->slug,
'effect_key' => $ride->effect_key,
'icon' => $ride->icon,
'description' => $ride->description,
'price' => $ride->price,
'duration_days' => $ride->duration_days,
'welcome_message' => $ride->welcome_message,
'sort_order' => $ride->sort_order,
'is_active' => (bool) $ride->is_active,
], JSON_UNESCAPED_UNICODE) }})"
class="{{ $adminListActionButtonClass }} text-indigo-600 hover:bg-indigo-50 hover:text-indigo-800">
编辑
</button>
@if ($isSuperAdmin)
<form method="POST" action="{{ route('admin.rides.destroy', $ride) }}"
data-admin-confirm="确定要删除「{{ $ride->name }}」吗?此操作不可撤销!">
@csrf @method('DELETE')
<button type="submit"
class="{{ $adminListActionButtonClass }} text-red-500 hover:bg-red-50 hover:text-red-700">
删除
</button>
</form>
@endif
</div>
</td>
</tr>
@empty
<tr>
<td colspan="7" class="{{ $adminListEmptyClass }}">暂无座驾数据</td>
</tr>
@endforelse
</tbody>
</table>
</div>
<div x-show="showForm" x-cloak class="fixed inset-0 z-50 flex items-center justify-center"
style="background: rgba(0,0,0,0.45);">
<div @click.stop class="max-h-[90vh] w-full max-w-xl overflow-y-auto rounded-2xl bg-white shadow-2xl"
x-transition:enter="transition ease-out duration-200" x-transition:enter-start="translate-y-4 opacity-0"
x-transition:enter-end="translate-y-0 opacity-100">
<div class="flex items-center justify-between border-b px-6 py-4">
<h3 class="text-lg font-bold text-gray-800" x-text="editing ? '编辑座驾:' + editing.name : '新增座驾'"></h3>
<button type="button" @click="closeForm()" class="text-2xl leading-none text-gray-400 hover:text-gray-600">&times;</button>
</div>
<form method="POST"
:action="editing ? '{{ url('admin/rides') }}/' + editing.id : '{{ route('admin.rides.store') }}'"
class="space-y-4 px-6 py-5">
@csrf
<template x-if="editing"><input type="hidden" name="_method" value="PUT"></template>
<div class="grid grid-cols-2 gap-4">
<div class="col-span-2">
<label class="mb-1 block text-xs font-semibold text-gray-600">座驾名称 <span class="text-red-500">*</span></label>
<input x-ref="nameInput" type="text" name="name" x-model="form.name" required maxlength="100"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-indigo-400 focus:ring-2 focus:ring-indigo-400">
</div>
<div>
<label class="mb-1 block text-xs font-semibold text-gray-600">Slug <span class="text-red-500">*</span></label>
<input type="text" name="slug" x-model="form.slug" required maxlength="100"
class="w-full rounded-lg border border-gray-300 px-3 py-2 font-mono text-sm outline-none focus:border-indigo-400 focus:ring-2 focus:ring-indigo-400">
<p class="mt-1 text-[11px] text-gray-500">格式:ride_j35、ride_df5c。</p>
</div>
<div>
<label class="mb-1 block text-xs font-semibold text-gray-600">特效 Key <span class="text-red-500">*</span></label>
<input type="text" name="effect_key" x-model="form.effect_key" required maxlength="50"
class="w-full rounded-lg border border-gray-300 px-3 py-2 font-mono text-sm outline-none focus:border-indigo-400 focus:ring-2 focus:ring-indigo-400">
<p class="mt-1 text-[11px] text-gray-500">对应 resources/js/effects/&lt;key&gt;.js。</p>
</div>
<div>
<label class="mb-1 block text-xs font-semibold text-gray-600">图标 <span class="text-red-500">*</span></label>
<input type="text" name="icon" x-model="form.icon" required maxlength="20"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-indigo-400 focus:ring-2 focus:ring-indigo-400">
</div>
<div>
<label class="mb-1 block text-xs font-semibold text-gray-600">价格(金币)<span class="text-red-500">*</span></label>
<input type="number" name="price" x-model="form.price" required min="0"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-indigo-400 focus:ring-2 focus:ring-indigo-400">
</div>
</div>
<div>
<label class="mb-1 block text-xs font-semibold text-gray-600">座驾描述</label>
<textarea name="description" x-model="form.description" rows="2" maxlength="500"
class="w-full resize-none rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-indigo-400 focus:ring-2 focus:ring-indigo-400"></textarea>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="mb-1 block text-xs font-semibold text-gray-600">使用天数 <span class="text-red-500">*</span></label>
<input type="number" name="duration_days" x-model="form.duration_days" required min="1"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-indigo-400 focus:ring-2 focus:ring-indigo-400">
</div>
<div>
<label class="mb-1 block text-xs font-semibold text-gray-600">排序权重</label>
<input type="number" name="sort_order" x-model="form.sort_order" min="0"
class="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-indigo-400 focus:ring-2 focus:ring-indigo-400">
</div>
</div>
<div>
<label class="mb-1 block text-xs font-semibold text-gray-600">入场欢迎语句</label>
<textarea name="welcome_message" x-model="form.welcome_message" rows="2" maxlength="255"
placeholder="支持 {name} 用户名、{ride} 座驾名,例如:【{name}】驾驶【{ride}】震撼入场!"
class="w-full resize-none rounded-lg border border-gray-300 px-3 py-2 text-sm outline-none focus:border-indigo-400 focus:ring-2 focus:ring-indigo-400"></textarea>
</div>
<div class="flex items-center gap-2">
<input type="hidden" name="is_active" :value="form.is_active ? 1 : 0">
<label class="relative inline-flex cursor-pointer items-center">
<input type="checkbox" x-model="form.is_active" class="peer sr-only">
<div class="h-5 w-10 rounded-full bg-gray-200 transition peer-checked:bg-indigo-500 peer-focus:ring-2 peer-focus:ring-indigo-300"></div>
<div class="absolute left-0.5 top-0.5 h-4 w-4 rounded-full bg-white shadow transition peer-checked:translate-x-5"></div>
</label>
<span class="text-sm text-gray-600" x-text="form.is_active ? '上架显示' : '下架隐藏'"></span>
</div>
<div class="mt-4 flex justify-end gap-3 border-t pt-2">
<button type="button" @click="closeForm()"
class="rounded-lg border border-gray-300 px-5 py-2 text-sm text-gray-600 transition hover:bg-gray-50">
取消
</button>
<button type="submit"
class="rounded-lg bg-indigo-600 px-6 py-2 text-sm font-bold text-white shadow transition hover:bg-indigo-700">
<span x-text="editing ? '保存修改' : '创建座驾'"></span>
</button>
</div>
</form>
</div>
</div>
</div>
@endsection
@@ -26,7 +26,6 @@
'msg_name_color' => ['label' => '昵称颜色', 'color' => 'bg-pink-100 text-pink-700'],
'msg_text_color' => ['label' => '文字颜色', 'color' => 'bg-cyan-100 text-cyan-700'],
'avatar_frame' => ['label' => '头像框', 'color' => 'bg-amber-100 text-amber-700'],
'ride' => ['label' => '聊天室座驾', 'color' => 'bg-slate-900 text-white'],
];
$isSuperAdmin = Auth::id() === 1;
@endphp
@@ -45,7 +44,6 @@
duration_minutes: 0,
intimacy_bonus: 0,
charm_bonus: 0,
welcome_message: '',
sort_order: 0,
is_active: true,
},
@@ -63,7 +61,6 @@
duration_minutes: 0,
intimacy_bonus: 0,
charm_bonus: 0,
welcome_message: '',
sort_order: 0,
is_active: true,
};
@@ -84,7 +81,6 @@
duration_minutes: item.duration_minutes || 0,
intimacy_bonus: item.intimacy_bonus || 0,
charm_bonus: item.charm_bonus || 0,
welcome_message: item.welcome_message || '',
sort_order: item.sort_order,
is_active: item.is_active,
};
@@ -196,7 +192,6 @@
'duration_minutes' => $item->duration_minutes,
'intimacy_bonus' => $item->intimacy_bonus,
'charm_bonus' => $item->charm_bonus,
'welcome_message' => $item->welcome_message,
'sort_order' => $item->sort_order,
'is_active' => (bool) $item->is_active,
]) }})"
@@ -300,19 +295,10 @@
<option value="msg_name_color">msg_name_color 昵称颜色</option>
<option value="msg_text_color">msg_text_color 文字颜色</option>
<option value="avatar_frame">avatar_frame 头像框</option>
<option value="ride">ride 聊天室座驾</option>
</select>
</div>
</div>
<div x-show="form.type === 'ride'">
<label class="block text-xs font-semibold text-gray-600 mb-1">座驾欢迎语句</label>
<textarea name="welcome_message" x-model="form.welcome_message" rows="2" maxlength="255"
placeholder="支持 {name} 用户名、{ride} 座驾名,例如:【{name}】驾驶【{ride}】震撼入场!"
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-indigo-400 focus:border-indigo-400 outline-none resize-none"></textarea>
<p class="mt-1 text-[11px] text-gray-500">仅座驾类型生效;不填写时使用系统默认欢迎语。</p>
</div>
<div class="grid grid-cols-3 gap-4">
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">有效天数</label>