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 */
|
/** 对齐 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
|
||||||
}
|
}
|
||||||
@@ -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;
|
||||||
|
|||||||
Reference in new issue
Block a user