Pārlūkot izejas kodu

feat:1新增聊天

suwenjiang 2 mēneši atpakaļ
vecāks
revīzija
62dd6f140a
47 mainītis faili ar 5507 papildinājumiem un 0 dzēšanām
  1. BIN
      src/assets/img/chat/qr-code-box.png
  2. 9 0
      src/assets/img/chat/user-add.svg
  3. 26 0
      src/components/Chat/Dialog.vue
  4. 41 0
      src/components/Chat/Empty.vue
  5. 54 0
      src/components/Chat/GroupAvatar.vue
  6. 24 0
      src/components/Chat/Header.vue
  7. 26 0
      src/components/Chat/HeaderBar.vue
  8. 69 0
      src/components/Chat/Image.vue
  9. 35 0
      src/components/Chat/Item.vue
  10. 39 0
      src/components/Chat/Search.vue
  11. 67 0
      src/components/Chat/Text.vue
  12. 5 0
      src/components/Profile/InteractionMessage/All.vue
  13. 5 0
      src/components/Profile/InteractionMessage/Eit.vue
  14. 5 0
      src/components/Profile/InteractionMessage/LikesandFavorites.vue
  15. 5 0
      src/components/Profile/InteractionMessage/MyComment.vue
  16. 5 0
      src/components/Profile/InteractionMessage/ReceiveComment.vue
  17. 5 0
      src/components/Profile/InteractionMessage/SendComment.vue
  18. 77 0
      src/components/Profile/News/AllMessage.vue
  19. 353 0
      src/components/Profile/News/ChatInput.vue
  20. 136 0
      src/components/Profile/News/GroupChat.vue
  21. 131 0
      src/components/Profile/News/SingleChat.vue
  22. 108 0
      src/components/Profile/News/emoji.js
  23. 133 0
      src/pages/chat/announcement.vue
  24. 366 0
      src/pages/chat/create-group.vue
  25. 169 0
      src/pages/chat/examine.vue
  26. 7 0
      src/pages/chat/find-chat-history.vue
  27. 187 0
      src/pages/chat/group-add.vue
  28. 129 0
      src/pages/chat/group-member.vue
  29. 313 0
      src/pages/chat/group-square.vue
  30. 102 0
      src/pages/chat/group.vue
  31. 194 0
      src/pages/chat/interrelation-friend.vue
  32. 83 0
      src/pages/chat/qr-code.vue
  33. 108 0
      src/pages/chat/qr-results.vue
  34. 325 0
      src/pages/chat/report.vue
  35. 225 0
      src/pages/chat/set-single.vue
  36. 182 0
      src/pages/chat/set-sub/index.vue
  37. 436 0
      src/pages/chat/set.vue
  38. 54 0
      src/pages/chat/single.vue
  39. 158 0
      src/pages/chat/sweep.vue
  40. 54 0
      src/pages/chat/sweep3.vue
  41. 197 0
      src/pages/chat/user-add.vue
  42. 349 0
      src/pages/profile/interaction-message/index.vue
  43. 307 0
      src/pages/profile/my-news/index.vue
  44. 39 0
      src/pages/profile/system-message/details.vue
  45. 30 0
      src/pages/profile/system-message/index.vue
  46. 80 0
      src/stores/useWebsoket.js
  47. 55 0
      src/utils/detalTime.js

BIN
src/assets/img/chat/qr-code-box.png


+ 9 - 0
src/assets/img/chat/user-add.svg

@@ -0,0 +1,9 @@
+<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
+<g id="user-add">
+<g id="union">
+<path d="M4.70455 6.06522C4.70455 3.95813 6.35295 2.25 8.38636 2.25C10.4198 2.25 12.0682 3.95813 12.0682 6.06522C12.0682 8.1723 10.4198 9.88043 8.38636 9.88043C6.35295 9.88043 4.70455 8.1723 4.70455 6.06522ZM10.8409 6.06522C10.8409 4.66049 9.74197 3.52174 8.38636 3.52174C7.03076 3.52174 5.93182 4.66049 5.93182 6.06522C5.93182 7.46994 7.03076 8.6087 8.38636 8.6087C9.74197 8.6087 10.8409 7.46994 10.8409 6.06522Z" fill="white"/>
+<path d="M8.38636 10.5163C9.23392 10.5163 10.0564 10.6276 10.8409 10.8368V12.1567C10.0632 11.9168 9.23928 11.788 8.38636 11.788C6.5611 11.788 4.8688 12.3779 3.47727 13.3837L3.47727 15.6033H10.8409V16.875H3.47727C2.79947 16.875 2.25 16.3056 2.25 15.6033V13.284C2.25 12.807 2.50755 12.3701 2.91708 12.1524L3.62682 11.788H3.62945C5.03866 10.9777 6.66051 10.5163 8.38636 10.5163Z" fill="white"/>
+<path d="M13.2955 10.5163V13.0598H15.75V14.3315H13.2955V16.875H12.0682V14.3315H9.61364V13.0598H12.0682V10.5163H13.2955Z" fill="white"/>
+</g>
+</g>
+</svg>

+ 26 - 0
src/components/Chat/Dialog.vue

@@ -0,0 +1,26 @@
+<template>
+  <van-dialog
+    style="--van-dialog-header-padding-top: 21px"
+    width="260"
+    confirmButtonColor="#FF9300"
+    v-model:show="show"
+    :title="title"
+    show-cancel-button
+    :confirmButtonText="confirmText"
+    :cancelButtonText="cancelText"
+    @confirm="$emit('confirm')"
+    @cancel="$emit('confirm')"
+  >
+    <slot></slot>
+  </van-dialog>
+</template>
+
+<script setup>
+const show = defineModel('show')
+const title = defineModel('title')
+const confirmText = defineModel('confirmText')
+const cancelText = defineModel('cancelText')
+defineEmits(['confirm', 'cancel'])
+</script>
+
+<style lang="scss" scoped></style>

+ 41 - 0
src/components/Chat/Empty.vue

@@ -0,0 +1,41 @@
+<template>
+  <div
+    :class="`w-full ${top ? 'mt-' + top : 'mt-100'} flex items-center justify-center flex-wrap min-h-200`"
+  >
+    <div v-if="img" class="w-160 h-160 shrink-0 mb-25">
+      <img class="w-full h-full object-cover" :src="img" alt="" />
+    </div>
+    <div class="w-full text-center text-[#BFC8DB]">{{ title }}</div>
+  </div>
+</template>
+
+<script setup>
+import search from '~/assets/img/chat/search.svg'
+const imageList = [
+  {
+    name: 'search',
+    img: search
+  }
+]
+
+const props = defineProps({
+  title: {
+    type: String,
+    default: '暂无数据'
+  },
+  top: {
+    type: String,
+    default: ''
+  },
+  image: {
+    type: String,
+    default: ''
+  }
+})
+
+const img = computed(() =>
+  props.image ? imageList.find((item) => item.name == props?.image).img : ''
+)
+</script>
+
+<style lang="scss" scoped></style>

+ 54 - 0
src/components/Chat/GroupAvatar.vue

@@ -0,0 +1,54 @@
+<template>
+  <div class="w-60 h-60 rounded-full overflow-hidden flex justify-start">
+    <!-- v-for="(item, index) in list :key="index"" -->
+    <div class="" v-for="(item, index) in 6" :key="index">
+      <img class="w-full h-full object-cover" :src="H5_default" alt="" />
+    </div>
+  </div>
+</template>
+
+<script setup>
+import H5_default from '../../assets/img/comment/H5_default.png'
+defineProps({
+  list: {
+    type: Array || String,
+    default: []
+  }
+})
+
+onMounted(() => {
+  // list = [H5_default, H5_default, H5_default, H5_default]
+  // console.log(list, 'list')
+})
+</script>
+
+<style lang="scss" scoped>
+.avatar-list {
+  display: grid;
+
+  grid-auto-flow: dense; /* 确保项目会填充所有的网格单元 */
+  grid-gap: 1px;
+}
+
+.one {
+  grid-template-columns: repeat(2, 1fr);
+  .item__child {
+    grid-column: span 2;
+  }
+}
+
+.two {
+  grid-template-columns: repeat(2, 1fr);
+  .item__child {
+    grid-column: span 2;
+  }
+}
+.three {
+  grid-template-columns: repeat(3, 1fr);
+  .item__child {
+    width: 17px;
+    height: 17px;
+    grid-column: span 3;
+  }
+}
+</style>

+ 24 - 0
src/components/Chat/Header.vue

