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