MarketingSystemDataExportTool/web/modules/api.js

436 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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('userId') || params.get('userid') || params.get('user_id');
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}>>} 选项数组
*/
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) }));
};
/**
* 加载易码通用户列表
* @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,
getMerchantId,
buildUserQueryString,
get,
post,
patch,
del,
// 模板
fetchTemplates,
fetchTemplateDetail,
createTemplate,
updateTemplate,
deleteTemplate,
// 任务
fetchJobs,
fetchJobDetail,
fetchJobSql,
createExportJob,
getDownloadUrl,
// 元数据
fetchFieldsMetadata,
// 选项数据
fetchCreators,
fetchYmtUsers,
fetchResellers,
fetchPlans,
fetchYmtMerchants,
fetchYmtActivities
};
})();