@@ -0,0 +1,24 @@
+<template>
+  <div class="w-full h-48 fixed top-0 left-0 bg-white flex justify-between items-center px-12">
+    <van-icon name="arrow-left" size="24" @click="getBack" />
+    <h1>{{ title }}</h1>
+    <div></div>
+  </div>
+</template>
+
+<script setup>
+const router = useRouter()
+
+const props = defineProps({
+  title: {
+    type: String,
+    default: ''
+  }
+})
+
+const getBack = () => {
+  router.back()
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 26 - 0
src/components/Chat/HeaderBar.vue

@@ -0,0 +1,26 @@
+<template>
+  <van-nav-bar :title="title" fixed @click-left="getBack">
+    <template #left>
+      <div>
+        <van-icon name="arrow-left" color="black" size="18" />
+      </div>
+    </template>
+  </van-nav-bar>
+</template>
+
+<script setup>
+const router = useRouter()
+
+const props = defineProps({
+  title: {
+    type: String,
+    default: ''
+  }
+})
+
+const getBack = () => {
+  router.back()
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 69 - 0
src/components/Chat/Image.vue

@@ -0,0 +1,69 @@
+<template>
+  <!-- 聊天消息内容的文本展示 -->
+
+  <van-popover
+    teleport="images"
+    theme="dark"
+    placement="bottom"
+    actions-direction="horizontal"
+    v-model:show="showPopover"
+    @select="select"
+  >
+    <div class="flex my-16 mx-10">
+      <div v-for="(item, index) in actions" :key="index" @click="" class="mx-10 text-center">
+        <span :class="`iconfont ${item.icon} text-white   mb-4`" style="font-size: 24px"></span>
+        <p class="text-white text-base">{{ item.text }}</p>
+      </div>
+    </div>
+    <template #reference>
+      <img
+        class="w-full images h-full object-contain"
+        :src="itemData?.url"
+        @touchstart="doTouchstart"
+        alt=""
+      />
+    </template>
+  </van-popover>
+</template>
+
+<script setup>
+import { useClipboard } from '@vueuse/core'
+const { copy, isSupported } = useClipboard()
+
+const showPopover = ref(false)
+
+defineProps({
+  itemData: {
+    type: String,
+    default: ''
+  }
+})
+
+defineEmits(['onDelete'])
+
+const actions = [
+  { text: '引用', icon: 'icon-quote' },
+  { text: '复制', icon: 'icon-copy' },
+  { text: '删除', icon: 'icon-delete-three' }
+]
+
+function doTouchstart() {
+  showPopover.value = true
+}
+
+function select(action, index) {
+  console.log(action, index)
+
+  if (action.text == '复制') {
+    copy(itemData.text)
+    showToast('已复制')
+  }
+  if (action.text == '删除') {
+    $emit('onDelete')
+    showToast('已复制')
+  }
+  showPopover.value = false
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 35 - 0
src/components/Chat/Item.vue

@@ -0,0 +1,35 @@
+<template>
+  <div style="width: calc(100vw - 74px)" class="flex justify-start items-start">
+    <div class="shrink-0 w-40 h-40 border rounded-[4px]">
+      <img class="w-full h-full" :src="itemData.haeadImage" alt="" />
+    </div>
+    <template v-if="itemData?.type == 'text'">
+      <div class="inline-block w-0 h-0 mt-10 border-[8px] border-transparent border-r-white"></div>
+      <ChatText item-data="" @on-delete="$emit('onDelete')"></ChatText>
+    </template>
+    <!-- <template v-if="itemData?.type == 'image'"> -->
+    <div class="ml-10 max-w-251 border">
+      <img
+        class="w-full h-full object-contain"
+        src="../../assets/img/comment/H5_default.png"
+        alt=""
+      />
+      <!-- <ChatImage :item-data="_default" @on-delete="$emit('onDelete')" /> -->
+    </div>
+    <!-- </template> -->
+    <template v-if="itemData?.type == 'like'"></template>
+  </div>
+</template>
+
+<script setup>
+defineProps({
+  itemData: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+defineEmits(['onDelete'])
+</script>
+
+<style lang="scss" scoped></style>

+ 39 - 0
src/components/Chat/Search.vue

@@ -0,0 +1,39 @@
+<template>
+  <!-- fixed top-60 left-0 -->
+  <div class="w-full pt-62 bg-white px-16">
+    <div class="flex h-40 mb-12 w-full items-center justify-between rounded-full border bg-white">
+      <input
+        type="text"
+        v-model="searchString"
+        class="ml-5 w-[80%] h-full pl-10 text-[13px]"
+        :placeholder="placeholder"
+        @keydown.enter="$emit('search')"
+        @change="$emit('search')"
+        style="outline: none; background: none"
+      />
+
+      <button
+        @click="$emit('search')"
+        class="h-full border-l-[1px] flex justify-center items-center w-66 shrink-0 rounded-r-full"
+      >
+        <div style="color: white" class="w-16 h-16 shrink-0">
+          <img class="w-full h-full object-cover" src="~/assets/img/visa/search.svg" alt="" />
+        </div>
+      </button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+const searchString = defineModel('searchString')
+defineProps({
+  placeholder: {
+    type: String,
+    default: '搜索'
+  }
+})
+
+defineEmits(['search'])
+</script>
+
+<style lang="scss" scoped></style>

+ 67 - 0
src/components/Chat/Text.vue

@@ -0,0 +1,67 @@
+<template>
+  <!-- 聊天消息内容的文本展示 -->
+
+  <van-popover
+    theme="dark"
+    placement="bottom"
+    v-model:show="showPopover"
+    :actions="actions"
+    actions-direction="horizontal"
+    @select="select"
+  >
+    <template #reference>
+      <div
+        @touchstart="doTouchstart"
+        class="inline-block min-h-40 max-w-full break-words overflow-auto rounded-[4px] relative bg-white box-border text-base p-5 text-wrap"
+      >
+        {{ item }}看哈金沙萨空间的哈师大将扩大解释
+      </div>
+    </template>
+  </van-popover>
+</template>
+
+<script setup>
+import { useClipboard } from '@vueuse/core'
+const { copy, isSupported } = useClipboard()
+
+const showPopover = ref(false)
+
+defineProps({
+  itemData: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+defineEmits(['onDelete'])
+// const longPressDuration = 1000
+// const pressTimer = ref(null)
+const actions = [
+  // { text: '引用', icon: 'add-o' },
+  { text: '复制', icon: 'add-o' },
+  { text: '删除', icon: 'add-o' },
+  { text: '撤回', icon: 'add-o' }
+]
+
+function doTouchstart() {
+  // pressTimer.value = setTimeout(() => {
+  showPopover.value = true
+  // }, longPressDuration)
+}
+
+function select(action, index) {
+  console.log(action, index)
+
+  if (action.text == '复制') {
+    copy(itemData.text)
+    showToast('已复制')
+  }
+  if (action.text == '删除') {
+    $emit('onDelete')
+    showToast('已复制')
+  }
+  showPopover.value = false
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 5 - 0
src/components/Profile/InteractionMessage/All.vue

@@ -0,0 +1,5 @@
+<template>
+  <div>全部消息</div>
+</template>
+<script setup></script>
+<style lang="scss" scoped></style>

+ 5 - 0
src/components/Profile/InteractionMessage/Eit.vue

@@ -0,0 +1,5 @@
+<template>
+  <div>艾特功能</div>
+</template>
+<script setup></script>
+<style lang="scss" scoped></style>

+ 5 - 0
src/components/Profile/InteractionMessage/LikesandFavorites.vue

@@ -0,0 +1,5 @@
+<template>
+  <div>赞与收藏</div>
+</template>
+<script setup></script>
+<style lang="scss" scoped></style>

+ 5 - 0
src/components/Profile/InteractionMessage/MyComment.vue

@@ -0,0 +1,5 @@
+<template>
+  <div>我的评论</div>
+</template>
+<script setup></script>
+<style lang="scss" scoped></style>

+ 5 - 0
src/components/Profile/InteractionMessage/ReceiveComment.vue

@@ -0,0 +1,5 @@
+<template>
+  <div>收到的评论</div>
+</template>
+<script setup></script>
+<style lang="scss" scoped></style>

+ 5 - 0
src/components/Profile/InteractionMessage/SendComment.vue

@@ -0,0 +1,5 @@
+<template>
+  <div>发出的评论</div>
+</template>
+<script setup></script>
+<style lang="scss" scoped></style>

+ 77 - 0
src/components/Profile/News/AllMessage.vue

@@ -0,0 +1,77 @@
+<template>
+  <div class="flex justify-between itmes-start px-16 py-8 mb-12">
+    <!-- <template #icon> -->
+    <div class="w-265 shrink-0 box-border flex justify-start itmes-start">
+      <div class="w-32 h-32 shrink-0 rounded-full mr-8 overflow-hidden">
+        <img
+          v-if="itemData?.headImageUrl"
+          class="w-full h-full object-cover"
+          :src="itemData?.headImageUrl"
+          alt=""
+        />
+        <img
+          v-else
+          class="w-full h-full object-cover"
+          src="https://www.xiaoyaotravel.com/_nuxt/default_avatar.gSq5JxK1.png"
+          alt=""
+        />
+      </div>
+      <!-- </template> -->
+      <!-- <template #title> -->
+      <div>
+        <div class="-mt-5">
+          <span class="text-sm text-black-6 pr-4">{{ itemData.name }} 暖阳</span>
+          <div :class="`inline-block box-border px-6 text-sm text-[#FF9300] bg-[#FF9300]/[0.08]`">
+            {{ state(itemData?.fansState)?.text ? '' : '粉丝' }}
+          </div>
+        </div>
+        <h1 class="font-semibold text-black-3 text-base">赞了你的游记</h1>
+        <p class="text-black-9 text-sm">2024-12-24 15:47</p>
+      </div>
+    </div>
+
+    <!-- </template> -->
+
+    <!-- <template #value> -->
+    <div class="w-71 h-47 shrink-0 rounded-[4px] overflow-hidden">
+      <img
+        v-if="itemData?.travelNoteUrl"
+        class="w-full h-full object-cover"
+        :src="itemData?.travelNoteUrl"
+        alt=""
+      />
+      <img
+        v-else
+        class="w-full h-full object-cover"
+        src="~/assets/img/comment/H5_default.png"
+        alt=""
+      />
+    </div>
+    <!-- </template> -->
+  </div>
+</template>
+<script setup>
+const listColor = [
+  {
+    text: '粉丝',
+    color: '#FF9300'
+  },
+  {
+    text: '互关',
+    color: '#3369E7'
+  }
+]
+
+defineProps({
+  itemData: {
+    type: Object,
+    default: () => ({})
+  }
+})
+
+const state = (parmas) => {
+  return listColor[parmas]
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 353 - 0
src/components/Profile/News/ChatInput.vue

@@ -0,0 +1,353 @@
+<template>
+  <!-- <div
+    v-if="showInput"
+    class="w-[100vw] h-[100vh] fixed top-0 border-[#000] left-0 z-100 bg-[#000]/[0.1]"
+  > -->
+  <div @click.stop="" class="fixed bottom-0 left-0 w-full bg-[#fff] pt-10 pb-30 z-52">
+    <div :class="`flex ${commentValue.length > 13 ? 'items-end' : 'items-center'} px-15 pb-10`">
+      <span
+        @click="showVoice = !showVoice"
+        v-if="!showVoice"
+        :class="`iconfont icon-voice-one text-black-6 `"
+        style="font-size: 32px"
+      ></span>
+      <van-icon @click="showVoice = !showVoice" v-else name="volume" size="22" />
+
+      <div
+        v-if="showVoice"
+        @mousedown="startRecording"
+        @mouseup="stopRecording"
+        @touchstart="startRecording"
+        @touchend="stopRecording"
+        :class="`relative rounded-full bg-white justify-center  flex-1 ml-12 mr-12 pl-5 pr-5 pt-4 pb-4  h-40 border flex items-center `"
+      >
+        {{ isRecording ? '松开结束' : '按住说话' }}
+      </div>
+      <div
+        v-else
+        style="overflow-y: scroll; -webkit-scrollbar-width: 0px; -webkit-scrollbar: none"
+        class="box-border rounded-full bg-[#F3F3F3] justify-between px-16 py-8 flex-1 mx-12 h-40 border flex items-center"
+      >
+        <textarea
+          v-model="commentValue"
+          ref="textareaRef"
+          placeholder="请输入"
+          @focus="textareaFocus"
+          class="ml-8 flex-1 box-border w-full h-full bg-[#F3F3F3]"
+          maxlength="5000"
+          style="resize: none"
+          @keydown.enter="commentValue ? addComment : () => {}"
+        ></textarea>
+      </div>
+      <span
+        @click="openEmoji"
+        class="iconfont icon-slightly-smiling-face text-black-6 mr-12"
+        style="font-size: 32px"
+      ></span>
+      <span
+        @click="openOther"
+        class="iconfont icon-close-one text-black-6"
+        style="font-size: 32px"
+      ></span>
+
+      <!-- <div
+          @click="addComment"
+          class="py-6 px-16 mr-12 bg-[#FD9A00] text-[#fff] flex items-center justify-center rounded-full shrink-0"
+        >
+          发送
+        </div> -->
+    </div>
+
+    <div v-if="showEmoji" class="w-full h-300 bg-[#fff] overflow-auto">
+      <div @click="closeEmojiBox" class="flex justify-end pr-15 text-black-9 text-sm">收起表情</div>
+      <div class="flex items-center flex-wrap w-full px-8">
+        <div
+          v-for="(item, index) in emojiJson"
+          :key="index"
+          class="active:bg-[#ddd] text-4xl w-[10%] aspect-[1/1] flex items-center justify-center"
+        >
+          <div @click="selectEmoji(index)" v-html="item.emoji"></div>
+        </div>
+      </div>
+    </div>
+    <div v-if="showOther" class="w-full h-200 bg-[#fff] overflow-auto">
+      <div class="flex items-start flex-wrap w-full h-full py-15 px-8 bg-white">
+        <template v-for="(item, index) in otherList" :key="index">
+          <div
+            @click="item.fn"
+            v-if="item?.isShow"
+            class="mx-10 text-4xl active:text-[#FF9300] w-[15%] aspect-[1/1] flex flex-wrap items-center justify-center"
+          >
+            <div
+              class="w-54 h-54 active:bg-[#FF9300]/[0.1] bg-[#F3F3F3] shrink-0 rounded-full mb-5 overflow-auto flex justify-center items-center"
+            >
+              <img :src="item.icon" alt="" />
+              <span
+                :class="`iconfont ${item.icon} text-black-6 active:text-[#FF9300] `"
+                style="font-size: 32px"
+              ></span>
+            </div>
+            <p class="text-sm w-full text-center">{{ item.title }}</p>
+          </div>
+        </template>
+      </div>
+    </div>
+  </div>
+</template>
+<script setup>
+import emojiJson from './emoji.js'
+
+const shareGroup = defineModel('shareGroup')
+
+const textareaRef = ref(null)
+
+// 显示输入框
+// const showInput = ref(true)
+// 是否展示表情
+const showEmoji = ref(false)
+// 是否展示其他功能
+const showOther = ref(false)
+
+// 是否展示语音
+const showVoice = ref(false)
+
+// 是否在录音
+const isRecording = ref(false)
+const transcript = ref('') // 存储语音识别结果
+// 输入的内容
+const commentValue = ref('')
+
+const addContent = ref(true)
+
+// 记录光标位置
+const cursorIndex = ref(0)
+
+// 创建语音识别实例
+let recognition = null
+// if ('SpeechRecognition' in window || 'webkitSpeechRecognition' in window) {
+//   recognition = new (window.SpeechRecognition || window.webkitSpeechRecognition)()
+//   recognition.continuous = true // 连续识别
+//   recognition.interimResults = true // 显示中间结果
+//   recognition.lang = 'zh-CN' // 设置识别语言(中文)
+
+//   // 监听识别结果
+//   recognition.onresult = (event) => {
+//     const lastResult = event.results[event.resultIndex]
+//     if (lastResult.isFinal) {
+//       transcript.value = lastResult[0].transcript // 获取最终结果
+//     } else {
+//       transcript.value = lastResult[0].transcript // 获取中间结果
+//     }
+//   }
+
+//   // 错误处理
+//   recognition.onerror = (event) => {
+//     console.error('语音识别出错:', event.error)
+//     isRecording.value = false
+//   }
+
+//   // 结束时重置状态
+//   recognition.onend = () => {
+//     isRecording.value = false
+//   }
+// }
+
+// 启动录音
+const startRecording = () => {
+  if (recognition) {
+    // recognition?.start()
+    isRecording.value = true
+  }
+}
+
+// 停止录音
+const stopRecording = () => {
+  if (recognition) {
+    // recognition?.stop()
+    isRecording.value = false
+  }
+}
+
+const { open, onChange } = useFileDialog({
+  accept: '.png,.png,.jpeg,.JPG,Png '
+})
+
+// 上传相册
+const uploadPictures = () => {
+  open()
+}
+
+onChange(async (files) => {
+  if (!files.length) return
+
+  const formData = new FormData()
+  formData.append('uploadFile', files[0])
+  formData.append('asImage', true)
+  formData.append('fieldName', 'image')
+  const maxSize = 10 * 1024 * 1024 // 10 MB
+
+  if (files[0].size > maxSize) {
+    showToast('上传图片过大,请重新上传')
+    return
+  } else {
+    try {
+      showLoadingToast({
+        message: '图片上传中...',
+        duration: 1000000
+      })
+
+      const { data } = await request('/website/tourMessage/upload', {
+        method: 'post',
+        body: formData
+      })
+      // form.image.push(data.fileUrl)
+      closeToast()
+      showToast('图片上传成功')
+    } catch (error) {
+      // form.image.push({
+      //   url: files[0].name,
+      //   status: 'failed',
+      //   isImage: true,
+      //   message: '上传失败',
+      //   imageFit: 'contain'
+      // })
+      closeToast()
+      showToast('图片上传失败')
+
+      console.log('图片上传失败')
+    }
+  }
+})
+
+// 分享群聊
+const shareGroupChat = () => {
+  console.log('分享群聊')
+}
+
+// 按住说话 松开结束
+const changeShowVoiceing = () => {}
+
+const otherList = reactive([
+  {
+    title: '相册',
+    icon: 'icon-pic',
+    isShow: true,
+    fn: uploadPictures
+  },
+  {
+    title: '分享群聊',
+    icon: 'icon-peoples-two',
+    isShow: shareGroup.value,
+    fn: shareGroupChat
+  }
+])
+
+// 转换评论中的一些非字符emoji
+// function coveredContent(val) {
+//   if (!val) return ''
+//   return val.replace(/\[.*?]/g, function (str) {
+//     const baseApi = import.meta.env.VITE_APP_EMOJI_API
+//     const emojiName = emojiJson[str]
+//     console.log(emojiName, 'emojiName')
+
+//     if (!emojiName) return str
+
+//     return `<img src=${baseApi}${emojiJson[str]} style="width: 20px; height: 20px;display: inline-block; vertical-align: middle"/>`
+//   })
+// }
+
+async function addComment() {
+  console.log(commentValue.value, '111')
+
+  //   if (!addContent.value) return
+  //   if (!token.value) {
+  //     showConfirmDialog({
+  //       showConfirmDialog: true,
+  //       title: '提示',
+  //       message: '登录后可以发布评论',
+  //       theme: 'round-button'
+  //     }).then(async () => {
+  //       navigateTo({ path: '/login' })
+  //     })
+  //     return
+  //   }
+  if (!commentValue.value.trim()) {
+    showToast('输入内容不能为空!')
+    return
+  }
+  //   canAddComment.value = false
+  //   const body = { travelNoteId: replyComment.value.travelNoteId, commentContent: commentValue.value }
+  //   if (replyComment.value.id) {
+  //     body.replyCommentId = replyComment.value.id
+  //     body.replyUserId = replyComment.value.createUserId
+  //     body.parentId = replyComment.value.parentId
+  //   }
+  //   request('website/comment/tourTravelNotesComment/add', { method: 'post', body })
+  //     .then(() => {
+  //       commentValue.value = ''
+  //       showToast('评论成功')
+  //       // getMyCommentList()
+  //       showInput.value = false
+  //       showEmoji.value = false
+  //     })
+  //     .finally(() => (canAddComment.value = true), (replyComment.value = {}), (cursorIndex.value = 0))
+}
+
+// 获取焦点
+function textareaFocus() {
+  showEmoji.value = false
+  // showInput.value = true
+  textareaRef.value?.focus()
+}
+
+// 文本域失焦
+function handleBlur() {
+  showEmoji.value = false
+  textareaRef.value?.blur()
+  // showInput.value = false
+  replyComment.value = {}
+}
+
+// 打开表情
+function openEmoji() {
+  showOther.value = false
+  nextTick(() => {
+    textareaRef.value.selectionStart && (cursorIndex.value = textareaRef.value.selectionStart)
+    showEmoji.value = true
+  })
+}
+
+// 打开上传图片或者其他的
+function openOther() {
+  showEmoji.value = false
+  showOther.value = true
+}
+
+// 收起表情
+function closeEmojiBox() {
+  showEmoji.value = false
+  nextTick(() => {
+    textareaRef.value.focus()
+  })
+}
+
+// 选择表情
+function selectEmoji(emojiStr = '') {
+  const length = emojiStr.length
+  commentValue.value =
+    commentValue.value.slice(0, cursorIndex.value) +
+    emojiJson[emojiStr].emoji +
+    commentValue.value.slice(cursorIndex.value)
+
+  nextTick(() => {
+    cursorIndex.value += length
+    textareaRef.value.setSelectionRange(cursorIndex.value, cursorIndex.value)
+  })
+}
+</script>
+
+<style lang="scss" scoped>
+.no-scrollbar::-webkit-scrollbar {
+  width: 0;
+}
+</style>

+ 136 - 0
src/components/Profile/News/GroupChat.vue

@@ -0,0 +1,136 @@
+<template>
+  <van-swipe-cell>
+    <div
+      @click="$emit('onChatPage')"
+      class="w-full relative h-82 flex justify-start items-center px-16"
+    >
+      <div class="w-48 h-48">
+        <van-badge v-if="messagesNumber > 0" v-bind="messageNumber(messagesNumber)" max="99">
+          <!-- v-if="itemData?.avatar" -->
+          <div
+            v-if="itemData?.avatar"
+            class="w-48 h-48 rounded-full overflow-hidden flex justify-center items-center"
+          >
+            <img class="w-full h-full shrink-0 object-cover" :src="itemData?.avatar" alt="" />
+          </div>
+          <div
+            v-else
+            class="w-48 h-48 rounded-full overflow-hidden flex justify-center items-center"
+          >
+            <img
+              class="w-full h-full shrink-0 object-cover"
+              src="~/assets/img/chat/group-avatar.svg"
+              alt=""
+            />
+          </div>
+        </van-badge>
+
+        <template v-else>
+          <div
+            v-if="itemData?.avatar"
+            class="w-48 h-48 rounded-full overflow-hidden flex justify-center items-center flex-wrap"
+          >
+            <img class="w-full h-full shrink-0 object-cover" :src="itemData?.avatar" alt="" />
+            <img
+              class="w-full h-full shrink-0 object-cover"
+              src="~/assets/img/chat/group-avatar.svg"
+              alt=""
+            />
+          </div>
+          <div
+            v-else
+            class="w-48 h-48 rounded-full overflow-hidden flex justify-center items-center"
+          >
+            <img
+              class="w-full h-full shrink-0 object-cover"
+              src="~/assets/img/chat/group-avatar.svg"
+              alt=""
+            />
+          </div>
+        </template>
+      </div>
+
+      <div class="h-48 w-245 ml-12 flex flex-wrap">
+        <h1 class="line-clamp-1 mb-8 w-full text-xl text-black-3 font-semibold">
+          群聊{{ itemData?.title }}
+        </h1>
+        <p class="line-clamp-1 w-full h-20 text-base text-black/[0.6] leading-3xl">
+          你吃饭了么 {{ itemData?.message }}
+        </p>
+      </div>
+
+      <div class="w-35 h-48 shrink-0">
+        <p class="text-black/[0.6] mb-12 text-sm text-end">02:06{{ itemData?.time }}</p>
+
+        <div v-if="itemData?.isNotDisturb == 1" class="w-full shrink-0 flex justify-end">
+          <span class="iconfont icon-close-remind text-black-6" style="font-size: 16px"></span>
+        </div>
+      </div>
+      <div class="absolute bottom-0 right-0 w-[96%] h-1 border-b-[1px]"></div>
+    </div>
+
+    <template #right>
+      <div class="pl-2 h-full">
+        <van-button
+          style="height: 100%"
+          square
+          text="设为免打扰"
+          @click="$emit('onNoBother')"
+          type="danger"
+          color="#FF9300"
+          class="delete-button"
+        />
+        <van-button
+          style="height: 100%"
+          square
+          text="不显示聊天"
+          @click="$emit('onNoBother')"
+          type="danger"
+          color="#E37318"
+          class="delete-button"
+        />
+        <van-button
+          style="height: 100%"
+          square
+          text="删除聊天"
+          @click="$emit('onConvDelete')"
+          type="danger"
+          class="delete-button"
+        />
+      </div>
+    </template>
+  </van-swipe-cell>
+</template>
+
+<script setup>
+defineProps({
+  itemData: {
+    type: Object,
+    default: () => ({})
+  }
+})
+defineEmits(['onNoBother', 'onConvDelete', 'onChatPage'])
+
+const messagesNumber = ref(10)
+
+// 消息数量通知的展示  需要动态的展示
+const messageNumber = (content) => {
+  let messageNumberObj = {}
+  if (content <= 1) {
+    messageNumberObj = {
+      offset: [-5, 4],
+      dot: true,
+      content
+    }
+  }
+  if (content > 1) {
+    messageNumberObj = {
+      offset: [-10, 7],
+      content
+    }
+  }
+  return messageNumberObj
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 131 - 0
src/components/Profile/News/SingleChat.vue

@@ -0,0 +1,131 @@
+<template>
+  <van-swipe-cell>
+    <div
+      @click="$emit('onChatPage')"
+      class="w-full h-82 relative flex justify-start items-center px-16 mb-20"
+    >
+      <div class="w-48 h-48">
+        <van-badge v-if="messagesNumber > 0" v-bind="messageNumber(messagesNumber)" max="99">
+          <div
+            v-if="itemData?.avatar"
+            class="w-48 h-48 rounded-full overflow-hidden flex justify-center items-center"
+          >
+            <img class="w-full h-full shrink-0 object-cover" :src="itemData?.avatar" alt="" />
+          </div>
+          <div
+            v-else
+            class="w-48 h-48 bg-[#E7E7E7] rounded-full overflow-hidden flex justify-center items-center"
+          >
+            <img
+              class="w-24 h-24 shrink-0 object-cover"
+              src="~/assets/img/chat/user-grey.svg"
+              alt=""
+            />
+          </div>
+        </van-badge>
+
+        <template v-else>
+          <div
+            v-if="itemData?.avatar"
+            class="w-48 h-48 rounded-full overflow-hidden flex justify-center items-center"
+          >
+            <img class="w-full h-full shrink-0 object-cover" :src="itemData?.avatar" alt="" />
+          </div>
+          <div
+            v-else
+            class="w-48 h-48 bg-[#E7E7E7] rounded-full overflow-hidden flex justify-center items-center"
+          >
+            <img
+              class="w-24 h-24 shrink-0 object-cover"
+              src="~/assets/img/chat/user-grey.svg"
+              alt=""
+            />
+          </div>
+        </template>
+      </div>
+
+      <div class="h-48 w-245 ml-12 flex flex-wrap">
+        <h1 class="line-clamp-1 mb-8 w-full text-xl text-black-3 font-semibold">单个会话</h1>
+        <p class="line-clamp-1 w-full h-20 text-base text-black/[0.6] leading-3xl">你吃饭了么</p>
+      </div>
+
+      <div class="w-35 h-48 shrink-0">
+        <p class="text-black/[0.6] text-sm text-end">02:06{{ itemData?.time }}</p>
+
+        <div v-if="itemData?.isNotDisturb == 1" class="w-full h-16 shrink-0 mt-12 flex justify-end">
+          <img
+            class="w-16 h-16 object-cover"
+            src="~/assets/img/chat/close-remind.svg
+            "
+            alt=""
+          />
+        </div>
+      </div>
+      <div class="absolute bottom-0 right-0 w-[96%] h-1 border-b-[1px]"></div>
+    </div>
+
+    <template #right>
+      <div class="pl-2 h-full">
+        <van-button
+          style="height: 100%"
+          square
+          text="设为免打扰"
+          @click="$emit('onNoBother')"
+          type="danger"
+          color="#FF9300"
+          class="delete-button h-full"
+        />
+        <van-button
+          style="height: 100%"
+          square
+          text="不显示聊天"
+          @click="$emit('onNoBother')"
+          type="danger"
+          color="#E37318"
+          class="delete-button"
+        />
+        <van-button
+          style="height: 100%"
+          square
+          text="删除聊天"
+          @click="$emit('onConvDelete')"
+          type="danger"
+          class="delete-button"
+        />
+      </div>
+    </template>
+  </van-swipe-cell>
+</template>
+
+<script setup>
+defineProps({
+  itemData: {
+    type: Object,
+    default: () => ({})
+  }
+})
+defineEmits(['onNoBother', 'onConvDelete', 'onChatPage'])
+
+const messagesNumber = ref(10)
+
+// 消息数量通知的展示  需要动态的展示
+const messageNumber = (content) => {
+  let messageNumberObj = {}
+  if (content <= 1) {
+    messageNumberObj = {
+      offset: [-5, 4],
+      dot: true,
+      content
+    }
+  }
+  if (content > 1) {
+    messageNumberObj = {
+      offset: [-10, 7],
+      content
+    }
+  }
+  return messageNumberObj
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 108 - 0
src/components/Profile/News/emoji.js

@@ -0,0 +1,108 @@
+export default [
+  { name: '嘿嘿', emoji: '😀' },
+  { name: '哈哈', emoji: '😃' },
+  { name: '嘻嘻', emoji: '😁' },
+  { name: '苦笑', emoji: '😅' },
+  { name: '融化', emoji: '🫠' },
+  { name: '笑得满地打滚', emoji: '🤣' },
+  { name: '眨眼', emoji: '😉' },
+  { name: '大笑', emoji: '😄' },
+  { name: '笑哭了', emoji: '😂' },
+  { name: '羞涩微笑', emoji: '😊' },
+  { name: '呵呵', emoji: '🙂' },
+  { name: '微笑天使', emoji: '😇' },
+  { name: '斜眼笑', emoji: '😆' },
+  { name: '倒脸', emoji: '🙃' },
+  { name: '喜笑颜开', emoji: '🥰' },
+  { name: '微笑', emoji: '☺️' },
+  { name: '花痴', emoji: '😍' },
+  { name: '羞涩亲亲', emoji: '😚' },
+  { name: '好崇拜哦', emoji: '🤩' },
+  { name: '微笑亲亲', emoji: '😙' },
+  { name: '飞吻', emoji: '😘' },
+  { name: '含泪的笑脸', emoji: '🥲' },
+  { name: '亲亲', emoji: '😗' },
+  { name: '好吃', emoji: '😋' },
+  { name: '吐舌', emoji: '😛' },
+  { name: '单眼吐舌', emoji: '😜' },
+  { name: '滑稽', emoji: '🤪' },
+  { name: '眯眼吐舌', emoji: '😝' },
+  { name: '发财', emoji: '🤑' },
+  { name: '抱抱', emoji: '🤗' },
+  { name: '想一想', emoji: '🤔' },
+  { name: '不说', emoji: '🤭' },
+  { name: '致敬', emoji: '🫡' },
+  { name: '睁眼捂嘴', emoji: '🫢' },
+  { name: '偷看', emoji: '🫣' },
+  { name: '安静的脸', emoji: '🤫' },
+  { name: '闭嘴', emoji: '🤐' },
+  { name: '龇牙咧嘴', emoji: '😬' },
+  { name: '挑眉', emoji: '🤨' },
+  { name: '迷茫', emoji: '😶‍🌫️' },
+  { name: '呼气', emoji: '😮‍💨' },
+  { name: '冷漠', emoji: '😐' },
+  { name: '得意', emoji: '😏' },
+  { name: '说谎', emoji: '🤥' },
+  { name: '无语', emoji: '😑' },
+  { name: '不高兴', emoji: '😒' },
+  { name: '沉默', emoji: '😶' },
+  { name: '翻白眼', emoji: '🙄' },
+  { name: '松了口气', emoji: '😌' },
+  { name: '沉思', emoji: '😔' },
+  { name: '流口水', emoji: '🤤' },
+  { name: '睡着了', emoji: '😴' },
+  { name: '困', emoji: '😪' },
+  { name: '感冒', emoji: '😷' },
+  { name: '打喷嚏', emoji: '🤧' },
+  { name: '发烧脸发烧', emoji: '🥵' },
+  { name: '受伤', emoji: '🤕' },
+  { name: '冷脸', emoji: '🥶' },
+  { name: '恶心', emoji: '🤢' },
+  { name: '头昏眼花', emoji: '🥴' },
+  { name: '呕吐', emoji: '🤮' },
+  { name: '晕头转向', emoji: '😵' },
+  { name: '爆炸头', emoji: '🤯' },
+  { name: '晕', emoji: '😵‍💫' },
+  { name: '困扰', emoji: '😕' },
+  { name: '吃惊', emoji: '😮' },
+  { name: '忍住泪水', emoji: '🥹' },
+  { name: '失望但如释重负', emoji: '😥' },
+  { name: '痛苦', emoji: '😣' },
+  { name: '郁闷', emoji: '🫤' },
+  { name: '缄默', emoji: '😯' },
+  { name: '啊', emoji: '😦' },
+  { name: '哭', emoji: '😢' },
+  { name: '失望', emoji: '😞' },
+  { name: '担心', emoji: '😟' },
+  { name: '震惊', emoji: '😲' },
+  { name: '极度痛苦', emoji: '😧' },
+  { name: '放声大哭', emoji: '😭' },
+  { name: '汗', emoji: '😓' },
+  { name: '微微不满', emoji: '🙁' },
+  { name: '脸红', emoji: '😳' },
+  { name: '害怕', emoji: '😨' },
+  { name: '吓死了', emoji: '😱' },
+  { name: '累死了', emoji: '😩' },
+  { name: '不满', emoji: '☹️' },
+  { name: '恳求的脸', emoji: '🥺' },
+  { name: '冷汗', emoji: '😰' },
+  { name: '困惑', emoji: '😖' },
+  { name: '汗', emoji: '😓' },
+  { name: '打呵欠', emoji: '🥱' },
+  { name: '傲慢', emoji: '😤' },
+  { name: '生气的恶魔', emoji: '👿' },
+  { name: '怒火中烧', emoji: '😡' },
+  { name: '头骨', emoji: '💀' },
+  { name: '生气', emoji: '😠' },
+  { name: '骷髅', emoji: '☠️' },
+  { name: '嘴上有符号的脸', emoji: '🤬' },
+  { name: '恶魔微笑', emoji: '😈' },
+  { name: '大便', emoji: '💩' },
+  { name: '外星人', emoji: '👽' },
+  { name: '小丑脸', emoji: '🤡' },
+  { name: '外星怪物', emoji: '👾' },
+  { name: '食人魔', emoji: '👹' },
+  { name: '机器人', emoji: '🤖' },
+  { name: '小妖精', emoji: '👺' },
+  { name: '鬼', emoji: '👻' }
+]

+ 133 - 0
src/pages/chat/announcement.vue

@@ -0,0 +1,133 @@
+<template>
+  <div class="w-full h-[100vh] border">
+    <van-nav-bar title="群公告" fixed @click-left="getBack">
+      <template #left>
+        <div>
+          <van-icon name="arrow-left" color="black" size="18" />
+        </div>
+      </template>
+      <template v-if="isRankAndFiler(queryDataName?.groupRole)" #right>
+        <div
+          :class="`font-semibold text-xl ${showBottom ? 'text-[#FF9300]' : 'text-black-9'} `"
+          @click="showBottom ? getUpdAnnouncement : () => {}"
+        >
+          确认
+        </div>
+      </template>
+    </van-nav-bar>
+    <div class="h-60"></div>
+
+    <template v-if="isRankAndFiler(queryDataName?.groupRole)">
+      <van-cell-group inset>
+        <van-field
+          style="background-color: #f7f8fa"
+          v-model="queryDataName.announcement"
+          required
+          size="large"
+          rows="5"
+          autosize
+          @focus="showBottom = true"
+          @update:model-value="
+            (val) => {
+              if (val.lenght >= 1000) {
+                showToast('输入的内容只能是1000个字')
+              }
+            }
+          "
+          autocorrect
+          type="textarea"
+          maxlength="1000"
+          label-align="top"
+          show-word-limit
+        />
+      </van-cell-group>
+
+      <van-cell
+        @click="
+          () => {
+            queryDataName.announcement = groupBulletinTemplate
+            showBottom = true
+          }
+        "
+        :label="groupBulletinTemplate"
+      >
+        <template #title>
+          <span class="font-semibold text-black-6">群公告模版</span>
+        </template>
+      </van-cell>
+    </template>
+    <template v-else>
+      <div class="w-full px-16">
+        <div class="w-full mb-10" v-html="queryDataName?.announcement"></div>
+
+        <p class="text-sm text-black-9 text-center">仅群主及群管理员可编辑</p>
+      </div>
+    </template>
+  </div>
+</template>
+
+<script setup>
+const router = useRouter()
+const route = useRoute()
+console.log(route?.query?.groupId)
+
+onMounted(() => {
+  getAnnouncement()
+})
+
+const groupBulletinTemplate =
+  '1.本群提倡友好理性交流,鼓励群友多发言,多互动 2.禁止无意义刷屏、发送广告信息以及谩骂等不良消息 3.为了保证群活跃不经常发言的群友,可能会被定时清理出群'
+
+const queryDataName = reactive({
+  groupId: computed(() => route?.query?.groupId ?? ''),
+  userId: computed(() => route?.query?.userId ?? ''),
+  groupRole: computed(() => route?.query?.groupRole ?? ''),
+  announcement: ''
+})
+
+//
+const showBottom = ref(false)
+
+// 是否是普通成员
+const isRankAndFiler = (role) => {
+  return role == 1 || role == 2 ? true : false
+}
+
+const getBack = () => {
+  router.back()
+  showBottom.value = false
+}
+definePageMeta({
+  layout: false
+})
+
+// 修改的接口
+const getUpdAnnouncement = async () => {
+  let { data } = await request('/website/tourism/visa/list/page', {
+    ...queryDataName
+  })
+  if (data) {
+    queryDataName.announcement = data
+    showBottom.value = false
+  } else {
+    showBottom.value = false
+  }
+}
+
+// 获取公告
+const getAnnouncement = async () => {
+  let { data } = await request('/website/tourism/visa/list/page', {
+    ...queryDataName
+  })
+  if (data) {
+    queryDataName.announcement = data
+  } else {
+  }
+}
+
+useSeoMeta({
+  title: '群公告'
+})
+</script>
+
+<style lang="scss" scoped></style>

+ 366 - 0
src/pages/chat/create-group.vue

@@ -0,0 +1,366 @@
+<template>
+  <!-- 创建群聊 -->
+  <div class="w-full h-[100vh] bg-[#F7F8FA] box-border pt-66">
+    <ChatHeader title="创建群聊" />
+
+    <van-cell-group style="margin-bottom: 12px" inset>
+      <van-field
+        v-model="formData.groupName"
+        rows="1"
+        autosize
+        :rules="[{ required: true, message: '请输入群名称' }]"
+        type="textarea"
+        placeholder="请输入群名称"
+        label-align="top"
+        maxlength="30"
+        show-word-limit
+      >
+        <template #label>
+          <span class="text-xl text-black-3">
+            群名称
+            <span class="text-[#EE0C0C] m-0 p-0">*</span>
+          </span>
+        </template>
+      </van-field>
+    </van-cell-group>
+    <van-cell-group style="margin-bottom: 12px" inset>
+      <van-field
+        v-model="formData.description"
+        rows="3"
+        autosize
+        type="textarea"
+        :rules="[{ required: true, message: '请输入群介绍' }]"
+        placeholder="简单说说你想在群内讨论的话题、以及希望哪些人加入群聊"
+        label-align="top"
+        maxlength="200"
+        show-word-limit
+      >
+        <template #label>
+          <span class="text-xl text-black-3">
+            群介绍
+            <span class="text-[#EE0C0C] m-0 p-0">*</span>
+          </span>
+        </template>
+      </van-field>
+    </van-cell-group>
+
+    <van-cell-group style="margin-bottom: 12px" inset>
+      <!-- value="未选择" -->
+      <van-cell @click="show = true" center is-link :value="groupTypeName">
+        <template #title>
+          <span class="text-xl text-black-3">群类型</span>
+        </template>
+        <template #label>
+          <span class="text-base text-black/[0.4]">同城生活,聊天交友等</span>
+        </template>
+      </van-cell>
+    </van-cell-group>
+
+    <van-cell-group style="margin-bottom: 12px" inset>
+      <van-cell center>
+        <template #title>
+          <span class="text-xl text-black-3">个人主页展示</span>
+        </template>
+        <template #label>
+          <span class="text-base text-black/[0.4]">开启后,在群聊广场和个人主页</span>
+        </template>
+        <template #right-icon>
+          <van-switch v-model="checked" active-color="#FF9300" inactive-color="#dcdee0" />
+        </template>
+      </van-cell>
+    </van-cell-group>
+
+    <!-- 弹窗 -->
+
+    <van-dialog width="90%" v-model:show="show" show-cancel-button>
+      <template #title>
+        <div class="w-full flex justify-between items-center px-16 py-11 border-b-[1px]">
+          <h1>群类型</h1>
+          <div class="w-32 h-32 -mt-3 shrink-0" @click="show = false">
+            <img
+              @click="visible = false"
+              class="w-full h-full object-cover"
+              src="~/assets/img/note-create/close.svg"
+              alt=""
+            />
+          </div>
+        </div>
+      </template>
+
+      <!-- <div class="w-full px-16 py-20" >
+        <van-row :gutter="[12, 16]">
+          <template v-for="(subItem, subIndex) in groupTypeList" :key="subIndex">
+            <van-col @click="handleTypeClick(subItem)" span="12" class="h-40 mb-16">
+              <div
+                :class="` relative ${showIndex == subItem.id ? ' border-[#FF9300] border-2  shadow-[0_4px_4px_0px_rgba(0,0,0,0.1)]' : ''} h-40 pl-22 flex justify-start items-center shrink-0 text-xl text-black-6 font-semibold bg-[#F7F8FA] rounded-md`"
+              >
+                <div class="w-24 h-24 border shrink-0 mr-6">
+                  <img class="w-full h-full object-cover" :src="city" alt="" />
+                </div>
+                {{ subItem.typeName }}
+
+                <div
+                  v-if="showIndex == subItem.id"
+                  class="absolute rounded-t-md square1 -top-2 left-0 z-1"
+                ></div>
+                <div v-if="showIndex == subItem.id" class="w-14 h-14 absolute top-2 left-2 z-2">
+                  <img
+                    class="w-full h-full object-cover"
+                    alt=""
+                    src="~/assets/img/chat/check.svg"
+                  />
+                </div>
+              </div>
+            </van-col>
+
+            <div
+              v-if="showIndex == subItem.id"
+              class="w-full relative flex justify-start box-border flex-wrap pl-21 mb-18 pt-14 bg-[#F7F7F7] rounded-md"
+            >
+              <div
+                :class="`w-32 h-8 absolute -top-[8px] ${subIndex % 2 != 0 ? 'right-[23px]' : 'left-[23px]'} `"
+              >
+                <img class="w-full h-full" src="~/assets/img/chat/polygon.svg" alt="" />
+              </div>
+              <template v-for="(el, index2) in subItem.children" :key="`ss${index2}`">
+                <div
+                  @click="childrenHandleTypeClick(el)"
+                  :class="`${childrenIndex == el.id ? 'text-[#FF9300] border-[#FF9300]  border-[2px] bg-[#FF9300]/[0.08]' : 'bg-white border text-black-6'} py-5 mr-8 mb-12 rounded-[4px] text-sm  box-border px-12`"
+                >
+                  {{ el.typeName }}
+                </div>
+              </template>
+            </div>
+          </template>
+        </van-row>
+      </div> -->
+
+      <div class="w-full px-16 py-20 card-list">
+        <template v-for="(subItem, subIndex) in groupTypeList" :key="subItem?.id">
+          <div
+            :class="` h-40 mb-4  relative ${showIndex == subItem.id ? ' border-[#FF9300] border-2  shadow-[0_4px_4px_0px_rgba(0,0,0,0.1)]' : ''}  pl-22 flex justify-start items-center shrink-0 text-xl text-black-6 font-semibold bg-[#F7F8FA] rounded-md`"
+            @click="handleTypeClick(subItem)"
+          >
+            <div class="w-24 h-24 border shrink-0 mr-6">
+              <img
+                class="w-full h-full object-cover"
+                :src="item?.typeIcon ? item?.typeIcon : city"
+                alt=""
+              />
+            </div>
+            <span class="line-clamp-1">
+              {{ subItem.typeName }}
+            </span>
+
+            <div
+              v-if="showIndex == subItem.id"
+              class="absolute rounded-t-md square1 -top-2 -left-2 z-1"
+            ></div>
+            <div v-if="showIndex == subItem.id" class="w-14 h-14 absolute top-0 left-0 z-2">
+              <img class="w-full h-full object-cover" alt="" src="~/assets/img/chat/check.svg" />
+            </div>
+          </div>
+
+          <div
+            v-if="showIndex == subItem.id && subItem?.children.length > 0"
+            class="item__child mb-4 w-full relative flex justify-start box-border flex-wrap pl-21 pt-14 bg-[#F7F7F7] rounded-md"
+          >
+            <div
+              :class="`w-32 h-8 absolute -top-[8px] ${subIndex % 2 != 0 ? 'right-[23px]' : 'left-[23px]'} `"
+            >
+              <img class="w-full h-full" src="~/assets/img/chat/polygon.svg" alt="" />
+            </div>
+            <template v-for="el in subItem.children" :key="el?.id">
+              <div
+                @click="childrenHandleTypeClick(el)"
+                :class="`${childrenIndex == el.id ? 'text-[#FF9300] border-[#FF9300]  border-[2px] bg-[#FF9300]/[0.08]' : 'bg-white border text-black-6'} py-5 mr-8 mb-12 rounded-[4px] text-sm  box-border px-12`"
+              >
+                {{ el.typeName }}
+              </div>
+            </template>
+          </div>
+        </template>
+      </div>
+
+      <template #footer>
+        <div class="w-full px-40 pb-30">
+          <van-button
+            style="font-size: 16px"
+            type="primary"
+            color="#FF9300"
+            round
+            block
+            @click="show = false"
+          >
+            确认
+          </van-button>
+        </div>
+      </template>
+    </van-dialog>
+
+    <div class="w-full h-72 px-16 pb-24 fixed bottom-0 left-0 box-border">
+      <van-button
+        style="font-size: 16px"
+        type="primary"
+        native-type="submit"
+        color="#FF9300"
+        round
+        block
+        @click="handleCreateGroup"
+        :loading="isSubmiting"
+      >
+        立即创建
+      </van-button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import city from '~/assets/img/chat/city-one.svg'
+
+const userInfoStore = useUserInfoStore()
+const { userInfo } = storeToRefs(userInfoStore)
+
+const TYPE_TEXT = '未选择'
+
+const groupTypeList = ref([])
+
+const subTypeList = ref([])
+
+definePageMeta({
+  layout: false
+})
+
+const formData = reactive({
+  createType: '1',
+  groupName: '',
+  creatUserId: computed(() => userInfo.value.userId),
+  description: '',
+  belongTypeId: ''
+})
+
+// 是否群类型的弹窗打开
+const show = ref(false)
+const showIndex = ref(null)
+const groupTypeName = ref(TYPE_TEXT)
+
+const childrenIndex = ref(null)
+
+// 是否个人主页展示
+const checked = ref(true)
+
+// 获取群类型
+async function getTreeType() {
+  try {
+    const { data } = await request('/website/tourGroupType/treeType')
+    if (Array.isArray(data) && data.length) {
+      groupTypeList.value = data
+    } else {
+      groupTypeList.value = []
+    }
+  } finally {
+  }
+}
+
+//
+const handleTypeClick = (item) => {
+  if (showIndex.value == item?.id) {
+    showIndex.value = null
+    subTypeList.value = []
+  } else {
+    showIndex.value = item.id
+    subTypeList.value = item.children
+  }
+}
+
+//
+const childrenHandleTypeClick = (item) => {
+  if (childrenIndex.value == item?.id) {
+    formData.belongTypeId = ''
+    childrenIndex.value = null
+
+    groupTypeName.value = TYPE_TEXT
+  } else {
+    formData.belongTypeId = item.id
+    childrenIndex.value = item.id
+    groupTypeName.value = item.typeName
+  }
+}
+
+const isSubmiting = ref(false)
+// 创建群聊
+async function handleCreateGroup() {
+  try {
+    isSubmiting.value = true
+
+    if (!formData.groupName) {
+      showToast('请输入群名称')
+      return
+    }
+
+    if (!formData.description) {
+      showToast('请输入群描述')
+      return
+    }
+    checked ? (formData.isPublic = 1) : (formData.isPublic = 0)
+    const { data } = await request('/website/tourGroup/createGroup', {
+      method: 'post',
+      body: {
+        ...formData
+      }
+    })
+
+    if (data) {
+      showSuccessToast('群聊创建成功')
+
+      // navigateTo({
+      //   path: '/profile/my-news',
+
+      //   replace: true
+      // })
+
+      navigateTo({
+        path: '/chat/group',
+        query: {
+          group: data
+        },
+        replace: true
+      })
+    }
+  } finally {
+    isSubmiting.value = false
+  }
+}
+
+onMounted(() => {
+  getTreeType()
+})
+
+useSeoMeta({
+  title: '我的消息'
+})
+</script>
+
+<style lang="scss" scoped>
+::v-deep .van-dialog__header {
+  padding-top: 0;
+}
+
+.card-list {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  grid-auto-flow: dense; /* 确保项目会填充所有的网格单元 */
+  grid-gap: 12px;
+
+  .item__child {
+    grid-column: span 2;
+  }
+
+  .square1 {
+    width: 0;
+    height: 0;
+    border-bottom: 28px solid transparent; /* 创建三角形 */
+    border-left: 28px solid #ff9300; /* 三角形的颜色 */
+  }
+}
+</style>

+ 169 - 0
src/pages/chat/examine.vue

@@ -0,0 +1,169 @@
+<template>
+  <div>
+    <ChatHeaderBar title="管理员审核成员"></ChatHeaderBar>
+    <ChatSearch placeholder="请输入关键词" v-model:searchString="searchString" @search="search" />
+
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+      <ChatEmpty v-if="!list?.length && !loading" image="search" title="暂无数据" />
+      <van-list
+        v-else-if="addDataList.length"
+        v-model:loading="loading"
+        error-text="获取失败"
+        finished-text="-- 没有更多了 --"
+        :finished="finished"
+        :immediate-check="false"
+        @load="getLoadList"
+      >
+        <van-checkbox-group v-model="checked">
+          <template v-for="(item, index) in addDataList" :key="item?.id">
+            <van-cell
+              v-for="(item, index) in addDataList"
+              :key="item?.id"
+              center
+              clickable
+              @click="toggle(index)"
+              disabled
+            >
+              <template #icon>
+                <div class="flex justify-start">
+                  <van-checkbox
+                    checked-color="#FD9A00"
+                    :name="item.id"
+                    :ref="(el) => (checkboxRefs[index] = el)"
+                    @click.stop
+                  />
+
+                  <div class="w-40 h-40 ml-13 mr-12 rounded-full overflow-hidden">
+                    <img class="w-full h-full shrink-0 object-cover" :src="item?.img" alt="" />
+                  </div>
+                </div>
+              </template>
+              <template #title>
+                <div class="flex items-center">
+                  <h1 class="text-xl text-black-3">{{ item?.visaTitle }}</h1>
+                </div>
+              </template>
+            </van-cell>
+          </template>
+        </van-checkbox-group>
+      </van-list>
+    </van-pull-refresh>
+
+    <div class="fixed bottom-0 left-0 w-full flex justify-between items-center p-16 pb-40 bg-white">
+      <van-button
+        @click="handlePass"
+        size="large"
+        style="color: #ff9300; margin-right: 8px"
+        class="font-semibold w-[48%]"
+        round
+        color="#FEF4E6"
+      >
+        取消
+      </van-button>
+      <van-button
+        @click="handleCancel"
+        size="large"
+        class="font-semibold w-[48%]"
+        round
+        color="#FF9300"
+      >
+        通过
+      </van-button>
+    </div>
+  </div>
+</template>
+<script setup>
+definePageMeta({
+  layout: false
+})
+
+useSeoMeta({
+  title: '管理员审核成员'
+})
+
+const refreshing = ref(false)
+const loading = ref(false)
+const finished = ref(false)
+const searchString = ref('')
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  searchString: ''
+})
+
+const checked = ref([])
+onMounted(() => {
+  getList()
+})
+
+const addDataList = ref([])
+
+const toggle = (index) => {
+  checkboxRefs.value[index].toggle()
+}
+
+const search = () => {
+  addDataList.value = []
+  queryParams.pageNum = 1
+  queryParams.searchString = searchString.value
+  getList()
+}
+
+const onRefresh = () => {
+  queryParams.pageNum = 1
+  addDataList.value = []
+  getList()
+}
+
+// 触底加载
+const getLoadList = () => {
+  queryParams.pageNum++
+  finished.value = true
+  getList()
+}
+
+// 获取数据
+const getList = async () => {
+  try {
+    let url = `/website/tourism/visa/list/page`
+
+    loading.value = true
+    let {
+      data: { dataList, totalCount }
+    } = await request(url, {
+      query: {
+        ...queryParams
+      }
+    })
+
+    if (Array.isArray(dataList) && dataList?.length) {
+      addDataList.value = addDataList.value.concat(dataList)
+    } else {
+      addDataList.value = []
+    }
+
+    loading.value = false
+    refreshing.value = false
+    if (addDataList.value.length >= totalCount) {
+      finished.value = true
+    } else {
+      finished.value = false
+    }
+  } catch (err) {
+  } finally {
+    refreshing.value = false
+    loading.value = false
+  }
+}
+
+// 通过成员
+function handlePass() {}
+// 取消
+function handleCancel() {
+  navigateTo('/chat/group', {
+    replace: true
+  })
+}
+</script>
+<style lang="scss" scoped></style>

+ 7 - 0
src/pages/chat/find-chat-history.vue

@@ -0,0 +1,7 @@
+<template>
+  <div></div>
+</template>
+
+<script setup></script>
+
+<style lang="scss" scoped></style>

+ 187 - 0
src/pages/chat/group-add.vue

@@ -0,0 +1,187 @@
+<template>
+  <div class="w-full h-[100vh]">
+    <ChatHeaderBar title="添加成员" />
+
+    <ChatSearch v-model:searchString="searchString" @search="search" placeholder="请输入关键词" />
+
+    <div class="h-118"></div>
+
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+      <Empty v-if="!addDataList?.length && !loading" title="暂无数据" top="100" />
+      <van-list
+        v-else-if="addDataList.length"
+        v-model:loading="loading"
+        error-text="获取失败"
+        finished-text="-- 没有更多了 --"
+        :finished="finished"
+        :immediate-check="false"
+        @load="getLoadList"
+      >
+        <van-checkbox-group v-model="checked">
+          <van-index-bar highlight-color="#FD9A00" index-list :sticky="false">
+            <template v-for="(item, index) in addDataList" :key="item?.id">
+              <van-index-anchor index="A" />
+              <van-cell
+                v-for="(item, index) in addDataList"
+                :key="item?.id"
+                center
+                clickable
+                @click="toggle(index)"
+                disabled
+              >
+                <template #icon>
+                  <div class="flex justify-start">
+                    <van-checkbox
+                      checked-color="#FD9A00"
+                      :name="item.id"
+                      :ref="(el) => (checkboxRefs[index] = el)"
+                      @click.stop
+                    />
+
+                    <div class="w-40 h-40 ml-13 mr-12 rounded-full overflow-hidden">
+                      <img class="w-full h-full shrink-0 object-cover" :src="item?.img" alt="" />
+                    </div>
+                  </div>
+                </template>
+                <template #title>
+                  <div class="flex items-center">
+                    <h1 class="text-xl text-black-3">{{ item?.visaTitle }}</h1>
+                    <van-tag style="margin-left: 5px" color="#F7F8FA" text-color="#666666">
+                      相互关注
+                    </van-tag>
+                  </div>
+                </template>
+              </van-cell>
+            </template>
+          </van-index-bar>
+        </van-checkbox-group>
+      </van-list>
+    </van-pull-refresh>
+  </div>
+</template>
+<script setup>
+import { pinyin } from 'pinyin-pro'
+
+definePageMeta({
+  layout: false
+})
+
+onMounted(() => {
+  getList()
+})
+
+const refreshing = ref(false)
+const loading = ref(false)
+const finished = ref(false)
+
+const checked = ref([])
+const searchString = ref('')
+const checkboxRefs = ref([])
+
+// 字母的数组
+const letterList = ref([])
+
+const toggle = (index) => {
+  checkboxRefs.value[index].toggle()
+}
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  searchString: ''
+})
+
+const addDataList = ref([])
+
+useSeoMeta({
+  title: '添加成员'
+})
+
+const search = () => {
+  addDataList.value = []
+  queryParams.pageNum = 1
+  queryParams.searchString = searchString.value
+  getList()
+}
+
+// 刷新
+const onRefresh = () => {
+  queryParams.pageNum = 1
+  addDataList.value = []
+  getList()
+}
+
+// 触底加载
+const getLoadList = () => {
+  queryParams.pageNum++
+  finished.value = true
+  getList()
+}
+
+// 获取数据
+const getList = async () => {
+  try {
+    let url = `/website/tourism/visa/list/page`
+
+    loading.value = true
+    let {
+      data: { dataList, totalCount }
+    } = await request(url, {
+      query: {
+        ...queryParams
+      }
+    })
+    let arr = []
+    if (Array.isArray(dataList) && dataList?.length) {
+      addDataList.value = addDataList.value.concat(dataList)
+      arr = addDataList.value
+      arr = arr.map((item, index) => {
+        let letter = pinyin(item?.visaTitle, { pattern: 'first', toneType: 'none' })
+        console.log(letter, '123')
+
+        item.letter = letter
+        return item
+      })
+      console.log(arr, 'addDataList.value .value')
+    } else {
+      addDataList.value = []
+    }
+
+    loading.value = false
+    refreshing.value = false
+    if (addDataList.value.length >= totalCount) {
+      finished.value = true
+    } else {
+      finished.value = false
+    }
+  } catch (err) {
+  } finally {
+    refreshing.value = false
+    loading.value = false
+  }
+}
+
+// // 数据转化
+// const changeInitials = (list) => {
+//   console.log(list)
+
+//   let letter = []
+//   let listArr = []
+// listArr = list.map((item, index) => {
+//   item.letter = pinyin(item?.visaTitle, { pattern: 'first', toneType: 'none' })
+//     .map((pinyin) => pinyin[0].toUpperCase()) // 获取拼音的首字母并转换为大写
+//     .join('')
+
+//     letter.push(item.letter)
+//     return item
+//   })
+//   console.log(list, '21')
+//   console.log(listArr, '444')
+
+//   return {
+//     letter,
+//     listArr
+//   }
+// }
+</script>
+<style lang="scss" scoped></style>

+ 129 - 0
src/pages/chat/group-member.vue

@@ -0,0 +1,129 @@
+<template>
+  <div class="w-full h-[100vh]">
+    <ChatHeader title="群成员" />
+    <ChatSearch v-model:searchString="searchString" @search="search" />
+
+    <Empty
+      v-if="!groupMember?.length && !loading"
+      :title="`没有找到&quot;${searchString}&quot;相关成员`"
+    />
+
+    <div
+      v-if="groupMember?.length && !searchString"
+      class="box-border w-full min-h-400 mt-16 mb-12 pt-12 pl-12"
+    >
+      <van-row>
+        <van-col
+          style="width: 54px"
+          v-for="(item, index) in groupMember"
+          :key="index"
+          span="4"
+          class="mb-12 mr-10"
+        >
+          <div class="w-40 h-40 rounded-full mx-auto overflow-hidden mb-4">
+            <img class="w-full h-full object-cover" :src="item?.avatar" alt="" />
+          </div>
+          <p class="w-full line-clamp-1 lin text-sm text-center text-black-6">{{ item?.name }}</p>
+        </van-col>
+        <van-col span="4" class="mb-12 mr-10" @click="navigateTo('/chat/group-add')">
+          <div
+            class="w-40 h-40 rounded-full flex justify-center items-center bg-[#F3F3F3] border mx-auto overflow-hidden mb-4"
+          >
+            <van-icon name="plus" size="20" />
+          </div>
+          <p class="w-full line-clamp-1 lin text-sm text-center text-black-6">添加成员</p>
+        </van-col>
+        <van-col v-if="isRankAndFiler(setData?.groupRole)" span="4 mb-12 mr-10">
+          <div
+            class="w-40 h-40 rounded-full flex justify-center items-center bg-[#F3F3F3] border mx-auto overflow-hidden mb-4"
+          >
+            <van-icon name="minus" size="20" />
+          </div>
+          <p class="w-full line-clamp-1 lin text-sm text-center text-black-6">删除成员</p>
+        </van-col>
+      </van-row>
+    </div>
+    <!-- 搜索后的结果 -->
+    <div v-if="searchMember?.length && searchString" class="w-full border-box min-h-400 pl-16">
+      <van-cell
+        v-for="(item, index) in searchMember"
+        :key="item?.id"
+        center
+        clickable
+        size="large"
+        class="border-b-[1px]"
+        :title="item?.nickname"
+      >
+        <template #icon>
+          <div class="w-40 h-40 rounded-full mr-12 overflow-hidden">
+            <img class="w-full h-full object-cover" :src="item?.avatar" alt="" />
+          </div>
+        </template>
+      </van-cell>
+    </div>
+  </div>
+</template>
+<script setup>
+const route = useRoute()
+const queryParams = reactive({
+  groupRole: computed(() => route.query?.groupRole ?? '')
+})
+
+const loading = ref(false)
+const searchString = ref('')
+
+onMounted(() => {
+  getList()
+})
+
+// 全部成员
+const groupMember = ref([])
+
+// 搜索的成员
+const searchMember = ref([])
+
+const setData = ref(null)
+
+// 搜索
+const search = () => {
+  getList()
+}
+
+// 获取群设置的配置信息
+const getList = async () => {
+  try {
+    loading.value = true
+    let {
+      data: { dataList, totalCount }
+    } = await request('/website/tourism/visa/list/page')
+    // // query: {
+    //   groupId: '0'
+    // }
+
+    if (Array.isArray(dataList) && dataList?.length) {
+      searchString.value ? (searchMember.value = dataList) : (groupMember.value = dataList)
+    } else {
+      searchString.value ? (searchMember.value = []) : (groupMember.value = [])
+    }
+
+    loading.value = false
+  } catch (err) {
+  } finally {
+    loading.value = false
+  }
+}
+
+// 是否是普通成员
+const isRankAndFiler = (role) => {
+  return role == 1 || role == 2 ? true : false
+}
+
+definePageMeta({
+  layout: false
+})
+
+useSeoMeta({
+  title: '群成员'
+})
+</script>
+<style lang="scss" scoped></style>

+ 313 - 0
src/pages/chat/group-square.vue

@@ -0,0 +1,313 @@
+<template>
+  <div class="w-full h-full">
+    <ChatHeaderBar title="群聊广场" />
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+      <div
+        class="flex h-40 mt-62 mb-12 w-[92%] mx-auto items-center justify-between rounded-full border bg-[#fff]"
+      >
+        <input
+          type="text"
+          v-model="groupName"
+          class="ml-5 w-[80%] h-full pl-10 text-[13px]"
+          placeholder="搜索你想找的群聊"
+          @keydown.enter=""
+          style="outline: none; background: none"
+        />
+
+        <button
+          @click="search"
+          class="h-full border-l-[1px] flex justify-center items-center w-66 shrink-0 rounded-r-full"
+        >
+          <div style="color: white" class="w-16 h-16 shrink-0">
+            <img class="w-full h-full object-cover" src="~/assets/img/visa/search.svg" alt="" />
+          </div>
+        </button>
+      </div>
+
+      <van-tabs
+        class="w-full pl-0"
+        title-active-color="#FF9300"
+        title-inactive-color="#333333"
+        v-model:active="active"
+        @click-tab="onClickTab"
+        style="--van-tabs-bottom-bar-color: #ff9300; --van-tabs-bottom-bar-width: 16px"
+        swipeable
+      >
+        <van-tab
+          class="border-t-[1px]"
+          v-for="(item, index) in 10"
+          :key="index"
+          :title="`标签 ${item}`"
+        >
+          <div class="w-full h-[90%] pl-16 box-border">
+            <Empty v-if="!groupSquareList?.length && !loading" title="暂无群聊" top="100" />
+            <van-list
+              v-else-if="groupSquareList.length"
+              v-model:loading="loading"
+              error-text="获取失败"
+              finished-text=""
+              :finished="finished"
+              :immediate-check="false"
+              @load="getLoadList"
+            >
+              <van-cell
+                v-for="(item, index) in groupSquareList"
+                :key="item.id"
+                style="--van-cell-horizontal-padding: 0; padding: 16px 16px 16px 0"
+                center
+                class="border-b-[1px] py-16 pl-0"
+              >
+                <template #icon>
+                  <div class="h-48 w-48 border rounded-full mr-12">头像</div>
+                </template>
+                <template #title>
+                  <div class="flex justify-start font-semibold text-xl text-black-3 mb-4">
+                    <h1 class="shrink-0 max-w-180 line-clamp-1">
+                      {{ item.title }}
+                    </h1>
+                    <span class="shrink-0">({{ item.number }})</span>
+                  </div>
+
+                  <van-tag
+                    style="--van-tag-padding: 4px; --van-tag-radius: 6px"
+                    color="#FEF4E6"
+                    text-color="#FF9300"
+                    type="primary"
+                  >
+                    {{ item.tag }}
+                  </van-tag>
+                </template>
+                <template #label>
+                  <p class="w-212 line-clamp-1 text-black-6 text-base mt-5">
+                    {{ item.label }}
+                  </p>
+                </template>
+                <template #value>
+                  <van-button
+                    v-if="item.state == 0"
+                    size="small"
+                    color="#fa8446"
+                    round
+                    class="w-60 text-base font-semibold"
+                    plain
+                    style="--van-button-default-padding: 0"
+                    @click="handleJoinGroup(item)"
+                  >
+                    加入
+                  </van-button>
+                  <van-button
+                    v-if="item.state == 1"
+                    size="small"
+                    color="#fa8446"
+                    round
+                    class="w-60 text-base font-semibold"
+                    plain
+                    style="--van-button-default-padding: 0"
+                    @click="handleJoinGroup(item)"
+                  >
+                    去聊天
+                  </van-button>
+                  <van-button
+                    v-if="item.state == 2"
+                    size="small"
+                    color="#999999"
+                    round
+                    class="w-60 text-base font-semibold"
+                    plain
+                    style="--van-button-default-padding: 0"
+                    @click="handleJoinGroup(item)"
+                  >
+                    已申请
+                  </van-button>
+                </template>
+              </van-cell>
+            </van-list>
+          </div>
+        </van-tab>
+      </van-tabs>
+      <!-- </div> -->
+    </van-pull-refresh>
+
+    <div class="fixed w-full p-16 pb-40 bottom-0 left-0 bg-white">
+      <van-button
+        size="large"
+        style="background: #fa8446; margin-top: 30px"
+        color="#fff"
+        round
+        block
+        icon="plus"
+        class="w-full"
+        @click="navigateTo('/chat/create-group')"
+      >
+        创建群聊
+      </van-button>
+    </div>
+  </div>
+</template>
+
+<script setup>
+definePageMeta({
+  layout: false
+})
+
+onMounted(() => {
+  getTabList()
+  getList()
+})
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  groupName: '',
+  groupTypeId: ''
+})
+
+const groupName = ref('')
+const refreshing = ref(false)
+const loading = ref(false)
+const finished = ref(false)
+
+const groupSquareList = ref([])
+const squareTabList = ref([])
+
+const active = ref(0)
+
+// 获取切换的数据
+const onClickTab = ({ title }) => {
+  showToast(title)
+  active.value = squareTabList.value.find((item = item.name == title)).id
+}
+
+// 加入群聊
+const handleJoinGroup = (item) => {
+  if (item.state == 0) {
+    navigateTo('/chat/group', {
+      query: { groupId: item.id },
+      replace: true
+    })
+  }
+  if (item.state == 1) {
+    navigateTo('/chat/group', {
+      query: { groupId: item.id },
+      replace: true
+    })
+  }
+  if (item.state == 2) {
+    navigateTo('/chat/group', {
+      query: { groupId: item.id },
+      replace: true
+    })
+  }
+}
+
+// 获取群聊列表
+function getLoadList() {
+  queryParams.pageNum++
+  finished.value = true
+  getList()
+}
+
+const getTabList = async () => {
+  try {
+    let url = `/website/tourGroup/getGroupTypeFirst`
+
+    const {
+      data: { dataList, totalCount }
+    } = await request(url)
+
+    if (Array.isArray(dataList) && dataList?.length) {
+      active.value = dataList[0].id
+      squareTabList.value = dataList
+    } else {
+      groupSquareList.value = []
+    }
+  } catch (err) {
+  } finally {
+  }
+}
+
+// 获取数据
+const getList = async () => {
+  try {
+    let url = `/website/tourism/visa/list/page`
+    loading.value = true
+    let {
+      data: { dataList, totalCount }
+    } = await request(url, {
+      query: {
+        ...queryParams
+      }
+    })
+
+    if (Array.isArray(dataList) && dataList?.length) {
+      // groupSquareList.value = groupSquareList.value.concat(dataList)
+      groupSquareList.value = [
+        {
+          id: 'square-1',
+          title: '分享家装心得',
+          label: '分享装修经验',
+          tag: '兴趣交流',
+          state: 0,
+          number: 71
+        },
+        {
+          id: 'square-1',
+          title: '分享家装心得',
+          label: '分享装修经验',
+          tag: '兴趣交流',
+          state: 2,
+          number: 340
+        },
+        {
+          id: 'square-1',
+          title: '分享家装心得',
+          label: '分享装修经验',
+          tag: '兴趣交流',
+          state: 1,
+          number: 71
+        }
+      ]
+    } else {
+      groupSquareList.value = []
+    }
+
+    loading.value = false
+    refreshing.value = false
+    if (groupSquareList.value.length >= totalCount) {
+      finished.value = true
+    } else {
+      finished.value = false
+    }
+  } catch (err) {
+  } finally {
+    refreshing.value = false
+    loading.value = false
+  }
+}
+
+// 搜索
+const search = () => {
+  groupSquareList.value = []
+  queryParams.pageNum = 1
+  queryParams.groupName = groupName.value
+  getList()
+}
+
+// 下拉刷新
+const onRefresh = () => {
+  refreshing.value = true
+  queryParams.pageNum = 1
+  groupSquareList.value = []
+  getList()
+}
+
+useSeoMeta({
+  title: '群聊广场'
+})
+</script>
+
+<style lang="scss" scoped>
+::v-deep .van-tabs__nav--line.van-tabs__nav--complete {
+  padding-left: 5px;
+}
+</style>

+ 102 - 0
src/pages/chat/group.vue

@@ -0,0 +1,102 @@
+<template>
+  <div>
+    <!--  left-text="" title="群聊" -->
+    <van-nav-bar fixed @click-left="onClickLeft" @click-right="onClickRight">
+      <template #left>
+        <div>
+          <van-icon name="arrow-left" color="black" size="18" />
+        </div>
+      </template>
+      <template #right>
+        <van-icon name="ellipsis" color="black" size="18" />
+      </template>
+    </van-nav-bar>
+    <div class="w-full fixed top-48 left-0">
+      <van-notice-bar
+        left-icon="volume-o"
+        mode="link"
+        text="无论我们能活多久,我们能够享受的只有无法分割的此刻,此外别无其他。"
+      ></van-notice-bar>
+    </div>
+
+    <van-pull-refresh v-model="loading" @refresh="onRefresh">
+      <div style="height: calc(100vh - 50px)" class="w-full px-12 pt-60 border bg-[#F3F3F3]">
+        <ChatItem></ChatItem>
+        <!-- <div style="width: calc(100vw - 74px)" class="flex justify-start items-start">
+          <div class="shrink-0 w-40 h-40 border rounded-[4px]">
+            <img class="w-full h-full" src="" alt="" />
+          </div>
+
+          <div class="w-0 h-0 mt-10 border-[8px] border-transparent border-r-white"></div>
+          <div
+            class="max-w-full break-words overflow-auto rounded-[4px] relative bg-white box-border text-base p-5 text-wrap"
+            v-html="`ajshdgahsddasdasdasdgjasgdjhgdhajdadasdhjagdhjagsjhasgjdasgjhagsjagshd`"
+          ></div>
+        </div> -->
+      </div>
+    </van-pull-refresh>
+
+    <!-- <ProfileNewsChatInput></ProfileNewsChatInput> -->
+  </div>
+</template>
+<script setup>
+const route = useRoute()
+const router = useRouter()
+
+definePageMeta({
+  layout: false
+})
+
+useSeoMeta({
+  title: '群聊'
+})
+
+// 刷新次数
+const count = ref(0)
+const loading = ref(false)
+const idInfo = reactive({
+  groupId: computed(() => route.query.groupId ?? ''),
+  userId: computed(() => route.query.userId ?? '')
+})
+const messageData = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  messageId: '',
+  searchMessage: ''
+})
+
+// 刷新
+const onRefresh = () => {
+  setTimeout(() => {
+    showToast('刷新成功')
+    loading.value = false
+    count.value++
+  }, 1000)
+}
+
+const onClickLeft = () => router.back()
+
+const onClickRight = () => {
+  navigateTo({
+    path: '/chat/set',
+    query: idInfo
+  })
+}
+
+// 获取群聊消息
+async function getListMessage() {
+  let { data } = await request('/website/tourMessage/getMessageByGroupId', {})
+}
+
+//
+// 用户删除消息
+async function userDelMessage(params) {
+  let { data } = await request('/website/tourMessage/delMessage', {
+    method: 'post',
+    body: {}
+  })
+}
+
+onMounted(() => {})
+</script>
+<style lang="scss" scoped></style>

+ 194 - 0
src/pages/chat/interrelation-friend.vue

@@ -0,0 +1,194 @@
+<template>
+  <div class="w-full h-[100vh]">
+    <ChatHeaderBar title="选择互关好友" />
+
+    <ChatSearch v-model:searchString="searchString" @search="search" placeholder="请输入关键词" />
+
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+      <ChatEmpty v-if="!friendList?.length && !loading" title="请输入关键词,没有找到相关结果" />
+      <van-list
+        v-else-if="friendList.length"
+        v-model:loading="loading"
+        error-text="获取失败"
+        finished-text="-- 没有更多了 --"
+        :finished="finished"
+        :immediate-check="false"
+        @load="getLoadList"
+      >
+        <van-checkbox-group v-model="checked">
+          <!-- <van-index-bar highlight-color="#FD9A00" index-list :sticky="false"> -->
+          <template v-for="(item, index) in friendList" :key="item?.id">
+            <!-- <van-index-anchor index="A" /> -->
+            <van-cell
+              v-for="(item, index) in friendList"
+              :key="item?.id"
+              center
+              size="large"
+              clickable
+              @click="toggle(index)"
+              disabled
+            >
+              <template #icon>
+                <div class="flex justify-start">
+                  <van-checkbox
+                    checked-color="#FD9A00"
+                    :name="item.id"
+                    :ref="(el) => (checkboxRefs[index] = el)"
+                    @click.stop
+                  />
+
+                  <div class="w-40 h-40 ml-13 mr-12 rounded-full overflow-hidden">
+                    <img class="w-full h-full shrink-0 object-cover" :src="item?.img" alt="" />
+                  </div>
+                </div>
+              </template>
+              <template #title>
+                <div class="flex items-center">
+                  <h1 class="text-xl text-black-3 line-clamp-1">{{ item?.visaTitle }}</h1>
+                </div>
+              </template>
+            </van-cell>
+          </template>
+          <!-- </van-index-bar> -->
+        </van-checkbox-group>
+      </van-list>
+      <!-- <div class="relative">
+        <van-cell center size="large" clickable title="123546" disabled></van-cell>
+        <div class="w-full h-46 bg-white/[0.3] absolute top-0 left-0"></div>
+      </div> -->
+    </van-pull-refresh>
+
+    <div
+      class="w-full box-border p-16 pb-40 bg-white fixed bottom-0 left-0 flex justify-between items-center shadow-[0px_-4px_4px_0px_rgba(0,0,0,0.1)]"
+    >
+      <div class="shrink-0 flex justify-start items-center">
+        <div
+          v-for="(item, index) in 5"
+          :key="index + 'avatar'"
+          :class="`w-36 h-36  ${index == 0 ? '' : '-ml-16'} shrink-0 rounded-full overflow-hidden`"
+        >
+          <img class="w-full h-full object-cover" src="../../assets/img/chat/search.svg" alt="" />
+        </div>
+
+        <div v-if="checked.length > 5" class="shrink-0 w-24 h-24 ml-8">
+          <img class="w-full h-full object-cover" src="~/assets/img/chat/ellipsis.svg" alt="" />
+        </div>
+      </div>
+      <van-button
+        @click="handleCreateGroup"
+        style="width: 160px"
+        class="shrink-0"
+        block
+        size="large"
+        color="#FD9A00"
+        round
+      >
+        新建
+        <span v-if="checked.length">(16{{ checked.length }})</span>
+      </van-button>
+    </div>
+  </div>
+</template>
+<script setup>
+const userInfoStore = useUserInfoStore()
+const { userInfo } = storeToRefs(userInfoStore)
+
+definePageMeta({
+  layout: false
+})
+
+useSeoMeta({
+  title: '我的消息'
+})
+
+const refreshing = ref(false)
+const loading = ref(false)
+const finished = ref(false)
+
+const checked = ref([])
+const searchString = ref('')
+const checkboxRefs = ref([])
+
+const friendList = ref([])
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  searchString: ''
+})
+
+const toggle = (index) => {
+  checkboxRefs.value[index].toggle()
+}
+
+const search = () => {
+  friendList.value = []
+  queryParams.pageNum = 1
+  queryParams.searchString = searchString.value
+  getList()
+}
+
+const onRefresh = () => {
+  queryParams.pageNum = 1
+  friendList.value = []
+  getList()
+}
+
+// 触底加载
+const getLoadList = () => {
+  queryParams.pageNum++
+  finished.value = true
+  getList()
+}
+
+// 获取互关好友列表
+const getList = () => {
+  try {
+    let {
+      data: { dataList }
+    } = request('/tourMember/memberLit', {
+      query: {
+        userId: userInfo?.value?.userInfo
+      }
+    })
+
+    if (Array.isArray(dataList) && dataList?.length) {
+      friendList.value = friendList.value.concat(dataList)
+    } else {
+      friendList.value = []
+    }
+  } catch (error) {}
+}
+
+// 创建多人聊天
+async function handleCreateGroup() {
+  try {
+    showLoadingToast({
+      message: '准备开始群聊...',
+      duration: 100000
+    })
+    let { data } = request('/tourGroup/createGroup', {
+      method: 'post',
+      body: {
+        createType: 2,
+        creatUserId: userInfo.value.userId
+      }
+    })
+    if (data) {
+      navigateTo('/chat/group', {
+        query: data?.groupId,
+        replace: true
+      })
+    }
+  } catch (error) {
+  } finally {
+    closeToast()
+  }
+}
+
+onMounted(() => {
+  getList()
+})
+</script>
+
+<style lang="scss" scoped></style>

+ 83 - 0
src/pages/chat/qr-code.vue

@@ -0,0 +1,83 @@
+<template>
+  <div class="w-full h-[100vh]">
+    <ChatHeaderBar title="群聊二维码" />
+    <div class="h-100"></div>
+
+    <!-- <div class="w-60 h-60 mx-auto border"></div> 
+    :list="queueParmars?.groupAvatar" 
+    -->
+    <ChatGroupAvatar></ChatGroupAvatar>
+
+    <h1 title="" class="w-300 mt-16 mb-18 text-center mx-auto text-xl text-black-3 font-semibold">
+      群聊:{{ queueParmars?.groupNickname }}
+    </h1>
+
+    <div class="relative mb-21 w-220 h-220 bg-white rounded-lg mx-auto box-border">
+      <img class="h-full w-full" src="~/assets/img/chat/qr-code-box.png" alt="" />
+      <img
+        class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 w-170 h-170 shrink-0 object-cover"
+        :src="qrCode"
+        alt=""
+      />
+    </div>
+
+    <p class="w-full text-center text-sm text-black-6">该二维码7天内有效,重新进入将更新</p>
+  </div>
+</template>
+
+<script setup>
+const route = useRoute()
+definePageMeta({
+  layout: false
+})
+
+onMounted(() => {
+  getQrCode()
+})
+
+// getGroupQR
+const QRURI = ref(`${import.meta.env.VITE_APP_BASE_URL}website/tourGroup/getGroupQR`)
+const qrCode = ref('')
+const queueParmars = reactive({
+  groupId: computed(() => route.query?.groupId ?? ''),
+  groupNickname: computed(() => route.query?.groupNickname ?? ''),
+  groupAvatar: computed(() => route.query?.groupAvatar ?? '')
+})
+
+useSeoMeta({
+  title: '群聊'
+})
+
+// 转换时间
+function convertTimeStamp(timestamp) {
+  // 将时间戳转换为日期对象
+  let date = new Date(timestamp)
+
+  // 增加7天
+  date.setDate(date.getDate() + 7)
+
+  // 获取新的月份和日期
+  let month = date.getMonth() + 1 // getMonth() 返回的月份是 0-11,所以需要加 1
+  let day = date.getDate() // 获取日期
+
+  // 格式化为 "m-d" 格式
+  let formattedDate = `${month}月${day < 10 ? '0' + day : day}日`
+  return formattedDate
+}
+
+function getQrCode() {
+  try {
+    showLoadingToast({
+      message: '加载中...',
+      duration: 100000
+    })
+    qrCode.value = QRURI.value + `?groupId=${queueParmars.groupId}&systemOs=1`
+
+    closeToast()
+  } catch (error) {
+  } finally {
+    closeToast()
+  }
+}
+</script>
+<style lang="scss" scoped></style>

+ 108 - 0
src/pages/chat/qr-results.vue

@@ -0,0 +1,108 @@
+<template>
+  <div class="w-full h-[100vh]">
+    <ChatHeaderBar title="扫码结果" />
+    <div class="h-250"></div>
+    <ChatGroupAvatar class="mx-auto"></ChatGroupAvatar>
+    <h1 class="w-220 text-center text-black-3 font-semibold text-xl mb-27 mt-16 mx-auto">
+      {{ itemData?.groupName }}
+    </h1>
+    <!-- @click="
+         navigateTo({
+           path: '/',
+           query: {
+             groupId: leaderId
+           }
+         })
+      " -->
+    <!-- <van-cell center is-link size="large" :title="itemData?.showName" value="查看群主主页">
+      <template #icon>
+        <div class="w-48 h-48 rounded-full overflow-hidden border mr-12">
+          <img
+            class="w-full h-full shrink-0 object-cover"
+            src="~/assets/img/chat/user.svg"
+            alt=""
+          />
+        </div>
+      </template>
+    </van-cell> -->
+    <!-- <van-cell
+      center
+      size="large"
+      title="群介绍"
+      label="44444444444444444444444444444444"
+    ></van-cell>
+
+    <van-cell
+      center
+      size="large"
+      label="为维护逍遥游良好社区氛围,请遵守《逍遥游社区规范》如群聊疑似违规,可点击举报群聊"
+    ></van-cell> -->
+
+    <div class="fixed bottom-0 left-0 w-full box-border pb-40 pt-16 bg-white px-16">
+      <van-button round type="primary" block color="#FF9300" @click="handleQrcode">
+        <template v-if="itemData?.bannedStatus == 1">立即聊天</template>
+      </van-button>
+    </div>
+  </div>
+</template>
+<script setup>
+const route = useRoute()
+
+const itemData = ref({})
+
+definePageMeta({
+  layout: false
+})
+
+useSeoMeta({
+  title: '我的消息'
+})
+
+onMounted(() => {
+  getGroupInfo()
+})
+
+async function getGroupInfo() {
+  try {
+    let { data } = await request('/website/tourGroup/getGroupInfoAndMemberByGroupId', {
+      query: {
+        groupId: route.query?.groupId
+      }
+    })
+
+    if (typeof data == 'object') {
+      itemData.value = data
+    }
+  } catch (error) {}
+}
+
+const handleQrcode = async () => {
+  try {
+    let { data } = await request('/website/tourGroup/isJoinGroup', {
+      query: {
+        groupId: itemData.value.id
+      }
+    })
+
+    if (data) {
+      navigateTo({
+        path: '/chat/group',
+        query: {
+          groupId: itemData.value.id
+        },
+        replace: true
+      })
+    } else {
+      showDialog({
+        width: 260,
+        title: '提示',
+        message: '该群聊异常,无法加入',
+        confirmButtonColor: '#FF9300'
+      }).then(() => {
+        // on close
+      })
+    }
+  } catch (error) {}
+}
+</script>
+<style scoped lang="scss"></style>

+ 325 - 0
src/pages/chat/report.vue

@@ -0,0 +1,325 @@
+<template>
+  <div class="w-full h-[100vh]">
+    <!--  @open="openReportDropdownMenuRef" -->
+    <van-dropdown-menu
+      id="reportDropdownMenuRef"
+      fixed
+      ref="reportDropdownMenuRef"
+      active-color="#FF9300"
+    >
+      <van-dropdown-item
+        ref="reportItemRef"
+        v-model="reportIndex"
+        @change="onreportFilterClose"
+        :options="
+          reportList.map((item) => ({
+            text: item?.typeName,
+            value: item?.id
+          }))
+        "
+      >
+        <template #title class="relative" active-color="#FF9300">
+          <div class="font-semibold text-2xl">
+            <div @click.stop="goBack" class="absolute top-0 -left-135">
+              <van-icon name="arrow-left" size="24" color="#000000" />
+            </div>
+            {{ title }}
+          </div>
+        </template>
+      </van-dropdown-item>
+    </van-dropdown-menu>
+
+    <van-form @submit="onSubmit">
+      <van-cell-group>
+        <van-field
+          :rules="[{ required: true, message: '请输入内容' }]"
+          size="large"
+          rows="2"
+          readonly
+          autosize
+          type="textarea"
+          v-model="form.elseTypeReason"
+          :placeholder="
+            form.elseTypeReason
+              ? form.elseTypeReason
+              : '对话中可能含有血腥、恐怖等内容,或者其他未提及的违规类型。'
+          "
+          label-align="top"
+        >
+          <template #label>
+            <div>举报描述:{{ title }}</div>
+          </template>
+        </van-field>
+        <van-field
+          :rules="[{ required: true, message: '请输入违规描述' }]"
+          required
+          size="large"
+          rows="6"
+          autosize
+          type="textarea"
+          v-model="form.description"
+          label="举报描述"
+          maxlength="200"
+          placeholder="请尽可能的描述存在的问题,如:让您感到不适的画面、或其他未提及的违规内容。"
+          label-align="top"
+        />
+
+        <van-field
+          id="image"
+          class="image"
+          style="background-color: white"
+          required
+          size="large"
+          name="uploader"
+          label="图片证据"
+          label-align="top"
+          :rules="[{ required: true, message: '请至少上传一张图片' }]"
+        >
+          <template #input>
+            <div class="w-full flex justify-start items-start flex-wrap">
+              <template v-if="form.image.length">
+                <div
+                  v-for="(item, index) in form.image"
+                  :key="`img${index}`"
+                  class="shrink-0 relative w-80 h-80 mr-7 rounded-xl overflow-hidden"
+                >
+                  <img class="w-full h-full object-cover" :src="item" alt="" />
+
+                  <div
+                    @click="deleteImage(index)"
+                    class="absolute z-10 top-0 right-0 w-20 rounded-bl-md h-20 bg-black/[0.4] flex justify-center items-center"
+                  >
+                    <span class="icon iconfont text-white" style="font-size: 16px">&#xe7fc;</span>
+                  </div>
+                </div>
+              </template>
+
+              <div
+                @click="handleChangeAvatar"
+                class="border shrink-0 w-80 h-80 rounded-xl bg-[#F3F3F3] flex justify-center flex-wrap items-center"
+              >
+                <div>
+                  <span class="iconfont icon-plus text-black/[0.4]" style="font-size: 18px"></span>
+                  <p class="leading-3xl py-0 w-full text-sm text-center text-black/[0.4]">
+                    {{ form.image?.length }}/3
+                  </p>
+                </div>
+              </div>
+            </div>
+            <!-- <div
+              class="border w-80 h-80 rounded-xl bg-[#F3F3F3] flex justify-center flex-wrap items-center"
+            >
+              <div @click="handleChangeAvatar">
+                <span class="iconfont icon-plus text-black/[0.4]" style="font-size: 18px"></span>
+                <p class="leading-3xl py-0 w-full text-sm text-center text-black/[0.4]">0/3</p>
+              </div>
+            </div> -->
+          </template>
+        </van-field>
+      </van-cell-group>
+      <div class="w-full fixed bottom-0 left-0 mt-90 px-16 pt-16 pb-40 box-border">
+        <van-button
+          size="large"
+          round
+          :color="isBtnDisabled ? '#A6A6A6' : '#FF9300'"
+          class="font-semibold"
+          block
+          @click="handleReport"
+          :loading="isSubmiting"
+        >
+          <!--  @click="isBtnDisabled ? handleReport : () => {}" -->
+          提交
+        </van-button>
+      </div>
+    </van-form>
+  </div>
+</template>
+<script setup>
+const route = useRoute()
+const router = useRouter()
+const goBack = () => router.back()
+
+definePageMeta({
+  layout: false
+})
+
+const TEXT = '举报类型'
+
+// 刷新次数
+const reportIndex = ref('')
+const loading = ref(false)
+
+const title = ref(TEXT)
+const reportDropdownMenuRef = ref(null)
+const reportItemRef = ref(null)
+
+const form = reactive({
+  typeId: null,
+  objectType: computed(() => (route?.query?.objectType == 2 ? 2 : 1)),
+  elseTypeReason: null,
+  description: null,
+  image: []
+})
+
+const { open, onChange } = useFileDialog({
+  accept: '.png,.png,.jpeg,.JPG,Png '
+})
+
+function handleChangeAvatar() {
+  open()
+}
+
+onChange(async (files) => {
+  if (!files.length) return
+
+  const formData = new FormData()
+  formData.append('uploadFile', files[0])
+  formData.append('asImage', true)
+  formData.append('fieldName', 'image')
+  const maxSize = 5 * 1024 * 1024 // 10 MB
+  if (form.image.length <= 3) {
+    if (files[0].size > maxSize) {
+      showToast('上传图片过大,请重新上传')
+      return
+    } else {
+      try {
+        showLoadingToast({
+          message: '图片上传中...',
+          duration: 1000000
+        })
+
+        const { data } = await request('/website/tourComplait/upload', {
+          method: 'post',
+          body: formData
+        })
+
+        form.image.push(data.fileUrl)
+        closeToast()
+        showToast('图片上传成功')
+      } catch (error) {
+        form.image.push({
+          url: files[0].name,
+          status: 'failed',
+          isImage: true,
+          message: '上传失败',
+          imageFit: 'contain'
+        })
+        closeToast()
+        showToast('图片上传失败')
+
+        console.log('图片上传失败')
+      }
+    }
+  } else {
+    showToast('最多上传图片数量3张')
+  }
+})
+
+// 删除图片
+const deleteImage = (index) => {
+  form.image = form.image.filter((it, filterIndex) => filterIndex != index)
+}
+
+const reportList = ref([])
+
+// 下拉菜单的方法
+function onreportFilterClose(value) {
+  form.elseTypeReason = '55555'
+  console.log(value, '12')
+
+  reportIndex.value = value
+
+  let el = reportList.find((item) => item?.id == value)
+  console.log(el, 'el')
+
+  title.value = el.typeName
+  form.elseTypeReason = el.description
+}
+
+function openReportDropdownMenuRef() {
+  nextTick(() => {
+    reportItemRef.value?.toggle(true)
+  })
+}
+
+// 获取举报类型
+const getreportType = async () => {
+  try {
+    let { data } = await request('/website/tourComplaintType/getTypeList')
+
+    if (Array.isArray(data) && data?.length) {
+      reportIndex.value = data[0]?.id
+      form.typeId = data[0]?.id
+      title.value = data[0]?.typeName
+      form.elseTypeReason = '5555'
+      reportList.value = data
+    } else {
+      reportList.value = []
+    }
+  } catch (err) {}
+}
+
+const isBtnDisabled = computed(() => {
+  return !form.elseTypeReason || !form.description || !form.image.length != 0
+})
+
+const isSubmiting = ref(false)
+
+const handleReport = async () => {
+  try {
+    isSubmiting.value = true
+
+    if (form.objectType == 2) {
+      // form.groupId = route.query.groupId
+      form.groupId = '1876571259412688897'
+    } else {
+      form.userId = route.query.userId
+    }
+    let { data } = await request('/website/tourComplait/add', {
+      method: 'post',
+      body: form
+    })
+    if (data) {
+      showSuccessToast('操作成功')
+    }
+    isSubmiting.value = false
+  } catch (err) {
+  } finally {
+    isSubmiting.value = false
+  }
+}
+
+onMounted(() => {
+  getreportType()
+})
+
+useSeoMeta({
+  title: TEXT
+})
+</script>
+<style lang="scss" scoped>
+::v-deep .van-field__body {
+  background-color: #f3f3f3;
+  border-radius: 8px;
+  padding: 12px;
+  padding-bottom: 20px;
+}
+
+::v-deep .image .van-field__value .van-field__body {
+  background-color: #fff !important;
+  padding: 0;
+}
+
+::v-deep .van-field__label {
+  font-weight: 600;
+}
+
+::v-deep .van-field__label--required::after {
+  margin-left: 2px;
+  color: var(--van-field-required-mark-color);
+  content: '*';
+}
+::v-deep .van-field__label--required::before {
+  content: '';
+}
+</style>

+ 225 - 0
src/pages/chat/set-single.vue

@@ -0,0 +1,225 @@
+<template>
+  <div class="w-full h-[100vh] bg-[#F7F8FA]">
+    <ChatHeaderBar title="聊天设置" />
+    <div class="h-66"></div>
+    <van-cell-group
+      style="margin-bottom: 12px; padding-top: 12px; padding-left: 16px"
+      class="box-border"
+      inset
+    >
+      <van-row>
+        <van-col style="width: 54px" span="4" class="mb-12 mr-10">
+          <div class="w-40 h-40 rounded-full mx-auto overflow-hidden mb-4">
+            <img class="w-full h-full object-cover" :src="itemData?.avatar" alt="" />
+          </div>
+          <p class="w-full line-clamp-1 lin text-sm text-center text-black-6">
+            {{ itemData?.nickname }}
+          </p>
+        </van-col>
+        <van-col style="width: 54px" span="4" class="mb-12 mr-10">
+          <div
+            @click="navigateTo('/chat/interrelation-friend')"
+            class="w-40 h-40 flex justify-center items-center bg-[#F3F3F3] rounded-full mx-auto overflow-hidden mb-4"
+          >
+            <span class="iconfont icon-plus text-black-6" style="font-size: 24px"></span>
+          </div>
+          <p class="w-full line-clamp-1 lin text-sm text-center text-black-6">多人聊天</p>
+        </van-col>
+      </van-row>
+    </van-cell-group>
+
+    <van-cell-group style="margin-bottom: 12px" class="box-border" inset>
+      <van-cell
+        v-for="(item, index) in list"
+        :key="index"
+        size="large"
+        :title="item.title"
+        :is-link="item.isLink"
+        @click="item.fn"
+      >
+        <template #icon>
+          <span
+            :class="`iconfont ${item.icon} text-[#FF9300]  mr-12`"
+            style="font-size: 24px"
+          ></span>
+        </template>
+        <template v-if="item.vModel != null" #right-icon>
+          <van-switch v-model="item.vModel" active-color="#FF9300" inactive-color="#dcdee0" />
+        </template>
+      </van-cell>
+    </van-cell-group>
+
+    <ChatDialog
+      v-model:show="dialogParmas.show"
+      v-model:title="dialogParmas.title"
+      v-model:confirmText="dialogParmas.confirmText"
+      v-model:cancelText="dialogParmas.cancelText"
+      @confirm="confirm"
+      @cancel="cancel"
+    >
+      <div class="w-full px-12 text-center mt-4">
+        <p class="mx-auto w-[80%] text-sm text-black-9 mb-16">{{ dialogParmas.subTitle }}</p>
+
+        <van-field
+          class=""
+          style="height: 40px; background: #f5f5f5; border-radius: 8px; margin-bottom: 30px"
+          clearable
+          :placeholder="dialogParmas.placeholder"
+          v-model="dialogParmas.remark"
+          maxlength="12"
+        />
+      </div>
+    </ChatDialog>
+  </div>
+</template>
+<script setup>
+definePageMeta({
+  layout: false
+})
+
+const itemData = ref(null)
+const isNotDisturb = ref(false)
+const isTop = ref(false)
+
+const dialogParmas = reactive({
+  show: false,
+  title: '',
+  placeholder: '',
+  remark: '',
+  subTitle: ''
+})
+
+// 弹窗确认的事件
+const confirm = async () => {
+  try {
+    let { data } = await request('', {})
+  } catch (error) {}
+  dialogParmas.show = false
+  itemData.value.remark = dialogParmas.remark
+}
+
+const cancel = () => {
+  dialogParmas.show = false
+}
+
+// 修改备注名
+const modifyNoteName = () => {
+  dialogParmas.show = true
+  dialogParmas.subTitle = '备注不得超过30个字'
+  dialogParmas.placeholder = '请输入备注'
+
+  if (itemData.value?.remark != '') {
+    dialogParmas.title = '修改备注'
+    dialogParmas.confirmText = '确认'
+    dialogParmas.cancelText = '取消'
+  } else {
+    dialogParmas.title = '添加备注'
+    dialogParmas.confirmText = '添加'
+    dialogParmas.cancelText = '拒绝'
+  }
+}
+
+// 查找聊天记录
+const findChatHistory = () => {
+  navigateTo('/chat/set-sub', {
+    query: {
+      objectType: 1,
+      userId: itemData.value?.userId
+    }
+  })
+}
+
+// 消息免打扰
+const notDisturb = () => {}
+
+// 置顶聊天
+const topChat = () => {}
+
+// 举报该用户
+const reportUser = () => {
+  navigateTo('/chat/report', {
+    query: {
+      objectType: 1,
+      userId: itemData.value?.userId
+    }
+  })
+}
+
+// 清空聊天记录
+const clearChatHistory = () => {
+  showConfirmDialog({
+    width: 260,
+    // title: '提示',
+    message: '清空聊天记录',
+    confirmButtonColor: '#FF9300'
+  })
+    .then(async () => {
+      const { data } = await request('/tourMessage/clearGroupMessage', {
+        query: {
+          groupId: itemData.value?.groupId
+        }
+      })
+      if (data) return
+    })
+    .catch(() => {})
+}
+
+const list = reactive([
+  {
+    title: '设置备注名',
+    // icon: setting,
+    icon: 'icon-setting-one',
+    isLink: true,
+    vModel: null,
+    fn: modifyNoteName,
+    value: ''
+  },
+  {
+    title: '查找聊天记录',
+    value: '',
+    icon: 'icon-log',
+    isLink: true,
+    vModel: null,
+    fn: findChatHistory
+  },
+  {
+    title: '消息面打扰',
+    value: '',
+
+    icon: 'icon-close-remind',
+    isLink: false,
+    vModel: isNotDisturb.value,
+    fn: notDisturb
+  },
+  {
+    title: '置顶聊天',
+    value: '',
+    icon: 'icon-set-top',
+    isLink: false,
+    vModel: isTop.value,
+    fn: topChat
+  },
+  {
+    title: '举报该用户',
+    value: '',
+    icon: 'icon-jubaoguanli',
+    isLink: true,
+    vModel: null,
+    fn: reportUser
+  },
+  {
+    title: '清空聊天记录',
+    value: '',
+    icon: 'icon-delete-one',
+    isLink: true,
+    vModel: null,
+    fn: clearChatHistory
+  }
+])
+
+useSeoMeta({
+  title: '我的消息'
+})
+</script>
+
+<style lang="scss" scoped></style>

+ 182 - 0
src/pages/chat/set-sub/index.vue

@@ -0,0 +1,182 @@
+<template>
+  <div class="w-full h-[100vh]">
+    <ChatHeader :title="title" />
+    <ChatSearch
+      v-model:searchString="searchString"
+      v-model:placeholder="placeholder"
+      @search="search"
+    />
+
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+      <div v-if="tab == 'records'" class="w-full min-h-400 pt-20">
+        <!--  :title="`没有找到&quot;${searchString}&quot;相关的结果`"   v-if="!list?.length && !loading"-->
+        <ChatEmpty
+          v-if="!list?.length && !loading"
+          image="search"
+          title="输入关键词,搜索聊天记录"
+        />
+
+        <template v-if="searchString && list?.length > 0">
+          <van-cell
+            clickable
+            v-for="(item, index) in list"
+            :key="item?.id"
+            :value="beforeTime(item?.createTime)"
+          >
+            <template #icon>
+              <div class="w-40 h-40 rounded-full mr-12 overflow-hidden">
+                <img class="w-full h-full object-cover" :src="item?.avatar" alt="" />
+              </div>
+            </template>
+            <template :title>
+              <p class="text-sm text-black-9">
+                {{ item?.nickname }}
+              </p>
+            </template>
+            <template :label>
+              <van-highlight
+                :keywords="searchString"
+                :source-string="item?.nickname"
+                class="text-xl text-black-3"
+                highlight-class="custom-class"
+              />
+            </template>
+          </van-cell>
+        </template>
+
+        <!-- <template v-if="!searchString && list?.length">
+          <p class="text-bsae text-center">按以下条件查找</p>
+          <div class="w-full flex justify-center mt-15">
+            <van-button
+              icon="contact-o"
+              type="primary"
+              color="#F6F6F6"
+              round
+              @click=""
+              size="small"
+              style="color: #333333; margin-right: 10px"
+            >
+              群成员
+            </van-button>
+            <van-button
+              icon="notes-o"
+              type="primary"
+              color="#F6F6F6"
+              color-text="#333333"
+              size="small"
+              round
+              @click=""
+              style="color: #333333"
+            >
+              日期
+            </van-button>
+          </div>
+        </template> -->
+      </div>
+    </van-pull-refresh>
+  </div>
+</template>
+<script setup>
+import { beforeTime } from '~/utils/detalTime'
+
+const route = useRoute()
+
+const tab = useRouteQuery('tab')
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 20
+})
+
+const loading = ref(false)
+const refreshing = ref(false)
+const searchString = ref('')
+// const groupRole = computed(() => route.query?.groupRole ?? '')
+
+onMounted(() => {
+  getList()
+})
+
+// const tabs = [
+//   {
+//     label: '群成员',
+//     name: 'member',
+//     to: ''
+//   },
+//   {
+//     label: '添加成员',
+//     name: 'addMember',
+//     to: ''
+//   },
+//   {
+//     label: '聊天记录',
+//     name: 'records',
+//     to: ''
+//   }
+// ]
+
+// 全部成员
+const list = ref([])
+
+const title = ref('聊天记录')
+
+// 搜索
+const search = () => {
+  queryParams.pageNum = 1
+  list.value = []
+  getList()
+}
+
+// 下拉刷新
+const onRefresh = () => {
+  list.value ? queryParams.pageNum++ : (queryParams.pageNum = 1)
+
+  getList()
+}
+
+// 获取群设置的配置信息
+const getList = async () => {
+  try {
+    loading.value = true
+    let data = {}
+    if (tab == 'records') data.records = searchString.value
+
+    let {
+      data: { dataList, totalCount }
+    } = await request('/website/tourism/visa/list/page')
+    // // query: {
+    //   groupId: '0',
+    //   ...data
+    // }
+
+    if (Array.isArray(dataList) && dataList?.length) {
+      list.value.unshift(...dataList)
+    } else {
+      list.value = []
+    }
+
+    loading.value = false
+  } catch (err) {
+  } finally {
+    loading.value = false
+  }
+}
+
+// 是否是普通成员
+const isRankAndFiler = (role) => {
+  return role == 1 || role == 2 ? true : false
+}
+
+definePageMeta({
+  layout: false
+})
+
+useSeoMeta({
+  title
+})
+</script>
+<style lang="scss" scoped>
+.custom-class {
+  color: #ff9300;
+}
+</style>

