Initial commit

This commit is contained in:
sjk
2025-11-17 13:32:54 +08:00
commit e788eab6eb
1659 changed files with 171560 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
import { config } from '../../../config/index';
import { queryCommentDetail } from '../../../model/comments/queryDetail';
/** 获取商品评价数据 */
function mockQueryCommentDetail(params) {
const { delay } = require('../../_utils/delay');
const data = queryCommentDetail(params);
return delay().then(() => {
return data;
});
}
/** 获取评价详情 */
export function getCommentDetail(params) {
if (config.useMock) {
return mockQueryCommentDetail(params);
}
return new Promise((resolve) => {
resolve('real api');
});
}

View File

@@ -0,0 +1,141 @@
import { config } from '../../config/index';
/**
* 检查商品是否已收藏
* @param {number} productId 商品ID
* @returns {Promise<boolean>} 是否已收藏
*/
export function checkIsFavorite(productId) {
return new Promise((resolve, reject) => {
// 获取用户token
const token = wx.getStorageSync('token') || wx.getStorageSync('jwt_token');
if (!token) {
console.log('用户未登录,默认未收藏');
resolve(false);
return;
}
wx.request({
url: `${config.apiBase}/products/${productId}/favorite/status`,
method: 'GET',
header: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
success: (res) => {
if (res.statusCode === 200 && (res.data?.code === 200 || res.data?.success === true || res.data?.code === 'Success')) {
try {
const payload = res.data || {};
const data = payload.data || payload;
const isFavorite = (
data.is_favorite ??
data.isFavorite ??
data.favorite ??
data.favorited ??
false
);
resolve(Boolean(isFavorite));
} catch (e) {
console.error('解析收藏状态失败:', e, res);
resolve(false);
}
} else {
console.error('检查收藏状态失败:', res);
resolve(false);
}
},
fail: (error) => {
console.error('检查收藏状态请求失败:', error);
resolve(false);
}
});
});
}
/**
* 添加商品到收藏
* @param {number} productId 商品ID
* @returns {Promise<boolean>} 操作是否成功
*/
export function addToFavorite(productId) {
return new Promise((resolve, reject) => {
// 获取用户token
const token = wx.getStorageSync('token') || wx.getStorageSync('jwt_token');
if (!token) {
reject(new Error('用户未登录'));
return;
}
wx.request({
url: `${config.apiBase}/products/${productId}/favorite`,
method: 'POST',
header: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
success: (res) => {
if ((res.statusCode === 200 || res.statusCode === 201) && (res.data?.code === 200 || res.data?.success === true)) {
resolve(true);
} else {
console.error('添加收藏失败:', res);
reject(new Error(res.data.message || '收藏失败'));
}
},
fail: (error) => {
console.error('添加收藏请求失败:', error);
reject(error);
}
});
});
}
/**
* 取消收藏商品
* @param {number} productId 商品ID
* @returns {Promise<boolean>} 操作是否成功
*/
export function removeFromFavorite(productId) {
return new Promise((resolve, reject) => {
// 获取用户token
const token = wx.getStorageSync('token') || wx.getStorageSync('jwt_token');
if (!token) {
reject(new Error('用户未登录'));
return;
}
wx.request({
url: `${config.apiBase}/products/${productId}/favorite`,
method: 'DELETE',
header: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
success: (res) => {
if ((res.statusCode === 200 || res.statusCode === 204) && (res.data?.code === 200 || res.data?.success === true)) {
resolve(true);
} else {
console.error('取消收藏失败:', res);
reject(new Error(res.data.message || '取消收藏失败'));
}
},
fail: (error) => {
console.error('取消收藏请求失败:', error);
reject(error);
}
});
});
}
/**
* 切换收藏状态
* @param {number} productId 商品ID
* @param {boolean} currentStatus 当前收藏状态
* @returns {Promise<boolean>} 新的收藏状态
*/
export function toggleFavorite(productId, currentStatus) {
if (currentStatus) {
return removeFromFavorite(productId).then(() => false);
} else {
return addToFavorite(productId).then(() => true);
}
}

