491 lines
19 KiB
TypeScript
491 lines
19 KiB
TypeScript
import { Router, type Request, type Response } from 'express';
|
|
import { eq, and, max } from 'drizzle-orm';
|
|
import { db } from '../../config/database.js';
|
|
import { wwwSectionBlocks, wwwSpecGroups, wwwSpecItems } from '../../schemas/index.js';
|
|
import logger from '../../config/logger.js';
|
|
import { ApiResponse } from '../../utils/response.js';
|
|
import { authMiddleware } from '../../middleware/auth.js';
|
|
|
|
const router = Router();
|
|
router.use(authMiddleware);
|
|
|
|
// ===== 3.1 Section 区块 =====
|
|
|
|
// GET /api/www/sections?page_type=home&product_id=1
|
|
router.get('/api/www/sections', async (req: Request, res: Response) => {
|
|
try {
|
|
const pageType = req.query.page_type as string;
|
|
const productId = req.query.product_id ? Number(req.query.product_id) : undefined;
|
|
|
|
if (!pageType) {
|
|
res.json(ApiResponse.error('page_type 参数必填'));
|
|
return;
|
|
}
|
|
|
|
const conditions = [eq(wwwSectionBlocks.pageType, pageType)];
|
|
if (productId) conditions.push(eq(wwwSectionBlocks.productId, productId));
|
|
|
|
const rows = await db.select().from(wwwSectionBlocks)
|
|
.where(and(...conditions))
|
|
.orderBy(wwwSectionBlocks.sortOrder);
|
|
|
|
res.json(ApiResponse.success({ items: rows, total: rows.length }));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/sections] GET error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('获取 Section 列表失败'));
|
|
}
|
|
});
|
|
|
|
// GET /api/www/sections/:id(含子数据)
|
|
router.get('/api/www/sections/:id', async (req: Request, res: Response) => {
|
|
try {
|
|
const id = Number(req.params.id);
|
|
const [section] = await db.select().from(wwwSectionBlocks).where(eq(wwwSectionBlocks.id, id));
|
|
if (!section) {
|
|
res.json(ApiResponse.noData('Section 不存在'));
|
|
return;
|
|
}
|
|
|
|
// 如果是 spec_table 布局,附带参数分组数据
|
|
let specGroups: unknown[] = [];
|
|
if (section.layout === 'spec_table') {
|
|
const groups = await db.select().from(wwwSpecGroups)
|
|
.where(eq(wwwSpecGroups.sectionBlockId, id))
|
|
.orderBy(wwwSpecGroups.sortOrder);
|
|
|
|
specGroups = await Promise.all(groups.map(async (g) => {
|
|
const items = await db.select().from(wwwSpecItems)
|
|
.where(eq(wwwSpecItems.specGroupId, g.id))
|
|
.orderBy(wwwSpecItems.sortOrder);
|
|
return { ...g, items };
|
|
}));
|
|
}
|
|
|
|
res.json(ApiResponse.success({ ...section, specGroups }));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/sections/:id] GET error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('获取 Section 详情失败'));
|
|
}
|
|
});
|
|
|
|
// POST /api/www/sections
|
|
router.post('/api/www/sections', async (req: Request, res: Response) => {
|
|
try {
|
|
const b = req.body;
|
|
if (!b.page_type || !b.layout) {
|
|
res.json(ApiResponse.error('page_type 和 layout 为必填项'));
|
|
return;
|
|
}
|
|
|
|
// 未指定排序时,自动追加到同 page_type(及同产品)已有区块的最后,避免默认 0 导致乱序
|
|
let sortOrder = Number(b.sort_order) || 0;
|
|
if (b.sort_order === undefined || b.sort_order === null) {
|
|
const scope = [eq(wwwSectionBlocks.pageType, b.page_type)];
|
|
if (b.product_id) scope.push(eq(wwwSectionBlocks.productId, b.product_id));
|
|
const [agg] = await db.select({ max: max(wwwSectionBlocks.sortOrder) }).from(wwwSectionBlocks).where(and(...scope));
|
|
sortOrder = (agg?.max ?? -1) + 1;
|
|
}
|
|
|
|
const result = await db.insert(wwwSectionBlocks).values({
|
|
pageType: b.page_type,
|
|
productId: b.product_id || null,
|
|
layout: b.layout,
|
|
theme: b.theme || 'light',
|
|
overlineColor: b.overline_color || '',
|
|
titleColor: b.title_color || '',
|
|
bodyColor: b.body_color || '',
|
|
bgType: b.bg_type || 'color',
|
|
bgValue: b.bg_value || '',
|
|
overlineZh: b.overline_zh || '',
|
|
overlineEn: b.overline_en || '',
|
|
titleZh: b.title_zh || '',
|
|
titleEn: b.title_en || '',
|
|
subtitleZh: b.subtitle_zh || '',
|
|
subtitleEn: b.subtitle_en || '',
|
|
contentZh: b.content_zh || null,
|
|
contentEn: b.content_en || null,
|
|
ctaPrimaryZh: b.cta_primary_zh || '',
|
|
ctaPrimaryEn: b.cta_primary_en || '',
|
|
ctaPrimaryUrl: b.cta_primary_url || '',
|
|
ctaSecondaryZh: b.cta_secondary_zh || '',
|
|
ctaSecondaryEn: b.cta_secondary_en || '',
|
|
ctaSecondaryUrl: b.cta_secondary_url || '',
|
|
mediaUrlZh: b.media_url_zh || '',
|
|
mediaUrlEn: b.media_url_en || '',
|
|
videoUrl: b.video_url || '',
|
|
config: b.config || null,
|
|
isVisible: b.is_visible !== undefined ? (b.is_visible ? 1 : 0) : 1,
|
|
sortOrder,
|
|
});
|
|
|
|
const newId = result[0].insertId;
|
|
const [created] = await db.select().from(wwwSectionBlocks).where(eq(wwwSectionBlocks.id, newId));
|
|
res.json(ApiResponse.success(created, 'Section 已创建'));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/sections] POST error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('创建 Section 失败'));
|
|
}
|
|
});
|
|
|
|
// PUT /api/www/sections/:id
|
|
router.put('/api/www/sections/:id', async (req: Request, res: Response) => {
|
|
try {
|
|
const id = Number(req.params.id);
|
|
const [existing] = await db.select().from(wwwSectionBlocks).where(eq(wwwSectionBlocks.id, id));
|
|
if (!existing) {
|
|
res.json(ApiResponse.noData('Section 不存在'));
|
|
return;
|
|
}
|
|
|
|
const b = req.body;
|
|
await db.update(wwwSectionBlocks).set({
|
|
layout: b.layout ?? existing.layout,
|
|
theme: b.theme ?? existing.theme,
|
|
overlineColor: b.overline_color ?? existing.overlineColor,
|
|
titleColor: b.title_color ?? existing.titleColor,
|
|
bodyColor: b.body_color ?? existing.bodyColor,
|
|
bgType: b.bg_type ?? existing.bgType,
|
|
bgValue: b.bg_value ?? existing.bgValue,
|
|
overlineZh: b.overline_zh ?? existing.overlineZh,
|
|
overlineEn: b.overline_en ?? existing.overlineEn,
|
|
titleZh: b.title_zh ?? existing.titleZh,
|
|
titleEn: b.title_en ?? existing.titleEn,
|
|
subtitleZh: b.subtitle_zh ?? existing.subtitleZh,
|
|
subtitleEn: b.subtitle_en ?? existing.subtitleEn,
|
|
contentZh: b.content_zh !== undefined ? b.content_zh : existing.contentZh,
|
|
contentEn: b.content_en !== undefined ? b.content_en : existing.contentEn,
|
|
ctaPrimaryZh: b.cta_primary_zh ?? existing.ctaPrimaryZh,
|
|
ctaPrimaryEn: b.cta_primary_en ?? existing.ctaPrimaryEn,
|
|
ctaPrimaryUrl: b.cta_primary_url ?? existing.ctaPrimaryUrl,
|
|
ctaSecondaryZh: b.cta_secondary_zh ?? existing.ctaSecondaryZh,
|
|
ctaSecondaryEn: b.cta_secondary_en ?? existing.ctaSecondaryEn,
|
|
ctaSecondaryUrl: b.cta_secondary_url ?? existing.ctaSecondaryUrl,
|
|
mediaUrlZh: b.media_url_zh ?? existing.mediaUrlZh,
|
|
mediaUrlEn: b.media_url_en ?? existing.mediaUrlEn,
|
|
videoUrl: b.video_url ?? existing.videoUrl,
|
|
config: b.config !== undefined ? b.config : existing.config,
|
|
isVisible: b.is_visible !== undefined ? (b.is_visible ? 1 : 0) : existing.isVisible,
|
|
sortOrder: b.sort_order ?? existing.sortOrder,
|
|
}).where(eq(wwwSectionBlocks.id, id));
|
|
|
|
const [updated] = await db.select().from(wwwSectionBlocks).where(eq(wwwSectionBlocks.id, id));
|
|
res.json(ApiResponse.success(updated, 'Section 已更新'));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/sections/:id] PUT error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('更新 Section 失败'));
|
|
}
|
|
});
|
|
|
|
// DELETE /api/www/sections/:id
|
|
router.delete('/api/www/sections/:id', async (req: Request, res: Response) => {
|
|
try {
|
|
const id = Number(req.params.id);
|
|
const [existing] = await db.select().from(wwwSectionBlocks).where(eq(wwwSectionBlocks.id, id));
|
|
if (!existing) {
|
|
res.json(ApiResponse.noData('Section 不存在'));
|
|
return;
|
|
}
|
|
|
|
// 级联删除参数规格
|
|
const groups = await db.select().from(wwwSpecGroups).where(eq(wwwSpecGroups.sectionBlockId, id));
|
|
for (const g of groups) {
|
|
await db.delete(wwwSpecItems).where(eq(wwwSpecItems.specGroupId, g.id));
|
|
}
|
|
await db.delete(wwwSpecGroups).where(eq(wwwSpecGroups.sectionBlockId, id));
|
|
await db.delete(wwwSectionBlocks).where(eq(wwwSectionBlocks.id, id));
|
|
|
|
res.json(ApiResponse.success(null, '删除成功'));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/sections/:id] DELETE error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('删除 Section 失败'));
|
|
}
|
|
});
|
|
|
|
// PATCH /api/www/sections/sort
|
|
router.patch('/api/www/sections/sort', async (req: Request, res: Response) => {
|
|
try {
|
|
const items: { id: number; sort_order: number }[] = req.body;
|
|
if (!Array.isArray(items)) {
|
|
res.json(ApiResponse.error('请求体应为数组'));
|
|
return;
|
|
}
|
|
for (const item of items) {
|
|
await db.update(wwwSectionBlocks).set({ sortOrder: item.sort_order }).where(eq(wwwSectionBlocks.id, item.id));
|
|
}
|
|
res.json(ApiResponse.success(null, '排序已更新'));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/sections/sort] PATCH error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('更新排序失败'));
|
|
}
|
|
});
|
|
|
|
// POST /api/www/sections/:id/duplicate
|
|
router.post('/api/www/sections/:id/duplicate', async (req: Request, res: Response) => {
|
|
try {
|
|
const id = Number(req.params.id);
|
|
const [source] = await db.select().from(wwwSectionBlocks).where(eq(wwwSectionBlocks.id, id));
|
|
if (!source) {
|
|
res.json(ApiResponse.noData('Section 不存在'));
|
|
return;
|
|
}
|
|
|
|
const { id: _, createdAt: __, updatedAt: ___, ...data } = source;
|
|
const result = await db.insert(wwwSectionBlocks).values({
|
|
...data,
|
|
titleZh: `${source.titleZh} (副本)`,
|
|
titleEn: `${source.titleEn} (copy)`,
|
|
sortOrder: source.sortOrder + 1,
|
|
});
|
|
|
|
const newId = result[0].insertId;
|
|
|
|
// 复制参数规格
|
|
if (source.layout === 'spec_table') {
|
|
const groups = await db.select().from(wwwSpecGroups).where(eq(wwwSpecGroups.sectionBlockId, id));
|
|
for (const g of groups) {
|
|
const gResult = await db.insert(wwwSpecGroups).values({
|
|
sectionBlockId: newId,
|
|
titleZh: g.titleZh,
|
|
titleEn: g.titleEn,
|
|
sortOrder: g.sortOrder,
|
|
defaultVisible: g.defaultVisible,
|
|
});
|
|
const newGroupId = gResult[0].insertId;
|
|
const items = await db.select().from(wwwSpecItems).where(eq(wwwSpecItems.specGroupId, g.id));
|
|
if (items.length > 0) {
|
|
await db.insert(wwwSpecItems).values(items.map((item) => ({
|
|
specGroupId: newGroupId,
|
|
nameZh: item.nameZh,
|
|
nameEn: item.nameEn,
|
|
valueZh: item.valueZh,
|
|
valueEn: item.valueEn,
|
|
sortOrder: item.sortOrder,
|
|
})));
|
|
}
|
|
}
|
|
}
|
|
|
|
const [created] = await db.select().from(wwwSectionBlocks).where(eq(wwwSectionBlocks.id, newId));
|
|
res.json(ApiResponse.success(created, 'Section 已复制'));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/sections/duplicate] POST error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('复制 Section 失败'));
|
|
}
|
|
});
|
|
|
|
// ===== 3.2 Spec Table 参数规格 =====
|
|
|
|
// GET /api/www/sections/:section_id/spec-groups
|
|
router.get('/api/www/sections/:section_id/spec-groups', async (req: Request, res: Response) => {
|
|
try {
|
|
const sectionId = Number(req.params.section_id);
|
|
const groups = await db.select().from(wwwSpecGroups)
|
|
.where(eq(wwwSpecGroups.sectionBlockId, sectionId))
|
|
.orderBy(wwwSpecGroups.sortOrder);
|
|
|
|
// 附带每组的参数项
|
|
const result = await Promise.all(groups.map(async (g) => {
|
|
const items = await db.select().from(wwwSpecItems)
|
|
.where(eq(wwwSpecItems.specGroupId, g.id))
|
|
.orderBy(wwwSpecItems.sortOrder);
|
|
return { ...g, items };
|
|
}));
|
|
|
|
res.json(ApiResponse.success({ items: result, total: result.length }));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/spec-groups] GET error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('获取参数分组失败'));
|
|
}
|
|
});
|
|
|
|
// POST /api/www/sections/:section_id/spec-groups
|
|
router.post('/api/www/sections/:section_id/spec-groups', async (req: Request, res: Response) => {
|
|
try {
|
|
const sectionId = Number(req.params.section_id);
|
|
const { title_zh, title_en, default_visible } = req.body;
|
|
if (!title_zh || !title_en) {
|
|
res.json(ApiResponse.error('title_zh 和 title_en 为必填项'));
|
|
return;
|
|
}
|
|
|
|
const result = await db.insert(wwwSpecGroups).values({
|
|
sectionBlockId: sectionId,
|
|
titleZh: title_zh,
|
|
titleEn: title_en,
|
|
defaultVisible: default_visible || 5,
|
|
});
|
|
|
|
const newId = result[0].insertId;
|
|
const [created] = await db.select().from(wwwSpecGroups).where(eq(wwwSpecGroups.id, newId));
|
|
res.json(ApiResponse.success(created, '参数分组已创建'));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/spec-groups] POST error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('创建参数分组失败'));
|
|
}
|
|
});
|
|
|
|
// PUT /api/www/sections/:section_id/spec-groups/:group_id
|
|
router.put('/api/www/sections/:section_id/spec-groups/:group_id', async (req: Request, res: Response) => {
|
|
try {
|
|
const groupId = Number(req.params.group_id);
|
|
const [existing] = await db.select().from(wwwSpecGroups).where(eq(wwwSpecGroups.id, groupId));
|
|
if (!existing) {
|
|
res.json(ApiResponse.noData('参数分组不存在'));
|
|
return;
|
|
}
|
|
|
|
const { title_zh, title_en, default_visible } = req.body;
|
|
await db.update(wwwSpecGroups).set({
|
|
titleZh: title_zh ?? existing.titleZh,
|
|
titleEn: title_en ?? existing.titleEn,
|
|
defaultVisible: default_visible ?? existing.defaultVisible,
|
|
}).where(eq(wwwSpecGroups.id, groupId));
|
|
|
|
const [updated] = await db.select().from(wwwSpecGroups).where(eq(wwwSpecGroups.id, groupId));
|
|
res.json(ApiResponse.success(updated, '参数分组已更新'));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/spec-groups] PUT error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('更新参数分组失败'));
|
|
}
|
|
});
|
|
|
|
// DELETE /api/www/sections/:section_id/spec-groups/:group_id
|
|
router.delete('/api/www/sections/:section_id/spec-groups/:group_id', async (req: Request, res: Response) => {
|
|
try {
|
|
const groupId = Number(req.params.group_id);
|
|
const [existing] = await db.select().from(wwwSpecGroups).where(eq(wwwSpecGroups.id, groupId));
|
|
if (!existing) {
|
|
res.json(ApiResponse.noData('参数分组不存在'));
|
|
return;
|
|
}
|
|
await db.delete(wwwSpecItems).where(eq(wwwSpecItems.specGroupId, groupId));
|
|
await db.delete(wwwSpecGroups).where(eq(wwwSpecGroups.id, groupId));
|
|
res.json(ApiResponse.success(null, '删除成功'));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/spec-groups] DELETE error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('删除参数分组失败'));
|
|
}
|
|
});
|
|
|
|
// PATCH /api/www/sections/:section_id/spec-groups/sort
|
|
router.patch('/api/www/sections/:section_id/spec-groups/sort', async (req: Request, res: Response) => {
|
|
try {
|
|
const items: { id: number; sort_order: number }[] = req.body;
|
|
if (!Array.isArray(items)) {
|
|
res.json(ApiResponse.error('请求体应为数组'));
|
|
return;
|
|
}
|
|
for (const item of items) {
|
|
await db.update(wwwSpecGroups).set({ sortOrder: item.sort_order }).where(eq(wwwSpecGroups.id, item.id));
|
|
}
|
|
res.json(ApiResponse.success(null, '排序已更新'));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/spec-groups/sort] PATCH error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('更新排序失败'));
|
|
}
|
|
});
|
|
|
|
// GET /api/www/spec-groups/:group_id/items
|
|
router.get('/api/www/spec-groups/:group_id/items', async (req: Request, res: Response) => {
|
|
try {
|
|
const groupId = Number(req.params.group_id);
|
|
const items = await db.select().from(wwwSpecItems)
|
|
.where(eq(wwwSpecItems.specGroupId, groupId))
|
|
.orderBy(wwwSpecItems.sortOrder);
|
|
res.json(ApiResponse.success({ items, total: items.length }));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/spec-items] GET error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('获取参数项失败'));
|
|
}
|
|
});
|
|
|
|
// POST /api/www/spec-groups/:group_id/items
|
|
router.post('/api/www/spec-groups/:group_id/items', async (req: Request, res: Response) => {
|
|
try {
|
|
const groupId = Number(req.params.group_id);
|
|
const { name_zh, name_en, value_zh, value_en } = req.body;
|
|
if (!name_zh || !name_en || !value_zh || !value_en) {
|
|
res.json(ApiResponse.error('name_zh、name_en、value_zh、value_en 为必填项'));
|
|
return;
|
|
}
|
|
|
|
const result = await db.insert(wwwSpecItems).values({
|
|
specGroupId: groupId,
|
|
nameZh: name_zh,
|
|
nameEn: name_en,
|
|
valueZh: value_zh,
|
|
valueEn: value_en,
|
|
});
|
|
|
|
const newId = result[0].insertId;
|
|
const [created] = await db.select().from(wwwSpecItems).where(eq(wwwSpecItems.id, newId));
|
|
res.json(ApiResponse.success(created, '参数项已创建'));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/spec-items] POST error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('创建参数项失败'));
|
|
}
|
|
});
|
|
|
|
// PUT /api/www/spec-items/:id
|
|
router.put('/api/www/spec-items/:id', async (req: Request, res: Response) => {
|
|
try {
|
|
const id = Number(req.params.id);
|
|
const [existing] = await db.select().from(wwwSpecItems).where(eq(wwwSpecItems.id, id));
|
|
if (!existing) {
|
|
res.json(ApiResponse.noData('参数项不存在'));
|
|
return;
|
|
}
|
|
|
|
const { name_zh, name_en, value_zh, value_en } = req.body;
|
|
await db.update(wwwSpecItems).set({
|
|
nameZh: name_zh ?? existing.nameZh,
|
|
nameEn: name_en ?? existing.nameEn,
|
|
valueZh: value_zh ?? existing.valueZh,
|
|
valueEn: value_en ?? existing.valueEn,
|
|
}).where(eq(wwwSpecItems.id, id));
|
|
|
|
const [updated] = await db.select().from(wwwSpecItems).where(eq(wwwSpecItems.id, id));
|
|
res.json(ApiResponse.success(updated, '参数项已更新'));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/spec-items] PUT error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('更新参数项失败'));
|
|
}
|
|
});
|
|
|
|
// DELETE /api/www/spec-items/:id
|
|
router.delete('/api/www/spec-items/:id', async (req: Request, res: Response) => {
|
|
try {
|
|
const id = Number(req.params.id);
|
|
const [existing] = await db.select().from(wwwSpecItems).where(eq(wwwSpecItems.id, id));
|
|
if (!existing) {
|
|
res.json(ApiResponse.noData('参数项不存在'));
|
|
return;
|
|
}
|
|
await db.delete(wwwSpecItems).where(eq(wwwSpecItems.id, id));
|
|
res.json(ApiResponse.success(null, '删除成功'));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/spec-items] DELETE error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('删除参数项失败'));
|
|
}
|
|
});
|
|
|
|
// PATCH /api/www/spec-groups/:group_id/items/sort
|
|
router.patch('/api/www/spec-groups/:group_id/items/sort', async (req: Request, res: Response) => {
|
|
try {
|
|
const items: { id: number; sort_order: number }[] = req.body;
|
|
if (!Array.isArray(items)) {
|
|
res.json(ApiResponse.error('请求体应为数组'));
|
|
return;
|
|
}
|
|
for (const item of items) {
|
|
await db.update(wwwSpecItems).set({ sortOrder: item.sort_order }).where(eq(wwwSpecItems.id, item.id));
|
|
}
|
|
res.json(ApiResponse.success(null, '排序已更新'));
|
|
} catch (e: unknown) {
|
|
logger.error(`[www/spec-items/sort] PATCH error: ${e instanceof Error ? e.message : e}`);
|
|
res.json(ApiResponse.error('更新排序失败'));
|
|
}
|
|
});
|
|
|
|
export default router;
|