+ 436 - 0
src/pages/chat/set.vue

@@ -0,0 +1,436 @@
+<template>
+  <div>
+    <ChatHeaderBar title="聊天设置" />
+
+    <div class="w-full min-h-300 pt-60 box-border bg-[#F7F8FA]">
+      <van-cell-group
+        style="margin-bottom: 12px; padding-top: 12px; padding-left: 16px"
+        class="box-border"
+        inset
+      >
+        <van-row>
+          <van-col
+            style="width: 54px"
+            v-for="(item, index) in 14"
+            :key="index"
+            span="4"
+            class="mb-12 mr-10"
+          >
+            <div class="w-40 h-40 rounded-full border mx-auto overflow-hidden mb-4">
+              <img class="w-full h-full object-cover" src="" alt="" />
+            </div>
+            <p class="w-full line-clamp-1 lin text-sm text-center text-black-6">陈真82222222</p>
+          </van-col>
+          <van-col span="4" class="mb-12 mr-10" @click="navigateTo('/chat/group-add')">
+            <div
+              class="w-40 h-40 rounded-full flex justify-center items-center bg-[#F3F3F3] border mx-auto overflow-hidden mb-4"
+            >
+              <span class="iconfont icon-plus text-black-6" style="font-size: 24px"></span>
+            </div>
+            <p class="w-full line-clamp-1 lin text-sm text-center text-black-6">添加成员</p>
+          </van-col>
+          <van-col v-if="isRankAndFiler(setData?.groupRole)" span="4 mb-12 mr-10">
+            <div
+              class="w-40 h-40 rounded-full flex justify-center items-center bg-[#F3F3F3] border mx-auto overflow-hidden mb-4"
+            >
+              <span class="iconfont icon-minus text-black-6" style="font-size: 24px"></span>
+
+              <!-- <van-icon name="minus" size="24" /> -->
+            </div>
+            <p class="w-full line-clamp-1 lin text-sm text-center text-black-6">删除成员</p>
+          </van-col>
+        </van-row>
+
+        <!-- showBottom  @click="showBottom = true"  v-if="setData?.groupMember?.lenght == 15"   @click="navigateTo('/chat/group-member')"-->
+        <div
+          @click="navigateTo('/chat/group-member')"
+          class="w-full flex pb-5 box-border justify-center items-center leading-3xl text-sm text-black"
+        >
+          查看更多群成员
+          <van-icon name="arrow" class="-mt-2" size="16" />
+        </div>
+      </van-cell-group>
+
+      <van-cell-group style="margin-bottom: 12px" inset>
+        <van-cell
+          size="large"
+          @click="
+            openDialog({
+              // title: setData?.groupNickname ? '修改群名称' : '添加群名称',
+              title: '修改群名称',
+              value: setData?.groupNickname,
+              placeholder: '未命名',
+              subTitle: '最多不能超过12个字'
+            })
+          "
+          center
+          is-link
+          title="群名称"
+        >
+          <template #value>
+            <p class="w-full line-clamp-2 text-xl text-black/[0.4] leading-5xl">
+              {{ setData?.groupNickname ? setData?.groupNickname : '未命名' }}
+            </p>
+          </template>
+        </van-cell>
+        <van-cell
+          @click="
+            navigateTo({
+              path: '/chat/qr-code',
+              query: {
+                groupNickname: setData.groupNickname,
+                groupAvatar: setData.groupAvatar,
+                groupId: setData?.groupId
+              }
+            })
+          "
+          size="large"
+          center
+          is-link
+          title="群二维码"
+        >
+          <template #value>
+            <div class="w-full flex justify-end items-center">
+              <img class="w-16 h-16 shrink-0" src="~/assets/img/chat/chat-code.svg" alt="" />
+            </div>
+          </template>
+        </van-cell>
+
+        <van-cell
+          :clickable="isRankAndFiler(setData?.groupRole)"
+          @click="handleAnnouncement"
+          size="large"
+          center
+          is-link
+          title="群公告"
+          :value="setData?.groupAnnouncement ? '' : '未设置'"
+        ></van-cell>
+
+        <van-cell size="large" center>
+          <template #title>
+            <p class="w-full line-clamp-1">
+              群介绍:{{ '我们是一个设计讨论群,欢家多黄金时代...' }}
+            </p>
+          </template>
+        </van-cell>
+        <van-cell size="large" is-link center title="群聊类型">
+          <template #value>
+            <p class="w-full line-clamp-1">{{ setData?.typeName }}</p>
+          </template>
+        </van-cell>
+        <van-cell
+          @click="
+            openDialog({
+              title: '群备注',
+              value: setData?.groupRemark,
+              placeholder: '备注',
+              subTitle: '群备注仅自己可见'
+            })
+          "
+          size="large"
+          is-link
+          center
+          title="群备注"
+        >
+          <template #value>
+            <p class="w-full line-clamp-1">
+              {{ setData?.groupRemark ? setData?.groupRemark : '未设置' }}
+            </p>
+          </template>
+        </van-cell>
+      </van-cell-group>
+
+      <van-cell-group v-if="isRankAndFiler(setData?.groupRole)" style="margin-bottom: 12px" inset>
+        <van-cell size="large" center title="个人主页展示">
+          <template #label>
+            <!-- class="text-base text-black/[0.4]" -->
+            <span>开启后,在群聊广场和个人主页</span>
+          </template>
+          <template #right-icon>
+            <van-switch v-model="showHomePage" active-color="#FF9300" inactive-color="#dcdee0" />
+          </template>
+        </van-cell>
+        <van-cell size="large" center title="群聊邀请确认">
+          <template #label>
+            <!-- class="text-base text-black/[0.4]" -->
+            <span>
+              启用后,群成员需群主或群管理员确认才能邀请朋友进群。扫描二维码进群将同时停用
+            </span>
+          </template>
+          <template #right-icon>
+            <van-switch v-model="checked" active-color="#FF9300" inactive-color="#dcdee0" />
+          </template>
+        </van-cell>
+
+        <van-cell
+          @click="
+            navigateTo({
+              path: '/chat/examine',
+              query: {
+                groupId: setData?.groupId
+              }
+            })
+          "
+          size="large"
+          is-link
+          center
+          title="收到的进群申请"
+        ></van-cell>
+      </van-cell-group>
+
+      <van-cell-group style="margin-bottom: 12px" inset>
+        <van-cell
+          @click="
+            openDialog({
+              title: '我在群里的昵称',
+              value: setData?.myGroupNickname ? setData?.myGroupNickname : setData?.Nickname,
+              placeholder: '昵称',
+              subTitle: '昵称修改之后,只会在此群内显示,群内成员都可以看见'
+            })
+          "
+          size="large"
+          is-link
+          center
+          title="我在群里的昵称"
+        >
+          <template #value>
+            <p class="w-full line-clamp-1">
+              {{ setData?.myGroupNickname ? setData?.myGroupNickname : setData?.Nickname }}
+            </p>
+          </template>
+        </van-cell>
+      </van-cell-group>
+      <van-cell-group style="margin-bottom: 12px" inset>
+        <van-cell
+          @click="
+            navigateTo({
+              path: '/chat/set-sub',
+              query: {
+                tab: 'records',
+                groupId: setData?.groupId
+              }
+            })
+          "
+          size="large"
+          is-link
+          center
+          title="查找聊天记录"
+        ></van-cell>
+      </van-cell-group>
+      <van-cell-group style="margin-bottom: 12px" inset>
+        <van-cell size="large" is-link center title="消息免打扰">
+          <template #right-icon>
+            <van-switch v-model="isNotDisturb" active-color="#FF9300" inactive-color="#dcdee0" />
+          </template>
+        </van-cell>
+        <van-cell size="large" is-link center title="置顶聊天">
+          <template #right-icon>
+            <van-switch v-model="isTop" active-color="#FF9300" inactive-color="#dcdee0" />
+          </template>
+        </van-cell>
+      </van-cell-group>
+
+      <van-cell-group style="margin-bottom: 12px" inset>
+        <van-cell
+          @click="
+            navigateTo({
+              path: '/chat/report',
+              query: {
+                groupId: setData?.groupId,
+                objectType: 2
+              }
+            })
+          "
+          size="large"
+          is-link
+          center
+          title="举报"
+        ></van-cell>
+        <van-cell
+          @click="clearChatHistory"
+          size="large"
+          is-link
+          center
+          title="清空聊天记录"
+        ></van-cell>
+      </van-cell-group>
+
+      <div class="w-full pt-10 pb-40 px-16 box-border">
+        <van-button @click="handleExitGroupChat" color="#FF9300" round block>退出群聊</van-button>
+      </div>
+    </div>
+
+    <ChatDialog
+      v-model:show="showDialog"
+      v-model:title="dialogTitle"
+      @confirm="confirm"
+      @cancel="cancel"
+    >
+      <div class="w-full px-12 text-center mt-4">
+        <p class="mx-auto w-[80%] text-sm text-black-9 mb-16">{{ dialogSubTitle }}</p>
+
+        <van-field
+          class=""
+          style="height: 40px; background: #f5f5f5; border-radius: 8px; margin-bottom: 30px"
+          clearable
+          :placeholder="dialogPlaceholder"
+          v-model="groupNickname"
+          maxlength="12"
+        />
+      </div>
+    </ChatDialog>
+  </div>
+</template>
+<script setup>
+const route = useRoute()
+const router = useRouter()
+console.log(route.query, '12312')
+
+const userInfoStore = useUserInfoStore()
+const { userInfo } = storeToRefs(userInfoStore)
+
+// console.log(userInfo.value, 'userInfo')
+
+definePageMeta({
+  layout: false
+})
+
+useSeoMeta({
+  title: '我的消息'
+})
+
+onMounted(() => {
+  getGroupSetData()
+})
+
+let setData = reactive({
+  groupId: computed(() => route.query.groupId ?? ''),
+  userId: computed(() => route.query.userId ?? ''),
+  groupRole: 1,
+  groupAnnouncement:
+    '群公告模版 1.本群提倡友好理性交流,鼓励群友多发言,多互动 2.禁止无意义刷屏、发送广告信息以及谩骂等不良消息 3.为了保证群活跃不经常发言的群友,可能会被定时清理出群'
+})
+const groupNickname = ref('')
+
+// 是否显示到个人主页
+const showHomePage = ref(false)
+const isNotDisturb = ref(false)
+const isTop = ref(false)
+
+// 弹出窗
+const showDialog = ref(false)
+const dialogTitle = ref('')
+const dialogPlaceholder = ref('')
+const dialogSubTitle = ref('')
+
+// 弹窗的方法
+const openDialog = (item) => {
+  showDialog.value = true
+  dialogTitle.value = item?.title
+  groupNickname.value = item?.value
+  dialogPlaceholder.value = item?.placeholder
+  dialogSubTitle.value = item?.subTitle
+}
+// 弹窗确认的事件
+const confirm = () => {
+  setData.groupNickname = groupNickname.value
+  showDialog.value = false
+}
+
+const cancel = () => {
+  showDialog.value = false
+}
+
+// 修改群名称
+const handleUpdGroupNickname = () => {}
+
+// 获取群设置的配置信息
+const getGroupSetData = async () => {
+  // /website/tourGroup/getGroupInfoAndMemberByGroupId
+  const { data } = await request('/website/tourGroup/getGroupInfoAndMemberByGroupId', {
+    query: {
+      groupId: setData.groupId
+    }
+  })
+  if (typeof data == 'object') {
+    setData = data
+  }
+}
+
+// 返回上一页
+const onClickLeft = () => {
+  router.back()
+}
+
+// 是否是普通成员
+const isRankAndFiler = (role) => {
+  return role == 1 || role == 2 ? true : false
+}
+
+// 清空聊天记录
+const clearChatHistory = () => {
+  showConfirmDialog({
+    width: 260,
+    title: '提示',
+    message: '清空聊天记录',
+    confirmButtonColor: '#FF9300'
+  })
+    .then(async () => {
+      const { data } = await request('/tourMessage/clearGroupMessage', {
+        query: {
+          groupId: setData?.groupId
+        }
+      })
+      if (data) {
+        return
+      } else {
+        return
+      }
+    })
+    .catch(() => {})
+}
+
+// 点击公告
+const handleAnnouncement = () => {
+  console.log(!isRankAndFiler(setData?.groupRole))
+
+  if (!isRankAndFiler(setData?.groupRole) && !setData?.groupAnnouncement) {
+    showConfirmDialog({
+      width: 260,
+      message: `只有群主${setData?.groupNickname ? setData?.groupNickname : ''}或群管理员才能修改公告`,
+      showCancelButton: false,
+      confirmButtonColor: '#FF9300',
+      confirmButtonText: '我知道了'
+    }).then(() => {})
+    return
+  }
+
+  navigateTo({
+    path: '/chat/announcement',
+    query: {
+      groupId: setData?.groupId ? setData?.groupId : 0,
+      userId: '1',
+      groupRole: setData?.groupRole
+    }
+  })
+}
+
+// 退出群聊
+async function handleExitGroupChat() {
+  let { data } = await request('/website/tourGroup/exitGroup', {
+    query: {
+      groupId: setData.groupId,
+      userId: user
+    }
+  })
+  navigateTo({
+    path: '/profile/my-news',
+    replace: true
+  })
+}
+</script>
+<style lang="scss" scoped>
+::v-deep .van-dialog__header {
+  padding-top: 21px;
+}
+</style>

