Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
345071c7b6 |
File diff suppressed because it is too large
Load Diff
@ -1,39 +1,60 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const chatForm = document.getElementById('chat-form');
|
||||
const chatInput = document.getElementById('chat-input');
|
||||
const chatMessages = document.getElementById('chat-messages');
|
||||
if (window.bootstrap) {
|
||||
document.querySelectorAll('.toast').forEach((toastEl) => {
|
||||
const toast = bootstrap.Toast.getOrCreateInstance(toastEl);
|
||||
toast.show();
|
||||
});
|
||||
}
|
||||
|
||||
const appendMessage = (text, sender) => {
|
||||
const msgDiv = document.createElement('div');
|
||||
msgDiv.classList.add('message', sender);
|
||||
msgDiv.textContent = text;
|
||||
chatMessages.appendChild(msgDiv);
|
||||
chatMessages.scrollTop = chatMessages.scrollHeight;
|
||||
};
|
||||
document.querySelectorAll('[data-countdown]').forEach((node) => {
|
||||
const unlockAt = Number.parseInt(node.getAttribute('data-countdown') || '0', 10);
|
||||
const targetSelector = node.getAttribute('data-countdown-target');
|
||||
const targetButton = targetSelector ? document.querySelector(targetSelector) : null;
|
||||
const card = node.closest('[data-countdown-card]') || node.closest('.countdown-card');
|
||||
|
||||
chatForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const message = chatInput.value.trim();
|
||||
if (!message) return;
|
||||
const updateCountdown = () => {
|
||||
const remaining = Math.max(0, unlockAt - Math.floor(Date.now() / 1000));
|
||||
if (remaining > 0) {
|
||||
node.textContent = `${remaining} 秒`;
|
||||
node.classList.remove('ready');
|
||||
if (targetButton) {
|
||||
targetButton.disabled = true;
|
||||
targetButton.setAttribute('aria-disabled', 'true');
|
||||
}
|
||||
if (card) {
|
||||
card.classList.remove('ready');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
appendMessage(message, 'visitor');
|
||||
chatInput.value = '';
|
||||
node.textContent = '可提交';
|
||||
node.classList.add('ready');
|
||||
if (targetButton) {
|
||||
targetButton.disabled = false;
|
||||
targetButton.removeAttribute('aria-disabled');
|
||||
}
|
||||
if (card) {
|
||||
card.classList.add('ready');
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('api/chat.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ message })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
// Artificial delay for realism
|
||||
setTimeout(() => {
|
||||
appendMessage(data.reply, 'bot');
|
||||
}, 500);
|
||||
} catch (error) {
|
||||
console.error('Error:', error);
|
||||
appendMessage("Sorry, something went wrong. Please try again.", 'bot');
|
||||
const done = updateCountdown();
|
||||
if (!done) {
|
||||
const interval = window.setInterval(() => {
|
||||
if (updateCountdown()) {
|
||||
window.clearInterval(interval);
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
|
||||
document.querySelectorAll('[data-confirm]').forEach((button) => {
|
||||
button.addEventListener('click', (event) => {
|
||||
const message = button.getAttribute('data-confirm');
|
||||
if (message && !window.confirm(message)) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
BIN
assets/pasted-20260525-120457-0c06ca85.png
Normal file
BIN
assets/pasted-20260525-120457-0c06ca85.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
BIN
assets/pasted-20260525-130328-7c40a29a.png
Normal file
BIN
assets/pasted-20260525-130328-7c40a29a.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.2 MiB |
20
db/migrations/20260525_add_email_verification_codes.sql
Normal file
20
db/migrations/20260525_add_email_verification_codes.sql
Normal file
@ -0,0 +1,20 @@
|
||||
-- Add email_verification_codes table for real email signup flow.
|
||||
-- users.email_verified_at and users.last_login_at are also ensured defensively in app.php.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS email_verification_codes (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
email VARCHAR(190) NOT NULL,
|
||||
purpose VARCHAR(32) NOT NULL DEFAULT 'signup',
|
||||
password_hash VARCHAR(255) NOT NULL,
|
||||
code_hash VARCHAR(255) NOT NULL,
|
||||
attempt_count TINYINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
max_attempts TINYINT UNSIGNED NOT NULL DEFAULT 5,
|
||||
expires_at DATETIME NOT NULL,
|
||||
verified_at DATETIME NULL,
|
||||
consumed_at DATETIME NULL,
|
||||
ip_address VARCHAR(64) NULL,
|
||||
meta_note VARCHAR(255) NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_email_codes_email_purpose (email, purpose, created_at),
|
||||
INDEX idx_email_codes_status (purpose, consumed_at, expires_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
18
db/migrations/20260525_add_vip_orders.sql
Normal file
18
db/migrations/20260525_add_vip_orders.sql
Normal file
@ -0,0 +1,18 @@
|
||||
-- Add vip_orders table for real VIP upgrade flow.
|
||||
-- The app also creates this table defensively in app.php via CREATE TABLE IF NOT EXISTS.
|
||||
|
||||
CREATE TABLE IF NOT EXISTS vip_orders (
|
||||
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
||||
user_id INT UNSIGNED NOT NULL,
|
||||
from_level TINYINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
to_level TINYINT UNSIGNED NOT NULL,
|
||||
price_usdt DECIMAL(12,2) NOT NULL DEFAULT 0.00,
|
||||
status VARCHAR(32) NOT NULL DEFAULT 'completed',
|
||||
available_after DECIMAL(12,2) NOT NULL DEFAULT 0.00,
|
||||
note VARCHAR(255) NOT NULL,
|
||||
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
completed_at DATETIME NULL,
|
||||
CONSTRAINT fk_vip_order_user FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||
INDEX idx_vip_orders_user_created (user_id, created_at),
|
||||
INDEX idx_vip_orders_status_created (status, created_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
24
healthz.php
Normal file
24
healthz.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/app.php';
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
try {
|
||||
db()->query('SELECT 1');
|
||||
echo json_encode([
|
||||
'status' => 'ok',
|
||||
'app' => project_name(),
|
||||
'time_utc' => gmdate('c'),
|
||||
'database' => 'connected',
|
||||
'client_ip' => client_ip(),
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
} catch (Throwable $exception) {
|
||||
http_response_code(500);
|
||||
echo json_encode([
|
||||
'status' => 'error',
|
||||
'app' => project_name(),
|
||||
'time_utc' => gmdate('c'),
|
||||
'database' => 'disconnected',
|
||||
'error' => $exception->getMessage(),
|
||||
], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
||||
}
|
||||
346
index.php
346
index.php
@ -1,150 +1,208 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
@ini_set('display_errors', '1');
|
||||
@error_reporting(E_ALL);
|
||||
@date_default_timezone_set('UTC');
|
||||
require_once __DIR__ . '/app.php';
|
||||
|
||||
$phpVersion = PHP_VERSION;
|
||||
$now = date('Y-m-d H:i:s');
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
flash('warning', '首页认证入口已升级为独立页面,请从“启动页 / 注册 / 登录”继续。');
|
||||
redirect(start_page_url());
|
||||
}
|
||||
|
||||
$user = current_user();
|
||||
$previewMode = !$user;
|
||||
$stats = $user
|
||||
? get_dashboard_stats((int) $user['id'])
|
||||
: [
|
||||
'available_balance' => 5560.25,
|
||||
'frozen_balance' => 120.00,
|
||||
'total_balance' => 5680.25,
|
||||
'today_earnings' => 1000.00,
|
||||
'month_earnings' => 18560.20,
|
||||
'today_tasks' => 30,
|
||||
'active_orders' => 3,
|
||||
'completed_orders' => 12,
|
||||
];
|
||||
$recentOrders = $user ? get_user_orders((int) $user['id'], 4) : [];
|
||||
$showcaseTasks = array_slice(array_values(task_catalog()), 0, 4);
|
||||
$quickActions = [
|
||||
['label' => '充值', 'icon' => 'deposit', 'href' => 'wallet.php#deposit-panel'],
|
||||
['label' => '提现', 'icon' => 'withdraw', 'href' => 'wallet.php#withdraw-panel'],
|
||||
['label' => '转账', 'icon' => 'swap', 'href' => 'wallet.php#transfer-panel'],
|
||||
['label' => '记录', 'icon' => 'history', 'href' => 'wallet.php#history-panel'],
|
||||
];
|
||||
$taskShortcuts = [
|
||||
['label' => '看视频', 'icon' => 'play', 'href' => 'task.php?category=video', 'accent' => 'accent-red'],
|
||||
['label' => '点赞', 'icon' => 'heart', 'href' => 'task.php?category=like', 'accent' => 'accent-pink'],
|
||||
['label' => '关注', 'icon' => 'spark', 'href' => 'task.php?category=social', 'accent' => 'accent-violet'],
|
||||
['label' => '网站', 'icon' => 'globe', 'href' => 'task.php?category=web', 'accent' => 'accent-blue'],
|
||||
['label' => '应用', 'icon' => 'download', 'href' => 'task.php?category=app', 'accent' => 'accent-orange'],
|
||||
];
|
||||
$onboardingSteps = [
|
||||
['step' => '01', 'title' => '启动页', 'desc' => '先看平台介绍、流程说明和开户注册入口。'],
|
||||
['step' => '02', 'title' => '邮箱注册', 'desc' => '填写邮箱和密码,系统准备发送 6 位验证码。'],
|
||||
['step' => '03', 'title' => '邮箱验证码', 'desc' => '输入邮件里的 6 位数字,验证通过后再正式建号。'],
|
||||
['step' => '04', 'title' => '账号登录', 'desc' => '用已验证邮箱登录,再进入首页 / 任务 / VIP / 钱包 / 我的。'],
|
||||
];
|
||||
|
||||
render_layout_start('首页', '任务返佣平台首页,提供资产总览、快捷任务、VIP 入口与真实邮箱开户流程。', 'home');
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>New Style</title>
|
||||
<?php
|
||||
// Read project preview data from environment
|
||||
$projectDescription = $_SERVER['PROJECT_DESCRIPTION'] ?? '';
|
||||
$projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
?>
|
||||
<?php if ($projectDescription): ?>
|
||||
<!-- Meta description -->
|
||||
<meta name="description" content='<?= htmlspecialchars($projectDescription) ?>' />
|
||||
<!-- Open Graph meta tags -->
|
||||
<meta property="og:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<!-- Twitter meta tags -->
|
||||
<meta property="twitter:description" content="<?= htmlspecialchars($projectDescription) ?>" />
|
||||
<?php endif; ?>
|
||||
<?php if ($projectImageUrl): ?>
|
||||
<!-- Open Graph image -->
|
||||
<meta property="og:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<!-- Twitter image -->
|
||||
<meta property="twitter:image" content="<?= htmlspecialchars($projectImageUrl) ?>" />
|
||||
<?php endif; ?>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg-color-start: #6a11cb;
|
||||
--bg-color-end: #2575fc;
|
||||
--text-color: #ffffff;
|
||||
--card-bg-color: rgba(255, 255, 255, 0.01);
|
||||
--card-border-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: linear-gradient(45deg, var(--bg-color-start), var(--bg-color-end));
|
||||
color: var(--text-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
body::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-image: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><path d="M-10 10L110 10M10 -10L10 110" stroke-width="1" stroke="rgba(255,255,255,0.05)"/></svg>');
|
||||
animation: bg-pan 20s linear infinite;
|
||||
z-index: -1;
|
||||
}
|
||||
@keyframes bg-pan {
|
||||
0% { background-position: 0% 0%; }
|
||||
100% { background-position: 100% 100%; }
|
||||
}
|
||||
main {
|
||||
padding: 2rem;
|
||||
}
|
||||
.card {
|
||||
background: var(--card-bg-color);
|
||||
border: 1px solid var(--card-border-color);
|
||||
border-radius: 16px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.loader {
|
||||
margin: 1.25rem auto 1.25rem;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border: 3px solid rgba(255, 255, 255, 0.25);
|
||||
border-top-color: #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
.hint {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.sr-only {
|
||||
position: absolute;
|
||||
width: 1px; height: 1px;
|
||||
padding: 0; margin: -1px;
|
||||
overflow: hidden;
|
||||
clip: rect(0, 0, 0, 0);
|
||||
white-space: nowrap; border: 0;
|
||||
}
|
||||
h1 {
|
||||
font-size: 3rem;
|
||||
font-weight: 700;
|
||||
margin: 0 0 1rem;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
p {
|
||||
margin: 0.5rem 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
code {
|
||||
background: rgba(0,0,0,0.2);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
footer {
|
||||
position: absolute;
|
||||
bottom: 1rem;
|
||||
font-size: 0.8rem;
|
||||
opacity: 0.7;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<div class="card">
|
||||
<h1>Analyzing your requirements and generating your website…</h1>
|
||||
<div class="loader" role="status" aria-live="polite" aria-label="Applying initial changes">
|
||||
<span class="sr-only">Loading…</span>
|
||||
<section class="app-page-section">
|
||||
<article class="app-card gradient-card hero-card">
|
||||
<div class="d-flex align-items-start justify-content-between gap-3">
|
||||
<div>
|
||||
<div class="tiny-eyebrow">Hello, <?= h(app_user_name($user)) ?></div>
|
||||
<h1 class="app-hero-title">任务返佣平台</h1>
|
||||
<p class="app-hero-copy mb-0">做任务,赚佣金。先领取任务,再等待审核入账。</p>
|
||||
</div>
|
||||
<p class="hint"><?= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.</p>
|
||||
<p class="hint">This page will update automatically as the plan is implemented.</p>
|
||||
<p>Runtime: PHP <code><?= htmlspecialchars($phpVersion) ?></code> — UTC <code><?= htmlspecialchars($now) ?></code></p>
|
||||
<div class="hero-avatar"><?= h(app_user_initial($user)) ?></div>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
Page updated: <?= htmlspecialchars($now) ?> (UTC)
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<div class="balance-card mt-3">
|
||||
<div class="d-flex align-items-start justify-content-between gap-3">
|
||||
<div>
|
||||
<div class="mini-label">总资产(USDT)</div>
|
||||
<div class="balance-value"><?= h(number_format((float) $stats['total_balance'], 2)) ?></div>
|
||||
<div class="balance-sub">可用 <?= h(number_format((float) $stats['available_balance'], 2)) ?> · 冻结 <?= h(number_format((float) $stats['frozen_balance'], 2)) ?></div>
|
||||
</div>
|
||||
<span class="vip-chip-large"><?= h($user ? vip_info((int) $user['vip_level'])['name'] : 'VIP3') ?></span>
|
||||
</div>
|
||||
|
||||
<div class="quick-action-grid mt-3">
|
||||
<?php foreach ($quickActions as $action): ?>
|
||||
<a class="quick-action" href="<?= h($action['href']) ?>">
|
||||
<span class="quick-action-icon"><?= app_icon_svg((string) $action['icon']) ?></span>
|
||||
<span class="quick-action-label"><?= h((string) $action['label']) ?></span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($previewMode): ?>
|
||||
<div class="app-inline-note mt-3">当前显示的是参考图风格预览数据。登录后会显示你的真实余额、任务与流水。</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">今日数据</div>
|
||||
<h2 class="app-section-title">账户看板</h2>
|
||||
</div>
|
||||
<div class="mini-stat-grid three-cols">
|
||||
<article class="app-card stat-card">
|
||||
<div class="mini-label">今日任务</div>
|
||||
<div class="stat-value"><?= h((string) $stats['today_tasks']) ?>/30</div>
|
||||
<div class="stat-meta">已开始任务数</div>
|
||||
</article>
|
||||
<article class="app-card stat-card">
|
||||
<div class="mini-label">今日收益</div>
|
||||
<div class="stat-value"><?= h(number_format((float) $stats['today_earnings'], 2)) ?></div>
|
||||
<div class="stat-meta">USDT</div>
|
||||
</article>
|
||||
<article class="app-card stat-card">
|
||||
<div class="mini-label">本月总收益</div>
|
||||
<div class="stat-value"><?= h(number_format((float) $stats['month_earnings'], 2)) ?></div>
|
||||
<div class="stat-meta">USDT</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">快捷任务</div>
|
||||
<h2 class="app-section-title">常用入口</h2>
|
||||
</div>
|
||||
<article class="app-card">
|
||||
<div class="feature-shortcuts">
|
||||
<?php foreach ($taskShortcuts as $shortcut): ?>
|
||||
<a class="feature-shortcut" href="<?= h($shortcut['href']) ?>">
|
||||
<span class="feature-icon <?= h((string) $shortcut['accent']) ?>"><?= app_icon_svg((string) $shortcut['icon']) ?></span>
|
||||
<span><?= h((string) $shortcut['label']) ?></span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">热门任务</div>
|
||||
<h2 class="app-section-title">推荐任务</h2>
|
||||
</div>
|
||||
<div class="app-task-list">
|
||||
<?php foreach ($showcaseTasks as $task): ?>
|
||||
<?php $meta = task_visual_meta($task); ?>
|
||||
<a class="app-card task-row-card" href="task.php?task=<?= h((string) $task['slug']) ?>">
|
||||
<span class="task-platform-icon <?= h((string) $meta['accent']) ?>"><?= app_icon_svg((string) $meta['icon']) ?></span>
|
||||
<span class="task-row-content">
|
||||
<span class="task-row-title"><?= h((string) $task['title']) ?></span>
|
||||
<span class="task-row-subtitle"><?= h((string) $meta['brand']) ?> · <?= h((string) $task['summary']) ?></span>
|
||||
</span>
|
||||
<span class="task-row-side">
|
||||
<span class="task-row-reward">+<?= h(number_format((float) $task['reward'], 2)) ?></span>
|
||||
<span class="task-row-arrow"><?= app_icon_svg('chevron-right') ?></span>
|
||||
</span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php if ($user): ?>
|
||||
<section class="app-page-section">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">最近动态</div>
|
||||
<h2 class="app-section-title">我的任务</h2>
|
||||
</div>
|
||||
<article class="app-card">
|
||||
<?php if ($recentOrders): ?>
|
||||
<div class="app-list-stack">
|
||||
<?php foreach ($recentOrders as $order): ?>
|
||||
<a class="app-list-item" href="order.php?id=<?= h((string) $order['id']) ?>">
|
||||
<span>
|
||||
<span class="list-title-strong">#<?= h((string) $order['id']) ?> · <?= h((string) $order['task_title']) ?></span>
|
||||
<span class="list-meta-line"><?= h(format_datetime((string) $order['claimed_at'])) ?> · <?= h(format_usdt($order['reward_usdt'])) ?></span>
|
||||
</span>
|
||||
<span><?= render_status_badge((string) $order['status']) ?></span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="empty-tip">你还没有任务记录。先进入“任务”页领取第一笔佣金。</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</section>
|
||||
<?php else: ?>
|
||||
<section class="app-page-section" id="auth">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">邮箱入口</div>
|
||||
<h2 class="app-section-title">真实开户流程</h2>
|
||||
</div>
|
||||
<article class="app-card auth-card-panel">
|
||||
<div class="auth-panel-head">
|
||||
<div>
|
||||
<div class="list-title-strong">4 步完成邮箱开户</div>
|
||||
<div class="list-meta-line">现在已经拆成独立页面:启动页 → 邮箱注册 → 验证码 → 登录,避免跳过验证码直接建号。</div>
|
||||
</div>
|
||||
<span class="tag-chip">真实流程</span>
|
||||
</div>
|
||||
|
||||
<div class="onboarding-flow-grid">
|
||||
<?php foreach ($onboardingSteps as $step): ?>
|
||||
<div class="onboarding-step-card">
|
||||
<div class="step-kicker">STEP <?= h((string) $step['step']) ?></div>
|
||||
<div class="list-title-strong mt-2"><?= h((string) $step['title']) ?></div>
|
||||
<div class="list-meta-line mt-1"><?= h((string) $step['desc']) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div class="auth-link-grid mt-3">
|
||||
<a class="btn btn-gradient" href="<?= h(start_page_url()) ?>">打开启动页</a>
|
||||
<a class="btn btn-outline-light" href="<?= h(register_page_url()) ?>">邮箱注册</a>
|
||||
<a class="btn btn-outline-light" href="<?= h(login_page_url()) ?>">已有账号登录</a>
|
||||
</div>
|
||||
|
||||
<div class="app-inline-note mt-3">验证码会发送到你填写的邮箱;如果当前 SMTP 还没配置,页面会真实提示发送失败,不会假装成功。</div>
|
||||
</article>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
<?php render_layout_end(); ?>
|
||||
|
||||
99
login.php
Normal file
99
login.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/app.php';
|
||||
|
||||
$redirectTarget = safe_redirect_target((string) ($_POST['redirect'] ?? $_GET['redirect'] ?? 'index.php'));
|
||||
if (current_user()) {
|
||||
redirect($redirectTarget);
|
||||
}
|
||||
|
||||
$prefillEmail = normalize_email((string) ($_GET['email'] ?? ''));
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$email = normalize_email((string) ($_POST['email'] ?? ''));
|
||||
|
||||
try {
|
||||
verify_csrf_or_fail();
|
||||
$user = authenticate_user($email, (string) ($_POST['password'] ?? ''));
|
||||
flash('success', '欢迎回来,当前账号 ' . ($user['user_code'] ?? '') . ' 已登录。');
|
||||
redirect($redirectTarget);
|
||||
} catch (Throwable $exception) {
|
||||
flash('danger', $exception->getMessage());
|
||||
redirect(login_page_url($redirectTarget, ['email' => $email]));
|
||||
}
|
||||
}
|
||||
|
||||
render_layout_start('账号登录', '登录页,使用已验证邮箱与密码进入前台 5 个页面。', 'auth');
|
||||
?>
|
||||
<section class="app-page-section auth-page-shell">
|
||||
<article class="app-card gradient-card hero-card">
|
||||
<div class="tiny-eyebrow">Step 4 / 4</div>
|
||||
<h1 class="app-hero-title compact-title">账号登录</h1>
|
||||
<p class="app-hero-copy mb-0">这是开户链路的最后一步。登录成功后,你就能进入首页、任务、VIP、钱包、我的 5 个主页面。</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section auth-page-shell">
|
||||
<div class="auth-stack auth-page-grid">
|
||||
<article class="app-card auth-card-panel">
|
||||
<div class="auth-panel-head">
|
||||
<div>
|
||||
<div class="list-title-strong">输入邮箱和密码</div>
|
||||
<div class="list-meta-line">如果你刚完成验证码验证,可以直接在这里登录。</div>
|
||||
</div>
|
||||
<span class="tag-chip">登录</span>
|
||||
</div>
|
||||
|
||||
<form method="post" class="auth-form-grid">
|
||||
<input type="hidden" name="csrf_token" value="<?= h(csrf_token()) ?>">
|
||||
<input type="hidden" name="redirect" value="<?= h($redirectTarget) ?>">
|
||||
<div>
|
||||
<label class="form-label" for="login-email">邮箱地址</label>
|
||||
<input class="form-control" id="login-email" type="email" name="email" placeholder="example@email.com" value="<?= h($prefillEmail) ?>" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label" for="login-password">密码</label>
|
||||
<input class="form-control" id="login-password" type="password" name="password" placeholder="输入你的密码" required>
|
||||
</div>
|
||||
<button class="btn btn-gradient" type="submit">登录账号</button>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<article class="app-card auth-card-panel">
|
||||
<div class="auth-panel-head">
|
||||
<div>
|
||||
<div class="list-title-strong">登录后你能做什么</div>
|
||||
<div class="list-meta-line">这不是演示按钮,而是已经接入真实数据库的前台入口。</div>
|
||||
</div>
|
||||
<span class="tag-chip secondary-chip">进入系统</span>
|
||||
</div>
|
||||
|
||||
<div class="app-list-stack">
|
||||
<div class="app-list-item static-row">
|
||||
<span>
|
||||
<span class="list-title-strong">首页</span>
|
||||
<span class="list-meta-line">查看真实账户余额、最近任务和快捷入口。</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="app-list-item static-row">
|
||||
<span>
|
||||
<span class="list-title-strong">任务 / VIP / 钱包 / 我的</span>
|
||||
<span class="list-meta-line">这些页面都已经可以打开,任务和 VIP 也有真实数据库逻辑。</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="app-list-item static-row">
|
||||
<span>
|
||||
<span class="list-title-strong">以后还能继续扩展</span>
|
||||
<span class="list-meta-line">比如充值、提现、转账、邀请、安全中心和客服系统。</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="auth-link-grid mt-3">
|
||||
<a class="btn btn-outline-light" href="<?= h(register_page_url($redirectTarget, $prefillEmail !== '' ? ['email' => $prefillEmail] : [])) ?>">没有账号,去注册</a>
|
||||
<a class="btn btn-outline-light" href="<?= h(start_page_url($redirectTarget)) ?>">返回启动页</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
<?php render_layout_end(); ?>
|
||||
6
logout.php
Normal file
6
logout.php
Normal file
@ -0,0 +1,6 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/app.php';
|
||||
logout_current_user();
|
||||
flash('success', '你已安全退出当前账号。');
|
||||
redirect(start_page_url());
|
||||
153
operations.php
Normal file
153
operations.php
Normal file
@ -0,0 +1,153 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/app.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
try {
|
||||
verify_csrf_or_fail();
|
||||
$action = (string) ($_POST['action'] ?? '');
|
||||
if ($action === 'review_order') {
|
||||
$orderId = (int) ($_POST['order_id'] ?? 0);
|
||||
$decision = (string) ($_POST['decision'] ?? 'reject');
|
||||
$reviewed = review_task_order($orderId, $decision, (string) ($_POST['review_note'] ?? ''));
|
||||
flash('success', '订单 #' . ($reviewed['id'] ?? $orderId) . ' 已' . ($decision === 'approve' ? '审核通过并入账。' : '驳回并释放冻结金额。'));
|
||||
redirect('operations.php');
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
flash('danger', $exception->getMessage());
|
||||
redirect('operations.php');
|
||||
}
|
||||
}
|
||||
|
||||
$stats = get_operator_stats();
|
||||
$pendingOrders = get_operator_orders('pending_review', 20);
|
||||
$recentOrders = get_recent_reviewed_orders(12);
|
||||
render_layout_start('运营审核台', '任务待审核队列与资金联动。', 'ops');
|
||||
?>
|
||||
<section class="mb-4 mb-lg-5">
|
||||
<div class="section-heading d-flex align-items-center justify-content-between flex-wrap gap-3 mb-3">
|
||||
<div>
|
||||
<div class="eyebrow">Ops board</div>
|
||||
<h1 class="section-title">任务审核台</h1>
|
||||
</div>
|
||||
<div class="supporting-copy">这是第一版后台切片:用于验证审核动作、冻结佣金与到账流水联动。</div>
|
||||
</div>
|
||||
<div class="stats-grid four-up">
|
||||
<article class="metric-card">
|
||||
<div class="metric-label">今日新增用户</div>
|
||||
<div class="metric-value"><?= h((string) $stats['today_users']) ?></div>
|
||||
</article>
|
||||
<article class="metric-card">
|
||||
<div class="metric-label">待审核订单</div>
|
||||
<div class="metric-value"><?= h((string) $stats['pending_reviews']) ?></div>
|
||||
</article>
|
||||
<article class="metric-card">
|
||||
<div class="metric-label">已通过订单</div>
|
||||
<div class="metric-value"><?= h((string) $stats['approved_orders']) ?></div>
|
||||
</article>
|
||||
<article class="metric-card">
|
||||
<div class="metric-label">累计入账</div>
|
||||
<div class="metric-value"><?= h(format_usdt($stats['credited_amount'])) ?></div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mb-4 mb-lg-5">
|
||||
<div class="section-heading mb-3">
|
||||
<div class="eyebrow">Pending queue</div>
|
||||
<h2 class="section-title">待审核任务</h2>
|
||||
</div>
|
||||
<div class="row g-3">
|
||||
<?php if ($pendingOrders): ?>
|
||||
<?php foreach ($pendingOrders as $order): ?>
|
||||
<div class="col-12">
|
||||
<article class="panel ops-card h-100">
|
||||
<div class="d-flex justify-content-between gap-3 flex-wrap mb-3">
|
||||
<div>
|
||||
<div class="list-title"><?= h($order['task_title']) ?> · <?= h($order['platform_name']) ?></div>
|
||||
<div class="supporting-copy">用户 <?= h($order['email']) ?> · ID <?= h($order['user_code']) ?> · <?= h(vip_info((int) $order['vip_level'])['name']) ?></div>
|
||||
</div>
|
||||
<?= render_status_badge((string) $order['status']) ?>
|
||||
</div>
|
||||
<div class="task-meta-grid mb-3">
|
||||
<div>
|
||||
<div class="metric-label">提交时间</div>
|
||||
<div class="task-meta-value"><?= h(format_datetime((string) $order['submitted_at'])) ?></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="metric-label">领取时间</div>
|
||||
<div class="task-meta-value"><?= h(format_datetime((string) $order['claimed_at'])) ?></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="metric-label">任务佣金</div>
|
||||
<div class="task-meta-value"><?= h(format_usdt($order['reward_usdt'])) ?></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="metric-label">用户 VIP</div>
|
||||
<div class="task-meta-value"><?= h(vip_info((int) $order['vip_level'])['name']) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field-panel mb-3">
|
||||
<div class="metric-label">用户完成备注</div>
|
||||
<div class="field-value pre-wrap"><?= h((string) $order['proof_note']) ?></div>
|
||||
</div>
|
||||
<form method="post" class="d-grid gap-3">
|
||||
<input type="hidden" name="csrf_token" value="<?= h(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="review_order">
|
||||
<input type="hidden" name="order_id" value="<?= h((string) $order['id']) ?>">
|
||||
<div>
|
||||
<label class="form-label" for="review-note-<?= h((string) $order['id']) ?>">审核说明</label>
|
||||
<textarea class="form-control" id="review-note-<?= h((string) $order['id']) ?>" name="review_note" rows="3" placeholder="例如:内容验证通过 / 备注不足,需重做"></textarea>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<button class="btn btn-light btn-sm px-3" type="submit" name="decision" value="approve" data-confirm="确认审核通过并将佣金转入可用余额?">审核通过并入账</button>
|
||||
<button class="btn btn-outline-danger btn-sm px-3" type="submit" name="decision" value="reject" data-confirm="确认驳回并释放冻结金额?">驳回并释放冻结</button>
|
||||
<a class="btn btn-outline-light btn-sm px-3" href="order.php?id=<?= h((string) $order['id']) ?>&ops=1">用户订单视图</a>
|
||||
</div>
|
||||
</form>
|
||||
</article>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
<?php else: ?>
|
||||
<div class="col-12">
|
||||
<div class="panel empty-state">
|
||||
<div class="empty-title">暂无待审核订单</div>
|
||||
<p class="mb-0">先用前台账号领取并提交一个任务,再回到这里执行审核。</p>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="mb-4">
|
||||
<div class="section-heading mb-3">
|
||||
<div class="eyebrow">History</div>
|
||||
<h2 class="section-title">最近已处理订单</h2>
|
||||
</div>
|
||||
<div class="panel p-0 overflow-hidden">
|
||||
<?php if ($recentOrders): ?>
|
||||
<div class="list-shell">
|
||||
<?php foreach ($recentOrders as $order): ?>
|
||||
<div class="list-row">
|
||||
<div>
|
||||
<div class="list-title">#<?= h((string) $order['id']) ?> · <?= h($order['task_title']) ?></div>
|
||||
<div class="supporting-copy"><?= h($order['email']) ?> · <?= h(format_datetime((string) $order['reviewed_at'])) ?></div>
|
||||
</div>
|
||||
<div class="d-flex align-items-center gap-3 ms-auto">
|
||||
<div class="text-end">
|
||||
<div class="list-amount"><?= h(format_usdt($order['reward_usdt'])) ?></div>
|
||||
</div>
|
||||
<?= render_status_badge((string) $order['status']) ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="empty-state">
|
||||
<div class="empty-title">暂无历史审核记录</div>
|
||||
<p class="mb-0">处理第一笔任务后,这里会形成后台审核时间线。</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php render_layout_end(); ?>
|
||||
149
order.php
Normal file
149
order.php
Normal file
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/app.php';
|
||||
|
||||
$opsMode = (string) ($_GET['ops'] ?? '') === '1';
|
||||
$orderId = (int) ($_GET['id'] ?? 0);
|
||||
$user = $opsMode ? current_user() : require_user();
|
||||
$order = get_order_by_id($orderId, $opsMode ? null : (int) $user['id']);
|
||||
if (!$order) {
|
||||
http_response_code(404);
|
||||
render_layout_start('订单不存在', '未找到指定订单。', 'tasks');
|
||||
?>
|
||||
<div class="panel">
|
||||
<div class="eyebrow">404</div>
|
||||
<h1 class="section-title">订单不存在</h1>
|
||||
<p class="supporting-copy mb-3">请返回概览查看你的任务记录。</p>
|
||||
<a class="btn btn-light btn-sm" href="index.php">返回概览</a>
|
||||
</div>
|
||||
<?php
|
||||
render_layout_end();
|
||||
exit;
|
||||
}
|
||||
|
||||
$wallet = wallet_snapshot((int) $order['user_id']);
|
||||
$task = task_by_slug((string) $order['task_slug']);
|
||||
$unlockTimestamp = strtotime((string) $order['claimed_at']) + (int) $order['countdown_seconds'];
|
||||
render_layout_start('订单 #' . $orderId, '订单详情、审核结果与钱包联动。', $opsMode ? 'ops' : 'tasks');
|
||||
?>
|
||||
<section class="mb-4">
|
||||
<div class="row g-4">
|
||||
<div class="col-lg-7">
|
||||
<article class="panel h-100">
|
||||
<div class="d-flex align-items-start justify-content-between gap-3 flex-wrap mb-3">
|
||||
<div>
|
||||
<div class="eyebrow">Order detail</div>
|
||||
<h1 class="section-title mb-2">订单 #<?= h((string) $order['id']) ?> · <?= h($order['task_title']) ?></h1>
|
||||
<div class="supporting-copy"><?= h($order['platform_name']) ?> · <?= h($order['task_type']) ?></div>
|
||||
</div>
|
||||
<?= render_status_badge((string) $order['status']) ?>
|
||||
</div>
|
||||
|
||||
<div class="stats-grid three-up mb-4">
|
||||
<div class="metric-card compact">
|
||||
<div class="metric-label">佣金</div>
|
||||
<div class="metric-value"><?= h(format_usdt($order['reward_usdt'])) ?></div>
|
||||
</div>
|
||||
<div class="metric-card compact">
|
||||
<div class="metric-label">VIP 限制</div>
|
||||
<div class="metric-value">VIP<?= h((string) $order['vip_required']) ?></div>
|
||||
</div>
|
||||
<div class="metric-card compact">
|
||||
<div class="metric-label">倒计时</div>
|
||||
<div class="metric-value"><?= h((string) $order['countdown_seconds']) ?> 秒</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="timeline-list mb-4">
|
||||
<div class="timeline-item">
|
||||
<div class="timeline-index">1</div>
|
||||
<div>
|
||||
<div class="list-title">已领取</div>
|
||||
<div class="supporting-copy"><?= h(format_datetime($order['claimed_at'])) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item<?= !empty($order['submitted_at']) ? '' : ' is-dimmed' ?>">
|
||||
<div class="timeline-index">2</div>
|
||||
<div>
|
||||
<div class="list-title">已提交审核</div>
|
||||
<div class="supporting-copy"><?= h(format_datetime($order['submitted_at'])) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="timeline-item<?= !empty($order['reviewed_at']) ? '' : ' is-dimmed' ?>">
|
||||
<div class="timeline-index">3</div>
|
||||
<div>
|
||||
<div class="list-title">审核完成</div>
|
||||
<div class="supporting-copy"><?= h(format_datetime($order['reviewed_at'])) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ((string) $order['status'] === 'claimed'): ?>
|
||||
<div class="countdown-card mb-3">
|
||||
<div class="metric-label">剩余倒计时</div>
|
||||
<div class="countdown-value" data-countdown="<?= h((string) $unlockTimestamp) ?>"><?= h((string) max(0, (int) ($unlockTimestamp - time()))) ?> 秒</div>
|
||||
<div class="supporting-copy">完成后回到任务详情页提交完成备注。</div>
|
||||
</div>
|
||||
<a class="btn btn-light btn-sm" href="task.php?task=<?= h((string) $order['task_slug']) ?>">返回任务详情提交</a>
|
||||
<?php elseif ((string) $order['status'] === 'pending_review'): ?>
|
||||
<div class="notice-card notice-warning mb-3">
|
||||
<div class="notice-title">佣金冻结中</div>
|
||||
<p class="mb-0">该订单已提交审核,<?= h(format_usdt($order['reward_usdt'])) ?> 已计入冻结余额,等待运营审批后转入可用余额。</p>
|
||||
</div>
|
||||
<?php elseif ((string) $order['status'] === 'approved'): ?>
|
||||
<div class="notice-card mb-3">
|
||||
<div class="notice-title">佣金已入账</div>
|
||||
<p class="mb-0">审核通过后,佣金已转入可用余额,可在钱包页查看流水。</p>
|
||||
</div>
|
||||
<?php elseif ((string) $order['status'] === 'rejected'): ?>
|
||||
<div class="notice-card notice-muted mb-3">
|
||||
<div class="notice-title">任务未通过审核</div>
|
||||
<p class="mb-0">冻结佣金已释放。你可以返回任务大厅领取其他任务继续测试流程。</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($order['proof_note'])): ?>
|
||||
<div class="field-panel mt-3">
|
||||
<div class="metric-label">完成备注</div>
|
||||
<div class="field-value pre-wrap"><?= h((string) $order['proof_note']) ?></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($order['review_note'])): ?>
|
||||
<div class="field-panel mt-3">
|
||||
<div class="metric-label">审核说明</div>
|
||||
<div class="field-value pre-wrap"><?= h((string) $order['review_note']) ?></div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<article class="panel h-100">
|
||||
<div class="eyebrow">Wallet effect</div>
|
||||
<h2 class="panel-title">钱包联动结果</h2>
|
||||
<div class="stats-grid two-up mt-3">
|
||||
<div class="metric-card compact">
|
||||
<div class="metric-label">可提现余额</div>
|
||||
<div class="metric-value"><?= h(format_usdt($wallet['available_balance'])) ?></div>
|
||||
</div>
|
||||
<div class="metric-card compact">
|
||||
<div class="metric-label">冻结余额</div>
|
||||
<div class="metric-value"><?= h(format_usdt($wallet['frozen_balance'])) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="notice-card mt-3">
|
||||
<div class="notice-title">下一步</div>
|
||||
<p class="mb-0">打开钱包查看资金流水,或进入运营台执行审核动作,体验“冻结 → 入账/释放”的完整链路。</p>
|
||||
</div>
|
||||
<div class="d-flex flex-wrap gap-2 mt-3">
|
||||
<a class="btn btn-light btn-sm px-3" href="wallet.php">查看钱包</a>
|
||||
<a class="btn btn-outline-light btn-sm px-3" href="operations.php">打开运营台</a>
|
||||
<?php if ($task): ?>
|
||||
<a class="btn btn-outline-light btn-sm px-3" href="task.php?task=<?= h((string) $task['slug']) ?>">返回任务</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php render_layout_end(); ?>
|
||||
120
profile.php
Normal file
120
profile.php
Normal file
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/app.php';
|
||||
|
||||
$user = current_user();
|
||||
$previewMode = !$user;
|
||||
$profileUser = $user ?: [
|
||||
'email' => 'example@email.com',
|
||||
'user_code' => '100086',
|
||||
'vip_level' => 3,
|
||||
];
|
||||
$stats = $user
|
||||
? get_dashboard_stats((int) $user['id'])
|
||||
: [
|
||||
'available_balance' => 5560.25,
|
||||
'frozen_balance' => 120.00,
|
||||
'total_balance' => 5680.25,
|
||||
'today_earnings' => 1000.00,
|
||||
'month_earnings' => 18560.20,
|
||||
'today_tasks' => 30,
|
||||
'active_orders' => 3,
|
||||
'completed_orders' => 12,
|
||||
];
|
||||
$menuItems = [
|
||||
['title' => '我的资产', 'desc' => '查看余额与资金流水', 'href' => 'wallet.php', 'icon' => 'wallet'],
|
||||
['title' => '我的任务', 'desc' => '查看任务大厅与订单', 'href' => 'task.php', 'icon' => 'tasks'],
|
||||
['title' => 'VIP 中心', 'desc' => '选择更高返佣等级', 'href' => 'vip.php', 'icon' => 'vip'],
|
||||
['title' => '邀请好友', 'desc' => '后续可扩展邀请返佣', 'href' => 'index.php', 'icon' => 'spark'],
|
||||
['title' => '安全中心', 'desc' => '当前账户与系统状态', 'href' => 'healthz.php', 'icon' => 'lock'],
|
||||
['title' => '联系客服', 'desc' => '下一步接入内置 IM', 'href' => '#support-panel', 'icon' => 'support'],
|
||||
];
|
||||
|
||||
render_layout_start('我的', '个人中心页,展示账号信息、常用菜单入口与客服/后台预览。', 'profile');
|
||||
?>
|
||||
<section class="app-page-section">
|
||||
<article class="app-card gradient-card hero-card">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<div class="profile-avatar-large"><?= h(app_user_initial($profileUser)) ?></div>
|
||||
<div class="flex-grow-1 min-w-0">
|
||||
<div class="tiny-eyebrow">我的(个人中心)</div>
|
||||
<h1 class="app-section-title mb-1"><?= h(app_user_name($profileUser)) ?></h1>
|
||||
<div class="list-meta-line"><?= h((string) $profileUser['email']) ?> · ID <?= h((string) $profileUser['user_code']) ?></div>
|
||||
</div>
|
||||
<span class="vip-chip-large"><?= h(vip_info((int) $profileUser['vip_level'])['name']) ?></span>
|
||||
</div>
|
||||
<?php if ($previewMode): ?>
|
||||
<div class="app-inline-note mt-3">当前是“我的”页面视觉预览。登录后会显示你的真实账户资料与任务状态。</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section">
|
||||
<div class="mini-stat-grid three-cols">
|
||||
<article class="app-card stat-card">
|
||||
<div class="mini-label">总资产</div>
|
||||
<div class="stat-value"><?= h(number_format((float) $stats['total_balance'], 2)) ?></div>
|
||||
<div class="stat-meta">USDT</div>
|
||||
</article>
|
||||
<article class="app-card stat-card">
|
||||
<div class="mini-label">进行中</div>
|
||||
<div class="stat-value"><?= h((string) $stats['active_orders']) ?></div>
|
||||
<div class="stat-meta">任务</div>
|
||||
</article>
|
||||
<article class="app-card stat-card">
|
||||
<div class="mini-label">已完成</div>
|
||||
<div class="stat-value"><?= h((string) $stats['completed_orders']) ?></div>
|
||||
<div class="stat-meta">任务</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section">
|
||||
<article class="app-card">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">常用功能</div>
|
||||
<h2 class="app-section-title">菜单入口</h2>
|
||||
</div>
|
||||
<div class="app-list-stack">
|
||||
<?php foreach ($menuItems as $item): ?>
|
||||
<a class="app-list-item" href="<?= h((string) $item['href']) ?>">
|
||||
<span class="menu-item-left">
|
||||
<span class="task-platform-icon accent-blue small-menu-icon"><?= app_icon_svg((string) $item['icon']) ?></span>
|
||||
<span>
|
||||
<span class="list-title-strong"><?= h((string) $item['title']) ?></span>
|
||||
<span class="list-meta-line"><?= h((string) $item['desc']) ?></span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="task-row-arrow"><?= app_icon_svg('chevron-right') ?></span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
<?php if ($user): ?>
|
||||
<a class="app-list-item" href="logout.php">
|
||||
<span class="menu-item-left">
|
||||
<span class="task-platform-icon accent-pink small-menu-icon"><?= app_icon_svg('withdraw') ?></span>
|
||||
<span>
|
||||
<span class="list-title-strong">退出登录</span>
|
||||
<span class="list-meta-line">安全退出当前账号</span>
|
||||
</span>
|
||||
</span>
|
||||
<span class="task-row-arrow"><?= app_icon_svg('chevron-right') ?></span>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section" id="support-panel">
|
||||
<article class="app-card">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">客服 / 后台</div>
|
||||
<h2 class="app-section-title">下一步建议</h2>
|
||||
</div>
|
||||
<div class="app-inline-note mb-3">你前面确认过要做“内置 IM 客服”。这一步我先把入口样式放好了;下一步我可以继续帮你把“联系客服 → 建会话 → 发消息 → 后台客服工作台”接成真实页面。</div>
|
||||
<div class="d-grid gap-2">
|
||||
<a class="btn btn-gradient" href="operations.php">先看后台工作台预览</a>
|
||||
<a class="btn btn-outline-light" href="healthz.php">查看系统健康检查</a>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
<?php render_layout_end(); ?>
|
||||
112
register.php
Normal file
112
register.php
Normal file
@ -0,0 +1,112 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/app.php';
|
||||
|
||||
$redirectTarget = safe_redirect_target((string) ($_POST['redirect'] ?? $_GET['redirect'] ?? 'index.php'));
|
||||
if (current_user()) {
|
||||
redirect($redirectTarget);
|
||||
}
|
||||
|
||||
$prefillEmail = normalize_email((string) ($_GET['email'] ?? ''));
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$email = normalize_email((string) ($_POST['email'] ?? ''));
|
||||
|
||||
try {
|
||||
verify_csrf_or_fail();
|
||||
|
||||
$password = (string) ($_POST['password'] ?? '');
|
||||
$confirmPassword = (string) ($_POST['confirm_password'] ?? '');
|
||||
if ($password !== $confirmPassword) {
|
||||
throw new RuntimeException('两次输入的密码不一致。');
|
||||
}
|
||||
|
||||
begin_signup_verification($email, $password);
|
||||
flash('success', '验证码已发送到你的邮箱,请在 10 分钟内完成验证。');
|
||||
redirect(verify_page_url($email, $redirectTarget));
|
||||
} catch (Throwable $exception) {
|
||||
flash('danger', $exception->getMessage());
|
||||
redirect(register_page_url($redirectTarget, ['email' => $email]));
|
||||
}
|
||||
}
|
||||
|
||||
render_layout_start('邮箱注册', '邮箱注册页,提交邮箱与密码后发送 6 位验证码。', 'auth');
|
||||
?>
|
||||
<section class="app-page-section auth-page-shell">
|
||||
<article class="app-card gradient-card hero-card">
|
||||
<div class="tiny-eyebrow">Step 2 / 4</div>
|
||||
<h1 class="app-hero-title compact-title">邮箱注册</h1>
|
||||
<p class="app-hero-copy mb-0">这里不再直接创建账号。先填写邮箱和密码,系统会先发 6 位验证码,验证通过后才真正建号。</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section auth-page-shell">
|
||||
<div class="auth-stack auth-page-grid">
|
||||
<article class="app-card auth-card-panel">
|
||||
<div class="auth-panel-head">
|
||||
<div>
|
||||
<div class="list-title-strong">填写注册信息</div>
|
||||
<div class="list-meta-line">验证码将发送到你的邮箱,账户创建会等到验证通过后再执行。</div>
|
||||
</div>
|
||||
<span class="tag-chip">注册</span>
|
||||
</div>
|
||||
|
||||
<form method="post" class="auth-form-grid">
|
||||
<input type="hidden" name="csrf_token" value="<?= h(csrf_token()) ?>">
|
||||
<input type="hidden" name="redirect" value="<?= h($redirectTarget) ?>">
|
||||
<div>
|
||||
<label class="form-label" for="register-email">邮箱地址</label>
|
||||
<input class="form-control" id="register-email" type="email" name="email" placeholder="example@email.com" value="<?= h($prefillEmail) ?>" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label" for="register-password">登录密码</label>
|
||||
<input class="form-control" id="register-password" type="password" name="password" placeholder="至少 6 位字符" required>
|
||||
</div>
|
||||
<div>
|
||||
<label class="form-label" for="register-password-confirm">确认密码</label>
|
||||
<input class="form-control" id="register-password-confirm" type="password" name="confirm_password" placeholder="再次输入密码" required>
|
||||
</div>
|
||||
<button class="btn btn-gradient" type="submit">发送验证码</button>
|
||||
</form>
|
||||
</article>
|
||||
|
||||
<article class="app-card auth-card-panel">
|
||||
<div class="auth-panel-head">
|
||||
<div>
|
||||
<div class="list-title-strong">注册后会发生什么</div>
|
||||
<div class="list-meta-line">我把复杂流程翻成一句人话:先确认邮箱,再正式开户。</div>
|
||||
</div>
|
||||
<span class="tag-chip secondary-chip">说明</span>
|
||||
</div>
|
||||
|
||||
<div class="app-list-stack">
|
||||
<div class="app-list-item static-row">
|
||||
<span>
|
||||
<span class="list-title-strong">第 1 步:发送 6 位验证码</span>
|
||||
<span class="list-meta-line">系统会用你填写的邮箱作为收件人,验证码有效期 10 分钟。</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="app-list-item static-row">
|
||||
<span>
|
||||
<span class="list-title-strong">第 2 步:验证通过后建号</span>
|
||||
<span class="list-meta-line">数据库会真实创建用户、6 位数字 ID、钱包与 VIP0。</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="app-list-item static-row">
|
||||
<span>
|
||||
<span class="list-title-strong">第 3 步:回到登录页</span>
|
||||
<span class="list-meta-line">邮箱验证通过后,再进入登录页使用密码登录。</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="app-inline-note mt-3">验证码依赖当前 SMTP 环境。如果还没有配置好发信参数,这里会真实提示失败,方便你知道问题在邮件配置而不是页面。</div>
|
||||
|
||||
<div class="auth-link-grid mt-3">
|
||||
<a class="btn btn-outline-light" href="<?= h(login_page_url($redirectTarget)) ?>">已有账号,去登录</a>
|
||||
<a class="btn btn-outline-light" href="<?= h(start_page_url($redirectTarget)) ?>">返回启动页</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
<?php render_layout_end(); ?>
|
||||
94
start.php
Normal file
94
start.php
Normal file
@ -0,0 +1,94 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/app.php';
|
||||
|
||||
$user = current_user();
|
||||
$redirectTarget = safe_redirect_target((string) ($_GET['redirect'] ?? 'index.php'));
|
||||
$steps = [
|
||||
['step' => '01', 'title' => '先看启动页', 'desc' => '了解首页、任务、VIP、钱包、我的 5 个主入口怎么运作。'],
|
||||
['step' => '02', 'title' => '邮箱注册', 'desc' => '填写邮箱和密码,系统准备发送 6 位数字验证码。'],
|
||||
['step' => '03', 'title' => '输入验证码', 'desc' => '邮件验证通过后,系统才会正式创建账户、钱包和 VIP0。'],
|
||||
['step' => '04', 'title' => '登录系统', 'desc' => '使用已验证邮箱登录,再进入任务、VIP、钱包和我的。'],
|
||||
];
|
||||
$highlights = [
|
||||
['label' => '账户创建', 'value' => '真实数据库写入'],
|
||||
['label' => '验证方式', 'value' => '邮箱 6 位验证码'],
|
||||
['label' => '开户结果', 'value' => '6 位 ID + 钱包 + VIP0'],
|
||||
];
|
||||
|
||||
render_layout_start('启动页', '启动页,展示真实邮箱开户注册流程入口与 4 步说明。', 'auth');
|
||||
?>
|
||||
<section class="app-page-section auth-page-shell">
|
||||
<article class="app-card gradient-card hero-card">
|
||||
<div class="tiny-eyebrow">Step 1 / 4</div>
|
||||
<h1 class="app-hero-title">邮箱开户从这里开始</h1>
|
||||
<p class="app-hero-copy mb-0">这一步相当于“欢迎页”。你先看清流程,再进入注册、验证码和登录页面,整个入口会比直接把表单塞进首页更清楚。</p>
|
||||
|
||||
<div class="detail-meta-grid mt-3">
|
||||
<?php foreach ($highlights as $item): ?>
|
||||
<div>
|
||||
<div class="mini-label"><?= h((string) $item['label']) ?></div>
|
||||
<div class="detail-mini-value"><?= h((string) $item['value']) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<div class="auth-link-grid mt-4">
|
||||
<?php if ($user): ?>
|
||||
<a class="btn btn-gradient" href="<?= h($redirectTarget) ?>">继续进入系统</a>
|
||||
<a class="btn btn-outline-light" href="index.php">回到首页</a>
|
||||
<?php else: ?>
|
||||
<a class="btn btn-gradient" href="<?= h(register_page_url($redirectTarget)) ?>">开始注册</a>
|
||||
<a class="btn btn-outline-light" href="<?= h(login_page_url($redirectTarget)) ?>">已有账号登录</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section auth-page-shell">
|
||||
<article class="app-card auth-card-panel">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">开户流程</div>
|
||||
<h2 class="app-section-title">4 个页面都是真实页面</h2>
|
||||
</div>
|
||||
<div class="onboarding-flow-grid">
|
||||
<?php foreach ($steps as $step): ?>
|
||||
<div class="onboarding-step-card">
|
||||
<div class="step-kicker">STEP <?= h((string) $step['step']) ?></div>
|
||||
<div class="list-title-strong mt-2"><?= h((string) $step['title']) ?></div>
|
||||
<div class="list-meta-line mt-1"><?= h((string) $step['desc']) ?></div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section auth-page-shell">
|
||||
<article class="app-card">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">说明</div>
|
||||
<h2 class="app-section-title">对你来说,这一步做了什么</h2>
|
||||
</div>
|
||||
<div class="app-list-stack">
|
||||
<div class="app-list-item static-row">
|
||||
<span>
|
||||
<span class="list-title-strong">不再从首页直接建号</span>
|
||||
<span class="list-meta-line">避免用户跳过验证码,减少“看起来是真的,但其实没验证邮箱”的问题。</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="app-list-item static-row">
|
||||
<span>
|
||||
<span class="list-title-strong">注册和登录拆成独立页面</span>
|
||||
<span class="list-meta-line">这样更接近你给的参考图结构,也更像真实 App 的开户流程。</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="app-list-item static-row">
|
||||
<span>
|
||||
<span class="list-title-strong">验证码真的会发邮件</span>
|
||||
<span class="list-meta-line">如果当前 SMTP 未配置,页面会真实提示失败,你就知道不是 UI 假动作。</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
<?php render_layout_end(); ?>
|
||||
282
task.php
Normal file
282
task.php
Normal file
@ -0,0 +1,282 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/app.php';
|
||||
|
||||
$user = current_user();
|
||||
$taskSlug = (string) ($_GET['task'] ?? '');
|
||||
$selectedCategory = (string) ($_GET['category'] ?? 'all');
|
||||
$categories = task_category_options();
|
||||
if (!isset($categories[$selectedCategory])) {
|
||||
$selectedCategory = 'all';
|
||||
}
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
try {
|
||||
verify_csrf_or_fail();
|
||||
$user = require_user();
|
||||
$task = task_by_slug((string) ($_POST['task_slug'] ?? $taskSlug));
|
||||
if (!$task) {
|
||||
throw new RuntimeException('任务不存在,请返回任务大厅重试。');
|
||||
}
|
||||
|
||||
$action = (string) ($_POST['action'] ?? '');
|
||||
if ($action === 'claim_task') {
|
||||
$order = create_task_order((int) $user['id'], $task);
|
||||
flash('success', '任务已领取,倒计时已开始。订单 #' . ($order['id'] ?? '') . ' 已创建。');
|
||||
redirect('task.php?task=' . urlencode((string) $task['slug']));
|
||||
}
|
||||
|
||||
if ($action === 'submit_order') {
|
||||
$orderId = (int) ($_POST['order_id'] ?? 0);
|
||||
$order = submit_task_order((int) $user['id'], $orderId, (string) ($_POST['proof_note'] ?? ''));
|
||||
flash('success', '任务已提交审核,佣金已进入冻结余额。');
|
||||
redirect('order.php?id=' . urlencode((string) ($order['id'] ?? $orderId)));
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
flash('danger', $exception->getMessage());
|
||||
if ($taskSlug !== '') {
|
||||
redirect('task.php?task=' . urlencode($taskSlug));
|
||||
}
|
||||
redirect('task.php');
|
||||
}
|
||||
}
|
||||
|
||||
$task = $taskSlug !== '' ? task_by_slug($taskSlug) : null;
|
||||
if ($taskSlug !== '' && !$task) {
|
||||
http_response_code(404);
|
||||
render_layout_start('任务不存在', '未找到指定任务。', 'tasks');
|
||||
?>
|
||||
<section class="app-page-section">
|
||||
<article class="app-card">
|
||||
<div class="tiny-eyebrow">404</div>
|
||||
<h1 class="app-section-title mb-2">任务不存在</h1>
|
||||
<p class="app-copy mb-3">请返回任务大厅选择一个有效任务。</p>
|
||||
<a class="btn btn-gradient" href="task.php">返回任务大厅</a>
|
||||
</article>
|
||||
</section>
|
||||
<?php
|
||||
render_layout_end();
|
||||
exit;
|
||||
}
|
||||
|
||||
$previewNotice = !$user;
|
||||
$allTasks = array_values(task_catalog());
|
||||
$activeOrdersBySlug = [];
|
||||
if ($user) {
|
||||
foreach (get_user_orders((int) $user['id'], 30) as $orderRow) {
|
||||
$status = (string) ($orderRow['status'] ?? '');
|
||||
$slug = (string) ($orderRow['task_slug'] ?? '');
|
||||
if ($slug !== '' && in_array($status, ['claimed', 'pending_review'], true) && !isset($activeOrdersBySlug[$slug])) {
|
||||
$activeOrdersBySlug[$slug] = $orderRow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!$task) {
|
||||
$filteredTasks = array_values(array_filter($allTasks, static function (array $entry) use ($selectedCategory): bool {
|
||||
if ($selectedCategory === 'all') {
|
||||
return true;
|
||||
}
|
||||
return task_visual_meta($entry)['category'] === $selectedCategory;
|
||||
}));
|
||||
|
||||
render_layout_start('任务', '任务大厅页,支持分类筛选、任务详情入口与领取状态展示。', 'tasks');
|
||||
?>
|
||||
<section class="app-page-section">
|
||||
<article class="app-card gradient-card slim-hero-card">
|
||||
<div class="tiny-eyebrow">任务大厅</div>
|
||||
<h1 class="app-hero-title compact-title">选择你想完成的任务</h1>
|
||||
<p class="app-hero-copy mb-0">视频、点赞、社媒、网站、应用任务都能从这里进入。</p>
|
||||
<?php if ($previewNotice): ?>
|
||||
<div class="app-inline-note mt-3">现在是游客预览模式:可以浏览 5 个页面,登录后才能正式领取任务。</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section">
|
||||
<div class="category-tabs" aria-label="任务分类">
|
||||
<?php foreach ($categories as $key => $label): ?>
|
||||
<a class="category-tab<?= $selectedCategory === $key ? ' active' : '' ?>" href="task.php<?= $key === 'all' ? '' : '?category=' . urlencode($key) ?>"><?= h($label) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section">
|
||||
<div class="app-task-list">
|
||||
<?php foreach ($filteredTasks as $entry): ?>
|
||||
<?php
|
||||
$meta = task_visual_meta($entry);
|
||||
$activeOrder = $user ? ($activeOrdersBySlug[(string) $entry['slug']] ?? null) : null;
|
||||
$canAccess = $user ? can_access_task($user, $entry) : ((int) $entry['vip_required'] === 0);
|
||||
?>
|
||||
<article class="app-card task-list-row">
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<span class="task-platform-icon <?= h((string) $meta['accent']) ?>"><?= app_icon_svg((string) $meta['icon']) ?></span>
|
||||
<div class="flex-grow-1 min-w-0">
|
||||
<div class="task-row-title"><?= h((string) $entry['title']) ?></div>
|
||||
<div class="task-row-subtitle"><?= h((string) $meta['brand']) ?> · <?= h((string) $meta['hint']) ?></div>
|
||||
</div>
|
||||
<div class="text-end">
|
||||
<div class="reward-strong">+<?= h(number_format((float) $entry['reward'], 2)) ?></div>
|
||||
<div class="list-meta-line">USDT</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-list-footer mt-3">
|
||||
<div class="d-flex flex-wrap gap-2">
|
||||
<span class="tag-chip"><?= h('VIP' . (string) $entry['vip_required']) ?></span>
|
||||
<span class="tag-chip secondary-chip"><?= h((string) $categories[$meta['category']] ?? '任务') ?></span>
|
||||
<?php if ($activeOrder): ?>
|
||||
<?= render_status_badge((string) $activeOrder['status']) ?>
|
||||
<?php elseif (!$canAccess): ?>
|
||||
<span class="status-pill tone-muted">未解锁</span>
|
||||
<?php else: ?>
|
||||
<span class="status-pill tone-success">可领取</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<a class="btn btn-gradient btn-sm px-3" href="task.php?task=<?= h((string) $entry['slug']) ?>">查看</a>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
<?php
|
||||
render_layout_end();
|
||||
exit;
|
||||
}
|
||||
|
||||
$meta = task_visual_meta($task);
|
||||
$activeOrder = $user ? active_order_for_task((int) $user['id'], (string) $task['slug']) : null;
|
||||
$history = $user ? get_user_orders_for_task((int) $user['id'], (string) $task['slug'], 5) : [];
|
||||
$canAccess = $user ? can_access_task($user, $task) : ((int) $task['vip_required'] === 0);
|
||||
$unlockTimestamp = $activeOrder ? strtotime((string) $activeOrder['claimed_at']) + (int) $activeOrder['countdown_seconds'] : null;
|
||||
|
||||
render_layout_start((string) $task['title'], '任务详情页,包含任务说明、倒计时与提交审核入口。', 'tasks');
|
||||
?>
|
||||
<section class="app-page-section">
|
||||
<article class="app-card gradient-card detail-hero-card">
|
||||
<div class="task-detail-head">
|
||||
<span class="task-platform-icon large-icon <?= h((string) $meta['accent']) ?>"><?= app_icon_svg((string) $meta['icon']) ?></span>
|
||||
<div class="flex-grow-1">
|
||||
<div class="tiny-eyebrow"><?= h((string) $meta['brand']) ?> · <?= h((string) $task['type']) ?></div>
|
||||
<h1 class="app-section-title mb-2"><?= h((string) $task['title']) ?></h1>
|
||||
<p class="app-copy mb-0"><?= h((string) $task['summary']) ?></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-metrics mt-3">
|
||||
<div class="detail-metric-box">
|
||||
<div class="mini-label">奖励金额</div>
|
||||
<div class="detail-amount">+<?= h(number_format((float) $task['reward'], 2)) ?> USDT</div>
|
||||
</div>
|
||||
<div class="detail-meta-grid mt-3">
|
||||
<div>
|
||||
<div class="mini-label">任务要求</div>
|
||||
<div class="detail-mini-value">VIP<?= h((string) $task['vip_required']) ?></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mini-label">倒计时</div>
|
||||
<div class="detail-mini-value"><?= h((string) $task['countdown']) ?> 秒</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mini-label">当前状态</div>
|
||||
<div class="detail-mini-value">
|
||||
<?php if ($activeOrder): ?>
|
||||
<?= render_status_badge((string) $activeOrder['status']) ?>
|
||||
<?php elseif ($canAccess): ?>
|
||||
<span class="status-pill tone-success">可领取</span>
|
||||
<?php else: ?>
|
||||
<span class="status-pill tone-muted">需升级 VIP</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section">
|
||||
<article class="app-card">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">任务要求</div>
|
||||
<h2 class="app-section-title">操作步骤</h2>
|
||||
</div>
|
||||
<div class="timeline-mini-list">
|
||||
<?php foreach ($task['steps'] as $index => $step): ?>
|
||||
<div class="timeline-mini-item">
|
||||
<span class="timeline-mini-index"><?= h((string) ($index + 1)) ?></span>
|
||||
<div>
|
||||
<div class="list-title-strong">步骤 <?= h((string) ($index + 1)) ?></div>
|
||||
<div class="list-meta-line"><?= h((string) $step) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section">
|
||||
<article class="app-card">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">开始任务</div>
|
||||
<h2 class="app-section-title">领取与提交</h2>
|
||||
</div>
|
||||
|
||||
<?php if (!$user): ?>
|
||||
<div class="app-inline-note mb-3">你正在浏览视觉预览版。登录后才可以正式领取并提交任务。</div>
|
||||
<a class="btn btn-gradient w-100" href="<?= h(login_page_url(current_request_target())) ?>">登录后开始任务</a>
|
||||
<?php elseif (!$canAccess): ?>
|
||||
<div class="app-inline-note mb-3">当前账号为 <?= h(vip_info((int) $user['vip_level'])['name']) ?>,该任务需要 VIP<?= h((string) $task['vip_required']) ?>。</div>
|
||||
<a class="btn btn-gradient w-100" href="vip.php?level=<?= h((string) $task['vip_required']) ?>">去开通 VIP<?= h((string) $task['vip_required']) ?></a>
|
||||
<?php elseif (!$activeOrder): ?>
|
||||
<form method="post" class="d-grid gap-3">
|
||||
<input type="hidden" name="csrf_token" value="<?= h(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="claim_task">
|
||||
<input type="hidden" name="task_slug" value="<?= h((string) $task['slug']) ?>">
|
||||
<button class="btn btn-gradient" type="submit">开始任务</button>
|
||||
</form>
|
||||
<?php elseif ((string) $activeOrder['status'] === 'claimed'): ?>
|
||||
<div class="countdown-card front-countdown-card mb-3" data-countdown-card>
|
||||
<div class="mini-label">倒计时剩余</div>
|
||||
<div class="countdown-display" data-countdown="<?= h((string) $unlockTimestamp) ?>" data-countdown-target="#submit-order-button"><?= h((string) max(0, (int) ($unlockTimestamp - time()))) ?> 秒</div>
|
||||
<div class="list-meta-line mt-2">时间到之后才可以提交,服务端也会再次验证。</div>
|
||||
</div>
|
||||
<form method="post" class="d-grid gap-3">
|
||||
<input type="hidden" name="csrf_token" value="<?= h(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="submit_order">
|
||||
<input type="hidden" name="task_slug" value="<?= h((string) $task['slug']) ?>">
|
||||
<input type="hidden" name="order_id" value="<?= h((string) $activeOrder['id']) ?>">
|
||||
<div>
|
||||
<label class="form-label" for="proof-note">完成备注</label>
|
||||
<textarea class="form-control" id="proof-note" name="proof_note" rows="4" placeholder="例如:已观看 60 秒,已完成点赞与停留" required></textarea>
|
||||
</div>
|
||||
<button class="btn btn-gradient" id="submit-order-button" type="submit" disabled>提交审核</button>
|
||||
</form>
|
||||
<?php else: ?>
|
||||
<div class="app-inline-note mb-3">该任务已提交审核或已处理完成,你可以查看订单详情了解最新状态。</div>
|
||||
<a class="btn btn-gradient w-100" href="order.php?id=<?= h((string) $activeOrder['id']) ?>">查看订单详情</a>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<?php if ($history): ?>
|
||||
<section class="app-page-section">
|
||||
<article class="app-card">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">任务历史</div>
|
||||
<h2 class="app-section-title">最近记录</h2>
|
||||
</div>
|
||||
<div class="app-list-stack">
|
||||
<?php foreach ($history as $record): ?>
|
||||
<a class="app-list-item" href="order.php?id=<?= h((string) $record['id']) ?>">
|
||||
<span>
|
||||
<span class="list-title-strong">订单 #<?= h((string) $record['id']) ?></span>
|
||||
<span class="list-meta-line"><?= h(format_datetime((string) $record['claimed_at'])) ?> · <?= h(format_usdt($record['reward_usdt'])) ?></span>
|
||||
</span>
|
||||
<span><?= render_status_badge((string) $record['status']) ?></span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
<?php render_layout_end(); ?>
|
||||
142
verify.php
Normal file
142
verify.php
Normal file
@ -0,0 +1,142 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/app.php';
|
||||
|
||||
$redirectTarget = safe_redirect_target((string) ($_POST['redirect'] ?? $_GET['redirect'] ?? 'index.php'));
|
||||
if (current_user()) {
|
||||
redirect($redirectTarget);
|
||||
}
|
||||
|
||||
$email = normalize_email((string) ($_POST['email'] ?? $_GET['email'] ?? ($_SESSION['last_signup_email'] ?? '')));
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
try {
|
||||
verify_csrf_or_fail();
|
||||
$action = (string) ($_POST['action'] ?? 'verify_signup');
|
||||
$email = normalize_email((string) ($_POST['email'] ?? ''));
|
||||
|
||||
if ($action === 'resend_signup') {
|
||||
resend_signup_verification($email);
|
||||
flash('success', '新的验证码已经发送,请查收邮箱。');
|
||||
redirect(verify_page_url($email, $redirectTarget));
|
||||
}
|
||||
|
||||
complete_signup_verification($email, (string) ($_POST['code'] ?? ''));
|
||||
flash('success', '邮箱验证成功,账号已经创建完成,请使用密码登录。');
|
||||
redirect(login_page_url($redirectTarget, ['email' => $email]));
|
||||
} catch (Throwable $exception) {
|
||||
flash('danger', $exception->getMessage());
|
||||
if ($email !== '') {
|
||||
redirect(verify_page_url($email, $redirectTarget));
|
||||
}
|
||||
redirect(register_page_url($redirectTarget));
|
||||
}
|
||||
}
|
||||
|
||||
$latestRequest = null;
|
||||
if ($email !== '') {
|
||||
try {
|
||||
$latestRequest = latest_signup_verification($email, false);
|
||||
} catch (Throwable $exception) {
|
||||
$latestRequest = null;
|
||||
}
|
||||
}
|
||||
|
||||
render_layout_start('邮箱验证码', '邮箱验证码页,输入 6 位数字后完成注册激活。', 'auth');
|
||||
?>
|
||||
<section class="app-page-section auth-page-shell">
|
||||
<article class="app-card gradient-card hero-card">
|
||||
<div class="tiny-eyebrow">Step 3 / 4</div>
|
||||
<h1 class="app-hero-title compact-title">邮箱验证码</h1>
|
||||
<?php if ($email !== ''): ?>
|
||||
<p class="app-hero-copy mb-0">验证码已经发送到 <strong><?= h(mask_email($email)) ?></strong>。把邮件里的 6 位数字填进来,验证通过后系统才会真正创建账号。</p>
|
||||
<?php else: ?>
|
||||
<p class="app-hero-copy mb-0">这一页负责“确认你真的拥有这个邮箱”。如果你还没发验证码,请先返回注册页。</p>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section auth-page-shell">
|
||||
<div class="auth-stack auth-page-grid">
|
||||
<article class="app-card auth-card-panel">
|
||||
<div class="auth-panel-head">
|
||||
<div>
|
||||
<div class="list-title-strong">输入 6 位数字验证码</div>
|
||||
<div class="list-meta-line">验证码错误次数过多或过期后,需要重新发送。</div>
|
||||
</div>
|
||||
<span class="tag-chip">验证</span>
|
||||
</div>
|
||||
|
||||
<?php if ($email === ''): ?>
|
||||
<div class="empty-tip">还没有待验证的邮箱记录。请先回到注册页填写邮箱和密码。</div>
|
||||
<div class="auth-link-grid mt-3">
|
||||
<a class="btn btn-gradient" href="<?= h(register_page_url($redirectTarget)) ?>">去注册</a>
|
||||
<a class="btn btn-outline-light" href="<?= h(start_page_url($redirectTarget)) ?>">返回启动页</a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<form method="post" class="auth-form-grid">
|
||||
<input type="hidden" name="csrf_token" value="<?= h(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="verify_signup">
|
||||
<input type="hidden" name="redirect" value="<?= h($redirectTarget) ?>">
|
||||
<input type="hidden" name="email" value="<?= h($email) ?>">
|
||||
<div>
|
||||
<label class="form-label" for="signup-code">验证码</label>
|
||||
<input class="form-control auth-code-input" id="signup-code" type="text" name="code" inputmode="numeric" autocomplete="one-time-code" maxlength="6" pattern="\d{6}" placeholder="请输入 6 位数字" required>
|
||||
</div>
|
||||
<button class="btn btn-gradient" type="submit">确认验证码</button>
|
||||
</form>
|
||||
|
||||
<form method="post" class="mt-3">
|
||||
<input type="hidden" name="csrf_token" value="<?= h(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="resend_signup">
|
||||
<input type="hidden" name="redirect" value="<?= h($redirectTarget) ?>">
|
||||
<input type="hidden" name="email" value="<?= h($email) ?>">
|
||||
<button class="btn btn-outline-light w-100" type="submit">重新发送验证码</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
|
||||
<article class="app-card auth-card-panel">
|
||||
<div class="auth-panel-head">
|
||||
<div>
|
||||
<div class="list-title-strong">当前说明</div>
|
||||
<div class="list-meta-line">为了让你更容易理解,我把“验证码页”翻译成了 3 句话。</div>
|
||||
</div>
|
||||
<span class="tag-chip secondary-chip">说明</span>
|
||||
</div>
|
||||
|
||||
<div class="app-list-stack">
|
||||
<div class="app-list-item static-row">
|
||||
<span>
|
||||
<span class="list-title-strong">为什么要这一步</span>
|
||||
<span class="list-meta-line">它用来确认邮箱归属,避免随便填一个邮箱就直接生成账号。</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="app-list-item static-row">
|
||||
<span>
|
||||
<span class="list-title-strong">验证成功后会发生什么</span>
|
||||
<span class="list-meta-line">系统会真实写入用户表、生成 6 位 ID、自动创建钱包并把账号标记为已验证。</span>
|
||||
</span>
|
||||
</div>
|
||||
<div class="app-list-item static-row">
|
||||
<span>
|
||||
<span class="list-title-strong">下一页是什么</span>
|
||||
<span class="list-meta-line">验证通过后,会跳到登录页。你再输入密码,就能进入 5 个主页面。</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($latestRequest && !empty($latestRequest['expires_at'])): ?>
|
||||
<div class="app-inline-note mt-3">最近一次验证码过期时间:<?= h(format_datetime((string) $latestRequest['expires_at'])) ?></div>
|
||||
<?php else: ?>
|
||||
<div class="app-inline-note mt-3">如果没收到邮件,请先检查垃圾箱,再点击“重新发送验证码”。</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="auth-link-grid mt-3">
|
||||
<a class="btn btn-outline-light" href="<?= h(register_page_url($redirectTarget, $email !== '' ? ['email' => $email] : [])) ?>">返回注册页</a>
|
||||
<a class="btn btn-outline-light" href="<?= h(login_page_url($redirectTarget, $email !== '' ? ['email' => $email] : [])) ?>">去登录页</a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
<?php render_layout_end(); ?>
|
||||
219
vip.php
Normal file
219
vip.php
Normal file
@ -0,0 +1,219 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/app.php';
|
||||
|
||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||
$action = (string) ($_POST['action'] ?? '');
|
||||
|
||||
try {
|
||||
verify_csrf_or_fail();
|
||||
|
||||
if ($action === 'purchase_vip') {
|
||||
$user = require_user();
|
||||
$targetLevel = (int) ($_POST['level'] ?? 0);
|
||||
$purchase = purchase_vip_upgrade((int) $user['id'], $targetLevel);
|
||||
$upgradedVip = vip_info((int) ($purchase['to_level'] ?? $targetLevel));
|
||||
$paidAmount = (float) ($purchase['price_usdt'] ?? 0);
|
||||
flash('success', $upgradedVip['name'] . ' 开通成功,已从可用余额扣除 ' . number_format($paidAmount, 2) . ' USDT。');
|
||||
redirect('vip.php?level=' . $targetLevel . '#vip-history');
|
||||
}
|
||||
} catch (Throwable $exception) {
|
||||
$targetLevel = (int) ($_POST['level'] ?? 0);
|
||||
flash('danger', $exception->getMessage());
|
||||
redirect('vip.php' . ($targetLevel > 0 ? '?level=' . $targetLevel . '#vip-checkout' : '#vip-checkout'));
|
||||
}
|
||||
}
|
||||
|
||||
$user = current_user();
|
||||
$previewMode = !$user;
|
||||
$catalog = vip_catalog();
|
||||
$currentVip = $user ? (int) $user['vip_level'] : 3;
|
||||
$defaultLevel = $currentVip > 0 ? $currentVip : 1;
|
||||
$selectedLevel = (int) ($_GET['level'] ?? $defaultLevel);
|
||||
if (!isset($catalog[$selectedLevel]) || $selectedLevel === 0) {
|
||||
$selectedLevel = $defaultLevel;
|
||||
}
|
||||
$selectedVip = $catalog[$selectedLevel];
|
||||
$wallet = $user ? wallet_snapshot((int) $user['id']) : ['available_balance' => 5560.25, 'frozen_balance' => 120.00];
|
||||
$availableBalance = (float) ($wallet['available_balance'] ?? 0);
|
||||
$selectedPrice = (float) $selectedVip['price'];
|
||||
$balanceGap = max(0, $selectedPrice - $availableBalance);
|
||||
$isCurrentOrLower = !$previewMode && $selectedLevel <= $currentVip;
|
||||
$canPurchase = !$previewMode && !$isCurrentOrLower && $balanceGap <= 0;
|
||||
$vipOrders = $user ? get_user_vip_orders((int) $user['id'], 8) : [];
|
||||
$latestVipOrder = $vipOrders[0] ?? null;
|
||||
$nextVip = (!$previewMode && isset($catalog[$currentVip + 1])) ? $catalog[$currentVip + 1] : null;
|
||||
|
||||
render_layout_start('VIP', 'VIP 等级页,支持真实余额开通、写入数据库、升级等级与查看升级记录。', 'vip');
|
||||
?>
|
||||
<section class="app-page-section">
|
||||
<article class="app-card gradient-card hero-card">
|
||||
<div class="tiny-eyebrow">VIP 等级</div>
|
||||
<h1 class="app-hero-title compact-title">余额直接开通更高返佣等级</h1>
|
||||
<p class="app-hero-copy mb-0">现在这个页面已经接上真实数据库:开通后会写入 VIP 订单记录、扣减钱包余额,并立即升级你的账号等级。</p>
|
||||
<?php if ($previewMode): ?>
|
||||
<div class="app-inline-note mt-3">当前是视觉预览模式。登录后可以使用真实钱包余额开通 VIP,并在下方看到升级记录。</div>
|
||||
<?php else: ?>
|
||||
<div class="app-inline-note mt-3">当前账号是 <?= h(vip_info($currentVip)['name']) ?>。如果余额充足,点一次“立即开通”就会直接完成升级。</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section">
|
||||
<article class="app-card vip-highlight-card">
|
||||
<div class="d-flex align-items-center justify-content-between gap-3 flex-wrap">
|
||||
<div>
|
||||
<div class="tiny-eyebrow">当前等级</div>
|
||||
<div class="vip-banner-title"><?= h(vip_info($currentVip)['name']) ?></div>
|
||||
<div class="list-meta-line">可用余额 <?= h(number_format($availableBalance, 2)) ?> USDT · 冻结余额 <?= h(number_format((float) ($wallet['frozen_balance'] ?? 0), 2)) ?> USDT</div>
|
||||
</div>
|
||||
<span class="vip-chip-large"><?= h(vip_info($currentVip)['name']) ?></span>
|
||||
</div>
|
||||
|
||||
<div class="detail-meta-grid mt-3">
|
||||
<div>
|
||||
<div class="mini-label">目标等级</div>
|
||||
<div class="detail-mini-value"><?= h((string) $selectedVip['name']) ?></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mini-label">开通金额</div>
|
||||
<div class="detail-mini-value"><?= h(number_format($selectedPrice, 2)) ?> USDT</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mini-label">最近升级</div>
|
||||
<div class="detail-mini-value"><?= h($latestVipOrder ? vip_info((int) $latestVipOrder['to_level'])['name'] : '暂无') ?></div>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section">
|
||||
<div class="app-task-list">
|
||||
<?php foreach ($catalog as $level => $vip): ?>
|
||||
<?php if ($level === 0) { continue; } ?>
|
||||
<article class="app-card vip-level-row<?= $selectedLevel === (int) $level ? ' is-selected' : '' ?>">
|
||||
<div class="d-flex align-items-start gap-3">
|
||||
<span class="task-platform-icon <?= (int) $level === 1 ? 'accent-gold' : ((int) $level === 2 ? 'accent-blue' : ((int) $level === 3 ? 'accent-violet' : 'accent-pink')) ?>"><?= app_icon_svg('vip') ?></span>
|
||||
<div class="flex-grow-1 min-w-0">
|
||||
<div class="d-flex align-items-center gap-2 flex-wrap">
|
||||
<div class="task-row-title"><?= h((string) $vip['name']) ?></div>
|
||||
<?php if (!$previewMode && $currentVip === (int) $level): ?>
|
||||
<span class="status-pill tone-success">当前等级</span>
|
||||
<?php elseif (!$previewMode && $currentVip > (int) $level): ?>
|
||||
<span class="status-pill tone-muted">已解锁</span>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<div class="task-row-subtitle mt-1"><?= h((string) $vip['benefit']) ?></div>
|
||||
<div class="d-flex flex-wrap gap-2 mt-2">
|
||||
<span class="tag-chip">单任务佣金 <?= h(number_format((float) $vip['reward'], 2)) ?> USDT</span>
|
||||
<span class="tag-chip secondary-chip">每日 <?= h((string) $vip['daily_tasks']) ?> 单</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-end ms-auto">
|
||||
<div class="reward-strong"><?= h(number_format((float) $vip['price'], 0)) ?> USDT</div>
|
||||
<?php if ($selectedLevel === (int) $level): ?>
|
||||
<span class="status-pill tone-warning mt-2">已选中</span>
|
||||
<?php else: ?>
|
||||
<a class="btn btn-outline-light btn-sm px-3 mt-2" href="vip.php?level=<?= h((string) $level) ?>#vip-checkout">选择</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section" id="vip-checkout">
|
||||
<article class="app-card payment-preview-card">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">开通支付</div>
|
||||
<h2 class="app-section-title"><?= h((string) $selectedVip['name']) ?> 结算卡</h2>
|
||||
</div>
|
||||
|
||||
<div class="payment-hero-box">
|
||||
<div class="vip-banner-title mb-2"><?= h((string) $selectedVip['name']) ?></div>
|
||||
<div class="payment-price"><?= h(number_format($selectedPrice, 2)) ?> USDT</div>
|
||||
<div class="list-meta-line mt-2">单任务佣金 <?= h(number_format((float) $selectedVip['reward'], 2)) ?> USDT · 每日 <?= h((string) $selectedVip['daily_tasks']) ?> 单</div>
|
||||
</div>
|
||||
|
||||
<div class="detail-meta-grid mt-3">
|
||||
<div>
|
||||
<div class="mini-label">支付方式</div>
|
||||
<div class="detail-mini-value">钱包可用余额</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mini-label">当前可用</div>
|
||||
<div class="detail-mini-value"><?= h(number_format($availableBalance, 2)) ?> USDT</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mini-label"><?= $isCurrentOrLower ? '升级状态' : '还差金额' ?></div>
|
||||
<div class="detail-mini-value"><?= $isCurrentOrLower ? h('已开通或更高等级') : h(number_format($balanceGap, 2) . ' USDT') ?></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($previewMode): ?>
|
||||
<div class="app-inline-note mt-3">要真正开通 VIP,需要先登录。登录后这个按钮会直接连到数据库:生成订单记录、扣款并升级你的等级。</div>
|
||||
<div class="d-grid gap-2 mt-3">
|
||||
<a class="btn btn-gradient" href="<?= h(login_page_url(current_request_target())) ?>">先登录账号</a>
|
||||
<a class="btn btn-outline-light" href="task.php">先查看任务大厅</a>
|
||||
</div>
|
||||
<?php elseif ($isCurrentOrLower): ?>
|
||||
<div class="app-inline-note mt-3">当前账号已经是 <?= h(vip_info($currentVip)['name']) ?>。无需重复开通,直接去做更高等级任务即可。</div>
|
||||
<div class="d-grid gap-2 mt-3">
|
||||
<a class="btn btn-gradient" href="task.php">去做任务</a>
|
||||
<?php if ($nextVip): ?>
|
||||
<a class="btn btn-outline-light" href="vip.php?level=<?= h((string) $nextVip['level']) ?>#vip-checkout">查看 <?= h((string) $nextVip['name']) ?></a>
|
||||
<?php else: ?>
|
||||
<a class="btn btn-outline-light" href="wallet.php#history-panel">查看钱包流水</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php elseif ($balanceGap > 0): ?>
|
||||
<div class="app-inline-note mt-3">当前余额还差 <?= h(number_format($balanceGap, 2)) ?> USDT,暂时不能直接开通。等你下一步把充值流程接上后,这里就能完整跑通。</div>
|
||||
<div class="d-grid gap-2 mt-3">
|
||||
<a class="btn btn-gradient" href="wallet.php#deposit-panel">去钱包查看充值入口</a>
|
||||
<a class="btn btn-outline-light" href="profile.php#support-panel">联系专属客服</a>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="app-inline-note mt-3">确认后会立刻执行 3 件事:1)写入 VIP 订单;2)从可用余额扣款;3)更新你的账号等级。</div>
|
||||
<form method="post" class="d-grid gap-2 mt-3">
|
||||
<input type="hidden" name="csrf_token" value="<?= h(csrf_token()) ?>">
|
||||
<input type="hidden" name="action" value="purchase_vip">
|
||||
<input type="hidden" name="level" value="<?= h((string) $selectedLevel) ?>">
|
||||
<button class="btn btn-gradient" type="submit" data-confirm="确认使用钱包可用余额开通 <?= h((string) $selectedVip['name']) ?>?扣款后会立即升级,且此动作不可撤销。">立即开通 <?= h((string) $selectedVip['name']) ?></button>
|
||||
<a class="btn btn-outline-light" href="wallet.php#history-panel">先看资金流水</a>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section" id="vip-history">
|
||||
<article class="app-card">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">升级记录</div>
|
||||
<h2 class="app-section-title">最近 VIP 订单</h2>
|
||||
</div>
|
||||
|
||||
<?php if ($previewMode): ?>
|
||||
<div class="app-inline-note">登录后这里会显示你的真实 VIP 订单时间线,例如“VIP0 → VIP1、扣款金额、开通时间”。</div>
|
||||
<?php elseif ($vipOrders): ?>
|
||||
<div class="app-list-stack">
|
||||
<?php foreach ($vipOrders as $order): ?>
|
||||
<div class="app-list-item align-items-start">
|
||||
<span>
|
||||
<span class="list-title-strong"><?= h(vip_info((int) $order['from_level'])['name']) ?> → <?= h(vip_info((int) $order['to_level'])['name']) ?></span>
|
||||
<span class="list-meta-line"><?= h(format_datetime((string) ($order['completed_at'] ?: $order['created_at']))) ?> · 扣款 <?= h(number_format((float) $order['price_usdt'], 2)) ?> USDT</span>
|
||||
<span class="list-meta-line"><?= h((string) $order['note']) ?></span>
|
||||
</span>
|
||||
<span class="d-inline-flex flex-column align-items-end gap-2">
|
||||
<span class="status-pill tone-success">已升级</span>
|
||||
<span class="ledger-value-inline is-negative">-<?= h(number_format((float) $order['price_usdt'], 2)) ?> USDT</span>
|
||||
</span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="empty-tip">你还没有 VIP 升级记录。等余额充足后,第一次开通就会自动写入这里。</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</section>
|
||||
<?php render_layout_end(); ?>
|
||||
169
wallet.php
Normal file
169
wallet.php
Normal file
@ -0,0 +1,169 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/app.php';
|
||||
|
||||
$user = current_user();
|
||||
$previewMode = !$user;
|
||||
$wallet = $user
|
||||
? wallet_snapshot((int) $user['id'])
|
||||
: ['available_balance' => 5560.25, 'frozen_balance' => 120.00];
|
||||
$stats = $user
|
||||
? get_dashboard_stats((int) $user['id'])
|
||||
: [
|
||||
'available_balance' => 5560.25,
|
||||
'frozen_balance' => 120.00,
|
||||
'total_balance' => 5680.25,
|
||||
'today_earnings' => 1000.00,
|
||||
'month_earnings' => 18560.20,
|
||||
'today_tasks' => 30,
|
||||
'active_orders' => 3,
|
||||
'completed_orders' => 12,
|
||||
];
|
||||
$logs = $user ? get_wallet_logs((int) $user['id'], 8) : [
|
||||
['entry_type' => 'task_credit', 'amount' => 1000.00, 'frozen_amount' => 0.00, 'note' => '任务佣金到账', 'created_at' => gmdate('Y-m-d H:i:s')],
|
||||
['entry_type' => 'task_reject', 'amount' => 0.00, 'frozen_amount' => -50.00, 'note' => '未通过审核,释放冻结金额', 'created_at' => gmdate('Y-m-d H:i:s', time() - 3600)],
|
||||
['entry_type' => 'task_freeze', 'amount' => 0.00, 'frozen_amount' => 100.00, 'note' => '任务提交后冻结佣金', 'created_at' => gmdate('Y-m-d H:i:s', time() - 7200)],
|
||||
];
|
||||
$available = (float) $wallet['available_balance'];
|
||||
$fee = $available > 0 ? min(1.00, $available) : 1.00;
|
||||
$estimated = max(0, $available - $fee);
|
||||
$quickActions = [
|
||||
['label' => '充值', 'icon' => 'deposit', 'href' => '#deposit-panel'],
|
||||
['label' => '提现', 'icon' => 'withdraw', 'href' => '#withdraw-panel'],
|
||||
['label' => '转账', 'icon' => 'swap', 'href' => '#transfer-panel'],
|
||||
['label' => '记录', 'icon' => 'history', 'href' => '#history-panel'],
|
||||
];
|
||||
|
||||
render_layout_start('钱包', '钱包页,展示可用余额、冻结余额、提现卡片与资金流水记录。', 'wallet');
|
||||
?>
|
||||
<section class="app-page-section">
|
||||
<article class="app-card gradient-card hero-card">
|
||||
<div class="tiny-eyebrow">我的钱包</div>
|
||||
<h1 class="app-hero-title compact-title">资金总览</h1>
|
||||
<div class="balance-card mt-3">
|
||||
<div class="mini-label">可用余额(USDT)</div>
|
||||
<div class="balance-value"><?= h(number_format((float) $wallet['available_balance'], 2)) ?></div>
|
||||
<div class="balance-sub">总余额 <?= h(number_format((float) $stats['total_balance'], 2)) ?> · 冻结 <?= h(number_format((float) $wallet['frozen_balance'], 2)) ?></div>
|
||||
<div class="quick-action-grid mt-3">
|
||||
<?php foreach ($quickActions as $action): ?>
|
||||
<a class="quick-action" href="<?= h($action['href']) ?>">
|
||||
<span class="quick-action-icon"><?= app_icon_svg((string) $action['icon']) ?></span>
|
||||
<span class="quick-action-label"><?= h((string) $action['label']) ?></span>
|
||||
</a>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php if ($previewMode): ?>
|
||||
<div class="app-inline-note mt-3">当前是参考图风格预览,登录后这里会显示你的真实资金流水。</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section">
|
||||
<div class="mini-stat-grid three-cols">
|
||||
<article class="app-card stat-card">
|
||||
<div class="mini-label">可用余额</div>
|
||||
<div class="stat-value"><?= h(number_format((float) $wallet['available_balance'], 2)) ?></div>
|
||||
<div class="stat-meta">USDT</div>
|
||||
</article>
|
||||
<article class="app-card stat-card">
|
||||
<div class="mini-label">冻结余额</div>
|
||||
<div class="stat-value"><?= h(number_format((float) $wallet['frozen_balance'], 2)) ?></div>
|
||||
<div class="stat-meta">等待审核</div>
|
||||
</article>
|
||||
<article class="app-card stat-card">
|
||||
<div class="mini-label">累计入账</div>
|
||||
<div class="stat-value"><?= h(number_format((float) $stats['month_earnings'], 2)) ?></div>
|
||||
<div class="stat-meta">本月收益</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section" id="deposit-panel">
|
||||
<article class="app-card">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">充值</div>
|
||||
<h2 class="app-section-title">USDT 充值说明</h2>
|
||||
</div>
|
||||
<div class="detail-meta-grid">
|
||||
<div>
|
||||
<div class="mini-label">网络</div>
|
||||
<div class="detail-mini-value">TRC20</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mini-label">到账方式</div>
|
||||
<div class="detail-mini-value">人工审核</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="mini-label">处理时间</div>
|
||||
<div class="detail-mini-value">5 - 15 分钟</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="app-inline-note mt-3">第一版先保留参考图中的充值入口样式,下一步我可以继续帮你接“充值 / 提现 → 客服工单 / 内置 IM”。</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section" id="withdraw-panel">
|
||||
<article class="app-card">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">提现</div>
|
||||
<h2 class="app-section-title">提现预览</h2>
|
||||
</div>
|
||||
<div class="withdraw-card-shell">
|
||||
<div class="wallet-field-row">
|
||||
<span>提现地址</span>
|
||||
<span>USDT-TRC20</span>
|
||||
</div>
|
||||
<div class="wallet-field-input">请输入 USDT 钱包地址</div>
|
||||
<div class="wallet-field-row mt-3">
|
||||
<span>提现金额</span>
|
||||
<span>全部</span>
|
||||
</div>
|
||||
<div class="wallet-field-input"><?= h(number_format($available > 0 ? $available : 1000, 2)) ?> USDT</div>
|
||||
<div class="wallet-summary-list mt-3">
|
||||
<div><span>手续费</span><strong><?= h(number_format($fee, 2)) ?> USDT</strong></div>
|
||||
<div><span>实际到账</span><strong><?= h(number_format($estimated, 2)) ?> USDT</strong></div>
|
||||
</div>
|
||||
<a class="btn btn-gradient w-100 mt-3" href="profile.php#support-panel">联系客服提现</a>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section" id="transfer-panel">
|
||||
<article class="app-card">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">转账</div>
|
||||
<h2 class="app-section-title">账户间转账</h2>
|
||||
</div>
|
||||
<div class="app-inline-note">这一块我先按照你发的设计图放了入口和卡片结构。下一步可以继续接“转账表单 + 审核记录 + 钱包扣减逻辑”。</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="app-page-section" id="history-panel">
|
||||
<article class="app-card">
|
||||
<div class="section-heading-app mb-3">
|
||||
<div class="tiny-eyebrow">资金流水</div>
|
||||
<h2 class="app-section-title">最近记录</h2>
|
||||
</div>
|
||||
<?php if ($logs): ?>
|
||||
<div class="app-list-stack">
|
||||
<?php foreach ($logs as $log): ?>
|
||||
<?php
|
||||
$delta = (float) $log['amount'] + (float) $log['frozen_amount'];
|
||||
$deltaClass = $delta > 0 ? 'is-positive' : ($delta < 0 ? 'is-negative' : '');
|
||||
?>
|
||||
<div class="app-list-item static-row">
|
||||
<span>
|
||||
<span class="list-title-strong"><?= h(wallet_entry_label((string) $log['entry_type'])) ?></span>
|
||||
<span class="list-meta-line"><?= h((string) $log['note']) ?> · <?= h(format_datetime((string) $log['created_at'])) ?></span>
|
||||
</span>
|
||||
<span class="ledger-value-inline <?= h($deltaClass) ?>"><?= h(format_delta($delta)) ?> USDT</span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<div class="empty-tip">目前还没有资金流水。先完成一次任务提交与审核,就会在这里看到变化。</div>
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
</section>
|
||||
<?php render_layout_end(); ?>
|
||||
Loading…
x
Reference in New Issue
Block a user