更新组件和API,新增NImage支持,优化文件上传功能,调整主题颜色,删除不必要的图片,改进用户界面和交互体验。

This commit is contained in:
Phoenix 2025-05-15 16:07:56 +08:00
parent 661472a70a
commit fad84e5bf3
23 changed files with 383 additions and 477 deletions

1
components.d.ts vendored
View File

@ -52,6 +52,7 @@ declare module 'vue' {
NButton: typeof import('naive-ui')['NButton']
NEmpty: typeof import('naive-ui')['NEmpty']
NIcon: typeof import('naive-ui')['NIcon']
NImage: typeof import('naive-ui')['NImage']
NInput: typeof import('naive-ui')['NInput']
NModal: typeof import('naive-ui')['NModal']
NoticeEditor: typeof import('./src/components/group/manage/NoticeEditor.vue')['default']

View File

@ -25,3 +25,7 @@ export const ServeRefreshToken = () => {
export const ServeForgetPassword = (data) => {
return post('/api/v1/auth/forget', data)
}
// 获取用户信息服务
export const GetUserInfo = (data) => {
return post('/api/v1/users/info', data)
}

View File

@ -45,10 +45,12 @@ export const ServeFindFriendApplyNum = () => {
}
// 搜索用户信息服务接口
// export const ServeSearchUser = (data) => {
// return get('/api/v1/contact/detail', data)
// }
export const ServeSearchUser = (data) => {
return get('/api/v1/contact/detail', data)
return post('/api/v1/users/info', data)
}
// 搜索用户信息服务接口
export const ServeContactGroupList = (data) => {
return get('/api/v1/contact/group/list', data)

View File

@ -1,6 +1,6 @@
// 默认主题
html {
--im-primary-color: #1890ff;
--im-primary-color: #462AA0;
--im-bg-color: #ffffff;
--line-border-color: #f5f5f5;
--border-color: #eeeaea;

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -1,7 +1,12 @@
<script setup>
import { fileFormatSize } from '@/utils/strings'
import { download, getFileNameSuffix } from '@/utils/functions'
import { ref, computed } from 'vue'
import { useUploadsStore } from '@/store'
import pptText from '@/assets/image/ppt-text.png'
import excelText from '@/assets/image/excel-text.png'
import wordText from '@/assets/image/word-text.png'
import pdfText from '@/assets/image/pdf-text.png'
import fileText from '@/assets/image/file-text.png'
//
const props = defineProps({
@ -22,54 +27,76 @@ const props = defineProps({
}
})
//
const uploadsStore = useUploadsStore()
const isPlaying = ref(false)
/**
* 切换播放状态
* 在上传过程中可以暂停/继续
*/
const togglePlay = () => {
isPlaying.value = !isPlaying.value
console.log('播放状态:', isPlaying.value ? '播放中' : '暂停')
//
const fileTypes = {
PDF: { icon: pdfText, color: '#DE4E4E', type: 'PDF' },
PPT: { icon: pptText, color: '#B74B2B', type: 'PPT' },
EXCEL: { icon: excelText, color: '#3C7F4B', type: 'EXCEL' },
WORD: { icon: wordText, color: '#2750B2', type: 'WORD' },
DEFAULT: { icon: fileText, color: '#747474', type: '文件' }
}
/**
* 从文件URL中提取并返回大写的文件扩展名
* @param {string} url - 文件的URL或名称
* @returns {string} 大写的文件扩展名
*/
function getFileExtensionUpperCase(url) {
// URL
const fileName = url.split('/').pop()
//
return fileName.split('.').pop().toUpperCase()
// Excel
const EXCEL_EXTENSIONS = ['XLS', 'XLSX', 'CSV']
// Word
const WORD_EXTENSIONS = ['DOC', 'DOCX', 'RTF', 'DOT', 'DOTX']
// PPT
const PPT_EXTENSIONS = ['PPT', 'PPTX', 'PPS', 'PPSX']
//
const fileInfo = computed(() => {
const extension = getFileExtension(props.extra.name)
if (EXCEL_EXTENSIONS.includes(extension)) {
return fileTypes.EXCEL
}
if (WORD_EXTENSIONS.includes(extension)) {
return fileTypes.WORD
}
if (PPT_EXTENSIONS.includes(extension)) {
return fileTypes.PPT
}
return fileTypes[extension] || fileTypes.DEFAULT
})
//
function getFileExtension(filename) {
const parts = filename.split('.')
return parts.length > 1 ? parts.pop().toUpperCase() : ''
}
//
const togglePlay = () => {
isPlaying.value = !isPlaying.value
if (props.extra.is_uploading && props.extra.upload_id) {
const action = isPlaying.value ? 'resumeUpload' : 'pauseUpload'
uploadsStore[action](props.extra.upload_id)
}
}
// SVG
const radius = 9 //
const circumference = computed(() => 2 * Math.PI * radius) //
//
const radius = 9
const circumference = computed(() => 2 * Math.PI * radius)
const strokeDashoffset = computed(() =>
circumference.value * (1 - props.extra.percentage / 100)
circumference.value * (1 - (props.extra.percentage || 0) / 100)
)
</script>
<template>
<div class="w-243px bg-#fff rounded-8px shadow-md px-14px pointer">
<div class="file-message">
<!-- 文件头部信息 -->
<div class="flex py-14px pr-5px justify-between w-full" style="border-bottom: 1px solid #EEEEEE;">
<div class="file-header">
<!-- 文件名 -->
<div class="text-#1A1A1A text-14px">{{ extra.name }}</div>
<div class="file-name">{{ extra.name }}</div>
<!-- 文件图标区域 -->
<div class="relative">
<img class="w-47.91px h-47.91px" src="@/assets/image/file-paper-line@2x.png" alt="文件图标">
<!-- 文件扩展名显示 - 非上传状态 -->
<div v-if="!extra.is_uploading" class="absolute top-11px left-16px text-#DE4E4E text-10px font-bold">
{{ getFileExtensionUpperCase(extra.name) }}
</div>
<div class="file-icon-container">
<img class="file-icon" :src="fileInfo.icon" alt="文件图标">
<!-- 上传进度圆环 - 上传状态 -->
<div v-else class="absolute top-9px left-16px w-20px h-20px">
<div v-if="extra.is_uploading" class="progress-overlay">
<div class="circle-progress-container" @click="togglePlay">
<svg class="circle-progress" width="20" height="20" viewBox="0 0 20 20">
<!-- 底色圆环 -->
@ -87,7 +114,7 @@ const strokeDashoffset = computed(() =>
cy="10"
r="9"
fill="transparent"
stroke="#D54C4B"
:stroke="fileInfo.color"
stroke-width="2"
:stroke-dasharray="circumference"
:stroke-dashoffset="strokeDashoffset"
@ -95,15 +122,13 @@ const strokeDashoffset = computed(() =>
class="progress-circle"
/>
<!-- 暂停图标 - 播放中显示 -->
<g v-if="isPlaying" class="pause-icon transform-rotate-90">
<rect x="7" y="5" width="2" height="10" fill="#D54C4B" />
<rect x="11" y="5" width="2" height="10" fill="#D54C4B" />
<!-- 暂停/播放图标 -->
<g v-if="isPlaying" class="pause-icon">
<rect x="7" y="5" width="2" height="10" :fill="fileInfo.color" />
<rect x="11" y="5" width="2" height="10" :fill="fileInfo.color" />
</g>
<!-- 播放图标 - 暂停时显示 -->
<g v-else class="play-icon">
<rect x="6" y="6" width="8" height="8" fill="#D54C4B" />
<rect x="6" y="6" width="8" height="8" :fill="fileInfo.color" />
</g>
</svg>
</div>
@ -111,11 +136,66 @@ const strokeDashoffset = computed(() =>
</div>
</div>
<!-- 文件大小信息 -->
<div class="text-#747474 text-12px pt-5px pb-11px">{{ fileFormatSize(extra.size) }}</div>
<div class="file-size">{{ fileFormatSize(extra.size) }}</div>
</div>
</template>
<style lang="less" scoped>
.file-message {
width: 243px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
padding: 0 14px;
cursor: pointer;
}
.file-header {
display: flex;
padding: 14px 5px 14px 0;
justify-content: space-between;
width: 100%;
border-bottom: 1px solid #EEEEEE;
}
.file-name {
color: #1A1A1A;
font-size: 14px;
word-break: break-word;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.file-icon-container {
position: relative;
}
.file-icon {
width: 48px;
height: 48px;
}
.progress-overlay {
background-color: #fff;
position: absolute;
top: 6px;
left: 11px;
width: 30px;
height: 30px;
display: flex;
justify-content: center;
align-items: center;
}
.file-size {
color: #747474;
font-size: 12px;
padding: 5px 0 11px;
}
.circle-progress-container {
width: 20px;
height: 20px;
@ -136,7 +216,7 @@ const strokeDashoffset = computed(() =>
transform-origin: center;
}
.transform-rotate-90 {
.pause-icon {
transform: rotate(90deg);
}
</style>

View File

@ -100,7 +100,7 @@ async function onPlay() {
function pauseUpload(e) {
e.stopPropagation()
if (props.extra.is_uploading && props.extra.upload_id) {
uploadsStore.pauseVideoUpload(props.extra.upload_id)
uploadsStore.pauseUpload(props.extra.upload_id)
isPaused.value = true
}
}
@ -110,7 +110,7 @@ function resumeUpload(e) {
console.log('resumeUpload')
e.stopPropagation()
if (props.extra.is_uploading && props.extra.upload_id) {
uploadsStore.resumeVideoUpload(props.extra.upload_id)
uploadsStore.resumeUpload(props.extra.upload_id)
isPaused.value = false
}
}
@ -123,7 +123,7 @@ function retryUpload(e) {
uploadFailed.value = false
//
uploadsStore.resumeVideoUpload(props.extra.upload_id)
uploadsStore.resumeUpload(props.extra.upload_id)
message.success('正在重新上传视频...')
}
}

View File

@ -4,7 +4,7 @@ import { NModal, NInput, NScrollbar, NCheckbox, NTabs, NTab } from 'naive-ui'
import { Search, Delete } from '@icon-park/vue-next'
import { ServeGetContacts } from '@/api/contact'
import { ServeGetGroups } from '@/api/group'
import XNModal from '@/components/x-naive-ui/x-n-modal/index.vue'
const emit = defineEmits(['close', 'on-submit'])
interface Item {
@ -130,20 +130,12 @@ onLoad()
</script>
<template>
<n-modal
<x-n-modal
v-model:show="isShowBox"
preset="card"
title="选择联系人"
class="modal-radius"
style="max-width: 650px; height: 550px"
style="width: 997px; height: 740px;background-color: #F9F9FD"
:on-after-leave="onMaskClick"
:segmented="{
content: true,
footer: true
}"
:content-style="{
padding: 0
}"
>
<section class="el-container launch-box">
<aside class="el-aside bdr-r" style="width: 240px">
@ -154,6 +146,7 @@ onLoad()
<n-tab name="2"> 群聊 </n-tab>
<!-- <n-tab name="企业"> 企业 </n-tab> -->
</n-tabs>
</header>
<header class="el-header sub-header">
@ -256,7 +249,7 @@ onLoad()
</div>
</div>
</template>
</n-modal>
</x-n-modal>
</template>
<style lang="less" scoped>

View File

@ -1,6 +1,5 @@
<script lang="ts" setup>
import { ref, computed, reactive } from 'vue'
import { NIcon, NModal, NButton, NInput, NDropdown, NPopover,NImage } from 'naive-ui'
import { CloseOne, Male, Female, SendOne } from '@icon-park/vue-next'
import { ServeSearchUser } from '@/api/contact'
import { ServeCreateContact } from '@/api/contact'
@ -8,11 +7,10 @@ import { ServeContactGroupList, ServeContactMoveGroup, ServeEditContactRemark }
import { useTalkStore } from '@/store'
import { useRouter } from 'vue-router'
import xNModal from '@/components/x-naive-ui/x-n-modal/index.vue'
import { NSkeleton } from 'naive-ui'
const router = useRouter()
const talkStore = useTalkStore()
const emit = defineEmits(['update:show', 'update:uid', 'updateRemark'])
const props = defineProps({
show: {
type: Boolean,
@ -26,7 +24,7 @@ const props = defineProps({
const loading = ref(true)
const isOpenFrom = ref(false)
const state: any = reactive({
const userInfo: any = ref({
id: 0,
avatar: '',
gender: 0,
@ -43,26 +41,26 @@ const editCardPopover: any = ref(false)
const modelRemark = ref('')
const options = ref<any>([])
const groupName = computed(() => {
const item = options.value.find((item: any) => {
return item.key == state.group_id
})
// const groupName = computed(() => {
// const item = options.value.find((item: any) => {
// return item.key == state.group_id
// })
if (item) {
return item.label
}
// if (item) {
// return item.label
// }
return '未设置分组'
})
// return ''
// })
const onLoadData = () => {
ServeSearchUser({
user_id: props.uid
erp_user_id: props.uid
}).then(({ code, data }) => {
if (code == 200) {
Object.assign(state, data)
userInfo.value = data
modelRemark.value = state.remark
// modelRemark.value = state.remark
loading.value = false
} else {
@ -70,15 +68,15 @@ const onLoadData = () => {
}
})
ServeContactGroupList().then((res) => {
if (res.code == 200) {
let items = res.data.items || []
options.value = []
for (const iter of items) {
options.value.push({ label: iter.name, key: iter.id })
}
}
})
// ServeContactGroupList().then((res) => {
// if (res.code == 200) {
// let items = res.data.items || []
// options.value = []
// for (const iter of items) {
// options.value.push({ label: iter.name, key: iter.id })
// }
// }
// })
}
const onToTalk = () => {
@ -86,84 +84,84 @@ const onToTalk = () => {
emit('update:show', false)
}
const onJoinContact = () => {
if (!state.text.length) {
return window['$message'].info('备注信息不能为空')
}
// const onJoinContact = () => {
// if (!state.text.length) {
// return window['$message'].info('')
// }
ServeCreateContact({
friend_id: props.uid,
remark: state.text
}).then((res) => {
if (res.code == 200) {
isOpenFrom.value = false
window['$message'].success('申请发送成功')
} else {
window['$message'].error(res.message)
}
})
}
// ServeCreateContact({
// friend_id: props.uid,
// remark: state.text
// }).then((res) => {
// if (res.code == 200) {
// isOpenFrom.value = false
// window['$message'].success('')
// } else {
// window['$message'].error(res.message)
// }
// })
// }
const onChangeRemark = () => {
ServeEditContactRemark({
friend_id: props.uid,
remark: modelRemark.value
}).then(({ code, message }) => {
if (code == 200) {
editCardPopover.value.setShow(false)
window['$message'].success('备注成功')
state.remark = modelRemark.value
// const onChangeRemark = () => {
// ServeEditContactRemark({
// friend_id: props.uid,
// remark: modelRemark.value
// }).then(({ code, message }) => {
// if (code == 200) {
// editCardPopover.value.setShow(false)
// window['$message'].success('')
// state.remark = modelRemark.value
emit('updateRemark', {
user_id: props.uid,
remark: modelRemark.value
})
} else {
window['$message'].error(message)
}
})
}
// emit('updateRemark', {
// user_id: props.uid,
// remark: modelRemark.value
// })
// } else {
// window['$message'].error(message)
// }
// })
// }
const handleSelectGroup = (value) => {
ServeContactMoveGroup({
user_id: props.uid,
group_id: value
}).then(({ code, message }) => {
if (code == 200) {
state.group_id = value
window['$message'].success('分组修改成功')
} else {
window['$message'].error(message)
}
})
}
// const handleSelectGroup = (value) => {
// ServeContactMoveGroup({
// user_id: props.uid,
// group_id: value
// }).then(({ code, message }) => {
// if (code == 200) {
// state.group_id = value
// window['$message'].success('')
// } else {
// window['$message'].error(message)
// }
// })
// }
const reset = () => {
loading.value = true
// const reset = () => {
// loading.value = true
Object.assign(state, {
id: 0,
avatar: '',
gender: 0,
mobile: '',
motto: '',
nickname: '',
remark: '',
email: '',
status: 1,
text: ''
})
// Object.assign(state, {
// id: 0,
// avatar: '',
// gender: 0,
// mobile: '',
// motto: '',
// nickname: '',
// remark: '',
// email: '',
// status: 1,
// text: ''
// })
isOpenFrom.value = false
}
// isOpenFrom.value = false
// }
const onUpdate = (value) => {
if (!value) {
setTimeout(reset, 100)
}
// const onUpdate = (value) => {
// if (!value) {
// setTimeout(reset, 100)
// }
emit('update:show', value)
}
// emit('update:show', value)
// }
const onAfterEnter = () => {
onLoadData()
@ -171,201 +169,80 @@ const onAfterEnter = () => {
</script>
<template>
<x-n-modal content-style="padding:0;" :closable="false" class="w-311px min-h-445px" style="border-radius: 10px;overflow:hidden;" :show="show" :on-update:show="onUpdate" :on-after-enter="onAfterEnter">
<div class="section relative px-7px pt-82px pb-20px" v-loading="loading">
<div class="absolute top-9px right-7px pointer" @click="onUpdate(false)" >
<x-n-modal content-style="padding:0;" :closable="false" class="w-311px min-h-445px" style="border-radius: 10px;overflow:hidden;" :show="show" :on-after-enter="onAfterEnter">
<div class="section relative px-7px pt-82px pb-20px">
<div class="absolute top-9px right-7px pointer z-10" @click="emit('update:show', false)">
<img class="w-20px h-20px" src="@/assets/image/close.png" alt="">
</div>
<div class="flex py-10px bg-#fff px-16px rounded-4px items-center mb-10px">
<div class="w-59px h-59px rounded-8px mr-12px overflow-hidden">
<n-image width="59" :src="state.avatar" >
</n-image>
<template v-if="loading">
<div class="flex py-10px bg-#fff px-16px rounded-4px items-center mb-10px">
<div class="w-59px h-59px rounded-8px mr-12px overflow-hidden">
<n-skeleton circle height="59px" width="59px" />
</div>
<div class="w-full">
<n-skeleton text style="width: 80%; margin-bottom: 5px;" />
<n-skeleton text style="width: 60%;" />
</div>
</div>
<div class="bg-#fff rounded-4px mb-20px">
<div class="flex px-15px py-9px" v-for="i in 6" :key="i">
<n-skeleton text style="width: 30%; margin-right: 10px;" />
<n-skeleton text style="width: 60%;" />
</div>
</div>
<div>
<div class="text-#000 text-16px mb-5px">{{ state.nickname }}</div>
<div class="text-#ACACAC text-12px">工号{{ state.job_num }}</div>
<n-skeleton text style="width: 100%; height: 42px; border-radius: 4px;" />
</div>
</div>
<div class="bg-#fff rounded-4px mb-20px">
<div class="flex px-15px py-9px">
<div class="text-#000 text-12px w-84px">公司别</div>
<div class="text-#747474 text-12px">江苏泰丰文化传播股份有限公司</div>
</div>
<div class="flex px-15px py-9px">
<div class="text-#000 text-12px w-84px">主管</div>
<div class="text-#747474 text-12px">江苏泰丰文化传播股份有限公司</div>
</div>
<div class="flex px-15px py-9px">
<div class="text-#000 text-12px w-84px">部门</div>
<div class="text-#747474 text-12px">江苏泰丰文化传播股份有限公司</div>
</div>
<div class="flex px-15px py-9px">
<div class="text-#000 text-12px w-84px">手机号</div>
<div class="text-#747474 text-12px">{{ state.mobile }}</div>
</div>
<div class="flex px-15px py-9px">
<div class="text-#000 text-12px w-84px">岗位</div>
<div class="text-#747474 text-12px">江苏泰丰文化传播股份有限公司</div>
</div>
<div class="flex px-15px py-9px">
<div class="text-#000 text-12px w-84px">入职日期</div>
<div class="text-#747474 text-12px">江苏泰丰文化传播股份有限公司</div>
</div>
</div>
<div>
<n-button block color="#EEE9F8" text-color="#46299D" @click="onToTalk">
<div class="flex items-center justify-center py-11px">
<img class="w-19.8px h-20px mr-15px" src="@/assets/image/faxi@2x.png" alt="">
<span>发送消息</span>
</div>
</n-button>
</div>
<!-- <section class="el-container is-vertical">
<header class="el-header header">
<im-avatar
class="avatar"
:size="100"
:src="state.avatar"
:username="state.remark || state.nickname"
:font-size="30"
/>
</template>
<template v-else>
<div class="flex py-10px bg-#fff px-16px rounded-4px items-center mb-10px">
<div class="w-59px h-59px rounded-8px mr-12px overflow-hidden">
<n-image width="59" :src="userInfo.avatar" >
<div class="gender" v-show="state.gender > 0">
<n-icon v-if="state.gender == 1" :component="Male" color="#508afe" />
<n-icon v-if="state.gender == 2" :component="Female" color="#ff5722" />
</n-image>
</div>
<div class="close" @click="onUpdate(false)">
<close-one theme="outline" size="22" fill="#fff" :strokeWidth="2" />
<div>
<div class="text-#000 text-16px mb-5px">{{ userInfo.nickname }}</div>
<div class="text-#ACACAC text-12px">工号{{ userInfo.job_num }}</div>
</div>
<div class="nickname text-ellipsis">
{{ state.remark || state.nickname || '未设置昵称' }}
</div>
<div class="bg-#fff rounded-4px mb-20px">
<div class="flex px-15px py-9px">
<div class="text-#000 text-12px w-84px">公司别</div>
<div class="text-#747474 text-12px">{{ userInfo.company_name }}</div>
</div>
</header>
<main class="el-main main me-scrollbar me-scrollbar-thumb">
<div class="motto">
{{ state.motto || '编辑个签,展示我的独特态度。' }}
<div class="flex px-15px py-9px">
<div class="text-#000 text-12px w-84px">主管</div>
<div class="text-#747474 text-12px">{{ userInfo.leaders?.map(x=>x.user_name)?.join(',') }}</div>
</div>
<div class="infos">
<div class="info-item">
<span class="name">工号 :</span>
<span class="text">{{ state.job_num}}</span>
</div>
<div class="info-item">
<span class="name">手机 :</span>
<span class="text">{{ state.mobile }}</span>
</div>
<div class="info-item">
<span class="name">昵称 :</span>
<span class="text text-ellipsis">{{ state.nickname || '-' }} </span>
</div>
<div class="info-item">
<span class="name">性别 :</span>
<span class="text">{{
state.gender == 1 ? '男' : state.gender == 2 ? '女' : '未知'
}}</span>
</div>
<div class="info-item" v-if="state.friend_status == 2">
<span class="name">备注 :</span>
<n-popover trigger="click" placement="top-start" ref="editCardPopover">
<template #trigger>
<span class="text edit pointer text-ellipsis">
{{ state.remark || '未设置' }}&nbsp;&nbsp;
</span>
</template>
<template #header> 设置备注 </template>
<div style="display: flex">
<n-input
type="text"
placeholder="请填写备注"
:autofocus="true"
maxlength="10"
v-model:value="modelRemark"
@keydown.enter="onChangeRemark"
/>
<n-button
type="primary"
text-color="#ffffff"
class="mt-l5"
@click="onChangeRemark"
>
确定
</n-button>
</div>
</n-popover>
</div>
<div class="info-item">
<span class="name">邮箱 :</span>
<span class="text">{{ state.email || '-' }}</span>
</div>
<div class="info-item" v-if="state.friend_status == 2">
<span class="name">分组 :</span>
<n-dropdown
trigger="click"
placement="top-start"
:show-arrow="true"
:options="options"
@select="handleSelectGroup"
>
<span class="text edit pointer">{{ groupName }}</span>
</n-dropdown>
</div>
<div class="info-item">
<span class="name">入职时间 :</span>
<span class="text">{{ state.enter_date}}</span>
</div>
<div class="flex px-15px py-9px">
<div class="text-#000 text-12px w-84px">部门</div>
<div class="text-#747474 text-12px">{{ userInfo.erp_dept_position?.map(x=>x.department_name)?.join(',') }}</div>
</div>
</main>
<footer v-if="state.friend_status == 2" class="el-footer footer bdr-t flex-center">
<n-button
round
block
type="primary"
text-color="#ffffff"
@click="onToTalk"
style="width: 91%"
>
<template #icon>
<n-icon :component="SendOne" />
</template>
发送消息
<div class="flex px-15px py-9px">
<div class="text-#000 text-12px w-84px">手机号</div>
<div class="text-#747474 text-12px">{{ userInfo.tel_num }}</div>
</div>
<div class="flex px-15px py-9px">
<div class="text-#000 text-12px w-84px">岗位</div>
<div class="text-#747474 text-12px">{{ userInfo.erp_dept_position?.map(x=>x.position_name)?.join(',') }}</div>
</div>
<div class="flex px-15px py-9px">
<div class="text-#000 text-12px w-84px">入职日期</div>
<div class="text-#747474 text-12px">{{ userInfo.enter_date }}</div>
</div>
</div>
<div>
<n-button block color="#EEE9F8" text-color="#46299D" @click="onToTalk">
<div class="flex items-center justify-center py-11px">
<img class="w-19.8px h-20px mr-15px" src="@/assets/image/faxi@2x.png" alt="">
<span>发送消息</span>
</div>
</n-button>
</footer>
<footer v-else-if="state.friend_status == 1" class="el-footer footer bdr-t flex-center">
<template v-if="isOpenFrom">
<n-input
type="text"
placeholder="请填写申请备注"
v-model:value="state.text"
@keydown.enter="onJoinContact"
/>
<n-button type="primary" text-color="#ffffff" class="mt-l5" @click="onJoinContact">
确定
</n-button>
</template>
<template v-else>
<n-button
type="primary"
text-color="#ffffff"
block
round
style="width: 91%"
@click="isOpenFrom = true"
>
添加好友
</n-button>
</template>
</footer>
</section> -->
</div>
</template>
</div>
</x-n-modal>
</template>

View File

@ -80,26 +80,26 @@ export const useUploadsStore = defineStore('uploads', {
return this.items.find((item) => item.client_upload_id === clientUploadId)
},
// 暂停文件上传
pauseUpload(uploadId: string) {
const item = this.findItem(uploadId)
if (!item) return
// // 暂停文件上传
// pauseUpload(uploadId: string) {
// const item = this.findItem(uploadId)
// if (!item) return
item.is_paused = true
console.log(`暂停上传: ${uploadId}`)
},
// item.is_paused = true
// console.log(`暂停上传: ${uploadId}`)
// },
// 恢复文件上传
resumeUpload(uploadId: string) {
const item = this.findItem(uploadId)
if (!item) return
// resumeUpload(uploadId: string) {
// const item = this.findItem(uploadId)
// if (!item) return
item.is_paused = false
console.log(`恢复上传: ${uploadId}`)
// item.is_paused = false
// console.log(`恢复上传: ${uploadId}`)
// 继续上传
this.triggerUpload(uploadId)
},
// // 继续上传
// this.triggerUpload(uploadId)
// },
// 发送上传消息
async sendUploadMessage(item: any) {
@ -114,7 +114,7 @@ export const useUploadsStore = defineStore('uploads', {
}
},
// 初始化视频上传(使用分片上传方式)
// 初始化上传(使用分片上传方式)
async initUploadFile(
file: File,
talkType: number,
@ -306,16 +306,16 @@ export const useUploadsStore = defineStore('uploads', {
}
},
// 暂停视频上传
pauseVideoUpload(clientUploadId: string) {
pauseUpload(clientUploadId: string) {
const item = this.findItemByClientId(clientUploadId)
if (!item) return
item.is_paused = true
},
// 恢复视频上传
resumeVideoUpload(clientUploadId: string) {
// 恢复上传
resumeUpload(clientUploadId: string) {
const item = this.findItemByClientId(clientUploadId)
if (!item) return

View File

@ -4,7 +4,7 @@ import { ServeFindFriendApplyNum } from '@/api/contact'
import { ServeGroupApplyUnread } from '@/api/group'
import { delAccessToken } from '@/utils/auth'
import { storage } from '@/utils/storage'
import { GetUserInfo } from '@/api/auth'
interface UserStoreState {
uid: number
nickname: string
@ -35,7 +35,7 @@ export const useUserStore = defineStore('user', {
online: false, // 在线状态
isQiye: false,
isContactApply: false,
isGroupApply: false
isGroupApply: false,
}
},
getters: {},

View File

@ -18,7 +18,7 @@ export function isLoggedIn() {
*/
export function getAccessToken() {
// return storage.get(AccessToken) || ''
return JSON.parse(localStorage.getItem('token'))||'79b5c732d96d2b27a48a99dfd4a5566c43aaa5796242e854ebe3ffc198d6876b9628e7b764d9af65ab5dbb2d517ced88170491b74b048c0ba827c0d3741462cb89dc59ed46653a449af837a8262941caaef1334d640773710f8cd96473bacfb190cba595a5d6a9c87d70f0999a3ebb41147213b31b4bdccffca66a56acf3baab5af0154f0dce360079f37709f78e13711036899344bddb0fb4cf0f2890287cb62c3fcbe33368caa5e213624577be8b8420ab75b1f50775ee16142a4321c5d56995f37354a66a969da98d95ba6e65d142ed097e04b411c1ebad2f62866d0ec7e1838420530a9941dbbcd00490199f8b89574a563986daa80674dd774ef18032ee6016a202902c95452e1e81931358d4d3cb7f0db0c6fc66f406f57e411cb1e2aeb77318f7c36b2b61f48c4c645d27920f05c204fe133ab9bfa481e9c1ae2e384c'
return JSON.parse(localStorage.getItem('token'))||'46d71a72d8d845ad7ed23eba9bdde260e635407190c2ce1bf7fd22088e41682ea07773ec65cae8946d2003f264d55961f96e0fc5da10eb96d3a348c1664e9644ce2108c311309f398ae8ea1b8200bfd490e5cb6e8c52c9e5d493cbabb163368f8351420451a631dbfa749829ee4cda49b77b5ed2d3dced5d0f2b7dd9ee76ba5465c84a17c23af040cd92b6b2a4ea48befbb5c729dcdad0a9c9668befe84074cc24f78899c1d947f8e7f94c7eda5325b8ed698df729e76febb98549ef3482ae942fb4f4a1c92d21836fa784728f0c5483aab2760a991b6b36e6b10c84f840a6433a6ecc31dee36e8f1c6158818bc89d228655b2039673c1108d29c13b75d1fc2bfd3d1071f49b461822d6c2a320ae711492ac514d1a31043c8b7f2d10ff9e3869928844485d0ed575b936311797f3446e4a6c7ee1d9a63aba9445bc8b41c89143'
}
/**

View File

@ -13,7 +13,8 @@ import SkipBottom from './SkipBottom.vue'
import { ITalkRecord } from '@/types/chat'
import { EditorConst } from '@/constant/event-bus'
import { useInject, useTalkRecord, useUtil } from '@/hooks'
import { ExclamationCircleFilled } from '@ant-design/icons-vue';
import { useUserStore } from '@/store'
const props = defineProps({
uid: {
type: Number,
@ -39,17 +40,20 @@ const { useMessage } = useUtil()
const { dropdown, showDropdownMenu, closeDropdownMenu } = useMenu()
const { showUserInfoModal } = useInject()
const dialogueStore = useDialogueStore()
const userStore = useUserStore()
// const showUserInfoModal = (uid: number) => {
// userStore.getUserInfo(uid)
// }
watch(() => records, (newValue, oldValue) => {
console.log(newValue);
},{deep:true,immediate:true})
}, { deep: true, immediate: true })
//
const skipBottom = ref(false)
setTimeout(()=>{
console.log(records.value,'records.value');
},1000)
setTimeout(() => {
console.log(records.value, 'records.value');
}, 1000)
//
const isShowTalkTime = (index: number, datetime: string) => {
if (datetime == undefined) {
@ -276,11 +280,7 @@ onMounted(() => {
<template>
<section class="section">
<div
id="imChatPanel"
class="me-scrollbar me-scrollbar-thumb talk-container"
@scroll="onPanelScroll($event)"
>
<div id="imChatPanel" class="me-scrollbar me-scrollbar-thumb talk-container" @scroll="onPanelScroll($event)">
<!-- 数据加载状态栏 -->
<div class="load-toolbar pointer">
<span v-if="loadConfig.status == 0"> 正在加载数据中 ... </span>
@ -288,88 +288,54 @@ onMounted(() => {
<span v-else class="no-more"> 没有更多消息了 </span>
</div>
<div
class="message-item"
v-for="(item, index) in records"
:key="item.msg_id"
:id="item.msg_id"
>
<div class="message-item" v-for="(item, index) in records" :key="item.msg_id" :id="item.msg_id">
<!-- 系统消息 -->
<div v-if="item.msg_type >= 1000" class="message-box">
<component
:is="MessageComponents[item.msg_type] || 'unknown-message'"
:extra="item.extra"
:data="item"
/>
<component :is="MessageComponents[item.msg_type] || 'unknown-message'" :extra="item.extra" :data="item" />
</div>
<!-- 撤回消息 -->
<div v-else-if="item.is_revoke == 1" class="message-box">
<revoke-message
:login_uid="uid"
:user_id="item.user_id"
:nickname="item.nickname"
:talk_type="item.talk_type"
:datetime="item.created_at"
/>
<revoke-message :login_uid="uid" :user_id="item.user_id" :nickname="item.nickname" :talk_type="item.talk_type"
:datetime="item.created_at" />
</div>
<div
v-else
class="message-box record-box"
:class="{
'direction-rt': item.float == 'right',
'multi-select': dialogueStore.isOpenMultiSelect,
'multi-select-check': item.isCheck
}"
>
<div v-else class="message-box record-box" :class="{
'direction-rt': item.float == 'right',
'multi-select': dialogueStore.isOpenMultiSelect,
'multi-select-check': item.isCheck
}">
<!-- 多选按钮 -->
<aside v-if="dialogueStore.isOpenMultiSelect" class="checkbox-column">
<n-checkbox
size="small"
:checked="item.isCheck"
@update:checked="item.isCheck = !item.isCheck"
/>
<n-checkbox size="small" :checked="item.isCheck" @update:checked="item.isCheck = !item.isCheck" />
</aside>
<!-- 头像信息 -->
<aside class="avatar-column">
<im-avatar
class="pointer"
:src="item.avatar"
:size="42"
:username="item.nickname"
@click="showUserInfoModal(item.user_id)"
/>
<im-avatar class="pointer" :src="item.avatar" :size="42" :username="item.nickname"
@click="showUserInfoModal(item.erp_user_id)" />
</aside>
<!-- 主体信息 -->
<main class="main-column">
<div class="talk-title">
<span
class="nickname pointer"
v-show="talk_type == 2 && item.float == 'left'"
@click="onClickNickname(item)"
>
<span class="nickname pointer" v-show="talk_type == 2 && item.float == 'left'"
@click="onClickNickname(item)">
<span class="at">@</span>{{ item.nickname }}
</span>
<span>{{ parseTime(item.created_at, '{y}/{m}/{d} {h}:{i}') }}</span>
</div>
<div
class="talk-content"
:class="{ pointer: dialogueStore.isOpenMultiSelect }"
@click="onRowClick(item)"
>
<component
:is="MessageComponents[item.msg_type] || 'unknown-message'"
:extra="item.extra"
:data="item"
:max-width="true"
:source="'panel'"
@contextmenu.prevent="onContextMenu($event, item)"
/>
<div class="talk-content" :class="{ pointer: dialogueStore.isOpenMultiSelect }" @click="onRowClick(item)">
<component :is="MessageComponents[item.msg_type] || 'unknown-message'" :extra="item.extra" :data="item"
:max-width="true" :source="'panel'" @contextmenu.prevent="onContextMenu($event, item)" />
<div class="mr-10px"> <n-button text style="font-size: 20px">
<n-icon color="#CF3050">
<ExclamationCircleFilled />
</n-icon>
</n-button></div>
<!-- <div class="talk-tools">
<template v-if="talk_type == 1 && item.float == 'right'">
<loading
@ -385,19 +351,11 @@ onMounted(() => {
<span v-show="item.send_status != 1"> 已送达 </span>
</template>
<n-icon
class="more-tools pointer"
:component="MoreThree"
@click="onContextMenu($event, item)"
/>
</div> -->
<n-icon class="more-tools pointer" :component="MoreThree" @click="onContextMenu($event, item)" />
</div> -->
</div>
<div
v-if="item.extra.reply"
class="talk-reply pointer"
@click="onJumpMessage(item.extra?.reply?.msg_id)"
>
<div v-if="item.extra.reply" class="talk-reply pointer" @click="onJumpMessage(item.extra?.reply?.msg_id)">
<n-icon :component="ToTop" size="14" class="icon-top" />
<span class="ellipsis">
回复 {{ item.extra?.reply?.nickname }}:
@ -418,15 +376,8 @@ onMounted(() => {
</section>
<!-- 右键菜单 -->
<n-dropdown
:show="dropdown.show"
:x="dropdown.x"
:y="dropdown.y"
style="width: 142px;"
:options="dropdown.options"
@select="onContextMenuHandle"
@clickoutside="closeDropdownMenu"
/>
<n-dropdown :show="dropdown.show" :x="dropdown.x" :y="dropdown.y" style="width: 142px;" :options="dropdown.options"
@select="onContextMenuHandle" @clickoutside="closeDropdownMenu" />
</template>
<style lang="less" scoped>
@ -451,6 +402,7 @@ onMounted(() => {
text-align: center;
line-height: 38px;
font-size: 13px;
.no-more {
color: #b9b3b3;
}
@ -528,6 +480,7 @@ onMounted(() => {
color: var(--im-text-color);
margin-right: 5px;
font-size: 12px;
.at {
display: none;
}

View File

@ -144,34 +144,32 @@ const onSendVideoEvent = async ({ data }) => {
dialogueStore.talk.username,
uploadId,
async (percentage) => {
console.log('percentage', percentage)
//
dialogueStore.updateUploadProgress(uploadId, percentage)
},
async (videoData) => {
console.log('videoData', videoData)
//
if (videoData.code != 0) return
async () => {
dialogueStore.batchDelDialogueRecord([uploadId])
// console.log('videoData', videoData)
// //
// //
// dialogueStore.completeUpload(uploadId, {
// url: videoData.data.ori_url,
// cover: videoData.data.cover_url
// })
//
dialogueStore.completeUpload(uploadId, {
url: videoData.data.ori_url,
cover: videoData.data.cover_url
})
//
let finalMessage = {
type: 'video',
url: videoData.data.ori_url,
// //
// let finalMessage = {
// type: 'video',
// url: videoData.data.ori_url,
size: data.size
}
// size: data.size
// }
//
onSendMessage(finalMessage, () => {
//
dialogueStore.batchDelDialogueRecord([uploadId])
})
// //
// onSendMessage(finalMessage, () => {
// //
// dialogueStore.batchDelDialogueRecord([uploadId])
// })
}
)
}
@ -218,10 +216,8 @@ const onSendFileEvent = ({ data }) => {
async (percentage) => {
dialogueStore.updateUploadProgress(uploadId, percentage)
},
async (data) => {
console.log('data', data)
//
if (data.code != 0) return
async () => {
dialogueStore.batchDelDialogueRecord([uploadId])
}
)
}