+ 54 - 0
src/pages/chat/single.vue

@@ -0,0 +1,54 @@
+<template>
+  <div>
+    <van-nav-bar title="单聊" fixed @click-left="onClickLeft" @click-right="onClickRight">
+      <template #left>
+        <div>
+          <van-icon name="arrow-left" color="black" size="18" />
+        </div>
+      </template>
+      <template #right>
+        <van-icon name="ellipsis" color="black" size="18" />
+      </template>
+    </van-nav-bar>
+
+    <van-pull-refresh v-model="loading" @refresh="onRefresh">
+      <div class="w-full h-full border">12231</div>
+    </van-pull-refresh>
+
+    <ProfileNewsChatInput :shareGroup="false"></ProfileNewsChatInput>
+  </div>
+</template>
+<script setup>
+const route = useRoute()
+const router = useRouter()
+
+definePageMeta({
+  layout: false
+})
+
+// 刷新次数
+const count = ref(0)
+const loading = ref(false)
+// 刷新
+const onRefresh = () => {
+  setTimeout(() => {
+    showToast('刷新成功')
+    loading.value = false
+    count.value++
+  }, 1000)
+}
+
+const onClickLeft = () => router.back()
+
+const onClickRight = () => {
+  navigateTo({
+    path: '/chat/set-single',
+    query: {
+      ...{
+        userId: '2'
+      }
+    }
+  })
+}
+</script>
+<style lang="scss" scoped></style>

