修改:场景道具bug处理
continuous-integration/drone/push Build was killed
continuous-integration/drone/promote/prod Build is passing

This commit is contained in:
Linzm committed 2026-11-10 10:58:44 +08:00
1 parent 31754ef398
commit 3c3c71ebee
3 files changed
+102 -14

No files matched your search

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 16 KiB

+65 -9
View File
@@ -1,9 +1,23 @@
/** 对齐 agent catalog.productTitleSpec:哪些产品需要标题输入,以及提交 scene_prop */
export type ProductTitleMode = 'chars' | 'props'
export interface ProductTitleSpec {
label: string
placeholder: string
/** chars:按字符数;props:按分号分隔的道具个数 */
mode: ProductTitleMode
max: number
/** 输入框下方辅助说明 */
hint?: string
}
const SCENE_PROP_SPEC: ProductTitleSpec = {
label: '场景道具',
placeholder: '例如:沙发;台灯;花瓶;地毯',
mode: 'props',
max: 4,
hint: '最多填写4个场景道具,用英文或中文分号隔开',
}
export function isRibbonVariant(name: string) {
@@ -13,25 +27,58 @@ export function isRibbonVariant(name: string) {
export function productTitleSpec(label: string, variantName = ''): ProductTitleSpec | null {
const name = String(label || '').trim()
if (name.includes('场景手办')) {
return { label: '场景道具', placeholder: '可输入1-4个场景道具', max: 4 }
return { ...SCENE_PROP_SPEC }
}
if (name.includes('相框手办挂饰') || (name.includes('相框') && name.includes('挂饰'))) {
return { label: '相框标题', placeholder: '输入相框名称,如「我们的小家」', max: 18 }
return {
label: '相框标题',
placeholder: '输入相框名称,如「我们的小家」',
mode: 'chars',
max: 18,
}
}
if (name.includes('相框手办') && isRibbonVariant(variantName)) {
return { label: '飘带标题', placeholder: '输入飘带名称,如「生日快乐」', max: 18 }
return {
label: '飘带标题',
placeholder: '输入飘带名称,如「生日快乐」',
mode: 'chars',
max: 18,
}
}
return null
}
/** 按分号拆成道具列表(忽略空段) */
export function splitSceneProps(text: string): string[] {
return String(text || '')
.split(/[;;]/)
.map((item) => item.trim())
.filter(Boolean)
}
/** 限制最多 max 个道具,统一用英文分号拼接 */
export function clampSceneProps(text: string, max = 4): string {
const parts = splitSceneProps(text).slice(0, Math.max(0, max))
const endsWithSep = /[;;]\s*$/.test(String(text || ''))
// 输入未满且以分号结尾时,保留末尾分号,方便继续填下一项
if (endsWithSep && parts.length < max) {
return parts.length ? `${parts.join(';')};` : ''
}
return parts.join(';')
}
/** 生成接口 scene_prop:仅当产品需要标题字段且已填写时传原文 */
export function resolveSceneProp(
productName: string,
title: string,
variantName = '',
): string {
if (!productTitleSpec(productName, variantName)) return ''
return String(title || '').trim()
const spec = productTitleSpec(productName, variantName)
if (!spec) return ''
const raw = String(title || '').trim()
if (!raw) return ''
if (spec.mode === 'props') return clampSceneProps(raw, spec.max)
return raw
}
function textOf(...values: unknown[]) {
@@ -81,10 +128,15 @@ export function productTitleSpecFromOrder(
const typeId = Number(orderStat.type_id || 0)
if (typeId === 9) {
return { label: '场景道具', placeholder: '可输入1-4个场景道具', max: 4 }
return { ...SCENE_PROP_SPEC }
}
if (typeId === 11) {
return { label: '相框标题', placeholder: '输入相框名称,如「我们的小家」', max: 18 }
return {
label: '相框标题',
placeholder: '输入相框名称,如「我们的小家」',
mode: 'chars',
max: 18,
}
}
return null
}
@@ -93,6 +145,10 @@ export function resolveScenePropFromOrder(
orderStat: Record<string, any> | null | undefined,
title: string,
): string {
if (!productTitleSpecFromOrder(orderStat)) return ''
return String(title || '').trim()
const spec = productTitleSpecFromOrder(orderStat)
if (!spec) return ''
const raw = String(title || '').trim()
if (!raw) return ''
if (spec.mode === 'props') return clampSceneProps(raw, spec.max)
return raw
}
+37 -5
View File
@@ -36,14 +36,15 @@
class="composer__title-input"
type="text"
:value="productTitle"
:maxlength="titleSpec.max"
:maxlength="titleInputMaxlength"
:placeholder="titleSpec.placeholder"
:disabled="disabled"
:aria-invalid="titleInvalid"
@input="onTitleInput"
/>
<span class="composer__title-count">{{ productTitleLength }} / {{ titleSpec.max }}</span>
<span class="composer__title-count">{{ titleCount }} / {{ titleSpec.max }}</span>
</div>
<p v-if="titleSpec.hint" class="composer__title-hint">{{ titleSpec.hint }}</p>
<p v-if="titleInvalid" class="composer__title-error" role="alert">
请填写{{ titleSpec.label }}
</p>
@@ -96,7 +97,11 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
import { Close, EditPen, Loading, Lock, Picture, Top } from '@element-plus/icons-vue'
import type { ProductTitleSpec } from '@/utils/jingdong-product-title'
import {
clampSceneProps,
splitSceneProps,
type ProductTitleSpec,
} from '@/utils/jingdong-product-title'
export type ComposerTag = {
key: string
@@ -159,7 +164,20 @@ const emit = defineEmits<{
}>()
const titleInputId = 'jd-composer-product-title'
const productTitleLength = computed(() => String(props.productTitle || '').length)
const isPropsTitle = computed(() => props.titleSpec?.mode === 'props')
/** 道具模式不按字符截断;字符模式仍用 max */
const titleInputMaxlength = computed(() => {
if (!props.titleSpec) return undefined
if (props.titleSpec.mode === 'props') return undefined
return props.titleSpec.max
})
const titleCount = computed(() => {
const text = String(props.productTitle || '')
if (isPropsTitle.value) return splitSceneProps(text).length
return text.length
})
const iconMap = {
lock: Lock,
@@ -200,7 +218,14 @@ function onInput(e: Event) {
function onTitleInput(e: Event) {
const target = e.target as HTMLInputElement
emit('update:productTitle', target.value)
const raw = target.value
if (props.titleSpec?.mode === 'props') {
const next = clampSceneProps(raw, props.titleSpec.max)
if (next !== raw) target.value = next
emit('update:productTitle', next)
return
}
emit('update:productTitle', raw)
}
function onKeydown(e: KeyboardEvent) {
@@ -400,6 +425,13 @@ defineExpose({ focus, getPreviewEl, getFlyTargetEl })
white-space: nowrap;
}
.composer__title-hint {
margin: 6px 0 0;
font-size: 11px;
line-height: 1.4;
color: #888;
}
.composer__title-error {
margin: 6px 0 0;
font-size: 12px;