|
@@ -0,0 +1,405 @@
|
|
|
|
+<template>
|
|
|
|
+ <div v-if="!loading" class="box-border pb-80">
|
|
|
|
+ <CreateNoteHeaderBanner v-model:bannerUrl="noteJson.travelNotesBanner" />
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ <CreateNoteForm
|
|
|
|
+ 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 items-center pl-16 w-full h-40 ">
|
|
|
|
+ <div class="w-2 h-14 bg-[#FF9300] mr-16"></div>
|
|
|
|
+ <h1 class="text-sm font-bold">编辑游记文章</h1>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ <van-cell-group class="border" inset>
|
|
|
|
+ <van-field
|
|
|
|
+ v-model="noteJson.projectTitle"
|
|
|
|
+ rows="1"
|
|
|
|
+ autosize
|
|
|
|
+ label-width="5"
|
|
|
|
+ type="textarea"
|
|
|
|
+ placeholder="从这里开始游记大标题..."
|
|
|
|
+ maxlength="50"
|
|
|
|
+ show-word-limit
|
|
|
|
+ >
|
|
|
|
+ </van-field>
|
|
|
|
+ </van-cell-group>
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ <div class="fixed box-border p-16 shadow-[0_-4px_4px_0px_rgba(0,0,0,0.1)] bottom-0 left-0 w-full h-80 flex justify-between bg-white items-center">
|
|
|
|
+ <div class="flex justify-start items-center h-50">
|
|
|
|
+ <div class="w-50 text-center">
|
|
|
|
+ <van-icon name="notes-o" size="24px" />
|
|
|
|
+ <p class="text-sm text-black-3">草稿</p>
|
|
|
|
+ </div>
|
|
|
|
+ <div class="w-50 text-center">
|
|
|
|
+ <van-icon name="eye-o" size="24px" />
|
|
|
|
+ <p class="text-sm text-black-3">预览</p>
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+ </div>
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ <van-button class="w-full h-full flex items-center" size="large" type="primary" round color="#FD9A00" block>
|
|
|
|
+ <div class=" inline-block w-22 h-22 ">
|
|
|
|
+ <van-image
|
|
|
|
+ width="100%"
|
|
|
|
+ height="100%"
|
|
|
|
+ :src="upload"
|
|
|
|
+ />
|
|
|
|
+ </div> 发布
|
|
|
|
+ </van-button>
|
|
|
|
+
|
|
|
|
+
|
|
|
|
+ </div>
|
|
|
|
+ <div
|
|
|
|
+ class="flex justify-center "
|
|
|
|
+ >
|
|
|
|
+ <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>
|
|
|
|
+ </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 upload from '../../assets/img/note-create/upload.svg'
|
|
|
|
+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) {
|
|
|
|
+ showNotify({
|
|
|
|
+ message: '请设置游记头图',
|
|
|
|
+ duration: 3000,
|
|
|
|
+ });
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if (!noteJson.projectTitle) {
|
|
|
|
+ showNotify({
|
|
|
|
+ message: '请输入游记标题',
|
|
|
|
+ duration: 3000,
|
|
|
|
+ });
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if (!noteJson.endPlace) {
|
|
|
|
+ showNotify({
|
|
|
|
+ message: '请选择目的地',
|
|
|
|
+ duration: 3000,
|
|
|
|
+ });
|
|
|
|
+ return
|
|
|
|
+ }
|
|
|
|
+ if (noteJson.travelNotesContent.length === 0) {
|
|
|
|
+ showNotify({
|
|
|
|
+ message: '游记内容不能为空',
|
|
|
|
+ duration: 3000,
|
|
|
|
+ });
|
|
|
|
+ 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>
|
|
|
|
+
|
|
|
|
+<style lang="scss" scoped></style>
|