优化了任务

This commit is contained in:
yml
2026-05-03 19:13:22 +08:00
parent 90cb7aa154
commit dd2192b6ae
8 changed files with 258 additions and 72 deletions
+5 -5
View File
@@ -1,11 +1,11 @@
{ {
"syncFromCreatedAt": "2026-05-02T10:53:28.550Z", "syncFromCreatedAt": "2026-05-02T10:53:28.550Z",
"lastRunStartedAt": "2026-05-03T10:44:18.857Z", "lastRunStartedAt": "2026-05-03T11:11:55.949Z",
"lastRunFinishedAt": "2026-05-03T10:44:19.569Z", "lastRunFinishedAt": "2026-05-03T11:11:56.571Z",
"lastRunStatus": "success", "lastRunStatus": "success",
"lastErrorMessage": "", "lastErrorMessage": "",
"fetchedCount": 40, "fetchedCount": 40,
"syncedCount": 1, "syncedCount": 0,
"ignoredCount": 39, "ignoredCount": 40,
"lastOrderCreatedAt": "2026-05-03T10:44:07.000Z" "lastOrderCreatedAt": "2026-05-03T11:11:29.000Z"
} }
@@ -20,6 +20,7 @@ import { getWebhookEventById } from '../../repositories/webhook-event-repo.js'
import { createTaskClaimToken } from '../claim/claim-service.js' import { createTaskClaimToken } from '../claim/claim-service.js'
import { confirmClaimRoleForAdminTask, redeemClaimTaskForAdminTask } from '../claim/claim-session-service.js' import { confirmClaimRoleForAdminTask, redeemClaimTaskForAdminTask } from '../claim/claim-session-service.js'
import { reserveInventoryForTask } from '../order/inventory-service.js' import { reserveInventoryForTask } from '../order/inventory-service.js'
import { normalizeProductName } from '../order/product-match-service.js'
import { replayAgisoTradeWebhookEvent } from '../order/webhook-service.js' import { replayAgisoTradeWebhookEvent } from '../order/webhook-service.js'
import { ensureAgisoXianyuAutoDeliveryForDeliveredTask } from '../platforms/agiso/xianyu/auto-delivery-service.js' import { ensureAgisoXianyuAutoDeliveryForDeliveredTask } from '../platforms/agiso/xianyu/auto-delivery-service.js'
import { getCloudtentaclesSourceConfig } from '../platforms/cloudtentacles/source-config-service.js' import { getCloudtentaclesSourceConfig } from '../platforms/cloudtentacles/source-config-service.js'
@@ -75,6 +76,8 @@ import {
/** @typedef {import('../../types/admin-write-models.js').AdminWebhookReplayResponse} AdminWebhookReplayResponse */ /** @typedef {import('../../types/admin-write-models.js').AdminWebhookReplayResponse} AdminWebhookReplayResponse */
/** @typedef {import('../../types/admin-write-inputs.js').AdminTaskKuaishouCloudDispatchInput} AdminTaskKuaishouCloudDispatchInput */ /** @typedef {import('../../types/admin-write-inputs.js').AdminTaskKuaishouCloudDispatchInput} AdminTaskKuaishouCloudDispatchInput */
const KUAISHOU_CLOUD_FIXED_VN_KEY = '1'
/** @returns {Promise<AdminInventoryMutationResponse>} */ /** @returns {Promise<AdminInventoryMutationResponse>} */
/** @param {AdminInventoryCreateInput} [payload] */ /** @param {AdminInventoryCreateInput} [payload] */
export async function createAdminInventoryItem(payload = /** @type {AdminInventoryCreateInput} */ ({})) { export async function createAdminInventoryItem(payload = /** @type {AdminInventoryCreateInput} */ ({})) {
@@ -718,37 +721,54 @@ export async function prepareAdminTaskKuaishouCloudFulfillment(taskId, session =
const taskContext = parseTaskContext(task) const taskContext = parseTaskContext(task)
const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment) const flow = normalizeKuaishouCloudFlow(taskContext.kuaishouCloudFulfillment)
const cloudContext = resolvePersistedCloudtentaclesContext() const cloudContext = resolvePersistedCloudtentaclesContext()
const [knapsack, skuList] = await Promise.all([
getCloudtentaclesKnapsack(cloudContext),
listCloudtentaclesSku(cloudContext),
])
const resolvedBinding = resolveKuaishouCloudBindingResources(flow, {
skuItems: Array.isArray(skuList.items) ? skuList.items : [],
knapsackItems: Array.isArray(knapsack.items) ? knapsack.items : [],
})
const vnKeyCandidates = resolveKuaishouCloudVnKeyCandidates({
flow,
binding: resolvedBinding,
})
if (!flow.binding.skuId || !flow.binding.vnKey) { if (!resolvedBinding.skuId || vnKeyCandidates.length === 0) {
throw createHttpError('当前任务缺少 cloud SKU VN Key 配置', { throw createHttpError('当前任务缺少可用 cloud 资源,且无法根据商品名自动解析 SKU / VN Key', {
statusCode: 409, statusCode: 409,
errorCode: 'admin_task_kuaishou_cloud_missing_binding_config', errorCode: 'admin_task_kuaishou_cloud_missing_binding_config',
}) })
} }
const knapsack = await getCloudtentaclesKnapsack(cloudContext) const flowWithResolvedBinding = {
const knapsackItem = (Array.isArray(knapsack.items) ? knapsack.items : []).find((item) => Number(item.id || 0) === flow.binding.skuId) || null ...flow,
binding: {
...flow.binding,
skuId: resolvedBinding.skuId,
skuName: resolvedBinding.skuName,
vnKey: KUAISHOU_CLOUD_FIXED_VN_KEY,
},
}
const knapsackItem = resolvedBinding.knapsackItem
let usedKnapsack = Number(knapsackItem?.count || 0) > 0 let usedKnapsack = Number(knapsackItem?.count || 0) > 0
let purchaseTriggered = false let purchaseTriggered = false
let assetBefore = 0 let assetBefore = 0
let assetAfter = 0 let assetAfter = 0
if (!usedKnapsack) { if (!usedKnapsack) {
if (!flow.purchase.autoBuyEnabled) { if (!flowWithResolvedBinding.purchase.autoBuyEnabled) {
throw createHttpError('背包中没有现成库存,且当前配置未开启自动购买', { throw createHttpError('背包中没有现成库存,且当前配置未开启自动购买', {
statusCode: 409, statusCode: 409,
errorCode: 'admin_task_kuaishou_cloud_auto_buy_disabled', errorCode: 'admin_task_kuaishou_cloud_auto_buy_disabled',
}) })
} }
const [asset, skuList] = await Promise.all([ const asset = await getCloudtentaclesAsset(cloudContext)
getCloudtentaclesAsset(cloudContext), const targetSku = resolvedBinding.skuItem
listCloudtentaclesSku(cloudContext),
])
const targetSku = (Array.isArray(skuList.items) ? skuList.items : []).find((item) => Number(item.id || 0) === flow.binding.skuId) || null
if (!targetSku) { if (!targetSku) {
throw createHttpError(`cloudtentacles 未找到 SKU ${flow.binding.skuId}`, { throw createHttpError(`cloudtentacles 未找到 SKU ${flowWithResolvedBinding.binding.skuId}`, {
statusCode: 404, statusCode: 404,
errorCode: 'admin_task_kuaishou_cloud_sku_not_found', errorCode: 'admin_task_kuaishou_cloud_sku_not_found',
}) })
@@ -756,7 +776,7 @@ export async function prepareAdminTaskKuaishouCloudFulfillment(taskId, session =
assetBefore = Number(asset.asset || 0) || 0 assetBefore = Number(asset.asset || 0) || 0
const targetPrice = Number(targetSku.price || 0) || 0 const targetPrice = Number(targetSku.price || 0) || 0
const requiredAsset = targetPrice + flow.purchase.minAssetReserve const requiredAsset = targetPrice + flowWithResolvedBinding.purchase.minAssetReserve
if (assetBefore < requiredAsset) { if (assetBefore < requiredAsset) {
throw createHttpError(`余额不足,当前 ${assetBefore},至少需要 ${requiredAsset}`, { throw createHttpError(`余额不足,当前 ${assetBefore},至少需要 ${requiredAsset}`, {
statusCode: 409, statusCode: 409,
@@ -766,7 +786,7 @@ export async function prepareAdminTaskKuaishouCloudFulfillment(taskId, session =
await buyCloudtentaclesSku({ await buyCloudtentaclesSku({
...cloudContext, ...cloudContext,
id: flow.binding.skuId, id: flowWithResolvedBinding.binding.skuId,
count: 1, count: 1,
}) })
purchaseTriggered = true purchaseTriggered = true
@@ -775,51 +795,23 @@ export async function prepareAdminTaskKuaishouCloudFulfillment(taskId, session =
assetAfter = Number(assetResult.asset || 0) || 0 assetAfter = Number(assetResult.asset || 0) || 0
} }
const appointed = await appointCloudtentaclesVirtualNumber({ const preparedBinding = await prepareKuaishouCloudBindResourceWithFallback({
...cloudContext, cloudContext,
key: flow.binding.vnKey, vnKeyCandidates,
})
const vnId = Number(appointed.item?.id || 0)
const vnPhone = String(appointed.item?.phone || '').trim()
if (!vnId || !vnPhone) {
throw createHttpError('申请虚拟号成功但返回数据不完整', {
statusCode: 502,
errorCode: 'admin_task_kuaishou_cloud_invalid_vn',
}) })
const vnId = preparedBinding.vnId
const vnPhone = preparedBinding.vnPhone
const bindUrlResult = {
bindUrl: preparedBinding.bindUrl,
} }
await generateCloudtentaclesLoginCode({
...cloudContext,
key: flow.binding.vnKey,
id: vnId,
})
const fetchedCode = await fetchCloudtentaclesVirtualNumberCode({
...cloudContext,
key: flow.binding.vnKey,
phone: vnPhone,
})
await verifyCloudtentaclesLoginCode({
...cloudContext,
key: flow.binding.vnKey,
id: vnId,
code: fetchedCode.code,
})
const bindUrlResult = await getCloudtentaclesBindUrl({
...cloudContext,
key: flow.binding.vnKey,
id: vnId,
})
const nextContext = { const nextContext = {
...taskContext, ...taskContext,
kuaishouCloudFulfillment: { kuaishouCloudFulfillment: {
...flow, ...flowWithResolvedBinding,
binding: { binding: {
...flow.binding, ...flowWithResolvedBinding.binding,
vnKey: preparedBinding.vnKey,
prepareStatus: 'ready', prepareStatus: 'ready',
vnId, vnId,
vnPhone, vnPhone,
@@ -827,12 +819,12 @@ export async function prepareAdminTaskKuaishouCloudFulfillment(taskId, session =
bindPreparedAt: now, bindPreparedAt: now,
}, },
purchase: { purchase: {
...flow.purchase, ...flowWithResolvedBinding.purchase,
usedKnapsack, usedKnapsack,
purchaseTriggered, purchaseTriggered,
assetBefore, assetBefore,
assetAfter, assetAfter,
purchaseAt: purchaseTriggered ? now : flow.purchase.purchaseAt, purchaseAt: purchaseTriggered ? now : flowWithResolvedBinding.purchase.purchaseAt,
}, },
}, },
} }
@@ -847,13 +839,15 @@ export async function prepareAdminTaskKuaishouCloudFulfillment(taskId, session =
}) })
await createTaskEvent(task.id, 'kuaishou_cloud_binding_prepared', { await createTaskEvent(task.id, 'kuaishou_cloud_binding_prepared', {
skuId: flow.binding.skuId, skuId: flowWithResolvedBinding.binding.skuId,
vnKey: flow.binding.vnKey, skuName: flowWithResolvedBinding.binding.skuName,
vnKey: preparedBinding.vnKey,
vnId, vnId,
vnPhoneMasked: maskPhone(vnPhone), vnPhoneMasked: maskPhone(vnPhone),
bindUrl: bindUrlResult.bindUrl, bindUrl: bindUrlResult.bindUrl,
purchaseTriggered, purchaseTriggered,
usedKnapsack, usedKnapsack,
resolvedByName: resolvedBinding.resolvedByName,
}, now) }, now)
return { return {
@@ -1173,6 +1167,196 @@ function normalizeKuaishouCloudFlow(value) {
} }
} }
function resolveKuaishouCloudBindingResources(flow, { skuItems = [], knapsackItems = [] } = {}) {
const normalizedSkuItems = Array.isArray(skuItems) ? skuItems.filter(isCloudSkuLikeItem) : []
const normalizedKnapsackItems = Array.isArray(knapsackItems) ? knapsackItems.filter(isCloudSkuLikeItem) : []
const currentSkuId = Number(flow?.binding?.skuId || 0) || 0
const currentVnKey = String(flow?.binding?.vnKey || '').trim()
const currentSkuName = String(flow?.binding?.skuName || '').trim()
const nameCandidates = collectKuaishouCloudNameCandidates(flow)
const skuItemById = currentSkuId > 0
? normalizedSkuItems.find((item) => Number(item.id || 0) === currentSkuId) || null
: null
const knapsackItemById = currentSkuId > 0
? normalizedKnapsackItems.find((item) => Number(item.id || 0) === currentSkuId) || null
: null
if (skuItemById || knapsackItemById) {
const matchedItem = skuItemById || knapsackItemById
return {
skuId: Number(matchedItem?.id || 0) || 0,
skuName: String(currentSkuName || matchedItem?.name || '').trim(),
vnKey: KUAISHOU_CLOUD_FIXED_VN_KEY,
skuItem: skuItemById,
knapsackItem: knapsackItemById,
resolvedByName: false,
}
}
const matchedSkuItem = findCloudItemByNames(normalizedSkuItems, nameCandidates)
const matchedKnapsackItem = findCloudItemByNames(
normalizedKnapsackItems,
nameCandidates,
matchedSkuItem ? Number(matchedSkuItem.id || 0) : 0,
)
const matchedItem = matchedSkuItem || matchedKnapsackItem
return {
skuId: Number(matchedItem?.id || 0) || 0,
skuName: String(currentSkuName || matchedItem?.name || '').trim(),
vnKey: KUAISHOU_CLOUD_FIXED_VN_KEY,
skuItem: matchedSkuItem,
knapsackItem: matchedKnapsackItem,
resolvedByName: Boolean(matchedItem),
}
}
/**
* @param {{ flow?: any, binding?: any }} [input]
*/
function resolveKuaishouCloudVnKeyCandidates(input = {}) {
return [KUAISHOU_CLOUD_FIXED_VN_KEY]
}
/**
* @param {{ cloudContext?: Record<string, unknown>, vnKeyCandidates?: string[] }} [input]
*/
async function prepareKuaishouCloudBindResourceWithFallback(input = {}) {
const { cloudContext = {}, vnKeyCandidates = [] } = input
const candidates = Array.isArray(vnKeyCandidates) ? vnKeyCandidates : []
let lastError = null
for (const vnKey of candidates) {
let vnId = 0
let vnPhone = ''
try {
const appointed = await appointCloudtentaclesVirtualNumber({
...cloudContext,
key: vnKey,
})
vnId = Number(appointed.item?.id || 0)
vnPhone = String(appointed.item?.phone || '').trim()
if (!vnId || !vnPhone) {
throw createHttpError('申请虚拟号成功但返回数据不完整', {
statusCode: 502,
errorCode: 'admin_task_kuaishou_cloud_invalid_vn',
})
}
await generateCloudtentaclesLoginCode({
...cloudContext,
key: vnKey,
id: vnId,
})
const fetchedCode = await fetchCloudtentaclesVirtualNumberCode({
...cloudContext,
key: vnKey,
phone: vnPhone,
})
await verifyCloudtentaclesLoginCode({
...cloudContext,
key: vnKey,
id: vnId,
code: fetchedCode.code,
})
const bindUrlResult = await getCloudtentaclesBindUrl({
...cloudContext,
key: vnKey,
id: vnId,
})
return {
vnKey,
vnId,
vnPhone,
bindUrl: String(bindUrlResult.bindUrl || '').trim(),
}
} catch (error) {
lastError = error
if (vnId > 0) {
try {
await backCloudtentaclesVirtualNumber({
...cloudContext,
key: vnKey,
id: vnId,
})
} catch {
// 兜底退号失败时保留原始错误,避免吞掉主链路异常
}
}
if (!isRecoverableKuaishouCloudVnKeyError(error)) {
throw error
}
}
}
throw lastError || createHttpError('没有找到可用的 VN Key', {
statusCode: 409,
errorCode: 'admin_task_kuaishou_cloud_missing_binding_config',
})
}
function collectKuaishouCloudNameCandidates(flow) {
return Array.from(new Set([
String(flow?.binding?.skuName || '').trim(),
String(flow?.internalSkuName || '').trim(),
String(flow?.internalSkuCode || '').trim(),
].filter(Boolean)))
}
function findCloudItemByNames(items, nameCandidates, preferredId = 0) {
const normalizedItems = Array.isArray(items) ? items : []
const normalizedNames = nameCandidates
.map((item) => ({
raw: String(item || '').trim(),
normalized: normalizeProductName(item),
}))
.filter((item) => item.raw && item.normalized)
if (normalizedNames.length === 0 || normalizedItems.length === 0) {
return preferredId > 0
? normalizedItems.find((item) => Number(item.id || 0) === preferredId) || null
: null
}
if (preferredId > 0) {
const preferred = normalizedItems.find((item) => Number(item.id || 0) === preferredId) || null
if (preferred) {
return preferred
}
}
const exactMatches = normalizedItems.filter((item) => {
const itemName = normalizeProductName(item.name)
return normalizedNames.some((candidate) => candidate.normalized === itemName)
})
if (exactMatches.length > 0) {
return exactMatches[0]
}
const partialMatches = normalizedItems.filter((item) => {
const itemName = normalizeProductName(item.name)
return normalizedNames.some((candidate) => itemName.includes(candidate.normalized) || candidate.normalized.includes(itemName))
})
if (partialMatches.length > 0) {
return partialMatches.sort((left, right) => String(left.name || '').length - String(right.name || '').length)[0]
}
return null
}
function isCloudSkuLikeItem(item) {
return Boolean(item) && typeof item === 'object' && Number(item.id || 0) > 0
}
function maskPhone(value) { function maskPhone(value) {
const text = String(value || '').trim() const text = String(value || '').trim()
if (!text) { if (!text) {
@@ -1258,6 +1442,13 @@ function isRecoverableTaskSessionCloseError(error) {
return errorCode === 'session_not_found' || errorCode === 'session_closed' return errorCode === 'session_not_found' || errorCode === 'session_closed'
} }
function isRecoverableKuaishouCloudVnKeyError(error) {
const errorCode = String(error?.errorCode || error?.code || '').trim()
const errorMessage = String(error?.message || '').trim()
return errorCode === 'cloudtentacles_vn_bind_url_failed'
&& errorMessage.includes('不支持的游戏类型')
}
function normalizeManualDispatchOutcome(value) { function normalizeManualDispatchOutcome(value) {
const normalized = String(value || '').trim().toLowerCase() const normalized = String(value || '').trim().toLowerCase()
@@ -101,7 +101,7 @@ export async function syncDeliveryTasksForOrder(order, orderItems) {
cloudSourceKey: String(fulfillmentConfig.cloudtentacles?.cloudSourceKey || 'default').trim() || 'default', cloudSourceKey: String(fulfillmentConfig.cloudtentacles?.cloudSourceKey || 'default').trim() || 'default',
skuId: Number(fulfillmentConfig.cloudtentacles?.skuId || 0) || 0, skuId: Number(fulfillmentConfig.cloudtentacles?.skuId || 0) || 0,
skuName: String(fulfillmentConfig.cloudtentacles?.skuName || '').trim(), skuName: String(fulfillmentConfig.cloudtentacles?.skuName || '').trim(),
vnKey: String(fulfillmentConfig.cloudtentacles?.vnKey || '').trim(), vnKey: '1',
vnId: 0, vnId: 0,
vnPhone: '', vnPhone: '',
bindUrl: '', bindUrl: '',
@@ -45,7 +45,7 @@ export function mapKuaishouCloudFulfillmentItemsToBindings(config = {}) {
cloudSourceKey: String(item.cloudSourceKey || 'default').trim() || 'default', cloudSourceKey: String(item.cloudSourceKey || 'default').trim() || 'default',
skuId: normalizePositiveInteger(item.cloudSkuId), skuId: normalizePositiveInteger(item.cloudSkuId),
skuName: String(item.cloudSkuName || '').trim(), skuName: String(item.cloudSkuName || '').trim(),
vnKey: String(item.vnKey || '').trim(), vnKey: '1',
autoBuyEnabled: item.autoBuyEnabled !== false, autoBuyEnabled: item.autoBuyEnabled !== false,
minAssetReserve: normalizeNonNegativeInteger(item.minAssetReserve, 0), minAssetReserve: normalizeNonNegativeInteger(item.minAssetReserve, 0),
autoReturnNumberAfterDispatch: item.autoReturnNumberAfterDispatch === true, autoReturnNumberAfterDispatch: item.autoReturnNumberAfterDispatch === true,
@@ -99,7 +99,6 @@ function normalizeKuaishouCloudFulfillmentItem(rawValue) {
const internalSkuCode = String(rawValue.internalSkuCode || '').trim() const internalSkuCode = String(rawValue.internalSkuCode || '').trim()
const cloudSkuId = normalizePositiveInteger(rawValue.cloudSkuId) const cloudSkuId = normalizePositiveInteger(rawValue.cloudSkuId)
const vnKey = String(rawValue.vnKey || '').trim()
const externalSkuCode = String(rawValue.externalSkuCode || '').trim() const externalSkuCode = String(rawValue.externalSkuCode || '').trim()
const externalItemId = String(rawValue.externalItemId || '').trim() const externalItemId = String(rawValue.externalItemId || '').trim()
const externalSkuName = String(rawValue.externalSkuName || '').trim() const externalSkuName = String(rawValue.externalSkuName || '').trim()
@@ -108,7 +107,7 @@ function normalizeKuaishouCloudFulfillmentItem(rawValue) {
return null return null
} }
if (!cloudSkuId || !vnKey) { if (!cloudSkuId) {
return null return null
} }
@@ -132,7 +131,7 @@ function normalizeKuaishouCloudFulfillmentItem(rawValue) {
cloudSourceKey: String(rawValue.cloudSourceKey || 'default').trim() || 'default', cloudSourceKey: String(rawValue.cloudSourceKey || 'default').trim() || 'default',
cloudSkuId, cloudSkuId,
cloudSkuName: String(rawValue.cloudSkuName || '').trim(), cloudSkuName: String(rawValue.cloudSkuName || '').trim(),
vnKey, vnKey: '1',
autoBuyEnabled: rawValue.autoBuyEnabled !== false, autoBuyEnabled: rawValue.autoBuyEnabled !== false,
minAssetReserve: normalizeNonNegativeInteger(rawValue.minAssetReserve, 0), minAssetReserve: normalizeNonNegativeInteger(rawValue.minAssetReserve, 0),
autoReturnNumberAfterDispatch: rawValue.autoReturnNumberAfterDispatch === true, autoReturnNumberAfterDispatch: rawValue.autoReturnNumberAfterDispatch === true,
@@ -59,7 +59,7 @@ function createEmptyItem(): EditableItem {
cloudSourceKey: 'default', cloudSourceKey: 'default',
cloudSkuId: 0, cloudSkuId: 0,
cloudSkuName: '', cloudSkuName: '',
vnKey: '', vnKey: '1',
autoBuyEnabled: true, autoBuyEnabled: true,
minAssetReserve: 0, minAssetReserve: 0,
autoReturnNumberAfterDispatch: false, autoReturnNumberAfterDispatch: false,
@@ -157,7 +157,7 @@ function isItemComplete(item: EditableItem) {
return Boolean( return Boolean(
item.internalSkuCode.trim() item.internalSkuCode.trim()
&& Number(item.cloudSkuId || 0) > 0 && Number(item.cloudSkuId || 0) > 0
&& item.vnKey.trim() && true
&& hasExternalMatch(item), && hasExternalMatch(item),
) )
} }
@@ -177,10 +177,6 @@ function resolveValidation(item: EditableItem, index: number): ValidationState {
return { localId: item.localId, message: `${title} 需要填写 cloud SKU ID` } return { localId: item.localId, message: `${title} 需要填写 cloud SKU ID` }
} }
if (!item.vnKey.trim()) {
return { localId: item.localId, message: `${title} 需要填写虚拟号 VN Key` }
}
if (!hasExternalMatch(item)) { if (!hasExternalMatch(item)) {
return { localId: item.localId, message: `${title} 至少填写一种外部匹配条件` } return { localId: item.localId, message: `${title} 至少填写一种外部匹配条件` }
} }
@@ -466,7 +462,7 @@ onMounted(loadConfigs)
</label> </label>
<label class="field-block"> <label class="field-block">
<span>虚拟号 VN Key</span> <span>虚拟号 VN Key</span>
<el-input v-model="item.vnKey" maxlength="40" placeholder="必填,例如 1" /> <el-input :model-value="'1'" disabled placeholder="固定为 1" />
</label> </label>
</div> </div>
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 245 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB