补全一些基础信息
This commit is contained in:
@@ -1,6 +1,32 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"items": [
|
||||
{
|
||||
"id": "khhao 订单取样导入",
|
||||
"enabled": true,
|
||||
"priority": 100,
|
||||
"provider": "91kaquan",
|
||||
"platform": "kuaishou",
|
||||
"shopId": "91kaquan",
|
||||
"khhaoShopId": "",
|
||||
"internalSkuCode": "套装-浪漫天命",
|
||||
"internalSkuName": "套装-浪漫天命",
|
||||
"externalSkuCode": "183999074510936",
|
||||
"externalItemId": "183999074510936",
|
||||
"externalSkuName": "183999074510936",
|
||||
"resolvedSkuName": "183999074510936",
|
||||
"cloudSourceKey": "default",
|
||||
"cloudSkuId": 28,
|
||||
"cloudSkuName": "套装-浪漫天命",
|
||||
"vnKey": "1",
|
||||
"autoBuyEnabled": true,
|
||||
"minAssetReserve": 0,
|
||||
"autoReturnNumberAfterDispatch": true,
|
||||
"autoConsumeAfterDispatch": true,
|
||||
"kuaishouConsumeShopId": "3676797936",
|
||||
"kuaishouConsumeShopName": "小鱼儿游戏服务店",
|
||||
"notes": "从 91卡券订单 2613300010204561 导入"
|
||||
},
|
||||
{
|
||||
"id": "套装-浪漫天命",
|
||||
"enabled": true,
|
||||
|
||||
@@ -2,6 +2,13 @@
|
||||
"enabled": true,
|
||||
"baseUrl": "https://s.kwaixiaodian.com",
|
||||
"shops": [
|
||||
{
|
||||
"shopId": "3676797936",
|
||||
"kshopName": "小鱼儿游戏服务店",
|
||||
"cookie": "_did=web_977722931F45F3C4; did=web_mxsox80uwsj3o4bkdsi2xhbxnx5gdlpn; sid=kshop.api; kshop.api_st=Cgxrc2hvcC5hcGkuc3QSoAHnDz5KbyPrdR8uWNB0MspVtX2zUOBrDcysluKZ9P6NT0IaEtnV-24otPiLyxcbH1iStuNg84bSxClfdu63cgMKzi0AovJn5DFqJUjXPfJZ1XxL8xDXOIdvv73SVr0eQhPKrD_twqnWJu-1HLgxl294XMhDPhO8JbNcFhv_cOwBsxjZYUNMs_7kpRHIpDEU_adQH0zQOBVQYu4AzU0rhF2EGhI9zclILZeuwZLmGG1mEKpcYGMiIOkT8avvcn-qSsIRdLyBf0U1Fjoud_FP9qcCFMjohN1AKAUwAQ; kshop.api_ph=a5c01f4da50e7eda8facb70fd1b13af57eb3; bUserId=1000324969453; userId=3676797936; kuaishou.shop.b_st=ChJrdWFpc2hvdS5zaG9wLmIuc3QSoAHbRIrpTvTjW-nbSsGDVimfmFK01ohV83Wh1v9vCN89z4-bJqpa_r7jmXBgL53hZ0OD5Y8Jz5fUPxWT-6PYNCfLVB20XogIDibxfXXjJSW5d4nchty1_veIl6r4NsUhHu0N93XRsx8vUxfQvAwlczsvefFqvPeZ3iAQnACDEVqJ7m7oI5BK9MCobzGGGuznAgtY8l0Z10qy3e5WmPgiSaI4GhLSubyi-uEBAthqCoUoWxNxjR0iINJ8MTa8OcsVUjQ5AuIuH9BtGr4JuS89zMjEz3McjsjmKAUwAQ; kuaishou.shop.b_ph=0a29c066148190b0759c776da8e94775e7dd",
|
||||
"userAvatar": "https://p2-pro.a.yximgs.com/uhead/AB/2024/11/17/20/BMjAyNDExMTcyMDQ3NTdfMzY3Njc5NzkzNl8yX2hkODUyXzIwNg==_s.jpg",
|
||||
"enabled": true
|
||||
},
|
||||
{
|
||||
"shopId": "140000112682050",
|
||||
"kshopName": "洋洋运营",
|
||||
|
||||
@@ -20,12 +20,15 @@ import {
|
||||
getAdminAgisoShopConfigs,
|
||||
getAdminFulfillmentBindingConfigs,
|
||||
getAdminKhhaoSourceConfig,
|
||||
getAdminNinetyoneOrders,
|
||||
consumeAdminKuaishouEticket,
|
||||
failAdminNinetyoneOrder,
|
||||
listAdminCloudtentaclesVirtualNumbers,
|
||||
lookupAdminFulfillmentBindingOrder,
|
||||
queryAdminKuaishouEticketShopInfo,
|
||||
queryAdminKuaishouEticketDetail,
|
||||
queryAdminKhhaoOrders,
|
||||
retryAdminNinetyoneOrder,
|
||||
runAdminCloudtentaclesFullFlow,
|
||||
sendAdminCloudtentaclesSmsCode,
|
||||
syncAdminKhhaoOrders,
|
||||
@@ -48,6 +51,7 @@ import { createJsonHandler, requireAdminRoles } from './shared.js'
|
||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminKhhaoOrderQueryRouteBody} AdminKhhaoOrderQueryRouteBody */
|
||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminKhhaoSourceConfigRouteBody} AdminKhhaoSourceConfigRouteBody */
|
||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminKhhaoTestLoginRouteBody} AdminKhhaoTestLoginRouteBody */
|
||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminEntityRouteParams} AdminEntityRouteParams */
|
||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminKuaishouEticketSourceConfigRouteBody} AdminKuaishouEticketSourceConfigRouteBody */
|
||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminKuaishouEticketDetailQueryRouteBody} AdminKuaishouEticketDetailQueryRouteBody */
|
||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminKuaishouEticketConsumeRouteBody} AdminKuaishouEticketConsumeRouteBody */
|
||||
@@ -234,6 +238,60 @@ router.post('/platform-config/khhao/sync-orders', createJsonHandler(
|
||||
},
|
||||
))
|
||||
|
||||
router.get('/platform-config/ninetyone/orders', createJsonHandler(
|
||||
(req) => getAdminNinetyoneOrders(req.query),
|
||||
{
|
||||
successMessage: 'ok',
|
||||
errorMessage: '读取 91卡券订单失败',
|
||||
scope: '[admin/platform-config/ninetyone/orders]',
|
||||
},
|
||||
))
|
||||
|
||||
router.post('/platform-config/ninetyone/orders/:id/retry', createJsonHandler(
|
||||
(req) => retryAdminNinetyoneOrder((/** @type {AdminEntityRouteParams} */ (req.params)).id),
|
||||
{
|
||||
successMessage: '91卡券订单已重试',
|
||||
errorMessage: '重试 91卡券订单失败',
|
||||
scope: '[admin/platform-config/ninetyone/orders/:id/retry]',
|
||||
audit: (_req, data) => {
|
||||
const result = /** @type {{ orderId?: number, orderNo?: string, taskCount?: number }} */ (data)
|
||||
return {
|
||||
action: 'platform_ninetyone_order_retried',
|
||||
targetType: 'order',
|
||||
targetId: String(result.orderId || '').trim(),
|
||||
data: {
|
||||
orderNo: String(result.orderNo || '').trim(),
|
||||
taskCount: Number(result.taskCount || 0),
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
))
|
||||
|
||||
router.post('/platform-config/ninetyone/orders/:id/fail', createJsonHandler(
|
||||
(req) => failAdminNinetyoneOrder(
|
||||
(/** @type {AdminEntityRouteParams} */ (req.params)).id,
|
||||
/** @type {{ reason?: string }} */ (req.body),
|
||||
),
|
||||
{
|
||||
successMessage: '91卡券订单已标记失败',
|
||||
errorMessage: '标记 91卡券订单失败',
|
||||
scope: '[admin/platform-config/ninetyone/orders/:id/fail]',
|
||||
audit: (req, data) => {
|
||||
const result = /** @type {{ orderId?: number, orderNo?: string }} */ (data)
|
||||
return {
|
||||
action: 'platform_ninetyone_order_failed',
|
||||
targetType: 'order',
|
||||
targetId: String(result.orderId || '').trim(),
|
||||
data: {
|
||||
orderNo: String(result.orderNo || '').trim(),
|
||||
reason: String((/** @type {{ reason?: string }} */ (req.body)).reason || '').trim(),
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
))
|
||||
|
||||
router.post('/platform-config/kuaishou-eticket/query-detail', createJsonHandler(
|
||||
(req) => queryAdminKuaishouEticketDetail(/** @type {AdminKuaishouEticketDetailQueryRouteBody} */ (req.body)),
|
||||
{
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// @ts-check
|
||||
|
||||
import {
|
||||
failOpen91Order,
|
||||
listOpen91Orders,
|
||||
retryOpen91Order,
|
||||
} from '../../platforms/ninetyone/order-service.js'
|
||||
|
||||
/** @param {{ page?: number | string, pageSize?: number | string, status?: string }} [query] */
|
||||
export async function getAdminNinetyoneOrders(query = {}) {
|
||||
return listOpen91Orders({
|
||||
page: Number(query.page || 1) || 1,
|
||||
pageSize: Number(query.pageSize || 20) || 20,
|
||||
status: query.status || 'pending_config',
|
||||
})
|
||||
}
|
||||
|
||||
/** @param {number | string} orderId */
|
||||
export async function retryAdminNinetyoneOrder(orderId) {
|
||||
const result = await retryOpen91Order(orderId)
|
||||
return {
|
||||
orderId: Number(result.order?.id || 0),
|
||||
orderNo: String(result.order?.platform_order_id || '').trim(),
|
||||
orderStatus: String(result.order?.order_status || '').trim(),
|
||||
orderItemCount: Array.isArray(result.orderItems) ? result.orderItems.length : 0,
|
||||
taskCount: Array.isArray(result.tasks) ? result.tasks.length : 0,
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {number | string} orderId */
|
||||
/** @param {{ reason?: string }} [payload] */
|
||||
export async function failAdminNinetyoneOrder(orderId, payload = {}) {
|
||||
const result = await failOpen91Order(orderId, payload.reason)
|
||||
return {
|
||||
orderId: Number(result.order?.id || 0),
|
||||
orderNo: String(result.order?.platform_order_id || '').trim(),
|
||||
orderStatus: String(result.order?.order_status || '').trim(),
|
||||
orderItemCount: Array.isArray(result.orderItems) ? result.orderItems.length : 0,
|
||||
taskCount: Array.isArray(result.tasks) ? result.tasks.length : 0,
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,12 @@ export {
|
||||
syncAdminKhhaoOrders,
|
||||
} from './khhao-service.js'
|
||||
|
||||
export {
|
||||
getAdminNinetyoneOrders,
|
||||
retryAdminNinetyoneOrder,
|
||||
failAdminNinetyoneOrder,
|
||||
} from './ninetyone-service.js'
|
||||
|
||||
export {
|
||||
getAdminKuaishouEticketSourceConfig,
|
||||
updateAdminKuaishouEticketSourceConfig,
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import { hasConfiguredOrderItems } from '../order/product-match-service.js'
|
||||
import { upsertOrderFromSource } from '../order/order-service.js'
|
||||
import { nowIso } from '../../utils/time.js'
|
||||
import { logWebhook } from '../../utils/logger.js'
|
||||
import { parseAmountToFen } from '../../utils/money.js'
|
||||
import {
|
||||
buildOpen91SourceEvent,
|
||||
upsertOpen91PendingOrder,
|
||||
} from '../platforms/ninetyone/order-service.js'
|
||||
import {
|
||||
assertOpen91Config,
|
||||
assertOpen91CreatePayload,
|
||||
@@ -14,8 +16,6 @@ import {
|
||||
normalizeOpen91CreatePayload,
|
||||
OPEN_91_COST_EXCEED_FAIL_CODE,
|
||||
OPEN_91_DEFAULT_FAIL_CODE,
|
||||
OPEN_91_PLATFORM,
|
||||
OPEN_91_PROVIDER,
|
||||
} from './shared.js'
|
||||
|
||||
export async function createOpen91Order(payload = {}, { requestId = '' } = {}) {
|
||||
@@ -26,42 +26,6 @@ export async function createOpen91Order(payload = {}, { requestId = '' } = {}) {
|
||||
assertOpen91Timestamp(normalized.timestamp, config.timestampToleranceSeconds)
|
||||
assertOpen91Signature(payload, config)
|
||||
|
||||
const now = nowIso()
|
||||
const items = [
|
||||
{
|
||||
externalItemId: normalized.productNo,
|
||||
externalSkuCode: normalized.productNo,
|
||||
externalSkuName: normalized.productNo,
|
||||
quantity: normalized.buyNum,
|
||||
spec: {
|
||||
source: OPEN_91_PROVIDER,
|
||||
},
|
||||
snapshot: {
|
||||
source: OPEN_91_PROVIDER,
|
||||
orderNo: normalized.orderNo,
|
||||
productNo: normalized.productNo,
|
||||
callbackUrl: normalized.callbackUrl,
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const configured = await hasConfiguredOrderItems({
|
||||
provider: OPEN_91_PROVIDER,
|
||||
platform: OPEN_91_PLATFORM,
|
||||
shopId: config.shopId,
|
||||
items,
|
||||
})
|
||||
|
||||
if (!configured) {
|
||||
return buildOpen91SuccessResponse({
|
||||
orderNo: normalized.orderNo,
|
||||
outTradeNo: '',
|
||||
orderStatus: 30,
|
||||
failCode: OPEN_91_DEFAULT_FAIL_CODE,
|
||||
failReason: '商品未配置或未匹配到履约规则',
|
||||
})
|
||||
}
|
||||
|
||||
if (normalized.maxAmount) {
|
||||
const maxAmountFen = parseAmountToFen(normalized.maxAmount)
|
||||
if (maxAmountFen < 0) {
|
||||
@@ -75,26 +39,8 @@ export async function createOpen91Order(payload = {}, { requestId = '' } = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
const result = await upsertOrderFromSource({
|
||||
provider: OPEN_91_PROVIDER,
|
||||
platform: OPEN_91_PLATFORM,
|
||||
shopId: config.shopId,
|
||||
shopName: config.shopName,
|
||||
platformOrderId: normalized.orderNo,
|
||||
orderStatus: 'paid',
|
||||
payStatus: 'paid',
|
||||
buyerId: '',
|
||||
buyerName: '',
|
||||
receiverContact: '',
|
||||
totalAmount: 0,
|
||||
currency: 'CNY',
|
||||
rawPayload: {
|
||||
source: OPEN_91_PROVIDER,
|
||||
...normalized,
|
||||
},
|
||||
paidAt: now,
|
||||
items,
|
||||
}, {
|
||||
const sourceEvent = buildOpen91SourceEvent(normalized, config)
|
||||
const result = await upsertOrderFromSource(sourceEvent, {
|
||||
sourceLabel: 'open91',
|
||||
})
|
||||
|
||||
@@ -103,13 +49,31 @@ export async function createOpen91Order(payload = {}, { requestId = '' } = {}) {
|
||||
orderNo: normalized.orderNo,
|
||||
productNo: normalized.productNo,
|
||||
buyNum: normalized.buyNum,
|
||||
configured,
|
||||
ignored: Boolean(result.ignored),
|
||||
ignoreReason: result.ignoreReason || '',
|
||||
orderId: result.order?.id || null,
|
||||
taskCount: Array.isArray(result.tasks) ? result.tasks.length : 0,
|
||||
})
|
||||
|
||||
if (result.ignored || !result.order || !Array.isArray(result.tasks) || result.tasks.length === 0) {
|
||||
if (result.ignored) {
|
||||
const pending = await upsertOpen91PendingOrder(normalized, config)
|
||||
logWebhook('[open-91/create]', '91卡券订单已进入待补全队列', {
|
||||
requestId,
|
||||
orderNo: normalized.orderNo,
|
||||
productNo: normalized.productNo,
|
||||
orderId: pending.order?.id || null,
|
||||
})
|
||||
|
||||
return buildOpen91SuccessResponse({
|
||||
orderNo: normalized.orderNo,
|
||||
outTradeNo: buildOpen91OutTradeNo(pending.order),
|
||||
orderStatus: 10,
|
||||
orderCost: buildOpen91OrderCost(0),
|
||||
cards: '',
|
||||
})
|
||||
}
|
||||
|
||||
if (!result.order || !Array.isArray(result.tasks) || result.tasks.length === 0) {
|
||||
return buildOpen91SuccessResponse({
|
||||
orderNo: normalized.orderNo,
|
||||
outTradeNo: '',
|
||||
|
||||
@@ -3,6 +3,10 @@ import { listTasksByOrderId } from '../../repositories/task-repo.js'
|
||||
import { buildClaimUrl } from '../claim/claim-service.js'
|
||||
import { ensureTaskClaimLink } from '../fulfillment/kuaishou-cloud-task-service.js'
|
||||
import { logWebhook } from '../../utils/logger.js'
|
||||
import {
|
||||
OPEN_91_MANUAL_FAILED_STATUS,
|
||||
OPEN_91_PENDING_CONFIG_STATUS,
|
||||
} from '../platforms/ninetyone/order-service.js'
|
||||
import {
|
||||
assertOpen91Config,
|
||||
assertOpen91QueryPayload,
|
||||
@@ -46,6 +50,31 @@ export async function queryOpen91Order(payload = {}, { requestId = '' } = {}) {
|
||||
}
|
||||
|
||||
const tasks = await listTasksByOrderId(order.id)
|
||||
|
||||
if (tasks.length === 0) {
|
||||
if (String(order.order_status || '').trim() === OPEN_91_PENDING_CONFIG_STATUS) {
|
||||
return buildOpen91SuccessResponse({
|
||||
orderNo: normalized.orderNo,
|
||||
outTradeNo: buildOpen91OutTradeNo(order),
|
||||
orderStatus: 10,
|
||||
failCode: 0,
|
||||
failReason: '',
|
||||
orderCost: buildOpen91OrderCost(0),
|
||||
cards: '',
|
||||
})
|
||||
}
|
||||
|
||||
if (String(order.order_status || '').trim() === OPEN_91_MANUAL_FAILED_STATUS) {
|
||||
return buildOpen91SuccessResponse({
|
||||
orderNo: normalized.orderNo,
|
||||
outTradeNo: buildOpen91OutTradeNo(order),
|
||||
orderStatus: 30,
|
||||
failCode: OPEN_91_DEFAULT_FAIL_CODE,
|
||||
failReason: resolveOpen91OrderFailReason(order) || '商家手动标记无法履约',
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const cardItems = []
|
||||
const readyTaskIds = []
|
||||
|
||||
@@ -123,3 +152,25 @@ export async function queryOpen91Order(payload = {}, { requestId = '' } = {}) {
|
||||
cards: buildOpen91Cards(cardItems, config),
|
||||
})
|
||||
}
|
||||
|
||||
function resolveOpen91OrderFailReason(order) {
|
||||
const payload = parseJsonObject(order?.raw_payload_json)
|
||||
return String(payload.manualFailedReason || '').trim()
|
||||
}
|
||||
|
||||
function parseJsonObject(value) {
|
||||
if (!value) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && !Array.isArray(value)) {
|
||||
return value
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(String(value || '{}'))
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,346 @@
|
||||
// @ts-check
|
||||
|
||||
import { query } from '../../../db/client.js'
|
||||
import { createOrder, findOrderByPlatformOrderId, getOrderById, updateOrder } from '../../../repositories/order-repo.js'
|
||||
import { listOrderItemsByOrderId, replaceOrderItems } from '../../../repositories/order-item-repo.js'
|
||||
import { listTasksByOrderId } from '../../../repositories/task-repo.js'
|
||||
import { upsertOrderFromSource } from '../../order/order-service.js'
|
||||
import {
|
||||
buildOpen91OutTradeNo,
|
||||
normalizeOpen91CreatePayload,
|
||||
OPEN_91_PLATFORM,
|
||||
OPEN_91_PROVIDER,
|
||||
} from '../../open-91/shared.js'
|
||||
import { createHttpError } from '../../../utils/http.js'
|
||||
import { nowIso } from '../../../utils/time.js'
|
||||
|
||||
export const OPEN_91_PENDING_CONFIG_STATUS = 'pending_config'
|
||||
export const OPEN_91_MANUAL_FAILED_STATUS = 'manual_failed'
|
||||
|
||||
export function buildOpen91SourceEvent(payload = {}, config = {}) {
|
||||
const normalized = normalizeOpen91CreatePayload(payload)
|
||||
const productNo = normalized.productNo
|
||||
|
||||
return {
|
||||
provider: OPEN_91_PROVIDER,
|
||||
platform: OPEN_91_PLATFORM,
|
||||
shopId: String(config.shopId || OPEN_91_PROVIDER).trim() || OPEN_91_PROVIDER,
|
||||
shopName: String(config.shopName || '91卡券').trim() || '91卡券',
|
||||
platformOrderId: normalized.orderNo,
|
||||
orderStatus: 'paid',
|
||||
payStatus: 'paid',
|
||||
buyerId: '',
|
||||
buyerName: '',
|
||||
receiverContact: '',
|
||||
totalAmount: 0,
|
||||
currency: 'CNY',
|
||||
paidAt: nowIso(),
|
||||
rawPayload: {
|
||||
source: OPEN_91_PROVIDER,
|
||||
receivedAt: nowIso(),
|
||||
body: normalized,
|
||||
},
|
||||
items: [
|
||||
{
|
||||
itemId: productNo,
|
||||
externalItemId: productNo,
|
||||
externalSkuCode: productNo,
|
||||
externalSkuName: productNo,
|
||||
skuCode: productNo,
|
||||
skuName: productNo,
|
||||
quantity: normalized.buyNum,
|
||||
spec: {
|
||||
source: OPEN_91_PROVIDER,
|
||||
orderNo: normalized.orderNo,
|
||||
productNo,
|
||||
},
|
||||
snapshot: {
|
||||
source: OPEN_91_PROVIDER,
|
||||
orderNo: normalized.orderNo,
|
||||
productNo,
|
||||
externalItemId: productNo,
|
||||
externalSkuCode: productNo,
|
||||
externalSkuName: productNo,
|
||||
callbackUrl: normalized.callbackUrl,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
export async function upsertOpen91PendingOrder(payload = {}, config = {}) {
|
||||
const event = buildOpen91SourceEvent(payload, config)
|
||||
const now = nowIso()
|
||||
const existing = await findOrderByPlatformOrderId({
|
||||
provider: event.provider,
|
||||
platform: event.platform,
|
||||
shopId: event.shopId,
|
||||
platformOrderId: event.platformOrderId,
|
||||
})
|
||||
|
||||
const rawPayloadJson = JSON.stringify({
|
||||
...event.rawPayload,
|
||||
pendingReason: 'unconfigured_items',
|
||||
pendingAt: now,
|
||||
})
|
||||
const orderPayload = {
|
||||
provider: event.provider,
|
||||
platform: event.platform,
|
||||
shopId: event.shopId,
|
||||
shopName: event.shopName,
|
||||
platformOrderId: event.platformOrderId,
|
||||
orderStatus: OPEN_91_PENDING_CONFIG_STATUS,
|
||||
payStatus: 'paid',
|
||||
buyerId: '',
|
||||
buyerName: '',
|
||||
receiverContact: '',
|
||||
totalAmount: 0,
|
||||
currency: 'CNY',
|
||||
rawPayloadJson,
|
||||
paidAt: existing?.paid_at || now,
|
||||
}
|
||||
|
||||
const order = existing
|
||||
? await updateOrder(existing.id, { ...orderPayload, updatedAt: now })
|
||||
: await createOrder({ ...orderPayload, createdAt: now, updatedAt: now })
|
||||
|
||||
if (!order) {
|
||||
throw createHttpError('91卡券待处理订单保存失败', {
|
||||
statusCode: 500,
|
||||
errorCode: 'open91_pending_order_save_failed',
|
||||
})
|
||||
}
|
||||
|
||||
const [item] = event.items
|
||||
const orderItems = await replaceOrderItems(order.id, [
|
||||
{
|
||||
skuCode: item.skuCode,
|
||||
skuName: item.skuName,
|
||||
quantity: item.quantity,
|
||||
specJson: JSON.stringify(item.spec || {}),
|
||||
itemSnapshotJson: JSON.stringify(item.snapshot || item.spec || {}),
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
},
|
||||
])
|
||||
|
||||
return {
|
||||
order,
|
||||
orderItems,
|
||||
tasks: [],
|
||||
}
|
||||
}
|
||||
|
||||
export async function retryOpen91Order(orderId) {
|
||||
const order = await getRequiredOpen91Order(orderId)
|
||||
const items = await listOrderItemsByOrderId(order.id)
|
||||
const event = buildOpen91SourceEventFromOrder(order, items)
|
||||
const result = await upsertOrderFromSource(event, { sourceLabel: 'open91-admin-retry' })
|
||||
|
||||
if (result.ignored) {
|
||||
throw createHttpError('当前 91 订单仍未命中履约配置,请先补全商品规则', {
|
||||
statusCode: 409,
|
||||
errorCode: 'open91_order_still_unconfigured',
|
||||
context: {
|
||||
ignoreReason: result.ignoreReason || 'unconfigured_items',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
order: result.order,
|
||||
orderItems: result.orderItems || [],
|
||||
tasks: result.tasks || [],
|
||||
}
|
||||
}
|
||||
|
||||
export async function failOpen91Order(orderId, reason = '') {
|
||||
const order = await getRequiredOpen91Order(orderId)
|
||||
const now = nowIso()
|
||||
const rawPayload = parseJsonObject(order.raw_payload_json)
|
||||
const updated = await updateOrder(order.id, {
|
||||
provider: order.provider,
|
||||
platform: order.platform,
|
||||
shopId: order.shop_id,
|
||||
shopName: order.shop_name,
|
||||
platformOrderId: order.platform_order_id,
|
||||
orderStatus: OPEN_91_MANUAL_FAILED_STATUS,
|
||||
payStatus: order.pay_status,
|
||||
buyerId: order.buyer_id,
|
||||
buyerName: order.buyer_name,
|
||||
receiverContact: order.receiver_contact,
|
||||
totalAmount: order.total_amount,
|
||||
currency: order.currency,
|
||||
rawPayloadJson: JSON.stringify({
|
||||
...rawPayload,
|
||||
manualFailedAt: now,
|
||||
manualFailedReason: String(reason || '').trim() || '商家手动标记无法履约',
|
||||
}),
|
||||
paidAt: order.paid_at,
|
||||
updatedAt: now,
|
||||
})
|
||||
|
||||
return {
|
||||
order: updated,
|
||||
orderItems: await listOrderItemsByOrderId(order.id),
|
||||
tasks: await listTasksByOrderId(order.id),
|
||||
}
|
||||
}
|
||||
|
||||
export async function listOpen91Orders({ page = 1, pageSize = 20, status = 'pending_config' } = {}) {
|
||||
const normalizedPage = Math.max(1, Number(page) || 1)
|
||||
const normalizedPageSize = Math.min(100, Math.max(1, Number(pageSize) || 20))
|
||||
const normalizedStatus = String(status || 'pending_config').trim()
|
||||
const filters = [
|
||||
'o.provider = $1',
|
||||
'o.platform = $2',
|
||||
]
|
||||
/** @type {Array<string | number>} */
|
||||
const params = [OPEN_91_PROVIDER, OPEN_91_PLATFORM]
|
||||
|
||||
if (normalizedStatus && normalizedStatus !== 'all') {
|
||||
params.push(normalizedStatus)
|
||||
filters.push(`o.order_status = $${params.length}`)
|
||||
}
|
||||
|
||||
const whereClause = `WHERE ${filters.join(' AND ')}`
|
||||
const totalResult = await query(`SELECT COUNT(*)::int AS total FROM orders o ${whereClause}`, params)
|
||||
|
||||
params.push(normalizedPageSize)
|
||||
params.push((normalizedPage - 1) * normalizedPageSize)
|
||||
const rowsResult = await query(
|
||||
`
|
||||
SELECT
|
||||
o.*,
|
||||
COALESCE(item_summary.items, '[]'::jsonb) AS items_json,
|
||||
COALESCE(task_summary.task_count, 0)::int AS task_count
|
||||
FROM orders o
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT jsonb_agg(jsonb_build_object(
|
||||
'orderItemId', oi.id,
|
||||
'skuCode', oi.sku_code,
|
||||
'skuName', oi.sku_name,
|
||||
'quantity', oi.quantity,
|
||||
'snapshot', oi.item_snapshot_json
|
||||
) ORDER BY oi.id ASC) AS items
|
||||
FROM order_items oi
|
||||
WHERE oi.order_id = o.id
|
||||
) item_summary ON TRUE
|
||||
LEFT JOIN LATERAL (
|
||||
SELECT COUNT(*)::int AS task_count
|
||||
FROM fulfillment_tasks ft
|
||||
WHERE ft.order_id = o.id
|
||||
) task_summary ON TRUE
|
||||
${whereClause}
|
||||
ORDER BY o.id DESC
|
||||
LIMIT $${params.length - 1} OFFSET $${params.length}
|
||||
`,
|
||||
params,
|
||||
)
|
||||
|
||||
return {
|
||||
page: normalizedPage,
|
||||
pageSize: normalizedPageSize,
|
||||
total: Number(totalResult.rows[0]?.total || 0),
|
||||
items: rowsResult.rows.map((row) => mapOpen91AdminOrderRow(row)),
|
||||
}
|
||||
}
|
||||
|
||||
export async function getRequiredOpen91Order(orderId) {
|
||||
const order = await getOrderById(Number(orderId))
|
||||
|
||||
if (!order || order.provider !== OPEN_91_PROVIDER || order.platform !== OPEN_91_PLATFORM) {
|
||||
throw createHttpError('91卡券订单不存在', {
|
||||
statusCode: 404,
|
||||
errorCode: 'open91_order_not_found',
|
||||
})
|
||||
}
|
||||
|
||||
return order
|
||||
}
|
||||
|
||||
function buildOpen91SourceEventFromOrder(order, items = []) {
|
||||
const rawPayload = parseJsonObject(order.raw_payload_json)
|
||||
const body = parseJsonObject(rawPayload.body)
|
||||
const normalizedItems = Array.isArray(items) ? items : []
|
||||
const eventItems = normalizedItems.map((item) => {
|
||||
const snapshot = parseJsonObject(item.item_snapshot_json)
|
||||
const productNo = String(snapshot.productNo || snapshot.externalSkuCode || item.sku_code || '').trim()
|
||||
|
||||
return {
|
||||
itemId: productNo,
|
||||
externalItemId: String(snapshot.externalItemId || productNo).trim(),
|
||||
externalSkuCode: String(snapshot.externalSkuCode || productNo).trim(),
|
||||
externalSkuName: String(snapshot.externalSkuName || item.sku_name || productNo).trim(),
|
||||
skuCode: productNo,
|
||||
skuName: String(item.sku_name || productNo).trim(),
|
||||
quantity: Math.max(1, Number(item.quantity || body.buyNum || 1) || 1),
|
||||
spec: parseJsonObject(item.spec_json),
|
||||
snapshot,
|
||||
}
|
||||
})
|
||||
|
||||
return {
|
||||
provider: order.provider,
|
||||
platform: order.platform,
|
||||
shopId: order.shop_id,
|
||||
shopName: order.shop_name,
|
||||
platformOrderId: order.platform_order_id,
|
||||
orderStatus: 'paid',
|
||||
payStatus: 'paid',
|
||||
buyerId: order.buyer_id || '',
|
||||
buyerName: order.buyer_name || '',
|
||||
receiverContact: order.receiver_contact || '',
|
||||
totalAmount: Number(order.total_amount || 0) || 0,
|
||||
currency: order.currency || 'CNY',
|
||||
paidAt: order.paid_at || nowIso(),
|
||||
rawPayload: {
|
||||
...rawPayload,
|
||||
retriedAt: nowIso(),
|
||||
},
|
||||
items: eventItems,
|
||||
}
|
||||
}
|
||||
|
||||
function mapOpen91AdminOrderRow(row) {
|
||||
const rawPayload = parseJsonObject(row.raw_payload_json)
|
||||
const items = Array.isArray(row.items_json) ? row.items_json : []
|
||||
const firstItem = items[0] || {}
|
||||
const firstSnapshot = parseJsonObject(firstItem.snapshot)
|
||||
const failReason = String(rawPayload.manualFailedReason || '').trim()
|
||||
|
||||
return {
|
||||
orderId: Number(row.id || 0),
|
||||
orderNo: String(row.platform_order_id || '').trim(),
|
||||
outTradeNo: buildOpen91OutTradeNo(row),
|
||||
orderStatus: String(row.order_status || '').trim(),
|
||||
payStatus: String(row.pay_status || '').trim(),
|
||||
shopId: String(row.shop_id || '').trim(),
|
||||
shopName: String(row.shop_name || '').trim(),
|
||||
productNo: String(firstSnapshot.productNo || firstSnapshot.externalSkuCode || firstItem.skuCode || '').trim(),
|
||||
productName: String(firstSnapshot.externalSkuName || firstItem.skuName || '').trim(),
|
||||
buyNum: Number(firstItem.quantity || 0),
|
||||
taskCount: Number(row.task_count || 0),
|
||||
failReason,
|
||||
createdAt: row.created_at,
|
||||
updatedAt: row.updated_at,
|
||||
items,
|
||||
}
|
||||
}
|
||||
|
||||
function parseJsonObject(value) {
|
||||
if (!value) {
|
||||
return {}
|
||||
}
|
||||
|
||||
if (typeof value === 'object' && !Array.isArray(value)) {
|
||||
return value
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(String(value || '{}'))
|
||||
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {}
|
||||
} catch {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
|
||||
import { buildOpen91SourceEvent } from './order-service.js'
|
||||
|
||||
test('buildOpen91SourceEvent maps 91 productNo into source item identities', () => {
|
||||
const event = buildOpen91SourceEvent({
|
||||
orderNo: 'P91KS202605130001',
|
||||
productNo: 'KS-CLOUD-SKU-001',
|
||||
buyNum: 2,
|
||||
maxAmount: '0.01',
|
||||
timestamp: 1778670308,
|
||||
version: '1.0',
|
||||
sign: 'SIGN',
|
||||
}, {
|
||||
shopId: '91kaquan',
|
||||
shopName: '91卡券',
|
||||
})
|
||||
|
||||
assert.equal(event.provider, '91kaquan')
|
||||
assert.equal(event.platform, 'kuaishou')
|
||||
assert.equal(event.shopId, '91kaquan')
|
||||
assert.equal(event.platformOrderId, 'P91KS202605130001')
|
||||
assert.equal(event.payStatus, 'paid')
|
||||
assert.equal(event.items.length, 1)
|
||||
assert.equal(event.items[0].externalItemId, 'KS-CLOUD-SKU-001')
|
||||
assert.equal(event.items[0].externalSkuCode, 'KS-CLOUD-SKU-001')
|
||||
assert.equal(event.items[0].externalSkuName, 'KS-CLOUD-SKU-001')
|
||||
assert.equal(event.items[0].quantity, 2)
|
||||
assert.equal(event.items[0].snapshot.productNo, 'KS-CLOUD-SKU-001')
|
||||
})
|
||||
@@ -157,3 +157,7 @@ export {}
|
||||
/**
|
||||
* @typedef {{ eventId?: string }} AdminWebhookEventRouteParams
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{ id?: string }} AdminEntityRouteParams
|
||||
*/
|
||||
|
||||
Vendored
+2
@@ -16,9 +16,11 @@ declare module 'vue' {
|
||||
AdminPlatformCloudtentaclesSection: typeof import('./components/admin/AdminPlatformCloudtentaclesSection.vue')['default']
|
||||
AdminPlatformKhhaoSection: typeof import('./components/admin/AdminPlatformKhhaoSection.vue')['default']
|
||||
AdminPlatformKuaishouEticketSection: typeof import('./components/admin/AdminPlatformKuaishouEticketSection.vue')['default']
|
||||
AdminPlatformNinetyoneSection: typeof import('./components/admin/AdminPlatformNinetyoneSection.vue')['default']
|
||||
AdminResultCard: typeof import('./components/admin/AdminResultCard.vue')['default']
|
||||
AdminStatusTag: typeof import('./components/admin/AdminStatusTag.vue')['default']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElButtonGroup: typeof import('element-plus/es')['ElButtonGroup']
|
||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||
ElCollapse: typeof import('element-plus/es')['ElCollapse']
|
||||
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<script setup lang="ts">
|
||||
import type { AdminNinetyoneOrderItem, AdminNinetyoneOrderListResult } from '@/types/admin'
|
||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||
|
||||
type Props = {
|
||||
ninetyoneLoading: boolean
|
||||
ninetyoneActionLoadingId: number | null
|
||||
ninetyoneResultError: string
|
||||
ninetyoneStatus: 'pending_config' | 'all' | 'manual_failed'
|
||||
ninetyoneOrders: AdminNinetyoneOrderListResult
|
||||
ninetyoneStats: {
|
||||
total: number
|
||||
currentCount: number
|
||||
pendingCount: number
|
||||
failedCount: number
|
||||
taskReadyCount: number
|
||||
}
|
||||
loadNinetyoneOrders: (page?: number) => void | Promise<void>
|
||||
handleNinetyoneStatusChange: (status: 'pending_config' | 'all' | 'manual_failed') => void | Promise<void>
|
||||
handleNinetyoneRetryOrder: (item: AdminNinetyoneOrderItem) => void | Promise<void>
|
||||
handleNinetyoneFailOrder: (item: AdminNinetyoneOrderItem) => void | Promise<void>
|
||||
}
|
||||
|
||||
defineProps<Props>()
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section class="meta-card">
|
||||
<div class="meta-line">
|
||||
<span class="meta-label">职责</span>
|
||||
<span>接收 91卡券推送订单,未命中履约配置时先进入待补全队列,补完商品规则后可手动重试生成任务。</span>
|
||||
</div>
|
||||
<div class="meta-line">
|
||||
<span class="meta-label">处理建议</span>
|
||||
<span>先到“快手 Cloud 新履约”页为 91 的 productNo 配置规则,再回到这里点击重试。</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="table-card">
|
||||
<div class="section-title-row">
|
||||
<div>
|
||||
<h3>91卡券订单队列</h3>
|
||||
<p>当前筛选 {{ ninetyoneStats.currentCount }} 条,总计 {{ ninetyoneStats.total }} 条,已生成任务 {{ ninetyoneStats.taskReadyCount }} 条。</p>
|
||||
</div>
|
||||
<div class="section-actions">
|
||||
<el-button-group>
|
||||
<el-button :type="ninetyoneStatus === 'pending_config' ? 'primary' : 'default'" round @click="handleNinetyoneStatusChange('pending_config')">待补全</el-button>
|
||||
<el-button :type="ninetyoneStatus === 'manual_failed' ? 'primary' : 'default'" round @click="handleNinetyoneStatusChange('manual_failed')">失败</el-button>
|
||||
<el-button :type="ninetyoneStatus === 'all' ? 'primary' : 'default'" round @click="handleNinetyoneStatusChange('all')">全部</el-button>
|
||||
</el-button-group>
|
||||
<el-button :loading="ninetyoneLoading" round @click="loadNinetyoneOrders(ninetyoneOrders.page)">刷新</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="ninetyoneResultError" class="error-copy">{{ ninetyoneResultError }}</p>
|
||||
<div v-if="ninetyoneLoading" class="empty-inline">正在读取 91卡券订单…</div>
|
||||
|
||||
<table v-else class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>订单</th>
|
||||
<th>商品</th>
|
||||
<th>状态</th>
|
||||
<th>时间</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in ninetyoneOrders.items" :key="item.orderId">
|
||||
<td>
|
||||
<div class="cell-stack">
|
||||
<strong>{{ item.orderNo }}</strong>
|
||||
<span class="cell-subtle">{{ item.outTradeNo || '-' }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="cell-stack">
|
||||
<strong>{{ item.productName || item.productNo || '-' }}</strong>
|
||||
<span class="cell-subtle">productNo: {{ item.productNo || '-' }}</span>
|
||||
<span class="cell-subtle">数量:{{ item.buyNum || 0 }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="cell-stack">
|
||||
<strong>{{ item.orderStatus }}</strong>
|
||||
<span class="cell-subtle">任务 {{ item.taskCount }}</span>
|
||||
<span v-if="item.failReason" class="cell-subtle">{{ item.failReason }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{{ formatAdminDateTime(item.updatedAt || item.createdAt) }}</td>
|
||||
<td>
|
||||
<div class="row-actions">
|
||||
<el-button
|
||||
:loading="ninetyoneActionLoadingId === item.orderId"
|
||||
link
|
||||
type="primary"
|
||||
@click="handleNinetyoneRetryOrder(item)"
|
||||
>
|
||||
重试生成任务
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="item.orderStatus !== 'manual_failed'"
|
||||
:loading="ninetyoneActionLoadingId === item.orderId"
|
||||
link
|
||||
type="danger"
|
||||
@click="handleNinetyoneFailOrder(item)"
|
||||
>
|
||||
标记失败
|
||||
</el-button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="ninetyoneOrders.items.length === 0">
|
||||
<td colspan="5" class="empty-inline">当前没有 91卡券订单。</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
</template>
|
||||
@@ -1,4 +1,4 @@
|
||||
export type PlatformTab = 'agiso' | 'khhao' | 'kuaishouEticket' | 'cloudtentacles'
|
||||
export type PlatformTab = 'agiso' | 'khhao' | 'ninetyone' | 'kuaishouEticket' | 'cloudtentacles'
|
||||
|
||||
export type EditableDefaults = {
|
||||
messageTemplate: string
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
import { showConfirm, showError, showSuccess } from '@/lib/feedback'
|
||||
import {
|
||||
failAdminNinetyoneOrder,
|
||||
fetchAdminNinetyoneOrders,
|
||||
retryAdminNinetyoneOrder,
|
||||
} from '@/services/admin'
|
||||
import type { AdminNinetyoneOrderItem, AdminNinetyoneOrderListResult } from '@/types/admin'
|
||||
|
||||
export function useAdminNinetyonePlatform() {
|
||||
const ninetyoneLoading = ref(false)
|
||||
const ninetyoneActionLoadingId = ref<number | null>(null)
|
||||
const ninetyoneResultError = ref('')
|
||||
const ninetyoneStatus = ref<'pending_config' | 'all' | 'manual_failed'>('pending_config')
|
||||
const ninetyoneOrders = ref<AdminNinetyoneOrderListResult>({
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
total: 0,
|
||||
items: [],
|
||||
})
|
||||
|
||||
const ninetyoneStats = computed(() => {
|
||||
const pendingCount = ninetyoneOrders.value.items.filter((item) => item.orderStatus === 'pending_config').length
|
||||
const failedCount = ninetyoneOrders.value.items.filter((item) => item.orderStatus === 'manual_failed').length
|
||||
const taskReadyCount = ninetyoneOrders.value.items.filter((item) => item.taskCount > 0).length
|
||||
|
||||
return {
|
||||
total: ninetyoneOrders.value.total,
|
||||
currentCount: ninetyoneOrders.value.items.length,
|
||||
pendingCount,
|
||||
failedCount,
|
||||
taskReadyCount,
|
||||
}
|
||||
})
|
||||
|
||||
async function loadNinetyoneOrders(page = 1) {
|
||||
ninetyoneLoading.value = true
|
||||
ninetyoneResultError.value = ''
|
||||
|
||||
try {
|
||||
const response = await fetchAdminNinetyoneOrders({
|
||||
page,
|
||||
pageSize: ninetyoneOrders.value.pageSize || 20,
|
||||
status: ninetyoneStatus.value,
|
||||
})
|
||||
ninetyoneOrders.value = response.data
|
||||
} catch (error) {
|
||||
ninetyoneResultError.value = error instanceof Error ? error.message : '91卡券订单读取失败'
|
||||
showError(ninetyoneResultError.value)
|
||||
} finally {
|
||||
ninetyoneLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function handleNinetyoneStatusChange(status: 'pending_config' | 'all' | 'manual_failed') {
|
||||
ninetyoneStatus.value = status
|
||||
await loadNinetyoneOrders(1)
|
||||
}
|
||||
|
||||
async function handleNinetyoneRetryOrder(item: AdminNinetyoneOrderItem) {
|
||||
ninetyoneActionLoadingId.value = item.orderId
|
||||
ninetyoneResultError.value = ''
|
||||
|
||||
try {
|
||||
const response = await retryAdminNinetyoneOrder(item.orderId)
|
||||
showSuccess(`91卡券订单已重试,生成任务 ${response.data.taskCount} 个`)
|
||||
await loadNinetyoneOrders(ninetyoneOrders.value.page || 1)
|
||||
} catch (error) {
|
||||
ninetyoneResultError.value = error instanceof Error ? error.message : '91卡券订单重试失败'
|
||||
showError(ninetyoneResultError.value)
|
||||
} finally {
|
||||
ninetyoneActionLoadingId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function handleNinetyoneFailOrder(item: AdminNinetyoneOrderItem) {
|
||||
try {
|
||||
await showConfirm(`确认把 91卡券订单 ${item.orderNo} 标记为无法履约吗?查询接口会返回失败状态。`, '确认标记失败', {
|
||||
type: 'warning',
|
||||
confirmButtonText: '标记失败',
|
||||
cancelButtonText: '取消',
|
||||
})
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
|
||||
ninetyoneActionLoadingId.value = item.orderId
|
||||
ninetyoneResultError.value = ''
|
||||
|
||||
try {
|
||||
await failAdminNinetyoneOrder(item.orderId, {
|
||||
reason: '商家后台手动标记无法履约',
|
||||
})
|
||||
showSuccess('91卡券订单已标记失败')
|
||||
await loadNinetyoneOrders(ninetyoneOrders.value.page || 1)
|
||||
} catch (error) {
|
||||
ninetyoneResultError.value = error instanceof Error ? error.message : '91卡券订单标记失败失败'
|
||||
showError(ninetyoneResultError.value)
|
||||
} finally {
|
||||
ninetyoneActionLoadingId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
ninetyoneLoading,
|
||||
ninetyoneActionLoadingId,
|
||||
ninetyoneResultError,
|
||||
ninetyoneStatus,
|
||||
ninetyoneOrders,
|
||||
ninetyoneStats,
|
||||
loadNinetyoneOrders,
|
||||
handleNinetyoneStatusChange,
|
||||
handleNinetyoneRetryOrder,
|
||||
handleNinetyoneFailOrder,
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,8 @@ import type {
|
||||
AdminKhhaoOrderSyncResult,
|
||||
AdminKhhaoSourceConfig,
|
||||
AdminKhhaoSyncState,
|
||||
AdminNinetyoneOrderActionResult,
|
||||
AdminNinetyoneOrderListResult,
|
||||
AdminKuaishouCloudFulfillmentConfig,
|
||||
AdminKuaishouEticketConsumeResult,
|
||||
AdminKuaishouEticketDetailResult,
|
||||
@@ -94,6 +96,41 @@ export function syncAdminKhhaoOrders(payload: {
|
||||
return apiPost<AdminKhhaoOrderSyncResult>('/api/v1/admin/platform-config/khhao/sync-orders', payload)
|
||||
}
|
||||
|
||||
export function fetchAdminNinetyoneOrders(params: {
|
||||
page?: number
|
||||
pageSize?: number
|
||||
status?: string
|
||||
} = {}) {
|
||||
const searchParams = new URLSearchParams()
|
||||
if (params.page) {
|
||||
searchParams.set('page', String(params.page))
|
||||
}
|
||||
if (params.pageSize) {
|
||||
searchParams.set('pageSize', String(params.pageSize))
|
||||
}
|
||||
if (params.status) {
|
||||
searchParams.set('status', params.status)
|
||||
}
|
||||
const queryString = searchParams.toString()
|
||||
return apiGet<AdminNinetyoneOrderListResult>(
|
||||
`/api/v1/admin/platform-config/ninetyone/orders${queryString ? `?${queryString}` : ''}`,
|
||||
)
|
||||
}
|
||||
|
||||
export function retryAdminNinetyoneOrder(orderId: number | string) {
|
||||
return apiPost<AdminNinetyoneOrderActionResult>(
|
||||
`/api/v1/admin/platform-config/ninetyone/orders/${orderId}/retry`,
|
||||
{},
|
||||
)
|
||||
}
|
||||
|
||||
export function failAdminNinetyoneOrder(orderId: number | string, payload: { reason?: string }) {
|
||||
return apiPost<AdminNinetyoneOrderActionResult>(
|
||||
`/api/v1/admin/platform-config/ninetyone/orders/${orderId}/fail`,
|
||||
payload,
|
||||
)
|
||||
}
|
||||
|
||||
export function fetchAdminKuaishouEticketSourceConfig() {
|
||||
return apiGet<{
|
||||
filePath: string
|
||||
|
||||
@@ -54,6 +54,13 @@
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.admin-platform-shops .row-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.admin-platform-shops .overview-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
@@ -550,4 +557,4 @@
|
||||
.admin-platform-shops .shop-summary-side {
|
||||
justify-items: start;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -271,6 +271,39 @@ export interface AdminKhhaoOrderSyncResult {
|
||||
results: AdminKhhaoOrderSyncItem[]
|
||||
}
|
||||
|
||||
export interface AdminNinetyoneOrderItem {
|
||||
orderId: number
|
||||
orderNo: string
|
||||
outTradeNo: string
|
||||
orderStatus: string
|
||||
payStatus: string
|
||||
shopId: string
|
||||
shopName: string
|
||||
productNo: string
|
||||
productName: string
|
||||
buyNum: number
|
||||
taskCount: number
|
||||
failReason: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
items: Array<Record<string, unknown>>
|
||||
}
|
||||
|
||||
export interface AdminNinetyoneOrderListResult {
|
||||
page: number
|
||||
pageSize: number
|
||||
total: number
|
||||
items: AdminNinetyoneOrderItem[]
|
||||
}
|
||||
|
||||
export interface AdminNinetyoneOrderActionResult {
|
||||
orderId: number
|
||||
orderNo: string
|
||||
orderStatus: string
|
||||
orderItemCount: number
|
||||
taskCount: number
|
||||
}
|
||||
|
||||
export interface AdminCloudtentaclesSourceConfig {
|
||||
enabled: boolean
|
||||
baseUrl: string
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
fetchAdminCloudtentaclesSkuList,
|
||||
fetchAdminKhhaoSourceConfig,
|
||||
fetchAdminKuaishouCloudFulfillmentConfig,
|
||||
fetchAdminNinetyoneOrders,
|
||||
queryAdminKhhaoOrders,
|
||||
saveAdminKuaishouCloudFulfillmentConfig,
|
||||
} from '@/services/admin'
|
||||
@@ -14,6 +15,7 @@ import type {
|
||||
AdminKhhaoOrderPreview,
|
||||
AdminKuaishouCloudFulfillmentConfig,
|
||||
AdminKuaishouCloudFulfillmentItem,
|
||||
AdminNinetyoneOrderItem,
|
||||
} from '@/types/admin'
|
||||
import { hasAdminRole } from '@/utils/admin-auth'
|
||||
|
||||
@@ -41,6 +43,10 @@ const khhaoLookupLoading = ref(false)
|
||||
const khhaoLookupErrorMessage = ref('')
|
||||
const khhaoLookupResults = ref<AdminKhhaoOrderPreview[]>([])
|
||||
const importedKhhaoOrderKeys = ref<string[]>([])
|
||||
const ninetyoneLookupLoading = ref(false)
|
||||
const ninetyoneLookupErrorMessage = ref('')
|
||||
const ninetyoneLookupResults = ref<AdminNinetyoneOrderItem[]>([])
|
||||
const importedNinetyoneOrderKeys = ref<string[]>([])
|
||||
const cloudSkuCatalogLoading = ref(false)
|
||||
const cloudSkuCatalogErrorMessage = ref('')
|
||||
const cloudSkuCatalog = ref<AdminCloudtentaclesSkuItem[]>([])
|
||||
@@ -52,6 +58,11 @@ const khhaoLookupForm = reactive({
|
||||
limit: 10,
|
||||
maxCaptchaAttempts: 3,
|
||||
})
|
||||
const ninetyoneLookupForm = reactive({
|
||||
status: 'pending_config' as 'pending_config' | 'all' | 'manual_failed',
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
})
|
||||
|
||||
const metrics = computed(() => {
|
||||
const readyCount = items.value.filter(isItemComplete).length
|
||||
@@ -90,6 +101,17 @@ const khhaoLookupMetrics = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const ninetyoneLookupMetrics = computed(() => {
|
||||
const importedCount = ninetyoneLookupResults.value.filter((item) => isNinetyoneProductImported(item)).length
|
||||
const pendingCount = ninetyoneLookupResults.value.filter((item) => item.orderStatus === 'pending_config').length
|
||||
return {
|
||||
total: ninetyoneLookupResults.value.length,
|
||||
importedCount,
|
||||
availableCount: Math.max(ninetyoneLookupResults.value.length - importedCount, 0),
|
||||
pendingCount,
|
||||
}
|
||||
})
|
||||
|
||||
function createEmptyItem(): EditableItem {
|
||||
return {
|
||||
localId: crypto.randomUUID(),
|
||||
@@ -423,6 +445,38 @@ function createItemFromKhhaoOrder(item: AdminKhhaoOrderPreview): EditableItem {
|
||||
}
|
||||
}
|
||||
|
||||
function createItemFromNinetyoneOrder(item: AdminNinetyoneOrderItem): EditableItem {
|
||||
const productNo = String(item.productNo || '').trim()
|
||||
const productName = String(item.productName || productNo).trim()
|
||||
return {
|
||||
localId: crypto.randomUUID(),
|
||||
id: '',
|
||||
enabled: true,
|
||||
priority: 100,
|
||||
provider: '91kaquan',
|
||||
platform: 'kuaishou',
|
||||
shopId: item.shopId || '91kaquan',
|
||||
khhaoShopId: '',
|
||||
internalSkuCode: '',
|
||||
internalSkuName: productName,
|
||||
externalSkuCode: productNo,
|
||||
externalItemId: productNo,
|
||||
externalSkuName: productName,
|
||||
resolvedSkuName: productName,
|
||||
cloudSourceKey: 'default',
|
||||
cloudSkuId: 0,
|
||||
cloudSkuName: '',
|
||||
vnKey: '1',
|
||||
autoBuyEnabled: true,
|
||||
minAssetReserve: 0,
|
||||
autoReturnNumberAfterDispatch: false,
|
||||
autoConsumeAfterDispatch: false,
|
||||
kuaishouConsumeShopId: '',
|
||||
kuaishouConsumeShopName: '',
|
||||
notes: `从 91卡券订单 ${item.orderNo} 导入`,
|
||||
}
|
||||
}
|
||||
|
||||
function findExistingItemFromKhhaoOrder(order: AdminKhhaoOrderPreview) {
|
||||
const orderShopId = String(order.kuaishouShopId || order.shopId || '').trim()
|
||||
const orderItemId = String(order.itemId || '').trim()
|
||||
@@ -441,6 +495,23 @@ function findExistingItemFromKhhaoOrder(order: AdminKhhaoOrderPreview) {
|
||||
}) || null
|
||||
}
|
||||
|
||||
function findExistingItemFromNinetyoneOrder(order: AdminNinetyoneOrderItem) {
|
||||
const productNo = String(order.productNo || '').trim()
|
||||
const productName = String(order.productName || '').trim()
|
||||
const shopId = String(order.shopId || '91kaquan').trim()
|
||||
|
||||
return items.value.find((item) => {
|
||||
const sameSource = item.provider.trim() === '91kaquan' && item.platform.trim() === 'kuaishou'
|
||||
const sameShop = !shopId || item.shopId.trim() === shopId
|
||||
const sameProductNo = productNo && (
|
||||
item.externalSkuCode.trim() === productNo
|
||||
|| item.externalItemId.trim() === productNo
|
||||
)
|
||||
const sameName = productName && [item.externalSkuName, item.internalSkuName, item.resolvedSkuName].some((value) => value.trim() === productName)
|
||||
return sameSource && sameShop && (sameProductNo || sameName)
|
||||
}) || null
|
||||
}
|
||||
|
||||
async function importKhhaoProduct(item: AdminKhhaoOrderPreview) {
|
||||
validationState.value = null
|
||||
const importKey = getKhhaoOrderImportKey(item)
|
||||
@@ -466,6 +537,31 @@ async function importKhhaoProduct(item: AdminKhhaoOrderPreview) {
|
||||
await focusValidationTarget(next.localId)
|
||||
}
|
||||
|
||||
async function importNinetyoneProduct(item: AdminNinetyoneOrderItem) {
|
||||
validationState.value = null
|
||||
const importKey = getNinetyoneOrderImportKey(item)
|
||||
const existing = findExistingItemFromNinetyoneOrder(item)
|
||||
if (existing) {
|
||||
setCollapsed(existing.localId, false)
|
||||
if (!importedNinetyoneOrderKeys.value.includes(importKey)) {
|
||||
importedNinetyoneOrderKeys.value = [...importedNinetyoneOrderKeys.value, importKey]
|
||||
}
|
||||
showSuccess('已定位到现有 91卡券规则草稿,直接继续完善即可')
|
||||
await focusValidationTarget(existing.localId)
|
||||
return
|
||||
}
|
||||
|
||||
const next = createItemFromNinetyoneOrder(item)
|
||||
items.value.unshift(next)
|
||||
setCollapsed(next.localId, false)
|
||||
|
||||
if (!importedNinetyoneOrderKeys.value.includes(importKey)) {
|
||||
importedNinetyoneOrderKeys.value = [...importedNinetyoneOrderKeys.value, importKey]
|
||||
}
|
||||
|
||||
await focusValidationTarget(next.localId)
|
||||
}
|
||||
|
||||
function getKhhaoOrderImportKey(item: AdminKhhaoOrderPreview) {
|
||||
return [
|
||||
item.platformOrderId,
|
||||
@@ -481,6 +577,20 @@ function isKhhaoProductImported(item: AdminKhhaoOrderPreview) {
|
||||
return importedKhhaoOrderKeys.value.includes(getKhhaoOrderImportKey(item))
|
||||
}
|
||||
|
||||
function getNinetyoneOrderImportKey(item: AdminNinetyoneOrderItem) {
|
||||
return [
|
||||
item.orderNo,
|
||||
item.productNo,
|
||||
item.shopId || '91kaquan',
|
||||
]
|
||||
.map((value) => String(value || '').trim())
|
||||
.join(':')
|
||||
}
|
||||
|
||||
function isNinetyoneProductImported(item: AdminNinetyoneOrderItem) {
|
||||
return importedNinetyoneOrderKeys.value.includes(getNinetyoneOrderImportKey(item))
|
||||
}
|
||||
|
||||
async function ensureCloudSkuCatalogLoaded(force = false) {
|
||||
if (!force && cloudSkuCatalog.value.length > 0) {
|
||||
return cloudSkuCatalog.value
|
||||
@@ -686,6 +796,26 @@ async function lookupKhhaoProducts() {
|
||||
}
|
||||
}
|
||||
|
||||
async function lookupNinetyoneProducts() {
|
||||
ninetyoneLookupLoading.value = true
|
||||
ninetyoneLookupErrorMessage.value = ''
|
||||
|
||||
try {
|
||||
const response = await fetchAdminNinetyoneOrders({
|
||||
page: Number(ninetyoneLookupForm.page || 1),
|
||||
pageSize: Number(ninetyoneLookupForm.pageSize || 20),
|
||||
status: ninetyoneLookupForm.status,
|
||||
})
|
||||
ninetyoneLookupResults.value = response.data.items
|
||||
importedNinetyoneOrderKeys.value = []
|
||||
} catch (error) {
|
||||
ninetyoneLookupResults.value = []
|
||||
ninetyoneLookupErrorMessage.value = error instanceof Error ? error.message : '91卡券订单查询失败'
|
||||
} finally {
|
||||
ninetyoneLookupLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadConfigs)
|
||||
</script>
|
||||
|
||||
@@ -745,6 +875,7 @@ onMounted(loadConfigs)
|
||||
|
||||
<div class="overview-notes">
|
||||
<span class="overview-note">khhao 订单只用于取样导入,不写入订单库</span>
|
||||
<span class="overview-note">91卡券订单来自已接收的待补全队列</span>
|
||||
<span class="overview-note">内部 SKU 决定最终任务与库存绑定</span>
|
||||
<span class="overview-note">cloud SKU 决定自动购买、发货与退号资源</span>
|
||||
</div>
|
||||
@@ -860,6 +991,104 @@ onMounted(loadConfigs)
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="table-card table-card--dense">
|
||||
<div class="section-title-row section-title-row--tight">
|
||||
<div>
|
||||
<h3>91卡券订单取样导入</h3>
|
||||
<p>查询已接收的 91卡券订单,把 productNo 导入为规则草稿,再补齐内部 SKU 和 cloud 资源。</p>
|
||||
</div>
|
||||
<div class="section-mini-stats">
|
||||
<span class="mini-stat-chip">结果 {{ ninetyoneLookupMetrics.total }}</span>
|
||||
<span class="mini-stat-chip">待补 {{ ninetyoneLookupMetrics.pendingCount }}</span>
|
||||
<span class="mini-stat-chip">待导入 {{ ninetyoneLookupMetrics.availableCount }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="khhao-toolbar">
|
||||
<label class="field-block">
|
||||
<span>订单状态</span>
|
||||
<select v-model="ninetyoneLookupForm.status" class="text-input">
|
||||
<option value="pending_config">待补全</option>
|
||||
<option value="manual_failed">已失败</option>
|
||||
<option value="all">全部</option>
|
||||
</select>
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>页码</span>
|
||||
<input v-model.number="ninetyoneLookupForm.page" class="text-input" min="1" type="number" />
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>每页条数</span>
|
||||
<input v-model.number="ninetyoneLookupForm.pageSize" class="text-input" max="100" min="1" type="number" />
|
||||
</label>
|
||||
<div class="field-block field-block--action">
|
||||
<span>操作</span>
|
||||
<el-button :loading="ninetyoneLookupLoading" round type="primary" @click="lookupNinetyoneProducts">查询 91卡券订单</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="toolbar-hint">导入后会创建 provider=91kaquan、platform=kuaishou、外部 SKU=productNo 的规则草稿;保存规则后,回到“平台配置 -> 91卡券接入”重试订单。</p>
|
||||
|
||||
<p v-if="ninetyoneLookupErrorMessage" class="error-copy lookup-error">{{ ninetyoneLookupErrorMessage }}</p>
|
||||
<div v-else-if="ninetyoneLookupLoading" class="empty-inline">正在查询 91卡券订单…</div>
|
||||
|
||||
<table v-else class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>订单</th>
|
||||
<th>来源</th>
|
||||
<th>商品</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in ninetyoneLookupResults" :key="getNinetyoneOrderImportKey(item)">
|
||||
<td>
|
||||
<div class="cell-stack">
|
||||
<strong>{{ item.orderNo || '-' }}</strong>
|
||||
<span class="cell-subtle">{{ item.outTradeNo || '-' }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="cell-stack">
|
||||
<strong>{{ item.shopName || '91卡券' }}</strong>
|
||||
<span class="cell-subtle">provider: 91kaquan</span>
|
||||
<span class="cell-subtle">shopId: {{ item.shopId || '91kaquan' }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="cell-stack">
|
||||
<strong>{{ item.productName || item.productNo || '-' }}</strong>
|
||||
<span class="cell-subtle">productNo: {{ item.productNo || '-' }}</span>
|
||||
<span class="cell-subtle">数量:{{ item.buyNum || 0 }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="cell-stack">
|
||||
<strong>{{ item.orderStatus || '-' }}</strong>
|
||||
<span class="cell-subtle">任务 {{ item.taskCount || 0 }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<el-button
|
||||
v-if="!isNinetyoneProductImported(item)"
|
||||
link
|
||||
type="primary"
|
||||
@click="importNinetyoneProduct(item)"
|
||||
>
|
||||
导入到规则
|
||||
</el-button>
|
||||
<span v-else class="cell-subtle">已导入草稿</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr v-if="ninetyoneLookupResults.length === 0">
|
||||
<td colspan="5" class="empty-inline">还没有 91卡券查询结果。</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<section class="table-card table-card--dense">
|
||||
<div class="section-title-row section-title-row--tight">
|
||||
<div>
|
||||
@@ -924,13 +1153,13 @@ onMounted(loadConfigs)
|
||||
<section class="mapping-panel mapping-panel--external">
|
||||
<div class="mapping-panel-head">
|
||||
<strong>外部商品</strong>
|
||||
<span>khhao / 快手侧命中条件</span>
|
||||
<span>来源平台 / 快手侧命中条件</span>
|
||||
</div>
|
||||
|
||||
<div class="mapping-grid">
|
||||
<label class="field-block">
|
||||
<span>来源 provider</span>
|
||||
<input v-model="item.provider" class="text-input" maxlength="32" placeholder="默认 khhao" />
|
||||
<input v-model="item.provider" class="text-input" maxlength="32" placeholder="khhao / 91kaquan" />
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>来源 platform</span>
|
||||
@@ -942,7 +1171,7 @@ onMounted(loadConfigs)
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>khhao 店铺 ID</span>
|
||||
<input v-model="item.khhaoShopId" class="text-input" maxlength="80" placeholder="例如 10,可用于 khhao 内部店铺匹配" />
|
||||
<input v-model="item.khhaoShopId" class="text-input" maxlength="80" placeholder="仅 khhao 使用,例如 10" />
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>外部 SKU</span>
|
||||
|
||||
@@ -4,10 +4,12 @@ import { onMounted, ref } from 'vue'
|
||||
import AdminPlatformAgisoSection from '@/components/admin/AdminPlatformAgisoSection.vue'
|
||||
import AdminPlatformCloudtentaclesSection from '@/components/admin/AdminPlatformCloudtentaclesSection.vue'
|
||||
import AdminPlatformKhhaoSection from '@/components/admin/AdminPlatformKhhaoSection.vue'
|
||||
import AdminPlatformNinetyoneSection from '@/components/admin/AdminPlatformNinetyoneSection.vue'
|
||||
import AdminPlatformKuaishouEticketSection from '@/components/admin/AdminPlatformKuaishouEticketSection.vue'
|
||||
import { useAdminAgisoPlatform } from '@/composables/admin/platform-shops/useAdminAgisoPlatform'
|
||||
import { useAdminCloudtentaclesPlatform } from '@/composables/admin/platform-shops/useAdminCloudtentaclesPlatform'
|
||||
import { useAdminKhhaoPlatform } from '@/composables/admin/platform-shops/useAdminKhhaoPlatform'
|
||||
import { useAdminNinetyonePlatform } from '@/composables/admin/platform-shops/useAdminNinetyonePlatform'
|
||||
import { useAdminKuaishouEticketPlatform } from '@/composables/admin/platform-shops/useAdminKuaishouEticketPlatform'
|
||||
import type { PlatformTab } from '@/composables/admin/platform-shops/types'
|
||||
import {
|
||||
@@ -64,6 +66,19 @@ const {
|
||||
handleKhhaoSyncOrders,
|
||||
} = useAdminKhhaoPlatform()
|
||||
|
||||
const {
|
||||
ninetyoneLoading,
|
||||
ninetyoneActionLoadingId,
|
||||
ninetyoneResultError,
|
||||
ninetyoneStatus,
|
||||
ninetyoneOrders,
|
||||
ninetyoneStats,
|
||||
loadNinetyoneOrders,
|
||||
handleNinetyoneStatusChange,
|
||||
handleNinetyoneRetryOrder,
|
||||
handleNinetyoneFailOrder,
|
||||
} = useAdminNinetyonePlatform()
|
||||
|
||||
const {
|
||||
kuaishouEticketFilePath,
|
||||
kuaishouEticketShops,
|
||||
@@ -152,6 +167,7 @@ async function loadConfigs() {
|
||||
hydrateKhhaoConfig(khhaoResponse.data)
|
||||
hydrateKuaishouEticketConfig(kuaishouEticketResponse.data)
|
||||
hydrateCloudtentaclesConfig(cloudtentaclesResponse.data)
|
||||
await loadNinetyoneOrders(1)
|
||||
} catch (error) {
|
||||
errorMessage.value = error instanceof Error ? error.message : '读取平台配置失败'
|
||||
} finally {
|
||||
@@ -208,6 +224,23 @@ onMounted(loadConfigs)
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
:class="['platform-overview-card', { 'is-active': activePlatform === 'ninetyone' }]"
|
||||
@click="switchPlatform('ninetyone')"
|
||||
>
|
||||
<div class="platform-head">
|
||||
<span class="platform-badge">91卡券</span>
|
||||
<span class="platform-tag">接入 / 待补全</span>
|
||||
</div>
|
||||
<strong>{{ ninetyoneStats.pendingCount }}</strong>
|
||||
<span>待补全订单</span>
|
||||
<div class="platform-metrics">
|
||||
<span>当前 {{ ninetyoneStats.currentCount }} 条</span>
|
||||
<span>已生成任务 {{ ninetyoneStats.taskReadyCount }} 条</span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
:class="['platform-overview-card', { 'is-active': activePlatform === 'khhao' }]"
|
||||
@@ -276,6 +309,13 @@ onMounted(loadConfigs)
|
||||
>
|
||||
khhao 来源与同步
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="['switch-chip', { 'is-active': activePlatform === 'ninetyone' }]"
|
||||
@click="switchPlatform('ninetyone')"
|
||||
>
|
||||
91卡券接入
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
:class="['switch-chip', { 'is-active': activePlatform === 'kuaishouEticket' }]"
|
||||
@@ -330,6 +370,20 @@ onMounted(loadConfigs)
|
||||
:handle-khhao-sync-orders="handleKhhaoSyncOrders"
|
||||
/>
|
||||
|
||||
<AdminPlatformNinetyoneSection
|
||||
v-else-if="activePlatform === 'ninetyone'"
|
||||
:ninetyone-loading="ninetyoneLoading"
|
||||
:ninetyone-action-loading-id="ninetyoneActionLoadingId"
|
||||
:ninetyone-result-error="ninetyoneResultError"
|
||||
:ninetyone-status="ninetyoneStatus"
|
||||
:ninetyone-orders="ninetyoneOrders"
|
||||
:ninetyone-stats="ninetyoneStats"
|
||||
:load-ninetyone-orders="loadNinetyoneOrders"
|
||||
:handle-ninetyone-status-change="handleNinetyoneStatusChange"
|
||||
:handle-ninetyone-retry-order="handleNinetyoneRetryOrder"
|
||||
:handle-ninetyone-fail-order="handleNinetyoneFailOrder"
|
||||
/>
|
||||
|
||||
<AdminPlatformKuaishouEticketSection
|
||||
v-else-if="activePlatform === 'kuaishouEticket'"
|
||||
:kuaishou-eticket-file-path="kuaishouEticketFilePath"
|
||||
|
||||
Reference in New Issue
Block a user