589 lines
17 KiB
JavaScript
589 lines
17 KiB
JavaScript
// API接口调用模块
|
||
|
||
// 环境检测函数
|
||
function getAPIBaseURL() {
|
||
// 优先使用 localStorage 中的手动配置(用于调试)
|
||
const manualBaseURL = localStorage.getItem('API_BASE_URL');
|
||
if (manualBaseURL) {
|
||
console.log('[API] 使用手动配置的后端地址:', manualBaseURL);
|
||
return manualBaseURL;
|
||
}
|
||
|
||
const hostname = window.location.hostname;
|
||
const protocol = window.location.protocol;
|
||
|
||
// 开发环境判断:localhost 或 127.0.0.1
|
||
if (hostname === 'localhost' || hostname === '127.0.0.1') {
|
||
return 'http://localhost:8081/api/v1';
|
||
}
|
||
|
||
// 生产环境:根据域名映射API地址
|
||
// vizee.cn 域名使用 https://tral.cc/api/v1
|
||
if (hostname.includes('vizee.cn')) {
|
||
console.log('[API] 检测到 vizee.cn 域名,使用 tral.cc API');
|
||
return 'https://tral.cc/api/v1';
|
||
}
|
||
|
||
// gvizee.com 域名使用相对路径,由 Nginx 代理到后端
|
||
if (hostname.includes('gvizee.com')) {
|
||
console.log('[API] 检测到 gvizee.com 域名,使用 Nginx 代理');
|
||
// 使用相对路径,自动继承当前页面的协议(HTTPS)
|
||
// Nginx 会将 /api/v1 请求代理到 http://104.244.91.212:8060/api/v1
|
||
return '/api/v1';
|
||
}
|
||
|
||
// 默认使用 tral.cc
|
||
console.log('[API] 使用默认 API 地址: tral.cc');
|
||
return 'https://tral.cc/api/v1';
|
||
}
|
||
|
||
// API配置
|
||
const API_CONFIG = {
|
||
baseURL: getAPIBaseURL(),
|
||
timeout: 30000,
|
||
headers: {
|
||
'Content-Type': 'application/json'
|
||
},
|
||
|
||
// 环境信息
|
||
env: {
|
||
isDevelopment: window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1',
|
||
isProduction: window.location.hostname !== 'localhost' && window.location.hostname !== '127.0.0.1'
|
||
}
|
||
};
|
||
|
||
// 控制台输出当前环境信息
|
||
console.log('[API] 当前环境:', API_CONFIG.env.isDevelopment ? '开发环境' : '生产环境');
|
||
console.log('[API] 后端地址:', API_CONFIG.baseURL);
|
||
|
||
// 全局辅助函数(用于开发调试)
|
||
window.API_DEBUG = {
|
||
// 设置后端地址
|
||
setBaseURL: function(url) {
|
||
localStorage.setItem('API_BASE_URL', url);
|
||
console.log('[API] 后端地址已设置为:', url);
|
||
console.log('[API] 请刷新页面使配置生效');
|
||
},
|
||
|
||
// 重置为自动检测
|
||
resetBaseURL: function() {
|
||
localStorage.removeItem('API_BASE_URL');
|
||
console.log('[API] 已重置为自动检测模式');
|
||
console.log('[API] 请刷新页面使配置生效');
|
||
},
|
||
|
||
// 查看当前配置
|
||
getConfig: function() {
|
||
return {
|
||
baseURL: API_CONFIG.baseURL,
|
||
environment: API_CONFIG.env.isDevelopment ? 'development' : 'production',
|
||
manualConfig: localStorage.getItem('API_BASE_URL') || 'auto',
|
||
currentURL: window.location.href
|
||
};
|
||
},
|
||
|
||
// 测试连接
|
||
testConnection: function() {
|
||
console.log('[API] 测试后端连接...');
|
||
fetch(API_CONFIG.baseURL.replace('/api/v1', '/health'))
|
||
.then(response => response.json())
|
||
.then(data => {
|
||
console.log('[API] ✅ 后端连接成功:', data);
|
||
})
|
||
.catch(error => {
|
||
console.error('[API] ❌ 后端连接失败:', error.message);
|
||
console.log('[API] 请确认后端服务已启动');
|
||
});
|
||
},
|
||
|
||
// 使用说明
|
||
help: function() {
|
||
console.log(`
|
||
=== API调试工具使用说明 ===
|
||
|
||
域名映射规则:
|
||
- vizee.cn 域名使用: https://tral.cc/api/v1
|
||
- gvizee.com 域名使用: https://104.244.91.212:8060/api/v1
|
||
- localhost/127.0.0.1 使用: http://localhost:8081/api/v1
|
||
- 其他域名默认使用: https://tral.cc/api/v1
|
||
|
||
1. 查看当前配置:
|
||
API_DEBUG.getConfig()
|
||
|
||
2. 手动设置后端地址:
|
||
API_DEBUG.setBaseURL('http://localhost:8080/api/v1')
|
||
API_DEBUG.setBaseURL('https://api.example.com/api/v1')
|
||
|
||
3. 重置为自动检测:
|
||
API_DEBUG.resetBaseURL()
|
||
|
||
4. 测试后端连接:
|
||
API_DEBUG.testConnection()
|
||
|
||
5. 查看帮助:
|
||
API_DEBUG.help()
|
||
`);
|
||
}
|
||
};
|
||
|
||
// 启动时提示
|
||
if (API_CONFIG.env.isDevelopment) {
|
||
console.log('[API] 💡 开发提示:在控制台输入 API_DEBUG.help() 查看调试工具使用说明');
|
||
}
|
||
|
||
// API模块
|
||
const API = {
|
||
// 获取Token
|
||
getToken: function() {
|
||
const user = localStorage.getItem('currentUser');
|
||
if (user) {
|
||
try {
|
||
const userData = JSON.parse(user);
|
||
return userData.token || '';
|
||
} catch (e) {
|
||
return '';
|
||
}
|
||
}
|
||
return '';
|
||
},
|
||
|
||
// 设置Token
|
||
setToken: function(token) {
|
||
const user = localStorage.getItem('currentUser');
|
||
if (user) {
|
||
try {
|
||
const userData = JSON.parse(user);
|
||
userData.token = token;
|
||
localStorage.setItem('currentUser', JSON.stringify(userData));
|
||
} catch (e) {
|
||
console.error('设置Token失败:', e);
|
||
}
|
||
}
|
||
},
|
||
|
||
// 通用请求方法
|
||
request: function(options) {
|
||
const url = API_CONFIG.baseURL + options.url;
|
||
const method = options.method || 'GET';
|
||
const data = options.data || null;
|
||
const headers = Object.assign({}, API_CONFIG.headers, options.headers || {});
|
||
|
||
// 添加认证Token
|
||
const token = this.getToken();
|
||
if (token) {
|
||
headers['Authorization'] = 'Bearer ' + token;
|
||
}
|
||
|
||
const ajaxOptions = {
|
||
url: url,
|
||
method: method,
|
||
headers: headers,
|
||
timeout: API_CONFIG.timeout,
|
||
xhrFields: {
|
||
withCredentials: true
|
||
}
|
||
};
|
||
|
||
// 添加数据
|
||
if (data) {
|
||
if (method === 'GET') {
|
||
ajaxOptions.data = data;
|
||
} else {
|
||
ajaxOptions.data = JSON.stringify(data);
|
||
}
|
||
}
|
||
|
||
// 返回Promise
|
||
return new Promise((resolve, reject) => {
|
||
$.ajax(ajaxOptions)
|
||
.done(function(response) {
|
||
// 检查响应格式
|
||
if (response && response.code === 200) {
|
||
resolve(response.data);
|
||
} else {
|
||
reject(response);
|
||
}
|
||
})
|
||
.fail(function(xhr, status, error) {
|
||
// 处理错误
|
||
const errorMsg = xhr.responseJSON?.message || error || '网络请求失败';
|
||
|
||
// 401未授权,提示并跳转登录
|
||
if (xhr.status === 401) {
|
||
// 判断是未登录还是登录过期
|
||
const hadToken = !!localStorage.getItem('currentUser');
|
||
const message = hadToken ? '登录已过期,请重新登录' : '请先登录';
|
||
|
||
console.log('[401 拦截]', message);
|
||
|
||
// 清除登录状态
|
||
localStorage.removeItem('currentUser');
|
||
|
||
// 使用 Toast.alert 显示提示(iOS 风格)
|
||
if (typeof Toast !== 'undefined' && Toast.alert) {
|
||
Toast.alert(message).then(() => {
|
||
// 用户点击确定后跳转
|
||
handleRedirectToLogin();
|
||
});
|
||
} else if (typeof Toast !== 'undefined' && Toast.warning) {
|
||
// 降级使用 warning
|
||
Toast.warning(message);
|
||
setTimeout(handleRedirectToLogin, 2000);
|
||
} else {
|
||
// 最后降级使用原生 alert
|
||
alert(message);
|
||
handleRedirectToLogin();
|
||
}
|
||
|
||
// 跳转逻辑封装
|
||
function handleRedirectToLogin() {
|
||
console.log('[401 跳转] 准备跳转登录页');
|
||
// 记录当前页面,登录后可以返回(排除登录页和用户中心)
|
||
const currentPage = window.location.pathname + window.location.search;
|
||
const shouldRedirect = !currentPage.includes('login.html') &&
|
||
!currentPage.includes('user-center.html');
|
||
|
||
if (shouldRedirect) {
|
||
localStorage.setItem('redirectUrl', currentPage);
|
||
console.log('[401 跳转] 保存 redirectUrl:', currentPage);
|
||
} else {
|
||
// 确保清除之前的redirectUrl
|
||
localStorage.removeItem('redirectUrl');
|
||
console.log('[401 跳转] 不保存 redirectUrl');
|
||
}
|
||
|
||
window.location.href = 'login.html';
|
||
}
|
||
|
||
reject({
|
||
code: 401,
|
||
message: message,
|
||
data: xhr.responseJSON
|
||
});
|
||
return;
|
||
}
|
||
|
||
reject({
|
||
code: xhr.status,
|
||
message: errorMsg,
|
||
data: xhr.responseJSON
|
||
});
|
||
});
|
||
});
|
||
},
|
||
|
||
// GET请求
|
||
get: function(url, params) {
|
||
return this.request({
|
||
url: url,
|
||
method: 'GET',
|
||
data: params
|
||
});
|
||
},
|
||
|
||
// POST请求
|
||
post: function(url, data) {
|
||
return this.request({
|
||
url: url,
|
||
method: 'POST',
|
||
data: data
|
||
});
|
||
},
|
||
|
||
// PUT请求
|
||
put: function(url, data) {
|
||
return this.request({
|
||
url: url,
|
||
method: 'PUT',
|
||
data: data
|
||
});
|
||
},
|
||
|
||
// DELETE请求
|
||
delete: function(url, data) {
|
||
return this.request({
|
||
url: url,
|
||
method: 'DELETE',
|
||
data: data
|
||
});
|
||
}
|
||
};
|
||
|
||
// 价格工具函数
|
||
const PriceUtils = {
|
||
/**
|
||
* 将后端返回的价格(分)转换为前端显示的价格(元)
|
||
* @param {number} price - 以分为单位的价格
|
||
* @returns {number} - 以元为单位的价格
|
||
*/
|
||
fenToYuan: function(price) {
|
||
return (price || 0) / 100;
|
||
},
|
||
|
||
/**
|
||
* 将前端输入的价格(元)转换为后端需要的价格(分)
|
||
* @param {number} price - 以元为单位的价格
|
||
* @returns {number} - 以分为单位的价格
|
||
*/
|
||
yuanToFen: function(price) {
|
||
return Math.round((price || 0) * 100);
|
||
},
|
||
|
||
/**
|
||
* 格式化价格显示(带货币符号)
|
||
* @param {number} price - 以分为单位的价格
|
||
* @param {string} currency - 货币符号,默认为¥
|
||
* @returns {string} - 格式化后的价格字符串
|
||
*/
|
||
formatPrice: function(price, currency = '¥') {
|
||
return currency + this.fenToYuan(price).toFixed(2);
|
||
}
|
||
};
|
||
|
||
// 具体业务API
|
||
const UserAPI = {
|
||
// 用户登录(邮箱密码)
|
||
login: function(email, password) {
|
||
return API.post('/users/email-login', {
|
||
email: email,
|
||
password: password
|
||
});
|
||
},
|
||
|
||
// 用户注册(邮箱密码)
|
||
register: function(email, password, nickname) {
|
||
return API.post('/users/email-register', {
|
||
email: email,
|
||
password: password,
|
||
nickname: nickname
|
||
});
|
||
},
|
||
|
||
// 获取用户信息
|
||
getProfile: function() {
|
||
return API.get('/users/profile');
|
||
},
|
||
|
||
// 更新用户信息
|
||
updateProfile: function(data) {
|
||
return API.put('/users/profile', data);
|
||
},
|
||
|
||
// 获取地址列表
|
||
getAddresses: function() {
|
||
return API.get('/users/addresses');
|
||
},
|
||
|
||
// 添加地址
|
||
addAddress: function(data) {
|
||
return API.post('/users/addresses', data);
|
||
},
|
||
|
||
// 更新地址
|
||
updateAddress: function(id, data) {
|
||
return API.put('/users/addresses/' + id, data);
|
||
},
|
||
|
||
// 删除地址
|
||
deleteAddress: function(id) {
|
||
return API.delete('/users/addresses/' + id);
|
||
}
|
||
};
|
||
|
||
const ProductAPI = {
|
||
// 获取商品列表
|
||
getList: function(params) {
|
||
return API.get('/products', params);
|
||
},
|
||
|
||
// 获取商品详情
|
||
getDetail: function(id) {
|
||
return API.get('/products/' + id);
|
||
},
|
||
|
||
// 获取热门商品
|
||
getHot: function() {
|
||
return API.get('/products/hot');
|
||
},
|
||
|
||
// 获取推荐商品
|
||
getRecommend: function() {
|
||
return API.get('/products/recommend');
|
||
},
|
||
|
||
// 搜索商品
|
||
search: function(keyword) {
|
||
return API.get('/products/search', { keyword: keyword });
|
||
},
|
||
|
||
// 获取商品评价
|
||
getReviews: function(id, params) {
|
||
return API.get('/products/' + id + '/reviews', params);
|
||
},
|
||
|
||
// 获取分类列表
|
||
getCategories: function() {
|
||
return API.get('/products/categories', { platform: 'web' });
|
||
}
|
||
};
|
||
|
||
const CartAPI = {
|
||
// 获取购物车
|
||
getCart: function() {
|
||
return API.get('/cart');
|
||
},
|
||
|
||
// 添加到购物车
|
||
addToCart: function(productId, skuId, quantity) {
|
||
return API.post('/cart', {
|
||
product_id: productId,
|
||
sku_id: skuId,
|
||
quantity: quantity
|
||
});
|
||
},
|
||
|
||
// 更新购物车数量
|
||
updateQuantity: function(productId, quantity) {
|
||
return API.put('/cart/' + productId, {
|
||
quantity: quantity
|
||
});
|
||
},
|
||
|
||
// 删除购物车商品
|
||
removeItem: function(productId) {
|
||
return API.delete('/cart/' + productId);
|
||
},
|
||
|
||
// 清空购物车
|
||
clearCart: function() {
|
||
return API.delete('/cart');
|
||
},
|
||
|
||
// 获取购物车数量
|
||
getCount: function() {
|
||
return API.get('/cart/count');
|
||
}
|
||
};
|
||
|
||
const OrderAPI = {
|
||
// 创建订单
|
||
createOrder: function(data) {
|
||
return API.post('/orders', data);
|
||
},
|
||
|
||
// 获取订单列表
|
||
getList: function(params) {
|
||
return API.get('/orders', params);
|
||
},
|
||
|
||
// 获取订单详情
|
||
getDetail: function(id) {
|
||
return API.get('/orders/' + id);
|
||
},
|
||
|
||
// 取消订单
|
||
cancelOrder: function(id, reason) {
|
||
return API.post('/orders/' + id + '/cancel', {
|
||
reason: reason
|
||
});
|
||
},
|
||
|
||
// 确认收货
|
||
confirmReceive: function(id) {
|
||
return API.post('/orders/' + id + '/confirm');
|
||
}
|
||
};
|
||
|
||
const BannerAPI = {
|
||
// 获取轮播图
|
||
getList: function() {
|
||
return API.get('/banners');
|
||
}
|
||
};
|
||
|
||
const CouponAPI = {
|
||
// 获取可用优惠券
|
||
getAvailable: function() {
|
||
return API.get('/coupons');
|
||
},
|
||
|
||
// 获取我的优惠券
|
||
getMyCoupons: function(status) {
|
||
return API.get('/coupons/user', { status: status });
|
||
},
|
||
|
||
// 领取优惠券
|
||
receive: function(id) {
|
||
return API.post('/coupons/' + id + '/receive');
|
||
}
|
||
};
|
||
|
||
const CommentAPI = {
|
||
// 获取商品评论
|
||
getProductComments: function(productId, params) {
|
||
return API.get('/comments/products/' + productId, params);
|
||
},
|
||
|
||
// 获取评论统计
|
||
getStats: function(productId) {
|
||
return API.get('/comments/products/' + productId + '/stats');
|
||
},
|
||
|
||
// 创建评论
|
||
create: function(data) {
|
||
return API.post('/comments', data);
|
||
}
|
||
};
|
||
|
||
const UploadAPI = {
|
||
// 上传图片
|
||
uploadImage: function(file) {
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
|
||
return new Promise((resolve, reject) => {
|
||
const token = API.getToken();
|
||
|
||
$.ajax({
|
||
url: API_CONFIG.baseURL + '/upload/image',
|
||
method: 'POST',
|
||
data: formData,
|
||
processData: false,
|
||
contentType: false,
|
||
headers: {
|
||
'Authorization': 'Bearer ' + token
|
||
},
|
||
xhrFields: {
|
||
withCredentials: true
|
||
}
|
||
})
|
||
.done(function(response) {
|
||
if (response && response.code === 200) {
|
||
resolve(response.data);
|
||
} else {
|
||
reject(response);
|
||
}
|
||
})
|
||
.fail(function(xhr, status, error) {
|
||
reject({
|
||
code: xhr.status,
|
||
message: xhr.responseJSON?.message || error,
|
||
data: xhr.responseJSON
|
||
});
|
||
});
|
||
});
|
||
}
|
||
};
|
||
|
||
const LiveStreamAPI = {
|
||
// 获取启用的投流源
|
||
getActiveLiveStreams: function() {
|
||
return API.get('/livestreams');
|
||
},
|
||
|
||
// 增加观看次数
|
||
incrementViewCount: function(id) {
|
||
return API.post('/livestreams/' + id + '/view');
|
||
}
|
||
};
|