Compare commits

..

No commits in common. "main" and "yink" have entirely different histories.
main ... yink

14 changed files with 112 additions and 688 deletions

13
env/.env.prod vendored
View File

@ -3,14 +3,5 @@ ENV = 'production'
VITE_BASE=/
VITE_ROUTER_MODE=history
VUE_APP_PREVIEW=false
VUE_APP_WEBSITE_NAME=""
# baseUrl
VITE_BASE_API = 'https://chat.szjixun.cn' #体制内
#VITE_SOCKET_API
VITE_SOCKET_API = 'wss://chat.szjixun.cn' #体制内
# EPRAPI baseUrl
VITE_EPR_BASEURL = 'https://erpapi.fontree.cn' #体制内
# 文档查看器
VITE_PAGE_URL = https://chat-pc.szjixun.cn #体制内
VITE_BASE_API=https://xxxx.xxx.com
VITE_SOCKET_API=wss://xxxx.xxxx.com

16
env/.env.prodOut vendored
View File

@ -1,16 +0,0 @@
# just a flag
ENV = 'production'
VITE_BASE=/
VITE_ROUTER_MODE=history
VUE_APP_PREVIEW=false
VUE_APP_WEBSITE_NAME=""
# baseUrl
VITE_BASE_API = 'https://chat-out.szjixun.cn' #体制外
#VITE_SOCKET_API
VITE_SOCKET_API = 'wss://chat-out.szjixun.cn' #体制外
# EPRAPI baseUrl
VITE_EPR_BASEURL = 'https://erpapi-out.szjixun.cn' #体制外
# 文档查看器
VITE_PAGE_URL = https://chat-pc-out.szjixun.cn #体制外

View File

