feat(快手 cloud 履约): 三项前端改造 — cloudSourceKey 下拉选择、备选账号多选、SKU 按 sourceKey 缓存
需求1: cloudSourceKey 从文本输入改为下拉选择(选项来自 cloudtentacles sources 列表)
- Type: AdminKuaishouCloudFulfillmentItem 新增 cloudSourceKeyFallbacks?: string[]
- RuleCard.vue: cloudSourceKey 从 el-input 改为 el-select,选项来自 cloudtentaclesSourceOptions prop
- RuleSection.vue: 传递 cloudtentaclesSourceOptions prop 到 RuleCard
- View: 传递 config.cloudtentaclesSourceOptions 到 RuleSection
需求2: 同一 SKU 支持多个 cloudtentacles 账号(备选退避)
- RuleCard.vue: 新增备选 cloud 账号多选 el-select(multiple),过滤掉已选主账号
- useKuaishouCloudConfig.ts: createEmptyItem/mapEditableItem/mapSaveItem 处理 cloudSourceKeyFallbacks 字段
需求3: 选择 cloudSkuId 后自动补全 + sourceKey 感知的 SKU 查询
- useKuaishouCloudSku.ts: cloudSkuCatalog 从全局单一缓存改为按 sourceKey 缓存(cloudSkuCatalogBySourceKey)
- ensureCloudSkuCatalogLoaded(item) 传入 item 取其 sourceKey,fetchAdminCloudtentaclesSkuList({ sourceKey })
- getCloudSkuOptions/handleCloudSkuSelected/handleCloudSkuDropdownVisible 均按 sourceKey 取缓存的 catalog
- refreshCloudSkuCatalog(item?) 支持按 item 的 sourceKey 刷新或全部刷新
额外: useKuaishouCloudConfig 加载时额外调用 fetchAdminCloudtentaclesSourceConfig 获取 sources 列表
This commit is contained in:
+1
@@ -45,6 +45,7 @@ onMounted(config.loadConfigs)
|
||||
<AdminKuaishouCloudRuleSection
|
||||
:enabled="config.enabled.value"
|
||||
:saving="config.saving.value"
|
||||
:cloudtentacles-source-options="config.cloudtentaclesSourceOptions.value"
|
||||
:cloud-sku-catalog-loading="cloudSku.cloudSkuCatalogLoading.value"
|
||||
:validation-state="config.validationState.value"
|
||||
:items="config.items.value"
|
||||
|
||||
+30
-5
@@ -7,6 +7,7 @@ defineProps<{
|
||||
item: EditableItem
|
||||
index: number
|
||||
validationState: ValidationState
|
||||
cloudtentaclesSourceOptions: { key: string; label: string }[]
|
||||
cloudSkuCatalogErrorMessage: string
|
||||
cloudSkuCatalogLoading: boolean
|
||||
isCollapsed: boolean
|
||||
@@ -177,13 +178,37 @@ const emit = defineEmits<{
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>cloudSourceKey</span>
|
||||
<el-input
|
||||
<span>cloud 主账号</span>
|
||||
<el-select
|
||||
v-model="item.cloudSourceKey"
|
||||
class="text-input"
|
||||
maxlength="60"
|
||||
placeholder="默认 default"
|
||||
/>
|
||||
placeholder="选择 cloud 账号"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in cloudtentaclesSourceOptions"
|
||||
:key="opt.key"
|
||||
:label="opt.label || opt.key"
|
||||
:value="opt.key"
|
||||
/>
|
||||
</el-select>
|
||||
</label>
|
||||
<label class="field-block field-wide">
|
||||
<span>备选 cloud 账号</span>
|
||||
<el-select
|
||||
v-model="item.cloudSourceKeyFallbacks"
|
||||
class="text-input"
|
||||
multiple
|
||||
placeholder="主账号不可用时自动尝试备选"
|
||||
>
|
||||
<el-option
|
||||
v-for="opt in cloudtentaclesSourceOptions.filter(
|
||||
(o) => o.key !== item.cloudSourceKey,
|
||||
)"
|
||||
:key="opt.key"
|
||||
:label="opt.label || opt.key"
|
||||
:value="opt.key"
|
||||
/>
|
||||
</el-select>
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>cloud SKU ID</span>
|
||||
|
||||
+2
@@ -8,6 +8,7 @@ import AdminKuaishouCloudRuleCard from './AdminKuaishouCloudRuleCard.vue'
|
||||
defineProps<{
|
||||
enabled: boolean
|
||||
saving: boolean
|
||||
cloudtentaclesSourceOptions: { key: string; label: string }[]
|
||||
cloudSkuCatalogLoading: boolean
|
||||
validationState: ValidationState
|
||||
items: EditableItem[]
|
||||
@@ -94,6 +95,7 @@ const emit = defineEmits<{
|
||||
:item="item"
|
||||
:index="index"
|
||||
:validation-state="validationState"
|
||||
:cloudtentacles-source-options="cloudtentaclesSourceOptions"
|
||||
:cloud-sku-catalog-error-message="cloudSkuCatalogErrorMessage"
|
||||
:cloud-sku-catalog-loading="cloudSkuCatalogLoading"
|
||||
:is-collapsed="isCollapsed(item.localId)"
|
||||
|
||||
+47
-26
@@ -4,6 +4,7 @@ import { showError, showSuccess } from '@/lib/feedback'
|
||||
import {
|
||||
fetchAdminKuaishouCloudFulfillmentConfig,
|
||||
fetchAdminKuaishouEticketSourceConfig,
|
||||
fetchAdminCloudtentaclesSourceConfig,
|
||||
saveAdminKuaishouCloudFulfillmentConfig,
|
||||
} from '@/services/admin'
|
||||
import type {
|
||||
@@ -26,6 +27,7 @@ export function useKuaishouCloudConfig() {
|
||||
const collapsedIds = ref<string[]>([])
|
||||
const ruleFilter = ref<RuleFilter>('all')
|
||||
const kuaishouConsumeShops = ref<AdminKuaishouEticketShopConfigItem[]>([])
|
||||
const cloudtentaclesSourceOptions = ref<{ key: string; label: string }[]>([])
|
||||
|
||||
// ── computed ──────────────────────────────────────────
|
||||
|
||||
@@ -84,6 +86,7 @@ export function useKuaishouCloudConfig() {
|
||||
kuaishouConsumeShopId: defaultShop?.shopId || '',
|
||||
kuaishouConsumeShopName: defaultShop?.kshopName || '',
|
||||
notes: '',
|
||||
cloudSourceKeyFallbacks: [],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +120,9 @@ export function useKuaishouCloudConfig() {
|
||||
kuaishouConsumeShopId: matchedShop?.shopId || item.kuaishouConsumeShopId || '',
|
||||
kuaishouConsumeShopName: matchedShop?.kshopName || item.kuaishouConsumeShopName || '',
|
||||
notes: item.notes || '',
|
||||
cloudSourceKeyFallbacks: Array.isArray(item.cloudSourceKeyFallbacks)
|
||||
? item.cloudSourceKeyFallbacks
|
||||
: [],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,8 +153,13 @@ export function useKuaishouCloudConfig() {
|
||||
autoReturnNumberAfterDispatch: item.autoReturnNumberAfterDispatch,
|
||||
autoConsumeAfterDispatch: item.autoConsumeAfterDispatch,
|
||||
kuaishouConsumeShopId: String(matchedShop?.shopId || item.kuaishouConsumeShopId).trim(),
|
||||
kuaishouConsumeShopName: String(matchedShop?.kshopName || item.kuaishouConsumeShopName).trim(),
|
||||
kuaishouConsumeShopName: String(
|
||||
matchedShop?.kshopName || item.kuaishouConsumeShopName,
|
||||
).trim(),
|
||||
notes: item.notes.trim(),
|
||||
cloudSourceKeyFallbacks: Array.isArray(item.cloudSourceKeyFallbacks)
|
||||
? item.cloudSourceKeyFallbacks.filter(Boolean)
|
||||
: [],
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,36 +176,36 @@ export function useKuaishouCloudConfig() {
|
||||
function hasMeaningfulContent(item: EditableItem) {
|
||||
return Boolean(
|
||||
item.internalSkuCode.trim() ||
|
||||
item.internalSkuName.trim() ||
|
||||
item.externalSkuCode.trim() ||
|
||||
item.externalItemId.trim() ||
|
||||
item.externalSkuName.trim() ||
|
||||
item.resolvedSkuName.trim() ||
|
||||
item.cloudSourceKey.trim() !== 'default' ||
|
||||
Number(item.cloudSkuId || 0) > 0 ||
|
||||
item.cloudSkuName.trim() ||
|
||||
item.vnKey.trim() ||
|
||||
item.shopId.trim() ||
|
||||
item.notes.trim() ||
|
||||
item.provider.trim() !== '91kaquan' ||
|
||||
item.platform.trim() !== 'kuaishou' ||
|
||||
item.priority !== 100 ||
|
||||
item.autoBuyEnabled !== true ||
|
||||
item.minAssetReserve !== 0 ||
|
||||
item.autoReturnNumberAfterDispatch ||
|
||||
item.autoConsumeAfterDispatch ||
|
||||
item.kuaishouConsumeShopId.trim() ||
|
||||
item.kuaishouConsumeShopName.trim() ||
|
||||
item.enabled !== true,
|
||||
item.internalSkuName.trim() ||
|
||||
item.externalSkuCode.trim() ||
|
||||
item.externalItemId.trim() ||
|
||||
item.externalSkuName.trim() ||
|
||||
item.resolvedSkuName.trim() ||
|
||||
item.cloudSourceKey.trim() !== 'default' ||
|
||||
Number(item.cloudSkuId || 0) > 0 ||
|
||||
item.cloudSkuName.trim() ||
|
||||
item.vnKey.trim() ||
|
||||
item.shopId.trim() ||
|
||||
item.notes.trim() ||
|
||||
item.provider.trim() !== '91kaquan' ||
|
||||
item.platform.trim() !== 'kuaishou' ||
|
||||
item.priority !== 100 ||
|
||||
item.autoBuyEnabled !== true ||
|
||||
item.minAssetReserve !== 0 ||
|
||||
item.autoReturnNumberAfterDispatch ||
|
||||
item.autoConsumeAfterDispatch ||
|
||||
item.kuaishouConsumeShopId.trim() ||
|
||||
item.kuaishouConsumeShopName.trim() ||
|
||||
item.enabled !== true,
|
||||
)
|
||||
}
|
||||
|
||||
function isItemComplete(item: EditableItem) {
|
||||
return Boolean(
|
||||
item.internalSkuCode.trim() &&
|
||||
Number(item.cloudSkuId || 0) > 0 &&
|
||||
(!item.autoConsumeAfterDispatch || Boolean(item.kuaishouConsumeShopId.trim())) &&
|
||||
hasExternalMatch(item),
|
||||
Number(item.cloudSkuId || 0) > 0 &&
|
||||
(!item.autoConsumeAfterDispatch || Boolean(item.kuaishouConsumeShopId.trim())) &&
|
||||
hasExternalMatch(item),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -387,13 +398,21 @@ export function useKuaishouCloudConfig() {
|
||||
errorMessage.value = ''
|
||||
|
||||
try {
|
||||
const [response, eticketResponse] = await Promise.all([
|
||||
const [response, eticketResponse, cloudtentaclesSourceResponse] = await Promise.all([
|
||||
fetchAdminKuaishouCloudFulfillmentConfig(),
|
||||
fetchAdminKuaishouEticketSourceConfig(),
|
||||
fetchAdminCloudtentaclesSourceConfig(),
|
||||
])
|
||||
kuaishouConsumeShops.value = Array.isArray(eticketResponse.data.source.shops)
|
||||
? eticketResponse.data.source.shops
|
||||
: []
|
||||
const sources = Array.isArray(cloudtentaclesSourceResponse.data.sources)
|
||||
? cloudtentaclesSourceResponse.data.sources
|
||||
: []
|
||||
cloudtentaclesSourceOptions.value = sources.map((s) => ({
|
||||
key: s.key,
|
||||
label: s.label || s.key,
|
||||
}))
|
||||
filePath.value = response.data.filePath
|
||||
enabled.value = response.data.source.enabled !== false
|
||||
items.value = (response.data.source.items || []).map(mapEditableItem)
|
||||
@@ -523,5 +542,7 @@ export function useKuaishouCloudConfig() {
|
||||
removeItem,
|
||||
saveConfigs,
|
||||
focusValidationTarget,
|
||||
// cloudtentacles sources
|
||||
cloudtentaclesSourceOptions,
|
||||
}
|
||||
}
|
||||
|
||||
+49
-19
@@ -7,29 +7,34 @@ import type { AdminCloudtentaclesSkuItem } from '@/types/admin'
|
||||
import type { EditableItem } from './types'
|
||||
|
||||
export function useKuaishouCloudSku() {
|
||||
const cloudSkuCatalogLoading = ref(false)
|
||||
const cloudSkuCatalogLoadingBySourceKey = ref<Record<string, boolean>>({})
|
||||
const cloudSkuCatalogErrorMessage = ref('')
|
||||
const cloudSkuCatalog = ref<AdminCloudtentaclesSkuItem[]>([])
|
||||
const cloudSkuCatalogBySourceKey = ref<Record<string, AdminCloudtentaclesSkuItem[]>>({})
|
||||
|
||||
async function ensureCloudSkuCatalogLoaded(force = false) {
|
||||
if (!force && cloudSkuCatalog.value.length > 0) {
|
||||
return cloudSkuCatalog.value
|
||||
function getSourceKey(item: EditableItem) {
|
||||
return String(item.cloudSourceKey || 'default').trim()
|
||||
}
|
||||
|
||||
async function ensureCloudSkuCatalogLoaded(item: EditableItem, force = false) {
|
||||
const sourceKey = getSourceKey(item)
|
||||
if (!force && (cloudSkuCatalogBySourceKey.value[sourceKey]?.length ?? 0) > 0) {
|
||||
return cloudSkuCatalogBySourceKey.value[sourceKey]
|
||||
}
|
||||
|
||||
cloudSkuCatalogLoading.value = true
|
||||
cloudSkuCatalogLoadingBySourceKey.value[sourceKey] = true
|
||||
cloudSkuCatalogErrorMessage.value = ''
|
||||
|
||||
try {
|
||||
const response = await fetchAdminCloudtentaclesSkuList({})
|
||||
cloudSkuCatalog.value = Array.isArray(response.data.items) ? response.data.items : []
|
||||
return cloudSkuCatalog.value
|
||||
const response = await fetchAdminCloudtentaclesSkuList({ sourceKey })
|
||||
cloudSkuCatalogBySourceKey.value[sourceKey] = Array.isArray(response.data.items) ? response.data.items : []
|
||||
return cloudSkuCatalogBySourceKey.value[sourceKey]
|
||||
} catch (error) {
|
||||
cloudSkuCatalog.value = []
|
||||
cloudSkuCatalogBySourceKey.value[sourceKey] = []
|
||||
cloudSkuCatalogErrorMessage.value =
|
||||
error instanceof Error ? error.message : 'cloud SKU 列表查询失败'
|
||||
throw error
|
||||
} finally {
|
||||
cloudSkuCatalogLoading.value = false
|
||||
cloudSkuCatalogLoadingBySourceKey.value[sourceKey] = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,8 +81,10 @@ export function useKuaishouCloudSku() {
|
||||
}
|
||||
|
||||
function getCloudSkuOptions(item: EditableItem) {
|
||||
const sourceKey = getSourceKey(item)
|
||||
const catalog = cloudSkuCatalogBySourceKey.value[sourceKey] || []
|
||||
const keyword = getCloudSkuKeyword(item)
|
||||
const scored = cloudSkuCatalog.value
|
||||
const scored = catalog
|
||||
.map((sku) => ({ sku, score: scoreCloudSkuMatch(sku, keyword) }))
|
||||
.filter((entry) => entry.score > 0 || entry.sku.id === Number(item.cloudSkuId || 0))
|
||||
.sort((left, right) => {
|
||||
@@ -93,7 +100,7 @@ export function useKuaishouCloudSku() {
|
||||
return scored.slice(0, 80)
|
||||
}
|
||||
|
||||
return cloudSkuCatalog.value.slice(0, 80)
|
||||
return catalog.slice(0, 80)
|
||||
}
|
||||
|
||||
function formatCloudSkuOptionLabel(item: AdminCloudtentaclesSkuItem) {
|
||||
@@ -111,7 +118,9 @@ export function useKuaishouCloudSku() {
|
||||
return
|
||||
}
|
||||
|
||||
const matched = cloudSkuCatalog.value.find((sku) => sku.id === item.cloudSkuId)
|
||||
const sourceKey = getSourceKey(item)
|
||||
const catalog = cloudSkuCatalogBySourceKey.value[sourceKey] || []
|
||||
const matched = catalog.find((sku) => sku.id === item.cloudSkuId)
|
||||
item.cloudSkuName = matched?.name || item.cloudSkuName || ''
|
||||
}
|
||||
|
||||
@@ -121,7 +130,7 @@ export function useKuaishouCloudSku() {
|
||||
}
|
||||
|
||||
try {
|
||||
await ensureCloudSkuCatalogLoaded()
|
||||
await ensureCloudSkuCatalogLoaded(item)
|
||||
if (item.cloudSkuId && !item.cloudSkuName) {
|
||||
handleCloudSkuSelected(item, item.cloudSkuId)
|
||||
}
|
||||
@@ -130,23 +139,44 @@ export function useKuaishouCloudSku() {
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshCloudSkuCatalog() {
|
||||
async function refreshCloudSkuCatalog(item?: EditableItem) {
|
||||
try {
|
||||
await ensureCloudSkuCatalogLoaded(true)
|
||||
showSuccess(`cloud SKU 列表已刷新,共 ${cloudSkuCatalog.value.length} 条`)
|
||||
if (item) {
|
||||
await ensureCloudSkuCatalogLoaded(item, true)
|
||||
const sourceKey = getSourceKey(item)
|
||||
const count = cloudSkuCatalogBySourceKey.value[sourceKey]?.length ?? 0
|
||||
showSuccess(`cloud SKU 列表(${sourceKey})已刷新,共 ${count} 条`)
|
||||
} else {
|
||||
// No item provided — refresh all loaded caches
|
||||
const keys = Object.keys(cloudSkuCatalogBySourceKey.value)
|
||||
for (const key of keys) {
|
||||
await ensureCloudSkuCatalogLoaded({ cloudSourceKey: key, localId: '' } as EditableItem, true)
|
||||
}
|
||||
const total = Object.values(cloudSkuCatalogBySourceKey.value).reduce((sum, list) => sum + list.length, 0)
|
||||
showSuccess(`cloud SKU 列表已全部刷新,共 ${total} 条`)
|
||||
}
|
||||
} catch (error) {
|
||||
showError(error instanceof Error ? error.message : 'cloud SKU 列表查询失败')
|
||||
}
|
||||
}
|
||||
|
||||
const cloudSkuCatalogLoading = ref(false)
|
||||
// Maintain backward-compat: derive a combined loading state
|
||||
// The template uses cloudSkuCatalogLoading for the refresh button spinner
|
||||
function syncCloudSkuCatalogLoading() {
|
||||
cloudSkuCatalogLoading.value = Object.values(cloudSkuCatalogLoadingBySourceKey.value).some(Boolean)
|
||||
}
|
||||
|
||||
return {
|
||||
cloudSkuCatalogLoading,
|
||||
cloudSkuCatalogLoadingBySourceKey,
|
||||
cloudSkuCatalogErrorMessage,
|
||||
cloudSkuCatalog,
|
||||
cloudSkuCatalogBySourceKey,
|
||||
getCloudSkuOptions,
|
||||
formatCloudSkuOptionLabel,
|
||||
handleCloudSkuSelected,
|
||||
handleCloudSkuDropdownVisible,
|
||||
refreshCloudSkuCatalog,
|
||||
syncCloudSkuCatalogLoading,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user