View File

@@ -0,0 +1,77 @@
import { config } from '../../config/index';
/** 获取商品列表 */
function mockFetchGoodCategory() {
const { delay } = require('../_utils/delay');
const { getCategoryList } = require('../../model/category');
return delay().then(() => getCategoryList());
}
/** 将后端分类数据转换为前端需要的格式 */
function transformCategoryData(categories) {
if (!categories || !Array.isArray(categories)) {
return [];
}
// 递归转换分类数据
function transformCategory(category) {
const transformed = {
groupId: category.groupId || category.id.toString(),
name: category.name,
thumbnail: category.thumbnail || category.icon || 'https://tdesign.gtimg.com/miniprogram/template/retail/category/category-default.png',
};
// 为每个一级分类添加"全部"按钮
const allButton = {
groupId: `${category.id}_all`,
name: '全部',
thumbnail: 'https://tdesign.gtimg.com/miniprogram/template/retail/category/category-all.png',
categoryId: category.id.toString(),
categoryName: category.name,
isAll: true
};
// 如果有子分类,添加"全部"按钮并处理子分类
if (category.children && Array.isArray(category.children) && category.children.length > 0) {
transformed.children = [allButton, ...category.children.map(child => transformCategory(child))];
} else {
// 如果没有子分类,只显示"全部"按钮
transformed.children = [allButton];
}
return transformed;
}
return categories.map(category => transformCategory(category));
}
/** 获取分类列表 */
export function getCategoryList() {
if (config.useMock) {
return mockFetchGoodCategory();
}
return new Promise((resolve, reject) => {
wx.request({
url: `${config.apiBase}/products/categories`,
method: 'GET',
success: (res) => {
if (res.statusCode === 200 && res.data.code === 200) {
console.log('原始分类数据:', res.data.data);
const transformedData = transformCategoryData(res.data.data);
console.log('转换后的分类数据:', transformedData);
resolve(transformedData);
} else {
console.error('获取分类失败:', res.data);
// 失败时返回空数组,避免页面崩溃
resolve([]);
}
},
fail: (err) => {
console.error('分类请求失败:', err);
// 失败时返回空数组,避免页面崩溃
resolve([]);
}
});
});
}

View File

