移除了 khhao 平台相关

This commit is contained in:
yml2213
2026-05-14 13:08:24 +08:00
parent d335b430e6
commit 83cb2ff1fa
56 changed files with 108 additions and 3893 deletions
@@ -4,7 +4,6 @@ import { computed, nextTick, onMounted, reactive, ref } from 'vue'
import { showSuccess } from '@/lib/feedback'
import {
fetchAdminFulfillmentBindingConfigs,
fetchAdminKuaishouEticketSourceConfig,
lookupAdminFulfillmentBindingOrder,
saveAdminFulfillmentBindingConfigs,
} from '@/services/admin'
@@ -12,7 +11,6 @@ import type {
AdminFulfillmentBindingConfigItem,
AdminFulfillmentLookupItem,
AdminFulfillmentLookupResult,
AdminKuaishouEticketShopConfigItem,
AdminObservedProductItem,
} from '@/types/admin'
import { hasAdminRole } from '@/utils/admin-auth'
@@ -24,7 +22,6 @@ type EditableBinding = {
platform: string
shopId: string
shopName: string
khhaoShopId: string
skuCode: string
skuName: string
profileKey: string
@@ -41,7 +38,6 @@ type SaveBindingPayload = {
platform: string
shopId: string
shopName: string
khhaoShopId: string
skuCode: string
skuName: string
profileKey: string
@@ -57,7 +53,7 @@ type SaveBindingPayload = {
}
}
type ValidationField = 'skuCode' | 'match' | 'profileKey' | 'shopId' | 'khhaoShopId'
type ValidationField = 'skuCode' | 'match' | 'profileKey' | 'shopId'
type ValidationState = {
bindingId: string
@@ -78,7 +74,6 @@ const errorMessage = ref('')
const filePath = ref('')
const bindings = ref<EditableBinding[]>([])
const observedProducts = ref<AdminObservedProductItem[]>([])
const kuaishouShopOptions = ref<AdminKuaishouEticketShopConfigItem[]>([])
const lookupLoading = ref(false)
const lookupErrorMessage = ref('')
const lookupResult = ref<AdminFulfillmentLookupResult | null>(null)
@@ -113,7 +108,6 @@ type ImportableProductCandidate = Pick<
AdminObservedProductItem,
'provider' | 'platform' | 'shopId' | 'shopName' | 'externalSkuCode' | 'externalItemId' | 'externalSkuName'
> & {
khhaoShopId?: string
}
function createEmptyBinding(): EditableBinding {
@@ -123,7 +117,6 @@ function createEmptyBinding(): EditableBinding {
platform: '',
shopId: '',
shopName: '',
khhaoShopId: '',
skuCode: '',
skuName: '',
profileKey: 'manual_review',
@@ -137,14 +130,12 @@ function createEmptyBinding(): EditableBinding {
}
function mapEditableBinding(item: AdminFulfillmentBindingConfigItem): EditableBinding {
const matchedShop = resolveKuaishouShopById(item.shopId) || resolveKuaishouShopByName(item.shopName || '')
const binding = {
return {
id: crypto.randomUUID(),
provider: item.provider || 'agiso',
platform: item.platform,
shopId: matchedShop?.shopId || item.shopId,
shopName: item.shopName || matchedShop?.kshopName || '',
khhaoShopId: item.khhaoShopId || '',
shopId: item.shopId,
shopName: item.shopName || '',
skuCode: item.skuCode,
skuName: item.skuName,
profileKey: item.profileKey || 'manual_review',
@@ -155,9 +146,6 @@ function mapEditableBinding(item: AdminFulfillmentBindingConfigItem): EditableBi
externalSkuName: item.match.externalSkuName,
resolvedSkuName: String(item.match.config?.resolvedSkuName || ''),
}
syncBindingShopSelection(binding)
return binding
}
function hasMatchCondition(item: Pick<EditableBinding, 'externalSkuCode' | 'externalItemId' | 'externalSkuName'>) {
@@ -205,64 +193,16 @@ function getBindingStatusLabel(binding: EditableBinding) {
}
function getBindingSummary(binding: EditableBinding) {
const shopLabel = isKhhaoKuaishouBinding(binding)
? [
binding.shopName.trim() || binding.shopId.trim() || '未选快手店铺',
binding.khhaoShopId.trim() ? `khhao#${binding.khhaoShopId.trim()}` : '',
].filter(Boolean).join(' / ')
: (binding.shopName.trim() || binding.shopId.trim() || '跨店铺')
const parts = [
binding.provider.trim() || 'agiso',
binding.platform.trim() || '未选平台',
shopLabel,
binding.shopName.trim() || binding.shopId.trim() || '跨店铺',
binding.profileKey.trim() || 'manual_review',
]
return parts.join(' / ')
}
function isKhhaoKuaishouBinding(binding: Pick<EditableBinding, 'provider' | 'platform'>) {
return binding.provider.trim() === 'khhao' && binding.platform.trim() === 'kuaishou'
}
function resolveKuaishouShopById(shopId: string) {
const normalizedShopId = shopId.trim()
return kuaishouShopOptions.value.find((item) => item.shopId === normalizedShopId) || null
}
function resolveKuaishouShopByName(shopName: string) {
const normalizedShopName = shopName.trim()
return kuaishouShopOptions.value.find((item) => item.kshopName === normalizedShopName) || null
}
function getKuaishouShopOptionLabel(item: AdminKuaishouEticketShopConfigItem) {
return item.kshopName.trim()
? `${item.kshopName} (${item.shopId})`
: item.shopId
}
function syncBindingShopSelection(binding: EditableBinding) {
if (!isKhhaoKuaishouBinding(binding)) {
return
}
const matchedById = resolveKuaishouShopById(binding.shopId)
if (matchedById) {
binding.shopId = matchedById.shopId
binding.shopName = matchedById.kshopName
return
}
const matchedByName = resolveKuaishouShopByName(binding.shopName)
if (matchedByName) {
binding.shopId = matchedByName.shopId
binding.shopName = matchedByName.kshopName
return
}
binding.shopId = ''
}
async function loadConfigs() {
if (!hasAdminRole('admin')) {
loading.value = false
@@ -273,12 +213,8 @@ async function loadConfigs() {
errorMessage.value = ''
try {
const [response, kuaishouSourceResponse] = await Promise.all([
fetchAdminFulfillmentBindingConfigs(),
fetchAdminKuaishouEticketSourceConfig(),
])
const response = await fetchAdminFulfillmentBindingConfigs()
filePath.value = response.data.filePath
kuaishouShopOptions.value = kuaishouSourceResponse.data.source.shops.filter((item) => item.enabled !== false)
const nextBindings = response.data.bindings.map(mapEditableBinding)
bindings.value = nextBindings
collapsedBindingIds.value = buildCollapsedIds(nextBindings)
@@ -312,7 +248,6 @@ function createBindingFromProductCandidate(item: ImportableProductCandidate): Ed
platform: item.platform,
shopId: item.shopId,
shopName: item.shopName,
khhaoShopId: item.khhaoShopId || '',
skuCode: '',
skuName: item.externalSkuName,
profileKey: 'manual_review',
@@ -324,7 +259,6 @@ function createBindingFromProductCandidate(item: ImportableProductCandidate): Ed
resolvedSkuName: item.externalSkuName,
}
syncBindingShopSelection(binding)
return binding
}
@@ -347,14 +281,11 @@ function isLookupProductImported(lineId: string) {
}
function normalizeBindingForSave(item: EditableBinding): SaveBindingPayload {
syncBindingShopSelection(item)
return {
provider: item.provider.trim() || 'agiso',
platform: item.platform.trim(),
shopId: item.shopId.trim(),
shopName: item.shopName.trim(),
khhaoShopId: item.khhaoShopId.trim(),
skuCode: item.skuCode.trim(),
skuName: item.skuName.trim(),
profileKey: item.profileKey.trim() || 'manual_review',
@@ -376,7 +307,6 @@ function hasBindingContent(item: SaveBindingPayload) {
item.platform
|| item.shopId
|| item.shopName
|| item.khhaoShopId
|| item.skuCode
|| item.skuName
|| item.match.externalSkuCode
@@ -392,24 +322,6 @@ function hasBindingContent(item: SaveBindingPayload) {
function resolveBindingValidationState(item: SaveBindingPayload, index: number, bindingId: string): ValidationState {
const label = `${index + 1} 条规则`
const provider = item.provider.trim() || 'agiso'
const platform = item.platform.trim()
if (provider === 'khhao' && platform === 'kuaishou' && (!item.shopId || !item.shopName)) {
return {
bindingId,
field: 'shopId',
message: `${label} 需要选择已配置的快手官方店铺`,
}
}
if (provider === 'khhao' && platform === 'kuaishou' && !item.khhaoShopId) {
return {
bindingId,
field: 'khhaoShopId',
message: `${label} 需要填写 khhao 店铺 ID`,
}
}
if (!item.skuCode) {
return {
@@ -454,7 +366,6 @@ async function focusValidationTarget(state: ValidationState) {
card.scrollIntoView({ behavior: 'smooth', block: 'center' })
const selectors: Record<ValidationField, string> = {
khhaoShopId: '[data-field="khhaoShopId"]',
shopId: '[data-field="shopId"]',
skuCode: '[data-field="skuCode"]',
match: '[data-field="externalSkuCode"]',
@@ -485,16 +396,6 @@ function isFieldInvalid(bindingId: string, field: ValidationField | 'externalSku
return validationState.value.field === field
}
function handleBindingProviderPlatformChange(binding: EditableBinding) {
syncBindingShopSelection(binding)
clearValidationState()
}
function handleKuaishouShopChange(binding: EditableBinding) {
syncBindingShopSelection(binding)
clearValidationState()
}
async function saveConfigs() {
const normalizedBindings = bindings.value.map(normalizeBindingForSave)
const nonEmptyBindings = normalizedBindings.filter(hasBindingContent)
@@ -694,7 +595,7 @@ onMounted(loadConfigs)
v-model="binding.provider"
class="text-input"
placeholder="agiso"
@input="handleBindingProviderPlatformChange(binding)"
@input="clearValidationState"
/>
</label>
@@ -704,76 +605,30 @@ onMounted(loadConfigs)
v-model="binding.platform"
class="text-input"
placeholder="xianyu / taobao / pdd"
@input="handleBindingProviderPlatformChange(binding)"
@input="clearValidationState"
/>
</label>
<template v-if="isKhhaoKuaishouBinding(binding)">
<label class="field-block">
<span>khhao 店铺 ID</span>
<input
v-model="binding.khhaoShopId"
:class="['text-input', { 'text-input--invalid': isFieldInvalid(binding.id, 'khhaoShopId') }]"
data-field="khhaoShopId"
placeholder="例如 12 / 13 / 14"
@input="clearValidationState"
/>
<small class="field-help">这里填 khhao 系统里的店铺编号专门用于同步订单命中规则</small>
</label>
<label class="field-block">
<span>店铺 ID</span>
<input
v-model="binding.shopId"
:class="['text-input', { 'text-input--invalid': isFieldInvalid(binding.id, 'shopId') }]"
data-field="shopId"
placeholder="留空表示跨店铺通用"
@input="clearValidationState"
/>
</label>
<label class="field-block">
<span>快手店铺</span>
<select
v-model="binding.shopId"
:class="['text-input', { 'text-input--invalid': isFieldInvalid(binding.id, 'shopId') }]"
data-field="shopId"
@change="handleKuaishouShopChange(binding)"
>
<option value="">请选择已配置的快手官方店铺</option>
<option
v-for="shop in kuaishouShopOptions"
:key="shop.shopId"
:value="shop.shopId"
>
{{ getKuaishouShopOptionLabel(shop) }}
</option>
</select>
<small class="field-help">这里会直接写入快手官方店铺 ID不再使用 khhao 内部店铺编号</small>
</label>
<label class="field-block">
<span>快手店铺名</span>
<input
:value="binding.shopName || ''"
class="text-input text-input--readonly"
placeholder="选择店铺后自动带出"
readonly
/>
</label>
</template>
<template v-else>
<label class="field-block">
<span>店铺 ID</span>
<input
v-model="binding.shopId"
:class="['text-input', { 'text-input--invalid': isFieldInvalid(binding.id, 'shopId') }]"
data-field="shopId"
placeholder="留空表示跨店铺通用"
@input="clearValidationState"
/>
</label>
<label class="field-block">
<span>店铺名称</span>
<input
v-model="binding.shopName"
class="text-input"
placeholder="用于保存和识别店铺"
@input="clearValidationState"
/>
</label>
</template>
<label class="field-block">
<span>店铺名称</span>
<input
v-model="binding.shopName"
class="text-input"
placeholder="用于保存和识别店铺"
@input="clearValidationState"
/>
</label>
<label class="field-block">
<span>优先级</span>
@@ -1213,7 +1068,6 @@ onMounted(loadConfigs)
}
.lookup-toolbar,
.khhao-toolbar,
.lookup-summary {
display: grid;
gap: 12px;
@@ -1224,11 +1078,6 @@ onMounted(loadConfigs)
align-items: end;
}
.khhao-toolbar {
grid-template-columns: repeat(4, minmax(0, 1fr));
align-items: end;
}
.field-block--action :deep(.el-button) {
width: 100%;
}
@@ -4,15 +4,12 @@ import { computed, nextTick, onMounted, reactive, ref } from 'vue'
import { showError, showSuccess } from '@/lib/feedback'
import {
fetchAdminCloudtentaclesSkuList,
fetchAdminKhhaoSourceConfig,
fetchAdminKuaishouCloudFulfillmentConfig,
fetchAdminNinetyoneOrders,
queryAdminKhhaoOrders,
saveAdminKuaishouCloudFulfillmentConfig,
} from '@/services/admin'
import type {
AdminCloudtentaclesSkuItem,
AdminKhhaoOrderPreview,
AdminKuaishouCloudFulfillmentConfig,
AdminKuaishouCloudFulfillmentItem,
AdminNinetyoneOrderItem,
@@ -39,10 +36,6 @@ const enabled = ref(true)
const items = ref<EditableItem[]>([])
const collapsedIds = ref<string[]>([])
const ruleFilter = ref<RuleFilter>('all')
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[]>([])
@@ -50,14 +43,6 @@ const importedNinetyoneOrderKeys = ref<string[]>([])
const cloudSkuCatalogLoading = ref(false)
const cloudSkuCatalogErrorMessage = ref('')
const cloudSkuCatalog = ref<AdminCloudtentaclesSkuItem[]>([])
const khhaoLookupForm = reactive({
baseUrl: 'https://admin.khhao.com',
username: '',
password: '',
page: 1,
limit: 10,
maxCaptchaAttempts: 3,
})
const ninetyoneLookupForm = reactive({
status: 'pending_config' as 'pending_config' | 'all' | 'manual_failed',
page: 1,
@@ -86,21 +71,6 @@ const ruleFilterOptions = computed<Array<{ value: RuleFilter; label: string; cou
{ value: 'disabled', label: '已停用', count: metrics.value.disabledCount },
])
const khhaoLookupMetrics = computed(() => {
const importedCount = khhaoLookupResults.value.filter((item) => isKhhaoProductImported(item)).length
const shopCount = new Set(
khhaoLookupResults.value
.map((item) => String(item.shopName || item.kuaishouShopId || item.shopId || '').trim())
.filter(Boolean),
).size
return {
total: khhaoLookupResults.value.length,
importedCount,
availableCount: Math.max(khhaoLookupResults.value.length - importedCount, 0),
shopCount,
}
})
const ninetyoneLookupMetrics = computed(() => {
const importedCount = ninetyoneLookupResults.value.filter((item) => isNinetyoneProductImported(item)).length
const pendingCount = ninetyoneLookupResults.value.filter((item) => item.orderStatus === 'pending_config').length
@@ -118,10 +88,9 @@ function createEmptyItem(): EditableItem {
id: '',
enabled: true,
priority: 100,
provider: 'khhao',
provider: '91kaquan',
platform: 'kuaishou',
shopId: '',
khhaoShopId: '',
internalSkuCode: '',
internalSkuName: '',
externalSkuCode: '',
@@ -148,10 +117,9 @@ function mapEditableItem(item: AdminKuaishouCloudFulfillmentItem): EditableItem
id: item.id || crypto.randomUUID(),
enabled: item.enabled !== false,
priority: item.priority || 100,
provider: item.provider || 'khhao',
provider: item.provider || '91kaquan',
platform: item.platform || 'kuaishou',
shopId: item.shopId || '',
khhaoShopId: item.khhaoShopId || '',
internalSkuCode: item.internalSkuCode || '',
internalSkuName: item.internalSkuName || '',
externalSkuCode: item.externalSkuCode || '',
@@ -177,10 +145,9 @@ function mapSaveItem(item: EditableItem): AdminKuaishouCloudFulfillmentItem {
id: item.id.trim() || item.internalSkuCode.trim() || item.localId,
enabled: item.enabled,
priority: Number(item.priority || 100) || 100,
provider: item.provider.trim() || 'khhao',
provider: item.provider.trim() || '91kaquan',
platform: item.platform.trim() || 'kuaishou',
shopId: item.shopId.trim(),
khhaoShopId: item.khhaoShopId.trim(),
internalSkuCode: item.internalSkuCode.trim(),
internalSkuName: item.internalSkuName.trim(),
externalSkuCode: item.externalSkuCode.trim(),
@@ -218,9 +185,8 @@ function hasMeaningfulContent(item: EditableItem) {
|| item.cloudSkuName.trim()
|| item.vnKey.trim()
|| item.shopId.trim()
|| item.khhaoShopId.trim()
|| item.notes.trim()
|| item.provider.trim() !== 'khhao'
|| item.provider.trim() !== '91kaquan'
|| item.platform.trim() !== 'kuaishou'
|| item.priority !== 100
|| item.autoBuyEnabled !== true
@@ -268,7 +234,7 @@ function getCardTitle(item: EditableItem, index: number) {
function getCardSummary(item: EditableItem) {
const parts = [
item.provider.trim() || 'khhao',
item.provider.trim() || '91kaquan',
item.platform.trim() || 'kuaishou',
item.shopId.trim() || '跨店铺',
item.cloudSkuId > 0 ? `cloud#${item.cloudSkuId}` : '待填 cloud SKU',
@@ -337,10 +303,6 @@ function getCloudSkuSummary(item: EditableItem) {
return '待选择 cloud SKU'
}
function formatAmountFen(value: number) {
return (Number(value || 0) / 100).toFixed(2)
}
function isCollapsed(localId: string) {
return collapsedIds.value.includes(localId)
}
@@ -381,18 +343,11 @@ async function loadConfigs() {
errorMessage.value = ''
try {
const [response, khhaoSourceResponse] = await Promise.all([
fetchAdminKuaishouCloudFulfillmentConfig(),
fetchAdminKhhaoSourceConfig(),
])
const response = await fetchAdminKuaishouCloudFulfillmentConfig()
filePath.value = response.data.filePath
enabled.value = response.data.source.enabled !== false
items.value = (response.data.source.items || []).map(mapEditableItem)
rebuildCollapsedState()
khhaoLookupForm.baseUrl = khhaoSourceResponse.data.source.baseUrl || 'https://admin.khhao.com'
khhaoLookupForm.username = khhaoSourceResponse.data.source.username || ''
khhaoLookupForm.password = khhaoSourceResponse.data.source.password || ''
khhaoLookupForm.maxCaptchaAttempts = khhaoSourceResponse.data.source.maxCaptchaAttempts || 3
} catch (error) {
errorMessage.value = error instanceof Error ? error.message : '读取新履约配置失败'
} finally {
@@ -415,36 +370,6 @@ function removeItem(localId: string) {
setCollapsed(localId, false)
}
function createItemFromKhhaoOrder(item: AdminKhhaoOrderPreview): EditableItem {
return {
localId: crypto.randomUUID(),
id: '',
enabled: true,
priority: 100,
provider: item.provider || 'khhao',
platform: item.platform || 'kuaishou',
shopId: item.kuaishouShopId || item.shopId || '',
khhaoShopId: item.khhaoShopId || item.shopId || '',
internalSkuCode: '',
internalSkuName: item.itemTitle || '',
externalSkuCode: item.skuCode || '',
externalItemId: item.itemId || '',
externalSkuName: item.itemTitle || '',
resolvedSkuName: item.itemTitle || '',
cloudSourceKey: 'default',
cloudSkuId: 0,
cloudSkuName: '',
vnKey: '1',
autoBuyEnabled: true,
minAssetReserve: 0,
autoReturnNumberAfterDispatch: false,
autoConsumeAfterDispatch: false,
kuaishouConsumeShopId: item.kuaishouShopId || item.shopId || '',
kuaishouConsumeShopName: item.shopName || '',
notes: `从 khhao 订单 ${item.platformOrderId} 导入`,
}
}
function createItemFromNinetyoneOrder(item: AdminNinetyoneOrderItem): EditableItem {
const productNo = String(item.productNo || '').trim()
const productName = String(item.productName || productNo).trim()
@@ -456,7 +381,6 @@ function createItemFromNinetyoneOrder(item: AdminNinetyoneOrderItem): EditableIt
provider: '91kaquan',
platform: 'kuaishou',
shopId: item.shopId || '91kaquan',
khhaoShopId: '',
internalSkuCode: '',
internalSkuName: productName,
externalSkuCode: productNo,
@@ -477,24 +401,6 @@ function createItemFromNinetyoneOrder(item: AdminNinetyoneOrderItem): EditableIt
}
}
function findExistingItemFromKhhaoOrder(order: AdminKhhaoOrderPreview) {
const orderShopId = String(order.kuaishouShopId || order.shopId || '').trim()
const orderItemId = String(order.itemId || '').trim()
const orderSkuCode = String(order.skuCode || '').trim()
const orderTitle = String(order.itemTitle || '').trim()
return items.value.find((item) => {
const sameShop = !orderShopId
|| item.shopId.trim() === orderShopId
|| item.khhaoShopId.trim() === String(order.khhaoShopId || '').trim()
|| item.kuaishouConsumeShopId.trim() === orderShopId
const sameItemId = orderItemId && item.externalItemId.trim() === orderItemId
const sameSkuCode = orderSkuCode && item.externalSkuCode.trim() === orderSkuCode
const sameTitle = orderTitle && [item.externalSkuName, item.internalSkuName, item.resolvedSkuName].some((value) => value.trim() === orderTitle)
return sameShop && (sameItemId || sameSkuCode || sameTitle)
}) || null
}
function findExistingItemFromNinetyoneOrder(order: AdminNinetyoneOrderItem) {
const productNo = String(order.productNo || '').trim()
const productName = String(order.productName || '').trim()
@@ -512,31 +418,6 @@ function findExistingItemFromNinetyoneOrder(order: AdminNinetyoneOrderItem) {
}) || null
}
async function importKhhaoProduct(item: AdminKhhaoOrderPreview) {
validationState.value = null
const importKey = getKhhaoOrderImportKey(item)
const existing = findExistingItemFromKhhaoOrder(item)
if (existing) {
setCollapsed(existing.localId, false)
if (!importedKhhaoOrderKeys.value.includes(importKey)) {
importedKhhaoOrderKeys.value = [...importedKhhaoOrderKeys.value, importKey]
}
showSuccess('已定位到现有规则草稿,直接继续完善即可')
await focusValidationTarget(existing.localId)
return
}
const next = createItemFromKhhaoOrder(item)
items.value.unshift(next)
setCollapsed(next.localId, false)
if (!importedKhhaoOrderKeys.value.includes(importKey)) {
importedKhhaoOrderKeys.value = [...importedKhhaoOrderKeys.value, importKey]
}
await focusValidationTarget(next.localId)
}
async function importNinetyoneProduct(item: AdminNinetyoneOrderItem) {
validationState.value = null
const importKey = getNinetyoneOrderImportKey(item)
@@ -562,21 +443,6 @@ async function importNinetyoneProduct(item: AdminNinetyoneOrderItem) {
await focusValidationTarget(next.localId)
}
function getKhhaoOrderImportKey(item: AdminKhhaoOrderPreview) {
return [
item.platformOrderId,
item.itemId,
item.skuCode,
item.kuaishouShopId || item.shopId,
]
.map((value) => String(value || '').trim())
.join(':')
}
function isKhhaoProductImported(item: AdminKhhaoOrderPreview) {
return importedKhhaoOrderKeys.value.includes(getKhhaoOrderImportKey(item))
}
function getNinetyoneOrderImportKey(item: AdminNinetyoneOrderItem) {
return [
item.orderNo,
@@ -768,34 +634,6 @@ async function saveConfigs() {
}
}
async function lookupKhhaoProducts() {
if (!khhaoLookupForm.username.trim() || !khhaoLookupForm.password.trim()) {
khhaoLookupErrorMessage.value = '请先填写 khhao 账号和密码'
return
}
khhaoLookupLoading.value = true
khhaoLookupErrorMessage.value = ''
try {
const response = await queryAdminKhhaoOrders({
baseUrl: khhaoLookupForm.baseUrl.trim() || 'https://admin.khhao.com',
username: khhaoLookupForm.username.trim(),
password: khhaoLookupForm.password.trim(),
page: Number(khhaoLookupForm.page || 1),
limit: Number(khhaoLookupForm.limit || 10),
maxCaptchaAttempts: Number(khhaoLookupForm.maxCaptchaAttempts || 3),
})
khhaoLookupResults.value = response.data.items
importedKhhaoOrderKeys.value = []
} catch (error) {
khhaoLookupResults.value = []
khhaoLookupErrorMessage.value = error instanceof Error ? error.message : 'khhao 订单查询失败'
} finally {
khhaoLookupLoading.value = false
}
}
async function lookupNinetyoneProducts() {
ninetyoneLookupLoading.value = true
ninetyoneLookupErrorMessage.value = ''
@@ -824,7 +662,7 @@ onMounted(loadConfigs)
<header class="panel-header">
<div class="panel-header-copy">
<h1>快手 Cloud 新履约</h1>
<p>维护 khhao 快手订单到内部 SKUcloud SKU虚拟号与后续动作的映射规则</p>
<p>维护 91卡券快手订单到内部 SKUcloud SKU虚拟号与后续动作的映射规则</p>
</div>
<div class="header-tags">
<span class="header-tag">独立新流程</span>
@@ -874,7 +712,6 @@ onMounted(loadConfigs)
</div>
<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>
@@ -885,112 +722,6 @@ onMounted(loadConfigs)
<div v-if="loading" class="empty-block">新履约配置加载中</div>
<template v-else>
<section class="table-card table-card--dense">
<div class="section-title-row section-title-row--tight">
<div>
<h3>khhao 订单取样导入</h3>
<p>先查快手订单样本再导入到下面的规则草稿里继续补齐内部 SKU cloud 资源</p>
</div>
<div class="section-mini-stats">
<span class="mini-stat-chip">结果 {{ khhaoLookupMetrics.total }}</span>
<span class="mini-stat-chip">店铺 {{ khhaoLookupMetrics.shopCount }}</span>
<span class="mini-stat-chip">待导入 {{ khhaoLookupMetrics.availableCount }}</span>
</div>
</div>
<div class="khhao-toolbar">
<label class="field-block field-wide">
<span>Base URL</span>
<input v-model="khhaoLookupForm.baseUrl" class="text-input" placeholder="https://admin.khhao.com" />
</label>
<label class="field-block">
<span>账号</span>
<input v-model="khhaoLookupForm.username" class="text-input" placeholder="khhao 登录账号" />
</label>
<label class="field-block">
<span>密码</span>
<input v-model="khhaoLookupForm.password" class="text-input" placeholder="khhao 登录密码" type="password" />
</label>
<label class="field-block">
<span>页码</span>
<input v-model.number="khhaoLookupForm.page" class="text-input" min="1" type="number" />
</label>
<label class="field-block">
<span>每页条数</span>
<input v-model.number="khhaoLookupForm.limit" class="text-input" max="50" min="1" type="number" />
</label>
<label class="field-block">
<span>验证码重试</span>
<input v-model.number="khhaoLookupForm.maxCaptchaAttempts" class="text-input" max="5" min="1" type="number" />
</label>
<div class="field-block field-block--action">
<span>操作</span>
<el-button :loading="khhaoLookupLoading" round type="primary" @click="lookupKhhaoProducts">查询 khhao 订单</el-button>
</div>
</div>
<p class="toolbar-hint">查询结果只用于补配置不会写入订单库也不会影响现有订单数据</p>
<p v-if="khhaoLookupErrorMessage" class="error-copy lookup-error">{{ khhaoLookupErrorMessage }}</p>
<div v-else-if="khhaoLookupLoading" class="empty-inline">正在查询 khhao 订单</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 khhaoLookupResults" :key="getKhhaoOrderImportKey(item)">
<td>
<div class="cell-stack">
<strong>{{ item.platformOrderId || '-' }}</strong>
<span class="cell-subtle">{{ item.platformLabel || item.platform || 'kuaishou' }}</span>
</div>
</td>
<td>
<div class="cell-stack">
<strong>{{ item.shopName || item.shopId || '-' }}</strong>
<span class="cell-subtle">khhao ID: {{ item.khhaoShopId || item.shopId || '-' }}</span>
<span class="cell-subtle">快手 ID: {{ item.kuaishouShopId || '-' }}</span>
</div>
</td>
<td>
<div class="cell-stack">
<strong>{{ item.itemTitle || '-' }}</strong>
<span class="cell-subtle">SKU: {{ item.skuCode || '-' }}</span>
<span class="cell-subtle">ItemId: {{ item.itemId || '-' }}</span>
</div>
</td>
<td>
<div class="cell-stack">
<strong>{{ formatAmountFen(item.totalAmountFen) }}</strong>
<span class="cell-subtle">{{ item.statusLabel || item.status || '-' }}</span>
</div>
</td>
<td>
<el-button
v-if="!isKhhaoProductImported(item)"
link
type="primary"
@click="importKhhaoProduct(item)"
>
导入到规则
</el-button>
<span v-else class="cell-subtle">已导入草稿</span>
</td>
</tr>
<tr v-if="khhaoLookupResults.length === 0">
<td colspan="5" class="empty-inline">还没有 khhao 查询结果</td>
</tr>
</tbody>
</table>
</section>
<section class="table-card table-card--dense">
<div class="section-title-row section-title-row--tight">
<div>
@@ -1004,7 +735,7 @@ onMounted(loadConfigs)
</div>
</div>
<div class="khhao-toolbar">
<div class="lookup-toolbar">
<label class="field-block">
<span>订单状态</span>
<select v-model="ninetyoneLookupForm.status" class="text-input">
@@ -1159,7 +890,7 @@ onMounted(loadConfigs)
<div class="mapping-grid">
<label class="field-block">
<span>来源 provider</span>
<input v-model="item.provider" class="text-input" maxlength="32" placeholder="khhao / 91kaquan" />
<input v-model="item.provider" class="text-input" maxlength="32" placeholder="默认 91kaquan" />
</label>
<label class="field-block">
<span>来源 platform</span>
@@ -1169,13 +900,9 @@ onMounted(loadConfigs)
<span>店铺 ID</span>
<input v-model="item.shopId" class="text-input" maxlength="80" placeholder="留空表示跨店铺共用" />
</label>
<label class="field-block">
<span>khhao 店铺 ID</span>
<input v-model="item.khhaoShopId" class="text-input" maxlength="80" placeholder="仅 khhao 使用,例如 10" />
</label>
<label class="field-block">
<span>外部 SKU</span>
<input v-model="item.externalSkuCode" class="text-input" maxlength="120" placeholder="例如 khhao 的 skuCode" />
<input v-model="item.externalSkuCode" class="text-input" maxlength="120" placeholder="例如 91卡券 productNo" />
</label>
<label class="field-block">
<span>外部商品 ID</span>
@@ -2102,9 +1829,9 @@ onMounted(loadConfigs)
align-items: center;
}
.khhao-toolbar {
.lookup-toolbar {
display: grid;
grid-template-columns: minmax(220px, 1.6fr) minmax(140px, 1fr) minmax(140px, 1fr) 90px 110px 120px 150px;
grid-template-columns: minmax(180px, 1fr) 110px 130px 180px;
gap: 12px;
align-items: end;
}
@@ -2407,7 +2134,7 @@ onMounted(loadConfigs)
@media (max-width: 1100px) {
.overview-stats,
.khhao-toolbar,
.lookup-toolbar,
.binding-grid,
.mapping-editor {
grid-template-columns: repeat(2, minmax(0, 1fr));
@@ -2433,7 +2160,7 @@ onMounted(loadConfigs)
}
.overview-stats,
.khhao-toolbar,
.lookup-toolbar,
.binding-grid,
.mapping-editor,
.mapping-grid,
@@ -3,19 +3,16 @@ 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 {
fetchAdminAgisoShopConfigs,
fetchAdminCloudtentaclesSourceConfig,
fetchAdminKhhaoSourceConfig,
fetchAdminKuaishouEticketSourceConfig,
} from '@/services/admin'
import { hasAdminRole } from '@/utils/admin-auth'
@@ -45,27 +42,6 @@ const {
saveAgisoConfigs: persistAgisoConfigs,
} = useAdminAgisoPlatform()
const {
khhaoFilePath,
khhaoStateFilePath,
khhaoForm,
khhaoSaving,
khhaoTesting,
khhaoQuerying,
khhaoSyncing,
khhaoResultError,
khhaoLoginResult,
khhaoQueryResult,
khhaoSyncResult,
khhaoAutoSyncState,
khhaoStats,
hydrateKhhaoConfig,
handleKhhaoSaveSource,
handleKhhaoTestLogin,
handleKhhaoQueryOrders,
handleKhhaoSyncOrders,
} = useAdminKhhaoPlatform()
const {
ninetyoneLoading,
ninetyoneActionLoadingId,
@@ -156,15 +132,13 @@ async function loadConfigs() {
errorMessage.value = ''
try {
const [agisoResponse, khhaoResponse, kuaishouEticketResponse, cloudtentaclesResponse] = await Promise.all([
const [agisoResponse, kuaishouEticketResponse, cloudtentaclesResponse] = await Promise.all([
fetchAdminAgisoShopConfigs(),
fetchAdminKhhaoSourceConfig(),
fetchAdminKuaishouEticketSourceConfig(),
fetchAdminCloudtentaclesSourceConfig(),
])
hydrateAgisoConfig(agisoResponse.data)
hydrateKhhaoConfig(khhaoResponse.data)
hydrateKuaishouEticketConfig(kuaishouEticketResponse.data)
hydrateCloudtentaclesConfig(cloudtentaclesResponse.data)
await loadNinetyoneOrders(1)
@@ -191,7 +165,7 @@ onMounted(loadConfigs)
<header class="hero-card">
<div class="hero-copy">
<h1>平台配置</h1>
<p>按平台分别管理来源接入凭据与店铺能力Agiso 负责 webhook / 店铺消息配置khhao 负责来源拉单快手小店核销负责独立券核销调试cloudtentacles 负责外部履约平台登录</p>
<p>按平台分别管理来源接入凭据与店铺能力Agiso 负责 webhook / 店铺消息配置91卡券负责新订单来源快手小店核销负责独立券核销调试cloudtentacles 负责外部履约平台登录</p>
</div>
<div class="hero-actions">
<el-button round @click="loadConfigs">刷新全部配置</el-button>
@@ -241,23 +215,6 @@ onMounted(loadConfigs)
</div>
</button>
<button
type="button"
:class="['platform-overview-card', { 'is-active': activePlatform === 'khhao' }]"
@click="switchPlatform('khhao')"
>
<div class="platform-head">
<span class="platform-badge">khhao</span>
<span class="platform-tag">账号 / 拉单</span>
</div>
<strong>{{ khhaoStats.hasCredential ? '已配置' : '未配置' }}</strong>
<span>来源凭据</span>
<div class="platform-metrics">
<span>最近查询 {{ khhaoStats.queryCount }} </span>
<span>最近同步成功 {{ khhaoStats.syncedCount }} </span>
</div>
</button>
<button
type="button"
:class="['platform-overview-card', { 'is-active': activePlatform === 'kuaishouEticket' }]"
@@ -302,13 +259,6 @@ onMounted(loadConfigs)
>
Agiso 店铺与消息
</button>
<button
type="button"
:class="['switch-chip', { 'is-active': activePlatform === 'khhao' }]"
@click="switchPlatform('khhao')"
>
khhao 来源与同步
</button>
<button
type="button"
:class="['switch-chip', { 'is-active': activePlatform === 'ninetyone' }]"
@@ -350,26 +300,6 @@ onMounted(loadConfigs)
:save-agiso-configs="saveAgisoConfigs"
/>
<AdminPlatformKhhaoSection
v-else-if="activePlatform === 'khhao'"
:khhao-file-path="khhaoFilePath"
:khhao-state-file-path="khhaoStateFilePath"
:khhao-form="khhaoForm"
:khhao-saving="khhaoSaving"
:khhao-testing="khhaoTesting"
:khhao-querying="khhaoQuerying"
:khhao-syncing="khhaoSyncing"
:khhao-result-error="khhaoResultError"
:khhao-login-result="khhaoLoginResult"
:khhao-query-result="khhaoQueryResult"
:khhao-sync-result="khhaoSyncResult"
:khhao-auto-sync-state="khhaoAutoSyncState"
:handle-khhao-save-source="handleKhhaoSaveSource"
:handle-khhao-test-login="handleKhhaoTestLogin"
:handle-khhao-query-orders="handleKhhaoQueryOrders"
:handle-khhao-sync-orders="handleKhhaoSyncOrders"
/>
<AdminPlatformNinetyoneSection
v-else-if="activePlatform === 'ninetyone'"
:ninetyone-loading="ninetyoneLoading"