+ 158 - 0
src/pages/chat/sweep.vue

@@ -0,0 +1,158 @@
+<template>
+  <div class="w-[100vw] relative h-[100vh]">
+    <template v-if="show">
+      <div
+        style="z-index: 1000; justify-content: space-between"
+        class="absolute w-full flex h-24 justify-between items-center top-0 pt-12 px-16 left-0"
+      >
+        <span
+          @click="router.back()"
+          :class="`iconfont  icon-left text-white `"
+          style="font-size: 24px"
+        ></span>
+        <h1 class="text-white font-semibold text-base">扫描二维码</h1>
+        <div></div>
+      </div>
+      <div id="qr-reader" class="w-full h-full"></div>
+
+      <div class="w-54 absolute bottom-75 left-1/2 -translate-x-1/2">
+        <div class="w-54 h-54 rounded-full mb-6 bg-black text-center">
+          <span :class="`iconfont  icon-pic text-white `" style="font-size: 32px"></span>
+        </div>
+        <p @click="stopScan" class="text-[13px] text-white text-center">相册</p>
+      </div>
+    </template>
+
+    <div v-if="!show">
+      <div
+        style="z-index: 1000; justify-content: space-between"
+        class="absolute w-full flex h-24 justify-between items-center top-0 pt-12 px-16 left-0"
+      >
+        <span
+          @click="router.back()"
+          :class="`iconfont  icon-left text-black `"
+          style="font-size: 24px"
+        ></span>
+        <h1 class="text-black font-semibold text-base">扫码结果</h1>
+        <div></div>
+      </div>
+      <ChatEmpty image="search" title="无法识别二维码" />
+    </div>
+
+    <!-- <div id="qr-reader-results" class="absolute bottom-75">
+      {{ results }}
+    </div> -->
+  </div>
+</template>
+<script setup>
+import { Html5QrcodeScanner, Html5Qrcode, Html5QrcodeScanType } from 'html5-qrcode'
+const router = useRouter()
+
+const show = ref(true)
+let results = ref(null)
+const state = reactive({
+  html5QrCode: null,
+  fileList: []
+})
+
+definePageMeta({
+  layout: false
+})
+
+useSeoMeta({
+  title: '我的消息'
+})
+
+onMounted(() => {
+  handleQrcode()
+})
+
+const handleQrcode = () => {
+  Html5Qrcode.getCameras()
+    .then((devices) => {
+      if (devices && devices.length) {
+        // 当前环境下能识别出摄像头,并且摄像头的数据可能不止一个
+        state.html5QrCode = new Html5Qrcode('qr-reader') //  reader  放置扫码功能的元素ID
+        // alert('摄像头识别成功')
+        state.html5QrCode.start(
+          //  environment后置摄像头 user前置摄像头
+          {
+            facingMode: 'environment'
+          },
+
+          {
+            fps: 1, // 可选,每n秒帧扫描一次
+            qrbox: {
+              width: 250,
+              height: 250
+            }, // 扫描的UI框
+            videoConstraints: {
+              width: 375,
+              aspectRatio: 16 / 9
+              // echoCancellation: true,
+            }
+          },
+          (decodedText, decodedResult) => {
+            let groupId
+            if (decodedText) {
+              groupId = decodedText.split('?')[1].split('=')[1]
+
+              // 扫描结果
+              results.value = {
+                decodedText,
+                decodedResult,
+                groupId
+              }
+
+              scanQRCode()
+            } else {
+              // show.value = false
+            }
+            stopScan()
+            // scanQRCode()
+            console.log(results.value)
+          }
+          // (errorMessage) => {
+          //   // 错误回调
+          //   console.warn(`扫码错误:${errorMessage}`)
+          // }
+        )
+      }
+    })
+    .catch((err) => {
+      // console.error(`启动扫码失败: ${err}`)
+      // 摄像头无访问权限
+    })
+}
+
+const stopScan = () => {
+  state.html5QrCode
+    .stop()
+    .then((a) => {
+      console.log('已停止扫码', a)
+    })
+    .catch((err) => {
+      console.log(err)
+      showToast('停止失败')
+    })
+}
+
+// 读码
+async function scanQRCode() {
+  navigateTo({
+    path: '/chat/qr-results',
+    query: {
+      groupId: results.value.groupId
+    },
+    replace: true
+  })
+}
+</script>
+<style scoped lang="scss">
+// /* 使视频全屏适配 */
+video {
+  width: 100%;
+  height: 100vh;
+  object-fit: cover; /* 保持宽高比,填充整个屏幕 */
+}
+</style>