@@ -0,0 +1,93 @@
import { config } from '../../config/index';
/** 获取商品列表 */
function mockFetchGood(ID = 0) {
const { delay } = require('../_utils/delay');
const { genGood } = require('../../model/good');
return delay().then(() => genGood(ID));
}
/** 获取商品详情 */
export function fetchGood(ID = 0) {
if (config.useMock) {
return mockFetchGood(ID);
}
return new Promise((resolve, reject) => {
wx.request({
url: `${config.apiBase}/frontend/products/${ID}/detail`,
method: 'GET',
success: (res) => {
if (res.statusCode === 200 && res.data.code === 200) {
// 转换后端数据格式为前端期望的格式
const product = res.data.data;
// 计算价格范围
let minPrice = parseInt(product.minSalePrice) || 0;
let maxPrice = parseInt(product.maxSalePrice) || 0;
let minOriginPrice = parseInt(product.minLinePrice) || 0;
let maxOriginPrice = parseInt(product.maxLinePrice) || 0;
if (product.skuList && product.skuList.length > 0) {
const prices = product.skuList.map(sku => {
const priceInfo = sku.priceInfo && sku.priceInfo.length > 0 ? sku.priceInfo[0] : null;
return priceInfo ? parseInt(priceInfo.price) : 0; // 直接使用分为单位
});
const originPrices = product.skuList.map(sku => {
const priceInfo = sku.priceInfo && sku.priceInfo.length > 0 ? sku.priceInfo[0] : null;
return priceInfo ? parseInt(priceInfo.originPrice || priceInfo.price) : 0; // 直接使用分为单位
});
minPrice = Math.min(...prices);
maxPrice = Math.max(...prices);
minOriginPrice = Math.min(...originPrices);
maxOriginPrice = Math.max(...originPrices);
}
const result = {
// 基础信息:优先读取后端前端化字段,其次回退旧字段
spuId: product.spuId || product.id,
title: product.title || product.name,
price: minPrice,
originPrice: minOriginPrice,
minSalePrice: minPrice,
maxSalePrice: maxPrice,
maxLinePrice: maxOriginPrice,
primaryImage: product.primaryImage || product.main_image,
images: (product.images && product.images.length ? product.images : (product.primaryImage || product.main_image ? [product.primaryImage || product.main_image] : [])),
video: product.video || null,
// 详情描述:仅返回图片列表,支持后端返回字符串单图的情况
desc: (Array.isArray(product.desc) && product.desc.length)
? product.desc
: ((typeof product.desc === 'string' && product.desc.trim())
? [product.desc.trim()]
: ((Array.isArray(product.detail_images) && product.detail_images.length)
? product.detail_images
: [])),
// 文本描述:单独提供文本字段作为兜底
descriptionText: product.description || '',
details: product.details,
brand: product.brand,
category: product.category,
isPutOnSale: product.isPutOnSale ?? 1, // 1表示上架0表示下架
isStock: (product.spuStockQuantity ?? product.stock ?? 0) > 0,
stockNum: product.spuStockQuantity ?? product.stock ?? 0,
spuStockQuantity: product.spuStockQuantity ?? product.stock ?? 0,
soldNum: product.soldNum ?? product.sold_count ?? 0,
spuTagList: product.spuTagList ?? product.tags ?? [],
limitInfo: product.limitInfo ?? [],
specList: product.specList || [],
skuList: product.skuList || [],
etitle: product.etitle || product.name,
available: product.available ?? ((product.spuStockQuantity ?? product.stock ?? 0) > 0 ? 1 : 0)
};
resolve(result);
} else {
reject(new Error(res.data.message || '获取商品详情失败'));
}
},
fail: (err) => {
reject(err);
}
});
});
}

View File

@@ -0,0 +1,70 @@
import { config } from '../../config/index';
/** 获取商品列表 */
function mockFetchGoodsList(pageIndex = 1, pageSize = 20) {
const { delay } = require('../_utils/delay');
const { getGoodsList } = require('../../model/goods');
return delay().then(() =>
getGoodsList(pageIndex, pageSize).map((item) => {
return {
spuId: item.spuId,
thumb: item.primaryImage,
title: item.title,
price: item.minSalePrice,
originPrice: item.maxLinePrice,
tags: item.spuTagList.map((tag) => tag.title),
};
}),
);
}
/** 获取商品列表 */
export function fetchGoodsList(pageIndex = 1, pageSize = 20, categoryId = null) {
if (config.useMock) {
return mockFetchGoodsList(pageIndex, pageSize);
}
return new Promise((resolve, reject) => {
const requestData = {
page: pageIndex,
limit: pageSize,
};
// 如果有分类ID添加到请求参数中
if (categoryId) {
requestData.category_id = categoryId;
}
wx.request({
url: `${config.apiBase}/products`,
method: 'GET',
data: requestData,
success: (res) => {
if (res.statusCode === 200 && res.data.code === 200) {
// 转换API数据格式为小程序期望的格式
const products = res.data.data?.list || [];
const goodsList = products.map((item) => {
return {
spuId: item.id,
thumb: item.main_image,
title: item.name,
price: item.price,
originPrice: item.orig_price || item.price,
tags: [], // API暂时没有标签数据
stock: item.stock || 0, // 添加库存信息
isStock: item.stock > 0, // 添加库存状态
};
});
resolve(goodsList);
} else {
console.error('获取商品列表失败:', res.data);
resolve([]);
}
},
fail: (err) => {
console.error('商品列表请求失败:', err);
resolve([]);
}
});
});
}

View File

