fix(viral-video): v1.6 UI/交互5项修复 (select双框/锁页/步骤解锁/结构19项/分析结果多行) #2134

Merged
xiaoxia merged 1 commits from fix/2041-v16-ui-fixes into develop 2026-10-02 09:57:51 +08:00
2 changed files with 146 additions and 153 deletions
+66 -117
View File
@@ -172,14 +172,7 @@
}
/* ── 通用表单元素 ─────────────────────────────────── */
.vv-label {
display: block;
font-size: 12px;
color: #6b7280;
margin-bottom: 6px;
}
.vv-input,
.vv-select,
.vv-textarea {
width: 100%;
border: 1px solid #e5e7eb;
@@ -194,29 +187,70 @@
box-shadow 0.15s;
font-family: inherit;
}
.vv-input {
height: 36px;
padding: 0 12px;
}
.vv-input:disabled {
background: #f9fafb;
color: #9ca3af;
cursor: not-allowed;
}
.vv-input::placeholder,
.vv-textarea::placeholder {
color: #9ca3af;
}
.vv-select {
width: 100%;
}
.vv-select .ant-select-selector {
border-radius: 8px !important;
border-color: #e5e7eb !important;
box-shadow: none !important;
height: 36px !important;
padding: 0 12px !important;
transition:
border-color 0.15s,
box-shadow 0.15s !important;
}
.vv-select.ant-select-focused .ant-select-selector,
.vv-select.ant-select-open .ant-select-selector {
border-color: #7c3aed !important;
box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.1) !important;
}
.vv-select .ant-select-selection-item {
font-size: 13px !important;
color: #111827 !important;
line-height: 34px !important;
}
.vv-select .ant-select-selection-placeholder {
font-size: 13px !important;
color: #d1d5db !important;
line-height: 34px !important;
}
.vv-select .ant-select-arrow {
color: #9ca3af;
inset-inline-end: 10px;
}
.vv-select.ant-select-disabled .ant-select-selector {
background: #f9fafb !important;
color: #9ca3af !important;
cursor: not-allowed;
}
.vv-select.ant-select-disabled .ant-select-selection-item,
.vv-select.ant-select-disabled .ant-select-selection-placeholder {
color: #9ca3af !important;
}
.vv-textarea {
line-height: 1.6;
resize: vertical;
min-height: 90px;
}
.vv-input:focus,
.vv-select:focus,
.vv-textarea:focus {
border-color: #7c3aed;
box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.1);
}
.vv-input::placeholder,
.vv-textarea::placeholder {
color: #d1d5db;
}
.vv-form-row {
margin-bottom: 12px;
}
.vv-form-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 10px;
}
/* ── Tab 分段 ─────────────────────────────────────── */
.vv-seg-tabs {
@@ -905,15 +939,7 @@
box-shadow: none !important;
transform: none !important;
}
.vv-btn-ghost {
background: #fff;
color: #6b7280;
border-color: #e5e7eb;
}
.vv-btn-ghost:hover {
border-color: #7c3aed;
color: #7c3aed;
}
.vv-btn-warn {
background: #fef3c7;
color: #b45309;
@@ -1235,31 +1261,7 @@
color: #9ca3af !important;
}
/* 列禁用态 */
.vv-col-disabled .vv-section-body,
.vv-col-disabled .vv-step-subhead,
.vv-col-disabled .vv-credits,
.vv-col-disabled .vv-preview,
.vv-col-disabled .vv-error,
.vv-col-disabled .vv-hint-gray,
.vv-col-disabled .vv-hint-yellow {
opacity: 0.55;
}
.vv-col-disabled .vv-btn-primary,
.vv-col-disabled .vv-btn-primary:hover {
opacity: 1 !important;
}
.vv-col-disabled .vv-section .vv-form-row,
.vv-col-disabled .vv-section .vv-pill-row,
.vv-col-disabled .vv-section input,
.vv-col-disabled .vv-section textarea,
.vv-col-disabled .vv-section .ant-select,
.vv-col-disabled .vv-section .vv-pill,
.vv-col-disabled .vv-section .vv-action-btn,
.vv-col-disabled .vv-section .vv-audio-cell,
.vv-col-disabled .vv-section .vv-step-lock {
pointer-events: none;
}
/* 步骤列禁用态已移除:不再锁控件,仅通过按钮 disabled 控制 */
/* Section 内小标题 */
.vv-section-subtitle {
@@ -1622,68 +1624,6 @@
color: #9ca3af;
font-weight: 400;
}
.vv-input {
width: 100%;
height: 36px;
border-radius: 8px;
border: 1px solid #e5e7eb;
background: #fff;
padding: 0 12px;
font-size: 13px;
color: #111827;
outline: none;
transition:
border-color 0.15s,
box-shadow 0.15s;
}
.vv-input:focus {
border-color: #7c3aed;
box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.15);
}
.vv-input:disabled {
background: #f9fafb;
color: #9ca3af;
cursor: not-allowed;
}
.vv-input::placeholder {
color: #9ca3af;
}
/* 自定义 Select 样式(浅色紫调) */
.vv-select-purple .ant-select-selector {
border-radius: 8px !important;
border: 1px solid #e5e7eb !important;
background: #fff !important;
min-height: 36px !important;
padding: 0 28px 0 12px !important;
box-shadow: none !important;
transition:
border-color 0.15s,
box-shadow 0.15s !important;
}
.vv-select-purple.ant-select-focused .ant-select-selector,
.vv-select-purple .ant-select-focused .ant-select-selector {
border-color: #7c3aed !important;
box-shadow: 0 0 0 2px rgba(124, 58, 237, 0.15) !important;
}
.vv-select-purple .ant-select-selection-item,
.vv-select-purple .ant-select-selection-placeholder {
line-height: 34px !important;
font-size: 13px !important;
color: #111827 !important;
}
.vv-select-purple .ant-select-selection-placeholder {
color: #9ca3af !important;
}
.vv-select-purple .ant-select-arrow {
color: #6b7280;
right: 10px !important;
}
.vv-select-purple.ant-select-disabled .ant-select-selector {
background: #f9fafb !important;
color: #9ca3af !important;
cursor: not-allowed;
}
/* ghost/outline 按钮 */
.vv-btn-ghost {
@@ -1739,9 +1679,18 @@
margin-bottom: 4px;
}
.vv-sb-input {
height: 32px;
font-size: 13px;
}
.vv-sb-input.vv-input {
height: 32px;
}
.vv-sb-input.vv-select .ant-select-selector {
height: 32px !important;
}
.vv-sb-input.vv-select .ant-select-selection-item,
.vv-sb-input.vv-select .ant-select-selection-placeholder {
line-height: 30px !important;
}
/* 分块标题 */
.vv-sb-section {
@@ -152,10 +152,25 @@ const TARGET_CUSTOMERS = [
]
const PERSONAS = ["亲切邻家姐姐", "专业顾问", "搞笑段子手", "真实测评博主", "闺蜜种草", "行业专家"]
const STRUCTURES = [
"痛点开场→卖点→促单",
"反差对比→证据→促单",
"故事讲述→共鸣→促单",
"专家背书→数据→促单",
"反差破局+亮明观点+还原现状",
"痛点场景植入+给出通关秘籍",
"反常识开场+扎心现状+制造焦虑",
"核心难题+化解焦虑+描绘愿景",
"痛点前置+前后对比+给出期许",
"悬念拉满+揭露危害+明确目的",
"情绪共鸣+勾起好奇+拔高价值",
"颠覆认知+现状拆解+赋予价值",
"结果先行+消除顾虑+保留悬念",
"真实困境+认知反转+传授秘籍",
"反差事实+案例佐证+戳破真相",
"预埋悬念+标杆案例+引出核心",
"危机渲染+放大痛点+输出干货",
"逆袭开场+成功诀窍+低价引流",
"反常识观点+分析危害+描述现状",
"正反对比+解析原因+简易方法",
"真实场景+内心刻画+共情引导+展望未来",
"连续否定+代入感受+打破认知+精准戳痛",
"揭秘行业+类比施压+放大焦虑+精准锁客",
]
const PURPOSES = ["品牌种草", "新品上市", "促销转化", "活动引流"]
const DURATIONS = [15, 20, 30, 45, 60]
@@ -943,24 +958,61 @@ const ViralVideoPage: React.FC = () => {
</div>
{products.map((p, i) => (
<div key={i} className="vv-recog-item">
{p.name && (
<div className="vv-recog-line">
<span className="vv-recog-k">商品:</span>
<span>
{p.name || "未识别"}
{p.spec && <span className="vv-recog-meta">({p.spec})</span>}
{p.brand && <span className="vv-recog-meta"> · {p.brand}</span>}
{p.category && <span className="vv-recog-meta"> · {p.category}</span>}
</span>
</div>
{featureText(p.key_features ?? p.features) && (
<div className="vv-recog-line">
<span className="vv-recog-k">商品:</span>
<span>{p.name}</span>
{p.spec && <span style={{ color: "#6b7280" }}>({p.spec})</span>}
{p.brand && <span style={{ color: "#6b7280" }}> · {p.brand}</span>}
<span className="vv-recog-k">核心特征:</span>
<span className="vv-recog-v">{featureText(p.key_features ?? p.features)}</span>
</div>
)}
{featureText(p.features) && (
{p.colors && p.colors.length > 0 && (
<div className="vv-recog-line">
<span className="vv-recog-k">视觉特征:</span>
<span>{featureText(p.features)}</span>
<span className="vv-recog-k">主色调:</span>
<span className="vv-recog-v">{p.colors.join(" / ")}</span>
</div>
)}
{p.label_text && (
{p.material_or_texture && (
<div className="vv-recog-line">
<span className="vv-recog-k">品牌/标签文字:</span>
<span>{p.label_text}</span>
<span className="vv-recog-k">材质/纹理:</span>
<span className="vv-recog-v">{p.material_or_texture}</span>
</div>
)}
{p.visual_style && (
<div className="vv-recog-line">
<span className="vv-recog-k">视觉风格:</span>
<span className="vv-recog-v">{p.visual_style}</span>
</div>
)}
{p.scene && (
<div className="vv-recog-line">
<span className="vv-recog-k">场景:</span>
<span className="vv-recog-v">{p.scene}</span>
</div>
)}
{p.target_audience_hint && (
<div className="vv-recog-line">
<span className="vv-recog-k">目标人群:</span>
<span className="vv-recog-v">{p.target_audience_hint}</span>
</div>
)}
{(p.text_on_image || p.label_text) && (
<div className="vv-recog-line">
<span className="vv-recog-k">包装文字:</span>
<span className="vv-recog-v">{p.text_on_image || p.label_text}</span>
</div>
)}
{p.selling_points && (
<div className="vv-recog-line">
<span className="vv-recog-k">卖点:</span>
<span className="vv-recog-v">{p.selling_points}</span>
</div>
)}
</div>
@@ -1689,7 +1741,7 @@ const ViralVideoPage: React.FC = () => {
</div>
{/* ═══════════ 中列 STEP 2 ═══════════ */}
<div className={`vv-col ${step2Enabled ? "" : "vv-col-disabled"}`}>
<div className="vv-col">
<div className="vv-section">
<div className="vv-step-subhead">
<span className={`vv-step-badge ${step2Enabled ? "" : "vv-step-badge-dim"}`}>2</span>
@@ -1705,7 +1757,7 @@ const ViralVideoPage: React.FC = () => {
key={f.value}
className={`vv-fusion-btn ${task.fusionLevel === f.value ? "active" : ""}`}
onClick={() => setTask({ fusionLevel: f.value })}
disabled={!canEditStep2 || !step2Enabled}
disabled={!canEditStep2}
>
<strong>{f.label}</strong>
{f.desc}
@@ -1720,7 +1772,7 @@ const ViralVideoPage: React.FC = () => {
placeholder="请输入你想要的原始文案,AI 将严格保留你的表达"
value={task.userCopy}
onChange={(e) => setTask({ userCopy: e.target.value })}
disabled={!canEditStep2 || !step2Enabled}
disabled={!canEditStep2}
/>
</div>
)}
@@ -1736,7 +1788,7 @@ const ViralVideoPage: React.FC = () => {
placeholder="如:家具店、宠物店、装修公司"
value={task.industry}
onChange={(e) => setTask({ industry: e.target.value })}
disabled={!canEditStep2 || !step2Enabled}
disabled={!canEditStep2}
/>
</div>
<div className="vv-form-row">
@@ -1749,7 +1801,7 @@ const ViralVideoPage: React.FC = () => {
onChange={(v) => setTask({ targetCustomer: v })}
options={TARGET_CUSTOMERS.map((i) => ({ value: i, label: i }))}
allowClear
disabled={!canEditStep2 || !step2Enabled}
disabled={!canEditStep2}
/>
</div>
<div className="vv-form-row">
@@ -1760,7 +1812,7 @@ const ViralVideoPage: React.FC = () => {
value={task.language}
onChange={(v) => setTask({ language: v })}
options={LANGUAGES.map((i) => ({ value: i, label: i }))}
disabled={!canEditStep2 || !step2Enabled}
disabled={!canEditStep2}
/>
</div>
<div className="vv-form-row">
@@ -1773,7 +1825,7 @@ const ViralVideoPage: React.FC = () => {
onChange={(v) => setTask({ persona: v })}
options={PERSONAS.map((i) => ({ value: i, label: i }))}
allowClear
disabled={!canEditStep2 || !step2Enabled}
disabled={!canEditStep2}
/>
</div>
<div className="vv-form-row">
@@ -1784,7 +1836,7 @@ const ViralVideoPage: React.FC = () => {
value={task.viralStructure}
onChange={(v) => setTask({ viralStructure: v })}
options={STRUCTURES.map((i) => ({ value: i, label: i }))}
disabled={!canEditStep2 || !step2Enabled}
disabled={!canEditStep2}
/>
</div>
<div className="vv-form-row">
@@ -1795,7 +1847,7 @@ const ViralVideoPage: React.FC = () => {
value={task.marketingPurpose}
onChange={(v) => setTask({ marketingPurpose: v })}
options={PURPOSES.map((i) => ({ value: i, label: i }))}
disabled={!canEditStep2 || !step2Enabled}
disabled={!canEditStep2}
/>
</div>
<div className="vv-form-row" style={{ gridColumn: "1 / -1" }}>
@@ -1806,7 +1858,7 @@ const ViralVideoPage: React.FC = () => {
value={task.duration}
onChange={(v) => setTask({ duration: v })}
options={DURATIONS.map((n) => ({ value: n, label: `${n}秒` }))}
disabled={!canEditStep2 || !step2Enabled}
disabled={!canEditStep2}
/>
</div>
</div>
@@ -1860,7 +1912,7 @@ const ViralVideoPage: React.FC = () => {
</div>
{/* ═══════════ 右列 STEP 3 ═══════════ */}
<div className={`vv-col ${step3Enabled ? "" : "vv-col-disabled"}`}>
<div className="vv-col">
<div className="vv-section">
<div className="vv-step-subhead">
<span className={`vv-step-badge ${step3Enabled ? "" : "vv-step-badge-dim"}`}>3</span>
@@ -1876,11 +1928,7 @@ const ViralVideoPage: React.FC = () => {
value={task.videoRatio}
onChange={(v) => setTask({ videoRatio: v })}
options={RATIOS.map((r) => ({ value: r.v, label: r.label }))}
disabled={
!step3Enabled ||
task.uiStep === "step3_generating" ||
task.uiStep === "step3_done"
}
disabled={task.uiStep === "step3_generating" || task.uiStep === "step3_done"}
/>
</div>
<div className="vv-form-row">
@@ -1891,11 +1939,7 @@ const ViralVideoPage: React.FC = () => {
value={task.videoModel}
onChange={(v) => setTask({ videoModel: v })}
options={MODELS.map((m) => ({ value: m.v, label: m.label }))}
disabled={
!step3Enabled ||
task.uiStep === "step3_generating" ||
task.uiStep === "step3_done"
}
disabled={task.uiStep === "step3_generating" || task.uiStep === "step3_done"}
/>
</div>
</div>