源车型库的编辑;加分页,小程序内容的新建按钮,精简状态,预览对齐(保留了原版,新增了一种预览页面,选专题页时变化表单)
This commit is contained in:
@@ -27,6 +27,14 @@ import {
|
|||||||
|
|
||||||
type WorkflowStatus = "draft" | "prepublished" | "published"
|
type WorkflowStatus = "draft" | "prepublished" | "published"
|
||||||
|
|
||||||
|
type TopicSection = {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
imageUrl: string
|
||||||
|
specLeftLabel: string
|
||||||
|
specRightLabel: string
|
||||||
|
}
|
||||||
|
|
||||||
type SourceSnapshot = {
|
type SourceSnapshot = {
|
||||||
title: string
|
title: string
|
||||||
subtitle: string
|
subtitle: string
|
||||||
@@ -34,6 +42,9 @@ type SourceSnapshot = {
|
|||||||
description: string
|
description: string
|
||||||
ctaText: string
|
ctaText: string
|
||||||
imageUrls: string[]
|
imageUrls: string[]
|
||||||
|
topicNavTitle?: string
|
||||||
|
topicHeroTitle?: string
|
||||||
|
topicSections?: TopicSection[]
|
||||||
}
|
}
|
||||||
|
|
||||||
type MiniAppContent = {
|
type MiniAppContent = {
|
||||||
@@ -48,6 +59,9 @@ type MiniAppContent = {
|
|||||||
ctaText: string
|
ctaText: string
|
||||||
scheduledPublishAt: string
|
scheduledPublishAt: string
|
||||||
imageUrls: string[]
|
imageUrls: string[]
|
||||||
|
topicNavTitle?: string
|
||||||
|
topicHeroTitle?: string
|
||||||
|
topicSections?: TopicSection[]
|
||||||
workflowStatus: WorkflowStatus
|
workflowStatus: WorkflowStatus
|
||||||
updatedBy: string
|
updatedBy: string
|
||||||
updatedAt: string
|
updatedAt: string
|
||||||
@@ -135,6 +149,24 @@ const CONTENTS: MiniAppContent[] = [
|
|||||||
imageUrls: [
|
imageUrls: [
|
||||||
"/images/cars/q3-1.jpg",
|
"/images/cars/q3-1.jpg",
|
||||||
],
|
],
|
||||||
|
topicNavTitle: "Q3详情",
|
||||||
|
topicHeroTitle: "先锋设计",
|
||||||
|
topicSections: [
|
||||||
|
{
|
||||||
|
id: "q3-topic-1",
|
||||||
|
title: "RS竞速套件",
|
||||||
|
imageUrl: "/images/cars/q3-1.jpg",
|
||||||
|
specLeftLabel: "长",
|
||||||
|
specRightLabel: "宽",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "q3-topic-2",
|
||||||
|
title: "宽体低趴 超长轴距",
|
||||||
|
imageUrl: "/images/cars/q3-2.jpg",
|
||||||
|
specLeftLabel: "长",
|
||||||
|
specRightLabel: "宽",
|
||||||
|
},
|
||||||
|
],
|
||||||
workflowStatus: "draft",
|
workflowStatus: "draft",
|
||||||
updatedBy: "内容运营专员",
|
updatedBy: "内容运营专员",
|
||||||
updatedAt: "2026-04-11 08:40",
|
updatedAt: "2026-04-11 08:40",
|
||||||
@@ -180,7 +212,7 @@ const CONTENTS: MiniAppContent[] = [
|
|||||||
id: "c7",
|
id: "c7",
|
||||||
sourceCarId: "rs7",
|
sourceCarId: "rs7",
|
||||||
sourceCarName: "Audi RS7",
|
sourceCarName: "Audi RS7",
|
||||||
pageType: "车型页",
|
pageType: "专题页",
|
||||||
title: "Audi RS7 Performance",
|
title: "Audi RS7 Performance",
|
||||||
subtitle: "高性能美学,锋芒尽释",
|
subtitle: "高性能美学,锋芒尽释",
|
||||||
highlights: "V8双涡轮\nquattro四驱\nRS专属运动套件",
|
highlights: "V8双涡轮\nquattro四驱\nRS专属运动套件",
|
||||||
@@ -292,6 +324,29 @@ const SOURCE_CAR_OPTIONS = [
|
|||||||
{ id: "rs7", name: "Audi RS7" },
|
{ id: "rs7", name: "Audi RS7" },
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
|
function createTopicSectionsFromImages(images: string[]) {
|
||||||
|
const picks = images.slice(0, 2)
|
||||||
|
const fallback = ["/images/cars/a7-1.jpg", "/images/cars/a7-2.jpg"]
|
||||||
|
const selected = picks.length > 0 ? picks : fallback
|
||||||
|
|
||||||
|
return selected.map((imageUrl, index) => ({
|
||||||
|
id: `topic-section-${index + 1}`,
|
||||||
|
title: index === 0 ? "RS竞速套件" : "宽体低趴 超长轴距",
|
||||||
|
imageUrl,
|
||||||
|
specLeftLabel: "长",
|
||||||
|
specRightLabel: "宽",
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildTopicPatch(sourceCarName: string, snapshot?: SourceSnapshot): Pick<MiniAppContent, "topicNavTitle" | "topicHeroTitle" | "topicSections"> {
|
||||||
|
const hero = snapshot?.topicHeroTitle ?? snapshot?.subtitle ?? "先锋设计"
|
||||||
|
return {
|
||||||
|
topicNavTitle: snapshot?.topicNavTitle ?? `${sourceCarName || "车型"}详情`,
|
||||||
|
topicHeroTitle: hero,
|
||||||
|
topicSections: snapshot?.topicSections ?? createTopicSectionsFromImages(snapshot?.imageUrls ?? []),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function workflowBadgeClass(status: WorkflowStatus) {
|
function workflowBadgeClass(status: WorkflowStatus) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "draft":
|
case "draft":
|
||||||
@@ -336,6 +391,9 @@ export function MiniAppContentLibrary({ roleView, workflowConfig, onAddAuditLog,
|
|||||||
ctaText: "立即预约试驾",
|
ctaText: "立即预约试驾",
|
||||||
scheduledPublishAt: "",
|
scheduledPublishAt: "",
|
||||||
imageUrls: [],
|
imageUrls: [],
|
||||||
|
topicNavTitle: `${newContentRequest.sourceCarName}详情`,
|
||||||
|
topicHeroTitle: "先锋设计",
|
||||||
|
topicSections: createTopicSectionsFromImages([]),
|
||||||
workflowStatus: "draft",
|
workflowStatus: "draft",
|
||||||
updatedBy: "内容运营专员",
|
updatedBy: "内容运营专员",
|
||||||
updatedAt: nowString(),
|
updatedAt: nowString(),
|
||||||
@@ -388,8 +446,9 @@ export function MiniAppContentLibrary({ roleView, workflowConfig, onAddAuditLog,
|
|||||||
subtitle: source.subtitle,
|
subtitle: source.subtitle,
|
||||||
highlights: source.highlights,
|
highlights: source.highlights,
|
||||||
description: source.description,
|
description: source.description,
|
||||||
ctaText: source.ctaText,
|
ctaText: item.pageType === "专题页" ? "预约体验" : source.ctaText,
|
||||||
imageUrls: source.imageUrls,
|
imageUrls: item.pageType === "专题页" ? [] : source.imageUrls,
|
||||||
|
...buildTopicPatch(item.sourceCarName, source),
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
onAddAuditLog(`内容运营专员发起 ${item.sourceCarName} 源数据重置(待保存生效)`)
|
onAddAuditLog(`内容运营专员发起 ${item.sourceCarName} 源数据重置(待保存生效)`)
|
||||||
@@ -429,6 +488,9 @@ export function MiniAppContentLibrary({ roleView, workflowConfig, onAddAuditLog,
|
|||||||
ctaText: "立即预约试驾",
|
ctaText: "立即预约试驾",
|
||||||
scheduledPublishAt: "",
|
scheduledPublishAt: "",
|
||||||
imageUrls: [],
|
imageUrls: [],
|
||||||
|
topicNavTitle: "车型详情",
|
||||||
|
topicHeroTitle: "先锋设计",
|
||||||
|
topicSections: createTopicSectionsFromImages([]),
|
||||||
workflowStatus: "draft",
|
workflowStatus: "draft",
|
||||||
updatedBy: "内容运营专员",
|
updatedBy: "内容运营专员",
|
||||||
updatedAt: nowString(),
|
updatedAt: nowString(),
|
||||||
@@ -464,6 +526,38 @@ export function MiniAppContentLibrary({ roleView, workflowConfig, onAddAuditLog,
|
|||||||
updateContent(item.id, { imageUrls: item.imageUrls.filter((_, i) => i !== idx) })
|
updateContent(item.id, { imageUrls: item.imageUrls.filter((_, i) => i !== idx) })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const updateTopicSection = (item: MiniAppContent, sectionId: string, patch: Partial<TopicSection>) => {
|
||||||
|
const sections = item.topicSections ?? []
|
||||||
|
updateContent(item.id, {
|
||||||
|
topicSections: sections.map((section) => (section.id === sectionId ? { ...section, ...patch } : section)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const addTopicSection = (item: MiniAppContent) => {
|
||||||
|
const sections = item.topicSections ?? []
|
||||||
|
if (sections.length >= 6) return
|
||||||
|
updateContent(item.id, {
|
||||||
|
topicSections: [
|
||||||
|
...sections,
|
||||||
|
{
|
||||||
|
id: `${item.id}-topic-${Date.now()}`,
|
||||||
|
title: `专题模块 ${sections.length + 1}`,
|
||||||
|
imageUrl: item.imageUrls[0] ?? "",
|
||||||
|
specLeftLabel: "长",
|
||||||
|
specRightLabel: "宽",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeTopicSection = (item: MiniAppContent, sectionId: string) => {
|
||||||
|
const sections = item.topicSections ?? []
|
||||||
|
if (sections.length <= 1) return
|
||||||
|
updateContent(item.id, {
|
||||||
|
topicSections: sections.filter((section) => section.id !== sectionId),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const renderActions = (item: MiniAppContent) => {
|
const renderActions = (item: MiniAppContent) => {
|
||||||
const actions: React.ReactNode[] = []
|
const actions: React.ReactNode[] = []
|
||||||
|
|
||||||
@@ -513,6 +607,10 @@ export function MiniAppContentLibrary({ roleView, workflowConfig, onAddAuditLog,
|
|||||||
if (viewMode === "editor" && selected) {
|
if (viewMode === "editor" && selected) {
|
||||||
const isNewContent = !selected.sourceCarId
|
const isNewContent = !selected.sourceCarId
|
||||||
const canResetFromSource = Boolean(selected.sourceCarId && SOURCE_SNAPSHOTS[selected.sourceCarId])
|
const canResetFromSource = Boolean(selected.sourceCarId && SOURCE_SNAPSHOTS[selected.sourceCarId])
|
||||||
|
const isTopicPage = selected.pageType === "专题页"
|
||||||
|
const topicSections = (selected.topicSections && selected.topicSections.length > 0)
|
||||||
|
? selected.topicSections
|
||||||
|
: createTopicSectionsFromImages(selected.imageUrls)
|
||||||
const previewHeroImage = selected.imageUrls[0]
|
const previewHeroImage = selected.imageUrls[0]
|
||||||
const highlights = selected.highlights
|
const highlights = selected.highlights
|
||||||
.split("\n")
|
.split("\n")
|
||||||
@@ -543,6 +641,35 @@ export function MiniAppContentLibrary({ roleView, workflowConfig, onAddAuditLog,
|
|||||||
<CardDescription>基于官网源车型生成的内容实体(MiniAppContent)。</CardDescription>
|
<CardDescription>基于官网源车型生成的内容实体(MiniAppContent)。</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">页面类型</label>
|
||||||
|
{isNewContent && canEdit ? (
|
||||||
|
<select
|
||||||
|
className="h-10 rounded-lg border border-input bg-background px-3 text-sm"
|
||||||
|
value={selected.pageType}
|
||||||
|
onChange={(e) => {
|
||||||
|
const nextPageType = e.target.value as MiniAppContent["pageType"]
|
||||||
|
const snapshot = SOURCE_SNAPSHOTS[selected.sourceCarId]
|
||||||
|
const sourceName = selected.sourceCarName || "车型"
|
||||||
|
updateContent(selected.id, {
|
||||||
|
pageType: nextPageType,
|
||||||
|
...(nextPageType === "专题页" ? {
|
||||||
|
...buildTopicPatch(sourceName, snapshot),
|
||||||
|
imageUrls: [],
|
||||||
|
ctaText: "预约体验",
|
||||||
|
} : {}),
|
||||||
|
})
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<option value="车型页">车型页</option>
|
||||||
|
<option value="活动页">活动页</option>
|
||||||
|
<option value="专题页">专题页</option>
|
||||||
|
</select>
|
||||||
|
) : (
|
||||||
|
<Input value={selected.pageType} disabled />
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">来源车型</label>
|
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">来源车型</label>
|
||||||
{isNewContent && canEdit ? (
|
{isNewContent && canEdit ? (
|
||||||
@@ -553,15 +680,17 @@ export function MiniAppContentLibrary({ roleView, workflowConfig, onAddAuditLog,
|
|||||||
const nextId = e.target.value
|
const nextId = e.target.value
|
||||||
const option = SOURCE_CAR_OPTIONS.find((item) => item.id === nextId)
|
const option = SOURCE_CAR_OPTIONS.find((item) => item.id === nextId)
|
||||||
const snapshot = SOURCE_SNAPSHOTS[nextId]
|
const snapshot = SOURCE_SNAPSHOTS[nextId]
|
||||||
|
const sourceName = option?.name ?? ""
|
||||||
updateContent(selected.id, {
|
updateContent(selected.id, {
|
||||||
sourceCarId: nextId,
|
sourceCarId: nextId,
|
||||||
sourceCarName: option?.name ?? "",
|
sourceCarName: sourceName,
|
||||||
title: snapshot?.title ?? selected.title,
|
title: snapshot?.title ?? selected.title,
|
||||||
subtitle: snapshot?.subtitle ?? selected.subtitle,
|
subtitle: snapshot?.subtitle ?? selected.subtitle,
|
||||||
highlights: snapshot?.highlights ?? selected.highlights,
|
highlights: snapshot?.highlights ?? selected.highlights,
|
||||||
description: snapshot?.description ?? selected.description,
|
description: snapshot?.description ?? selected.description,
|
||||||
ctaText: snapshot?.ctaText ?? selected.ctaText,
|
ctaText: selected.pageType === "专题页" ? "预约体验" : (snapshot?.ctaText ?? selected.ctaText),
|
||||||
imageUrls: snapshot?.imageUrls ?? selected.imageUrls,
|
imageUrls: selected.pageType === "专题页" ? [] : (snapshot?.imageUrls ?? selected.imageUrls),
|
||||||
|
...buildTopicPatch(sourceName, snapshot),
|
||||||
})
|
})
|
||||||
if (snapshot) {
|
if (snapshot) {
|
||||||
onAddAuditLog(`内容运营专员选择来源车型 ${option?.name ?? nextId} 并载入官网基线内容`)
|
onAddAuditLog(`内容运营专员选择来源车型 ${option?.name ?? nextId} 并载入官网基线内容`)
|
||||||
@@ -578,59 +707,104 @@ export function MiniAppContentLibrary({ roleView, workflowConfig, onAddAuditLog,
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-2">
|
{isTopicPage ? (
|
||||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">页面类型</label>
|
<>
|
||||||
{isNewContent && canEdit ? (
|
<div className="grid gap-2">
|
||||||
<select
|
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">导航标题</label>
|
||||||
className="h-10 rounded-lg border border-input bg-background px-3 text-sm"
|
<Input
|
||||||
value={selected.pageType}
|
value={selected.topicNavTitle ?? `${selected.sourceCarName || "车型"}详情`}
|
||||||
onChange={(e) => updateContent(selected.id, { pageType: e.target.value as MiniAppContent["pageType"] })}
|
disabled={!canEdit}
|
||||||
>
|
onChange={(e) => updateContent(selected.id, { topicNavTitle: e.target.value })}
|
||||||
<option value="车型页">车型页</option>
|
/>
|
||||||
<option value="活动页">活动页</option>
|
</div>
|
||||||
<option value="专题页">专题页</option>
|
|
||||||
</select>
|
|
||||||
) : (
|
|
||||||
<Input value={selected.pageType} disabled />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">标题</label>
|
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">主视觉标题</label>
|
||||||
<Input value={selected.title} disabled={!canEdit} onChange={(e) => updateContent(selected.id, { title: e.target.value })} />
|
<Input
|
||||||
</div>
|
value={selected.topicHeroTitle ?? "先锋设计"}
|
||||||
|
disabled={!canEdit}
|
||||||
|
onChange={(e) => updateContent(selected.id, { topicHeroTitle: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">副标题</label>
|
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">底部按钮文案</label>
|
||||||
<Input value={selected.subtitle} disabled={!canEdit} onChange={(e) => updateContent(selected.id, { subtitle: e.target.value })} />
|
<Input value={selected.ctaText} disabled={!canEdit} onChange={(e) => updateContent(selected.id, { ctaText: e.target.value })} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-3">
|
||||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">卖点(每行一条)</label>
|
<div className="flex items-center justify-between">
|
||||||
<textarea
|
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">专题模块(1-6)</label>
|
||||||
className="min-h-24 rounded-lg border border-input bg-background px-3 py-2 text-sm outline-none focus-visible:border-ring"
|
<Button size="sm" variant="outline" disabled={!canEdit || topicSections.length >= 6} onClick={() => addTopicSection(selected)}>
|
||||||
value={selected.highlights}
|
新增模块
|
||||||
disabled={!canEdit}
|
</Button>
|
||||||
onChange={(e) => updateContent(selected.id, { highlights: e.target.value })}
|
</div>
|
||||||
/>
|
<div className="space-y-3">
|
||||||
</div>
|
{topicSections.map((section, index) => (
|
||||||
|
<div key={section.id} className="rounded-lg border p-3 space-y-2">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
|
<p className="text-xs font-semibold text-muted-foreground">模块 {index + 1}</p>
|
||||||
|
<Button size="sm" variant="ghost" disabled={!canEdit || topicSections.length <= 1} onClick={() => removeTopicSection(selected, section.id)}>
|
||||||
|
删除
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<Input
|
||||||
|
value={section.title}
|
||||||
|
disabled={!canEdit}
|
||||||
|
onChange={(e) => updateTopicSection(selected, section.id, { title: e.target.value })}
|
||||||
|
placeholder="模块标题"
|
||||||
|
/>
|
||||||
|
<Input
|
||||||
|
value={section.imageUrl}
|
||||||
|
disabled={!canEdit}
|
||||||
|
onChange={(e) => updateTopicSection(selected, section.id, { imageUrl: e.target.value })}
|
||||||
|
placeholder="模块图片 URL"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">标题</label>
|
||||||
|
<Input value={selected.title} disabled={!canEdit} onChange={(e) => updateContent(selected.id, { title: e.target.value })} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">图文描述(轻量编排)</label>
|
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">副标题</label>
|
||||||
<textarea
|
<Input value={selected.subtitle} disabled={!canEdit} onChange={(e) => updateContent(selected.id, { subtitle: e.target.value })} />
|
||||||
className="min-h-28 rounded-lg border border-input bg-background px-3 py-2 text-sm outline-none focus-visible:border-ring"
|
</div>
|
||||||
value={selected.description}
|
|
||||||
disabled={!canEdit}
|
|
||||||
onChange={(e) => updateContent(selected.id, { description: e.target.value })}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">按钮文案</label>
|
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">卖点(每行一条)</label>
|
||||||
<Input value={selected.ctaText} disabled={!canEdit} onChange={(e) => updateContent(selected.id, { ctaText: e.target.value })} />
|
<textarea
|
||||||
</div>
|
className="min-h-24 rounded-lg border border-input bg-background px-3 py-2 text-sm outline-none focus-visible:border-ring"
|
||||||
|
value={selected.highlights}
|
||||||
|
disabled={!canEdit}
|
||||||
|
onChange={(e) => updateContent(selected.id, { highlights: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
{workflowConfig.publishStrategy === "scheduled" && (
|
<div className="grid gap-2">
|
||||||
|
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">图文描述(轻量编排)</label>
|
||||||
|
<textarea
|
||||||
|
className="min-h-28 rounded-lg border border-input bg-background px-3 py-2 text-sm outline-none focus-visible:border-ring"
|
||||||
|
value={selected.description}
|
||||||
|
disabled={!canEdit}
|
||||||
|
onChange={(e) => updateContent(selected.id, { description: e.target.value })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid gap-2">
|
||||||
|
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">按钮文案</label>
|
||||||
|
<Input value={selected.ctaText} disabled={!canEdit} onChange={(e) => updateContent(selected.id, { ctaText: e.target.value })} />
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{!isTopicPage && workflowConfig.publishStrategy === "scheduled" && (
|
||||||
<div className="grid gap-2">
|
<div className="grid gap-2">
|
||||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">预约发布时间</label>
|
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">预约发布时间</label>
|
||||||
<Input
|
<Input
|
||||||
@@ -642,25 +816,27 @@ export function MiniAppContentLibrary({ roleView, workflowConfig, onAddAuditLog,
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="grid gap-2">
|
{!isTopicPage && (
|
||||||
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">图片素材(多图)</label>
|
<div className="grid gap-2">
|
||||||
<div className="flex gap-2">
|
<label className="text-xs font-semibold tracking-wide text-muted-foreground uppercase">图片素材(多图)</label>
|
||||||
<Input value={newImageUrl} disabled={!canEdit} onChange={(e) => setNewImageUrl(e.target.value)} placeholder="粘贴图片 URL" />
|
<div className="flex gap-2">
|
||||||
<Button variant="outline" disabled={!canEdit} onClick={() => addImage(selected)}>新增</Button>
|
<Input value={newImageUrl} disabled={!canEdit} onChange={(e) => setNewImageUrl(e.target.value)} placeholder="粘贴图片 URL" />
|
||||||
</div>
|
<Button variant="outline" disabled={!canEdit} onClick={() => addImage(selected)}>新增</Button>
|
||||||
<div className="grid grid-cols-2 gap-2">
|
</div>
|
||||||
{selected.imageUrls.map((url, idx) => (
|
<div className="grid grid-cols-2 gap-2">
|
||||||
<div key={`${url}-${idx}`} className="rounded-lg border p-2">
|
{selected.imageUrls.map((url, idx) => (
|
||||||
<img src={url} alt={`${selected.sourceCarName}-${idx}`} className="aspect-video w-full rounded object-cover" />
|
<div key={`${url}-${idx}`} className="rounded-lg border p-2">
|
||||||
<div className="mt-2 flex justify-end">
|
<img src={url} alt={`${selected.sourceCarName}-${idx}`} className="aspect-video w-full rounded object-cover" />
|
||||||
<Button size="sm" variant="ghost" disabled={!canEdit} onClick={() => removeImage(selected, idx)}>
|
<div className="mt-2 flex justify-end">
|
||||||
删除
|
<Button size="sm" variant="ghost" disabled={!canEdit} onClick={() => removeImage(selected, idx)}>
|
||||||
</Button>
|
删除
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
))}
|
||||||
))}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
{pendingResetPatch[selected.id] && (
|
{pendingResetPatch[selected.id] && (
|
||||||
<div className="rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-700">
|
<div className="rounded-lg border border-amber-200 bg-amber-50 px-3 py-2 text-xs text-amber-700">
|
||||||
@@ -692,86 +868,152 @@ export function MiniAppContentLibrary({ roleView, workflowConfig, onAddAuditLog,
|
|||||||
<Card className="border-none shadow-sm bg-white">
|
<Card className="border-none shadow-sm bg-white">
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="text-lg font-bold">小程序预览(手机窗口)</CardTitle>
|
<CardTitle className="text-lg font-bold">小程序预览(手机窗口)</CardTitle>
|
||||||
<CardDescription>按钮上方展示预约试驾表单视觉区(仅演示,不提交;真实留资将直接进入企业 CRM)。</CardDescription>
|
<CardDescription>
|
||||||
|
{isTopicPage
|
||||||
|
? "专题页预览:按来源车型模板自动同步,可对模块图文做手工编辑。"
|
||||||
|
: "按钮上方展示预约试驾表单视觉区(仅演示,不提交;真实留资将直接进入企业 CRM)。"}
|
||||||
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="flex justify-center">
|
<CardContent className="flex justify-center">
|
||||||
<div className="w-[360px] rounded-[28px] border border-gray-200 bg-white shadow-2xl overflow-hidden">
|
{isTopicPage ? (
|
||||||
<div className="h-9 bg-black text-white px-4 flex items-center text-[10px] tracking-[0.18em] uppercase">Audi</div>
|
<div className="min-h-screen w-[360px] bg-white text-[#1a1a1a] flex flex-col relative overflow-hidden shadow-2xl rounded-[28px] border border-gray-200">
|
||||||
<div className="relative">
|
<div className="px-8 pt-4 pb-2 flex justify-between items-center text-sm font-semibold">
|
||||||
{previewHeroImage ? (
|
<span>9:41</span>
|
||||||
<img src={previewHeroImage} alt={selected.title} className="h-48 w-full object-cover" />
|
<div className="flex items-center gap-1.5">
|
||||||
) : (
|
<span className="h-2.5 w-2.5 rotate-45 border border-current inline-block" />
|
||||||
<div className="flex h-48 w-full items-center justify-center bg-gray-100 text-xs text-gray-500">请先添加图片素材</div>
|
<span className="h-2.5 w-2.5 rotate-45 border border-current inline-block" />
|
||||||
)}
|
<div className="w-6 h-3 border border-current rounded-sm relative">
|
||||||
<div className="absolute inset-0 bg-gradient-to-t from-black/65 to-transparent" />
|
<div className="absolute inset-0.5 bg-current rounded-[2px] w-3" />
|
||||||
<div className="absolute left-4 right-4 bottom-3 text-white">
|
|
||||||
<p className="text-lg font-bold tracking-tight">{selected.title}</p>
|
|
||||||
<p className="text-xs opacity-85">{selected.subtitle}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="p-4">
|
|
||||||
<div className="mb-3 grid grid-cols-3 gap-2">
|
|
||||||
{selected.imageUrls.slice(0, 3).map((url, idx) => (
|
|
||||||
<img key={`${url}-${idx}`} src={url} alt={`thumb-${idx}`} className="h-16 w-full rounded object-cover" />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
{highlights.map((item) => (
|
|
||||||
<div key={item} className="inline-flex mr-2 mb-1 rounded-full border px-2 py-0.5 text-[10px] text-gray-600">
|
|
||||||
{item}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="mt-3 text-sm leading-6 text-gray-600">{selected.description}</p>
|
|
||||||
|
|
||||||
{workflowConfig.publishStrategy === "scheduled" && selected.scheduledPublishAt && (
|
|
||||||
<div className="mt-3 rounded-md border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-700">
|
|
||||||
预约发布时间:{selected.scheduledPublishAt.replace("T", " ")}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div className="mt-4 space-y-3 rounded-lg border border-gray-200 bg-gray-50 p-3">
|
|
||||||
<div className="grid gap-1">
|
|
||||||
<label className="text-[10px] uppercase tracking-wider text-gray-500 font-semibold">姓名</label>
|
|
||||||
<Input placeholder="请输入您的姓名" disabled />
|
|
||||||
</div>
|
|
||||||
<div className="grid gap-1">
|
|
||||||
<label className="text-[10px] uppercase tracking-wider text-gray-500 font-semibold">手机号</label>
|
|
||||||
<Input placeholder="请输入手机号" disabled />
|
|
||||||
</div>
|
|
||||||
<div className="grid gap-1">
|
|
||||||
<label className="text-[10px] uppercase tracking-wider text-gray-500 font-semibold">意向城市</label>
|
|
||||||
<div className="flex gap-2">
|
|
||||||
<select className="h-8 flex-1 rounded-lg border border-input bg-white px-2 text-sm text-gray-500" disabled>
|
|
||||||
<option>请选择城市</option>
|
|
||||||
</select>
|
|
||||||
<Button
|
|
||||||
type="button"
|
|
||||||
size="sm"
|
|
||||||
variant="outline"
|
|
||||||
onClick={() => alert("获取位置功能为原型占位提示")}
|
|
||||||
>
|
|
||||||
获取位置
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label className="flex items-start gap-2 text-[11px] leading-5 text-gray-600">
|
|
||||||
<input type="checkbox" className="mt-0.5" />
|
|
||||||
<span>
|
|
||||||
我已阅读并同意《个人信息保护政策》,授权奥迪及授权经销商与我联系。信息提交后将直接进入企业 CRM,本平台不留存潜客数据。
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Button className="mt-4 w-full bg-audi-black hover:bg-audi-dark-gray text-white" disabled>
|
<header className="px-4 py-2 flex items-center justify-between sticky top-0 bg-white z-10">
|
||||||
{selected.ctaText || "立即预约试驾"}
|
<button className="p-2 rounded-full">
|
||||||
</Button>
|
<ArrowLeft className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
<h1 className="text-[17px] font-medium">{selected.topicNavTitle || `${selected.sourceCarName || "车型"}详情`}</h1>
|
||||||
|
<div className="flex items-center bg-gray-50 border border-gray-200 rounded-full px-3 py-1.5 gap-3">
|
||||||
|
<span className="text-xs text-gray-500">···</span>
|
||||||
|
<div className="w-px h-4 bg-gray-300" />
|
||||||
|
<span className="text-xs text-gray-500">x</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<main className="flex-1 overflow-y-auto pb-32">
|
||||||
|
<section className="pt-8 pb-6 text-center">
|
||||||
|
<h2 className="text-[32px] font-bold tracking-tight mb-2">{selected.topicHeroTitle || "先锋设计"}</h2>
|
||||||
|
<div className="w-12 h-[3px] bg-[#d51c2a] mx-auto" />
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="space-y-4 px-0 max-h-[520px] overflow-y-auto">
|
||||||
|
{topicSections.map((section) => (
|
||||||
|
<section key={section.id} className="bg-[#f8f9fb] rounded-t-[32px] overflow-hidden">
|
||||||
|
<div className="aspect-[16/9] relative overflow-hidden">
|
||||||
|
{section.imageUrl ? (
|
||||||
|
<img
|
||||||
|
src={section.imageUrl}
|
||||||
|
alt={section.title}
|
||||||
|
className="w-full h-full object-cover mix-blend-multiply"
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<div className="w-full h-full flex items-center justify-center text-xs text-gray-500 bg-gray-100">请添加模块图片</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="px-6 py-2">
|
||||||
|
<h3 className="text-[22px] font-normal text-gray-700">{section.title}</h3>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<footer className="absolute bottom-0 left-0 right-0 bg-white/80 backdrop-blur-md px-4 pt-2 pb-8 border-t border-gray-100">
|
||||||
|
<button className="w-full bg-[#1c1f23] text-white py-4 rounded-md text-[16px] font-medium">
|
||||||
|
{selected.ctaText || "预约体验"}
|
||||||
|
</button>
|
||||||
|
<div className="mt-6 w-32 h-1.5 bg-black/80 mx-auto rounded-full" />
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
) : (
|
||||||
|
<div className="w-[360px] rounded-[28px] border border-gray-200 bg-white shadow-2xl overflow-hidden">
|
||||||
|
<div className="h-9 bg-black text-white px-4 flex items-center text-[10px] tracking-[0.18em] uppercase">Audi</div>
|
||||||
|
<div className="relative">
|
||||||
|
{previewHeroImage ? (
|
||||||
|
<img src={previewHeroImage} alt={selected.title} className="h-48 w-full object-cover" />
|
||||||
|
) : (
|
||||||
|
<div className="flex h-48 w-full items-center justify-center bg-gray-100 text-xs text-gray-500">请先添加图片素材</div>
|
||||||
|
)}
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-t from-black/65 to-transparent" />
|
||||||
|
<div className="absolute left-4 right-4 bottom-3 text-white">
|
||||||
|
<p className="text-lg font-bold tracking-tight">{selected.title}</p>
|
||||||
|
<p className="text-xs opacity-85">{selected.subtitle}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="p-4">
|
||||||
|
<div className="mb-3 grid grid-cols-3 gap-2">
|
||||||
|
{selected.imageUrls.slice(0, 3).map((url, idx) => (
|
||||||
|
<img key={`${url}-${idx}`} src={url} alt={`thumb-${idx}`} className="h-16 w-full rounded object-cover" />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
{highlights.map((item) => (
|
||||||
|
<div key={item} className="inline-flex mr-2 mb-1 rounded-full border px-2 py-0.5 text-[10px] text-gray-600">
|
||||||
|
{item}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="mt-3 text-sm leading-6 text-gray-600">{selected.description}</p>
|
||||||
|
|
||||||
|
{workflowConfig.publishStrategy === "scheduled" && selected.scheduledPublishAt && (
|
||||||
|
<div className="mt-3 rounded-md border border-blue-200 bg-blue-50 px-3 py-2 text-xs text-blue-700">
|
||||||
|
预约发布时间:{selected.scheduledPublishAt.replace("T", " ")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="mt-4 space-y-3 rounded-lg border border-gray-200 bg-gray-50 p-3">
|
||||||
|
<div className="grid gap-1">
|
||||||
|
<label className="text-[10px] uppercase tracking-wider text-gray-500 font-semibold">姓名</label>
|
||||||
|
<Input placeholder="请输入您的姓名" disabled />
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-1">
|
||||||
|
<label className="text-[10px] uppercase tracking-wider text-gray-500 font-semibold">手机号</label>
|
||||||
|
<Input placeholder="请输入手机号" disabled />
|
||||||
|
</div>
|
||||||
|
<div className="grid gap-1">
|
||||||
|
<label className="text-[10px] uppercase tracking-wider text-gray-500 font-semibold">意向城市</label>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<select className="h-8 flex-1 rounded-lg border border-input bg-white px-2 text-sm text-gray-500" disabled>
|
||||||
|
<option>请选择城市</option>
|
||||||
|
</select>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
size="sm"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => alert("获取位置功能为原型占位提示")}
|
||||||
|
>
|
||||||
|
获取位置
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<label className="flex items-start gap-2 text-[11px] leading-5 text-gray-600">
|
||||||
|
<input type="checkbox" className="mt-0.5" />
|
||||||
|
<span>
|
||||||
|
我已阅读并同意《个人信息保护政策》,授权奥迪及授权经销商与我联系。信息提交后将直接进入企业 CRM,本平台不留存潜客数据。
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Button className="mt-4 w-full bg-audi-black hover:bg-audi-dark-gray text-white" disabled>
|
||||||
|
{selected.ctaText || "立即预约试驾"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user