移除了 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
-1
View File
@@ -14,7 +14,6 @@ declare module 'vue' {
AdminPaginationBar: typeof import('./components/admin/AdminPaginationBar.vue')['default']
AdminPlatformAgisoSection: typeof import('./components/admin/AdminPlatformAgisoSection.vue')['default']
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']
@@ -31,7 +31,7 @@ defineProps<Props>()
</div>
<div class="meta-line">
<span class="meta-label">职责</span>
<span>管理 Agiso 默认消息模板店铺 AccessToken 以及店铺级模板覆盖该区域不处理 khhao 登录与拉单</span>
<span>管理 Agiso 默认消息模板店铺 AccessToken 以及店铺级模板覆盖</span>
</div>
</section>
@@ -1,264 +0,0 @@
<script setup lang="ts">
import AdminResultCard from '@/components/admin/AdminResultCard.vue'
import type {
AdminKhhaoLoginTestResult,
AdminKhhaoOrderQueryResult,
AdminKhhaoOrderSyncResult,
AdminKhhaoSyncState,
} from '@/types/admin'
import { formatAdminDateTime } from '@/utils/admin-time'
type KhhaoForm = {
baseUrl: string
username: string
password: string
page: number
limit: number
maxCaptchaAttempts: number
autoSyncEnabled: boolean
autoSyncIntervalMinutes: number
autoSyncPages: number
autoSyncPageSize: number
}
type Props = {
khhaoFilePath: string
khhaoStateFilePath: string
khhaoForm: KhhaoForm
khhaoSaving: boolean
khhaoTesting: boolean
khhaoQuerying: boolean
khhaoSyncing: boolean
khhaoResultError: string
khhaoLoginResult: AdminKhhaoLoginTestResult | null
khhaoQueryResult: AdminKhhaoOrderQueryResult | null
khhaoSyncResult: AdminKhhaoOrderSyncResult | null
khhaoAutoSyncState: AdminKhhaoSyncState | null
handleKhhaoSaveSource: () => void | Promise<void>
handleKhhaoTestLogin: () => void | Promise<void>
handleKhhaoQueryOrders: () => void | Promise<void>
handleKhhaoSyncOrders: () => void | Promise<void>
}
defineProps<Props>()
</script>
<template>
<section class="meta-card">
<div class="meta-line">
<span class="meta-label">配置文件</span>
<code>{{ khhaoFilePath || '-' }}</code>
</div>
<div class="meta-line">
<span class="meta-label">状态文件</span>
<code>{{ khhaoStateFilePath || '-' }}</code>
</div>
<div class="meta-line">
<span class="meta-label">职责</span>
<span>管理 khhao 的来源地址登录账号密码与验证码重试次数并执行测试登录查询订单与手动同步</span>
</div>
</section>
<section class="table-card">
<div class="section-title-row">
<div>
<h3>khhao 来源配置</h3>
<p>先保存 khhao 凭据再按需执行登录测试订单查询或同步履约规则请前往履约配置页处理</p>
</div>
<div class="section-actions">
<el-button :loading="khhaoSaving" round @click="handleKhhaoSaveSource">保存来源配置</el-button>
<el-button :loading="khhaoTesting" round @click="handleKhhaoTestLogin">测试登录</el-button>
<el-button :loading="khhaoQuerying" round @click="handleKhhaoQueryOrders">查询订单</el-button>
<el-button :loading="khhaoSyncing" round type="primary" @click="handleKhhaoSyncOrders">同步订单</el-button>
</div>
</div>
<div class="form-grid">
<label class="field-block field-wide">
<span>Base URL</span>
<input v-model="khhaoForm.baseUrl" class="text-input" placeholder="https://admin.khhao.com" />
</label>
<label class="field-block">
<span>账号</span>
<input v-model="khhaoForm.username" class="text-input" placeholder="khhao 登录账号" />
</label>
<label class="field-block">
<span>密码</span>
<input v-model="khhaoForm.password" class="text-input" type="password" placeholder="khhao 登录密码" />
</label>
<label class="field-block">
<span>页码</span>
<input v-model.number="khhaoForm.page" class="text-input" type="number" min="1" />
</label>
<label class="field-block">
<span>每页条数</span>
<input v-model.number="khhaoForm.limit" class="text-input" type="number" min="1" max="50" />
</label>
<label class="field-block">
<span>验证码重试次数</span>
<input v-model.number="khhaoForm.maxCaptchaAttempts" class="text-input" type="number" min="1" max="5" />
</label>
<label class="field-block">
<span>自动同步开关</span>
<label class="checkbox-line checkbox-box">
<input v-model="khhaoForm.autoSyncEnabled" type="checkbox" />
<span>启用自动同步</span>
</label>
</label>
<label class="field-block">
<span>同步间隔分钟</span>
<input v-model.number="khhaoForm.autoSyncIntervalMinutes" class="text-input" type="number" min="1" max="60" />
</label>
<label class="field-block">
<span>同步页数</span>
<input v-model.number="khhaoForm.autoSyncPages" class="text-input" type="number" min="1" max="5" />
</label>
<label class="field-block">
<span>每页条数</span>
<input v-model.number="khhaoForm.autoSyncPageSize" class="text-input" type="number" min="1" max="50" />
</label>
</div>
<p v-if="khhaoResultError" class="error-copy">{{ khhaoResultError }}</p>
<div class="result-grid">
<AdminResultCard label="登录结果" :value="khhaoLoginResult ? '成功' : '未执行'">
<p v-if="khhaoLoginResult">
{{ khhaoLoginResult.attempt }} 次成功验证码识别 {{ khhaoLoginResult.captcha.recognizedText || '-' }}
{{ formatAdminDateTime(khhaoLoginResult.loggedInAt) }}
</p>
<p v-else>可先点击测试登录验证账号密码与 OCR</p>
</AdminResultCard>
<AdminResultCard label="查询结果" :value="`${khhaoQueryResult?.itemCount || 0} 条`">
<p v-if="khhaoQueryResult">当前页 {{ khhaoQueryResult.page }} / 总计 {{ khhaoQueryResult.total }} </p>
<p v-else>可先点击查询订单验证拉单链路</p>
</AdminResultCard>
<AdminResultCard label="同步结果" :value="`${khhaoSyncResult?.syncedCount || 0} 条`">
<p v-if="khhaoSyncResult">忽略 {{ khhaoSyncResult.ignoredCount }} 拉取 {{ khhaoSyncResult.fetchedCount }} </p>
<p v-else>完成履约配置后可在这里手动同步</p>
</AdminResultCard>
</div>
<div v-if="khhaoAutoSyncState" class="meta-card compact-meta">
<div class="meta-line">
<span class="meta-label">自动同步状态</span>
<span>{{ khhaoAutoSyncState.lastRunStatus || 'idle' }}</span>
</div>
<div class="meta-line">
<span class="meta-label">当前节奏</span>
<span>{{ khhaoAutoSyncState.watchMode === 'boosted' ? '提频观察中(3-5 秒)' : '常规同步' }}</span>
</div>
<div class="meta-line">
<span class="meta-label">同步基线</span>
<span>{{ formatAdminDateTime(khhaoAutoSyncState.syncFromCreatedAt) }}</span>
</div>
<div class="meta-line">
<span class="meta-label">最近开始</span>
<span>{{ formatAdminDateTime(khhaoAutoSyncState.lastRunStartedAt) }}</span>
</div>
<div class="meta-line">
<span class="meta-label">最近完成</span>
<span>{{ formatAdminDateTime(khhaoAutoSyncState.lastRunFinishedAt) }}</span>
</div>
<div class="meta-line">
<span class="meta-label">最近结果</span>
<span>拉取 {{ khhaoAutoSyncState.fetchedCount }} 成功 {{ khhaoAutoSyncState.syncedCount }} 忽略 {{ khhaoAutoSyncState.ignoredCount }} </span>
</div>
<div v-if="khhaoAutoSyncState.watchOrders.length > 0" class="meta-line">
<span class="meta-label">观察订单</span>
<span>{{ khhaoAutoSyncState.watchOrders.map((item) => `${item.shopName || item.shopId || '-'} / ${item.platformOrderId}`).join('') }}</span>
</div>
<div v-if="khhaoAutoSyncState.lastErrorMessage" class="meta-line">
<span class="meta-label">最近错误</span>
<span class="error-copy">{{ khhaoAutoSyncState.lastErrorMessage }}</span>
</div>
</div>
</section>
<section v-if="khhaoQueryResult" class="table-card">
<div class="section-title-row">
<div>
<h3>khhao 查询结果</h3>
<p>当前页 {{ khhaoQueryResult.page }}本页 {{ khhaoQueryResult.itemCount }} 总数 {{ khhaoQueryResult.total }}</p>
</div>
</div>
<table class="data-table">
<thead>
<tr>
<th>订单</th>
<th>店铺</th>
<th>商品</th>
<th>金额</th>
<th>状态</th>
</tr>
</thead>
<tbody>
<tr v-for="item in khhaoQueryResult.items" :key="`${item.platformOrderId}:${item.skuCode}:${item.itemId}`">
<td>
<div class="cell-stack">
<strong>{{ item.platformOrderId }}</strong>
<span class="cell-subtle">{{ item.platformLabel || item.platform || '-' }}</span>
</div>
</td>
<td>
<div class="cell-stack">
<strong>{{ item.shopName || item.shopId || '-' }}</strong>
<span class="cell-subtle">ID: {{ item.shopId || '-' }}</span>
</div>
</td>
<td>
<div class="cell-stack">
<strong>{{ item.itemTitle || '-' }}</strong>
<span class="cell-subtle">SKU: {{ item.skuCode || '-' }}</span>
</div>
</td>
<td>{{ (item.totalAmountFen / 100).toFixed(2) }}</td>
<td>{{ item.statusLabel || item.status || '-' }}</td>
</tr>
</tbody>
</table>
</section>
<section v-if="khhaoSyncResult" class="table-card">
<div class="section-title-row">
<div>
<h3>khhao 同步结果</h3>
<p>拉取 {{ khhaoSyncResult.fetchedCount }} 成功 {{ khhaoSyncResult.syncedCount }} 忽略 {{ khhaoSyncResult.ignoredCount }} </p>
</div>
</div>
<table class="data-table">
<thead>
<tr>
<th>订单</th>
<th>店铺</th>
<th>商品</th>
<th>结果</th>
</tr>
</thead>
<tbody>
<tr v-for="item in khhaoSyncResult.results" :key="`${item.platformOrderId}:${item.skuCode}`">
<td>{{ item.platformOrderId }}</td>
<td>{{ item.shopId || '-' }}</td>
<td>{{ item.itemTitle || item.skuCode || '-' }}</td>
<td>
<span v-if="item.ignored">已忽略{{ item.ignoreReason || '-' }}</span>
<span v-else>已入库订单 {{ item.orderId || '-' }}任务 {{ item.taskCount }}</span>
</td>
</tr>
</tbody>
</table>
</section>
</template>
@@ -1,4 +1,4 @@
export type PlatformTab = 'agiso' | 'khhao' | 'ninetyone' | 'kuaishouEticket' | 'cloudtentacles'
export type PlatformTab = 'agiso' | 'ninetyone' | 'kuaishouEticket' | 'cloudtentacles'
export type EditableDefaults = {
messageTemplate: string
@@ -1,215 +0,0 @@
import { computed, ref } from 'vue'
import { showError, showSuccess } from '@/lib/feedback'
import {
queryAdminKhhaoOrders,
saveAdminKhhaoSourceConfig,
syncAdminKhhaoOrders,
testAdminKhhaoLogin,
} from '@/services/admin'
import type {
AdminKhhaoLoginTestResult,
AdminKhhaoOrderQueryResult,
AdminKhhaoOrderSyncResult,
AdminKhhaoSyncState,
} from '@/types/admin'
export function useAdminKhhaoPlatform() {
const khhaoFilePath = ref('')
const khhaoStateFilePath = ref('')
const khhaoForm = ref({
baseUrl: 'https://admin.khhao.com',
username: '',
password: '',
page: 1,
limit: 10,
maxCaptchaAttempts: 3,
autoSyncEnabled: false,
autoSyncIntervalMinutes: 3,
autoSyncPages: 2,
autoSyncPageSize: 20,
})
const khhaoSaving = ref(false)
const khhaoTesting = ref(false)
const khhaoQuerying = ref(false)
const khhaoSyncing = ref(false)
const khhaoResultError = ref('')
const khhaoLoginResult = ref<AdminKhhaoLoginTestResult | null>(null)
const khhaoQueryResult = ref<AdminKhhaoOrderQueryResult | null>(null)
const khhaoSyncResult = ref<AdminKhhaoOrderSyncResult | null>(null)
const khhaoAutoSyncState = ref<AdminKhhaoSyncState | null>(null)
const khhaoStats = computed(() => ({
hasCredential: Boolean(khhaoForm.value.username.trim() && khhaoForm.value.password.trim()),
queryCount: khhaoQueryResult.value?.itemCount || 0,
syncedCount: khhaoAutoSyncState.value?.syncedCount || khhaoSyncResult.value?.syncedCount || 0,
ignoredCount: khhaoAutoSyncState.value?.ignoredCount || khhaoSyncResult.value?.ignoredCount || 0,
}))
function hydrateKhhaoConfig(data: {
filePath: string
stateFilePath: string
syncState: AdminKhhaoSyncState
source: {
baseUrl?: string
username?: string
password?: string
maxCaptchaAttempts?: number
autoSync?: {
enabled?: boolean
intervalMinutes?: number
pages?: number
pageSize?: number
}
}
}) {
khhaoFilePath.value = data.filePath
khhaoStateFilePath.value = data.stateFilePath
khhaoAutoSyncState.value = data.syncState
khhaoForm.value = {
baseUrl: data.source.baseUrl || 'https://admin.khhao.com',
username: data.source.username || '',
password: data.source.password || '',
page: 1,
limit: 10,
maxCaptchaAttempts: data.source.maxCaptchaAttempts || 3,
autoSyncEnabled: data.source.autoSync?.enabled === true,
autoSyncIntervalMinutes: data.source.autoSync?.intervalMinutes || 3,
autoSyncPages: data.source.autoSync?.pages || 2,
autoSyncPageSize: data.source.autoSync?.pageSize || 20,
}
}
function buildKhhaoPayload() {
return {
baseUrl: khhaoForm.value.baseUrl.trim() || 'https://admin.khhao.com',
username: khhaoForm.value.username.trim(),
password: khhaoForm.value.password.trim(),
page: Number(khhaoForm.value.page || 1),
limit: Number(khhaoForm.value.limit || 10),
maxCaptchaAttempts: Number(khhaoForm.value.maxCaptchaAttempts || 3),
}
}
function ensureKhhaoCredentials() {
if (!khhaoForm.value.username.trim() || !khhaoForm.value.password.trim()) {
khhaoResultError.value = '请先填写 khhao 账号和密码'
return false
}
return true
}
async function handleKhhaoSaveSource() {
khhaoSaving.value = true
khhaoResultError.value = ''
try {
const response = await saveAdminKhhaoSourceConfig({
enabled: true,
baseUrl: khhaoForm.value.baseUrl.trim() || 'https://admin.khhao.com',
username: khhaoForm.value.username.trim(),
password: khhaoForm.value.password.trim(),
maxCaptchaAttempts: Number(khhaoForm.value.maxCaptchaAttempts || 3),
autoSync: {
enabled: Boolean(khhaoForm.value.autoSyncEnabled),
intervalMinutes: Number(khhaoForm.value.autoSyncIntervalMinutes || 3),
pages: Number(khhaoForm.value.autoSyncPages || 2),
pageSize: Number(khhaoForm.value.autoSyncPageSize || 20),
},
})
khhaoFilePath.value = response.data.filePath
khhaoStateFilePath.value = response.data.stateFilePath
khhaoAutoSyncState.value = response.data.syncState
showSuccess('khhao 来源配置已保存')
} catch (error) {
khhaoResultError.value = error instanceof Error ? error.message : 'khhao 来源配置保存失败'
showError(khhaoResultError.value)
} finally {
khhaoSaving.value = false
}
}
async function handleKhhaoTestLogin() {
if (!ensureKhhaoCredentials()) {
return
}
khhaoTesting.value = true
khhaoResultError.value = ''
try {
const response = await testAdminKhhaoLogin({
...buildKhhaoPayload(),
includeImageBase64: false,
})
khhaoLoginResult.value = response.data
showSuccess('khhao 登录测试成功')
} catch (error) {
khhaoResultError.value = error instanceof Error ? error.message : 'khhao 登录测试失败'
showError(khhaoResultError.value)
} finally {
khhaoTesting.value = false
}
}
async function handleKhhaoQueryOrders() {
if (!ensureKhhaoCredentials()) {
return
}
khhaoQuerying.value = true
khhaoResultError.value = ''
try {
const response = await queryAdminKhhaoOrders(buildKhhaoPayload())
khhaoQueryResult.value = response.data
showSuccess(`khhao 订单查询成功,共返回 ${response.data.itemCount}`)
} catch (error) {
khhaoResultError.value = error instanceof Error ? error.message : 'khhao 订单查询失败'
showError(khhaoResultError.value)
} finally {
khhaoQuerying.value = false
}
}
async function handleKhhaoSyncOrders() {
if (!ensureKhhaoCredentials()) {
return
}
khhaoSyncing.value = true
khhaoResultError.value = ''
try {
const response = await syncAdminKhhaoOrders(buildKhhaoPayload())
khhaoSyncResult.value = response.data
showSuccess(`khhao 同步完成:拉取 ${response.data.fetchedCount} 条,成功 ${response.data.syncedCount}`)
} catch (error) {
khhaoResultError.value = error instanceof Error ? error.message : 'khhao 订单同步失败'
showError(khhaoResultError.value)
} finally {
khhaoSyncing.value = false
}
}
return {
khhaoFilePath,
khhaoStateFilePath,
khhaoForm,
khhaoSaving,
khhaoTesting,
khhaoQuerying,
khhaoSyncing,
khhaoResultError,
khhaoLoginResult,
khhaoQueryResult,
khhaoSyncResult,
khhaoAutoSyncState,
khhaoStats,
hydrateKhhaoConfig,
handleKhhaoSaveSource,
handleKhhaoTestLogin,
handleKhhaoQueryOrders,
handleKhhaoSyncOrders,
}
}
@@ -11,11 +11,6 @@ import type {
AdminCloudtentaclesValidateSessionResult,
AdminFulfillmentBindingConfigItem,
AdminFulfillmentLookupResult,
AdminKhhaoLoginTestResult,
AdminKhhaoOrderQueryResult,
AdminKhhaoOrderSyncResult,
AdminKhhaoSourceConfig,
AdminKhhaoSyncState,
AdminNinetyoneOrderActionResult,
AdminNinetyoneOrderListResult,
AdminKuaishouCloudFulfillmentConfig,
@@ -46,56 +41,6 @@ export function saveAdminAgisoShopConfigs(payload: {
}>('/api/v1/admin/platform-config/agiso-shops', payload)
}
export function fetchAdminKhhaoSourceConfig() {
return apiGet<{
filePath: string
source: AdminKhhaoSourceConfig
stateFilePath: string
syncState: AdminKhhaoSyncState
}>('/api/v1/admin/platform-config/khhao-source')
}
export function saveAdminKhhaoSourceConfig(payload: AdminKhhaoSourceConfig) {
return apiPost<{
filePath: string
source: AdminKhhaoSourceConfig
stateFilePath: string
syncState: AdminKhhaoSyncState
}>('/api/v1/admin/platform-config/khhao-source', payload as unknown as Record<string, unknown>)
}
export function testAdminKhhaoLogin(payload: {
baseUrl?: string
username: string
password: string
maxCaptchaAttempts?: number
includeImageBase64?: boolean
}) {
return apiPost<AdminKhhaoLoginTestResult>('/api/v1/admin/platform-config/khhao/test-login', payload)
}
export function queryAdminKhhaoOrders(payload: {
baseUrl?: string
username: string
password: string
page?: number
limit?: number
maxCaptchaAttempts?: number
}) {
return apiPost<AdminKhhaoOrderQueryResult>('/api/v1/admin/platform-config/khhao/query-orders', payload)
}
export function syncAdminKhhaoOrders(payload: {
baseUrl?: string
username: string
password: string
page?: number
limit?: number
maxCaptchaAttempts?: number
}) {
return apiPost<AdminKhhaoOrderSyncResult>('/api/v1/admin/platform-config/khhao/sync-orders', payload)
}
export function fetchAdminNinetyoneOrders(params: {
page?: number
pageSize?: number
-109
View File
@@ -80,37 +80,6 @@ export interface AdminAgisoObservedShopItem {
configured: boolean
}
export interface AdminKhhaoLoginTestResult {
baseUrl: string
username: string
loggedInAt: string
attempt: number
responseMessage: string
captcha: {
recognizedText: string
imageBase64: string
}
session: {
cookieKeys: string[]
cookieCount: number
cookieHeaderMasked: string
}
}
export interface AdminKhhaoSourceConfig {
enabled: boolean
baseUrl: string
username: string
password: string
maxCaptchaAttempts: number
autoSync: {
enabled: boolean
intervalMinutes: number
pages: number
pageSize: number
}
}
export interface AdminKuaishouEticketSourceConfig {
enabled: boolean
baseUrl: string
@@ -195,82 +164,6 @@ export interface AdminKuaishouEticketConsumeResult {
raw: Record<string, unknown>
}
export interface AdminKhhaoSyncState {
syncFromCreatedAt: string
lastRunStartedAt: string
lastRunFinishedAt: string
lastRunStatus: string
lastErrorMessage: string
fetchedCount: number
syncedCount: number
ignoredCount: number
watchMode: string
watchOrders: Array<{
platformOrderId: string
shopId: string
shopName: string
itemTitle: string
payStatus: string
detectedAt: string
lastSeenAt: string
}>
}
export interface AdminKhhaoOrderPreview {
provider: string
platform: string
platformLabel: string
platformOrderId: string
shopId: string
kuaishouShopId: string
khhaoShopId: string
shopName: string
itemId: string
itemTitle: string
skuCode: string
quantity: number
totalAmountFen: number
status: string
statusLabel: string
orderCreatedAt: string
raw: Record<string, unknown>
}
export interface AdminKhhaoOrderQueryResult {
baseUrl: string
page: number
limit: number
total: number
itemCount: number
items: AdminKhhaoOrderPreview[]
rawItems: Record<string, unknown>[]
}
export interface AdminKhhaoOrderSyncItem {
platformOrderId: string
provider: string
platform: string
shopId: string
skuCode: string
itemTitle: string
ignored: boolean
ignoreReason: string
orderId: number | null
orderItemCount: number
taskCount: number
}
export interface AdminKhhaoOrderSyncResult {
baseUrl: string
page: number
limit: number
total: number
fetchedCount: number
syncedCount: number
ignoredCount: number
results: AdminKhhaoOrderSyncItem[]
}
export interface AdminNinetyoneOrderItem {
orderId: number
orderNo: string
@@ -389,7 +282,6 @@ export interface AdminFulfillmentBindingConfigItem {
platform: string
shopId: string
shopName: string
khhaoShopId: string
skuCode: string
skuName: string
profileKey: string
@@ -411,7 +303,6 @@ export interface AdminKuaishouCloudFulfillmentItem {
provider: string
platform: string
shopId: string
khhaoShopId: string
internalSkuCode: string
internalSkuName: string
externalSkuCode: string
@@ -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"