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
|
||||
ai
|
||||
tmp_uploads/
|
||||
.trae
|
||||
File diff suppressed because one or more lines are too long
|
|
@ -9,6 +9,12 @@ import {
|
|||
UnifiedSubscriptionDTO,
|
||||
UnifiedCustomerDTO,
|
||||
} from '../dto/site-api.dto';
|
||||
import {
|
||||
ShopyyProduct,
|
||||
ShopyyOrder,
|
||||
ShopyyCustomer,
|
||||
ShopyyVariant,
|
||||
} from '../dto/shopyy.dto';
|
||||
|
||||
export class ShopyyAdapter implements ISiteAdapter {
|
||||
constructor(private site: any, private shopyyService: ShopyyService) { }
|
||||
|
|
@ -16,19 +22,19 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
// return status === 1 ? 'publish' : 'draft';
|
||||
// }
|
||||
|
||||
private mapProduct(item: any): UnifiedProductDTO {
|
||||
private mapProduct(item: ShopyyProduct): UnifiedProductDTO {
|
||||
function mapProductStatus(status: number) {
|
||||
return status === 1 ? 'publish' : 'draft';
|
||||
}
|
||||
return {
|
||||
id: item.id,
|
||||
name: item.name || item.title,
|
||||
type: item.product_type,
|
||||
type: String(item.product_type ?? ''),
|
||||
status: mapProductStatus(item.status),
|
||||
sku: item.variant?.sku || '',
|
||||
regular_price: item.variant?.price,
|
||||
sale_price: item.special_price,
|
||||
price: item.price,
|
||||
regular_price: String(item.variant?.price ?? ''),
|
||||
sale_price: String(item.special_price ?? ''),
|
||||
price: String(item.price ?? ''),
|
||||
stock_status: item.inventory_tracking === 1 ? 'instock' : 'outofstock',
|
||||
stock_quantity: item.inventory_quantity,
|
||||
images: (item.images || []).map((img: any) => ({
|
||||
|
|
@ -42,26 +48,26 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
attributes: [],
|
||||
tags: item.tags || [],
|
||||
variations: item.variants?.map(this.mapVariation.bind(this)) || [],
|
||||
date_created: item.created_at,
|
||||
date_modified: item.updated_at,
|
||||
date_created: typeof item.created_at === 'number' ? new Date(item.created_at * 1000).toISOString() : String(item.created_at ?? ''),
|
||||
date_modified: typeof item.updated_at === 'number' ? new Date(item.updated_at * 1000).toISOString() : String(item.updated_at ?? ''),
|
||||
raw: item,
|
||||
};
|
||||
}
|
||||
mapVariation(mapVariation: any) {
|
||||
mapVariation(mapVariation: ShopyyVariant) {
|
||||
return {
|
||||
id: mapVariation.id,
|
||||
sku: mapVariation.sku || '',
|
||||
regular_price: mapVariation.price,
|
||||
sale_price: mapVariation.special_price,
|
||||
price: mapVariation.price,
|
||||
regular_price: String(mapVariation.price ?? ''),
|
||||
sale_price: String(mapVariation.special_price ?? ''),
|
||||
price: String(mapVariation.price ?? ''),
|
||||
stock_status: mapVariation.inventory_tracking === 1 ? 'instock' : 'outofstock',
|
||||
stock_quantity: mapVariation.inventory_quantity,
|
||||
}
|
||||
}
|
||||
|
||||
private mapOrder(item: any): UnifiedOrderDTO {
|
||||
const billing = item.billing_address || {};
|
||||
const shipping = item.shipping_address || {};
|
||||
private mapOrder(item: ShopyyOrder): UnifiedOrderDTO {
|
||||
const billing = (item as any).billing_address || {};
|
||||
const shipping = (item as any).shipping_address || {};
|
||||
|
||||
const billingObj = {
|
||||
first_name: billing.first_name || item.firstname || '',
|
||||
|
|
@ -69,7 +75,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
fullname: billing.name || `${item.firstname} ${item.lastname}`.trim(),
|
||||
company: billing.company || '',
|
||||
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_2: billing.address2 || '',
|
||||
city: billing.city || item.payment_city || '',
|
||||
|
|
@ -83,7 +89,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
last_name: shipping.last_name || item.lastname || '',
|
||||
fullname: shipping.name || '',
|
||||
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 || '',
|
||||
city: shipping.city || item.shipping_city || '',
|
||||
state: shipping.province || item.shipping_zone || '',
|
||||
|
|
@ -110,7 +116,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
number: item.order_number || item.order_sn,
|
||||
status: String(item.status || item.order_status),
|
||||
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_name: item.customer_name || `${item.firstname} ${item.lastname}`.trim(),
|
||||
email: item.customer_email || item.email,
|
||||
|
|
@ -119,7 +125,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
name: p.product_title || p.name,
|
||||
product_id: p.product_id,
|
||||
quantity: p.quantity,
|
||||
total: String(p.price),
|
||||
total: String(p.price ?? ''),
|
||||
sku: p.sku || p.sku_code || ''
|
||||
})),
|
||||
sales: (item.products || []).map((p: any) => ({
|
||||
|
|
@ -128,7 +134,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
product_id: p.product_id,
|
||||
productId: p.product_id,
|
||||
quantity: p.quantity,
|
||||
total: String(p.price),
|
||||
total: String(p.price ?? ''),
|
||||
sku: p.sku || p.sku_code || ''
|
||||
})),
|
||||
billing: billingObj,
|
||||
|
|
@ -136,12 +142,19 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
billing_full_address: formatAddress(billingObj),
|
||||
shipping_full_address: formatAddress(shippingObj),
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
private mapCustomer(item: any): UnifiedCustomerDTO {
|
||||
private mapCustomer(item: ShopyyCustomer): UnifiedCustomerDTO {
|
||||
// 处理多地址结构
|
||||
const addresses = item.addresses || [];
|
||||
const defaultAddress = item.default_address || (addresses.length > 0 ? addresses[0] : {});
|
||||
|
|
@ -153,6 +166,8 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
|
||||
return {
|
||||
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 || '',
|
||||
last_name: item.last_name || item.lastname || '',
|
||||
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 || '',
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
|
@ -203,6 +226,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
totalPages,
|
||||
page,
|
||||
per_page,
|
||||
page_size: per_page,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -269,6 +293,7 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
totalPages,
|
||||
page,
|
||||
per_page,
|
||||
page_size: per_page,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -310,7 +335,8 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
total,
|
||||
totalPages,
|
||||
page,
|
||||
per_page
|
||||
per_page,
|
||||
page_size: per_page
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,17 +9,31 @@ import {
|
|||
UnifiedSubscriptionDTO,
|
||||
UnifiedCustomerDTO,
|
||||
} from '../dto/site-api.dto';
|
||||
import {
|
||||
WooProduct,
|
||||
WooOrder,
|
||||
WooSubscription,
|
||||
WpMedia,
|
||||
WooCustomer,
|
||||
} from '../dto/woocommerce.dto';
|
||||
|
||||
export class WooCommerceAdapter implements ISiteAdapter {
|
||||
// 构造函数接收站点配置与服务实例
|
||||
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 {
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
type: item.type,
|
||||
status: item.status,
|
||||
date_created: item.date_created,
|
||||
date_modified: item.date_modified,
|
||||
type: item.type, // simple grouped external variable
|
||||
status: item.status, // draft pending private publish
|
||||
sku: item.sku,
|
||||
name: item.name,
|
||||
//价格
|
||||
regular_price: item.regular_price,
|
||||
sale_price: item.sale_price,
|
||||
price: item.price,
|
||||
|
|
@ -33,13 +47,13 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
|||
})),
|
||||
attributes: item.attributes,
|
||||
variations: item.variations,
|
||||
date_created: item.date_created,
|
||||
date_modified: item.date_modified,
|
||||
|
||||
raw: item,
|
||||
};
|
||||
}
|
||||
|
||||
private mapOrder(item: any): UnifiedOrderDTO {
|
||||
private mapOrder(item: WooOrder): UnifiedOrderDTO {
|
||||
// 地址格式化函数用于生成完整地址字符串
|
||||
const formatAddress = (addr: any) => {
|
||||
if (!addr) return '';
|
||||
const name = addr.fullname || `${addr.first_name || ''} ${addr.last_name || ''}`.trim();
|
||||
|
|
@ -56,6 +70,8 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
|||
].filter(Boolean).join(', ');
|
||||
};
|
||||
|
||||
// 将 WooCommerce 订单数据映射为统一订单DTO
|
||||
// 包含账单地址与收货地址以及创建与更新时间
|
||||
return {
|
||||
id: item.id,
|
||||
number: item.number,
|
||||
|
|
@ -79,17 +95,22 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
|||
shipping_full_address: formatAddress(item.shipping),
|
||||
payment_method: item.payment_method_title,
|
||||
date_created: item.date_created,
|
||||
date_modified: item.date_modified,
|
||||
raw: item,
|
||||
};
|
||||
}
|
||||
|
||||
private mapSubscription(item: any): UnifiedSubscriptionDTO {
|
||||
private mapSubscription(item: WooSubscription): UnifiedSubscriptionDTO {
|
||||
// 将 WooCommerce 订阅数据映射为统一订阅DTO
|
||||
// 若缺少创建时间则回退为开始时间
|
||||
return {
|
||||
id: item.id,
|
||||
status: item.status,
|
||||
customer_id: item.customer_id,
|
||||
billing_period: item.billing_period,
|
||||
billing_interval: item.billing_interval,
|
||||
date_created: item.date_created ?? item.start_date,
|
||||
date_modified: item.date_modified,
|
||||
start_date: item.start_date,
|
||||
next_payment_date: item.next_payment_date,
|
||||
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 {
|
||||
id: item.id,
|
||||
title: item.title?.rendered || '',
|
||||
title:
|
||||
typeof item.title === 'string'
|
||||
? item.title
|
||||
: item.title?.rendered || '',
|
||||
media_type: item.media_type,
|
||||
mime_type: item.mime_type,
|
||||
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(
|
||||
params: UnifiedSearchParamsDTO
|
||||
): Promise<UnifiedPaginationDTO<UnifiedProductDTO>> {
|
||||
// 获取产品列表并使用统一分页结构返回
|
||||
const { items, total, totalPages, page, per_page } =
|
||||
await this.wpService.fetchResourcePaged<any>(
|
||||
this.site,
|
||||
|
|
@ -123,43 +151,51 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
|||
totalPages,
|
||||
page,
|
||||
per_page,
|
||||
page_size: per_page,
|
||||
};
|
||||
}
|
||||
|
||||
async getProduct(id: string | number): Promise<UnifiedProductDTO> {
|
||||
// 获取单个产品详情并映射为统一产品DTO
|
||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||
const res = await api.get(`products/${id}`);
|
||||
return this.mapProduct(res.data);
|
||||
}
|
||||
|
||||
async createProduct(data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO> {
|
||||
// 创建产品并返回统一产品DTO
|
||||
const res = await this.wpService.createProduct(this.site, data);
|
||||
return this.mapProduct(res);
|
||||
}
|
||||
|
||||
async updateProduct(id: string | number, data: Partial<UnifiedProductDTO>): Promise<UnifiedProductDTO> {
|
||||
// 更新产品并返回统一产品DTO
|
||||
const res = await this.wpService.updateProduct(this.site, String(id), data as any);
|
||||
return this.mapProduct(res);
|
||||
}
|
||||
|
||||
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);
|
||||
return res;
|
||||
}
|
||||
|
||||
async getOrderNotes(orderId: string | number): Promise<any[]> {
|
||||
// 获取订单备注列表
|
||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||
const res = await api.get(`orders/${orderId}/notes`);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
async createOrderNote(orderId: string | number, data: any): Promise<any> {
|
||||
// 创建订单备注
|
||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||
const res = await api.post(`orders/${orderId}/notes`, data);
|
||||
return res.data;
|
||||
}
|
||||
|
||||
async deleteProduct(id: string | number): Promise<boolean> {
|
||||
// 删除产品
|
||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||
try {
|
||||
await api.delete(`products/${id}`, { force: true });
|
||||
|
|
@ -172,12 +208,14 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
|||
async batchProcessProducts(
|
||||
data: { create?: any[]; update?: any[]; delete?: Array<string | number> }
|
||||
): Promise<any> {
|
||||
// 批量处理产品增删改
|
||||
return await this.wpService.batchProcessProducts(this.site, data);
|
||||
}
|
||||
|
||||
async getOrders(
|
||||
params: UnifiedSearchParamsDTO
|
||||
): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>> {
|
||||
// 获取订单列表并映射为统一订单DTO集合
|
||||
const { items, total, totalPages, page, per_page } =
|
||||
await this.wpService.fetchResourcePaged<any>(this.site, 'orders', params);
|
||||
return {
|
||||
|
|
@ -186,26 +224,31 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
|||
totalPages,
|
||||
page,
|
||||
per_page,
|
||||
page_size: per_page,
|
||||
};
|
||||
}
|
||||
|
||||
async getOrder(id: string | number): Promise<UnifiedOrderDTO> {
|
||||
// 获取单个订单详情
|
||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||
const res = await api.get(`orders/${id}`);
|
||||
return this.mapOrder(res.data);
|
||||
}
|
||||
|
||||
async createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO> {
|
||||
// 创建订单并返回统一订单DTO
|
||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||
const res = await api.post('orders', data);
|
||||
return this.mapOrder(res.data);
|
||||
}
|
||||
|
||||
async updateOrder(id: string | number, data: Partial<UnifiedOrderDTO>): Promise<boolean> {
|
||||
// 更新订单并返回布尔结果
|
||||
return await this.wpService.updateOrder(this.site, String(id), data as any);
|
||||
}
|
||||
|
||||
async deleteOrder(id: string | number): Promise<boolean> {
|
||||
// 删除订单
|
||||
const api = (this.wpService as any).createApi(this.site, 'wc/v3');
|
||||
await api.delete(`orders/${id}`, { force: true });
|
||||
return true;
|
||||
|
|
@ -214,6 +257,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
|||
async getSubscriptions(
|
||||
params: UnifiedSearchParamsDTO
|
||||
): Promise<UnifiedPaginationDTO<UnifiedSubscriptionDTO>> {
|
||||
// 获取订阅列表并映射为统一订阅DTO集合
|
||||
const { items, total, totalPages, page, per_page } =
|
||||
await this.wpService.fetchResourcePaged<any>(
|
||||
this.site,
|
||||
|
|
@ -226,45 +270,56 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
|||
totalPages,
|
||||
page,
|
||||
per_page,
|
||||
page_size: per_page,
|
||||
};
|
||||
}
|
||||
|
||||
async getMedia(
|
||||
params: UnifiedSearchParamsDTO
|
||||
): Promise<UnifiedPaginationDTO<UnifiedMediaDTO>> {
|
||||
const { items, total, totalPages } = await this.wpService.getMedia(
|
||||
this.site.id,
|
||||
params.page || 1,
|
||||
params.per_page || 20
|
||||
// 获取媒体列表并映射为统一媒体DTO集合
|
||||
const { items, total, totalPages, page, per_page } = await this.wpService.fetchMediaPaged(
|
||||
this.site,
|
||||
params
|
||||
);
|
||||
return {
|
||||
items: items.map(this.mapMedia),
|
||||
total,
|
||||
totalPages,
|
||||
page: params.page || 1,
|
||||
per_page: params.per_page || 20,
|
||||
page,
|
||||
per_page,
|
||||
page_size: per_page,
|
||||
};
|
||||
}
|
||||
|
||||
async deleteMedia(id: string | number): Promise<boolean> {
|
||||
// 删除媒体资源
|
||||
await this.wpService.deleteMedia(Number(this.site.id), Number(id), true);
|
||||
return true;
|
||||
}
|
||||
|
||||
async updateMedia(id: string | number, data: any): Promise<any> {
|
||||
// 更新媒体信息
|
||||
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 {
|
||||
id: item.id,
|
||||
avatar: item.avatar_url,
|
||||
email: item.email,
|
||||
orders: Number(item.orders?? 0),
|
||||
total_spend: Number(item.total_spent ?? 0),
|
||||
first_name: item.first_name,
|
||||
last_name: item.last_name,
|
||||
username: item.username,
|
||||
phone: item.billing?.phone || item.shipping?.phone,
|
||||
billing: item.billing,
|
||||
shipping: item.shipping,
|
||||
date_created: item.date_created,
|
||||
date_modified: item.date_modified,
|
||||
raw: item,
|
||||
};
|
||||
}
|
||||
|
|
@ -281,6 +336,7 @@ export class WooCommerceAdapter implements ISiteAdapter {
|
|||
totalPages,
|
||||
page,
|
||||
per_page,
|
||||
page_size: per_page,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -49,10 +49,42 @@ export class SiteApiController {
|
|||
) {
|
||||
try {
|
||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||
const data = await adapter.getProducts(query);
|
||||
const header = ['id','name','type','status','sku','regular_price','sale_price','price','stock_status','stock_quantity'];
|
||||
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 csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
||||
const perPage = (query.page_size ?? query.per_page) || 100;
|
||||
let page = 1;
|
||||
const all: any[] = [];
|
||||
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 });
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
|
|
@ -69,15 +101,20 @@ export class SiteApiController {
|
|||
const site = await this.siteApiService.siteService.get(siteId, true);
|
||||
if (site.type === 'woocommerce') {
|
||||
const page = query.page || 1;
|
||||
const per_page = query.per_page || 100;
|
||||
const res = await this.siteApiService.wpService.getProducts(site, page, per_page);
|
||||
const perPage = (query.page_size ?? query.per_page) || 100;
|
||||
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 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');
|
||||
return successResponse({ csv });
|
||||
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 });
|
||||
}
|
||||
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 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');
|
||||
|
|
@ -329,7 +366,12 @@ export class SiteApiController {
|
|||
this.logger.info(`[Site API] 获取订单列表开始, siteId: ${siteId}, query: ${JSON.stringify(query)}`);
|
||||
try {
|
||||
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} 个订单`);
|
||||
return successResponse(data);
|
||||
} 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')
|
||||
async exportOrders(
|
||||
@Param('siteId') siteId: number,
|
||||
|
|
@ -345,10 +407,44 @@ export class SiteApiController {
|
|||
) {
|
||||
try {
|
||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||
const data = await adapter.getOrders(query);
|
||||
const header = ['id','number','status','currency','total','customer_id','customer_name','email','date_created'];
|
||||
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 csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
||||
const perPage = (query.page_size ?? query.per_page) || 100;
|
||||
let page = 1;
|
||||
const all: any[] = [];
|
||||
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 });
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
|
|
@ -579,9 +675,24 @@ export class SiteApiController {
|
|||
) {
|
||||
try {
|
||||
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 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');
|
||||
return successResponse({ csv });
|
||||
} catch (error) {
|
||||
|
|
@ -614,9 +725,24 @@ export class SiteApiController {
|
|||
) {
|
||||
try {
|
||||
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 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');
|
||||
return successResponse({ csv });
|
||||
} catch (error) {
|
||||
|
|
@ -738,9 +864,49 @@ export class SiteApiController {
|
|||
) {
|
||||
try {
|
||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||
const data = await adapter.getCustomers(query);
|
||||
const header = ['id','email','first_name','last_name','fullname','username','phone'];
|
||||
const rows = data.items.map((c: any) => [c.id,c.email,c.first_name,c.last_name,c.fullname,c.username,c.phone]);
|
||||
const perPage = (query.page_size ?? query.per_page) || 100;
|
||||
let page = 1;
|
||||
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');
|
||||
return successResponse({ csv });
|
||||
} 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';
|
||||
|
||||
export class UnifiedPaginationDTO<T> {
|
||||
// 分页DTO用于承载统一分页信息与列表数据
|
||||
@ApiProperty({ description: '列表数据' })
|
||||
items: T[];
|
||||
|
||||
|
|
@ -13,11 +14,15 @@ export class UnifiedPaginationDTO<T> {
|
|||
@ApiProperty({ description: '每页数量', example: 20 })
|
||||
per_page: number;
|
||||
|
||||
@ApiProperty({ description: '每页数量别名', example: 20 })
|
||||
page_size?: number;
|
||||
|
||||
@ApiProperty({ description: '总页数', example: 5 })
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export class UnifiedImageDTO {
|
||||
// 图片DTO用于承载统一图片数据
|
||||
@ApiProperty({ description: '图片ID' })
|
||||
id: number | string;
|
||||
|
||||
|
|
@ -32,6 +37,7 @@ export class UnifiedImageDTO {
|
|||
}
|
||||
|
||||
export class UnifiedProductDTO {
|
||||
// 产品DTO用于承载统一产品数据
|
||||
@ApiProperty({ description: '产品ID' })
|
||||
id: string | number;
|
||||
|
||||
|
|
@ -85,6 +91,7 @@ export class UnifiedProductDTO {
|
|||
}
|
||||
|
||||
export class UnifiedOrderDTO {
|
||||
// 订单DTO用于承载统一订单数据
|
||||
@ApiProperty({ description: '订单ID' })
|
||||
id: string | number;
|
||||
|
||||
|
|
@ -133,16 +140,35 @@ export class UnifiedOrderDTO {
|
|||
@ApiProperty({ description: '创建时间' })
|
||||
date_created: string;
|
||||
|
||||
@ApiProperty({ description: '更新时间' })
|
||||
date_modified?: string;
|
||||
|
||||
@ApiProperty({ description: '原始数据', type: 'json' })
|
||||
raw?: any;
|
||||
}
|
||||
|
||||
export class UnifiedCustomerDTO {
|
||||
// 客户DTO用于承载统一客户数据
|
||||
@ApiProperty({ description: '客户ID' })
|
||||
id: string | number;
|
||||
|
||||
@ApiProperty({ description: '头像URL' })
|
||||
avatar?: string;
|
||||
|
||||
@ApiProperty({ description: '邮箱' })
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ description: '订单总数' })
|
||||
orders?: number;
|
||||
|
||||
@ApiProperty({ description: '总花费' })
|
||||
total_spend?: number;
|
||||
|
||||
@ApiProperty({ description: '创建时间' })
|
||||
date_created?: string;
|
||||
|
||||
@ApiProperty({ description: '更新时间' })
|
||||
date_modified?: string;
|
||||
|
||||
@ApiProperty({ description: '名' })
|
||||
first_name?: string;
|
||||
|
|
@ -170,6 +196,7 @@ export class UnifiedCustomerDTO {
|
|||
}
|
||||
|
||||
export class UnifiedSubscriptionDTO {
|
||||
// 订阅DTO用于承载统一订阅数据
|
||||
@ApiProperty({ description: '订阅ID' })
|
||||
id: string | number;
|
||||
|
||||
|
|
@ -185,6 +212,12 @@ export class UnifiedSubscriptionDTO {
|
|||
@ApiProperty({ description: '计费间隔' })
|
||||
billing_interval: number;
|
||||
|
||||
@ApiProperty({ description: '创建时间' })
|
||||
date_created?: string;
|
||||
|
||||
@ApiProperty({ description: '更新时间' })
|
||||
date_modified?: string;
|
||||
|
||||
@ApiProperty({ description: '开始时间' })
|
||||
start_date: string;
|
||||
|
||||
|
|
@ -199,6 +232,7 @@ export class UnifiedSubscriptionDTO {
|
|||
}
|
||||
|
||||
export class UnifiedMediaDTO {
|
||||
// 媒体DTO用于承载统一媒体数据
|
||||
@ApiProperty({ description: '媒体ID' })
|
||||
id: number;
|
||||
|
||||
|
|
@ -216,49 +250,73 @@ export class UnifiedMediaDTO {
|
|||
|
||||
@ApiProperty({ description: '创建时间' })
|
||||
date_created: string;
|
||||
|
||||
@ApiProperty({ description: '更新时间' })
|
||||
date_modified?: string;
|
||||
}
|
||||
|
||||
export class UnifiedProductPaginationDTO extends UnifiedPaginationDTO<UnifiedProductDTO> {
|
||||
// 产品分页DTO用于承载产品列表分页数据
|
||||
@ApiProperty({ description: '列表数据', type: [UnifiedProductDTO] })
|
||||
items: UnifiedProductDTO[];
|
||||
}
|
||||
|
||||
export class UnifiedOrderPaginationDTO extends UnifiedPaginationDTO<UnifiedOrderDTO> {
|
||||
// 订单分页DTO用于承载订单列表分页数据
|
||||
@ApiProperty({ description: '列表数据', type: [UnifiedOrderDTO] })
|
||||
items: UnifiedOrderDTO[];
|
||||
}
|
||||
|
||||
export class UnifiedCustomerPaginationDTO extends UnifiedPaginationDTO<UnifiedCustomerDTO> {
|
||||
// 客户分页DTO用于承载客户列表分页数据
|
||||
@ApiProperty({ description: '列表数据', type: [UnifiedCustomerDTO] })
|
||||
items: UnifiedCustomerDTO[];
|
||||
}
|
||||
|
||||
export class UnifiedSubscriptionPaginationDTO extends UnifiedPaginationDTO<UnifiedSubscriptionDTO> {
|
||||
// 订阅分页DTO用于承载订阅列表分页数据
|
||||
@ApiProperty({ description: '列表数据', type: [UnifiedSubscriptionDTO] })
|
||||
items: UnifiedSubscriptionDTO[];
|
||||
}
|
||||
|
||||
export class UnifiedMediaPaginationDTO extends UnifiedPaginationDTO<UnifiedMediaDTO> {
|
||||
// 媒体分页DTO用于承载媒体列表分页数据
|
||||
@ApiProperty({ description: '列表数据', type: [UnifiedMediaDTO] })
|
||||
items: UnifiedMediaDTO[];
|
||||
}
|
||||
|
||||
export class UnifiedSearchParamsDTO {
|
||||
// 统一查询参数DTO用于承载分页与筛选与排序参数
|
||||
@ApiProperty({ description: '页码', example: 1 })
|
||||
page?: number;
|
||||
|
||||
@ApiProperty({ description: '每页数量', example: 20 })
|
||||
per_page?: number;
|
||||
|
||||
@ApiProperty({ description: '每页数量别名', example: 20 })
|
||||
page_size?: number;
|
||||
|
||||
@ApiProperty({ description: '搜索关键词' })
|
||||
search?: string;
|
||||
|
||||
@ApiProperty({ description: '状态' })
|
||||
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;
|
||||
|
||||
@ApiProperty({ description: '排序方式' })
|
||||
order?: string;
|
||||
@ApiProperty({ description: '排序方式(兼容旧入参)' })
|
||||
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> = {}) {
|
||||
// 映射 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 = {
|
||||
...params,
|
||||
page: params.page || 1,
|
||||
limit: params.per_page || 20
|
||||
...where,
|
||||
...(params.search ? { search: params.search } : {}),
|
||||
...(params.status ? { status: params.status } : {}),
|
||||
...(orderby ? { orderby } : {}),
|
||||
...(order ? { order } : {}),
|
||||
page,
|
||||
limit
|
||||
};
|
||||
const response = await this.request(site, endpoint, 'GET', null, requestParams);
|
||||
if (response?.code !== 0) {
|
||||
|
|
@ -89,7 +106,8 @@ export class ShopyyService implements IPlatformService {
|
|||
total: response.data?.paginate?.total || 0,
|
||||
totalPages: response.data?.paginate?.pageTotal || 0,
|
||||
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> = {}) {
|
||||
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> = {}) {
|
||||
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 });
|
||||
if (res?.headers?.['content-type']?.includes('text/html')) {
|
||||
throw new Error('接口返回了 text/html,可能为 WordPress 登录页或错误页,请检查站点配置或权限');
|
||||
|
|
@ -67,7 +89,7 @@ export class WPService implements IPlatformService {
|
|||
const data = res.data as T[];
|
||||
const totalPages = Number(res.headers?.['x-wp-totalpages'] ?? 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
|
||||
|
|
|
|||
Loading…
Reference in New Issue