|
@@ -1,340 +1,20 @@
|
|
|
<template>
|
|
|
- <div v-if="!loading">
|
|
|
- <CreateNoteHeaderBanner v-model:bannerUrl="noteJson.travelNotesBanner" />
|
|
|
- <CreateNoteHeaderTitle v-model="noteJson.projectTitle" />
|
|
|
- <CreateNoteForm
|
|
|
- class="mt-40"
|
|
|
- v-model:departureTime="noteJson.departureTime"
|
|
|
- v-model:countTimes="noteJson.countTimes"
|
|
|
- v-model:endPlace="noteJson.endPlace"
|
|
|
- v-model:role="noteJson.role"
|
|
|
- v-model:travelMode="noteJson.travelMode"
|
|
|
- v-model:averageCost="noteJson.averageCost"
|
|
|
- v-model:recommendationRate="noteJson.recommendationRate"
|
|
|
- v-model:travelNumber="noteJson.travelNumber"
|
|
|
- />
|
|
|
- <div
|
|
|
- class="flex justify-center bg-[url('~/assets/img/note-create/note_create_content_bg.png')] bg-contain bg-bottom bg-no-repeat"
|
|
|
- >
|
|
|
- <div class="mx-auto mt-30 flex w-wrap space-x-50">
|
|
|
- <div class="min-h-360 w-[860px] pb-80">
|
|
|
- <div>
|
|
|
- <VueDraggable v-model="noteJson.travelNotesContent">
|
|
|
- <template
|
|
|
- v-for="(item, index) in noteJson.travelNotesContent"
|
|
|
- :key="item.tmpId"
|
|
|
- >
|
|
|
- <CreateNoteInsertTitleSection
|
|
|
- v-if="item.type === defaultSectionTitle.type"
|
|
|
- :title="item.content"
|
|
|
- @onEdit="handleInsertOrEditTitle(index)"
|
|
|
- @onDelete="handleDeleteTitle(index)"
|
|
|
- />
|
|
|
- <template v-else-if="item.type === defaultSectionContent.type">
|
|
|
- <CreateNoteInsertContentSection
|
|
|
- v-model="item.content"
|
|
|
- @on-delete="handleDeleteContent(index)"
|
|
|
- />
|
|
|
- </template>
|
|
|
- <template v-else-if="item.type === defaultSectionImage.type">
|
|
|
- <CreateNoteInsertImageSection
|
|
|
- :url="item.content"
|
|
|
- @on-save-cover="handleSaveCover(item)"
|
|
|
- @on-delete="handleDeleteImage(index)"
|
|
|
- />
|
|
|
- </template>
|
|
|
- </template>
|
|
|
- </VueDraggable>
|
|
|
- <CreateNoteBottomActions
|
|
|
- class="mt-50"
|
|
|
- :publishLoading="publishLoading"
|
|
|
- @on-preview="handlePreview"
|
|
|
- @on-publish="handlePublish"
|
|
|
- />
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <el-affix :offset="20">
|
|
|
- <CreateNoteLeftActions
|
|
|
- @on-insert-title="handleInsertOrEditTitle"
|
|
|
- @on-insert-content="handleInsertContent"
|
|
|
- @on-insert-img="handleInsertImage"
|
|
|
- @on-save-draft="handleSaveDraft"
|
|
|
- />
|
|
|
- </el-affix>
|
|
|
+ <div class="flex items-center justify-center bg-[#EEF1F8] pb-200 pt-20">
|
|
|
+ <div class="shadow-[0_4px_4px_0px_#e4e7ef w-wrap rounded-lg bg-white pb-20">
|
|
|
+ <div
|
|
|
+ class="flex h-50 items-center space-x-5 border-b border-[#E7E7E7] px-15 text-xl font-semibold text-black-3"
|
|
|
+ >
|
|
|
+ <span class="iconfont icon-left text-black-9"></span>
|
|
|
+ <span>发布图文</span>
|
|
|
+ </div>
|
|
|
+ <div class="px-20">
|
|
|
+ <NoteCreatePic />
|
|
|
+ <NoteCreateForm />
|
|
|
</div>
|
|
|
</div>
|
|
|
-
|
|
|
- <CreateNoteInsertTitleModal
|
|
|
- v-model:visible="insertTilteOptions.show"
|
|
|
- :title="insertTilteOptions.content"
|
|
|
- @on-ok="handleInsertOrEditTitleOk"
|
|
|
- />
|
|
|
- <CreateNoteInsertImageModal
|
|
|
- v-model:visible="insertImageOptions.show"
|
|
|
- @on-ok="handleInsertImageOk"
|
|
|
- />
|
|
|
- <CreateNotePreviewModal
|
|
|
- v-model:visible="previewOptions.show"
|
|
|
- :data="noteJson"
|
|
|
- />
|
|
|
- <CreateNoteUserInfoModal
|
|
|
- v-model:visible="userInfoOptions.show"
|
|
|
- @submit-ok="handleCollectUserInfoOk"
|
|
|
- />
|
|
|
- <CreateNotePublishResultModal
|
|
|
- v-model:visible="publishResultModalOptions.show"
|
|
|
- />
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
-<script setup>
|
|
|
-import { cloneDeep } from 'lodash-es'
|
|
|
-import { VueDraggable } from 'vue-draggable-plus'
|
|
|
-import { nanoid } from 'nanoid'
|
|
|
-
|
|
|
-const { loading, setLoading } = useLoading()
|
|
|
-loading.value = false
|
|
|
-
|
|
|
-const defaultSectionTitle = {
|
|
|
- type: 'sectionTitle',
|
|
|
- content: ''
|
|
|
-}
|
|
|
-const defaultSectionContent = {
|
|
|
- type: 'sectionContent',
|
|
|
- content: ''
|
|
|
-}
|
|
|
-const defaultSectionImage = {
|
|
|
- type: 'image',
|
|
|
- content: ''
|
|
|
-}
|
|
|
-
|
|
|
-const defaultNoteJson = {
|
|
|
- travelNotesBanner: null,
|
|
|
- projectTitle: null,
|
|
|
- departureTime: null,
|
|
|
- countTimes: null,
|
|
|
- endPlace: null,
|
|
|
- role: null,
|
|
|
- travelMode: null,
|
|
|
- averageCost: null,
|
|
|
- recommendationRate: null,
|
|
|
- travelNumber: null,
|
|
|
- travelNotesContent: []
|
|
|
-}
|
|
|
-const noteJson = reactive(defaultNoteJson)
|
|
|
-
|
|
|
-watch(noteJson, () => {}, { deep: true })
|
|
|
-
|
|
|
-const id = useRouteQuery('id')
|
|
|
-
|
|
|
-watch(
|
|
|
- id,
|
|
|
- () => {
|
|
|
- getNoteDetail()
|
|
|
- },
|
|
|
- { immediate: true }
|
|
|
-)
|
|
|
-
|
|
|
-// 获取草稿详情
|
|
|
-async function getNoteDetail() {
|
|
|
- try {
|
|
|
- setLoading(true)
|
|
|
- const res = await request(
|
|
|
- `/website/tourism/publishTravelNotes/getDraftDetail?writeId=${id.value}`
|
|
|
- )
|
|
|
- const data = res.data ?? {}
|
|
|
- Object.keys(noteJson).forEach((key) => {
|
|
|
- noteJson[key] = data[key]
|
|
|
- noteJson.travelNotesContent = data.travelNotesContent ?? []
|
|
|
- if (noteJson.travelNotesContent.length === 0) {
|
|
|
- noteJson.travelNotesContent.push({
|
|
|
- type: defaultSectionContent.type,
|
|
|
- content: '',
|
|
|
- tmpId: nanoid()
|
|
|
- })
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- setLoading(false)
|
|
|
- } catch (error) {
|
|
|
- setLoading(false)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-/************ 插入段落标题逻辑 ********** */
|
|
|
-
|
|
|
-const insertTilteOptions = reactive({
|
|
|
- show: false,
|
|
|
- content: null,
|
|
|
- editIndex: null
|
|
|
-})
|
|
|
-
|
|
|
-// 点击编辑或者新增段落标题,弹出dialog
|
|
|
-function handleInsertOrEditTitle(index) {
|
|
|
- if (index === null || index === undefined) {
|
|
|
- // 新增
|
|
|
- insertTilteOptions.editIndex = null
|
|
|
- insertTilteOptions.content = null
|
|
|
- } else {
|
|
|
- // 编辑
|
|
|
- insertTilteOptions.editIndex = index
|
|
|
- insertTilteOptions.content = noteJson.travelNotesContent[index].content
|
|
|
- }
|
|
|
- insertTilteOptions.show = true
|
|
|
-}
|
|
|
-
|
|
|
-// 确认编辑或者新增段落标题
|
|
|
-function handleInsertOrEditTitleOk(newTitle) {
|
|
|
- if (insertTilteOptions.editIndex === null) {
|
|
|
- noteJson.travelNotesContent.push({
|
|
|
- type: defaultSectionTitle.type,
|
|
|
- content: newTitle,
|
|
|
- tmpId: nanoid()
|
|
|
- })
|
|
|
- } else {
|
|
|
- noteJson.travelNotesContent[insertTilteOptions.editIndex].content = newTitle
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 删除段落标题
|
|
|
-function handleDeleteTitle(index) {
|
|
|
- noteJson.travelNotesContent.splice(index, 1)
|
|
|
-}
|
|
|
-
|
|
|
-/******************插入正文相关逻辑*******************/
|
|
|
-
|
|
|
-function handleInsertContent() {
|
|
|
- noteJson.travelNotesContent.push(
|
|
|
- cloneDeep({
|
|
|
- ...defaultSectionContent,
|
|
|
- tmpId: nanoid()
|
|
|
- })
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
-function handleDeleteContent(index) {
|
|
|
- noteJson.travelNotesContent.splice(index, 1)
|
|
|
-}
|
|
|
-
|
|
|
-/******************插入图片逻辑*******************/
|
|
|
-const insertImageOptions = reactive({
|
|
|
- show: false
|
|
|
-})
|
|
|
-function handleInsertImage() {
|
|
|
- insertImageOptions.show = true
|
|
|
-}
|
|
|
-
|
|
|
-function handleInsertImageOk(fileUrlList) {
|
|
|
- const imageList = fileUrlList.map((e) => ({
|
|
|
- type: defaultSectionImage.type,
|
|
|
- content: e.fileUrl,
|
|
|
- tmpId: nanoid()
|
|
|
- }))
|
|
|
- noteJson.travelNotesContent = (noteJson.travelNotesContent ?? []).concat(
|
|
|
- imageList
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
-function handleDeleteImage(index) {
|
|
|
- if (noteJson.travelNotesContent[index].type === 'image') {
|
|
|
- noteJson.travelNotesContent.splice(index, 1)
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 设为封面图
|
|
|
-function handleSaveCover(item) {
|
|
|
- noteJson.travelNotesContent.forEach((e) => {
|
|
|
- if (e.type === 'image') {
|
|
|
- e.cover = 0
|
|
|
- }
|
|
|
- item.cover = 1
|
|
|
- })
|
|
|
- ElMessage.success('设置封面成功')
|
|
|
-}
|
|
|
-
|
|
|
-//保存为草稿
|
|
|
-async function handleSaveDraft() {
|
|
|
- try {
|
|
|
- await request('/website/tourism/publishTravelNotes/saveDraft', {
|
|
|
- method: 'post',
|
|
|
- body: {
|
|
|
- ...noteJson,
|
|
|
- id: id.value
|
|
|
- }
|
|
|
- })
|
|
|
- ElMessage.success('草稿保存成功')
|
|
|
- } finally {
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-// 预览
|
|
|
-const previewOptions = reactive({
|
|
|
- show: false
|
|
|
-})
|
|
|
-function handlePreview() {
|
|
|
- previewOptions.show = true
|
|
|
-}
|
|
|
-
|
|
|
-// 收集个人信息
|
|
|
-const userInfoOptions = reactive({
|
|
|
- show: false
|
|
|
-})
|
|
|
-
|
|
|
-function handleCollectUserInfoOk() {
|
|
|
- requestPublish()
|
|
|
-}
|
|
|
-
|
|
|
-const publishResultModalOptions = reactive({
|
|
|
- show: false
|
|
|
-})
|
|
|
-
|
|
|
-// 发布
|
|
|
-async function handlePublish() {
|
|
|
- if (!noteJson.travelNotesBanner) {
|
|
|
- ElMessage.warning('请设置游记头图')
|
|
|
- return
|
|
|
- }
|
|
|
- if (!noteJson.projectTitle) {
|
|
|
- ElMessage.warning('请输入游记标题')
|
|
|
- return
|
|
|
- }
|
|
|
- if (!noteJson.endPlace) {
|
|
|
- ElMessage.warning('请选择目的地')
|
|
|
- return
|
|
|
- }
|
|
|
- if (noteJson.travelNotesContent.length === 0) {
|
|
|
- ElMessage.warning('游记内容不能为空')
|
|
|
- return
|
|
|
- }
|
|
|
-
|
|
|
- const { data: isPerfect } = await request(
|
|
|
- '/website/tourism/publishTravelNotes/isPerfect'
|
|
|
- )
|
|
|
- if (isPerfect === 0) {
|
|
|
- // 需要收集个人信息
|
|
|
- userInfoOptions.show = true
|
|
|
- } else {
|
|
|
- requestPublish()
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-const publishLoading = ref(false)
|
|
|
-async function requestPublish() {
|
|
|
- try {
|
|
|
- publishLoading.value = true
|
|
|
- await request('/website/tourism/publishTravelNotes/publishDraft', {
|
|
|
- method: 'post',
|
|
|
- body: {
|
|
|
- ...noteJson,
|
|
|
- id: id.value
|
|
|
- }
|
|
|
- })
|
|
|
- publishResultModalOptions.show = true
|
|
|
- publishLoading.value = false
|
|
|
- } catch (error) {
|
|
|
- publishLoading.value = false
|
|
|
- }
|
|
|
-}
|
|
|
-</script>
|
|
|
+<script setup></script>
|
|
|
|
|
|
<style lang="scss" scoped></style>
|