@@ -0,0 +1,134 @@
import { config } from '../../config/index';
/** 获取商品详情页评论数 */
function mockFetchGoodDetailsCommentsCount(spuId = 0) {
const { delay } = require('../_utils/delay');
const {
getGoodsDetailsCommentsCount,
} = require('../../model/detailsComments');
return delay().then(() => getGoodsDetailsCommentsCount(spuId));
}
/** 获取商品详情页评论数 */
export function getGoodsDetailsCommentsCount(spuId = 0) {
if (config.useMock) {
return mockFetchGoodDetailsCommentsCount(spuId);
}
return new Promise((resolve, reject) => {
wx.request({
url: `${config.apiBase}/products/${spuId}/reviews/count`,
method: 'GET',
header: {
'Content-Type': 'application/json',
},
success: (res) => {
if (res.statusCode === 200) {
// 转换后端数据格式为前端需要的格式
const data = {
commentCount: res.data.data.total || 0,
goodRate: res.data.data.good_rate || '100',
badCount: res.data.data.bad_count || 0,
goodCount: res.data.data.good_count || 0,
middleCount: res.data.data.middle_count || 0,
hasImageCount: res.data.data.has_image_count || 0,
};
resolve(data);
} else {
console.error('获取评论统计失败:', res);
// 失败时返回默认数据
resolve({
commentCount: 0,
goodRate: '100',
badCount: 0,
goodCount: 0,
middleCount: 0,
hasImageCount: 0,
});
}
},
fail: (error) => {
console.error('获取评论统计请求失败:', error);
// 网络错误时返回默认数据
resolve({
commentCount: 0,
goodRate: '100',
badCount: 0,
goodCount: 0,
middleCount: 0,
hasImageCount: 0,
});
}
});
});
}
/** 获取商品详情页评论 */
function mockFetchGoodDetailsCommentList(spuId = 0) {
const { delay } = require('../_utils/delay');
const { getGoodsDetailsComments } = require('../../model/detailsComments');
return delay().then(() => getGoodsDetailsComments(spuId));
}
/** 获取商品详情页评论 */
export function getGoodsDetailsCommentList(spuId = 0, page = 1, limit = 10) {
if (config.useMock) {
return mockFetchGoodDetailsCommentList(spuId);
}
return new Promise((resolve, reject) => {
wx.request({
url: `${config.apiBase}/products/${spuId}/reviews`,
method: 'GET',
data: {
page: page,
limit: limit,
},
header: {
'Content-Type': 'application/json',
},
success: (res) => {
if (res.statusCode === 200) {
// 转换后端数据格式为前端需要的格式
const reviews = res.data.data.list || [];
const transformedData = reviews.map(review => ({
spuId: review.product_id?.toString() || spuId.toString(),
skuId: review.sku_id?.toString() || '0',
specInfo: review.spec_info || '',
commentContent: review.content || '',
commentResources: review.images ? review.images.map(img => ({
src: img,
type: 'image'
})) : [],
commentScore: review.rating || 5,
uid: review.user_id?.toString() || '',
userName: review.user_name || '匿名用户',
userHeadUrl: review.user_avatar || 'https://tdesign.gtimg.com/miniprogram/template/retail/avatar/avatar1.png',
isAnonymity: review.is_anonymous || false,
commentTime: review.created_at ? new Date(review.created_at).getTime().toString() : Date.now().toString(),
isAutoComment: false,
sellerReply: review.reply || '',
goodsDetailInfo: review.spec_info || '',
}));
resolve({
homePageComments: transformedData
});
} else {
console.error('获取评论列表失败:', res);
// 失败时返回空数组
resolve({
homePageComments: []
});
}
},
fail: (error) => {
console.error('获取评论列表请求失败:', error);
// 网络错误时返回空数组
resolve({
homePageComments: []
});
}
});
});
}

View File

