Merge branch 'main' of https://gitea-inner.fontree.cn/scout666/chat-app
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
* 'main' of https://gitea-inner.fontree.cn/scout666/chat-app: 处理文件上传中友好提示;处理文件上传失败场景图标展示;解决图片上传loading问题;替换所有进度条颜色更醒目; 处理聊天未读消息数量统计、应用角标、视频预览、切换账号清除缓存问题 解决删除会话后发消息,数量统计不正确问题;解决图片显示不正常问题 处理未读消息数量显示、删除回话自动已读、显示样式等问题
This commit is contained in:
commit
c374154f0f
2
components.d.ts
vendored
2
components.d.ts
vendored
@ -8,6 +8,8 @@ export {}
|
||||
/* prettier-ignore */
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
AsyncError: typeof import('./src/components/async-error/index.vue')['default']
|
||||
AsyncLoading: typeof import('./src/components/async-loading/index.vue')['default']
|
||||
AudioMessage: typeof import('./src/components/talk/message/AudioMessage.vue')['default']
|
||||
Avatar: typeof import('./src/components/base/Avatar.vue')['default']
|
||||
AvatarCropper: typeof import('./src/components/base/AvatarCropper.vue')['default']
|
||||
|
@ -1,10 +1,11 @@
|
||||
<script setup>
|
||||
import { useStatus } from '@/store/status'
|
||||
import { useUserStore } from '@/store'
|
||||
import { useUserStore, useDialogueListStore } from '@/store'
|
||||
import { useProvideUserModal } from '@/hooks'
|
||||
import {useAuth} from "@/store/auth";
|
||||
const {token} = useAuth()
|
||||
import ws from '@/connect'
|
||||
import {uniStorage} from "@/utils/uniStorage.js"
|
||||
const { statusBarHeight } = useStatus()
|
||||
const { uid, isShow } = useProvideUserModal()
|
||||
const userStore = useUserStore()
|
||||
@ -19,6 +20,10 @@ const handleWebview = () => {
|
||||
// })
|
||||
console.log("webview", webview)
|
||||
token.value = webview.token
|
||||
if(webview?.doClearDialogueList){
|
||||
useDialogueListStore().dialogueList.value = []
|
||||
uniStorage.removeItem('dialogueList')
|
||||
}
|
||||
userStore.loadSetting()
|
||||
ws.connect()
|
||||
}
|
||||
|
79
src/components/async-error/index.vue
Normal file
79
src/components/async-error/index.vue
Normal file
@ -0,0 +1,79 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
location.reload(true);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="loader">
|
||||
<p class="heading">加载中</p>
|
||||
<div class="loading">
|
||||
<div class="load"></div>
|
||||
<div class="load"></div>
|
||||
<div class="load"></div>
|
||||
<div class="load"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 重启页面 -->
|
||||
|
||||
<style scoped lang="scss">
|
||||
.loader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.heading {
|
||||
color: black;
|
||||
letter-spacing: 0.2em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
width: 5em;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.load {
|
||||
width: 23px;
|
||||
height: 3px;
|
||||
background-color: limegreen;
|
||||
animation: 1s move_5011 infinite;
|
||||
border-radius: 5px;
|
||||
margin: 0.1em;
|
||||
}
|
||||
|
||||
.load:nth-child(1) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.load:nth-child(2) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.load:nth-child(3) {
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
@keyframes move_5011 {
|
||||
0% {
|
||||
width: 0.2em;
|
||||
}
|
||||
|
||||
25% {
|
||||
width: 0.7em;
|
||||
}
|
||||
|
||||
50% {
|
||||
width: 1.5em;
|
||||
}
|
||||
|
||||
100% {
|
||||
width: 0.2em;
|
||||
}
|
||||
}
|
||||
</style>
|
78
src/components/async-loading/index.vue
Normal file
78
src/components/async-loading/index.vue
Normal file
@ -0,0 +1,78 @@
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="loader">
|
||||
<p class="heading">加载中</p>
|
||||
<div class="loading">
|
||||
<div class="load"></div>
|
||||
<div class="load"></div>
|
||||
<div class="load"></div>
|
||||
<div class="load"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.loader {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.heading {
|
||||
color: black;
|
||||
letter-spacing: 0.2em;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: flex;
|
||||
width: 5em;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.load {
|
||||
width: 23px;
|
||||
height: 3px;
|
||||
background-color: limegreen;
|
||||
animation: 1s move_5011 infinite;
|
||||
border-radius: 5px;
|
||||
margin: 0.1em;
|
||||
}
|
||||
|
||||
.load:nth-child(1) {
|
||||
animation-delay: 0.2s;
|
||||
}
|
||||
|
||||
.load:nth-child(2) {
|
||||
animation-delay: 0.4s;
|
||||
}
|
||||
|
||||
.load:nth-child(3) {
|
||||
animation-delay: 0.6s;
|
||||
}
|
||||
|
||||
@keyframes move_5011 {
|
||||
0% {
|
||||
width: 0.2em;
|
||||
}
|
||||
|
||||
25% {
|
||||
width: 0.7em;
|
||||
}
|
||||
|
||||
50% {
|
||||
width: 1.5em;
|
||||
}
|
||||
|
||||
100% {
|
||||
width: 0.2em;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -55,7 +55,7 @@ const getFileTypeIMG = computed(() => {
|
||||
default:
|
||||
objT.finishedImg = filePaperOther
|
||||
objT.blankImg = filePaperOtherBlank
|
||||
objT.progressColor = '#747474'
|
||||
objT.progressColor = '#46299d'
|
||||
}
|
||||
return objT
|
||||
})
|
||||
@ -132,7 +132,7 @@ const downloadAndOpenFile = () => {
|
||||
:height="95"
|
||||
:src="getFileTypeIMG.blankImg"
|
||||
></tm-image>
|
||||
<wd-circle
|
||||
<wd-circle v-if="data.uploadStatus === 1"
|
||||
customClass="circleProgress"
|
||||
:modelValue="data.uploadCurrent"
|
||||
layerColor="#E3E3E3"
|
||||
@ -140,6 +140,9 @@ const downloadAndOpenFile = () => {
|
||||
:strokeWidth="3"
|
||||
:size="20"
|
||||
></wd-circle>
|
||||
<div class="upload-failed" v-if="data.uploadStatus === 3">
|
||||
<tm-icon :font-size="20" name="tmicon-times" color="#fff"></tm-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider mt-[28rpx]"></div>
|
||||
@ -288,4 +291,20 @@ const downloadAndOpenFile = () => {
|
||||
width: 40rpx !important;
|
||||
height: 40rpx !important;
|
||||
}
|
||||
|
||||
.upload-failed {
|
||||
position: absolute;
|
||||
top: 120rpx;
|
||||
right: 52rpx;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 1;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ff4d4f;
|
||||
background: #ff4d4f;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
|
@ -12,40 +12,40 @@ const img = computed(() => {
|
||||
// console.log(props.extra);
|
||||
let info = {
|
||||
width: 0,
|
||||
height: 0
|
||||
height: 0,
|
||||
}
|
||||
if (props.extra.url.includes('blob:http://')) {
|
||||
info = {
|
||||
width: props.extra.width,
|
||||
height: props.extra.height
|
||||
height: props.extra.height,
|
||||
}
|
||||
}else {
|
||||
} else {
|
||||
info = getImageInfo(props.extra.url)
|
||||
}
|
||||
|
||||
if (info.width == 0 || info.height == 0) {
|
||||
return {
|
||||
width: 450,
|
||||
height: 298
|
||||
height: 298,
|
||||
}
|
||||
}
|
||||
if(info.width<300){
|
||||
if (info.width < 300) {
|
||||
return {
|
||||
width: 300,
|
||||
height: info.height / (info.width / 300)
|
||||
height: info.height / (info.width / 300),
|
||||
}
|
||||
}
|
||||
|
||||
if (info.width < 350) {
|
||||
return {
|
||||
width: info.width,
|
||||
height: info.height
|
||||
height: info.height,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
width: 350,
|
||||
height: info.height / (info.width / 350)
|
||||
height: info.height / (info.width / 350),
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@ -54,13 +54,30 @@ const img = computed(() => {
|
||||
class="im-message-image"
|
||||
:class="{
|
||||
left: data.float === 'left',
|
||||
right: data.float === 'right'
|
||||
right: data.float === 'right',
|
||||
}"
|
||||
>
|
||||
<div class="image-container">
|
||||
<tm-image preview :width="img.width" :height="img.height" :src="extra.url" model="aspectFill"/>
|
||||
<wd-circle custom-class="circleProgress" v-if="props.data.uploadCurrent && props.data.uploadCurrent<100" v-model="props.data.uploadCurrent" color="#ffffff" layer-color="#E3E3E3"></wd-circle>
|
||||
</div>
|
||||
<div class="image-container">
|
||||
<div class="relative">
|
||||
<tm-image
|
||||
preview
|
||||
:width="img.width"
|
||||
:height="img.height"
|
||||
:src="extra.url"
|
||||
model="aspectFill"
|
||||
/>
|
||||
<wd-circle
|
||||
custom-class="circleProgress"
|
||||
v-if="data.uploadStatus === 1"
|
||||
v-model="props.data.uploadCurrent"
|
||||
color="#46299d"
|
||||
layer-color="#E3E3E3"
|
||||
></wd-circle>
|
||||
<div class="upload-failed" v-if="data.uploadStatus === 3">
|
||||
<tm-icon :font-size="20" name="tmicon-times" color="#fff"></tm-icon>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
@ -79,12 +96,12 @@ const img = computed(() => {
|
||||
}
|
||||
|
||||
&.right {
|
||||
background-color: #46299D;
|
||||
background-color: #46299d;
|
||||
border-radius: 16rpx 0 16rpx 16rpx;
|
||||
}
|
||||
}
|
||||
.image-container {
|
||||
position: relative;
|
||||
position: relative;
|
||||
|
||||
.circleProgress {
|
||||
position: absolute;
|
||||
@ -94,4 +111,18 @@ const img = computed(() => {
|
||||
z-index: 1;
|
||||
}
|
||||
}
|
||||
.upload-failed {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 1;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #ff4d4f;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
|
@ -1,8 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, nextTick, getCurrentInstance, computed, onMounted } from 'vue'
|
||||
import { getImageInfo } from '@/utils/functions'
|
||||
import playCircle from "@/static/image/chatList/playCircle@2x.png";
|
||||
import { useStatus } from "@/store/status";
|
||||
import playCircle from '@/static/image/chatList/playCircle@2x.png'
|
||||
import { useStatus } from '@/store/status'
|
||||
|
||||
const { statusBarHeight } = useStatus()
|
||||
const instance = getCurrentInstance()
|
||||
@ -20,12 +20,12 @@ const open = ref(false)
|
||||
const img = computed(() => {
|
||||
let info = {
|
||||
width: 0,
|
||||
height: 0
|
||||
height: 0,
|
||||
}
|
||||
if (props.extra.url.includes('blob:http://')) {
|
||||
info = {
|
||||
width: props.extra.width,
|
||||
height: props.extra.height
|
||||
height: props.extra.height,
|
||||
}
|
||||
} else {
|
||||
info = getImageInfo(props.extra.url)
|
||||
@ -34,26 +34,26 @@ const img = computed(() => {
|
||||
if (info.width == 0 || info.height == 0) {
|
||||
return {
|
||||
width: 450,
|
||||
height: 298
|
||||
height: 298,
|
||||
}
|
||||
}
|
||||
if (info.width < 300) {
|
||||
return {
|
||||
width: 300,
|
||||
height: info.height / (info.width / 300)
|
||||
height: info.height / (info.width / 300),
|
||||
}
|
||||
}
|
||||
|
||||
if (info.width < 350) {
|
||||
return {
|
||||
width: info.width,
|
||||
height: info.height
|
||||
height: info.height,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
width: 350,
|
||||
height: info.height / (info.width / 350)
|
||||
height: info.height / (info.width / 350),
|
||||
}
|
||||
})
|
||||
|
||||
@ -67,57 +67,98 @@ const fullscreenchange = (e) => {
|
||||
|
||||
/* 视频播放 获取第一帧 */
|
||||
const canplay = (e) => {
|
||||
console.log('Video can play:', e);
|
||||
console.log('Video can play:', e)
|
||||
|
||||
if (e.target) {
|
||||
setTimeout(() => {
|
||||
e.target.pause();
|
||||
}, 200);
|
||||
e.target.pause()
|
||||
}, 200)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function onPlay() {
|
||||
videoContext.value = uni.createVideoContext(props.extra.url, instance);
|
||||
videoContext.value.requestFullScreen({ direction: 2 });
|
||||
videoContext.value.play()
|
||||
open.value = true
|
||||
await nextTick()
|
||||
videoContext.value = uni.createVideoContext(props.extra.url, instance)
|
||||
|
||||
setTimeout(() => {
|
||||
// 先请求全屏
|
||||
videoContext.value.requestFullScreen({ direction: 2 })
|
||||
|
||||
// 延迟一下再播放,确保全屏已经完成
|
||||
setTimeout(() => {
|
||||
videoContext.value.play()
|
||||
}, 100)
|
||||
}, 200)
|
||||
}
|
||||
onMounted(() => {
|
||||
videoRef.value = uni.createVideoContext(props.data.msg_id);
|
||||
videoRef.value = uni.createVideoContext(props.data.msg_id)
|
||||
videoRef.value.play()
|
||||
setTimeout(() => {
|
||||
videoRef.value.pause()
|
||||
}, 200);
|
||||
|
||||
}, 200)
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<section class="im-message-video" :class="{ left: data.float === 'left' }" @click="onPlay">
|
||||
<div class="coverVideo" :style="{
|
||||
width: img.width + 'rpx',
|
||||
height: img.height + 'rpx'
|
||||
}" v-if="props.extra.url.includes('blob:http://')">
|
||||
<video :id="data.msg_id" :autoplay="false" disablepictureinpicture muted :src="props.extra.url" width="100%"
|
||||
height="100%" playsinline preload="auto" controls="false" x5-playsinline
|
||||
webkit-playsinline style="object-fit: cover; pointer-events: none;">
|
||||
</video>
|
||||
<section
|
||||
class="im-message-video"
|
||||
:class="{ left: data.float === 'left' }"
|
||||
@click="onPlay"
|
||||
>
|
||||
<div
|
||||
class="coverVideo"
|
||||
:style="{
|
||||
width: img.width + 'rpx',
|
||||
height: img.height + 'rpx',
|
||||
}"
|
||||
v-if="props.extra.url.includes('blob:http://')"
|
||||
>
|
||||
<video
|
||||
:id="data.msg_id"
|
||||
:autoplay="false"
|
||||
disablepictureinpicture
|
||||
muted
|
||||
:src="props.extra.url"
|
||||
width="100%"
|
||||
height="100%"
|
||||
playsinline
|
||||
preload="auto"
|
||||
controls="false"
|
||||
x5-playsinline
|
||||
webkit-playsinline
|
||||
style="object-fit: cover; pointer-events: none;"
|
||||
></video>
|
||||
</div>
|
||||
<wd-img v-else :width="`${img.width}rpx`" :height="`${img.height}rpx`" :src="data.extra.cover" />
|
||||
<div v-if="data.uploadStatus === 2 || !data.uploadStatus" class="btn-video" :style="{
|
||||
width: img.width + 'rpx',
|
||||
height: img.height + 'rpx'
|
||||
}">
|
||||
<wd-img
|
||||
v-else
|
||||
:width="`${img.width}rpx`"
|
||||
:height="`${img.height}rpx`"
|
||||
:src="data.extra.cover"
|
||||
/>
|
||||
<div
|
||||
v-if="data.uploadStatus === 2 || !data.uploadStatus"
|
||||
class="btn-video"
|
||||
:style="{
|
||||
width: img.width + 'rpx',
|
||||
height: img.height + 'rpx',
|
||||
}"
|
||||
>
|
||||
<tm-image :src="playCircle" :width="80" :height="80" />
|
||||
</div>
|
||||
<div v-else class="btn-video" :style="{
|
||||
width: img.width + 'rpx',
|
||||
height: img.height + 'rpx'
|
||||
}" >
|
||||
<div
|
||||
v-else
|
||||
class="btn-video"
|
||||
:style="{
|
||||
width: img.width + 'rpx',
|
||||
height: img.height + 'rpx',
|
||||
}"
|
||||
>
|
||||
<wd-circle
|
||||
v-if="data.uploadStatus === 1"
|
||||
v-model="props.data.uploadCurrent"
|
||||
customClass="circleProgress"
|
||||
layerColor="#E3E3E3"
|
||||
color="#FFFFFF"
|
||||
color="#46299d"
|
||||
layer-color="#E3E3E3"
|
||||
:strokeWidth="6"
|
||||
:size="40"
|
||||
></wd-circle>
|
||||
@ -131,18 +172,32 @@ onMounted(() => {
|
||||
:width="70"
|
||||
:percent="props.data.uploadCurrent">
|
||||
</tm-progress> -->
|
||||
</div>
|
||||
<div v-show="open">
|
||||
<video :src="props.extra.url" controls @fullscreenchange="fullscreenchange" :id="props.extra.url">
|
||||
</video>
|
||||
<div class="upload-failed" v-if="data.uploadStatus === 3">
|
||||
<tm-icon :font-size="20" name="tmicon-times" color="#fff"></tm-icon>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<teleport to="body">
|
||||
<div v-show="open" class="video-container">
|
||||
<video
|
||||
:src="props.extra.url"
|
||||
controls
|
||||
@fullscreenchange="fullscreenchange"
|
||||
:id="props.extra.url"
|
||||
playsinline
|
||||
webkit-playsinline
|
||||
x5-playsinline
|
||||
class="fullscreen-video"
|
||||
></video>
|
||||
</div>
|
||||
</teleport>
|
||||
</template>
|
||||
<style lang="less" scoped>
|
||||
.im-message-video {
|
||||
overflow: hidden;
|
||||
padding: 20rpx 18rpx;
|
||||
background: #46299D;
|
||||
background: #46299d;
|
||||
min-width: 30rpx;
|
||||
min-height: 30rpx;
|
||||
display: inline-flex;
|
||||
@ -200,10 +255,43 @@ onMounted(() => {
|
||||
:deep(.uni-video-bar) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
}
|
||||
.circleProgress {
|
||||
width: 80rpx !important;
|
||||
height: 80rpx !important;
|
||||
}
|
||||
|
||||
.video-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: #000;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.fullscreen-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.upload-failed {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 1;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #ff4d4f;
|
||||
border-radius: 50%;
|
||||
}
|
||||
</style>
|
||||
|
@ -143,27 +143,32 @@ class Talk extends Base {
|
||||
!useTalkStore().items[useTalkStore().findTalkIndex(this.getIndexName())]
|
||||
?.is_disturb
|
||||
) {
|
||||
if (typeof plus !== 'undefined') {
|
||||
let OAWebView = plus.webview.all()
|
||||
OAWebView.forEach((webview) => {
|
||||
if (webview.id === 'webviewId1') {
|
||||
webview.evalJS(`updateUnreadMsgNumAdd()`)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
document.addEventListener('plusready', () => {
|
||||
let OAWebView = plus.webview.all()
|
||||
OAWebView.forEach((webview) => {
|
||||
if (webview.id === 'webviewId1') {
|
||||
webview.evalJS(`updateUnreadMsgNumAdd()`)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
this.updateUnreadMsgNumAdd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//更新未读数量+1
|
||||
updateUnreadMsgNumAdd() {
|
||||
if (typeof plus !== 'undefined') {
|
||||
let OAWebView = plus.webview.all()
|
||||
OAWebView.forEach((webview) => {
|
||||
if (webview.id === 'webviewId1') {
|
||||
webview.evalJS(`updateUnreadMsgNumAdd()`)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
document.addEventListener('plusready', () => {
|
||||
let OAWebView = plus.webview.all()
|
||||
OAWebView.forEach((webview) => {
|
||||
if (webview.id === 'webviewId1') {
|
||||
webview.evalJS(`updateUnreadMsgNumAdd()`)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示消息提示
|
||||
* @returns
|
||||
@ -206,7 +211,10 @@ class Talk extends Base {
|
||||
}).then(async ({ code, data }) => {
|
||||
if (code == 200) {
|
||||
let item = formatTalkItem(data)
|
||||
item.unread_num = 1
|
||||
if (!item?.is_disturb) {
|
||||
item.unread_num = 1
|
||||
this.updateUnreadMsgNumAdd()
|
||||
}
|
||||
useTalkStore().addItem(item)
|
||||
await useTalkStore().loadTalkList()
|
||||
}
|
||||
|
@ -11,7 +11,12 @@ import { reactive, nextTick, computed, h, inject } from 'vue'
|
||||
// EditTwo,
|
||||
// IdCard
|
||||
// } from '@icon-park/vue-next'
|
||||
import { ServeTopTalkList, ServeDeleteTalkList, ServeSetNotDisturb } from '@/api/chat'
|
||||
import {
|
||||
ServeTopTalkList,
|
||||
ServeDeleteTalkList,
|
||||
ServeSetNotDisturb,
|
||||
ServeClearTalkUnreadNum,
|
||||
} from '@/api/chat'
|
||||
import { useDialogueStore, useTalkStore, useDialogueListStore } from '@/store'
|
||||
import { ServeSecedeGroup } from '@/api/group'
|
||||
// import { ServeDeleteContact, ServeEditContactRemark } from '@/api/contact'
|
||||
@ -23,7 +28,7 @@ export function useSessionMenu() {
|
||||
show: false,
|
||||
x: 0,
|
||||
y: 0,
|
||||
item: {}
|
||||
item: {},
|
||||
})
|
||||
|
||||
const dialogueStore = useDialogueStore()
|
||||
@ -118,10 +123,22 @@ export function useSessionMenu() {
|
||||
// 移除会话
|
||||
const onRemoveTalk = (item) => {
|
||||
ServeDeleteTalkList({
|
||||
list_id: item.id
|
||||
list_id: item.id,
|
||||
}).then(({ code }) => {
|
||||
if (code == 200) {
|
||||
onDeleteTalk(item.index_name)
|
||||
console.error(item, 'item')
|
||||
if (item.unread_num > 0) {
|
||||
//同时已读
|
||||
ServeClearTalkUnreadNum(
|
||||
{
|
||||
talk_type: item.talk_type,
|
||||
receiver_id: item.receiver_id,
|
||||
},
|
||||
item.unread_num,
|
||||
).then(() => {
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -131,13 +148,13 @@ export function useSessionMenu() {
|
||||
ServeSetNotDisturb({
|
||||
talk_type: item.talk_type,
|
||||
receiver_id: item.receiver_id,
|
||||
is_disturb: item.is_disturb == 0 ? 1 : 0
|
||||
is_disturb: item.is_disturb == 0 ? 1 : 0,
|
||||
}).then(({ code, message }) => {
|
||||
if (code == 200) {
|
||||
message.success('设置成功!')
|
||||
talkStore.updateItem({
|
||||
index_name: item.index_name,
|
||||
is_disturb: item.is_disturb == 0 ? 1 : 0
|
||||
is_disturb: item.is_disturb == 0 ? 1 : 0,
|
||||
})
|
||||
} else {
|
||||
message.error(message)
|
||||
@ -153,12 +170,12 @@ export function useSessionMenu() {
|
||||
|
||||
ServeTopTalkList({
|
||||
list_id: item.id,
|
||||
type: item.is_top == 0 ? 1 : 2
|
||||
type: item.is_top == 0 ? 1 : 2,
|
||||
}).then(({ code, message }) => {
|
||||
if (code == 200) {
|
||||
talkStore.updateItem({
|
||||
index_name: item.index_name,
|
||||
is_top: item.is_top == 0 ? 1 : 0
|
||||
is_top: item.is_top == 0 ? 1 : 0,
|
||||
})
|
||||
} else {
|
||||
message.error(message)
|
||||
@ -201,7 +218,7 @@ export function useSessionMenu() {
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => {
|
||||
ServeSecedeGroup({
|
||||
group_id: item.receiver_id
|
||||
group_id: item.receiver_id,
|
||||
}).then(({ code, message }) => {
|
||||
if (code == 200) {
|
||||
message.success('已退出群聊')
|
||||
@ -210,7 +227,7 @@ export function useSessionMenu() {
|
||||
message.error(message)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
@ -259,12 +276,18 @@ export function useSessionMenu() {
|
||||
disturb: onSetDisturb,
|
||||
signout_group: onSignOutGroup,
|
||||
delete_contact: onDeleteContact,
|
||||
remark: onChangeRemark
|
||||
remark: onChangeRemark,
|
||||
}
|
||||
|
||||
dropdown.show = false
|
||||
evnets[key] && evnets[key](dropdown.item)
|
||||
}
|
||||
|
||||
return { dropdown, onCloseContextMenu, onContextMenuTalkHandle, onToTopTalk, onRemoveTalk }
|
||||
return {
|
||||
dropdown,
|
||||
onCloseContextMenu,
|
||||
onContextMenuTalkHandle,
|
||||
onToTopTalk,
|
||||
onRemoveTalk,
|
||||
}
|
||||
}
|
||||
|
@ -10,6 +10,8 @@ import tmui from '@/uni_modules/tmui'
|
||||
import { config } from '@/config/tmui/index.js'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
import xLoaderror from '@/components/x-loaderror/index.vue'
|
||||
import asyncLoading from '@/components/async-loading/index.vue'
|
||||
import asyncError from '@/components/async-error/index.vue'
|
||||
import { vLoading } from '@/components/x-loading/index.js'
|
||||
import messagePopup from '@/components/x-message/useMessagePopup'
|
||||
import pageAnimation from '@/components/page-animation/index.vue'
|
||||
@ -29,6 +31,8 @@ export function createApp() {
|
||||
app.mixin(pageAnimation)
|
||||
app.component('customNavbar', customNavbar)
|
||||
app.component('x-loaderror', xLoaderror)
|
||||
app.component('AsyncLoading', asyncLoading)
|
||||
app.component('AsyncError', asyncError)
|
||||
app.directive('no-space', {
|
||||
mounted(el) {
|
||||
el.addEventListener('input', (e) => {
|
||||
|
@ -101,6 +101,11 @@ const photoActionsSelect = (index) => {
|
||||
success: async (res) => {
|
||||
console.log(res, 'res')
|
||||
res.tempFiles.forEach(async (file) => {
|
||||
const fileSizeInMB = (file.size / (1024 * 1024)).toFixed(2)
|
||||
if (fileSizeInMB > 100) {
|
||||
plus.nativeUI.toast('图片大小不能超过100MB')
|
||||
return
|
||||
}
|
||||
let data = await onUploadImageVideo(file, 'image')
|
||||
emit('selectImg', data, data.file_num)
|
||||
})
|
||||
@ -121,6 +126,13 @@ const photoActionsSelect = (index) => {
|
||||
fileReader.onloadend = async (e) => {
|
||||
const base64Url = e.target.result
|
||||
const fileObj = base64ToFile(base64Url)
|
||||
const fileSizeInMB = (fileObj.size / (1024 * 1024)).toFixed(
|
||||
2,
|
||||
)
|
||||
if (fileSizeInMB > 100) {
|
||||
plus.nativeUI.toast('图片大小不能超过100MB')
|
||||
return
|
||||
}
|
||||
let data = await onUploadImageVideo(fileObj, 'image')
|
||||
emit('selectImg', data, data.file_num)
|
||||
}
|
||||
@ -152,6 +164,11 @@ const photoActionsSelect = (index) => {
|
||||
maxDuration: 60,
|
||||
success: async (res) => {
|
||||
console.log(res, 'res')
|
||||
const fileSizeInMB = (res.tempFile.size / (1024 * 1024)).toFixed(2)
|
||||
if (fileSizeInMB > 100) {
|
||||
plus.nativeUI.toast('视频大小不能超过100MB')
|
||||
return
|
||||
}
|
||||
let data = await onUploadImageVideo(
|
||||
res.tempFile,
|
||||
'video',
|
||||
@ -164,12 +181,13 @@ const photoActionsSelect = (index) => {
|
||||
}
|
||||
|
||||
const onUploadImageVideo = async (file, type = 'image', fileUrl) => {
|
||||
console.log(file, 'file')
|
||||
console.log('开始上传文件:', file.name)
|
||||
uploadsStore.updateUploadStatus(true)
|
||||
return new Promise(async (resolve) => {
|
||||
if (type === 'image') {
|
||||
let image = new Image()
|
||||
image.src = URL.createObjectURL(file)
|
||||
image.onload = () => {
|
||||
image.onload = async () => {
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
form.append('source', 'fonchain-chat')
|
||||
@ -203,40 +221,62 @@ const onUploadImageVideo = async (file, type = 'image', fileUrl) => {
|
||||
}
|
||||
|
||||
virtualList.value.unshift(newItem)
|
||||
uploadImg(form, (e) => onProgressFn(e, randomId)).then(
|
||||
({ status, data, msg }) => {
|
||||
if (status == 0) {
|
||||
// 更新上传状态为成功
|
||||
const index = virtualList.value.findIndex(item => item.file_num === randomId)
|
||||
if (index !== -1) {
|
||||
virtualList.value[index].uploadStatus = 2
|
||||
virtualList.value[index].uploadCurrent = 100
|
||||
}
|
||||
resolve({
|
||||
type: 'image',
|
||||
url: data.ori_url,
|
||||
size: file.size,
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
file_num: randomId,
|
||||
})
|
||||
} else {
|
||||
// 更新上传状态为失败
|
||||
const index = virtualList.value.findIndex(item => item.file_num === randomId)
|
||||
if (index !== -1) {
|
||||
virtualList.value[index].uploadStatus = 3
|
||||
}
|
||||
resolve('')
|
||||
message.error(msg)
|
||||
|
||||
try {
|
||||
const result = await uploadImg(form, (e) => onProgressFn(e, randomId))
|
||||
console.log('上传完成,结果:', result)
|
||||
|
||||
if (result.status === 0) {
|
||||
// 更新上传状态为成功
|
||||
const index = virtualList.value.findIndex(
|
||||
(item) => item.file_num === randomId,
|
||||
)
|
||||
if (index !== -1) {
|
||||
virtualList.value[index].uploadStatus = 2
|
||||
virtualList.value[index].uploadCurrent = 100
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
// 确保返回数据
|
||||
resolve({
|
||||
type: 'image',
|
||||
url: result.data.ori_url,
|
||||
size: file.size,
|
||||
width: image.width,
|
||||
height: image.height,
|
||||
file_num: randomId,
|
||||
})
|
||||
} else {
|
||||
uploadsStore.updateUploadStatus(false)
|
||||
// 更新上传状态为失败
|
||||
const index = virtualList.value.findIndex(
|
||||
(item) => item.file_num === randomId,
|
||||
)
|
||||
if (index !== -1) {
|
||||
virtualList.value[index].uploadStatus = 3
|
||||
}
|
||||
message.error(result.msg)
|
||||
resolve('')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('上传出错:', error)
|
||||
uploadsStore.updateUploadStatus(false)
|
||||
// 更新上传状态为失败
|
||||
const index = virtualList.value.findIndex(
|
||||
(item) => item.file_num === randomId,
|
||||
)
|
||||
if (index !== -1) {
|
||||
virtualList.value[index].uploadStatus = 3
|
||||
}
|
||||
message.error('上传失败')
|
||||
resolve('')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uni.getVideoInfo({
|
||||
src: fileUrl,
|
||||
success: (resp) => {
|
||||
console.log(resp)
|
||||
success: async (resp) => {
|
||||
console.log('视频信息:', resp)
|
||||
const form = new FormData()
|
||||
form.append('file', file)
|
||||
form.append('source', 'fonchain-chat')
|
||||
form.append('type', 'video')
|
||||
@ -266,41 +306,65 @@ const onUploadImageVideo = async (file, type = 'image', fileUrl) => {
|
||||
talk_type: dialogueStore.talk.talk_type,
|
||||
user_id: userStore.uid,
|
||||
uploadCurrent: 0,
|
||||
uploadStatus: 1, // 1 上传中 2 上传成功 3 上传失败
|
||||
uploadStatus: 1,
|
||||
}
|
||||
virtualList.value.unshift(newItem)
|
||||
uploadImg(form, (e) => onProgressFn(e, randomId)).then(
|
||||
({ status, data, msg }) => {
|
||||
if (status == 0) {
|
||||
// 更新上传状态为成功
|
||||
const index = virtualList.value.findIndex(item => item.file_num === randomId)
|
||||
if (index !== -1) {
|
||||
virtualList.value[index].uploadStatus = 2
|
||||
virtualList.value[index].uploadCurrent = 100
|
||||
}
|
||||
console.log(data)
|
||||
resolve({
|
||||
type: 'video',
|
||||
url: data.ori_url,
|
||||
cover: data.cover_url,
|
||||
duration: parseInt(resp.duration),
|
||||
size: file.size,
|
||||
file_num: randomId,
|
||||
})
|
||||
} else {
|
||||
// 更新上传状态为失败
|
||||
const index = virtualList.value.findIndex(item => item.file_num === randomId)
|
||||
if (index !== -1) {
|
||||
virtualList.value[index].uploadStatus = 3
|
||||
}
|
||||
resolve('')
|
||||
message.error(msg)
|
||||
|
||||
try {
|
||||
const result = await uploadImg(form, (e) => onProgressFn(e, randomId))
|
||||
console.log('视频上传完成,结果:', result)
|
||||
|
||||
if (result.status === 0) {
|
||||
// 更新上传状态为成功
|
||||
const index = virtualList.value.findIndex(
|
||||
(item) => item.file_num === randomId,
|
||||
)
|
||||
if (index !== -1) {
|
||||
virtualList.value[index].uploadStatus = 2
|
||||
virtualList.value[index].uploadCurrent = 100
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
resolve({
|
||||
type: 'video',
|
||||
url: result.data.ori_url,
|
||||
cover: result.data.cover_url,
|
||||
duration: parseInt(resp.duration),
|
||||
size: file.size,
|
||||
file_num: randomId,
|
||||
})
|
||||
} else {
|
||||
uploadsStore.updateUploadStatus(false)
|
||||
// 更新上传状态为失败
|
||||
const index = virtualList.value.findIndex(
|
||||
(item) => item.file_num === randomId,
|
||||
)
|
||||
if (index !== -1) {
|
||||
virtualList.value[index].uploadStatus = 3
|
||||
}
|
||||
message.error(result.msg)
|
||||
resolve('')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('视频上传出错:', error)
|
||||
uploadsStore.updateUploadStatus(false)
|
||||
// 更新上传状态为失败
|
||||
const index = virtualList.value.findIndex(
|
||||
(item) => item.file_num === randomId,
|
||||
)
|
||||
if (index !== -1) {
|
||||
virtualList.value[index].uploadStatus = 3
|
||||
}
|
||||
message.error('上传失败')
|
||||
resolve('')
|
||||
}
|
||||
},
|
||||
fail: (error) => {
|
||||
console.error('获取视频信息失败:', error)
|
||||
uploadsStore.updateUploadStatus(false)
|
||||
message.error('获取视频信息失败')
|
||||
resolve('')
|
||||
}
|
||||
})
|
||||
const form = new FormData()
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -451,6 +515,11 @@ const chooseFile = () => {
|
||||
count: 1,
|
||||
extension: [''],
|
||||
success: (res) => {
|
||||
const fileSizeInMB = (res.tempFiles[0].size / (1024 * 1024)).toFixed(2)
|
||||
if (fileSizeInMB > 100) {
|
||||
plus.nativeUI.toast('文件大小不能超过100MB')
|
||||
return
|
||||
}
|
||||
let randomId = uniqueId()
|
||||
let newItem = {
|
||||
avatar: userStore.avatar,
|
||||
@ -478,23 +547,34 @@ const chooseFile = () => {
|
||||
uploadStatus: 1, // 1 上传中 2 上传成功 3 上传失败
|
||||
}
|
||||
virtualList.value.unshift(newItem)
|
||||
uploadsStore.initUploadFile(res.tempFiles[0], props.talkParams, randomId, (status, data, msg) => {
|
||||
if (status === 0) {
|
||||
// 更新上传状态为成功
|
||||
const index = virtualList.value.findIndex(item => item.file_num === randomId)
|
||||
if (index !== -1) {
|
||||
virtualList.value[index].uploadStatus = 2
|
||||
virtualList.value[index].uploadCurrent = 100
|
||||
uploadsStore.updateUploadStatus(true)
|
||||
uploadsStore.initUploadFile(
|
||||
res.tempFiles[0],
|
||||
props.talkParams,
|
||||
randomId,
|
||||
(status, data, msg) => {
|
||||
if (status === 0) {
|
||||
// 更新上传状态为成功
|
||||
const index = virtualList.value.findIndex(
|
||||
(item) => item.file_num === randomId,
|
||||
)
|
||||
if (index !== -1) {
|
||||
virtualList.value[index].uploadStatus = 2
|
||||
virtualList.value[index].uploadCurrent = 100
|
||||
}
|
||||
} else {
|
||||
uploadsStore.updateUploadStatus(false)
|
||||
// 更新上传状态为失败
|
||||
const index = virtualList.value.findIndex(
|
||||
(item) => item.file_num === randomId,
|
||||
)
|
||||
if (index !== -1) {
|
||||
virtualList.value[index].uploadStatus = 3
|
||||
}
|
||||
message.error(msg)
|
||||
}
|
||||
} else {
|
||||
// 更新上传状态为失败
|
||||
const index = virtualList.value.findIndex(item => item.file_num === randomId)
|
||||
if (index !== -1) {
|
||||
virtualList.value[index].uploadStatus = 3
|
||||
}
|
||||
message.error(msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
|
@ -22,7 +22,12 @@
|
||||
:hideBack="dialogueStore.isOpenMultiSelect"
|
||||
>
|
||||
<template #left v-if="dialogueStore.isOpenMultiSelect">
|
||||
<text class="ml-[36rpx]" @click="dialogueStore.isOpenMultiSelect = false">取消</text>
|
||||
<text
|
||||
class="ml-[36rpx]"
|
||||
@click="dialogueStore.isOpenMultiSelect = false"
|
||||
>
|
||||
取消
|
||||
</text>
|
||||
</template>
|
||||
<template
|
||||
#subTitle
|
||||
@ -211,17 +216,28 @@
|
||||
</div>
|
||||
<template #bottom>
|
||||
<div class="footBox" id="footBoxArea">
|
||||
<span
|
||||
class="flex items-center justify-center text-[24rpx] text-[#999999]"
|
||||
style="background-color: #e5e5e5; padding: 12rpx 24rpx;"
|
||||
v-if="uploadsParams.isUploading"
|
||||
>
|
||||
{{
|
||||
'正在发送中,剩余' +
|
||||
uploadsParams.uploadingNum +
|
||||
'个...请不要离开哦~'
|
||||
}}
|
||||
</span>
|
||||
<div v-if="!dialogueStore.isOpenMultiSelect">
|
||||
<div
|
||||
class="pt-[16rpx] ml-[32rpx] mr-[32rpx] flex items-start justify-between"
|
||||
>
|
||||
<div class="flex-1 quillBox">
|
||||
<div class="flex-1 quillBox" style="">
|
||||
<QuillEditor
|
||||
ref="editor"
|
||||
id="editor"
|
||||
:options="editorOption"
|
||||
@editorChange="onEditorChange"
|
||||
style="width: 100%; flex: 1; height: 100%; border: none;"
|
||||
style="width: 100%; flex: 1; height: 100%;"
|
||||
@click="onEditorClick"
|
||||
/>
|
||||
<!-- <tm-input type=textarea autoHeight focusColor="#F9F9F9" color="#F9F9F9" :inputPadding="[12]"
|
||||
@ -238,7 +254,7 @@
|
||||
}}
|
||||
</span>
|
||||
<span
|
||||
v-if="state?.quoteInfo"
|
||||
v-if="state?.quoteInfo?.msg_type !== 1"
|
||||
class="text-[28rpx] text-[#999]"
|
||||
>
|
||||
{{
|
||||
@ -535,6 +551,7 @@ const settingsStore = useSettingsStore()
|
||||
const userStore = useUserStore()
|
||||
const dialogueStore = useDialogueStore()
|
||||
const editorDraftStore = useEditorDraftStore()
|
||||
const uploadsStore = useUploadsStore()
|
||||
const editor = ref()
|
||||
const zpagingRef = ref()
|
||||
useZPaging(zpagingRef)
|
||||
@ -554,6 +571,11 @@ const talkParams = reactive({
|
||||
unReadNum: computed(() => dialogueStore.unreadNum),
|
||||
})
|
||||
|
||||
const uploadsParams = reactive({
|
||||
isUploading: computed(() => uploadsStore.isUploading),
|
||||
uploadingNum: computed(() => uploadsStore.uploadingNum),
|
||||
})
|
||||
|
||||
const state = ref({
|
||||
isOpenEmojiPanel: false,
|
||||
isOpenFilePanel: false,
|
||||
@ -639,6 +661,9 @@ const handleHidePanel = () => {
|
||||
const onEditorClick = () => {
|
||||
handleHidePanel()
|
||||
const quill = getQuill()
|
||||
// if (quill.getText().endsWith('@\n')) {
|
||||
// showMentionSelectDebounced(quill)
|
||||
// }
|
||||
quill.focus()
|
||||
}
|
||||
|
||||
@ -654,14 +679,17 @@ const onSendMessage = (data = {}, callBack) => {
|
||||
ServePublishMessage(message)
|
||||
.then(({ code, message }) => {
|
||||
if (code == 200) {
|
||||
uploadsStore.updateUploadStatus(false)
|
||||
if (callBack) {
|
||||
callBack(true)
|
||||
}
|
||||
} else {
|
||||
uploadsStore.updateUploadStatus(false)
|
||||
message.warning(message)
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
uploadsStore.updateUploadStatus(false)
|
||||
message.warning('网络繁忙,请稍后重试!')
|
||||
})
|
||||
}
|
||||
@ -688,8 +716,11 @@ const onSendMessageClick = () => {
|
||||
|
||||
switch (data.msgType) {
|
||||
case 1: // 文字消息
|
||||
if (data.items[0].content.trim() === '') {
|
||||
return
|
||||
}
|
||||
if (data.items[0].content.length > 1024) {
|
||||
return message.info('发送内容超长,请分条发送')
|
||||
return message.warning('发送内容超长,请分条发送')
|
||||
}
|
||||
onSendTextEvent({
|
||||
data,
|
||||
@ -816,6 +847,9 @@ const onEmoticonEvent = (data) => {
|
||||
}
|
||||
|
||||
const onEditorChange = () => {
|
||||
if (getQuill().getText() !== state.value.lastMentionText) {
|
||||
state.value.lastMentionTriggered = false
|
||||
}
|
||||
let delta = getQuill().getContents()
|
||||
let text = deltaToString(delta)
|
||||
|
||||
@ -1558,6 +1592,9 @@ const isLeader = computed(() => {
|
||||
//长按头像@用户
|
||||
const doMentionUser = (mentionSelect) => {
|
||||
console.log(mentionSelect)
|
||||
if (talkParams.type === 1) {
|
||||
return
|
||||
}
|
||||
// 构造正确的 mention 对象
|
||||
const mentionObj = {
|
||||
id: mentionSelect.user_id, // 使用 user_id 而不是 erp_user_id
|
||||
@ -1632,29 +1669,23 @@ onUnmounted(() => {
|
||||
if (state.value.showMentionSelectTimer) {
|
||||
clearTimeout(state.value.showMentionSelectTimer)
|
||||
}
|
||||
if (uploadsStore.isUploading) {
|
||||
uploadsStore.clearUpload()
|
||||
}
|
||||
})
|
||||
|
||||
// 修改防抖函数的实现
|
||||
const showMentionSelectDebounced = (quill) => {
|
||||
const text = quill.getText()
|
||||
// 如果文本内容与上次不同,重置触发状态
|
||||
console.log(
|
||||
'lastMentionText',
|
||||
state.value.lastMentionText,
|
||||
state.value.lastMentionText.length,
|
||||
)
|
||||
console.log('text', text, text.length)
|
||||
console.log(state.value.lastMentionTriggered)
|
||||
|
||||
if (text !== state.value.lastMentionText) {
|
||||
state.value.lastMentionTriggered = false
|
||||
}
|
||||
if (text.length === 2) {
|
||||
state.value.lastMentionText = text
|
||||
state.value.lastMentionTriggered = true
|
||||
state.value.isShowMentionSelect = true
|
||||
}
|
||||
|
||||
// 如果已经触发过,则不再触发
|
||||
if (state.value.lastMentionTriggered) {
|
||||
console.log('return')
|
||||
return
|
||||
}
|
||||
|
||||
@ -1718,13 +1749,16 @@ const showMentionSelectDebounced = (quill) => {
|
||||
justify-content: space-between;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
|
||||
span {
|
||||
display: -webkit-inline-box;
|
||||
text-overflow: ellipsis;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
width: 100%;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
img {
|
||||
margin: 0 0 0 30rpx;
|
||||
flex-shrink: 0;
|
||||
@ -1937,6 +1971,7 @@ const showMentionSelectDebounced = (quill) => {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
|
||||
:deep(.ql-clipboard) {
|
||||
position: relative;
|
||||
opacity: 0;
|
||||
@ -1988,6 +2023,7 @@ const showMentionSelectDebounced = (quill) => {
|
||||
z-index: 1;
|
||||
/* 确保 z-index 低于 deepBubble */
|
||||
}
|
||||
|
||||
.divider {
|
||||
width: 100%;
|
||||
height: 1rpx;
|
||||
@ -2000,20 +2036,24 @@ const showMentionSelectDebounced = (quill) => {
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 36rpx 32rpx 0;
|
||||
|
||||
.cancel-btns {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.hide-btn {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
|
||||
text {
|
||||
}
|
||||
|
||||
img {
|
||||
width: 18rpx;
|
||||
height: 10rpx;
|
||||
@ -2030,6 +2070,7 @@ const showMentionSelectDebounced = (quill) => {
|
||||
background-color: #f3f3f3;
|
||||
border-radius: 8rpx;
|
||||
flex-shrink: 0;
|
||||
|
||||
span {
|
||||
color: #bababa;
|
||||
line-height: 40rpx;
|
||||
@ -2039,6 +2080,7 @@ const showMentionSelectDebounced = (quill) => {
|
||||
|
||||
.mention-done-btn-can-do {
|
||||
background-color: #46299d;
|
||||
|
||||
span {
|
||||
color: #fff;
|
||||
}
|
||||
@ -2050,11 +2092,13 @@ const showMentionSelectDebounced = (quill) => {
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
flex-shrink: 0;
|
||||
|
||||
span {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.mention) {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { ServeGetForwardRecords } from '@/api/chat'
|
||||
import { MessageComponents } from '@/constant/message'
|
||||
@ -47,12 +48,12 @@ onMounted(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="outer-layer">
|
||||
<div>
|
||||
<tm-navbar :hideBack="false" hideHome :title="`${title}的会话记录`" >
|
||||
</tm-navbar>
|
||||
</div>
|
||||
<div class="main-box">
|
||||
<div class="forward-record-page">
|
||||
<zPaging ref="zPaging" :show-scrollbar="false">
|
||||
<template #top>
|
||||
<customNavbar :title="`${title}的会话记录`"></customNavbar>
|
||||
</template>
|
||||
<div class="main-box">
|
||||
<div v-if="items.length === 0" class="flex justify-center items-center w-full mt-[200rpx]">
|
||||
<wd-loading />
|
||||
</div>
|
||||
@ -76,16 +77,15 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</zPaging>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.outer-layer {
|
||||
overflow-y: auto;
|
||||
.forward-record-page {
|
||||
flex: 1;
|
||||
background-image: url("@/static/image/clockIn/z3280@3x.png");
|
||||
background-size: cover;
|
||||
padding: 0 66rpx 20rpx 50rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
@ -96,7 +96,7 @@ onMounted(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
padding-top: 28rpx;
|
||||
padding: 28rpx 66rpx 20rpx 50rpx;
|
||||
}
|
||||
|
||||
.message-item {
|
||||
|
@ -265,6 +265,7 @@ const handleDelete = () => {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.divider {
|
||||
|
@ -9,7 +9,7 @@
|
||||
:default-page-size="props.searchResultPageSize"
|
||||
:loading-more-default-as-loading="true"
|
||||
:inside-more="true"
|
||||
:empty-view-img="'@/static/image/search/search-no-data.png'"
|
||||
:empty-view-img="searchNoData"
|
||||
:empty-view-text="$t('search.hint')"
|
||||
:empty-view-img-style="{ width: '476rpx', height: '261rpx' }"
|
||||
:empty-view-title-style="{
|
||||
@ -113,6 +113,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import searchNoData from '@/static/image/search/search-no-data.png'
|
||||
import customInput from '@/components/custom-input/custom-input.vue'
|
||||
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
|
||||
import useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js'
|
||||
|
@ -17,7 +17,9 @@ import { ServeSeachQueryAll, ServeGetSessionId } from '@/api/search/index'
|
||||
import { onMounted } from 'vue'
|
||||
import { handleSetWebviewStyle } from '@/utils/common'
|
||||
|
||||
import { useDialogueStore } from '@/store'
|
||||
import { useDialogueStore, useTalkStore } from '@/store'
|
||||
import { ServeCreateTalkList } from '@/api/chat/index.js'
|
||||
import { formatTalkItem } from '@/utils/talk'
|
||||
|
||||
const dialogueStore = useDialogueStore()
|
||||
|
||||
@ -50,6 +52,18 @@ const clickSearchItem = async (
|
||||
console.log(talk_type, receiver_id)
|
||||
const sessionId = await getSessionId(talk_type, receiver_id)
|
||||
if (searchResultKey === 'user_infos') {
|
||||
if (useTalkStore().findTalkIndex(`${talk_type}_${receiver_id}`) === -1) {
|
||||
ServeCreateTalkList({
|
||||
talk_type,
|
||||
receiver_id,
|
||||
erp_user_id: result.erp_user_id,
|
||||
}).then(async ({ code, data }) => {
|
||||
if (code == 200) {
|
||||
let item = formatTalkItem(data)
|
||||
useTalkStore().addItem(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
dialogueStore.setDialogue({
|
||||
name: result.nickname,
|
||||
talk_type: 1,
|
||||
@ -65,7 +79,7 @@ const clickSearchItem = async (
|
||||
receiver_id: result.group_id || result.id,
|
||||
})
|
||||
uni.navigateTo({
|
||||
url: '/pages/dialog/index?sessionId=' + sessionId
|
||||
url: '/pages/dialog/index?sessionId=' + sessionId,
|
||||
})
|
||||
} else if (searchResultKey === 'general_infos') {
|
||||
uni.navigateTo({
|
||||
|
@ -25,7 +25,9 @@ import {
|
||||
} from '@/api/search/index'
|
||||
import { reactive } from 'vue'
|
||||
|
||||
import { useDialogueStore } from '@/store'
|
||||
import { useDialogueStore, useTalkStore } from '@/store'
|
||||
import { ServeCreateTalkList } from '@/api/chat/index.js'
|
||||
import { formatTalkItem } from '@/utils/talk'
|
||||
|
||||
const dialogueStore = useDialogueStore()
|
||||
|
||||
@ -75,7 +77,13 @@ onLoad((options) => {
|
||||
})
|
||||
|
||||
//分页查询时,最后一条id变化
|
||||
const lastIdChange = (last_id, last_group_id, last_member_id, last_receiver_user_name, last_receiver_group_name) => {
|
||||
const lastIdChange = (
|
||||
last_id,
|
||||
last_group_id,
|
||||
last_member_id,
|
||||
last_receiver_user_name,
|
||||
last_receiver_group_name,
|
||||
) => {
|
||||
let idChanges = {
|
||||
last_id,
|
||||
last_group_id,
|
||||
@ -108,6 +116,18 @@ const clickSearchItem = async (
|
||||
console.log(talk_type, receiver_id)
|
||||
const sessionId = await getSessionId(talk_type, receiver_id)
|
||||
if (state.searchResultKey === 'user_infos') {
|
||||
if (useTalkStore().findTalkIndex(`${talk_type}_${receiver_id}`) === -1) {
|
||||
ServeCreateTalkList({
|
||||
talk_type,
|
||||
receiver_id,
|
||||
erp_user_id: result.erp_user_id,
|
||||
}).then(async ({ code, data }) => {
|
||||
if (code == 200) {
|
||||
let item = formatTalkItem(data)
|
||||
useTalkStore().addItem(item)
|
||||
}
|
||||
})
|
||||
}
|
||||
dialogueStore.setDialogue({
|
||||
name: result.nickname,
|
||||
talk_type: 1,
|
||||
|
@ -168,7 +168,10 @@
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="item?.msg_type === 5">
|
||||
<div class="video-preview" @click="onPlay(item?.extra?.url)">
|
||||
<div
|
||||
class="video-preview"
|
||||
@click="onPlay(item?.extra?.url)"
|
||||
>
|
||||
<tm-image
|
||||
:src="item?.extra?.cover"
|
||||
model="aspectFill"
|
||||
@ -264,18 +267,20 @@
|
||||
</div>
|
||||
</div>
|
||||
</ZPaging>
|
||||
<div v-show="open">
|
||||
<video
|
||||
:src="currentVideoUrl"
|
||||
controls
|
||||
@fullscreenchange="fullscreenchange"
|
||||
:id="currentVideoUrl"
|
||||
playsinline
|
||||
webkit-playsinline
|
||||
x5-playsinline
|
||||
>
|
||||
</video>
|
||||
</div>
|
||||
<teleport to="body">
|
||||
<div v-show="open" class="video-container">
|
||||
<video
|
||||
:src="currentVideoUrl"
|
||||
controls
|
||||
@fullscreenchange="fullscreenchange"
|
||||
:id="currentVideoUrl"
|
||||
playsinline
|
||||
webkit-playsinline
|
||||
x5-playsinline
|
||||
class="fullscreen-video"
|
||||
></video>
|
||||
</div>
|
||||
</teleport>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@ -285,7 +290,7 @@ import fileType_EXCEL from '@/static/image/search/fileType_EXCEL.png'
|
||||
import fileType_WORD from '@/static/image/search/fileType_WORD.png'
|
||||
import fileType_PDF from '@/static/image/search/fileType_PDF.png'
|
||||
import fileType_Files from '@/static/image/search/fileType_Files.png'
|
||||
import playCircle from "@/static/image/chatList/playCircle@2x.png"
|
||||
import playCircle from '@/static/image/chatList/playCircle@2x.png'
|
||||
import { fileFormatSize, fileSuffix } from '@/utils/strings'
|
||||
import searchItem from '../components/searchItem.vue'
|
||||
import customInput from '@/components/custom-input/custom-input.vue'
|
||||
@ -353,13 +358,15 @@ async function onPlay(url) {
|
||||
// 创建新的视频上下文
|
||||
videoContext.value = uni.createVideoContext(url, getCurrentInstance())
|
||||
|
||||
// 先请求全屏
|
||||
videoContext.value.requestFullScreen({ direction: 2 })
|
||||
|
||||
// 延迟一下再播放,确保全屏已经完成
|
||||
setTimeout(() => {
|
||||
videoContext.value.play()
|
||||
}, 100)
|
||||
// 先请求全屏
|
||||
videoContext.value.requestFullScreen({ direction: 2 })
|
||||
|
||||
// 延迟一下再播放,确保全屏已经完成
|
||||
setTimeout(() => {
|
||||
videoContext.value.play()
|
||||
}, 100)
|
||||
}, 200)
|
||||
}
|
||||
|
||||
onLoad((options) => {
|
||||
@ -561,7 +568,7 @@ const cancelSearch = () => {
|
||||
})
|
||||
} else {
|
||||
uni.reLaunch({
|
||||
url: '/pages/index/index'
|
||||
url: '/pages/index/index',
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -904,7 +911,7 @@ body::v-deep .round-3 {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img{
|
||||
img {
|
||||
width: 80rpx !important;
|
||||
height: 80rpx !important;
|
||||
}
|
||||
@ -918,4 +925,23 @@ body::v-deep .round-3 {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.video-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
background: #000;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.fullscreen-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
|
@ -33,7 +33,9 @@ export const useUploadsStore = defineStore('uploads', {
|
||||
state: () => {
|
||||
return {
|
||||
isShow: false,
|
||||
items: []
|
||||
items: [],
|
||||
isUploading: false,//当前是否正在上传
|
||||
uploadingNum: 0//当前正在上传数量
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
@ -74,7 +76,11 @@ export const useUploadsStore = defineStore('uploads', {
|
||||
|
||||
this.triggerUpload(upload_id,msgId)
|
||||
this.isShow = true
|
||||
} else {
|
||||
this.updateUploadStatus(false)
|
||||
}
|
||||
}).catch(()=> {
|
||||
this.updateUploadStatus(false)
|
||||
})
|
||||
},
|
||||
|
||||
@ -128,6 +134,7 @@ export const useUploadsStore = defineStore('uploads', {
|
||||
this.triggerUpload(uploadId, msgId)
|
||||
}
|
||||
} else {
|
||||
this.updateUploadStatus(false)
|
||||
item.status = 3
|
||||
// 更新虚拟列表中的状态为失败
|
||||
const { virtualList } = useDialogueListStore()
|
||||
@ -138,6 +145,7 @@ export const useUploadsStore = defineStore('uploads', {
|
||||
}
|
||||
})
|
||||
.catch(() => {
|
||||
this.updateUploadStatus(false)
|
||||
item.status = 3
|
||||
// 更新虚拟列表中的状态为失败
|
||||
const { virtualList } = useDialogueListStore()
|
||||
@ -155,7 +163,38 @@ export const useUploadsStore = defineStore('uploads', {
|
||||
receiver_id: item.receiver_id,
|
||||
talk_type: item.talk_type,
|
||||
file_num: file_num
|
||||
}).then((res) => {
|
||||
console.log(res, 'res')
|
||||
if(res.code == 200){
|
||||
this.updateUploadStatus(false)
|
||||
}else{
|
||||
this.updateUploadStatus(false)
|
||||
}
|
||||
}).catch(() => {
|
||||
this.updateUploadStatus(false)
|
||||
})
|
||||
},
|
||||
|
||||
//更新资源上传状态
|
||||
updateUploadStatus(isUploading: boolean){
|
||||
if(isUploading){
|
||||
this.uploadingNum++
|
||||
this.isUploading = true
|
||||
}else{
|
||||
this.uploadingNum--
|
||||
if(this.uploadingNum < 0){
|
||||
this.uploadingNum = 0
|
||||
}
|
||||
if(this.uploadingNum === 0){
|
||||
this.isUploading = false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 清除上传
|
||||
clearUpload(){
|
||||
this.isUploading = false
|
||||
this.uploadingNum = 0
|
||||
}
|
||||
}
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user