初始提交: Qihu-AIFlow v2.0.1 完整项目

- 前端 React+TypeScript (源码+构建产物)
- 后端 Python FastAPI
- 数据库迁移 001-015
- Docker部署配置
- 微信ClawBot API频道绑定
- 项目文档 README.md
This commit is contained in:
qyyfw committed 2026-07-02 14:40:46 +08:00
commit 78ff261b5a
199 files changed
+69126

No files matched your search

+367
View File
@@ -0,0 +1,367 @@
import React, { useState, useEffect } from 'react';
import { adminAPI } from '@/api';
import type { PaymentConfig, Order } from '@/types';
const SVGIcon: React.FC<{ children: React.ReactNode; size?: number }> = ({ children, size = 16 }) => (
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
{children}
</svg>
);
const statusBadge = (status: string) => {
const map: Record<string, { cls: string; text: string }> = {
active: { cls: 'badge-success', text: '已启用' },
inactive: { cls: 'badge-neutral', text: '未启用' },
paid: { cls: 'badge-success', text: '已支付' },
pending: { cls: 'badge-warning', text: '待支付' },
refunded: { cls: 'badge-error', text: '已退款' },
};
const s = map[status] || { cls: 'badge-neutral', text: status };
return <span className={`admin-badge ${s.cls}`}>{s.text}</span>;
};
const paymentMethodBadge = (method: string) => {
return method === 'wechat'
? <span className="admin-badge badge-success">微信支付</span>
: <span className="admin-badge badge-info">支付宝</span>;
};
const Payment: React.FC = () => {
const [configs, setConfigs] = useState<PaymentConfig[]>([]);
const [orders, setOrders] = useState<Order[]>([]);
const [saving, setSaving] = useState<Record<string, boolean>>({});
const [msg, setMsg] = useState<Record<string, { type: string; text: string }>>({});
const wechat = configs.find(c => c.type === 'wechat');
const alipay = configs.find(c => c.type === 'alipay');
const [wechatForm, setWechatForm] = useState({
app_id: '', merchant_id: '', api_key: '', api_v3_key: '',
cert_path: '', cert_serial_no: '', notify_url: '',
});
const [alipayForm, setAlipayForm] = useState({
app_id: '', app_private_key: '', alipay_public_key: '',
gateway_url: 'https://openapi.alipay.com/gateway.do', notify_url: '', return_url: '',
});
useEffect(() => {
loadConfigs();
loadOrders();
}, []);
useEffect(() => {
if (wechat) {
setWechatForm({
app_id: wechat.app_id || '',
merchant_id: wechat.merchant_id || '',
api_key: wechat.api_key || '',
api_v3_key: wechat.api_v3_key || '',
cert_path: wechat.cert_path || '/etc/certs/apiclient_cert.pem',
cert_serial_no: wechat.cert_serial_no || '',
notify_url: wechat.notify_url || '',
});
}
if (alipay) {
setAlipayForm({
app_id: alipay.app_id || '',
app_private_key: alipay.app_private_key || '',
alipay_public_key: alipay.alipay_public_key || '',
gateway_url: alipay.gateway_url || 'https://openapi.alipay.com/gateway.do',
notify_url: alipay.notify_url || '',
return_url: alipay.return_url || '',
});
}
}, [configs]);
const loadConfigs = async () => {
try {
const res = await adminAPI.getPaymentConfigs();
const list = res?.data || res;
if (Array.isArray(list)) {
setConfigs(list);
}
} catch (e) { console.error('加载支付配置失败', e); }
};
const loadOrders = async () => {
try {
const res = await adminAPI.getAdminOrders({ page: 1, page_size: 20 });
const data: any = res?.data || res;
if (data?.items) {
setOrders(data.items);
} else if (Array.isArray(data)) {
setOrders(data);
}
} catch (e) { console.error('加载订单失败', e); }
};
const handleSaveConfig = async (id: string, data: any) => {
setSaving(prev => ({ ...prev, [id]: true }));
setMsg(prev => ({ ...prev, [id]: { type: '', text: '' } }));
try {
await adminAPI.updatePaymentConfig(id, data);
setMsg(prev => ({ ...prev, [id]: { type: 'success', text: '保存成功' } }));
loadConfigs();
} catch (e: any) {
setMsg(prev => ({ ...prev, [id]: { type: 'error', text: e.message || '保存失败' } }));
} finally {
setSaving(prev => ({ ...prev, [id]: false }));
}
};
const handleToggleConfig = async (type: string) => {
const config = type === 'wechat' ? wechat : alipay;
if (!config) return;
const newStatus = config.status === 'active' ? 'inactive' : 'active';
await handleSaveConfig(config.id, { status: newStatus });
};
const handleRefund = async (orderId: string) => {
try {
await adminAPI.refundOrder(orderId);
loadOrders();
} catch (e) { console.error('退款失败', e); }
};
const formatDate = (d: string) => {
const dt = new Date(d);
return dt.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
};
return (
<div style={{ animation: 'fadeIn var(--transition-normal)' }}>
<h1 className="admin-page-title">支付渠道配置</h1>
{Object.entries(msg).map(([id, m]) => m.text ? (
<div key={id} style={{
padding: 'var(--space-md) var(--space-lg)',
marginBottom: 'var(--space-lg)',
borderRadius: 'var(--radius-md)',
background: m.type === 'success' ? 'var(--color-success-light)' : 'var(--color-error-light)',
color: m.type === 'success' ? 'var(--color-success)' : 'var(--color-error)',
fontWeight: 500,
}}>
{m.text}
</div>
) : null)}
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(420px, 1fr))', gap: 'var(--space-xl)', marginBottom: 'var(--space-2xl)' }}>
{/* WeChat Pay Card */}
<div className="admin-card" style={{ marginBottom: 0 }}>
<div className="admin-card-header">
<h3 className="admin-card-title" style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-sm)' }}>
<div style={{ width: 32, height: 32, borderRadius: 'var(--radius-md)', background: '#E6F7EE', color: '#09BB07', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<SVGIcon size={18}><path d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2z"/></SVGIcon>
</div>
微信支付
</h3>
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-sm)' }}>
{wechat ? statusBadge(wechat.status) : <span className="admin-badge badge-neutral">未配置</span>}
{wechat && (
<button
onClick={() => handleToggleConfig('wechat')}
style={{
width: 44, height: 24, borderRadius: 12, border: 'none',
background: wechat.status === 'active' ? 'var(--color-accent)' : 'var(--color-text-disabled)',
cursor: 'pointer', position: 'relative', transition: 'background var(--transition-fast)',
}}
>
<span style={{
position: 'absolute', top: 2, left: wechat.status === 'active' ? 22 : 2,
width: 20, height: 20, borderRadius: '50%', background: '#FFFFFF',
transition: 'left var(--transition-fast)', boxShadow: '0 1px 3px rgba(0,0,0,0.2)',
}} />
</button>
)}
</div>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">AppID</label>
<input className="admin-form-input" value={wechatForm.app_id} onChange={e => setWechatForm({ ...wechatForm, app_id: e.target.value })} />
</div>
<div className="admin-form-group">
<label className="admin-form-label">商户号</label>
<input className="admin-form-input" value={wechatForm.merchant_id} onChange={e => setWechatForm({ ...wechatForm, merchant_id: e.target.value })} />
</div>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">API v2 密钥</label>
<input className="admin-form-input" type="password" value={wechatForm.api_key} onChange={e => setWechatForm({ ...wechatForm, api_key: e.target.value })} />
</div>
<div className="admin-form-group">
<label className="admin-form-label">API v3 密钥</label>
<input className="admin-form-input" type="password" value={wechatForm.api_v3_key} onChange={e => setWechatForm({ ...wechatForm, api_v3_key: e.target.value })} />
</div>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">证书路径</label>
<input className="admin-form-input" value={wechatForm.cert_path} onChange={e => setWechatForm({ ...wechatForm, cert_path: e.target.value })} />
</div>
<div className="admin-form-group">
<label className="admin-form-label">回调通知地址</label>
<input className="admin-form-input" value={wechatForm.notify_url} placeholder="https://your-domain.com/api/payment/wechat/notify" onChange={e => setWechatForm({ ...wechatForm, notify_url: e.target.value })} />
</div>
</div>
<div className="admin-form-actions">
<button className="admin-btn admin-btn-primary" disabled={saving[wechat?.id || ''] || !wechat} onClick={() => wechat && handleSaveConfig(wechat.id, {
app_id: wechatForm.app_id,
merchant_id: wechatForm.merchant_id,
api_key: wechatForm.api_key,
api_v3_key: wechatForm.api_v3_key,
cert_path: wechatForm.cert_path,
notify_url: wechatForm.notify_url,
})}>
{saving[wechat?.id || ''] ? '保存中...' : '保存配置'}
</button>
</div>
</div>
{/* Alipay Card */}
<div className="admin-card" style={{ marginBottom: 0 }}>
<div className="admin-card-header">
<h3 className="admin-card-title" style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-sm)' }}>
<div style={{ width: 32, height: 32, borderRadius: 'var(--radius-md)', background: '#EBF2FE', color: '#1677FF', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<SVGIcon size={18}><path d="M12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2z"/></SVGIcon>
</div>
支付宝
</h3>
<div style={{ display: 'flex', alignItems: 'center', gap: 'var(--space-sm)' }}>
{alipay ? statusBadge(alipay.status) : <span className="admin-badge badge-neutral">未配置</span>}
{alipay && (
<button
onClick={() => handleToggleConfig('alipay')}
style={{
width: 44, height: 24, borderRadius: 12, border: 'none',
background: alipay.status === 'active' ? 'var(--color-accent)' : 'var(--color-text-disabled)',
cursor: 'pointer', position: 'relative', transition: 'background var(--transition-fast)',
}}
>
<span style={{
position: 'absolute', top: 2, left: alipay.status === 'active' ? 22 : 2,
width: 20, height: 20, borderRadius: '50%', background: '#FFFFFF',
transition: 'left var(--transition-fast)', boxShadow: '0 1px 3px rgba(0,0,0,0.2)',
}} />
</button>
)}
</div>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">AppID</label>
<input className="admin-form-input" value={alipayForm.app_id} onChange={e => setAlipayForm({ ...alipayForm, app_id: e.target.value })} />
</div>
<div className="admin-form-group">
<label className="admin-form-label">接口地址</label>
<input className="admin-form-input" value={alipayForm.gateway_url} onChange={e => setAlipayForm({ ...alipayForm, gateway_url: e.target.value })} />
</div>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">应用私钥</label>
<input className="admin-form-input" type="password" value={alipayForm.app_private_key} placeholder="-----BEGIN RSA PRIVATE KEY-----" onChange={e => setAlipayForm({ ...alipayForm, app_private_key: e.target.value })} />
</div>
<div className="admin-form-group">
<label className="admin-form-label">支付宝公钥</label>
<input className="admin-form-input" type="password" value={alipayForm.alipay_public_key} placeholder="-----BEGIN PUBLIC KEY-----" onChange={e => setAlipayForm({ ...alipayForm, alipay_public_key: e.target.value })} />
</div>
</div>
<div className="admin-form-row">
<div className="admin-form-group">
<label className="admin-form-label">异步回调地址</label>
<input className="admin-form-input" value={alipayForm.notify_url} placeholder="https://your-domain.com/api/payment/alipay/notify" onChange={e => setAlipayForm({ ...alipayForm, notify_url: e.target.value })} />
</div>
<div className="admin-form-group">
<label className="admin-form-label">同步跳转地址</label>
<input className="admin-form-input" value={alipayForm.return_url} placeholder="https://your-domain.com/#/payment-result" onChange={e => setAlipayForm({ ...alipayForm, return_url: e.target.value })} />
</div>
</div>
<div className="admin-form-actions">
<button className="admin-btn admin-btn-primary" disabled={saving[alipay?.id || ''] || !alipay} onClick={() => alipay && handleSaveConfig(alipay.id, {
app_id: alipayForm.app_id,
app_private_key: alipayForm.app_private_key,
alipay_public_key: alipayForm.alipay_public_key,
gateway_url: alipayForm.gateway_url,
notify_url: alipayForm.notify_url,
return_url: alipayForm.return_url,
})}>
{saving[alipay?.id || ''] ? '保存中...' : '保存配置'}
</button>
</div>
</div>
</div>
{/* Order Management */}
<div className="admin-card">
<div className="admin-card-header">
<h3 className="admin-card-title">订单管理</h3>
<button className="admin-btn admin-btn-outline admin-btn-sm">
<SVGIcon><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></SVGIcon>
导出
</button>
</div>
<div style={{ overflowX: 'auto' }}>
<table className="admin-table">
<thead>
<tr>
<th>订单号</th>
<th>用户</th>
<th>类型</th>
<th>项目</th>
<th>金额</th>
<th>支付方式</th>
<th>状态</th>
<th>时间</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{orders.map(order => (
<tr key={order.id}>
<td style={{ fontFamily: 'var(--font-mono)', fontSize: 'var(--text-xs)' }}>{order.id}</td>
<td style={{ fontWeight: 500 }}>{order.userId}</td>
<td>
<span className={`admin-badge ${order.orderType === 'membership' ? 'badge-info' : order.orderType === 'app' ? 'badge-success' : 'badge-warning'}`}>
{order.orderType === 'membership' ? '会员' : order.orderType === 'app' ? '应用' : '服务'}
</span>
</td>
<td style={{ maxWidth: 160, overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{order.itemName}</td>
<td style={{ fontFamily: 'var(--font-mono)', fontWeight: 600 }}>{order.amount.toFixed(2)} 元</td>
<td>{paymentMethodBadge(order.paymentMethod)}</td>
<td>{statusBadge(order.status)}</td>
<td style={{ fontSize: 'var(--text-xs)', color: 'var(--color-text-tertiary)' }}>{formatDate(order.createdAt)}</td>
<td>
{order.status === 'paid' && (
<button className="admin-btn admin-btn-danger admin-btn-sm" onClick={() => handleRefund(order.id)}>
退款
</button>
)}
{order.status === 'refunded' && (
<span style={{ fontSize: 'var(--text-xs)', color: 'var(--color-text-disabled)' }}>已退款</span>
)}
{order.status === 'pending' && (
<span style={{ fontSize: 'var(--text-xs)', color: 'var(--color-text-tertiary)' }}>等待支付</span>
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
};
export default Payment;