feat: 增强产品同步功能并优化SKU生成逻辑
添加字典排序字段支持 优化产品同步流程,支持通过SKU同步 重构SKU模板生成逻辑,支持分类属性排序 完善产品导入导出功能,增加分类字段处理 统一产品操作方法,提升代码可维护性
This commit is contained in:
parent
93931f7915
commit
cd0bcedfad
|
|
@ -523,23 +523,6 @@
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@faker-js/faker": {
|
|
||||||
"version": "10.1.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-10.1.0.tgz",
|
|
||||||
"integrity": "sha512-C3mrr3b5dRVlKPJdfrAXS8+dq+rq8Qm5SNRazca0JKgw1HQERFmrVb0towvMmw5uu8hHKNiQasMaR/tydf3Zsg==",
|
|
||||||
"funding": [
|
|
||||||
{
|
|
||||||
"type": "opencollective",
|
|
||||||
"url": "https://opencollective.com/fakerjs"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"license": "MIT",
|
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
|
||||||
"node": "^20.19.0 || ^22.13.0 || ^23.5.0 || >=24.0.0",
|
|
||||||
"npm": ">=10"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/@hapi/bourne": {
|
"node_modules/@hapi/bourne": {
|
||||||
"version": "3.0.0",
|
"version": "3.0.0",
|
||||||
"resolved": "https://registry.npmmirror.com/@hapi/bourne/-/bourne-3.0.0.tgz",
|
"resolved": "https://registry.npmmirror.com/@hapi/bourne/-/bourne-3.0.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -742,7 +742,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
mapCreateProductParams(data: Partial<UnifiedProductDTO>): any {
|
mapCreateProductParams(data: Partial<UnifiedProductDTO>): Partial<ShopyyProduct> {
|
||||||
// 构建 ShopYY 产品创建参数
|
// 构建 ShopYY 产品创建参数
|
||||||
const params: any = {
|
const params: any = {
|
||||||
name: data.name || '',
|
name: data.name || '',
|
||||||
|
|
|
||||||
|
|
@ -133,9 +133,30 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 客户操作方法
|
// 客户操作方法
|
||||||
async getCustomer(where: {id: string | number}): Promise<UnifiedCustomerDTO> {
|
async getCustomer(where: Partial<Pick<UnifiedCustomerDTO, 'id' | 'email' | 'phone'>>): Promise<UnifiedCustomerDTO> {
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
const res = await api.get(`customers/${where.id}`);
|
// 根据提供的条件构建查询参数
|
||||||
|
let endpoint: string;
|
||||||
|
if (where.id) {
|
||||||
|
endpoint = `customers/${where.id}`;
|
||||||
|
} else if (where.email) {
|
||||||
|
// 使用邮箱查询客户
|
||||||
|
const res = await api.get('customers', { params: { email: where.email } });
|
||||||
|
if (!res.data || res.data.length === 0) {
|
||||||
|
throw new Error('Customer not found');
|
||||||
|
}
|
||||||
|
return this.mapPlatformToUnifiedCustomer(res.data[0]);
|
||||||
|
} else if (where.phone) {
|
||||||
|
// 使用电话查询客户
|
||||||
|
const res = await api.get('customers', { params: { search: where.phone } });
|
||||||
|
if (!res.data || res.data.length === 0) {
|
||||||
|
throw new Error('Customer not found');
|
||||||
|
}
|
||||||
|
return this.mapPlatformToUnifiedCustomer(res.data[0]);
|
||||||
|
} else {
|
||||||
|
throw new Error('Must provide at least one of id, email, or phone');
|
||||||
|
}
|
||||||
|
const res = await api.get(endpoint);
|
||||||
return this.mapPlatformToUnifiedCustomer(res.data);
|
return this.mapPlatformToUnifiedCustomer(res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,7 +179,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
|
|
||||||
async getAllCustomers(params?: UnifiedSearchParamsDTO): Promise<UnifiedCustomerDTO[]> {
|
async getAllCustomers(params?: UnifiedSearchParamsDTO): Promise<UnifiedCustomerDTO[]> {
|
||||||
// 使用sdkGetAll获取所有客户数据,不受分页限制
|
// 使用sdkGetAll获取所有客户数据,不受分页限制
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
|
|
||||||
// 处理orderBy参数,转换为WooCommerce API需要的格式
|
// 处理orderBy参数,转换为WooCommerce API需要的格式
|
||||||
const requestParams = this.mapCustomerSearchParams(params || {});
|
const requestParams = this.mapCustomerSearchParams(params || {});
|
||||||
|
|
@ -168,20 +189,42 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
async createCustomer(data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
|
async createCustomer(data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
const res = await api.post('customers', data);
|
const res = await api.post('customers', data);
|
||||||
return this.mapPlatformToUnifiedCustomer(res.data);
|
return this.mapPlatformToUnifiedCustomer(res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateCustomer(where: {id: string | number}, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
|
async updateCustomer(where: Partial<Pick<UnifiedCustomerDTO, 'id' | 'email' | 'phone'>>, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO> {
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
const res = await api.put(`customers/${where.id}`, data);
|
let customerId: string | number;
|
||||||
|
|
||||||
|
// 先根据条件获取客户ID
|
||||||
|
if (where.id) {
|
||||||
|
customerId = where.id;
|
||||||
|
} else {
|
||||||
|
// 如果没有提供ID,则先查询客户
|
||||||
|
const customer = await this.getCustomer(where);
|
||||||
|
customerId = customer.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await api.put(`customers/${customerId}`, data);
|
||||||
return this.mapPlatformToUnifiedCustomer(res.data);
|
return this.mapPlatformToUnifiedCustomer(res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteCustomer(where: {id: string | number}): Promise<boolean> {
|
async deleteCustomer(where: Partial<Pick<UnifiedCustomerDTO, 'id' | 'email' | 'phone'>>): Promise<boolean> {
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
await api.delete(`customers/${where.id}`, { force: true });
|
let customerId: string | number;
|
||||||
|
|
||||||
|
// 先根据条件获取客户ID
|
||||||
|
if (where.id) {
|
||||||
|
customerId = where.id;
|
||||||
|
} else {
|
||||||
|
// 如果没有提供ID,则先查询客户
|
||||||
|
const customer = await this.getCustomer(where);
|
||||||
|
customerId = customer.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
await api.delete(`customers/${customerId}`, { force: true });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,7 +268,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
|
|
||||||
async getAllMedia(params?: UnifiedSearchParamsDTO): Promise<UnifiedMediaDTO[]> {
|
async getAllMedia(params?: UnifiedSearchParamsDTO): Promise<UnifiedMediaDTO[]> {
|
||||||
// 使用sdkGetAll获取所有媒体数据,不受分页限制
|
// 使用sdkGetAll获取所有媒体数据,不受分页限制
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
const media = await this.wpService.sdkGetAll(api, 'media', params);
|
const media = await this.wpService.sdkGetAll(api, 'media', params);
|
||||||
return media.map((mediaItem: any) => this.mapPlatformToUnifiedMedia(mediaItem));
|
return media.map((mediaItem: any) => this.mapPlatformToUnifiedMedia(mediaItem));
|
||||||
}
|
}
|
||||||
|
|
@ -403,7 +446,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
// 订单操作方法
|
// 订单操作方法
|
||||||
async getOrder(where: {id: string | number}): Promise<UnifiedOrderDTO> {
|
async getOrder(where: {id: string | number}): Promise<UnifiedOrderDTO> {
|
||||||
// 获取单个订单详情
|
// 获取单个订单详情
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
const res = await api.get(`orders/${where.id}`);
|
const res = await api.get(`orders/${where.id}`);
|
||||||
return this.mapPlatformToUnifiedOrder(res.data);
|
return this.mapPlatformToUnifiedOrder(res.data);
|
||||||
}
|
}
|
||||||
|
|
@ -445,7 +488,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
|
|
||||||
async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> {
|
async getAllOrders(params?: UnifiedSearchParamsDTO): Promise<UnifiedOrderDTO[]> {
|
||||||
// 使用sdkGetAll获取所有订单数据,不受分页限制
|
// 使用sdkGetAll获取所有订单数据,不受分页限制
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
const orders = await this.wpService.sdkGetAll(api, 'orders', params);
|
const orders = await this.wpService.sdkGetAll(api, 'orders', params);
|
||||||
return orders.map((order: any) => this.mapPlatformToUnifiedOrder(order));
|
return orders.map((order: any) => this.mapPlatformToUnifiedOrder(order));
|
||||||
}
|
}
|
||||||
|
|
@ -464,7 +507,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
|
|
||||||
async createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO> {
|
async createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO> {
|
||||||
// 创建订单并返回统一订单DTO
|
// 创建订单并返回统一订单DTO
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
const res = await api.post('orders', data);
|
const res = await api.post('orders', data);
|
||||||
return this.mapPlatformToUnifiedOrder(res.data);
|
return this.mapPlatformToUnifiedOrder(res.data);
|
||||||
}
|
}
|
||||||
|
|
@ -476,7 +519,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
|
|
||||||
async deleteOrder(where: {id: string | number}): Promise<boolean> {
|
async deleteOrder(where: {id: string | number}): Promise<boolean> {
|
||||||
// 删除订单
|
// 删除订单
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
await api.delete(`orders/${where.id}`, { force: true });
|
await api.delete(`orders/${where.id}`, { force: true });
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -541,14 +584,14 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
|
|
||||||
async getOrderNotes(orderId: string | number): Promise<any[]> {
|
async getOrderNotes(orderId: string | number): Promise<any[]> {
|
||||||
// 获取订单备注列表
|
// 获取订单备注列表
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
const res = await api.get(`orders/${orderId}/notes`);
|
const res = await api.get(`orders/${orderId}/notes`);
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async createOrderNote(orderId: string | number, data: any): Promise<any> {
|
async createOrderNote(orderId: string | number, data: any): Promise<any> {
|
||||||
// 创建订单备注
|
// 创建订单备注
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
const res = await api.post(`orders/${orderId}/notes`, data);
|
const res = await api.post(`orders/${orderId}/notes`, data);
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
|
|
@ -559,7 +602,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
}): Promise<any> {
|
}): Promise<any> {
|
||||||
throw new Error('暂未实现');
|
throw new Error('暂未实现');
|
||||||
// 取消订单履行
|
// 取消订单履行
|
||||||
// const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
// const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
|
|
||||||
// try {
|
// try {
|
||||||
// // 将订单状态改回处理中
|
// // 将订单状态改回处理中
|
||||||
|
|
@ -582,15 +625,105 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 产品映射方法 ==========
|
// ========== 产品映射方法 ==========
|
||||||
mapUnifiedToPlatformProduct(data: Partial<UnifiedProductDTO>) {
|
mapUnifiedToPlatformProduct(data: Partial<UnifiedProductDTO>): Partial<WooProduct> {
|
||||||
return data;
|
// 将统一产品DTO映射为WooCommerce产品数据
|
||||||
|
// 基本字段映射
|
||||||
|
const mapped: Partial<WooProduct> = {
|
||||||
|
id: data.id as number,
|
||||||
|
name: data.name,
|
||||||
|
type: data.type,
|
||||||
|
status: data.status,
|
||||||
|
sku: data.sku,
|
||||||
|
regular_price: data.regular_price,
|
||||||
|
sale_price: data.sale_price,
|
||||||
|
price: data.price,
|
||||||
|
stock_status: data.stock_status as 'instock' | 'outofstock' | 'onbackorder',
|
||||||
|
stock_quantity: data.stock_quantity,
|
||||||
|
// 映射更多WooCommerce产品特有的字段
|
||||||
|
// featured: data.featured,
|
||||||
|
// catalog_visibility: data.catalog_visibility,
|
||||||
|
// date_on_sale_from: data.date_on_sale_from,
|
||||||
|
// date_on_sale_to: data.date_on_sale_to,
|
||||||
|
// virtual: data.virtual,
|
||||||
|
// downloadable: data.downloadable,
|
||||||
|
// description: data.description,
|
||||||
|
// short_description: data.short_description,
|
||||||
|
// slug: data.slug,
|
||||||
|
// manage_stock: data.manage_stock,
|
||||||
|
// backorders: data.backorders as 'no' | 'notify' | 'yes',
|
||||||
|
// sold_individually: data.sold_individually,
|
||||||
|
// weight: data.weight,
|
||||||
|
// dimensions: data.dimensions,
|
||||||
|
// shipping_class: data.shipping_class,
|
||||||
|
// tax_class: data.tax_class,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 映射图片数据
|
||||||
|
if (data.images && Array.isArray(data.images)) {
|
||||||
|
mapped.images = data.images.map(img => ({
|
||||||
|
id: img.id as number,
|
||||||
|
src: img.src,
|
||||||
|
name: img.name,
|
||||||
|
alt: img.alt,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
mapCreateProductParams(data: Partial<UnifiedProductDTO>) {
|
// 映射分类数据
|
||||||
return data;
|
if (data.categories && Array.isArray(data.categories)) {
|
||||||
|
mapped.categories = data.categories.map(cat => ({
|
||||||
|
// id: cat.id as number, //TODO
|
||||||
|
name: cat.name,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
mapUpdateProductParams(data: Partial<UnifiedProductDTO>) {
|
|
||||||
return data;
|
// 映射标签数据
|
||||||
|
// TODO tags 应该可以设置
|
||||||
|
// if (data.tags && Array.isArray(data.tags)) {
|
||||||
|
// mapped.tags = data.tags.map(tag => {
|
||||||
|
// return ({
|
||||||
|
// // id: tag.id as number,
|
||||||
|
// name: tag.name,
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 映射属性数据
|
||||||
|
if (data.attributes && Array.isArray(data.attributes)) {
|
||||||
|
mapped.attributes = data.attributes.map(attr => ({
|
||||||
|
// id 由于我们这个主要用来存,所以不映射 id
|
||||||
|
name: attr.name,
|
||||||
|
visible: attr.visible,
|
||||||
|
variation: attr.variation,
|
||||||
|
options: attr.options
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 映射变体数据(注意:WooCommerce API 中变体通常通过单独的端点处理)
|
||||||
|
// 这里只映射变体的基本信息,具体创建/更新变体需要额外处理
|
||||||
|
if (data.variations && Array.isArray(data.variations)) {
|
||||||
|
// 对于WooProduct类型,variations字段只存储变体ID
|
||||||
|
mapped.variations = data.variations.map(variation => variation.id as number);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 映射下载数据(如果产品是可下载的)
|
||||||
|
// if (data.downloads && Array.isArray(data.downloads)) {
|
||||||
|
// mapped.downloads = data.downloads.map(download => ({
|
||||||
|
// id: download.id as number,
|
||||||
|
// name: download.name,
|
||||||
|
// file: download.file,
|
||||||
|
// }));
|
||||||
|
// }
|
||||||
|
|
||||||
|
return mapped;
|
||||||
|
}
|
||||||
|
|
||||||
|
mapCreateProductParams(data: Partial<UnifiedProductDTO>):Partial<WooProduct> {
|
||||||
|
const {id,...mapped}= this.mapUnifiedToPlatformProduct(data);
|
||||||
|
// 创建不带 id
|
||||||
|
return mapped
|
||||||
|
}
|
||||||
|
mapUpdateProductParams(data: Partial<UnifiedProductDTO>): Partial<WooProduct> {
|
||||||
|
return this.mapUnifiedToPlatformProduct(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
mapProductSearchParams(params: UnifiedSearchParamsDTO): Partial<WooProductSearchParams> {
|
mapProductSearchParams(params: UnifiedSearchParamsDTO): Partial<WooProductSearchParams> {
|
||||||
|
|
@ -649,14 +782,14 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
return mapped;
|
return mapped;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapPlatformToUnifiedProduct(item: WooProduct): UnifiedProductDTO {
|
mapPlatformToUnifiedProduct(data: WooProduct): UnifiedProductDTO {
|
||||||
// 将 WooCommerce 产品数据映射为统一产品DTO
|
// 将 WooCommerce 产品数据映射为统一产品DTO
|
||||||
// 保留常用字段与时间信息以便前端统一展示
|
// 保留常用字段与时间信息以便前端统一展示
|
||||||
// https://woocommerce.github.io/woocommerce-rest-api-docs/?javascript#product-properties
|
// https://woocommerce.github.io/woocommerce-rest-api-docs/?javascript#product-properties
|
||||||
|
|
||||||
// 映射变体数据
|
// 映射变体数据
|
||||||
const mappedVariations = item.variations && Array.isArray(item.variations)
|
const mappedVariations = data.variations && Array.isArray(data.variations)
|
||||||
? item.variations
|
? data.variations
|
||||||
.filter((variation: any) => typeof variation !== 'number') // 过滤掉数字类型的变体ID
|
.filter((variation: any) => typeof variation !== 'number') // 过滤掉数字类型的变体ID
|
||||||
.map((variation: any) => {
|
.map((variation: any) => {
|
||||||
// 将变体属性转换为统一格式
|
// 将变体属性转换为统一格式
|
||||||
|
|
@ -683,7 +816,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: variation.id,
|
id: variation.id,
|
||||||
name: variation.name || item.name, // 如果变体没有名称,使用父产品名称
|
name: variation.name || data.name, // 如果变体没有名称,使用父产品名称
|
||||||
sku: variation.sku || '',
|
sku: variation.sku || '',
|
||||||
regular_price: String(variation.regular_price || ''),
|
regular_price: String(variation.regular_price || ''),
|
||||||
sale_price: String(variation.sale_price || ''),
|
sale_price: String(variation.sale_price || ''),
|
||||||
|
|
@ -697,34 +830,34 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: data.id,
|
||||||
date_created: item.date_created,
|
date_created: data.date_created,
|
||||||
date_modified: item.date_modified,
|
date_modified: data.date_modified,
|
||||||
type: item.type, // simple grouped external variable
|
type: data.type, // simple grouped external variable
|
||||||
status: item.status, // draft pending private publish
|
status: data.status, // draft pending private publish
|
||||||
sku: item.sku,
|
sku: data.sku,
|
||||||
name: item.name,
|
name: data.name,
|
||||||
//价格
|
//价格
|
||||||
regular_price: item.regular_price,
|
regular_price: data.regular_price,
|
||||||
sale_price: item.sale_price,
|
sale_price: data.sale_price,
|
||||||
price: item.price,
|
price: data.price,
|
||||||
stock_status: item.stock_status,
|
stock_status: data.stock_status,
|
||||||
stock_quantity: item.stock_quantity,
|
stock_quantity: data.stock_quantity,
|
||||||
images: (item.images || []).map((img: any) => ({
|
images: (data.images || []).map((img: any) => ({
|
||||||
id: img.id,
|
id: img.id,
|
||||||
src: img.src,
|
src: img.src,
|
||||||
name: img.name,
|
name: img.name,
|
||||||
alt: img.alt,
|
alt: img.alt,
|
||||||
})),
|
})),
|
||||||
categories: (item.categories || []).map((c: any) => ({
|
categories: (data.categories || []).map((c: any) => ({
|
||||||
id: c.id,
|
id: c.id,
|
||||||
name: c.name,
|
name: c.name,
|
||||||
})),
|
})),
|
||||||
tags: (item.tags || []).map((t: any) => ({
|
tags: (data.tags || []).map((t: any) => ({
|
||||||
id: t.id,
|
id: t.id,
|
||||||
name: t.name,
|
name: t.name,
|
||||||
})),
|
})),
|
||||||
attributes: (item.attributes || []).map(attr => ({
|
attributes: (data.attributes || []).map(attr => ({
|
||||||
id: attr.id,
|
id: attr.id,
|
||||||
name: attr.name || '',
|
name: attr.name || '',
|
||||||
position: attr.position,
|
position: attr.position,
|
||||||
|
|
@ -733,25 +866,39 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
options: attr.options || []
|
options: attr.options || []
|
||||||
})),
|
})),
|
||||||
variations: mappedVariations,
|
variations: mappedVariations,
|
||||||
permalink: item.permalink,
|
permalink: data.permalink,
|
||||||
raw: item,
|
raw: data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
async getProduct({id, sku}: {id?: string, sku?:string}){
|
// 判断是否是这个站点的sku
|
||||||
|
isSiteSkuThisSite(sku: string,){
|
||||||
|
return sku.startsWith(this.site.skuPrefix+'-');
|
||||||
|
}
|
||||||
|
async getProduct(where: Partial<Pick<UnifiedProductDTO, 'id' | 'sku'>>): Promise<UnifiedProductDTO>{
|
||||||
|
const { id, sku } = where;
|
||||||
if(id) return this.getProductById(id);
|
if(id) return this.getProductById(id);
|
||||||
if(sku) return this.getProductBySku(sku)
|
if(sku) return this.getProductBySku(sku);
|
||||||
return this.getProductById(id || sku || '');
|
throw new Error('必须提供id或sku参数');
|
||||||
}
|
}
|
||||||
async getProductBySku(sku: string){
|
async getProductBySku(sku: string){
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
// const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
const res = await api.get(`products?sku=${sku}`);
|
// const res = await api.get(`products`,{
|
||||||
const product = res.data[0];
|
// sku
|
||||||
|
// });
|
||||||
|
// const product = res.data[0];
|
||||||
|
const res = await this.wpService.getProducts(this.site,{
|
||||||
|
sku,
|
||||||
|
page:1,
|
||||||
|
per_page:1,
|
||||||
|
});
|
||||||
|
const product = res?.items?.[0];
|
||||||
|
if(!product) return null
|
||||||
return this.mapPlatformToUnifiedProduct(product);
|
return this.mapPlatformToUnifiedProduct(product);
|
||||||
}
|
}
|
||||||
// 产品操作方法
|
// 产品操作方法
|
||||||
async getProductById(id: string | number): Promise<UnifiedProductDTO> {
|
async getProductById(id: string | number): Promise<UnifiedProductDTO> {
|
||||||
// 获取单个产品详情并映射为统一产品DTO
|
// 获取单个产品详情并映射为统一产品DTO
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
|
|
||||||
const res = await api.get(`products/${id}`);
|
const res = await api.get(`products/${id}`);
|
||||||
const product = res.data;
|
const product = res.data;
|
||||||
|
|
@ -792,7 +939,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
try {
|
try {
|
||||||
// 批量获取该产品的所有变体数据
|
// 批量获取该产品的所有变体数据
|
||||||
const variations = await this.wpService.sdkGetAll(
|
const variations = await this.wpService.sdkGetAll(
|
||||||
(this.wpService as any).createApi(this.site, 'wc/v3'),
|
this.wpService.createApi(this.site, 'wc/v3'),
|
||||||
`products/${item.id}/variations`
|
`products/${item.id}/variations`
|
||||||
);
|
);
|
||||||
// 将完整的变体数据添加到产品对象中
|
// 将完整的变体数据添加到产品对象中
|
||||||
|
|
@ -818,7 +965,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
|
|
||||||
async getAllProducts(params?: UnifiedSearchParamsDTO): Promise<UnifiedProductDTO[]> {
|
async getAllProducts(params?: UnifiedSearchParamsDTO): Promise<UnifiedProductDTO[]> {
|
||||||
// 使用sdkGetAll获取所有产品数据,不受分页限制
|
// 使用sdkGetAll获取所有产品数据,不受分页限制
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
const products = await this.wpService.sdkGetAll(api, 'products', params);
|
const products = await this.wpService.sdkGetAll(api, 'products', params);
|
||||||
|
|
||||||
// 对于类型为 variable 的产品,需要加载完整的变体数据
|
// 对于类型为 variable 的产品,需要加载完整的变体数据
|
||||||
|
|
@ -848,41 +995,31 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
|
|
||||||
async createProduct(data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO> {
|
async createProduct(data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO> {
|
||||||
// 创建产品并返回统一产品DTO
|
// 创建产品并返回统一产品DTO
|
||||||
const res = await this.wpService.createProduct(this.site, data);
|
const createData = this.mapCreateProductParams(data);
|
||||||
|
const res = await this.wpService.createProduct(this.site, createData);
|
||||||
return this.mapPlatformToUnifiedProduct(res);
|
return this.mapPlatformToUnifiedProduct(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateProduct(where: {id?: string | number, sku?: string}, data: Partial<UnifiedProductDTO>): Promise<boolean> {
|
async updateProduct(where: Partial<Pick<UnifiedProductDTO, 'id' | 'sku'>>, data: Partial<UnifiedProductDTO>): Promise<boolean> {
|
||||||
// 更新产品并返回统一产品DTO
|
// 更新产品并返回统一产品DTO
|
||||||
let productId: string;
|
const product = await this.getProduct(where);
|
||||||
if (where.id) {
|
if(!product){
|
||||||
productId = String(where.id);
|
throw new Error('产品不存在');
|
||||||
} else if (where.sku) {
|
|
||||||
// 通过sku获取产品ID
|
|
||||||
const product = await this.getProductBySku(where.sku);
|
|
||||||
productId = String(product.id);
|
|
||||||
} else {
|
|
||||||
throw new Error('必须提供id或sku参数');
|
|
||||||
}
|
}
|
||||||
const res = await this.wpService.updateProduct(this.site, productId, data as any);
|
const updateData = this.mapUpdateProductParams(data);
|
||||||
|
const res = await this.wpService.updateProduct(this.site, String(product.id), updateData as any);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteProduct(where: {id?: string | number, sku?: string}): Promise<boolean> {
|
async deleteProduct(where: Partial<Pick<UnifiedProductDTO, 'id' | 'sku'>>): Promise<boolean> {
|
||||||
// 删除产品
|
// 删除产品
|
||||||
let productId: string;
|
const product = await this.getProduct(where);
|
||||||
if (where.id) {
|
if(!product){
|
||||||
productId = String(where.id);
|
throw new Error('产品不存在');
|
||||||
} else if (where.sku) {
|
|
||||||
// 通过sku获取产品ID
|
|
||||||
const product = await this.getProductBySku(where.sku);
|
|
||||||
productId = String(product.id);
|
|
||||||
} else {
|
|
||||||
throw new Error('必须提供id或sku参数');
|
|
||||||
}
|
}
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
try {
|
try {
|
||||||
await api.delete(`products/${productId}`, { force: true });
|
await api.delete(`products/${product.id}`, { force: true });
|
||||||
return true;
|
return true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -902,7 +1039,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
mapPlatformToUnifiedReview(item: any): UnifiedReviewDTO & { raw: any } {
|
mapPlatformToUnifiedReview(item: any): UnifiedReviewDTO {
|
||||||
// 将 WooCommerce 评论数据映射为统一评论DTO
|
// 将 WooCommerce 评论数据映射为统一评论DTO
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
|
|
@ -913,7 +1050,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
rating: item.rating,
|
rating: item.rating,
|
||||||
status: item.status,
|
status: item.status,
|
||||||
date_created: item.date_created,
|
date_created: item.date_created,
|
||||||
raw: item
|
date_modified: item.date_modified,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -937,23 +1074,31 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
|
|
||||||
async getAllReviews(params?: UnifiedSearchParamsDTO): Promise<UnifiedReviewDTO[]> {
|
async getAllReviews(params?: UnifiedSearchParamsDTO): Promise<UnifiedReviewDTO[]> {
|
||||||
// 使用sdkGetAll获取所有评论数据,不受分页限制
|
// 使用sdkGetAll获取所有评论数据,不受分页限制
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
const reviews = await this.wpService.sdkGetAll(api, 'products/reviews', params);
|
const reviews = await this.wpService.sdkGetAll(api, 'products/reviews', params);
|
||||||
return reviews.map((review: any) => this.mapPlatformToUnifiedReview(review));
|
return reviews.map((review: any) => this.mapPlatformToUnifiedReview(review));
|
||||||
}
|
}
|
||||||
|
|
||||||
async createReview(data: any): Promise<UnifiedReviewDTO> {
|
async createReview(data: CreateReviewDTO): Promise<UnifiedReviewDTO> {
|
||||||
const res = await this.wpService.createReview(this.site, data);
|
const res = await this.wpService.createReview(this.site, data);
|
||||||
return this.mapPlatformToUnifiedReview(res);
|
return this.mapPlatformToUnifiedReview(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateReview(where: {id: number}, data: any): Promise<UnifiedReviewDTO> {
|
async updateReview(where: Partial<Pick<UnifiedReviewDTO, 'id'>>, data: UpdateReviewDTO): Promise<UnifiedReviewDTO> {
|
||||||
const res = await this.wpService.updateReview(this.site, where.id, data);
|
const { id } = where;
|
||||||
|
if (!id) {
|
||||||
|
throw new Error('必须提供评论ID');
|
||||||
|
}
|
||||||
|
const res = await this.wpService.updateReview(this.site, Number(id), data);
|
||||||
return this.mapPlatformToUnifiedReview(res);
|
return this.mapPlatformToUnifiedReview(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteReview(where: {id: number}): Promise<boolean> {
|
async deleteReview(where: Partial<Pick<UnifiedReviewDTO, 'id'>>): Promise<boolean> {
|
||||||
return await this.wpService.deleteReview(this.site, where.id);
|
const { id } = where;
|
||||||
|
if (!id) {
|
||||||
|
throw new Error('必须提供评论ID');
|
||||||
|
}
|
||||||
|
return await this.wpService.deleteReview(this.site, Number(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 订阅映射方法 ==========
|
// ========== 订阅映射方法 ==========
|
||||||
|
|
@ -999,14 +1144,15 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
|
|
||||||
async getAllSubscriptions(params?: UnifiedSearchParamsDTO): Promise<UnifiedSubscriptionDTO[]> {
|
async getAllSubscriptions(params?: UnifiedSearchParamsDTO): Promise<UnifiedSubscriptionDTO[]> {
|
||||||
// 使用sdkGetAll获取所有订阅数据,不受分页限制
|
// 使用sdkGetAll获取所有订阅数据,不受分页限制
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
const subscriptions = await this.wpService.sdkGetAll(api, 'subscriptions', params);
|
const subscriptions = await this.wpService.sdkGetAll(api, 'subscriptions', params);
|
||||||
return subscriptions.map((subscription: any) => this.mapPlatformToUnifiedSubscription(subscription));
|
return subscriptions.map((subscription: any) => this.mapPlatformToUnifiedSubscription(subscription));
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 变体映射方法 ==========
|
// ========== 变体映射方法 ==========
|
||||||
mapPlatformToUnifiedVariation(data: any): UnifiedProductVariationDTO {
|
mapPlatformToUnifiedVariation(data: any): UnifiedProductVariationDTO {
|
||||||
return data;
|
// 使用mapVariation方法来实现统一的变体映射逻辑
|
||||||
|
return this.mapVariation(data);
|
||||||
}
|
}
|
||||||
mapUnifiedToPlatformVariation(data: Partial<UnifiedProductVariationDTO>) {
|
mapUnifiedToPlatformVariation(data: Partial<UnifiedProductVariationDTO>) {
|
||||||
return data;
|
return data;
|
||||||
|
|
@ -1089,7 +1235,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
// 获取所有产品变体
|
// 获取所有产品变体
|
||||||
async getAllVariations(productId: string | number, params?: UnifiedSearchParamsDTO): Promise<UnifiedProductVariationDTO[]> {
|
async getAllVariations(productId: string | number, params?: UnifiedSearchParamsDTO): Promise<UnifiedProductVariationDTO[]> {
|
||||||
try {
|
try {
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
const variations = await this.wpService.sdkGetAll(api, `products/${productId}/variations`, params);
|
const variations = await this.wpService.sdkGetAll(api, `products/${productId}/variations`, params);
|
||||||
|
|
||||||
// 获取产品名称用于变体显示
|
// 获取产品名称用于变体显示
|
||||||
|
|
@ -1287,7 +1433,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
// 获取所有webhooks
|
// 获取所有webhooks
|
||||||
async getAllWebhooks(params?: UnifiedSearchParamsDTO): Promise<UnifiedWebhookDTO[]> {
|
async getAllWebhooks(params?: UnifiedSearchParamsDTO): Promise<UnifiedWebhookDTO[]> {
|
||||||
try {
|
try {
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = this.wpService.createApi(this.site, 'wc/v3');
|
||||||
const webhooks = await this.wpService.sdkGetAll(api, 'webhooks', params);
|
const webhooks = await this.wpService.sdkGetAll(api, 'webhooks', params);
|
||||||
return webhooks.map((webhook: any) => this.mapPlatformToUnifiedWebhook(webhook));
|
return webhooks.map((webhook: any) => this.mapPlatformToUnifiedWebhook(webhook));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -1368,11 +1514,11 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
return links;
|
return links;
|
||||||
}
|
}
|
||||||
|
|
||||||
batchProcessOrders?(data: { create?: any[]; update?: any[]; delete?: Array<string | number>; }): Promise<any> {
|
batchProcessOrders?(data: BatchOperationDTO): Promise<BatchOperationResultDTO> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
|
||||||
batchProcessCustomers?(data: { create?: any[]; update?: any[]; delete?: Array<string | number>; }): Promise<any> {
|
batchProcessCustomers?(data: BatchOperationDTO): Promise<BatchOperationResultDTO> {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -698,10 +698,10 @@ export class ProductController {
|
||||||
// 从站点同步产品到本地
|
// 从站点同步产品到本地
|
||||||
@ApiOkResponse({ description: '从站点同步产品到本地', type: ProductRes })
|
@ApiOkResponse({ description: '从站点同步产品到本地', type: ProductRes })
|
||||||
@Post('/sync-from-site')
|
@Post('/sync-from-site')
|
||||||
async syncProductFromSite(@Body() body: { siteId: number; siteProductId: string | number }) {
|
async syncProductFromSite(@Body() body: { siteId: number; siteProductId: string | number ,sku: string}) {
|
||||||
try {
|
try {
|
||||||
const { siteId, siteProductId } = body;
|
const { siteId, siteProductId, sku } = body;
|
||||||
const product = await this.productService.syncProductFromSite(siteId, siteProductId);
|
const product = await this.productService.syncProductFromSite(siteId, siteProductId, sku);
|
||||||
return successResponse(product);
|
return successResponse(product);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
|
|
@ -713,25 +713,26 @@ export class ProductController {
|
||||||
@Post('/batch-sync-from-site')
|
@Post('/batch-sync-from-site')
|
||||||
async batchSyncFromSite(@Body() body: { siteId: number; siteProductIds: (string | number)[] }) {
|
async batchSyncFromSite(@Body() body: { siteId: number; siteProductIds: (string | number)[] }) {
|
||||||
try {
|
try {
|
||||||
const { siteId, siteProductIds } = body;
|
throw new Error('批量同步产品到本地暂未实现');
|
||||||
const result = await this.productService.batchSyncFromSite(siteId, siteProductIds);
|
// const { siteId, siteProductIds } = body;
|
||||||
// 将服务层返回的结果转换为统一格式
|
// const result = await this.productService.batchSyncFromSite(siteId, siteProductIds.map((id) => ({ siteProductId: id, sku: '' })));
|
||||||
const errors = result.errors.map((error: string) => {
|
// // 将服务层返回的结果转换为统一格式
|
||||||
// 提取产品ID部分作为标识符
|
// const errors = result.errors.map((error: string) => {
|
||||||
const match = error.match(/站点产品ID (\d+) /);
|
// // 提取产品ID部分作为标识符
|
||||||
const identifier = match ? match[1] : 'unknown';
|
// const match = error.match(/站点产品ID (\d+) /);
|
||||||
return {
|
// const identifier = match ? match[1] : 'unknown';
|
||||||
identifier: identifier,
|
// return {
|
||||||
error: error
|
// identifier: identifier,
|
||||||
};
|
// error: error
|
||||||
});
|
// };
|
||||||
|
// });
|
||||||
|
|
||||||
return successResponse({
|
// return successResponse({
|
||||||
total: siteProductIds.length,
|
// total: siteProductIds.length,
|
||||||
processed: result.synced + errors.length,
|
// processed: result.synced + errors.length,
|
||||||
synced: result.synced,
|
// synced: result.synced,
|
||||||
errors: errors
|
// errors: errors
|
||||||
});
|
// });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error?.message || error);
|
return errorResponse(error?.message || error);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,19 +23,38 @@ export default class TemplateSeeder implements Seeder {
|
||||||
const templates = [
|
const templates = [
|
||||||
{
|
{
|
||||||
name: 'product.sku',
|
name: 'product.sku',
|
||||||
value: "<%= [it.category.shortName].concat(it.attributes.map(a => a.shortName)).join('-') %>",
|
value: `<%
|
||||||
|
// 按分类判断属性排序逻辑
|
||||||
|
if (it.category.name === 'nicotine-pouches') {
|
||||||
|
// 1. 定义 nicotine-pouches 专属的属性固定顺序
|
||||||
|
const fixedOrder = ['brand','category', 'flavor', 'strength', 'humidity'];
|
||||||
|
sortedAttrShortNames = fixedOrder.map(attrKey => {
|
||||||
|
if(attrKey === 'category') return it.category.shortName
|
||||||
|
// 排序
|
||||||
|
const matchedAttr = it.attributes.find(a => a?.dict?.name === attrKey);
|
||||||
|
return matchedAttr ? matchedAttr.shortName : '';
|
||||||
|
}).filter(Boolean); // 移除空值,避免多余的 "-"
|
||||||
|
} else {
|
||||||
|
// 非目标分类,保留 attributes 原有顺序
|
||||||
|
sortedAttrShortNames = it.attributes.map(a => a.shortName);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 拼接分类名 + 排序后的属性名
|
||||||
|
%><%= sortedAttrShortNames.join('-') %><%
|
||||||
|
%>`,
|
||||||
description: '产品SKU模板',
|
description: '产品SKU模板',
|
||||||
testData: JSON.stringify({
|
testData: JSON.stringify({
|
||||||
category: {
|
"category": {
|
||||||
shortName: 'CAT',
|
"name": "nicotine-pouches",
|
||||||
|
"shortName": "NP"
|
||||||
},
|
},
|
||||||
attributes: [
|
"attributes": [
|
||||||
{ shortName: 'BR' },
|
{ "dict": {"name": "brand"},"shortName": "YOONE" },
|
||||||
{ shortName: 'FL' },
|
{ "dict": {"name": "flavor"},"shortName": "FL" },
|
||||||
{ shortName: '10MG' },
|
{ "dict": {"name": "strength"},"shortName": "10MG" },
|
||||||
{ shortName: 'DRY' },
|
{ "dict": {"name": "humidity"},"shortName": "DRY" }
|
||||||
],
|
]
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'product.title',
|
name: 'product.title',
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import { ApiProperty } from '@midwayjs/swagger';
|
||||||
import {
|
import {
|
||||||
UnifiedPaginationDTO,
|
UnifiedPaginationDTO,
|
||||||
} from './api.dto';
|
} from './api.dto';
|
||||||
|
import { Dict } from '../entity/dict.entity';
|
||||||
// export class UnifiedOrderWhere{
|
// export class UnifiedOrderWhere{
|
||||||
// []
|
// []
|
||||||
// }
|
// }
|
||||||
|
|
@ -137,6 +138,8 @@ export class UnifiedProductAttributeDTO {
|
||||||
|
|
||||||
@ApiProperty({ description: '变体属性值(单个值)', required: false })
|
@ApiProperty({ description: '变体属性值(单个值)', required: false })
|
||||||
option?: string;
|
option?: string;
|
||||||
|
// 这个是属性的父级字典项
|
||||||
|
dict?: Dict;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedProductVariationDTO {
|
export class UnifiedProductVariationDTO {
|
||||||
|
|
|
||||||
|
|
@ -117,9 +117,9 @@ export interface WooProduct {
|
||||||
// 购买备注
|
// 购买备注
|
||||||
purchase_note?: string;
|
purchase_note?: string;
|
||||||
// 分类列表
|
// 分类列表
|
||||||
categories?: Array<{ id: number; name?: string; slug?: string }>;
|
categories?: Array<{ id?: number; name?: string; slug?: string }>;
|
||||||
// 标签列表
|
// 标签列表
|
||||||
tags?: Array<{ id: number; name?: string; slug?: string }>;
|
tags?: Array<{ id?: number; name?: string; slug?: string }>;
|
||||||
// 菜单排序
|
// 菜单排序
|
||||||
menu_order?: number;
|
menu_order?: number;
|
||||||
// 元数据
|
// 元数据
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,10 @@ export class Dict {
|
||||||
@OneToMany(() => DictItem, item => item.dict)
|
@OneToMany(() => DictItem, item => item.dict)
|
||||||
items: DictItem[];
|
items: DictItem[];
|
||||||
|
|
||||||
|
// 排序
|
||||||
|
@Column({ default: 0, comment: '排序' })
|
||||||
|
sort: number;
|
||||||
|
|
||||||
// 是否可删除
|
// 是否可删除
|
||||||
@Column({ default: true, comment: '是否可删除' })
|
@Column({ default: true, comment: '是否可删除' })
|
||||||
deletable: boolean;
|
deletable: boolean;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ import { StockPoint } from '../entity/stock_point.entity';
|
||||||
import { StockService } from './stock.service';
|
import { StockService } from './stock.service';
|
||||||
import { TemplateService } from './template.service';
|
import { TemplateService } from './template.service';
|
||||||
|
|
||||||
import { SyncOperationResultDTO, UnifiedSearchParamsDTO } from '../dto/api.dto';
|
import { BatchErrorItem, BatchOperationResult, SyncOperationResultDTO, UnifiedSearchParamsDTO } from '../dto/api.dto';
|
||||||
import { UnifiedProductDTO } from '../dto/site-api.dto';
|
import { UnifiedProductDTO } from '../dto/site-api.dto';
|
||||||
import { ProductSiteSkuDTO, SyncProductToSiteDTO } from '../dto/site-sync.dto';
|
import { ProductSiteSkuDTO, SyncProductToSiteDTO } from '../dto/site-sync.dto';
|
||||||
import { Category } from '../entity/category.entity';
|
import { Category } from '../entity/category.entity';
|
||||||
|
|
@ -225,7 +225,7 @@ export class ProductService {
|
||||||
where: {
|
where: {
|
||||||
sku,
|
sku,
|
||||||
},
|
},
|
||||||
relations: ['category', 'attributes', 'attributes.dict', 'siteSkus']
|
relations: ['category', 'attributes', 'attributes.dict']
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1455,6 +1455,9 @@ export class ProductService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理分类字段
|
||||||
|
const category = val(rec.category);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
sku,
|
sku,
|
||||||
name: val(rec.name),
|
name: val(rec.name),
|
||||||
|
|
@ -1464,6 +1467,7 @@ export class ProductService {
|
||||||
promotionPrice: num(rec.promotionPrice),
|
promotionPrice: num(rec.promotionPrice),
|
||||||
type: val(rec.type),
|
type: val(rec.type),
|
||||||
siteSkus: rec.siteSkus ? String(rec.siteSkus).split(',').map(s => s.trim()).filter(Boolean) : undefined,
|
siteSkus: rec.siteSkus ? String(rec.siteSkus).split(',').map(s => s.trim()).filter(Boolean) : undefined,
|
||||||
|
category, // 添加分类字段
|
||||||
|
|
||||||
attributes: attributes.length > 0 ? attributes : undefined,
|
attributes: attributes.length > 0 ? attributes : undefined,
|
||||||
} as any;
|
} as any;
|
||||||
|
|
@ -1483,10 +1487,15 @@ export class ProductService {
|
||||||
if (data.price !== undefined) dto.price = Number(data.price);
|
if (data.price !== undefined) dto.price = Number(data.price);
|
||||||
if (data.promotionPrice !== undefined) dto.promotionPrice = Number(data.promotionPrice);
|
if (data.promotionPrice !== undefined) dto.promotionPrice = Number(data.promotionPrice);
|
||||||
|
|
||||||
if (data.categoryId !== undefined) dto.categoryId = Number(data.categoryId);
|
// 处理分类字段
|
||||||
|
if (data.categoryId !== undefined) {
|
||||||
|
dto.categoryId = Number(data.categoryId);
|
||||||
|
} else if (data.category) {
|
||||||
|
// 如果是字符串,需要后续在createProduct中处理
|
||||||
|
dto.attributes = [...(dto.attributes || []), { dictName: 'category', title: data.category }];
|
||||||
|
}
|
||||||
|
|
||||||
// 默认值和特殊处理
|
// 默认值和特殊处理
|
||||||
|
|
||||||
dto.attributes = Array.isArray(data.attributes) ? data.attributes : [];
|
dto.attributes = Array.isArray(data.attributes) ? data.attributes : [];
|
||||||
|
|
||||||
// 如果有组件信息,透传
|
// 如果有组件信息,透传
|
||||||
|
|
@ -1508,7 +1517,13 @@ export class ProductService {
|
||||||
if (data.price !== undefined) dto.price = Number(data.price);
|
if (data.price !== undefined) dto.price = Number(data.price);
|
||||||
if (data.promotionPrice !== undefined) dto.promotionPrice = Number(data.promotionPrice);
|
if (data.promotionPrice !== undefined) dto.promotionPrice = Number(data.promotionPrice);
|
||||||
|
|
||||||
if (data.categoryId !== undefined) dto.categoryId = Number(data.categoryId);
|
// 处理分类字段
|
||||||
|
if (data.categoryId !== undefined) {
|
||||||
|
dto.categoryId = Number(data.categoryId);
|
||||||
|
} else if (data.category) {
|
||||||
|
// 如果是字符串,需要后续在updateProduct中处理
|
||||||
|
dto.attributes = [...(dto.attributes || []), { dictName: 'category', title: data.category }];
|
||||||
|
}
|
||||||
|
|
||||||
if (data.type !== undefined) dto.type = data.type;
|
if (data.type !== undefined) dto.type = data.type;
|
||||||
if (data.attributes !== undefined) dto.attributes = data.attributes;
|
if (data.attributes !== undefined) dto.attributes = data.attributes;
|
||||||
|
|
@ -1548,8 +1563,8 @@ export class ProductService {
|
||||||
esc(p.price),
|
esc(p.price),
|
||||||
esc(p.promotionPrice),
|
esc(p.promotionPrice),
|
||||||
esc(p.type),
|
esc(p.type),
|
||||||
|
|
||||||
esc(p.description),
|
esc(p.description),
|
||||||
|
esc(p.category ? p.category.name || p.category.title : ''), // 添加分类字段
|
||||||
];
|
];
|
||||||
|
|
||||||
// 属性数据
|
// 属性数据
|
||||||
|
|
@ -1575,9 +1590,9 @@ export class ProductService {
|
||||||
|
|
||||||
// 导出所有产品为 CSV 文本
|
// 导出所有产品为 CSV 文本
|
||||||
async exportProductsCSV(): Promise<string> {
|
async exportProductsCSV(): Promise<string> {
|
||||||
// 查询所有产品及其属性(包含字典关系)和组成
|
// 查询所有产品及其属性(包含字典关系)、组成和分类
|
||||||
const products = await this.productModel.find({
|
const products = await this.productModel.find({
|
||||||
relations: ['attributes', 'attributes.dict', 'components'],
|
relations: ['attributes', 'attributes.dict', 'components', 'category'],
|
||||||
order: { id: 'ASC' },
|
order: { id: 'ASC' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -1612,8 +1627,8 @@ export class ProductService {
|
||||||
'price',
|
'price',
|
||||||
'promotionPrice',
|
'promotionPrice',
|
||||||
'type',
|
'type',
|
||||||
|
|
||||||
'description',
|
'description',
|
||||||
|
'category',
|
||||||
];
|
];
|
||||||
|
|
||||||
// 动态属性表头
|
// 动态属性表头
|
||||||
|
|
@ -1640,7 +1655,7 @@ export class ProductService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从 CSV 导入产品;存在则更新,不存在则创建
|
// 从 CSV 导入产品;存在则更新,不存在则创建
|
||||||
async importProductsCSV(file: any): Promise<{ created: number; updated: number; errors: string[] }> {
|
async importProductsCSV(file: any): Promise<BatchOperationResult> {
|
||||||
let buffer: Buffer;
|
let buffer: Buffer;
|
||||||
if (Buffer.isBuffer(file)) {
|
if (Buffer.isBuffer(file)) {
|
||||||
buffer = file;
|
buffer = file;
|
||||||
|
|
@ -1676,19 +1691,19 @@ export class ProductService {
|
||||||
console.log('First record keys:', Object.keys(records[0]));
|
console.log('First record keys:', Object.keys(records[0]));
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
return { created: 0, updated: 0, errors: [`CSV 解析失败:${e?.message || e}`] };
|
throw new Error(`CSV 解析失败:${e?.message || e}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
let created = 0;
|
let created = 0;
|
||||||
let updated = 0;
|
let updated = 0;
|
||||||
const errors: string[] = [];
|
const errors: BatchErrorItem[] = [];
|
||||||
|
|
||||||
// 逐条处理记录
|
// 逐条处理记录
|
||||||
for (const rec of records) {
|
for (const rec of records) {
|
||||||
try {
|
try {
|
||||||
const data = this.transformCsvRecordToData(rec);
|
const data = this.transformCsvRecordToData(rec);
|
||||||
if (!data) {
|
if (!data) {
|
||||||
errors.push('缺少 SKU 的记录已跳过');
|
errors.push({ identifier: data.sku, error: '缺少 SKU 的记录已跳过'});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const { sku } = data;
|
const { sku } = data;
|
||||||
|
|
@ -1708,11 +1723,11 @@ export class ProductService {
|
||||||
updated += 1;
|
updated += 1;
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
errors.push(`产品${rec?.sku}导入失败:${e?.message || String(e)}`);
|
errors.push({ identifier: '' + rec.sku, error: `产品${rec?.sku}导入失败:${e?.message || String(e)}`});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { created, updated, errors };
|
return { total: records.length, processed: records.length - errors.length, created, updated, errors };
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将库存记录的 sku 添加到产品单品中
|
// 将库存记录的 sku 添加到产品单品中
|
||||||
|
|
@ -1831,9 +1846,7 @@ export class ProductService {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将本地产品转换为站点API所需格式
|
// 将本地产品转换为站点API所需格式
|
||||||
const unifiedProduct = await this.convertLocalProductToUnifiedProduct(localProduct, params.siteSku);
|
const unifiedProduct = await this.mapLocalToUnifiedProduct(localProduct, params.siteSku);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 调用站点API的upsertProduct方法
|
// 调用站点API的upsertProduct方法
|
||||||
try {
|
try {
|
||||||
|
|
@ -1842,7 +1855,7 @@ export class ProductService {
|
||||||
await this.bindSiteSkus(localProduct.id, [unifiedProduct.sku]);
|
await this.bindSiteSkus(localProduct.id, [unifiedProduct.sku]);
|
||||||
return result;
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error(`同步产品到站点失败: ${error.message}`);
|
throw new Error(`同步产品到站点失败: ${error?.response?.data?.message??error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1869,9 +1882,6 @@ export class ProductService {
|
||||||
siteSku: item.siteSku
|
siteSku: item.siteSku
|
||||||
});
|
});
|
||||||
|
|
||||||
// 然后绑定站点SKU
|
|
||||||
await this.bindSiteSkus(item.productId, [item.siteSku]);
|
|
||||||
|
|
||||||
results.synced++;
|
results.synced++;
|
||||||
results.processed++;
|
results.processed++;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -1892,30 +1902,23 @@ export class ProductService {
|
||||||
* @param siteProductId 站点产品ID
|
* @param siteProductId 站点产品ID
|
||||||
* @returns 同步后的本地产品
|
* @returns 同步后的本地产品
|
||||||
*/
|
*/
|
||||||
async syncProductFromSite(siteId: number, siteProductId: string | number): Promise<any> {
|
async syncProductFromSite(siteId: number, siteProductId: string | number, sku: string): Promise<any> {
|
||||||
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
|
const siteProduct = await adapter.getProduct({ id: siteProductId });
|
||||||
// 从站点获取产品信息
|
// 从站点获取产品信息
|
||||||
const siteProduct = await this.siteApiService.getProductFromSite(siteId, siteProductId);
|
|
||||||
if (!siteProduct) {
|
if (!siteProduct) {
|
||||||
throw new Error(`站点产品 ID ${siteProductId} 不存在`);
|
throw new Error(`站点产品 ID ${siteProductId} 不存在`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否已存在相同SKU的本地产品
|
|
||||||
let localProduct = null;
|
|
||||||
if (siteProduct.sku) {
|
|
||||||
try {
|
|
||||||
localProduct = await this.findProductBySku(siteProduct.sku);
|
|
||||||
} catch (error) {
|
|
||||||
// 产品不存在,继续创建
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将站点产品转换为本地产品格式
|
// 将站点产品转换为本地产品格式
|
||||||
const productData = await this.convertSiteProductToLocalProduct(siteProduct);
|
const productData = await this.mapUnifiedToLocalProduct(siteProduct);
|
||||||
|
return await this.upsertProduct({sku}, productData);
|
||||||
if (localProduct) {
|
}
|
||||||
|
async upsertProduct(where: Partial<Pick<Product,'id'| 'sku'>>, productData: any) {
|
||||||
|
const existingProduct = await this.productModel.findOne({ where: where});
|
||||||
|
if (existingProduct) {
|
||||||
// 更新现有产品
|
// 更新现有产品
|
||||||
const updateData: UpdateProductDTO = productData;
|
const updateData: UpdateProductDTO = productData;
|
||||||
return await this.updateProduct(localProduct.id, updateData);
|
return await this.updateProduct(existingProduct.id, updateData);
|
||||||
} else {
|
} else {
|
||||||
// 创建新产品
|
// 创建新产品
|
||||||
const createData: CreateProductDTO = productData;
|
const createData: CreateProductDTO = productData;
|
||||||
|
|
@ -1929,18 +1932,18 @@ export class ProductService {
|
||||||
* @param siteProductIds 站点产品ID数组
|
* @param siteProductIds 站点产品ID数组
|
||||||
* @returns 批量同步结果
|
* @returns 批量同步结果
|
||||||
*/
|
*/
|
||||||
async batchSyncFromSite(siteId: number, siteProductIds: (string | number)[]): Promise<{ synced: number, errors: string[] }> {
|
async batchSyncFromSite(siteId: number, data: Array<{siteProductId:string, sku: string}>): Promise<{ synced: number, errors: string[] }> {
|
||||||
const results = {
|
const results = {
|
||||||
synced: 0,
|
synced: 0,
|
||||||
errors: []
|
errors: []
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const siteProductId of siteProductIds) {
|
for (const item of data) {
|
||||||
try {
|
try {
|
||||||
await this.syncProductFromSite(siteId, siteProductId);
|
await this.syncProductFromSite(siteId, item.siteProductId, item.sku);
|
||||||
results.synced++;
|
results.synced++;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
results.errors.push(`站点产品ID ${siteProductId} 同步失败: ${error.message}`);
|
results.errors.push(`站点产品ID ${item.siteProductId} 同步失败: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1952,7 +1955,7 @@ export class ProductService {
|
||||||
* @param siteProduct 站点产品对象
|
* @param siteProduct 站点产品对象
|
||||||
* @returns 本地产品数据
|
* @returns 本地产品数据
|
||||||
*/
|
*/
|
||||||
private async convertSiteProductToLocalProduct(siteProduct: any): Promise<CreateProductDTO> {
|
private async mapUnifiedToLocalProduct(siteProduct: any): Promise<CreateProductDTO> {
|
||||||
const productData: any = {
|
const productData: any = {
|
||||||
sku: siteProduct.sku,
|
sku: siteProduct.sku,
|
||||||
name: siteProduct.name,
|
name: siteProduct.name,
|
||||||
|
|
@ -2015,18 +2018,20 @@ export class ProductService {
|
||||||
* @param localProduct 本地产品对象
|
* @param localProduct 本地产品对象
|
||||||
* @returns 统一产品对象
|
* @returns 统一产品对象
|
||||||
*/
|
*/
|
||||||
private async convertLocalProductToUnifiedProduct(localProduct: Product,siteSku?: string): Promise<Partial<UnifiedProductDTO>> {
|
private async mapLocalToUnifiedProduct(localProduct: Product,siteSku?: string): Promise<Partial<UnifiedProductDTO>> {
|
||||||
|
const tags = localProduct.attributes?.map(a => ({name: a.name})) || [];
|
||||||
// 将本地产品数据转换为UnifiedProductDTO格式
|
// 将本地产品数据转换为UnifiedProductDTO格式
|
||||||
const unifiedProduct: any = {
|
const unifiedProduct: any = {
|
||||||
id: localProduct.id ? String(localProduct.id) : undefined, // 如果产品已存在,使用现有ID
|
id: localProduct.id ? String(localProduct.id) : undefined, // 如果产品已存在,使用现有ID
|
||||||
name: localProduct.nameCn || localProduct.name || localProduct.sku,
|
name: localProduct.name,
|
||||||
type: 'simple', // 默认类型,可以根据实际需要调整
|
type: localProduct.type === 'single'? 'simple' : 'bundle', // 默认类型,可以根据实际需要调整
|
||||||
status: 'publish', // 默认状态,可以根据实际需要调整
|
status: 'publish', // 默认状态,可以根据实际需要调整
|
||||||
sku: siteSku || await this.templateService.render('site.product.sku', { sku: localProduct.sku }),
|
sku: siteSku || await this.templateService.render('site.product.sku', { product: localProduct, sku: localProduct.sku }),
|
||||||
regular_price: String(localProduct.price || 0),
|
regular_price: String(localProduct.price || 0),
|
||||||
sale_price: String(localProduct.promotionPrice || localProduct.price || 0),
|
sale_price: String(localProduct.promotionPrice || localProduct.price || 0),
|
||||||
price: String(localProduct.price || 0),
|
price: String(localProduct.price || 0),
|
||||||
// stock_status: localProduct.stockQuantity && localProduct.stockQuantity > 0 ? 'instock' : 'outofstock',
|
// TODO 库存暂时无法同步
|
||||||
|
// stock_status: localProduct.components && localProduct.stockQuantity > 0 ? 'instock' : 'outofstock',
|
||||||
// stock_quantity: localProduct.stockQuantity || 0,
|
// stock_quantity: localProduct.stockQuantity || 0,
|
||||||
// images: localProduct.images ? localProduct.images.map(img => ({
|
// images: localProduct.images ? localProduct.images.map(img => ({
|
||||||
// id: img.id,
|
// id: img.id,
|
||||||
|
|
@ -2034,25 +2039,24 @@ export class ProductService {
|
||||||
// name: img.name || '',
|
// name: img.name || '',
|
||||||
// alt: img.alt || ''
|
// alt: img.alt || ''
|
||||||
// })) : [],
|
// })) : [],
|
||||||
tags: [],
|
tags,
|
||||||
categories: localProduct.category ? [{
|
categories: localProduct.category ? [{
|
||||||
id: localProduct.category.id,
|
id: localProduct.category.id,
|
||||||
name: localProduct.category.name
|
name: localProduct.category.name
|
||||||
}] : [],
|
}] : [],
|
||||||
attributes: localProduct.attributes ? localProduct.attributes.map(attr => ({
|
attributes: localProduct.attributes ? localProduct.attributes.map(attr => ({
|
||||||
id: attr.id,
|
id: attr.dict.id,
|
||||||
name: attr.name,
|
name: attr.dict.name,
|
||||||
position: 0,
|
position: attr.dict.sort || 0,
|
||||||
visible: true,
|
visible: true,
|
||||||
variation: false,
|
variation: false,
|
||||||
options: [attr.value]
|
options: [attr.name]
|
||||||
})) : [],
|
})) : [],
|
||||||
variations: [],
|
variations: [],
|
||||||
date_created: localProduct.createdAt ? new Date(localProduct.createdAt).toISOString() : new Date().toISOString(),
|
date_created: localProduct.createdAt ? new Date(localProduct.createdAt).toISOString() : new Date().toISOString(),
|
||||||
date_modified: localProduct.updatedAt ? new Date(localProduct.updatedAt).toISOString() : new Date().toISOString(),
|
date_modified: localProduct.updatedAt ? new Date(localProduct.updatedAt).toISOString() : new Date().toISOString(),
|
||||||
raw: {
|
raw: {
|
||||||
localProductId: localProduct.id,
|
...localProduct
|
||||||
localProductSku: localProduct.sku
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -110,22 +110,18 @@ export class SiteApiService {
|
||||||
const adapter = await this.getAdapter(siteId);
|
const adapter = await this.getAdapter(siteId);
|
||||||
|
|
||||||
// 首先尝试查找产品
|
// 首先尝试查找产品
|
||||||
if (product.sku) {
|
if (!product.sku) {
|
||||||
// 如果没有提供ID但提供了SKU,尝试通过SKU查找产品
|
throw new Error('产品SKU不能为空');
|
||||||
try {
|
}
|
||||||
// 尝试搜索具有相同SKU的产品
|
// 尝试搜索具有相同SKU的产品
|
||||||
const existingProduct = await adapter.getProduct( { sku: product.sku });
|
const existingProduct = await adapter.getProduct({ sku: product.sku });
|
||||||
if (existingProduct) {
|
if (existingProduct) {
|
||||||
// 找到现有产品,更新它
|
// 找到现有产品,更新它
|
||||||
return await adapter.updateProduct({ id: existingProduct.id }, product);
|
return await adapter.updateProduct({ id: existingProduct.id }, product);
|
||||||
}
|
}
|
||||||
// 产品不存在,执行创建
|
// 产品不存在,执行创建
|
||||||
return await adapter.createProduct(product);
|
return await adapter.createProduct(product);
|
||||||
} catch (error) {
|
|
||||||
// 搜索失败,继续执行创建逻辑
|
|
||||||
console.log(`通过SKU搜索产品失败:`, error.message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -175,17 +171,6 @@ export class SiteApiService {
|
||||||
return await adapter.getProducts(params);
|
return await adapter.getProducts(params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 从站点获取单个产品
|
|
||||||
* @param siteId 站点ID
|
|
||||||
* @param productId 产品ID
|
|
||||||
* @returns 站点产品
|
|
||||||
*/
|
|
||||||
async getProductFromSite(siteId: number, productId: string | number): Promise<any> {
|
|
||||||
const adapter = await this.getAdapter(siteId);
|
|
||||||
return await adapter.getProduct({ id: productId });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 从站点获取所有产品
|
* 从站点获取所有产品
|
||||||
* @param siteId 站点ID
|
* @param siteId 站点ID
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ export class WPService implements IPlatformService {
|
||||||
* @param site 站点配置
|
* @param site 站点配置
|
||||||
* @param namespace API 命名空间,默认 wc/v3;订阅推荐 wcs/v1
|
* @param namespace API 命名空间,默认 wc/v3;订阅推荐 wcs/v1
|
||||||
*/
|
*/
|
||||||
private createApi(site: any, namespace: WooCommerceRestApiVersion = 'wc/v3') {
|
public createApi(site: any, namespace: WooCommerceRestApiVersion = 'wc/v3') {
|
||||||
return new WooCommerceRestApi({
|
return new WooCommerceRestApi({
|
||||||
url: site.apiUrl,
|
url: site.apiUrl,
|
||||||
consumerKey: site.consumerKey,
|
consumerKey: site.consumerKey,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue