|
@@ -1,52 +1,44 @@
|
|
|
<template>
|
|
|
- <div v-if="!loading" class="box-border pb-110">
|
|
|
+ <div v-if="!loading" class="h-[100vh] box-border pb-110">
|
|
|
<div v-if="!previewOptions.show">
|
|
|
<CreateNoteHeaderImage
|
|
|
v-model:bannerUrl="noteJson.travelNotesBanner"
|
|
|
v-model:imgUrls="noteJson.imgUrls"
|
|
|
v-model:show="showExpandTextInput"
|
|
|
/>
|
|
|
+ <div ref="projectTitleRef">
|
|
|
+ <van-field
|
|
|
+ style="font-size: 16px; font-weight: 600"
|
|
|
+ size="large"
|
|
|
+ v-model="noteJson.projectTitle"
|
|
|
+ rows="1"
|
|
|
+ autosize
|
|
|
+ clearable
|
|
|
+ type="textarea"
|
|
|
+ @update:model-value="handleInsertOrEditProjectTitle"
|
|
|
+ @focus="showExpandTextInput = true"
|
|
|
+ placeholder="请输入游记标题"
|
|
|
+ maxlength="50"
|
|
|
+ ></van-field>
|
|
|
+ </div>
|
|
|
|
|
|
- <van-field
|
|
|
- style="font-size: 16px; font-weight: 600"
|
|
|
- size="large"
|
|
|
- v-model="noteJson.projectTitle"
|
|
|
- rows="1"
|
|
|
- autosize
|
|
|
- clearable
|
|
|
- type="textarea"
|
|
|
- @update:model-value="handleInsertOrEditProjectTitle"
|
|
|
- @focus="showExpandTextInput = true"
|
|
|
- @blur="showExpandTextInput = false"
|
|
|
- placeholder="请输入游记标题"
|
|
|
- maxlength="50"
|
|
|
- ></van-field>
|
|
|
<div class="border-b-[1px] mx-16"></div>
|
|
|
|
|
|
<div style="overflow: hidden; overflow-y: scroll" class="h-200 mb-12">
|
|
|
- <div class="mb-12 relative box-border">
|
|
|
+ <div ref="editorFather" :class="`min-h-160 mx-12 my-16 w-[93%] mb-20 text-sm `">
|
|
|
<div
|
|
|
- id="view-note"
|
|
|
- contenteditable
|
|
|
- class="inputSectionContent w-[93%] box-border min-h-100 mx-12 my-16 text-sm"
|
|
|
- ref="inputDiv"
|
|
|
- @click="handleInsertOrEditTitleIndex2"
|
|
|
- @input="handleInsertOrEditTitleText"
|
|
|
- @focus="showExpandTextInput = true"
|
|
|
- @blur="showExpandTextInput = false"
|
|
|
- v-html="noteJson?.tourTourismProjectTravelNotesWriteContentDto?.content"
|
|
|
+ ref="editor"
|
|
|
+ class="inputSectionContent"
|
|
|
+ v-html="noteJson?.tourTourismProjectTravelNotesWriteContentDto.content"
|
|
|
></div>
|
|
|
- <!-- <div class="min-h-100 mx-12 my-16 w-full mb-20 text-sm">
|
|
|
- <div ref="editor" id="editor" class="min-h-100"></div>
|
|
|
- </div> -->
|
|
|
</div>
|
|
|
</div>
|
|
|
|
|
|
- <div
|
|
|
+ <!-- <div
|
|
|
v-if="(topicList && topicList.length) || (eitList && eitList.length)"
|
|
|
class="w-full px-16 box-border max-h-227 overflow-y-auto overflow-hidden"
|
|
|
>
|
|
|
- <!-- 话题 -->
|
|
|
+ 话题
|
|
|
<template v-if="showTopicEit == TOPIC_TEXT">
|
|
|
<div
|
|
|
v-for="item in topicList"
|
|
@@ -68,7 +60,7 @@
|
|
|
</template>
|
|
|
|
|
|
<template v-if="showTopicEit == EIT_TEXT">
|
|
|
- <!-- 艾特的好友 -->
|
|
|
+ 艾特的好友
|
|
|
<div
|
|
|
v-for="item in eitList"
|
|
|
:key="item?.userId"
|
|
@@ -81,21 +73,24 @@
|
|
|
<p class="text-sm text-black-6 line-clamp-1">{{ item?.showName }}</p>
|
|
|
</div>
|
|
|
</template>
|
|
|
- </div>
|
|
|
+ </div> -->
|
|
|
<div
|
|
|
- :class="`${showExpandTextInput ? 'fixed bottom-0 left-0' : ''} px-16 flex justify-start mb-12`"
|
|
|
+ :class="`${showExpandTextInput ? 'fixed bottom-390 left-0' : ''} w-full px-16 flex justify-between mb-12`"
|
|
|
>
|
|
|
- <button
|
|
|
- v-for="(operate, i) in userControlsList"
|
|
|
- :key="operate?.title + i"
|
|
|
- @click="handleOperate(operate)"
|
|
|
- class="h-26 shrink-0 active:bg-[#000]/[0.1] box-border text-[10px] px-8 mr-8 text-black-3 flex justify-center rounded-full items-center bg-[#F3F3F3]"
|
|
|
- >
|
|
|
- {{ operate?.title }}
|
|
|
- </button>
|
|
|
+ <div class="flex shrink-0 justify-start">
|
|
|
+ <button
|
|
|
+ v-for="(operate, i) in userControlsList"
|
|
|
+ :key="operate?.title + i"
|
|
|
+ @click="openMention(operate.icon)"
|
|
|
+ :ref="(el) => (checkboxRefs[i] = el)"
|
|
|
+ class="h-26 shrink-0 active:bg-[#000]/[0.1] box-border text-[10px] px-8 mr-8 text-black-3 flex justify-center rounded-full items-center bg-[#F3F3F3]"
|
|
|
+ >
|
|
|
+ {{ operate?.title }}
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
|
|
|
<button
|
|
|
- @click="expandTextInput"
|
|
|
+ @click.stop="showExpandTextInput = !showExpandTextInput"
|
|
|
class="h-26 ml-auto shrink-0 active:bg-[#000]/[0.1] box-border text-[10px] px-8 text-black-3 flex justify-center rounded-full items-center bg-[#F3F3F3]"
|
|
|
>
|
|
|
<img
|
|
@@ -260,13 +255,25 @@
|
|
|
></div>
|
|
|
|
|
|
<div class="text-sm text-black-6 py-12 mb-12 box-border border-b-[1px]">
|
|
|
- <span class="mr-8">出发时间{{ contentIsEmpty(defaultNoteJson?.departureTime) }}</span>
|
|
|
- <span class="mr-8">出行方式{{ contentIsEmpty(defaultNoteJson?.travelMode) }}</span>
|
|
|
- <span class="mr-8">出发天数{{ contentIsEmpty(defaultNoteJson?.countTimes) }}</span>
|
|
|
- <span class="mr-8">游玩人数{{ contentIsEmpty(defaultNoteJson?.travelNumber) }}</span>
|
|
|
- <span class="mr-8">人物关系{{ contentIsEmpty(defaultNoteJson?.role) }}</span>
|
|
|
- <span class="mr-8">人均费用{{ contentIsEmpty(defaultNoteJson?.averageCost) }}</span>
|
|
|
- <span class="mr-8">
|
|
|
+ <span v-if="detailData?.departureTime" class="mr-8">
|
|
|
+ 出发时间{{ contentIsEmpty(defaultNoteJson?.departureTime) }}
|
|
|
+ </span>
|
|
|
+ <span v-if="detailData?.travelMode" class="mr-8">
|
|
|
+ 出行方式{{ contentIsEmpty(defaultNoteJson?.travelMode) }}
|
|
|
+ </span>
|
|
|
+ <span v-if="detailData?.countTimes" class="mr-8">
|
|
|
+ 出发天数{{ contentIsEmpty(defaultNoteJson?.countTimes) }}
|
|
|
+ </span>
|
|
|
+ <span v-if="detailData?.travelNumber" class="mr-8">
|
|
|
+ 游玩人数{{ contentIsEmpty(defaultNoteJson?.travelNumber) }}
|
|
|
+ </span>
|
|
|
+ <span v-if="detailData?.role" class="mr-8">
|
|
|
+ 人物关系{{ contentIsEmpty(defaultNoteJson?.role) }}
|
|
|
+ </span>
|
|
|
+ <span v-if="detailData?.averageCost" class="mr-8">
|
|
|
+ 人均费用{{ contentIsEmpty(defaultNoteJson?.averageCost) }}
|
|
|
+ </span>
|
|
|
+ <span v-if="detailData?.recommendationRate" class="mr-8">
|
|
|
推荐指数{{ contentIsEmpty(defaultNoteJson?.recommendationRate) }}
|
|
|
</span>
|
|
|
</div>
|
|
@@ -330,6 +337,7 @@
|
|
|
v-model:visible="userInfoOptions.show"
|
|
|
@submit-ok="handleCollectUserInfoOk"
|
|
|
/>
|
|
|
+ <!-- 继续发布组件 -->
|
|
|
<CreateNotePublishResultModal v-model:visible="publishResultModalOptions" />
|
|
|
</div>
|
|
|
</template>
|
|
@@ -337,85 +345,42 @@
|
|
|
<script setup>
|
|
|
import upload from '~/assets/img/note-create/upload.svg'
|
|
|
import draft from '~/assets/img/note-create/draft.svg'
|
|
|
-// import { VueDraggable } from 'vue-draggable-plus'
|
|
|
import defaultAvatar from '~/assets/img/default_avatar.png'
|
|
|
-// import { nanoid } from 'nanoid'
|
|
|
-// import Quill from 'quill'
|
|
|
-// import 'quill-mention/dist/quill.mention.min.css'
|
|
|
-// import { Mention, MentionBlot } from 'quill-mention'
|
|
|
-// Quill.register({ 'blots/mention': MentionBlot, 'modules/mention': Mention })
|
|
|
-
|
|
|
-// import { cloneDeep } from 'lodash-es'
|
|
|
+import Quill from 'quill'
|
|
|
+import 'quill-mention/dist/quill.mention.min.css'
|
|
|
+import { Mention, MentionBlot } from 'quill-mention'
|
|
|
+Quill.register({ 'blots/mention': MentionBlot, 'modules/mention': Mention })
|
|
|
|
|
|
const userInfoStore = useUserInfoStore()
|
|
|
const { userInfo } = storeToRefs(userInfoStore)
|
|
|
|
|
|
-// const editor = ref(null)
|
|
|
-// const editorInnerHTML = ref('')
|
|
|
-
|
|
|
-// const getMember = () => {
|
|
|
-// let domStr = editor.value.outerHTML
|
|
|
-// let reg = /<span[^>]+data-id=['"]([^'"]+)['"]+/g
|
|
|
-// let result = [],
|
|
|
-// temp
|
|
|
-// while ((temp = reg.exec(domStr)) != null) {
|
|
|
-// if (temp[0].includes('data-denotation-char="@"')) result.push({ id: temp[1] })
|
|
|
-// }
|
|
|
-// return result
|
|
|
-// }
|
|
|
-// const getTalk = () => {
|
|
|
-// let domStr = editor.value.outerHTML
|
|
|
-// let reg = /<span[^>]+data-id=['"]([^'"]+)['"]+/g
|
|
|
-// let result = [],
|
|
|
-// temp
|
|
|
-// while ((temp = reg.exec(domStr)) != null) {
|
|
|
-// if (temp[0].includes('data-denotation-char="#"')) result.push({ id: temp[1] })
|
|
|
-// }
|
|
|
-// return result
|
|
|
-// }
|
|
|
-
|
|
|
const TOPIC_TEXT = 'topic'
|
|
|
const EIT_TEXT = 'eit'
|
|
|
-const PTITLE_TEXT = 'paragraphTitle'
|
|
|
-const PCONTENT_TEXT = 'paragraphContent'
|
|
|
|
|
|
+const checkboxRefs = ref([null, null])
|
|
|
const userControlsList = ref([
|
|
|
{
|
|
|
+ icon: '#',
|
|
|
title: '# 话题',
|
|
|
fn: TOPIC_TEXT,
|
|
|
empty: '暂无话题',
|
|
|
apiUrl: '/website/tourism/publishTravelNotes/getTopicListByName'
|
|
|
},
|
|
|
{
|
|
|
+ icon: '@',
|
|
|
title: '@ 用户',
|
|
|
fn: EIT_TEXT,
|
|
|
empty: '暂无用户',
|
|
|
apiUrl: '/website/tourism/publishTravelNotes/getFouceEachFriendsByName'
|
|
|
}
|
|
|
- // {
|
|
|
- // title: 'H 段落标题',
|
|
|
- // fn: PTITLE_TEXT
|
|
|
- // },
|
|
|
- // {
|
|
|
- // title: 'P 段落内容',
|
|
|
- // fn: PCONTENT_TEXT
|
|
|
- // }
|
|
|
])
|
|
|
|
|
|
const { loading, setLoading } = useLoading()
|
|
|
loading.value = false
|
|
|
|
|
|
-const defaultSectionTitle = {
|
|
|
- type: 'sectionTitle',
|
|
|
- content: ''
|
|
|
-}
|
|
|
-const defaultSectionContent = {
|
|
|
- type: 'sectionContent',
|
|
|
- content: ''
|
|
|
-}
|
|
|
-
|
|
|
const swipeItemIndex = ref(1)
|
|
|
const inputDiv = ref(null)
|
|
|
+const projectTitleRef = ref(null)
|
|
|
|
|
|
const defaultNoteJson = {
|
|
|
travelNotesBanner: null,
|
|
@@ -439,13 +404,13 @@ const defaultNoteJson = {
|
|
|
chauId: null, // 洲 id
|
|
|
imgUrls: [], // 头部的图片数组
|
|
|
topics: [], // 话题
|
|
|
+ topicsList: [], //保存草稿箱用来校验 话题是否新增的
|
|
|
mentions: [] // 艾特的用户 {userName:"",userId:123}
|
|
|
}
|
|
|
+const id = useRouteQuery('id')
|
|
|
const noteJson = reactive(defaultNoteJson)
|
|
|
-
|
|
|
watch(noteJson, () => {}, { deep: true, immediate: true })
|
|
|
|
|
|
-const id = useRouteQuery('id')
|
|
|
let currentMention2 = '' // 当前正在输入的 # 用户名
|
|
|
|
|
|
watch(
|
|
@@ -457,13 +422,6 @@ watch(
|
|
|
{ immediate: true }
|
|
|
)
|
|
|
|
|
|
-// // 删除的弹窗内容
|
|
|
-// const deleteDialogContent = {
|
|
|
-// title: '温馨提示',
|
|
|
-// message: '是否删除',
|
|
|
-// confirmButtonColor: '#FF9300'
|
|
|
-// }
|
|
|
-
|
|
|
// 保存草稿的弹窗内容
|
|
|
const draftDialogContent = {
|
|
|
title: '是否保存到草稿箱',
|
|
@@ -489,7 +447,8 @@ async function getNoteDetail() {
|
|
|
const res = await request(
|
|
|
`/website/tourism/publishTravelNotes/getDraftDetail?writeId=${id.value}`
|
|
|
)
|
|
|
- const data = res.data ?? {}
|
|
|
+ const data = res.data ?? defaultNoteJson
|
|
|
+ // console.log(data)
|
|
|
|
|
|
for (let i in noteJson) {
|
|
|
if (data[i]) {
|
|
@@ -508,7 +467,7 @@ async function getNoteDetail() {
|
|
|
noteJson.endPlaceId = data?.endPlaceDictMap?.id
|
|
|
noteJson.endPlace = data?.endPlaceDictMap?.name
|
|
|
}
|
|
|
- console.log(noteJson, 'noteJson')
|
|
|
+ // console.log(noteJson, 'noteJson')
|
|
|
|
|
|
setLoading(false)
|
|
|
} catch (error) {
|
|
@@ -517,23 +476,8 @@ async function getNoteDetail() {
|
|
|
}
|
|
|
|
|
|
/************ 插入段落标题逻辑 ********** */
|
|
|
-// const insertTilteOptions = reactive({
|
|
|
-// show: false,
|
|
|
-// content: null,
|
|
|
-// editIndex: null
|
|
|
-// })
|
|
|
-
|
|
|
-const editIndex = ref(null)
|
|
|
-
|
|
|
-function handleInsertOrEditTitleIndex2(event) {
|
|
|
- // editIndex.value = index
|
|
|
- // 隐藏建议框
|
|
|
- // if (!inputDiv.contains(event.target) && !suggestionsBox.contains(event.target)) {
|
|
|
- // suggestionsBox.style.display = 'none'
|
|
|
- // } else {
|
|
|
- // suggestionsBox.style.display = 'block'
|
|
|
- // }
|
|
|
-}
|
|
|
+
|
|
|
+// const editIndex = ref(null)
|
|
|
|
|
|
// 点击编辑大标题,
|
|
|
function handleInsertOrEditProjectTitle(value) {
|
|
@@ -543,47 +487,58 @@ function handleInsertOrEditProjectTitle(value) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-let cursorPosition = { offsetX: 0, offsetY: 0 } // 光标位置
|
|
|
+// let cursorPosition = { offsetX: 0, offsetY: 0 } // 光标位置
|
|
|
|
|
|
// 点击编辑或者新增段落标题,
|
|
|
-const eidContent = ref('') //记录艾特后输入的内容
|
|
|
-
|
|
|
-function handleInsertOrEditTitleText(value) {
|
|
|
- const selection = window.getSelection()
|
|
|
- cursorPosition = selection.getRangeAt(0).getBoundingClientRect()
|
|
|
- let text = value.target.innerHTML
|
|
|
-
|
|
|
- let lastAtSign = -1
|
|
|
- let lastAtSign2 = -1
|
|
|
+// const eidContent = ref('') //记录艾特后输入的内容
|
|
|
+// function handleInsertOrEditTitleText(value) {
|
|
|
+// const selection = window.getSelection()
|
|
|
+// cursorPosition = selection.getRangeAt(0).getBoundingClientRect()
|
|
|
+// let text = value.target.innerHTML
|
|
|
+
|
|
|
+// let lastAtSign = -1
|
|
|
+// let lastAtSign2 = -1
|
|
|
+
|
|
|
+// if (text) {
|
|
|
+// lastAtSign = text.indexOf('@')
|
|
|
+// lastAtSign2 = text.indexOf('#')
|
|
|
+// } else {
|
|
|
+// currentMention2 = ''
|
|
|
+// lastAtSign = -1
|
|
|
+// lastAtSign2 = -1
|
|
|
+// }
|
|
|
|
|
|
- if (text) {
|
|
|
- lastAtSign = text.indexOf('@')
|
|
|
- lastAtSign2 = text.indexOf('#')
|
|
|
- } else {
|
|
|
- currentMention2 = ''
|
|
|
- lastAtSign = -1
|
|
|
- lastAtSign2 = -1
|
|
|
- }
|
|
|
+// currentMention2 = text
|
|
|
+// // 如果最后输入的是 @,显示用户列表
|
|
|
+// if (lastAtSign !== -1 && text[lastAtSign + 1] !== ' ') {
|
|
|
+// eidContent.value = text.slice(lastAtSign) // 获取 @ 后的部分 包含@符号
|
|
|
|
|
|
- currentMention2 = text
|
|
|
- // 如果最后输入的是 @,显示用户列表
|
|
|
- if (lastAtSign !== -1 && text[lastAtSign + 1] !== ' ') {
|
|
|
- eidContent.value = text.slice(lastAtSign) // 获取 @ 后的部分 包含@符号
|
|
|
+// showTopicEit.value = userControlsList.value[1].fn
|
|
|
+// getTopicList(userControlsList.value[1])
|
|
|
+// }
|
|
|
|
|
|
- showTopicEit.value = userControlsList.value[1].fn
|
|
|
- getTopicList(userControlsList.value[1])
|
|
|
- }
|
|
|
+// if (lastAtSign2 !== -1 && text[lastAtSign2 + 1] !== ' ') {
|
|
|
+// eidContent.value = text.slice(lastAtSign2) // 获取 # 后的部分 包含@符号
|
|
|
|
|
|
- if (lastAtSign2 !== -1 && text[lastAtSign2 + 1] !== ' ') {
|
|
|
- eidContent.value = text.slice(lastAtSign2) // 获取 # 后的部分 包含@符号
|
|
|
+// showTopicEit.value = userControlsList.value[0].fn
|
|
|
+// getTopicList(userControlsList.value[0])
|
|
|
+// }
|
|
|
+// }
|
|
|
|
|
|
- showTopicEit.value = userControlsList.value[0].fn
|
|
|
- getTopicList(userControlsList.value[0])
|
|
|
+// 提取@和# 的内容
|
|
|
+const getMention = (type) => {
|
|
|
+ if (!type) return []
|
|
|
+ let domStr = editor.value.outerHTML
|
|
|
+ // let reg = /<span[^>]+data-id=['"]([^'"]+)['"]+/g;
|
|
|
+ let reg = /<span[^>]*\bdata-id="([^">]+)"[^>]*\bdata-value="([^">]+)"/g
|
|
|
+ let result = [],
|
|
|
+ temp
|
|
|
+ while ((temp = reg.exec(domStr)) != null) {
|
|
|
+ console.log(temp[1], temp[2], temp[0])
|
|
|
+ if (temp[0].includes(`data-denotation-char="${type}"`))
|
|
|
+ result.push({ value: temp[1], label: temp[2] })
|
|
|
}
|
|
|
-
|
|
|
- // if (lastAtSign == -1 || lastAtSign2 == -1) {
|
|
|
- // noteJson.tourTourismProjectTravelNotesWriteContentDto.content = currentMention2
|
|
|
- // }
|
|
|
+ return result
|
|
|
}
|
|
|
|
|
|
/******************插入正文相关逻辑*******************/
|
|
@@ -595,9 +550,44 @@ async function handleSaveDraft() {
|
|
|
try {
|
|
|
parmas = { ...noteJson }
|
|
|
parmas.endPlace = noteJson.endPlaceId
|
|
|
+ const innerHTML = editor.value.innerHTML
|
|
|
+ .replace('ql-editor', '')
|
|
|
+ .replace('contenteditable', 'true')
|
|
|
+
|
|
|
+ parmas.tourTourismProjectTravelNotesWriteContentDto.content = innerHTML ?? ''
|
|
|
|
|
|
- if (inputDiv.value.innerHTML) {
|
|
|
- parmas.tourTourismProjectTravelNotesWriteContentDto.content = inputDiv.value.innerHTML
|
|
|
+ const talkList = getMention('#')
|
|
|
+
|
|
|
+ if (talkList.length) {
|
|
|
+ parmas.topics = talkList.map((item) => {
|
|
|
+ if (item.label == item.value) {
|
|
|
+ return {
|
|
|
+ id: '',
|
|
|
+ name: item.label
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return {
|
|
|
+ id: item.value,
|
|
|
+ name: item.label
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ if (parmas?.topicsList.length) {
|
|
|
+ parmas.topics.forEach((i) => {
|
|
|
+ const matchingItem = parmas.topicsList.find((o) => o.name === i.name)
|
|
|
+ if (matchingItem && i.id === '') {
|
|
|
+ i.id = matchingItem.id
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ console.log(parmas.topics, ' parmas.topics')
|
|
|
+
|
|
|
+ let memberList = getMention('@')
|
|
|
+
|
|
|
+ if (memberList.length) {
|
|
|
+ parmas.mentions = memberList.map((item) => ({ userName: item.label, userId: item.value }))
|
|
|
}
|
|
|
|
|
|
await request('/website/tourism/publishTravelNotes/saveDraft', {
|
|
@@ -607,8 +597,12 @@ async function handleSaveDraft() {
|
|
|
id: id?.value
|
|
|
}
|
|
|
})
|
|
|
-
|
|
|
showToast('草稿保存成功')
|
|
|
+ // 后端保存草稿没有对 topics 做新增的处理
|
|
|
+ navigateTo({
|
|
|
+ path: '/profile/notes?tab=draft',
|
|
|
+ replace: true
|
|
|
+ })
|
|
|
} finally {
|
|
|
}
|
|
|
}
|
|
@@ -620,8 +614,11 @@ const previewOptions = reactive({
|
|
|
|
|
|
function handlePreview() {
|
|
|
previewOptions.show = !previewOptions.show
|
|
|
+ const innerHTML = editor.value.innerHTML
|
|
|
+ .replace('ql-editor', '')
|
|
|
+ .replace('contenteditable', 'true')
|
|
|
|
|
|
- noteJson.tourTourismProjectTravelNotesWriteContentDto.content = inputDiv.value.innerHTML
|
|
|
+ noteJson.tourTourismProjectTravelNotesWriteContentDto.content = innerHTML ?? ''
|
|
|
}
|
|
|
|
|
|
// 收集个人信息
|
|
@@ -643,6 +640,15 @@ function handlePublishRule(length, endPlace) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+// 触发 mention 搜索功能
|
|
|
+const openMention = (type) => {
|
|
|
+ const range = quillContent.getSelection()
|
|
|
+ const index = range?.index ?? 0
|
|
|
+ quillContent.setSelection(index)
|
|
|
+ const mentionModule = quillContent.getModule('mention')
|
|
|
+ mentionModule.openMenu(type)
|
|
|
+}
|
|
|
+
|
|
|
// 发布
|
|
|
async function handlePublish() {
|
|
|
if (!noteJson.imgUrls.length) {
|
|
@@ -653,30 +659,15 @@ async function handlePublish() {
|
|
|
})
|
|
|
return
|
|
|
}
|
|
|
- // if (!noteJson.projectTitle) {
|
|
|
- // showNotify({
|
|
|
- // type: 'warning',
|
|
|
- // message: '请输入游记标题',
|
|
|
- // duration: 3000
|
|
|
- // })
|
|
|
- // return
|
|
|
- // }
|
|
|
+
|
|
|
if (!noteJson.endPlace) {
|
|
|
showNotify({
|
|
|
type: 'warning',
|
|
|
- message: '请选择目的地',
|
|
|
+ message: '请选择地区',
|
|
|
duration: 3000
|
|
|
})
|
|
|
return
|
|
|
}
|
|
|
- // if (noteJson.travelNotesContent.length === 0) {
|
|
|
- // showNotify({
|
|
|
- // type: 'warning',
|
|
|
- // message: '游记内容不能为空',
|
|
|
- // duration: 3000
|
|
|
- // })
|
|
|
- // return
|
|
|
- // }
|
|
|
|
|
|
const { data: isPerfect } = await request('/website/tourism/publishTravelNotes/isPerfect')
|
|
|
if (isPerfect === 0) {
|
|
@@ -690,17 +681,49 @@ async function handlePublish() {
|
|
|
const publishLoading = ref(false)
|
|
|
|
|
|
async function requestPublish() {
|
|
|
- let parmas = {}
|
|
|
try {
|
|
|
publishLoading.value = true
|
|
|
+ let parmas = {}
|
|
|
|
|
|
parmas = { ...noteJson }
|
|
|
if (noteJson.endPlaceId) {
|
|
|
parmas.endPlace = noteJson.endPlaceId
|
|
|
}
|
|
|
|
|
|
- if (inputDiv.value.innerHTML) {
|
|
|
- parmas.tourTourismProjectTravelNotesWriteContentDto.content = inputDiv.value.innerHTML
|
|
|
+ //TODO 取笔记内容
|
|
|
+ const innerHTML = editor.value.innerHTML
|
|
|
+ .replace('ql-editor', '')
|
|
|
+ .replace('contenteditable', 'false')
|
|
|
+
|
|
|
+ if (innerHTML) {
|
|
|
+ parmas.tourTourismProjectTravelNotesWriteContentDto.content = innerHTML
|
|
|
+ }
|
|
|
+
|
|
|
+ // 后端可能会要求你把@的人或话题ID传回去
|
|
|
+ // const memberList = getMention('@')
|
|
|
+ const talkList = getMention('#')
|
|
|
+
|
|
|
+ console.log(talkList, 'talkList')
|
|
|
+ if (talkList.length) {
|
|
|
+ parmas.topics = talkList.map((item) => {
|
|
|
+ if (item.label == item.value) {
|
|
|
+ return {
|
|
|
+ id: '',
|
|
|
+ name: item.label
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ return {
|
|
|
+ id: item.value,
|
|
|
+ name: item.label
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ let memberList = getMention('@')
|
|
|
+
|
|
|
+ if (memberList.length) {
|
|
|
+ parmas.mentions = memberList.map((item) => ({ userName: item.label, userId: item.value }))
|
|
|
}
|
|
|
|
|
|
await request('/website/tourism/publishTravelNotes/publishDraft', {
|
|
@@ -712,6 +735,8 @@ async function requestPublish() {
|
|
|
})
|
|
|
|
|
|
publishResultModalOptions.value = true
|
|
|
+
|
|
|
+ editor.value.innerHTML = ''
|
|
|
publishLoading.value = false
|
|
|
} catch (error) {
|
|
|
publishLoading.value = false
|
|
@@ -732,13 +757,14 @@ const queryParmas = reactive({
|
|
|
})
|
|
|
|
|
|
const topicLoading = ref(false)
|
|
|
-async function getTopicList(parmas) {
|
|
|
+async function getTopicList(parmas, searchText) {
|
|
|
try {
|
|
|
// if (!topicLoading.value) return
|
|
|
topicLoading.value = true
|
|
|
const { data } = await request(parmas.apiUrl, {
|
|
|
query: {
|
|
|
- ...queryParmas
|
|
|
+ ...queryParmas,
|
|
|
+ name: searchText
|
|
|
}
|
|
|
}).finally(() => (topicLoading.value = false))
|
|
|
|
|
@@ -768,77 +794,37 @@ const hotTopicList = computed(() => {
|
|
|
})
|
|
|
|
|
|
const showExpandTextInput = ref(false)
|
|
|
-// 扩展内容的插件 同时图片放大和缩小
|
|
|
-function expandTextInput() {
|
|
|
- showExpandTextInput.value = !showExpandTextInput.value
|
|
|
-}
|
|
|
|
|
|
// 对应的操作
|
|
|
-const handleOperate = (operate) => {
|
|
|
- try {
|
|
|
- switch (operate.fn) {
|
|
|
- case TOPIC_TEXT:
|
|
|
- if (showTopicEit.value == TOPIC_TEXT) {
|
|
|
- showTopicEit.value = null
|
|
|
- } else {
|
|
|
- noteJson.tourTourismProjectTravelNotesWriteContentDto.content =
|
|
|
- inputDiv.value.innerHTML + '#'
|
|
|
- showTopicEit.value = operate.fn
|
|
|
- getTopicList(operate)
|
|
|
- // handleInsertOrEditTitleText(noteJson.tourTourismProjectTravelNotesWriteContentDto.content)
|
|
|
- }
|
|
|
- break
|
|
|
- case EIT_TEXT:
|
|
|
- if (showTopicEit.value == EIT_TEXT) {
|
|
|
- showTopicEit.value = null
|
|
|
- } else {
|
|
|
- showTopicEit.value = operate.fn
|
|
|
- noteJson.tourTourismProjectTravelNotesWriteContentDto.content =
|
|
|
- inputDiv.value.innerHTML + '@'
|
|
|
- getTopicList(operate)
|
|
|
- // handleInsertOrEditTitleText(noteJson.tourTourismProjectTravelNotesWriteContentDto.content)
|
|
|
- }
|
|
|
+// const handleOperate = (operate) => {
|
|
|
+// try {
|
|
|
+// switch (operate.fn) {
|
|
|
+// case TOPIC_TEXT:
|
|
|
+// if (showTopicEit.value == TOPIC_TEXT) {
|
|
|
+// showTopicEit.value = null
|
|
|
+// } else {
|
|
|
+// noteJson.tourTourismProjectTravelNotesWriteContentDto.content =
|
|
|
+// inputDiv.value.innerHTML + '#'
|
|
|
+// showTopicEit.value = operate.fn
|
|
|
+// getTopicList(operate)
|
|
|
+// // handleInsertOrEditTitleText(noteJson.tourTourismProjectTravelNotesWriteContentDto.content)
|
|
|
+// }
|
|
|
+// break
|
|
|
+// case EIT_TEXT:
|
|
|
+// if (showTopicEit.value == EIT_TEXT) {
|
|
|
+// showTopicEit.value = null
|
|
|
+// } else {
|
|
|
+// showTopicEit.value = operate.fn
|
|
|
+// noteJson.tourTourismProjectTravelNotesWriteContentDto.content =
|
|
|
+// inputDiv.value.innerHTML + '@'
|
|
|
+// getTopicList(operate)
|
|
|
+// // handleInsertOrEditTitleText(noteJson.tourTourismProjectTravelNotesWriteContentDto.content)
|
|
|
+// }
|
|
|
|
|
|
- break
|
|
|
- case PTITLE_TEXT:
|
|
|
- handleInsertOrEditTitleOk(defaultSectionTitle)
|
|
|
- break
|
|
|
- case PCONTENT_TEXT:
|
|
|
- handleInsertOrEditTitleOk(defaultSectionContent)
|
|
|
- break
|
|
|
- }
|
|
|
- } catch (e) {}
|
|
|
-}
|
|
|
-const bindFn = () => {
|
|
|
- // 获取文档中的所有 span 标签
|
|
|
- const container = document.getElementById('view-note')
|
|
|
- const spans = container.querySelectorAll('span')
|
|
|
- spans.forEach((span) => {
|
|
|
- if (span.getAttribute('data-denotation-char') === '@') {
|
|
|
- span.addEventListener('click', (event) => {
|
|
|
- // console.log('@用户ID', span.getAttribute('data-id'))
|
|
|
- // console.log('@用户名字', span.innerHTML)
|
|
|
- // alert('@用户:' + span.innerText)
|
|
|
- navigateTo({
|
|
|
- path: `/profile-others/${span.getAttribute('data-id')}`
|
|
|
- })
|
|
|
- })
|
|
|
- }
|
|
|
- if (span.getAttribute('data-denotation-char') === '#') {
|
|
|
- span.addEventListener('click', (event) => {
|
|
|
- // console.log('话题ID', span.getAttribute('data-id'))
|
|
|
- // console.log('话题内容', span.innerHTML)
|
|
|
- // alert('话题内容:' + span.innerText)
|
|
|
- navigateTo({
|
|
|
- path: '/topic',
|
|
|
- query: {
|
|
|
- topicName: span.innerText
|
|
|
- }
|
|
|
- })
|
|
|
- })
|
|
|
- }
|
|
|
- })
|
|
|
-}
|
|
|
+// break
|
|
|
+// }
|
|
|
+// } catch (e) {}
|
|
|
+// }
|
|
|
|
|
|
// 点击话题的事件
|
|
|
function handleTopicAndEit(parmas, pType) {
|
|
@@ -849,14 +835,14 @@ function handleTopicAndEit(parmas, pType) {
|
|
|
if (contentText.includes('#')) {
|
|
|
contentText = contentText.replace(
|
|
|
new RegExp(eidContent.value, 'g'),
|
|
|
- `<span class="mention" data-denotation-char='#' >#${parmas.name}</span> `
|
|
|
+ `<span contenteditable="false" class="mention" data-denotation-char='#' >#${parmas.name}</span> `
|
|
|
)
|
|
|
showTopicEit.value = null
|
|
|
eidContent.value = ''
|
|
|
} else {
|
|
|
contentText = contentText.replace(
|
|
|
new RegExp(eidContent.value, 'g'),
|
|
|
- `<span class="mention" data-denotation-char='#' >#${parmas.name}</span>`
|
|
|
+ `<span contenteditable="false" class="mention" data-denotation-char='#' >#${parmas.name}</span>`
|
|
|
)
|
|
|
}
|
|
|
let findIndex = noteJson.topics.findIndex((i) => i?.name == parmas.name)
|
|
@@ -868,7 +854,7 @@ function handleTopicAndEit(parmas, pType) {
|
|
|
if (contentText.includes('@')) {
|
|
|
contentText = contentText.replace(
|
|
|
new RegExp(eidContent.value, 'g'),
|
|
|
- `<span class="mention" data-denotation-char="@" data-id="${parmas.userId}" >@${parmas.showName}</span>`
|
|
|
+ `<span contenteditable="false" class="mention" data-denotation-char="@" data-id="${parmas.userId}" >@${parmas.showName}</span>`
|
|
|
)
|
|
|
|
|
|
showTopicEit.value = null
|
|
@@ -876,7 +862,7 @@ function handleTopicAndEit(parmas, pType) {
|
|
|
} else {
|
|
|
contentText = contentText.replace(
|
|
|
new RegExp(eidContent.value, 'g'),
|
|
|
- `<span class="mention" data-denotation-char="@" data-id="${parmas.userId}" >@${parmas.showName}</span>`
|
|
|
+ `<span contenteditable="false" class="mention" data-denotation-char="@" data-id="${parmas.userId}" >@${parmas.showName}</span>`
|
|
|
)
|
|
|
}
|
|
|
|
|
@@ -903,55 +889,123 @@ function contentIsEmpty(content) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// const initEdit = () => {
|
|
|
-// new Quill(editor.value, {
|
|
|
-// placeholder: '请输入内容...',
|
|
|
-// modules: {
|
|
|
-// mention: {
|
|
|
-// allowedChars: /^[\u4e00-\u9fa5]*$/, //匹配中文搜索
|
|
|
-// mentionDenotationChars: ['@', '#'],
|
|
|
-// positioningStrategy: 'fixed',
|
|
|
-// renderItem: (data) => {
|
|
|
-// // 这里文档是可以返回html的,但是没有显示正确
|
|
|
-// return data.value
|
|
|
-// },
|
|
|
-// renderLoading: () => {
|
|
|
-// return 'Loading...'
|
|
|
-// },
|
|
|
-// source: (searchTerm, renderList, mentionChar) => {
|
|
|
-// console.log(searchTerm, 'searchTerm')
|
|
|
-// console.log(renderList, 'renderList')
|
|
|
-// console.log(mentionChar, 'mentionChar')
|
|
|
-
|
|
|
-// let list = []
|
|
|
-// if (mentionChar === '@') {
|
|
|
-// // 调用接口 获取数据 传searchTerm字段实现搜索效果
|
|
|
-// getTopicList(userControlsList.value[1])
|
|
|
-// list = cloneDeep(eitList.value)
|
|
|
-// } else {
|
|
|
-// // 调用接口 获取数据 传searchTerm字段实现搜索效果
|
|
|
-// getTopicList(userControlsList.value[0])
|
|
|
-// list = cloneDeep(topicList.value)
|
|
|
-// }
|
|
|
-
|
|
|
-// if (!searchTerm) {
|
|
|
-// renderList(list, searchTerm)
|
|
|
-// } else {
|
|
|
-// const matches = []
|
|
|
-// for (let i = 0; i < list.length; i++)
|
|
|
-// if (list[i].value.toLowerCase().indexOf(searchTerm.toLowerCase()))
|
|
|
-// matches.push(list[i])
|
|
|
-// renderList(matches, searchTerm)
|
|
|
-// }
|
|
|
-// }
|
|
|
-// }
|
|
|
-// }
|
|
|
-// })
|
|
|
-// }
|
|
|
+const editor = ref(null)
|
|
|
+let quillContent = null
|
|
|
+const initEdit = () => {
|
|
|
+ // console.log(editor.value, 'editor-ref')
|
|
|
+ quillContent = new Quill(editor.value, {
|
|
|
+ placeholder: '请输入内容...',
|
|
|
+ modules: {
|
|
|
+ mention: {
|
|
|
+ allowedChars: /[\s\S]*$/, //支持任意字符得正则
|
|
|
+ mentionDenotationChars: ['@', '#'],
|
|
|
+ positioningStrategy: 'fixed',
|
|
|
+ renderItem: (data) => {
|
|
|
+ // 这里文档是可以返回html的,但是没有显示正确
|
|
|
+ return data.value
|
|
|
+ },
|
|
|
+ renderLoading: () => {
|
|
|
+ return 'Loading...'
|
|
|
+ },
|
|
|
+ source: async (searchTerm, renderList, mentionChar) => {
|
|
|
+ // console.log(searchTerm, 'searchTerm')
|
|
|
+ // console.log(renderList, 'renderList')
|
|
|
+ // console.log(mentionChar, 'mentionChar')
|
|
|
+
|
|
|
+ let responseData = [],
|
|
|
+ requestDta = {
|
|
|
+ pageNum: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ name: searchTerm
|
|
|
+ }
|
|
|
+
|
|
|
+ if (mentionChar === '@') {
|
|
|
+ // 调用接口 获取数据 传searchTerm字段实现搜索效果
|
|
|
+ const { data } = await request(
|
|
|
+ '/website/tourism/publishTravelNotes/getFouceEachFriendsByName',
|
|
|
+ {
|
|
|
+ query: requestDta
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ if (data) {
|
|
|
+ eitList.value = data
|
|
|
+ }
|
|
|
+ responseData = data?.map((o) => {
|
|
|
+ return {
|
|
|
+ id: o.userId,
|
|
|
+ value: o.showName,
|
|
|
+ icon: o.headImageUrl
|
|
|
+ }
|
|
|
+ })
|
|
|
+ } else {
|
|
|
+ const { data } = await request(
|
|
|
+ '/website/tourism/publishTravelNotes/getTopicListByName',
|
|
|
+ {
|
|
|
+ query: requestDta
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+ if (data) {
|
|
|
+ topicList.value = data
|
|
|
+ }
|
|
|
+ responseData = data?.map((o) => {
|
|
|
+ return {
|
|
|
+ id: o.id || o.name,
|
|
|
+ value: o.name,
|
|
|
+ count: o.totalViewCount ?? 0
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!searchTerm) {
|
|
|
+ renderList(responseData, searchTerm)
|
|
|
+ } else {
|
|
|
+ const matches = []
|
|
|
+ for (let i = 0; i < responseData.length; i++)
|
|
|
+ if (~responseData[i].value.toLowerCase().indexOf(searchTerm.toLowerCase()))
|
|
|
+ matches.push(responseData[i])
|
|
|
+ renderList(matches, searchTerm)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ // 监听获取焦点事件
|
|
|
+ quillContent.root.addEventListener('focus', () => {
|
|
|
+ console.log('quillContent编辑器获取了焦点')
|
|
|
+ showExpandTextInput.value = true
|
|
|
+ })
|
|
|
+}
|
|
|
|
|
|
-// onMounted(() => {
|
|
|
-// // initEdit()
|
|
|
-// })
|
|
|
+// 判断是否点击的非输入框 || @按钮 #按钮
|
|
|
+const editorFather = ref(null)
|
|
|
+const handleClickOutside = (event) => {
|
|
|
+ let projectTitleRefCompEl = projectTitleRef.value
|
|
|
+ const isChatInputCompEl =
|
|
|
+ [checkboxRefs.value[0], checkboxRefs.value[1]].some((o) => o === event.target) ||
|
|
|
+ editorFather.value.contains(event.target) ||
|
|
|
+ projectTitleRefCompEl.contains(event.target)
|
|
|
+
|
|
|
+ if (!isChatInputCompEl) {
|
|
|
+ // 不是点输入框部分
|
|
|
+ showExpandTextInput.value = false
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+watch(
|
|
|
+ editor,
|
|
|
+ (val) => {
|
|
|
+ if (val && !quillContent) initEdit()
|
|
|
+ },
|
|
|
+ { immediate: true }
|
|
|
+)
|
|
|
+
|
|
|
+onMounted(() => {
|
|
|
+ useEventListener('click', handleClickOutside, { target: document })
|
|
|
+ useEventListener('mousedown', handleClickOutside, { target: document })
|
|
|
+ useEventListener('touchstart', handleClickOutside, { target: document })
|
|
|
+})
|
|
|
</script>
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
@@ -962,7 +1016,7 @@ function contentIsEmpty(content) {
|
|
|
outline: none;
|
|
|
}
|
|
|
|
|
|
-.inputSectionContent:empty:before {
|
|
|
+::v-deep .inputSectionContent:empty:before {
|
|
|
content: '请输入内容...';
|
|
|
color: #e7e7e7;
|
|
|
}
|
|
@@ -975,15 +1029,12 @@ function contentIsEmpty(content) {
|
|
|
color: #2c405b;
|
|
|
font-weight: bold;
|
|
|
margin-left: 4px;
|
|
|
+ background-color: transparent;
|
|
|
}
|
|
|
|
|
|
-.ql-editor {
|
|
|
- border: 1px solid #a3a3a3;
|
|
|
- border-radius: 6px;
|
|
|
-}
|
|
|
-
|
|
|
-.ql-editor:focus {
|
|
|
- border: 1px solid #409eff;
|
|
|
+::v-deep .ql-editor:focus {
|
|
|
+ caret-color: #ff9300 !important;
|
|
|
+ outline: none;
|
|
|
}
|
|
|
|
|
|
.ql-mention-list-container {
|
|
@@ -999,11 +1050,6 @@ function contentIsEmpty(content) {
|
|
|
background-color: #f5f7fa !important;
|
|
|
}
|
|
|
|
|
|
-.mention {
|
|
|
- color: #409eff;
|
|
|
- background-color: transparent;
|
|
|
-}
|
|
|
-
|
|
|
.member-item {
|
|
|
display: flex;
|
|
|
align-items: center;
|