chore: init saasshop repo + sql migrations runner + gitee go
This commit is contained in:
11
resources/css/app.css
Normal file
11
resources/css/app.css
Normal file
@@ -0,0 +1,11 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
|
||||
@source '../../storage/framework/views/*.php';
|
||||
@source '../**/*.blade.php';
|
||||
@source '../**/*.js';
|
||||
|
||||
@theme {
|
||||
--font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji',
|
||||
'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
}
|
||||
1
resources/js/app.js
Normal file
1
resources/js/app.js
Normal file
@@ -0,0 +1 @@
|
||||
import './bootstrap';
|
||||
4
resources/js/bootstrap.js
vendored
Normal file
4
resources/js/bootstrap.js
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
import axios from 'axios';
|
||||
window.axios = axios;
|
||||
|
||||
window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
|
||||
25
resources/views/admin/auth/login.blade.php
Normal file
25
resources/views/admin/auth/login.blade.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>总台管理登录 - SaaSShop</title>
|
||||
<link rel="stylesheet" href="/css/admin-base.css">
|
||||
<link rel="stylesheet" href="/css/admin.css">
|
||||
</head>
|
||||
<body class="login-page">
|
||||
<div class="card-login">
|
||||
<h1>总台管理登录</h1>
|
||||
<p class="muted">当前入口面向平台运营方。演示账号:platform.admin@demo.local / Platform@123456</p>
|
||||
@if ($errors->any())
|
||||
<div class="error">{{ $errors->first() }}</div>
|
||||
@endif
|
||||
<form method="post" action="/admin/login">
|
||||
@csrf
|
||||
<input type="email" name="email" placeholder="邮箱" value="{{ old('email', 'platform.admin@demo.local') }}">
|
||||
<input type="password" name="password" placeholder="密码" value="Platform@123456">
|
||||
<button type="submit">登录总台管理</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
32
resources/views/admin/dashboard.blade.php
Normal file
32
resources/views/admin/dashboard.blade.php
Normal file
@@ -0,0 +1,32 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '总台管理仪表盘')
|
||||
@section('page_title', '总台管理仪表盘')
|
||||
|
||||
@section('content')
|
||||
<div class="two-col mb-20">
|
||||
<div class="card">
|
||||
<p>欢迎回来,{{ $adminName }}。当前入口已明确为 <strong>总台管理</strong>,用于平台运营方统一查看站点、渠道、全局配置和平台级业务数据。</p>
|
||||
<p class="muted">当前平台上下文已通过封装统一解析,不再依赖控制器里零散读取 session。</p>
|
||||
<p class="muted mb-0">仪表盘统计已接入缓存:{{ $cacheMeta['store'] }} / TTL {{ $cacheMeta['ttl'] }}。</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3 class="mt-0">平台定位</h3>
|
||||
<table>
|
||||
<tr><th>后台角色</th><td>{{ $platformOverview['system_role'] }}</td></tr>
|
||||
<tr><th>当前视角</th><td>{{ $platformOverview['current_scope'] }}</td></tr>
|
||||
<tr><th>商家模式</th><td>{{ $platformOverview['merchant_mode'] }}</td></tr>
|
||||
<tr><th>渠道数</th><td>{{ $platformOverview['channel_count'] }}</td></tr>
|
||||
<tr><th>活跃商家</th><td>{{ $platformOverview['active_merchants'] }}</td></tr>
|
||||
<tr><th>待处理订单</th><td>{{ $platformOverview['pending_orders'] }}</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="card"><div class="muted">站点</div><div class="num">{{ $stats['merchants'] }}</div></div>
|
||||
<div class="card"><div class="muted">管理员</div><div class="num">{{ $stats['admins'] }}</div></div>
|
||||
<div class="card"><div class="muted">用户</div><div class="num">{{ $stats['users'] }}</div></div>
|
||||
<div class="card"><div class="muted">商品</div><div class="num">{{ $stats['products'] }}</div></div>
|
||||
<div class="card"><div class="muted">订单</div><div class="num">{{ $stats['orders'] }}</div></div>
|
||||
</div>
|
||||
@endsection
|
||||
68
resources/views/admin/layouts/app.blade.php
Normal file
68
resources/views/admin/layouts/app.blade.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>@yield('title', 'SaaSShop 总台管理')</title>
|
||||
<link rel="stylesheet" href="/css/admin-base.css">
|
||||
<link rel="stylesheet" href="/css/admin.css">
|
||||
<link rel="stylesheet" href="/css/admin-components.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout">
|
||||
<aside class="sidebar">
|
||||
<h2 class="sidebar-title">SaaSShop</h2>
|
||||
<div class="muted">总台管理 / Platform Ops</div>
|
||||
|
||||
<div class="group-title">总览</div>
|
||||
<a href="/admin">总台仪表盘</a>
|
||||
|
||||
<div class="group-title">平台运营</div>
|
||||
<a href="/admin/merchants">站点管理</a>
|
||||
<a href="/admin/plans">套餐管理</a>
|
||||
<a href="/admin/orders">订单监控</a>
|
||||
<a href="/admin/platform-orders">平台订单</a>
|
||||
<a href="/admin/site-subscriptions">订阅管理</a>
|
||||
<a href="/admin/products">商品巡检</a>
|
||||
<a href="/admin/product-categories">商品分类</a>
|
||||
|
||||
<div class="group-title">平台配置</div>
|
||||
<a href="/admin/settings/system">系统配置</a>
|
||||
<a href="/admin/settings/channels">渠道配置</a>
|
||||
|
||||
<div class="group-title">外部入口</div>
|
||||
<a href="/">返回前台</a>
|
||||
<form method="post" action="/admin/logout" class="logout-form">@csrf <button type="submit">退出登录</button></form>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<div class="top">
|
||||
<div>
|
||||
<h1 class="page-title">@yield('page_title', '总台管理')</h1>
|
||||
<div class="muted">当前登录:{{ session('admin_name') }}({{ session('admin_email') }}) / 角色:{{ session('admin_role', 'unknown') }}</div>
|
||||
<div class="badge">登录范围:{{ session('admin_scope', 'platform') === 'platform' ? '总台管理员' : '商家管理员' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
@if(session('success'))
|
||||
<div class="flash">{{ session('success') }}</div>
|
||||
@endif
|
||||
@if(session('warning'))
|
||||
<div class="warning">{{ session('warning') }}</div>
|
||||
@endif
|
||||
@if(session('error'))
|
||||
<div class="error-box">{{ session('error') }}</div>
|
||||
@endif
|
||||
@if($errors->any())
|
||||
<div class="error-box">
|
||||
<strong>提交失败:</strong>
|
||||
<ul class="list-indent">
|
||||
@foreach($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
@yield('content')
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
50
resources/views/admin/merchants/index.blade.php
Normal file
50
resources/views/admin/merchants/index.blade.php
Normal file
@@ -0,0 +1,50 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '站点管理')
|
||||
@section('page_title', '站点管理')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<p class="muted muted-tight">这里是总台视角的站点管理入口,用于开通、查看和维护 SaaS 站点主体。</p>
|
||||
<p class="muted">当前站点列表已接入缓存:{{ $cacheMeta['store'] }} / TTL {{ $cacheMeta['ttl'] }}。</p>
|
||||
<h3>新增站点</h3>
|
||||
<form method="post" action="/admin/merchants">
|
||||
@csrf
|
||||
<div class="grid-3">
|
||||
<input name="name" placeholder="站点名称" value="{{ old('name') }}">
|
||||
<input name="slug" placeholder="站点标识 slug" value="{{ old('slug') }}">
|
||||
<input name="plan" placeholder="套餐,如 pro/basic" value="{{ old('plan', 'basic') }}">
|
||||
<input name="status" placeholder="状态" value="{{ old('status', 'active') }}">
|
||||
<input name="contact_name" placeholder="联系人" value="{{ old('contact_name') }}">
|
||||
<input name="contact_phone" placeholder="联系电话" value="{{ old('contact_phone') }}">
|
||||
</div>
|
||||
<div class="mt-12"><input name="contact_email" placeholder="联系邮箱" class="w-full" value="{{ old('contact_email') }}"></div>
|
||||
<div class="mt-12"><button type="submit">创建站点</button></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>站点列表</h3>
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>站点名称</th><th>Slug</th><th>套餐</th><th>状态</th><th>联系人</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
@foreach($merchants as $merchant)
|
||||
<tr>
|
||||
<td>{{ $merchant->id }}</td>
|
||||
<td>{{ $merchant->name }}</td>
|
||||
<td>{{ $merchant->slug }}</td>
|
||||
<td>{{ $merchant->plan }}</td>
|
||||
<td>{{ $merchant->status }}</td>
|
||||
<td>{{ $merchant->contact_name }} / {{ $merchant->contact_phone }}</td>
|
||||
<td>
|
||||
<a href="/site-admin/login" target="_blank" rel="noopener">进入站点后台</a>
|
||||
<div class="muted muted-xs">当前阶段请使用该站点管理员账号登录</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pagination-wrap">{{ $merchants->links() }}</div>
|
||||
@endsection
|
||||
171
resources/views/admin/orders/index.blade.php
Normal file
171
resources/views/admin/orders/index.blade.php
Normal file
@@ -0,0 +1,171 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '订单监控')
|
||||
@section('page_title', '订单监控')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<p class="muted muted-tight">当前页面用于总台视角查看全局订单状态,后续商家后台将承接商家侧订单处理动作。</p>
|
||||
<p class="muted">当前订单列表已接入缓存:{{ $cacheMeta['store'] }} / TTL {{ $cacheMeta['ttl'] }}。</p>
|
||||
@php
|
||||
$exportQuery = http_build_query(array_filter($filters, fn ($value) => $value !== null && $value !== ''));
|
||||
@endphp
|
||||
<div class="mb-12">
|
||||
<a href="{{ '/admin/orders/export' . ($exportQuery ? ('?' . $exportQuery) : '') }}">导出当前筛选结果 CSV</a>
|
||||
</div>
|
||||
<h3>筛选条件</h3>
|
||||
@if(!empty($filters['validation_errors']))
|
||||
<div class="filter-error">
|
||||
<strong>筛选条件有误:</strong>
|
||||
<ul class="list-indent">
|
||||
@foreach($filters['validation_errors'] as $validationError)
|
||||
<li>{{ $validationError }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
<form method="get" action="/admin/orders">
|
||||
<div class="grid-5">
|
||||
<select name="status"><option value="">全部状态</option>@foreach($filterOptions['statuses'] as $status)<option value="{{ $statusLabels[$status] ?? $status }}" @selected($filters['status'] === $status)>{{ $statusLabels[$status] ?? $status }}</option>@endforeach</select>
|
||||
<select name="payment_status"><option value="">全部支付状态</option>@foreach($filterOptions['paymentStatuses'] as $paymentStatus)<option value="{{ $paymentStatusLabels[$paymentStatus] ?? $paymentStatus }}" @selected($filters['payment_status'] === $paymentStatus)>{{ $paymentStatusLabels[$paymentStatus] ?? $paymentStatus }}</option>@endforeach</select>
|
||||
<select name="platform"><option value="">全部平台</option>@foreach($filterOptions['platforms'] as $platform)<option value="{{ $platformLabels[$platform] ?? $platform }}" @selected($filters['platform'] === $platform)>{{ $platformLabels[$platform] ?? $platform }}</option>@endforeach</select>
|
||||
<select name="device_type"><option value="">全部设备类型</option>@foreach($filterOptions['deviceTypes'] as $deviceType)<option value="{{ $deviceType }}" @selected($filters['device_type'] === $deviceType)>{{ $deviceTypeLabels[$deviceType] ?? $deviceType }}</option>@endforeach</select>
|
||||
<select name="payment_channel"><option value="">全部支付渠道</option>@foreach($filterOptions['paymentChannels'] as $paymentChannel)<option value="{{ $paymentChannel }}" @selected($filters['payment_channel'] === $paymentChannel)>{{ $paymentChannelLabels[$paymentChannel] ?? $paymentChannel }}</option>@endforeach</select>
|
||||
<input name="keyword" placeholder="订单号 / 买家 / 手机 / 邮箱" value="{{ $filters['keyword'] }}">
|
||||
<select name="time_range">@foreach($filterOptions['timeRanges'] as $timeRangeValue => $timeRangeLabel)<option value="{{ $timeRangeValue }}" @selected(($filters['time_range'] ?? 'all') === $timeRangeValue)>{{ $timeRangeLabel }}</option>@endforeach</select>
|
||||
<input type="date" name="start_date" value="{{ $filters['start_date'] }}">
|
||||
<input type="date" name="end_date" value="{{ $filters['end_date'] }}">
|
||||
<input type="number" step="0.01" min="0" name="min_pay_amount" placeholder="最低实付金额" value="{{ $filters['min_pay_amount'] }}">
|
||||
<input type="number" step="0.01" min="0" name="max_pay_amount" placeholder="最高实付金额" value="{{ $filters['max_pay_amount'] }}">
|
||||
<select name="sort">@foreach($filterOptions['sortOptions'] as $sortValue => $sortLabel)<option value="{{ $sortValue }}" @selected(($filters['sort'] ?? 'latest') === $sortValue)>{{ $sortLabel }}</option>@endforeach</select>
|
||||
<div class="actions"><button type="submit">筛选</button><a href="/admin/orders">重置</a></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card section-dark mb-20">
|
||||
<h3 class="mt-0">当前筛选摘要</h3>
|
||||
<div class="grid-3">
|
||||
@foreach($activeFilterSummary as $summaryLabel => $summaryValue)
|
||||
<div class="summary-box"><div class="muted">{{ $summaryLabel }}</div><strong class="num-md">{{ $summaryValue }}</strong></div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-20">
|
||||
<h3 class="mt-0">订单汇总</h3>
|
||||
<div class="grid-5">
|
||||
<div class="stat-box-light"><div class="muted">订单总数</div><strong class="num-md">{{ $summaryStats['total_orders'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box-light"><div class="muted">实付总额</div><strong class="num-md">¥{{ number_format($summaryStats['total_pay_amount'] ?? 0, 2) }}</strong></div>
|
||||
<div class="stat-box-light"><div class="muted">平均客单价</div><strong class="num-md">¥{{ number_format($summaryStats['average_order_amount'] ?? 0, 2) }}</strong></div>
|
||||
<div class="stat-box-light"><div class="muted">待支付金额</div><strong class="num-md">¥{{ number_format($summaryStats['unpaid_pay_amount'] ?? 0, 2) }}</strong></div>
|
||||
<div class="stat-box-light"><div class="muted">已支付金额</div><strong class="num-md">¥{{ number_format($summaryStats['paid_pay_amount'] ?? 0, 2) }}</strong></div>
|
||||
<div class="stat-box-light"><div class="muted">已支付订单数</div><strong class="num-md">{{ $summaryStats['paid_orders'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box-light"><div class="muted">支付率</div><strong class="num-md">{{ number_format($summaryStats['payment_rate'] ?? 0, 2) }}%</strong></div>
|
||||
<div class="stat-box-light"><div class="muted">退款订单数</div><strong class="num-md">{{ $summaryStats['refunded_orders'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box-light"><div class="muted">退款率</div><strong class="num-md">{{ number_format($summaryStats['refund_rate'] ?? 0, 2) }}%</strong></div>
|
||||
<div class="stat-box-light"><div class="muted">待发货订单数</div><strong class="num-md">{{ $summaryStats['pending_shipment_orders'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box-light"><div class="muted">完成率</div><strong class="num-md">{{ number_format($summaryStats['completion_rate'] ?? 0, 2) }}%</strong></div>
|
||||
<div class="stat-box-light"><div class="muted">支付失败订单</div><strong class="num-md">{{ $summaryStats['failed_payment_orders'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box-light"><div class="muted">已取消订单</div><strong class="num-md">{{ $summaryStats['cancelled_orders'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box-light"><div class="muted">取消率</div><strong class="num-md">{{ number_format($summaryStats['cancellation_rate'] ?? 0, 2) }}%</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-20">
|
||||
<h3 class="mt-0">时间趋势指标</h3>
|
||||
<div class="grid-5">
|
||||
<div class="stat-box-light"><div class="muted">今日订单数</div><strong class="num-md">{{ $trendStats['today_orders'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box-light"><div class="muted">今日实付金额</div><strong class="num-md">¥{{ number_format($trendStats['today_pay_amount'] ?? 0, 2) }}</strong></div>
|
||||
<div class="stat-box-light"><div class="muted">近7天订单数</div><strong class="num-md">{{ $trendStats['last_7_days_orders'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box-light"><div class="muted">近7天实付金额</div><strong class="num-md">¥{{ number_format($trendStats['last_7_days_pay_amount'] ?? 0, 2) }}</strong></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card section-dark card-spaced">
|
||||
<h3 class="mt-0">运营关注项</h3>
|
||||
<div class="grid-3">
|
||||
<div class="focus-box">
|
||||
<div class="muted">订单盘面</div>
|
||||
<strong class="text-md">{{ $operationsFocus['headline'] ?? '当前总台订单运营信息已就绪。' }}</strong>
|
||||
<div class="muted mt-8">建议动作</div>
|
||||
<div class="inline-links mt-6">
|
||||
@foreach(($operationsFocus['actions'] ?? []) as $action)
|
||||
<a href="{{ $action['url'] }}">{{ $action['label'] }}</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="focus-box">
|
||||
<div class="muted">当前信号</div>
|
||||
<div class="grid-3 mt-8">
|
||||
@foreach(($operationsFocus['signals'] ?? []) as $label => $value)
|
||||
<div class="summary-box"><div class="muted">{{ $label }}</div><strong class="num-sm">{{ $value }}</strong></div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="focus-box">
|
||||
<div class="muted">工作台导航</div>
|
||||
<div class="inline-links mt-8">
|
||||
@foreach(($operationsFocus['workbench'] ?? []) as $label => $url)
|
||||
<a href="{{ $url }}">{{ $label }}</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-20">
|
||||
<h3 class="mt-0">状态统计</h3>
|
||||
@php
|
||||
$baseQuery = [
|
||||
'payment_status' => $filters['payment_status'] ?: null,
|
||||
'platform' => $filters['platform'] ?: null,
|
||||
'device_type' => $filters['device_type'] ?: null,
|
||||
'payment_channel' => $filters['payment_channel'] ?: null,
|
||||
'keyword' => $filters['keyword'] ?: null,
|
||||
'start_date' => $filters['start_date'] ?: null,
|
||||
'end_date' => $filters['end_date'] ?: null,
|
||||
'min_pay_amount' => $filters['min_pay_amount'] ?: null,
|
||||
'max_pay_amount' => $filters['max_pay_amount'] ?: null,
|
||||
'time_range' => ($filters['time_range'] ?? 'all') !== 'all' ? $filters['time_range'] : null,
|
||||
'sort' => ($filters['sort'] ?? 'latest') !== 'latest' ? $filters['sort'] : null,
|
||||
];
|
||||
@endphp
|
||||
<div class="grid-6">
|
||||
<a href="{{ '/admin/orders?' . http_build_query(array_filter($baseQuery, fn ($value) => $value !== null && $value !== '')) }}" class="status-link-light {{ $filters['status'] === '' ? 'is-active-light' : '' }}"><div class="muted">全部</div><strong class="num-md">{{ $statusStats['all'] ?? 0 }}</strong></a>
|
||||
@foreach($filterOptions['statuses'] as $status)
|
||||
<a href="{{ '/admin/orders?' . http_build_query(array_filter(array_merge($baseQuery, ['status' => $status]), fn ($value) => $value !== null && $value !== '')) }}" class="status-link-light {{ $filters['status'] === $status ? 'is-active-light' : '' }}"><div class="muted">{{ $statusLabels[$status] ?? $status }}</div><strong class="num-md">{{ $statusStats[$status] ?? 0 }}</strong></a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>订单列表</h3>
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>商家</th><th>订单号</th><th>平台</th><th>买家</th><th>支付</th><th>金额</th><th>创建时间</th><th>支付时间</th><th>发货时间</th><th>完成时间</th><th>状态</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($orders as $order)
|
||||
<tr>
|
||||
<td>{{ $order->id }}</td>
|
||||
<td>{{ $order->merchant?->name ?? ('商家#'.$order->merchant_id) }}</td>
|
||||
<td><a href="/admin/orders/{{ $order->id }}">{{ $order->order_no }}</a></td>
|
||||
<td>{{ $platformLabels[$order->platform] ?? $order->platform }}</td>
|
||||
<td>{{ $order->buyer_name }}</td>
|
||||
<td><div>{{ $paymentChannelLabels[$order->payment_channel] ?? $order->payment_channel }}</div><div class="muted">{{ $paymentStatusLabels[$order->payment_status] ?? $order->payment_status }}</div></td>
|
||||
<td>¥{{ number_format($order->pay_amount, 2) }}</td>
|
||||
<td>{{ $order->created_at?->format('Y-m-d H:i') }}</td>
|
||||
<td>{{ $order->paid_at?->format('Y-m-d H:i') ?? '-' }}</td>
|
||||
<td>{{ $order->shipped_at?->format('Y-m-d H:i') ?? '-' }}</td>
|
||||
<td>{{ $order->completed_at?->format('Y-m-d H:i') ?? '-' }}</td>
|
||||
<td>{{ $statusLabels[$order->status] ?? $order->status }}</td>
|
||||
<td><form class="inline" method="post" action="/admin/orders/{{ $order->id }}/status">@csrf <select name="status"><option value="pending" @selected($order->status==='pending')>待处理</option><option value="paid" @selected($order->status==='paid')>已支付</option><option value="shipped" @selected($order->status==='shipped')>已发货</option><option value="completed" @selected($order->status==='completed')>已完成</option><option value="cancelled" @selected($order->status==='cancelled')>已取消</option></select><button type="submit">更新</button></form></td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="13" class="muted text-center">暂无订单</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pagination-wrap">{{ $orders->links() }}</div>
|
||||
@endsection
|
||||
50
resources/views/admin/orders/show.blade.php
Normal file
50
resources/views/admin/orders/show.blade.php
Normal file
@@ -0,0 +1,50 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '平台订单详情')
|
||||
@section('page_title', '平台订单详情')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<h3>订单 {{ $order->order_no }}</h3>
|
||||
<table>
|
||||
<tr><th>ID</th><td>{{ $order->id }}</td></tr>
|
||||
<tr><th>商家</th><td>{{ $order->merchant?->name ?? ('商家#'.$order->merchant_id) }}</td></tr>
|
||||
<tr><th>平台</th><td>{{ $order->platform }}</td></tr>
|
||||
<tr><th>订单状态</th><td>{{ $order->status }}</td></tr>
|
||||
<tr><th>支付渠道</th><td>{{ $order->payment_channel }}</td></tr>
|
||||
<tr><th>支付状态</th><td>{{ $order->payment_status }}</td></tr>
|
||||
<tr><th>买家</th><td>{{ $order->buyer_name }}</td></tr>
|
||||
<tr><th>手机</th><td>{{ $order->buyer_phone }}</td></tr>
|
||||
<tr><th>邮箱</th><td>{{ $order->buyer_email }}</td></tr>
|
||||
<tr><th>商品金额</th><td>¥{{ number_format($order->product_amount, 2) }}</td></tr>
|
||||
<tr><th>优惠金额</th><td>¥{{ number_format($order->discount_amount, 2) }}</td></tr>
|
||||
<tr><th>运费</th><td>¥{{ number_format($order->shipping_amount, 2) }}</td></tr>
|
||||
<tr><th>应付金额</th><td>¥{{ number_format($order->pay_amount, 2) }}</td></tr>
|
||||
<tr><th>备注</th><td>{{ $order->remark }}</td></tr>
|
||||
<tr><th>创建时间</th><td>{{ $order->created_at }}</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>订单明细</h3>
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>商品</th><th>SKU</th><th>单价</th><th>数量</th><th>小计</th><th>快照</th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($order->items as $item)
|
||||
<tr>
|
||||
<td>{{ $item->id }}</td>
|
||||
<td>{{ $item->product_title }}</td>
|
||||
<td>{{ $item->product_sku }}</td>
|
||||
<td>¥{{ number_format($item->product_price, 2) }}</td>
|
||||
<td>{{ $item->quantity }}</td>
|
||||
<td>¥{{ number_format($item->line_total_amount, 2) }}</td>
|
||||
<td>{{ $item->snapshot['category'] ?? '-' }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="7" class="muted">暂无订单明细</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="mt-16"><a href="/admin/orders">返回订单列表</a></p>
|
||||
</div>
|
||||
@endsection
|
||||
74
resources/views/admin/plans/form.blade.php
Normal file
74
resources/views/admin/plans/form.blade.php
Normal file
@@ -0,0 +1,74 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', $plan->exists ? '编辑套餐' : '新建套餐')
|
||||
@section('page_title', $plan->exists ? '编辑套餐' : '新建套餐')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<p class="muted muted-tight">套餐是平台授权与收费的基础单位,这里维护套餐的基本信息与售卖口径。</p>
|
||||
<p class="muted">当前阶段先提交套餐主数据,后续再补授权项、配额与订阅联动。</p>
|
||||
</div>
|
||||
|
||||
<form method="post" action="{{ $formAction }}" class="card form-grid">
|
||||
@csrf
|
||||
|
||||
<label>
|
||||
<span>套餐名称</span>
|
||||
<input name="name" value="{{ old('name', $plan->name) }}" required>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>套餐编码</span>
|
||||
<input name="code" value="{{ old('code', $plan->code) }}" required>
|
||||
<small class="muted">仅限字母、数字、短横线和下划线,用于接口/内部引用。</small>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>计费周期</span>
|
||||
<select name="billing_cycle" required>
|
||||
@foreach($billingCycleLabels as $value => $label)
|
||||
<option value="{{ $value }}" @selected(old('billing_cycle', $plan->billing_cycle) === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>售价</span>
|
||||
<input type="number" step="0.01" min="0" name="price" value="{{ old('price', $plan->price) }}" required>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>划线价</span>
|
||||
<input type="number" step="0.01" min="0" name="list_price" value="{{ old('list_price', $plan->list_price) }}">
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>状态</span>
|
||||
<select name="status" required>
|
||||
@foreach($statusLabels as $value => $label)
|
||||
<option value="{{ $value }}" @selected(old('status', $plan->status) === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<span>排序</span>
|
||||
<input type="number" min="0" name="sort" value="{{ old('sort', $plan->sort ?? 0) }}">
|
||||
</label>
|
||||
|
||||
<label class="full">
|
||||
<span>发布时间</span>
|
||||
<input type="datetime-local" name="published_at" value="{{ old('published_at', optional($plan->published_at)->format('Y-m-d\TH:i')) }}">
|
||||
</label>
|
||||
|
||||
<label class="full">
|
||||
<span>套餐说明</span>
|
||||
<textarea name="description" rows="4" placeholder="可描述套餐包含的能力与适用场景">{{ old('description', $plan->description) }}</textarea>
|
||||
</label>
|
||||
|
||||
<div class="form-actions">
|
||||
<a href="/admin/plans" class="btn-secondary">返回</a>
|
||||
<button type="submit">保存套餐</button>
|
||||
</div>
|
||||
</form>
|
||||
@endsection
|
||||
139
resources/views/admin/plans/index.blade.php
Normal file
139
resources/views/admin/plans/index.blade.php
Normal file
@@ -0,0 +1,139 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '套餐管理')
|
||||
@section('page_title', '套餐管理')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<p class="muted muted-tight">这里是总台视角的套餐目录页,用于沉淀平台可售卖的标准能力包。</p>
|
||||
<p class="muted">当前阶段先完成套餐主数据可见、可筛与口径收拢,后续再接授权项、售价规则与上下架动作。</p>
|
||||
</div>
|
||||
|
||||
<div class="card mb-20">
|
||||
<h3>工具</h3>
|
||||
<form method="get" action="/admin/plans/export">
|
||||
<input type="hidden" name="status" value="{{ $filters['status'] ?? '' }}">
|
||||
<input type="hidden" name="published" value="{{ $filters['published'] ?? '' }}">
|
||||
<input type="hidden" name="billing_cycle" value="{{ $filters['billing_cycle'] ?? '' }}">
|
||||
<input type="hidden" name="keyword" value="{{ $filters['keyword'] ?? '' }}">
|
||||
<button type="submit">导出当前筛选结果(CSV)</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card mb-20">
|
||||
<h3>筛选条件</h3>
|
||||
<form method="get" action="/admin/plans" class="grid-3">
|
||||
<select name="status">
|
||||
<option value="">全部状态</option>
|
||||
@foreach(($filterOptions['statuses'] ?? []) as $value => $label)
|
||||
<option value="{{ $value }}" @selected(($filters['status'] ?? '') === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="published">
|
||||
<option value="">全部发布状态</option>
|
||||
<option value="published" @selected(($filters['published'] ?? '') === 'published')>已发布</option>
|
||||
<option value="unpublished" @selected(($filters['published'] ?? '') === 'unpublished')>未发布</option>
|
||||
</select>
|
||||
<select name="billing_cycle">
|
||||
<option value="">全部计费周期</option>
|
||||
@foreach(($filterOptions['billingCycles'] ?? []) as $value => $label)
|
||||
<option value="{{ $value }}" @selected(($filters['billing_cycle'] ?? '') === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<input name="keyword" placeholder="搜索套餐名称 / 编码 / 描述" value="{{ $filters['keyword'] ?? '' }}">
|
||||
<div>
|
||||
<button type="submit">应用筛选</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="grid-4 mb-20">
|
||||
<div class="card">
|
||||
<h3>套餐总数</h3>
|
||||
<div class="num-md">{{ $summaryStats['total_plans'] ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>启用中套餐</h3>
|
||||
<div class="num-md">{{ $summaryStats['active_plans'] ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>月付套餐</h3>
|
||||
<div class="num-md">{{ $summaryStats['monthly_plans'] ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>年付套餐</h3>
|
||||
<div class="num-md">{{ $summaryStats['yearly_plans'] ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>已发布</h3>
|
||||
<div class="num-md">{{ $summaryStats['published_plans'] ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>未发布</h3>
|
||||
<div class="num-md">{{ $summaryStats['unpublished_plans'] ?? 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<div class="flex-between">
|
||||
<div>
|
||||
<h3>套餐列表</h3>
|
||||
<p class="muted muted-xs">后续将从这里进入套餐详情、授权项与订阅联动。</p>
|
||||
</div>
|
||||
<a href="/admin/plans/create" class="btn">新建套餐</a>
|
||||
</div>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>套餐名称</th>
|
||||
<th>编码</th>
|
||||
<th>计费周期</th>
|
||||
<th>售价</th>
|
||||
<th>划线价</th>
|
||||
<th>状态</th>
|
||||
<th>排序</th>
|
||||
<th>发布时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($plans as $plan)
|
||||
<tr>
|
||||
<td>{{ $plan->id }}</td>
|
||||
<td>
|
||||
<strong>{{ $plan->name }}</strong>
|
||||
<div class="muted muted-xs">{{ $plan->description ?: '暂无说明' }}</div>
|
||||
</td>
|
||||
<td>{{ $plan->code }}</td>
|
||||
<td>{{ $billingCycleLabels[$plan->billing_cycle] ?? $plan->billing_cycle }}</td>
|
||||
<td>¥{{ number_format((float) $plan->price, 2) }}</td>
|
||||
<td>¥{{ number_format((float) $plan->list_price, 2) }}</td>
|
||||
<td>{{ $statusLabels[$plan->status] ?? $plan->status }}</td>
|
||||
<td>{{ $plan->sort }}</td>
|
||||
<td>{{ optional($plan->published_at)->format('Y-m-d H:i:s') ?: '-' }}</td>
|
||||
<td>
|
||||
<a href="/admin/plans/{{ $plan->id }}/edit" class="link">编辑</a>
|
||||
|
||||
<form method="post" action="/admin/plans/{{ $plan->id }}/set-status" style="margin-top:6px;">
|
||||
@csrf
|
||||
<select name="status" onchange="this.form.submit()" style="width:140px;">
|
||||
@foreach(($filterOptions['statuses'] ?? []) as $value => $label)
|
||||
<option value="{{ $value }}" @selected($plan->status === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<noscript><button type="submit">更新状态</button></noscript>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="10" class="muted">暂无套餐数据,当前阶段先把套餐主表与总台目录立起来,后续可继续接套餐创建、授权项与订阅关联。</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pagination-wrap">{{ $plans->links() }}</div>
|
||||
@endsection
|
||||
298
resources/views/admin/platform_orders/index.blade.php
Normal file
298
resources/views/admin/platform_orders/index.blade.php
Normal file
@@ -0,0 +1,298 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '平台订单')
|
||||
@section('page_title', '平台订单')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<p class="muted muted-tight">这里是总台视角的平台收费主链骨架页,当前阶段先承接套餐订购 / 续费 / 生效跟踪。</p>
|
||||
<p class="muted">本页先提供可访问列表、基础筛选与摘要卡,后续再补详情、导出、支付记录与退款轨迹。</p>
|
||||
</div>
|
||||
|
||||
<div class="card mb-20">
|
||||
<h3>筛选条件</h3>
|
||||
<form method="get" action="/admin/platform-orders" class="grid-3">
|
||||
<select name="status">
|
||||
<option value="">全部订单状态</option>
|
||||
@foreach(($filterOptions['statuses'] ?? []) as $value => $label)
|
||||
<option value="{{ $value }}" @selected(($filters['status'] ?? '') === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="payment_status">
|
||||
<option value="">全部支付状态</option>
|
||||
@foreach(($filterOptions['paymentStatuses'] ?? []) as $value => $label)
|
||||
<option value="{{ $value }}" @selected(($filters['payment_status'] ?? '') === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="sync_status">
|
||||
<option value="">全部同步状态</option>
|
||||
<option value="unsynced" @selected(($filters['sync_status'] ?? '') === 'unsynced')>未同步</option>
|
||||
<option value="synced" @selected(($filters['sync_status'] ?? '') === 'synced')>已同步</option>
|
||||
<option value="failed" @selected(($filters['sync_status'] ?? '') === 'failed')>同步失败</option>
|
||||
</select>
|
||||
<label class="form-inline-row">
|
||||
<input type="checkbox" name="fail_only" value="1" @checked(($filters['fail_only'] ?? '') === '1')>
|
||||
<span>只看同步失败</span>
|
||||
</label>
|
||||
<label class="form-inline-row">
|
||||
<input type="checkbox" name="synced_only" value="1" @checked(($filters['synced_only'] ?? '') === '1')>
|
||||
<span>只看已同步</span>
|
||||
</label>
|
||||
<label class="form-inline-row">
|
||||
<input type="checkbox" name="syncable_only" value="1" @checked(($filters['syncable_only'] ?? '') === '1')>
|
||||
<span>只看可同步</span>
|
||||
</label>
|
||||
<label class="form-inline-row">
|
||||
<input type="checkbox" name="batch_synced_24h" value="1" @checked(($filters['batch_synced_24h'] ?? '') === '1')>
|
||||
<span>最近24小时批量同步过</span>
|
||||
</label>
|
||||
<div>
|
||||
<button type="submit">应用筛选</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="grid-3 mb-20">
|
||||
<div class="card">
|
||||
<h3>平台订单总数</h3>
|
||||
<div class="metric-number">{{ $summaryStats['total_orders'] ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>已支付 / 已生效</h3>
|
||||
<div class="metric-number">{{ $summaryStats['paid_orders'] ?? 0 }} / {{ $summaryStats['activated_orders'] ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>已同步 / 未同步</h3>
|
||||
<div class="metric-number">{{ $summaryStats['synced_orders'] ?? 0 }} / {{ $summaryStats['unsynced_orders'] ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>同步失败数</h3>
|
||||
<div class="metric-number">{{ $summaryStats['failed_sync_orders'] ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>同步失败原因 TOP5</h3>
|
||||
@php $failedReasonStats = $failedReasonStats ?? []; @endphp
|
||||
@if(count($failedReasonStats) > 0)
|
||||
<div class="muted mt-6">
|
||||
@foreach($failedReasonStats as $item)
|
||||
<div>{{ $item['reason'] }} <span class="muted">({{ $item['count'] }})</span></div>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
<div class="muted">暂无失败原因聚合数据</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-20">
|
||||
<h3>工具</h3>
|
||||
<div class="muted mb-10">清除仅影响订单 meta 中的失败标记,不改变订单/订阅状态。</div>
|
||||
|
||||
<form method="get" action="/admin/platform-orders/export" class="mb-10">
|
||||
<input type="hidden" name="status" value="{{ $filters['status'] ?? '' }}">
|
||||
<input type="hidden" name="payment_status" value="{{ $filters['payment_status'] ?? '' }}">
|
||||
<input type="hidden" name="fail_only" value="{{ $filters['fail_only'] ?? '' }}">
|
||||
<input type="hidden" name="synced_only" value="{{ $filters['synced_only'] ?? '' }}">
|
||||
<input type="hidden" name="sync_status" value="{{ $filters['sync_status'] ?? '' }}">
|
||||
<input type="hidden" name="syncable_only" value="{{ $filters['syncable_only'] ?? '' }}">
|
||||
<input type="hidden" name="batch_synced_24h" value="{{ $filters['batch_synced_24h'] ?? '' }}">
|
||||
|
||||
<label class="form-inline-row mb-8">
|
||||
<input type="checkbox" name="include_meta" value="1">
|
||||
<span>包含原始 meta(JSON)(用于排障)</span>
|
||||
</label>
|
||||
|
||||
<button type="submit">导出当前筛选结果(CSV)</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="/admin/platform-orders/batch-activate-subscriptions" onsubmit="return confirm('确认批量同步当前筛选范围内“可同步”的订单?(仅处理:已支付+已生效+未同步)');" class="mb-10">
|
||||
@csrf
|
||||
<input type="hidden" name="scope" value="filtered">
|
||||
<input type="hidden" name="status" value="{{ $filters['status'] ?? '' }}">
|
||||
<input type="hidden" name="payment_status" value="{{ $filters['payment_status'] ?? '' }}">
|
||||
<input type="hidden" name="fail_only" value="{{ $filters['fail_only'] ?? '' }}">
|
||||
<input type="hidden" name="synced_only" value="{{ $filters['synced_only'] ?? '' }}">
|
||||
<input type="hidden" name="sync_status" value="{{ $filters['sync_status'] ?? '' }}">
|
||||
<input type="hidden" name="syncable_only" value="{{ $filters['syncable_only'] ?? '' }}">
|
||||
<input type="hidden" name="batch_synced_24h" value="{{ $filters['batch_synced_24h'] ?? '' }}">
|
||||
|
||||
<label class="muted form-inline-row mb-8">
|
||||
<span>本次最多处理</span>
|
||||
<input type="number" name="limit" value="50" min="1" max="500" class="w-90">
|
||||
<span>条(安全阀)</span>
|
||||
</label>
|
||||
|
||||
<div class="muted mb-8">提示:建议先勾选筛选条件「只看可同步」,再执行批量同步。</div>
|
||||
<button type="submit">批量同步订阅(当前筛选范围)</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="/admin/platform-orders/batch-activate-subscriptions" onsubmit="return confirm('确认对全部订单执行批量同步?该操作仍只处理“已支付+已生效+未同步”的订单,但范围可能很大。');" class="mb-10">
|
||||
@csrf
|
||||
<input type="hidden" name="scope" value="all">
|
||||
<label class="muted form-inline-row mb-8">
|
||||
<span>确认输入</span>
|
||||
<input type="text" name="confirm" placeholder="YES" class="w-140">
|
||||
<span>(必须输入 YES 才会执行)</span>
|
||||
</label>
|
||||
<label class="muted form-inline-row mb-8">
|
||||
<span>本次最多处理</span>
|
||||
<input type="number" name="limit" value="50" min="1" max="500" class="w-90">
|
||||
<span>条(安全阀)</span>
|
||||
</label>
|
||||
<button type="submit">批量同步订阅(全部订单)</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="/admin/platform-orders/clear-sync-errors" onsubmit="return confirm('确认清除当前筛选范围内命中的订单的“同步失败”标记?');" class="mb-10">
|
||||
@csrf
|
||||
<input type="hidden" name="scope" value="filtered">
|
||||
<input type="hidden" name="status" value="{{ $filters['status'] ?? '' }}">
|
||||
<input type="hidden" name="payment_status" value="{{ $filters['payment_status'] ?? '' }}">
|
||||
<input type="hidden" name="fail_only" value="{{ $filters['fail_only'] ?? '' }}">
|
||||
<input type="hidden" name="synced_only" value="{{ $filters['synced_only'] ?? '' }}">
|
||||
<input type="hidden" name="sync_status" value="{{ $filters['sync_status'] ?? '' }}">
|
||||
<input type="hidden" name="syncable_only" value="{{ $filters['syncable_only'] ?? '' }}">
|
||||
<input type="hidden" name="batch_synced_24h" value="{{ $filters['batch_synced_24h'] ?? '' }}">
|
||||
<button type="submit">清除当前筛选范围的失败标记</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="/admin/platform-orders/clear-sync-errors" onsubmit="return confirm('确认清除全部订单的“同步失败”标记?');">
|
||||
@csrf
|
||||
<input type="hidden" name="scope" value="all">
|
||||
<button type="submit">清除全部失败标记</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>平台订单列表</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>订单号</th>
|
||||
<th>站点</th>
|
||||
<th>套餐</th>
|
||||
<th>订单类型</th>
|
||||
<th>订单状态</th>
|
||||
<th>支付状态</th>
|
||||
<th>应付金额</th>
|
||||
<th>已付金额</th>
|
||||
<th>下单时间</th>
|
||||
<th>支付时间</th>
|
||||
<th>同步状态</th>
|
||||
<th>订阅号</th>
|
||||
<th>订阅到期</th>
|
||||
<th>同步时间</th>
|
||||
<th>最近批量同步</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($orders as $order)
|
||||
<tr>
|
||||
<td>{{ $order->id }}</td>
|
||||
<td><a href="/admin/platform-orders/{{ $order->id }}">{{ $order->order_no }}</a></td>
|
||||
<td>{{ $order->merchant?->name ?? '未关联站点' }}</td>
|
||||
<td>{{ $order->plan_name ?: ($order->plan?->name ?? '未设置') }}</td>
|
||||
<td>{{ $order->order_type }}</td>
|
||||
<td>
|
||||
{{ $statusLabels[$order->status] ?? $order->status }}
|
||||
<div class="muted">{{ $order->status }}</div>
|
||||
</td>
|
||||
<td>
|
||||
{{ $paymentStatusLabels[$order->payment_status] ?? $order->payment_status }}
|
||||
<div class="muted">{{ $order->payment_status }}</div>
|
||||
</td>
|
||||
<td>¥{{ number_format((float) $order->payable_amount, 2) }}</td>
|
||||
<td>¥{{ number_format((float) $order->paid_amount, 2) }}</td>
|
||||
<td>{{ optional($order->placed_at)->format('Y-m-d H:i:s') ?: '-' }}</td>
|
||||
<td>{{ optional($order->paid_at)->format('Y-m-d H:i:s') ?: '-' }}</td>
|
||||
<td>
|
||||
@php
|
||||
$syncedId = (int) data_get($order->meta, 'subscription_activation.subscription_id', 0);
|
||||
$syncErr = (string) (data_get($order->meta, 'subscription_activation_error.message') ?? '');
|
||||
@endphp
|
||||
@if($syncedId > 0)
|
||||
<span>已同步</span>
|
||||
@elseif($syncErr !== '')
|
||||
<span class="text-danger">同步失败</span>
|
||||
@else
|
||||
<span class="muted">未同步</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ $order->siteSubscription?->subscription_no ?: '-' }}</td>
|
||||
<td>{{ optional($order->siteSubscription?->ends_at)->format('Y-m-d H:i:s') ?: '-' }}</td>
|
||||
<td>{{ data_get($order->meta, 'subscription_activation.synced_at') ?: '-' }}</td>
|
||||
<td>
|
||||
@php
|
||||
// 优先使用扁平字段 meta.batch_activation(便于筛选/统计,也避免遍历 audit)
|
||||
$batchActivation = (array) (data_get($order->meta, 'batch_activation', []) ?? []);
|
||||
$batchAt = (string) (data_get($batchActivation, 'at') ?? '');
|
||||
$batchAdminId = data_get($batchActivation, 'admin_id');
|
||||
|
||||
// 兼容旧数据:若尚未写入 batch_activation,则回退从 meta.audit[] 中取最近一次批量同步记录
|
||||
$audit = (array) (data_get($order->meta, 'audit', []) ?? []);
|
||||
$lastBatch = null;
|
||||
if ($batchAt === '' && count($audit) > 0) {
|
||||
foreach (array_reverse($audit) as $item) {
|
||||
if ((string) data_get($item, 'action') === 'batch_activate_subscription') {
|
||||
$lastBatch = $item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($batchAt === '' && $lastBatch) {
|
||||
$batchAt = (string) (data_get($lastBatch, 'at') ?? '');
|
||||
$batchAdminId = data_get($lastBatch, 'admin_id');
|
||||
}
|
||||
@endphp
|
||||
@if($batchAt !== '')
|
||||
<div>{{ $batchAt }}</div>
|
||||
<div class="muted">管理员:{{ $batchAdminId ?: '-' }}</div>
|
||||
@else
|
||||
<span class="muted">-</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
@php
|
||||
$canActivate = ($order->payment_status === 'paid') && ($order->status === 'activated');
|
||||
$alreadySynced = (int) data_get($order->meta, 'subscription_activation.subscription_id', 0) > 0;
|
||||
$syncError = data_get($order->meta, 'subscription_activation_error.message');
|
||||
$syncErrorAt = data_get($order->meta, 'subscription_activation_error.at');
|
||||
@endphp
|
||||
@php
|
||||
$canMarkPaid = ($order->payment_status !== 'paid') || ($order->status !== 'activated');
|
||||
@endphp
|
||||
|
||||
<form method="post" action="/admin/platform-orders/{{ $order->id }}/mark-paid-and-activate" onsubmit="return confirm('确认将该订单标记为已支付并生效?此操作会推进状态并尝试同步订阅');" class="mb-6">
|
||||
@csrf
|
||||
<button type="submit" @disabled(! $canMarkPaid)>标记支付并生效</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="/admin/platform-orders/{{ $order->id }}/activate-subscription" onsubmit="return confirm('确认同步订阅?将根据该订单创建/续期订阅');">
|
||||
@csrf
|
||||
<button type="submit" @disabled(! $canActivate || $alreadySynced)>{{ $alreadySynced ? '已同步' : '同步订阅' }}</button>
|
||||
</form>
|
||||
|
||||
@if(! $canActivate)
|
||||
<div class="muted mt-6">需已支付且订单状态为已生效</div>
|
||||
@elseif($alreadySynced)
|
||||
<div class="muted mt-6">该订单已完成同步(幂等保护)</div>
|
||||
@endif
|
||||
|
||||
@if($syncError)
|
||||
<div class="muted text-danger mt-6">同步失败:{{ $syncError }}{{ $syncErrorAt ? '(' . $syncErrorAt . ')' : '' }}</div>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="17" class="muted">暂无平台订单,当前阶段骨架已就位,可继续补套餐下单、支付回执与订阅生效链路。</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pagination-wrap">{{ $orders->links() }}</div>
|
||||
@endsection
|
||||
158
resources/views/admin/platform_orders/show.blade.php
Normal file
158
resources/views/admin/platform_orders/show.blade.php
Normal file
@@ -0,0 +1,158 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '平台订单详情')
|
||||
@section('page_title', '平台订单详情')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<p class="muted muted-tight">这里用于运营排查:订单核心字段、关联订阅、以及订阅同步元数据(meta)。</p>
|
||||
</div>
|
||||
|
||||
<div class="card mb-20">
|
||||
<h3>订单信息</h3>
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><th style="width:160px;">ID</th><td>{{ $order->id }}</td></tr>
|
||||
<tr><th>订单号</th><td>{{ $order->order_no }}</td></tr>
|
||||
<tr><th>站点</th><td>{{ $order->merchant?->name ?? '未关联站点' }}</td></tr>
|
||||
<tr><th>套餐</th><td>{{ $order->plan_name ?: ($order->plan?->name ?? '-') }}</td></tr>
|
||||
<tr><th>订单类型</th><td>{{ $order->order_type }}</td></tr>
|
||||
<tr><th>订单状态</th><td>{{ $statusLabels[$order->status] ?? $order->status }} <span class="muted">({{ $order->status }})</span></td></tr>
|
||||
<tr><th>支付状态</th><td>{{ $paymentStatusLabels[$order->payment_status] ?? $order->payment_status }} <span class="muted">({{ $order->payment_status }})</span></td></tr>
|
||||
<tr><th>应付/已付</th><td>¥{{ number_format((float) $order->payable_amount, 2) }} / ¥{{ number_format((float) $order->paid_amount, 2) }}</td></tr>
|
||||
<tr><th>下单时间</th><td>{{ optional($order->placed_at)->format('Y-m-d H:i:s') ?: '-' }}</td></tr>
|
||||
<tr><th>支付时间</th><td>{{ optional($order->paid_at)->format('Y-m-d H:i:s') ?: '-' }}</td></tr>
|
||||
<tr><th>生效时间</th><td>{{ optional($order->activated_at)->format('Y-m-d H:i:s') ?: '-' }}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@php
|
||||
$canActivate = ($order->payment_status === 'paid') && ($order->status === 'activated');
|
||||
$alreadySynced = (int) data_get($order->meta, 'subscription_activation.subscription_id', 0) > 0;
|
||||
$canMarkPaid = ($order->payment_status !== 'paid') || ($order->status !== 'activated');
|
||||
$syncError = data_get($order->meta, 'subscription_activation_error.message');
|
||||
$syncErrorAt = data_get($order->meta, 'subscription_activation_error.at');
|
||||
@endphp
|
||||
|
||||
<div style="margin-top:16px; display:flex; gap:12px; flex-wrap:wrap;">
|
||||
<form method="post" action="/admin/platform-orders/{{ $order->id }}/mark-paid-and-activate" onsubmit="return confirm('确认将该订单标记为已支付并生效?此操作会推进状态并尝试同步订阅');">
|
||||
@csrf
|
||||
<button type="submit" @disabled(! $canMarkPaid)>标记支付并生效</button>
|
||||
</form>
|
||||
|
||||
<form method="post" action="/admin/platform-orders/{{ $order->id }}/activate-subscription" onsubmit="return confirm('确认同步订阅?将根据该订单创建/续期订阅');">
|
||||
@csrf
|
||||
<button type="submit" @disabled(! $canActivate || $alreadySynced)>{{ $alreadySynced ? '已同步' : '同步订阅' }}</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@if(! $canActivate)
|
||||
<div class="muted" style="margin-top:10px;">同步订阅需满足:已支付 + 订单状态已生效</div>
|
||||
@elseif($alreadySynced)
|
||||
<div class="muted" style="margin-top:10px;">该订单已完成同步(幂等保护)</div>
|
||||
@endif
|
||||
|
||||
@if($syncError)
|
||||
<div class="muted" style="margin-top:10px; color:#b42318;">最近同步失败:{{ $syncError }}{{ $syncErrorAt ? '(' . $syncErrorAt . ')' : '' }}</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="card mb-20">
|
||||
<h3>关联订阅</h3>
|
||||
@if($order->siteSubscription)
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><th style="width:160px;">订阅ID</th><td>{{ $order->siteSubscription->id }}</td></tr>
|
||||
<tr><th>订阅号</th><td>{{ $order->siteSubscription->subscription_no }}</td></tr>
|
||||
<tr><th>订阅管理</th><td><a href="/admin/site-subscriptions?keyword={{ urlencode($order->siteSubscription->subscription_no) }}">打开订阅管理(按订阅号筛选)</a></td></tr>
|
||||
<tr><th>状态</th><td>{{ $order->siteSubscription->status }}</td></tr>
|
||||
<tr><th>开始/到期</th><td>{{ optional($order->siteSubscription->starts_at)->format('Y-m-d H:i:s') ?: '-' }} / {{ optional($order->siteSubscription->ends_at)->format('Y-m-d H:i:s') ?: '-' }}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<p class="muted">该订单尚未关联订阅(site_subscription_id 为空)。</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@php
|
||||
$activation = data_get($order->meta, 'subscription_activation');
|
||||
$activationError = data_get($order->meta, 'subscription_activation_error');
|
||||
$audit = (array) (data_get($order->meta, 'audit', []) ?? []);
|
||||
@endphp
|
||||
|
||||
<div class="card mb-20">
|
||||
<h3>订阅同步记录</h3>
|
||||
@if($activation)
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><th style="width:160px;">订阅ID</th><td>{{ data_get($activation, 'subscription_id') }}</td></tr>
|
||||
<tr><th>同步时间</th><td>{{ data_get($activation, 'synced_at') ?: '-' }}</td></tr>
|
||||
<tr><th>操作管理员</th><td>{{ data_get($activation, 'admin_id') ?: '-' }}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<p class="muted">暂无同步记录。</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="card mb-20">
|
||||
<h3>最近一次同步失败</h3>
|
||||
@if($activationError)
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><th style="width:160px;">失败原因</th><td>{{ data_get($activationError, 'message') }}</td></tr>
|
||||
<tr><th>失败时间</th><td>{{ data_get($activationError, 'at') ?: '-' }}</td></tr>
|
||||
<tr><th>操作管理员</th><td>{{ data_get($activationError, 'admin_id') ?: '-' }}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<p class="muted">暂无失败记录。</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="card mb-20">
|
||||
<h3>审计记录(最近 20 条)</h3>
|
||||
@if(count($audit) > 0)
|
||||
@php
|
||||
$auditItems = array_slice(array_reverse($audit), 0, 20);
|
||||
$auditActionLabels = [
|
||||
'clear_sync_error' => '清除同步失败标记',
|
||||
'batch_activate_subscription' => '批量同步订阅',
|
||||
];
|
||||
@endphp
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:180px;">动作</th>
|
||||
<th style="width:120px;">范围</th>
|
||||
<th style="width:200px;">时间</th>
|
||||
<th style="width:100px;">管理员</th>
|
||||
<th>备注</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($auditItems as $item)
|
||||
<tr>
|
||||
<td>{{ $auditActionLabels[data_get($item, 'action')] ?? (data_get($item, 'action') ?: '-') }}</td>
|
||||
<td>{{ data_get($item, 'scope') ?: '-' }}</td>
|
||||
<td>{{ data_get($item, 'at') ?: '-' }}</td>
|
||||
<td>{{ data_get($item, 'admin_id') ?: '-' }}</td>
|
||||
<td class="muted">{{ data_get($item, 'note') ?: '' }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
@else
|
||||
<p class="muted">暂无审计记录。</p>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>原始 meta(JSON)</h3>
|
||||
<pre style="white-space: pre-wrap;">{{ json_encode($order->meta, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) }}</pre>
|
||||
</div>
|
||||
|
||||
<div class="mb-20" style="margin-top:16px;">
|
||||
<a href="/admin/platform-orders">← 返回平台订单列表</a>
|
||||
</div>
|
||||
@endsection
|
||||
70
resources/views/admin/product_categories/index.blade.php
Normal file
70
resources/views/admin/product_categories/index.blade.php
Normal file
@@ -0,0 +1,70 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '商品分类')
|
||||
@section('page_title', '商品分类')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<p class="muted muted-tight">这里是总台视角的商品分类巡检与维护入口,可跨站点查看分类结构。</p>
|
||||
<p class="muted">当前分类列表已接入缓存:{{ $cacheMeta['store'] }} / TTL {{ $cacheMeta['ttl'] }}。</p>
|
||||
<h3>新增分类</h3>
|
||||
<form method="post" action="/admin/product-categories">
|
||||
@csrf
|
||||
<div class="grid-3">
|
||||
<select name="merchant_id">
|
||||
@foreach($merchants as $merchant)
|
||||
<option value="{{ $merchant->id }}" @selected((string) old('merchant_id', $merchants->first()?->id) === (string) $merchant->id)>{{ $merchant->id }} / {{ $merchant->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<input name="name" placeholder="分类名称" value="{{ old('name') }}">
|
||||
<input name="slug" placeholder="slug" value="{{ old('slug') }}">
|
||||
<select name="status">
|
||||
<option value="active" @selected(old('status', 'active') === 'active')>active</option>
|
||||
<option value="inactive" @selected(old('status') === 'inactive')>inactive</option>
|
||||
</select>
|
||||
<input name="sort" placeholder="排序" value="{{ old('sort', 0) }}">
|
||||
<input name="description" placeholder="分类说明" class="span-2" value="{{ old('description') }}">
|
||||
</div>
|
||||
<div class="mt-12"><button type="submit">创建分类</button></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>分类列表</h3>
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>商家</th><th>名称</th><th>Slug</th><th>状态</th><th>排序</th><th>说明</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
@foreach($categories as $category)
|
||||
<tr>
|
||||
<td>{{ $category->id }}</td>
|
||||
<td>{{ $category->merchant?->name ?? ('商家#'.$category->merchant_id) }}</td>
|
||||
<td>
|
||||
<form method="post" action="/admin/product-categories/{{ $category->id }}" class="actions">
|
||||
@csrf
|
||||
<input name="name" value="{{ $category->name }}">
|
||||
</td>
|
||||
<td><input name="slug" value="{{ $category->slug }}"></td>
|
||||
<td>
|
||||
<select name="status">
|
||||
<option value="active" @selected($category->status==='active')>active</option>
|
||||
<option value="inactive" @selected($category->status==='inactive')>inactive</option>
|
||||
</select>
|
||||
</td>
|
||||
<td><input name="sort" value="{{ $category->sort }}" class="w-90"></td>
|
||||
<td><input name="description" value="{{ $category->description }}"></td>
|
||||
<td>
|
||||
<button type="submit">更新</button>
|
||||
</form>
|
||||
<form method="post" action="/admin/product-categories/{{ $category->id }}/delete" class="inline" onsubmit="return confirm('确定删除这个分类?');">
|
||||
@csrf
|
||||
<button type="submit" class="button-danger">删除</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pagination-wrap">{{ $categories->links() }}</div>
|
||||
@endsection
|
||||
109
resources/views/admin/products/import_histories.blade.php
Normal file
109
resources/views/admin/products/import_histories.blade.php
Normal file
@@ -0,0 +1,109 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '平台商品导入历史')
|
||||
@section('page_title', '平台商品导入历史')
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
<p class="muted muted-tight">这里集中查看平台视角的商品导入历史,适合运营侧复盘批量导入结果、失败明细与清理后文件状态。</p>
|
||||
|
||||
@php
|
||||
$exportHistoryQuery = http_build_query(array_filter([
|
||||
'merchant_id' => $importHistoryFilters['merchant_id'] ?? '',
|
||||
'import_result_status' => $importHistoryFilters['result_status'] ?? 'all',
|
||||
'import_time_range' => $importHistoryFilters['time_range'] ?? 'all',
|
||||
'start_date' => $importHistoryFilters['start_date'] ?? '',
|
||||
'end_date' => $importHistoryFilters['end_date'] ?? '',
|
||||
'import_sort' => $importHistoryFilters['sort'] ?? 'latest',
|
||||
], fn ($value, $key) => match ($key) {
|
||||
'start_date', 'end_date' => $value !== null && $value !== '',
|
||||
default => $value !== null && $value !== '' && $value !== 'all' && $value !== 'latest',
|
||||
}, ARRAY_FILTER_USE_BOTH));
|
||||
@endphp
|
||||
<form method="get" action="/admin/products/import-histories" class="mb-12">
|
||||
<div class="actions">
|
||||
<select name="merchant_id">
|
||||
<option value="">全部商家</option>
|
||||
@foreach(($historyMerchants ?? collect()) as $merchant)
|
||||
<option value="{{ $merchant->id }}" @selected(($importHistoryFilters['merchant_id'] ?? '') === (string) $merchant->id)>{{ $merchant->id }} / {{ $merchant->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="import_result_status">
|
||||
<option value="all" @selected(($importHistoryFilters['result_status'] ?? 'all') === 'all')>全部导入结果</option>
|
||||
<option value="success_only" @selected(($importHistoryFilters['result_status'] ?? 'all') === 'success_only')>仅看成功导入</option>
|
||||
<option value="has_failures" @selected(($importHistoryFilters['result_status'] ?? 'all') === 'has_failures')>仅看含失败导入</option>
|
||||
</select>
|
||||
<select name="import_time_range">
|
||||
<option value="all" @selected(($importHistoryFilters['time_range'] ?? 'all') === 'all')>全部时间</option>
|
||||
<option value="today" @selected(($importHistoryFilters['time_range'] ?? 'all') === 'today')>今天</option>
|
||||
<option value="last_7_days" @selected(($importHistoryFilters['time_range'] ?? 'all') === 'last_7_days')>近7天</option>
|
||||
<option value="custom" @selected(($importHistoryFilters['time_range'] ?? 'all') === 'custom')>自定义区间</option>
|
||||
</select>
|
||||
<input type="date" name="start_date" value="{{ $importHistoryFilters['start_date'] ?? '' }}">
|
||||
<span class="muted">至</span>
|
||||
<input type="date" name="end_date" value="{{ $importHistoryFilters['end_date'] ?? '' }}">
|
||||
<select name="import_sort">
|
||||
@foreach(($importHistoryFilterOptions['sorts'] ?? []) as $sortValue => $sortLabel)
|
||||
<option value="{{ $sortValue }}" @selected(($importHistoryFilters['sort'] ?? 'latest') === $sortValue)>{{ $sortLabel }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<button type="submit">筛选导入历史</button>
|
||||
<a href="{{ '/admin/products/import-histories/export' . ($exportHistoryQuery ? ('?' . $exportHistoryQuery) : '') }}">导出当前筛选 CSV</a>
|
||||
<a href="/admin/products/import-histories">清空筛选</a>
|
||||
<a href="/admin/products">返回商品页</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@if(!empty($importHistoryFilters['date_errors']))
|
||||
<div class="info-warning">
|
||||
@foreach($importHistoryFilters['date_errors'] as $dateError)
|
||||
<div>{{ $dateError }}</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="grid-4 mb-12">
|
||||
<div class="stat-box"><div class="muted">累计导入批次</div><strong class="num-sm">{{ $importHistoryStats['total_imports'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box"><div class="muted">累计成功商品</div><strong class="num-sm">{{ $importHistoryStats['total_success'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box"><div class="muted">累计失败商品</div><strong class="num-sm">{{ $importHistoryStats['total_failed'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box"><div class="muted">含失败批次</div><strong class="num-sm">{{ $importHistoryStats['warning_imports'] ?? 0 }}</strong></div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>导入时间</th><th>商家</th><th>上传文件</th><th>结果</th><th>成功</th><th>失败</th><th>操作者</th><th>失败明细</th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($importHistories as $history)
|
||||
<tr>
|
||||
<td>{{ $history->id }}</td>
|
||||
<td>{{ $history->imported_at?->format('Y-m-d H:i:s') }}</td>
|
||||
<td>{{ $history->merchant?->name ?? '平台批量导入' }}</td>
|
||||
<td>{{ $history->file_name }}</td>
|
||||
<td>
|
||||
@if(($history->failed_count ?? 0) > 0)
|
||||
<span class="result-warning">部分失败</span>
|
||||
@else
|
||||
<span class="result-success">成功</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ $history->success_count }}</td>
|
||||
<td>{{ $history->failed_count }}</td>
|
||||
<td>{{ $history->admin?->name ?? '-' }}</td>
|
||||
<td>
|
||||
@if($history->failure_file && ($history->failure_file_available ?? false))
|
||||
<a href="/admin/products/import-failures/{{ $history->failure_file }}">下载 failure CSV</a>
|
||||
@elseif($history->failure_file)
|
||||
<span class="muted">文件已过保留期</span>
|
||||
@else
|
||||
<span class="muted">无</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="9" class="muted">暂无导入记录</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pagination-wrap">{{ $importHistories->links() }}</div>
|
||||
</div>
|
||||
@endsection
|
||||
225
resources/views/admin/products/index.blade.php
Normal file
225
resources/views/admin/products/index.blade.php
Normal file
@@ -0,0 +1,225 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '商品巡检')
|
||||
@section('page_title', '商品巡检')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<p class="muted muted-tight">当前页面作为总台视角的商品巡检与演示管理入口。后续商家商品运营将拆分到独立商家后台。</p>
|
||||
<p class="muted">当前商品列表已接入缓存:{{ $cacheMeta['store'] }} / TTL {{ $cacheMeta['ttl'] }}。</p>
|
||||
@php
|
||||
$exportQuery = http_build_query(array_filter($filters, fn ($value) => $value !== null && $value !== ''));
|
||||
@endphp
|
||||
<div class="card my-16">
|
||||
<h3 class="mt-0">批量导入商品</h3>
|
||||
<p class="muted">先下载模板,按模板填写后上传 CSV。平台模板首列需要填写 `merchant_id`,分类通过 `category_slug` 匹配。</p>
|
||||
<div class="card-link-list mb-12">
|
||||
<a href="/admin/products/import-template">下载导入模板</a>
|
||||
<a href="{{ '/admin/products/export' . ($exportQuery ? ('?' . $exportQuery) : '') }}">导出当前筛选结果 CSV</a>
|
||||
<a href="/admin/products/import-histories">查看独立导入历史页</a>
|
||||
</div>
|
||||
@if(session('import_result'))
|
||||
@php $importResult = session('import_result'); @endphp
|
||||
<div class="card section-dark mb-12">
|
||||
<div><strong>导入结果:</strong>成功 {{ $importResult['success'] ?? 0 }} 条,失败 {{ $importResult['failed'] ?? 0 }} 条。</div>
|
||||
@if(!empty($importResult['messages']))
|
||||
<div class="muted mt-8">失败原因预览:</div>
|
||||
<ul class="list-indent">
|
||||
@foreach($importResult['messages'] as $message)
|
||||
<li>{{ $message }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
@if(!empty($importResult['failure_file']))
|
||||
<div class="mt-10"><a href="/admin/products/import-failures/{{ $importResult['failure_file'] }}">下载失败明细 CSV</a></div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
<form method="post" action="/admin/products/import" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="actions"><input type="file" name="import_file" accept=".csv,text/csv"><button type="submit">开始导入</button></div>
|
||||
</form>
|
||||
|
||||
<div class="mt-16">
|
||||
<div class="actions-spread mb-10">
|
||||
<div>
|
||||
<h4 class="mb-6">导入历史摘要</h4>
|
||||
<div class="muted">商品页仅保留摘要与最近 5 条记录;完整筛选、分页与复盘请前往独立导入历史页。</div>
|
||||
</div>
|
||||
<a href="/admin/products/import-histories">进入完整导入历史页</a>
|
||||
</div>
|
||||
<div class="grid-4 mb-12">
|
||||
<div class="stat-box"><div class="muted">累计导入批次</div><strong class="num-sm">{{ $importHistoryStats['total_imports'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box"><div class="muted">累计成功商品</div><strong class="num-sm">{{ $importHistoryStats['total_success'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box"><div class="muted">累计失败商品</div><strong class="num-sm">{{ $importHistoryStats['total_failed'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box"><div class="muted">含失败批次</div><strong class="num-sm">{{ $importHistoryStats['warning_imports'] ?? 0 }}</strong></div>
|
||||
</div>
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>导入时间</th><th>商家</th><th>上传文件</th><th>结果</th><th>失败明细</th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($importHistories->take(5) as $history)
|
||||
<tr>
|
||||
<td>{{ $history->id }}</td>
|
||||
<td>{{ $history->imported_at?->format('Y-m-d H:i:s') }}</td>
|
||||
<td>{{ $history->merchant?->name ?? '平台批量导入' }}</td>
|
||||
<td>{{ $history->file_name }}</td>
|
||||
<td>@if(($history->failed_count ?? 0) > 0)<span class="result-warning">部分失败({{ $history->failed_count }})</span>@else<span class="result-success">成功</span>@endif</td>
|
||||
<td>
|
||||
@if($history->failure_file && ($history->failure_file_available ?? false))
|
||||
<a href="/admin/products/import-failures/{{ $history->failure_file }}">下载</a>
|
||||
@elseif($history->failure_file)
|
||||
<span class="muted">已过保留期</span>
|
||||
@else
|
||||
<span class="muted">无</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="6" class="muted">暂无导入记录</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3>筛选条件</h3>
|
||||
<form method="get" action="/admin/products" class="mb-20">
|
||||
<div class="grid-4">
|
||||
<select name="merchant_id"><option value="">全部商家</option>@foreach($merchants as $merchant)<option value="{{ $merchant->id }}" @selected($filters['merchant_id'] === (string) $merchant->id)>{{ $merchant->id }} / {{ $merchant->name }}</option>@endforeach</select>
|
||||
<select name="category_id"><option value="">全部分类</option>@foreach($categories as $category)<option value="{{ $category->id }}" @selected($filters['category_id'] === (string) $category->id)>商家{{ $category->merchant_id }} / {{ $category->name }}</option>@endforeach</select>
|
||||
<select name="status"><option value="">全部状态</option>@foreach($filterOptions['statuses'] as $status)<option value="{{ $statusLabels[$status] ?? $status }}" @selected($filters['status'] === $status)>{{ $statusLabels[$status] ?? $status }}</option>@endforeach</select>
|
||||
<input name="keyword" placeholder="标题 / slug / SKU" value="{{ $filters['keyword'] }}">
|
||||
<input type="number" step="0.01" min="0" name="min_price" placeholder="最低价格" value="{{ $filters['min_price'] }}">
|
||||
<input type="number" step="0.01" min="0" name="max_price" placeholder="最高价格" value="{{ $filters['max_price'] }}">
|
||||
<input type="number" min="0" name="min_stock" placeholder="最低库存" value="{{ $filters['min_stock'] }}">
|
||||
<input type="number" min="0" name="max_stock" placeholder="最高库存" value="{{ $filters['max_stock'] }}">
|
||||
<select name="sort">@foreach($filterOptions['sortOptions'] as $sortValue => $sortLabel)<option value="{{ $sortValue }}" @selected(($filters['sort'] ?? 'latest') === $sortValue)>{{ $sortLabel }}</option>@endforeach</select>
|
||||
</div>
|
||||
<div class="mt-12 actions"><button type="submit">筛选</button><a href="/admin/products">重置</a></div>
|
||||
</form>
|
||||
<div class="card section-dark my-20"><h3 class="mt-0">当前筛选摘要</h3><div class="grid-4">@foreach($activeFilterSummary as $summaryLabel => $summaryValue)<div class="summary-box"><div class="muted">{{ $summaryLabel }}</div><strong class="num-md">{{ $summaryValue }}</strong></div>@endforeach</div></div>
|
||||
|
||||
<div class="card my-20"><h3 class="mt-0">商品运营汇总</h3><div class="grid-4"><div class="stat-box"><div class="muted">商品总数</div><strong class="num-md">{{ $summaryStats['total_products'] ?? 0 }}</strong></div><div class="stat-box"><div class="muted">总库存</div><strong class="num-md">{{ $summaryStats['total_stock'] ?? 0 }}</strong></div><div class="stat-box"><div class="muted">总货值</div><strong class="num-md">¥{{ number_format($summaryStats['total_stock_value'] ?? 0, 2) }}</strong></div><div class="stat-box"><div class="muted">平均售价</div><strong class="num-md">¥{{ number_format($summaryStats['average_price'] ?? 0, 2) }}</strong></div></div></div>
|
||||
|
||||
<div class="card section-dark card-spaced">
|
||||
<h3 class="mt-0">运营关注项</h3>
|
||||
<div class="grid-3">
|
||||
<div class="focus-box">
|
||||
<div class="muted">商品盘面</div>
|
||||
<strong class="text-md">{{ $operationsFocus['headline'] ?? '当前总台商品运营信息已就绪。' }}</strong>
|
||||
<div class="muted mt-8">建议动作</div>
|
||||
<div class="inline-links mt-6">
|
||||
@foreach(($operationsFocus['actions'] ?? []) as $action)
|
||||
<a href="{{ $action['url'] }}">{{ $action['label'] }}</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="focus-box">
|
||||
<div class="muted">当前信号</div>
|
||||
<div class="grid-3 mt-8">
|
||||
@foreach(($operationsFocus['signals'] ?? []) as $label => $value)
|
||||
<div class="summary-box"><div class="muted">{{ $label }}</div><strong class="num-sm">{{ $value }}</strong></div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="focus-box">
|
||||
<div class="muted">工作台导航</div>
|
||||
<div class="inline-links mt-8">
|
||||
@foreach(($operationsFocus['workbench'] ?? []) as $label => $url)
|
||||
<a href="{{ $url }}">{{ $label }}</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card section-dark my-20">
|
||||
<h3 class="mt-0">商品状态统计</h3>
|
||||
@php
|
||||
$productBaseQuery = [
|
||||
'merchant_id' => $filters['merchant_id'] ?: null,
|
||||
'category_id' => $filters['category_id'] ?: null,
|
||||
'keyword' => $filters['keyword'] ?: null,
|
||||
'min_price' => $filters['min_price'] ?: null,
|
||||
'max_price' => $filters['max_price'] ?: null,
|
||||
'min_stock' => $filters['min_stock'] ?: null,
|
||||
'max_stock' => $filters['max_stock'] ?: null,
|
||||
'sort' => $filters['sort'] ?: 'latest',
|
||||
];
|
||||
@endphp
|
||||
<div class="grid-4">
|
||||
<a href="{{ '/admin/products?' . http_build_query(array_filter($productBaseQuery, fn ($value) => $value !== null && $value !== '')) }}" class="status-link {{ $filters['status'] === '' ? 'is-active-dark' : '' }}"><div class="muted">全部</div><strong class="num-md">{{ $statusStats['all'] ?? 0 }}</strong></a>
|
||||
@foreach($filterOptions['statuses'] as $status)
|
||||
<a href="{{ '/admin/products?' . http_build_query(array_filter(array_merge($productBaseQuery, ['status' => $status]), fn ($value) => $value !== null && $value !== '')) }}" class="status-link {{ $filters['status'] === $status ? 'is-active-dark' : '' }}"><div class="muted">{{ $statusLabels[$status] ?? $status }}</div><strong class="num-md">{{ $statusStats[$status] ?? 0 }}</strong></a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>新增商品</h3>
|
||||
<form method="post" action="/admin/products">
|
||||
@csrf
|
||||
<div class="grid-3">
|
||||
<select name="merchant_id">@foreach($merchants as $merchant)<option value="{{ $merchant->id }}" @selected((string) old('merchant_id', $merchants->first()?->id) === (string) $merchant->id)>{{ $merchant->id }} / {{ $merchant->name }}</option>@endforeach</select>
|
||||
<select name="category_id"><option value="">选择分类(可留空)</option>@foreach($categories as $category)<option value="{{ $category->id }}" @selected((string) old('category_id') === (string) $category->id)>商家{{ $category->merchant_id }} / {{ $category->name }}</option>@endforeach</select>
|
||||
<input name="title" placeholder="商品标题" value="{{ old('title') }}">
|
||||
<input name="slug" placeholder="slug" value="{{ old('slug') }}">
|
||||
<input name="sku" placeholder="SKU" value="{{ old('sku') }}">
|
||||
<input name="price" placeholder="价格" value="{{ old('price') }}">
|
||||
<input name="stock" placeholder="库存" value="{{ old('stock') }}">
|
||||
</div>
|
||||
<div class="mt-12"><input name="summary" placeholder="简介" class="w-full" value="{{ old('summary') }}"></div>
|
||||
<div class="mt-12"><button type="submit">创建商品</button></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>商品列表</h3>
|
||||
<form id="platform-batch-form" method="post" action="/admin/products/batch" onsubmit="return confirm('确认执行本次批量操作?');">
|
||||
@csrf
|
||||
<input type="hidden" name="return_url" value="{{ request()->fullUrl() }}">
|
||||
<div class="actions mb-12">
|
||||
<select name="action"><option value="change_status">批量改状态</option><option value="change_category">批量改分类</option></select>
|
||||
<select name="status"><option value="">选择状态(用于批量改状态)</option>@foreach($filterOptions['statuses'] as $status)<option value="{{ $statusLabels[$status] ?? $status }}">{{ $statusLabels[$status] ?? $status }}</option>@endforeach</select>
|
||||
<select name="category_id"><option value="">清空分类 / 不指定(用于批量改分类)</option>@foreach($categories as $category)<option value="{{ $category->id }}">商家{{ $category->merchant_id }} / {{ $category->name }}</option>@endforeach</select>
|
||||
<button type="submit">执行批量操作</button>
|
||||
</div>
|
||||
</form>
|
||||
<p class="muted muted-tight">平台侧批量改分类会校验所选分类是否属于被勾选商品对应商家;若混选了不同商家的商品,请选择各自可用的分类或先分批处理。</p>
|
||||
<table>
|
||||
<thead><tr><th><input type="checkbox" data-check-all="platform-products"></th><th>ID</th><th>商家</th><th>标题</th><th>分类</th><th>SKU</th><th>售价/原价</th><th>库存</th><th>创建时间</th><th>更新时间</th><th>状态</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
@foreach($products as $product)
|
||||
<tr>
|
||||
<td><input type="checkbox" name="product_ids[]" value="{{ $product->id }}" data-check-item="platform-products" form="platform-batch-form"></td>
|
||||
<td>{{ $product->id }}</td>
|
||||
<td>{{ $product->merchant?->name ?? ('商家#'.$product->merchant_id) }}</td>
|
||||
<td><form method="post" action="/admin/products/{{ $product->id }}" class="actions">@csrf <input name="title" value="{{ $product->title }}"></td>
|
||||
<td><select name="category_id"><option value="">未分类</option>@foreach($categories->where('merchant_id', $product->merchant_id) as $category)<option value="{{ $category->id }}" @selected($product->category_id === $category->id)>{{ $category->name }}</option>@endforeach</select></td>
|
||||
<td><input name="slug" value="{{ $product->slug }}" placeholder="slug" class="w-140 block mb-6"><input name="sku" value="{{ $product->sku }}" placeholder="SKU" class="w-140"></td>
|
||||
<td><input name="price" value="{{ $product->price }}" class="w-90 block mb-6"><div class="muted">原价:¥{{ number_format((float) $product->original_price, 2) }}</div></td>
|
||||
<td><input name="stock" value="{{ $product->stock }}" class="w-90"></td>
|
||||
<td>{{ $product->created_at?->format('Y-m-d H:i') }}</td>
|
||||
<td>{{ $product->updated_at?->format('Y-m-d H:i') }}</td>
|
||||
<td><select name="status"><option value="draft" @selected($product->status==='draft')>草稿</option><option value="published" @selected($product->status==='published')>已上架</option><option value="offline" @selected($product->status==='offline')>已下架</option></select></td>
|
||||
<td><button type="submit">更新</button></form><form method="post" action="/admin/products/{{ $product->id }}/delete" class="inline" onsubmit="return confirm('确定删除这个商品?');">@csrf <button type="submit" class="button-danger">删除</button></form></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const master = document.querySelector('[data-check-all="platform-products"]');
|
||||
const items = document.querySelectorAll('[data-check-item="platform-products"]');
|
||||
if (!master || !items.length) {
|
||||
return;
|
||||
}
|
||||
master.addEventListener('change', function () {
|
||||
items.forEach(function (item) {
|
||||
item.checked = master.checked;
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="pagination-wrap">{{ $products->links() }}</div>
|
||||
@endsection
|
||||
98
resources/views/admin/settings/channels.blade.php
Normal file
98
resources/views/admin/settings/channels.blade.php
Normal file
@@ -0,0 +1,98 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '渠道配置')
|
||||
@section('page_title', '渠道配置')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<p class="mt-0">渠道配置已经切到数据库读取,当前用于承接多端渠道和平台支付能力的基线定义,并已支持基础编辑。</p>
|
||||
<div class="muted">当前渠道与支付配置概览已接入缓存读取。</div>
|
||||
<div class="muted">当前平台商家数:{{ $merchantCount }}</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-20">
|
||||
<h3 class="mt-0">渠道基线</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>渠道编码</th>
|
||||
<th>渠道名称</th>
|
||||
<th>类型</th>
|
||||
<th>状态</th>
|
||||
<th>入口</th>
|
||||
<th>登录</th>
|
||||
<th>支付</th>
|
||||
<th>分享</th>
|
||||
<th>备注</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($channels as $channel)
|
||||
<tr>
|
||||
<form method="post" action="/admin/settings/channels/{{ $channel->id }}">
|
||||
@csrf
|
||||
<td>{{ $channel->channel_code }}</td>
|
||||
<td><input name="channel_name" value="{{ old('channel_name', $channel->channel_name) }}"></td>
|
||||
<td><input name="channel_type" value="{{ old('channel_type', $channel->channel_type) }}"></td>
|
||||
<td>
|
||||
<select name="status">
|
||||
@foreach(['enabled', 'disabled', 'reserved'] as $status)
|
||||
<option value="{{ $status }}" @selected(old('status', $channel->status) === $status)>{{ $status }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</td>
|
||||
<td><input name="entry_path" value="{{ old('entry_path', $channel->entry_path) }}"></td>
|
||||
<td><label><input type="checkbox" name="supports_login" value="1" @checked((bool) old('supports_login', $channel->supports_login))> yes</label></td>
|
||||
<td><label><input type="checkbox" name="supports_payment" value="1" @checked((bool) old('supports_payment', $channel->supports_payment))> yes</label></td>
|
||||
<td><label><input type="checkbox" name="supports_share" value="1" @checked((bool) old('supports_share', $channel->supports_share))> yes</label></td>
|
||||
<td><textarea name="remark" rows="2" class="w-full">{{ old('remark', $channel->remark) }}</textarea></td>
|
||||
<td><button type="submit">保存</button></td>
|
||||
</form>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3 class="mt-0">支付配置基线</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>支付编码</th>
|
||||
<th>支付名称</th>
|
||||
<th>提供方</th>
|
||||
<th>状态</th>
|
||||
<th>沙箱</th>
|
||||
<th>退款</th>
|
||||
<th>备注</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($paymentConfigs as $payment)
|
||||
<tr>
|
||||
<form method="post" action="/admin/settings/payments/{{ $payment->id }}">
|
||||
@csrf
|
||||
<td>{{ $payment->payment_code }}</td>
|
||||
<td><input name="payment_name" value="{{ old('payment_name', $payment->payment_name) }}"></td>
|
||||
<td><input name="provider" value="{{ old('provider', $payment->provider) }}"></td>
|
||||
<td>
|
||||
<select name="status">
|
||||
@foreach(['enabled', 'disabled', 'reserved'] as $status)
|
||||
<option value="{{ $status }}" @selected(old('status', $payment->status) === $status)>{{ $status }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</td>
|
||||
<td><label><input type="checkbox" name="is_sandbox" value="1" @checked((bool) old('is_sandbox', $payment->is_sandbox))> yes</label></td>
|
||||
<td><label><input type="checkbox" name="supports_refund" value="1" @checked((bool) old('supports_refund', $payment->supports_refund))> yes</label></td>
|
||||
<td><textarea name="remark" rows="2" class="w-full">{{ old('remark', $payment->remark) }}</textarea></td>
|
||||
<td><button type="submit">保存</button></td>
|
||||
</form>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endsection
|
||||
79
resources/views/admin/settings/system.blade.php
Normal file
79
resources/views/admin/settings/system.blade.php
Normal file
@@ -0,0 +1,79 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '系统配置')
|
||||
@section('page_title', '系统配置')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<p class="mt-0">系统配置已经从静态骨架切到数据库读取,当前已支持基础编辑、保存和缓存刷新。</p>
|
||||
<div class="muted">当前系统配置列表已接入缓存读取。</div>
|
||||
<div class="muted">当前配置分组数:{{ $groupedCount }}</div>
|
||||
</div>
|
||||
|
||||
@foreach($systemSettings->groupBy('group') as $group => $items)
|
||||
<div class="card mb-20">
|
||||
<h3 class="mt-0">配置分组:{{ $group }}</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>配置键</th>
|
||||
<th>名称</th>
|
||||
<th>配置值</th>
|
||||
<th>类型</th>
|
||||
<th>自动加载</th>
|
||||
<th>备注</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($items as $item)
|
||||
@php
|
||||
$isEditing = $editingConfigId === $item->id;
|
||||
$configName = $isEditing ? old('config_name', $item->config_name) : $item->config_name;
|
||||
$configValue = $isEditing ? old('config_value', $item->config_value) : $item->config_value;
|
||||
$valueType = $isEditing ? old('value_type', $item->value_type) : $item->value_type;
|
||||
$autoload = $isEditing ? old('autoload', $item->autoload) : $item->autoload;
|
||||
$remark = $isEditing ? old('remark', $item->remark) : $item->remark;
|
||||
@endphp
|
||||
<tr>
|
||||
<form method="post" action="/admin/settings/system/{{ $item->id }}">
|
||||
@csrf
|
||||
<td>{{ $item->config_key }}<input type="hidden" name="group" value="{{ $item->group }}"></td>
|
||||
<td><input name="config_name" value="{{ $configName }}"></td>
|
||||
<td>
|
||||
@if($valueType === 'json')
|
||||
<textarea name="config_value" rows="4" class="w-full">{{ $configValue }}</textarea>
|
||||
@if($isEditing && $errors->has('config_value'))
|
||||
<div class="mt-6 muted" role="alert">{{ $errors->first('config_value') }}</div>
|
||||
@endif
|
||||
@elseif($valueType === 'boolean')
|
||||
<select name="config_value">
|
||||
<option value="1" @selected($configValue == '1')>true</option>
|
||||
<option value="0" @selected($configValue == '0')>false</option>
|
||||
</select>
|
||||
@elseif($valueType === 'number')
|
||||
<input type="number" step="any" name="config_value" value="{{ $configValue }}">
|
||||
@else
|
||||
<input name="config_value" value="{{ $configValue }}">
|
||||
@endif
|
||||
</td>
|
||||
<td>
|
||||
<select name="value_type">
|
||||
@foreach($valueTypeOptions as $type)
|
||||
<option value="{{ $type }}" @selected($valueType === $type)>{{ $type }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</td>
|
||||
<td>
|
||||
<label><input type="checkbox" name="autoload" value="1" @checked((bool) $autoload)> yes</label>
|
||||
</td>
|
||||
<td><input name="remark" value="{{ $remark }}"></td>
|
||||
<td><button type="submit">保存</button></td>
|
||||
</form>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endforeach
|
||||
@endsection
|
||||
144
resources/views/admin/site_subscriptions/index.blade.php
Normal file
144
resources/views/admin/site_subscriptions/index.blade.php
Normal file
@@ -0,0 +1,144 @@
|
||||
@extends('admin.layouts.app')
|
||||
|
||||
@section('title', '订阅管理')
|
||||
@section('page_title', '订阅管理')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<p class="muted muted-tight">这里是总台视角的订阅目录页,承接“套餐 -> 订阅 -> 平台订单”的收费主链中间层。</p>
|
||||
<p class="muted">当前阶段先做到:可访问列表、可筛选、统计摘要;后续再接:订阅激活服务 / 续费 / 取消 / 对账。</p>
|
||||
</div>
|
||||
|
||||
<div class="card mb-20">
|
||||
<h3>筛选条件</h3>
|
||||
<form method="get" action="/admin/site-subscriptions" class="grid-4">
|
||||
<select name="status">
|
||||
<option value="">全部状态</option>
|
||||
@foreach(($filterOptions['statuses'] ?? []) as $value => $label)
|
||||
<option value="{{ $value }}" @selected(($filters['status'] ?? '') === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="expiry">
|
||||
<option value="">全部到期状态</option>
|
||||
<option value="expiring_7d" @selected(($filters['expiry'] ?? '') === 'expiring_7d')>7天内到期</option>
|
||||
<option value="expired" @selected(($filters['expiry'] ?? '') === 'expired')>已过期</option>
|
||||
</select>
|
||||
<select name="merchant_id">
|
||||
<option value="">全部站点</option>
|
||||
@foreach(($merchants ?? []) as $merchant)
|
||||
<option value="{{ $merchant->id }}" @selected(($filters['merchant_id'] ?? '') == $merchant->id)>{{ $merchant->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="plan_id">
|
||||
<option value="">全部套餐</option>
|
||||
@foreach(($plans ?? []) as $plan)
|
||||
<option value="{{ $plan->id }}" @selected(($filters['plan_id'] ?? '') == $plan->id)>{{ $plan->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<input name="keyword" placeholder="搜索订阅号 / 套餐快照 / 计费周期" value="{{ $filters['keyword'] ?? '' }}">
|
||||
<div>
|
||||
<button type="submit">应用筛选</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="grid-4 mb-20">
|
||||
<div class="card">
|
||||
<h3>订阅总数</h3>
|
||||
<div class="num-md">{{ $summaryStats['total_subscriptions'] ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>已生效</h3>
|
||||
<div class="num-md">{{ $summaryStats['activated_subscriptions'] ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>待生效</h3>
|
||||
<div class="num-md">{{ $summaryStats['pending_subscriptions'] ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>已取消</h3>
|
||||
<div class="num-md">{{ $summaryStats['cancelled_subscriptions'] ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>已过期(按到期时间)</h3>
|
||||
<div class="num-md">{{ $summaryStats['expired_subscriptions'] ?? 0 }}</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>7天内到期</h3>
|
||||
<div class="num-md">{{ $summaryStats['expiring_7d_subscriptions'] ?? 0 }}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-20">
|
||||
<h3>工具</h3>
|
||||
<form method="get" action="/admin/site-subscriptions/export">
|
||||
<input type="hidden" name="status" value="{{ $filters['status'] ?? '' }}">
|
||||
<input type="hidden" name="merchant_id" value="{{ $filters['merchant_id'] ?? '' }}">
|
||||
<input type="hidden" name="plan_id" value="{{ $filters['plan_id'] ?? '' }}">
|
||||
<input type="hidden" name="expiry" value="{{ $filters['expiry'] ?? '' }}">
|
||||
<input type="hidden" name="keyword" value="{{ $filters['keyword'] ?? '' }}">
|
||||
<button type="submit">导出当前筛选结果(CSV)</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>订阅列表</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>订阅号</th>
|
||||
<th>站点</th>
|
||||
<th>套餐</th>
|
||||
<th>状态</th>
|
||||
<th>计费周期</th>
|
||||
<th>周期(月)</th>
|
||||
<th>金额</th>
|
||||
<th>开始时间</th>
|
||||
<th>到期时间</th>
|
||||
<th>到期状态</th>
|
||||
<th>生效时间</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@forelse($subscriptions as $subscription)
|
||||
<tr>
|
||||
<td>{{ $subscription->id }}</td>
|
||||
<td>{{ $subscription->subscription_no }}</td>
|
||||
<td>{{ $subscription->merchant?->name ?? '未关联站点' }}</td>
|
||||
<td>{{ $subscription->plan_name ?: ($subscription->plan?->name ?? '未设置') }}</td>
|
||||
<td>{{ ($statusLabels[$subscription->status] ?? $subscription->status) }} <span class="muted">({{ $subscription->status }})</span></td>
|
||||
<td>{{ $subscription->billing_cycle ?: '-' }}</td>
|
||||
<td>{{ $subscription->period_months }}</td>
|
||||
<td>¥{{ number_format((float) $subscription->amount, 2) }}</td>
|
||||
<td>{{ optional($subscription->starts_at)->format('Y-m-d H:i:s') ?: '-' }}</td>
|
||||
<td>{{ optional($subscription->ends_at)->format('Y-m-d H:i:s') ?: '-' }}</td>
|
||||
<td>
|
||||
@php
|
||||
$endsAt = $subscription->ends_at;
|
||||
$expiryLabel = '无到期';
|
||||
if ($endsAt) {
|
||||
if ($endsAt->lt(now())) {
|
||||
$expiryLabel = '已过期';
|
||||
} elseif ($endsAt->lt(now()->addDays(7))) {
|
||||
$expiryLabel = '7天内到期';
|
||||
} else {
|
||||
$expiryLabel = '未到期';
|
||||
}
|
||||
}
|
||||
@endphp
|
||||
{{ $expiryLabel }}
|
||||
</td>
|
||||
<td>{{ optional($subscription->activated_at)->format('Y-m-d H:i:s') ?: '-' }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="12" class="muted">暂无订阅数据,当前阶段先把订阅主表与总台目录立起来,后续再接订阅创建/激活/续费链路。</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pagination-wrap">{{ $subscriptions->links() }}</div>
|
||||
@endsection
|
||||
1
resources/views/admin/tenants/index.blade.php
Normal file
1
resources/views/admin/tenants/index.blade.php
Normal file
@@ -0,0 +1 @@
|
||||
@extends('admin.merchants.index')
|
||||
23
resources/views/front/h5/index.blade.php
Normal file
23
resources/views/front/h5/index.blade.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
|
||||
<title>H5 商城首页 - SaaSShop</title>
|
||||
<link rel="stylesheet" href="/css/public-pages.css">
|
||||
</head>
|
||||
<body class="h5-body">
|
||||
<div class="h5-header">SaaSShop H5 商城首页</div>
|
||||
<div class="h5-wrap">
|
||||
<p>这是 H5 端模板入口,后续可扩展底部导航、个人中心、下单与支付流程。</p>
|
||||
<p><a href="/">返回项目首页</a></p>
|
||||
@foreach($products as $product)
|
||||
<div class="h5-card">
|
||||
<h3>{{ $product->title }}</h3>
|
||||
<p>{{ $product->summary }}</p>
|
||||
<p class="price-sm">¥{{ number_format($product->price, 2) }}</p>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
26
resources/views/front/pc/index.blade.php
Normal file
26
resources/views/front/pc/index.blade.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>PC 商城首页 - SaaSShop</title>
|
||||
<link rel="stylesheet" href="/css/public-pages.css">
|
||||
</head>
|
||||
<body class="pc-body">
|
||||
<div class="pc-top"><strong>SaaSShop PC 端商城首页</strong></div>
|
||||
<div class="pc-wrap">
|
||||
<p>这是 PC 端模板入口,后续可扩展导航、分类、搜索、购物车与会员中心。</p>
|
||||
<p><a href="/">返回项目首页</a></p>
|
||||
<div class="pc-grid">
|
||||
@foreach($products as $product)
|
||||
<div class="shop-card">
|
||||
<h3>{{ $product->title }}</h3>
|
||||
<p>{{ $product->summary }}</p>
|
||||
<p class="price">¥{{ number_format($product->price, 2) }}</p>
|
||||
<p>SKU: {{ $product->sku }}</p>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
45
resources/views/home.blade.php
Normal file
45
resources/views/home.blade.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>SaaSShop 首页</title>
|
||||
<link rel="stylesheet" href="/css/public-pages.css">
|
||||
</head>
|
||||
<body class="dark-page">
|
||||
<div class="public-wrap">
|
||||
<h1>SaaSShop 多端项目首页</h1>
|
||||
<p class="muted">当前基础框架已按多端方向铺开:PC、H5 可直接访问,微信生态与 APP 接口层已预留入口;后台当前已形成总台管理、站点后台、商家后台三层入口骨架。</p>
|
||||
<div class="grid-3">
|
||||
<div class="card"><h3>商家数</h3><p>{{ $merchantCount }}</p></div>
|
||||
<div class="card"><h3>商品数</h3><p>{{ $productCount }}</p></div>
|
||||
<div class="card"><h3>订单数</h3><p>{{ $orderCount }}</p></div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-20">
|
||||
<h3>管理与系统入口</h3>
|
||||
<ul>
|
||||
<li><a href="/admin">总台管理</a></li>
|
||||
<li><a href="/site-admin">站点后台</a></li>
|
||||
<li><a href="/merchant-admin">商家后台</a></li>
|
||||
<li><a href="/health">环境健康检查页</a></li>
|
||||
<li><a href="http://192.168.10.199:888/">phpMyAdmin</a></li>
|
||||
<li><a href="/api/v1/ping">API Ping</a></li>
|
||||
<li><a href="/api/v1/platforms">平台能力接口</a></li>
|
||||
<li><a href="/api/v1/products">商品列表接口</a></li>
|
||||
<li><a href="/api/v1/orders">订单列表接口</a></li>
|
||||
</ul>
|
||||
<p>项目目录:<code>/var/www/sites/app</code></p>
|
||||
</div>
|
||||
|
||||
<div class="platforms">
|
||||
@foreach($platforms as $platform)
|
||||
<div class="card">
|
||||
<h3>{{ $platform['name'] }} <span class="tag">{{ $platform['status'] }}</span></h3>
|
||||
<p><a href="{{ $platform['path'] }}">访问入口 {{ $platform['path'] }}</a></p>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
25
resources/views/merchant_admin/auth/login.blade.php
Normal file
25
resources/views/merchant_admin/auth/login.blade.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>商家后台登录 - SaaSShop</title>
|
||||
<link rel="stylesheet" href="/css/admin-base.css">
|
||||
<link rel="stylesheet" href="/css/merchant-admin.css">
|
||||
</head>
|
||||
<body class="login-page">
|
||||
<div class="card-login">
|
||||
<h1>商家后台登录</h1>
|
||||
<p class="muted">仅商家管理员可登录。演示账号:merchant.admin@demo.local / Merchant@123456</p>
|
||||
@if ($errors->any())
|
||||
<div class="error">{{ $errors->first() }}</div>
|
||||
@endif
|
||||
<form method="post" action="/merchant-admin/login">
|
||||
@csrf
|
||||
<input type="email" name="email" placeholder="邮箱" value="{{ old('email', 'merchant.admin@demo.local') }}">
|
||||
<input type="password" name="password" placeholder="密码" value="Merchant@123456">
|
||||
<button type="submit">登录商家后台</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
19
resources/views/merchant_admin/dashboard.blade.php
Normal file
19
resources/views/merchant_admin/dashboard.blade.php
Normal file
@@ -0,0 +1,19 @@
|
||||
@extends('merchant_admin.layouts.app')
|
||||
|
||||
@section('title', '商家后台仪表盘')
|
||||
@section('page_title', '商家后台仪表盘')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<p>当前商家:<strong>{{ $merchant->name }}</strong>({{ $merchant->slug }})。这里是商家运营视角后台,只展示当前商家范围内的数据。</p>
|
||||
</div>
|
||||
<div class="card mb-20">
|
||||
<div class="muted">缓存状态:当前仪表盘统计已接入缓存,缓存驱动 {{ $cacheMeta['store'] }},TTL {{ $cacheMeta['ttl'] }}。</div>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="card"><div class="muted">商家用户</div><div class="num">{{ $stats['users'] }}</div></div>
|
||||
<div class="card"><div class="muted">商家商品</div><div class="num">{{ $stats['products'] }}</div></div>
|
||||
<div class="card"><div class="muted">商家订单</div><div class="num">{{ $stats['orders'] }}</div></div>
|
||||
<div class="card"><div class="muted">待处理订单</div><div class="num">{{ $stats['pending_orders'] }}</div></div>
|
||||
</div>
|
||||
@endsection
|
||||
58
resources/views/merchant_admin/layouts/app.blade.php
Normal file
58
resources/views/merchant_admin/layouts/app.blade.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>@yield('title', 'SaaSShop 商家后台')</title>
|
||||
<link rel="stylesheet" href="/css/admin-base.css">
|
||||
<link rel="stylesheet" href="/css/merchant-admin.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout">
|
||||
<aside class="sidebar">
|
||||
<h2 class="sidebar-title">SaaSShop</h2>
|
||||
<div class="muted">商家后台 / Merchant Console</div>
|
||||
|
||||
<div class="group-title">总览</div>
|
||||
<a href="/merchant-admin">商家仪表盘</a>
|
||||
|
||||
<div class="group-title">店铺运营</div>
|
||||
<a href="/merchant-admin/products">商品管理</a>
|
||||
<a href="/merchant-admin/product-categories">商品分类</a>
|
||||
<a href="/merchant-admin/orders">订单管理</a>
|
||||
<a href="/merchant-admin/users">用户管理</a>
|
||||
|
||||
<div class="group-title">切换入口</div>
|
||||
<a href="/admin">总台管理</a>
|
||||
<a href="/">返回首页</a>
|
||||
<form method="post" action="/merchant-admin/logout" class="logout-form">@csrf <button type="submit">退出登录</button></form>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<div class="top">
|
||||
<div>
|
||||
<h1 class="page-title">@yield('page_title', '商家后台')</h1>
|
||||
<div class="muted">当前登录:{{ session('admin_name') }}({{ session('admin_email') }}) / 商家 ID:{{ session('admin_merchant_id') }}</div>
|
||||
<div class="badge">当前作用域:商家管理员</div>
|
||||
</div>
|
||||
</div>
|
||||
@if(session('success'))
|
||||
<div class="flash">{{ session('success') }}</div>
|
||||
@endif
|
||||
@if(session('warning'))
|
||||
<div class="warning">{{ session('warning') }}</div>
|
||||
@endif
|
||||
@if($errors->any())
|
||||
<div class="error-box">
|
||||
<strong>提交失败:</strong>
|
||||
<ul class="list-indent">
|
||||
@foreach($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
@yield('content')
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
117
resources/views/merchant_admin/orders/index.blade.php
Normal file
117
resources/views/merchant_admin/orders/index.blade.php
Normal file
@@ -0,0 +1,117 @@
|
||||
@extends('merchant_admin.layouts.app')
|
||||
|
||||
@section('title', '商家订单管理')
|
||||
@section('page_title', '商家订单管理')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<p class="muted muted-tight">当前订单列表已按登录商家过滤,商家管理员只能处理自己店铺的数据。</p>
|
||||
<p class="muted">当前订单列表已接入缓存:{{ $cacheMeta['store'] }} / TTL {{ $cacheMeta['ttl'] }}。</p>
|
||||
@php
|
||||
$exportQuery = http_build_query(array_filter($filters, fn ($value) => $value !== null && $value !== ''));
|
||||
@endphp
|
||||
<div class="mb-12"><a href="{{ '/merchant-admin/orders/export' . ($exportQuery ? ('?' . $exportQuery) : '') }}">导出当前筛选结果 CSV</a></div>
|
||||
<h3>筛选条件</h3>
|
||||
@if(!empty($filters['validation_errors']))
|
||||
<div class="filter-error"><strong>筛选条件有误:</strong><ul class="list-indent">@foreach($filters['validation_errors'] as $validationError)<li>{{ $validationError }}</li>@endforeach</ul></div>
|
||||
@endif
|
||||
<form method="get" action="/merchant-admin/orders">
|
||||
<div class="grid-5">
|
||||
<select name="status"><option value="">全部状态</option>@foreach($filterOptions['statuses'] as $status)<option value="{{ $statusLabels[$status] ?? $status }}" @selected($filters['status'] === $status)>{{ $statusLabels[$status] ?? $status }}</option>@endforeach</select>
|
||||
<select name="payment_status"><option value="">全部支付状态</option>@foreach($filterOptions['paymentStatuses'] as $paymentStatus)<option value="{{ $paymentStatusLabels[$paymentStatus] ?? $paymentStatus }}" @selected($filters['payment_status'] === $paymentStatus)>{{ $paymentStatusLabels[$paymentStatus] ?? $paymentStatus }}</option>@endforeach</select>
|
||||
<select name="platform"><option value="">全部平台</option>@foreach($filterOptions['platforms'] as $platform)<option value="{{ $platformLabels[$platform] ?? $platform }}" @selected($filters['platform'] === $platform)>{{ $platformLabels[$platform] ?? $platform }}</option>@endforeach</select>
|
||||
<select name="device_type"><option value="">全部设备类型</option>@foreach($filterOptions['deviceTypes'] as $deviceType)<option value="{{ $deviceType }}" @selected($filters['device_type'] === $deviceType)>{{ $deviceTypeLabels[$deviceType] ?? $deviceType }}</option>@endforeach</select>
|
||||
<select name="payment_channel"><option value="">全部支付渠道</option>@foreach($filterOptions['paymentChannels'] as $paymentChannel)<option value="{{ $paymentChannel }}" @selected($filters['payment_channel'] === $paymentChannel)>{{ $paymentChannelLabels[$paymentChannel] ?? $paymentChannel }}</option>@endforeach</select>
|
||||
<input name="keyword" placeholder="订单号 / 买家 / 手机 / 邮箱" value="{{ $filters['keyword'] }}">
|
||||
<select name="time_range">@foreach($filterOptions['timeRanges'] as $timeRangeValue => $timeRangeLabel)<option value="{{ $timeRangeValue }}" @selected(($filters['time_range'] ?? 'all') === $timeRangeValue)>{{ $timeRangeLabel }}</option>@endforeach</select>
|
||||
<input type="date" name="start_date" value="{{ $filters['start_date'] }}">
|
||||
<input type="date" name="end_date" value="{{ $filters['end_date'] }}">
|
||||
<input type="number" step="0.01" min="0" name="min_pay_amount" placeholder="最低实付金额" value="{{ $filters['min_pay_amount'] }}">
|
||||
<input type="number" step="0.01" min="0" name="max_pay_amount" placeholder="最高实付金额" value="{{ $filters['max_pay_amount'] }}">
|
||||
<select name="sort">@foreach($filterOptions['sortOptions'] as $sortValue => $sortLabel)<option value="{{ $sortValue }}" @selected(($filters['sort'] ?? 'latest') === $sortValue)>{{ $sortLabel }}</option>@endforeach</select>
|
||||
<div class="actions"><button type="submit">筛选</button><a href="/merchant-admin/orders">重置</a></div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card section-dark mb-20"><h3 class="mt-0">当前筛选摘要</h3><div class="grid-3">@foreach($activeFilterSummary as $summaryLabel => $summaryValue)<div class="summary-box"><div class="muted">{{ $summaryLabel }}</div><strong class="num-md">{{ $summaryValue }}</strong></div>@endforeach</div></div>
|
||||
|
||||
<div class="card mb-20"><h3 class="mt-0">订单汇总</h3><div class="grid-5"><div class="stat-box-light"><div class="muted">订单总数</div><strong class="num-md">{{ $summaryStats['total_orders'] ?? 0 }}</strong></div><div class="stat-box-light"><div class="muted">实付总额</div><strong class="num-md">¥{{ number_format($summaryStats['total_pay_amount'] ?? 0, 2) }}</strong></div><div class="stat-box-light"><div class="muted">平均客单价</div><strong class="num-md">¥{{ number_format($summaryStats['average_order_amount'] ?? 0, 2) }}</strong></div><div class="stat-box-light"><div class="muted">待支付金额</div><strong class="num-md">¥{{ number_format($summaryStats['unpaid_pay_amount'] ?? 0, 2) }}</strong></div><div class="stat-box-light"><div class="muted">已支付金额</div><strong class="num-md">¥{{ number_format($summaryStats['paid_pay_amount'] ?? 0, 2) }}</strong></div><div class="stat-box-light"><div class="muted">已支付订单数</div><strong class="num-md">{{ $summaryStats['paid_orders'] ?? 0 }}</strong></div><div class="stat-box-light"><div class="muted">支付率</div><strong class="num-md">{{ number_format($summaryStats['payment_rate'] ?? 0, 2) }}%</strong></div><div class="stat-box-light"><div class="muted">退款订单数</div><strong class="num-md">{{ $summaryStats['refunded_orders'] ?? 0 }}</strong></div><div class="stat-box-light"><div class="muted">退款率</div><strong class="num-md">{{ number_format($summaryStats['refund_rate'] ?? 0, 2) }}%</strong></div><div class="stat-box-light"><div class="muted">待发货订单数</div><strong class="num-md">{{ $summaryStats['pending_shipment_orders'] ?? 0 }}</strong></div><div class="stat-box-light"><div class="muted">完成率</div><strong class="num-md">{{ number_format($summaryStats['completion_rate'] ?? 0, 2) }}%</strong></div><div class="stat-box-light"><div class="muted">支付失败订单</div><strong class="num-md">{{ $summaryStats['failed_payment_orders'] ?? 0 }}</strong></div><div class="stat-box-light"><div class="muted">已取消订单</div><strong class="num-md">{{ $summaryStats['cancelled_orders'] ?? 0 }}</strong></div><div class="stat-box-light"><div class="muted">取消率</div><strong class="num-md">{{ number_format($summaryStats['cancellation_rate'] ?? 0, 2) }}%</strong></div></div></div>
|
||||
|
||||
<div class="card mb-20"><h3 class="mt-0">时间趋势指标</h3><div class="grid-5"><div class="stat-box-light"><div class="muted">今日订单数</div><strong class="num-md">{{ $trendStats['today_orders'] ?? 0 }}</strong></div><div class="stat-box-light"><div class="muted">今日实付金额</div><strong class="num-md">¥{{ number_format($trendStats['today_pay_amount'] ?? 0, 2) }}</strong></div><div class="stat-box-light"><div class="muted">近7天订单数</div><strong class="num-md">{{ $trendStats['last_7_days_orders'] ?? 0 }}</strong></div><div class="stat-box-light"><div class="muted">近7天实付金额</div><strong class="num-md">¥{{ number_format($trendStats['last_7_days_pay_amount'] ?? 0, 2) }}</strong></div></div></div>
|
||||
|
||||
<div class="card section-dark card-spaced">
|
||||
<h3 class="mt-0">运营关注项</h3>
|
||||
<div class="grid-3">
|
||||
<div class="focus-box">
|
||||
<div class="muted">订单盘面</div>
|
||||
<strong class="text-md">{{ $operationsFocus['headline'] ?? '当前商家订单运营信息已就绪。' }}</strong>
|
||||
<div class="muted mt-8">建议动作</div>
|
||||
<div class="inline-links mt-6">
|
||||
@foreach(($operationsFocus['actions'] ?? []) as $action)
|
||||
<a href="{{ $action['url'] }}">{{ $action['label'] }}</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="focus-box">
|
||||
<div class="muted">当前信号</div>
|
||||
<div class="grid-3 mt-8">
|
||||
@foreach(($operationsFocus['signals'] ?? []) as $label => $value)
|
||||
<div class="summary-box"><div class="muted">{{ $label }}</div><strong class="num-sm">{{ $value }}</strong></div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="focus-box">
|
||||
<div class="muted">工作台导航</div>
|
||||
<div class="inline-links mt-8">
|
||||
@foreach(($operationsFocus['workbench'] ?? []) as $label => $url)
|
||||
<a href="{{ $url }}">{{ $label }}</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mb-20">
|
||||
<h3 class="mt-0">状态统计</h3>
|
||||
@php
|
||||
$baseQuery = [
|
||||
'payment_status' => $filters['payment_status'] ?: null,
|
||||
'platform' => $filters['platform'] ?: null,
|
||||
'device_type' => $filters['device_type'] ?: null,
|
||||
'payment_channel' => $filters['payment_channel'] ?: null,
|
||||
'keyword' => $filters['keyword'] ?: null,
|
||||
'start_date' => $filters['start_date'] ?: null,
|
||||
'end_date' => $filters['end_date'] ?: null,
|
||||
'min_pay_amount' => $filters['min_pay_amount'] ?: null,
|
||||
'max_pay_amount' => $filters['max_pay_amount'] ?: null,
|
||||
'time_range' => ($filters['time_range'] ?? 'all') !== 'all' ? $filters['time_range'] : null,
|
||||
'sort' => ($filters['sort'] ?? 'latest') !== 'latest' ? $filters['sort'] : null,
|
||||
];
|
||||
@endphp
|
||||
<div class="grid-6">
|
||||
<a href="{{ '/merchant-admin/orders?' . http_build_query(array_filter($baseQuery, fn ($value) => $value !== null && $value !== '')) }}" class="status-link-light {{ $filters['status'] === '' ? 'is-active-light' : '' }}"><div class="muted">全部</div><strong class="num-md">{{ $statusStats['all'] ?? 0 }}</strong></a>
|
||||
@foreach($filterOptions['statuses'] as $status)
|
||||
<a href="{{ '/merchant-admin/orders?' . http_build_query(array_filter(array_merge($baseQuery, ['status' => $status]), fn ($value) => $value !== null && $value !== '')) }}" class="status-link-light {{ $filters['status'] === $status ? 'is-active-light' : '' }}"><div class="muted">{{ $statusLabels[$status] ?? $status }}</div><strong class="num-md">{{ $statusStats[$status] ?? 0 }}</strong></a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>订单列表</h3>
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>订单号</th><th>平台</th><th>买家</th><th>支付</th><th>金额</th><th>创建时间</th><th>支付时间</th><th>发货时间</th><th>完成时间</th><th>状态</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($orders as $order)
|
||||
<tr>
|
||||
<td>{{ $order->id }}</td><td><a href="/merchant-admin/orders/{{ $order->id }}">{{ $order->order_no }}</a></td><td>{{ $platformLabels[$order->platform] ?? $order->platform }}</td><td>{{ $order->buyer_name }}</td><td><div>{{ $paymentChannelLabels[$order->payment_channel] ?? $order->payment_channel }}</div><div class="muted">{{ $paymentStatusLabels[$order->payment_status] ?? $order->payment_status }}</div></td><td>¥{{ number_format($order->pay_amount, 2) }}</td><td>{{ $order->created_at?->format('Y-m-d H:i') }}</td><td>{{ $order->paid_at?->format('Y-m-d H:i') ?? '-' }}</td><td>{{ $order->shipped_at?->format('Y-m-d H:i') ?? '-' }}</td><td>{{ $order->completed_at?->format('Y-m-d H:i') ?? '-' }}</td><td>{{ $statusLabels[$order->status] ?? $order->status }}</td><td><form class="inline" method="post" action="/merchant-admin/orders/{{ $order->id }}/status">@csrf <select name="status"><option value="pending" @selected($order->status==='pending')>待处理</option><option value="paid" @selected($order->status==='paid')>已支付</option><option value="shipped" @selected($order->status==='shipped')>已发货</option><option value="completed" @selected($order->status==='completed')>已完成</option><option value="cancelled" @selected($order->status==='cancelled')>已取消</option></select><button type="submit">更新</button></form></td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="12" class="muted text-center">暂无订单</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pagination-wrap">{{ $orders->links() }}</div>
|
||||
@endsection
|
||||
46
resources/views/merchant_admin/orders/show.blade.php
Normal file
46
resources/views/merchant_admin/orders/show.blade.php
Normal file
@@ -0,0 +1,46 @@
|
||||
@extends('merchant_admin.layouts.app')
|
||||
|
||||
@section('title', '商家订单详情')
|
||||
@section('page_title', '商家订单详情')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<h3>订单 {{ $order->order_no }}</h3>
|
||||
<table>
|
||||
<tr><th>ID</th><td>{{ $order->id }}</td></tr>
|
||||
<tr><th>平台</th><td>{{ $order->platform }}</td></tr>
|
||||
<tr><th>订单状态</th><td>{{ $order->status }}</td></tr>
|
||||
<tr><th>支付渠道</th><td>{{ $order->payment_channel }}</td></tr>
|
||||
<tr><th>支付状态</th><td>{{ $order->payment_status }}</td></tr>
|
||||
<tr><th>买家</th><td>{{ $order->buyer_name }}</td></tr>
|
||||
<tr><th>手机</th><td>{{ $order->buyer_phone }}</td></tr>
|
||||
<tr><th>邮箱</th><td>{{ $order->buyer_email }}</td></tr>
|
||||
<tr><th>应付金额</th><td>¥{{ number_format($order->pay_amount, 2) }}</td></tr>
|
||||
<tr><th>备注</th><td>{{ $order->remark }}</td></tr>
|
||||
<tr><th>创建时间</th><td>{{ $order->created_at }}</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>订单明细</h3>
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>商品</th><th>SKU</th><th>单价</th><th>数量</th><th>小计</th><th>快照</th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($order->items as $item)
|
||||
<tr>
|
||||
<td>{{ $item->id }}</td>
|
||||
<td>{{ $item->product_title }}</td>
|
||||
<td>{{ $item->product_sku }}</td>
|
||||
<td>¥{{ number_format($item->product_price, 2) }}</td>
|
||||
<td>{{ $item->quantity }}</td>
|
||||
<td>¥{{ number_format($item->line_total_amount, 2) }}</td>
|
||||
<td>@if(!empty($item->snapshot)) 分类:{{ $item->snapshot['category'] ?? '-' }} @else - @endif</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="7" class="muted">暂无订单明细</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
<p class="mt-16"><a href="/merchant-admin/orders">返回订单列表</a></p>
|
||||
</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,48 @@
|
||||
@extends('merchant_admin.layouts.app')
|
||||
|
||||
@section('title', '商家商品分类')
|
||||
@section('page_title', '商家商品分类')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<p class="muted muted-tight">这里是当前商家的商品分类管理入口,分类数据天然受商家作用域限制。</p>
|
||||
<p class="muted">当前分类列表已接入缓存:{{ $cacheMeta['store'] }} / TTL {{ $cacheMeta['ttl'] }}。</p>
|
||||
<h3>新增分类</h3>
|
||||
<form method="post" action="/merchant-admin/product-categories">
|
||||
@csrf
|
||||
<div class="grid-3">
|
||||
<input name="name" placeholder="分类名称" value="{{ old('name') }}">
|
||||
<input name="slug" placeholder="slug" value="{{ old('slug') }}">
|
||||
<select name="status">
|
||||
<option value="active" @selected(old('status', 'active') === 'active')>active</option>
|
||||
<option value="inactive" @selected(old('status') === 'inactive')>inactive</option>
|
||||
</select>
|
||||
<input name="sort" placeholder="排序" value="{{ old('sort', 0) }}">
|
||||
<input name="description" placeholder="分类说明" class="span-2" value="{{ old('description') }}">
|
||||
</div>
|
||||
<div class="mt-12"><button type="submit">创建分类</button></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>分类列表</h3>
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>名称</th><th>Slug</th><th>状态</th><th>排序</th><th>说明</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
@foreach($categories as $category)
|
||||
<tr>
|
||||
<td>{{ $category->id }}</td>
|
||||
<td><form method="post" action="/merchant-admin/product-categories/{{ $category->id }}" class="actions">@csrf <input name="name" value="{{ $category->name }}"></td>
|
||||
<td><input name="slug" value="{{ $category->slug }}"></td>
|
||||
<td><select name="status"><option value="active" @selected($category->status==='active')>active</option><option value="inactive" @selected($category->status==='inactive')>inactive</option></select></td>
|
||||
<td><input name="sort" value="{{ $category->sort }}" class="w-90"></td>
|
||||
<td><input name="description" value="{{ $category->description }}"></td>
|
||||
<td><button type="submit">更新</button></form><form method="post" action="/merchant-admin/product-categories/{{ $category->id }}/delete" class="inline" onsubmit="return confirm('确定删除这个分类?');">@csrf <button type="submit">删除</button></form></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pagination-wrap">{{ $categories->links() }}</div>
|
||||
@endsection
|
||||
@@ -0,0 +1,101 @@
|
||||
@extends('merchant_admin.layouts.app')
|
||||
|
||||
@section('title', '商家商品导入历史')
|
||||
@section('page_title', '商家商品导入历史')
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
<p class="muted muted-tight">这里集中查看当前商家范围内的商品导入历史,便于复盘成功率、失败批次与 failure CSV 保留情况。</p>
|
||||
|
||||
@php
|
||||
$exportHistoryQuery = http_build_query(array_filter([
|
||||
'import_result_status' => $importHistoryFilters['result_status'] ?? 'all',
|
||||
'import_time_range' => $importHistoryFilters['time_range'] ?? 'all',
|
||||
'start_date' => $importHistoryFilters['start_date'] ?? '',
|
||||
'end_date' => $importHistoryFilters['end_date'] ?? '',
|
||||
'import_sort' => $importHistoryFilters['sort'] ?? 'latest',
|
||||
], fn ($value, $key) => match ($key) {
|
||||
'start_date', 'end_date' => $value !== null && $value !== '',
|
||||
default => $value !== null && $value !== '' && $value !== 'all' && $value !== 'latest',
|
||||
}, ARRAY_FILTER_USE_BOTH));
|
||||
@endphp
|
||||
<form method="get" action="/merchant-admin/products/import-histories" class="mb-12">
|
||||
<div class="actions">
|
||||
<select name="import_result_status">
|
||||
<option value="all" @selected(($importHistoryFilters['result_status'] ?? 'all') === 'all')>全部导入结果</option>
|
||||
<option value="success_only" @selected(($importHistoryFilters['result_status'] ?? 'all') === 'success_only')>仅看成功导入</option>
|
||||
<option value="has_failures" @selected(($importHistoryFilters['result_status'] ?? 'all') === 'has_failures')>仅看含失败导入</option>
|
||||
</select>
|
||||
<select name="import_time_range">
|
||||
<option value="all" @selected(($importHistoryFilters['time_range'] ?? 'all') === 'all')>全部时间</option>
|
||||
<option value="today" @selected(($importHistoryFilters['time_range'] ?? 'all') === 'today')>今天</option>
|
||||
<option value="last_7_days" @selected(($importHistoryFilters['time_range'] ?? 'all') === 'last_7_days')>近7天</option>
|
||||
<option value="custom" @selected(($importHistoryFilters['time_range'] ?? 'all') === 'custom')>自定义区间</option>
|
||||
</select>
|
||||
<input type="date" name="start_date" value="{{ $importHistoryFilters['start_date'] ?? '' }}">
|
||||
<span class="muted">至</span>
|
||||
<input type="date" name="end_date" value="{{ $importHistoryFilters['end_date'] ?? '' }}">
|
||||
<select name="import_sort">
|
||||
@foreach(($importHistoryFilterOptions['sorts'] ?? []) as $sortValue => $sortLabel)
|
||||
<option value="{{ $sortValue }}" @selected(($importHistoryFilters['sort'] ?? 'latest') === $sortValue)>{{ $sortLabel }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<button type="submit">筛选导入历史</button>
|
||||
<a href="{{ '/merchant-admin/products/import-histories/export' . ($exportHistoryQuery ? ('?' . $exportHistoryQuery) : '') }}">导出当前筛选 CSV</a>
|
||||
<a href="/merchant-admin/products/import-histories">清空筛选</a>
|
||||
<a href="/merchant-admin/products">返回商品页</a>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@if(!empty($importHistoryFilters['date_errors']))
|
||||
<div class="info-warning">
|
||||
@foreach($importHistoryFilters['date_errors'] as $dateError)
|
||||
<div>{{ $dateError }}</div>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="grid-4 mb-12">
|
||||
<div class="stat-box"><div class="muted">累计导入批次</div><strong class="num-sm">{{ $importHistoryStats['total_imports'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box"><div class="muted">累计成功商品</div><strong class="num-sm">{{ $importHistoryStats['total_success'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box"><div class="muted">累计失败商品</div><strong class="num-sm">{{ $importHistoryStats['total_failed'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box"><div class="muted">含失败批次</div><strong class="num-sm">{{ $importHistoryStats['warning_imports'] ?? 0 }}</strong></div>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>导入时间</th><th>上传文件</th><th>结果</th><th>成功</th><th>失败</th><th>操作者</th><th>失败明细</th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($importHistories as $history)
|
||||
<tr>
|
||||
<td>{{ $history->id }}</td>
|
||||
<td>{{ $history->imported_at?->format('Y-m-d H:i:s') }}</td>
|
||||
<td>{{ $history->file_name }}</td>
|
||||
<td>
|
||||
@if(($history->failed_count ?? 0) > 0)
|
||||
<span class="result-warning">部分失败</span>
|
||||
@else
|
||||
<span class="result-success">成功</span>
|
||||
@endif
|
||||
</td>
|
||||
<td>{{ $history->success_count }}</td>
|
||||
<td>{{ $history->failed_count }}</td>
|
||||
<td>{{ $history->admin?->name ?? '-' }}</td>
|
||||
<td>
|
||||
@if($history->failure_file && ($history->failure_file_available ?? false))
|
||||
<a href="/merchant-admin/products/import-failures/{{ $history->failure_file }}">下载 failure CSV</a>
|
||||
@elseif($history->failure_file)
|
||||
<span class="muted">文件已过保留期</span>
|
||||
@else
|
||||
<span class="muted">无</span>
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="8" class="muted">暂无导入记录</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pagination-wrap">{{ $importHistories->links() }}</div>
|
||||
</div>
|
||||
@endsection
|
||||
203
resources/views/merchant_admin/products/index.blade.php
Normal file
203
resources/views/merchant_admin/products/index.blade.php
Normal file
@@ -0,0 +1,203 @@
|
||||
@extends('merchant_admin.layouts.app')
|
||||
|
||||
@section('title', '商家商品管理')
|
||||
@section('page_title', '商家商品管理')
|
||||
|
||||
@section('content')
|
||||
<div class="card mb-20">
|
||||
<p class="muted muted-tight">当前商品列表已按登录商家过滤,新增、编辑、删除商品都会限制在当前商家作用域内。</p>
|
||||
<p class="muted">当前已接入缓存:商品列表使用 {{ $cacheMeta['store'] }},TTL {{ $cacheMeta['ttl'] }};商品变更后会自动失效。</p>
|
||||
@php
|
||||
$exportQuery = http_build_query(array_filter($filters, fn ($value) => $value !== null && $value !== ''));
|
||||
@endphp
|
||||
<div class="card my-16">
|
||||
<h3 class="mt-0">批量导入商品</h3>
|
||||
<p class="muted">先下载模板,按模板填写后上传 CSV。商家模板不需要填写 `merchant_id`,系统会自动归属到当前登录商家。</p>
|
||||
<div class="card-link-list mb-12">
|
||||
<a href="/merchant-admin/products/import-template">下载导入模板</a>
|
||||
<a href="{{ '/merchant-admin/products/export' . ($exportQuery ? ('?' . $exportQuery) : '') }}">导出当前筛选结果 CSV</a>
|
||||
<a href="/merchant-admin/products/import-histories">查看独立导入历史页</a>
|
||||
</div>
|
||||
@if(session('import_result'))
|
||||
@php $importResult = session('import_result'); @endphp
|
||||
<div class="card section-dark mb-12">
|
||||
<div><strong>导入结果:</strong>成功 {{ $importResult['success'] ?? 0 }} 条,失败 {{ $importResult['failed'] ?? 0 }} 条。</div>
|
||||
@if(!empty($importResult['messages']))
|
||||
<div class="muted mt-8">失败原因预览:</div>
|
||||
<ul class="list-indent">@foreach($importResult['messages'] as $message)<li>{{ $message }}</li>@endforeach</ul>
|
||||
@endif
|
||||
@if(!empty($importResult['failure_file']))
|
||||
<div class="mt-10"><a href="/merchant-admin/products/import-failures/{{ $importResult['failure_file'] }}">下载失败明细 CSV</a></div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
<form method="post" action="/merchant-admin/products/import" enctype="multipart/form-data">@csrf <div class="actions"><input type="file" name="import_file" accept=".csv,text/csv"><button type="submit">开始导入</button></div></form>
|
||||
|
||||
<div class="mt-16">
|
||||
<div class="actions-spread mb-10">
|
||||
<div><h4 class="mb-6">导入历史摘要</h4><div class="muted">商品页仅保留摘要与最近 5 条记录;完整筛选、分页与复盘请前往独立导入历史页。</div></div>
|
||||
<a href="/merchant-admin/products/import-histories">进入完整导入历史页</a>
|
||||
</div>
|
||||
<div class="grid-4 mb-12">
|
||||
<div class="stat-box"><div class="muted">累计导入批次</div><strong class="num-sm">{{ $importHistoryStats['total_imports'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box"><div class="muted">累计成功商品</div><strong class="num-sm">{{ $importHistoryStats['total_success'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box"><div class="muted">累计失败商品</div><strong class="num-sm">{{ $importHistoryStats['total_failed'] ?? 0 }}</strong></div>
|
||||
<div class="stat-box"><div class="muted">含失败批次</div><strong class="num-sm">{{ $importHistoryStats['warning_imports'] ?? 0 }}</strong></div>
|
||||
</div>
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>导入时间</th><th>上传文件</th><th>结果</th><th>失败明细</th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($importHistories->take(5) as $history)
|
||||
<tr>
|
||||
<td>{{ $history->id }}</td><td>{{ $history->imported_at?->format('Y-m-d H:i:s') }}</td><td>{{ $history->file_name }}</td><td>@if(($history->failed_count ?? 0) > 0)<span class="result-warning">部分失败({{ $history->failed_count }})</span>@else<span class="result-success">成功</span>@endif</td>
|
||||
<td>@if($history->failure_file && ($history->failure_file_available ?? false))<a href="/merchant-admin/products/import-failures/{{ $history->failure_file }}">下载</a>@elseif($history->failure_file)<span class="muted">已过保留期</span>@else<span class="muted">无</span>@endif</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="5" class="muted">暂无导入记录</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<h3>商品分类</h3>
|
||||
<table class="mb-20">
|
||||
<thead><tr><th>ID</th><th>分类名称</th><th>Slug</th><th>状态</th><th>排序</th><th>说明</th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($categories as $category)
|
||||
<tr><td>{{ $category->id }}</td><td>{{ $category->name }}</td><td>{{ $category->slug }}</td><td>{{ $category->status }}</td><td>{{ $category->sort }}</td><td>{{ $category->description }}</td></tr>
|
||||
@empty
|
||||
<tr><td colspan="6" class="muted">暂无分类</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>筛选条件</h3>
|
||||
<form method="get" action="/merchant-admin/products" class="mb-20">
|
||||
<div class="grid-4">
|
||||
<select name="category_id"><option value="">全部分类</option>@foreach($categories as $category)<option value="{{ $category->id }}" @selected($filters['category_id'] === (string) $category->id)>{{ $category->name }}</option>@endforeach</select>
|
||||
<select name="status"><option value="">全部状态</option>@foreach($filterOptions['statuses'] as $status)<option value="{{ $statusLabels[$status] ?? $status }}" @selected($filters['status'] === $status)>{{ $statusLabels[$status] ?? $status }}</option>@endforeach</select>
|
||||
<input name="keyword" placeholder="标题 / slug / SKU" value="{{ $filters['keyword'] }}">
|
||||
<input type="number" step="0.01" min="0" name="min_price" placeholder="最低价格" value="{{ $filters['min_price'] }}">
|
||||
<input type="number" step="0.01" min="0" name="max_price" placeholder="最高价格" value="{{ $filters['max_price'] }}">
|
||||
<input type="number" min="0" name="min_stock" placeholder="最低库存" value="{{ $filters['min_stock'] }}">
|
||||
<input type="number" min="0" name="max_stock" placeholder="最高库存" value="{{ $filters['max_stock'] }}">
|
||||
<select name="sort">@foreach($filterOptions['sortOptions'] as $sortValue => $sortLabel)<option value="{{ $sortValue }}" @selected(($filters['sort'] ?? 'latest') === $sortValue)>{{ $sortLabel }}</option>@endforeach</select>
|
||||
</div>
|
||||
<div class="mt-12 actions"><button type="submit">筛选</button><a href="/merchant-admin/products">重置</a></div>
|
||||
</form>
|
||||
|
||||
<div class="card section-dark my-20"><h3 class="mt-0">当前筛选摘要</h3><div class="grid-4">@foreach($activeFilterSummary as $summaryLabel => $summaryValue)<div class="summary-box"><div class="muted">{{ $summaryLabel }}</div><strong class="num-md">{{ $summaryValue }}</strong></div>@endforeach</div></div>
|
||||
|
||||
<div class="card my-20"><h3 class="mt-0">商品运营汇总</h3><div class="grid-4"><div class="stat-box"><div class="muted">商品总数</div><strong class="num-md">{{ $summaryStats['total_products'] ?? 0 }}</strong></div><div class="stat-box"><div class="muted">总库存</div><strong class="num-md">{{ $summaryStats['total_stock'] ?? 0 }}</strong></div><div class="stat-box"><div class="muted">总货值</div><strong class="num-md">¥{{ number_format($summaryStats['total_stock_value'] ?? 0, 2) }}</strong></div><div class="stat-box"><div class="muted">平均售价</div><strong class="num-md">¥{{ number_format($summaryStats['average_price'] ?? 0, 2) }}</strong></div></div></div>
|
||||
|
||||
<div class="card section-dark card-spaced">
|
||||
<h3 class="mt-0">运营关注项</h3>
|
||||
<div class="grid-3">
|
||||
<div class="focus-box">
|
||||
<div class="muted">商品盘面</div>
|
||||
<strong class="text-md">{{ $operationsFocus['headline'] ?? '当前商家商品运营信息已就绪。' }}</strong>
|
||||
<div class="muted mt-8">建议动作</div>
|
||||
<div class="inline-links mt-6">
|
||||
@foreach(($operationsFocus['actions'] ?? []) as $action)
|
||||
<a href="{{ $action['url'] }}">{{ $action['label'] }}</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="focus-box">
|
||||
<div class="muted">当前信号</div>
|
||||
<div class="grid-3 mt-8">
|
||||
@foreach(($operationsFocus['signals'] ?? []) as $label => $value)
|
||||
<div class="summary-box"><div class="muted">{{ $label }}</div><strong class="num-sm">{{ $value }}</strong></div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="focus-box">
|
||||
<div class="muted">工作台导航</div>
|
||||
<div class="inline-links mt-8">
|
||||
@foreach(($operationsFocus['workbench'] ?? []) as $label => $url)
|
||||
<a href="{{ $url }}">{{ $label }}</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card section-dark my-20">
|
||||
<h3 class="mt-0">商品状态统计</h3>
|
||||
@php
|
||||
$productBaseQuery = [
|
||||
'category_id' => $filters['category_id'] ?: null,
|
||||
'keyword' => $filters['keyword'] ?: null,
|
||||
'min_price' => $filters['min_price'] ?: null,
|
||||
'max_price' => $filters['max_price'] ?: null,
|
||||
'min_stock' => $filters['min_stock'] ?: null,
|
||||
'max_stock' => $filters['max_stock'] ?: null,
|
||||
'sort' => $filters['sort'] ?: 'latest',
|
||||
];
|
||||
@endphp
|
||||
<div class="grid-4">
|
||||
<a href="{{ '/merchant-admin/products?' . http_build_query(array_filter($productBaseQuery, fn ($value) => $value !== null && $value !== '')) }}" class="status-link {{ $filters['status'] === '' ? 'is-active-dark' : '' }}"><div class="muted">全部</div><strong class="num-md">{{ $statusStats['all'] ?? 0 }}</strong></a>
|
||||
@foreach($filterOptions['statuses'] as $status)
|
||||
<a href="{{ '/merchant-admin/products?' . http_build_query(array_filter(array_merge($productBaseQuery, ['status' => $status]), fn ($value) => $value !== null && $value !== '')) }}" class="status-link {{ $filters['status'] === $status ? 'is-active-dark' : '' }}"><div class="muted">{{ $statusLabels[$status] ?? $status }}</div><strong class="num-md">{{ $statusStats[$status] ?? 0 }}</strong></a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>新增商品</h3>
|
||||
<form method="post" action="/merchant-admin/products">
|
||||
@csrf
|
||||
<div class="grid-3">
|
||||
<input name="title" placeholder="商品标题" value="{{ old('title') }}">
|
||||
<input name="slug" placeholder="slug" value="{{ old('slug') }}">
|
||||
<input name="sku" placeholder="SKU" value="{{ old('sku') }}">
|
||||
<select name="category_id"><option value="">选择分类(可留空)</option>@foreach($categories as $category)<option value="{{ $category->id }}" @selected((string) old('category_id') === (string) $category->id)>{{ $category->name }}</option>@endforeach</select>
|
||||
<input name="price" placeholder="价格" value="{{ old('price') }}">
|
||||
<input name="stock" placeholder="库存" value="{{ old('stock') }}">
|
||||
<input name="summary" placeholder="简介" value="{{ old('summary') }}">
|
||||
</div>
|
||||
<div class="mt-12"><button type="submit">创建商品</button></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>商品列表</h3>
|
||||
<form id="merchant-batch-form" method="post" action="/merchant-admin/products/batch" onsubmit="return confirm('确认执行本次批量操作?');">@csrf <input type="hidden" name="return_url" value="{{ request()->fullUrl() }}"><div class="actions mb-12"><select name="action"><option value="change_status">批量改状态</option><option value="change_category">批量改分类</option></select><select name="status"><option value="">选择状态(用于批量改状态)</option>@foreach($filterOptions['statuses'] as $status)<option value="{{ $statusLabels[$status] ?? $status }}">{{ $statusLabels[$status] ?? $status }}</option>@endforeach</select><select name="category_id"><option value="">清空分类 / 不指定(用于批量改分类)</option>@foreach($categories as $category)<option value="{{ $category->id }}">{{ $category->name }}</option>@endforeach</select><button type="submit">执行批量操作</button></div></form>
|
||||
<p class="muted muted-tight">批量操作只会作用于当前登录商家可见商品,越权或已删除数据会被拦截。</p>
|
||||
<table>
|
||||
<thead><tr><th><input type="checkbox" data-check-all="merchant-products"></th><th>ID</th><th>标题</th><th>分类</th><th>SKU</th><th>售价/原价</th><th>库存</th><th>创建时间</th><th>更新时间</th><th>状态</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
@foreach($products as $product)
|
||||
<tr>
|
||||
<td><input type="checkbox" name="product_ids[]" value="{{ $product->id }}" data-check-item="merchant-products" form="merchant-batch-form"></td>
|
||||
<td>{{ $product->id }}</td>
|
||||
<td><form method="post" action="/merchant-admin/products/{{ $product->id }}" class="actions">@csrf <input name="title" value="{{ $product->title }}"></td>
|
||||
<td><select name="category_id"><option value="">未分类</option>@foreach($categories as $category)<option value="{{ $category->id }}" @selected($product->category_id === $category->id)>{{ $category->name }}</option>@endforeach</select></td>
|
||||
<td><input name="slug" value="{{ $product->slug }}" placeholder="slug" class="w-140 block mb-6"><input name="sku" value="{{ $product->sku }}" placeholder="SKU" class="w-140"></td>
|
||||
<td><input name="price" value="{{ $product->price }}" class="w-90 block mb-6"><div class="muted">原价:¥{{ number_format((float) $product->original_price, 2) }}</div></td>
|
||||
<td><input name="stock" value="{{ $product->stock }}" class="w-90"></td>
|
||||
<td>{{ $product->created_at?->format('Y-m-d H:i') }}</td>
|
||||
<td>{{ $product->updated_at?->format('Y-m-d H:i') }}</td>
|
||||
<td><select name="status"><option value="draft" @selected($product->status==='draft')>草稿</option><option value="published" @selected($product->status==='published')>已上架</option><option value="offline" @selected($product->status==='offline')>已下架</option></select></td>
|
||||
<td><button type="submit">更新</button></form><form method="post" action="/merchant-admin/products/{{ $product->id }}/delete" class="inline" onsubmit="return confirm('确定删除这个商品?');">@csrf <button type="submit">删除</button></form></td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const master = document.querySelector('[data-check-all="merchant-products"]');
|
||||
const items = document.querySelectorAll('[data-check-item="merchant-products"]');
|
||||
if (!master || !items.length) {
|
||||
return;
|
||||
}
|
||||
master.addEventListener('change', function () {
|
||||
items.forEach(function (item) {
|
||||
item.checked = master.checked;
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="pagination-wrap">{{ $products->links() }}</div>
|
||||
@endsection
|
||||
30
resources/views/merchant_admin/users/index.blade.php
Normal file
30
resources/views/merchant_admin/users/index.blade.php
Normal file
@@ -0,0 +1,30 @@
|
||||
@extends('merchant_admin.layouts.app')
|
||||
|
||||
@section('title', '商家用户管理')
|
||||
@section('page_title', '商家用户管理')
|
||||
|
||||
@section('content')
|
||||
<div class="card">
|
||||
<p class="muted muted-tight">当前用户列表已按登录商家过滤,用于查看商家侧用户资产和注册来源。</p>
|
||||
<p class="muted">当前用户列表已接入缓存:{{ $cacheMeta['store'] }} / TTL {{ $cacheMeta['ttl'] }}。</p>
|
||||
<h3>用户列表</h3>
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>姓名</th><th>邮箱</th><th>手机</th><th>状态</th><th>注册来源</th><th>最近登录来源</th></tr></thead>
|
||||
<tbody>
|
||||
@foreach($users as $user)
|
||||
<tr>
|
||||
<td>{{ $user->id }}</td>
|
||||
<td>{{ $user->name }}</td>
|
||||
<td>{{ $user->email }}</td>
|
||||
<td>{{ $user->phone }}</td>
|
||||
<td>{{ $user->status }}</td>
|
||||
<td>{{ $user->register_source }}</td>
|
||||
<td>{{ $user->last_login_source }}</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="pagination-wrap">{{ $users->links() }}</div>
|
||||
@endsection
|
||||
25
resources/views/site_admin/auth/login.blade.php
Normal file
25
resources/views/site_admin/auth/login.blade.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>站点后台登录 - SaaSShop</title>
|
||||
<link rel="stylesheet" href="/css/admin-base.css">
|
||||
<link rel="stylesheet" href="/css/site-admin.css">
|
||||
</head>
|
||||
<body class="login-page">
|
||||
<div class="login-card">
|
||||
<h1>站点后台登录</h1>
|
||||
<p class="muted">当前入口面向站点运营方。第一阶段复用已绑定商家的管理员账号作为站点管理员演示登录。</p>
|
||||
@if ($errors->any())
|
||||
<div class="error-inline">{{ $errors->first() }}</div>
|
||||
@endif
|
||||
<form class="login-form" method="post" action="/site-admin/login">
|
||||
@csrf
|
||||
<input type="email" name="email" placeholder="邮箱" value="{{ old('email', 'merchant.admin@demo.local') }}">
|
||||
<input type="password" name="password" placeholder="密码" value="Merchant@123456">
|
||||
<button type="submit">登录站点后台</button>
|
||||
</form>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
18
resources/views/site_admin/dashboard.blade.php
Normal file
18
resources/views/site_admin/dashboard.blade.php
Normal file
@@ -0,0 +1,18 @@
|
||||
@extends('site_admin.layouts.app')
|
||||
|
||||
@section('title', '站点后台仪表盘')
|
||||
@section('page_title', '站点后台仪表盘')
|
||||
|
||||
@section('content')
|
||||
<div class="card card-spaced">
|
||||
<p>当前站点:<strong>{{ $site->name }}</strong>({{ $site->slug }})。这里是站点运营视角后台,第一阶段先提供站点级总览、商品、订单与商家入口骨架。</p>
|
||||
<p class="muted mb-0">缓存状态:{{ $cacheMeta['store'] }} / TTL {{ $cacheMeta['ttl'] }}。</p>
|
||||
</div>
|
||||
<div class="grid">
|
||||
<div class="card"><div class="muted">站点管理员</div><div class="num">{{ $stats['admins'] }}</div></div>
|
||||
<div class="card"><div class="muted">站点用户</div><div class="num">{{ $stats['users'] }}</div></div>
|
||||
<div class="card"><div class="muted">站点商品</div><div class="num">{{ $stats['products'] }}</div></div>
|
||||
<div class="card"><div class="muted">站点订单</div><div class="num">{{ $stats['orders'] }}</div></div>
|
||||
<div class="card"><div class="muted">待处理订单</div><div class="num">{{ $stats['pending_orders'] }}</div></div>
|
||||
</div>
|
||||
@endsection
|
||||
61
resources/views/site_admin/layouts/app.blade.php
Normal file
61
resources/views/site_admin/layouts/app.blade.php
Normal file
@@ -0,0 +1,61 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>@yield('title', 'SaaSShop 站点后台')</title>
|
||||
<link rel="stylesheet" href="/css/admin-base.css">
|
||||
<link rel="stylesheet" href="/css/site-admin.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="layout">
|
||||
<aside class="sidebar">
|
||||
<h2 class="sidebar-brand">SaaSShop</h2>
|
||||
<div class="muted">站点后台 / Site Console</div>
|
||||
|
||||
<div class="group-title">总览</div>
|
||||
<a class="sidebar-link" href="/site-admin">站点仪表盘</a>
|
||||
|
||||
<div class="group-title">站点运营</div>
|
||||
<a class="sidebar-link" href="/site-admin/merchants">站点商家</a>
|
||||
<a class="sidebar-link" href="/site-admin/products">站点商品</a>
|
||||
<a class="sidebar-link" href="/site-admin/orders">站点订单</a>
|
||||
|
||||
<div class="group-title">切换入口</div>
|
||||
<a class="sidebar-link" href="/admin">总台管理</a>
|
||||
<a class="sidebar-link" href="/merchant-admin">商家后台</a>
|
||||
<a class="sidebar-link" href="/">返回首页</a>
|
||||
<form class="sidebar-logout" method="post" action="/site-admin/logout">
|
||||
@csrf
|
||||
<button type="submit">退出登录</button>
|
||||
</form>
|
||||
</aside>
|
||||
<main class="content">
|
||||
<div class="top">
|
||||
<div>
|
||||
<h1 class="page-title">@yield('page_title', '站点后台')</h1>
|
||||
<div class="muted">当前登录:{{ session('admin_name') }}({{ session('admin_email') }}) / 站点 ID:{{ session('admin_site_id') }}</div>
|
||||
<div class="badge badge-scope">当前作用域:站点管理员</div>
|
||||
</div>
|
||||
</div>
|
||||
@if(session('success'))
|
||||
<div class="flash">{{ session('success') }}</div>
|
||||
@endif
|
||||
@if(session('warning'))
|
||||
<div class="warning">{{ session('warning') }}</div>
|
||||
@endif
|
||||
@if($errors->any())
|
||||
<div class="error-box">
|
||||
<strong>提交失败:</strong>
|
||||
<ul class="ml-18">
|
||||
@foreach($errors->all() as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
@yield('content')
|
||||
</main>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
308
resources/views/site_admin/merchants/index.blade.php
Normal file
308
resources/views/site_admin/merchants/index.blade.php
Normal file
@@ -0,0 +1,308 @@
|
||||
@extends('site_admin.layouts.app')
|
||||
|
||||
@section('title', '站点商家')
|
||||
@section('page_title', '站点商家')
|
||||
|
||||
@section('content')
|
||||
@php
|
||||
$productWorkbenchUrl = '/site-admin/products?sort=stock_desc&status=published';
|
||||
$lowStockWorkbenchUrl = '/site-admin/products?sort=stock_asc&status=published';
|
||||
$latestProductWorkbenchUrl = '/site-admin/products?sort=latest';
|
||||
$orderWorkbenchUrl = '/site-admin/orders?sort=pay_amount_desc&payment_status=paid';
|
||||
$pendingOrderWorkbenchUrl = '/site-admin/orders?sort=latest&payment_status=unpaid';
|
||||
$failedOrderWorkbenchUrl = '/site-admin/orders?sort=latest&payment_status=failed';
|
||||
@endphp
|
||||
<div class="card card-spaced">
|
||||
<p class="muted mt-0">当前阶段继续复用 merchant 承接站点关系,但页面已先补最小运营查看能力,方便后续从“承接记录”平滑过渡到真实站点治理。</p>
|
||||
@php
|
||||
$merchantExportQuery = http_build_query(array_filter([
|
||||
'keyword' => $filters['keyword'] ?? '',
|
||||
'status' => $filters['status'] ?? '',
|
||||
'plan' => $filters['plan'] ?? '',
|
||||
'sort' => $filters['sort'] ?? 'latest',
|
||||
], fn ($value) => $value !== null && $value !== ''));
|
||||
@endphp
|
||||
<div class="mb-12">
|
||||
<a href="{{ '/site-admin/merchants/export' . ($merchantExportQuery ? ('?' . $merchantExportQuery) : '') }}">导出当前筛选结果 CSV</a>
|
||||
</div>
|
||||
<h3>筛选条件</h3>
|
||||
<form method="get" action="/site-admin/merchants">
|
||||
<div class="grid-4 form-grid grid-align-end">
|
||||
<input type="text" name="keyword" placeholder="站点名称 / slug / 联系人 / 手机 / 邮箱" value="{{ $filters['keyword'] ?? '' }}">
|
||||
<select name="status">
|
||||
<option value="">全部状态</option>
|
||||
@foreach(($filterOptions['statuses'] ?? []) as $status)
|
||||
<option value="{{ $status }}" @selected(($filters['status'] ?? '') === $status)>{{ $statusLabels[$status] ?? $status }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="plan">
|
||||
<option value="">全部套餐</option>
|
||||
@foreach(($filterOptions['plans'] ?? []) as $plan)
|
||||
<option value="{{ $plan }}" @selected(($filters['plan'] ?? '') === $plan)>{{ $planLabels[$plan] ?? $plan }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="sort">
|
||||
@foreach(($filterOptions['sortOptions'] ?? []) as $value => $label)
|
||||
<option value="{{ $value }}" @selected(($filters['sort'] ?? 'latest') === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
<div class="actions mt-12">
|
||||
<button type="submit">应用筛选</button>
|
||||
<a href="/site-admin/merchants">重置</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card card-dark card-spaced">
|
||||
<h3 class="mt-0">当前筛选摘要</h3>
|
||||
<div class="grid-4">
|
||||
@foreach(($activeFilterSummary ?? []) as $label => $value)
|
||||
<div class="summary-box">
|
||||
<div class="muted">{{ $label }}</div>
|
||||
<strong class="text-md">{{ $value }}</strong>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-spaced">
|
||||
<h3 class="mt-0">站点运营摘要</h3>
|
||||
<div class="grid-4">
|
||||
<div class="stat-box">
|
||||
<div class="muted">承接站点数</div>
|
||||
<strong class="num-md">{{ $summaryStats['site_count'] ?? 0 }}</strong>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="muted">启用中站点</div>
|
||||
<strong class="num-md">{{ $summaryStats['active_site_count'] ?? 0 }}</strong>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="muted">站点管理员数</div>
|
||||
<strong class="num-md">{{ $summaryStats['admin_count'] ?? 0 }}</strong>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="muted">站点用户数</div>
|
||||
<strong class="num-md">{{ $summaryStats['user_count'] ?? 0 }}</strong>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="muted">站点商品数</div>
|
||||
<strong class="num-md">{{ $summaryStats['product_count'] ?? 0 }}</strong>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="muted">站点订单数</div>
|
||||
<strong class="num-md">{{ $summaryStats['order_count'] ?? 0 }}</strong>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="muted">商品分类数</div>
|
||||
<strong class="num-md">{{ $summaryStats['category_count'] ?? 0 }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-dark card-spaced">
|
||||
<h3 class="mt-0">运营关注项</h3>
|
||||
<div class="grid-3">
|
||||
<div class="focus-box">
|
||||
<div class="muted">商品侧</div>
|
||||
<strong class="text-md">
|
||||
@if(($summaryStats['product_count'] ?? 0) <= 0)
|
||||
当前站点暂无商品,建议优先补齐基础商品数据。
|
||||
@elseif(($summaryStats['product_count'] ?? 0) < 3)
|
||||
当前站点商品仍较少,建议优先查看最近新增与基础信息是否完整。
|
||||
@else
|
||||
当前站点已有较稳定商品沉淀,建议优先巡检低库存与高库存商品结构。
|
||||
@endif
|
||||
</strong>
|
||||
<div class="muted mt-8">建议动作</div>
|
||||
<div class="inline-links mt-6">
|
||||
@if(($summaryStats['product_count'] ?? 0) <= 0)
|
||||
<a href="{{ $latestProductWorkbenchUrl }}">先看商品空白情况</a>
|
||||
@elseif(($summaryStats['product_count'] ?? 0) < 3)
|
||||
<a href="{{ $latestProductWorkbenchUrl }}">去看最近新增商品</a>
|
||||
<a href="{{ $productWorkbenchUrl }}">去看已上架商品</a>
|
||||
@else
|
||||
<a href="{{ $lowStockWorkbenchUrl }}">去看低库存商品</a>
|
||||
<a href="{{ $productWorkbenchUrl }}">去看高库存商品</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="focus-box">
|
||||
<div class="muted">订单侧</div>
|
||||
<strong class="text-md">
|
||||
@if(($summaryStats['order_count'] ?? 0) <= 0)
|
||||
当前站点暂无订单,建议先确认交易链路与下单链路是否已完成联调。
|
||||
@elseif(($summaryStats['order_count'] ?? 0) < 5)
|
||||
当前站点已有少量订单沉淀,建议先查看待支付与最近订单走势。
|
||||
@else
|
||||
当前站点订单已形成基础规模,建议优先关注待支付、支付失败与高金额订单。
|
||||
@endif
|
||||
</strong>
|
||||
<div class="muted mt-8">建议动作</div>
|
||||
<div class="inline-links mt-6">
|
||||
@if(($summaryStats['order_count'] ?? 0) <= 0)
|
||||
<a href="{{ $orderWorkbenchUrl }}">先看订单整体情况</a>
|
||||
@elseif(($summaryStats['order_count'] ?? 0) < 5)
|
||||
<a href="{{ $pendingOrderWorkbenchUrl }}">去看待支付订单</a>
|
||||
<a href="{{ $orderWorkbenchUrl }}">去看已支付订单</a>
|
||||
@else
|
||||
<a href="{{ $pendingOrderWorkbenchUrl }}">去看待支付订单</a>
|
||||
<a href="{{ $failedOrderWorkbenchUrl }}">去看支付失败订单</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="focus-box">
|
||||
<div class="muted">联系信息</div>
|
||||
<strong class="text-md">
|
||||
@if(! $site->contact_name && ! $site->contact_phone && ! $site->contact_email)
|
||||
联系信息仍为空,建议尽快补齐联系人、手机与邮箱,避免后续运营跟进断点。
|
||||
@elseif(! $site->contact_name)
|
||||
当前缺少联系人姓名,建议先补联系人主体,再继续做日常跟进。
|
||||
@elseif(! $site->contact_phone && ! $site->contact_email)
|
||||
当前缺少联系方式,建议至少补齐手机号或邮箱中的一项。
|
||||
@else
|
||||
联系人信息已具备,可继续作为站点日常跟进入口。
|
||||
@endif
|
||||
</strong>
|
||||
<div class="muted mt-8">建议动作</div>
|
||||
<div class="inline-links mt-6">
|
||||
<a href="#site-profile">去看站点资料</a>
|
||||
@if(! $site->contact_name && ! $site->contact_phone && ! $site->contact_email)
|
||||
<a href="#site-profile">优先补联系人与联系方式</a>
|
||||
@elseif(! $site->contact_name)
|
||||
<a href="#site-profile">优先补联系人</a>
|
||||
@elseif(! $site->contact_phone && ! $site->contact_email)
|
||||
<a href="#site-profile">优先补联系方式</a>
|
||||
@else
|
||||
<a href="#site-records">去看承接记录</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-spaced">
|
||||
<h3 class="mt-0">工作台导航</h3>
|
||||
<div class="grid-3">
|
||||
<div class="nav-box">
|
||||
<div class="muted mb-8">商品工作台</div>
|
||||
<div class="inline-links">
|
||||
<a href="{{ $productWorkbenchUrl }}">高库存已上架</a>
|
||||
<a href="{{ $lowStockWorkbenchUrl }}">低库存补货</a>
|
||||
<a href="{{ $latestProductWorkbenchUrl }}">最近新增</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-box">
|
||||
<div class="muted mb-8">订单工作台</div>
|
||||
<div class="inline-links">
|
||||
<a href="{{ $orderWorkbenchUrl }}">高金额已支付</a>
|
||||
<a href="{{ $pendingOrderWorkbenchUrl }}">待支付跟进</a>
|
||||
<a href="{{ $failedOrderWorkbenchUrl }}">支付失败排查</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nav-box">
|
||||
<div class="muted mb-8">站点信息</div>
|
||||
<div class="inline-links">
|
||||
<a href="#site-profile">查看当前站点资料</a>
|
||||
<a href="#site-records">查看承接记录</a>
|
||||
<a href="{{ '/site-admin/merchants' . ($merchantExportQuery ? ('?' . $merchantExportQuery) : '') }}">返回当前筛选视图</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-spaced" id="site-profile">
|
||||
<h3 class="mt-0">当前站点资料</h3>
|
||||
<div class="grid-3">
|
||||
<div class="profile-box">
|
||||
<div class="muted">站点名称</div>
|
||||
<strong class="num-sm">{{ $site->name }}</strong>
|
||||
</div>
|
||||
<div class="profile-box">
|
||||
<div class="muted">站点标识</div>
|
||||
<strong class="num-sm">{{ $site->slug }}</strong>
|
||||
</div>
|
||||
<div class="profile-box">
|
||||
<div class="muted">当前状态</div>
|
||||
<strong class="num-sm"><span class="badge badge-success">{{ $statusLabels[$site->status] ?? $site->status }}</span></strong>
|
||||
</div>
|
||||
<div class="profile-box">
|
||||
<div class="muted">当前套餐</div>
|
||||
<strong class="num-sm"><span class="badge badge-info">{{ $planLabels[$site->plan] ?? ($site->plan ?: '未设置') }}</span></strong>
|
||||
</div>
|
||||
<div class="profile-box">
|
||||
<div class="muted">联系人</div>
|
||||
<strong class="num-sm">{{ $site->contact_name ?: '未设置' }}</strong>
|
||||
</div>
|
||||
<div class="profile-box">
|
||||
<div class="muted">联系电话</div>
|
||||
<strong class="num-sm">{{ $site->contact_phone ?: '未设置' }}</strong>
|
||||
</div>
|
||||
<div class="profile-box">
|
||||
<div class="muted">联系邮箱</div>
|
||||
<strong class="num-sm">{{ $site->contact_email ?: '未设置' }}</strong>
|
||||
</div>
|
||||
<div class="profile-box">
|
||||
<div class="muted">激活时间</div>
|
||||
<strong class="num-sm">{{ $site->activated_at?->format('Y-m-d H:i') ?? '未激活' }}</strong>
|
||||
</div>
|
||||
<div class="profile-box span-3">
|
||||
<div class="muted">站点承接说明</div>
|
||||
<strong class="text-md">当前阶段由站点后台承接商品、订单与联系人信息查看;后续若拆出“站点 -> 商家”实体关系,这里将平滑升级为真正的站点运营台。</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="actions mt-12">
|
||||
<a href="{{ $productWorkbenchUrl }}">查看站点商品</a>
|
||||
<a href="{{ $orderWorkbenchUrl }}">查看站点订单</a>
|
||||
<a href="{{ $lowStockWorkbenchUrl }}">查看低库存商品</a>
|
||||
<a href="{{ $pendingOrderWorkbenchUrl }}">查看待支付订单</a>
|
||||
</div>
|
||||
<div class="card card-dark card-dashed mt-12">
|
||||
<div class="muted mb-8">运营建议</div>
|
||||
<div class="inline-links">
|
||||
<a href="{{ $productWorkbenchUrl }}">高库存已上架商品</a>
|
||||
<a href="{{ $lowStockWorkbenchUrl }}">低库存补货视角</a>
|
||||
<a href="{{ $latestProductWorkbenchUrl }}">最近新增商品</a>
|
||||
<a href="{{ $orderWorkbenchUrl }}">高金额已支付订单</a>
|
||||
<a href="{{ $pendingOrderWorkbenchUrl }}">待支付订单跟进</a>
|
||||
<a href="{{ $failedOrderWorkbenchUrl }}">支付失败订单排查</a>
|
||||
</div>
|
||||
</div>
|
||||
<p class="muted helper-text">快捷入口默认带入更适合运营查看的排序与状态条件:商品优先看已上架高库存,订单优先看已支付高金额;同时也补了低库存、待支付、支付失败等常用运营视角。</p>
|
||||
</div>
|
||||
|
||||
<div class="card" id="site-records">
|
||||
<h3 class="mt-0">当前站点承接记录</h3>
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>名称</th><th>Slug</th><th>状态</th><th>套餐</th><th>联系人</th><th>管理员数</th><th>用户数</th><th>商品数</th><th>订单数</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($merchants as $merchant)
|
||||
<tr>
|
||||
<td>{{ $merchant->id }}</td>
|
||||
<td>{{ $merchant->name }}</td>
|
||||
<td>{{ $merchant->slug }}</td>
|
||||
<td><span class="badge badge-success">{{ $statusLabels[$merchant->status] ?? $merchant->status }}</span></td>
|
||||
<td><span class="badge badge-info">{{ $planLabels[$merchant->plan] ?? ($merchant->plan ?: '未设置') }}</span></td>
|
||||
<td>{{ ($merchant->contact_name ?: '未设置') }} / {{ ($merchant->contact_phone ?: '未设置') }}</td>
|
||||
<td>{{ $merchant->admins_count ?? 0 }}</td>
|
||||
<td>{{ $merchant->users_count ?? 0 }}</td>
|
||||
<td>{{ $merchant->products_count ?? 0 }}</td>
|
||||
<td>{{ $merchant->orders_count ?? 0 }}</td>
|
||||
<td>
|
||||
<div class="inline-links">
|
||||
<a href="{{ $productWorkbenchUrl }}">查看商品</a>
|
||||
<a href="{{ $orderWorkbenchUrl }}">查看订单</a>
|
||||
<a href="{{ $lowStockWorkbenchUrl }}">低库存</a>
|
||||
<a href="{{ $failedOrderWorkbenchUrl }}">支付失败</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="11" class="muted table-empty">暂无站点承接记录</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@endsection
|
||||
206
resources/views/site_admin/orders/index.blade.php
Normal file
206
resources/views/site_admin/orders/index.blade.php
Normal file
@@ -0,0 +1,206 @@
|
||||
@extends('site_admin.layouts.app')
|
||||
|
||||
@section('title', '站点订单')
|
||||
@section('page_title', '站点订单')
|
||||
|
||||
@section('content')
|
||||
<div class="card card-spaced">
|
||||
<p class="muted mt-0">当前页面展示站点范围内的订单列表,已补最小筛选与导出入口,便于站点侧先做基础运营查看。</p>
|
||||
@if(!empty($filters['validation_errors'] ?? []))
|
||||
<div class="error-box mt-12">
|
||||
<strong>筛选条件有误:</strong>
|
||||
<ul class="ml-18">
|
||||
@foreach(($filters['validation_errors'] ?? []) as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
@php
|
||||
$orderExportQuery = http_build_query(array_filter([
|
||||
'keyword' => $filters['keyword'] ?? '',
|
||||
'status' => $filters['status'] ?? '',
|
||||
'payment_status' => $filters['payment_status'] ?? '',
|
||||
'platform' => $filters['platform'] ?? '',
|
||||
'device_type' => $filters['device_type'] ?? '',
|
||||
'payment_channel' => $filters['payment_channel'] ?? '',
|
||||
'min_pay_amount' => $filters['min_pay_amount'] ?? '',
|
||||
'max_pay_amount' => $filters['max_pay_amount'] ?? '',
|
||||
'sort' => $filters['sort'] ?? 'latest',
|
||||
], fn ($value) => $value !== null && $value !== ''));
|
||||
@endphp
|
||||
<h3>筛选条件</h3>
|
||||
<form method="get" action="/site-admin/orders">
|
||||
<div class="grid-5 form-grid">
|
||||
<input type="text" name="keyword" placeholder="关键词:订单号 / 买家信息" value="{{ $filters['keyword'] ?? '' }}">
|
||||
<select name="status">
|
||||
<option value="">全部订单状态</option>
|
||||
@foreach(($filterOptions['statuses'] ?? []) as $status)
|
||||
<option value="{{ $status }}" @selected(($filters['status'] ?? '') === $status)>{{ ($statusLabels[$status] ?? $status) }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="payment_status">
|
||||
<option value="">全部支付状态</option>
|
||||
@foreach(($filterOptions['paymentStatuses'] ?? []) as $status)
|
||||
<option value="{{ $status }}" @selected(($filters['payment_status'] ?? '') === $status)>{{ ($paymentStatusLabels[$status] ?? $status) }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="platform">
|
||||
<option value="">全部平台</option>
|
||||
@foreach(($filterOptions['platforms'] ?? []) as $platform)
|
||||
<option value="{{ $platform }}" @selected(($filters['platform'] ?? '') === $platform)>{{ ($platformLabels[$platform] ?? $platform) }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="device_type">
|
||||
<option value="">全部设备类型</option>
|
||||
@foreach(['desktop','mobile','mini-program','mobile-webview','app-api'] as $deviceType)
|
||||
<option value="{{ $deviceType }}" @selected(($filters['device_type'] ?? '') === $deviceType)>{{ $deviceTypeLabels[$deviceType] ?? $deviceType }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="payment_channel">
|
||||
<option value="">全部支付渠道</option>
|
||||
@foreach(($filterOptions['paymentChannels'] ?? []) as $paymentChannel)
|
||||
<option value="{{ $paymentChannel }}" @selected(($filters['payment_channel'] ?? '') === $paymentChannel)>{{ ($paymentChannelLabels[$paymentChannel] ?? $paymentChannel) }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="sort">
|
||||
@foreach(($filterOptions['sortOptions'] ?? []) as $value => $label)
|
||||
<option value="{{ $value }}" @selected(($filters['sort'] ?? 'latest') === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<input type="number" step="0.01" min="0" name="min_pay_amount" placeholder="最低实付金额" value="{{ $filters['min_pay_amount'] ?? '' }}">
|
||||
<input type="number" step="0.01" min="0" name="max_pay_amount" placeholder="最高实付金额" value="{{ $filters['max_pay_amount'] ?? '' }}">
|
||||
</div>
|
||||
<div class="actions mt-12">
|
||||
<button type="submit">应用筛选</button>
|
||||
<a href="/site-admin/orders">重置</a>
|
||||
<a href="{{ '/site-admin/orders/export' . ($orderExportQuery ? ('?' . $orderExportQuery) : '') }}">导出当前筛选结果 CSV</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card card-spaced">
|
||||
<h3 class="mt-0">当前筛选摘要</h3>
|
||||
<div class="grid-3">
|
||||
@foreach(($activeFilterSummary ?? []) as $label => $value)
|
||||
<div class="summary-box">
|
||||
<div class="muted">{{ $label }}</div>
|
||||
<strong class="text-md">{{ $value }}</strong>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-spaced">
|
||||
<h3 class="mt-0">订单汇总</h3>
|
||||
<div class="grid-5">
|
||||
<div class="stat-box">
|
||||
<div class="muted">订单总数</div>
|
||||
<strong class="num-md">{{ $summaryStats['total_orders'] ?? 0 }}</strong>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="muted">实付总额</div>
|
||||
<strong class="num-md">¥{{ number_format($summaryStats['total_pay_amount'] ?? 0, 2) }}</strong>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="muted">平均客单价</div>
|
||||
<strong class="num-md">¥{{ number_format($summaryStats['average_order_amount'] ?? 0, 2) }}</strong>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="muted">已支付订单数</div>
|
||||
<strong class="num-md">{{ $summaryStats['paid_orders'] ?? 0 }}</strong>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="muted">支付失败订单</div>
|
||||
<strong class="num-md">{{ $summaryStats['failed_payment_orders'] ?? 0 }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-dark card-spaced">
|
||||
<h3 class="mt-0">运营关注项</h3>
|
||||
<div class="grid-3">
|
||||
<div class="focus-box">
|
||||
<div class="muted">订单盘面</div>
|
||||
<strong class="text-md">{{ $operationsFocus['headline'] ?? '当前站点订单运营信息已就绪。' }}</strong>
|
||||
<div class="muted mt-8">建议动作</div>
|
||||
<div class="inline-links mt-6">
|
||||
@foreach(($operationsFocus['actions'] ?? []) as $action)
|
||||
<a href="{{ $action['url'] }}">{{ $action['label'] }}</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="focus-box">
|
||||
<div class="muted">当前信号</div>
|
||||
<div class="grid-3 mt-8">
|
||||
@foreach(($operationsFocus['signals'] ?? []) as $label => $value)
|
||||
<div class="summary-box">
|
||||
<div class="muted">{{ $label }}</div>
|
||||
<strong class="num-sm">{{ $value }}</strong>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="focus-box">
|
||||
<div class="muted">工作台导航</div>
|
||||
<div class="inline-links mt-8">
|
||||
@foreach(($operationsFocus['workbench'] ?? []) as $label => $url)
|
||||
<a href="{{ $url }}">{{ $label }}</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-dark card-spaced">
|
||||
<h3 class="mt-0">订单状态统计</h3>
|
||||
@php
|
||||
$orderBaseQuery = [
|
||||
'payment_status' => ($filters['payment_status'] ?? '') ?: null,
|
||||
'platform' => ($filters['platform'] ?? '') ?: null,
|
||||
'device_type' => ($filters['device_type'] ?? '') ?: null,
|
||||
'payment_channel' => ($filters['payment_channel'] ?? '') ?: null,
|
||||
'keyword' => ($filters['keyword'] ?? '') ?: null,
|
||||
'min_pay_amount' => ($filters['min_pay_amount'] ?? '') ?: null,
|
||||
'max_pay_amount' => ($filters['max_pay_amount'] ?? '') ?: null,
|
||||
'sort' => ($filters['sort'] ?? 'latest') !== 'latest' ? ($filters['sort'] ?? 'latest') : null,
|
||||
];
|
||||
@endphp
|
||||
<div class="grid-6">
|
||||
<a class="status-card {{ ($filters['status'] ?? '') === '' ? 'status-card-active' : '' }}" href="{{ '/site-admin/orders?' . http_build_query(array_filter($orderBaseQuery, fn ($value) => $value !== null && $value !== '')) }}">
|
||||
<div class="muted">全部</div>
|
||||
<strong class="num-md">{{ $statusStats['all'] ?? 0 }}</strong>
|
||||
</a>
|
||||
@foreach(($filterOptions['statuses'] ?? []) as $status)
|
||||
<a class="status-card {{ ($filters['status'] ?? '') === $status ? 'status-card-active' : '' }}" href="{{ '/site-admin/orders?' . http_build_query(array_filter(array_merge($orderBaseQuery, ['status' => $status]), fn ($value) => $value !== null && $value !== '')) }}">
|
||||
<div class="muted">{{ ($statusLabels[$status] ?? $status) }}</div>
|
||||
<strong class="num-md">{{ $statusStats[$status] ?? 0 }}</strong>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>订单列表</h3>
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>订单号</th><th>状态</th><th>支付状态</th><th>平台</th><th>设备</th><th>支付渠道</th><th>实付金额</th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($orders as $order)
|
||||
<tr>
|
||||
<td>{{ $order->id }}</td>
|
||||
<td>{{ $order->order_no }}</td>
|
||||
<td>{{ $statusLabels[$order->status] ?? $order->status }}</td>
|
||||
<td>{{ $paymentStatusLabels[$order->payment_status] ?? $order->payment_status }}</td>
|
||||
<td>{{ $platformLabels[$order->platform] ?? $order->platform }}</td>
|
||||
<td>{{ $deviceTypeLabels[$order->device_type] ?? $order->device_type }}</td>
|
||||
<td>{{ $paymentChannelLabels[$order->payment_channel] ?? $order->payment_channel }}</td>
|
||||
<td>{{ number_format((float) $order->pay_amount, 2) }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="8" class="muted">暂无订单</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mt-16">{{ $orders->links() }}</div>
|
||||
@endsection
|
||||
183
resources/views/site_admin/products/index.blade.php
Normal file
183
resources/views/site_admin/products/index.blade.php
Normal file
@@ -0,0 +1,183 @@
|
||||
@extends('site_admin.layouts.app')
|
||||
|
||||
@section('title', '站点商品')
|
||||
@section('page_title', '站点商品')
|
||||
|
||||
@section('content')
|
||||
<div class="card card-spaced">
|
||||
<p class="muted mt-0">当前页面展示站点范围内的商品列表,已补最小筛选与导出入口,便于站点侧先做基础运营查看。</p>
|
||||
@if(!empty($filters['validation_errors'] ?? []))
|
||||
<div class="error-box mt-12">
|
||||
<strong>筛选条件有误:</strong>
|
||||
<ul class="ml-18">
|
||||
@foreach(($filters['validation_errors'] ?? []) as $error)
|
||||
<li>{{ $error }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
@php
|
||||
$productExportQuery = http_build_query(array_filter([
|
||||
'keyword' => $filters['keyword'] ?? '',
|
||||
'status' => $filters['status'] ?? '',
|
||||
'category_id' => $filters['category_id'] ?? '',
|
||||
'min_price' => $filters['min_price'] ?? '',
|
||||
'max_price' => $filters['max_price'] ?? '',
|
||||
'min_stock' => $filters['min_stock'] ?? '',
|
||||
'max_stock' => $filters['max_stock'] ?? '',
|
||||
'sort' => $filters['sort'] ?? 'latest',
|
||||
], fn ($value) => $value !== null && $value !== ''));
|
||||
@endphp
|
||||
<h3>筛选条件</h3>
|
||||
<form method="get" action="/site-admin/products">
|
||||
<div class="grid-4 form-grid">
|
||||
<input type="text" name="keyword" placeholder="关键词:标题 / SKU / slug" value="{{ $filters['keyword'] ?? '' }}">
|
||||
<select name="status">
|
||||
<option value="">全部状态</option>
|
||||
@foreach(($filterOptions['statuses'] ?? []) as $status)
|
||||
<option value="{{ $status }}" @selected(($filters['status'] ?? '') === $status)>{{ ($statusLabels[$status] ?? $status) }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="category_id">
|
||||
<option value="">全部分类</option>
|
||||
@foreach($categories as $category)
|
||||
<option value="{{ $category->id }}" @selected((string) ($filters['category_id'] ?? '') === (string) $category->id)>{{ $category->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<select name="sort">
|
||||
@foreach(($filterOptions['sortOptions'] ?? []) as $value => $label)
|
||||
<option value="{{ $value }}" @selected(($filters['sort'] ?? 'latest') === $value)>{{ $label }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
<input type="number" step="0.01" min="0" name="min_price" placeholder="最低价格" value="{{ $filters['min_price'] ?? '' }}">
|
||||
<input type="number" step="0.01" min="0" name="max_price" placeholder="最高价格" value="{{ $filters['max_price'] ?? '' }}">
|
||||
<input type="number" min="0" name="min_stock" placeholder="最低库存" value="{{ $filters['min_stock'] ?? '' }}">
|
||||
<input type="number" min="0" name="max_stock" placeholder="最高库存" value="{{ $filters['max_stock'] ?? '' }}">
|
||||
</div>
|
||||
<div class="actions mt-12">
|
||||
<button type="submit">应用筛选</button>
|
||||
<a href="/site-admin/products">重置</a>
|
||||
<a href="{{ '/site-admin/products/export' . ($productExportQuery ? ('?' . $productExportQuery) : '') }}">导出当前筛选结果 CSV</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="card card-spaced">
|
||||
<h3 class="mt-0">当前筛选摘要</h3>
|
||||
<div class="grid-3">
|
||||
@foreach(($activeFilterSummary ?? []) as $label => $value)
|
||||
<div class="summary-box">
|
||||
<div class="muted">{{ $label }}</div>
|
||||
<strong class="text-md">{{ $value }}</strong>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-spaced">
|
||||
<h3 class="mt-0">商品运营汇总</h3>
|
||||
<div class="grid-4">
|
||||
<div class="stat-box">
|
||||
<div class="muted">商品总数</div>
|
||||
<strong class="num-md">{{ $summaryStats['total_products'] ?? 0 }}</strong>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="muted">总库存</div>
|
||||
<strong class="num-md">{{ $summaryStats['total_stock'] ?? 0 }}</strong>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="muted">总货值</div>
|
||||
<strong class="num-md">¥{{ number_format($summaryStats['total_stock_value'] ?? 0, 2) }}</strong>
|
||||
</div>
|
||||
<div class="stat-box">
|
||||
<div class="muted">平均售价</div>
|
||||
<strong class="num-md">¥{{ number_format($summaryStats['average_price'] ?? 0, 2) }}</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-dark card-spaced">
|
||||
<h3 class="mt-0">运营关注项</h3>
|
||||
<div class="grid-3">
|
||||
<div class="focus-box">
|
||||
<div class="muted">商品盘面</div>
|
||||
<strong class="text-md">{{ $operationsFocus['headline'] ?? '当前站点商品运营信息已就绪。' }}</strong>
|
||||
<div class="muted mt-8">建议动作</div>
|
||||
<div class="inline-links mt-6">
|
||||
@foreach(($operationsFocus['actions'] ?? []) as $action)
|
||||
<a href="{{ $action['url'] }}">{{ $action['label'] }}</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="focus-box">
|
||||
<div class="muted">当前信号</div>
|
||||
<div class="grid-3 mt-8">
|
||||
@foreach(($operationsFocus['signals'] ?? []) as $label => $value)
|
||||
<div class="summary-box">
|
||||
<div class="muted">{{ $label }}</div>
|
||||
<strong class="num-sm">{{ $value }}</strong>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
<div class="focus-box">
|
||||
<div class="muted">工作台导航</div>
|
||||
<div class="inline-links mt-8">
|
||||
@foreach(($operationsFocus['workbench'] ?? []) as $label => $url)
|
||||
<a href="{{ $url }}">{{ $label }}</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-dark card-spaced">
|
||||
<h3 class="mt-0">商品状态统计</h3>
|
||||
@php
|
||||
$productBaseQuery = [
|
||||
'keyword' => ($filters['keyword'] ?? '') ?: null,
|
||||
'category_id' => ($filters['category_id'] ?? '') ?: null,
|
||||
'min_price' => ($filters['min_price'] ?? '') ?: null,
|
||||
'max_price' => ($filters['max_price'] ?? '') ?: null,
|
||||
'min_stock' => ($filters['min_stock'] ?? '') ?: null,
|
||||
'max_stock' => ($filters['max_stock'] ?? '') ?: null,
|
||||
'sort' => ($filters['sort'] ?? 'latest') !== 'latest' ? ($filters['sort'] ?? 'latest') : null,
|
||||
];
|
||||
@endphp
|
||||
<div class="grid-4">
|
||||
<a class="status-card {{ ($filters['status'] ?? '') === '' ? 'status-card-active' : '' }}" href="{{ '/site-admin/products?' . http_build_query(array_filter($productBaseQuery, fn ($value) => $value !== null && $value !== '')) }}">
|
||||
<div class="muted">全部</div>
|
||||
<strong class="num-md">{{ $statusStats['all'] ?? 0 }}</strong>
|
||||
</a>
|
||||
@foreach(($filterOptions['statuses'] ?? []) as $status)
|
||||
<a class="status-card {{ ($filters['status'] ?? '') === $status ? 'status-card-active' : '' }}" href="{{ '/site-admin/products?' . http_build_query(array_filter(array_merge($productBaseQuery, ['status' => $status]), fn ($value) => $value !== null && $value !== '')) }}">
|
||||
<div class="muted">{{ ($statusLabels[$status] ?? $status) }}</div>
|
||||
<strong class="num-md">{{ $statusStats[$status] ?? 0 }}</strong>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h3>商品列表</h3>
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>标题</th><th>SKU</th><th>分类</th><th>价格</th><th>库存</th><th>状态</th></tr></thead>
|
||||
<tbody>
|
||||
@forelse($products as $product)
|
||||
<tr>
|
||||
<td>{{ $product->id }}</td>
|
||||
<td>{{ $product->title }}</td>
|
||||
<td>{{ $product->sku }}</td>
|
||||
<td>{{ $product->category?->name ?? '-' }}</td>
|
||||
<td>{{ number_format((float) $product->price, 2) }}</td>
|
||||
<td>{{ $product->stock }}</td>
|
||||
<td>{{ $statusLabels[$product->status] ?? $product->status }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr><td colspan="7" class="muted">暂无商品</td></tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="mt-16">{{ $products->links() }}</div>
|
||||
@endsection
|
||||
36
resources/views/welcome.blade.php
Normal file
36
resources/views/welcome.blade.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ config('app.name', 'SaaSShop') }}</title>
|
||||
<link rel="stylesheet" href="/css/public-pages.css">
|
||||
</head>
|
||||
<body class="dark-page">
|
||||
<div class="public-wrap public-wrap-narrow">
|
||||
<h1>{{ config('app.name', 'SaaSShop') }}</h1>
|
||||
<p class="muted">Laravel 基础框架已启动。当前项目已切到 SaaS 电商多端底座方向,默认欢迎页已简化为可维护版本,样式统一走外置 CSS。</p>
|
||||
<div class="grid-2">
|
||||
<div class="card">
|
||||
<h3>开发入口</h3>
|
||||
<ul>
|
||||
<li><a href="/">项目首页</a></li>
|
||||
<li><a href="/health">环境健康检查</a></li>
|
||||
<li><a href="/admin">总台管理</a></li>
|
||||
<li><a href="/site-admin">站点后台</a></li>
|
||||
<li><a href="/merchant-admin">商家后台</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h3>接口入口</h3>
|
||||
<ul>
|
||||
<li><a href="/api/v1/ping">API Ping</a></li>
|
||||
<li><a href="/api/v1/platforms">平台能力</a></li>
|
||||
<li><a href="/api/v1/products">商品列表</a></li>
|
||||
<li><a href="/api/v1/orders">订单列表</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
30
resources/views/welcome_status.blade.php
Normal file
30
resources/views/welcome_status.blade.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>SaaSShop Laravel 初始化页</title>
|
||||
<link rel="stylesheet" href="/css/public-pages.css">
|
||||
</head>
|
||||
<body class="dark-page">
|
||||
<div class="public-wrap public-wrap-narrow">
|
||||
<h1>SaaSShop Laravel 初始化页</h1>
|
||||
<p class="muted">访问地址:<code>http://192.168.10.199:9001/</code></p>
|
||||
<div class="grid-2">
|
||||
<div class="card"><h3>框架</h3><p class="ok">Laravel 已部署</p></div>
|
||||
<div class="card"><h3>PHP</h3><p class="ok">PHP {{ $phpVersion }}</p></div>
|
||||
<div class="card"><h3>数据库</h3><p class="{{ $dbOk ? 'ok' : 'bad' }}">{{ $dbMsg }}</p></div>
|
||||
<div class="card"><h3>Redis</h3><p class="{{ $redisOk ? 'ok' : 'bad' }}">{{ $redisMsg }}</p></div>
|
||||
</div>
|
||||
<div class="card mt-20">
|
||||
<h3>当前已完成</h3>
|
||||
<ul>
|
||||
<li>LNMP + Redis 环境已就绪</li>
|
||||
<li>Laravel 项目已安装到 <code>/var/www/sites/app</code></li>
|
||||
<li>数据库已切换为 <code>appdb</code></li>
|
||||
<li>Redis 已接入 Laravel 作为队列连接</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user