Compare commits

...

1 Commits

Author SHA1 Message Date
Flatlogic Bot
345071c7b6 Autosave: 20260525-131142 2026-05-25 13:11:32 +00:00
20 changed files with 5285 additions and 528 deletions

1904
app.php Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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();
}
});
});
});

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

View 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;

View 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
View 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
View File

@ -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
View 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
View 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
View 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']) ?>&amp;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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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(); ?>