|
@@ -1,7 +1,7 @@
|
|
|
<template>
|
|
<template>
|
|
|
<Modal
|
|
<Modal
|
|
|
:visible="visible"
|
|
:visible="visible"
|
|
|
- :width="980"
|
|
|
|
|
|
|
+ :width="1120"
|
|
|
:close-button="true"
|
|
:close-button="true"
|
|
|
:close-on-click-mask="!working"
|
|
:close-on-click-mask="!working"
|
|
|
@update:visible="value => emit('update:visible', value)"
|
|
@update:visible="value => emit('update:visible', value)"
|
|
@@ -9,19 +9,20 @@
|
|
|
<div class="ai-dialog">
|
|
<div class="ai-dialog">
|
|
|
<div class="header">
|
|
<div class="header">
|
|
|
<div>
|
|
<div>
|
|
|
- <div class="title">AI生成课件</div>
|
|
|
|
|
- <div class="sub">根据当前教材、单元和章节生成15页音乐课件</div>
|
|
|
|
|
|
|
+ <div class="title">{{ pageTitle }}</div>
|
|
|
|
|
+ <div class="sub">{{ pageMeta }}</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <div v-if="outline" class="page-count">{{ outline.pages.length }}页</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div v-if="taskInfo" class="progress-panel">
|
|
|
|
|
- <div class="progress-head">
|
|
|
|
|
- <div class="progress-title">{{ taskInfo.message || "正在处理" }}</div>
|
|
|
|
|
- <div class="progress-value">{{ taskInfo.progress || 0 }}%</div>
|
|
|
|
|
|
|
+ <div v-if="taskInfo" class="progress-strip">
|
|
|
|
|
+ <div class="step-list">
|
|
|
|
|
+ <span v-for="step in progressSteps" :key="step.threshold" :class="{ active: progressValue >= step.threshold }">{{ step.label }}</span>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="progress-track">
|
|
|
|
|
- <div class="progress-bar" :style="{ width: `${taskInfo.progress || 0}%` }"></div>
|
|
|
|
|
|
|
+ <div class="progress-line">
|
|
|
|
|
+ <div class="progress-bar" :style="{ width: `${progressValue}%` }"></div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <div class="progress-value">{{ progressValue }}%</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<div class="extra">
|
|
<div class="extra">
|
|
@@ -29,48 +30,66 @@
|
|
|
<TextArea v-model:value="extraPrompt" :rows="2" placeholder="可选,例如:多一些节奏互动,语言更适合小学低年级" :disabled="working" />
|
|
<TextArea v-model:value="extraPrompt" :rows="2" placeholder="可选,例如:多一些节奏互动,语言更适合小学低年级" :disabled="working" />
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div class="background-setting">
|
|
|
|
|
- <div>
|
|
|
|
|
- <div class="label">课件背景图(可选)</div>
|
|
|
|
|
- <div class="hint">建议 16:9,推荐 1920×1080 或 1600×900。上传后会应用到每一页。</div>
|
|
|
|
|
- </div>
|
|
|
|
|
- <div class="background-actions">
|
|
|
|
|
- <FileInput @change="handleBackgroundUpload">
|
|
|
|
|
- <button class="upload-btn" :disabled="working || uploadingBackground" type="button">
|
|
|
|
|
- {{ uploadingBackground ? "上传中..." : backgroundImageUrl ? "更换背景图" : "上传背景图" }}
|
|
|
|
|
- </button>
|
|
|
|
|
- </FileInput>
|
|
|
|
|
- <button v-if="backgroundImageUrl" class="remove-btn" :disabled="working" type="button" @click="removeBackground">移除</button>
|
|
|
|
|
|
|
+ <div v-if="outline" class="outline">
|
|
|
|
|
+ <div class="page-nav">
|
|
|
|
|
+ <button
|
|
|
|
|
+ v-for="page in outline.pages"
|
|
|
|
|
+ :key="page.pageNo"
|
|
|
|
|
+ class="nav-item"
|
|
|
|
|
+ :class="{ active: selectedPageNo === page.pageNo }"
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ @click="selectedPageNo = page.pageNo"
|
|
|
|
|
+ >
|
|
|
|
|
+ <span class="nav-no">{{ formatPageNo(page.pageNo) }}</span>
|
|
|
|
|
+ <span class="nav-title">{{ page.title || "未命名页面" }}</span>
|
|
|
|
|
+ </button>
|
|
|
</div>
|
|
</div>
|
|
|
- <div v-if="backgroundImageUrl" class="background-preview" :style="{ backgroundImage: `url(${backgroundImageUrl})` }"></div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
|
|
|
- <div v-if="outline" class="outline">
|
|
|
|
|
- <div class="outline-head">
|
|
|
|
|
- <div>
|
|
|
|
|
- <div class="outline-title">{{ outline.title }}</div>
|
|
|
|
|
- <div class="outline-meta">{{ outlineMeta }}</div>
|
|
|
|
|
|
|
+ <div v-if="selectedPage" class="page-editor">
|
|
|
|
|
+ <div class="editor-head">
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <div class="editor-page">第{{ selectedPage.pageNo }}页</div>
|
|
|
|
|
+ <input v-model="selectedPage.title" class="page-title" :disabled="working" />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div class="page-type">{{ pageTypeLabel(selectedPage.type) }}</div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div class="count">{{ outline.pages.length }}页</div>
|
|
|
|
|
- </div>
|
|
|
|
|
|
|
|
|
|
- <div class="page-list">
|
|
|
|
|
- <div v-for="page in outline.pages" :key="page.pageNo" class="page-item">
|
|
|
|
|
- <div class="page-no">第{{ page.pageNo }}页</div>
|
|
|
|
|
- <div class="page-content">
|
|
|
|
|
- <input v-model="page.title" class="page-title" :disabled="working" />
|
|
|
|
|
|
|
+ <div class="field-grid">
|
|
|
|
|
+ <div class="field-card">
|
|
|
|
|
+ <div class="field-title">页面内容</div>
|
|
|
<textarea
|
|
<textarea
|
|
|
- :value="page.bullets.join('\n')"
|
|
|
|
|
|
|
+ :value="selectedPage.bullets.join('\n')"
|
|
|
:disabled="working"
|
|
:disabled="working"
|
|
|
class="page-bullets"
|
|
class="page-bullets"
|
|
|
- @input="event => updateBullets(page, event)"
|
|
|
|
|
|
|
+ @input="event => updateBullets(selectedPage!, event)"
|
|
|
></textarea>
|
|
></textarea>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <div class="field-card visual">
|
|
|
|
|
+ <div class="field-title">设计要求</div>
|
|
|
|
|
+ <textarea
|
|
|
|
|
+ v-model="selectedPage.designRequirement"
|
|
|
|
|
+ :disabled="working"
|
|
|
|
|
+ class="page-design"
|
|
|
|
|
+ placeholder="例如:左文右图,右侧放卡通山谷和音符,整体暖色活泼,文字保持大字号。"
|
|
|
|
|
+ ></textarea>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <div class="keyword-row">
|
|
|
|
|
+ <span class="keyword-label">图片关键词</span>
|
|
|
|
|
+ <input
|
|
|
|
|
+ :value="selectedPage.imageKeywords.join('、')"
|
|
|
|
|
+ class="keyword-input"
|
|
|
|
|
+ :disabled="working"
|
|
|
|
|
+ placeholder="用于检索网络图片,例如:山谷、音乐、卡通、小鸟"
|
|
|
|
|
+ @input="event => updateImageKeywords(selectedPage!, event)"
|
|
|
|
|
+ />
|
|
|
</div>
|
|
</div>
|
|
|
|
|
+ <div v-if="selectedPage.teacherTips" class="teacher-tip">教师提示:{{ selectedPage.teacherTips }}</div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
- <div v-else class="empty">点击生成大纲后,可在这里确认和微调15页结构。</div>
|
|
|
|
|
|
|
+ <div v-else class="empty">生成大纲后,可在这里确认15页结构、页面内容和设计要求。</div>
|
|
|
|
|
|
|
|
<div class="footer">
|
|
<div class="footer">
|
|
|
<Button @click="emit('update:visible', false)" :disabled="working">取消</Button>
|
|
<Button @click="emit('update:visible', false)" :disabled="working">取消</Button>
|
|
@@ -87,13 +106,11 @@ import { ElMessage, ElMessageBox } from "element-plus"
|
|
|
import Modal from "@/components/Modal.vue"
|
|
import Modal from "@/components/Modal.vue"
|
|
|
import Button from "@/components/Button.vue"
|
|
import Button from "@/components/Button.vue"
|
|
|
import TextArea from "@/components/TextArea.vue"
|
|
import TextArea from "@/components/TextArea.vue"
|
|
|
-import FileInput from "@/components/FileInput.vue"
|
|
|
|
|
import queryParams from "@/queryParams"
|
|
import queryParams from "@/queryParams"
|
|
|
import usePptWork from "@/store/pptWork"
|
|
import usePptWork from "@/store/pptWork"
|
|
|
import { useSlidesStore } from "@/store"
|
|
import { useSlidesStore } from "@/store"
|
|
|
import useHistorySnapshot from "@/hooks/useHistorySnapshot"
|
|
import useHistorySnapshot from "@/hooks/useHistorySnapshot"
|
|
|
import { httpAjaxErrMsg } from "@/plugins/httpAjax"
|
|
import { httpAjaxErrMsg } from "@/plugins/httpAjax"
|
|
|
-import fileUpload from "@/utils/oss-file-upload"
|
|
|
|
|
import {
|
|
import {
|
|
|
aiCoursewareGenerateApi,
|
|
aiCoursewareGenerateApi,
|
|
|
aiCoursewareOutlineApi,
|
|
aiCoursewareOutlineApi,
|
|
@@ -122,13 +139,24 @@ const extraPrompt = ref("")
|
|
|
const outline = ref<AiCoursewareOutline>()
|
|
const outline = ref<AiCoursewareOutline>()
|
|
|
const taskInfo = ref<AiCoursewareTaskInfo>()
|
|
const taskInfo = ref<AiCoursewareTaskInfo>()
|
|
|
const pollTimer = ref<number>()
|
|
const pollTimer = ref<number>()
|
|
|
-const backgroundImageUrl = ref("")
|
|
|
|
|
-const uploadingBackground = ref(false)
|
|
|
|
|
|
|
+const selectedPageNo = ref(1)
|
|
|
const working = computed(() => taskInfo.value?.status === "PENDING" || taskInfo.value?.status === "RUNNING")
|
|
const working = computed(() => taskInfo.value?.status === "PENDING" || taskInfo.value?.status === "RUNNING")
|
|
|
-const outlineMeta = computed(() => {
|
|
|
|
|
- if (!outline.value) return ""
|
|
|
|
|
|
|
+const progressValue = computed(() => taskInfo.value?.progress || 0)
|
|
|
|
|
+const progressSteps = [
|
|
|
|
|
+ { threshold: 0, label: "解析需求" },
|
|
|
|
|
+ { threshold: 25, label: "拆分结构" },
|
|
|
|
|
+ { threshold: 55, label: "生成内容" },
|
|
|
|
|
+ { threshold: 80, label: "匹配图片" }
|
|
|
|
|
+]
|
|
|
|
|
+const pageTitle = computed(() => outline.value?.chapterName || outline.value?.title || "课件大纲")
|
|
|
|
|
+const pageMeta = computed(() => {
|
|
|
|
|
+ if (!outline.value) return "根据当前教材、单元和章节生成15页音乐课件"
|
|
|
return [outline.value.gradeName, outline.value.unitName].filter(Boolean).join(" · ")
|
|
return [outline.value.gradeName, outline.value.unitName].filter(Boolean).join(" · ")
|
|
|
})
|
|
})
|
|
|
|
|
+const selectedPage = computed(() => {
|
|
|
|
|
+ const pages = outline.value?.pages || []
|
|
|
|
|
+ return pages.find(page => page.pageNo === selectedPageNo.value) || pages[0]
|
|
|
|
|
+})
|
|
|
|
|
|
|
|
watch(
|
|
watch(
|
|
|
() => props.visible,
|
|
() => props.visible,
|
|
@@ -151,6 +179,7 @@ async function handleOutline() {
|
|
|
taskInfo.value = undefined
|
|
taskInfo.value = undefined
|
|
|
if (res.code === 200) {
|
|
if (res.code === 200) {
|
|
|
outline.value = res.data as AiCoursewareOutline
|
|
outline.value = res.data as AiCoursewareOutline
|
|
|
|
|
+ selectedPageNo.value = outline.value.pages[0]?.pageNo || 1
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -168,8 +197,7 @@ async function handleGenerate() {
|
|
|
const res = await httpAjaxErrMsg(aiCoursewareGenerateApi, {
|
|
const res = await httpAjaxErrMsg(aiCoursewareGenerateApi, {
|
|
|
id: pptWork.id,
|
|
id: pptWork.id,
|
|
|
fromType: queryParams.pptResourcesType || queryParams.fromType,
|
|
fromType: queryParams.pptResourcesType || queryParams.fromType,
|
|
|
- outline: outline.value,
|
|
|
|
|
- visualPrompt: buildVisualPrompt()
|
|
|
|
|
|
|
+ outline: outline.value
|
|
|
})
|
|
})
|
|
|
if (res.code !== 200) return
|
|
if (res.code !== 200) return
|
|
|
|
|
|
|
@@ -216,7 +244,7 @@ async function applyResult(taskId: string) {
|
|
|
if (res.code !== 200) return
|
|
if (res.code !== 200) return
|
|
|
|
|
|
|
|
const result = res.data as AiCoursewareGenerateResult
|
|
const result = res.data as AiCoursewareGenerateResult
|
|
|
- const converted = aiCoursewareToSlides(result.outline, { backgroundImage: backgroundImageUrl.value })
|
|
|
|
|
|
|
+ const converted = aiCoursewareToSlides(result.outline)
|
|
|
slidesStore.setTitle(converted.title)
|
|
slidesStore.setTitle(converted.title)
|
|
|
slidesStore.setTheme(converted.theme)
|
|
slidesStore.setTheme(converted.theme)
|
|
|
slidesStore.setSlides(converted.slides)
|
|
slidesStore.setSlides(converted.slides)
|
|
@@ -233,43 +261,39 @@ function updateBullets(page: AiCoursewarePage, event: Event) {
|
|
|
.filter(Boolean)
|
|
.filter(Boolean)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-async function handleBackgroundUpload(files: FileList) {
|
|
|
|
|
- if (working.value || uploadingBackground.value) return
|
|
|
|
|
- const file = files[0]
|
|
|
|
|
- if (!file) return
|
|
|
|
|
- if (!file.type.startsWith("image/")) {
|
|
|
|
|
- ElMessage.error("请上传图片文件")
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- if (!pptWork.id) {
|
|
|
|
|
- ElMessage.error("当前课件ID为空")
|
|
|
|
|
- return
|
|
|
|
|
- }
|
|
|
|
|
- uploadingBackground.value = true
|
|
|
|
|
- try {
|
|
|
|
|
- backgroundImageUrl.value = await fileUpload(file.name, file, `${pptWork.id}/ai-background/`, true, { isLoading: true, text: "背景图上传中" })
|
|
|
|
|
- } catch {
|
|
|
|
|
- ElMessage.error("背景图上传失败")
|
|
|
|
|
- } finally {
|
|
|
|
|
- uploadingBackground.value = false
|
|
|
|
|
- }
|
|
|
|
|
|
|
+function updateImageKeywords(page: AiCoursewarePage, event: Event) {
|
|
|
|
|
+ page.imageKeywords = (event.target as HTMLInputElement).value
|
|
|
|
|
+ .split(/[,,、;;\s]+/)
|
|
|
|
|
+ .map(item => item.trim())
|
|
|
|
|
+ .filter(Boolean)
|
|
|
|
|
+ .slice(0, 6)
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function removeBackground() {
|
|
|
|
|
- backgroundImageUrl.value = ""
|
|
|
|
|
|
|
+function buildExtraPrompt() {
|
|
|
|
|
+ return extraPrompt.value.trim()
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function buildExtraPrompt() {
|
|
|
|
|
- const prompts = [extraPrompt.value]
|
|
|
|
|
- if (backgroundImageUrl.value) {
|
|
|
|
|
- prompts.push(buildVisualPrompt())
|
|
|
|
|
- }
|
|
|
|
|
- return prompts.filter(Boolean).join("\n")
|
|
|
|
|
|
|
+function formatPageNo(pageNo?: number) {
|
|
|
|
|
+ return String(pageNo || 0).padStart(2, "0")
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-function buildVisualPrompt() {
|
|
|
|
|
- if (!backgroundImageUrl.value) return ""
|
|
|
|
|
- return "用户将使用自定义16:9背景图作为每页底图。请生成适合叠加在背景图上的课件内容:每页文字更精简,标题清楚,保留留白,强调高对比和清晰层次,避免生成需要大面积纯色背景才能阅读的密集内容。"
|
|
|
|
|
|
|
+function pageTypeLabel(type?: string) {
|
|
|
|
|
+ const labels: Record<string, string> = {
|
|
|
|
|
+ cover: "封面",
|
|
|
|
|
+ intro: "导入",
|
|
|
|
|
+ goal: "目标",
|
|
|
|
|
+ background: "背景",
|
|
|
|
|
+ listen: "聆听",
|
|
|
|
|
+ content: "内容",
|
|
|
|
|
+ knowledge: "知识",
|
|
|
|
|
+ skill: "技能",
|
|
|
|
|
+ interaction: "互动",
|
|
|
|
|
+ activity: "活动",
|
|
|
|
|
+ extension: "拓展",
|
|
|
|
|
+ summary: "总结",
|
|
|
|
|
+ homework: "练习"
|
|
|
|
|
+ }
|
|
|
|
|
+ return labels[type || ""] || "页面"
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|
|
|
|
|
|
|
@@ -278,51 +302,58 @@ function buildVisualPrompt() {
|
|
|
height: 82vh;
|
|
height: 82vh;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
flex-direction: column;
|
|
|
|
|
+ color: #1f2933;
|
|
|
}
|
|
}
|
|
|
.header,
|
|
.header,
|
|
|
-.outline-head,
|
|
|
|
|
.footer {
|
|
.footer {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: space-between;
|
|
justify-content: space-between;
|
|
|
}
|
|
}
|
|
|
.title {
|
|
.title {
|
|
|
- font-size: 18px;
|
|
|
|
|
|
|
+ font-size: 24px;
|
|
|
font-weight: 700;
|
|
font-weight: 700;
|
|
|
color: #1f2933;
|
|
color: #1f2933;
|
|
|
}
|
|
}
|
|
|
-.sub,
|
|
|
|
|
-.outline-meta,
|
|
|
|
|
-.hint {
|
|
|
|
|
|
|
+.sub {
|
|
|
margin-top: 6px;
|
|
margin-top: 6px;
|
|
|
color: #7b8794;
|
|
color: #7b8794;
|
|
|
- font-size: 13px;
|
|
|
|
|
|
|
+ font-size: 14px;
|
|
|
}
|
|
}
|
|
|
-.progress-panel {
|
|
|
|
|
- margin-top: 18px;
|
|
|
|
|
- padding: 14px 16px;
|
|
|
|
|
- border: 1px solid rgba($color: $themeColor, $alpha: 0.2);
|
|
|
|
|
- background: rgba($color: $themeColor, $alpha: 0.06);
|
|
|
|
|
- border-radius: 8px;
|
|
|
|
|
|
|
+.page-count {
|
|
|
|
|
+ color: #52616b;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
}
|
|
}
|
|
|
-.progress-head {
|
|
|
|
|
|
|
+.progress-strip {
|
|
|
|
|
+ margin-top: 12px;
|
|
|
|
|
+ height: 42px;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
|
|
- margin-bottom: 10px;
|
|
|
|
|
|
|
+ gap: 12px;
|
|
|
}
|
|
}
|
|
|
-.progress-title {
|
|
|
|
|
- font-size: 14px;
|
|
|
|
|
- font-weight: 600;
|
|
|
|
|
- color: #1f2933;
|
|
|
|
|
|
|
+.step-list {
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 14px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ color: #9aa5b1;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ span.active {
|
|
|
|
|
+ color: $themeColor;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
.progress-value {
|
|
.progress-value {
|
|
|
- font-size: 14px;
|
|
|
|
|
|
|
+ width: 42px;
|
|
|
|
|
+ text-align: right;
|
|
|
|
|
+ font-size: 13px;
|
|
|
color: $themeColor;
|
|
color: $themeColor;
|
|
|
font-weight: 700;
|
|
font-weight: 700;
|
|
|
}
|
|
}
|
|
|
-.progress-track {
|
|
|
|
|
- height: 8px;
|
|
|
|
|
|
|
+.progress-line {
|
|
|
|
|
+ height: 6px;
|
|
|
|
|
+ flex: 1;
|
|
|
overflow: hidden;
|
|
overflow: hidden;
|
|
|
border-radius: 999px;
|
|
border-radius: 999px;
|
|
|
background: #e7edf5;
|
|
background: #e7edf5;
|
|
@@ -334,104 +365,124 @@ function buildVisualPrompt() {
|
|
|
transition: width 0.25s ease;
|
|
transition: width 0.25s ease;
|
|
|
}
|
|
}
|
|
|
.extra {
|
|
.extra {
|
|
|
- margin-top: 18px;
|
|
|
|
|
|
|
+ margin-top: 12px;
|
|
|
}
|
|
}
|
|
|
-.background-setting {
|
|
|
|
|
- position: relative;
|
|
|
|
|
|
|
+.label {
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ font-size: 14px;
|
|
|
|
|
+ color: #52616b;
|
|
|
|
|
+}
|
|
|
|
|
+.outline {
|
|
|
margin-top: 14px;
|
|
margin-top: 14px;
|
|
|
- padding: 14px 150px 14px 14px;
|
|
|
|
|
- min-height: 74px;
|
|
|
|
|
|
|
+ min-height: 0;
|
|
|
|
|
+ flex: 1;
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: 280px minmax(0, 1fr);
|
|
|
|
|
+ gap: 14px;
|
|
|
|
|
+}
|
|
|
|
|
+.page-nav {
|
|
|
|
|
+ overflow: auto;
|
|
|
border: 1px solid #edf1f5;
|
|
border: 1px solid #edf1f5;
|
|
|
border-radius: 8px;
|
|
border-radius: 8px;
|
|
|
background: #fafcff;
|
|
background: #fafcff;
|
|
|
|
|
+ padding: 8px;
|
|
|
}
|
|
}
|
|
|
-.background-actions {
|
|
|
|
|
|
|
+.nav-item {
|
|
|
|
|
+ width: 100%;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
- gap: 8px;
|
|
|
|
|
- margin-top: 10px;
|
|
|
|
|
-}
|
|
|
|
|
-.upload-btn,
|
|
|
|
|
-.remove-btn {
|
|
|
|
|
- height: 32px;
|
|
|
|
|
- padding: 0 12px;
|
|
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+ min-height: 42px;
|
|
|
|
|
+ padding: 8px 10px;
|
|
|
|
|
+ border: 0;
|
|
|
border-radius: 6px;
|
|
border-radius: 6px;
|
|
|
- border: 1px solid #d9d9d9;
|
|
|
|
|
- background: #fff;
|
|
|
|
|
|
|
+ background: transparent;
|
|
|
|
|
+ color: #52616b;
|
|
|
|
|
+ text-align: left;
|
|
|
cursor: pointer;
|
|
cursor: pointer;
|
|
|
|
|
+ &.active {
|
|
|
|
|
+ background: #eaf4ff;
|
|
|
|
|
+ color: #1f2933;
|
|
|
|
|
+ .nav-no {
|
|
|
|
|
+ color: #fff;
|
|
|
|
|
+ background: $themeColor;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
-.upload-btn {
|
|
|
|
|
- color: $themeColor;
|
|
|
|
|
- border-color: rgba($color: $themeColor, $alpha: 0.55);
|
|
|
|
|
-}
|
|
|
|
|
-.remove-btn {
|
|
|
|
|
|
|
+.nav-no {
|
|
|
|
|
+ width: 34px;
|
|
|
|
|
+ height: 28px;
|
|
|
|
|
+ line-height: 28px;
|
|
|
|
|
+ text-align: center;
|
|
|
|
|
+ border-radius: 5px;
|
|
|
color: #7b8794;
|
|
color: #7b8794;
|
|
|
|
|
+ background: #edf1f5;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ font-size: 12px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
}
|
|
}
|
|
|
-.background-preview {
|
|
|
|
|
- position: absolute;
|
|
|
|
|
- right: 14px;
|
|
|
|
|
- top: 14px;
|
|
|
|
|
- width: 120px;
|
|
|
|
|
- height: 68px;
|
|
|
|
|
- border-radius: 6px;
|
|
|
|
|
- border: 1px solid #d9d9d9;
|
|
|
|
|
- background-size: cover;
|
|
|
|
|
- background-position: center;
|
|
|
|
|
-}
|
|
|
|
|
-.label {
|
|
|
|
|
- margin-bottom: 8px;
|
|
|
|
|
|
|
+.nav-title {
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ overflow: hidden;
|
|
|
|
|
+ text-overflow: ellipsis;
|
|
|
|
|
+ white-space: nowrap;
|
|
|
font-size: 13px;
|
|
font-size: 13px;
|
|
|
- color: #52616b;
|
|
|
|
|
|
|
+ font-weight: 600;
|
|
|
}
|
|
}
|
|
|
-.outline {
|
|
|
|
|
- margin-top: 18px;
|
|
|
|
|
- min-height: 0;
|
|
|
|
|
- flex: 1;
|
|
|
|
|
|
|
+.page-editor {
|
|
|
|
|
+ min-width: 0;
|
|
|
|
|
+ overflow: auto;
|
|
|
|
|
+ border: 1px solid #edf1f5;
|
|
|
|
|
+ border-radius: 8px;
|
|
|
|
|
+ background: #fff;
|
|
|
|
|
+ padding: 16px;
|
|
|
|
|
+}
|
|
|
|
|
+.editor-head {
|
|
|
display: flex;
|
|
display: flex;
|
|
|
- flex-direction: column;
|
|
|
|
|
|
|
+ align-items: flex-start;
|
|
|
|
|
+ justify-content: space-between;
|
|
|
|
|
+ gap: 16px;
|
|
|
}
|
|
}
|
|
|
-.outline-title {
|
|
|
|
|
- font-size: 15px;
|
|
|
|
|
- font-weight: 700;
|
|
|
|
|
|
|
+.editor-page {
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ color: #7b8794;
|
|
|
|
|
+ font-size: 13px;
|
|
|
}
|
|
}
|
|
|
-.count {
|
|
|
|
|
- color: #52616b;
|
|
|
|
|
|
|
+.page-type {
|
|
|
|
|
+ height: 28px;
|
|
|
|
|
+ padding: 0 12px;
|
|
|
|
|
+ line-height: 28px;
|
|
|
|
|
+ border-radius: 999px;
|
|
|
|
|
+ color: $themeColor;
|
|
|
|
|
+ background: rgba($color: $themeColor, $alpha: 0.08);
|
|
|
font-size: 13px;
|
|
font-size: 13px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
}
|
|
|
-.page-list {
|
|
|
|
|
|
|
+.field-grid {
|
|
|
margin-top: 12px;
|
|
margin-top: 12px;
|
|
|
- overflow: auto;
|
|
|
|
|
|
|
+ display: grid;
|
|
|
|
|
+ grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
|
|
|
|
+ gap: 12px;
|
|
|
|
|
+}
|
|
|
|
|
+.field-card {
|
|
|
|
|
+ padding: 12px;
|
|
|
border: 1px solid #edf1f5;
|
|
border: 1px solid #edf1f5;
|
|
|
border-radius: 8px;
|
|
border-radius: 8px;
|
|
|
-}
|
|
|
|
|
-.page-item {
|
|
|
|
|
- display: flex;
|
|
|
|
|
- padding: 14px;
|
|
|
|
|
- border-bottom: 1px solid #edf1f5;
|
|
|
|
|
- background: #fff;
|
|
|
|
|
- &:last-child {
|
|
|
|
|
- border-bottom: 0;
|
|
|
|
|
|
|
+ background: #fbfcfe;
|
|
|
|
|
+ &.visual {
|
|
|
|
|
+ background: #fffaf3;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-.page-no {
|
|
|
|
|
- width: 64px;
|
|
|
|
|
- height: 30px;
|
|
|
|
|
- line-height: 30px;
|
|
|
|
|
- text-align: center;
|
|
|
|
|
- border-radius: 6px;
|
|
|
|
|
- color: #fff;
|
|
|
|
|
- background: $themeColor;
|
|
|
|
|
- flex-shrink: 0;
|
|
|
|
|
|
|
+.field-title {
|
|
|
|
|
+ margin-bottom: 8px;
|
|
|
|
|
+ color: #52616b;
|
|
|
font-size: 13px;
|
|
font-size: 13px;
|
|
|
font-weight: 600;
|
|
font-weight: 600;
|
|
|
}
|
|
}
|
|
|
-.page-content {
|
|
|
|
|
- margin-left: 12px;
|
|
|
|
|
- min-width: 0;
|
|
|
|
|
- flex: 1;
|
|
|
|
|
-}
|
|
|
|
|
.page-title,
|
|
.page-title,
|
|
|
-.page-bullets {
|
|
|
|
|
|
|
+.page-bullets,
|
|
|
|
|
+.page-design,
|
|
|
|
|
+.keyword-input {
|
|
|
width: 100%;
|
|
width: 100%;
|
|
|
border: 1px solid #d9d9d9;
|
|
border: 1px solid #d9d9d9;
|
|
|
border-radius: 6px;
|
|
border-radius: 6px;
|
|
@@ -441,18 +492,45 @@ function buildVisualPrompt() {
|
|
|
.page-title {
|
|
.page-title {
|
|
|
height: 36px;
|
|
height: 36px;
|
|
|
padding: 0 10px;
|
|
padding: 0 10px;
|
|
|
|
|
+ font-size: 16px;
|
|
|
font-weight: 600;
|
|
font-weight: 600;
|
|
|
}
|
|
}
|
|
|
-.page-bullets {
|
|
|
|
|
- margin-top: 8px;
|
|
|
|
|
- height: 86px;
|
|
|
|
|
|
|
+.page-bullets,
|
|
|
|
|
+.page-design {
|
|
|
|
|
+ height: 180px;
|
|
|
padding: 10px;
|
|
padding: 10px;
|
|
|
resize: vertical;
|
|
resize: vertical;
|
|
|
line-height: 1.5;
|
|
line-height: 1.5;
|
|
|
}
|
|
}
|
|
|
|
|
+.keyword-row {
|
|
|
|
|
+ margin-top: 12px;
|
|
|
|
|
+ display: flex;
|
|
|
|
|
+ align-items: center;
|
|
|
|
|
+ gap: 10px;
|
|
|
|
|
+}
|
|
|
|
|
+.keyword-label {
|
|
|
|
|
+ width: 72px;
|
|
|
|
|
+ flex-shrink: 0;
|
|
|
|
|
+ color: #52616b;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ font-weight: 600;
|
|
|
|
|
+}
|
|
|
|
|
+.keyword-input {
|
|
|
|
|
+ height: 34px;
|
|
|
|
|
+ padding: 0 10px;
|
|
|
|
|
+}
|
|
|
|
|
+.teacher-tip {
|
|
|
|
|
+ margin-top: 12px;
|
|
|
|
|
+ padding: 10px 12px;
|
|
|
|
|
+ border-radius: 6px;
|
|
|
|
|
+ color: #52616b;
|
|
|
|
|
+ background: #f7f9fc;
|
|
|
|
|
+ font-size: 13px;
|
|
|
|
|
+ line-height: 1.5;
|
|
|
|
|
+}
|
|
|
.empty {
|
|
.empty {
|
|
|
flex: 1;
|
|
flex: 1;
|
|
|
- margin-top: 18px;
|
|
|
|
|
|
|
+ margin-top: 14px;
|
|
|
display: flex;
|
|
display: flex;
|
|
|
align-items: center;
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
justify-content: center;
|
|
@@ -463,7 +541,7 @@ function buildVisualPrompt() {
|
|
|
min-height: 20px;
|
|
min-height: 20px;
|
|
|
}
|
|
}
|
|
|
.footer {
|
|
.footer {
|
|
|
- margin-top: 18px;
|
|
|
|
|
|
|
+ margin-top: 14px;
|
|
|
justify-content: flex-end;
|
|
justify-content: flex-end;
|
|
|
gap: 10px;
|
|
gap: 10px;
|
|
|
}
|
|
}
|