+ 54 - 0
src/pages/chat/sweep3.vue

@@ -0,0 +1,54 @@
+<template>
+  <div>
+    <van-button @click="handleQrcode">扫一扫</van-button>
+    <div id="qr-reader" style="width:500px"></div>
+    <div id="qr-reader-results">
+      {{ results }}
+    </div>
+  </div>
+</template>
+<script setup>
+import {Html5QrcodeScanner, Html5Qrcode, Html5QrcodeScanType} from "html5-qrcode";
+
+let results = ref(null)
+const state = reactive({
+  html5QrCode: null,
+  fileList: []
+})
+
+const handleQrcode = () => {
+  Html5Qrcode.getCameras()
+      .then(devices => {
+        if (devices && devices.length) {
+          // 当前环境下能识别出摄像头,并且摄像头的数据可能不止一个
+          state.html5QrCode = new Html5Qrcode('qr-reader')//  reader  放置扫码功能的元素ID
+          alert('摄像头识别成功')
+          state.html5QrCode.start(
+              //  environment后置摄像头 user前置摄像头
+              {facingMode: 'environment'},
+              {
+                fps: 1, // 可选,每n秒帧扫描一次
+                qrbox: {
+                  width: 250,
+                  height: 250
+                } // 扫描的UI框
+              },
+              (decodedText, decodedResult) => {
+                // 扫描结果
+                results.value = {
+                  decodedText,
+                  decodedResult
+                }
+              }
+          )
+        }
+      })
+      .catch((e) => {
+        // 摄像头无访问权限
+        alert(e)
+      })
+}
+</script>
+<style scoped lang="scss">
+
+</style>

