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

* 'main' of https://gitea-inner.fontree.cn/scout666/chat-app:
  处理文件上传中友好提示;处理文件上传失败场景图标展示;解决图片上传loading问题;替换所有进度条颜色更醒目;
  处理聊天未读消息数量统计、应用角标、视频预览、切换账号清除缓存问题
  解决删除会话后发消息,数量统计不正确问题;解决图片显示不正常问题
  处理未读消息数量显示、删除回话自动已读、显示样式等问题
This commit is contained in:
wwt 2025-03-31 09:26:45 +08:00
commit c374154f0f
19 changed files with 785 additions and 223 deletions

2
components.d.ts vendored
View File

@ -8,6 +8,8 @@ export {}
/* prettier-ignore */ /* prettier-ignore */
declare module 'vue' { declare module 'vue' {
export interface GlobalComponents { 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'] AudioMessage: typeof import('./src/components/talk/message/AudioMessage.vue')['default']
Avatar: typeof import('./src/components/base/Avatar.vue')['default'] Avatar: typeof import('./src/components/base/Avatar.vue')['default']
AvatarCropper: typeof import('./src/components/base/AvatarCropper.vue')['default'] AvatarCropper: typeof import('./src/components/base/AvatarCropper.vue')['default']

View File

@ -1,10 +1,11 @@
<script setup> <script setup>
import { useStatus } from '@/store/status' import { useStatus } from '@/store/status'
import { useUserStore } from '@/store' import { useUserStore, useDialogueListStore } from '@/store'
import { useProvideUserModal } from '@/hooks' import { useProvideUserModal } from '@/hooks'
import {useAuth} from "@/store/auth"; import {useAuth} from "@/store/auth";
const {token} = useAuth() const {token} = useAuth()
import ws from '@/connect' import ws from '@/connect'
import {uniStorage} from "@/utils/uniStorage.js"
const { statusBarHeight } = useStatus() const { statusBarHeight } = useStatus()
const { uid, isShow } = useProvideUserModal() const { uid, isShow } = useProvideUserModal()
const userStore = useUserStore() const userStore = useUserStore()
@ -19,6 +20,10 @@ const handleWebview = () => {
// }) // })
console.log("webview", webview) console.log("webview", webview)
token.value = webview.token token.value = webview.token
if(webview?.doClearDialogueList){
useDialogueListStore().dialogueList.value = []
uniStorage.removeItem('dialogueList')
}
userStore.loadSetting() userStore.loadSetting()
ws.connect() ws.connect()
} }

View 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>

View 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>

View File

@ -55,7 +55,7 @@ const getFileTypeIMG = computed(() => {
default: default:
objT.finishedImg = filePaperOther objT.finishedImg = filePaperOther
objT.blankImg = filePaperOtherBlank objT.blankImg = filePaperOtherBlank
objT.progressColor = '#747474' objT.progressColor = '#46299d'
} }
return objT return objT
}) })
@ -132,7 +132,7 @@ const downloadAndOpenFile = () => {
:height="95" :height="95"
:src="getFileTypeIMG.blankImg" :src="getFileTypeIMG.blankImg"
></tm-image> ></tm-image>
<wd-circle <wd-circle v-if="data.uploadStatus === 1"
customClass="circleProgress" customClass="circleProgress"
:modelValue="data.uploadCurrent" :modelValue="data.uploadCurrent"
layerColor="#E3E3E3" layerColor="#E3E3E3"
@ -140,6 +140,9 @@ const downloadAndOpenFile = () => {
:strokeWidth="3" :strokeWidth="3"
:size="20" :size="20"
></wd-circle> ></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> </div>
<div class="divider mt-[28rpx]"></div> <div class="divider mt-[28rpx]"></div>
@ -288,4 +291,20 @@ const downloadAndOpenFile = () => {
width: 40rpx !important; width: 40rpx !important;
height: 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> </style>

View File

@ -12,40 +12,40 @@ const img = computed(() => {
// console.log(props.extra); // console.log(props.extra);
let info = { let info = {
width: 0, width: 0,
height: 0 height: 0,
} }
if (props.extra.url.includes('blob:http://')) { if (props.extra.url.includes('blob:http://')) {
info = { info = {
width: props.extra.width, width: props.extra.width,
height: props.extra.height height: props.extra.height,
} }
}else { } else {
info = getImageInfo(props.extra.url) info = getImageInfo(props.extra.url)
} }
if (info.width == 0 || info.height == 0) { if (info.width == 0 || info.height == 0) {
return { return {
width: 450, width: 450,
height: 298 height: 298,
} }
} }
if(info.width<300){ if (info.width < 300) {
return { return {
width: 300, width: 300,
height: info.height / (info.width / 300) height: info.height / (info.width / 300),
} }
} }
if (info.width < 350) { if (info.width < 350) {
return { return {
width: info.width, width: info.width,
height: info.height height: info.height,
} }
} }
return { return {
width: 350, width: 350,
height: info.height / (info.width / 350) height: info.height / (info.width / 350),
} }
}) })
</script> </script>
@ -54,12 +54,29 @@ const img = computed(() => {
class="im-message-image" class="im-message-image"
:class="{ :class="{
left: data.float === 'left', left: data.float === 'left',
right: data.float === 'right' right: data.float === 'right',
}" }"
> >
<div class="image-container"> <div class="image-container">
<tm-image preview :width="img.width" :height="img.height" :src="extra.url" model="aspectFill"/> <div class="relative">
<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> <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> </div>
</section> </section>
</template> </template>
@ -79,7 +96,7 @@ const img = computed(() => {
} }
&.right { &.right {
background-color: #46299D; background-color: #46299d;
border-radius: 16rpx 0 16rpx 16rpx; border-radius: 16rpx 0 16rpx 16rpx;
} }
} }
@ -94,4 +111,18 @@ const img = computed(() => {
z-index: 1; 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> </style>

View File

@ -1,8 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, nextTick, getCurrentInstance, computed, onMounted } from 'vue' import { ref, nextTick, getCurrentInstance, computed, onMounted } from 'vue'
import { getImageInfo } from '@/utils/functions' import { getImageInfo } from '@/utils/functions'
import playCircle from "@/static/image/chatList/playCircle@2x.png"; import playCircle from '@/static/image/chatList/playCircle@2x.png'
import { useStatus } from "@/store/status"; import { useStatus } from '@/store/status'
const { statusBarHeight } = useStatus() const { statusBarHeight } = useStatus()
const instance = getCurrentInstance() const instance = getCurrentInstance()
@ -20,12 +20,12 @@ const open = ref(false)
const img = computed(() => { const img = computed(() => {
let info = { let info = {
width: 0, width: 0,
height: 0 height: 0,
} }
if (props.extra.url.includes('blob:http://')) { if (props.extra.url.includes('blob:http://')) {
info = { info = {
width: props.extra.width, width: props.extra.width,
height: props.extra.height height: props.extra.height,
} }
} else { } else {
info = getImageInfo(props.extra.url) info = getImageInfo(props.extra.url)
@ -34,26 +34,26 @@ const img = computed(() => {
if (info.width == 0 || info.height == 0) { if (info.width == 0 || info.height == 0) {
return { return {
width: 450, width: 450,
height: 298 height: 298,
} }
} }
if (info.width < 300) { if (info.width < 300) {
return { return {
width: 300, width: 300,
height: info.height / (info.width / 300) height: info.height / (info.width / 300),
} }
} }
if (info.width < 350) { if (info.width < 350) {
return { return {
width: info.width, width: info.width,
height: info.height height: info.height,
} }
} }
return { return {
width: 350, width: 350,
height: info.height / (info.width / 350) height: info.height / (info.width / 350),
} }
}) })
@ -67,57 +67,98 @@ const fullscreenchange = (e) => {
/* 视频播放 获取第一帧 */ /* 视频播放 获取第一帧 */
const canplay = (e) => { const canplay = (e) => {
console.log('Video can play:', e); console.log('Video can play:', e)
if (e.target) { if (e.target) {
setTimeout(() => { setTimeout(() => {
e.target.pause(); e.target.pause()
}, 200); }, 200)
} }
}; }
async function onPlay() { async function onPlay() {
videoContext.value = uni.createVideoContext(props.extra.url, instance);
videoContext.value.requestFullScreen({ direction: 2 });
videoContext.value.play()
open.value = true 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(() => { onMounted(() => {
videoRef.value = uni.createVideoContext(props.data.msg_id); videoRef.value = uni.createVideoContext(props.data.msg_id)
videoRef.value.play() videoRef.value.play()
setTimeout(() => { setTimeout(() => {
videoRef.value.pause() videoRef.value.pause()
}, 200); }, 200)
}) })
</script> </script>
<template> <template>
<section class="im-message-video" :class="{ left: data.float === 'left' }" @click="onPlay"> <section
<div class="coverVideo" :style="{ class="im-message-video"
:class="{ left: data.float === 'left' }"
@click="onPlay"
>
<div
class="coverVideo"
:style="{
width: img.width + 'rpx', width: img.width + 'rpx',
height: img.height + '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%" v-if="props.extra.url.includes('blob:http://')"
height="100%" playsinline preload="auto" controls="false" x5-playsinline >
webkit-playsinline style="object-fit: cover; pointer-events: none;"> <video
</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> </div>
<wd-img v-else :width="`${img.width}rpx`" :height="`${img.height}rpx`" :src="data.extra.cover" /> <wd-img
<div v-if="data.uploadStatus === 2 || !data.uploadStatus" class="btn-video" :style="{ 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', width: img.width + 'rpx',
height: img.height + 'rpx' height: img.height + 'rpx',
}"> }"
>
<tm-image :src="playCircle" :width="80" :height="80" /> <tm-image :src="playCircle" :width="80" :height="80" />
</div> </div>
<div v-else class="btn-video" :style="{ <div
v-else
class="btn-video"
:style="{
width: img.width + 'rpx', width: img.width + 'rpx',
height: img.height + 'rpx' height: img.height + 'rpx',
}" > }"
>
<wd-circle <wd-circle
v-if="data.uploadStatus === 1"
v-model="props.data.uploadCurrent" v-model="props.data.uploadCurrent"
customClass="circleProgress" customClass="circleProgress"
layerColor="#E3E3E3" color="#46299d"
color="#FFFFFF" layer-color="#E3E3E3"
:strokeWidth="6" :strokeWidth="6"
:size="40" :size="40"
></wd-circle> ></wd-circle>
@ -131,18 +172,32 @@ onMounted(() => {
:width="70" :width="70"
:percent="props.data.uploadCurrent"> :percent="props.data.uploadCurrent">
</tm-progress> --> </tm-progress> -->
<div class="upload-failed" v-if="data.uploadStatus === 3">
<tm-icon :font-size="20" name="tmicon-times" color="#fff"></tm-icon>
</div> </div>
<div v-show="open">
<video :src="props.extra.url" controls @fullscreenchange="fullscreenchange" :id="props.extra.url">
</video>
</div> </div>
</section> </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> </template>
<style lang="less" scoped> <style lang="less" scoped>
.im-message-video { .im-message-video {
overflow: hidden; overflow: hidden;
padding: 20rpx 18rpx; padding: 20rpx 18rpx;
background: #46299D; background: #46299d;
min-width: 30rpx; min-width: 30rpx;
min-height: 30rpx; min-height: 30rpx;
display: inline-flex; display: inline-flex;
@ -200,10 +255,43 @@ onMounted(() => {
:deep(.uni-video-bar) { :deep(.uni-video-bar) {
display: none; display: none;
} }
} }
.circleProgress { .circleProgress {
width: 80rpx !important; width: 80rpx !important;
height: 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> </style>

View File

@ -143,6 +143,13 @@ class Talk extends Base {
!useTalkStore().items[useTalkStore().findTalkIndex(this.getIndexName())] !useTalkStore().items[useTalkStore().findTalkIndex(this.getIndexName())]
?.is_disturb ?.is_disturb
) { ) {
this.updateUnreadMsgNumAdd()
}
}
}
//更新未读数量+1
updateUnreadMsgNumAdd() {
if (typeof plus !== 'undefined') { if (typeof plus !== 'undefined') {
let OAWebView = plus.webview.all() let OAWebView = plus.webview.all()
OAWebView.forEach((webview) => { OAWebView.forEach((webview) => {
@ -161,8 +168,6 @@ class Talk extends Base {
}) })
} }
} }
}
}
/** /**
* 显示消息提示 * 显示消息提示
@ -206,7 +211,10 @@ class Talk extends Base {
}).then(async ({ code, data }) => { }).then(async ({ code, data }) => {
if (code == 200) { if (code == 200) {
let item = formatTalkItem(data) let item = formatTalkItem(data)
if (!item?.is_disturb) {
item.unread_num = 1 item.unread_num = 1
this.updateUnreadMsgNumAdd()
}
useTalkStore().addItem(item) useTalkStore().addItem(item)
await useTalkStore().loadTalkList() await useTalkStore().loadTalkList()
} }

View File

@ -11,7 +11,12 @@ import { reactive, nextTick, computed, h, inject } from 'vue'
// EditTwo, // EditTwo,
// IdCard // IdCard
// } from '@icon-park/vue-next' // } 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 { useDialogueStore, useTalkStore, useDialogueListStore } from '@/store'
import { ServeSecedeGroup } from '@/api/group' import { ServeSecedeGroup } from '@/api/group'
// import { ServeDeleteContact, ServeEditContactRemark } from '@/api/contact' // import { ServeDeleteContact, ServeEditContactRemark } from '@/api/contact'
@ -23,7 +28,7 @@ export function useSessionMenu() {
show: false, show: false,
x: 0, x: 0,
y: 0, y: 0,
item: {} item: {},
}) })
const dialogueStore = useDialogueStore() const dialogueStore = useDialogueStore()
@ -118,10 +123,22 @@ export function useSessionMenu() {
// 移除会话 // 移除会话
const onRemoveTalk = (item) => { const onRemoveTalk = (item) => {
ServeDeleteTalkList({ ServeDeleteTalkList({
list_id: item.id list_id: item.id,
}).then(({ code }) => { }).then(({ code }) => {
if (code == 200) { if (code == 200) {
onDeleteTalk(item.index_name) 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({ ServeSetNotDisturb({
talk_type: item.talk_type, talk_type: item.talk_type,
receiver_id: item.receiver_id, receiver_id: item.receiver_id,
is_disturb: item.is_disturb == 0 ? 1 : 0 is_disturb: item.is_disturb == 0 ? 1 : 0,
}).then(({ code, message }) => { }).then(({ code, message }) => {
if (code == 200) { if (code == 200) {
message.success('设置成功!') message.success('设置成功!')
talkStore.updateItem({ talkStore.updateItem({
index_name: item.index_name, index_name: item.index_name,
is_disturb: item.is_disturb == 0 ? 1 : 0 is_disturb: item.is_disturb == 0 ? 1 : 0,
}) })
} else { } else {
message.error(message) message.error(message)
@ -153,12 +170,12 @@ export function useSessionMenu() {
ServeTopTalkList({ ServeTopTalkList({
list_id: item.id, list_id: item.id,
type: item.is_top == 0 ? 1 : 2 type: item.is_top == 0 ? 1 : 2,
}).then(({ code, message }) => { }).then(({ code, message }) => {
if (code == 200) { if (code == 200) {
talkStore.updateItem({ talkStore.updateItem({
index_name: item.index_name, index_name: item.index_name,
is_top: item.is_top == 0 ? 1 : 0 is_top: item.is_top == 0 ? 1 : 0,
}) })
} else { } else {
message.error(message) message.error(message)
@ -201,7 +218,7 @@ export function useSessionMenu() {
negativeText: '取消', negativeText: '取消',
onPositiveClick: () => { onPositiveClick: () => {
ServeSecedeGroup({ ServeSecedeGroup({
group_id: item.receiver_id group_id: item.receiver_id,
}).then(({ code, message }) => { }).then(({ code, message }) => {
if (code == 200) { if (code == 200) {
message.success('已退出群聊') message.success('已退出群聊')
@ -210,7 +227,7 @@ export function useSessionMenu() {
message.error(message) message.error(message)
} }
}) })
} },
}) })
} }
@ -259,12 +276,18 @@ export function useSessionMenu() {
disturb: onSetDisturb, disturb: onSetDisturb,
signout_group: onSignOutGroup, signout_group: onSignOutGroup,
delete_contact: onDeleteContact, delete_contact: onDeleteContact,
remark: onChangeRemark remark: onChangeRemark,
} }
dropdown.show = false dropdown.show = false
evnets[key] && evnets[key](dropdown.item) evnets[key] && evnets[key](dropdown.item)
} }
return { dropdown, onCloseContextMenu, onContextMenuTalkHandle, onToTopTalk, onRemoveTalk } return {
dropdown,
onCloseContextMenu,
onContextMenuTalkHandle,
onToTopTalk,
onRemoveTalk,
}
} }

View File

@ -10,6 +10,8 @@ import tmui from '@/uni_modules/tmui'
import { config } from '@/config/tmui/index.js' import { config } from '@/config/tmui/index.js'
import 'dayjs/locale/zh-cn' import 'dayjs/locale/zh-cn'
import xLoaderror from '@/components/x-loaderror/index.vue' 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 { vLoading } from '@/components/x-loading/index.js'
import messagePopup from '@/components/x-message/useMessagePopup' import messagePopup from '@/components/x-message/useMessagePopup'
import pageAnimation from '@/components/page-animation/index.vue' import pageAnimation from '@/components/page-animation/index.vue'
@ -29,6 +31,8 @@ export function createApp() {
app.mixin(pageAnimation) app.mixin(pageAnimation)
app.component('customNavbar', customNavbar) app.component('customNavbar', customNavbar)
app.component('x-loaderror', xLoaderror) app.component('x-loaderror', xLoaderror)
app.component('AsyncLoading', asyncLoading)
app.component('AsyncError', asyncError)
app.directive('no-space', { app.directive('no-space', {
mounted(el) { mounted(el) {
el.addEventListener('input', (e) => { el.addEventListener('input', (e) => {

View File

@ -101,6 +101,11 @@ const photoActionsSelect = (index) => {
success: async (res) => { success: async (res) => {
console.log(res, 'res') console.log(res, 'res')
res.tempFiles.forEach(async (file) => { 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') let data = await onUploadImageVideo(file, 'image')
emit('selectImg', data, data.file_num) emit('selectImg', data, data.file_num)
}) })
@ -121,6 +126,13 @@ const photoActionsSelect = (index) => {
fileReader.onloadend = async (e) => { fileReader.onloadend = async (e) => {
const base64Url = e.target.result const base64Url = e.target.result
const fileObj = base64ToFile(base64Url) 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') let data = await onUploadImageVideo(fileObj, 'image')
emit('selectImg', data, data.file_num) emit('selectImg', data, data.file_num)
} }
@ -152,6 +164,11 @@ const photoActionsSelect = (index) => {
maxDuration: 60, maxDuration: 60,
success: async (res) => { success: async (res) => {
console.log(res, '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( let data = await onUploadImageVideo(
res.tempFile, res.tempFile,
'video', 'video',
@ -164,12 +181,13 @@ const photoActionsSelect = (index) => {
} }
const onUploadImageVideo = async (file, type = 'image', fileUrl) => { const onUploadImageVideo = async (file, type = 'image', fileUrl) => {
console.log(file, 'file') console.log('开始上传文件:', file.name)
uploadsStore.updateUploadStatus(true)
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
if (type === 'image') { if (type === 'image') {
let image = new Image() let image = new Image()
image.src = URL.createObjectURL(file) image.src = URL.createObjectURL(file)
image.onload = () => { image.onload = async () => {
const form = new FormData() const form = new FormData()
form.append('file', file) form.append('file', file)
form.append('source', 'fonchain-chat') form.append('source', 'fonchain-chat')
@ -203,40 +221,62 @@ const onUploadImageVideo = async (file, type = 'image', fileUrl) => {
} }
virtualList.value.unshift(newItem) virtualList.value.unshift(newItem)
uploadImg(form, (e) => onProgressFn(e, randomId)).then(
({ status, data, msg }) => { try {
if (status == 0) { 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) const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) { if (index !== -1) {
virtualList.value[index].uploadStatus = 2 virtualList.value[index].uploadStatus = 2
virtualList.value[index].uploadCurrent = 100 virtualList.value[index].uploadCurrent = 100
} }
//
resolve({ resolve({
type: 'image', type: 'image',
url: data.ori_url, url: result.data.ori_url,
size: file.size, size: file.size,
width: image.width, width: image.width,
height: image.height, height: image.height,
file_num: randomId, file_num: randomId,
}) })
} else { } else {
uploadsStore.updateUploadStatus(false)
// //
const index = virtualList.value.findIndex(item => item.file_num === randomId) const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) { if (index !== -1) {
virtualList.value[index].uploadStatus = 3 virtualList.value[index].uploadStatus = 3
} }
message.error(result.msg)
resolve('') resolve('')
message.error(msg)
} }
}, } 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 { } else {
uni.getVideoInfo({ uni.getVideoInfo({
src: fileUrl, src: fileUrl,
success: (resp) => { success: async (resp) => {
console.log(resp) console.log('视频信息:', resp)
const form = new FormData()
form.append('file', file) form.append('file', file)
form.append('source', 'fonchain-chat') form.append('source', 'fonchain-chat')
form.append('type', 'video') form.append('type', 'video')
@ -266,41 +306,65 @@ const onUploadImageVideo = async (file, type = 'image', fileUrl) => {
talk_type: dialogueStore.talk.talk_type, talk_type: dialogueStore.talk.talk_type,
user_id: userStore.uid, user_id: userStore.uid,
uploadCurrent: 0, uploadCurrent: 0,
uploadStatus: 1, // 1 2 3 uploadStatus: 1,
} }
virtualList.value.unshift(newItem) virtualList.value.unshift(newItem)
uploadImg(form, (e) => onProgressFn(e, randomId)).then(
({ status, data, msg }) => { try {
if (status == 0) { 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) const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) { if (index !== -1) {
virtualList.value[index].uploadStatus = 2 virtualList.value[index].uploadStatus = 2
virtualList.value[index].uploadCurrent = 100 virtualList.value[index].uploadCurrent = 100
} }
console.log(data)
resolve({ resolve({
type: 'video', type: 'video',
url: data.ori_url, url: result.data.ori_url,
cover: data.cover_url, cover: result.data.cover_url,
duration: parseInt(resp.duration), duration: parseInt(resp.duration),
size: file.size, size: file.size,
file_num: randomId, file_num: randomId,
}) })
} else { } else {
uploadsStore.updateUploadStatus(false)
// //
const index = virtualList.value.findIndex(item => item.file_num === randomId) const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) { if (index !== -1) {
virtualList.value[index].uploadStatus = 3 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('') resolve('')
message.error(msg)
} }
}, },
) fail: (error) => {
}, console.error('获取视频信息失败:', error)
uploadsStore.updateUploadStatus(false)
message.error('获取视频信息失败')
resolve('')
}
}) })
const form = new FormData()
} }
}) })
} }
@ -451,6 +515,11 @@ const chooseFile = () => {
count: 1, count: 1,
extension: [''], extension: [''],
success: (res) => { success: (res) => {
const fileSizeInMB = (res.tempFiles[0].size / (1024 * 1024)).toFixed(2)
if (fileSizeInMB > 100) {
plus.nativeUI.toast('文件大小不能超过100MB')
return
}
let randomId = uniqueId() let randomId = uniqueId()
let newItem = { let newItem = {
avatar: userStore.avatar, avatar: userStore.avatar,
@ -478,23 +547,34 @@ const chooseFile = () => {
uploadStatus: 1, // 1 2 3 uploadStatus: 1, // 1 2 3
} }
virtualList.value.unshift(newItem) virtualList.value.unshift(newItem)
uploadsStore.initUploadFile(res.tempFiles[0], props.talkParams, randomId, (status, data, msg) => { uploadsStore.updateUploadStatus(true)
uploadsStore.initUploadFile(
res.tempFiles[0],
props.talkParams,
randomId,
(status, data, msg) => {
if (status === 0) { if (status === 0) {
// //
const index = virtualList.value.findIndex(item => item.file_num === randomId) const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) { if (index !== -1) {
virtualList.value[index].uploadStatus = 2 virtualList.value[index].uploadStatus = 2
virtualList.value[index].uploadCurrent = 100 virtualList.value[index].uploadCurrent = 100
} }
} else { } else {
uploadsStore.updateUploadStatus(false)
// //
const index = virtualList.value.findIndex(item => item.file_num === randomId) const index = virtualList.value.findIndex(
(item) => item.file_num === randomId,
)
if (index !== -1) { if (index !== -1) {
virtualList.value[index].uploadStatus = 3 virtualList.value[index].uploadStatus = 3
} }
message.error(msg) message.error(msg)
} }
}) },
)
}, },
}) })
} }

View File

@ -22,7 +22,12 @@
:hideBack="dialogueStore.isOpenMultiSelect" :hideBack="dialogueStore.isOpenMultiSelect"
> >
<template #left v-if="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>
<template <template
#subTitle #subTitle
@ -211,17 +216,28 @@
</div> </div>
<template #bottom> <template #bottom>
<div class="footBox" id="footBoxArea"> <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 v-if="!dialogueStore.isOpenMultiSelect">
<div <div
class="pt-[16rpx] ml-[32rpx] mr-[32rpx] flex items-start justify-between" class="pt-[16rpx] ml-[32rpx] mr-[32rpx] flex items-start justify-between"
> >
<div class="flex-1 quillBox"> <div class="flex-1 quillBox" style="">
<QuillEditor <QuillEditor
ref="editor" ref="editor"
id="editor" id="editor"
:options="editorOption" :options="editorOption"
@editorChange="onEditorChange" @editorChange="onEditorChange"
style="width: 100%; flex: 1; height: 100%; border: none;" style="width: 100%; flex: 1; height: 100%;"
@click="onEditorClick" @click="onEditorClick"
/> />
<!-- <tm-input type=textarea autoHeight focusColor="#F9F9F9" color="#F9F9F9" :inputPadding="[12]" <!-- <tm-input type=textarea autoHeight focusColor="#F9F9F9" color="#F9F9F9" :inputPadding="[12]"
@ -238,7 +254,7 @@
}} }}
</span> </span>
<span <span
v-if="state?.quoteInfo" v-if="state?.quoteInfo?.msg_type !== 1"
class="text-[28rpx] text-[#999]" class="text-[28rpx] text-[#999]"
> >
{{ {{
@ -535,6 +551,7 @@ const settingsStore = useSettingsStore()
const userStore = useUserStore() const userStore = useUserStore()
const dialogueStore = useDialogueStore() const dialogueStore = useDialogueStore()
const editorDraftStore = useEditorDraftStore() const editorDraftStore = useEditorDraftStore()
const uploadsStore = useUploadsStore()
const editor = ref() const editor = ref()
const zpagingRef = ref() const zpagingRef = ref()
useZPaging(zpagingRef) useZPaging(zpagingRef)
@ -554,6 +571,11 @@ const talkParams = reactive({
unReadNum: computed(() => dialogueStore.unreadNum), unReadNum: computed(() => dialogueStore.unreadNum),
}) })
const uploadsParams = reactive({
isUploading: computed(() => uploadsStore.isUploading),
uploadingNum: computed(() => uploadsStore.uploadingNum),
})
const state = ref({ const state = ref({
isOpenEmojiPanel: false, isOpenEmojiPanel: false,
isOpenFilePanel: false, isOpenFilePanel: false,
@ -639,6 +661,9 @@ const handleHidePanel = () => {
const onEditorClick = () => { const onEditorClick = () => {
handleHidePanel() handleHidePanel()
const quill = getQuill() const quill = getQuill()
// if (quill.getText().endsWith('@\n')) {
// showMentionSelectDebounced(quill)
// }
quill.focus() quill.focus()
} }
@ -654,14 +679,17 @@ const onSendMessage = (data = {}, callBack) => {
ServePublishMessage(message) ServePublishMessage(message)
.then(({ code, message }) => { .then(({ code, message }) => {
if (code == 200) { if (code == 200) {
uploadsStore.updateUploadStatus(false)
if (callBack) { if (callBack) {
callBack(true) callBack(true)
} }
} else { } else {
uploadsStore.updateUploadStatus(false)
message.warning(message) message.warning(message)
} }
}) })
.catch(() => { .catch(() => {
uploadsStore.updateUploadStatus(false)
message.warning('网络繁忙,请稍后重试!') message.warning('网络繁忙,请稍后重试!')
}) })
} }
@ -688,8 +716,11 @@ const onSendMessageClick = () => {
switch (data.msgType) { switch (data.msgType) {
case 1: // case 1: //
if (data.items[0].content.trim() === '') {
return
}
if (data.items[0].content.length > 1024) { if (data.items[0].content.length > 1024) {
return message.info('发送内容超长,请分条发送') return message.warning('发送内容超长,请分条发送')
} }
onSendTextEvent({ onSendTextEvent({
data, data,
@ -816,6 +847,9 @@ const onEmoticonEvent = (data) => {
} }
const onEditorChange = () => { const onEditorChange = () => {
if (getQuill().getText() !== state.value.lastMentionText) {
state.value.lastMentionTriggered = false
}
let delta = getQuill().getContents() let delta = getQuill().getContents()
let text = deltaToString(delta) let text = deltaToString(delta)
@ -1558,6 +1592,9 @@ const isLeader = computed(() => {
//@ //@
const doMentionUser = (mentionSelect) => { const doMentionUser = (mentionSelect) => {
console.log(mentionSelect) console.log(mentionSelect)
if (talkParams.type === 1) {
return
}
// mention // mention
const mentionObj = { const mentionObj = {
id: mentionSelect.user_id, // 使 user_id erp_user_id id: mentionSelect.user_id, // 使 user_id erp_user_id
@ -1632,29 +1669,23 @@ onUnmounted(() => {
if (state.value.showMentionSelectTimer) { if (state.value.showMentionSelectTimer) {
clearTimeout(state.value.showMentionSelectTimer) clearTimeout(state.value.showMentionSelectTimer)
} }
if (uploadsStore.isUploading) {
uploadsStore.clearUpload()
}
}) })
// //
const showMentionSelectDebounced = (quill) => { const showMentionSelectDebounced = (quill) => {
const text = quill.getText() 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) { if (text !== state.value.lastMentionText) {
state.value.lastMentionTriggered = false state.value.lastMentionTriggered = false
} }
if (text.length === 2) {
state.value.lastMentionText = text
state.value.lastMentionTriggered = true
state.value.isShowMentionSelect = true
}
// //
if (state.value.lastMentionTriggered) { if (state.value.lastMentionTriggered) {
console.log('return')
return return
} }
@ -1718,13 +1749,16 @@ const showMentionSelectDebounced = (quill) => {
justify-content: space-between; justify-content: space-between;
overflow: hidden; overflow: hidden;
width: 100%; width: 100%;
span { span {
display: -webkit-inline-box; display: -webkit-inline-box;
text-overflow: ellipsis; text-overflow: ellipsis;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
width: 100%; width: 100%;
word-break: break-all;
} }
img { img {
margin: 0 0 0 30rpx; margin: 0 0 0 30rpx;
flex-shrink: 0; flex-shrink: 0;
@ -1937,6 +1971,7 @@ const showMentionSelectDebounced = (quill) => {
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
justify-content: center; justify-content: center;
:deep(.ql-clipboard) { :deep(.ql-clipboard) {
position: relative; position: relative;
opacity: 0; opacity: 0;
@ -1988,6 +2023,7 @@ const showMentionSelectDebounced = (quill) => {
z-index: 1; z-index: 1;
/* 确保 z-index 低于 deepBubble */ /* 确保 z-index 低于 deepBubble */
} }
.divider { .divider {
width: 100%; width: 100%;
height: 1rpx; height: 1rpx;
@ -2000,20 +2036,24 @@ const showMentionSelectDebounced = (quill) => {
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 36rpx 32rpx 0; padding: 36rpx 32rpx 0;
.cancel-btns { .cancel-btns {
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
.hide-btn { .hide-btn {
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
position: relative; position: relative;
text { text {
} }
img { img {
width: 18rpx; width: 18rpx;
height: 10rpx; height: 10rpx;
@ -2030,6 +2070,7 @@ const showMentionSelectDebounced = (quill) => {
background-color: #f3f3f3; background-color: #f3f3f3;
border-radius: 8rpx; border-radius: 8rpx;
flex-shrink: 0; flex-shrink: 0;
span { span {
color: #bababa; color: #bababa;
line-height: 40rpx; line-height: 40rpx;
@ -2039,6 +2080,7 @@ const showMentionSelectDebounced = (quill) => {
.mention-done-btn-can-do { .mention-done-btn-can-do {
background-color: #46299d; background-color: #46299d;
span { span {
color: #fff; color: #fff;
} }
@ -2050,11 +2092,13 @@ const showMentionSelectDebounced = (quill) => {
align-items: center; align-items: center;
justify-content: flex-end; justify-content: flex-end;
flex-shrink: 0; flex-shrink: 0;
span { span {
flex-shrink: 0; flex-shrink: 0;
} }
} }
} }
:deep(.mention) { :deep(.mention) {
color: #1890ff; color: #1890ff;
} }

View File

@ -1,4 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import zPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.vue'
import { ref, onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { ServeGetForwardRecords } from '@/api/chat' import { ServeGetForwardRecords } from '@/api/chat'
import { MessageComponents } from '@/constant/message' import { MessageComponents } from '@/constant/message'
@ -47,11 +48,11 @@ onMounted(() => {
</script> </script>
<template> <template>
<div class="outer-layer"> <div class="forward-record-page">
<div> <zPaging ref="zPaging" :show-scrollbar="false">
<tm-navbar :hideBack="false" hideHome :title="`${title}的会话记录`" > <template #top>
</tm-navbar> <customNavbar :title="`${title}的会话记录`"></customNavbar>
</div> </template>
<div class="main-box"> <div class="main-box">
<div v-if="items.length === 0" class="flex justify-center items-center w-full mt-[200rpx]"> <div v-if="items.length === 0" class="flex justify-center items-center w-full mt-[200rpx]">
<wd-loading /> <wd-loading />
@ -76,16 +77,15 @@ onMounted(() => {
</div> </div>
</div> </div>
</div> </div>
</zPaging>
</div> </div>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
.outer-layer { .forward-record-page {
overflow-y: auto;
flex: 1; flex: 1;
background-image: url("@/static/image/clockIn/z3280@3x.png"); background-image: url("@/static/image/clockIn/z3280@3x.png");
background-size: cover; background-size: cover;
padding: 0 66rpx 20rpx 50rpx;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@ -96,7 +96,7 @@ onMounted(() => {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: auto; overflow: auto;
padding-top: 28rpx; padding: 28rpx 66rpx 20rpx 50rpx;
} }
.message-item { .message-item {

View File

@ -265,6 +265,7 @@ const handleDelete = () => {
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 1; -webkit-line-clamp: 1;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
word-break: break-all;
} }
.divider { .divider {

View File

@ -9,7 +9,7 @@
:default-page-size="props.searchResultPageSize" :default-page-size="props.searchResultPageSize"
:loading-more-default-as-loading="true" :loading-more-default-as-loading="true"
:inside-more="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-text="$t('search.hint')"
:empty-view-img-style="{ width: '476rpx', height: '261rpx' }" :empty-view-img-style="{ width: '476rpx', height: '261rpx' }"
:empty-view-title-style="{ :empty-view-title-style="{
@ -113,6 +113,7 @@
</div> </div>
</template> </template>
<script setup> <script setup>
import searchNoData from '@/static/image/search/search-no-data.png'
import customInput from '@/components/custom-input/custom-input.vue' import customInput from '@/components/custom-input/custom-input.vue'
import ZPaging from '@/uni_modules/z-paging/components/z-paging/z-paging.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' import useZPaging from '@/uni_modules/z-paging/components/z-paging/js/hooks/useZPaging.js'

View File

@ -17,7 +17,9 @@ import { ServeSeachQueryAll, ServeGetSessionId } from '@/api/search/index'
import { onMounted } from 'vue' import { onMounted } from 'vue'
import { handleSetWebviewStyle } from '@/utils/common' 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() const dialogueStore = useDialogueStore()
@ -50,6 +52,18 @@ const clickSearchItem = async (
console.log(talk_type, receiver_id) console.log(talk_type, receiver_id)
const sessionId = await getSessionId(talk_type, receiver_id) const sessionId = await getSessionId(talk_type, receiver_id)
if (searchResultKey === 'user_infos') { 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({ dialogueStore.setDialogue({
name: result.nickname, name: result.nickname,
talk_type: 1, talk_type: 1,
@ -65,7 +79,7 @@ const clickSearchItem = async (
receiver_id: result.group_id || result.id, receiver_id: result.group_id || result.id,
}) })
uni.navigateTo({ uni.navigateTo({
url: '/pages/dialog/index?sessionId=' + sessionId url: '/pages/dialog/index?sessionId=' + sessionId,
}) })
} else if (searchResultKey === 'general_infos') { } else if (searchResultKey === 'general_infos') {
uni.navigateTo({ uni.navigateTo({

View File

@ -25,7 +25,9 @@ import {
} from '@/api/search/index' } from '@/api/search/index'
import { reactive } from 'vue' 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() const dialogueStore = useDialogueStore()
@ -75,7 +77,13 @@ onLoad((options) => {
}) })
//id //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 = { let idChanges = {
last_id, last_id,
last_group_id, last_group_id,
@ -108,6 +116,18 @@ const clickSearchItem = async (
console.log(talk_type, receiver_id) console.log(talk_type, receiver_id)
const sessionId = await getSessionId(talk_type, receiver_id) const sessionId = await getSessionId(talk_type, receiver_id)
if (state.searchResultKey === 'user_infos') { 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({ dialogueStore.setDialogue({
name: result.nickname, name: result.nickname,
talk_type: 1, talk_type: 1,

View File

@ -168,7 +168,10 @@
/> />
</template> </template>
<template v-else-if="item?.msg_type === 5"> <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 <tm-image
:src="item?.extra?.cover" :src="item?.extra?.cover"
model="aspectFill" model="aspectFill"
@ -264,7 +267,8 @@
</div> </div>
</div> </div>
</ZPaging> </ZPaging>
<div v-show="open"> <teleport to="body">
<div v-show="open" class="video-container">
<video <video
:src="currentVideoUrl" :src="currentVideoUrl"
controls controls
@ -273,9 +277,10 @@
playsinline playsinline
webkit-playsinline webkit-playsinline
x5-playsinline x5-playsinline
> class="fullscreen-video"
</video> ></video>
</div> </div>
</teleport>
</div> </div>
</div> </div>
</template> </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_WORD from '@/static/image/search/fileType_WORD.png'
import fileType_PDF from '@/static/image/search/fileType_PDF.png' import fileType_PDF from '@/static/image/search/fileType_PDF.png'
import fileType_Files from '@/static/image/search/fileType_Files.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 { fileFormatSize, fileSuffix } from '@/utils/strings'
import searchItem from '../components/searchItem.vue' import searchItem from '../components/searchItem.vue'
import customInput from '@/components/custom-input/custom-input.vue' import customInput from '@/components/custom-input/custom-input.vue'
@ -353,6 +358,7 @@ async function onPlay(url) {
// //
videoContext.value = uni.createVideoContext(url, getCurrentInstance()) videoContext.value = uni.createVideoContext(url, getCurrentInstance())
setTimeout(() => {
// //
videoContext.value.requestFullScreen({ direction: 2 }) videoContext.value.requestFullScreen({ direction: 2 })
@ -360,6 +366,7 @@ async function onPlay(url) {
setTimeout(() => { setTimeout(() => {
videoContext.value.play() videoContext.value.play()
}, 100) }, 100)
}, 200)
} }
onLoad((options) => { onLoad((options) => {
@ -561,7 +568,7 @@ const cancelSearch = () => {
}) })
} else { } else {
uni.reLaunch({ uni.reLaunch({
url: '/pages/index/index' url: '/pages/index/index',
}) })
} }
} }
@ -904,7 +911,7 @@ body::v-deep .round-3 {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
img{ img {
width: 80rpx !important; width: 80rpx !important;
height: 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> </style>

View File

@ -33,7 +33,9 @@ export const useUploadsStore = defineStore('uploads', {
state: () => { state: () => {
return { return {
isShow: false, isShow: false,
items: [] items: [],
isUploading: false,//当前是否正在上传
uploadingNum: 0//当前正在上传数量
} }
}, },
getters: { getters: {
@ -74,7 +76,11 @@ export const useUploadsStore = defineStore('uploads', {
this.triggerUpload(upload_id,msgId) this.triggerUpload(upload_id,msgId)
this.isShow = true this.isShow = true
} else {
this.updateUploadStatus(false)
} }
}).catch(()=> {
this.updateUploadStatus(false)
}) })
}, },
@ -128,6 +134,7 @@ export const useUploadsStore = defineStore('uploads', {
this.triggerUpload(uploadId, msgId) this.triggerUpload(uploadId, msgId)
} }
} else { } else {
this.updateUploadStatus(false)
item.status = 3 item.status = 3
// 更新虚拟列表中的状态为失败 // 更新虚拟列表中的状态为失败
const { virtualList } = useDialogueListStore() const { virtualList } = useDialogueListStore()
@ -138,6 +145,7 @@ export const useUploadsStore = defineStore('uploads', {
} }
}) })
.catch(() => { .catch(() => {
this.updateUploadStatus(false)
item.status = 3 item.status = 3
// 更新虚拟列表中的状态为失败 // 更新虚拟列表中的状态为失败
const { virtualList } = useDialogueListStore() const { virtualList } = useDialogueListStore()
@ -155,7 +163,38 @@ export const useUploadsStore = defineStore('uploads', {
receiver_id: item.receiver_id, receiver_id: item.receiver_id,
talk_type: item.talk_type, talk_type: item.talk_type,
file_num: file_num 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
} }
} }
}) })