feat(frontend): add P2 quality gates
This commit is contained in:
@@ -88,9 +88,15 @@ const lookupForm = reactive({
|
||||
|
||||
const ruleMetrics = computed(() => {
|
||||
const completedCount = bindings.value.filter(isEditableBindingComplete).length
|
||||
const manualCount = bindings.value.filter((item) => item.profileKey.trim() === 'manual_review').length
|
||||
const assistedCount = bindings.value.filter((item) => item.profileKey.trim() === 'tencent_claim_assisted').length
|
||||
const kuaishouCloudCount = bindings.value.filter((item) => item.profileKey.trim() === 'kuaishou_ct_assisted').length
|
||||
const manualCount = bindings.value.filter(
|
||||
(item) => item.profileKey.trim() === 'manual_review',
|
||||
).length
|
||||
const assistedCount = bindings.value.filter(
|
||||
(item) => item.profileKey.trim() === 'tencent_claim_assisted',
|
||||
).length
|
||||
const kuaishouCloudCount = bindings.value.filter(
|
||||
(item) => item.profileKey.trim() === 'kuaishou_ct_assisted',
|
||||
).length
|
||||
|
||||
return {
|
||||
totalCount: bindings.value.length,
|
||||
@@ -102,13 +108,20 @@ const ruleMetrics = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const pendingObservedCount = computed(() => observedProducts.value.filter((item) => !item.configured).length)
|
||||
const pendingObservedCount = computed(
|
||||
() => observedProducts.value.filter((item) => !item.configured).length,
|
||||
)
|
||||
|
||||
type ImportableProductCandidate = Pick<
|
||||
AdminObservedProductItem,
|
||||
'provider' | 'platform' | 'shopId' | 'shopName' | 'externalSkuCode' | 'externalItemId' | 'externalSkuName'
|
||||
> & {
|
||||
}
|
||||
| 'provider'
|
||||
| 'platform'
|
||||
| 'shopId'
|
||||
| 'shopName'
|
||||
| 'externalSkuCode'
|
||||
| 'externalItemId'
|
||||
| 'externalSkuName'
|
||||
> & {}
|
||||
|
||||
function createEmptyBinding(): EditableBinding {
|
||||
return {
|
||||
@@ -148,12 +161,20 @@ function mapEditableBinding(item: AdminFulfillmentBindingConfigItem): EditableBi
|
||||
}
|
||||
}
|
||||
|
||||
function hasMatchCondition(item: Pick<EditableBinding, 'externalSkuCode' | 'externalItemId' | 'externalSkuName'>) {
|
||||
return Boolean(item.externalSkuCode.trim() || item.externalItemId.trim() || item.externalSkuName.trim())
|
||||
function hasMatchCondition(
|
||||
item: Pick<EditableBinding, 'externalSkuCode' | 'externalItemId' | 'externalSkuName'>,
|
||||
) {
|
||||
return Boolean(
|
||||
item.externalSkuCode.trim() || item.externalItemId.trim() || item.externalSkuName.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
function isEditableBindingComplete(item: EditableBinding) {
|
||||
return Boolean(item.skuCode.trim() && hasMatchCondition(item) && VALID_PROFILE_KEYS.has(item.profileKey.trim() || 'manual_review'))
|
||||
return Boolean(
|
||||
item.skuCode.trim() &&
|
||||
hasMatchCondition(item) &&
|
||||
VALID_PROFILE_KEYS.has(item.profileKey.trim() || 'manual_review'),
|
||||
)
|
||||
}
|
||||
|
||||
function buildCollapsedIds(items: EditableBinding[]) {
|
||||
@@ -304,23 +325,27 @@ function normalizeBindingForSave(item: EditableBinding): SaveBindingPayload {
|
||||
|
||||
function hasBindingContent(item: SaveBindingPayload) {
|
||||
return Boolean(
|
||||
item.platform
|
||||
|| item.shopId
|
||||
|| item.shopName
|
||||
|| item.skuCode
|
||||
|| item.skuName
|
||||
|| item.match.externalSkuCode
|
||||
|| item.match.externalItemId
|
||||
|| item.match.externalSkuName
|
||||
|| item.match.config.resolvedSkuName
|
||||
|| item.provider !== 'agiso'
|
||||
|| item.profileKey !== 'manual_review'
|
||||
|| item.priority !== 100
|
||||
|| item.enabled !== true,
|
||||
item.platform ||
|
||||
item.shopId ||
|
||||
item.shopName ||
|
||||
item.skuCode ||
|
||||
item.skuName ||
|
||||
item.match.externalSkuCode ||
|
||||
item.match.externalItemId ||
|
||||
item.match.externalSkuName ||
|
||||
item.match.config.resolvedSkuName ||
|
||||
item.provider !== 'agiso' ||
|
||||
item.profileKey !== 'manual_review' ||
|
||||
item.priority !== 100 ||
|
||||
item.enabled !== true,
|
||||
)
|
||||
}
|
||||
|
||||
function resolveBindingValidationState(item: SaveBindingPayload, index: number, bindingId: string): ValidationState {
|
||||
function resolveBindingValidationState(
|
||||
item: SaveBindingPayload,
|
||||
index: number,
|
||||
bindingId: string,
|
||||
): ValidationState {
|
||||
const label = `第 ${index + 1} 条规则`
|
||||
|
||||
if (!item.skuCode) {
|
||||
@@ -384,7 +409,10 @@ function isBindingInvalid(bindingId: string) {
|
||||
return validationState.value?.bindingId === bindingId
|
||||
}
|
||||
|
||||
function isFieldInvalid(bindingId: string, field: ValidationField | 'externalSkuCode' | 'externalItemId' | 'externalSkuName') {
|
||||
function isFieldInvalid(
|
||||
bindingId: string,
|
||||
field: ValidationField | 'externalSkuCode' | 'externalItemId' | 'externalSkuName',
|
||||
) {
|
||||
if (validationState.value?.bindingId !== bindingId) {
|
||||
return false
|
||||
}
|
||||
@@ -552,14 +580,20 @@ onMounted(loadConfigs)
|
||||
</div>
|
||||
<div class="section-action-group">
|
||||
<el-button round @click="addBinding">新增规则</el-button>
|
||||
<el-button :loading="saving" round type="primary" @click="saveConfigs">保存配置</el-button>
|
||||
<el-button :loading="saving" round type="primary" @click="saveConfigs"
|
||||
>保存配置</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="validationState?.message" class="validation-banner">{{ validationState.message }}</p>
|
||||
<p v-if="validationState?.message" class="validation-banner">
|
||||
{{ validationState.message }}
|
||||
</p>
|
||||
|
||||
<div v-if="bindings.length === 0" class="empty-inline">当前还没有履约规则,先新增一条。</div>
|
||||
<div v-if="bindings.length === 0" class="empty-inline">
|
||||
当前还没有履约规则,先新增一条。
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="(binding, index) in bindings"
|
||||
@@ -571,21 +605,42 @@ onMounted(loadConfigs)
|
||||
<div class="binding-summary">
|
||||
<div class="binding-title-row">
|
||||
<strong>{{ getBindingTitle(binding, index) }}</strong>
|
||||
<span :class="['binding-status-chip', { 'binding-status-chip--done': isEditableBindingComplete(binding) }]">
|
||||
<span
|
||||
:class="[
|
||||
'binding-status-chip',
|
||||
{ 'binding-status-chip--done': isEditableBindingComplete(binding) },
|
||||
]"
|
||||
>
|
||||
{{ getBindingStatusLabel(binding) }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="binding-subtle">{{ getBindingSummary(binding) }}</span>
|
||||
</div>
|
||||
<button class="collapse-button" type="button" @click="toggleBindingCollapsed(binding.id)">
|
||||
<button
|
||||
class="collapse-button"
|
||||
type="button"
|
||||
@click="toggleBindingCollapsed(binding.id)"
|
||||
>
|
||||
{{ isBindingCollapsed(binding.id) ? '展开' : '折叠' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div v-if="isBindingCollapsed(binding.id)" class="binding-collapsed-preview">
|
||||
<span>外部商品:{{ binding.externalSkuName || binding.externalSkuCode || binding.externalItemId || '-' }}</span>
|
||||
<span
|
||||
>外部商品:{{
|
||||
binding.externalSkuName ||
|
||||
binding.externalSkuCode ||
|
||||
binding.externalItemId ||
|
||||
'-'
|
||||
}}</span
|
||||
>
|
||||
<span>内部履约 SKU:{{ binding.skuCode || '-' }}</span>
|
||||
<span>履约方式:{{ PROFILE_OPTIONS.find((item) => item.value === binding.profileKey)?.label || binding.profileKey }}</span>
|
||||
<span
|
||||
>履约方式:{{
|
||||
PROFILE_OPTIONS.find((item) => item.value === binding.profileKey)?.label ||
|
||||
binding.profileKey
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div v-else class="binding-grid">
|
||||
@@ -613,7 +668,10 @@ onMounted(loadConfigs)
|
||||
<span>店铺 ID</span>
|
||||
<input
|
||||
v-model="binding.shopId"
|
||||
:class="['text-input', { 'text-input--invalid': isFieldInvalid(binding.id, 'shopId') }]"
|
||||
:class="[
|
||||
'text-input',
|
||||
{ 'text-input--invalid': isFieldInvalid(binding.id, 'shopId') },
|
||||
]"
|
||||
data-field="shopId"
|
||||
placeholder="留空表示跨店铺通用"
|
||||
@input="clearValidationState"
|
||||
@@ -632,14 +690,23 @@ onMounted(loadConfigs)
|
||||
|
||||
<label class="field-block">
|
||||
<span>优先级</span>
|
||||
<input v-model.number="binding.priority" class="text-input" type="number" min="1" @input="clearValidationState" />
|
||||
<input
|
||||
v-model.number="binding.priority"
|
||||
class="text-input"
|
||||
type="number"
|
||||
min="1"
|
||||
@input="clearValidationState"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="field-block">
|
||||
<span>外部 SKU 编码</span>
|
||||
<input
|
||||
v-model="binding.externalSkuCode"
|
||||
:class="['text-input', { 'text-input--invalid': isFieldInvalid(binding.id, 'externalSkuCode') }]"
|
||||
:class="[
|
||||
'text-input',
|
||||
{ 'text-input--invalid': isFieldInvalid(binding.id, 'externalSkuCode') },
|
||||
]"
|
||||
data-field="externalSkuCode"
|
||||
placeholder="例如 6046726460016"
|
||||
@input="clearValidationState"
|
||||
@@ -650,7 +717,10 @@ onMounted(loadConfigs)
|
||||
<span>外部商品 ID</span>
|
||||
<input
|
||||
v-model="binding.externalItemId"
|
||||
:class="['text-input', { 'text-input--invalid': isFieldInvalid(binding.id, 'externalItemId') }]"
|
||||
:class="[
|
||||
'text-input',
|
||||
{ 'text-input--invalid': isFieldInvalid(binding.id, 'externalItemId') },
|
||||
]"
|
||||
data-field="externalItemId"
|
||||
placeholder="例如 1033324289962"
|
||||
@input="clearValidationState"
|
||||
@@ -661,7 +731,10 @@ onMounted(loadConfigs)
|
||||
<span>外部商品名</span>
|
||||
<input
|
||||
v-model="binding.externalSkuName"
|
||||
:class="['text-input', { 'text-input--invalid': isFieldInvalid(binding.id, 'externalSkuName') }]"
|
||||
:class="[
|
||||
'text-input',
|
||||
{ 'text-input--invalid': isFieldInvalid(binding.id, 'externalSkuName') },
|
||||
]"
|
||||
data-field="externalSkuName"
|
||||
placeholder="例如 奥利奥动作"
|
||||
@input="clearValidationState"
|
||||
@@ -672,24 +745,37 @@ onMounted(loadConfigs)
|
||||
<span>内部履约 SKU</span>
|
||||
<input
|
||||
v-model="binding.skuCode"
|
||||
:class="['text-input', { 'text-input--invalid': isFieldInvalid(binding.id, 'skuCode') }]"
|
||||
:class="[
|
||||
'text-input',
|
||||
{ 'text-input--invalid': isFieldInvalid(binding.id, 'skuCode') },
|
||||
]"
|
||||
data-field="skuCode"
|
||||
placeholder="例如 sjz_hdl_test01"
|
||||
@input="clearValidationState"
|
||||
/>
|
||||
<small class="field-help">填你们系统内部统一 SKU。相同履约商品,尽量跨平台共用同一个内部 SKU。</small>
|
||||
<small class="field-help"
|
||||
>填你们系统内部统一 SKU。相同履约商品,尽量跨平台共用同一个内部 SKU。</small
|
||||
>
|
||||
</label>
|
||||
|
||||
<label class="field-block">
|
||||
<span>内部商品名</span>
|
||||
<input v-model="binding.skuName" class="text-input" placeholder="用于后台展示" @input="clearValidationState" />
|
||||
<input
|
||||
v-model="binding.skuName"
|
||||
class="text-input"
|
||||
placeholder="用于后台展示"
|
||||
@input="clearValidationState"
|
||||
/>
|
||||
</label>
|
||||
|
||||
<label class="field-block">
|
||||
<span>履约方式</span>
|
||||
<select
|
||||
v-model="binding.profileKey"
|
||||
:class="['text-input', { 'text-input--invalid': isFieldInvalid(binding.id, 'profileKey') }]"
|
||||
:class="[
|
||||
'text-input',
|
||||
{ 'text-input--invalid': isFieldInvalid(binding.id, 'profileKey') },
|
||||
]"
|
||||
data-field="profileKey"
|
||||
@change="clearValidationState"
|
||||
>
|
||||
@@ -710,7 +796,9 @@ onMounted(loadConfigs)
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<p v-if="isBindingInvalid(binding.id)" class="binding-inline-error">{{ validationState?.message }}</p>
|
||||
<p v-if="isBindingInvalid(binding.id)" class="binding-inline-error">
|
||||
{{ validationState?.message }}
|
||||
</p>
|
||||
|
||||
<div class="binding-actions">
|
||||
<label class="checkbox-line">
|
||||
@@ -764,7 +852,9 @@ onMounted(loadConfigs)
|
||||
|
||||
<div class="field-block field-block--action">
|
||||
<span>操作</span>
|
||||
<el-button :loading="lookupLoading" round type="primary" @click="lookupOrderProducts">查询订单商品</el-button>
|
||||
<el-button :loading="lookupLoading" round type="primary" @click="lookupOrderProducts"
|
||||
>查询订单商品</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -778,17 +868,27 @@ onMounted(loadConfigs)
|
||||
<div class="lookup-summary-item">
|
||||
<span class="summary-label">订单</span>
|
||||
<strong>{{ lookupResult.order.platformOrderId }}</strong>
|
||||
<span class="cell-subtle">{{ lookupResult.order.shopName || lookupResult.order.shopId || '-' }}</span>
|
||||
<span class="cell-subtle">{{
|
||||
lookupResult.order.shopName || lookupResult.order.shopId || '-'
|
||||
}}</span>
|
||||
</div>
|
||||
<div class="lookup-summary-item">
|
||||
<span class="summary-label">买家</span>
|
||||
<strong>{{ lookupResult.order.buyerName || '-' }}</strong>
|
||||
<span class="cell-subtle">支付时间:{{ formatAdminDateTime(lookupResult.order.paidAt) }}</span>
|
||||
<span class="cell-subtle"
|
||||
>支付时间:{{ formatAdminDateTime(lookupResult.order.paidAt) }}</span
|
||||
>
|
||||
</div>
|
||||
<div class="lookup-summary-item">
|
||||
<span class="summary-label">金额</span>
|
||||
<strong>{{ lookupResult.order.totalAmount || '0.00' }}</strong>
|
||||
<span class="cell-subtle">补查结果:{{ lookupResult.order.enriched ? '已补全' : lookupResult.order.enrichReason || '已返回' }}</span>
|
||||
<span class="cell-subtle"
|
||||
>补查结果:{{
|
||||
lookupResult.order.enriched
|
||||
? '已补全'
|
||||
: lookupResult.order.enrichReason || '已返回'
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -805,7 +905,13 @@ onMounted(loadConfigs)
|
||||
<tr v-for="item in lookupResult.items" :key="item.lineId">
|
||||
<td>
|
||||
<div class="cell-stack">
|
||||
<strong>{{ item.externalSkuName || item.itemTitle || item.externalSkuCode || item.externalItemId || '-' }}</strong>
|
||||
<strong>{{
|
||||
item.externalSkuName ||
|
||||
item.itemTitle ||
|
||||
item.externalSkuCode ||
|
||||
item.externalItemId ||
|
||||
'-'
|
||||
}}</strong>
|
||||
<span class="cell-subtle">SKU: {{ item.externalSkuCode || '-' }}</span>
|
||||
<span class="cell-subtle">ItemId: {{ item.externalItemId || '-' }}</span>
|
||||
</div>
|
||||
@@ -815,7 +921,8 @@ onMounted(loadConfigs)
|
||||
<div class="cell-stack">
|
||||
<strong>{{ item.configured ? '已配置' : '未配置' }}</strong>
|
||||
<span v-if="item.matchedBinding" class="cell-subtle">
|
||||
{{ item.matchedBinding.skuName || item.matchedBinding.skuCode || '-' }} / {{ item.matchedBinding.profileKey || '-' }}
|
||||
{{ item.matchedBinding.skuName || item.matchedBinding.skuCode || '-' }} /
|
||||
{{ item.matchedBinding.profileKey || '-' }}
|
||||
</span>
|
||||
<span v-else class="cell-subtle">可直接导入成新规则草稿</span>
|
||||
</div>
|
||||
@@ -862,16 +969,23 @@ onMounted(loadConfigs)
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="item in observedProducts" :key="`${item.provider}:${item.platform}:${item.shopId}:${item.externalSkuCode}:${item.externalItemId}:${item.externalSkuName}`">
|
||||
<tr
|
||||
v-for="item in observedProducts"
|
||||
:key="`${item.provider}:${item.platform}:${item.shopId}:${item.externalSkuCode}:${item.externalItemId}:${item.externalSkuName}`"
|
||||
>
|
||||
<td>
|
||||
<div class="cell-stack">
|
||||
<strong>{{ item.platform || '-' }}</strong>
|
||||
<span class="cell-subtle">{{ item.provider }} · {{ item.shopName || item.shopId || '全店铺' }}</span>
|
||||
<span class="cell-subtle"
|
||||
>{{ item.provider }} · {{ item.shopName || item.shopId || '全店铺' }}</span
|
||||
>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<div class="cell-stack">
|
||||
<strong>{{ item.externalSkuName || item.externalSkuCode || item.externalItemId || '-' }}</strong>
|
||||
<strong>{{
|
||||
item.externalSkuName || item.externalSkuCode || item.externalItemId || '-'
|
||||
}}</strong>
|
||||
<span class="cell-subtle">SKU: {{ item.externalSkuCode || '-' }}</span>
|
||||
<span class="cell-subtle">ItemId: {{ item.externalItemId || '-' }}</span>
|
||||
</div>
|
||||
|
||||
@@ -724,7 +724,9 @@
|
||||
background: #fff;
|
||||
color: #1d3555;
|
||||
outline: none;
|
||||
transition: border-color 0.16s ease, box-shadow 0.16s ease;
|
||||
transition:
|
||||
border-color 0.16s ease,
|
||||
box-shadow 0.16s ease;
|
||||
}
|
||||
|
||||
.text-input:focus {
|
||||
@@ -969,4 +971,4 @@
|
||||
.field-wide {
|
||||
grid-column: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,18 +65,26 @@ const metrics = computed(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const filteredItems = computed(() => items.value.filter((item) => matchesRuleFilter(item, ruleFilter.value)))
|
||||
const filteredItems = computed(() =>
|
||||
items.value.filter((item) => matchesRuleFilter(item, ruleFilter.value)),
|
||||
)
|
||||
|
||||
const ruleFilterOptions = computed<Array<{ value: RuleFilter; label: string; count: number }>>(() => [
|
||||
{ value: 'all', label: '全部', count: metrics.value.total },
|
||||
{ value: 'draft', label: '待完善', count: metrics.value.draftCount },
|
||||
{ value: 'ready', label: '可投产', count: metrics.value.readyCount },
|
||||
{ value: 'disabled', label: '已停用', count: metrics.value.disabledCount },
|
||||
])
|
||||
const ruleFilterOptions = computed<Array<{ value: RuleFilter; label: string; count: number }>>(
|
||||
() => [
|
||||
{ value: 'all', label: '全部', count: metrics.value.total },
|
||||
{ value: 'draft', label: '待完善', count: metrics.value.draftCount },
|
||||
{ value: 'ready', label: '可投产', count: metrics.value.readyCount },
|
||||
{ value: 'disabled', label: '已停用', count: metrics.value.disabledCount },
|
||||
],
|
||||
)
|
||||
|
||||
const ninetyoneLookupMetrics = computed(() => {
|
||||
const importedCount = ninetyoneLookupResults.value.filter((item) => isNinetyoneProductImported(item)).length
|
||||
const pendingCount = ninetyoneLookupResults.value.filter((item) => item.orderStatus === 'pending_config').length
|
||||
const importedCount = ninetyoneLookupResults.value.filter((item) =>
|
||||
isNinetyoneProductImported(item),
|
||||
).length
|
||||
const pendingCount = ninetyoneLookupResults.value.filter(
|
||||
(item) => item.orderStatus === 'pending_config',
|
||||
).length
|
||||
return {
|
||||
total: ninetyoneLookupResults.value.length,
|
||||
importedCount,
|
||||
@@ -116,7 +124,10 @@ function createEmptyItem(): EditableItem {
|
||||
}
|
||||
|
||||
function mapEditableItem(item: AdminKuaishouCloudFulfillmentItem): EditableItem {
|
||||
const matchedShop = findKuaishouConsumeShop(item.kuaishouConsumeShopId, item.kuaishouConsumeShopName)
|
||||
const matchedShop = findKuaishouConsumeShop(
|
||||
item.kuaishouConsumeShopId,
|
||||
item.kuaishouConsumeShopName,
|
||||
)
|
||||
return {
|
||||
localId: crypto.randomUUID(),
|
||||
id: item.id || crypto.randomUUID(),
|
||||
@@ -146,7 +157,10 @@ function mapEditableItem(item: AdminKuaishouCloudFulfillmentItem): EditableItem
|
||||
}
|
||||
|
||||
function mapSaveItem(item: EditableItem): AdminKuaishouCloudFulfillmentItem {
|
||||
const matchedShop = findKuaishouConsumeShop(item.kuaishouConsumeShopId, item.kuaishouConsumeShopName)
|
||||
const matchedShop = findKuaishouConsumeShop(
|
||||
item.kuaishouConsumeShopId,
|
||||
item.kuaishouConsumeShopName,
|
||||
)
|
||||
return {
|
||||
id: item.id.trim() || item.internalSkuCode.trim() || item.localId,
|
||||
enabled: item.enabled,
|
||||
@@ -174,43 +188,47 @@ function mapSaveItem(item: EditableItem): AdminKuaishouCloudFulfillmentItem {
|
||||
}
|
||||
}
|
||||
|
||||
function hasExternalMatch(item: Pick<EditableItem, 'externalSkuCode' | 'externalItemId' | 'externalSkuName'>) {
|
||||
return Boolean(item.externalSkuCode.trim() || item.externalItemId.trim() || item.externalSkuName.trim())
|
||||
function hasExternalMatch(
|
||||
item: Pick<EditableItem, 'externalSkuCode' | 'externalItemId' | 'externalSkuName'>,
|
||||
) {
|
||||
return Boolean(
|
||||
item.externalSkuCode.trim() || item.externalItemId.trim() || item.externalSkuName.trim(),
|
||||
)
|
||||
}
|
||||
|
||||
function hasMeaningfulContent(item: EditableItem) {
|
||||
return Boolean(
|
||||
item.internalSkuCode.trim()
|
||||
|| item.internalSkuName.trim()
|
||||
|| item.externalSkuCode.trim()
|
||||
|| item.externalItemId.trim()
|
||||
|| item.externalSkuName.trim()
|
||||
|| item.resolvedSkuName.trim()
|
||||
|| item.cloudSourceKey.trim() !== 'default'
|
||||
|| Number(item.cloudSkuId || 0) > 0
|
||||
|| item.cloudSkuName.trim()
|
||||
|| item.vnKey.trim()
|
||||
|| item.shopId.trim()
|
||||
|| item.notes.trim()
|
||||
|| item.provider.trim() !== '91kaquan'
|
||||
|| item.platform.trim() !== 'kuaishou'
|
||||
|| item.priority !== 100
|
||||
|| item.autoBuyEnabled !== true
|
||||
|| item.minAssetReserve !== 0
|
||||
|| item.autoReturnNumberAfterDispatch
|
||||
|| item.autoConsumeAfterDispatch
|
||||
|| item.kuaishouConsumeShopId.trim()
|
||||
|| item.kuaishouConsumeShopName.trim()
|
||||
|| item.enabled !== true,
|
||||
item.internalSkuCode.trim() ||
|
||||
item.internalSkuName.trim() ||
|
||||
item.externalSkuCode.trim() ||
|
||||
item.externalItemId.trim() ||
|
||||
item.externalSkuName.trim() ||
|
||||
item.resolvedSkuName.trim() ||
|
||||
item.cloudSourceKey.trim() !== 'default' ||
|
||||
Number(item.cloudSkuId || 0) > 0 ||
|
||||
item.cloudSkuName.trim() ||
|
||||
item.vnKey.trim() ||
|
||||
item.shopId.trim() ||
|
||||
item.notes.trim() ||
|
||||
item.provider.trim() !== '91kaquan' ||
|
||||
item.platform.trim() !== 'kuaishou' ||
|
||||
item.priority !== 100 ||
|
||||
item.autoBuyEnabled !== true ||
|
||||
item.minAssetReserve !== 0 ||
|
||||
item.autoReturnNumberAfterDispatch ||
|
||||
item.autoConsumeAfterDispatch ||
|
||||
item.kuaishouConsumeShopId.trim() ||
|
||||
item.kuaishouConsumeShopName.trim() ||
|
||||
item.enabled !== true,
|
||||
)
|
||||
}
|
||||
|
||||
function isItemComplete(item: EditableItem) {
|
||||
return Boolean(
|
||||
item.internalSkuCode.trim()
|
||||
&& Number(item.cloudSkuId || 0) > 0
|
||||
&& (!item.autoConsumeAfterDispatch || Boolean(item.kuaishouConsumeShopId.trim()))
|
||||
&& hasExternalMatch(item),
|
||||
item.internalSkuCode.trim() &&
|
||||
Number(item.cloudSkuId || 0) > 0 &&
|
||||
(!item.autoConsumeAfterDispatch || Boolean(item.kuaishouConsumeShopId.trim())) &&
|
||||
hasExternalMatch(item),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -241,7 +259,12 @@ function resolveValidation(item: EditableItem, index: number): ValidationState {
|
||||
}
|
||||
|
||||
function getCardTitle(item: EditableItem, index: number) {
|
||||
return item.internalSkuName.trim() || item.externalSkuName.trim() || item.internalSkuCode.trim() || `规则 ${index + 1}`
|
||||
return (
|
||||
item.internalSkuName.trim() ||
|
||||
item.externalSkuName.trim() ||
|
||||
item.internalSkuCode.trim() ||
|
||||
`规则 ${index + 1}`
|
||||
)
|
||||
}
|
||||
|
||||
function getCardSummary(item: EditableItem) {
|
||||
@@ -299,10 +322,13 @@ function getKuaishouConsumeShopOptions() {
|
||||
function findKuaishouConsumeShop(shopId = '', shopName = '') {
|
||||
const normalizedShopId = String(shopId || '').trim()
|
||||
const normalizedShopName = String(shopName || '').trim()
|
||||
return getKuaishouConsumeShopOptions().find((shop) => (
|
||||
(normalizedShopId && shop.shopId === normalizedShopId)
|
||||
|| (normalizedShopName && shop.kshopName === normalizedShopName)
|
||||
)) || null
|
||||
return (
|
||||
getKuaishouConsumeShopOptions().find(
|
||||
(shop) =>
|
||||
(normalizedShopId && shop.shopId === normalizedShopId) ||
|
||||
(normalizedShopName && shop.kshopName === normalizedShopName),
|
||||
) || null
|
||||
)
|
||||
}
|
||||
|
||||
function getDefaultKuaishouConsumeShop() {
|
||||
@@ -319,7 +345,10 @@ function handleKuaishouConsumeShopSelected(item: EditableItem) {
|
||||
}
|
||||
|
||||
function getConsumeShopSummary(item: EditableItem) {
|
||||
const matchedShop = findKuaishouConsumeShop(item.kuaishouConsumeShopId, item.kuaishouConsumeShopName)
|
||||
const matchedShop = findKuaishouConsumeShop(
|
||||
item.kuaishouConsumeShopId,
|
||||
item.kuaishouConsumeShopName,
|
||||
)
|
||||
const name = String(matchedShop?.kshopName || item.kuaishouConsumeShopName).trim()
|
||||
const id = String(matchedShop?.shopId || item.kuaishouConsumeShopId).trim()
|
||||
|
||||
@@ -369,7 +398,9 @@ function expandAllRules() {
|
||||
}
|
||||
|
||||
function collapseReadyRules() {
|
||||
collapsedIds.value = items.value.filter((item) => isItemComplete(item) || !item.enabled).map((item) => item.localId)
|
||||
collapsedIds.value = items.value
|
||||
.filter((item) => isItemComplete(item) || !item.enabled)
|
||||
.map((item) => item.localId)
|
||||
}
|
||||
|
||||
async function loadConfigs() {
|
||||
@@ -386,7 +417,9 @@ async function loadConfigs() {
|
||||
fetchAdminKuaishouCloudFulfillmentConfig(),
|
||||
fetchAdminKuaishouEticketSourceConfig(),
|
||||
])
|
||||
kuaishouConsumeShops.value = Array.isArray(eticketResponse.data.source.shops) ? eticketResponse.data.source.shops : []
|
||||
kuaishouConsumeShops.value = Array.isArray(eticketResponse.data.source.shops)
|
||||
? eticketResponse.data.source.shops
|
||||
: []
|
||||
filePath.value = response.data.filePath
|
||||
enabled.value = response.data.source.enabled !== false
|
||||
items.value = (response.data.source.items || []).map(mapEditableItem)
|
||||
@@ -450,16 +483,21 @@ function findExistingItemFromNinetyoneOrder(order: AdminNinetyoneOrderItem) {
|
||||
const productName = String(order.productName || '').trim()
|
||||
const shopId = String(order.shopId || '91kaquan').trim()
|
||||
|
||||
return items.value.find((item) => {
|
||||
const sameSource = item.provider.trim() === '91kaquan' && item.platform.trim() === 'kuaishou'
|
||||
const sameShop = !shopId || item.shopId.trim() === shopId
|
||||
const sameProductNo = productNo && (
|
||||
item.externalSkuCode.trim() === productNo
|
||||
|| item.externalItemId.trim() === productNo
|
||||
)
|
||||
const sameName = productName && [item.externalSkuName, item.internalSkuName, item.resolvedSkuName].some((value) => value.trim() === productName)
|
||||
return sameSource && sameShop && (sameProductNo || sameName)
|
||||
}) || null
|
||||
return (
|
||||
items.value.find((item) => {
|
||||
const sameSource = item.provider.trim() === '91kaquan' && item.platform.trim() === 'kuaishou'
|
||||
const sameShop = !shopId || item.shopId.trim() === shopId
|
||||
const sameProductNo =
|
||||
productNo &&
|
||||
(item.externalSkuCode.trim() === productNo || item.externalItemId.trim() === productNo)
|
||||
const sameName =
|
||||
productName &&
|
||||
[item.externalSkuName, item.internalSkuName, item.resolvedSkuName].some(
|
||||
(value) => value.trim() === productName,
|
||||
)
|
||||
return sameSource && sameShop && (sameProductNo || sameName)
|
||||
}) || null
|
||||
)
|
||||
}
|
||||
|
||||
async function importNinetyoneProduct(item: AdminNinetyoneOrderItem) {
|
||||
@@ -488,11 +526,7 @@ async function importNinetyoneProduct(item: AdminNinetyoneOrderItem) {
|
||||
}
|
||||
|
||||
function getNinetyoneOrderImportKey(item: AdminNinetyoneOrderItem) {
|
||||
return [
|
||||
item.orderNo,
|
||||
item.productNo,
|
||||
item.shopId || '91kaquan',
|
||||
]
|
||||
return [item.orderNo, item.productNo, item.shopId || '91kaquan']
|
||||
.map((value) => String(value || '').trim())
|
||||
.join(':')
|
||||
}
|
||||
@@ -515,7 +549,8 @@ async function ensureCloudSkuCatalogLoaded(force = false) {
|
||||
return cloudSkuCatalog.value
|
||||
} catch (error) {
|
||||
cloudSkuCatalog.value = []
|
||||
cloudSkuCatalogErrorMessage.value = error instanceof Error ? error.message : 'cloud SKU 列表查询失败'
|
||||
cloudSkuCatalogErrorMessage.value =
|
||||
error instanceof Error ? error.message : 'cloud SKU 列表查询失败'
|
||||
throw error
|
||||
} finally {
|
||||
cloudSkuCatalogLoading.value = false
|
||||
@@ -523,18 +558,17 @@ async function ensureCloudSkuCatalogLoaded(force = false) {
|
||||
}
|
||||
|
||||
function getCloudSkuKeyword(item: EditableItem) {
|
||||
return [
|
||||
item.internalSkuCode,
|
||||
item.internalSkuName,
|
||||
item.resolvedSkuName,
|
||||
item.externalSkuName,
|
||||
]
|
||||
.map((value) => String(value || '').trim())
|
||||
.find(Boolean) || ''
|
||||
return (
|
||||
[item.internalSkuCode, item.internalSkuName, item.resolvedSkuName, item.externalSkuName]
|
||||
.map((value) => String(value || '').trim())
|
||||
.find(Boolean) || ''
|
||||
)
|
||||
}
|
||||
|
||||
function normalizeSearchText(value: string) {
|
||||
return String(value || '').trim().toLowerCase()
|
||||
return String(value || '')
|
||||
.trim()
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
function scoreCloudSkuMatch(item: AdminCloudtentaclesSkuItem, keyword: string) {
|
||||
@@ -644,7 +678,10 @@ async function focusValidationTarget(localId: string) {
|
||||
}
|
||||
|
||||
async function saveConfigs() {
|
||||
const invalid = items.value.reduce<ValidationState>((state, item, index) => state || resolveValidation(item, index), null)
|
||||
const invalid = items.value.reduce<ValidationState>(
|
||||
(state, item, index) => state || resolveValidation(item, index),
|
||||
null,
|
||||
)
|
||||
if (invalid) {
|
||||
validationState.value = invalid
|
||||
setCollapsed(invalid.localId, false)
|
||||
@@ -692,7 +729,8 @@ async function lookupNinetyoneProducts() {
|
||||
importedNinetyoneOrderKeys.value = []
|
||||
} catch (error) {
|
||||
ninetyoneLookupResults.value = []
|
||||
ninetyoneLookupErrorMessage.value = error instanceof Error ? error.message : '91卡券订单查询失败'
|
||||
ninetyoneLookupErrorMessage.value =
|
||||
error instanceof Error ? error.message : '91卡券订单查询失败'
|
||||
} finally {
|
||||
ninetyoneLookupLoading.value = false
|
||||
}
|
||||
@@ -770,7 +808,9 @@ onMounted(loadConfigs)
|
||||
<div class="section-title-row section-title-row--tight">
|
||||
<div>
|
||||
<h3>91卡券订单取样导入</h3>
|
||||
<p>查询已接收的 91卡券订单,把 productNo 导入为规则草稿,再补齐内部 SKU 和 cloud 资源。</p>
|
||||
<p>
|
||||
查询已接收的 91卡券订单,把 productNo 导入为规则草稿,再补齐内部 SKU 和 cloud 资源。
|
||||
</p>
|
||||
</div>
|
||||
<div class="section-mini-stats">
|
||||
<span class="mini-stat-chip">结果 {{ ninetyoneLookupMetrics.total }}</span>
|
||||
@@ -790,21 +830,43 @@ onMounted(loadConfigs)
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>页码</span>
|
||||
<input v-model.number="ninetyoneLookupForm.page" class="text-input" min="1" type="number" />
|
||||
<input
|
||||
v-model.number="ninetyoneLookupForm.page"
|
||||
class="text-input"
|
||||
min="1"
|
||||
type="number"
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>每页条数</span>
|
||||
<input v-model.number="ninetyoneLookupForm.pageSize" class="text-input" max="100" min="1" type="number" />
|
||||
<input
|
||||
v-model.number="ninetyoneLookupForm.pageSize"
|
||||
class="text-input"
|
||||
max="100"
|
||||
min="1"
|
||||
type="number"
|
||||
/>
|
||||
</label>
|
||||
<div class="field-block field-block--action">
|
||||
<span>操作</span>
|
||||
<el-button :loading="ninetyoneLookupLoading" round type="primary" @click="lookupNinetyoneProducts">查询 91卡券订单</el-button>
|
||||
<el-button
|
||||
:loading="ninetyoneLookupLoading"
|
||||
round
|
||||
type="primary"
|
||||
@click="lookupNinetyoneProducts"
|
||||
>查询 91卡券订单</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="toolbar-hint">导入后会创建 provider=91kaquan、platform=kuaishou、外部 SKU=productNo 的规则草稿;保存规则后,回到“平台配置 -> 91卡券接入”重试订单。</p>
|
||||
<p class="toolbar-hint">
|
||||
导入后会创建 provider=91kaquan、platform=kuaishou、外部 SKU=productNo
|
||||
的规则草稿;保存规则后,回到“平台配置 -> 91卡券接入”重试订单。
|
||||
</p>
|
||||
|
||||
<p v-if="ninetyoneLookupErrorMessage" class="error-copy lookup-error">{{ ninetyoneLookupErrorMessage }}</p>
|
||||
<p v-if="ninetyoneLookupErrorMessage" class="error-copy lookup-error">
|
||||
{{ ninetyoneLookupErrorMessage }}
|
||||
</p>
|
||||
<div v-else-if="ninetyoneLookupLoading" class="empty-inline">正在查询 91卡券订单…</div>
|
||||
|
||||
<table v-else class="data-table">
|
||||
@@ -875,7 +937,11 @@ onMounted(loadConfigs)
|
||||
<button
|
||||
v-for="option in ruleFilterOptions"
|
||||
:key="option.value"
|
||||
:class="['mini-stat-chip', 'mini-stat-chip--button', { 'mini-stat-chip--active': ruleFilter === option.value }]"
|
||||
:class="[
|
||||
'mini-stat-chip',
|
||||
'mini-stat-chip--button',
|
||||
{ 'mini-stat-chip--active': ruleFilter === option.value },
|
||||
]"
|
||||
type="button"
|
||||
@click="ruleFilter = option.value"
|
||||
>
|
||||
@@ -885,28 +951,46 @@ onMounted(loadConfigs)
|
||||
<div class="section-action-group">
|
||||
<el-button round @click="expandAllRules">全部展开</el-button>
|
||||
<el-button round @click="collapseReadyRules">收起已完成</el-button>
|
||||
<el-button round :loading="cloudSkuCatalogLoading" @click="refreshCloudSkuCatalog">刷新 cloud SKU</el-button>
|
||||
<el-button round :loading="cloudSkuCatalogLoading" @click="refreshCloudSkuCatalog"
|
||||
>刷新 cloud SKU</el-button
|
||||
>
|
||||
<el-button round @click="addItem">新增规则</el-button>
|
||||
<el-button round type="primary" :loading="saving" @click="saveConfigs">保存全部</el-button>
|
||||
<el-button round type="primary" :loading="saving" @click="saveConfigs"
|
||||
>保存全部</el-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="validationState?.message" class="validation-banner">{{ validationState.message }}</p>
|
||||
<div v-if="items.length === 0" class="empty-inline">当前还没有新履约规则,先新增一条。</div>
|
||||
<div v-else-if="filteredItems.length === 0" class="empty-inline">当前筛选下没有规则。</div>
|
||||
<p v-if="validationState?.message" class="validation-banner">
|
||||
{{ validationState.message }}
|
||||
</p>
|
||||
<div v-if="items.length === 0" class="empty-inline">
|
||||
当前还没有新履约规则,先新增一条。
|
||||
</div>
|
||||
<div v-else-if="filteredItems.length === 0" class="empty-inline">
|
||||
当前筛选下没有规则。
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="(item, index) in filteredItems"
|
||||
:key="item.localId"
|
||||
:data-local-id="item.localId"
|
||||
:class="['binding-card', { 'binding-card--invalid': validationState?.localId === item.localId }]"
|
||||
:class="[
|
||||
'binding-card',
|
||||
{ 'binding-card--invalid': validationState?.localId === item.localId },
|
||||
]"
|
||||
>
|
||||
<div class="binding-header">
|
||||
<div class="binding-summary">
|
||||
<div class="binding-title-row">
|
||||
<strong>{{ getCardTitle(item, index) }}</strong>
|
||||
<span :class="['binding-status-chip', { 'binding-status-chip--done': isItemComplete(item) }]">
|
||||
<span
|
||||
:class="[
|
||||
'binding-status-chip',
|
||||
{ 'binding-status-chip--done': isItemComplete(item) },
|
||||
]"
|
||||
>
|
||||
{{ getRuleState(item).label }}
|
||||
</span>
|
||||
</div>
|
||||
@@ -934,31 +1018,66 @@ onMounted(loadConfigs)
|
||||
<div class="mapping-grid">
|
||||
<label class="field-block">
|
||||
<span>来源 provider</span>
|
||||
<input v-model="item.provider" class="text-input" maxlength="32" placeholder="默认 91kaquan" />
|
||||
<input
|
||||
v-model="item.provider"
|
||||
class="text-input"
|
||||
maxlength="32"
|
||||
placeholder="默认 91kaquan"
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>来源 platform</span>
|
||||
<input v-model="item.platform" class="text-input" maxlength="32" placeholder="默认 kuaishou" />
|
||||
<input
|
||||
v-model="item.platform"
|
||||
class="text-input"
|
||||
maxlength="32"
|
||||
placeholder="默认 kuaishou"
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block field-wide">
|
||||
<span>店铺 ID</span>
|
||||
<input v-model="item.shopId" class="text-input" maxlength="80" placeholder="留空表示跨店铺共用" />
|
||||
<input
|
||||
v-model="item.shopId"
|
||||
class="text-input"
|
||||
maxlength="80"
|
||||
placeholder="留空表示跨店铺共用"
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>外部 SKU</span>
|
||||
<input v-model="item.externalSkuCode" class="text-input" maxlength="120" placeholder="例如 91卡券 productNo" />
|
||||
<input
|
||||
v-model="item.externalSkuCode"
|
||||
class="text-input"
|
||||
maxlength="120"
|
||||
placeholder="例如 91卡券 productNo"
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>外部商品 ID</span>
|
||||
<input v-model="item.externalItemId" class="text-input" maxlength="120" placeholder="平台商品 ID" />
|
||||
<input
|
||||
v-model="item.externalItemId"
|
||||
class="text-input"
|
||||
maxlength="120"
|
||||
placeholder="平台商品 ID"
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block field-wide">
|
||||
<span>外部商品名</span>
|
||||
<input v-model="item.externalSkuName" class="text-input" maxlength="200" placeholder="用于名称匹配" />
|
||||
<input
|
||||
v-model="item.externalSkuName"
|
||||
class="text-input"
|
||||
maxlength="200"
|
||||
placeholder="用于名称匹配"
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block field-wide">
|
||||
<span>解析商品名</span>
|
||||
<input v-model="item.resolvedSkuName" class="text-input" maxlength="120" placeholder="可作为商品名兜底" />
|
||||
<input
|
||||
v-model="item.resolvedSkuName"
|
||||
class="text-input"
|
||||
maxlength="120"
|
||||
placeholder="可作为商品名兜底"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
@@ -976,19 +1095,39 @@ onMounted(loadConfigs)
|
||||
<div class="mapping-grid">
|
||||
<label class="field-block">
|
||||
<span>内部 SKU</span>
|
||||
<input v-model="item.internalSkuCode" class="text-input" maxlength="80" placeholder="必填,用于生成任务" />
|
||||
<input
|
||||
v-model="item.internalSkuCode"
|
||||
class="text-input"
|
||||
maxlength="80"
|
||||
placeholder="必填,用于生成任务"
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>优先级</span>
|
||||
<input v-model.number="item.priority" class="text-input" min="1" type="number" />
|
||||
<input
|
||||
v-model.number="item.priority"
|
||||
class="text-input"
|
||||
min="1"
|
||||
type="number"
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block field-wide">
|
||||
<span>内部商品名</span>
|
||||
<input v-model="item.internalSkuName" class="text-input" maxlength="120" placeholder="展示用,可选但建议填" />
|
||||
<input
|
||||
v-model="item.internalSkuName"
|
||||
class="text-input"
|
||||
maxlength="120"
|
||||
placeholder="展示用,可选但建议填"
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>cloudSourceKey</span>
|
||||
<input v-model="item.cloudSourceKey" class="text-input" maxlength="60" placeholder="默认 default" />
|
||||
<input
|
||||
v-model="item.cloudSourceKey"
|
||||
class="text-input"
|
||||
maxlength="60"
|
||||
placeholder="默认 default"
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>cloud SKU ID</span>
|
||||
@@ -1007,7 +1146,12 @@ onMounted(loadConfigs)
|
||||
</label>
|
||||
<label class="field-block field-wide">
|
||||
<span>cloud SKU 名称</span>
|
||||
<input :value="item.cloudSkuName" class="text-input text-input--readonly" placeholder="选择 cloud SKU 后自动带出" readonly />
|
||||
<input
|
||||
:value="item.cloudSkuName"
|
||||
class="text-input text-input--readonly"
|
||||
placeholder="选择 cloud SKU 后自动带出"
|
||||
readonly
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>虚拟号 VN Key</span>
|
||||
@@ -1015,7 +1159,12 @@ onMounted(loadConfigs)
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>最低保留余额</span>
|
||||
<input v-model.number="item.minAssetReserve" class="text-input" min="0" type="number" />
|
||||
<input
|
||||
v-model.number="item.minAssetReserve"
|
||||
class="text-input"
|
||||
min="0"
|
||||
type="number"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
@@ -1038,24 +1187,43 @@ onMounted(loadConfigs)
|
||||
{{ formatKuaishouConsumeShopOption(shop) }}
|
||||
</option>
|
||||
</select>
|
||||
<small class="field-help">来自“平台店铺 -> 快手小店核销”配置,保存时会自动带出店铺名。</small>
|
||||
<small class="field-help"
|
||||
>来自“平台店铺 -> 快手小店核销”配置,保存时会自动带出店铺名。</small
|
||||
>
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>核销店铺 ID</span>
|
||||
<input :value="item.kuaishouConsumeShopId || '-'" class="text-input text-input--readonly" readonly />
|
||||
<input
|
||||
:value="item.kuaishouConsumeShopId || '-'"
|
||||
class="text-input text-input--readonly"
|
||||
readonly
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block">
|
||||
<span>核销店铺名</span>
|
||||
<input :value="item.kuaishouConsumeShopName || '-'" class="text-input text-input--readonly" readonly />
|
||||
<input
|
||||
:value="item.kuaishouConsumeShopName || '-'"
|
||||
class="text-input text-input--readonly"
|
||||
readonly
|
||||
/>
|
||||
</label>
|
||||
<label class="field-block field-wide">
|
||||
<span>备注</span>
|
||||
<textarea v-model="item.notes" class="text-input textarea-input" maxlength="400" placeholder="绑定场景、客服注意事项等" />
|
||||
<textarea
|
||||
v-model="item.notes"
|
||||
class="text-input textarea-input"
|
||||
maxlength="400"
|
||||
placeholder="绑定场景、客服注意事项等"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<p v-if="validationState?.localId === item.localId" class="binding-inline-error">{{ validationState?.message }}</p>
|
||||
<p v-if="cloudSkuCatalogErrorMessage" class="binding-inline-error">{{ cloudSkuCatalogErrorMessage }}</p>
|
||||
<p v-if="validationState?.localId === item.localId" class="binding-inline-error">
|
||||
{{ validationState?.message }}
|
||||
</p>
|
||||
<p v-if="cloudSkuCatalogErrorMessage" class="binding-inline-error">
|
||||
{{ cloudSkuCatalogErrorMessage }}
|
||||
</p>
|
||||
|
||||
<div class="binding-actions">
|
||||
<label class="checkbox-line">
|
||||
|
||||
Reference in New Issue
Block a user