@@ -0,0 +1,146 @@
/* eslint-disable no-param-reassign */
import { config } from '../../config/index';
/** 获取商品列表 */
function mockFetchGoodsList(params) {
const { delay } = require('../_utils/delay');
const { getSearchResult } = require('../../model/search');
const data = getSearchResult(params);
if (data.spuList.length) {
data.spuList.forEach((item) => {
item.spuId = item.spuId;
item.thumb = item.primaryImage;
item.title = item.title;
item.price = item.minSalePrice;
item.originPrice = item.maxLinePrice;
item.desc = '';
if (item.spuTagList) {
item.tags = item.spuTagList.map((tag) => tag.title);
} else {
item.tags = [];
}
});
}
return delay().then(() => {
return data;
});
}
/** 获取商品列表 */
export function fetchGoodsList(params) {
console.log('[API] fetchGoodsList 开始调用:', {
inputParams: params,
useMock: config.useMock,
timestamp: new Date().toLocaleString()
});
if (config.useMock) {
console.log('[API] 使用Mock数据');
return mockFetchGoodsList(params);
}
const requestData = {
page: params.pageNum || 1,
page_size: params.pageSize || 20,
category_id: params.category_id,
keyword: params.keyword,
min_price: params.minPrice,
max_price: params.maxPrice,
sort: params.sort,
sortType: params.sortType
};
// 过滤无效参数,避免传递 'undefined' 等值
Object.keys(requestData).forEach((key) => {
const val = requestData[key];
if (
val === undefined ||
val === null ||
(typeof val === 'string' && (val.trim() === '' || val.trim().toLowerCase() === 'undefined'))
) {
delete requestData[key];
}
});
console.log('[API] 发送请求到后端:', {
url: `${config.apiBase}/products`,
method: 'GET',
requestData: requestData,
sortInfo: {
hasSort: !!params.sort,
sortType: params.sortType,
sortText: params.sortType === 1 ? '价格从高到低' : params.sortType === 0 ? '价格从低到高' : '默认排序'
},
timestamp: new Date().toLocaleString()
});
return new Promise((resolve, reject) => {
wx.request({
url: `${config.apiBase}/products`,
method: 'GET',
data: requestData,
success: (res) => {
console.log('[API] 收到后端响应:', {
statusCode: res.statusCode,
dataCode: res.data?.code,
dataMessage: res.data?.message,
totalCount: res.data?.data?.total,
listLength: res.data?.data?.list?.length,
timestamp: new Date().toLocaleString()
});
if (res.statusCode === 200 && res.data.code === 200) {
// 转换后端数据格式为前端期望的格式
const products = res.data.data.list.map(item => ({
spuId: item.id.toString(),
thumb: item.main_image,
title: item.name,
price: item.price,
originPrice: item.orig_price,
desc: item.description,
tags: item.tags || [],
isStock: item.stock > 0
}));
const result = {
spuList: products,
totalCount: res.data.data.total,
pageNum: res.data.data.page,
pageSize: res.data.data.page_size
};
console.log('[API] 数据转换完成:', {
productsCount: products.length,
totalCount: result.totalCount,
pageNum: result.pageNum,
pageSize: result.pageSize,
firstProduct: products[0] ? {
title: products[0].title,
price: products[0].price
} : null,
timestamp: new Date().toLocaleString()
});
resolve(result);
} else {
console.error('[API] 请求失败:', {
statusCode: res.statusCode,
dataCode: res.data?.code,
message: res.data?.message || '获取商品列表失败',
timestamp: new Date().toLocaleString()
});
reject(new Error(res.data.message || '获取商品列表失败'));
}
},
fail: (err) => {
console.error('[API] 网络请求失败:', {
error: err,
timestamp: new Date().toLocaleString()
});
reject(err);
}
});
});
}

View File

@@ -0,0 +1,35 @@
import { config } from '../../config/index';
/** 获取搜索历史 */
function mockSearchHistory() {
const { delay } = require('../_utils/delay');
const { getSearchHistory } = require('../../model/search');
return delay().then(() => getSearchHistory());
}
/** 获取搜索历史 */
export function getSearchHistory() {
if (config.useMock) {
return mockSearchHistory();
}
return new Promise((resolve) => {
resolve('real api');
});
}
/** 获取搜索历史 */
function mockSearchPopular() {
const { delay } = require('../_utils/delay');
const { getSearchPopular } = require('../../model/search');
return delay().then(() => getSearchPopular());
}
/** 获取搜索历史 */
export function getSearchPopular() {
if (config.useMock) {
return mockSearchPopular();
}
return new Promise((resolve) => {
resolve('real api');
});
}

