Files
chatroom/resources/views/admin/shop/index.blade.php
T
2026-04-30 09:40:50 +08:00

378 lines
22 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{{--
文件功能:后台商店商品管理页面(站长功能)
支持查看、新增、编辑、上下架切换、删除商品。
字段:名称、Slug、图标、描述、价格、类型、有效期、排序、状态
@author ChatRoom Laravel
@version 1.0.0
--}}
@extends('admin.layouts.app')
@section('title', '🛒 商店商品管理')
@section('content')
@php require resource_path('views/admin/partials/list-theme.php'); @endphp
@php
$typeLabels = [
'instant' => ['label' => '即时特效', 'color' => 'bg-blue-100 text-blue-700'],
'duration' => ['label' => '周卡/时效', 'color' => 'bg-purple-100 text-purple-700'],
'one_time' => ['label' => '一次性道具', 'color' => 'bg-yellow-100 text-yellow-700'],
'ring' => ['label' => '求婚戒指', 'color' => 'bg-rose-100 text-rose-700'],
'auto_fishing' => ['label' => '自动钓鱼卡', 'color' => 'bg-emerald-100 text-emerald-700'],
'sign_repair' => ['label' => '签到补签卡', 'color' => 'bg-teal-100 text-teal-700'],
'msg_bubble' => ['label' => '消息气泡', 'color' => 'bg-violet-100 text-violet-700'],
'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
<div x-data="{
showForm: false,
editing: null,
form: {
name: '',
slug: '',
icon: '🎁',
description: '',
price: 100,
type: 'instant',
duration_days: 0,
duration_minutes: 0,
intimacy_bonus: 0,
charm_bonus: 0,
welcome_message: '',
sort_order: 0,
is_active: true,
},
openCreate() {
this.editing = null;
this.form = {
name: '',
slug: '',
icon: '🎁',
description: '',
price: 100,
type: 'instant',
duration_days: 0,
duration_minutes: 0,
intimacy_bonus: 0,
charm_bonus: 0,
welcome_message: '',
sort_order: 0,
is_active: true,
};
this.showForm = true;
this.$nextTick(() => this.$refs.nameInput?.focus());
},
openEdit(item) {
this.editing = item;
this.form = {
name: item.name,
slug: item.slug,
icon: item.icon,
description: item.description || '',
price: item.price,
type: item.type,
duration_days: item.duration_days || 0,
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,
};
this.showForm = true;
this.$nextTick(() => this.$refs.nameInput?.focus());
},
closeForm() {
this.showForm = false;
this.editing = null;
}
}">
{{-- 头部操作栏 --}}
<div class="{{ $adminListHeaderCardClass }} mb-6">
<div class="flex justify-between items-center">
<div>
<h2 class="{{ $adminListHeaderTitleClass }}">商店商品列表</h2>
<p class="{{ $adminListHeaderSubtitleClass }}">管理聊天室商店内所有可出售商品,支持上下架控制。</p>
</div>
@if ($isSuperAdmin)
<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 }}">类型</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 ($items as $item)
@php $tl = $typeLabels[$item->type] ?? ['label' => $item->type, 'color' => 'bg-gray-100 text-gray-600']; @endphp
<tr class="{{ $adminListTableRowClass }} {{ $item->is_active ? '' : 'opacity-50' }}">
{{-- 商品信息 --}}
<td class="px-4 py-3">
<div class="flex items-center gap-3">
<span class="text-2xl leading-none">{{ $item->icon }}</span>
<div>
<p class="{{ $adminListPrimaryTextClass }}">{{ $item->name }}</p>
<p class="{{ $adminListSecondaryTextClass }} font-mono">{{ $item->slug }}</p>
@if ($item->description)
<p class="text-xs text-gray-500 mt-0.5 max-w-xs truncate" title="{{ $item->description }}">
{{ $item->description }}</p>
@endif
</div>
</div>
</td>
{{-- 类型 --}}
<td class="px-4 py-3">
<span class="px-2 py-0.5 rounded-full text-xs font-semibold {{ $tl['color'] }}">
{{ $tl['label'] }}
</span>
</td>
{{-- 价格 --}}
<td class="px-4 py-3 text-right font-mono font-bold text-amber-600">
{{ number_format($item->price) }}
</td>
{{-- 有效期 --}}
<td class="px-4 py-3 text-center text-gray-500 text-xs">
@if ($item->duration_minutes > 0)
{{ $item->duration_minutes >= 60 ? floor($item->duration_minutes / 60) . '小时' : $item->duration_minutes . '分钟' }}
@elseif ($item->duration_days > 0)
{{ $item->duration_days }}
@else
@endif
</td>
{{-- 排序 --}}
<td class="px-4 py-3 text-center text-gray-400 font-mono text-xs">{{ $item->sort_order }}</td>
{{-- 状态 --}}
<td class="px-4 py-3 text-center">
<form method="POST" action="{{ route('admin.shop.toggle', $item) }}" class="inline">
@csrf @method('PATCH')
<button type="submit"
class="px-2.5 py-1 rounded-full text-xs font-bold transition
{{ $item->is_active
? 'bg-emerald-100 text-emerald-700 hover:bg-emerald-200'
: 'bg-gray-100 text-gray-500 hover:bg-gray-200' }}">
{{ $item->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' => $item->id,
'name' => $item->name,
'slug' => $item->slug,
'icon' => $item->icon,
'description' => $item->description,
'price' => $item->price,
'type' => $item->type,
'duration_days' => $item->duration_days,
'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,
]) }})"
class="{{ $adminListActionButtonClass }} text-indigo-600 hover:text-indigo-800 hover:bg-indigo-50">
编辑
</button>
@if ($isSuperAdmin)
<form method="POST" action="{{ route('admin.shop.destroy', $item) }}"
data-admin-confirm="确定要删除「{{ $item->name }}」吗?此操作不可撤销!">
@csrf @method('DELETE')
<button type="submit"
class="{{ $adminListActionButtonClass }} text-red-500 hover:text-red-700 hover:bg-red-50">
删除
</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="bg-white rounded-2xl shadow-2xl w-full max-w-xl max-h-[90vh] overflow-y-auto"
x-transition:enter="transition ease-out duration-200" x-transition:enter-start="opacity-0 translate-y-4"
x-transition:enter-end="opacity-100 translate-y-0">
{{-- 弹窗头部 --}}
<div class="flex items-center justify-between px-6 py-4 border-b">
<h3 class="font-bold text-gray-800 text-lg" x-text="editing ? '编辑商品:' + editing.name : '新增商品'"></h3>
<button @click="closeForm()"
class="text-gray-400 hover:text-gray-600 text-2xl leading-none">&times;</button>
</div>
{{-- 表单 --}}
<form method="POST"
:action="editing
?
'{{ url('admin/shop') }}/' + editing.id :
'{{ route('admin.shop.store') }}'"
class="px-6 py-5 space-y-4">
@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="block text-xs font-semibold text-gray-600 mb-1">商品名称 <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 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">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">Slug(唯一标识)<span
class="text-red-500">*</span></label>
<input type="text" name="slug" x-model="form.slug" required maxlength="100"
class="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm font-mono focus:ring-2 focus:ring-indigo-400 focus:border-indigo-400 outline-none">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">图标(Emoji<span
class="text-red-500">*</span></label>
<input type="text" name="icon" x-model="form.icon" required maxlength="20"
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">
</div>
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">商品描述</label>
<textarea name="description" x-model="form.description" rows="2" maxlength="500"
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>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">价格(金币)<span
class="text-red-500">*</span></label>
<input type="number" name="price" x-model="form.price" required min="0"
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">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">商品类型 <span
class="text-red-500">*</span></label>
<select name="type" x-model="form.type" required
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 bg-white">
<option value="instant">instant 即时特效</option>
<option value="duration">duration 周卡/时效</option>
<option value="one_time">one_time 一次性道具</option>
<option value="ring">ring 求婚戒指</option>
<option value="auto_fishing">auto_fishing 自动钓鱼卡</option>
<option value="sign_repair">sign_repair 签到补签卡</option>
<option value="msg_bubble">msg_bubble 消息气泡</option>
<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>
<input type="number" name="duration_days" x-model="form.duration_days" min="0"
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">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">有效分钟数</label>
<input type="number" name="duration_minutes" x-model="form.duration_minutes" min="0"
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">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">排序权重</label>
<input type="number" name="sort_order" x-model="form.sort_order" min="0"
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">
</div>
</div>
<div class="grid grid-cols-2 gap-4">
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">亲密度加成(戒指)</label>
<input type="number" name="intimacy_bonus" x-model="form.intimacy_bonus" min="0"
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">
</div>
<div>
<label class="block text-xs font-semibold text-gray-600 mb-1">魅力值加成(戒指)</label>
<input type="number" name="charm_bonus" x-model="form.charm_bonus" min="0"
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">
</div>
</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 items-center cursor-pointer">
<input type="checkbox" x-model="form.is_active" class="sr-only peer">
<div
class="w-10 h-5 bg-gray-200 peer-checked:bg-indigo-500 rounded-full transition peer-focus:ring-2 peer-focus:ring-indigo-300">
</div>
<div
class="absolute left-0.5 top-0.5 w-4 h-4 bg-white rounded-full 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="flex justify-end gap-3 pt-2 border-t mt-4">
<button type="button" @click="closeForm()"
class="px-5 py-2 rounded-lg border border-gray-300 text-gray-600 hover:bg-gray-50 transition text-sm">
取消
</button>
<button type="submit"
class="px-6 py-2 bg-indigo-600 text-white rounded-lg font-bold hover:bg-indigo-700 transition text-sm shadow">
<span x-text="editing ? '保存修改' : '创建商品'"></span>
</button>
</div>
</form>
</div>
</div>
</div>
@endsection