增加平台持久化
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"enabled": true,
|
||||||
|
"baseUrl": "https://admin.khhao.com",
|
||||||
|
"username": "order_check",
|
||||||
|
"password": "order_check_test1",
|
||||||
|
"maxCaptchaAttempts": 3
|
||||||
|
}
|
||||||
@@ -1,4 +1,23 @@
|
|||||||
[
|
[
|
||||||
|
{
|
||||||
|
"provider": "khhao",
|
||||||
|
"platform": "kuaishou",
|
||||||
|
"shopId": "10",
|
||||||
|
"skuCode": "测试快手1",
|
||||||
|
"skuName": "测试1",
|
||||||
|
"profileKey": "manual_review",
|
||||||
|
"enabled": true,
|
||||||
|
"priority": 100,
|
||||||
|
"config": {},
|
||||||
|
"match": {
|
||||||
|
"externalSkuCode": "830",
|
||||||
|
"externalItemId": "830",
|
||||||
|
"externalSkuName": "测试1",
|
||||||
|
"config": {
|
||||||
|
"resolvedSkuName": "测试1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"provider": "agiso",
|
"provider": "agiso",
|
||||||
"platform": "xianyu",
|
"platform": "xianyu",
|
||||||
|
|||||||
@@ -5,10 +5,13 @@ import { Router } from 'express'
|
|||||||
import {
|
import {
|
||||||
getAdminAgisoShopConfigs,
|
getAdminAgisoShopConfigs,
|
||||||
getAdminFulfillmentBindingConfigs,
|
getAdminFulfillmentBindingConfigs,
|
||||||
|
getAdminKhhaoSourceConfig,
|
||||||
lookupAdminFulfillmentBindingOrder,
|
lookupAdminFulfillmentBindingOrder,
|
||||||
queryAdminKhhaoOrders,
|
queryAdminKhhaoOrders,
|
||||||
|
syncAdminKhhaoOrders,
|
||||||
testAdminKhhaoLogin,
|
testAdminKhhaoLogin,
|
||||||
updateAdminAgisoShopConfigs,
|
updateAdminAgisoShopConfigs,
|
||||||
|
updateAdminKhhaoSourceConfig,
|
||||||
updateAdminFulfillmentBindingConfigs,
|
updateAdminFulfillmentBindingConfigs,
|
||||||
} from '../../services/admin/admin-platform-config-service.js'
|
} from '../../services/admin/admin-platform-config-service.js'
|
||||||
import { createJsonHandler, requireAdminRoles } from './shared.js'
|
import { createJsonHandler, requireAdminRoles } from './shared.js'
|
||||||
@@ -17,6 +20,7 @@ import { createJsonHandler, requireAdminRoles } from './shared.js'
|
|||||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminFulfillmentBindingConfigRouteBody} AdminFulfillmentBindingConfigRouteBody */
|
/** @typedef {import('../../types/admin-route-inputs.js').AdminFulfillmentBindingConfigRouteBody} AdminFulfillmentBindingConfigRouteBody */
|
||||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminFulfillmentBindingLookupRouteBody} AdminFulfillmentBindingLookupRouteBody */
|
/** @typedef {import('../../types/admin-route-inputs.js').AdminFulfillmentBindingLookupRouteBody} AdminFulfillmentBindingLookupRouteBody */
|
||||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminKhhaoOrderQueryRouteBody} AdminKhhaoOrderQueryRouteBody */
|
/** @typedef {import('../../types/admin-route-inputs.js').AdminKhhaoOrderQueryRouteBody} AdminKhhaoOrderQueryRouteBody */
|
||||||
|
/** @typedef {import('../../types/admin-route-inputs.js').AdminKhhaoSourceConfigRouteBody} AdminKhhaoSourceConfigRouteBody */
|
||||||
/** @typedef {import('../../types/admin-route-inputs.js').AdminKhhaoTestLoginRouteBody} AdminKhhaoTestLoginRouteBody */
|
/** @typedef {import('../../types/admin-route-inputs.js').AdminKhhaoTestLoginRouteBody} AdminKhhaoTestLoginRouteBody */
|
||||||
/** @typedef {import('../../types/admin-write-models.js').AdminAgisoShopConfigSaveResponse} AdminAgisoShopConfigSaveResponse */
|
/** @typedef {import('../../types/admin-write-models.js').AdminAgisoShopConfigSaveResponse} AdminAgisoShopConfigSaveResponse */
|
||||||
|
|
||||||
@@ -54,6 +58,37 @@ router.post('/platform-config/agiso-shops', createJsonHandler(
|
|||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
|
||||||
|
router.get('/platform-config/khhao-source', createJsonHandler(
|
||||||
|
() => getAdminKhhaoSourceConfig(),
|
||||||
|
{
|
||||||
|
successMessage: 'ok',
|
||||||
|
errorMessage: '读取 khhao 来源配置失败',
|
||||||
|
scope: '[admin/platform-config/khhao-source]',
|
||||||
|
},
|
||||||
|
))
|
||||||
|
|
||||||
|
router.post('/platform-config/khhao-source', createJsonHandler(
|
||||||
|
(req) => updateAdminKhhaoSourceConfig(/** @type {AdminKhhaoSourceConfigRouteBody} */ (req.body)),
|
||||||
|
{
|
||||||
|
successMessage: 'khhao 来源配置已保存',
|
||||||
|
errorMessage: '保存 khhao 来源配置失败',
|
||||||
|
scope: '[admin/platform-config/khhao-source]',
|
||||||
|
audit: (_req, data) => {
|
||||||
|
const result = /** @type {{ filePath?: string, source?: { username?: string, enabled?: boolean } }} */ (data)
|
||||||
|
return {
|
||||||
|
action: 'platform_khhao_source_updated',
|
||||||
|
targetType: 'platform_config',
|
||||||
|
targetId: 'khhao_source',
|
||||||
|
data: {
|
||||||
|
filePath: String(result.filePath || '').trim(),
|
||||||
|
username: String(result.source?.username || '').trim(),
|
||||||
|
enabled: Boolean(result.source?.enabled),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
))
|
||||||
|
|
||||||
router.post('/platform-config/khhao/test-login', createJsonHandler(
|
router.post('/platform-config/khhao/test-login', createJsonHandler(
|
||||||
(req) => testAdminKhhaoLogin(/** @type {AdminKhhaoTestLoginRouteBody} */ (req.body)),
|
(req) => testAdminKhhaoLogin(/** @type {AdminKhhaoTestLoginRouteBody} */ (req.body)),
|
||||||
{
|
{
|
||||||
@@ -102,6 +137,32 @@ router.post('/platform-config/khhao/query-orders', createJsonHandler(
|
|||||||
},
|
},
|
||||||
))
|
))
|
||||||
|
|
||||||
|
router.post('/platform-config/khhao/sync-orders', createJsonHandler(
|
||||||
|
(req) => syncAdminKhhaoOrders(/** @type {AdminKhhaoOrderQueryRouteBody} */ (req.body)),
|
||||||
|
{
|
||||||
|
successMessage: 'khhao 订单同步成功',
|
||||||
|
errorMessage: 'khhao 订单同步失败',
|
||||||
|
scope: '[admin/platform-config/khhao/sync-orders]',
|
||||||
|
audit: (req, data) => {
|
||||||
|
const body = /** @type {AdminKhhaoOrderQueryRouteBody} */ (req.body)
|
||||||
|
const result = /** @type {{ page?: number, limit?: number, fetchedCount?: number, syncedCount?: number, ignoredCount?: number, baseUrl?: string }} */ (data)
|
||||||
|
return {
|
||||||
|
action: 'platform_khhao_sync_orders',
|
||||||
|
targetType: 'platform_config',
|
||||||
|
targetId: String(body.username || '').trim() || 'khhao',
|
||||||
|
data: {
|
||||||
|
baseUrl: result.baseUrl || String(body.baseUrl || '').trim(),
|
||||||
|
page: Number(result.page || 1),
|
||||||
|
limit: Number(result.limit || 50),
|
||||||
|
fetchedCount: Number(result.fetchedCount || 0),
|
||||||
|
syncedCount: Number(result.syncedCount || 0),
|
||||||
|
ignoredCount: Number(result.ignoredCount || 0),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
))
|
||||||
|
|
||||||
router.get('/platform-config/fulfillment-bindings', createJsonHandler(
|
router.get('/platform-config/fulfillment-bindings', createJsonHandler(
|
||||||
() => getAdminFulfillmentBindingConfigs(),
|
() => getAdminFulfillmentBindingConfigs(),
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ import {
|
|||||||
import { enrichAgisoXianyuTradeOrder } from '../platforms/agiso/xianyu/order-detail-service.js'
|
import { enrichAgisoXianyuTradeOrder } from '../platforms/agiso/xianyu/order-detail-service.js'
|
||||||
import { mapKhhaoOrderPreviewList } from '../platforms/khhao/order-mapper-service.js'
|
import { mapKhhaoOrderPreviewList } from '../platforms/khhao/order-mapper-service.js'
|
||||||
import { queryKhhaoOrderList } from '../platforms/khhao/order-query-service.js'
|
import { queryKhhaoOrderList } from '../platforms/khhao/order-query-service.js'
|
||||||
|
import { syncKhhaoOrders } from '../platforms/khhao/order-sync-service.js'
|
||||||
|
import { getKhhaoSourceConfig, getKhhaoSourcesFilePath, saveKhhaoSourceConfig } from '../platforms/khhao/source-config-service.js'
|
||||||
import { loginKhhaoSession } from '../platforms/khhao/session-service.js'
|
import { loginKhhaoSession } from '../platforms/khhao/session-service.js'
|
||||||
import { normalizeAgisoMessageTemplate } from '../platforms/agiso/xianyu/message-service.js'
|
import { normalizeAgisoMessageTemplate } from '../platforms/agiso/xianyu/message-service.js'
|
||||||
import {
|
import {
|
||||||
@@ -26,6 +28,7 @@ import { normalizeProductName } from '../order/product-match-service.js'
|
|||||||
import { resolveDisplayShopName } from './admin-read-shared-helpers.js'
|
import { resolveDisplayShopName } from './admin-read-shared-helpers.js'
|
||||||
|
|
||||||
/** @typedef {import('../../types/admin-write-inputs.js').AdminAgisoShopConfigSaveInput} AdminAgisoShopConfigSaveInput */
|
/** @typedef {import('../../types/admin-write-inputs.js').AdminAgisoShopConfigSaveInput} AdminAgisoShopConfigSaveInput */
|
||||||
|
/** @typedef {import('../../types/admin-write-inputs.js').AdminKhhaoSourceConfigInput} AdminKhhaoSourceConfigInput */
|
||||||
/** @typedef {import('../../types/admin-write-inputs.js').AdminKhhaoOrderQueryInput} AdminKhhaoOrderQueryInput */
|
/** @typedef {import('../../types/admin-write-inputs.js').AdminKhhaoOrderQueryInput} AdminKhhaoOrderQueryInput */
|
||||||
/** @typedef {import('../../types/admin-write-inputs.js').AdminKhhaoTestLoginInput} AdminKhhaoTestLoginInput */
|
/** @typedef {import('../../types/admin-write-inputs.js').AdminKhhaoTestLoginInput} AdminKhhaoTestLoginInput */
|
||||||
/** @typedef {import('../../types/admin-write-inputs.js').AdminFulfillmentBindingConfigSaveInput} AdminFulfillmentBindingConfigSaveInput */
|
/** @typedef {import('../../types/admin-write-inputs.js').AdminFulfillmentBindingConfigSaveInput} AdminFulfillmentBindingConfigSaveInput */
|
||||||
@@ -181,15 +184,53 @@ function applyOptionalStringField(target, key, source) {
|
|||||||
delete target[key]
|
delete target[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getAdminKhhaoSourceConfig() {
|
||||||
|
const config = getKhhaoSourceConfig()
|
||||||
|
|
||||||
|
return {
|
||||||
|
filePath: getKhhaoSourcesFilePath(),
|
||||||
|
source: {
|
||||||
|
enabled: config.enabled !== false,
|
||||||
|
baseUrl: String(config.baseUrl || '').trim(),
|
||||||
|
username: String(config.username || '').trim(),
|
||||||
|
password: String(config.password || '').trim(),
|
||||||
|
maxCaptchaAttempts: Number(config.maxCaptchaAttempts || 3) || 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {AdminKhhaoSourceConfigInput} [payload] */
|
||||||
|
export function updateAdminKhhaoSourceConfig(payload = /** @type {AdminKhhaoSourceConfigInput} */ ({})) {
|
||||||
|
const saved = saveKhhaoSourceConfig({
|
||||||
|
enabled: payload.enabled !== false,
|
||||||
|
baseUrl: String(payload.baseUrl || '').trim() || 'https://admin.khhao.com',
|
||||||
|
username: String(payload.username || '').trim(),
|
||||||
|
password: String(payload.password || '').trim(),
|
||||||
|
maxCaptchaAttempts: Number(payload.maxCaptchaAttempts || 3) || 3,
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
filePath: getKhhaoSourcesFilePath(),
|
||||||
|
source: {
|
||||||
|
enabled: saved.enabled !== false,
|
||||||
|
baseUrl: String(saved.baseUrl || '').trim(),
|
||||||
|
username: String(saved.username || '').trim(),
|
||||||
|
password: String(saved.password || '').trim(),
|
||||||
|
maxCaptchaAttempts: Number(saved.maxCaptchaAttempts || 3) || 3,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** @param {AdminKhhaoTestLoginInput} [payload] */
|
/** @param {AdminKhhaoTestLoginInput} [payload] */
|
||||||
export async function testAdminKhhaoLogin(payload = /** @type {AdminKhhaoTestLoginInput} */ ({})) {
|
export async function testAdminKhhaoLogin(payload = /** @type {AdminKhhaoTestLoginInput} */ ({})) {
|
||||||
|
const savedSource = getKhhaoSourceConfig()
|
||||||
const session = await loginKhhaoSession({
|
const session = await loginKhhaoSession({
|
||||||
baseUrl: payload.baseUrl,
|
baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(),
|
||||||
username: payload.username,
|
username: String(payload.username || savedSource.username || '').trim(),
|
||||||
password: payload.password,
|
password: String(payload.password || savedSource.password || '').trim(),
|
||||||
maxCaptchaAttempts: payload.maxCaptchaAttempts,
|
maxCaptchaAttempts: payload.maxCaptchaAttempts || savedSource.maxCaptchaAttempts,
|
||||||
includeImageBase64: payload.includeImageBase64,
|
includeImageBase64: payload.includeImageBase64,
|
||||||
requestId: `admin-khhao-test-login:${String(payload.username || '').trim() || 'anonymous'}`,
|
requestId: `admin-khhao-test-login:${String(payload.username || savedSource.username || '').trim() || 'anonymous'}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -212,16 +253,17 @@ export async function testAdminKhhaoLogin(payload = /** @type {AdminKhhaoTestLog
|
|||||||
|
|
||||||
/** @param {AdminKhhaoOrderQueryInput} [payload] */
|
/** @param {AdminKhhaoOrderQueryInput} [payload] */
|
||||||
export async function queryAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQueryInput} */ ({})) {
|
export async function queryAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQueryInput} */ ({})) {
|
||||||
|
const savedSource = getKhhaoSourceConfig()
|
||||||
const session = await loginKhhaoSession({
|
const session = await loginKhhaoSession({
|
||||||
baseUrl: payload.baseUrl,
|
baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(),
|
||||||
username: payload.username,
|
username: String(payload.username || savedSource.username || '').trim(),
|
||||||
password: payload.password,
|
password: String(payload.password || savedSource.password || '').trim(),
|
||||||
maxCaptchaAttempts: payload.maxCaptchaAttempts,
|
maxCaptchaAttempts: payload.maxCaptchaAttempts || savedSource.maxCaptchaAttempts,
|
||||||
requestId: `admin-khhao-query-orders:${String(payload.username || '').trim() || 'anonymous'}`,
|
requestId: `admin-khhao-query-orders:${String(payload.username || savedSource.username || '').trim() || 'anonymous'}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
const result = await queryKhhaoOrderList({
|
const result = await queryKhhaoOrderList({
|
||||||
baseUrl: payload.baseUrl,
|
baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(),
|
||||||
page: payload.page,
|
page: payload.page,
|
||||||
limit: payload.limit,
|
limit: payload.limit,
|
||||||
session,
|
session,
|
||||||
@@ -239,6 +281,18 @@ export async function queryAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrder
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {AdminKhhaoOrderQueryInput} [payload] */
|
||||||
|
export async function syncAdminKhhaoOrders(payload = /** @type {AdminKhhaoOrderQueryInput} */ ({})) {
|
||||||
|
const savedSource = getKhhaoSourceConfig()
|
||||||
|
return syncKhhaoOrders({
|
||||||
|
...payload,
|
||||||
|
baseUrl: String(payload.baseUrl || savedSource.baseUrl || '').trim(),
|
||||||
|
username: String(payload.username || savedSource.username || '').trim(),
|
||||||
|
password: String(payload.password || savedSource.password || '').trim(),
|
||||||
|
maxCaptchaAttempts: payload.maxCaptchaAttempts || savedSource.maxCaptchaAttempts,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export async function getAdminFulfillmentBindingConfigs() {
|
export async function getAdminFulfillmentBindingConfigs() {
|
||||||
const bindings = getOrderFulfillmentBindingConfigs()
|
const bindings = getOrderFulfillmentBindingConfigs()
|
||||||
const rowsResult = await query(
|
const rowsResult = await query(
|
||||||
|
|||||||
@@ -9,6 +9,10 @@ import { nowIso } from '../../utils/time.js'
|
|||||||
import { logWebhook } from '../../utils/logger.js'
|
import { logWebhook } from '../../utils/logger.js'
|
||||||
|
|
||||||
export async function upsertOrderFromWebhook(event) {
|
export async function upsertOrderFromWebhook(event) {
|
||||||
|
return upsertOrderFromSource(event, { sourceLabel: 'webhook' })
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function upsertOrderFromSource(event, { sourceLabel = 'source' } = {}) {
|
||||||
const now = nowIso()
|
const now = nowIso()
|
||||||
const existing = await findOrderByPlatformOrderId({
|
const existing = await findOrderByPlatformOrderId({
|
||||||
provider: event.provider,
|
provider: event.provider,
|
||||||
@@ -17,7 +21,7 @@ export async function upsertOrderFromWebhook(event) {
|
|||||||
platformOrderId: event.platformOrderId,
|
platformOrderId: event.platformOrderId,
|
||||||
})
|
})
|
||||||
|
|
||||||
logWebhook('[order-service]', '开始处理 webhook 订单 upsert', {
|
logWebhook('[order-service]', `开始处理 ${sourceLabel} 订单 upsert`, {
|
||||||
provider: event.provider,
|
provider: event.provider,
|
||||||
platform: event.platform,
|
platform: event.platform,
|
||||||
shopId: event.shopId,
|
shopId: event.shopId,
|
||||||
@@ -37,7 +41,7 @@ export async function upsertOrderFromWebhook(event) {
|
|||||||
const configuredItems = resolvedItems.filter((item) => item.isConfigured)
|
const configuredItems = resolvedItems.filter((item) => item.isConfigured)
|
||||||
|
|
||||||
if (configuredItems.length === 0) {
|
if (configuredItems.length === 0) {
|
||||||
logWebhook('[order-service]', 'Webhook 订单已忽略:未命中任何已配置履约商品', {
|
logWebhook('[order-service]', `${sourceLabel} 订单已忽略:未命中任何已配置履约商品`, {
|
||||||
provider: event.provider,
|
provider: event.provider,
|
||||||
platform: event.platform,
|
platform: event.platform,
|
||||||
shopId: event.shopId,
|
shopId: event.shopId,
|
||||||
@@ -104,7 +108,7 @@ export async function upsertOrderFromWebhook(event) {
|
|||||||
const tasks = await syncDeliveryTasksForOrder(order, orderItems)
|
const tasks = await syncDeliveryTasksForOrder(order, orderItems)
|
||||||
const messageDeliveries = []
|
const messageDeliveries = []
|
||||||
|
|
||||||
logWebhook('[order-service]', 'Webhook 订单 upsert 完成', {
|
logWebhook('[order-service]', `${sourceLabel} 订单 upsert 完成`, {
|
||||||
orderId: order.id,
|
orderId: order.id,
|
||||||
provider: order.provider,
|
provider: order.provider,
|
||||||
platform: order.platform,
|
platform: order.platform,
|
||||||
|
|||||||
@@ -6,6 +6,42 @@ export function mapKhhaoOrderPreviewList(items = []) {
|
|||||||
return (Array.isArray(items) ? items : []).map((item) => mapKhhaoOrderPreview(item))
|
return (Array.isArray(items) ? items : []).map((item) => mapKhhaoOrderPreview(item))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mapKhhaoOrderToSourceEvent(item = {}) {
|
||||||
|
const preview = mapKhhaoOrderPreview(item)
|
||||||
|
const orderStatus = resolveKhhaoOrderStatus(preview.status)
|
||||||
|
const payStatus = resolveKhhaoPayStatus(preview.status)
|
||||||
|
|
||||||
|
return {
|
||||||
|
provider: 'khhao',
|
||||||
|
platform: preview.platform || 'unknown',
|
||||||
|
shopId: preview.shopId,
|
||||||
|
shopName: preview.shopName,
|
||||||
|
platformOrderId: preview.platformOrderId,
|
||||||
|
orderStatus,
|
||||||
|
payStatus,
|
||||||
|
buyerId: '',
|
||||||
|
buyerName: String(preview.raw.name || '').trim(),
|
||||||
|
receiverContact: '',
|
||||||
|
totalAmount: preview.totalAmountFen,
|
||||||
|
currency: 'CNY',
|
||||||
|
paidAt: payStatus === 'paid' ? normalizePaidAt(preview.orderCreatedAt) : null,
|
||||||
|
rawPayload: preview.raw,
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
itemId: preview.itemId,
|
||||||
|
externalItemId: preview.itemId,
|
||||||
|
externalSkuCode: preview.skuCode || preview.itemId,
|
||||||
|
externalSkuName: preview.itemTitle || preview.skuCode || preview.itemId,
|
||||||
|
skuCode: preview.skuCode || preview.itemId,
|
||||||
|
skuName: preview.itemTitle || preview.skuCode || preview.itemId,
|
||||||
|
quantity: preview.quantity,
|
||||||
|
snapshot: preview.raw,
|
||||||
|
spec: preview.raw,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function mapKhhaoOrderPreview(item = {}) {
|
export function mapKhhaoOrderPreview(item = {}) {
|
||||||
const raw = isPlainObject(item) ? item : {}
|
const raw = isPlainObject(item) ? item : {}
|
||||||
const platform = resolveKhhaoPlatform(raw.pingtai)
|
const platform = resolveKhhaoPlatform(raw.pingtai)
|
||||||
@@ -40,6 +76,34 @@ export function resolveKhhaoPlatform(value) {
|
|||||||
return normalized ? 'unknown' : ''
|
return normalized ? 'unknown' : ''
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function resolveKhhaoOrderStatus(value) {
|
||||||
|
const normalized = String(value || '').trim()
|
||||||
|
|
||||||
|
if (normalized === '2') {
|
||||||
|
return 'paid'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized === '0') {
|
||||||
|
return 'created'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
|
export function resolveKhhaoPayStatus(value) {
|
||||||
|
const normalized = String(value || '').trim()
|
||||||
|
|
||||||
|
if (normalized === '2') {
|
||||||
|
return 'paid'
|
||||||
|
}
|
||||||
|
|
||||||
|
if (normalized === '0') {
|
||||||
|
return 'unpaid'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'unknown'
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeQuantity(value) {
|
function normalizeQuantity(value) {
|
||||||
const parsed = Number(value)
|
const parsed = Number(value)
|
||||||
return Number.isInteger(parsed) && parsed > 0 ? parsed : 1
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : 1
|
||||||
@@ -49,6 +113,11 @@ function stripHtmlTags(value) {
|
|||||||
return String(value || '').replace(/<[^>]+>/g, '').trim()
|
return String(value || '').replace(/<[^>]+>/g, '').trim()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function normalizePaidAt(value) {
|
||||||
|
const text = String(value || '').trim()
|
||||||
|
return text ? text.replace(' ', 'T') : null
|
||||||
|
}
|
||||||
|
|
||||||
function isPlainObject(value) {
|
function isPlainObject(value) {
|
||||||
return Object.prototype.toString.call(value) === '[object Object]'
|
return Object.prototype.toString.call(value) === '[object Object]'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
// @ts-check
|
||||||
|
|
||||||
|
import { upsertOrderFromSource } from '../../order/order-service.js'
|
||||||
|
import { mapKhhaoOrderPreview, mapKhhaoOrderToSourceEvent } from './order-mapper-service.js'
|
||||||
|
import { queryKhhaoOrderList } from './order-query-service.js'
|
||||||
|
import { loginKhhaoSession } from './session-service.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {{
|
||||||
|
* baseUrl?: string
|
||||||
|
* username?: string
|
||||||
|
* password?: string
|
||||||
|
* page?: number | string
|
||||||
|
* limit?: number | string
|
||||||
|
* maxCaptchaAttempts?: number | string
|
||||||
|
* }} [payload]
|
||||||
|
*/
|
||||||
|
export async function syncKhhaoOrders(payload = {}) {
|
||||||
|
const session = await loginKhhaoSession({
|
||||||
|
baseUrl: payload.baseUrl,
|
||||||
|
username: payload.username,
|
||||||
|
password: payload.password,
|
||||||
|
maxCaptchaAttempts: payload.maxCaptchaAttempts,
|
||||||
|
requestId: `admin-khhao-sync-orders:${String(payload.username || '').trim() || 'anonymous'}`,
|
||||||
|
})
|
||||||
|
|
||||||
|
const queryResult = await queryKhhaoOrderList({
|
||||||
|
baseUrl: payload.baseUrl,
|
||||||
|
page: payload.page,
|
||||||
|
limit: payload.limit,
|
||||||
|
session,
|
||||||
|
})
|
||||||
|
|
||||||
|
const results = []
|
||||||
|
|
||||||
|
for (const item of queryResult.items) {
|
||||||
|
const preview = mapKhhaoOrderPreview(item)
|
||||||
|
const sourceEvent = mapKhhaoOrderToSourceEvent(item)
|
||||||
|
const upsertResult = await upsertOrderFromSource(sourceEvent, { sourceLabel: 'khhao-sync' })
|
||||||
|
|
||||||
|
results.push({
|
||||||
|
platformOrderId: preview.platformOrderId,
|
||||||
|
provider: sourceEvent.provider,
|
||||||
|
platform: sourceEvent.platform,
|
||||||
|
shopId: sourceEvent.shopId,
|
||||||
|
skuCode: preview.skuCode,
|
||||||
|
itemTitle: preview.itemTitle,
|
||||||
|
ignored: Boolean(upsertResult.ignored),
|
||||||
|
ignoreReason: String(upsertResult.ignoreReason || '').trim(),
|
||||||
|
orderId: Number(upsertResult.order?.id || 0) || null,
|
||||||
|
orderItemCount: Array.isArray(upsertResult.orderItems) ? upsertResult.orderItems.length : 0,
|
||||||
|
taskCount: Array.isArray(upsertResult.tasks) ? upsertResult.tasks.length : 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
baseUrl: session.baseUrl,
|
||||||
|
page: queryResult.page,
|
||||||
|
limit: queryResult.limit,
|
||||||
|
total: queryResult.total,
|
||||||
|
fetchedCount: queryResult.items.length,
|
||||||
|
syncedCount: results.filter((item) => !item.ignored && item.orderId).length,
|
||||||
|
ignoredCount: results.filter((item) => item.ignored).length,
|
||||||
|
results,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// @ts-check
|
||||||
|
|
||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
import { PROJECT_ROOT } from '../../../config/runtime.js'
|
||||||
|
|
||||||
|
const KHHAO_SOURCES_FILE_PATH = path.join(PROJECT_ROOT, 'data', 'khhao-sources.json')
|
||||||
|
|
||||||
|
export function getKhhaoSourcesFilePath() {
|
||||||
|
return KHHAO_SOURCES_FILE_PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getKhhaoSourceConfig() {
|
||||||
|
return loadKhhaoSourceConfigFromFile()
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveKhhaoSourceConfig(rawValue) {
|
||||||
|
const normalized = normalizeKhhaoSourceConfig(rawValue)
|
||||||
|
fs.mkdirSync(path.dirname(KHHAO_SOURCES_FILE_PATH), { recursive: true })
|
||||||
|
fs.writeFileSync(KHHAO_SOURCES_FILE_PATH, `${JSON.stringify(normalized, null, 2)}\n`, 'utf8')
|
||||||
|
return normalized
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadKhhaoSourceConfigFromFile() {
|
||||||
|
if (!fs.existsSync(KHHAO_SOURCES_FILE_PATH)) {
|
||||||
|
return createDefaultKhhaoSourceConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const rawText = fs.readFileSync(KHHAO_SOURCES_FILE_PATH, 'utf8')
|
||||||
|
return normalizeKhhaoSourceConfig(JSON.parse(rawText))
|
||||||
|
} catch {
|
||||||
|
return createDefaultKhhaoSourceConfig()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeKhhaoSourceConfig(rawValue) {
|
||||||
|
const source = isPlainObject(rawValue) ? rawValue : {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
enabled: typeof source.enabled === 'boolean' ? source.enabled : true,
|
||||||
|
baseUrl: String(source.baseUrl || 'https://admin.khhao.com').trim() || 'https://admin.khhao.com',
|
||||||
|
username: String(source.username || '').trim(),
|
||||||
|
password: String(source.password || '').trim(),
|
||||||
|
maxCaptchaAttempts: normalizePositiveInteger(source.maxCaptchaAttempts, 3),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createDefaultKhhaoSourceConfig() {
|
||||||
|
return {
|
||||||
|
enabled: true,
|
||||||
|
baseUrl: 'https://admin.khhao.com',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
maxCaptchaAttempts: 3,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizePositiveInteger(value, fallback) {
|
||||||
|
const parsed = Number(value)
|
||||||
|
return Number.isInteger(parsed) && parsed > 0 ? parsed : fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
function isPlainObject(value) {
|
||||||
|
return Object.prototype.toString.call(value) === '[object Object]'
|
||||||
|
}
|
||||||
@@ -30,6 +30,10 @@ export {}
|
|||||||
* @typedef {import('./admin-read-inputs.js').AdminWebhookEventListQueryInput} AdminWebhookEventRouteQuery
|
* @typedef {import('./admin-read-inputs.js').AdminWebhookEventListQueryInput} AdminWebhookEventRouteQuery
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {import('./admin-write-inputs.js').AdminKhhaoSourceConfigInput} AdminKhhaoSourceConfigRouteBody
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {import('./admin-write-inputs.js').AdminAgisoShopConfigSaveInput} AdminAgisoShopConfigRouteBody
|
* @typedef {import('./admin-write-inputs.js').AdminAgisoShopConfigSaveInput} AdminAgisoShopConfigRouteBody
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -67,6 +67,16 @@ export {}
|
|||||||
* }} AdminAgisoShopConfigSaveInput
|
* }} AdminAgisoShopConfigSaveInput
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {{
|
||||||
|
* enabled?: boolean
|
||||||
|
* baseUrl?: string
|
||||||
|
* username?: string
|
||||||
|
* password?: string
|
||||||
|
* maxCaptchaAttempts?: number | string
|
||||||
|
* }} AdminKhhaoSourceConfigInput
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {{
|
* @typedef {{
|
||||||
* baseUrl?: string
|
* baseUrl?: string
|
||||||
|
|||||||
Vendored
-7
@@ -13,20 +13,13 @@ declare module 'vue' {
|
|||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AdminPaginationBar: typeof import('./components/admin/AdminPaginationBar.vue')['default']
|
AdminPaginationBar: typeof import('./components/admin/AdminPaginationBar.vue')['default']
|
||||||
AdminStatusTag: typeof import('./components/admin/AdminStatusTag.vue')['default']
|
AdminStatusTag: typeof import('./components/admin/AdminStatusTag.vue')['default']
|
||||||
ElAlert: typeof import('element-plus/es')['ElAlert']
|
|
||||||
ElButton: typeof import('element-plus/es')['ElButton']
|
ElButton: typeof import('element-plus/es')['ElButton']
|
||||||
ElCard: typeof import('element-plus/es')['ElCard']
|
|
||||||
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
|
||||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
|
||||||
ElForm: typeof import('element-plus/es')['ElForm']
|
ElForm: typeof import('element-plus/es')['ElForm']
|
||||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||||
ElOption: typeof import('element-plus/es')['ElOption']
|
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
|
||||||
ElSpace: typeof import('element-plus/es')['ElSpace']
|
|
||||||
ElTag: typeof import('element-plus/es')['ElTag']
|
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
TencentAuthCard: typeof import('./components/tencent/TencentAuthCard.vue')['default']
|
TencentAuthCard: typeof import('./components/tencent/TencentAuthCard.vue')['default']
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import type {
|
|||||||
AdminAuditLogItem,
|
AdminAuditLogItem,
|
||||||
AdminFulfillmentLookupResult,
|
AdminFulfillmentLookupResult,
|
||||||
AdminFulfillmentBindingConfigItem,
|
AdminFulfillmentBindingConfigItem,
|
||||||
|
AdminKhhaoLoginTestResult,
|
||||||
|
AdminKhhaoOrderQueryResult,
|
||||||
|
AdminKhhaoSourceConfig,
|
||||||
|
AdminKhhaoOrderSyncResult,
|
||||||
AdminInventoryItemListItem,
|
AdminInventoryItemListItem,
|
||||||
AdminInventorySkuSuggestion,
|
AdminInventorySkuSuggestion,
|
||||||
AdminMessageDeliveryListItem,
|
AdminMessageDeliveryListItem,
|
||||||
@@ -90,6 +94,52 @@ export function saveAdminAgisoShopConfigs(payload: {
|
|||||||
}>('/api/v1/admin/platform-config/agiso-shops', payload)
|
}>('/api/v1/admin/platform-config/agiso-shops', payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function fetchAdminKhhaoSourceConfig() {
|
||||||
|
return apiGet<{
|
||||||
|
filePath: string
|
||||||
|
source: AdminKhhaoSourceConfig
|
||||||
|
}>('/api/v1/admin/platform-config/khhao-source')
|
||||||
|
}
|
||||||
|
|
||||||
|
export function saveAdminKhhaoSourceConfig(payload: AdminKhhaoSourceConfig) {
|
||||||
|
return apiPost<{
|
||||||
|
filePath: string
|
||||||
|
source: AdminKhhaoSourceConfig
|
||||||
|
}>('/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 fetchAdminFulfillmentBindingConfigs() {
|
export function fetchAdminFulfillmentBindingConfigs() {
|
||||||
return apiGet<{
|
return apiGet<{
|
||||||
filePath: string
|
filePath: string
|
||||||
|
|||||||
@@ -80,6 +80,84 @@ export interface AdminAgisoObservedShopItem {
|
|||||||
configured: boolean
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AdminKhhaoOrderPreview {
|
||||||
|
provider: string
|
||||||
|
platform: string
|
||||||
|
platformLabel: string
|
||||||
|
platformOrderId: string
|
||||||
|
shopId: 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 AdminFulfillmentBindingConfigItem {
|
export interface AdminFulfillmentBindingConfigItem {
|
||||||
provider: string
|
provider: string
|
||||||
platform: string
|
platform: string
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import { computed, nextTick, onMounted, reactive, ref } from 'vue'
|
|||||||
import { showSuccess } from '@/lib/feedback'
|
import { showSuccess } from '@/lib/feedback'
|
||||||
import {
|
import {
|
||||||
fetchAdminFulfillmentBindingConfigs,
|
fetchAdminFulfillmentBindingConfigs,
|
||||||
|
fetchAdminKhhaoSourceConfig,
|
||||||
|
queryAdminKhhaoOrders,
|
||||||
lookupAdminFulfillmentBindingOrder,
|
lookupAdminFulfillmentBindingOrder,
|
||||||
saveAdminFulfillmentBindingConfigs,
|
saveAdminFulfillmentBindingConfigs,
|
||||||
} from '@/services/admin'
|
} from '@/services/admin'
|
||||||
@@ -11,6 +13,7 @@ import type {
|
|||||||
AdminFulfillmentBindingConfigItem,
|
AdminFulfillmentBindingConfigItem,
|
||||||
AdminFulfillmentLookupItem,
|
AdminFulfillmentLookupItem,
|
||||||
AdminFulfillmentLookupResult,
|
AdminFulfillmentLookupResult,
|
||||||
|
AdminKhhaoOrderPreview,
|
||||||
AdminObservedProductItem,
|
AdminObservedProductItem,
|
||||||
} from '@/types/admin'
|
} from '@/types/admin'
|
||||||
import { hasAdminRole } from '@/utils/admin-auth'
|
import { hasAdminRole } from '@/utils/admin-auth'
|
||||||
@@ -82,6 +85,18 @@ const lookupForm = reactive({
|
|||||||
shopId: '',
|
shopId: '',
|
||||||
platformOrderId: '',
|
platformOrderId: '',
|
||||||
})
|
})
|
||||||
|
const khhaoLookupLoading = ref(false)
|
||||||
|
const khhaoLookupErrorMessage = ref('')
|
||||||
|
const khhaoLookupResults = ref<AdminKhhaoOrderPreview[]>([])
|
||||||
|
const importedKhhaoOrderIds = ref<string[]>([])
|
||||||
|
const khhaoLookupForm = reactive({
|
||||||
|
baseUrl: 'https://admin.khhao.com',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
page: 1,
|
||||||
|
limit: 10,
|
||||||
|
maxCaptchaAttempts: 3,
|
||||||
|
})
|
||||||
|
|
||||||
const ruleMetrics = computed(() => {
|
const ruleMetrics = computed(() => {
|
||||||
const completedCount = bindings.value.filter(isEditableBindingComplete).length
|
const completedCount = bindings.value.filter(isEditableBindingComplete).length
|
||||||
@@ -205,12 +220,19 @@ async function loadConfigs() {
|
|||||||
errorMessage.value = ''
|
errorMessage.value = ''
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetchAdminFulfillmentBindingConfigs()
|
const [response, khhaoSourceResponse] = await Promise.all([
|
||||||
|
fetchAdminFulfillmentBindingConfigs(),
|
||||||
|
fetchAdminKhhaoSourceConfig(),
|
||||||
|
])
|
||||||
filePath.value = response.data.filePath
|
filePath.value = response.data.filePath
|
||||||
const nextBindings = response.data.bindings.map(mapEditableBinding)
|
const nextBindings = response.data.bindings.map(mapEditableBinding)
|
||||||
bindings.value = nextBindings
|
bindings.value = nextBindings
|
||||||
collapsedBindingIds.value = buildCollapsedIds(nextBindings)
|
collapsedBindingIds.value = buildCollapsedIds(nextBindings)
|
||||||
observedProducts.value = response.data.observedProducts
|
observedProducts.value = response.data.observedProducts
|
||||||
|
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) {
|
} catch (error) {
|
||||||
errorMessage.value = error instanceof Error ? error.message : '读取履约配置失败'
|
errorMessage.value = error instanceof Error ? error.message : '读取履约配置失败'
|
||||||
} finally {
|
} finally {
|
||||||
@@ -269,6 +291,25 @@ function isLookupProductImported(lineId: string) {
|
|||||||
return importedLookupLineIds.value.includes(lineId)
|
return importedLookupLineIds.value.includes(lineId)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function importKhhaoProduct(item: AdminKhhaoOrderPreview) {
|
||||||
|
importObservedProduct({
|
||||||
|
provider: item.provider || 'khhao',
|
||||||
|
platform: item.platform || 'unknown',
|
||||||
|
shopId: item.shopId,
|
||||||
|
externalSkuCode: item.skuCode,
|
||||||
|
externalItemId: item.itemId,
|
||||||
|
externalSkuName: item.itemTitle,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!importedKhhaoOrderIds.value.includes(item.platformOrderId)) {
|
||||||
|
importedKhhaoOrderIds.value = [...importedKhhaoOrderIds.value, item.platformOrderId]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isKhhaoProductImported(platformOrderId: string) {
|
||||||
|
return importedKhhaoOrderIds.value.includes(platformOrderId)
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeBindingForSave(item: EditableBinding): SaveBindingPayload {
|
function normalizeBindingForSave(item: EditableBinding): SaveBindingPayload {
|
||||||
return {
|
return {
|
||||||
provider: item.provider.trim() || 'agiso',
|
provider: item.provider.trim() || 'agiso',
|
||||||
@@ -459,6 +500,34 @@ async function lookupOrderProducts() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
importedKhhaoOrderIds.value = []
|
||||||
|
} catch (error) {
|
||||||
|
khhaoLookupResults.value = []
|
||||||
|
khhaoLookupErrorMessage.value = error instanceof Error ? error.message : 'khhao 订单查询失败'
|
||||||
|
} finally {
|
||||||
|
khhaoLookupLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(loadConfigs)
|
onMounted(loadConfigs)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -681,6 +750,112 @@ onMounted(loadConfigs)
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="table-card table-card--dense">
|
||||||
|
<div class="section-title-row section-title-row--tight">
|
||||||
|
<div>
|
||||||
|
<h3>khhao 订单取样导入</h3>
|
||||||
|
<p>先用 khhao 账号查订单样本,再直接把商品导入成规则草稿,适合首批建规则。</p>
|
||||||
|
</div>
|
||||||
|
<span class="section-note-chip">khhao / kuaishou</span>
|
||||||
|
</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" type="password" placeholder="khhao 登录密码" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="field-block">
|
||||||
|
<span>页码</span>
|
||||||
|
<input v-model.number="khhaoLookupForm.page" class="text-input" type="number" min="1" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="field-block">
|
||||||
|
<span>每页条数</span>
|
||||||
|
<input v-model.number="khhaoLookupForm.limit" class="text-input" type="number" min="1" max="50" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="field-block">
|
||||||
|
<span>验证码重试</span>
|
||||||
|
<input v-model.number="khhaoLookupForm.maxCaptchaAttempts" class="text-input" type="number" min="1" max="5" />
|
||||||
|
</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">这里直接读取 khhao 订单列表,不写库,只用于导入规则草稿。</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="`${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>
|
||||||
|
<span class="cell-subtle">ItemId: {{ item.itemId || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<div class="cell-stack">
|
||||||
|
<strong>{{ (item.totalAmountFen / 100).toFixed(2) }}</strong>
|
||||||
|
<span class="cell-subtle">{{ item.statusLabel || item.status || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<el-button
|
||||||
|
v-if="!isKhhaoProductImported(item.platformOrderId)"
|
||||||
|
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">
|
<section class="table-card table-card--dense">
|
||||||
<div class="section-title-row section-title-row--tight">
|
<div class="section-title-row section-title-row--tight">
|
||||||
<div>
|
<div>
|
||||||
@@ -1027,6 +1202,7 @@ onMounted(loadConfigs)
|
|||||||
}
|
}
|
||||||
|
|
||||||
.lookup-toolbar,
|
.lookup-toolbar,
|
||||||
|
.khhao-toolbar,
|
||||||
.lookup-summary {
|
.lookup-summary {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
@@ -1037,6 +1213,11 @@ onMounted(loadConfigs)
|
|||||||
align-items: end;
|
align-items: end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.khhao-toolbar {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
align-items: end;
|
||||||
|
}
|
||||||
|
|
||||||
.field-block--action :deep(.el-button) {
|
.field-block--action :deep(.el-button) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const navItems = computed(() => {
|
|||||||
|
|
||||||
if (isAdmin.value) {
|
if (isAdmin.value) {
|
||||||
baseItems.splice(1, 0, { to: '/admin/users', label: '用户' })
|
baseItems.splice(1, 0, { to: '/admin/users', label: '用户' })
|
||||||
baseItems.push({ to: '/admin/platform-shops', label: 'Agiso店铺' })
|
baseItems.push({ to: '/admin/platform-shops', label: '平台配置' })
|
||||||
baseItems.push({ to: '/admin/platform-fulfillment', label: '履约配置' })
|
baseItems.push({ to: '/admin/platform-fulfillment', label: '履约配置' })
|
||||||
baseItems.push({ to: '/admin/audit-logs', label: '审计' })
|
baseItems.push({ to: '/admin/audit-logs', label: '审计' })
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,22 @@
|
|||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
|
|
||||||
import { showError, showSuccess } from '@/lib/feedback'
|
import { showError, showSuccess } from '@/lib/feedback'
|
||||||
import { fetchAdminAgisoShopConfigs, saveAdminAgisoShopConfigs } from '@/services/admin'
|
import {
|
||||||
|
fetchAdminAgisoShopConfigs,
|
||||||
|
fetchAdminKhhaoSourceConfig,
|
||||||
|
queryAdminKhhaoOrders,
|
||||||
|
saveAdminAgisoShopConfigs,
|
||||||
|
saveAdminKhhaoSourceConfig,
|
||||||
|
syncAdminKhhaoOrders,
|
||||||
|
testAdminKhhaoLogin,
|
||||||
|
} from '@/services/admin'
|
||||||
import type {
|
import type {
|
||||||
AdminAgisoMessagingDefaults,
|
AdminAgisoMessagingDefaults,
|
||||||
AdminAgisoObservedShopItem,
|
AdminAgisoObservedShopItem,
|
||||||
AdminAgisoShopConfigItem,
|
AdminAgisoShopConfigItem,
|
||||||
|
AdminKhhaoLoginTestResult,
|
||||||
|
AdminKhhaoOrderQueryResult,
|
||||||
|
AdminKhhaoOrderSyncResult,
|
||||||
} from '@/types/admin'
|
} from '@/types/admin'
|
||||||
import { hasAdminRole } from '@/utils/admin-auth'
|
import { hasAdminRole } from '@/utils/admin-auth'
|
||||||
import { formatAdminDateTime } from '@/utils/admin-time'
|
import { formatAdminDateTime } from '@/utils/admin-time'
|
||||||
@@ -28,12 +39,29 @@ type EditableShop = {
|
|||||||
|
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const saving = ref(false)
|
const saving = ref(false)
|
||||||
|
const khhaoSaving = ref(false)
|
||||||
const errorMessage = ref('')
|
const errorMessage = ref('')
|
||||||
const filePath = ref('')
|
const filePath = ref('')
|
||||||
|
const khhaoFilePath = ref('')
|
||||||
const defaults = ref<EditableDefaults>(createEmptyDefaults())
|
const defaults = ref<EditableDefaults>(createEmptyDefaults())
|
||||||
const shops = ref<EditableShop[]>([])
|
const shops = ref<EditableShop[]>([])
|
||||||
const observedShops = ref<AdminAgisoObservedShopItem[]>([])
|
const observedShops = ref<AdminAgisoObservedShopItem[]>([])
|
||||||
const expandedShopIds = ref<string[]>([])
|
const expandedShopIds = ref<string[]>([])
|
||||||
|
const khhaoForm = ref({
|
||||||
|
baseUrl: 'https://admin.khhao.com',
|
||||||
|
username: '',
|
||||||
|
password: '',
|
||||||
|
page: 1,
|
||||||
|
limit: 10,
|
||||||
|
maxCaptchaAttempts: 3,
|
||||||
|
})
|
||||||
|
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)
|
||||||
|
|
||||||
function createEmptyDefaults(): EditableDefaults {
|
function createEmptyDefaults(): EditableDefaults {
|
||||||
return {
|
return {
|
||||||
@@ -83,11 +111,23 @@ async function loadConfigs() {
|
|||||||
errorMessage.value = ''
|
errorMessage.value = ''
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetchAdminAgisoShopConfigs()
|
const [agisoResponse, khhaoResponse] = await Promise.all([
|
||||||
filePath.value = response.data.filePath
|
fetchAdminAgisoShopConfigs(),
|
||||||
defaults.value = mapEditableDefaults(response.data.defaults)
|
fetchAdminKhhaoSourceConfig(),
|
||||||
shops.value = response.data.shops.map(mapEditableShop)
|
])
|
||||||
observedShops.value = response.data.observedShops
|
filePath.value = agisoResponse.data.filePath
|
||||||
|
defaults.value = mapEditableDefaults(agisoResponse.data.defaults)
|
||||||
|
shops.value = agisoResponse.data.shops.map(mapEditableShop)
|
||||||
|
observedShops.value = agisoResponse.data.observedShops
|
||||||
|
khhaoFilePath.value = khhaoResponse.data.filePath
|
||||||
|
khhaoForm.value = {
|
||||||
|
baseUrl: khhaoResponse.data.source.baseUrl || 'https://admin.khhao.com',
|
||||||
|
username: khhaoResponse.data.source.username || '',
|
||||||
|
password: khhaoResponse.data.source.password || '',
|
||||||
|
page: 1,
|
||||||
|
limit: 10,
|
||||||
|
maxCaptchaAttempts: khhaoResponse.data.source.maxCaptchaAttempts || 3,
|
||||||
|
}
|
||||||
expandedShopIds.value = []
|
expandedShopIds.value = []
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
errorMessage.value = error instanceof Error ? error.message : '读取店铺配置失败'
|
errorMessage.value = error instanceof Error ? error.message : '读取店铺配置失败'
|
||||||
@@ -207,6 +247,111 @@ async function saveConfigs() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 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 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),
|
||||||
|
})
|
||||||
|
khhaoFilePath.value = response.data.filePath
|
||||||
|
showSuccess('khhao 来源配置已保存')
|
||||||
|
} catch (error) {
|
||||||
|
khhaoResultError.value = error instanceof Error ? error.message : 'khhao 来源配置保存失败'
|
||||||
|
showError(khhaoResultError.value)
|
||||||
|
} finally {
|
||||||
|
khhaoSaving.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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(loadConfigs)
|
onMounted(loadConfigs)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -270,6 +415,153 @@ onMounted(loadConfigs)
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</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="meta-card">
|
||||||
|
<div class="meta-line">
|
||||||
|
<span class="meta-label">配置文件</span>
|
||||||
|
<code>{{ khhaoFilePath || '-' }}</code>
|
||||||
|
</div>
|
||||||
|
<div class="meta-line">
|
||||||
|
<span class="meta-label">说明</span>
|
||||||
|
<span>khhao 的 Base URL、账号、密码和验证码重试次数会单独保存在该文件中,不与 Agiso 店铺配置混用。</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="shop-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>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p v-if="khhaoResultError" class="error-copy">{{ khhaoResultError }}</p>
|
||||||
|
|
||||||
|
<div v-if="khhaoLoginResult" class="meta-card">
|
||||||
|
<div class="meta-line">
|
||||||
|
<span class="meta-label">登录测试</span>
|
||||||
|
<span>账号 {{ khhaoLoginResult.username }},第 {{ khhaoLoginResult.attempt }} 次成功,Cookie {{ khhaoLoginResult.session.cookieCount }} 个,验证码识别:{{ khhaoLoginResult.captcha.recognizedText || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="meta-line">
|
||||||
|
<span class="meta-label">时间</span>
|
||||||
|
<span>{{ formatAdminDateTime(khhaoLoginResult.loggedInAt) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template v-if="khhaoQueryResult">
|
||||||
|
<div class="section-title-row compact-top">
|
||||||
|
<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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="khhaoSyncResult">
|
||||||
|
<div class="section-title-row compact-top">
|
||||||
|
<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>
|
||||||
|
</template>
|
||||||
|
</section>
|
||||||
|
|
||||||
<section class="table-card">
|
<section class="table-card">
|
||||||
<div class="section-title-row">
|
<div class="section-title-row">
|
||||||
<div>
|
<div>
|
||||||
@@ -437,6 +729,10 @@ onMounted(loadConfigs)
|
|||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.compact-top {
|
||||||
|
margin-top: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.meta-card,
|
.meta-card,
|
||||||
.table-card,
|
.table-card,
|
||||||
.empty-block,
|
.empty-block,
|
||||||
|
|||||||
Reference in New Issue
Block a user