+ 197 - 0
src/pages/chat/user-add.vue

@@ -0,0 +1,197 @@
+<template>
+  <div class="w-full h-[100vh]">
+    <ChatHeaderBar title="添加用户" />
+
+    <ChatSearch v-model:searchString="showName" @search="search" placeholder="请输入关键词" />
+
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+      <ChatEmpty
+        v-if="!list?.length && !loading"
+        image="search"
+        title="请输入关键词,没有找到关结果"
+        top="100"
+      />
+
+      <van-list
+        v-else-if="list.length"
+        v-model:loading="loading"
+        error-text="获取失败"
+        finished-text="-- 没有更多了 --"
+        :finished="finished"
+        :immediate-check="false"
+        @load="getLoadList"
+      >
+        <template v-for="(item, index) in list" :key="item?.userId">
+          <van-cell center disabled>
+            <template #icon>
+              <div class="w-40 h-40 ml-13 mr-12 rounded-full overflow-hidden">
+                <img class="w-full h-full shrink-0 object-cover" :src="item?.headImageUrl" alt="" />
+              </div>
+            </template>
+            <template #title>
+              <h1 class="text-xl line-clamp-1 text-black-3">{{ item?.tourUserVo?.showName }}</h1>
+
+              <p class="line-clamp-1 text-black-3 text-sm">粉丝 {{ transNum(item?.fansCount) }}</p>
+            </template>
+            <template #value>
+              <van-button
+                v-if="item.fansStatus == '0'"
+                style="width: 60px"
+                size="mini"
+                type="primary"
+                color="#FF9300"
+                round
+                plain
+                @click="handleFollow(item, index)"
+              >
+                关注
+              </van-button>
+              <van-button
+                style="width: 60px"
+                v-if="item.fansStatus == '1'"
+                size="mini"
+                type="primary"
+                color="#999999"
+                round
+                plain
+                @click="handleFollow(item, index)"
+              >
+                已关注
+              </van-button>
+              <van-button
+                style="width: 72px"
+                v-if="item.fansStatus == '2'"
+                size="mini"
+                type="primary"
+                color="#999999"
+                round
+                plain
+                @click="handleFollow(item, index)"
+              >
+                互相关注
+              </van-button>
+              <template v-if="item.fansStatus == '4'"></template>
+              <van-button
+                style="width: 60px"
+                v-if="item.fansStatus == '4'"
+                size="mini"
+                type="primary"
+                color="#FF9300"
+                round
+                plain
+                @click="handleFollow(item, index)"
+              >
+                回关
+              </van-button>
+            </template>
+          </van-cell>
+        </template>
+      </van-list>
+    </van-pull-refresh>
+  </div>
+</template>
+<script setup>
+definePageMeta({
+  layout: false
+})
+
+onMounted(() => {})
+
+const refreshing = ref(false)
+const loading = ref(false)
+const finished = ref(false)
+
+const showName = ref('')
+
+const queryParams = reactive({
+  pageNum: 1,
+  pageSize: 10,
+  showName: ''
+})
+
+const list = ref([])
+
+useSeoMeta({
+  title: '我的消息'
+})
+
+const search = () => {
+  list.value = []
+  queryParams.pageNum = 1
+  queryParams.showName = showName.value
+  getList()
+}
+
+// 关注
+const handleFollow = async (item, i) => {
+  try {
+    const { data } = await request('/website/tourism/fans/saveConcern', {
+      method: 'post',
+      body: {
+        attentionId: item.tourUserVo.userId
+      }
+    })
+
+    list.value[i].fansStatus = data.fansStatus
+
+    showSuccessToast('操作成功')
+  } catch (e) {
+  } finally {
+  }
+}
+
+// 刷新
+const onRefresh = () => {
+  queryParams.pageNum = 1
+  list.value = []
+  getList()
+}
+
+// 触底加载
+const getLoadList = () => {
+  queryParams.pageNum++
+  finished.value = true
+  getList()
+}
+
+// 获取数据
+const getList = async () => {
+  try {
+    let url = `/website/tourism/fans/getUserListByNickname`
+
+    loading.value = true
+    let {
+      data: { dataList, totalCount }
+    } = await request(url, {
+      query: {
+        ...queryParams
+      }
+    })
+
+    if (Array.isArray(dataList) && dataList?.length) {
+      list.value = list.value.concat(dataList)
+    } else {
+      list.value = []
+    }
+
+    loading.value = false
+    refreshing.value = false
+    if (list.value.length >= totalCount) {
+      finished.value = true
+    } else {
+      finished.value = false
+    }
+  } catch (err) {
+  } finally {
+    refreshing.value = false
+    loading.value = false
+  }
+}
+
+function transNum(num) {
+  if (isNaN(num)) return 0
+  if (num < 10000) return num
+  return (num / 10000).toFixed(1) + 'w'
+}
+</script>
+<style lang="scss" scoped></style>

+ 349 - 0
src/pages/profile/interaction-message/index.vue

@@ -0,0 +1,349 @@
+<template>
+  <!-- hud -->
+  <div class="w-full">
+    <van-dropdown-menu fixed ref="interactionDropdownMenuRef" active-color="#FF9300">
+      <van-dropdown-item
+        v-model="interactionIndex"
+        @change="onInteractionFilterClose"
+        :options="
+          interactionDropdownMenuList.map((item, index) => {
+            if (item.value == interactionIndex) {
+              item.icon = item.iconO
+              return item
+            } else {
+              item.icon = item.iconM
+              return item
+            }
+          })
+        "
+        ref="interactionItemRef"
+      >
+        <template #title class="relative" active-color="#FF9300">
+          <div class="font-semibold text-base">
+            <NuxtLink :to="'/profile/my-news'" class="absolute top-0 -left-135">
+              <van-icon name="arrow-left" size="24" color="#000000" />
+            </NuxtLink>
+            {{ interactionTitle }}
+          </div>
+        </template>
+      </van-dropdown-item>
+    </van-dropdown-menu>
+
+    <ProfileNewsAllMessage></ProfileNewsAllMessage>
+
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+      <Empty v-if="!interactionList.length && !loading" title="暂无评论" />
+      <van-list
+        v-if="interactionList.length"
+        v-model:loading="loading"
+        :finished="finished"
+        :immediate-check="false"
+        finished-text=""
+        @load="handleCurrentChange"
+      >
+        <template v-for="item in interactionList" :key="item?.id">
+          <div
+            v-if="interactionIndex == 0 || interactionIndex == 2"
+            style="scrollbar-width: none"
+            class="mx-12 pt-8 mb-12 box-border border-b-[1px] border-dashed flex justify-between items-start"
+          >
+            <div class="flex shrink-0 w-[74%] justify-start items-start">
+              <div class="w-32 h-32 rounded-full shrink-0">
+                <img
+                  v-if="item?.headImageUrlDictMap?.name"
+                  class="w-full h-full object-cover rounded-full"
+                  :src="item?.headImageUrlDictMap?.name"
+                  alt=""
+                />
+                <img
+                  v-else
+                  class="w-full h-full object-cover rounded-full"
+                  src="https://www.xiaoyaotravel.com/_nuxt/default_avatar.gSq5JxK1.png"
+                  alt=""
+                />
+              </div>
+
+              <div class="ml-8 w-[85%] w-full text-sm">
+                <NuxtLink :to="`/yj/${item?.travelNoteId}`">
+                  <p class="w-full line-clamp-1 text-black-6 font-normal leading-xl">
+                    {{ item?.commentUserIdDictMap?.name }}
+                  </p>
+                  <p
+                    :class="`w-full ${interactionIndex == 0 ? 'line-clamp-1' : 'line-clamp-2'} my-6 text-black-3 text-base leading-4xl`"
+                  >
+                    <span v-if="item?.replyUserId">
+                      回复{{
+                        item?.replyUserIdDictMap?.name != userInfo.showName
+                          ? `@${item?.replyUserIdDictMap?.name}`
+                          : ''
+                      }}:
+                    </span>
+                    <span v-else>评论:</span>
+
+                    <span v-html="coveredContent(item?.commentContent)"></span>
+                  </p>
+                  <p class="w-full mb-6 text-black-9 leading-xl">{{ item?.createTime }}</p>
+                </NuxtLink>
+                <div
+                  @click.stop="addReply(item, item?.id)"
+                  class="box-border mb-12 line-clamp-1 rounded-full w-95 h-26 flex justify-center items-center text-base px-8 text-black-3 leading-4xl bg-[#F5F5F5] active:bg-black/[0.1]"
+                >
+                  <div class="w-16 h-16 rounded-full shrink-0 mr-4">
+                    <img
+                      v-if="userInfo?.headImageUrl"
+                      class="w-full h-full object-cover rounded-full"
+                      :src="userInfo?.headImageUrl"
+                      alt=""
+                    />
+                    <img
+                      v-else
+                      class="w-full h-full object-cover rounded-full"
+                      src="https://www.xiaoyaotravel.com/_nuxt/default_avatar.gSq5JxK1.png"
+                      alt=""
+                    />
+                  </div>
+                  回复评论
+                </div>
+              </div>
+            </div>
+
+            <NuxtLink
+              :to="`/yj/${item.travelNoteId}`"
+              class="block w-71 ml-17 h-47 shrink-0 rounded-[4px] overflow-hidden"
+            >
+              <img
+                v-if="item?.travelNoteUrl"
+                class="w-full h-full object-cover"
+                :src="item?.travelNoteUrl"
+                alt=""
+              />
+              <img v-else class="w-full h-full object-cover" :src="defaultImg" alt="" />
+            </NuxtLink>
+          </div>
+
+          <div
+            v-if="interactionIndex == 1"
+            style="scrollbar-width: none"
+            class="block mx-12 pt-8 mb-12 box-border border-b-[1px] border-dashed flex justify-between items-start"
+          >
+            <div class="flex shrink-0 w-[74%] justify-start items-start">
+              <div class="w-32 h-32 rounded-full shrink-0">
+                <img
+                  v-if="userInfo?.headImageUrl"
+                  class="w-full h-full object-cover rounded-full"
+                  :src="userInfo?.headImageUrl"
+                  alt=""
+                />
+                <img
+                  v-else
+                  class="w-full h-full object-cover rounded-full"
+                  src="https://www.xiaoyaotravel.com/_nuxt/default_avatar.gSq5JxK1.png"
+                  alt=""
+                />
+              </div>
+
+              <div class="ml-8 w-[85%] mb-12 w-full text-sm">
+                <NuxtLink :to="`/yj/${item?.travelNoteId}`">
+                  <p
+                    v-if="userInfo?.showName"
+                    class="w-full line-clamp-1 text-black-6 font-normal leading-xl"
+                  >
+                    {{ userInfo?.showName }}
+                  </p>
+                  <p class="w-full line-clamp-2 my-6 text-black-3 text-base leading-4xl">
+                    <span v-if="item.replyUserId">
+                      回复{{
+                        item?.replyUserIdDictMap?.name != userInfo.showName
+                          ? `@${item?.replyUserIdDictMap?.name}`
+                          : ''
+                      }}:
+                    </span>
+                    <span v-else>
+                      评论{{
+                        item?.replyUserIdDictMap?.name ? `@${item?.replyUserIdDictMap?.name}` : ''
+                      }}
+                    </span>
+                    <span v-html="coveredContent(item?.commentContent)"></span>
+                  </p>
+                  <p class="w-full mb-6 text-black-9 leading-xl">{{ item?.createTime }}</p>
+                </NuxtLink>
+              </div>
+            </div>
+
+            <NuxtLink
+              :to="`/yj/${item.travelNoteId}`"
+              class="block w-71 ml-17 h-47 shrink-0 rounded-[4px] overflow-hidden"
+            >
+              <img
+                v-if="item?.travelNoteUrl"
+                class="w-full h-full object-cover"
+                :src="item?.travelNoteUrl"
+                alt=""
+              />
+              <img v-else class="w-full h-full object-cover" :src="defaultImg" alt="" />
+            </NuxtLink>
+          </div>
+        </template>
+      </van-list>
+    </van-pull-refresh>
+  </div>
+</template>
+
+<script setup>
+import defaultImg from '~/assets/img/comment/H5_default.png'
+import commentsBlack from '~/assets/img/chat/comments-black.svg'
+import commentsOrange from '~/assets/img/chat/comments-orange.svg'
+import like from '~/assets/img/chat/like.svg'
+import likeOrange from '~/assets/img/chat/like-orange.svg'
+import eit from '~/assets/img/chat/tiji.svg'
+import eitOrange from '~/assets/img/chat/tiji-orange.svg'
+import comment from '~/assets/img/chat/comment.svg'
+import commentOrange from '~/assets/img/chat/comment-orange.svg'
+import medicalFiles from '~/assets/img/chat/medical-files.svg'
+import medicalFilesOrange from '~/assets/img/chat/medical-files-orange.svg'
+import send from '~/assets/img/chat/send.svg'
+import sendOrange from '~/assets/img/chat/send-orange.svg'
+import emojiJson from '../../yj/emoji.json'
+const authStore = useAuthStore()
+const { token } = storeToRefs(authStore)
+
+onMounted(() => {
+  getUserInfo()
+})
+
+definePageMeta({
+  layout: false
+})
+
+// 默认的数组
+const interactionDropdownMenuList = [
+  {
+    text: '全部消息',
+    value: 5,
+    icon: commentsBlack,
+    iconM: commentsBlack,
+    iconO: commentsOrange
+  },
+  { text: '赞与收藏', value: 4, icon: like, iconM: like, iconO: likeOrange },
+  {
+    text: '提及',
+    value: 3,
+    icon: eit,
+    iconM: eit,
+    iconO: eitOrange
+  },
+  { text: '我的评论', value: 0, icon: comment, iconM: comment, iconO: commentOrange },
+  {
+    text: '收到的评论',
+    value: 2,
+    icon: medicalFiles,
+    iconM: medicalFiles,
+    iconO: medicalFilesOrange
+  },
+  { text: '发出的评论', value: 1, icon: send, iconM: send, iconO: sendOrange }
+]
+
+const pageNum = ref(1)
+const finished = ref(false)
+
+const loading = ref(false)
+const refreshing = ref(false)
+
+// 互动消息的索引
+const interactionIndex = ref(5)
+const interactionTitle = ref('全部消息')
+
+const interactionDropdownMenuRef = ref(null)
+
+const interactionItemRef = ref(null)
+// 评论的列表
+const interactionList = ref([])
+
+// 下拉菜单的方法
+function onInteractionFilterClose(value) {
+  interactionIndex.value = value
+  interactionTitle.value = interactionDropdownMenuList.find((item) => item.value == value).text
+  interactionList.value = []
+  getInteractionList()
+}
+
+useSeoMeta({
+  title: '互动消息'
+})
+
+// 获取用户信息
+const userInfo = ref({})
+async function getUserInfo() {
+  if (!token.value) return (userInfo.value = {})
+  request('/website/tourism/user/view').then(
+    (res) => {
+      userInfo.value = res.data || {}
+    },
+    () => {
+      userInfo.value = {}
+    }
+  )
+}
+
+// 转换评论中的一些非字符emoji
+function coveredContent(val) {
+  if (!val) return ''
+  return val.replace(/\[.*?]/g, function (str) {
+    const baseApi = import.meta.env.VITE_APP_EMOJI_API
+    const emojiName = emojiJson[str]
+    if (!emojiName) return str
+    return `<img src=${baseApi}${emojiJson[str]} style="width: 20px; height: 20px;display: inline-block; vertical-align: middle"/>`
+  })
+}
+
+// 获取互动消息的数据列表
+const getInteractionList = async () => {
+  try {
+    loading.value = true
+    const {
+      data: { dataList, totalCount }
+    } = await request(`/website/comment/tourTravelNotesComment/my-comments`, {
+      query: {
+        pageNum: pageNum.value,
+        pageSize: 20,
+        commentType: interactionIndex.value
+      }
+    })
+
+    if (!Array.isArray(dataList) || !dataList.length) return (interactionList.value = [])
+    interactionList.value = interactionList.value.concat(dataList)
+    refreshing.value = false
+
+    if (interactionList.value.length >= totalCount) {
+      finished.value = true
+    } else {
+      finished.value = false
+    }
+  } finally {
+    refreshing.value = false
+    loading.value = false
+  }
+}
+
+// 点赞的的方法
+
+// 下拉刷新
+const onRefresh = () => {
+  refreshing.value = true
+  commentList.value = []
+  getMyCommentList()
+}
+// 改变页数
+const handleCurrentChange = () => {
+  finished.value = true
+  pageNum.value += 1
+  getInteractionList()
+}
+</script>
+
+<style lang="scss" scoped>
+::v-deep .van-cell__left-icon {
+  display: flex;
+  align-items: center;
+}
+</style>

