Files
soul/addons/Universal_Payment_Module copy/3_逻辑参考_通用实现/前端收银台Demo.html
卡若 b60edb3d47 feat: 完整重构小程序匹配功能 + 修复UI对齐 + 文章数据API
主要更新:
1. 按H5网页端完全重构匹配功能(match页面)
   - 4种匹配类型: 创业合伙/资源对接/导师顾问/团队招募
   - 资源对接等类型弹出手机号/微信号输入框
   - 去掉重新匹配按钮,改为返回按钮

2. 修复所有卡片对齐和宽度问题
   - 目录页附录卡片居中
   - 首页阅读进度卡片满宽度
   - 我的页面菜单卡片对齐
   - 推广中心分享卡片统一宽度

3. 修复目录页图标和文字对齐
   - section-icon固定40rpx宽高
   - section-title与图标垂直居中

4. 更新真实完整文章标题(62篇)
   - 从book目录读取真实markdown文件名
   - 替换之前的简化标题

5. 新增文章数据API
   - /api/db/chapters - 获取完整书籍结构
   - 支持按ID获取单篇文章内容
2026-01-21 15:49:12 +08:00

749 lines
23 KiB
HTML

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>通用收银台 v4.0</title>
<style>
/*
* 通用收银台样式 - 支持多种支付方式
* 作者: 卡若
* 适配: PC + 移动端
*/
:root {
--primary-color: #1890ff;
--success-color: #52c41a;
--warning-color: #faad14;
--error-color: #ff4d4f;
--text-color: #333;
--text-secondary: #666;
--border-color: #e8e8e8;
--bg-color: #f5f5f7;
--card-bg: #ffffff;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: var(--bg-color);
color: var(--text-color);
min-height: 100vh;
padding: 20px;
}
.cashier-container {
max-width: 480px;
margin: 0 auto;
}
.cashier-card {
background: var(--card-bg);
border-radius: 16px;
padding: 24px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
margin-bottom: 16px;
}
/* 订单信息 */
.order-section {
text-align: center;
padding-bottom: 20px;
border-bottom: 1px dashed var(--border-color);
margin-bottom: 20px;
}
.order-title {
font-size: 16px;
color: var(--text-secondary);
margin-bottom: 8px;
}
.order-amount {
font-size: 42px;
font-weight: 700;
color: var(--text-color);
letter-spacing: -1px;
}
.order-amount .currency {
font-size: 24px;
font-weight: 400;
margin-right: 4px;
}
.order-info {
display: flex;
justify-content: space-between;
font-size: 14px;
color: var(--text-secondary);
margin-top: 12px;
}
.countdown {
color: var(--warning-color);
font-weight: 500;
}
/* 支付方式选择 */
.payment-section h3 {
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 12px;
}
.payment-methods {
display: flex;
flex-direction: column;
gap: 10px;
}
.payment-method {
display: flex;
align-items: center;
padding: 16px;
border: 2px solid var(--border-color);
border-radius: 12px;
cursor: pointer;
transition: all 0.2s ease;
background: var(--card-bg);
}
.payment-method:hover {
border-color: var(--primary-color);
background: rgba(24, 144, 255, 0.04);
}
.payment-method.active {
border-color: var(--primary-color);
background: rgba(24, 144, 255, 0.08);
}
.payment-method .icon {
width: 36px;
height: 36px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
margin-right: 12px;
}
.payment-method .icon.alipay { background: #1677ff; color: white; }
.payment-method .icon.wechat { background: #07c160; color: white; }
.payment-method .icon.paypal { background: #003087; color: white; }
.payment-method .icon.stripe { background: #635bff; color: white; }
.payment-method .icon.usdt { background: #26a17b; color: white; }
.payment-method .info {
flex: 1;
}
.payment-method .name {
font-size: 16px;
font-weight: 500;
margin-bottom: 2px;
}
.payment-method .desc {
font-size: 12px;
color: var(--text-secondary);
}
.payment-method .radio {
width: 20px;
height: 20px;
border: 2px solid var(--border-color);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.payment-method.active .radio {
border-color: var(--primary-color);
background: var(--primary-color);
}
.payment-method.active .radio::after {
content: '✓';
color: white;
font-size: 12px;
}
/* 二维码区域 */
.qrcode-section {
text-align: center;
padding: 24px;
display: none;
}
.qrcode-section.show {
display: block;
}
.qrcode-box {
width: 200px;
height: 200px;
margin: 0 auto 16px;
border: 1px solid var(--border-color);
border-radius: 12px;
overflow: hidden;
background: white;
}
.qrcode-box img {
width: 100%;
height: 100%;
object-fit: contain;
}
.qrcode-text {
font-size: 14px;
color: var(--text-secondary);
}
.qrcode-tip {
margin-top: 8px;
font-size: 12px;
color: var(--warning-color);
}
/* USDT 地址显示 */
.usdt-address {
background: #f9f9f9;
border-radius: 8px;
padding: 16px;
word-break: break-all;
font-family: monospace;
font-size: 14px;
margin-top: 12px;
}
.copy-btn {
margin-top: 8px;
padding: 8px 16px;
background: var(--primary-color);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
}
/* 支付按钮 */
.pay-btn {
width: 100%;
padding: 16px;
background: linear-gradient(135deg, #1890ff 0%, #096dd9 100%);
color: white;
border: none;
border-radius: 12px;
font-size: 18px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
margin-top: 24px;
}
.pay-btn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(24, 144, 255, 0.3);
}
.pay-btn:disabled {
background: #ccc;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.pay-btn.loading {
position: relative;
color: transparent;
}
.pay-btn.loading::after {
content: '';
position: absolute;
width: 20px;
height: 20px;
border: 2px solid transparent;
border-top-color: white;
border-radius: 50%;
animation: spin 0.8s linear infinite;
left: 50%;
top: 50%;
margin-left: -10px;
margin-top: -10px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 支付成功 */
.success-section {
text-align: center;
padding: 40px 20px;
display: none;
}
.success-section.show {
display: block;
}
.success-icon {
width: 80px;
height: 80px;
border-radius: 50%;
background: var(--success-color);
color: white;
font-size: 48px;
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 20px;
animation: scaleIn 0.5s ease;
}
@keyframes scaleIn {
0% { transform: scale(0); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
.success-section h2 {
font-size: 24px;
margin-bottom: 8px;
}
.success-section p {
color: var(--text-secondary);
}
.return-btn {
margin-top: 24px;
padding: 12px 32px;
background: var(--primary-color);
color: white;
border: none;
border-radius: 8px;
font-size: 16px;
cursor: pointer;
}
/* 安全提示 */
.security-tips {
text-align: center;
padding: 12px;
font-size: 12px;
color: var(--text-secondary);
}
.security-tips span {
display: inline-flex;
align-items: center;
gap: 4px;
}
/* 响应式 */
@media (max-width: 480px) {
body {
padding: 0;
}
.cashier-container {
max-width: 100%;
}
.cashier-card {
border-radius: 0;
padding: 20px 16px;
}
.order-amount {
font-size: 36px;
}
}
</style>
</head>
<body>
<div class="cashier-container">
<!-- 订单信息卡片 -->
<div class="cashier-card" id="orderCard">
<div class="order-section">
<div class="order-title" id="orderTitle">VIP会员月卡</div>
<div class="order-amount">
<span class="currency">¥</span>
<span id="displayAmount">99.00</span>
</div>
<div class="order-info">
<span>订单号: <span id="orderSn">202401170001</span></span>
<span class="countdown" id="countdown">29:59</span>
</div>
</div>
<!-- 支付方式选择 -->
<div class="payment-section" id="paymentSection">
<h3>选择支付方式</h3>
<div class="payment-methods" id="paymentMethods">
<!-- 支付宝 -->
<div class="payment-method" data-gateway="alipay_wap" onclick="selectMethod(this)">
<div class="icon alipay">💙</div>
<div class="info">
<div class="name">支付宝</div>
<div class="desc">推荐有支付宝账户的用户使用</div>
</div>
<div class="radio"></div>
</div>
<!-- 微信支付 -->
<div class="payment-method" data-gateway="wechat_native" onclick="selectMethod(this)">
<div class="icon wechat">💚</div>
<div class="info">
<div class="name">微信支付</div>
<div class="desc">扫码支付,微信用户首选</div>
</div>
<div class="radio"></div>
</div>
<!-- PayPal -->
<div class="payment-method" data-gateway="paypal" onclick="selectMethod(this)">
<div class="icon paypal">🅿️</div>
<div class="info">
<div class="name">PayPal</div>
<div class="desc">支持信用卡,海外用户推荐</div>
</div>
<div class="radio"></div>
</div>
<!-- USDT -->
<div class="payment-method" data-gateway="usdt" onclick="selectMethod(this)">
<div class="icon usdt"></div>
<div class="info">
<div class="name">USDT (TRC20)</div>
<div class="desc">加密货币支付</div>
</div>
<div class="radio"></div>
</div>
</div>
</div>
<!-- 二维码区域 -->
<div class="qrcode-section" id="qrcodeSection">
<div class="qrcode-box">
<img id="qrcodeImg" src="" alt="支付二维码">
</div>
<div class="qrcode-text" id="qrcodeText">请使用微信扫描二维码支付</div>
<div class="qrcode-tip">二维码有效期 30 分钟,请尽快支付</div>
<!-- USDT 地址 -->
<div class="usdt-address" id="usdtAddress" style="display: none;"></div>
<button class="copy-btn" id="copyBtn" style="display: none;" onclick="copyAddress()">复制地址</button>
</div>
<!-- 支付按钮 -->
<button class="pay-btn" id="payBtn" onclick="doPay()">确认支付 ¥99.00</button>
</div>
<!-- 支付成功 -->
<div class="cashier-card success-section" id="successSection">
<div class="success-icon"></div>
<h2>支付成功</h2>
<p>感谢您的购买,订单已完成</p>
<button class="return-btn" onclick="goBack()">返回商户</button>
</div>
<!-- 安全提示 -->
<div class="security-tips">
<span>🔒 安全支付由卡若私域提供技术支持</span>
</div>
</div>
<script>
/**
* 通用收银台 JavaScript v4.0
* 作者: 卡若
*
* 使用方法:
* 1. 修改 API_BASE 为你的后端地址
* 2. 通过 URL 参数传入订单信息: ?order_sn=xxx&amount=99.00&title=商品名称
*/
// 配置
const API_BASE = '/api/payment'; // 你的后端接口地址
const POLL_INTERVAL = 3000; // 轮询间隔 (毫秒)
const ORDER_TIMEOUT = 30 * 60; // 订单超时时间 (秒)
// 状态
let selectedGateway = null;
let orderSn = '';
let orderAmount = 0;
let countdownTimer = null;
let pollTimer = null;
let remainingSeconds = ORDER_TIMEOUT;
// 初始化
document.addEventListener('DOMContentLoaded', function() {
// 从 URL 解析订单信息
const params = new URLSearchParams(window.location.search);
orderSn = params.get('order_sn') || '202401170001';
orderAmount = parseFloat(params.get('amount')) || 99.00;
const title = params.get('title') || 'VIP会员月卡';
// 更新UI
document.getElementById('orderSn').textContent = orderSn;
document.getElementById('orderTitle').textContent = title;
document.getElementById('displayAmount').textContent = orderAmount.toFixed(2);
document.getElementById('payBtn').textContent = `确认支付 ¥${orderAmount.toFixed(2)}`;
// 启动倒计时
startCountdown();
// 检测微信环境
if (isWechat()) {
// 微信环境默认选择JSAPI
const wechatMethod = document.querySelector('[data-gateway="wechat_native"]');
if (wechatMethod) {
wechatMethod.dataset.gateway = 'wechat_jsapi';
wechatMethod.querySelector('.desc').textContent = '微信内直接支付';
}
}
});
// 选择支付方式
function selectMethod(element) {
// 移除其他选中状态
document.querySelectorAll('.payment-method').forEach(el => {
el.classList.remove('active');
});
// 设置选中状态
element.classList.add('active');
selectedGateway = element.dataset.gateway;
// 隐藏二维码区域
document.getElementById('qrcodeSection').classList.remove('show');
document.getElementById('paymentSection').style.display = 'block';
document.getElementById('payBtn').style.display = 'block';
// 更新金额显示 (USDT显示美元)
if (selectedGateway === 'usdt') {
const usdtAmount = (orderAmount / 7.2).toFixed(2); // 简单汇率转换
document.getElementById('displayAmount').textContent = usdtAmount;
document.querySelector('.currency').textContent = '₮';
document.getElementById('payBtn').textContent = `确认支付 ₮${usdtAmount}`;
} else if (selectedGateway === 'paypal') {
const usdAmount = (orderAmount / 7.2).toFixed(2);
document.getElementById('displayAmount').textContent = usdAmount;
document.querySelector('.currency').textContent = '$';
document.getElementById('payBtn').textContent = `确认支付 $${usdAmount}`;
} else {
document.getElementById('displayAmount').textContent = orderAmount.toFixed(2);
document.querySelector('.currency').textContent = '¥';
document.getElementById('payBtn').textContent = `确认支付 ¥${orderAmount.toFixed(2)}`;
}
}
// 发起支付
async function doPay() {
if (!selectedGateway) {
alert('请选择支付方式');
return;
}
const payBtn = document.getElementById('payBtn');
payBtn.classList.add('loading');
payBtn.disabled = true;
try {
// 调用后端接口
const response = await fetch(`${API_BASE}/checkout`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
order_sn: orderSn,
gateway: selectedGateway,
return_url: window.location.href
})
});
const result = await response.json();
if (result.code !== 200) {
throw new Error(result.message || '支付发起失败');
}
const { type, payload, trade_sn } = result.data;
switch (type) {
case 'url':
// 跳转支付 (支付宝H5, PayPal, Stripe)
window.location.href = payload;
break;
case 'qrcode':
// 显示二维码 (微信Native, 支付宝扫码)
showQrcode(payload, selectedGateway);
startPolling(trade_sn);
break;
case 'json':
// 调起SDK (微信JSAPI)
callWechatPay(payload);
break;
case 'address':
// 显示钱包地址 (USDT)
showUsdtAddress(payload);
startPolling(trade_sn);
break;
case 'direct':
// 直接完成 (虚拟币支付)
showSuccess();
break;
}
} catch (error) {
console.error('支付失败:', error);
alert(error.message || '支付发起失败,请重试');
} finally {
payBtn.classList.remove('loading');
payBtn.disabled = false;
}
}
// 显示二维码
function showQrcode(content, gateway) {
document.getElementById('paymentSection').style.display = 'none';
document.getElementById('payBtn').style.display = 'none';
document.getElementById('qrcodeSection').classList.add('show');
// 使用在线服务生成二维码
const qrcodeUrl = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(content)}`;
document.getElementById('qrcodeImg').src = qrcodeUrl;
// 更新提示文字
const texts = {
'wechat_native': '请使用微信扫描二维码支付',
'alipay_qr': '请使用支付宝扫描二维码支付'
};
document.getElementById('qrcodeText').textContent = texts[gateway] || '请扫描二维码支付';
}
// 显示USDT地址
function showUsdtAddress(addressInfo) {
document.getElementById('paymentSection').style.display = 'none';
document.getElementById('payBtn').style.display = 'none';
document.getElementById('qrcodeSection').classList.add('show');
const { address, amount_usdt, memo } = addressInfo;
document.getElementById('qrcodeImg').src = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(address)}`;
document.getElementById('qrcodeText').textContent = `请向以下地址转账 ${amount_usdt} USDT (TRC20)`;
document.getElementById('usdtAddress').textContent = address;
document.getElementById('usdtAddress').style.display = 'block';
document.getElementById('copyBtn').style.display = 'inline-block';
}
// 复制地址
function copyAddress() {
const address = document.getElementById('usdtAddress').textContent;
navigator.clipboard.writeText(address).then(() => {
alert('地址已复制');
});
}
// 调用微信JSAPI支付
function callWechatPay(params) {
if (typeof WeixinJSBridge === 'undefined') {
alert('请在微信中打开此页面');
return;
}
WeixinJSBridge.invoke('getBrandWCPayRequest', params, function(res) {
if (res.err_msg === 'get_brand_wcpay_request:ok') {
showSuccess();
} else {
alert('支付取消或失败');
}
});
}
// 轮询支付状态
function startPolling(tradeSn) {
if (pollTimer) clearInterval(pollTimer);
pollTimer = setInterval(async () => {
try {
const response = await fetch(`${API_BASE}/status/${orderSn}`);
const result = await response.json();
if (result.data && result.data.status === 'paid') {
clearInterval(pollTimer);
showSuccess();
}
} catch (error) {
console.error('查询状态失败:', error);
}
}, POLL_INTERVAL);
}
// 显示支付成功
function showSuccess() {
document.getElementById('orderCard').style.display = 'none';
document.getElementById('successSection').classList.add('show');
// 停止轮询和倒计时
if (pollTimer) clearInterval(pollTimer);
if (countdownTimer) clearInterval(countdownTimer);
}
// 返回商户
function goBack() {
const returnUrl = new URLSearchParams(window.location.search).get('return_url');
if (returnUrl) {
window.location.href = returnUrl;
} else {
window.history.back();
}
}
// 倒计时
function startCountdown() {
countdownTimer = setInterval(() => {
remainingSeconds--;
if (remainingSeconds <= 0) {
clearInterval(countdownTimer);
alert('订单已超时,请重新下单');
window.history.back();
return;
}
const minutes = Math.floor(remainingSeconds / 60);
const seconds = remainingSeconds % 60;
document.getElementById('countdown').textContent =
`${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}, 1000);
}
// 检测微信环境
function isWechat() {
return /MicroMessenger/i.test(navigator.userAgent);
}
</script>
</body>
</html>