index.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. <template>
  2. <div class="w-full">
  3. <van-dropdown-menu fixed ref="interactionDropdownMenuRef" active-color="#FF9300">
  4. <van-dropdown-item
  5. v-model="interactionIndex"
  6. @change="onInteractionFilterClose"
  7. :options="
  8. interactionDropdownMenuList.map((item, index) => {
  9. if (item.value == interactionIndex) {
  10. item.icon = item.iconO
  11. return item
  12. } else {
  13. item.icon = item.iconM
  14. return item
  15. }
  16. })
  17. "
  18. ref="interactionItemRef"
  19. >
  20. <template #title class="relative" active-color="#FF9300">
  21. <div class="font-semibold text-base">
  22. <NuxtLink :to="'/profile/my-news'" class="absolute top-0 -left-135">
  23. <van-icon name="arrow-left" size="24" color="#000000" />
  24. </NuxtLink>
  25. {{ interactionTitle }}
  26. </div>
  27. </template>
  28. </van-dropdown-item>
  29. </van-dropdown-menu>
  30. <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
  31. <div class="w-full h-[100vh] pt-10">
  32. <ChatEmpty
  33. v-if="!interactionList.length && !loading"
  34. image="conment"
  35. :title="`暂无${interactionTitle}`"
  36. />
  37. <van-list
  38. v-if="interactionList.length"
  39. v-model:loading="loading"
  40. :finished="finished"
  41. :immediate-check="false"
  42. finished-text=""
  43. @load="handleCurrentChange"
  44. >
  45. <template v-for="item in interactionList" :key="item?.id">
  46. <!-- 点赞与收藏 -->
  47. <ProfileInteractionMessageLikesandFavorites
  48. v-if="item.noticeType == 5 || item.noticeType == 7"
  49. :item-data="{
  50. ...item,
  51. messageContent:
  52. actionTypeList[item.noticeType].text + businessTypeList[item.businessType].text
  53. }"
  54. ></ProfileInteractionMessageLikesandFavorites>
  55. <!-- 艾特 -->
  56. <ProfileInteractionMessageEit
  57. v-if="item.noticeType == 10"
  58. :item-data="{
  59. ...item,
  60. messageContent:
  61. actionTypeList[item.noticeType].text + businessTypeList[item.businessType].text
  62. }"
  63. ></ProfileInteractionMessageEit>
  64. <!-- 收到的评论 是评论还是 回复 -->
  65. <ProfileInteractionMessageReceiveComment
  66. v-if="
  67. (item.noticeType == 6 && interactionIndex == 7) ||
  68. (item.noticeType == 12 && interactionIndex == 7)
  69. "
  70. :item-data="{
  71. ...item,
  72. messageContent: messageContentParse(item?.messageContent).messageContent
  73. }"
  74. :user-info="userInfo"
  75. @on-add-reply="addReply(item, item.reviewId)"
  76. />
  77. <!-- 发出的评论 -->
  78. <ProfileInteractionMessageSendComment
  79. v-if="
  80. (item.noticeType == 6 && interactionIndex == 8) ||
  81. (item.noticeType == 12 && interactionIndex == 8)
  82. "
  83. :item-data="{
  84. ...item,
  85. messageContent: messageContentParse(item?.messageContent)
  86. }"
  87. />
  88. </template>
  89. </van-list>
  90. <!-- <div
  91. v-if="showInput"
  92. @click="handleBlur"
  93. class="w-[100vw] h-[100vh] fixed top-0 border-[#000] left-0 z-100 bg-[#000]/[0.1]"
  94. >
  95. <div @click.stop="" class="fixed bottom-0 left-0 w-full bg-[#fff] pt-10 pb-30 z-52">
  96. <div class="flex items-start">
  97. <div
  98. class="relative bg-[#F3F3F3] flex-1 ml-12 mr-12 pl-5 pr-5 pt-4 pb-4 rounded-[4px] h-50 border flex items-center justify-between"
  99. >
  100. <textarea
  101. v-model="commentValue"
  102. ref="textareaRef"
  103. :placeholder="
  104. replyComment?.id ? `回复:${replyComment?.commentUserIdDictMap?.name}` : ''
  105. "
  106. @focus="textareaFocus"
  107. class="ml-8 flex-1 h-full bg-black/[0]"
  108. maxlength="5000"
  109. style="resize: none"
  110. ></textarea>
  111. <img @click="openEmoji" src="~/assets/img/yj/emoji.png" class="w-22 h-22" alt="" />
  112. </div>
  113. <div
  114. @click="addComment"
  115. class="py-6 px-16 mr-12 bg-[#FD9A00] text-[#fff] flex items-center justify-center rounded-full shrink-0"
  116. >
  117. 发送
  118. </div>
  119. </div>
  120. <div v-if="showEmoji" class="h-[300px] bg-[#fff] overflow-auto">
  121. <div @click="closeEmojiBox" class="flex justify-end pr-15 text-[#999] text-[12px]">
  122. 收起表情
  123. </div>
  124. <div class="flex items-center flex-wrap">
  125. <div
  126. v-for="(item, index) in emojiJson"
  127. :key="index"
  128. class="hover:bg-[#ddd] text-[22px] w-[10%] aspect-[1/1] flex items-center justify-center"
  129. >
  130. <div @click="selectEmoji(index)" v-html="coveredContent(index)"></div>
  131. </div>
  132. </div>
  133. </div>
  134. </div>
  135. </div> -->
  136. </div>
  137. </van-pull-refresh>
  138. </div>
  139. </template>
  140. <script setup>
  141. import { messageContentParse } from '~/utils/detalTime'
  142. import defaultImg from '~/assets/img/comment/H5_default.png'
  143. import commentsBlack from '~/assets/img/chat/comments-black.svg'
  144. import commentsOrange from '~/assets/img/chat/comments-orange.svg'
  145. import like from '~/assets/img/chat/like.svg'
  146. import likeOrange from '~/assets/img/chat/like-orange.svg'
  147. import eit from '~/assets/img/chat/tiji.svg'
  148. import eitOrange from '~/assets/img/chat/tiji-orange.svg'
  149. import comment from '~/assets/img/chat/comment.svg'
  150. import commentOrange from '~/assets/img/chat/comment-orange.svg'
  151. import medicalFiles from '~/assets/img/chat/medical-files.svg'
  152. import medicalFilesOrange from '~/assets/img/chat/medical-files-orange.svg'
  153. import send from '~/assets/img/chat/send.svg'
  154. import sendOrange from '~/assets/img/chat/send-orange.svg'
  155. import emojiJson from '../../yj/emoji.json'
  156. const authStore = useAuthStore()
  157. const { token } = storeToRefs(authStore)
  158. onMounted(() => {
  159. getUserInfo()
  160. getList()
  161. })
  162. // noticeType 6 是评论 12 是回复
  163. // 1单聊 2群聊 3系统消息 4关注消息 5点赞通知 6评论通知 7收藏通知 8浏览通知 9访问主页通知 10评论区艾特通知 11文章内艾特通知 12回复评论通知
  164. const actionTypeList = {
  165. 4: { text: '关注了你' },
  166. 5: { text: '赞了你的' },
  167. 6: { text: '评论' },
  168. 7: { text: '收藏了你的' },
  169. 8: { text: '浏览了你的' },
  170. 9: { text: '访问你' },
  171. 10: { text: '提到了你' },
  172. 11: { text: '提到了你' },
  173. 12: { text: '回复' }
  174. }
  175. // 1 游记
  176. const businessTypeList = {
  177. 1: { text: '游记' },
  178. 2: { text: '评论' }
  179. }
  180. definePageMeta({
  181. layout: false
  182. })
  183. // 0未关注 1已关注 2已经互关 3我自己 4被关注 focusStatus字段
  184. // 条件查询 互动消息--> 5赞与收藏 6提及 7收到的评论 8发出的评论
  185. const interactionDropdownMenuList = [
  186. {
  187. text: '全部消息',
  188. value: '-1',
  189. icon: commentsBlack,
  190. iconM: commentsBlack,
  191. iconO: commentsOrange
  192. },
  193. { text: '赞与收藏', value: 5, icon: like, iconM: like, iconO: likeOrange },
  194. {
  195. text: '提及',
  196. value: 6,
  197. icon: eit,
  198. iconM: eit,
  199. iconO: eitOrange
  200. },
  201. // { text: '我的评论', value: 0, icon: comment, iconM: comment, iconO: commentOrange },
  202. {
  203. text: '收到的评论',
  204. value: 7,
  205. icon: medicalFiles,
  206. iconM: medicalFiles,
  207. iconO: medicalFilesOrange
  208. },
  209. { text: '发出的评论', value: 8, icon: send, iconM: send, iconO: sendOrange }
  210. ]
  211. const pageNum = ref(1)
  212. const finished = ref(false)
  213. const loading = ref(false)
  214. const refreshing = ref(false)
  215. // 互动消息的索引
  216. const interactionIndex = ref('-1')
  217. const interactionTitle = ref('全部消息')
  218. const interactionDropdownMenuRef = ref(null)
  219. const interactionItemRef = ref(null)
  220. // 评论的列表
  221. const interactionList = ref([])
  222. // 下拉菜单的方法
  223. function onInteractionFilterClose(value) {
  224. interactionIndex.value = value
  225. interactionTitle.value = interactionDropdownMenuList.find((item) => item.value == value).text
  226. interactionList.value = []
  227. getList()
  228. }
  229. useSeoMeta({
  230. title: '互动消息'
  231. })
  232. // 获取用户信息
  233. const userInfo = ref({})
  234. async function getUserInfo() {
  235. if (!token.value) return (userInfo.value = {})
  236. request('/website/tourism/user/view').then(
  237. (res) => {
  238. userInfo.value = res.data || {}
  239. },
  240. () => {
  241. userInfo.value = {}
  242. }
  243. )
  244. }
  245. // 转换评论中的一些非字符emoji
  246. function coveredContent(val) {
  247. if (!val) return ''
  248. return val.replace(/\[.*?]/g, function (str) {
  249. const baseApi = import.meta.env.VITE_APP_EMOJI_API
  250. const emojiName = emojiJson[str]
  251. if (!emojiName) return str
  252. return `<img src=${baseApi}${emojiJson[str]} style="width: 20px; height: 20px;display: inline-block; vertical-align: middle"/>`
  253. })
  254. }
  255. // 获取互动的全部消息
  256. const getList = async () => {
  257. try {
  258. loading.value = true
  259. const {
  260. data: { dataList, totalCount }
  261. } = await request(`/website/tourMessage/getInteractionList`, {
  262. query: {
  263. pageNum: pageNum.value,
  264. pageSize: 10,
  265. noticeType: interactionIndex.value
  266. }
  267. })
  268. if (!Array.isArray(dataList) || !dataList.length) return (interactionList.value = [])
  269. interactionList.value = interactionList.value.concat(dataList)
  270. refreshing.value = false
  271. if (interactionList.value.length >= totalCount) {
  272. finished.value = true
  273. } else {
  274. finished.value = false
  275. }
  276. } finally {
  277. refreshing.value = false
  278. loading.value = false
  279. }
  280. }
  281. // 点赞的的方法
  282. const textareaRef = ref(null)
  283. // 评论内容
  284. const commentValue = ref('')
  285. // 是否展示表情
  286. const showEmoji = ref(false)
  287. // 回复评论
  288. const replyComment = ref({})
  289. // 显示输入框
  290. const showInput = ref(false)
  291. // 发布评论
  292. const canAddComment = ref(true)
  293. function addReply(item, reviewId) {
  294. if (!token.value) {
  295. showConfirmDialog({
  296. showConfirmDialog: true,
  297. title: '提示',
  298. message: '登录后可以回复评论',
  299. theme: 'round-button'
  300. }).then(async () => {
  301. navigateTo({ path: '/login' })
  302. })
  303. return
  304. }
  305. showInput.value = true
  306. nextTick(() => {
  307. textareaRef.value?.focus()
  308. })
  309. replyComment.value = item
  310. if (item.businessType == 1) {
  311. // 业务是游记
  312. replyComment.value.travelNoteId = item.businessId
  313. }
  314. reviewId && (replyComment.value.reviewId = item.reviewId)
  315. }
  316. // 文本域失焦
  317. function handleBlur() {
  318. showEmoji.value = false
  319. textareaRef.value?.blur()
  320. showInput.value = false
  321. replyComment.value = {}
  322. }
  323. const cursorIndex = ref(0)
  324. // 打开表情
  325. function openEmoji() {
  326. nextTick(() => {
  327. textareaRef.value.selectionStart && (cursorIndex.value = textareaRef.value.selectionStart)
  328. showEmoji.value = true
  329. })
  330. }
  331. // 收起表情
  332. function closeEmojiBox() {
  333. showEmoji.value = false
  334. nextTick(() => {
  335. textareaRef.value.focus()
  336. })
  337. }
  338. // 选择表情
  339. function selectEmoji(emojiStr = '') {
  340. const length = emojiStr.length
  341. commentValue.value =
  342. commentValue.value.slice(0, cursorIndex.value) +
  343. emojiStr +
  344. commentValue.value.slice(cursorIndex.value)
  345. nextTick(() => {
  346. cursorIndex.value += length
  347. textareaRef.value.setSelectionRange(cursorIndex.value, cursorIndex.value)
  348. })
  349. }
  350. // 回复评论
  351. async function addComment() {
  352. if (!canAddComment.value) return
  353. if (!token.value) {
  354. showConfirmDialog({
  355. showConfirmDialog: true,
  356. title: '提示',
  357. message: '登录后可以发布评论',
  358. theme: 'round-button'
  359. }).then(async () => {
  360. navigateTo({ path: '/login' })
  361. })
  362. return
  363. }
  364. if (!commentValue.value.trim()) {
  365. showToast('评论内容不能为空!')
  366. return
  367. }
  368. canAddComment.value = false
  369. const body = { travelNoteId: replyComment.value.travelNoteId, commentContent: commentValue.value }
  370. if (replyComment.value.id) {
  371. body.replyCommentId = replyComment.value.id
  372. }
  373. request('/website/comment/tourTravelNotesComment/add', { method: 'post', body })
  374. .then(() => {
  375. commentValue.value = ''
  376. showToast('评论成功')
  377. // getList()
  378. showInput.value = false
  379. showEmoji.value = false
  380. })
  381. .finally(() => (canAddComment.value = true), (replyComment.value = {}), (cursorIndex.value = 0))
  382. }
  383. // 下拉刷新
  384. const onRefresh = () => {
  385. refreshing.value = true
  386. interactionList.value = []
  387. getList()
  388. }
  389. // 改变页数
  390. const handleCurrentChange = () => {
  391. finished.value = true
  392. pageNum.value += 1
  393. getList()
  394. }
  395. </script>
  396. <style lang="scss" scoped>
  397. ::v-deep .van-cell__left-icon {
  398. display: flex;
  399. align-items: center;
  400. }
  401. </style>