修复小店核销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
@@ -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>