fix: 自定义标签融入原生标签栏,点击切换而非消失
- StyleSelector 新增 customTags 属性,自定义标签作为「自定义标签」行展示在原生标签栏中 - 自定义标签点击切换 true/false,不再移除 key,解决点击消失问题 - CustomTagsEditor 简化为仅输入框+添加按钮 - extractTags 仅提取激活态自定义标签(value=true) - 导出 CUSTOM_PREFIX 供组件使用
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import { post } from './client'
|
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 {
|
interface OptimizePromptParams {
|
||||||
tags: string[]
|
tags: string[]
|
||||||
@@ -34,8 +34,11 @@ export function extractTags(style: Record<string, string>): string[] {
|
|||||||
if (option) tags.push(option.label)
|
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
|
return tags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from 'react'
|
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 StyleSelector from './StyleSelector'
|
||||||
import CustomTagsEditor from './CustomTagsEditor'
|
import CustomTagsEditor from './CustomTagsEditor'
|
||||||
|
|
||||||
@@ -100,6 +100,7 @@ export default function CreateProjectModal({
|
|||||||
label: '工程类型',
|
label: '工程类型',
|
||||||
options: PROJECT_TYPES.map(p => ({ value: p.value, label: p.label })),
|
options: PROJECT_TYPES.map(p => ({ value: p.value, label: p.label })),
|
||||||
}]}
|
}]}
|
||||||
|
customTags={getCustomTags(style)}
|
||||||
/>
|
/>
|
||||||
<CustomTagsEditor style={style} onChange={setStyle} />
|
<CustomTagsEditor style={style} onChange={setStyle} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { getCustomTags, addCustomTag, removeCustomTag } from '../utils/style'
|
import { getCustomTags, addCustomTag } from '../utils/style'
|
||||||
|
|
||||||
interface CustomTagsEditorProps {
|
interface CustomTagsEditorProps {
|
||||||
style: Record<string, string>
|
style: Record<string, string>
|
||||||
@@ -26,49 +26,14 @@ export default function CustomTagsEditor({ style, onChange }: CustomTagsEditorPr
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ marginTop: 12 }}>
|
<div style={{ marginTop: 8 }}>
|
||||||
<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={{ display: 'flex', gap: 8 }}>
|
<div style={{ display: 'flex', gap: 8 }}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value={input}
|
value={input}
|
||||||
onChange={e => setInput(e.target.value)}
|
onChange={e => setInput(e.target.value)}
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
placeholder="输入标签后按 Enter 添加"
|
placeholder="输入自定义标签后按 Enter 添加"
|
||||||
style={{ flex: 1, padding: '6px 10px', fontSize: 13 }}
|
style={{ flex: 1, padding: '6px 10px', fontSize: 13 }}
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { AssetType } from '../api/types'
|
import type { AssetType } from '../api/types'
|
||||||
import { useProjectStore } from '../stores/project'
|
import { useProjectStore } from '../stores/project'
|
||||||
import { useTaskStore } from '../stores/task'
|
import { useTaskStore } from '../stores/task'
|
||||||
import { mergeStyles } from '../utils/style'
|
import { mergeStyles, getCustomTags } from '../utils/style'
|
||||||
import StyleSelector from './StyleSelector'
|
import StyleSelector from './StyleSelector'
|
||||||
import CustomTagsEditor from './CustomTagsEditor'
|
import CustomTagsEditor from './CustomTagsEditor'
|
||||||
import PromptEditor from './PromptEditor'
|
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 style={{ marginBottom: 12, fontSize: 14, color: 'var(--text-secondary)' }}>
|
||||||
素材风格
|
素材风格
|
||||||
</h3>
|
</h3>
|
||||||
<StyleSelector value={taskStyle} onChange={toggleTaskStyle} compact />
|
<StyleSelector value={taskStyle} onChange={toggleTaskStyle} compact customTags={getCustomTags(taskStyle)} />
|
||||||
<CustomTagsEditor style={taskStyle} onChange={setTaskStyle} />
|
<CustomTagsEditor style={taskStyle} onChange={setTaskStyle} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useEffect } from 'react'
|
|||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { useProjectStore } from '../stores/project'
|
import { useProjectStore } from '../stores/project'
|
||||||
import { useToastStore } from '../stores/toast'
|
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 StyleSelector from './StyleSelector'
|
||||||
import CustomTagsEditor from './CustomTagsEditor'
|
import CustomTagsEditor from './CustomTagsEditor'
|
||||||
import Skeleton from './Skeleton'
|
import Skeleton from './Skeleton'
|
||||||
@@ -82,6 +82,7 @@ export default function ProjectConfigPanel() {
|
|||||||
label: '工程类型',
|
label: '工程类型',
|
||||||
options: PROJECT_TYPES.map(p => ({ value: p.value, label: p.label })),
|
options: PROJECT_TYPES.map(p => ({ value: p.value, label: p.label })),
|
||||||
}]}
|
}]}
|
||||||
|
customTags={getCustomTags(draftStyle)}
|
||||||
/>
|
/>
|
||||||
<CustomTagsEditor style={draftStyle} onChange={setDraftStyle} />
|
<CustomTagsEditor style={draftStyle} onChange={setDraftStyle} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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'
|
import styles from './GenerateForm.module.css'
|
||||||
|
|
||||||
interface PromptEditorProps {
|
interface PromptEditorProps {
|
||||||
@@ -32,7 +32,9 @@ function styleToDescription(kvPairs: Record<string, string>): string {
|
|||||||
if (option) parts.push(`${cat.label}: ${option.label}`)
|
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(', ')}`)
|
if (customTags.length > 0) parts.push(`标签: ${customTags.join(', ')}`)
|
||||||
|
|
||||||
return parts.join(', ')
|
return parts.join(', ')
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { StyleCategory } from '../utils/style'
|
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'
|
import styles from './StyleSelector.module.css'
|
||||||
|
|
||||||
interface StyleSelectorProps {
|
interface StyleSelectorProps {
|
||||||
@@ -7,6 +7,13 @@ interface StyleSelectorProps {
|
|||||||
onChange: (key: string, value: string) => void
|
onChange: (key: string, value: string) => void
|
||||||
compact?: boolean
|
compact?: boolean
|
||||||
categories?: StyleCategory[]
|
categories?: StyleCategory[]
|
||||||
|
customTags?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const CUSTOM_TAG_CATEGORY: StyleCategory = {
|
||||||
|
key: '', // unused, pills use custom: prefix directly
|
||||||
|
label: '自定义标签',
|
||||||
|
options: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function StyleSelector({
|
export default function StyleSelector({
|
||||||
@@ -14,6 +21,7 @@ export default function StyleSelector({
|
|||||||
onChange,
|
onChange,
|
||||||
compact,
|
compact,
|
||||||
categories,
|
categories,
|
||||||
|
customTags,
|
||||||
}: StyleSelectorProps) {
|
}: StyleSelectorProps) {
|
||||||
const cats = categories ?? STYLE_CATEGORIES
|
const cats = categories ?? STYLE_CATEGORIES
|
||||||
return (
|
return (
|
||||||
@@ -37,6 +45,27 @@ export default function StyleSelector({
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export function mergeStyles(
|
|||||||
return { ...projectStyle, ...taskOverrides }
|
return { ...projectStyle, ...taskOverrides }
|
||||||
}
|
}
|
||||||
|
|
||||||
const CUSTOM_PREFIX = 'custom:'
|
export const CUSTOM_PREFIX = 'custom:'
|
||||||
|
|
||||||
export function isCustomKey(key: string): boolean {
|
export function isCustomKey(key: string): boolean {
|
||||||
return key.startsWith(CUSTOM_PREFIX)
|
return key.startsWith(CUSTOM_PREFIX)
|
||||||
|
|||||||
Reference in New Issue
Block a user