fix: 自定义标签融入原生标签栏,点击切换而非消失

- StyleSelector 新增 customTags 属性,自定义标签作为「自定义标签」行展示在原生标签栏中
- 自定义标签点击切换 true/false,不再移除 key,解决点击消失问题
- CustomTagsEditor 简化为仅输入框+添加按钮
- extractTags 仅提取激活态自定义标签(value=true)
- 导出 CUSTOM_PREFIX 供组件使用
This commit is contained in:
2026-05-25 19:55:29 +08:00
parent 9d3b8fcd20
commit f44fd56e22
8 changed files with 50 additions and 49 deletions
+6 -3
View File
@@ -1,5 +1,5 @@
import { post } from './client'
import { STYLE_CATEGORIES, PROJECT_TYPES, PROJECT_TYPE_KEY, getCustomTags } from '../utils/style'
import { STYLE_CATEGORIES, PROJECT_TYPES, PROJECT_TYPE_KEY, CUSTOM_PREFIX, getCustomTags } from '../utils/style'
interface OptimizePromptParams {
tags: string[]
@@ -34,8 +34,11 @@ export function extractTags(style: Record<string, string>): string[] {
if (option) tags.push(option.label)
}
// 自定义标签
tags.push(...getCustomTags(style))
// 自定义标签(仅包含激活的)
const customTags = getCustomTags(style).filter(
tag => style[`${CUSTOM_PREFIX}${tag}`] === 'true'
)
tags.push(...customTags)
return tags
}
@@ -1,5 +1,5 @@
import { useState } from 'react'
import { PROJECT_TYPES, PROJECT_TYPE_KEY } from '../utils/style'
import { PROJECT_TYPES, PROJECT_TYPE_KEY, getCustomTags } from '../utils/style'
import StyleSelector from './StyleSelector'
import CustomTagsEditor from './CustomTagsEditor'
@@ -100,6 +100,7 @@ export default function CreateProjectModal({
label: '工程类型',
options: PROJECT_TYPES.map(p => ({ value: p.value, label: p.label })),
}]}
customTags={getCustomTags(style)}
/>
<CustomTagsEditor style={style} onChange={setStyle} />
</div>
+3 -38
View File
@@ -1,5 +1,5 @@
import { useState } from 'react'
import { getCustomTags, addCustomTag, removeCustomTag } from '../utils/style'
import { getCustomTags, addCustomTag } from '../utils/style'
interface CustomTagsEditorProps {
style: Record<string, string>
@@ -26,49 +26,14 @@ export default function CustomTagsEditor({ style, onChange }: CustomTagsEditorPr
}
return (
<div style={{ marginTop: 12 }}>
<div style={{ fontSize: 13, fontWeight: 600, marginBottom: 8, color: 'var(--text-secondary)' }}>
自定义标签
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 6, marginBottom: 8 }}>
{tags.map(tag => (
<button
key={tag}
type="button"
onClick={() => onChange(removeCustomTag(style, tag))}
style={{
display: 'inline-flex',
alignItems: 'center',
gap: 4,
fontSize: 12,
padding: '3px 10px',
borderRadius: 999,
background: 'var(--bg-input)',
border: '1px solid var(--border)',
color: 'var(--text-primary)',
cursor: 'pointer',
}}
>
{tag}
<span
style={{
color: 'var(--text-muted)',
fontSize: 14,
lineHeight: 1,
}}
>
x
</span>
</button>
))}
</div>
<div style={{ marginTop: 8 }}>
<div style={{ display: 'flex', gap: 8 }}>
<input
type="text"
value={input}
onChange={e => setInput(e.target.value)}
onKeyDown={handleKeyDown}
placeholder="输入标签后按 Enter 添加"
placeholder="输入自定义标签后按 Enter 添加"
style={{ flex: 1, padding: '6px 10px', fontSize: 13 }}
/>
<button
+2 -2
View File
@@ -1,7 +1,7 @@
import type { AssetType } from '../api/types'
import { useProjectStore } from '../stores/project'
import { useTaskStore } from '../stores/task'
import { mergeStyles } from '../utils/style'
import { mergeStyles, getCustomTags } from '../utils/style'
import StyleSelector from './StyleSelector'
import CustomTagsEditor from './CustomTagsEditor'
import PromptEditor from './PromptEditor'
@@ -122,7 +122,7 @@ export default function GenerateForm({ onSubmit, submitting }: GenerateFormProps
<h3 style={{ marginBottom: 12, fontSize: 14, color: 'var(--text-secondary)' }}>
素材风格
</h3>
<StyleSelector value={taskStyle} onChange={toggleTaskStyle} compact />
<StyleSelector value={taskStyle} onChange={toggleTaskStyle} compact customTags={getCustomTags(taskStyle)} />
<CustomTagsEditor style={taskStyle} onChange={setTaskStyle} />
</div>
@@ -2,7 +2,7 @@ import { useEffect } from 'react'
import { useParams } from 'react-router-dom'
import { useProjectStore } from '../stores/project'
import { useToastStore } from '../stores/toast'
import { PROJECT_TYPES, PROJECT_TYPE_KEY } from '../utils/style'
import { PROJECT_TYPES, PROJECT_TYPE_KEY, getCustomTags } from '../utils/style'
import StyleSelector from './StyleSelector'
import CustomTagsEditor from './CustomTagsEditor'
import Skeleton from './Skeleton'
@@ -82,6 +82,7 @@ export default function ProjectConfigPanel() {
label: '工程类型',
options: PROJECT_TYPES.map(p => ({ value: p.value, label: p.label })),
}]}
customTags={getCustomTags(draftStyle)}
/>
<CustomTagsEditor style={draftStyle} onChange={setDraftStyle} />
</div>
+4 -2
View File
@@ -1,4 +1,4 @@
import { STYLE_CATEGORIES, PROJECT_TYPES, PROJECT_TYPE_KEY, getCustomTags } from '../utils/style'
import { STYLE_CATEGORIES, PROJECT_TYPES, PROJECT_TYPE_KEY, CUSTOM_PREFIX, getCustomTags } from '../utils/style'
import styles from './GenerateForm.module.css'
interface PromptEditorProps {
@@ -32,7 +32,9 @@ function styleToDescription(kvPairs: Record<string, string>): string {
if (option) parts.push(`${cat.label}: ${option.label}`)
}
const customTags = getCustomTags(kvPairs)
const customTags = getCustomTags(kvPairs).filter(
tag => kvPairs[`${CUSTOM_PREFIX}${tag}`] === 'true'
)
if (customTags.length > 0) parts.push(`标签: ${customTags.join(', ')}`)
return parts.join(', ')
+30 -1
View File
@@ -1,5 +1,5 @@
import type { StyleCategory } from '../utils/style'
import { STYLE_CATEGORIES } from '../utils/style'
import { STYLE_CATEGORIES, CUSTOM_PREFIX } from '../utils/style'
import styles from './StyleSelector.module.css'
interface StyleSelectorProps {
@@ -7,6 +7,13 @@ interface StyleSelectorProps {
onChange: (key: string, value: string) => void
compact?: boolean
categories?: StyleCategory[]
customTags?: string[]
}
const CUSTOM_TAG_CATEGORY: StyleCategory = {
key: '', // unused, pills use custom: prefix directly
label: '自定义标签',
options: [],
}
export default function StyleSelector({
@@ -14,6 +21,7 @@ export default function StyleSelector({
onChange,
compact,
categories,
customTags,
}: StyleSelectorProps) {
const cats = categories ?? STYLE_CATEGORIES
return (
@@ -37,6 +45,27 @@ export default function StyleSelector({
</div>
</div>
))}
{customTags && customTags.length > 0 && (
<div key="_custom" className={styles.category}>
<span className={styles.label}>{CUSTOM_TAG_CATEGORY.label}</span>
<div className={styles.options}>
{customTags.map(tag => {
const key = `${CUSTOM_PREFIX}${tag}`
const isActive = value[key] === 'true'
return (
<button
key={tag}
type="button"
className={`${styles.pill} ${isActive ? styles.pillActive : ''}`}
onClick={() => onChange(key, isActive ? 'false' : 'true')}
>
{tag}
</button>
)
})}
</div>
</div>
)}
</div>
)
}
+1 -1
View File
@@ -8,7 +8,7 @@ export function mergeStyles(
return { ...projectStyle, ...taskOverrides }
}
const CUSTOM_PREFIX = 'custom:'
export const CUSTOM_PREFIX = 'custom:'
export function isCustomKey(key: string): boolean {
return key.startsWith(CUSTOM_PREFIX)