forked from natuka/web.puabadge.com
修改:场景道具bug处理
This commit is contained in:
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 |
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user