修复小店核销bug, 链接10分钟

This commit is contained in:
yml2213
2026-05-14 16:31:56 +08:00
parent 89ffc9b087
commit 91d74b3e59
8 changed files with 104 additions and 31 deletions
+1 -1
View File
@@ -25,7 +25,7 @@ TENCENT_BROWSER_PREWARM=true
TENCENT_BROWSER_KEEP_ALIVE=true
TENCENT_BROWSER_SLOW_MO=0
TENCENT_SESSION_DEBUG=false
CLOUDTENTACLES_BIND_URL_TTL_SECONDS=300
CLOUDTENTACLES_BIND_URL_TTL_SECONDS=600
CLOUDTENTACLES_BIND_URL_PROBE_INTERVAL_SECONDS=30
CLOUDTENTACLES_BIND_URL_PROBE_TIMEOUT_MS=5000
CLOUDTENTACLES_BIND_URL_PROBE_ENDPOINT=https://comm.ams.game.qq.com/ide/
+1 -1
View File
@@ -99,7 +99,7 @@ module.exports = {
vnBindUrlPath: '/vn/bind_url',
vnBindInfoPath: '/vn/bind_info',
vnBackPath: '/vn/back',
bindUrlTtlSeconds: 300,
bindUrlTtlSeconds: 600,
bindUrlProbeIntervalSeconds: 30,
bindUrlProbeTimeoutMs: 5000,
bindUrlProbeUserAgent: 'Mozilla/5.0 (Linux; Android 12; M2012K11AC Build/SKQ1.211006.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/98.0.4758.102 MQQBrowser/6.2 TBS/046141 Mobile Safari/537.36 V1_AND_SQ_8.8.85_2674_YYB_D QQ/8.8.85.7685 NetType/WIFI WebP/0.3.0 Pixel/1080 StatusBarHeight/108 SimpleUISwitch/0 QQTheme/1000',
@@ -459,11 +459,12 @@ export async function returnNumberAdminTaskKuaishouCloudFulfillment(taskId, sess
const order = await getOrderById(task.order_id)
const ticketCode = String(flow.ticket.code || '').trim()
const shopId = String(order?.shop_id || '').trim()
const shopId = String(flow.consume.shopId || order?.shop_id || '').trim()
const shopName = String(flow.consume.shopName || order?.shop_name || '').trim()
const eticketSource = getKuaishouEticketSourceConfig()
const shopConfig = resolveKuaishouEticketShopConfig({
shopId,
shopName: String(order?.shop_name || '').trim(),
shopName,
})
let consumeStatus = 'pending'
let consumeErrorMessage = ''
@@ -530,7 +531,7 @@ export async function returnNumberAdminTaskKuaishouCloudFulfillment(taskId, sess
...flow.consume,
status: consumeStatus,
shopId: shopId || flow.consume.shopId,
shopName: String(flow.consume.shopName || order?.shop_name || '').trim(),
shopName,
autoConsumeEnabled: flow.consume.autoConsumeEnabled === true,
consumedAt,
errorMessage: consumeErrorMessage,
@@ -560,6 +561,7 @@ export async function returnNumberAdminTaskKuaishouCloudFulfillment(taskId, sess
{
ticketCodeMasked: maskCode(ticketCode),
shopId,
shopName,
consumeStatus,
errorMessage: consumeErrorMessage,
},
@@ -144,7 +144,7 @@ export function resolveKuaishouCloudBindUrlExpiresAt(preparedAt) {
}
const config = resolveCloudtentaclesConfig()
const ttlSeconds = Number(config.bindUrlTtlSeconds || 300)
const ttlSeconds = Number(config.bindUrlTtlSeconds || 600)
return new Date(preparedTime + Math.max(1, ttlSeconds) * 1000).toISOString()
}
@@ -890,11 +890,12 @@ export async function returnKuaishouCloudFulfillmentTask(task, options = {}) {
const order = await getOrderById(task.order_id)
const ticketCode = String(flow.ticket.code || '').trim()
const shopId = String(order?.shop_id || '').trim()
const shopId = String(flow.consume.shopId || order?.shop_id || '').trim()
const shopName = String(flow.consume.shopName || order?.shop_name || '').trim()
const eticketSource = getKuaishouEticketSourceConfig()
const shopConfig = resolveKuaishouEticketShopConfig({
shopId,
shopName: String(order?.shop_name || '').trim(),
shopName,
})
let consumeStatus = 'pending'
@@ -956,7 +957,7 @@ export async function returnKuaishouCloudFulfillmentTask(task, options = {}) {
...flow.consume,
status: consumeStatus,
shopId: shopId || flow.consume.shopId,
shopName: String(flow.consume.shopName || order?.shop_name || '').trim(),
shopName,
autoConsumeEnabled: flow.consume.autoConsumeEnabled === true,
consumedAt,
errorMessage: consumeErrorMessage,
@@ -989,6 +990,7 @@ export async function returnKuaishouCloudFulfillmentTask(task, options = {}) {
source: String(options.source || 'system').trim() || 'system',
ticketCodeMasked: maskCode(ticketCode),
shopId,
shopName,
consumeStatus,
errorMessage: consumeErrorMessage,
actor,
@@ -1035,7 +1037,7 @@ export function resolveKuaishouCloudBindUrlExpiresAt(preparedAt) {
}
const config = resolveCloudtentaclesConfig()
const ttlSeconds = Number(config.bindUrlTtlSeconds || 300)
const ttlSeconds = Number(config.bindUrlTtlSeconds || 600)
return new Date(preparedTime + Math.max(1, ttlSeconds) * 1000).toISOString()
}
@@ -2,6 +2,7 @@ import fs from 'node:fs'
import path from 'node:path'
import { PROJECT_ROOT } from '../../config/runtime.js'
import { resolveKuaishouEticketShopConfig } from '../platforms/kuaishou-eticket/source-config-service.js'
const KUAISHOU_CLOUD_FULFILLMENT_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'kuaishou-cloud-fulfillment.json')
@@ -103,6 +104,12 @@ function normalizeKuaishouCloudFulfillmentItem(rawValue) {
const externalSkuCode = String(rawValue.externalSkuCode || '').trim()
const externalItemId = String(rawValue.externalItemId || '').trim()
const externalSkuName = String(rawValue.externalSkuName || '').trim()
const kuaishouConsumeShopId = String(rawValue.kuaishouConsumeShopId || '').trim()
const kuaishouConsumeShopName = String(rawValue.kuaishouConsumeShopName || '').trim()
const kuaishouShopConfig = resolveKuaishouEticketShopConfig({
shopId: kuaishouConsumeShopId,
shopName: kuaishouConsumeShopName,
})
if (!internalSkuCode) {
return null
@@ -137,8 +144,8 @@ function normalizeKuaishouCloudFulfillmentItem(rawValue) {
minAssetReserve: normalizeNonNegativeInteger(rawValue.minAssetReserve, 0),
autoReturnNumberAfterDispatch: rawValue.autoReturnNumberAfterDispatch === true,
autoConsumeAfterDispatch: rawValue.autoConsumeAfterDispatch === true,
kuaishouConsumeShopId: String(rawValue.kuaishouConsumeShopId || '').trim(),
kuaishouConsumeShopName: String(rawValue.kuaishouConsumeShopName || '').trim(),
kuaishouConsumeShopId: String(kuaishouShopConfig?.shopId || kuaishouConsumeShopId).trim(),
kuaishouConsumeShopName: String(kuaishouShopConfig?.kshopName || kuaishouConsumeShopName).trim(),
notes: String(rawValue.notes || '').trim(),
}
}
@@ -27,7 +27,7 @@ export function resolveCloudtentaclesConfig(overrides = {}) {
vnBindUrlPath: '/vn/bind_url',
vnBindInfoPath: '/vn/bind_info',
vnBackPath: '/vn/back',
bindUrlTtlSeconds: 300,
bindUrlTtlSeconds: 600,
bindUrlProbeIntervalSeconds: 30,
bindUrlProbeTimeoutMs: 5000,
bindUrlProbeUserAgent: '',
@@ -71,7 +71,7 @@ export function resolveCloudtentaclesConfig(overrides = {}) {
vnBindUrlPath: normalizePath(overrides.vnBindUrlPath || baseConfig.vnBindUrlPath, '/vn/bind_url'),
vnBindInfoPath: normalizePath(overrides.vnBindInfoPath || baseConfig.vnBindInfoPath, '/vn/bind_info'),
vnBackPath: normalizePath(overrides.vnBackPath || baseConfig.vnBackPath, '/vn/back'),
bindUrlTtlSeconds: normalizePositiveInteger(overrides.bindUrlTtlSeconds || baseConfig.bindUrlTtlSeconds, 300),
bindUrlTtlSeconds: normalizePositiveInteger(overrides.bindUrlTtlSeconds || baseConfig.bindUrlTtlSeconds, 600),
bindUrlProbeIntervalSeconds: normalizePositiveInteger(
overrides.bindUrlProbeIntervalSeconds || baseConfig.bindUrlProbeIntervalSeconds,
30,
@@ -5,6 +5,7 @@ import { showError, showSuccess } from '@/lib/feedback'
import {
fetchAdminCloudtentaclesSkuList,
fetchAdminKuaishouCloudFulfillmentConfig,
fetchAdminKuaishouEticketSourceConfig,
fetchAdminNinetyoneOrders,
saveAdminKuaishouCloudFulfillmentConfig,
} from '@/services/admin'
@@ -12,6 +13,7 @@ import type {
AdminCloudtentaclesSkuItem,
AdminKuaishouCloudFulfillmentConfig,
AdminKuaishouCloudFulfillmentItem,
AdminKuaishouEticketShopConfigItem,
AdminNinetyoneOrderItem,
} from '@/types/admin'
import { hasAdminRole } from '@/utils/admin-auth'
@@ -43,6 +45,7 @@ const importedNinetyoneOrderKeys = ref<string[]>([])
const cloudSkuCatalogLoading = ref(false)
const cloudSkuCatalogErrorMessage = ref('')
const cloudSkuCatalog = ref<AdminCloudtentaclesSkuItem[]>([])
const kuaishouConsumeShops = ref<AdminKuaishouEticketShopConfigItem[]>([])
const ninetyoneLookupForm = reactive({
status: 'pending_config' as 'pending_config' | 'all' | 'manual_failed',
page: 1,
@@ -83,6 +86,7 @@ const ninetyoneLookupMetrics = computed(() => {
})
function createEmptyItem(): EditableItem {
const defaultShop = getDefaultKuaishouConsumeShop()
return {
localId: crypto.randomUUID(),
id: '',
@@ -105,13 +109,14 @@ function createEmptyItem(): EditableItem {
minAssetReserve: 0,
autoReturnNumberAfterDispatch: false,
autoConsumeAfterDispatch: false,
kuaishouConsumeShopId: '',
kuaishouConsumeShopName: '',
kuaishouConsumeShopId: defaultShop?.shopId || '',
kuaishouConsumeShopName: defaultShop?.kshopName || '',
notes: '',
}
}
function mapEditableItem(item: AdminKuaishouCloudFulfillmentItem): EditableItem {
const matchedShop = findKuaishouConsumeShop(item.kuaishouConsumeShopId, item.kuaishouConsumeShopName)
return {
localId: crypto.randomUUID(),
id: item.id || crypto.randomUUID(),
@@ -134,13 +139,14 @@ function mapEditableItem(item: AdminKuaishouCloudFulfillmentItem): EditableItem
minAssetReserve: Number(item.minAssetReserve || 0) || 0,
autoReturnNumberAfterDispatch: item.autoReturnNumberAfterDispatch === true,
autoConsumeAfterDispatch: item.autoConsumeAfterDispatch === true,
kuaishouConsumeShopId: item.kuaishouConsumeShopId || '',
kuaishouConsumeShopName: item.kuaishouConsumeShopName || '',
kuaishouConsumeShopId: matchedShop?.shopId || item.kuaishouConsumeShopId || '',
kuaishouConsumeShopName: matchedShop?.kshopName || item.kuaishouConsumeShopName || '',
notes: item.notes || '',
}
}
function mapSaveItem(item: EditableItem): AdminKuaishouCloudFulfillmentItem {
const matchedShop = findKuaishouConsumeShop(item.kuaishouConsumeShopId, item.kuaishouConsumeShopName)
return {
id: item.id.trim() || item.internalSkuCode.trim() || item.localId,
enabled: item.enabled,
@@ -162,8 +168,8 @@ function mapSaveItem(item: EditableItem): AdminKuaishouCloudFulfillmentItem {
minAssetReserve: Math.max(0, Number(item.minAssetReserve || 0) || 0),
autoReturnNumberAfterDispatch: item.autoReturnNumberAfterDispatch,
autoConsumeAfterDispatch: item.autoConsumeAfterDispatch,
kuaishouConsumeShopId: item.kuaishouConsumeShopId.trim(),
kuaishouConsumeShopName: item.kuaishouConsumeShopName.trim(),
kuaishouConsumeShopId: String(matchedShop?.shopId || item.kuaishouConsumeShopId).trim(),
kuaishouConsumeShopName: String(matchedShop?.kshopName || item.kuaishouConsumeShopName).trim(),
notes: item.notes.trim(),
}
}
@@ -193,6 +199,8 @@ function hasMeaningfulContent(item: EditableItem) {
|| item.minAssetReserve !== 0
|| item.autoReturnNumberAfterDispatch
|| item.autoConsumeAfterDispatch
|| item.kuaishouConsumeShopId.trim()
|| item.kuaishouConsumeShopName.trim()
|| item.enabled !== true,
)
}
@@ -201,7 +209,7 @@ function isItemComplete(item: EditableItem) {
return Boolean(
item.internalSkuCode.trim()
&& Number(item.cloudSkuId || 0) > 0
&& true
&& (!item.autoConsumeAfterDispatch || Boolean(item.kuaishouConsumeShopId.trim()))
&& hasExternalMatch(item),
)
}
@@ -225,6 +233,10 @@ function resolveValidation(item: EditableItem, index: number): ValidationState {
return { localId: item.localId, message: `${title} 至少填写一种外部匹配条件` }
}
if (item.autoConsumeAfterDispatch && !item.kuaishouConsumeShopId.trim()) {
return { localId: item.localId, message: `${title} 已开启发货后核销,需要选择快手核销店铺` }
}
return null
}
@@ -280,9 +292,36 @@ function getExternalMatchSummary(item: EditableItem) {
return parts.length > 0 ? parts.join(' / ') : '未设置外部命中条件'
}
function getKuaishouConsumeShopOptions() {
return kuaishouConsumeShops.value.filter((shop) => shop.enabled !== false && shop.hasCookie)
}
function findKuaishouConsumeShop(shopId = '', shopName = '') {
const normalizedShopId = String(shopId || '').trim()
const normalizedShopName = String(shopName || '').trim()
return getKuaishouConsumeShopOptions().find((shop) => (
(normalizedShopId && shop.shopId === normalizedShopId)
|| (normalizedShopName && shop.kshopName === normalizedShopName)
)) || null
}
function getDefaultKuaishouConsumeShop() {
return getKuaishouConsumeShopOptions()[0] || null
}
function formatKuaishouConsumeShopOption(shop: AdminKuaishouEticketShopConfigItem) {
return `${shop.kshopName || '未命名快手小店'} · ${shop.shopId}`
}
function handleKuaishouConsumeShopSelected(item: EditableItem) {
const matchedShop = findKuaishouConsumeShop(item.kuaishouConsumeShopId)
item.kuaishouConsumeShopName = matchedShop?.kshopName || ''
}
function getConsumeShopSummary(item: EditableItem) {
const name = item.kuaishouConsumeShopName.trim()
const id = item.kuaishouConsumeShopId.trim()
const matchedShop = findKuaishouConsumeShop(item.kuaishouConsumeShopId, item.kuaishouConsumeShopName)
const name = String(matchedShop?.kshopName || item.kuaishouConsumeShopName).trim()
const id = String(matchedShop?.shopId || item.kuaishouConsumeShopId).trim()
if (name && id) {
return `${name} · ${id}`
@@ -343,7 +382,11 @@ async function loadConfigs() {
errorMessage.value = ''
try {
const response = await fetchAdminKuaishouCloudFulfillmentConfig()
const [response, eticketResponse] = await Promise.all([
fetchAdminKuaishouCloudFulfillmentConfig(),
fetchAdminKuaishouEticketSourceConfig(),
])
kuaishouConsumeShops.value = Array.isArray(eticketResponse.data.source.shops) ? eticketResponse.data.source.shops : []
filePath.value = response.data.filePath
enabled.value = response.data.source.enabled !== false
items.value = (response.data.source.items || []).map(mapEditableItem)
@@ -373,6 +416,7 @@ function removeItem(localId: string) {
function createItemFromNinetyoneOrder(item: AdminNinetyoneOrderItem): EditableItem {
const productNo = String(item.productNo || '').trim()
const productName = String(item.productName || productNo).trim()
const defaultShop = getDefaultKuaishouConsumeShop()
return {
localId: crypto.randomUUID(),
id: '',
@@ -395,8 +439,8 @@ function createItemFromNinetyoneOrder(item: AdminNinetyoneOrderItem): EditableIt
minAssetReserve: 0,
autoReturnNumberAfterDispatch: false,
autoConsumeAfterDispatch: false,
kuaishouConsumeShopId: '',
kuaishouConsumeShopName: '',
kuaishouConsumeShopId: defaultShop?.shopId || '',
kuaishouConsumeShopName: defaultShop?.kshopName || '',
notes: `从 91卡券订单 ${item.orderNo} 导入`,
}
}
@@ -978,13 +1022,31 @@ onMounted(loadConfigs)
</div>
<div v-if="!isCollapsed(item.localId)" class="mapping-extra">
<label class="field-block">
<span>快手核销店铺 ID</span>
<input v-model="item.kuaishouConsumeShopId" class="text-input" maxlength="80" placeholder="预留给后续自动核销" />
<label class="field-block field-wide">
<span>快手核销店铺</span>
<select
v-model="item.kuaishouConsumeShopId"
class="text-input"
@change="handleKuaishouConsumeShopSelected(item)"
>
<option value="">请选择已配置 Cookie 的快手小店</option>
<option
v-for="shop in getKuaishouConsumeShopOptions()"
:key="shop.shopId"
:value="shop.shopId"
>
{{ formatKuaishouConsumeShopOption(shop) }}
</option>
</select>
<small class="field-help">来自平台店铺 -> 快手小店核销配置保存时会自动带出店铺名</small>
</label>
<label class="field-block">
<span>快手核销店铺</span>
<input v-model="item.kuaishouConsumeShopName" class="text-input" maxlength="120" placeholder="便于快速确认" />
<span>核销店铺 ID</span>
<input :value="item.kuaishouConsumeShopId || '-'" class="text-input text-input--readonly" readonly />
</label>
<label class="field-block">
<span>核销店铺名</span>
<input :value="item.kuaishouConsumeShopName || '-'" class="text-input text-input--readonly" readonly />
</label>
<label class="field-block field-wide">
<span>备注</span>