View File

@@ -0,0 +1,133 @@
/* eslint-disable no-param-reassign */
import { config } from '../../config/index';
/** 获取搜索结果 - Mock数据 */
function mockSearchResult(params) {
const { delay } = require('../_utils/delay');
const { getSearchResult } = require('../../model/search');
const data = getSearchResult(params);
if (data.spuList.length) {
data.spuList.forEach((item) => {
item.spuId = item.spuId;
item.thumb = item.primaryImage;
item.title = item.title;
item.price = item.minSalePrice;
item.originPrice = item.maxLinePrice;
if (item.spuTagList) {
item.tags = item.spuTagList.map((tag) => ({ title: tag.title }));
} else {
item.tags = [];
}
// 添加库存状态字段 - Mock数据
item.isStock = item.spuStockQuantity > 0;
item.stock = item.spuStockQuantity || 0;
});
}
return delay().then(() => {
return data;
});
}
/** 调用真实的搜索API */
function realSearchResult(params) {
return new Promise((resolve, reject) => {
// 构建请求参数
const requestParams = {
keyword: params.keyword || '',
page: params.pageNum || 1,
page_size: params.pageSize || 30,
};
// 添加排序参数
if (params.sort === 1) {
requestParams.sort_by = 'price';
requestParams.sort_order = params.sortType === 1 ? 'desc' : 'asc';
}
// 添加价格筛选
if (params.minPrice && params.minPrice > 0) {
requestParams.min_price = params.minPrice;
}
if (params.maxPrice && params.maxPrice > 0) {
requestParams.max_price = params.maxPrice;
}
// 构建查询字符串
const queryString = Object.keys(requestParams)
.filter(key => requestParams[key] !== undefined && requestParams[key] !== '')
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(requestParams[key])}`)
.join('&');
const url = `${config.apiBase}/products/search?${queryString}`;
console.log('🔍 搜索API请求:', {
url,
params: requestParams,
originalParams: params
});
wx.request({
url,
method: 'GET',
header: {
'Content-Type': 'application/json',
},
success: (res) => {
console.log('🔍 搜索API响应:', res);
if (res.statusCode === 200 && res.data && res.data.code === 200) {
const { data: responseData } = res.data;
console.log('🔍 后端响应数据结构:', responseData);
// 转换后端数据格式为前端期望的格式
const result = {
saasId: null,
storeId: null,
pageNum: responseData.page || params.pageNum || 1,
pageSize: responseData.page_size || params.pageSize || 30,
totalCount: responseData.total || 0,
spuList: (responseData.list || []).map(item => ({
spuId: item.id,
title: item.name,
primaryImage: item.main_image || item.image || '',
thumb: item.main_image || item.image || '',
minSalePrice: item.price || 0,
maxLinePrice: item.original_price || item.price || 0,
price: item.price || 0,
originPrice: item.original_price || item.price || 0,
spuTagList: (item.tags || []).map(tag => ({ title: tag })),
tags: (item.tags || []).map(tag => ({ title: tag })),
// 添加库存状态字段
isStock: item.stock > 0,
stock: item.stock || 0,
// 保留原始数据以备后用
_originalData: item
})),
algId: 0,
};
console.log('🔍 转换后的搜索结果:', result);
resolve(result);
} else {
console.error('🔍 搜索API错误响应:', res);
reject(new Error(res.data?.message || '搜索请求失败'));
}
},
fail: (error) => {
console.error('🔍 搜索API请求失败:', error);
reject(new Error('网络请求失败,请检查网络连接'));
}
});
});
}
/** 获取搜索结果 */
export function getSearchResult(params) {
if (config.useMock) {
return mockSearchResult(params);
}
return realSearchResult(params);
}