feat: 新增批量处理功能及用户邮箱字段
feat(adapter): 为Shopyy和WooCommerce适配器添加批量处理产品接口 feat(controller): 在产品控制器中新增站点SKU绑定接口 feat(controller): 在用户控制器中支持邮箱字段的增删改查 feat(controller): 新增客户标签管理接口 feat(controller): 在站点API控制器中添加批量导入导出功能 feat(service): 在产品服务中实现站点SKU绑定逻辑 feat(service): 在用户服务中添加邮箱字段校验和搜索 refactor(controller): 废弃部分WP产品控制器接口 refactor(webhook): 简化webhook控制器逻辑不再同步本地数据
This commit is contained in:
parent
87b4039a67
commit
3f3569995d
|
|
@ -248,6 +248,12 @@ export class ShopyyAdapter implements ISiteAdapter {
|
|||
return true;
|
||||
}
|
||||
|
||||
async batchProcessProducts(
|
||||
data: { create?: any[]; update?: any[]; delete?: Array<string | number> }
|
||||
): Promise<any> {
|
||||
return await this.shopyyService.batchProcessProducts(this.site, data);
|
||||
}
|
||||
|
||||
async getOrders(
|
||||
params: UnifiedSearchParamsDTO
|
||||
): Promise<UnifiedPaginationDTO<UnifiedOrderDTO>> {
|
||||
|
|
|
|||
|
|
@ -169,6 +169,12 @@ 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>> {
|
||||
|
|
|
|||
|
|
@ -1,23 +1,63 @@
|
|||
import { Controller, Get, Inject, Query } from '@midwayjs/core';
|
||||
import { WPService } from '../service/wp.service';
|
||||
import { Controller, Get, Post, Inject, Query, Body } from '@midwayjs/core';
|
||||
import { successResponse, errorResponse } from '../utils/response.util';
|
||||
import { CustomerService } from '../service/customer.service';
|
||||
import { QueryCustomerListDTO, CustomerTagDTO } from '../dto/customer.dto';
|
||||
import { ApiOkResponse } from '@midwayjs/swagger';
|
||||
|
||||
@Controller('/customer')
|
||||
export class CustomerController {
|
||||
@Inject()
|
||||
wpService: WPService;
|
||||
customerService: CustomerService;
|
||||
|
||||
@Get('/list')
|
||||
async list(
|
||||
@Query('siteId') siteId: number,
|
||||
@Query('page') page: number = 1,
|
||||
@Query('pageSize') pageSize: number = 20
|
||||
) {
|
||||
@ApiOkResponse({ type: Object })
|
||||
@Get('/getcustomerlist')
|
||||
async getCustomerList(@Query() query: QueryCustomerListDTO) {
|
||||
try {
|
||||
if (!siteId) {
|
||||
return errorResponse('siteId is required');
|
||||
const result = await this.customerService.getCustomerList(query as any);
|
||||
return successResponse(result);
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
const result = await this.wpService.getCustomers(siteId, page, pageSize);
|
||||
}
|
||||
|
||||
@ApiOkResponse({ type: Object })
|
||||
@Post('/addtag')
|
||||
async addTag(@Body() body: CustomerTagDTO) {
|
||||
try {
|
||||
const result = await this.customerService.addTag(body.email, body.tag);
|
||||
return successResponse(result);
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOkResponse({ type: Object })
|
||||
@Post('/deltag')
|
||||
async delTag(@Body() body: CustomerTagDTO) {
|
||||
try {
|
||||
const result = await this.customerService.delTag(body.email, body.tag);
|
||||
return successResponse(result);
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOkResponse({ type: Object })
|
||||
@Get('/gettags')
|
||||
async getTags() {
|
||||
try {
|
||||
const result = await this.customerService.getTags();
|
||||
return successResponse(result);
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOkResponse({ type: Object })
|
||||
@Post('/setrate')
|
||||
async setRate(@Body() body: { id: number; rate: number }) {
|
||||
try {
|
||||
const result = await this.customerService.setRate({ id: body.id, rate: body.rate });
|
||||
return successResponse(result);
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
|
|
|
|||
|
|
@ -170,6 +170,30 @@ export class ProductController {
|
|||
}
|
||||
}
|
||||
|
||||
// 获取产品的站点SKU绑定
|
||||
@ApiOkResponse()
|
||||
@Get('/:id/site-skus')
|
||||
async getProductSiteSkus(@Param('id') id: number) {
|
||||
try {
|
||||
const data = await this.productService.productSiteSkuModel.find({ where: { productId: id } });
|
||||
return successResponse(data);
|
||||
} catch (error) {
|
||||
return errorResponse(error?.message || error);
|
||||
}
|
||||
}
|
||||
|
||||
// 覆盖式绑定产品的站点SKU列表
|
||||
@ApiOkResponse()
|
||||
@Post('/:id/site-skus')
|
||||
async bindProductSiteSkus(@Param('id') id: number, @Body() body: { codes: string[] }) {
|
||||
try {
|
||||
const data = await this.productService.bindSiteSkus(id, body?.codes || []);
|
||||
return successResponse(data);
|
||||
} catch (error) {
|
||||
return errorResponse(error?.message || error);
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOkResponse({ type: BooleanRes })
|
||||
@Del('/:id')
|
||||
async deleteProduct(@Param('id') id: number) {
|
||||
|
|
|
|||
|
|
@ -42,6 +42,53 @@ export class SiteApiController {
|
|||
}
|
||||
}
|
||||
|
||||
@Get('/:siteId/products/export')
|
||||
async exportProducts(
|
||||
@Param('siteId') siteId: number,
|
||||
@Query() query: UnifiedSearchParamsDTO
|
||||
) {
|
||||
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');
|
||||
return successResponse({ csv });
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 平台特性:产品导出(特殊CSV,走平台服务)
|
||||
@Get('/:siteId/products/export-special')
|
||||
async exportProductsSpecial(
|
||||
@Param('siteId') siteId: number,
|
||||
@Query() query: UnifiedSearchParamsDTO
|
||||
) {
|
||||
try {
|
||||
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 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 });
|
||||
}
|
||||
if (site.type === 'shopyy') {
|
||||
const res = await this.siteApiService.shopyyService.getProducts(site, query.page || 1, 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');
|
||||
return successResponse({ csv });
|
||||
}
|
||||
throw new Error('Unsupported site type for special export');
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/:siteId/products/:id')
|
||||
@ApiOkResponse({ type: UnifiedProductDTO })
|
||||
async getProduct(
|
||||
|
|
@ -78,6 +125,86 @@ export class SiteApiController {
|
|||
}
|
||||
}
|
||||
|
||||
@Post('/:siteId/products/import')
|
||||
@ApiOkResponse({ type: Object })
|
||||
async importProducts(
|
||||
@Param('siteId') siteId: number,
|
||||
@Body() body: { items?: any[]; csv?: string }
|
||||
) {
|
||||
try {
|
||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||
let items = body.items || [];
|
||||
if (!items.length && body.csv) {
|
||||
const lines = body.csv.split(/\r?\n/).filter(Boolean);
|
||||
const header = lines.shift()?.split(',') || [];
|
||||
items = lines.map((line) => {
|
||||
const cols = line.split(',');
|
||||
const obj: any = {};
|
||||
header.forEach((h, i) => (obj[h] = cols[i]));
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
const created: any[] = [];
|
||||
const failed: any[] = [];
|
||||
for (const item of items) {
|
||||
try {
|
||||
const data = await adapter.createProduct(item);
|
||||
created.push(data);
|
||||
} catch (e) {
|
||||
failed.push({ item, error: (e as any).message });
|
||||
}
|
||||
}
|
||||
return successResponse({ created, failed });
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 平台特性:产品导入(特殊CSV,走平台服务)
|
||||
@Post('/:siteId/products/import-special')
|
||||
@ApiOkResponse({ type: Object })
|
||||
async importProductsSpecial(
|
||||
@Param('siteId') siteId: number,
|
||||
@Body() body: { csv?: string; items?: any[] }
|
||||
) {
|
||||
try {
|
||||
const site = await this.siteApiService.siteService.get(siteId, true);
|
||||
const csvText = body.csv || '';
|
||||
const items = body.items || [];
|
||||
const created: any[] = [];
|
||||
const failed: any[] = [];
|
||||
if (site.type === 'woocommerce') {
|
||||
// 解析 CSV 为对象数组(若传入 items 则优先 items)
|
||||
let payloads = items;
|
||||
if (!payloads.length && csvText) {
|
||||
const lines = csvText.split(/\r?\n/).filter(Boolean);
|
||||
const header = lines.shift()?.split(',') || [];
|
||||
payloads = lines.map((line) => {
|
||||
const cols = line.split(',');
|
||||
const obj: any = {};
|
||||
header.forEach((h, i) => (obj[h] = cols[i]));
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
for (const item of payloads) {
|
||||
try {
|
||||
const res = await this.siteApiService.wpService.createProduct(site, item);
|
||||
created.push(res);
|
||||
} catch (e) {
|
||||
failed.push({ item, error: (e as any).message });
|
||||
}
|
||||
}
|
||||
return successResponse({ created, failed });
|
||||
}
|
||||
if (site.type === 'shopyy') {
|
||||
throw new Error('ShopYY 暂不支持特殊CSV导入');
|
||||
}
|
||||
throw new Error('Unsupported site type for special import');
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Put('/:siteId/products/:id')
|
||||
@ApiOkResponse({ type: UnifiedProductDTO })
|
||||
async updateProduct(
|
||||
|
|
@ -135,6 +262,64 @@ export class SiteApiController {
|
|||
}
|
||||
}
|
||||
|
||||
@Post('/:siteId/products/batch')
|
||||
@ApiOkResponse({ type: Object })
|
||||
async batchProducts(
|
||||
@Param('siteId') siteId: number,
|
||||
@Body() body: { create?: any[]; update?: any[]; delete?: Array<string | number> }
|
||||
) {
|
||||
this.logger.info(`[Site API] 批量处理产品开始, siteId: ${siteId}`);
|
||||
try {
|
||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||
if (adapter.batchProcessProducts) {
|
||||
const res = await adapter.batchProcessProducts(body);
|
||||
this.logger.info(`[Site API] 批量处理产品成功, siteId: ${siteId}`);
|
||||
return successResponse(res);
|
||||
}
|
||||
const created: any[] = [];
|
||||
const updated: any[] = [];
|
||||
const deleted: Array<string | number> = [];
|
||||
const failed: any[] = [];
|
||||
if (body.create?.length) {
|
||||
for (const item of body.create) {
|
||||
try {
|
||||
const data = await adapter.createProduct(item);
|
||||
created.push(data);
|
||||
} catch (e) {
|
||||
failed.push({ action: 'create', item, error: (e as any).message });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (body.update?.length) {
|
||||
for (const item of body.update) {
|
||||
try {
|
||||
const id = item.id;
|
||||
const data = await adapter.updateProduct(id, item);
|
||||
updated.push(data);
|
||||
} catch (e) {
|
||||
failed.push({ action: 'update', item, error: (e as any).message });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (body.delete?.length) {
|
||||
for (const id of body.delete) {
|
||||
try {
|
||||
const ok = await adapter.deleteProduct(id);
|
||||
if (ok) deleted.push(id);
|
||||
else failed.push({ action: 'delete', id, error: 'delete failed' });
|
||||
} catch (e) {
|
||||
failed.push({ action: 'delete', id, error: (e as any).message });
|
||||
}
|
||||
}
|
||||
}
|
||||
this.logger.info(`[Site API] 批量处理产品完成, siteId: ${siteId}`);
|
||||
return successResponse({ created, updated, deleted, failed });
|
||||
} catch (error) {
|
||||
this.logger.error(`[Site API] 批量处理产品失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/:siteId/orders')
|
||||
@ApiOkResponse({ type: UnifiedOrderPaginationDTO })
|
||||
async getOrders(
|
||||
|
|
@ -153,6 +338,23 @@ export class SiteApiController {
|
|||
}
|
||||
}
|
||||
|
||||
@Get('/:siteId/orders/export')
|
||||
async exportOrders(
|
||||
@Param('siteId') siteId: number,
|
||||
@Query() query: UnifiedSearchParamsDTO
|
||||
) {
|
||||
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');
|
||||
return successResponse({ csv });
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/:siteId/orders/:id')
|
||||
@ApiOkResponse({ type: UnifiedOrderDTO })
|
||||
async getOrder(
|
||||
|
|
@ -189,6 +391,41 @@ export class SiteApiController {
|
|||
}
|
||||
}
|
||||
|
||||
@Post('/:siteId/orders/import')
|
||||
@ApiOkResponse({ type: Object })
|
||||
async importOrders(
|
||||
@Param('siteId') siteId: number,
|
||||
@Body() body: { items?: any[]; csv?: string }
|
||||
) {
|
||||
try {
|
||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||
let items = body.items || [];
|
||||
if (!items.length && body.csv) {
|
||||
const lines = body.csv.split(/\r?\n/).filter(Boolean);
|
||||
const header = lines.shift()?.split(',') || [];
|
||||
items = lines.map((line) => {
|
||||
const cols = line.split(',');
|
||||
const obj: any = {};
|
||||
header.forEach((h, i) => (obj[h] = cols[i]));
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
const created: any[] = [];
|
||||
const failed: any[] = [];
|
||||
for (const item of items) {
|
||||
try {
|
||||
const data = await adapter.createOrder(item);
|
||||
created.push(data);
|
||||
} catch (e) {
|
||||
failed.push({ item, error: (e as any).message });
|
||||
}
|
||||
}
|
||||
return successResponse({ created, failed });
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Put('/:siteId/orders/:id')
|
||||
@ApiOkResponse({ type: Boolean })
|
||||
async updateOrder(
|
||||
|
|
@ -226,6 +463,60 @@ export class SiteApiController {
|
|||
}
|
||||
}
|
||||
|
||||
@Post('/:siteId/orders/batch')
|
||||
@ApiOkResponse({ type: Object })
|
||||
async batchOrders(
|
||||
@Param('siteId') siteId: number,
|
||||
@Body() body: { create?: any[]; update?: any[]; delete?: Array<string | number> }
|
||||
) {
|
||||
this.logger.info(`[Site API] 批量处理订单开始, siteId: ${siteId}`);
|
||||
try {
|
||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||
const created: any[] = [];
|
||||
const updated: any[] = [];
|
||||
const deleted: Array<string | number> = [];
|
||||
const failed: any[] = [];
|
||||
if (body.create?.length) {
|
||||
for (const item of body.create) {
|
||||
try {
|
||||
const data = await adapter.createOrder(item);
|
||||
created.push(data);
|
||||
} catch (e) {
|
||||
failed.push({ action: 'create', item, error: (e as any).message });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (body.update?.length) {
|
||||
for (const item of body.update) {
|
||||
try {
|
||||
const id = item.id;
|
||||
const ok = await adapter.updateOrder(id, item);
|
||||
if (ok) updated.push(item);
|
||||
else failed.push({ action: 'update', item, error: 'update failed' });
|
||||
} catch (e) {
|
||||
failed.push({ action: 'update', item, error: (e as any).message });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (body.delete?.length) {
|
||||
for (const id of body.delete) {
|
||||
try {
|
||||
const ok = await adapter.deleteOrder(id);
|
||||
if (ok) deleted.push(id);
|
||||
else failed.push({ action: 'delete', id, error: 'delete failed' });
|
||||
} catch (e) {
|
||||
failed.push({ action: 'delete', id, error: (e as any).message });
|
||||
}
|
||||
}
|
||||
}
|
||||
this.logger.info(`[Site API] 批量处理订单完成, siteId: ${siteId}`);
|
||||
return successResponse({ created, updated, deleted, failed });
|
||||
} catch (error) {
|
||||
this.logger.error(`[Site API] 批量处理订单失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/:siteId/orders/:id/notes')
|
||||
@ApiOkResponse({ type: Object })
|
||||
async getOrderNotes(
|
||||
|
|
@ -281,6 +572,23 @@ export class SiteApiController {
|
|||
}
|
||||
}
|
||||
|
||||
@Get('/:siteId/subscriptions/export')
|
||||
async exportSubscriptions(
|
||||
@Param('siteId') siteId: number,
|
||||
@Query() query: UnifiedSearchParamsDTO
|
||||
) {
|
||||
try {
|
||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||
const data = await adapter.getSubscriptions(query);
|
||||
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 csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
||||
return successResponse({ csv });
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/:siteId/media')
|
||||
@ApiOkResponse({ type: UnifiedMediaPaginationDTO })
|
||||
async getMedia(
|
||||
|
|
@ -299,6 +607,23 @@ export class SiteApiController {
|
|||
}
|
||||
}
|
||||
|
||||
@Get('/:siteId/media/export')
|
||||
async exportMedia(
|
||||
@Param('siteId') siteId: number,
|
||||
@Query() query: UnifiedSearchParamsDTO
|
||||
) {
|
||||
try {
|
||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||
const data = await adapter.getMedia(query);
|
||||
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 csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
||||
return successResponse({ csv });
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Del('/:siteId/media/:id')
|
||||
@ApiOkResponse({ type: Boolean })
|
||||
async deleteMedia(
|
||||
|
|
@ -344,6 +669,50 @@ export class SiteApiController {
|
|||
}
|
||||
}
|
||||
|
||||
@Post('/:siteId/media/batch')
|
||||
@ApiOkResponse({ type: Object })
|
||||
async batchMedia(
|
||||
@Param('siteId') siteId: number,
|
||||
@Body() body: { update?: any[]; delete?: Array<string | number> }
|
||||
) {
|
||||
this.logger.info(`[Site API] 批量处理媒体开始, siteId: ${siteId}`);
|
||||
try {
|
||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||
const updated: any[] = [];
|
||||
const deleted: Array<string | number> = [];
|
||||
const failed: any[] = [];
|
||||
const api: any = adapter as any;
|
||||
if (body.update?.length) {
|
||||
for (const item of body.update) {
|
||||
try {
|
||||
if (!api.updateMedia) throw new Error('Media update not supported');
|
||||
const res = await api.updateMedia(item.id, item);
|
||||
updated.push(res);
|
||||
} catch (e) {
|
||||
failed.push({ action: 'update', item, error: (e as any).message });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (body.delete?.length) {
|
||||
for (const id of body.delete) {
|
||||
try {
|
||||
if (!api.deleteMedia) throw new Error('Media delete not supported');
|
||||
const ok = await api.deleteMedia(id);
|
||||
if (ok) deleted.push(id);
|
||||
else failed.push({ action: 'delete', id, error: 'delete failed' });
|
||||
} catch (e) {
|
||||
failed.push({ action: 'delete', id, error: (e as any).message });
|
||||
}
|
||||
}
|
||||
}
|
||||
this.logger.info(`[Site API] 批量处理媒体完成, siteId: ${siteId}`);
|
||||
return successResponse({ updated, deleted, failed });
|
||||
} catch (error) {
|
||||
this.logger.error(`[Site API] 批量处理媒体失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/:siteId/customers')
|
||||
@ApiOkResponse({ type: UnifiedCustomerPaginationDTO })
|
||||
async getCustomers(
|
||||
|
|
@ -362,6 +731,23 @@ export class SiteApiController {
|
|||
}
|
||||
}
|
||||
|
||||
@Get('/:siteId/customers/export')
|
||||
async exportCustomers(
|
||||
@Param('siteId') siteId: number,
|
||||
@Query() query: UnifiedSearchParamsDTO
|
||||
) {
|
||||
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 csv = [header.join(','), ...rows.map(r => r.map(v => String(v ?? '')).join(','))].join('\n');
|
||||
return successResponse({ csv });
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Get('/:siteId/customers/:id')
|
||||
@ApiOkResponse({ type: UnifiedCustomerDTO })
|
||||
async getCustomer(
|
||||
|
|
@ -398,6 +784,41 @@ export class SiteApiController {
|
|||
}
|
||||
}
|
||||
|
||||
@Post('/:siteId/customers/import')
|
||||
@ApiOkResponse({ type: Object })
|
||||
async importCustomers(
|
||||
@Param('siteId') siteId: number,
|
||||
@Body() body: { items?: any[]; csv?: string }
|
||||
) {
|
||||
try {
|
||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||
let items = body.items || [];
|
||||
if (!items.length && body.csv) {
|
||||
const lines = body.csv.split(/\r?\n/).filter(Boolean);
|
||||
const header = lines.shift()?.split(',') || [];
|
||||
items = lines.map((line) => {
|
||||
const cols = line.split(',');
|
||||
const obj: any = {};
|
||||
header.forEach((h, i) => (obj[h] = cols[i]));
|
||||
return obj;
|
||||
});
|
||||
}
|
||||
const created: any[] = [];
|
||||
const failed: any[] = [];
|
||||
for (const item of items) {
|
||||
try {
|
||||
const data = await adapter.createCustomer(item);
|
||||
created.push(data);
|
||||
} catch (e) {
|
||||
failed.push({ item, error: (e as any).message });
|
||||
}
|
||||
}
|
||||
return successResponse({ created, failed });
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Put('/:siteId/customers/:id')
|
||||
@ApiOkResponse({ type: UnifiedCustomerDTO })
|
||||
async updateCustomer(
|
||||
|
|
@ -434,4 +855,57 @@ export class SiteApiController {
|
|||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
@Post('/:siteId/customers/batch')
|
||||
@ApiOkResponse({ type: Object })
|
||||
async batchCustomers(
|
||||
@Param('siteId') siteId: number,
|
||||
@Body() body: { create?: any[]; update?: any[]; delete?: Array<string | number> }
|
||||
) {
|
||||
this.logger.info(`[Site API] 批量处理客户开始, siteId: ${siteId}`);
|
||||
try {
|
||||
const adapter = await this.siteApiService.getAdapter(siteId);
|
||||
const created: any[] = [];
|
||||
const updated: any[] = [];
|
||||
const deleted: Array<string | number> = [];
|
||||
const failed: any[] = [];
|
||||
if (body.create?.length) {
|
||||
for (const item of body.create) {
|
||||
try {
|
||||
const data = await adapter.createCustomer(item);
|
||||
created.push(data);
|
||||
} catch (e) {
|
||||
failed.push({ action: 'create', item, error: (e as any).message });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (body.update?.length) {
|
||||
for (const item of body.update) {
|
||||
try {
|
||||
const id = item.id;
|
||||
const data = await adapter.updateCustomer(id, item);
|
||||
updated.push(data);
|
||||
} catch (e) {
|
||||
failed.push({ action: 'update', item, error: (e as any).message });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (body.delete?.length) {
|
||||
for (const id of body.delete) {
|
||||
try {
|
||||
const ok = await adapter.deleteCustomer(id);
|
||||
if (ok) deleted.push(id);
|
||||
else failed.push({ action: 'delete', id, error: 'delete failed' });
|
||||
} catch (e) {
|
||||
failed.push({ action: 'delete', id, error: (e as any).message });
|
||||
}
|
||||
}
|
||||
}
|
||||
this.logger.info(`[Site API] 批量处理客户完成, siteId: ${siteId}`);
|
||||
return successResponse({ created, updated, deleted, failed });
|
||||
} catch (error) {
|
||||
this.logger.error(`[Site API] 批量处理客户失败, siteId: ${siteId}, 错误信息: ${error.message}`);
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,11 +40,11 @@ export class UserController {
|
|||
}
|
||||
|
||||
@Post('/add')
|
||||
async addUser(@Body() body: { username: string; password: string; remark?: string }) {
|
||||
const { username, password, remark } = body;
|
||||
async addUser(@Body() body: { username: string; password: string; email?: string; remark?: string }) {
|
||||
const { username, password, email, remark } = body;
|
||||
try {
|
||||
// 新增用户(支持备注)
|
||||
await this.userService.addUser(username, password, remark);
|
||||
// 新增用户 支持邮箱与备注
|
||||
await this.userService.addUser(username, password, remark, email);
|
||||
return successResponse(true);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
|
|
@ -60,6 +60,7 @@ export class UserController {
|
|||
pageSize: number;
|
||||
remark?: string;
|
||||
username?: string;
|
||||
email?: string;
|
||||
isActive?: string;
|
||||
isSuper?: string;
|
||||
isAdmin?: string;
|
||||
|
|
@ -67,7 +68,7 @@ export class UserController {
|
|||
sortOrder?: string;
|
||||
}
|
||||
) {
|
||||
const { current = 1, pageSize = 10, remark, username, isActive, isSuper, isAdmin, sortField, sortOrder } = query;
|
||||
const { current = 1, pageSize = 10, remark, username, email, isActive, isSuper, isAdmin, sortField, sortOrder } = query;
|
||||
// 将字符串布尔转换为真实布尔
|
||||
const toBool = (v?: string) => (v === undefined ? undefined : v === 'true');
|
||||
// 处理排序方向
|
||||
|
|
@ -80,6 +81,7 @@ export class UserController {
|
|||
{
|
||||
remark,
|
||||
username,
|
||||
email,
|
||||
isActive: toBool(isActive),
|
||||
isSuper: toBool(isSuper),
|
||||
isAdmin: toBool(isAdmin),
|
||||
|
|
@ -112,7 +114,7 @@ export class UserController {
|
|||
// 更新用户(支持用户名/密码/权限/角色更新)
|
||||
@Post('/update/:id')
|
||||
async updateUser(
|
||||
@Body() body: { username?: string; password?: string; isSuper?: boolean; isAdmin?: boolean; permissions?: string[]; remark?: string },
|
||||
@Body() body: { username?: string; password?: string; email?: string; isSuper?: boolean; isAdmin?: boolean; permissions?: string[]; remark?: string },
|
||||
@Query('id') id?: number
|
||||
) {
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -9,8 +9,7 @@ import {
|
|||
} from '@midwayjs/decorator';
|
||||
import { Context } from '@midwayjs/koa';
|
||||
import * as crypto from 'crypto';
|
||||
import { WpProductService } from '../service/wp_product.service';
|
||||
import { WPService } from '../service/wp.service';
|
||||
|
||||
import { SiteService } from '../service/site.service';
|
||||
import { OrderService } from '../service/order.service';
|
||||
|
||||
|
|
@ -18,11 +17,7 @@ import { OrderService } from '../service/order.service';
|
|||
export class WebhookController {
|
||||
private secret = 'YOONE24kd$kjcdjflddd';
|
||||
|
||||
@Inject()
|
||||
private readonly wpProductService: WpProductService;
|
||||
|
||||
@Inject()
|
||||
private readonly wpApiService: WPService;
|
||||
// 平台服务保留按需注入
|
||||
|
||||
@Inject()
|
||||
private readonly orderService: OrderService;
|
||||
|
|
@ -79,32 +74,10 @@ export class WebhookController {
|
|||
switch (topic) {
|
||||
case 'product.created':
|
||||
case 'product.updated':
|
||||
// 变体更新
|
||||
if (body.type === 'variation') {
|
||||
const variation = await this.wpApiService.getVariation(
|
||||
site,
|
||||
body.parent_id,
|
||||
body.id
|
||||
);
|
||||
this.wpProductService.syncVariation(
|
||||
siteId,
|
||||
body.parent_id,
|
||||
variation
|
||||
);
|
||||
break;
|
||||
}
|
||||
const variations =
|
||||
body.type === 'variable'
|
||||
? await this.wpApiService.getVariations(site, body.id)
|
||||
: [];
|
||||
await this.wpProductService.syncProductAndVariations(
|
||||
site.id,
|
||||
body,
|
||||
variations
|
||||
);
|
||||
// 不再写入本地,平台事件仅确认接收
|
||||
break;
|
||||
case 'product.deleted':
|
||||
await this.wpProductService.delWpProduct(site.id, body.id);
|
||||
// 不再写入本地,平台事件仅确认接收
|
||||
break;
|
||||
case 'order.created':
|
||||
case 'order.updated':
|
||||
|
|
|
|||
|
|
@ -22,8 +22,7 @@ import {
|
|||
BatchUpdateTagsDTO,
|
||||
BatchUpdateProductsDTO,
|
||||
} from '../dto/wp_product.dto';
|
||||
import { WPService } from '../service/wp.service';
|
||||
import { SiteService } from '../service/site.service';
|
||||
|
||||
import {
|
||||
ProductsRes,
|
||||
} from '../dto/reponse.dto';
|
||||
|
|
@ -34,23 +33,14 @@ export class WpProductController {
|
|||
@Inject()
|
||||
private readonly wpProductService: WpProductService;
|
||||
|
||||
@Inject()
|
||||
private readonly wpApiService: WPService;
|
||||
|
||||
@Inject()
|
||||
private readonly siteService: SiteService;
|
||||
// 平台服务保留按需注入
|
||||
|
||||
@ApiOkResponse({
|
||||
type: BooleanRes,
|
||||
})
|
||||
@Del('/:id')
|
||||
async delete(@Param('id') id: number) {
|
||||
try {
|
||||
await this.wpProductService.deleteById(id);
|
||||
return successResponse(true);
|
||||
} catch (error) {
|
||||
return errorResponse(error.message || '删除失败');
|
||||
}
|
||||
return errorResponse('接口已废弃,请改用 /site-api/:siteId/products 删除');
|
||||
}
|
||||
|
||||
@ApiOkResponse({
|
||||
|
|
@ -70,6 +60,18 @@ export class WpProductController {
|
|||
}
|
||||
}
|
||||
|
||||
@ApiOkResponse({
|
||||
type: BooleanRes,
|
||||
})
|
||||
@Post('/setconstitution')
|
||||
async setConstitution(@Body() body: any) {
|
||||
try {
|
||||
return successResponse(true);
|
||||
} catch (error) {
|
||||
return errorResponse(error.message || '设置失败');
|
||||
}
|
||||
}
|
||||
|
||||
@ApiOkResponse({
|
||||
type: BooleanRes,
|
||||
})
|
||||
|
|
@ -132,12 +134,7 @@ export class WpProductController {
|
|||
})
|
||||
@Get('/list')
|
||||
async getWpProducts(@Query() query: QueryWpProductDTO) {
|
||||
try {
|
||||
const data = await this.wpProductService.getProductList(query);
|
||||
return successResponse(data);
|
||||
} catch (error) {
|
||||
return errorResponse(error.message);
|
||||
}
|
||||
return errorResponse('接口已废弃,请改用 /site-api/:siteId/products 列表');
|
||||
}
|
||||
|
||||
@ApiOkResponse({
|
||||
|
|
@ -169,29 +166,7 @@ export class WpProductController {
|
|||
@Param('siteId') siteId: number,
|
||||
@Body() body: any
|
||||
) {
|
||||
try {
|
||||
// 过滤掉前端可能传入的多余字段
|
||||
const { fromProductId, ...productData } = body;
|
||||
|
||||
if (productData.type === 'single') {
|
||||
productData.type = 'simple';
|
||||
}
|
||||
const site = await this.siteService.get(siteId, true);
|
||||
const result = await this.wpApiService.createProduct(
|
||||
site,
|
||||
productData
|
||||
);
|
||||
if (result) {
|
||||
// 同步回本地数据库
|
||||
await this.wpProductService.syncProductAndVariations(siteId, result, []);
|
||||
return successResponse(result, '产品创建成功');
|
||||
}
|
||||
return errorResponse('产品创建失败');
|
||||
} catch (error) {
|
||||
console.error('创建产品失败:', error);
|
||||
// 返回更详细的错误信息,特别是来自 WooCommerce 的错误
|
||||
return errorResponse(error.response?.data?.message || error.message || '产品创建失败');
|
||||
}
|
||||
return errorResponse('接口已废弃,请改用 /site-api/:siteId/products 创建');
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -208,45 +183,7 @@ export class WpProductController {
|
|||
@Param('productId') productId: string,
|
||||
@Body() body: UpdateWpProductDTO
|
||||
) {
|
||||
try {
|
||||
// ? 这个是啥意思
|
||||
const isDuplicate = await this.wpProductService.isSkuDuplicate(
|
||||
body.sku,
|
||||
siteId,
|
||||
productId
|
||||
);
|
||||
if (isDuplicate) {
|
||||
return errorResponse('SKU已存在');
|
||||
}
|
||||
|
||||
const site = await this.siteService.get(siteId, true);
|
||||
|
||||
// Resolve tags
|
||||
if (body.tags && body.tags.length > 0) {
|
||||
const resolvedTags = await this.wpApiService.ensureTags(site, body.tags);
|
||||
(body as any).tags = resolvedTags;
|
||||
}
|
||||
|
||||
// Resolve categories
|
||||
if (body.categories && body.categories.length > 0) {
|
||||
const resolvedCategories = await this.wpApiService.ensureCategories(site, body.categories);
|
||||
(body as any).categories = resolvedCategories;
|
||||
}
|
||||
|
||||
const result = await this.wpApiService.updateProduct(
|
||||
site,
|
||||
productId,
|
||||
body
|
||||
);
|
||||
if (result) {
|
||||
this.wpProductService.updateWpProduct(siteId, productId, body);
|
||||
return successResponse(result, '产品更新成功');
|
||||
}
|
||||
return errorResponse('产品更新失败');
|
||||
} catch (error) {
|
||||
console.error('更新产品失败:', error);
|
||||
return errorResponse(error.message || '产品更新失败');
|
||||
}
|
||||
return errorResponse('接口已废弃,请改用 /site-api/:siteId/products/:id 更新');
|
||||
}
|
||||
|
||||
@ApiOkResponse({
|
||||
|
|
@ -275,37 +212,7 @@ export class WpProductController {
|
|||
@Param('variationId') variationId: string,
|
||||
@Body() body: UpdateVariationDTO
|
||||
) {
|
||||
try {
|
||||
const isDuplicate = await this.wpProductService.isSkuDuplicate(
|
||||
body.sku,
|
||||
siteId,
|
||||
productId,
|
||||
variationId
|
||||
);
|
||||
if (isDuplicate) {
|
||||
return errorResponse('SKU已存在');
|
||||
}
|
||||
const site = await this.siteService.get(siteId, true);
|
||||
const result = await this.wpApiService.updateVariation(
|
||||
site,
|
||||
productId,
|
||||
variationId,
|
||||
body
|
||||
);
|
||||
if (result) {
|
||||
this.wpProductService.updateWpProductVaritation(
|
||||
siteId,
|
||||
productId,
|
||||
variationId,
|
||||
body
|
||||
);
|
||||
return successResponse(result, '产品变体更新成功');
|
||||
}
|
||||
return errorResponse('变体更新失败');
|
||||
} catch (error) {
|
||||
console.error('更新变体失败:', error);
|
||||
return errorResponse(error.message || '产品变体更新失败');
|
||||
}
|
||||
return errorResponse('接口已废弃,请改用 /site-api/:siteId/products/:productId/variations/:variationId 更新');
|
||||
}
|
||||
|
||||
@ApiOkResponse({
|
||||
|
|
|
|||
|
|
@ -20,6 +20,10 @@ export class User {
|
|||
@Column({ type: 'simple-array', nullable: true })
|
||||
permissions: string[]; // 自定义权限 (如:['user:add', 'user:edit'])
|
||||
|
||||
// 新增邮箱字段,可选且唯一
|
||||
@Column({ unique: true, nullable: true })
|
||||
email?: string;
|
||||
|
||||
@Column({ default: false })
|
||||
isSuper: boolean; // 超级管理员
|
||||
|
||||
|
|
|
|||
|
|
@ -69,13 +69,19 @@ export interface ISiteAdapter {
|
|||
*/
|
||||
deleteProduct(id: string | number): Promise<boolean>;
|
||||
|
||||
batchProcessProducts?(data: { create?: any[]; update?: any[]; delete?: Array<string | number> }): Promise<any>;
|
||||
|
||||
createOrder(data: Partial<UnifiedOrderDTO>): Promise<UnifiedOrderDTO>;
|
||||
updateOrder(id: string | number, data: Partial<UnifiedOrderDTO>): Promise<boolean>;
|
||||
deleteOrder(id: string | number): Promise<boolean>;
|
||||
|
||||
batchProcessOrders?(data: { create?: any[]; update?: any[]; delete?: Array<string | number> }): Promise<any>;
|
||||
|
||||
getCustomers(params: UnifiedSearchParamsDTO): Promise<UnifiedPaginationDTO<UnifiedCustomerDTO>>;
|
||||
getCustomer(id: string | number): Promise<UnifiedCustomerDTO>;
|
||||
createCustomer(data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO>;
|
||||
updateCustomer(id: string | number, data: Partial<UnifiedCustomerDTO>): Promise<UnifiedCustomerDTO>;
|
||||
deleteCustomer(id: string | number): Promise<boolean>;
|
||||
|
||||
batchProcessCustomers?(data: { create?: any[]; update?: any[]; delete?: Array<string | number> }): Promise<any>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1 @@
|
|||
import { FORMAT, ILogger, Logger } from '@midwayjs/core';
|
||||
import { IJob, Job } from '@midwayjs/cron';
|
||||
|
||||
@Job({
|
||||
cronTime: FORMAT.CRONTAB.EVERY_DAY,
|
||||
runOnInit: true,
|
||||
})
|
||||
export class SyncProductJob implements IJob {
|
||||
@Logger()
|
||||
logger: ILogger;
|
||||
|
||||
onTick() {
|
||||
}
|
||||
onComplete?(result: any) {}
|
||||
}
|
||||
export {}
|
||||
|
|
|
|||
|
|
@ -783,6 +783,41 @@ export class ProductService {
|
|||
return await this.getProductComponents(productId);
|
||||
}
|
||||
|
||||
// 站点SKU绑定:覆盖式绑定一组站点SKU到产品
|
||||
async bindSiteSkus(productId: number, codes: string[]): Promise<ProductSiteSku[]> {
|
||||
const product = await this.productModel.findOne({ where: { id: productId } });
|
||||
if (!product) throw new Error(`产品 ID ${productId} 不存在`);
|
||||
const normalized = (codes || [])
|
||||
.map(c => String(c).trim())
|
||||
.filter(c => c.length > 0);
|
||||
await this.productSiteSkuModel.delete({ productId });
|
||||
if (normalized.length === 0) return [];
|
||||
const entities = normalized.map(code => {
|
||||
const e = new ProductSiteSku();
|
||||
e.productId = productId;
|
||||
e.code = code;
|
||||
return e;
|
||||
});
|
||||
return await this.productSiteSkuModel.save(entities);
|
||||
}
|
||||
|
||||
// 站点SKU绑定:按单个 code 绑定到指定产品(若已有则更新归属)
|
||||
async bindProductBySiteSku(code: string, productId: number): Promise<ProductSiteSku> {
|
||||
const product = await this.productModel.findOne({ where: { id: productId } });
|
||||
if (!product) throw new Error(`产品 ID ${productId} 不存在`);
|
||||
const skuCode = String(code || '').trim();
|
||||
if (!skuCode) throw new Error('站点SKU不能为空');
|
||||
const existing = await this.productSiteSkuModel.findOne({ where: { code: skuCode } });
|
||||
if (existing) {
|
||||
existing.productId = productId;
|
||||
return await this.productSiteSkuModel.save(existing);
|
||||
}
|
||||
const e = new ProductSiteSku();
|
||||
e.productId = productId;
|
||||
e.code = skuCode;
|
||||
return await this.productSiteSkuModel.save(e);
|
||||
}
|
||||
|
||||
// 重复定义的 getProductList 已合并到前面的实现(移除重复)
|
||||
|
||||
async updatenameCn(id: number, nameCn: string): Promise<Product> {
|
||||
|
|
@ -804,18 +839,7 @@ export class ProductService {
|
|||
throw new Error(`产品 ID ${id} 不存在`);
|
||||
}
|
||||
|
||||
// 查询 wp_product 表中是否存在与该 SKU 关联的产品
|
||||
const wpProduct = await this.wpProductModel.findOne({ where: { sku: product.sku } });
|
||||
if (wpProduct) {
|
||||
throw new Error('无法删除,请先删除关联的WP产品');
|
||||
}
|
||||
|
||||
const variation = await this.variationModel.findOne({ where: { sku: product.sku } });
|
||||
|
||||
if (variation) {
|
||||
console.log(variation);
|
||||
throw new Error('无法删除,请先删除关联的WP变体');
|
||||
}
|
||||
// 不再阻塞于远端站点商品/变体的存在,删除仅按本地引用保护
|
||||
|
||||
// 删除产品
|
||||
const result = await this.productModel.delete(id);
|
||||
|
|
|
|||
|
|
@ -82,7 +82,8 @@ export class UserService {
|
|||
}
|
||||
|
||||
// 新增用户(支持可选备注)
|
||||
async addUser(username: string, password: string, remark?: string) {
|
||||
async addUser(username: string, password: string, remark?: string, email?: string) {
|
||||
// 条件判断 检查用户名是否已存在
|
||||
const existingUser = await this.userModel.findOne({
|
||||
where: { username },
|
||||
});
|
||||
|
|
@ -90,9 +91,17 @@ export class UserService {
|
|||
throw new Error('用户已存在');
|
||||
}
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
// 条件判断 若提供邮箱则校验唯一性并赋值
|
||||
if (email) {
|
||||
const existingEmail = await this.userModel.findOne({ where: { email } });
|
||||
if (existingEmail) {
|
||||
throw new Error('邮箱已存在');
|
||||
}
|
||||
}
|
||||
const user = this.userModel.create({
|
||||
username,
|
||||
password: hashedPassword,
|
||||
...(email ? { email } : {}),
|
||||
// 备注字段赋值(若提供)
|
||||
...(remark ? { remark } : {}),
|
||||
});
|
||||
|
|
@ -106,6 +115,7 @@ export class UserService {
|
|||
filters: {
|
||||
remark?: string;
|
||||
username?: string;
|
||||
email?: string;
|
||||
isActive?: boolean;
|
||||
isSuper?: boolean;
|
||||
isAdmin?: boolean;
|
||||
|
|
@ -118,12 +128,15 @@ export class UserService {
|
|||
// 条件判断:构造 where 条件
|
||||
const where: Record<string, any> = {};
|
||||
if (filters.username) where.username = Like(`%${filters.username}%`); // 用户名精确匹配(如需模糊可改为 Like)
|
||||
// 条件判断 邮箱模糊搜索
|
||||
if (filters.email) where.email = Like(`%${filters.email}%`);
|
||||
if (typeof filters.isActive === 'boolean') where.isActive = filters.isActive; // 按启用状态过滤
|
||||
if (typeof filters.isSuper === 'boolean') where.isSuper = filters.isSuper; // 按超管过滤
|
||||
if (typeof filters.isAdmin === 'boolean') where.isAdmin = filters.isAdmin; // 按管理员过滤
|
||||
if (filters.remark) where.remark = Like(`%${filters.remark}%`); // 备注模糊搜索
|
||||
|
||||
const validSortFields = ['id', 'username', 'isActive', 'isSuper', 'isAdmin', 'remark'];
|
||||
// 条件判断 支持邮箱排序字段
|
||||
const validSortFields = ['id', 'username', 'email', 'isActive', 'isSuper', 'isAdmin', 'remark'];
|
||||
const sortField = validSortFields.includes(sorter.field) ? sorter.field : 'id';
|
||||
const sortOrder = sorter.order === 'ASC' ? 'ASC' : 'DESC';
|
||||
|
||||
|
|
@ -151,6 +164,7 @@ export class UserService {
|
|||
payload: {
|
||||
username?: string;
|
||||
password?: string;
|
||||
email?: string;
|
||||
isSuper?: boolean;
|
||||
isAdmin?: boolean;
|
||||
permissions?: string[];
|
||||
|
|
@ -175,6 +189,13 @@ export class UserService {
|
|||
user.password = await bcrypt.hash(payload.password, 10);
|
||||
}
|
||||
|
||||
// 条件判断 若提供新邮箱且与原邮箱不同,进行唯一性校验
|
||||
if (payload.email && payload.email !== user.email) {
|
||||
const existEmail = await this.userModel.findOne({ where: { email: payload.email } });
|
||||
if (existEmail) throw new Error('邮箱已存在');
|
||||
user.email = payload.email;
|
||||
}
|
||||
|
||||
// 条件判断:更新布尔与权限字段(若提供则覆盖)
|
||||
if (typeof payload.isSuper === 'boolean') user.isSuper = payload.isSuper;
|
||||
if (typeof payload.isAdmin === 'boolean') user.isAdmin = payload.isAdmin;
|
||||
|
|
|
|||
|
|
@ -168,6 +168,16 @@ export class WPService implements IPlatformService {
|
|||
return await this.sdkGetPage<WpProduct>(api, 'products', { page, per_page: pageSize });
|
||||
}
|
||||
|
||||
|
||||
// 导出 WooCommerce 产品为特殊CSV(平台特性)
|
||||
async exportProductsCsvSpecial(site: any, page: number = 1, pageSize: number = 100): Promise<string> {
|
||||
const list = await this.getProducts(site, page, pageSize);
|
||||
const header = ['id','name','type','status','sku','regular_price','sale_price','stock_status','stock_quantity'];
|
||||
const rows = (list.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 csv;
|
||||
}
|
||||
|
||||
async getVariations(site: any, productId: number, page: number = 1, pageSize: number = 100): Promise<any> {
|
||||
const api = this.createApi(site, 'wc/v3');
|
||||
return await this.sdkGetPage<Variation>(api, `products/${productId}/variations`, { page, per_page: pageSize });
|
||||
|
|
|
|||
Loading…
Reference in New Issue