@ -6,10 +6,8 @@
"scripts": {
"dev:test": "vite --mode test --port 5273",
"dev:prod": "vite --mode prod --port 5273",
"dev:prod:out": "vite --mode prodOut --port 5273",
"build:test": "vite build --mode test",
"build:prod": "vite build --mode prod",
"build:prod:out": "vite build --mode prodOut",
"build:prod": "vite build --mode test",
"preview": "vite preview",
"type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false",
"lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
@ -68,7 +66,6 @@
"@types/node": "^18.18.5",
"@types/vue": "^2.0.0",
"@unocss/reset": "^66.1.1",
"@vicons/tabler": "^0.13.0",
"@vitejs/plugin-vue": "^4.4.0",
"@vitejs/plugin-vue-jsx": "^3.0.2",
"@vue/tsconfig": "^0.4.0",

View File

@ -159,9 +159,6 @@ importers:
'@unocss/reset':
specifier: ^66.1.1
version: 66.3.0
'@vicons/tabler':
specifier: ^0.13.0
version: 0.13.0
'@vitejs/plugin-vue':
specifier: ^4.4.0
version: 4.6.2(vite@6.3.5(@types/node@18.19.112)(jiti@1.21.7)(less@4.3.0)(sass@1.89.2)(terser@5.43.1))(vue@3.5.17(typescript@5.2.2))
@ -1253,9 +1250,6 @@ packages:
'@vicons/ionicons5@0.13.0':
resolution: {integrity: sha512-zvZKBPjEXKN7AXNo2Na2uy+nvuv6SP4KAMQxpKL2vfHMj0fSvuw7JZcOPCjQC3e7ayssKnaoFVAhbYcW6v41qQ==}
'@vicons/tabler@0.13.0':
resolution: {integrity: sha512-AykuhiqjszkIoAL/7knIFm6RDOBS1ZmQdJfQ+RNLEah0fVsxykUFCfMBSNZh8lOzC85EtdD1k5g/sv5GYk0Ohg==}
'@vitejs/plugin-vue-jsx@3.1.0':
resolution: {integrity: sha512-w9M6F3LSEU5kszVb9An2/MmXNxocAnUb3WhRr8bHlimhDrXNt6n6D2nJQR3UXpGlZHh/EsgouOHCsM8V3Ln+WA==}
engines: {node: ^14.18.0 || >=16.0.0}
@ -5015,8 +5009,6 @@ snapshots:
'@vicons/ionicons5@0.13.0': {}
'@vicons/tabler@0.13.0': {}
'@vitejs/plugin-vue-jsx@3.1.0(vite@6.3.5(@types/node@18.19.112)(jiti@1.21.7)(less@4.3.0)(sass@1.89.2)(terser@5.43.1))(vue@3.5.17(typescript@5.2.2))':
dependencies:
'@babel/core': 7.27.4

View File

@ -296,7 +296,7 @@ import fileType_EXCEL from '@/assets/image/excel-text.png'
import fileType_WORD from '@/assets/image/word-text.png'
import fileType_PDF from '@/assets/image/pdf-text.png'
import fileType_Files from '@/assets/image/file-text.png'
import { useDialogueStore, useUserStore } from '@/store'
import { useDialogueStore } from '@/store'
import { onMounted, reactive, computed, ref, nextTick, watch } from 'vue'
import searchItem from './searchItem.vue'
import { ServeFindTalkRecords } from '@/api/chat.js'
@ -315,12 +315,10 @@ const emits = defineEmits([
])
const dialogueStore = useDialogueStore()
const userStore = useUserStore()
//
const dialogueParams = reactive({
talk_type: computed(() => dialogueStore.talk.talk_type),
receiver_id: computed(() => dialogueStore.talk.receiver_id),
uid: computed(() => userStore.uid)
receiver_id: computed(() => dialogueStore.talk.receiver_id)
})
let nowDay = new Date().setHours(0, 0, 0, 0)
@ -741,16 +739,11 @@ const downloadAndOpenFile = (item) => {
//
const toDialogueByMember = async (msgInfo) => {
console.error('====跳转到对应的记录位置====', msgInfo)
let receiver_id_ = msgInfo.receiver_id
//receiver_idid使user_id
if(msgInfo.talk_type === 1 && msgInfo.receiver_id === dialogueParams.uid){
receiver_id_ = msgInfo.user_id
}
// , sequence
dialogueStore.specifiedMsg = encodeURIComponent(
JSON.stringify({
talk_type: msgInfo.talk_type,
receiver_id: receiver_id_,
receiver_id: msgInfo.receiver_id,
msg_id: msgInfo.msg_id,
cursor: msgInfo.sequence - 15 > 0 ? msgInfo.sequence - 15 : 0,
direction: 'down',

View File

@ -1,173 +1,46 @@
<script lang="js" setup>
import { ref, computed, onMounted, watch, reactive, nextTick, getCurrentInstance, h } from 'vue'
<script lang="ts" setup>
import { ref, computed, onMounted, watch } from 'vue'
import { ServeGetTalkList } from '@/api/chat.js'
import { ServeGetGroups } from '@/api/group'
import XNModal from '@/components/x-naive-ui/x-n-modal/index.vue'
import { Plus } from '@vicons/tabler'
import { CloseCircle } from '@vicons/ionicons5'
import customModal from '@/components/common/customModal.vue'
import xSearchForm from '@/components/x-naive-ui/x-search-form/index.vue'
import xNDataTable from '@/components/x-naive-ui/x-n-data-table/index.vue'
import flTree from '@/components/flnlayout/tree/flnindex.vue'
import { processError, processSuccess } from '@/utils/helper/message.js'
import { ServeUserGroupChatList } from '@/api/search'
import { GetContactFriendList } from '@/api/chat'
import { getUserInfoByERPUserId } from '@/api/user'
import { useRouter } from 'vue-router'
import { useUtil } from '@/hooks/useUtil'
import { NButton } from 'naive-ui'
const emit = defineEmits(['close', 'on-submit'])
const { useMessage } = useUtil()
const router = useRouter()
const currentInstance = getCurrentInstance()
const $request = currentInstance?.appContext.config.globalProperties?.$request
import { CloseCircle } from '@vicons/ionicons5'
interface Item {
id: number
type: number
name: string
avatar: string
remark: string
checked: boolean
keyword: string
}
const isShowBox = defineModel('show')
const loading = ref(true)
const items = ref([])
const items = ref<Item[]>([])
const keywords = ref('')
const loadGroupStatus = ref(false)
const props = defineProps({
'forwardMode': {
type: Number,
default: 0
}
})
//
const state = reactive({
selectedGroupRowKeys: [],
isShowAddressBookModal: false,
customModalStyle: {
width: '1288px',
height: '846px',
backgroundColor: '#F9F9FD'
},
addressBookSearchConfig: [
{
label: '姓名',
key: 'nickName',
type: 'input',
valueType: 'string'
}
],
groupChatListSearchConfig: [
{
label: '群聊名称',
key: 'groupName',
type: 'input',
valueType: 'string'
}
],
treeData: [],
expandedKeys: [],
clickKey: 3,
treeRefreshCount: 0,
treeSelectData: {},
addressBookColumns: [
{
type: 'selection'
},
{
title: '姓名 【工号】',
field: 'nickname',
width: 200,
ellipsis: {
tooltip: true
},
render(row, index) {
return row.nickName + '【' + row.jobNum + '】'
}
},
{
title: '岗位名称',
field: 'user_position',
width: 400,
ellipsis: {
tooltip: true
},
render(row, index) {
let positionNames = Array.isArray(row.depPositions)
? row.depPositions.flatMap((dep) =>
Array.isArray(dep.positions) ? dep.positions.map((pos) => pos.name) : []
)
: []
return positionNames.join(' , ')
}
}
],
groupChatListColumns: [
{
type: 'selection'
},
{
title: '群聊名称',
field: 'groupName',
width: 400,
ellipsis: {
tooltip: true
},
render(row, index) {
return row.group_name
}
},
{
title: '群类型',
field: 'groupType',
width: 200,
ellipsis: true,
render(row, index) {
let groupType = row.group_type
if (groupType == 1) {
return '普通群'
} else if (groupType == 2) {
return '部门群'
} else if (groupType == 3) {
return '项目群'
} else if (groupType == 4) {
return '公司群'
}
}
}
],
addressBookData: [],
company_name: '',
groupChatListData: [],
addressBookTableHeight: 500,
addressBookTableWidth: 800,
addressBookPage: 1,
addressBookPageSize: 10,
addressBookTotal: 0,
addressBookSearchNickName: '',
addressBookCurrentTab: 'employeeAddressBook',
groupChatListPage: 1,
groupChatListPageSize: 10,
groupChatListTotal: 0,
groupChatListSearchGroupName: '',
selectedRowKeys: []
})
//
defineProps<{
forwardMode: number
}>()
//
const searchFilter = computed(() => {
return items.value.filter((item) => {
return items.value.filter((item: Item) => {
return item.name.toLowerCase().includes(keywords.value.toLowerCase())
})
})
const checkedFilter = computed(() => {
return items.value.filter((item) => item.checked)
return items.value.filter((item: Item) => item.checked)
})
const isCanSubmit = computed(() => {
return !checkedFilter.value.length
})
//
const onLoad = () => {
onLoadContact()
// onLoadGroup()
}
const onLoadContact = () => {
@ -177,7 +50,7 @@ const onLoadContact = () => {
if (res.code == 200) {
let list = res.data.items || []
items.value = list.filter((item) => ((item.talk_type === 1 && item.receiver_id !== 2) || item.talk_type !== 1)).map((item) => {
items.value = list.map((item: any) => {
return {
...item,
checked: false
@ -190,30 +63,57 @@ const onLoadContact = () => {
})
}
//
// const onLoadGroup = async () => {
// if (loadGroupStatus.value) {
// return
// }
// loading.value = true
// let { code, data } = await ServeGetGroups()
// if (code != 200) {
// loading.value = false
// return
// }
// let list = data.items.map((item: any) => {
// return {
// id: item.id,
// avatar: item.avatar,
// type: 2,
// name: item.group_name,
// keyword: item.group_name,
// remark: '',
// checked: false
// }
// })
// items.value.push(...list)
// loading.value = false
// loadGroupStatus.value = true
// }
const onMaskClick = () => {
emit('close')
}
const selectType = ref(2)
const onTriggerContact = (item: any) => {
//
if (selectType.value === 1) {
items.value.forEach(contact => {
contact.checked = false
})
}
const onTriggerContact = (item) => {
const clicked = items.value.find((val) => val.id === item.id)
if (!clicked) return
let data = items.value.find((val: any) => val.id === item.id)
//
if (!clicked.checked) {
clicked.checked = true
} else {
clicked.checked = false
if (data) {
data.checked = !data.checked
}
}
const onRemoveContact = (item) => {
let data = items.value.find((val) => val.id === item.id)
const onRemoveContact = (item: any) => {
let data = items.value.find((val: any) => val.id === item.id)
if (data) {
data.checked = false
@ -225,7 +125,7 @@ const onCancel = () => {
}
const onSubmit = () => {
let data = checkedFilter.value.map((item) => {
let data = checkedFilter.value.map((item: any) => {
return {
receiver_id: item.receiver_id,
talk_type: item.talk_type
@ -234,247 +134,30 @@ const onSubmit = () => {
emit('on-submit', data)
}
//
const onCreateContact = () => {
state.isShowAddressBookModal = true
getTreeData()
getDepPoisUser()
getUserGroupChatList()
}
// 1 2
const selectType = ref(1)
const changeSelectType = () => {
selectType.value = selectType.value == 1 ? 2 : 1
const closeAddressBookModal = () => {
state.isShowAddressBookModal = false
resetAddressBookModal()
}
const calcTreeData = (data) => {
for (let item of data) {
item.key = item.ID
item.label = item.name
item.title = item.name
if (item.sons) {
item.children = item.sons
calcTreeData(item.children)
}
delete item.ID
delete item.name
delete item.sons
}
}
const getTreeData = () => {
let url = '/department/v2/tree/filter'
let params = {}
$request.HTTP.components.postDataByParams(url, params).then(
(res) => {
if (res.status === 0 && Array.isArray(res.data.nodes)) {
let data = res.data.nodes
calcTreeData(data)
state.treeData = data
state.treeRefreshCount++
getDepPoisUser()
} else {
processError(res.msg || '获取失败!')
}
},
() => {
processError('获取失败!')
},
() => {
processError('获取失败!')
}
)
}
const getDepPoisUser = () => {
let url = '/user/v2/list'
let params = {
departmentId: state.addressBookSearchNickName ? undefined : state.clickKey,
page: state.addressBookPage,
pageSize: state.addressBookPageSize,
status: 'notactive',
nickName: state.addressBookSearchNickName
}
$request.HTTP.components.postDataByParams(url, params).then((res) => {
if (res.code === 200) {
state.addressBookData = res?.data?.data || []
state.addressBookTotal = res?.data?.count || 0
}
//
items.value.forEach(item => {
item.checked = false
})
}
const getUserGroupChatList = () => {
let params = {
page: state.groupChatListPage,
page_size: state.groupChatListPageSize,
group_name: state.groupChatListSearchGroupName
}
ServeUserGroupChatList(params).then((res) => {
if (res.code === 200) {
state.groupChatListData = res?.data?.items || []
state.groupChatListTotal = res?.data?.total || 0
}
})
}
const handleTreeClick = ({ selectedKey, tree }) => {
state.clickKey = tree.key
state.treeSelectData = tree
state.addressBookPage = 1
getDepPoisUser()
}
const resetAddressBookModal = () => {
nextTick(() => {
state.addressBookCurrentTab = 'employeeAddressBook'
state.addressBookSearchNickName = ''
state.groupChatListSearchGroupName = ''
state.addressBookTableWidth = 800
state.clickKey = 3
state.treeRefreshCount++
state.addressBookPage = 1
state.addressBookPageSize = 10
state.groupChatListPage = 1
state.groupChatListPageSize = 10
state.selectedRowKeys = []
getDepPoisUser()
getUserGroupChatList()
})
}
const handleAddressBookPagination = (page) => {
state.addressBookPage = page
getDepPoisUser()
}
const handleAddressBookPaginationSize = (pageSize) => {
state.addressBookPageSize = pageSize
state.addressBookPage = 1
getDepPoisUser()
}
const changeAddressBookSearch = (value) => {
if (!value.nickName?.trim()) {
state.addressBookSearchNickName = ''
} else {
state.addressBookSearchNickName = value.nickName
}
}
const handleAddressBookTabChange = (value) => {
state.addressBookCurrentTab = value
//
if (value === 'employeeAddressBook') {
state.selectedGroupRowKeys = []
} else if (value === 'groupChatList') {
state.selectedRowKeys = []
}
}
const changeGroupChatListSearch = (value) => {
if (!value.groupName?.trim()) {
state.groupChatListSearchGroupName = ''
} else {
state.groupChatListSearchGroupName = value.groupName
}
}
const handleGroupChatListPagination = (page) => {
state.groupChatListPage = page
getUserGroupChatList()
}
const handleGroupChatListPaginationSize = (pageSize) => {
state.groupChatListPageSize = pageSize
state.groupChatListPage = 1
getUserGroupChatList()
}
const onAddressBookCancel = () => {
state.isShowAddressBookModal = false
}
const onAddressBookSubmit = async () => {
if (state.addressBookCurrentTab === 'employeeAddressBook') {
if (!Array.isArray(state.selectedRowKeys) || state.selectedRowKeys.length === 0) {
processError('请选择联系人')
return
}
try {
const results = await Promise.all(
state.selectedRowKeys.map((erpId) => getUserInfoByERPUserId({ erp_user_id: erpId }))
)
const data = results
.filter((res) => res && res.code === 200 && res.data && res.data.sys_id)
.map((res) => ({ receiver_id: res.data.sys_id, talk_type: 1 }))
if (data.length === 0) {
processError('未获取到有效联系人')
return
}
emit('on-submit', data)
state.isShowAddressBookModal = false
} catch (e) {
processError('发送失败,请稍后重试')
}
} else if (state.addressBookCurrentTab === 'groupChatList') {
if (!Array.isArray(state.selectedGroupRowKeys) || state.selectedGroupRowKeys.length === 0) {
processError('请选择群聊')
return
}
const data = state.selectedGroupRowKeys.map((gid) => ({ receiver_id: gid, talk_type: 2 }))
emit('on-submit', data)
state.isShowAddressBookModal = false
}
}
watch(() => {
watch(()=>{
return isShowBox.value
}, (newVal) => {
if (newVal) {
onLoad()
}
})
watch(() => state.addressBookSearchNickName, (newValue, oldValue) => {
if (newValue) {
state.addressBookTableWidth = 1142
state.addressBookPage = 1
} else {
state.addressBookTableWidth = 800
state.clickKey = 3
state.treeRefreshCount++
state.addressBookPage = 1
}
getDepPoisUser()
})
watch(() => state.groupChatListSearchGroupName, (newValue, oldValue) => {
if (newValue) {
state.groupChatListPage = 1
} else {
state.groupChatListPage = 1
}
getUserGroupChatList()
},(newVal)=>{
if(newVal){
onLoad()
}
})
</script>
<template>
<x-n-modal v-model:show="isShowBox" :title="forwardMode === 2 ? '合并转发' : '逐条转发'" style="width: 997px; height: 740px;background-color: #F9F9FD"
:on-after-leave="onMaskClick" content-style="display: flex; justify-content: center; align-items: center;">
<div class="w-927px h-627px bg-#fff rounded-3px px-35px pb-20px pt-10px">
<div class="w-100% flex justify-end mb-5px">
<n-button text type="primary" @click="onCreateContact">
<template #icon>
<Plus />
</template>
创建新聊天
</n-button>
</div>
<div class="w-927px h-627px bg-#fff rounded-3px px-35px py-20px">
<div class="flex items-center justify-between mb-28px">
<div class="text-#333639">搜索</div>
<div class="w-779px h-34px">
@ -485,19 +168,19 @@ watch(() => state.groupChatListSearchGroupName, (newValue, oldValue) => {
</div>
<div class="flex justify-between">
<div class="w-260px h-517px rounded-4px border-1px border-solid border-#E5E5E5 px-12px">
<!-- <div class="border-b-2px border-b-solid border-b-#FBFBFB h-35px flex items-center justify-end">
<div class="border-b-2px border-b-solid border-b-#FBFBFB h-35px flex items-center justify-end">
<n-button text color="#46299D" class="text-14px" @click="changeSelectType">
{{ selectType === 1 ? '多选' : '取消多选' }}
</n-button>
</div> -->
</div>
<div>
<n-virtual-list v-if="!loading" style="max-height: 470px" :item-size="65" :items="searchFilter">
<template #default="{ item }">
<div class="flex items-center border-b-2px border-b-solid h-65px border-b-#FBFBFB"
>
@click="onTriggerContact(item)">
<div class="mr-22px">
<n-checkbox v-model:checked="item.checked" />
<n-radio v-if="selectType === 1" :checked="item.checked" />
<n-checkbox v-else :checked="item.checked" />
</div>
<div class="mr-10px">
@ -567,7 +250,7 @@ watch(() => state.groupChatListSearchGroupName, (newValue, oldValue) => {
</span>
<span v-else>请选择联系人</span>
</div>
<div class="flex justify-center items-center ">
<div class="flex justify-center items-center">
<n-button color="#C7C7C9" class="w-250px h-34px text-14px text-#fff mr-10px" @click="onCancel">取消</n-button>
<n-button color="#46299D" class="w-250px h-34px text-14px text-#fff"
@click="onSubmit" :disabled="isCanSubmit">发送</n-button>
@ -576,175 +259,5 @@ watch(() => state.groupChatListSearchGroupName, (newValue, oldValue) => {
</div>
</div>
</div>
</x-n-modal>
<!-- 通讯录弹窗 -->
<customModal
v-model:show="state.isShowAddressBookModal"
title="通讯录"
:style="state.customModalStyle"
:customCloseBtn="true"
:closable="false"
:customCloseEvent="true"
@customCloseModal="closeAddressBookModal"
>
<template #content>
<div class="custom-modal-content">
<n-card style="padding: 0 12px">
<n-tabs
type="line"
@update:value="handleAddressBookTabChange"
tab-style="font-size: 16px; font-weight: 600;color: #8B8B8B;"
>
<n-tab name="employeeAddressBook">员工通讯录</n-tab>
<n-tab name="groupChatList">群聊列表</n-tab>
</n-tabs>
<xSearchForm
v-if="state.addressBookCurrentTab == 'employeeAddressBook'"
:search-config="state.addressBookSearchConfig"
customInputPlaceholder="请输入姓名"
@change="changeAddressBookSearch"
:cols="3"
></xSearchForm>
<xSearchForm
v-if="state.addressBookCurrentTab == 'groupChatList'"
:search-config="state.groupChatListSearchConfig"
customInputPlaceholder="请输入群聊名称"
@change="changeGroupChatListSearch"
:cols="3"
></xSearchForm>
<div class="addressBook-content" v-if="state.addressBookCurrentTab == 'employeeAddressBook'">
<div class="addressBook-tree" v-if="!state.addressBookSearchNickName">
<fl-tree
:data="state.treeData"
:expandedKeys="state.expandedKeys"
:refreshCount="state.treeRefreshCount"
:clickKey="state.clickKey"
@triggerTreeClick="handleTreeClick"
></fl-tree>
</div>
<div class="addressBook-table">
<xNDataTable
:columns="state.addressBookColumns"
:data="state.addressBookData"
:style="{
height: `${state.addressBookTableHeight}px`,
width: `${state.addressBookTableWidth}px`
}"
:row-key="row => row.ID"
v-model:checked-row-keys="state.selectedRowKeys"
flex-height
></xNDataTable>
<div class="addressBook-pagination">
<n-pagination
v-model:page="state.addressBookPage"
v-model:page-size="state.addressBookPageSize"
:item-count="state.addressBookTotal"
show-quick-jumper
show-size-picker
:page-sizes="[10, 20, 50]"
:on-update:page="handleAddressBookPagination"
:on-update:page-size="handleAddressBookPaginationSize"
>
<template #prefix="{ itemCount }"> {{ itemCount }} 条记录 </template>
</n-pagination>
</div>
</div>
</div>
<div class="groupChatList-content" v-if="state.addressBookCurrentTab == 'groupChatList'">
<div class="groupChatList-table">
<xNDataTable
:columns="state.groupChatListColumns"
:data="state.groupChatListData"
:style="{
height: '500px',
width: '1148px'
}"
:row-key="row => row.id"
v-model:checked-row-keys="state.selectedGroupRowKeys"
flex-height
></xNDataTable>
<div class="groupChatList-pagination">
<n-pagination
v-model:page="state.groupChatListPage"
v-model:page-size="state.groupChatListPageSize"
:item-count="state.groupChatListTotal"
show-quick-jumper
show-size-picker
:page-sizes="[10, 20, 50]"
:on-update:page="handleGroupChatListPagination"
:on-update:page-size="handleGroupChatListPaginationSize"
>
<template #prefix="{ itemCount }"> {{ itemCount }} 条记录 </template>
</n-pagination>
</div>
</div>
</div>
<!-- 底部操作按钮仅保留展示 -->
<div style="display: flex; justify-content: center; align-items: center; padding: 10px 0;">
<n-button color="#C7C7C9" class="w-200px h-34px text-14px text-#fff mr-10px" @click="onAddressBookCancel">取消</n-button>
<n-button color="#46299D" class="w-200px h-34px text-14px text-#fff" @click="onAddressBookSubmit">发送</n-button>
</div>
</n-card>
</div>
</template>
</customModal>
</template>
<style scoped lang="scss">
.custom-modal-content {
.addressBook-content {
display: flex;
flex-direction: row;
gap: 20px;
.addressBook-tree {
width: 328px;
height: 500px;
overflow: auto;
border: 1px solid #efeff5;
border-radius: 4px;
padding: 12px 20px;
box-sizing: border-box;
}
.addressBook-table {
:deep(.n-data-table-th) {
background-color: #46299d;
color: #fff;
}
.addressBook-pagination {
display: flex;
justify-content: flex-end;
align-items: center;
padding: 22px 0 0;
box-sizing: border-box;
}
}
}
.groupChatList-content {
display: flex;
flex-direction: row;
gap: 20px;
.groupChatList-table {
:deep(.n-data-table-th) {
background-color: #46299d;
color: #fff;
}
.groupChatList-pagination {
display: flex;
justify-content: flex-end;
align-items: center;
padding: 22px 0 0;
box-sizing: border-box;
}
}
}
}
</style>

View File

@ -9,8 +9,6 @@ import { ServeClearTalkUnreadNum, ServeCreateTalkList } from '@/api/chat'
import { useTalkStore, useDialogueStore, useSettingsStore } from '@/store'
import { isScrollAtBottom, scrollToBottom } from '@/utils/dom'
const talkCreateThrottle = new Map()
/**
* 好友状态事件
*/
@ -114,25 +112,7 @@ class Talk extends Base {
//群成员被移出时,需要热更新会话列表
await useTalkStore().loadTalkList()
} else {
// 如果发送者ID为2则为聊天助手不在pc端处理不创建会话
if ((this.talk_type === 1 && this.receiver_id !== 2) || this.talk_type !== 1) {
// 节流:相同会话在 5 秒内只创建一次
const idx = this.getIndexName()
const now = Date.now()
const last = talkCreateThrottle.get(idx) || 0
if (now - last < 5000) {
return
}
talkCreateThrottle.set(idx, now)
try {
return this.addTalkItem()
} finally {
// 不立即清除时间戳,保留自然过期窗口
// 可根据需要在 addTalkItem 成功后也不清除,以维持 5s 窗口
}
} else {
return
}
return this.addTalkItem()
}
}
@ -187,8 +167,7 @@ class Talk extends Base {
}).then(({ code, data }) => {
if (code == 200) {
let item = formatTalkItem(data)
// 如果是自己发送的消息,不应该标记为未读
item.unread_num = this.isCurrSender() ? 0 : 1
item.unread_num = 1
useTalkStore().addItem(item)
}
})
@ -298,12 +277,10 @@ class Talk extends Base {
* 更新对话列表记录
*/
updateTalkItem() {
console.error("触发了更新")
useTalkStore().updateMessage({
index_name: this.getIndexName(),
msg_text: this.getTalkText(),
updated_at: parseTime(new Date()),
isCurrSender: this.isCurrSender()
updated_at: parseTime(new Date())
})
//收到新消息时,同时判断是否有人@我
if (this.resource.msg_type === 1 && this.resource?.extra?.mentions?.length > 0) {

View File

@ -62,10 +62,10 @@ const router = createRouter({
// 设置中间件,权限验证
router.beforeEach((to) => {
if (to.meta?.auth && !isLoggedIn()) {
// return {
// path: '/auth/login',
// query: { redirect: to.fullPath }
// }
return {
path: '/auth/login',
query: { redirect: to.fullPath }
}
}
})

View File

@ -103,10 +103,7 @@ export const useTalkStore = defineStore('talk', {
const item = this.items.find((item) => item.index_name === params.index_name)
if (item) {
if (!params?.isCurrSender) {
//如果消息不是自己发的,才更新未读数量
item.unread_num++
}
item.unread_num++
item.msg_text = params.msg_text
item.updated_at = params.updated_at
@ -168,7 +165,7 @@ export const useTalkStore = defineStore('talk', {
if (resp.code == 200) {
// 将服务器返回的会话列表转换为应用所需格式
const serverItems = resp.data.items.filter((item: any) => ((item.talk_type === 1 && item.receiver_id !== 2) || item.talk_type !== 1)).map((item: any) => {
const serverItems = resp.data.items.map((item: any) => {
const value = formatTalkItem(item)
const draft = useEditorDraftStore().items[value.index_name]

View File

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

View File

@ -42,7 +42,7 @@ service.interceptors.response.use(
if (response && response.data.status === 409) {
// 账户另一处登录 此处踢下线
message.error('您的账号已在其他设备登录')
// $router.push("/login");
$router.push("/login");
return;
}
if (response && response.data.code === 401) {
@ -88,7 +88,7 @@ const getRefreshToken = async (response) => {
return Promise.resolve(service(response.config));
} else {
// 跳转登录页
// $router.push("/login");
$router.push("/login");
res.message = res.message || res.msg;
return Promise.reject(res);
}
@ -100,7 +100,7 @@ const getRefreshToken = async (response) => {
refreshSubscribers = [];
}
} else {
// $router.push("/login");
$router.push("/login");
return Promise.reject(response);
}
} else {

View File

@ -11,7 +11,7 @@ import {
nextTick
} from 'vue'
import { onBeforeRouteUpdate } from 'vue-router'
import { useDialogueStore, useTalkStore, useUserStore } from '@/store'
import { useDialogueStore, useTalkStore } from '@/store'
import {
NDropdown,
NIcon,
@ -62,10 +62,6 @@ const {
const dialogueStore = useDialogueStore()
const talkStore = useTalkStore()
const userStore = useUserStore()
const userParams = reactive({
uid: computed(() => userStore.uid)
})
const isShowGroup = ref(false)
const searchKeyword = ref('')
const topItems = computed((): ISession[] => talkStore.topItems)
@ -606,11 +602,6 @@ const onTabTalk = (item: ISession, follow = false) => {
console.log('item.index_name === indexName.value', item.index_name === indexName.value)
if (item.index_name === indexName.value) return
if (dialogueStore.isOpenMultiSelect) {
//
dialogueStore.closeMultiSelect()
}
searchKeyword.value = ''
dialogueStore.isManualSwitch = true
@ -773,9 +764,9 @@ const getDepPoisUser = () => {
}
$request.HTTP.components.postDataByParams(url, params).then((res) => {
// console.log(res)
if (res.code === 200) {
state.addressBookData = res?.data?.data || []
state.addressBookTotal = res?.data?.count || 0
if (res.status === 0 && Array.isArray(res.data.data)) {
state.addressBookData = res.data.data || []
state.addressBookTotal = res.data.count
}
})
// let params = {
@ -977,16 +968,11 @@ const handleClickSearchItem = (searchText, searchResultKey, talk_type, receiver_
const handleClickSearchResultItem = (searchText, searchResultKey, talk_type, receiver_id, res) => {
const result = JSON.parse(decodeURIComponent(res))
console.error(result, 'result')
let receiver_id_ = receiver_id
//receiver_idid使user_id
if(talk_type === 1 && receiver_id === userParams.uid){
receiver_id_ = result.user_id
}
// , sequence
dialogueStore.specifiedMsg = encodeURIComponent(
JSON.stringify({
talk_type,
receiver_id: receiver_id_,
receiver_id,
msg_id: result.msg_id,
cursor: result.sequence - 15 > 0 ? result.sequence - 15 : 0,
direction: 'down',
@ -995,7 +981,7 @@ const handleClickSearchResultItem = (searchText, searchResultKey, talk_type, rec
})
)
console.error(dialogueStore.specifiedMsg, 'dialogueStore.specifiedMsg')
talkStore.toTalk(talk_type, receiver_id_, router)
talkStore.toTalk(talk_type, receiver_id, router)
state.isShowSearchRecordModal = false
state.searchRecordText = ''
searchKeyword.value = ''

View File

@ -21,7 +21,6 @@ import { confirmBox } from '@/components/confirm-box/service.js'
import ws from '@/connect'
import { useRouter } from 'vue-router'
import avatarModule from '@/components/avatar-module/index.vue'
import { scrollToBottom } from '@/utils/dom'
const router = useRouter()
@ -781,11 +780,6 @@ const loadMoreReadListDetail = () => {
const onCustomSkipBottomEvent = () => {
console.log('onCustomSkipBottomEvent')
if (dialogueStore.isOpenMultiSelect) {
//
scrollToBottom()
return
}
onLoad({ ...props, limit: 30 })
// scrollToBottom()
}

View File

@ -46,9 +46,9 @@ export default defineConfig(({ mode }) => {
vueJsx({}),
compressPlugin(),
UnoCSS(),
vueDevTools({
launchEditor: 'trae',
})
// vueDevTools({
// launchEditor: 'trae',
// })
],
define: {
__APP_ENV__: env.APP_ENV