web
This commit is contained in:
588
web/assets/js/api.js
Normal file
588
web/assets/js/api.js
Normal file
@@ -0,0 +1,588 @@
|
||||
// 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');
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user