/** * 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} 响应数据 */ 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} 响应数据 */ 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} 响应数据 */ 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} 响应数据 */ 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} 模板数组 */ const fetchTemplates = async () => { const data = await get('/api/templates', { withUserQuery: true }); return parseArrayResponse(data); }; /** * 获取模板详情 * @param {number|string} id - 模板 ID * @returns {Promise} 模板详情 */ const fetchTemplateDetail = async (id) => { const data = await get(`/api/templates/${id}`); return data?.data || {}; }; /** * 创建模板 * @param {Object} payload - 模板数据 * @returns {Promise} 创建结果 */ const createTemplate = async (payload) => { return post('/api/templates', payload, { withUserQuery: true }); }; /** * 更新模板 * @param {number|string} id - 模板 ID * @param {Object} payload - 更新数据 * @returns {Promise} 更新结果 */ const updateTemplate = async (id, payload) => { return patch(`/api/templates/${id}`, payload); }; /** * 删除模板(软删除) * @param {number|string} id - 模板 ID * @returns {Promise} 删除结果 */ 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} 任务详情 */ const fetchJobDetail = async (id) => { const data = await get(`/api/exports/${id}`, { withUserQuery: true }); return data?.data || {}; }; /** * 获取任务 SQL * @param {number|string} id - 任务 ID * @returns {Promise} 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} 创建结果 */ 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} 元数据对象 { 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>} 选项数组 */ 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>} 选项数组 */ 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>} 选项数组 */ 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>} 选项数组 */ 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>} 选项数组 */ 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>} 选项数组 */ 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 }; })();