feat: 完整分销系统 — 后端 + 主站管理 + 分站前端模板
分销商后端: - 分销商注册/审核/CRUD API - 分站用户注册/登录/登出 (session cookie auth) - 分站 Token CRUD、充值兑换、套餐订阅 - 分站域名/子域名自动识别中间件 (DistIdentify) - 分销商收益异步记账 + markup 加价计费 - 模型价格快照、渠道路由配置 - 管理员审核/管理分销商 主站前端 (web/): - 分销商注册申请、设置、客户管理(分页)、套餐管理(含速率限制) - 收益管理(含汇总卡片+分页列表)、模型管理 - 管理员分销商审核面板 - DistributorLayout 标签页布局 + 路由 分站前端模板 (dist-site/): - React + Vite + Tailwind 暗色主题 glassmorphism 设计 - react-bits 动画组件 (GradientText, BlurText, CountUp, SpotlightCard 等) - Session cookie 认证 + New-Api-User header - 首页/登录/注册/Dashboard/API Keys/定价/套餐 7 个页面 - Token 创建后一次性密钥展示、余额检查订阅确认 Bug 修复: - 修复 DistUserRegister 密码双重哈希导致用户无法登录 - 修复 401 响应不清除 React user 状态 - 修复订阅确认弹窗不显示余额且提前关闭 - 修复 Home 页 Subscribe 按钮跳转错误 (/dashboard → /packages) - 修复 Login/Register 组件卸载后 setState 警告 - 修复 DistributorEarnings 引用不存在的 username 字段 - 修复 DistributorCustomers 引用不存在的 created_at 字段 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
commit
9dfc5e79bc
29 files changed
+5540
No files matched your search
+74
@@ -0,0 +1,74 @@
|
||||
import axios from 'axios';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
export const Q = 500000; // QuotaPerUnit — single source of truth
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: '',
|
||||
timeout: 30000,
|
||||
withCredentials: true, // CRITICAL: send session cookies on every request
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
|
||||
// Attach New-Api-User header (required by backend auth middleware)
|
||||
api.interceptors.request.use((config) => {
|
||||
const userId = localStorage.getItem('dist_user_id');
|
||||
if (userId) {
|
||||
config.headers['New-Api-User'] = userId;
|
||||
}
|
||||
return config;
|
||||
});
|
||||
|
||||
// Global error handler
|
||||
api.interceptors.response.use(
|
||||
(res) => {
|
||||
// Handle success:false responses with user-visible errors
|
||||
if (res.data && res.data.success === false && res.data.message) {
|
||||
toast.error(res.data.message);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
(err) => {
|
||||
const msg = err.response?.data?.message || err.message || 'Request failed';
|
||||
if (err.response?.status === 401) {
|
||||
localStorage.removeItem('dist_user_id');
|
||||
// Emit event so AuthContext can clear React state
|
||||
window.dispatchEvent(new Event('auth:logout'));
|
||||
toast.error('Session expired, please log in again');
|
||||
} else {
|
||||
toast.error(msg);
|
||||
}
|
||||
return Promise.reject(err);
|
||||
}
|
||||
);
|
||||
|
||||
// ===== Public =====
|
||||
export const getSiteInfo = () => api.get('/api/dist/site/info');
|
||||
export const getSiteModels = () => api.get('/api/dist/site/models');
|
||||
export const getSitePricing = () => api.get('/api/dist/site/pricing');
|
||||
export const getSitePackages = () => api.get('/api/dist/site/packages');
|
||||
|
||||
// ===== Auth =====
|
||||
export const register = (data) => api.post('/api/dist/user/register', data);
|
||||
export const login = (data) => api.post('/api/dist/user/login', data);
|
||||
export const logout = () => api.post('/api/dist/user/logout');
|
||||
|
||||
// ===== User =====
|
||||
export const getUserSelf = () => api.get('/api/dist/user/self');
|
||||
export const getUserUsage = () => api.get('/api/dist/user/usage');
|
||||
|
||||
// ===== Tokens =====
|
||||
export const getTokens = () => api.get('/api/dist/token/list');
|
||||
export const createToken = (data) => api.post('/api/dist/token/create', data);
|
||||
export const updateToken = (id, data) => api.put(`/api/dist/token/${id}`, data);
|
||||
export const deleteToken = (id) => api.delete(`/api/dist/token/${id}`);
|
||||
|
||||
// ===== Purchase =====
|
||||
export const redeemCode = (key) => api.post('/api/dist/topup/redeem', { key }); // backend field is "key"
|
||||
export const subscribePackage = (packageId) => api.post('/api/dist/package/subscribe', { package_id: packageId });
|
||||
|
||||
// ===== Helpers =====
|
||||
export const quotaToDollar = (quota) => (quota / Q).toFixed(4);
|
||||
export const quotaToDollar6 = (quota) => (quota / Q).toFixed(6);
|
||||
|
||||
export default api;
|
||||
Reference in new issue
Block a user