feat: 整合 Preline UI 3.x 與重寫 README 為 Docker 架構
- 新增 Preline UI 3.2.3 作為 UI 組件庫 - 更新 tailwind.config.js 整合 Preline - 更新 app.js 初始化 Preline - 完全重寫 README.md 以 Docker 容器化架構為核心 - 新增 Docker 常用指令大全 - 新增故障排除與生產部署指南 - 新增會員系統相關功能(會員、錢包、點數、會籍、禮物) - 新增社交登入測試功能
This commit is contained in:
132
resources/views/admin/deposit-bonus-rules/index.blade.php
Normal file
132
resources/views/admin/deposit-bonus-rules/index.blade.php
Normal file
@@ -0,0 +1,132 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('content')
|
||||
@php
|
||||
$theme = request()->cookie('theme', 'dark-blue');
|
||||
$isLight = in_array($theme, ['light-blue', 'light-green']);
|
||||
$cardBg = $isLight ? 'bg-white' : 'bg-gray-800';
|
||||
$textPrimary = $isLight ? 'text-gray-900' : 'text-gray-200';
|
||||
$textSecondary = $isLight ? 'text-gray-600' : 'text-gray-400';
|
||||
$borderColor = $isLight ? 'border-gray-200' : 'border-gray-700';
|
||||
$thBg = $isLight ? 'bg-gray-50' : 'bg-gray-700';
|
||||
$inputBg = $isLight ? 'bg-white' : 'bg-gray-700';
|
||||
$inputBorder = $isLight ? 'border-gray-300' : 'border-gray-600';
|
||||
@endphp
|
||||
|
||||
{{-- Toast 通知 --}}
|
||||
@if(session('success'))
|
||||
<div x-data="{ show: false }"
|
||||
x-show="show"
|
||||
x-cloak
|
||||
x-init="setTimeout(() => { show = true; setTimeout(() => show = false, 3000) }, 50)"
|
||||
x-transition:enter="transition cubic-bezier(0.34, 1.56, 0.64, 1) duration-300"
|
||||
x-transition:enter-start="opacity-0 -translate-y-40"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-400"
|
||||
x-transition:leave-start="opacity-100 translate-y-0"
|
||||
x-transition:leave-end="opacity-0 -translate-y-40"
|
||||
class="fixed top-4 left-0 right-0 mx-auto w-max z-[100] bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="container mx-auto px-6 py-8">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="{{ $textPrimary }} text-3xl font-medium">儲值回饋設定</h3>
|
||||
<button onclick="document.getElementById('createModal').classList.remove('hidden')" class="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700">
|
||||
新增規則
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full {{ $cardBg }} rounded-lg overflow-hidden">
|
||||
<thead class="{{ $thBg }}">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">名稱</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">最低儲值</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">回饋類型</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">回饋值</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">狀態</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y {{ $borderColor }}">
|
||||
@forelse($rules as $rule)
|
||||
<tr>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">{{ $rule->name }}</td>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">${{ number_format($rule->min_amount) }}</td>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">{{ $rule->bonus_type == 'fixed' ? '固定金額' : '百分比' }}</td>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">{{ $rule->bonus_type == 'fixed' ? '$'.number_format($rule->bonus_value) : $rule->bonus_value.'%' }}</td>
|
||||
<td class="px-6 py-4">
|
||||
@if($rule->is_active)
|
||||
<span class="px-2 py-1 rounded-full bg-green-100 text-green-800 text-xs">啟用</span>
|
||||
@else
|
||||
<span class="px-2 py-1 rounded-full bg-gray-100 text-gray-800 text-xs">停用</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<form action="{{ route('admin.deposit-bonus-rules.destroy', $rule) }}" method="POST" class="inline" onsubmit="return confirm('確定要刪除嗎?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="text-red-600 hover:text-red-800">刪除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="px-6 py-4 text-center {{ $textSecondary }}">尚無資料</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Create Modal --}}
|
||||
<div id="createModal" class="hidden fixed inset-0 z-50 overflow-y-auto" @keydown.escape.window="document.getElementById('createModal').classList.add('hidden')">
|
||||
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:p-0">
|
||||
<div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" onclick="document.getElementById('createModal').classList.add('hidden')"></div>
|
||||
|
||||
<div class="relative {{ $cardBg }} rounded-lg shadow-xl transform transition-all sm:max-w-lg sm:w-full mx-4">
|
||||
<div class="px-6 py-4 border-b {{ $borderColor }}">
|
||||
<h3 class="{{ $textPrimary }} text-lg font-semibold">新增儲值回饋規則</h3>
|
||||
</div>
|
||||
<form action="{{ route('admin.deposit-bonus-rules.store') }}" method="POST">
|
||||
@csrf
|
||||
<div class="px-6 py-4 space-y-4">
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">名稱</label>
|
||||
<input type="text" name="name" required class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">最低儲值金額</label>
|
||||
<input type="number" name="min_amount" value="0" step="0.01" class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">回饋類型</label>
|
||||
<select name="bonus_type" class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="fixed">固定金額</option>
|
||||
<option value="percentage">百分比</option>
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">回饋值</label>
|
||||
<input type="number" name="bonus_value" value="0" step="0.01" class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" name="is_active" value="1" checked id="is_active" class="mr-2 rounded text-indigo-600 focus:ring-indigo-500">
|
||||
<label for="is_active" class="{{ $textSecondary }} text-sm">啟用</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-6 py-4 border-t {{ $borderColor }} flex justify-end space-x-3">
|
||||
<button type="button" onclick="document.getElementById('createModal').classList.add('hidden')" class="px-4 py-2 {{ $textSecondary }} border {{ $inputBorder }} rounded-md hover:bg-gray-100 dark:hover:bg-gray-700">取消</button>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">建立</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
168
resources/views/admin/gift-definitions/index.blade.php
Normal file
168
resources/views/admin/gift-definitions/index.blade.php
Normal file
@@ -0,0 +1,168 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('content')
|
||||
@php
|
||||
$theme = request()->cookie('theme', 'dark-blue');
|
||||
$isLight = in_array($theme, ['light-blue', 'light-green']);
|
||||
$cardBg = $isLight ? 'bg-white' : 'bg-gray-800';
|
||||
$textPrimary = $isLight ? 'text-gray-900' : 'text-gray-200';
|
||||
$textSecondary = $isLight ? 'text-gray-600' : 'text-gray-400';
|
||||
$borderColor = $isLight ? 'border-gray-200' : 'border-gray-700';
|
||||
$thBg = $isLight ? 'bg-gray-50' : 'bg-gray-700';
|
||||
$inputBg = $isLight ? 'bg-white' : 'bg-gray-700';
|
||||
$inputBorder = $isLight ? 'border-gray-300' : 'border-gray-600';
|
||||
|
||||
$typeLabels = [
|
||||
'points' => '點數',
|
||||
'coupon' => '優惠券',
|
||||
'product' => '商品',
|
||||
'discount' => '折扣',
|
||||
'cash' => '現金',
|
||||
];
|
||||
|
||||
$triggerLabels = [
|
||||
'register' => '註冊',
|
||||
'birthday' => '生日',
|
||||
'annual' => '年度',
|
||||
'upgrade' => '升級',
|
||||
'manual' => '手動',
|
||||
];
|
||||
@endphp
|
||||
|
||||
{{-- Toast 通知 --}}
|
||||
@if(session('success'))
|
||||
<div x-data="{ show: false }"
|
||||
x-show="show"
|
||||
x-cloak
|
||||
x-init="setTimeout(() => { show = true; setTimeout(() => show = false, 3000) }, 50)"
|
||||
x-transition:enter="transition cubic-bezier(0.34, 1.56, 0.64, 1) duration-300"
|
||||
x-transition:enter-start="opacity-0 -translate-y-40"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-400"
|
||||
x-transition:leave-start="opacity-100 translate-y-0"
|
||||
x-transition:leave-end="opacity-0 -translate-y-40"
|
||||
class="fixed top-4 left-0 right-0 mx-auto w-max z-[100] bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="container mx-auto px-6 py-8">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="{{ $textPrimary }} text-3xl font-medium">禮品設定</h3>
|
||||
<button onclick="document.getElementById('createModal').classList.remove('hidden')" class="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700">
|
||||
新增禮品
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full {{ $cardBg }} rounded-lg overflow-hidden">
|
||||
<thead class="{{ $thBg }}">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">名稱</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">類型</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">數值</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">適用等級</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">觸發條件</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">狀態</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y {{ $borderColor }}">
|
||||
@forelse($gifts as $gift)
|
||||
<tr>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">{{ $gift->name }}</td>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">{{ $typeLabels[$gift->type] ?? $gift->type }}</td>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">{{ $gift->value }}</td>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">{{ $gift->tier?->name ?? '全部' }}</td>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">{{ $triggerLabels[$gift->trigger] ?? $gift->trigger }}</td>
|
||||
<td class="px-6 py-4">
|
||||
@if($gift->is_active)
|
||||
<span class="px-2 py-1 rounded-full bg-green-100 text-green-800 text-xs">啟用</span>
|
||||
@else
|
||||
<span class="px-2 py-1 rounded-full bg-gray-100 text-gray-800 text-xs">停用</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<form action="{{ route('admin.gift-definitions.destroy', $gift) }}" method="POST" class="inline" onsubmit="return confirm('確定要刪除嗎?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="text-red-600 hover:text-red-800">刪除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="px-6 py-4 text-center {{ $textSecondary }}">尚無資料</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Create Modal --}}
|
||||
<div id="createModal" class="hidden fixed inset-0 z-50 overflow-y-auto" @keydown.escape.window="document.getElementById('createModal').classList.add('hidden')">
|
||||
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:p-0">
|
||||
<div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" onclick="document.getElementById('createModal').classList.add('hidden')"></div>
|
||||
|
||||
<div class="relative {{ $cardBg }} rounded-lg shadow-xl transform transition-all sm:max-w-lg sm:w-full mx-4">
|
||||
<div class="px-6 py-4 border-b {{ $borderColor }}">
|
||||
<h3 class="{{ $textPrimary }} text-lg font-semibold">新增禮品</h3>
|
||||
</div>
|
||||
<form action="{{ route('admin.gift-definitions.store') }}" method="POST">
|
||||
@csrf
|
||||
<div class="px-6 py-4 space-y-4">
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">名稱</label>
|
||||
<input type="text" name="name" required class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">類型</label>
|
||||
<select name="type" class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
@foreach($typeLabels as $key => $label)
|
||||
<option value="{{ $key }}">{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">數值</label>
|
||||
<input type="number" name="value" value="0" step="0.01" class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">適用等級</label>
|
||||
<select name="tier_id" class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
<option value="">全部</option>
|
||||
@foreach($tiers as $tier)
|
||||
<option value="{{ $tier->id }}">{{ $tier->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">觸發條件</label>
|
||||
<select name="trigger" class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
@foreach($triggerLabels as $key => $label)
|
||||
<option value="{{ $key }}">{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">有效天數</label>
|
||||
<input type="number" name="validity_days" value="30" min="1" class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" name="is_active" value="1" checked id="is_active" class="mr-2 rounded text-indigo-600 focus:ring-indigo-500">
|
||||
<label for="is_active" class="{{ $textSecondary }} text-sm">啟用</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-6 py-4 border-t {{ $borderColor }} flex justify-end space-x-3">
|
||||
<button type="button" onclick="document.getElementById('createModal').classList.add('hidden')" class="px-4 py-2 {{ $textSecondary }} border {{ $inputBorder }} rounded-md hover:bg-gray-100 dark:hover:bg-gray-700">取消</button>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">建立</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
93
resources/views/admin/members/index.blade.php
Normal file
93
resources/views/admin/members/index.blade.php
Normal file
@@ -0,0 +1,93 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('content')
|
||||
@php
|
||||
$theme = request()->cookie('theme', 'dark-blue');
|
||||
$isLight = in_array($theme, ['light-blue', 'light-green']);
|
||||
$cardBg = $isLight ? 'bg-white' : 'bg-gray-800';
|
||||
$textPrimary = $isLight ? 'text-gray-900' : 'text-gray-200';
|
||||
$textSecondary = $isLight ? 'text-gray-600' : 'text-gray-400';
|
||||
$borderColor = $isLight ? 'border-gray-200' : 'border-gray-700';
|
||||
$thBg = $isLight ? 'bg-gray-50' : 'bg-gray-700';
|
||||
@endphp
|
||||
<div class="container mx-auto px-6 py-8">
|
||||
<h3 class="{{ $textPrimary }} text-3xl font-medium">會員列表</h3>
|
||||
|
||||
<div class="mt-8">
|
||||
{{-- 搜尋與篩選 (預留空間) --}}
|
||||
|
||||
<div class="flex flex-col mt-4">
|
||||
<div class="-my-2 py-2 overflow-x-auto sm:-mx-6 sm:px-6 lg:-mx-8 lg:px-8">
|
||||
<div class="align-middle inline-block min-w-full shadow overflow-hidden sm:rounded-lg border-b {{ $borderColor }}">
|
||||
<table class="min-w-full">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 border-b {{ $borderColor }} {{ $thBg }} text-left text-xs leading-4 font-medium {{ $textSecondary }} uppercase tracking-wider">
|
||||
UUID
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b {{ $borderColor }} {{ $thBg }} text-left text-xs leading-4 font-medium {{ $textSecondary }} uppercase tracking-wider">
|
||||
姓名
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b {{ $borderColor }} {{ $thBg }} text-left text-xs leading-4 font-medium {{ $textSecondary }} uppercase tracking-wider">
|
||||
Email
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b {{ $borderColor }} {{ $thBg }} text-left text-xs leading-4 font-medium {{ $textSecondary }} uppercase tracking-wider">
|
||||
手機
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b {{ $borderColor }} {{ $thBg }} text-left text-xs leading-4 font-medium {{ $textSecondary }} uppercase tracking-wider">
|
||||
狀態
|
||||
</th>
|
||||
<th class="px-6 py-3 border-b {{ $borderColor }} {{ $thBg }} text-left text-xs leading-4 font-medium {{ $textSecondary }} uppercase tracking-wider">
|
||||
註冊時間
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="{{ $cardBg }}">
|
||||
@forelse ($members as $member)
|
||||
<tr>
|
||||
<td class="px-6 py-4 whitespace-no-wrap border-b {{ $borderColor }}">
|
||||
<div class="text-sm leading-5 font-medium {{ $textSecondary }}">{{ $member->uuid }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap border-b {{ $borderColor }}">
|
||||
<div class="text-sm leading-5 font-bold {{ $textPrimary }}">{{ $member->name }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap border-b {{ $borderColor }}">
|
||||
<div class="text-sm leading-5 {{ $textPrimary }}">{{ $member->email ?? '-' }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap border-b {{ $borderColor }}">
|
||||
<div class="text-sm leading-5 {{ $textPrimary }}">{{ $member->phone ?? '-' }}</div>
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap border-b {{ $borderColor }}">
|
||||
@if($member->is_active)
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
|
||||
啟用
|
||||
</span>
|
||||
@else
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-red-100 text-red-800">
|
||||
停用
|
||||
</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-no-wrap border-b {{ $borderColor }} text-sm leading-5 {{ $textSecondary }}">
|
||||
{{ $member->created_at->format('Y-m-d H:i') }}
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="px-6 py-4 whitespace-no-wrap border-b {{ $borderColor }} text-center {{ $textSecondary }}">
|
||||
尚無會員資料
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
{{ $members->links() }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
129
resources/views/admin/membership-tiers/index.blade.php
Normal file
129
resources/views/admin/membership-tiers/index.blade.php
Normal file
@@ -0,0 +1,129 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('content')
|
||||
@php
|
||||
$theme = request()->cookie('theme', 'dark-blue');
|
||||
$isLight = in_array($theme, ['light-blue', 'light-green']);
|
||||
$cardBg = $isLight ? 'bg-white' : 'bg-gray-800';
|
||||
$textPrimary = $isLight ? 'text-gray-900' : 'text-gray-200';
|
||||
$textSecondary = $isLight ? 'text-gray-600' : 'text-gray-400';
|
||||
$borderColor = $isLight ? 'border-gray-200' : 'border-gray-700';
|
||||
$thBg = $isLight ? 'bg-gray-50' : 'bg-gray-700';
|
||||
$inputBg = $isLight ? 'bg-white' : 'bg-gray-700';
|
||||
$inputBorder = $isLight ? 'border-gray-300' : 'border-gray-600';
|
||||
@endphp
|
||||
|
||||
{{-- Toast 通知 --}}
|
||||
@if(session('success'))
|
||||
<div x-data="{ show: false }"
|
||||
x-show="show"
|
||||
x-cloak
|
||||
x-init="setTimeout(() => { show = true; setTimeout(() => show = false, 3000) }, 50)"
|
||||
x-transition:enter="transition cubic-bezier(0.34, 1.56, 0.64, 1) duration-300"
|
||||
x-transition:enter-start="opacity-0 -translate-y-40"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-400"
|
||||
x-transition:leave-start="opacity-100 translate-y-0"
|
||||
x-transition:leave-end="opacity-0 -translate-y-40"
|
||||
class="fixed top-4 left-0 right-0 mx-auto w-max z-[100] bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="container mx-auto px-6 py-8">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="{{ $textPrimary }} text-3xl font-medium">會員等級設定</h3>
|
||||
<button onclick="document.getElementById('createModal').classList.remove('hidden')" class="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700">
|
||||
新增等級
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full {{ $cardBg }} rounded-lg overflow-hidden">
|
||||
<thead class="{{ $thBg }}">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">名稱</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">年費</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">折扣</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">點數倍率</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">預設</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y {{ $borderColor }}">
|
||||
@forelse($tiers as $tier)
|
||||
<tr>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">{{ $tier->name }}</td>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">{{ $tier->annual_fee == 0 ? '免費' : '$'.number_format($tier->annual_fee) }}</td>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">{{ $tier->discount_rate * 100 }}%</td>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">{{ $tier->point_multiplier }}x</td>
|
||||
<td class="px-6 py-4">
|
||||
@if($tier->is_default)
|
||||
<span class="px-2 py-1 rounded-full bg-green-100 text-green-800 text-xs">預設</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<form action="{{ route('admin.membership-tiers.destroy', $tier) }}" method="POST" class="inline" onsubmit="return confirm('確定要刪除嗎?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="text-red-600 hover:text-red-800">刪除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="6" class="px-6 py-4 text-center {{ $textSecondary }}">尚無資料</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Create Modal --}}
|
||||
<div id="createModal" class="hidden fixed inset-0 z-50 overflow-y-auto" x-data @keydown.escape.window="document.getElementById('createModal').classList.add('hidden')">
|
||||
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:p-0">
|
||||
{{-- 背景遮罩 --}}
|
||||
<div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" onclick="document.getElementById('createModal').classList.add('hidden')"></div>
|
||||
|
||||
{{-- Modal 內容 --}}
|
||||
<div class="relative {{ $cardBg }} rounded-lg shadow-xl transform transition-all sm:max-w-lg sm:w-full mx-4">
|
||||
<div class="px-6 py-4 border-b {{ $borderColor }}">
|
||||
<h3 class="{{ $textPrimary }} text-lg font-semibold">新增會員等級</h3>
|
||||
</div>
|
||||
<form action="{{ route('admin.membership-tiers.store') }}" method="POST">
|
||||
@csrf
|
||||
<div class="px-6 py-4 space-y-4">
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">名稱</label>
|
||||
<input type="text" name="name" required class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500 focus:border-transparent">
|
||||
</div>
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">年費</label>
|
||||
<input type="number" name="annual_fee" value="0" step="0.01" class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">折扣比例 (0.95 = 95折)</label>
|
||||
<input type="number" name="discount_rate" value="1.00" step="0.01" min="0" max="1" class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">點數倍率</label>
|
||||
<input type="number" name="point_multiplier" value="1.00" step="0.01" min="0" class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" name="is_default" value="1" id="is_default" class="mr-2 rounded text-indigo-600 focus:ring-indigo-500">
|
||||
<label for="is_default" class="{{ $textSecondary }} text-sm">設為預設等級</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-6 py-4 border-t {{ $borderColor }} flex justify-end space-x-3">
|
||||
<button type="button" onclick="document.getElementById('createModal').classList.add('hidden')" class="px-4 py-2 {{ $textSecondary }} border {{ $inputBorder }} rounded-md hover:bg-gray-100 dark:hover:bg-gray-700">取消</button>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">建立</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
147
resources/views/admin/point-rules/index.blade.php
Normal file
147
resources/views/admin/point-rules/index.blade.php
Normal file
@@ -0,0 +1,147 @@
|
||||
@extends('layouts.admin')
|
||||
|
||||
@section('content')
|
||||
@php
|
||||
$theme = request()->cookie('theme', 'dark-blue');
|
||||
$isLight = in_array($theme, ['light-blue', 'light-green']);
|
||||
$cardBg = $isLight ? 'bg-white' : 'bg-gray-800';
|
||||
$textPrimary = $isLight ? 'text-gray-900' : 'text-gray-200';
|
||||
$textSecondary = $isLight ? 'text-gray-600' : 'text-gray-400';
|
||||
$borderColor = $isLight ? 'border-gray-200' : 'border-gray-700';
|
||||
$thBg = $isLight ? 'bg-gray-50' : 'bg-gray-700';
|
||||
$inputBg = $isLight ? 'bg-white' : 'bg-gray-700';
|
||||
$inputBorder = $isLight ? 'border-gray-300' : 'border-gray-600';
|
||||
|
||||
$triggerLabels = [
|
||||
'purchase' => '消費',
|
||||
'deposit' => '儲值',
|
||||
'register' => '註冊',
|
||||
'birthday' => '生日',
|
||||
'referral' => '推薦',
|
||||
];
|
||||
@endphp
|
||||
|
||||
{{-- Toast 通知 --}}
|
||||
@if(session('success'))
|
||||
<div x-data="{ show: false }"
|
||||
x-show="show"
|
||||
x-cloak
|
||||
x-init="setTimeout(() => { show = true; setTimeout(() => show = false, 3000) }, 50)"
|
||||
x-transition:enter="transition cubic-bezier(0.34, 1.56, 0.64, 1) duration-300"
|
||||
x-transition:enter-start="opacity-0 -translate-y-40"
|
||||
x-transition:enter-end="opacity-100 translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-400"
|
||||
x-transition:leave-start="opacity-100 translate-y-0"
|
||||
x-transition:leave-end="opacity-0 -translate-y-40"
|
||||
class="fixed top-4 left-0 right-0 mx-auto w-max z-[100] bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
{{ session('success') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="container mx-auto px-6 py-8">
|
||||
<div class="flex justify-between items-center mb-6">
|
||||
<h3 class="{{ $textPrimary }} text-3xl font-medium">點數規則設定</h3>
|
||||
<button onclick="document.getElementById('createModal').classList.remove('hidden')" class="bg-indigo-600 text-white px-4 py-2 rounded-md hover:bg-indigo-700">
|
||||
新增規則
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full {{ $cardBg }} rounded-lg overflow-hidden">
|
||||
<thead class="{{ $thBg }}">
|
||||
<tr>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">名稱</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">觸發條件</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">每單位點數</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">單位金額</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">有效天數</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">狀態</th>
|
||||
<th class="px-6 py-3 text-left text-xs font-medium {{ $textSecondary }} uppercase">操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y {{ $borderColor }}">
|
||||
@forelse($rules as $rule)
|
||||
<tr>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">{{ $rule->name }}</td>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">{{ $triggerLabels[$rule->trigger] ?? $rule->trigger }}</td>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">{{ $rule->points_per_unit }} 點</td>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">${{ number_format($rule->unit_amount) }}</td>
|
||||
<td class="px-6 py-4 {{ $textPrimary }}">{{ $rule->validity_days }} 天</td>
|
||||
<td class="px-6 py-4">
|
||||
@if($rule->is_active)
|
||||
<span class="px-2 py-1 rounded-full bg-green-100 text-green-800 text-xs">啟用</span>
|
||||
@else
|
||||
<span class="px-2 py-1 rounded-full bg-gray-100 text-gray-800 text-xs">停用</span>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<form action="{{ route('admin.point-rules.destroy', $rule) }}" method="POST" class="inline" onsubmit="return confirm('確定要刪除嗎?')">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="text-red-600 hover:text-red-800">刪除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="7" class="px-6 py-4 text-center {{ $textSecondary }}">尚無資料</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Create Modal --}}
|
||||
<div id="createModal" class="hidden fixed inset-0 z-50 overflow-y-auto" @keydown.escape.window="document.getElementById('createModal').classList.add('hidden')">
|
||||
<div class="flex items-center justify-center min-h-screen px-4 pt-4 pb-20 text-center sm:p-0">
|
||||
<div class="fixed inset-0 transition-opacity bg-gray-500 bg-opacity-75" onclick="document.getElementById('createModal').classList.add('hidden')"></div>
|
||||
|
||||
<div class="relative {{ $cardBg }} rounded-lg shadow-xl transform transition-all sm:max-w-lg sm:w-full mx-4">
|
||||
<div class="px-6 py-4 border-b {{ $borderColor }}">
|
||||
<h3 class="{{ $textPrimary }} text-lg font-semibold">新增點數規則</h3>
|
||||
</div>
|
||||
<form action="{{ route('admin.point-rules.store') }}" method="POST">
|
||||
@csrf
|
||||
<div class="px-6 py-4 space-y-4">
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">名稱</label>
|
||||
<input type="text" name="name" required class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">觸發條件</label>
|
||||
<select name="trigger" class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
@foreach($triggerLabels as $key => $label)
|
||||
<option value="{{ $key }}">{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">每單位獲得點數</label>
|
||||
<input type="number" name="points_per_unit" value="1" min="1" class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">單位金額</label>
|
||||
<input type="number" name="unit_amount" value="100" step="0.01" class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div>
|
||||
<label class="{{ $textSecondary }} text-sm block mb-1">有效天數</label>
|
||||
<input type="number" name="validity_days" value="365" min="1" class="w-full px-3 py-2 {{ $inputBg }} {{ $inputBorder }} border rounded-md {{ $textPrimary }} focus:ring-2 focus:ring-indigo-500">
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<input type="checkbox" name="is_active" value="1" checked id="is_active" class="mr-2 rounded text-indigo-600 focus:ring-indigo-500">
|
||||
<label for="is_active" class="{{ $textSecondary }} text-sm">啟用</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-6 py-4 border-t {{ $borderColor }} flex justify-end space-x-3">
|
||||
<button type="button" onclick="document.getElementById('createModal').classList.add('hidden')" class="px-4 py-2 {{ $textSecondary }} border {{ $inputBorder }} rounded-md hover:bg-gray-100 dark:hover:bg-gray-700">取消</button>
|
||||
<button type="submit" class="px-4 py-2 bg-indigo-600 text-white rounded-md hover:bg-indigo-700">建立</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user