462 lines
13 KiB
JavaScript
462 lines
13 KiB
JavaScript
/**
|
||
* API 服务层 - 统一处理 HTTP 请求
|
||
* @module api
|
||
*/
|
||
|
||
;(function() {
|
||
'use strict';
|
||
|
||
/**
|
||
* 获取 API 基础地址
|
||
* @returns {string} API 基础 URL
|
||
*/
|
||
const getApiBase = () => {
|
||
if (window.__API_BASE__ && String(window.__API_BASE__).trim()) {
|
||
return String(window.__API_BASE__).trim();
|
||
}
|
||
return typeof location !== 'undefined' ? location.origin : 'http://localhost:8077';
|
||
};
|
||
|
||
const API_BASE = getApiBase();
|
||
|
||
/**
|
||
* 从 URL 参数中获取用户 ID
|
||
* @returns {string} 用户 ID,不存在返回空字符串
|
||
*/
|
||
const getUserId = () => {
|
||
const params = new URLSearchParams(window.location.search || '');
|
||
const value = params.get('current_user_id') || params.get('userId') || params.get('userid') || params.get('user_id');
|
||
return value && String(value).trim() ? String(value).trim() : '';
|
||
};
|
||
|
||
/**
|
||
* 检查 URL 中是否有 userId 参数(不包含 current_user_id)
|
||
* @returns {boolean}
|
||
*/
|
||
const hasOnlyUserId = () => {
|
||
const params = new URLSearchParams(window.location.search || '');
|
||
const value = params.get('userId') || params.get('userid') || params.get('user_id');
|
||
return !!(value && String(value).trim());
|
||
};
|
||
|
||
/**
|
||
* 从 URL 参数中获取手机号(用于营销系统订单数据创建者自动选择)
|
||
* @returns {string} 手机号,不存在返回空字符串
|
||
*/
|
||
const getMobile = () => {
|
||
const params = new URLSearchParams(window.location.search || '');
|
||
const value = params.get('mobile');
|
||
return value && String(value).trim() ? String(value).trim() : '';
|
||
};
|
||
|
||
/**
|
||
* 从 URL 参数中获取商户 ID
|
||
* @returns {string} 商户 ID,不存在返回空字符串
|
||
*/
|
||
const getMerchantId = () => {
|
||
const params = new URLSearchParams(window.location.search || '');
|
||
const value = params.get('merchantId') || params.get('merchantid') || params.get('merchant_id');
|
||
return value && String(value).trim() ? String(value).trim() : '';
|
||
};
|
||
|
||
/**
|
||
* 构建用户相关的查询字符串
|
||
* @returns {string} 查询字符串,如 '?current_user_id=1&merchantId=2'
|
||
*/
|
||
const buildUserQueryString = () => {
|
||
const userId = getUserId();
|
||
const merchantId = getMerchantId();
|
||
const parts = [];
|
||
if (userId) parts.push('current_user_id=' + encodeURIComponent(userId));
|
||
if (merchantId) parts.push('merchantId=' + encodeURIComponent(merchantId));
|
||
return parts.length ? ('?' + parts.join('&')) : '';
|
||
};
|
||
|
||
/**
|
||
* 解析 API 响应数据
|
||
* @param {Object} data - 响应数据对象
|
||
* @returns {Array} 解析后的数组数据
|
||
*/
|
||
const parseArrayResponse = (data) => {
|
||
if (Array.isArray(data?.data)) return data.data;
|
||
if (Array.isArray(data)) return data;
|
||
return [];
|
||
};
|
||
|
||
/**
|
||
* 解析分页响应数据
|
||
* @param {Object} data - 响应数据对象
|
||
* @returns {{items: Array, total: number, page: number}} 分页数据
|
||
*/
|
||
const parsePaginatedResponse = (data) => {
|
||
const payload = data?.data || data || {};
|
||
return {
|
||
items: Array.isArray(payload.items) ? payload.items : (Array.isArray(payload) ? payload : []),
|
||
total: Number(payload.total || 0),
|
||
page: Number(payload.page || 1)
|
||
};
|
||
};
|
||
|
||
/**
|
||
* 通用 GET 请求
|
||
* @param {string} endpoint - API 端点
|
||
* @param {Object} [options] - 请求选项
|
||
* @param {boolean} [options.withUserQuery=false] - 是否附加用户查询参数
|
||
* @param {Object} [options.params] - URL 查询参数
|
||
* @returns {Promise<Object>} 响应数据
|
||
*/
|
||
const get = async (endpoint, options = {}) => {
|
||
const { withUserQuery = false, params = {} } = options;
|
||
|
||
let url = API_BASE + endpoint;
|
||
const queryParams = new URLSearchParams(params);
|
||
|
||
if (withUserQuery) {
|
||
const userId = getUserId();
|
||
const merchantId = getMerchantId();
|
||
if (userId) queryParams.set('userId', userId);
|
||
if (userId) queryParams.set('current_user_id', userId);
|
||
if (merchantId) queryParams.set('merchantId', merchantId);
|
||
}
|
||
|
||
const queryString = queryParams.toString();
|
||
if (queryString) {
|
||
url += (endpoint.includes('?') ? '&' : '?') + queryString;
|
||
}
|
||
|
||
const response = await fetch(url);
|
||
if (!response.ok) {
|
||
throw new Error(`请求失败: ${response.status} ${response.statusText}`);
|
||
}
|
||
return response.json();
|
||
};
|
||
|
||
/**
|
||
* 通用 POST 请求
|
||
* @param {string} endpoint - API 端点
|
||
* @param {Object} body - 请求体
|
||
* @param {Object} [options] - 请求选项
|
||
* @param {boolean} [options.withUserQuery=false] - 是否附加用户查询参数
|
||
* @returns {Promise<Object>} 响应数据
|
||
*/
|
||
const post = async (endpoint, body, options = {}) => {
|
||
const { withUserQuery = false } = options;
|
||
|
||
let url = API_BASE + endpoint;
|
||
if (withUserQuery) {
|
||
url += buildUserQueryString();
|
||
}
|
||
|
||
const response = await fetch(url, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(body)
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorText = await response.text();
|
||
throw new Error(errorText || `请求失败: ${response.status}`);
|
||
}
|
||
return response.json();
|
||
};
|
||
|
||
/**
|
||
* 通用 PATCH 请求
|
||
* @param {string} endpoint - API 端点
|
||
* @param {Object} body - 请求体
|
||
* @returns {Promise<Object>} 响应数据
|
||
*/
|
||
const patch = async (endpoint, body) => {
|
||
const url = API_BASE + endpoint;
|
||
const response = await fetch(url, {
|
||
method: 'PATCH',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json'
|
||
},
|
||
body: JSON.stringify(body)
|
||
});
|
||
|
||
if (!response.ok) {
|
||
const errorText = await response.text();
|
||
throw new Error(errorText || `请求失败: ${response.status}`);
|
||
}
|
||
return response.json();
|
||
};
|
||
|
||
/**
|
||
* 通用 DELETE 请求
|
||
* @param {string} endpoint - API 端点
|
||
* @param {Object} [options] - 请求选项
|
||
* @param {boolean} [options.soft=false] - 是否软删除
|
||
* @returns {Promise<Object>} 响应数据
|
||
*/
|
||
const del = async (endpoint, options = {}) => {
|
||
const { soft = false } = options;
|
||
let url = API_BASE + endpoint;
|
||
if (soft) {
|
||
url += (endpoint.includes('?') ? '&' : '?') + 'soft=1';
|
||
}
|
||
|
||
const response = await fetch(url, { method: 'DELETE' });
|
||
if (!response.ok) {
|
||
const errorText = await response.text();
|
||
throw new Error(errorText || `请求失败: ${response.status}`);
|
||
}
|
||
return response.json();
|
||
};
|
||
|
||
// ==================== 模板相关 API ====================
|
||
|
||
/**
|
||
* 加载模板列表
|
||
* @returns {Promise<Array>} 模板数组
|
||
*/
|
||
const fetchTemplates = async () => {
|
||
const data = await get('/api/templates', { withUserQuery: true });
|
||
return parseArrayResponse(data);
|
||
};
|
||
|
||
/**
|
||
* 获取模板详情
|
||
* @param {number|string} id - 模板 ID
|
||
* @returns {Promise<Object>} 模板详情
|
||
*/
|
||
const fetchTemplateDetail = async (id) => {
|
||
const data = await get(`/api/templates/${id}`);
|
||
return data?.data || {};
|
||
};
|
||
|
||
/**
|
||
* 创建模板
|
||
* @param {Object} payload - 模板数据
|
||
* @returns {Promise<Object>} 创建结果
|
||
*/
|
||
const createTemplate = async (payload) => {
|
||
return post('/api/templates', payload, { withUserQuery: true });
|
||
};
|
||
|
||
/**
|
||
* 更新模板
|
||
* @param {number|string} id - 模板 ID
|
||
* @param {Object} payload - 更新数据
|
||
* @returns {Promise<Object>} 更新结果
|
||
*/
|
||
const updateTemplate = async (id, payload) => {
|
||
return patch(`/api/templates/${id}`, payload);
|
||
};
|
||
|
||
/**
|
||
* 删除模板(软删除)
|
||
* @param {number|string} id - 模板 ID
|
||
* @returns {Promise<Object>} 删除结果
|
||
*/
|
||
const deleteTemplate = async (id) => {
|
||
return del(`/api/templates/${id}`, { soft: true });
|
||
};
|
||
|
||
// ==================== 导出任务相关 API ====================
|
||
|
||
/**
|
||
* 加载导出任务列表
|
||
* @param {Object} params - 查询参数
|
||
* @param {number} params.page - 页码
|
||
* @param {number} params.pageSize - 每页数量
|
||
* @param {number} [params.templateId] - 模板 ID
|
||
* @returns {Promise<{items: Array, total: number, page: number}>} 分页数据
|
||
*/
|
||
const fetchJobs = async ({ page, pageSize, templateId }) => {
|
||
const params = {
|
||
page: String(page),
|
||
page_size: String(pageSize)
|
||
};
|
||
if (templateId) {
|
||
params.template_id = String(templateId);
|
||
}
|
||
const data = await get('/api/exports', { params, withUserQuery: true });
|
||
return parsePaginatedResponse(data);
|
||
};
|
||
|
||
/**
|
||
* 获取任务详情
|
||
* @param {number|string} id - 任务 ID
|
||
* @returns {Promise<Object>} 任务详情
|
||
*/
|
||
const fetchJobDetail = async (id) => {
|
||
const data = await get(`/api/exports/${id}`, { withUserQuery: true });
|
||
return data?.data || {};
|
||
};
|
||
|
||
/**
|
||
* 获取任务 SQL
|
||
* @param {number|string} id - 任务 ID
|
||
* @returns {Promise<string>} SQL 语句
|
||
*/
|
||
const fetchJobSql = async (id) => {
|
||
const data = await get(`/api/exports/${id}/sql`);
|
||
return data?.data?.final_sql || data?.final_sql || data?.data?.sql || data?.sql || '';
|
||
};
|
||
|
||
/**
|
||
* 创建导出任务
|
||
* @param {Object} payload - 任务数据
|
||
* @returns {Promise<Object>} 创建结果
|
||
*/
|
||
const createExportJob = async (payload) => {
|
||
return post('/api/exports', payload, { withUserQuery: true });
|
||
};
|
||
|
||
/**
|
||
* 获取下载地址
|
||
* @param {number|string} id - 任务 ID
|
||
* @returns {string} 下载 URL
|
||
*/
|
||
const getDownloadUrl = (id) => {
|
||
return `${API_BASE}/api/exports/${id}/download`;
|
||
};
|
||
|
||
// ==================== 元数据相关 API ====================
|
||
|
||
/**
|
||
* 加载字段元数据
|
||
* @param {string} datasource - 数据源
|
||
* @param {number} orderType - 订单类型
|
||
* @returns {Promise<Object>} 元数据对象 { tables, recommended }
|
||
*/
|
||
const fetchFieldsMetadata = async (datasource, orderType) => {
|
||
const params = {
|
||
datasource: datasource,
|
||
order_type: String(orderType || 0)
|
||
};
|
||
const data = await get('/api/metadata/fields', { params });
|
||
|
||
const tables = Array.isArray(data?.data?.tables)
|
||
? data.data.tables
|
||
: (Array.isArray(data?.tables) ? data.tables : []);
|
||
|
||
const recommended = Array.isArray(data?.data?.recommended)
|
||
? data.data.recommended
|
||
: (Array.isArray(data?.recommended) ? data.recommended : []);
|
||
|
||
return { tables, recommended };
|
||
};
|
||
|
||
// ==================== 创建者/分销商等选项 API ====================
|
||
|
||
/**
|
||
* 加载创建者列表(营销系统)
|
||
* @returns {Promise<Array<{label: string, value: number, mobile?: string}>>} 选项数组
|
||
*/
|
||
const fetchCreators = async () => {
|
||
const data = await get('/api/creators');
|
||
const arr = parseArrayResponse(data);
|
||
return arr.map(it => ({
|
||
label: it.name || String(it.id),
|
||
value: Number(it.id),
|
||
mobile: it.mobile ? String(it.mobile).trim() : ''
|
||
}));
|
||
};
|
||
|
||
/**
|
||
* 加载易码通用户列表
|
||
* @param {string} [query] - 搜索关键词
|
||
* @returns {Promise<Array<{label: string, value: number}>>} 选项数组
|
||
*/
|
||
const fetchYmtUsers = async (query) => {
|
||
const params = { limit: '2000' };
|
||
if (query) params.q = query;
|
||
const data = await get('/api/ymt/users', { params });
|
||
const arr = parseArrayResponse(data);
|
||
return arr.map(it => ({ label: it.name || String(it.id), value: Number(it.id) }));
|
||
};
|
||
|
||
/**
|
||
* 加载分销商列表
|
||
* @param {number[]} creatorIds - 创建者 ID 数组
|
||
* @returns {Promise<Array<{label: string, value: number}>>} 选项数组
|
||
*/
|
||
const fetchResellers = async (creatorIds) => {
|
||
if (!creatorIds?.length) return [];
|
||
const data = await get('/api/resellers', { params: { creator: creatorIds.join(',') } });
|
||
const arr = parseArrayResponse(data);
|
||
return arr.map(it => ({ label: it.name || String(it.id), value: Number(it.id) }));
|
||
};
|
||
|
||
/**
|
||
* 加载计划列表
|
||
* @param {number} resellerId - 分销商 ID
|
||
* @returns {Promise<Array<{label: string, value: number}>>} 选项数组
|
||
*/
|
||
const fetchPlans = async (resellerId) => {
|
||
if (!resellerId) return [];
|
||
const data = await get('/api/plans', { params: { reseller: String(resellerId) } });
|
||
const arr = parseArrayResponse(data);
|
||
return arr.map(it => ({ label: `${it.id} - ${it.title || ''}`, value: Number(it.id) }));
|
||
};
|
||
|
||
/**
|
||
* 加载易码通客户列表
|
||
* @param {number} userId - 用户 ID
|
||
* @returns {Promise<Array<{label: string, value: number}>>} 选项数组
|
||
*/
|
||
const fetchYmtMerchants = async (userId) => {
|
||
if (!userId) return [];
|
||
const data = await get('/api/ymt/merchants', { params: { user_id: String(userId), limit: '2000' } });
|
||
const arr = parseArrayResponse(data);
|
||
return arr.map(it => ({ label: `${it.id} - ${it.name || ''}`, value: Number(it.id) }));
|
||
};
|
||
|
||
/**
|
||
* 加载易码通活动列表
|
||
* @param {number} merchantId - 客户 ID
|
||
* @returns {Promise<Array<{label: string, value: number}>>} 选项数组
|
||
*/
|
||
const fetchYmtActivities = async (merchantId) => {
|
||
if (!merchantId) return [];
|
||
const data = await get('/api/ymt/activities', { params: { merchant_id: String(merchantId), limit: '2000' } });
|
||
const arr = parseArrayResponse(data);
|
||
return arr.map(it => ({ label: `${it.id} - ${it.name || ''}`, value: Number(it.id) }));
|
||
};
|
||
|
||
// 导出模块
|
||
window.ApiService = {
|
||
// 基础方法
|
||
API_BASE,
|
||
getUserId,
|
||
getMobile,
|
||
hasOnlyUserId,
|
||
getMerchantId,
|
||
buildUserQueryString,
|
||
get,
|
||
post,
|
||
patch,
|
||
del,
|
||
|
||
// 模板
|
||
fetchTemplates,
|
||
fetchTemplateDetail,
|
||
createTemplate,
|
||
updateTemplate,
|
||
deleteTemplate,
|
||
|
||
// 任务
|
||
fetchJobs,
|
||
fetchJobDetail,
|
||
fetchJobSql,
|
||
createExportJob,
|
||
getDownloadUrl,
|
||
|
||
// 元数据
|
||
fetchFieldsMetadata,
|
||
|
||
// 选项数据
|
||
fetchCreators,
|
||
fetchYmtUsers,
|
||
fetchResellers,
|
||
fetchPlans,
|
||
fetchYmtMerchants,
|
||
fetchYmtActivities
|
||
};
|
||
|
||
})();
|