feat: 统一分页查询参数处理与DTO增强
refactor(service): 重构分页查询参数处理逻辑 feat(dto): 增强统一分页DTO与搜索参数DTO feat(controller): 实现批量导出与分页查询优化 docs(dto): 添加DTO字段注释与类型定义
This commit is contained in:
parent
3f3569995d
commit
8ef150c1ba
|
|
@ -18,3 +18,4 @@ container
|
||||||
scripts
|
scripts
|
||||||
ai
|
ai
|
||||||
tmp_uploads/
|
tmp_uploads/
|
||||||
|
.trae
|
||||||
File diff suppressed because one or more lines are too long
|
|
@ -9,6 +9,12 @@ import {
|
||||||
UnifiedSubscriptionDTO,
|
UnifiedSubscriptionDTO,
|
||||||
UnifiedCustomerDTO,
|
UnifiedCustomerDTO,
|
||||||
} from '../dto/site-api.dto';
|
} from '../dto/site-api.dto';
|
||||||
|
import {
|
||||||
|
ShopyyProduct,
|
||||||
|
ShopyyOrder,
|
||||||
|
ShopyyCustomer,
|
||||||
|
ShopyyVariant,
|
||||||
|
} from '../dto/shopyy.dto';
|
||||||
|
|
||||||
export class ShopyyAdapter implements ISiteAdapter {
|
export class ShopyyAdapter implements ISiteAdapter {
|
||||||
constructor(private site: any, private shopyyService: ShopyyService) { }
|
constructor(private site: any, private shopyyService: ShopyyService) { }
|
||||||
|
|
@ -16,19 +22,19 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
// return status === 1 ? 'publish' : 'draft';
|
// return status === 1 ? 'publish' : 'draft';
|
||||||
// }
|
// }
|
||||||
|
|
||||||
private mapProduct(item: any): UnifiedProductDTO {
|
private mapProduct(item: ShopyyProduct): UnifiedProductDTO {
|
||||||
function mapProductStatus(status: number) {
|
function mapProductStatus(status: number) {
|
||||||
return status === 1 ? 'publish' : 'draft';
|
return status === 1 ? 'publish' : 'draft';
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
name: item.name || item.title,
|
name: item.name || item.title,
|
||||||
type: item.product_type,
|
type: String(item.product_type ?? ''),
|
||||||
status: mapProductStatus(item.status),
|
status: mapProductStatus(item.status),
|
||||||
sku: item.variant?.sku || '',
|
sku: item.variant?.sku || '',
|
||||||
regular_price: item.variant?.price,
|
regular_price: String(item.variant?.price ?? ''),
|
||||||
sale_price: item.special_price,
|
sale_price: String(item.special_price ?? ''),
|
||||||
price: item.price,
|
price: String(item.price ?? ''),
|
||||||
stock_status: item.inventory_tracking === 1 ? 'instock' : 'outofstock',
|
stock_status: item.inventory_tracking === 1 ? 'instock' : 'outofstock',
|
||||||
stock_quantity: item.inventory_quantity,
|
stock_quantity: item.inventory_quantity,
|
||||||
images: (item.images || []).map((img: any) => ({
|
images: (item.images || []).map((img: any) => ({
|
||||||
|
|
@ -42,26 +48,26 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
attributes: [],
|
attributes: [],
|
||||||
tags: item.tags || [],
|
tags: item.tags || [],
|
||||||
variations: item.variants?.map(this.mapVariation.bind(this)) || [],
|
variations: item.variants?.map(this.mapVariation.bind(this)) || [],
|
||||||
date_created: item.created_at,
|
date_created: typeof item.created_at === 'number' ? new Date(item.created_at * 1000).toISOString() : String(item.created_at ?? ''),
|
||||||
date_modified: item.updated_at,
|
date_modified: typeof item.updated_at === 'number' ? new Date(item.updated_at * 1000).toISOString() : String(item.updated_at ?? ''),
|
||||||
raw: item,
|
raw: item,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
mapVariation(mapVariation: any) {
|
mapVariation(mapVariation: ShopyyVariant) {
|
||||||
return {
|
return {
|
||||||
id: mapVariation.id,
|
id: mapVariation.id,
|
||||||
sku: mapVariation.sku || '',
|
sku: mapVariation.sku || '',
|
||||||
regular_price: mapVariation.price,
|
regular_price: String(mapVariation.price ?? ''),
|
||||||
sale_price: mapVariation.special_price,
|
sale_price: String(mapVariation.special_price ?? ''),
|
||||||
price: mapVariation.price,
|
price: String(mapVariation.price ?? ''),
|
||||||
stock_status: mapVariation.inventory_tracking === 1 ? 'instock' : 'outofstock',
|
stock_status: mapVariation.inventory_tracking === 1 ? 'instock' : 'outofstock',
|
||||||
stock_quantity: mapVariation.inventory_quantity,
|
stock_quantity: mapVariation.inventory_quantity,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapOrder(item: any): UnifiedOrderDTO {
|
private mapOrder(item: ShopyyOrder): UnifiedOrderDTO {
|
||||||
const billing = item.billing_address || {};
|
const billing = (item as any).billing_address || {};
|
||||||
const shipping = item.shipping_address || {};
|
const shipping = (item as any).shipping_address || {};
|
||||||
|
|
||||||
const billingObj = {
|
const billingObj = {
|
||||||
first_name: billing.first_name || item.firstname || '',
|
first_name: billing.first_name || item.firstname || '',
|
||||||
|
|
@ -69,7 +75,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
fullname: billing.name || `${item.firstname} ${item.lastname}`.trim(),
|
fullname: billing.name || `${item.firstname} ${item.lastname}`.trim(),
|
||||||
company: billing.company || '',
|
company: billing.company || '',
|
||||||
email: item.customer_email || item.email || '',
|
email: item.customer_email || item.email || '',
|
||||||
phone: billing.phone || item.telephone || '',
|
phone: billing.phone || (item as any).telephone || '',
|
||||||
address_1: billing.address1 || item.payment_address || '',
|
address_1: billing.address1 || item.payment_address || '',
|
||||||
address_2: billing.address2 || '',
|
address_2: billing.address2 || '',
|
||||||
city: billing.city || item.payment_city || '',
|
city: billing.city || item.payment_city || '',
|
||||||
|
|
@ -83,7 +89,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
last_name: shipping.last_name || item.lastname || '',
|
last_name: shipping.last_name || item.lastname || '',
|
||||||
fullname: shipping.name || '',
|
fullname: shipping.name || '',
|
||||||
company: shipping.company || '',
|
company: shipping.company || '',
|
||||||
address_1: shipping.address1 || item.shipping_address || '',
|
address_1: shipping.address1 || (typeof item.shipping_address === 'string' ? item.shipping_address : '') || '',
|
||||||
address_2: shipping.address2 || '',
|
address_2: shipping.address2 || '',
|
||||||
city: shipping.city || item.shipping_city || '',
|
city: shipping.city || item.shipping_city || '',
|
||||||
state: shipping.province || item.shipping_zone || '',
|
state: shipping.province || item.shipping_zone || '',
|
||||||
|
|
@ -110,7 +116,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
number: item.order_number || item.order_sn,
|
number: item.order_number || item.order_sn,
|
||||||
status: String(item.status || item.order_status),
|
status: String(item.status || item.order_status),
|
||||||
currency: item.currency_code || item.currency,
|
currency: item.currency_code || item.currency,
|
||||||
total: String(item.total_price || item.total_amount),
|
total: String(item.total_price ?? item.total_amount ?? ''),
|
||||||
customer_id: item.customer_id || item.user_id,
|
customer_id: item.customer_id || item.user_id,
|
||||||
customer_name: item.customer_name || `${item.firstname} ${item.lastname}`.trim(),
|
customer_name: item.customer_name || `${item.firstname} ${item.lastname}`.trim(),
|
||||||
email: item.customer_email || item.email,
|
email: item.customer_email || item.email,
|
||||||
|
|
@ -119,7 +125,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
name: p.product_title || p.name,
|
name: p.product_title || p.name,
|
||||||
product_id: p.product_id,
|
product_id: p.product_id,
|
||||||
quantity: p.quantity,
|
quantity: p.quantity,
|
||||||
total: String(p.price),
|
total: String(p.price ?? ''),
|
||||||
sku: p.sku || p.sku_code || ''
|
sku: p.sku || p.sku_code || ''
|
||||||
})),
|
})),
|
||||||
sales: (item.products || []).map((p: any) => ({
|
sales: (item.products || []).map((p: any) => ({
|
||||||
|
|
@ -128,7 +134,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
product_id: p.product_id,
|
product_id: p.product_id,
|
||||||
productId: p.product_id,
|
productId: p.product_id,
|
||||||
quantity: p.quantity,
|
quantity: p.quantity,
|
||||||
total: String(p.price),
|
total: String(p.price ?? ''),
|
||||||
sku: p.sku || p.sku_code || ''
|
sku: p.sku || p.sku_code || ''
|
||||||
})),
|
})),
|
||||||
billing: billingObj,
|
billing: billingObj,
|
||||||
|
|
@ -136,12 +142,19 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
billing_full_address: formatAddress(billingObj),
|
billing_full_address: formatAddress(billingObj),
|
||||||
shipping_full_address: formatAddress(shippingObj),
|
shipping_full_address: formatAddress(shippingObj),
|
||||||
payment_method: item.payment_method,
|
payment_method: item.payment_method,
|
||||||
date_created: item.created_at ? new Date(item.created_at * 1000).toISOString() : item.date_added,
|
date_created:
|
||||||
|
typeof item.created_at === 'number'
|
||||||
|
? new Date(item.created_at * 1000).toISOString()
|
||||||
|
: item.date_added || (typeof item.created_at === 'string' ? item.created_at : ''),
|
||||||
|
date_modified:
|
||||||
|
typeof item.updated_at === 'number'
|
||||||
|
? new Date(item.updated_at * 1000).toISOString()
|
||||||
|
: item.date_updated || item.last_modified || (typeof item.updated_at === 'string' ? item.updated_at : ''),
|
||||||
raw: item,
|
raw: item,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapCustomer(item: any): UnifiedCustomerDTO {
|
private mapCustomer(item: ShopyyCustomer): UnifiedCustomerDTO {
|
||||||
// 处理多地址结构
|
// 处理多地址结构
|
||||||
const addresses = item.addresses || [];
|
const addresses = item.addresses || [];
|
||||||
const defaultAddress = item.default_address || (addresses.length > 0 ? addresses[0] : {});
|
const defaultAddress = item.default_address || (addresses.length > 0 ? addresses[0] : {});
|
||||||
|
|
@ -153,6 +166,8 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: item.id || item.customer_id,
|
id: item.id || item.customer_id,
|
||||||
|
orders: Number(item.orders_count ?? item.order_count ?? item.orders ?? 0),
|
||||||
|
total_spend: Number(item.total_spent ?? item.total_spend_amount ?? item.total_spend_money ?? 0),
|
||||||
first_name: item.first_name || item.firstname || '',
|
first_name: item.first_name || item.firstname || '',
|
||||||
last_name: item.last_name || item.lastname || '',
|
last_name: item.last_name || item.lastname || '',
|
||||||
fullname: item.fullname || item.customer_name || `${item.first_name || item.firstname || ''} ${item.last_name || item.lastname || ''}`.trim(),
|
fullname: item.fullname || item.customer_name || `${item.first_name || item.firstname || ''} ${item.last_name || item.lastname || ''}`.trim(),
|
||||||
|
|
@ -184,6 +199,14 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
postcode: shipping.zip || '',
|
postcode: shipping.zip || '',
|
||||||
country: shipping.country_name || shipping.country_code || item.country?.country_name || ''
|
country: shipping.country_name || shipping.country_code || item.country?.country_name || ''
|
||||||
},
|
},
|
||||||
|
date_created:
|
||||||
|
typeof item.created_at === 'number'
|
||||||
|
? new Date(item.created_at * 1000).toISOString()
|
||||||
|
: (typeof item.created_at === 'string' ? item.created_at : item.date_added || ''),
|
||||||
|
date_modified:
|
||||||
|
typeof item.updated_at === 'number'
|
||||||
|
? new Date(item.updated_at * 1000).toISOString()
|
||||||
|
: (typeof item.updated_at === 'string' ? item.updated_at : item.date_updated || ''),
|
||||||
raw: item,
|
raw: item,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -203,6 +226,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
totalPages,
|
totalPages,
|
||||||
page,
|
page,
|
||||||
per_page,
|
per_page,
|
||||||
|
page_size: per_page,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -269,6 +293,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
totalPages,
|
totalPages,
|
||||||
page,
|
page,
|
||||||
per_page,
|
per_page,
|
||||||
|
page_size: per_page,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -310,7 +335,8 @@ export class ShopyyAdapter implements ISiteAdapter {
|
||||||
total,
|
total,
|
||||||
totalPages,
|
totalPages,
|
||||||
page,
|
page,
|
||||||
per_page
|
per_page,
|
||||||
|
page_size: per_page
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,17 +9,31 @@ import {
|
||||||
UnifiedSubscriptionDTO,
|
UnifiedSubscriptionDTO,
|
||||||
UnifiedCustomerDTO,
|
UnifiedCustomerDTO,
|
||||||
} from '../dto/site-api.dto';
|
} from '../dto/site-api.dto';
|
||||||
|
import {
|
||||||
|
WooProduct,
|
||||||
|
WooOrder,
|
||||||
|
WooSubscription,
|
||||||
|
WpMedia,
|
||||||
|
WooCustomer,
|
||||||
|
} from '../dto/woocommerce.dto';
|
||||||
|
|
||||||
export class WooCommerceAdapter implements ISiteAdapter {
|
export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
|
// 构造函数接收站点配置与服务实例
|
||||||
constructor(private site: any, private wpService: WPService) {}
|
constructor(private site: any, private wpService: WPService) {}
|
||||||
|
|
||||||
private mapProduct(item: any): UnifiedProductDTO {
|
private mapProduct(item: WooProduct): UnifiedProductDTO {
|
||||||
|
// 将 WooCommerce 产品数据映射为统一产品DTO
|
||||||
|
// 保留常用字段与时间信息以便前端统一展示
|
||||||
|
// https://woocommerce.github.io/woocommerce-rest-api-docs/?javascript#product-properties
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
name: item.name,
|
date_created: item.date_created,
|
||||||
type: item.type,
|
date_modified: item.date_modified,
|
||||||
status: item.status,
|
type: item.type, // simple grouped external variable
|
||||||
|
status: item.status, // draft pending private publish
|
||||||
sku: item.sku,
|
sku: item.sku,
|
||||||
|
name: item.name,
|
||||||
|
//价格
|
||||||
regular_price: item.regular_price,
|
regular_price: item.regular_price,
|
||||||
sale_price: item.sale_price,
|
sale_price: item.sale_price,
|
||||||
price: item.price,
|
price: item.price,
|
||||||
|
|
@ -33,13 +47,13 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
})),
|
})),
|
||||||
attributes: item.attributes,
|
attributes: item.attributes,
|
||||||
variations: item.variations,
|
variations: item.variations,
|
||||||
date_created: item.date_created,
|
|
||||||
date_modified: item.date_modified,
|
|
||||||
raw: item,
|
raw: item,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapOrder(item: any): UnifiedOrderDTO {
|
private mapOrder(item: WooOrder): UnifiedOrderDTO {
|
||||||
|
// 地址格式化函数用于生成完整地址字符串
|
||||||
const formatAddress = (addr: any) => {
|
const formatAddress = (addr: any) => {
|
||||||
if (!addr) return '';
|
if (!addr) return '';
|
||||||
const name = addr.fullname || `${addr.first_name || ''} ${addr.last_name || ''}`.trim();
|
const name = addr.fullname || `${addr.first_name || ''} ${addr.last_name || ''}`.trim();
|
||||||
|
|
@ -56,6 +70,8 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
].filter(Boolean).join(', ');
|
].filter(Boolean).join(', ');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 将 WooCommerce 订单数据映射为统一订单DTO
|
||||||
|
// 包含账单地址与收货地址以及创建与更新时间
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
number: item.number,
|
number: item.number,
|
||||||
|
|
@ -79,17 +95,22 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
shipping_full_address: formatAddress(item.shipping),
|
shipping_full_address: formatAddress(item.shipping),
|
||||||
payment_method: item.payment_method_title,
|
payment_method: item.payment_method_title,
|
||||||
date_created: item.date_created,
|
date_created: item.date_created,
|
||||||
|
date_modified: item.date_modified,
|
||||||
raw: item,
|
raw: item,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapSubscription(item: any): UnifiedSubscriptionDTO {
|
private mapSubscription(item: WooSubscription): UnifiedSubscriptionDTO {
|
||||||
|
// 将 WooCommerce 订阅数据映射为统一订阅DTO
|
||||||
|
// 若缺少创建时间则回退为开始时间
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
status: item.status,
|
status: item.status,
|
||||||
customer_id: item.customer_id,
|
customer_id: item.customer_id,
|
||||||
billing_period: item.billing_period,
|
billing_period: item.billing_period,
|
||||||
billing_interval: item.billing_interval,
|
billing_interval: item.billing_interval,
|
||||||
|
date_created: item.date_created ?? item.start_date,
|
||||||
|
date_modified: item.date_modified,
|
||||||
start_date: item.start_date,
|
start_date: item.start_date,
|
||||||
next_payment_date: item.next_payment_date,
|
next_payment_date: item.next_payment_date,
|
||||||
line_items: item.line_items,
|
line_items: item.line_items,
|
||||||
|
|
@ -97,20 +118,27 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapMedia(item: any): UnifiedMediaDTO {
|
private mapMedia(item: WpMedia): UnifiedMediaDTO {
|
||||||
|
// 将 WordPress 媒体数据映射为统一媒体DTO
|
||||||
|
// 兼容不同字段命名的时间信息
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
title: item.title?.rendered || '',
|
title:
|
||||||
|
typeof item.title === 'string'
|
||||||
|
? item.title
|
||||||
|
: item.title?.rendered || '',
|
||||||
media_type: item.media_type,
|
media_type: item.media_type,
|
||||||
mime_type: item.mime_type,
|
mime_type: item.mime_type,
|
||||||
source_url: item.source_url,
|
source_url: item.source_url,
|
||||||
date_created: item.date_created,
|
date_created: item.date_created ?? item.date,
|
||||||
|
date_modified: item.date_modified ?? item.modified,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProducts(
|
async getProducts(
|
||||||
params: UnifiedSearchParamsDTO
|
params: UnifiedSearchParamsDTO
|
||||||
): Promise<UnifiedPaginationDTO<UnifiedProductDTO>> {
|
): Promise<UnifiedPaginationDTO<UnifiedProductDTO>> {
|
||||||
|
// 获取产品列表并使用统一分页结构返回
|
||||||
const { items, total, totalPages, page, per_page } =
|
const { items, total, totalPages, page, per_page } =
|
||||||
await this.wpService.fetchResourcePaged<any>(
|
await this.wpService.fetchResourcePaged<any>(
|
||||||
this.site,
|
this.site,
|
||||||
|
|
@ -123,43 +151,51 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
totalPages,
|
totalPages,
|
||||||
page,
|
page,
|
||||||
per_page,
|
per_page,
|
||||||
|
page_size: per_page,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getProduct(id: string | number): Promise<UnifiedProductDTO> {
|
async getProduct(id: string | number): Promise<UnifiedProductDTO> {
|
||||||
|
// 获取单个产品详情并映射为统一产品DTO
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
const res = await api.get(`products/${id}`);
|
const res = await api.get(`products/${id}`);
|
||||||
return this.mapProduct(res.data);
|
return this.mapProduct(res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createProduct(data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO> {
|
async createProduct(data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO> {
|
||||||
|
// 创建产品并返回统一产品DTO
|
||||||
const res = await this.wpService.createProduct(this.site, data);
|
const res = await this.wpService.createProduct(this.site, data);
|
||||||
return this.mapProduct(res);
|
return this.mapProduct(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateProduct(id: string | number, data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO> {
|
async updateProduct(id: string | number, data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO> {
|
||||||
|
// 更新产品并返回统一产品DTO
|
||||||
const res = await this.wpService.updateProduct(this.site, String(id), data as any);
|
const res = await this.wpService.updateProduct(this.site, String(id), data as any);
|
||||||
return this.mapProduct(res);
|
return this.mapProduct(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateVariation(productId: string | number, variationId: string | number, data: any): Promise<any> {
|
async updateVariation(productId: string | number, variationId: string | number, data: any): Promise<any> {
|
||||||
|
// 更新变体信息并返回结果
|
||||||
const res = await this.wpService.updateVariation(this.site, String(productId), String(variationId), data);
|
const res = await this.wpService.updateVariation(this.site, String(productId), String(variationId), data);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
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 as any).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 as any).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;
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteProduct(id: string | number): Promise<boolean> {
|
async deleteProduct(id: string | number): Promise<boolean> {
|
||||||
|
// 删除产品
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
try {
|
try {
|
||||||
await api.delete(`products/${id}`, { force: true });
|
await api.delete(`products/${id}`, { force: true });
|
||||||
|
|
@ -172,12 +208,14 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
async batchProcessProducts(
|
async batchProcessProducts(
|
||||||
data: { create?: any[]; update?: any[]; delete?: Array<string | number> }
|
data: { create?: any[]; update?: any[]; delete?: Array<string | number> }
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
|
// 批量处理产品增删改
|
||||||
return await this.wpService.batchProcessProducts(this.site, data);
|
return await this.wpService.batchProcessProducts(this.site, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrders(
|
async getOrders(
|
||||||
params: UnifiedSearchParamsDTO
|
params: UnifiedSearchParamsDTO
|
||||||
): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>> {
|
): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>> {
|
||||||
|
// 获取订单列表并映射为统一订单DTO集合
|
||||||
const { items, total, totalPages, page, per_page } =
|
const { items, total, totalPages, page, per_page } =
|
||||||
await this.wpService.fetchResourcePaged<any>(this.site, 'orders', params);
|
await this.wpService.fetchResourcePaged<any>(this.site, 'orders', params);
|
||||||
return {
|
return {
|
||||||
|
|
@ -186,26 +224,31 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
totalPages,
|
totalPages,
|
||||||
page,
|
page,
|
||||||
per_page,
|
per_page,
|
||||||
|
page_size: per_page,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOrder(id: string | number): Promise<UnifiedOrderDTO> {
|
async getOrder(id: string | number): Promise<UnifiedOrderDTO> {
|
||||||
|
// 获取单个订单详情
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
const res = await api.get(`orders/${id}`);
|
const res = await api.get(`orders/${id}`);
|
||||||
return this.mapOrder(res.data);
|
return this.mapOrder(res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO> {
|
async createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO> {
|
||||||
|
// 创建订单并返回统一订单DTO
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
const res = await api.post('orders', data);
|
const res = await api.post('orders', data);
|
||||||
return this.mapOrder(res.data);
|
return this.mapOrder(res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateOrder(id: string | number, data: Partial<UnifiedOrderDTO>): Promise<boolean> {
|
async updateOrder(id: string | number, data: Partial<UnifiedOrderDTO>): Promise<boolean> {
|
||||||
|
// 更新订单并返回布尔结果
|
||||||
return await this.wpService.updateOrder(this.site, String(id), data as any);
|
return await this.wpService.updateOrder(this.site, String(id), data as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteOrder(id: string | number): Promise<boolean> {
|
async deleteOrder(id: string | number): Promise<boolean> {
|
||||||
|
// 删除订单
|
||||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||||
await api.delete(`orders/${id}`, { force: true });
|
await api.delete(`orders/${id}`, { force: true });
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -214,6 +257,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
async getSubscriptions(
|
async getSubscriptions(
|
||||||
params: UnifiedSearchParamsDTO
|
params: UnifiedSearchParamsDTO
|
||||||
): Promise<UnifiedPaginationDTO<UnifiedSubscriptionDTO>> {
|
): Promise<UnifiedPaginationDTO<UnifiedSubscriptionDTO>> {
|
||||||
|
// 获取订阅列表并映射为统一订阅DTO集合
|
||||||
const { items, total, totalPages, page, per_page } =
|
const { items, total, totalPages, page, per_page } =
|
||||||
await this.wpService.fetchResourcePaged<any>(
|
await this.wpService.fetchResourcePaged<any>(
|
||||||
this.site,
|
this.site,
|
||||||
|
|
@ -226,45 +270,56 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
totalPages,
|
totalPages,
|
||||||
page,
|
page,
|
||||||
per_page,
|
per_page,
|
||||||
|
page_size: per_page,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async getMedia(
|
async getMedia(
|
||||||
params: UnifiedSearchParamsDTO
|
params: UnifiedSearchParamsDTO
|
||||||
): Promise<UnifiedPaginationDTO<UnifiedMediaDTO>> {
|
): Promise<UnifiedPaginationDTO<UnifiedMediaDTO>> {
|
||||||
const { items, total, totalPages } = await this.wpService.getMedia(
|
// 获取媒体列表并映射为统一媒体DTO集合
|
||||||
this.site.id,
|
const { items, total, totalPages, page, per_page } = await this.wpService.fetchMediaPaged(
|
||||||
params.page || 1,
|
this.site,
|
||||||
params.per_page || 20
|
params
|
||||||
);
|
);
|
||||||
return {
|
return {
|
||||||
items: items.map(this.mapMedia),
|
items: items.map(this.mapMedia),
|
||||||
total,
|
total,
|
||||||
totalPages,
|
totalPages,
|
||||||
page: params.page || 1,
|
page,
|
||||||
per_page: params.per_page || 20,
|
per_page,
|
||||||
|
page_size: per_page,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteMedia(id: string | number): Promise<boolean> {
|
async deleteMedia(id: string | number): Promise<boolean> {
|
||||||
|
// 删除媒体资源
|
||||||
await this.wpService.deleteMedia(Number(this.site.id), Number(id), true);
|
await this.wpService.deleteMedia(Number(this.site.id), Number(id), true);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateMedia(id: string | number, data: any): Promise<any> {
|
async updateMedia(id: string | number, data: any): Promise<any> {
|
||||||
|
// 更新媒体信息
|
||||||
return await this.wpService.updateMedia(Number(this.site.id), Number(id), data);
|
return await this.wpService.updateMedia(Number(this.site.id), Number(id), data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private mapCustomer(item: any): UnifiedCustomerDTO {
|
private mapCustomer(item: WooCustomer): UnifiedCustomerDTO {
|
||||||
|
// 将 WooCommerce 客户数据映射为统一客户DTO
|
||||||
|
// 包含基础信息地址信息与时间信息
|
||||||
return {
|
return {
|
||||||
id: item.id,
|
id: item.id,
|
||||||
|
avatar: item.avatar_url,
|
||||||
email: item.email,
|
email: item.email,
|
||||||
|
orders: Number(item.orders?? 0),
|
||||||
|
total_spend: Number(item.total_spent ?? 0),
|
||||||
first_name: item.first_name,
|
first_name: item.first_name,
|
||||||
last_name: item.last_name,
|
last_name: item.last_name,
|
||||||
username: item.username,
|
username: item.username,
|
||||||
phone: item.billing?.phone || item.shipping?.phone,
|
phone: item.billing?.phone || item.shipping?.phone,
|
||||||
billing: item.billing,
|
billing: item.billing,
|
||||||
shipping: item.shipping,
|
shipping: item.shipping,
|
||||||
|
date_created: item.date_created,
|
||||||
|
date_modified: item.date_modified,
|
||||||
raw: item,
|
raw: item,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -281,6 +336,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
||||||
totalPages,
|
totalPages,
|
||||||
page,
|
page,
|
||||||
per_page,
|
per_page,
|
||||||
|
page_size: per_page,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,10 +49,42 @@ export class SiteApiController {
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.getProducts(query);
|
const perPage = (query.page_size ?? query.per_page) || 100;
|
||||||
const header = ['id','name','type','status','sku','regular_price','sale_price','price','stock_status','stock_quantity'];
|
let page = 1;
|
||||||
const rows = data.items.map((p: any) => [p.id,p.name,p.type,p.status,p.sku,p.regular_price,p.sale_price,p.price,p.stock_status,p.stock_quantity]);
|
const all: any[] = [];
|
||||||
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
while (true) {
|
||||||
|
const data = await adapter.getProducts({ ...query, page, per_page: perPage, page_size: perPage });
|
||||||
|
const items = data.items || [];
|
||||||
|
all.push(...items);
|
||||||
|
const totalPages = data.totalPages || Math.ceil((data.total || 0) / (data.per_page || perPage));
|
||||||
|
if (!items.length || page >= totalPages) break;
|
||||||
|
page += 1;
|
||||||
|
}
|
||||||
|
let items = all;
|
||||||
|
if (query.ids) {
|
||||||
|
const ids = new Set(String(query.ids).split(',').map(v => v.trim()).filter(Boolean));
|
||||||
|
items = items.filter(i => ids.has(String(i.id)));
|
||||||
|
}
|
||||||
|
const header = ['id','name','type','status','sku','regular_price','sale_price','price','stock_status','stock_quantity','image_src'];
|
||||||
|
const rows = items.map((p: any) => [
|
||||||
|
p.id,
|
||||||
|
p.name,
|
||||||
|
p.type,
|
||||||
|
p.status,
|
||||||
|
p.sku,
|
||||||
|
p.regular_price,
|
||||||
|
p.sale_price,
|
||||||
|
p.price,
|
||||||
|
p.stock_status,
|
||||||
|
p.stock_quantity,
|
||||||
|
((p.images && p.images[0]?.src) || ''),
|
||||||
|
]);
|
||||||
|
const toCsvValue = (val: any) => {
|
||||||
|
const s = String(val ?? '');
|
||||||
|
const escaped = s.replace(/"/g, '""');
|
||||||
|
return `"${escaped}"`;
|
||||||
|
};
|
||||||
|
const csv = [header.map(toCsvValue).join(','), ...rows.map(r => r.map(toCsvValue).join(','))].join('\n');
|
||||||
return successResponse({ csv });
|
return successResponse({ csv });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error.message);
|
return errorResponse(error.message);
|
||||||
|
|
@ -69,15 +101,20 @@ export class SiteApiController {
|
||||||
const site = await this.siteApiService.siteService.get(siteId, true);
|
const site = await this.siteApiService.siteService.get(siteId, true);
|
||||||
if (site.type === 'woocommerce') {
|
if (site.type === 'woocommerce') {
|
||||||
const page = query.page || 1;
|
const page = query.page || 1;
|
||||||
const per_page = query.per_page || 100;
|
const perPage = (query.page_size ?? query.per_page) || 100;
|
||||||
const res = await this.siteApiService.wpService.getProducts(site, page, per_page);
|
const res = await this.siteApiService.wpService.getProducts(site, page, perPage);
|
||||||
const header = ['id','name','type','status','sku','regular_price','sale_price','stock_status','stock_quantity'];
|
const header = ['id','name','type','status','sku','regular_price','sale_price','stock_status','stock_quantity'];
|
||||||
const rows = (res.items || []).map((p: any) => [p.id,p.name,p.type,p.status,p.sku,p.regular_price,p.sale_price,p.stock_status,p.stock_quantity]);
|
const rows = (res.items || []).map((p: any) => [p.id,p.name,p.type,p.status,p.sku,p.regular_price,p.sale_price,p.stock_status,p.stock_quantity]);
|
||||||
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
const toCsvValue = (val: any) => {
|
||||||
return successResponse({ csv });
|
const s = String(val ?? '');
|
||||||
|
const escaped = s.replace(/"/g, '""');
|
||||||
|
return `"${escaped}"`;
|
||||||
|
};
|
||||||
|
const csv = [header.map(toCsvValue).join(','), ...rows.map(r => r.map(toCsvValue).join(','))].join('\n');
|
||||||
|
return successResponse({ csv });
|
||||||
}
|
}
|
||||||
if (site.type === 'shopyy') {
|
if (site.type === 'shopyy') {
|
||||||
const res = await this.siteApiService.shopyyService.getProducts(site, query.page || 1, query.per_page || 100);
|
const res = await this.siteApiService.shopyyService.getProducts(site, query.page || 1, (query.page_size ?? query.per_page) || 100);
|
||||||
const header = ['id','name','type','status','sku','price','stock_status','stock_quantity'];
|
const header = ['id','name','type','status','sku','price','stock_status','stock_quantity'];
|
||||||
const rows = (res.items || []).map((p: any) => [p.id,p.name,p.type,p.status,p.sku,p.price,p.stock_status,p.stock_quantity]);
|
const rows = (res.items || []).map((p: any) => [p.id,p.name,p.type,p.status,p.sku,p.price,p.stock_status,p.stock_quantity]);
|
||||||
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
||||||
|
|
@ -329,7 +366,12 @@ export class SiteApiController {
|
||||||
this.logger.info(`[Site API] 获取订单列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`);
|
this.logger.info(`[Site API] 获取订单列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`);
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.getOrders(query);
|
const where = { ...(query.where || {}) };
|
||||||
|
if (query.customer_id) {
|
||||||
|
where.customer = query.customer_id;
|
||||||
|
where.customer_id = query.customer_id;
|
||||||
|
}
|
||||||
|
const data = await adapter.getOrders({ ...query, where });
|
||||||
this.logger.info(`[Site API] 获取订单列表成功, siteId: ${siteId}, 共获取到 ${data.total} 个订单`);
|
this.logger.info(`[Site API] 获取订单列表成功, siteId: ${siteId}, 共获取到 ${data.total} 个订单`);
|
||||||
return successResponse(data);
|
return successResponse(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -338,6 +380,26 @@ export class SiteApiController {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('/:siteId/customers/:customerId/orders')
|
||||||
|
@ApiOkResponse({ type: UnifiedOrderPaginationDTO })
|
||||||
|
async getCustomerOrders(
|
||||||
|
@Param('siteId') siteId: number,
|
||||||
|
@Param('customerId') customerId: number,
|
||||||
|
@Query() query: UnifiedSearchParamsDTO
|
||||||
|
) {
|
||||||
|
this.logger.info(`[Site API] 获取客户订单列表开始, siteId: ${siteId}, customerId: ${customerId}, query: ${JSON.stringify(query)}`);
|
||||||
|
try {
|
||||||
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
|
const where = { ...(query.where || {}), customer: customerId, customer_id: customerId };
|
||||||
|
const data = await adapter.getOrders({ ...query, where, customer_id: customerId });
|
||||||
|
this.logger.info(`[Site API] 获取客户订单列表成功, siteId: ${siteId}, customerId: ${customerId}, 共获取到 ${data.total} 个订单`);
|
||||||
|
return successResponse(data);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`[Site API] 获取客户订单列表失败, siteId: ${siteId}, customerId: ${customerId}, 错误信息: ${error.message}`);
|
||||||
|
return errorResponse(error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Get('/:siteId/orders/export')
|
@Get('/:siteId/orders/export')
|
||||||
async exportOrders(
|
async exportOrders(
|
||||||
@Param('siteId') siteId: number,
|
@Param('siteId') siteId: number,
|
||||||
|
|
@ -345,10 +407,44 @@ export class SiteApiController {
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.getOrders(query);
|
const perPage = (query.page_size ?? query.per_page) || 100;
|
||||||
const header = ['id','number','status','currency','total','customer_id','customer_name','email','date_created'];
|
let page = 1;
|
||||||
const rows = data.items.map((o: any) => [o.id,o.number,o.status,o.currency,o.total,o.customer_id,o.customer_name,o.email,o.date_created]);
|
const all: any[] = [];
|
||||||
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
while (true) {
|
||||||
|
const data = await adapter.getOrders({ ...query, page, per_page: perPage, page_size: perPage });
|
||||||
|
const items = data.items || [];
|
||||||
|
all.push(...items);
|
||||||
|
const totalPages = data.totalPages || Math.ceil((data.total || 0) / (data.per_page || perPage));
|
||||||
|
if (!items.length || page >= totalPages) break;
|
||||||
|
page += 1;
|
||||||
|
}
|
||||||
|
let items = all;
|
||||||
|
if (query.ids) {
|
||||||
|
const ids = new Set(String(query.ids).split(',').map(v => v.trim()).filter(Boolean));
|
||||||
|
items = items.filter(i => ids.has(String(i.id)));
|
||||||
|
}
|
||||||
|
const header = ['id','number','status','currency','total','customer_id','customer_name','email','payment_method','phone','billing_full_address','shipping_full_address','date_created'];
|
||||||
|
const rows = items.map((o: any) => [
|
||||||
|
o.id,
|
||||||
|
o.number,
|
||||||
|
o.status,
|
||||||
|
o.currency,
|
||||||
|
o.total,
|
||||||
|
o.customer_id,
|
||||||
|
o.customer_name,
|
||||||
|
o.email,
|
||||||
|
o.payment_method,
|
||||||
|
(o.shipping?.phone || o.billing?.phone || ''),
|
||||||
|
(o.billing_full_address || ''),
|
||||||
|
(o.shipping_full_address || ''),
|
||||||
|
o.date_created,
|
||||||
|
]);
|
||||||
|
const toCsvValue = (val: any) => {
|
||||||
|
const s = String(val ?? '');
|
||||||
|
const escaped = s.replace(/"/g, '""');
|
||||||
|
return `"${escaped}"`;
|
||||||
|
};
|
||||||
|
const csv = [header.map(toCsvValue).join(','), ...rows.map(r => r.map(toCsvValue).join(','))].join('\n');
|
||||||
return successResponse({ csv });
|
return successResponse({ csv });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return errorResponse(error.message);
|
return errorResponse(error.message);
|
||||||
|
|
@ -579,9 +675,24 @@ export class SiteApiController {
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.getSubscriptions(query);
|
const perPage = (query.page_size ?? query.per_page) || 100;
|
||||||
|
let page = 1;
|
||||||
|
const all: any[] = [];
|
||||||
|
while (true) {
|
||||||
|
const data = await adapter.getSubscriptions({ ...query, page, per_page: perPage, page_size: perPage });
|
||||||
|
const items = data.items || [];
|
||||||
|
all.push(...items);
|
||||||
|
const totalPages = data.totalPages || Math.ceil((data.total || 0) / (data.per_page || perPage));
|
||||||
|
if (!items.length || page >= totalPages) break;
|
||||||
|
page += 1;
|
||||||
|
}
|
||||||
|
let items = all;
|
||||||
|
if (query.ids) {
|
||||||
|
const ids = new Set(String(query.ids).split(',').map(v => v.trim()).filter(Boolean));
|
||||||
|
items = items.filter(i => ids.has(String(i.id)));
|
||||||
|
}
|
||||||
const header = ['id','status','customer_id','billing_period','billing_interval','start_date','next_payment_date'];
|
const header = ['id','status','customer_id','billing_period','billing_interval','start_date','next_payment_date'];
|
||||||
const rows = data.items.map((s: any) => [s.id,s.status,s.customer_id,s.billing_period,s.billing_interval,s.start_date,s.next_payment_date]);
|
const rows = items.map((s: any) => [s.id,s.status,s.customer_id,s.billing_period,s.billing_interval,s.start_date,s.next_payment_date]);
|
||||||
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
||||||
return successResponse({ csv });
|
return successResponse({ csv });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -614,9 +725,24 @@ export class SiteApiController {
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.getMedia(query);
|
const perPage = (query.page_size ?? query.per_page) || 100;
|
||||||
|
let page = 1;
|
||||||
|
const all: any[] = [];
|
||||||
|
while (true) {
|
||||||
|
const data = await adapter.getMedia({ ...query, page, per_page: perPage, page_size: perPage });
|
||||||
|
const items = data.items || [];
|
||||||
|
all.push(...items);
|
||||||
|
const totalPages = data.totalPages || Math.ceil((data.total || 0) / (data.per_page || perPage));
|
||||||
|
if (!items.length || page >= totalPages) break;
|
||||||
|
page += 1;
|
||||||
|
}
|
||||||
|
let items = all;
|
||||||
|
if (query.ids) {
|
||||||
|
const ids = new Set(String(query.ids).split(',').map(v => v.trim()).filter(Boolean));
|
||||||
|
items = items.filter(i => ids.has(String(i.id)));
|
||||||
|
}
|
||||||
const header = ['id','title','media_type','mime_type','source_url','date_created'];
|
const header = ['id','title','media_type','mime_type','source_url','date_created'];
|
||||||
const rows = data.items.map((m: any) => [m.id,m.title,m.media_type,m.mime_type,m.source_url,m.date_created]);
|
const rows = items.map((m: any) => [m.id,m.title,m.media_type,m.mime_type,m.source_url,m.date_created]);
|
||||||
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
||||||
return successResponse({ csv });
|
return successResponse({ csv });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -738,9 +864,49 @@ export class SiteApiController {
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||||
const data = await adapter.getCustomers(query);
|
const perPage = (query.page_size ?? query.per_page) || 100;
|
||||||
const header = ['id','email','first_name','last_name','fullname','username','phone'];
|
let page = 1;
|
||||||
const rows = data.items.map((c: any) => [c.id,c.email,c.first_name,c.last_name,c.fullname,c.username,c.phone]);
|
const all: any[] = [];
|
||||||
|
while (true) {
|
||||||
|
const data = await adapter.getCustomers({ ...query, page, per_page: perPage, page_size: perPage });
|
||||||
|
const items = data.items || [];
|
||||||
|
all.push(...items);
|
||||||
|
const totalPages = data.totalPages || Math.ceil((data.total || 0) / (data.per_page || perPage));
|
||||||
|
if (!items.length || page >= totalPages) break;
|
||||||
|
page += 1;
|
||||||
|
}
|
||||||
|
let items = all;
|
||||||
|
if (query.ids) {
|
||||||
|
const ids = new Set(String(query.ids).split(',').map(v => v.trim()).filter(Boolean));
|
||||||
|
items = items.filter(i => ids.has(String(i.id)));
|
||||||
|
}
|
||||||
|
const header = ['id','email','first_name','last_name','fullname','username','phone','orders','total_spend','role','billing_full_address','shipping_full_address','date_created'];
|
||||||
|
const formatAddress = (addr: any) => [
|
||||||
|
addr?.fullname,
|
||||||
|
addr?.company,
|
||||||
|
addr?.address_1,
|
||||||
|
addr?.address_2,
|
||||||
|
addr?.city,
|
||||||
|
addr?.state,
|
||||||
|
addr?.postcode,
|
||||||
|
addr?.country,
|
||||||
|
addr?.phone,
|
||||||
|
].filter(Boolean).join(', ');
|
||||||
|
const rows = items.map((c: any) => [
|
||||||
|
c.id,
|
||||||
|
c.email,
|
||||||
|
c.first_name,
|
||||||
|
c.last_name,
|
||||||
|
c.fullname,
|
||||||
|
(c.username || c.raw?.username || ''),
|
||||||
|
(c.phone || c.billing?.phone || c.shipping?.phone || ''),
|
||||||
|
c.orders,
|
||||||
|
c.total_spend,
|
||||||
|
(c.role || c.raw?.role || ''),
|
||||||
|
formatAddress(c.billing || {}),
|
||||||
|
formatAddress(c.shipping || {}),
|
||||||
|
c.date_created,
|
||||||
|
]);
|
||||||
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
const csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
||||||
return successResponse({ csv });
|
return successResponse({ csv });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,282 @@
|
||||||
|
// Shopyy 平台原始数据类型定义
|
||||||
|
// 仅包含当前映射逻辑所需字段以保持简洁与类型安全
|
||||||
|
|
||||||
|
// 产品类型
|
||||||
|
export interface ShopyyProduct {
|
||||||
|
// 产品主键
|
||||||
|
id: number;
|
||||||
|
// 产品名称或标题
|
||||||
|
name?: string;
|
||||||
|
title?: string;
|
||||||
|
// 产品类型
|
||||||
|
product_type?: string | number;
|
||||||
|
// 产品状态数值 1为发布 其他为草稿
|
||||||
|
status: number;
|
||||||
|
// 变体信息
|
||||||
|
variant?: {
|
||||||
|
sku?: string;
|
||||||
|
price?: string;
|
||||||
|
};
|
||||||
|
// 价格
|
||||||
|
special_price?: string;
|
||||||
|
price?: string;
|
||||||
|
// 库存追踪标识 1表示跟踪
|
||||||
|
inventory_tracking?: number;
|
||||||
|
// 库存数量
|
||||||
|
inventory_quantity?: number;
|
||||||
|
// 图片列表
|
||||||
|
images?: Array<{
|
||||||
|
id?: number;
|
||||||
|
src: string;
|
||||||
|
alt?: string;
|
||||||
|
position?: string | number;
|
||||||
|
}>;
|
||||||
|
// 主图
|
||||||
|
image?: {
|
||||||
|
src: string;
|
||||||
|
file_name?: string;
|
||||||
|
alt?: string;
|
||||||
|
file_size?: number;
|
||||||
|
width?: number;
|
||||||
|
height?: number;
|
||||||
|
id?: number;
|
||||||
|
position?: number | string;
|
||||||
|
file_type?: string;
|
||||||
|
};
|
||||||
|
// 标签
|
||||||
|
tags?: string[];
|
||||||
|
// 变体列表
|
||||||
|
variants?: ShopyyVariant[];
|
||||||
|
// 分类集合
|
||||||
|
collections?: Array<{ id?: number; title?: string }>;
|
||||||
|
// 规格选项列表
|
||||||
|
options?: Array<{
|
||||||
|
id?: number;
|
||||||
|
position?: number | string;
|
||||||
|
option_name?: string;
|
||||||
|
values?: Array<{ option_value?: string; id?: number; position?: number }>;
|
||||||
|
}>;
|
||||||
|
// 发布与标识
|
||||||
|
published_at?: string;
|
||||||
|
handle?: string;
|
||||||
|
spu?: string;
|
||||||
|
// 创建与更新时间
|
||||||
|
created_at?: string | number;
|
||||||
|
updated_at?: string | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 变体类型
|
||||||
|
export interface ShopyyVariant {
|
||||||
|
id: number;
|
||||||
|
sku?: string;
|
||||||
|
price?: string;
|
||||||
|
special_price?: string;
|
||||||
|
inventory_tracking?: number;
|
||||||
|
inventory_quantity?: number;
|
||||||
|
available?: number;
|
||||||
|
barcode?: string;
|
||||||
|
weight?: number;
|
||||||
|
image?: { src: string; id?: number; file_name?: string; alt?: string; position?: number | string };
|
||||||
|
position?: number | string;
|
||||||
|
sku_code?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单类型
|
||||||
|
export interface ShopyyOrder {
|
||||||
|
// 主键与外部ID
|
||||||
|
id?: number;
|
||||||
|
order_id?: number;
|
||||||
|
// 订单号
|
||||||
|
order_number?: string;
|
||||||
|
order_sn?: string;
|
||||||
|
// 状态
|
||||||
|
status?: number | string;
|
||||||
|
order_status?: number | string;
|
||||||
|
// 币种
|
||||||
|
currency_code?: string;
|
||||||
|
currency?: string;
|
||||||
|
// 金额
|
||||||
|
total_price?: string | number;
|
||||||
|
total_amount?: string | number;
|
||||||
|
current_total_price?: string | number;
|
||||||
|
current_subtotal_price?: string | number;
|
||||||
|
current_shipping_price?: string | number;
|
||||||
|
current_tax_price?: string | number;
|
||||||
|
current_coupon_price?: string | number;
|
||||||
|
current_payment_price?: string | number;
|
||||||
|
// 客户ID
|
||||||
|
customer_id?: number;
|
||||||
|
user_id?: number;
|
||||||
|
// 客户信息
|
||||||
|
customer_name?: string;
|
||||||
|
firstname?: string;
|
||||||
|
lastname?: string;
|
||||||
|
customer_email?: string;
|
||||||
|
email?: string;
|
||||||
|
// 地址字段
|
||||||
|
billing_address?: {
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
name?: string;
|
||||||
|
company?: string;
|
||||||
|
phone?: string;
|
||||||
|
address1?: string;
|
||||||
|
address2?: string;
|
||||||
|
city?: string;
|
||||||
|
province?: string;
|
||||||
|
zip?: string;
|
||||||
|
country_name?: string;
|
||||||
|
country_code?: string;
|
||||||
|
};
|
||||||
|
shipping_address?: {
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
name?: string;
|
||||||
|
company?: string;
|
||||||
|
phone?: string;
|
||||||
|
address1?: string;
|
||||||
|
address2?: string;
|
||||||
|
city?: string;
|
||||||
|
province?: string;
|
||||||
|
zip?: string;
|
||||||
|
country_name?: string;
|
||||||
|
country_code?: string;
|
||||||
|
} | string;
|
||||||
|
telephone?: string;
|
||||||
|
payment_address?: string;
|
||||||
|
payment_city?: string;
|
||||||
|
payment_zone?: string;
|
||||||
|
payment_postcode?: string;
|
||||||
|
payment_country?: string;
|
||||||
|
shipping_city?: string;
|
||||||
|
shipping_zone?: string;
|
||||||
|
shipping_postcode?: string;
|
||||||
|
shipping_country?: string;
|
||||||
|
// 订单项集合
|
||||||
|
products?: Array<{
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
product_title?: string;
|
||||||
|
product_id?: number;
|
||||||
|
quantity?: number;
|
||||||
|
price?: string | number;
|
||||||
|
sku?: string;
|
||||||
|
sku_code?: string;
|
||||||
|
}>;
|
||||||
|
// 支付方式
|
||||||
|
payment_method?: string;
|
||||||
|
payment_id?: number;
|
||||||
|
payment_cards?: Array<{
|
||||||
|
store_id?: number;
|
||||||
|
card_len?: number;
|
||||||
|
card_suffix?: number;
|
||||||
|
year?: number;
|
||||||
|
payment_status?: number;
|
||||||
|
created_at?: number;
|
||||||
|
month?: number;
|
||||||
|
updated_at?: number;
|
||||||
|
payment_id?: number;
|
||||||
|
payment_interface?: string;
|
||||||
|
card_prefix?: number;
|
||||||
|
id?: number;
|
||||||
|
order_id?: number;
|
||||||
|
card?: string;
|
||||||
|
transaction_no?: string;
|
||||||
|
}>;
|
||||||
|
fulfillments?: Array<{
|
||||||
|
payment_tracking_status?: number;
|
||||||
|
note?: string;
|
||||||
|
updated_at?: number;
|
||||||
|
courier_code?: string;
|
||||||
|
courier_id?: number;
|
||||||
|
created_at?: number;
|
||||||
|
tracking_number?: string;
|
||||||
|
id?: number;
|
||||||
|
tracking_company?: string;
|
||||||
|
payment_tracking_result?: string;
|
||||||
|
payment_tracking_at?: number;
|
||||||
|
products?: Array<{ order_product_id?: number; quantity?: number; updated_at?: number; created_at?: number; id?: number }>;
|
||||||
|
}>;
|
||||||
|
shipping_zone_plans?: Array<{
|
||||||
|
shipping_price?: number | string;
|
||||||
|
updated_at?: number;
|
||||||
|
created_at?: number;
|
||||||
|
id?: number;
|
||||||
|
shipping_zone_name?: string;
|
||||||
|
shipping_zone_id?: number;
|
||||||
|
shipping_zone_plan_id?: number;
|
||||||
|
shipping_zone_plan_name?: string;
|
||||||
|
}>;
|
||||||
|
transaction?: {
|
||||||
|
note?: string;
|
||||||
|
amount?: number | string;
|
||||||
|
created_at?: number;
|
||||||
|
merchant_id?: string;
|
||||||
|
payment_type?: string;
|
||||||
|
merchant_account?: string;
|
||||||
|
updated_at?: number;
|
||||||
|
payment_id?: number;
|
||||||
|
admin_id?: number;
|
||||||
|
admin_name?: string;
|
||||||
|
id?: number;
|
||||||
|
payment_method?: string;
|
||||||
|
transaction_no?: string;
|
||||||
|
};
|
||||||
|
coupon_code?: string;
|
||||||
|
coupon_name?: string;
|
||||||
|
store_id?: number;
|
||||||
|
visitor_id?: string;
|
||||||
|
currency_rate?: string | number;
|
||||||
|
landing_page?: string;
|
||||||
|
note?: string;
|
||||||
|
admin_note?: string;
|
||||||
|
source_device?: string;
|
||||||
|
checkout_type?: string;
|
||||||
|
version?: string;
|
||||||
|
brand_id?: number;
|
||||||
|
tags?: string[];
|
||||||
|
financial_status?: number;
|
||||||
|
fulfillment_status?: number;
|
||||||
|
// 创建与更新时间可能为时间戳
|
||||||
|
created_at?: number | string;
|
||||||
|
date_added?: string;
|
||||||
|
updated_at?: number | string;
|
||||||
|
date_updated?: string;
|
||||||
|
last_modified?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户类型
|
||||||
|
export interface ShopyyCustomer {
|
||||||
|
// 主键与兼容ID
|
||||||
|
id?: number;
|
||||||
|
customer_id?: number;
|
||||||
|
// 姓名
|
||||||
|
first_name?: string;
|
||||||
|
firstname?: string;
|
||||||
|
last_name?: string;
|
||||||
|
lastname?: string;
|
||||||
|
fullname?: string;
|
||||||
|
customer_name?: string;
|
||||||
|
// 联系信息
|
||||||
|
email?: string;
|
||||||
|
customer_email?: string;
|
||||||
|
contact?: string;
|
||||||
|
phone?: string;
|
||||||
|
// 地址集合
|
||||||
|
addresses?: any[];
|
||||||
|
default_address?: any;
|
||||||
|
// 国家
|
||||||
|
country?: { country_name?: string };
|
||||||
|
// 统计字段
|
||||||
|
orders_count?: number;
|
||||||
|
order_count?: number;
|
||||||
|
orders?: number;
|
||||||
|
total_spent?: number | string;
|
||||||
|
total_spend_amount?: number | string;
|
||||||
|
total_spend_money?: number | string;
|
||||||
|
// 创建与更新时间可能为时间戳
|
||||||
|
created_at?: number | string;
|
||||||
|
date_added?: string;
|
||||||
|
updated_at?: number | string;
|
||||||
|
date_updated?: string;
|
||||||
|
}
|
||||||
|
|
@ -1,264 +0,0 @@
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
|
||||||
|
|
||||||
export class UnifiedPaginationDTO<T> {
|
|
||||||
@ApiProperty({ description: '列表数据' })
|
|
||||||
items: T[];
|
|
||||||
|
|
||||||
@ApiProperty({ description: '总数', example: 100 })
|
|
||||||
total: number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '当前页', example: 1 })
|
|
||||||
page: number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '每页数量', example: 20 })
|
|
||||||
per_page: number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '总页数', example: 5 })
|
|
||||||
totalPages: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnifiedImageDTO {
|
|
||||||
@ApiProperty({ description: '图片ID' })
|
|
||||||
id: number | string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '图片URL' })
|
|
||||||
src: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '图片名称' })
|
|
||||||
name?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '替代文本' })
|
|
||||||
alt?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnifiedProductDTO {
|
|
||||||
@ApiProperty({ description: '产品ID' })
|
|
||||||
id: string | number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '产品名称' })
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '产品类型' })
|
|
||||||
type: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '产品状态' })
|
|
||||||
status: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '产品SKU' })
|
|
||||||
sku: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '常规价格' })
|
|
||||||
regular_price: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '销售价格' })
|
|
||||||
sale_price: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '当前价格' })
|
|
||||||
price: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '库存状态' })
|
|
||||||
stock_status: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '库存数量' })
|
|
||||||
stock_quantity: number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '产品图片', type: [UnifiedImageDTO] })
|
|
||||||
images: UnifiedImageDTO[];
|
|
||||||
|
|
||||||
@ApiProperty({ description: '产品标签', type: 'json' })
|
|
||||||
tags?: string[];
|
|
||||||
|
|
||||||
@ApiProperty({ description: '产品属性', type: 'json' })
|
|
||||||
attributes: any[];
|
|
||||||
|
|
||||||
@ApiProperty({ description: '产品变体', type: 'json' })
|
|
||||||
variations?: any[];
|
|
||||||
|
|
||||||
@ApiProperty({ description: '创建时间' })
|
|
||||||
date_created: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '更新时间' })
|
|
||||||
date_modified: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '原始数据(保留备用)', type: 'json' })
|
|
||||||
raw?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnifiedOrderDTO {
|
|
||||||
@ApiProperty({ description: '订单ID' })
|
|
||||||
id: string | number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '订单号' })
|
|
||||||
number: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '订单状态' })
|
|
||||||
status: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '货币' })
|
|
||||||
currency: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '总金额' })
|
|
||||||
total: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '客户ID' })
|
|
||||||
customer_id: number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '客户姓名' })
|
|
||||||
customer_name: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '客户邮箱' })
|
|
||||||
email: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '订单项', type: 'json' })
|
|
||||||
line_items: any[];
|
|
||||||
|
|
||||||
@ApiProperty({ description: '销售项(兼容前端)', type: 'json' })
|
|
||||||
sales?: any[];
|
|
||||||
|
|
||||||
@ApiProperty({ description: '账单地址', type: 'json' })
|
|
||||||
billing: any;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '收货地址', type: 'json' })
|
|
||||||
shipping: any;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '账单地址全称' })
|
|
||||||
billing_full_address?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '收货地址全称' })
|
|
||||||
shipping_full_address?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '支付方式' })
|
|
||||||
payment_method: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '创建时间' })
|
|
||||||
date_created: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '原始数据', type: 'json' })
|
|
||||||
raw?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnifiedCustomerDTO {
|
|
||||||
@ApiProperty({ description: '客户ID' })
|
|
||||||
id: string | number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '邮箱' })
|
|
||||||
email: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '名' })
|
|
||||||
first_name?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '姓' })
|
|
||||||
last_name?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '名字' })
|
|
||||||
fullname?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '用户名' })
|
|
||||||
username?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '电话' })
|
|
||||||
phone?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '账单地址', type: 'json' })
|
|
||||||
billing?: any;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '收货地址', type: 'json' })
|
|
||||||
shipping?: any;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '原始数据', type: 'json' })
|
|
||||||
raw?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnifiedSubscriptionDTO {
|
|
||||||
@ApiProperty({ description: '订阅ID' })
|
|
||||||
id: string | number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '订阅状态' })
|
|
||||||
status: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '客户ID' })
|
|
||||||
customer_id: number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '计费周期' })
|
|
||||||
billing_period: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '计费间隔' })
|
|
||||||
billing_interval: number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '开始时间' })
|
|
||||||
start_date: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '下次支付时间' })
|
|
||||||
next_payment_date: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '订单项', type: 'json' })
|
|
||||||
line_items: any[];
|
|
||||||
|
|
||||||
@ApiProperty({ description: '原始数据', type: 'json' })
|
|
||||||
raw?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnifiedMediaDTO {
|
|
||||||
@ApiProperty({ description: '媒体ID' })
|
|
||||||
id: number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '标题' })
|
|
||||||
title: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '媒体类型' })
|
|
||||||
media_type: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: 'MIME类型' })
|
|
||||||
mime_type: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '源URL' })
|
|
||||||
source_url: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '创建时间' })
|
|
||||||
date_created: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnifiedProductPaginationDTO extends UnifiedPaginationDTO<UnifiedProductDTO> {
|
|
||||||
@ApiProperty({ description: '列表数据', type: [UnifiedProductDTO] })
|
|
||||||
items: UnifiedProductDTO[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnifiedOrderPaginationDTO extends UnifiedPaginationDTO<UnifiedOrderDTO> {
|
|
||||||
@ApiProperty({ description: '列表数据', type: [UnifiedOrderDTO] })
|
|
||||||
items: UnifiedOrderDTO[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnifiedCustomerPaginationDTO extends UnifiedPaginationDTO<UnifiedCustomerDTO> {
|
|
||||||
@ApiProperty({ description: '列表数据', type: [UnifiedCustomerDTO] })
|
|
||||||
items: UnifiedCustomerDTO[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnifiedSubscriptionPaginationDTO extends UnifiedPaginationDTO<UnifiedSubscriptionDTO> {
|
|
||||||
@ApiProperty({ description: '列表数据', type: [UnifiedSubscriptionDTO] })
|
|
||||||
items: UnifiedSubscriptionDTO[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnifiedMediaPaginationDTO extends UnifiedPaginationDTO<UnifiedMediaDTO> {
|
|
||||||
@ApiProperty({ description: '列表数据', type: [UnifiedMediaDTO] })
|
|
||||||
items: UnifiedMediaDTO[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export class UnifiedSearchParamsDTO {
|
|
||||||
@ApiProperty({ description: '页码', example: 1 })
|
|
||||||
page?: number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '每页数量', example: 20 })
|
|
||||||
per_page?: number;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '搜索关键词' })
|
|
||||||
search?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '状态' })
|
|
||||||
status?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '排序字段' })
|
|
||||||
orderby?: string;
|
|
||||||
|
|
||||||
@ApiProperty({ description: '排序方式' })
|
|
||||||
order?: string;
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { ApiProperty } from '@midwayjs/swagger';
|
import { ApiProperty } from '@midwayjs/swagger';
|
||||||
|
|
||||||
export class UnifiedPaginationDTO<T> {
|
export class UnifiedPaginationDTO<T> {
|
||||||
|
// 分页DTO用于承载统一分页信息与列表数据
|
||||||
@ApiProperty({ description: '列表数据' })
|
@ApiProperty({ description: '列表数据' })
|
||||||
items: T[];
|
items: T[];
|
||||||
|
|
||||||
|
|
@ -13,11 +14,15 @@ export class UnifiedPaginationDTO<T> {
|
||||||
@ApiProperty({ description: '每页数量', example: 20 })
|
@ApiProperty({ description: '每页数量', example: 20 })
|
||||||
per_page: number;
|
per_page: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '每页数量别名', example: 20 })
|
||||||
|
page_size?: number;
|
||||||
|
|
||||||
@ApiProperty({ description: '总页数', example: 5 })
|
@ApiProperty({ description: '总页数', example: 5 })
|
||||||
totalPages: number;
|
totalPages: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedImageDTO {
|
export class UnifiedImageDTO {
|
||||||
|
// 图片DTO用于承载统一图片数据
|
||||||
@ApiProperty({ description: '图片ID' })
|
@ApiProperty({ description: '图片ID' })
|
||||||
id: number | string;
|
id: number | string;
|
||||||
|
|
||||||
|
|
@ -32,6 +37,7 @@ export class UnifiedImageDTO {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedProductDTO {
|
export class UnifiedProductDTO {
|
||||||
|
// 产品DTO用于承载统一产品数据
|
||||||
@ApiProperty({ description: '产品ID' })
|
@ApiProperty({ description: '产品ID' })
|
||||||
id: string | number;
|
id: string | number;
|
||||||
|
|
||||||
|
|
@ -85,6 +91,7 @@ export class UnifiedProductDTO {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedOrderDTO {
|
export class UnifiedOrderDTO {
|
||||||
|
// 订单DTO用于承载统一订单数据
|
||||||
@ApiProperty({ description: '订单ID' })
|
@ApiProperty({ description: '订单ID' })
|
||||||
id: string | number;
|
id: string | number;
|
||||||
|
|
||||||
|
|
@ -133,17 +140,36 @@ export class UnifiedOrderDTO {
|
||||||
@ApiProperty({ description: '创建时间' })
|
@ApiProperty({ description: '创建时间' })
|
||||||
date_created: string;
|
date_created: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新时间' })
|
||||||
|
date_modified?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '原始数据', type: 'json' })
|
@ApiProperty({ description: '原始数据', type: 'json' })
|
||||||
raw?: any;
|
raw?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedCustomerDTO {
|
export class UnifiedCustomerDTO {
|
||||||
|
// 客户DTO用于承载统一客户数据
|
||||||
@ApiProperty({ description: '客户ID' })
|
@ApiProperty({ description: '客户ID' })
|
||||||
id: string | number;
|
id: string | number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '头像URL' })
|
||||||
|
avatar?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '邮箱' })
|
@ApiProperty({ description: '邮箱' })
|
||||||
email: string;
|
email: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '订单总数' })
|
||||||
|
orders?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '总花费' })
|
||||||
|
total_spend?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间' })
|
||||||
|
date_created?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新时间' })
|
||||||
|
date_modified?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '名' })
|
@ApiProperty({ description: '名' })
|
||||||
first_name?: string;
|
first_name?: string;
|
||||||
|
|
||||||
|
|
@ -170,6 +196,7 @@ export class UnifiedCustomerDTO {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedSubscriptionDTO {
|
export class UnifiedSubscriptionDTO {
|
||||||
|
// 订阅DTO用于承载统一订阅数据
|
||||||
@ApiProperty({ description: '订阅ID' })
|
@ApiProperty({ description: '订阅ID' })
|
||||||
id: string | number;
|
id: string | number;
|
||||||
|
|
||||||
|
|
@ -185,6 +212,12 @@ export class UnifiedSubscriptionDTO {
|
||||||
@ApiProperty({ description: '计费间隔' })
|
@ApiProperty({ description: '计费间隔' })
|
||||||
billing_interval: number;
|
billing_interval: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间' })
|
||||||
|
date_created?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新时间' })
|
||||||
|
date_modified?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '开始时间' })
|
@ApiProperty({ description: '开始时间' })
|
||||||
start_date: string;
|
start_date: string;
|
||||||
|
|
||||||
|
|
@ -199,6 +232,7 @@ export class UnifiedSubscriptionDTO {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedMediaDTO {
|
export class UnifiedMediaDTO {
|
||||||
|
// 媒体DTO用于承载统一媒体数据
|
||||||
@ApiProperty({ description: '媒体ID' })
|
@ApiProperty({ description: '媒体ID' })
|
||||||
id: number;
|
id: number;
|
||||||
|
|
||||||
|
|
@ -216,49 +250,73 @@ export class UnifiedMediaDTO {
|
||||||
|
|
||||||
@ApiProperty({ description: '创建时间' })
|
@ApiProperty({ description: '创建时间' })
|
||||||
date_created: string;
|
date_created: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '更新时间' })
|
||||||
|
date_modified?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedProductPaginationDTO extends UnifiedPaginationDTO<UnifiedProductDTO> {
|
export class UnifiedProductPaginationDTO extends UnifiedPaginationDTO<UnifiedProductDTO> {
|
||||||
|
// 产品分页DTO用于承载产品列表分页数据
|
||||||
@ApiProperty({ description: '列表数据', type: [UnifiedProductDTO] })
|
@ApiProperty({ description: '列表数据', type: [UnifiedProductDTO] })
|
||||||
items: UnifiedProductDTO[];
|
items: UnifiedProductDTO[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedOrderPaginationDTO extends UnifiedPaginationDTO<UnifiedOrderDTO> {
|
export class UnifiedOrderPaginationDTO extends UnifiedPaginationDTO<UnifiedOrderDTO> {
|
||||||
|
// 订单分页DTO用于承载订单列表分页数据
|
||||||
@ApiProperty({ description: '列表数据', type: [UnifiedOrderDTO] })
|
@ApiProperty({ description: '列表数据', type: [UnifiedOrderDTO] })
|
||||||
items: UnifiedOrderDTO[];
|
items: UnifiedOrderDTO[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedCustomerPaginationDTO extends UnifiedPaginationDTO<UnifiedCustomerDTO> {
|
export class UnifiedCustomerPaginationDTO extends UnifiedPaginationDTO<UnifiedCustomerDTO> {
|
||||||
|
// 客户分页DTO用于承载客户列表分页数据
|
||||||
@ApiProperty({ description: '列表数据', type: [UnifiedCustomerDTO] })
|
@ApiProperty({ description: '列表数据', type: [UnifiedCustomerDTO] })
|
||||||
items: UnifiedCustomerDTO[];
|
items: UnifiedCustomerDTO[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedSubscriptionPaginationDTO extends UnifiedPaginationDTO<UnifiedSubscriptionDTO> {
|
export class UnifiedSubscriptionPaginationDTO extends UnifiedPaginationDTO<UnifiedSubscriptionDTO> {
|
||||||
|
// 订阅分页DTO用于承载订阅列表分页数据
|
||||||
@ApiProperty({ description: '列表数据', type: [UnifiedSubscriptionDTO] })
|
@ApiProperty({ description: '列表数据', type: [UnifiedSubscriptionDTO] })
|
||||||
items: UnifiedSubscriptionDTO[];
|
items: UnifiedSubscriptionDTO[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedMediaPaginationDTO extends UnifiedPaginationDTO<UnifiedMediaDTO> {
|
export class UnifiedMediaPaginationDTO extends UnifiedPaginationDTO<UnifiedMediaDTO> {
|
||||||
|
// 媒体分页DTO用于承载媒体列表分页数据
|
||||||
@ApiProperty({ description: '列表数据', type: [UnifiedMediaDTO] })
|
@ApiProperty({ description: '列表数据', type: [UnifiedMediaDTO] })
|
||||||
items: UnifiedMediaDTO[];
|
items: UnifiedMediaDTO[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UnifiedSearchParamsDTO {
|
export class UnifiedSearchParamsDTO {
|
||||||
|
// 统一查询参数DTO用于承载分页与筛选与排序参数
|
||||||
@ApiProperty({ description: '页码', example: 1 })
|
@ApiProperty({ description: '页码', example: 1 })
|
||||||
page?: number;
|
page?: number;
|
||||||
|
|
||||||
@ApiProperty({ description: '每页数量', example: 20 })
|
@ApiProperty({ description: '每页数量', example: 20 })
|
||||||
per_page?: number;
|
per_page?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '每页数量别名', example: 20 })
|
||||||
|
page_size?: number;
|
||||||
|
|
||||||
@ApiProperty({ description: '搜索关键词' })
|
@ApiProperty({ description: '搜索关键词' })
|
||||||
search?: string;
|
search?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '状态' })
|
@ApiProperty({ description: '状态' })
|
||||||
status?: string;
|
status?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '排序字段' })
|
@ApiProperty({ description: '客户ID,用于筛选订单' })
|
||||||
|
customer_id?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '过滤条件对象' })
|
||||||
|
where?: Record<string, any>;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '排序对象,例如 { "sku": "desc" }' })
|
||||||
|
order?: Record<string, 'asc' | 'desc'> | string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '排序字段(兼容旧入参)' })
|
||||||
orderby?: string;
|
orderby?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '排序方式' })
|
@ApiProperty({ description: '排序方式(兼容旧入参)' })
|
||||||
order?: string;
|
orderDir?: 'asc' | 'desc';
|
||||||
|
|
||||||
|
@ApiProperty({ description: '选中ID列表,逗号分隔', required: false })
|
||||||
|
ids?: string;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,389 @@
|
||||||
|
// WooCommerce 平台原始数据类型定义
|
||||||
|
// 仅包含当前映射逻辑所需字段以保持简洁与类型安全
|
||||||
|
|
||||||
|
// 产品类型
|
||||||
|
export interface WooProduct {
|
||||||
|
// 产品主键
|
||||||
|
id: number;
|
||||||
|
// 创建时间
|
||||||
|
date_created: string;
|
||||||
|
// 创建时间(GMT)
|
||||||
|
date_created_gmt: string;
|
||||||
|
// 更新时间
|
||||||
|
date_modified: string;
|
||||||
|
// 更新时间(GMT)
|
||||||
|
date_modified_gmt: string;
|
||||||
|
// 产品类型 simple grouped external variable
|
||||||
|
type: string;
|
||||||
|
// 产品状态 draft pending private publish
|
||||||
|
status: string;
|
||||||
|
// 是否为特色产品
|
||||||
|
featured: boolean;
|
||||||
|
// 目录可见性选项:visible, catalog, search and hidden. Default is visible.
|
||||||
|
catalog_visibility: string;
|
||||||
|
|
||||||
|
// 常规价格
|
||||||
|
regular_price?: string;
|
||||||
|
// 促销价格
|
||||||
|
sale_price?: string;
|
||||||
|
// 当前价格
|
||||||
|
price?: string;
|
||||||
|
price_html?: string;
|
||||||
|
date_on_sale_from?: string; // Date the product is on sale from.
|
||||||
|
date_on_sale_from_gmt?: string; // Date the product is on sale from (GMT).
|
||||||
|
date_on_sale_to?: string; // Date the product is on sale to.
|
||||||
|
date_on_sale_to_gmt?: string; // Date the product is on sale to (GMT).
|
||||||
|
on_sale: boolean; // Whether the product is on sale.
|
||||||
|
purchasable: boolean; // Whether the product is purchasable.
|
||||||
|
total_sales: number; // Total sales for this product.
|
||||||
|
virtual: boolean; // Whether the product is virtual.
|
||||||
|
downloadable: boolean; // Whether the product is downloadable.
|
||||||
|
downloads: Array<{ id?: number; name?: string; file?: string }>; // Downloadable files for the product.
|
||||||
|
download_limit: number; // Download limit.
|
||||||
|
download_expiry: number; // Download expiry days.
|
||||||
|
external_url: string; // URL of the external product.
|
||||||
|
|
||||||
|
global_unique_id: string; // GTIN, UPC, EAN or ISBN - a unique identifier for each distinct product and service that can be purchased.
|
||||||
|
// 产品SKU
|
||||||
|
sku: string;
|
||||||
|
// 产品名称
|
||||||
|
name: string;
|
||||||
|
// 产品描述
|
||||||
|
description: string;
|
||||||
|
// 产品短描述
|
||||||
|
short_description: string;
|
||||||
|
|
||||||
|
// 产品永久链接
|
||||||
|
permalink: string;
|
||||||
|
// 产品URL路径
|
||||||
|
slug: string;
|
||||||
|
|
||||||
|
// 库存状态
|
||||||
|
stock_status?: 'instock' | 'outofstock' | 'onbackorder';
|
||||||
|
// 库存数量
|
||||||
|
stock_quantity?: number;
|
||||||
|
// 是否管理库存
|
||||||
|
manage_stock?: boolean;
|
||||||
|
// 缺货预定设置 no notify yes
|
||||||
|
backorders?: 'no' | 'notify' | 'yes';
|
||||||
|
// 是否允许缺货预定 只读
|
||||||
|
backorders_allowed?: boolean;
|
||||||
|
// 是否处于缺货预定状态 只读
|
||||||
|
backordered?: boolean;
|
||||||
|
// 是否单独出售
|
||||||
|
sold_individually?: boolean;
|
||||||
|
// 重量
|
||||||
|
weight?: string;
|
||||||
|
// 尺寸
|
||||||
|
dimensions?: { length?: string; width?: string; height?: string };
|
||||||
|
// 是否需要运输 只读
|
||||||
|
shipping_required?: boolean;
|
||||||
|
// 运输是否计税 只读
|
||||||
|
shipping_taxable?: boolean;
|
||||||
|
// 运输类别 slug
|
||||||
|
shipping_class?: string;
|
||||||
|
// 运输类别ID 只读
|
||||||
|
shipping_class_id?: number;
|
||||||
|
// 图片列表
|
||||||
|
images?: Array<{ id: number; src: string; name?: string; alt?: string }>;
|
||||||
|
// 属性列表
|
||||||
|
attributes?: Array<{
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
position?: number;
|
||||||
|
visible?: boolean;
|
||||||
|
variation?: boolean;
|
||||||
|
options?: string[];
|
||||||
|
}>;
|
||||||
|
// 变体列表
|
||||||
|
variations?: number[];
|
||||||
|
// 默认变体属性
|
||||||
|
default_attributes?: Array<{ id?: number; name?: string; option?: string }>;
|
||||||
|
// 允许评论
|
||||||
|
reviews_allowed?: boolean;
|
||||||
|
// 平均评分 只读
|
||||||
|
average_rating?: string;
|
||||||
|
// 评分数量 只读
|
||||||
|
rating_count?: number;
|
||||||
|
// 相关产品ID列表 只读
|
||||||
|
related_ids?: number[];
|
||||||
|
// 追加销售产品ID列表
|
||||||
|
upsell_ids?: number[];
|
||||||
|
// 交叉销售产品ID列表
|
||||||
|
cross_sell_ids?: number[];
|
||||||
|
// 父产品ID
|
||||||
|
parent_id?: number;
|
||||||
|
// 购买备注
|
||||||
|
purchase_note?: string;
|
||||||
|
// 分类列表
|
||||||
|
categories?: Array<{ id: number; name?: string; slug?: string }>;
|
||||||
|
// 标签列表
|
||||||
|
tags?: Array<{ id: number; name?: string; slug?: string }>;
|
||||||
|
// 菜单排序
|
||||||
|
menu_order?: number;
|
||||||
|
// 元数据
|
||||||
|
meta_data?: Array<{ id?: number; key: string; value: any }>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订单类型
|
||||||
|
export interface WooOrder {
|
||||||
|
// 订单主键
|
||||||
|
id: number;
|
||||||
|
// 父订单ID
|
||||||
|
parent_id?: number;
|
||||||
|
// 订单号
|
||||||
|
number: string;
|
||||||
|
// 订单键 只读
|
||||||
|
order_key?: string;
|
||||||
|
// 创建来源
|
||||||
|
created_via?: string;
|
||||||
|
// WooCommerce版本 只读
|
||||||
|
version?: string;
|
||||||
|
// 状态
|
||||||
|
status: string;
|
||||||
|
// 币种
|
||||||
|
currency: string;
|
||||||
|
// 价格是否含税 只读
|
||||||
|
prices_include_tax?: boolean;
|
||||||
|
// 总金额
|
||||||
|
total: string;
|
||||||
|
// 总税额 只读
|
||||||
|
total_tax?: string;
|
||||||
|
// 折扣总额 只读
|
||||||
|
discount_total?: string;
|
||||||
|
// 折扣税额 只读
|
||||||
|
discount_tax?: string;
|
||||||
|
// 运费总额 只读
|
||||||
|
shipping_total?: string;
|
||||||
|
// 运费税额 只读
|
||||||
|
shipping_tax?: string;
|
||||||
|
// 购物车税额 只读
|
||||||
|
cart_tax?: string;
|
||||||
|
// 客户ID
|
||||||
|
customer_id: number;
|
||||||
|
// 客户IP 只读
|
||||||
|
customer_ip_address?: string;
|
||||||
|
// 客户UA 只读
|
||||||
|
customer_user_agent?: string;
|
||||||
|
// 客户备注
|
||||||
|
customer_note?: string;
|
||||||
|
// 账单信息
|
||||||
|
billing?: {
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
email?: string;
|
||||||
|
company?: string;
|
||||||
|
address_1?: string;
|
||||||
|
address_2?: string;
|
||||||
|
city?: string;
|
||||||
|
state?: string;
|
||||||
|
postcode?: string;
|
||||||
|
country?: string;
|
||||||
|
phone?: string;
|
||||||
|
fullname?: string;
|
||||||
|
};
|
||||||
|
// 收货信息
|
||||||
|
shipping?: {
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
company?: string;
|
||||||
|
address_1?: string;
|
||||||
|
address_2?: string;
|
||||||
|
city?: string;
|
||||||
|
state?: string;
|
||||||
|
postcode?: string;
|
||||||
|
country?: string;
|
||||||
|
phone?: string;
|
||||||
|
fullname?: string;
|
||||||
|
};
|
||||||
|
// 订单项
|
||||||
|
line_items?: Array<{
|
||||||
|
product_id?: number;
|
||||||
|
variation_id?: number;
|
||||||
|
quantity?: number;
|
||||||
|
subtotal?: string;
|
||||||
|
subtotal_tax?: string;
|
||||||
|
total?: string;
|
||||||
|
total_tax?: string;
|
||||||
|
name?: string;
|
||||||
|
sku?: string;
|
||||||
|
price?: number;
|
||||||
|
meta_data?: Array<{ key: string; value: any }>;
|
||||||
|
[key: string]: any;
|
||||||
|
}>;
|
||||||
|
// 税费行 只读
|
||||||
|
tax_lines?: Array<{
|
||||||
|
id?: number;
|
||||||
|
rate_code?: string;
|
||||||
|
rate_id?: number;
|
||||||
|
label?: string;
|
||||||
|
tax_total?: string;
|
||||||
|
shipping_tax_total?: string;
|
||||||
|
compound?: boolean;
|
||||||
|
meta_data?: any[];
|
||||||
|
}>;
|
||||||
|
// 物流费用行
|
||||||
|
shipping_lines?: Array<{
|
||||||
|
id?: number;
|
||||||
|
method_title?: string;
|
||||||
|
method_id?: string;
|
||||||
|
total?: string;
|
||||||
|
total_tax?: string;
|
||||||
|
taxes?: any[];
|
||||||
|
meta_data?: any[];
|
||||||
|
}>;
|
||||||
|
// 手续费行
|
||||||
|
fee_lines?: Array<{
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
tax_class?: string;
|
||||||
|
tax_status?: string;
|
||||||
|
total?: string;
|
||||||
|
total_tax?: string;
|
||||||
|
taxes?: any[];
|
||||||
|
meta_data?: any[];
|
||||||
|
}>;
|
||||||
|
// 优惠券行
|
||||||
|
coupon_lines?: Array<{
|
||||||
|
id?: number;
|
||||||
|
code?: string;
|
||||||
|
discount?: string;
|
||||||
|
discount_tax?: string;
|
||||||
|
meta_data?: any[];
|
||||||
|
}>;
|
||||||
|
// 退款列表 只读
|
||||||
|
refunds?: Array<{
|
||||||
|
id?: number;
|
||||||
|
reason?: string;
|
||||||
|
total?: string;
|
||||||
|
[key: string]: any;
|
||||||
|
}>;
|
||||||
|
// 支付方式标题
|
||||||
|
payment_method_title?: string;
|
||||||
|
// 支付方式ID
|
||||||
|
payment_method?: string;
|
||||||
|
// 交易ID
|
||||||
|
transaction_id?: string;
|
||||||
|
// 已支付时间
|
||||||
|
date_paid?: string;
|
||||||
|
date_paid_gmt?: string;
|
||||||
|
// 完成时间
|
||||||
|
date_completed?: string;
|
||||||
|
date_completed_gmt?: string;
|
||||||
|
// 购物车hash 只读
|
||||||
|
cart_hash?: string;
|
||||||
|
// 设置为已支付 写入专用
|
||||||
|
set_paid?: boolean;
|
||||||
|
// 元数据
|
||||||
|
meta_data?: Array<{ id?: number; key: string; value: any }>;
|
||||||
|
// 创建与更新时间
|
||||||
|
date_created: string;
|
||||||
|
date_created_gmt?: string;
|
||||||
|
date_modified?: string;
|
||||||
|
date_modified_gmt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 订阅类型
|
||||||
|
export interface WooSubscription {
|
||||||
|
// 订阅主键
|
||||||
|
id: number;
|
||||||
|
// 订阅状态
|
||||||
|
status: string;
|
||||||
|
// 客户ID
|
||||||
|
customer_id: number;
|
||||||
|
// 计费周期
|
||||||
|
billing_period?: string;
|
||||||
|
// 计费间隔
|
||||||
|
billing_interval?: number;
|
||||||
|
// 开始时间
|
||||||
|
start_date?: string;
|
||||||
|
// 下次支付时间
|
||||||
|
next_payment_date?: string;
|
||||||
|
// 订阅项
|
||||||
|
line_items?: any[];
|
||||||
|
// 创建时间
|
||||||
|
date_created?: string;
|
||||||
|
// 更新时间
|
||||||
|
date_modified?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// WordPress 媒体类型
|
||||||
|
export interface WpMedia {
|
||||||
|
// 媒体主键
|
||||||
|
id: number;
|
||||||
|
// 标题可能为字符串或包含rendered的对象
|
||||||
|
title?: { rendered?: string } | string;
|
||||||
|
// 媒体类型
|
||||||
|
media_type?: string;
|
||||||
|
// MIME类型
|
||||||
|
mime_type?: string;
|
||||||
|
// 源地址
|
||||||
|
source_url?: string;
|
||||||
|
// 创建时间兼容date字段
|
||||||
|
date_created?: string;
|
||||||
|
date?: string;
|
||||||
|
// 更新时间兼容modified字段
|
||||||
|
date_modified?: string;
|
||||||
|
modified?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 客户类型
|
||||||
|
export interface WooCustomer {
|
||||||
|
// 客户主键
|
||||||
|
id: number;
|
||||||
|
// 头像URL
|
||||||
|
avatar_url?: string;
|
||||||
|
// 邮箱
|
||||||
|
email: string;
|
||||||
|
// 订单总数
|
||||||
|
orders?: number;
|
||||||
|
// 总花费
|
||||||
|
total_spent?: number | string;
|
||||||
|
// 名
|
||||||
|
first_name?: string;
|
||||||
|
// 姓
|
||||||
|
last_name?: string;
|
||||||
|
// 用户名
|
||||||
|
username?: string;
|
||||||
|
// 角色 只读
|
||||||
|
role?: string;
|
||||||
|
// 密码 写入专用
|
||||||
|
password?: string;
|
||||||
|
// 账单信息
|
||||||
|
billing?: {
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
email?: string;
|
||||||
|
company?: string;
|
||||||
|
phone?: string;
|
||||||
|
address_1?: string;
|
||||||
|
address_2?: string;
|
||||||
|
city?: string;
|
||||||
|
state?: string;
|
||||||
|
postcode?: string;
|
||||||
|
country?: string;
|
||||||
|
};
|
||||||
|
// 收货信息
|
||||||
|
shipping?: {
|
||||||
|
first_name?: string;
|
||||||
|
last_name?: string;
|
||||||
|
company?: string;
|
||||||
|
phone?: string;
|
||||||
|
address_1?: string;
|
||||||
|
address_2?: string;
|
||||||
|
city?: string;
|
||||||
|
state?: string;
|
||||||
|
postcode?: string;
|
||||||
|
country?: string;
|
||||||
|
};
|
||||||
|
// 是否为付费客户 只读
|
||||||
|
is_paying_customer?: boolean;
|
||||||
|
// 元数据
|
||||||
|
meta_data?: Array<{ id?: number; key: string; value: any }>;
|
||||||
|
// 创建时间
|
||||||
|
date_created?: string;
|
||||||
|
date_created_gmt?: string;
|
||||||
|
// 更新时间
|
||||||
|
date_modified?: string;
|
||||||
|
date_modified_gmt?: string;
|
||||||
|
}
|
||||||
|
|
@ -74,11 +74,28 @@ export class ShopyyService implements IPlatformService {
|
||||||
* 通用分页获取资源
|
* 通用分页获取资源
|
||||||
*/
|
*/
|
||||||
public async fetchResourcePaged<T>(site: any, endpoint: string, params: Record<string, any> = {}) {
|
public async fetchResourcePaged<T>(site: any, endpoint: string, params: Record<string, any> = {}) {
|
||||||
// 映射 params 字段: page -> page, per_page -> limit
|
const page = Number(params.page || 1);
|
||||||
|
const limit = Number(params.page_size ?? params.per_page ?? 20);
|
||||||
|
const where = params.where && typeof params.where === 'object' ? params.where : {};
|
||||||
|
let orderby: string | undefined = params.orderby;
|
||||||
|
let order: 'asc' | 'desc' | undefined = params.orderDir as any;
|
||||||
|
if (!orderby && params.order && typeof params.order === 'object') {
|
||||||
|
const entries = Object.entries(params.order as Record<string, any>);
|
||||||
|
if (entries.length > 0) {
|
||||||
|
const [field, dir] = entries[0];
|
||||||
|
orderby = field;
|
||||||
|
order = String(dir).toLowerCase() === 'desc' ? 'desc' : 'asc';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 映射统一入参到平台入参
|
||||||
const requestParams = {
|
const requestParams = {
|
||||||
...params,
|
...where,
|
||||||
page: params.page || 1,
|
...(params.search ? { search: params.search } : {}),
|
||||||
limit: params.per_page || 20
|
...(params.status ? { status: params.status } : {}),
|
||||||
|
...(orderby ? { orderby } : {}),
|
||||||
|
...(order ? { order } : {}),
|
||||||
|
page,
|
||||||
|
limit
|
||||||
};
|
};
|
||||||
const response = await this.request(site, endpoint, 'GET', null, requestParams);
|
const response = await this.request(site, endpoint, 'GET', null, requestParams);
|
||||||
if (response?.code !== 0) {
|
if (response?.code !== 0) {
|
||||||
|
|
@ -89,7 +106,8 @@ export class ShopyyService implements IPlatformService {
|
||||||
total: response.data?.paginate?.total || 0,
|
total: response.data?.paginate?.total || 0,
|
||||||
totalPages: response.data?.paginate?.pageTotal || 0,
|
totalPages: response.data?.paginate?.pageTotal || 0,
|
||||||
page: response.data?.paginate?.current || requestParams.page,
|
page: response.data?.paginate?.current || requestParams.page,
|
||||||
per_page: response.data?.paginate?.pagesize || requestParams.limit
|
per_page: response.data?.paginate?.pagesize || requestParams.limit,
|
||||||
|
page_size: response.data?.paginate?.pagesize || requestParams.limit
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,29 @@ export class WPService implements IPlatformService {
|
||||||
*/
|
*/
|
||||||
public async fetchResourcePaged<T>(site: any, resource: string, params: Record<string, any> = {}) {
|
public async fetchResourcePaged<T>(site: any, resource: string, params: Record<string, any> = {}) {
|
||||||
const api = this.createApi(site, 'wc/v3');
|
const api = this.createApi(site, 'wc/v3');
|
||||||
return this.sdkGetPage<T>(api, resource, params);
|
const page = Number(params.page ?? 1);
|
||||||
|
const per_page = Number(params.page_size ?? params.per_page ?? 20);
|
||||||
|
const where = params.where && typeof params.where === 'object' ? params.where : {};
|
||||||
|
let orderby: string | undefined = params.orderby;
|
||||||
|
let order: 'asc' | 'desc' | undefined = params.orderDir as any;
|
||||||
|
if (!orderby && params.order && typeof params.order === 'object') {
|
||||||
|
const entries = Object.entries(params.order as Record<string, any>);
|
||||||
|
if (entries.length > 0) {
|
||||||
|
const [field, dir] = entries[0];
|
||||||
|
orderby = field;
|
||||||
|
order = String(dir).toLowerCase() === 'desc' ? 'desc' : 'asc';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const requestParams = {
|
||||||
|
...where,
|
||||||
|
...(params.search ? { search: params.search } : {}),
|
||||||
|
...(params.status ? { status: params.status } : {}),
|
||||||
|
...(orderby ? { orderby } : {}),
|
||||||
|
...(order ? { order } : {}),
|
||||||
|
page,
|
||||||
|
per_page
|
||||||
|
};
|
||||||
|
return this.sdkGetPage<T>(api, resource, requestParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -59,7 +81,7 @@ export class WPService implements IPlatformService {
|
||||||
*/
|
*/
|
||||||
private async sdkGetPage<T>(api: any, resource: string, params: Record<string, any> = {}) {
|
private async sdkGetPage<T>(api: any, resource: string, params: Record<string, any> = {}) {
|
||||||
const page = params.page ?? 1;
|
const page = params.page ?? 1;
|
||||||
const per_page = params.per_page ?? 100;
|
const per_page = params.per_page ?? params.page_size ?? 100;
|
||||||
const res = await api.get(resource.replace(/^\/+/, ''), { ...params, page, per_page });
|
const res = await api.get(resource.replace(/^\/+/, ''), { ...params, page, per_page });
|
||||||
if (res?.headers?.['content-type']?.includes('text/html')) {
|
if (res?.headers?.['content-type']?.includes('text/html')) {
|
||||||
throw new Error('接口返回了 text/html,可能为 WordPress 登录页或错误页,请检查站点配置或权限');
|
throw new Error('接口返回了 text/html,可能为 WordPress 登录页或错误页,请检查站点配置或权限');
|
||||||
|
|
@ -67,7 +89,7 @@ export class WPService implements IPlatformService {
|
||||||
const data = res.data as T[];
|
const data = res.data as T[];
|
||||||
const totalPages = Number(res.headers?.['x-wp-totalpages'] ?? 1);
|
const totalPages = Number(res.headers?.['x-wp-totalpages'] ?? 1);
|
||||||
const total = Number(res.headers?.['x-wp-total']?? 1)
|
const total = Number(res.headers?.['x-wp-total']?? 1)
|
||||||
return { items: data, total, totalPages, page, per_page };
|
return { items: data, total, totalPages, page, per_page, page_size: per_page };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -630,6 +652,40 @@ export class WPService implements IPlatformService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async fetchMediaPaged(site: any, params: Record<string, any> = {}) {
|
||||||
|
const page = Number(params.page ?? 1);
|
||||||
|
const per_page = Number(params.page_size ?? params.per_page ?? 20);
|
||||||
|
const where = params.where && typeof params.where === 'object' ? params.where : {};
|
||||||
|
let orderby: string | undefined = params.orderby;
|
||||||
|
let order: 'asc' | 'desc' | undefined = params.orderDir as any;
|
||||||
|
if (!orderby && params.order && typeof params.order === 'object') {
|
||||||
|
const entries = Object.entries(params.order as Record<string, any>);
|
||||||
|
if (entries.length > 0) {
|
||||||
|
const [field, dir] = entries[0];
|
||||||
|
orderby = field;
|
||||||
|
order = String(dir).toLowerCase() === 'desc' ? 'desc' : 'asc';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const apiUrl = site.apiUrl;
|
||||||
|
const { consumerKey, consumerSecret } = site as any;
|
||||||
|
const endpoint = 'wp/v2/media';
|
||||||
|
const url = this.buildURL(apiUrl, '/wp-json', endpoint);
|
||||||
|
const auth = Buffer.from(`${consumerKey}:${consumerSecret}`).toString('base64');
|
||||||
|
const response = await axios.get(url, {
|
||||||
|
headers: { Authorization: `Basic ${auth}` },
|
||||||
|
params: {
|
||||||
|
...where,
|
||||||
|
...(params.search ? { search: params.search } : {}),
|
||||||
|
...(orderby ? { orderby } : {}),
|
||||||
|
...(order ? { order } : {}),
|
||||||
|
page,
|
||||||
|
per_page
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const total = Number(response.headers['x-wp-total'] || 0);
|
||||||
|
const totalPages = Number(response.headers['x-wp-totalpages'] || 0);
|
||||||
|
return { items: response.data, total, totalPages, page, per_page, page_size: per_page };
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 上传媒体文件
|
* 上传媒体文件
|
||||||
* @param siteId 站点 ID
|
* @param siteId 站点 ID
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue