Browse Source

Merge branch 'follow' into dev_suwenjiang

suwenjiang 2 months ago
parent
commit
3e4085643b

+ 14 - 19
.env.development

@@ -4,32 +4,27 @@ VITE_APP_ENV=development
 # VITE_APP_BASE_URL=http://101.126.146.250:8082/
 # 测试服
 
-# 黄文本地
-# VITE_APP_BASE_URL=http://192.168.1.44:8082/
-
-# 畅哥本地
-# VITE_APP_BASE_URL=http://192.168.1.38:8082/
-
-# 张维本地
-VITE_APP_BASE_URL=http://192.168.1.73:8082/
 
-# 张维本地socoket
-VITE_APP_IM_URL=ws://192.168.1.73:8082/system/message
 
-# 李忠畅本地socoket
+# 李忠畅本地
+# VITE_APP_BASE_URL=http://192.168.1.38:8082/
+# 本地socoket
 # VITE_APP_IM_URL=ws://192.168.1.38:8082/system/message
+# 花生壳
+# VITE_APP_BASE_URL=http://cilicli.qicp.vip
 
-
-# 黄雯花生壳
-# VITE_APP_BASE_URL=http://q9943037p3.goho.co
-
-# 黄雯本地socoket
+# 黄雯本地
+# VITE_APP_BASE_URL=http://192.168.1.44:8082/
+# 本地socoket
 # VITE_APP_IM_URL=ws://192.168.1.44:8082/system/message
+# 花生壳
+# VITE_APP_BASE_URL=http://q9943037p3.goho.co
 # VITE_APP_IM_URL=ws://q9943037p3.goho.co/system/message
 
-
-# 李忠畅花生壳
-# VITE_APP_BASE_URL=http://cilicli.qicp.vip
+# 张维本地
+VITE_APP_BASE_URL=http://192.168.1.73:8082/
+# 本地socoket
+VITE_APP_IM_URL=ws://192.168.1.73:8082/system/message
 
 # VITE_APP_BASE_URL=http://192.168.1.204:8082
 VITE_APP_EMOJI_API=https://v.xiaoyaotravel.com/emoji/

+ 1 - 1
nuxt.config.ts

@@ -91,5 +91,5 @@ export default defineNuxtConfig({
         landscapeWidth: 1920, // 横屏时使用的视口宽度
       },
     },
-  }
+  },
 });

+ 1 - 0
package.json

@@ -15,6 +15,7 @@
   "dependencies": {
     "@pinia/nuxt": "^0.5.4",
     "@samk-dev/nuxt-vcalendar": "^1.0.4",
+    "@vant/use": "^1.6.0",
     "@vueuse/core": "^12.0.0",
     "@vueuse/nuxt": "^12.0.0",
     "accounting": "^0.4.1",

+ 3 - 0
pnpm-lock.yaml

@@ -14,6 +14,9 @@ importers:
       '@samk-dev/nuxt-vcalendar':
         specifier: ^1.0.4
         version: 1.0.4(magicast@0.3.5)(rollup@4.22.5)(vue@3.5.10)
+      '@vant/use':
+        specifier: ^1.6.0
+        version: 1.6.0(vue@3.5.10)
       '@vueuse/core':
         specifier: ^12.0.0
         version: 12.0.0

+ 264 - 0
src/pages/chat/chat-input/index.vue

@@ -0,0 +1,264 @@
+<template>
+  <div class="chat-input-comp" ref="chatInputCompRef">
+    <div class="input-box">
+      <div v-if="false" class="mr-12">语音todo</div>
+      <div
+          @click="openTool('operate')"
+          class="iconfont icon-close-one rotate-45 mr-12 text-black-6"
+          style="font-size: 32px"
+      ></div>
+      <div
+          @click="openTool('emoji')"
+          class="iconfont icon-slightly-smiling-face text-black-6 mr-12"
+          style="font-size: 32px"
+      ></div>
+      <div class="input-box__textarea mr-12" id="input-kary">
+        <van-field
+            v-model="inputValue"
+            autosize
+            ref="inputRef"
+            rows="1"
+            type="textarea"
+            placeholder="请输入"
+            @focus="handleFocus"
+            @blur="handleBlur"
+        />
+      </div>
+      <van-button size="small" type="warning" @click="sendTextMessage"> 发 送</van-button>
+    </div>
+    <div v-show="showTool" class="px-20 py-8">
+      <div class="operate-box" v-show="currTool === 'operate'">
+        <div v-for="(operate, i) in showOperateList"
+             :key="i"
+             @click="handleOperate(operate)"
+        >
+          <div
+              class="w-54 h-54 active:bg-[#FF9300]/[0.1] bg-[#F3F3F3] shrink-0 rounded-full mb-5 flex justify-center items-center">
+            <span
+                :class="`iconfont ${operate.icon} text-black-6 active:text-[#FF9300] `"
+                style="font-size: 32px"
+            ></span>
+          </div>
+          <p class="text-sm w-full text-center">{{ operate.title }}</p>
+        </div>
+      </div>
+      <div class="emoji-box h-144 overflow-y-auto" v-show="currTool === 'emoji'">
+        <div
+            v-for="(item, index) in emojiJson"
+            :key="index"
+            @click="selectEmoji(item, index)"
+            class="active:bg-[#ddd] text-4xl w-26 aspect-[1/1] grid place-items-center"
+        >
+          <div @click="selectEmoji(index)" v-html="item.emoji"></div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import emojiJson from "~/components/Profile/News/emoji";
+
+const props = defineProps({
+  operates: {
+    type: Array,
+    default: () => ['image']// ['image', 'share-group']
+  },
+})
+const emit = defineEmits(['send', 'focus', 'blur'])
+
+const inputValue = ref('')
+const inputRef = ref(null);
+
+const chatInputCompRef = ref(null);
+const operateList = [
+  {
+    title: '相册',
+    icon: 'icon-pic',
+    isShow: props.operates.includes('image'),
+    fn: 'uploadImage'
+  },
+  {
+    title: '分享群聊',
+    icon: 'icon-peoples-two',
+    isShow: props.operates.includes('share-group'),
+    fn: 'shareGroupChat'
+  }
+];
+
+const showOperateList = computed(() => {
+  return operateList.filter(o => o.isShow)
+})
+
+const {open, onChange: uploadImage} = useFileDialog({
+  accept: 'image/*'
+})
+
+const handleOperate = (operate) => {
+  try {
+    switch (operate.fn) {
+      case 'uploadImage':
+        open()
+        break;
+      case 'shareGroupChat':
+        console.log('分享群聊')
+        showToast('研发中,敬请期待~')
+        break;
+    }
+  } catch (e) {
+
+  }
+}
+
+uploadImage(async (files) => {
+  if (!files.length) return
+
+  console.log(files[0], 'files[0]files[0]')
+  const {type, size} = files[0];
+  if (!['image/jpeg', 'image/png', 'image/gif'].includes(type)) {
+    showToast('请上传图片')
+  }
+  const maxSize = 20 * 1024 * 1024 // 20 MB
+  if (size > maxSize) {
+    showToast('图片大小不能超过20MB')
+    return
+  }
+  /*  const formData = new FormData()
+    formData.append('uploadFile', files[0])
+    formData.append('asImage', true)
+    formData.append('fieldName', 'messageContent')
+    const {data} = await request('/website/tourMessage/upload', {
+      method: 'post',
+      body: formData
+    })*/
+  // closeToast()
+  emit('send', {
+    type: 'image',
+    messageContent: files[0]
+  })
+})
+
+const sendTextMessage = () => {
+  try {
+    if (!inputValue.value.trim()) {
+      showToast('发送内容不能为空!')
+      return
+    }
+    emit('send', {
+      type: 'text',
+      messageContent: inputValue.value
+    })
+    inputValue.value = ''
+  } catch (e) {
+
+  }
+}
+const selectEmoji = async (item, index) => {
+  try {
+    const position = getInputPosition()
+    const str = inputValue.value;
+    inputValue.value = `${str.slice(0, position)}${emojiJson[index].emoji}${str.slice(position)}`
+  } catch (e) {
+
+  }
+}
+
+const showTool = ref(false)
+const currTool = ref('')
+const openTool = (toolName) => {
+  showTool.value = true
+  currTool.value = toolName
+}
+const handleFocus = () => {
+  showTool.value = false
+  emit('focus')
+}
+const handleBlur = () => {
+  emit('blur')
+}
+const getInputPosition = () => {
+  try {
+    if (!inputValue.value) return 0
+    const el = inputRef.value?.$el.querySelector('input, textarea');
+    if (!el) return inputValue.value.length - 1;
+    return el.selectionStart
+  } catch (e) {
+    console.log(e, '??')
+  }
+}
+
+// 判断是否点击的非输入框
+const handleClickOutside = (event) => {
+  const chatInputCompEl = chatInputCompRef.value;
+  const isChatInputCompEl = chatInputCompEl.contains(event.target);
+
+  if (!isChatInputCompEl) {
+    showTool.value = false
+    console.log(inputRef.value.$el.blur, 'inputValue.valueblur')
+    inputRef.value.$el.blur()
+  }
+};
+onMounted(() => {
+  useEventListener('click', handleClickOutside, {target: document})
+  useEventListener('mousedown', handleClickOutside, {target: document})
+  useEventListener('touchstart', handleClickOutside, {target: document})
+
+  if (process.env.NODE_ENV === 'development') {
+    useEventListener('keyup', (event) => {
+      if (event.key === 'Enter') {
+        sendTextMessage()
+      }
+    })
+  }
+})
+</script>
+
+<style scoped lang="scss">
+.chat-input-comp {
+  width: 100%;
+  border-top: 1px solid #E7E7E7;
+
+  .input-box {
+    width: 100%;
+    //height: 64px;
+    padding: 12px;
+    box-sizing: border-box;
+    background: #fff;
+    display: flex;
+    align-items: center;
+
+    .input-box__textarea {
+      flex: 1;
+      background: #F3F3F3;
+      border-radius: 20px;
+      border: 1px solid #DCDCDC;
+      padding: 5px 10px;
+    }
+  }
+
+  .operate-box {
+    display: grid;
+    grid-template-columns: 54px 54px 54px 54px;
+    grid-template-rows: 54px 54px;
+    gap: 20px;
+  }
+
+  .emoji-box {
+    display: grid;
+    gap: 5px;
+    grid-template-columns: repeat(6, 1fr);
+    grid-auto-rows: auto;
+    box-sizing: border-box;
+    justify-content: center;
+    align-content: center;
+  }
+}
+
+:deep(.van-cell) {
+  padding: 0;
+  background: transparent;
+  min-height: 24px;
+  max-height: 100px;
+  overflow-y: auto;
+}
+</style>

+ 5 - 3
src/pages/chat/chat-message/index.vue

@@ -1,7 +1,6 @@
 <template v-if="message">
   <div class="chat-message" :class="msg.viewType ? 'chat-message--accept' : 'chat-message--send'">
     <div class="text-center text-sm text-[#000]/40 mb-16">{{ message.createTime }}</div>
-
     <div class="chat-message__content">
       <van-image
           v-if="msg.viewType === 1"
@@ -44,6 +43,7 @@ import {isValidJson} from "~/utils";
 const chatStore = useChatStore()
 const user = computed(() => chatStore.user)
 const {curConversiton} = storeToRefs(chatStore)
+
 const props = defineProps({
   message: Object
 });
@@ -51,13 +51,15 @@ const props = defineProps({
 let msg = ref(null)
 const initMsg = () => {
   try {
-    console.log(props.message, 'messagemessage')
     if (!isValidJson(props.message?.messageContent)) return
     const {createTime, getUserId, sendUserId, messageContent} = JSON.parse(props.message?.messageContent)
+    console.log(props.message, 'messagemessage', user.value)
+    console.warn(props.message?.sendUserId, user.value.pass, 'initMsg')
+
     msg.value = {
       messageContent: messageContent,
       type: getMessageType(props.message?.messageType),
-      viewType: getUserId === sendUserId ? 0 : 1,
+      viewType: props.message?.sendUserId === user.value.pass ? 0 : 1,
       createTime: createTime
     }
   } catch (e) {

+ 95 - 63
src/pages/chat/single.vue

@@ -20,24 +20,21 @@
     </van-nav-bar>
     <van-list
         ref="messageBoxRef"
-        class="flex-1 overflow-y-auto px-12"
+        class="flex-1 overflow-y-auto px-12 flex flex-col"
         :finished="true"
         finished-text=""
     >
       <template v-for="(message, index) in receiveGetter" :key="index">
         <ChatMessage :message="message"></ChatMessage>
       </template>
+      <div v-if="false" class="text-[#979797] text-sm text-center mt-auto mb-10">在对方关注或回复前,最多只能发送1条信息</div>
     </van-list>
-    <div class="h-88 w-full bg-[#333]"></div>
-    <ProfileNewsChatInput
-        :shareGroup="false"
-        @on-select-Img="selectImg"
-        @on-send-message="sendMessage"
-    ></ProfileNewsChatInput>
+    <ChatInput :operates="['image']"  @focus="scrollToBottom" @send="handleSendMessage"></ChatInput>
   </div>
 </template>
 <script setup>
 import ChatMessage from './chat-message'
+import ChatInput from "./chat-input";
 import {messageContentParse, formatTimestamp} from '~/utils/detalTime'
 import {findHyperlinks} from "~/pages/chat/chat-message/link-message/handle";
 
@@ -46,7 +43,7 @@ const router = useRouter()
 const refreshing = ref(false)
 const uploadUrl = `${import.meta.env.VITE_APP_BASE_URL}/website/tourMessage/upload`
 const chatStore = useChatStore()
-const {ws, curConversiton, receive, receiveGetter, connectSta, onNewMessage} =
+const {ws, curConversiton, receive,conversations, receiveGetter, connectSta, onNewMessage} =
     storeToRefs(chatStore)
 
 const user = computed(() => chatStore.user)
@@ -81,7 +78,7 @@ const followStatus = ref(0)
 const querySwParams = reactive({
   getUserId: computed(() => route.query.getUserId ?? ''),
   groupId: computed(() => route.query.groupId ?? ''),
-  sendUserId: computed(() => route.query.sendUserId ?? '')
+  sendUserId: computed(() => user.value?.pass)
 })
 
 // 本地生成一个唯一消息id
@@ -115,42 +112,6 @@ const onClickRight = () => {
   })
 }
 
-// 发送消息的方法
-// 发送文本消息
-function sendMessage(messageParams) {
-  console.log(messageParams, 'messageParams')
-  console.log(getUserId.value, '5555')
-  console.log(noticeType, '5555')
-  if (!messageParams.trim()) return
-  let msg = {
-    // getUserId: getUserId.value,
-    // sendUserId: sendUserId.value,
-    // groupId: groupId.value,
-    ...querySwParams,
-    specialUserId: specialUserId.value,
-    messageContent: messageParams,
-    messageType: 0,
-    noticeType: 1,
-    object: {
-      id: getLocalId()
-    }
-  }
-  const isLink = !!findHyperlinks(messageParams)
-  if (isLink) {
-    msg.messageType = 4
-  }
-
-  receive.value.push({
-    ...msg,
-    messageContent: JSON.stringify({messageContent: msg.messageContent})
-  })
-  messageContent.value = ''
-  messageBoxRef.value.scrollTop = messageBoxRef.value.scrollHeight
-
-  msg = JSON.stringify(msg)
-  ws.value.send(msg)
-}
-
 const pageSize = ref(10)
 
 const messageCount = ref(0)
@@ -185,7 +146,7 @@ async function getChatHistory(messageId = '') {
   if (!messageId) {
     nextTick(() => {
       setTimeout(() => {
-        messageBoxRef.value && messageBoxRef.value.scrollTo({top: msgBottomRef.value.offsetTop})
+        mscrollToBottom()
       }, 100)
     })
   }
@@ -201,7 +162,7 @@ function addEventListenerTextarea() {
       inputBoxRef.value.addEventListener('keydown', function (event) {
         if (event.key === 'Enter') {
           event.preventDefault()
-          sendMessage()
+          sendTextMessage()
         }
       })
     }
@@ -226,16 +187,64 @@ function addEventListenerMessage() {
   })
 }
 
+// 获取我与对方的关注情况
+async function isFollow() {
+  if (noticeType.value !== 1) return //只有单聊中才需要获取关注情况
+
+  const query = {
+    userId: curConversiton.value.toUserId
+  }
+  const {data: status = 0} = await request('/website/tourGroup/isFollow', {query})
+  followStatus.value = status
+  console.log('关注情况:', status)
+}
+
+
+// 发送文本消息
+const sendTextMessage = (text) => {
+  if(!text) return
+  let msg = {
+    // getUserId: getUserId.value,
+    // sendUserId: sendUserId.value,
+    // groupId: groupId.value,
+    ...querySwParams,
+    specialUserId: specialUserId.value,
+    messageContent: text,
+    messageType: 0,
+    noticeType: 1,
+    object: {
+      id: getLocalId()
+    }
+  }
+  const isLink = !!findHyperlinks(text)
+  if (isLink) msg.messageType = 4;
+
+  receive.value.push({
+    ...msg,
+    messageContent: JSON.stringify({messageContent: msg.messageContent})
+  })
+  ws.value.send(JSON.stringify(msg))
+}
+
 // 选择发送图片
-function selectImg(fileUrl) {
+const sendImageMessage = async (file) => {
   try {
-    console.log(fileUrl, 'fileUrl')
+    console.log(file, 'file')
+    const formData = new FormData()
+    formData.append('uploadFile', file)
+    formData.append('asImage', true)
+    formData.append('fieldName', 'messageContent')
+    const {data} = await request('/website/tourMessage/upload', {
+      method: 'post',
+      body: formData
+    })
+
     const msg = {
       getUserId: getUserId.value,
       sendUserId: sendUserId.value,
       specialUserId: '',
       groupId: groupId.value,
-      messageContent: JSON.stringify({messageContent: fileUrl}),
+      messageContent: JSON.stringify({messageContent: data.fileUrl}),
       messageType: 1,
       noticeType: noticeType.value,
       object: {
@@ -244,9 +253,9 @@ function selectImg(fileUrl) {
     }
     receive.value.push({
       ...msg,
-      messageContent: JSON.stringify({messageContent: fileUrl})
+      messageContent: JSON.stringify({messageContent: data.fileUrl})
     })
-    messageBoxRef.value.scrollTop = messageBoxRef.value.scrollHeight
+
     ws.value.send(JSON.stringify(msg))
   } catch (e) {
     console.error(e, '??')
@@ -255,24 +264,42 @@ function selectImg(fileUrl) {
   }
 }
 
-// 获取我与对方的关注情况
-async function isFollow() {
-  if (noticeType.value !== 1) return //只有单聊中才需要获取关注情况
+const handleSendMessage = async ({type, messageContent}) => {
+  try {
+    switch (type) {
+      case 'text':
+        sendTextMessage(messageContent)
+        break;
+      case 'image':
+        await sendImageMessage(messageContent)
+        break;
+    }
+    await scrollToBottom()
+  } catch (e) {
+
+  } finally {
 
-  const query = {
-    userId: curConversiton.value.toUserId
   }
-  const {data: status = 0} = await request('/website/tourGroup/isFollow', {query})
-  followStatus.value = status
-  console.log('关注情况:', status)
+
 }
 
+
+const scrollToBottom = async () => {
+  await nextTick(); // 确保DOM已经更新
+  const listElement = messageBoxRef.value?.$el;
+  if (listElement) {
+    const scrollContainer = listElement;
+    scrollContainer.scrollTop = scrollContainer.scrollHeight;
+  }
+};
+
+
 watch(groupId, async () => {
   // 消息置空
   receive.value = []
 
   // 监听消息输入框键盘enter事件
-  addEventListenerTextarea()
+  // addEventListenerTextarea()
 
   // 监听聊天框消息滚动事件
   addEventListenerMessage()
@@ -284,18 +311,23 @@ watch(groupId, async () => {
   getChatHistory()
 })
 
-watch(onNewMessage, getUserId, sendUserId, () => {
+watch(onNewMessage, () => {
+  scrollToBottom()
 })
 
 onMounted(() => {
   // 获取前会话用户的聊天记录
   getChatHistory()
 
-  addEventListenerTextarea()
+  // addEventListenerTextarea()
   addEventListenerMessage()
 
   // 获取与当前会话用户的关注状态
   isFollow()
+
+  setTimeout(() => {
+    scrollToBottom()
+  }, 500)
 })
 
 definePageMeta({