+ 307 - 0
src/pages/profile/my-news/index.vue

@@ -0,0 +1,307 @@
+<template>
+  <div class="w-full">
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+      <van-sticky :offset-top="50">
+        <div
+          class="w-full text-xl font-semibold px-16 h-48 flex justify-start items-center bg-white"
+        >
+          <span
+            @click="showPopover = true"
+            v-if="showPopover"
+            class="iconfont icon-plus text-[#FF9300]"
+            style="font-size: 24px"
+          ></span>
+          <span v-else class="iconfont icon-plus text-black" style="font-size: 24px"></span>
+
+          <van-popover
+            v-model:show="showPopover"
+            placement="bottom"
+            theme="dark"
+            offset="[5,20]"
+            :actions="actionsList"
+            @select="onSelect"
+          >
+            <template #reference>
+              <span :class="`${showPopover ? 'text-[#FF9300]' : 'text-black-3'}`">添加</span>
+            </template>
+          </van-popover>
+        </div>
+      </van-sticky>
+
+      <div
+        @click="onChatPage('/profile/system-message', {})"
+        class="w-full relative h-82 flex justify-start items-center px-16"
+      >
+        <van-badge v-if="messagesNumber > 0" v-bind="messageNumber(messagesNumber)" max="99">
+          <div
+            class="w-48 h-48 bg-[#0052D9] rounded-full overflow-hidden flex justify-center items-center"
+          >
+            <img
+              class="w-24 h-24 shrink-0 object-cover"
+              src="~/assets/img/chat/remind.svg"
+              alt=""
+            />
+          </div>
+        </van-badge>
+
+        <div
+          v-else
+          class="w-48 h-48 bg-[#0052D9] rounded-full overflow-hidden flex justify-center items-center"
+        >
+          <img class="w-24 h-24 shrink-0 object-cover" src="~/assets/img/chat/user.svg" alt="" />
+        </div>
+
+        <div class="h-48 w-245 shrink-0 ml-12 flex flex-wrap items-end">
+          <h1 class="line-clamp-1 w-full text-base text-black-3 font-semibold mb-4">系统消息</h1>
+          <p class="line-clamp-1 h-20 text-sm text-black-6 leading-3xl">
+            你吃饭了么wwwwwdasdasdasdwwwwwwwwwwwwwwwwwwwwwwwssssssssssssssssssssssssssssss
+          </p>
+        </div>
+
+        <div class="w-35 h-48 shrink-0">
+          <p class="text-black/[0.6] text-sm text-end">02:06</p>
+        </div>
+        <div class="absolute bottom-0 right-0 w-[96%] h-1 border-b-[1px]"></div>
+      </div>
+
+      <div
+        @click="onChatPage('/', {})"
+        class="w-full relative h-82 flex justify-start items-center px-16"
+      >
+        <van-badge v-if="messagesNumber > 0" v-bind="messageNumber(messagesNumber)" max="99">
+          <div
+            class="w-48 h-48 bg-[#FF9300] rounded-full overflow-hidden flex justify-center items-center"
+          >
+            <img class="w-24 h-24 shrink-0 object-cover" src="~/assets/img/chat/user.svg" alt="" />
+          </div>
+        </van-badge>
+
+        <div
+          v-else
+          class="w-48 h-48 bg-[#0052D9] rounded-full overflow-hidden flex justify-center items-center"
+        >
+          <img class="w-24 h-24 shrink-0 object-cover" src="~/assets/img/chat/user.svg" alt="" />
+        </div>
+
+        <div class="h-48 w-245 shrink-0 ml-12 flex flex-wrap items-end">
+          <h1 class="line-clamp-1 w-full text-base text-black-3 font-semibold mb-4">新增粉丝</h1>
+          <p class="line-clamp-1 h-20 text-sm text-black-6 leading-3xl">
+            你吃饭了么wwwwwdasdasdasdwwwwwwwwwwwwwwwwwwwwwwwssssssssssssssssssssssssssssss
+          </p>
+        </div>
+
+        <div class="w-35 h-48 shrink-0">
+          <p class="text-black/[0.6] text-sm text-end">02:06</p>
+        </div>
+        <div class="absolute bottom-0 right-0 w-[96%] h-1 border-b-[1px]"></div>
+      </div>
+      <div
+        @click="onChatPage('/profile/interaction-message', {})"
+        class="w-full relative h-82 flex justify-start items-center px-16"
+      >
+        <van-badge v-if="messagesNumber > 0" v-bind="messageNumber(messagesNumber)" max="99">
+          <div
+            class="w-48 h-48 bg-[#FF9300] rounded-full overflow-hidden flex justify-center items-center"
+          >
+            <img class="w-24 h-24 shrink-0 object-cover" src="~/assets/img/chat/user.svg" alt="" />
+          </div>
+        </van-badge>
+
+        <div
+          v-else
+          class="w-48 h-48 bg-[#0052D9] rounded-full overflow-hidden flex justify-center items-center"
+        >
+          <img class="w-24 h-24 shrink-0 object-cover" src="~/assets/img/chat/user.svg" alt="" />
+        </div>
+
+        <div class="h-48 w-245 shrink-0 ml-12 flex flex-wrap items-end">
+          <h1 class="line-clamp-1 w-full text-base text-black-3 font-semibold mb-4">互动消息</h1>
+          <p class="line-clamp-1 h-20 text-sm text-black-6 leading-3xl">收藏了你的游记</p>
+        </div>
+
+        <div class="w-35 h-48 shrink-0">
+          <p class="text-black/[0.6] text-sm text-end">02:06</p>
+        </div>
+        <div class="absolute bottom-0 right-0 w-[96%] h-1 border-b-[1px]"></div>
+      </div>
+
+      <!-- 置顶绘画列表 -->
+
+      <div v-if="activeNames" class="w-full">
+        <template v-for="(item, index) in pinnedList" :key="index">
+          <ProfileNewsGroupChat
+            :item-data="{ title: item }"
+            @on-chat-page="
+              onChatPage('/chat/group', { userId: userInfo.updateUserId, groupId: '2' })
+            "
+            @on-no-bother="noBother(item)"
+            @on-conv-delete="convDelete()"
+          />
+        </template>
+      </div>
+
+      <div
+        v-if="pinnedList.length >= 7"
+        @click="activeNames = !activeNames"
+        class="flex justify-between border items-center h-20 w-full p-16 box-border"
+      >
+        <div class="shrink-0 flex items-center">
+          <van-icon name="bars" size="16" class="mr-5" />
+          <span class="text-sm">{{ collapseTitle }}</span>
+        </div>
+        <div class="w-16 h-16 shrink-0">
+          <van-icon name="arrow" size="16" />
+          <!-- <img class="w-full h-full object-cover" src="" alt="" /> -->
+        </div>
+      </div>
+
+      <ProfileNewsGroupChat
+        @on-chat-page="onChatPage('/chat/group', { userId: userInfo.updateUserId, groupId: 111 })"
+        @on-no-bother="noBother()"
+        @on-conv-delete="convDelete()"
+      />
+      <ProfileNewsSingleChat
+        @on-chat-page="onChatPage('/chat/single', { userId: userInfo.updateUserId, singleId: '2' })"
+        @on-no-bother="noBother()"
+        @on-conv-delete="convDelete()"
+      />
+    </van-pull-refresh>
+  </div>
+</template>
+
+<script setup>
+import comments from '~/assets/img/chat/comments-white.svg'
+import plaza from '~/assets/img/chat/guangchang.svg'
+import userAdd from '~/assets/img/chat/user-add.svg'
+
+const actionsList = [
+  { text: '创建群聊', icon: comments },
+  { text: '群聊广场', icon: plaza },
+  { text: '加入群聊', icon: plaza },
+  { text: '添加用户', icon: userAdd }
+]
+
+const websocketStore = useWebSocketStore()
+
+const userInfoStore = useUserInfoStore()
+const { userInfo } = storeToRefs(userInfoStore)
+
+onMounted(() => {
+  userInfoStore.getUserInfo()
+})
+
+useSeoMeta({
+  title: '我的消息'
+})
+
+// 获取连接状态
+const isConnected = computed(() => websocketStore.isConnected)
+
+// 连接 WebSocket
+const connectSocket = () => {
+  websocketStore.connect(`?userId=${userInfo.value.updateUserId}`)
+}
+
+// 发送测试消息
+const sendTestMessage = () => {
+  websocketStore.sendMessage({ message: 'Hello WebSocket!' })
+}
+
+// 断开 WebSocket
+const disconnectSocket = () => {
+  websocketStore.disconnect()
+}
+
+// 消息历史
+const messages = computed(() => websocketStore.wsService.value?.messages || [])
+
+const finished = ref(false)
+
+const error = ref(false)
+
+const loading = ref(false)
+
+const showPopover = ref(false)
+
+const messagesNumber = ref(10)
+
+// 置顶列表
+const pinnedList = ref([1, 2, 3, 4, 5, 6, 7])
+
+// 个置顶聊天
+const collapse_text = '折叠置顶聊天'
+
+const activeNames = ref(true)
+// collapse
+const collapseTitle = ref(collapse_text)
+
+// 刷新
+const refreshing = ref(false)
+// 下拉刷新
+const onRefresh = () => {
+  refreshing.value = true
+  //   getVisaOrderList()
+}
+
+// 打扰和免打扰得
+const noBother = (parmas) => {
+  if (parmas?.bother == false) {
+    parmas.bother = true
+  } else {
+    parmas.bother = false
+  }
+}
+
+// 会话的删除
+const convDelete = (parmas) => {}
+
+// 消息数量通知的展示  需要动态的展示
+const messageNumber = (content) => {
+  let messageNumberObj = {}
+  if (content <= 1) {
+    messageNumberObj = {
+      offset: [-5, 4],
+      dot: true,
+      content
+    }
+  }
+  if (content > 1) {
+    messageNumberObj = {
+      offset: [-10, 7],
+      content
+    }
+  }
+  return messageNumberObj
+}
+
+// 跳转聊天页面
+const onChatPage = (path, query) => {
+  navigateTo({
+    path,
+    query
+    // params: query
+  })
+}
+
+// 选中的是那个页面
+const onSelect = (action) => {
+  if (action.text == '创建群聊') onChatPage('/chat/create-group', {})
+  if (action.text == '群聊广场') onChatPage('/chat/group-square', {})
+  if (action.text == '加入群聊') onChatPage('/chat/sweep', {})
+  if (action.text == '添加用户') onChatPage('/chat/user-add', {})
+}
+
+async function getList() {
+  try {
+    let {
+      data: { dataList }
+    } = await request('/website/tourism/fans/getTourMemberList')
+  } catch (error) {}
+}
+
+onMounted(() => {
+  getList()
+})
+</script>
+
+<style lang="scss" scoped></style>

+ 39 - 0
src/pages/profile/system-message/details.vue

@@ -0,0 +1,39 @@
+<template>
+  <div class="box-border p-16 mb-8">
+    <h1 class="font-medium mb-8 text-2xl text-black-3 leading-7xl">
+      在克鲁舍瓦茨停留半天,于是就去了这家新开的柴犬餐馆,非常好吃推荐给大家
+    </h1>
+
+    <div class="mb-16 text-sm text-black-9">2024/12/27 02:06</div>
+
+    <div class="text-black-3 text-[13px]" v-html="itemData?.content"></div>
+  </div>
+</template>
+
+<script setup>
+const id = useRouteQuery('id')
+
+useSeoMeta({
+  title: '系统消息详情'
+})
+
+onMounted(() => {
+  getData()
+})
+
+const itemData = ref(null)
+
+const loading = ref(false)
+
+const getData = () => {
+  // async
+  //  const {data}= await request()
+
+  itemData.value = {
+    content:
+      '这家餐馆位于老城区一条安静的小巷里,门口挂着一块简单的木制招牌,写着“柴犬餐馆”。听到这个名字,我顿时被吸引住了——这会是一家以柴犬为主题的餐厅吗?推门进去,果然映入眼帘的是几张柴犬的照片和玩偶装饰,整体环境散发出一种温馨的氛围,仿佛是来到了一家好友的家中。 我点了一份推荐的招牌菜,是一道用当地香料腌制的烤肉,搭配着新鲜的蔬菜和自制的酱料。食物端上来时,满室飘散着烤肉的香气,外焦里嫩的口感配合着独特的酱料,让人一口咬下去便沉浸在美味之中。桌旁的小柴犬时不时抬头看看我,好像在询问我是否喜欢这里的食物。 虽然只是短短的半天停留,但在这里,我感受到了克鲁舍瓦茨的另一面——不仅是历史的厚重感,更有着现代与温馨的碰撞。这家柴犬餐馆成为了这段旅途中温暖的回忆,不仅仅因为食物的美味,还因为这里的氛围带给人一种轻松和快乐。口咬下去便沉浸在美味之中。桌旁的小柴犬时不时抬头看看我,好像在询问我是否喜欢这里的食物。 虽然只是短短的半天停留,但在这里,我感受到了克鲁舍瓦茨的另一面——不仅是历史的厚重感,更有着现代与温馨的碰撞。这家柴犬餐馆成为了这段旅途中温暖的回忆,不仅仅因为食物的美味,还因为这里的氛围带给人一种轻松和快乐。口咬下去便沉浸在美味之中。桌旁的小柴犬时不时抬头看看我,好像在询问我是否喜欢这里的食物。 虽然只是短短的半天停留,但在这里,我感受到了克鲁舍瓦茨的另一面——不仅是历史的厚重感,更有着现代与温馨的碰撞。'
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 30 - 0
src/pages/profile/system-message/index.vue

@@ -0,0 +1,30 @@
+<template>
+  <div style="height: calc(100vh - 50px)" class="w-full bg-[#F7F8FA]">
+    <div v-for="(item, index) in [1, 2, 3, 5]" @click="onChatPage('0')" :key="index" class="w-full">
+      <p class="w-full pt-16 mb-12 text-center text-black-9 text-sm">{{ '2001/09/24 20:22' }}</p>
+      <div class="w-[91.2%] box-border h-103 mx-16 bg-white p-16">
+        <h1 class="text-black w-full line-clamp-1 text-2xl font-semibold mb-8">
+          {{ item?.title ? item?.title : '标题标题' }}
+        </h1>
+        <p class="line-clamp-2 w-full leading-3xl text-black-9 text-sm">
+          {{ 'ssss' }}
+          大苏打实打实科技大厦十八号大家开始电话卡设计的哈克阿坎德哈吉斯等哈考试大纲啊大噶说的话噶是售价高达九十
+        </p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+useSeoMeta({
+  title: '系统消息'
+})
+// 跳转聊天页面
+const onChatPage = (query) => {
+  navigateTo({
+    path: `/profile/system-message/details?id=${query}`
+  })
+}
+</script>
+
+<style lang="scss" scoped></style>

+ 80 - 0
src/stores/useWebsoket.js

@@ -0,0 +1,80 @@
+import { defineStore } from 'pinia'
+import { useStorage } from '@vueuse/core'
+import { skipHydrate } from 'pinia'
+// const userInfoStore = useUserInfoStore()
+// const { userInfo } = storeToRefs(userInfoStore)
+// userInfoStore.getUserInfo()
+export const useWebSocketStore = defineStore('websocket', () => {
+  const startUrl = ref('wss://your-websocket-url')
+
+  const message = ref(null)
+
+  // WebSocket 服务的实例
+  const wsService = ref(null)
+
+  // 使用 `useStorage` 来持久化 WebSocket 的连接状态
+  const isConnected = useStorage('isConnected', false)
+
+  // 连接 WebSocket
+  const connect = (url) => {
+    if (wsService.value) {
+      // 如果 WebSocket 已连接,不需要重新连接
+      return
+    }
+
+    wsService.value = new WebSocket(`${startUrl.value}${url}`)
+
+    wsService.value.connect()
+
+    // 当连接成功时
+    wsService.value.onopen = () => {
+      isConnected.value = true
+      console.log('WebSocket连接已打开')
+    }
+
+    // 当连接关闭时
+    wsService.value.onclose = () => {
+      isConnected.value = false
+      console.log('WebSocket连接已关闭')
+    }
+
+    // 监听错误事件
+    wsService.value.onerror = (error) => {
+      console.error('WebSocket错误:', error)
+      isConnected.value = false
+    }
+
+    // 监听消息
+    wsService.value.onMessage((message) => {
+      console.log('收到消息:', message)
+    })
+  }
+
+  // 发送消息
+  const sendMessage = (message) => {
+    if (wsService.value && wsService.value.readyState === WebSocket.OPEN) {
+      wsService.value.sendMessage(message)
+    } else {
+      console.error('WebSocket未连接,无法发送消息')
+    }
+  }
+
+  // 断开 WebSocket 连接
+  const disconnect = () => {
+    if (wsService.value) {
+      wsService.value.close()
+      wsService.value = null
+      isConnected.value = false
+      console.log('WebSocket已关闭')
+    }
+  }
+
+  // 返回 Pinia store 中的数据和方法
+  return {
+    isConnected: skipHydrate(isConnected), // 保证在服务器端渲染时不与客户端同步
+    wsService,
+    connect,
+    sendMessage,
+    disconnect
+  }
+})

+ 55 - 0
src/utils/detalTime.js

@@ -0,0 +1,55 @@
+/*
+ * 创建时间和现在时间作对比
+ * @param time 时间戳
+ * 
+ * */
+export function beforeTime(time) {
+    let way = Date.parse(new Date()) / 1000 - time;
+    let r = '';
+    if (way < 60) {
+        r = '刚刚';
+    } else if (way >= 60 && way < 3600) {
+        r = Math.floor(way / 60) + '分钟前';
+    } else if (way >= 3600 && way < 86400) {
+        r = Math.floor(way / 3600) + '小时前';
+    } else if (way >= 86400 && way < 2592000) {
+        r = Math.floor(way / 86400) + '天前';
+    } else if (way >= 2592000 && way < 15552000) {
+        r = Math.floor(way / 2592000) + '个月前';
+    } else if (time === 0 || !time) {
+        r = '暂无';
+    } else {
+        r = dateChange('Y-m-d', time);
+    }
+    return r;
+}
+
+/*
+ * 返回时间年月日时分秒
+ * @param {date}  时间戳
+ * @param {fmt}   转换格式  'yyyy-MM-dd hh:mm:ss'
+ * 
+ * */
+export function formatDate(date, fmt) {
+    if (/(y+)/.test(fmt)) {
+        fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
+    }
+    var o = {
+        'M+': date.getMonth() + 1,
+        'd+': date.getDate(),
+        'h+': date.getHours(),
+        'm+': date.getMinutes(),
+        's+': date.getSeconds()
+    };
+    for (let k in o) {
+        if (new RegExp(`(${k})`).test(fmt)) {
+            let str = o[k] + '';
+            fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? str : padLeftZero(str));
+        }
+    }
+    return fmt;
+};
+
+function padLeftZero(str) {
+    return ('00' + str).substr(str.length);
+}