feat(communication): 优化通讯录与会话消息发送体验

1. 重构DirectoryDetailDrawer为DirectoryDetailModal并完善详情弹窗功能
2. 调整MessageSendModal布局与样式,优化预填模式提示信息
3. 补充发送目标的名称展示,修复渠道标签获取逻辑
4. 统一通讯录发消息时的参数传递,添加目标名称参数
5. 修复channelTypeOptions可选链调用问题
This commit is contained in:
Kris 2026-07-08 00:18:10 +08:00
parent eb80382da0
commit 76981cd9bc
6 changed files with 334 additions and 173 deletions

View File

@ -1,10 +1,13 @@
<template>
<a-drawer
<a-modal
:open="visible"
:title="drawerTitle"
:title="modalTitle"
:width="640"
placement="right"
@close="handleClose"
:footer="null"
:destroy-on-close="true"
:mask-closable="!loading"
:body-style="{ maxHeight: '70vh', overflowY: 'auto' }"
@cancel="handleClose"
>
<!-- 用户资料 -->
<template v-if="type === 'user'">
@ -21,8 +24,41 @@
</template>
</a-alert>
<template v-else-if="data">
<h4 class="ch-dir-drawer__section-title">基本信息</h4>
<a-descriptions :column="1" size="small" bordered>
<!-- 身份卡片 -->
<div class="ch-dir-modal__identity">
<a
v-if="data.avatar_url && !avatarError"
:href="data.avatar_url"
target="_blank"
class="ch-dir-modal__avatar-link"
>
<img
:src="data.avatar_url"
alt="头像"
class="ch-dir-modal__avatar"
@error="avatarError = true"
/>
</a>
<a-avatar
v-else
:size="64"
:style="{ backgroundColor: avatarColor(data.name) }"
>
{{ avatarInitial(data.name) }}
</a-avatar>
<div class="ch-dir-modal__identity-main">
<div class="ch-dir-modal__identity-title">
<span class="ch-dir-modal__name">{{ data.name || '-' }}</span>
<a-tag size="small" color="processing">用户</a-tag>
</div>
<a-tooltip :title="targetId">
<span class="ch-dir-modal__identity-id">{{ targetId }}</span>
</a-tooltip>
</div>
</div>
<h4 class="ch-dir-modal__section-title">基本信息</h4>
<a-descriptions :column="1" size="small">
<a-descriptions-item v-for="item in descriptionItems" :key="item.key" :label="item.label">
<template v-if="item.key === 'avatar_url'">
<template v-if="item.value">
@ -30,12 +66,12 @@
v-if="!avatarError"
:href="item.value"
target="_blank"
class="ch-dir-drawer__avatar-link"
class="ch-dir-modal__avatar-link"
>
<img
:src="item.value"
alt="头像"
class="ch-dir-drawer__avatar"
class="ch-dir-modal__avatar--small"
@error="avatarError = true"
/>
</a>
@ -50,7 +86,7 @@
<span v-else>-</span>
</template>
<template v-else-if="item.key === 'tags'">
<span v-if="normalizeTags(item.value).length" class="ch-dir-drawer__tags">
<span v-if="normalizeTags(item.value).length" class="ch-dir-modal__tags">
<a-tag v-for="tag in normalizeTags(item.value)" :key="tag">{{ tag }}</a-tag>
</span>
<span v-else>-</span>
@ -59,14 +95,14 @@
{{ formatTime(item.value) }}
</template>
<template v-else>
<span class="ch-dir-drawer__mono" :title="formatValue(item.value)">
<span class="ch-dir-modal__mono" :title="formatValue(item.value)">
{{ formatValue(item.value) }}
</span>
</template>
</a-descriptions-item>
</a-descriptions>
<div v-if="targetId" class="ch-dir-drawer__actions">
<div v-if="targetId" class="ch-dir-modal__actions">
<a-tooltip
v-if="showSendMessage"
:title="canSendMessage ? '' : '请先选择渠道与账户'"
@ -79,18 +115,18 @@
</a-tooltip>
<router-link
:to="{ name: 'ChannelsSessions', query: sessionQuery }"
class="ch-dir-drawer__action-btn"
class="ch-dir-modal__action-btn"
>
<History :size="14" />
查看历史会话
</router-link>
<a-button type="link" class="ch-dir-drawer__action-btn" @click="copyId">
<a-button type="link" class="ch-dir-modal__action-btn" @click="copyId">
<template #icon><Copy :size="14" /></template>
复制 ID
</a-button>
<router-link
:to="{ name: 'ChannelsRouteBindings', query: routeBindingQuery }"
class="ch-dir-drawer__action-btn"
class="ch-dir-modal__action-btn"
>
<Route :size="14" />
查看路由绑定
@ -114,8 +150,41 @@
</template>
</a-alert>
<template v-else-if="data">
<h4 class="ch-dir-drawer__section-title">基本信息</h4>
<a-descriptions :column="1" size="small" bordered>
<!-- 身份卡片 -->
<div class="ch-dir-modal__identity">
<a
v-if="data.avatar_url && !avatarError"
:href="data.avatar_url"
target="_blank"
class="ch-dir-modal__avatar-link"
>
<img
:src="data.avatar_url"
alt="头像"
class="ch-dir-modal__avatar"
@error="avatarError = true"
/>
</a>
<a-avatar
v-else
:size="64"
:style="{ backgroundColor: avatarColor(data.name) }"
>
{{ avatarInitial(data.name) }}
</a-avatar>
<div class="ch-dir-modal__identity-main">
<div class="ch-dir-modal__identity-title">
<span class="ch-dir-modal__name">{{ data.name || '-' }}</span>
<a-tag size="small" color="success">群组</a-tag>
</div>
<a-tooltip :title="targetId">
<span class="ch-dir-modal__identity-id">{{ targetId }}</span>
</a-tooltip>
</div>
</div>
<h4 class="ch-dir-modal__section-title">基本信息</h4>
<a-descriptions :column="1" size="small">
<a-descriptions-item v-for="item in descriptionItems" :key="item.key" :label="item.label">
<template v-if="item.key === 'avatar_url'">
<template v-if="item.value">
@ -123,12 +192,12 @@
v-if="!avatarError"
:href="item.value"
target="_blank"
class="ch-dir-drawer__avatar-link"
class="ch-dir-modal__avatar-link"
>
<img
:src="item.value"
alt="头像"
class="ch-dir-drawer__avatar"
class="ch-dir-modal__avatar--small"
@error="avatarError = true"
/>
</a>
@ -143,7 +212,7 @@
<span v-else>-</span>
</template>
<template v-else-if="item.key === 'tags'">
<span v-if="normalizeTags(item.value).length" class="ch-dir-drawer__tags">
<span v-if="normalizeTags(item.value).length" class="ch-dir-modal__tags">
<a-tag v-for="tag in normalizeTags(item.value)" :key="tag">{{ tag }}</a-tag>
</span>
<span v-else>-</span>
@ -152,14 +221,14 @@
{{ formatTime(item.value) }}
</template>
<template v-else>
<span class="ch-dir-drawer__mono" :title="formatValue(item.value)">
<span class="ch-dir-modal__mono" :title="formatValue(item.value)">
{{ formatValue(item.value) }}
</span>
</template>
</a-descriptions-item>
</a-descriptions>
<div v-if="targetId" class="ch-dir-drawer__actions">
<div v-if="targetId" class="ch-dir-modal__actions">
<a-tooltip
v-if="showSendMessage"
:title="canSendMessage ? '' : '请先选择渠道与账户'"
@ -172,27 +241,27 @@
</a-tooltip>
<router-link
:to="{ name: 'ChannelsSessions', query: sessionQuery }"
class="ch-dir-drawer__action-btn"
class="ch-dir-modal__action-btn"
>
<History :size="14" />
查看历史会话
</router-link>
<a-button type="link" class="ch-dir-drawer__action-btn" @click="copyId">
<a-button type="link" class="ch-dir-modal__action-btn" @click="copyId">
<template #icon><Copy :size="14" /></template>
复制 ID
</a-button>
<router-link
:to="{ name: 'ChannelsRouteBindings', query: routeBindingQuery }"
class="ch-dir-drawer__action-btn"
class="ch-dir-modal__action-btn"
>
<Route :size="14" />
查看路由绑定
</router-link>
</div>
<div class="ch-dir-drawer__members-header">
<h4 class="ch-dir-drawer__section-title">群组成员</h4>
<span v-if="members.length" class="ch-dir-drawer__count"
<div class="ch-dir-modal__members-header">
<h4 class="ch-dir-modal__section-title">群组成员</h4>
<span v-if="members.length" class="ch-dir-modal__count"
> {{ members.length }} 条已加载</span
>
</div>
@ -206,36 +275,37 @@
style="margin-bottom: 12px"
/>
<LoadingState v-if="membersLoading" type="table" :rows="4" />
<a-table
v-else
:columns="memberColumns"
:data-source="members"
:pagination="false"
row-key="user_id"
size="small"
:locale="{ emptyText: '暂无成员数据' }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'user_id'">
<a-tooltip :title="record.user_id">
<span class="ch-dir-drawer__ellipsis">{{ record.user_id }}</span>
</a-tooltip>
<div v-else class="ch-dir-modal__members-table">
<a-table
:columns="memberColumns"
:data-source="members"
:pagination="false"
row-key="user_id"
size="small"
:locale="{ emptyText: '暂无成员数据' }"
>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'user_id'">
<a-tooltip :title="record.user_id">
<span class="ch-dir-modal__ellipsis">{{ record.user_id }}</span>
</a-tooltip>
</template>
<template v-else-if="column.key === 'display_name'">
<a-tooltip :title="getMemberDisplayName(record)">
<span class="ch-dir-modal__ellipsis">{{ getMemberDisplayName(record) }}</span>
</a-tooltip>
</template>
<template v-else-if="column.key === 'role'">
<a-tag :color="roleColor(record.role)">{{ roleLabel(record.role) }}</a-tag>
</template>
<template v-else-if="column.key === 'joined_at'">
{{ formatTime(record.joined_at) }}
</template>
</template>
<template v-else-if="column.key === 'display_name'">
<a-tooltip :title="getMemberDisplayName(record)">
<span class="ch-dir-drawer__ellipsis">{{ getMemberDisplayName(record) }}</span>
</a-tooltip>
</template>
<template v-else-if="column.key === 'role'">
<a-tag :color="roleColor(record.role)">{{ roleLabel(record.role) }}</a-tag>
</template>
<template v-else-if="column.key === 'joined_at'">
{{ formatTime(record.joined_at) }}
</template>
</template>
</a-table>
</a-table>
</div>
<div v-if="members.length" class="ch-dir-drawer__more">
<div v-if="members.length" class="ch-dir-modal__more">
<a-button
v-if="hasMoreMembers"
type="link"
@ -244,11 +314,11 @@
>
加载更多
</a-button>
<span v-else class="ch-dir-drawer__end">没有更多数据了</span>
<span v-else class="ch-dir-modal__end">没有更多数据了</span>
</div>
</template>
</template>
</a-drawer>
</a-modal>
</template>
<script setup>
@ -311,13 +381,26 @@ function avatarColor(name) {
const emit = defineEmits(['close', 'retry', 'load-more-members', 'send-message'])
const drawerTitle = computed(() => (props.type === 'user' ? '用户资料' : '群组详情'))
const modalTitle = computed(() => (props.type === 'user' ? '用户资料' : '群组详情'))
const errorInfo = computed(() => useDirectoryError(props.error))
const membersErrorInfo = computed(() => useDirectoryError(props.membersError))
/** 动态描述中排除的内部字段 */
const EXCLUDED_FIELDS = ['_raw', 'raw', 'metadata', 'extra', 'channel_type', 'account_id']
/** 动态描述中排除的字段(身份卡片已展示的不重复列出) */
const EXCLUDED_FIELDS = [
'_raw',
'raw',
'metadata',
'extra',
'channel_type',
'account_id',
'name',
'avatar_url',
'peer_id',
'peerId',
'group_id',
'groupId'
]
/** 字段中文标签映射 */
const FIELD_LABELS = {
@ -432,6 +515,7 @@ function handleSendMessage() {
if (!targetId.value) return
emit('send-message', {
peerId: targetId.value,
peerName: props.data?.name || '',
channelType: props.channelType,
accountId: props.accountId
})
@ -449,7 +533,69 @@ async function copyId() {
</script>
<style scoped lang="less">
.ch-dir-drawer {
.ch-dir-modal {
&__identity {
display: flex;
align-items: center;
gap: 16px;
padding: 16px;
margin-bottom: 20px;
background: var(--gray-10);
border: 1px solid var(--gray-150);
border-radius: 8px;
}
&__avatar-link {
display: inline-block;
flex-shrink: 0;
}
&__avatar {
width: 64px;
height: 64px;
border-radius: 6px;
object-fit: cover;
&--small {
width: 48px;
height: 48px;
border-radius: 4px;
object-fit: cover;
}
}
&__identity-main {
display: flex;
flex-direction: column;
gap: 6px;
min-width: 0;
}
&__identity-title {
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
&__name {
font-size: 18px;
font-weight: 600;
color: var(--gray-900);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__identity-id {
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 13px;
color: var(--gray-600);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
&__section-title {
margin: 0 0 12px;
font-size: 14px;
@ -490,7 +636,9 @@ async function copyId() {
flex-wrap: wrap;
align-items: center;
gap: 8px;
margin-top: 16px;
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid var(--gray-150);
}
&__action-btn {
@ -507,23 +655,19 @@ async function copyId() {
}
}
&__avatar-link {
display: inline-block;
}
&__avatar {
width: 48px;
height: 48px;
border-radius: 4px;
object-fit: cover;
}
&__tags {
display: flex;
flex-wrap: wrap;
gap: 4px;
}
&__members-table {
max-height: 320px;
overflow-y: auto;
border: 1px solid var(--gray-150);
border-radius: 6px;
}
&__more {
display: flex;
justify-content: center;
@ -535,4 +679,10 @@ async function copyId() {
font-size: 13px;
}
}
:deep(.ant-descriptions-item-label) {
color: var(--gray-600);
font-weight: 500;
width: 100px;
}
</style>

View File

@ -262,11 +262,12 @@ function handlePanelSearch() {
const sendDrawerVisible = ref(false)
const sendPreset = ref(null)
function handleSendMessage({ peerId, channelType: ct, accountId: aid }) {
function handleSendMessage({ peerId, peerName, channelType: ct, accountId: aid }) {
sendPreset.value = {
channelType: ct,
accountId: aid,
target: peerId,
targetName: peerName || '',
targetType: 'peer_id',
readonly: true
}

View File

@ -258,8 +258,8 @@
/>
</div>
<!-- 详情抽屉 -->
<DirectoryDetailDrawer
<!-- 详情弹窗 -->
<DirectoryDetailModal
:visible="detailVisible"
:type="detailType === 'users' ? 'user' : 'group'"
:loading="detailType === 'users' ? userProfileLoading : groupDetailLoading"
@ -287,7 +287,7 @@ import { Empty, message } from 'ant-design-vue'
import { Search, List, Table, ChevronRight, Download, Trash2, MessageSquare } from 'lucide-vue-next'
import ErrorRetryAlert from '@/components/channels/common/ErrorRetryAlert.vue'
import LoadingState from '@/components/external-systems/common/LoadingState.vue'
import DirectoryDetailDrawer from './DirectoryDetailDrawer.vue'
import DirectoryDetailModal from './DirectoryDetailModal.vue'
import {
useDirectoryQuery,
DIRECTORY_TAB,
@ -619,15 +619,17 @@ function handleViewDetail(entry) {
}
}
function handleSendMessage({ peerId, channelType, accountId }) {
emit('send-message', { peerId, channelType, accountId })
function handleSendMessage({ peerId, peerName, channelType, accountId }) {
emit('send-message', { peerId, peerName, channelType, accountId })
}
// ===== =====
function handleBatchSend() {
if (!selectedPeerIds.value.length || selectedPeerIds.value.length > 1) return
const entry = selectedEntries.value[0]
emit('send-message', {
peerId: selectedPeerIds.value[0],
peerName: entry?.name || '',
channelType: props.channelType,
accountId: props.accountId
})

View File

@ -1,11 +1,12 @@
<template>
<a-modal
:open="open"
width="560"
width="600"
title="发送消息"
:destroy-on-close="true"
:mask-closable="false"
:closable="!sending"
:body-style="{ maxHeight: '70vh', overflowY: 'auto', overflowX: 'hidden' }"
@cancel="handleClose"
>
<a-form
@ -18,7 +19,7 @@
@finish="handleSubmit"
@finishFailed="handleFinishFailed"
>
<!-- 预填模式上下文提示 -->
<!-- 预填模式上下文提示直接替代发送目标区域避免信息重复 -->
<a-alert
v-if="isPresetReadonly"
type="info"
@ -27,87 +28,88 @@
class="ch-send-form__preset-hint"
/>
<!-- 发送目标 -->
<h4 class="ch-send-form__section-title">发送目标</h4>
<!-- 发送目标仅非预填模式显示 -->
<template v-if="!isPresetReadonly">
<h4 class="ch-send-form__section-title">发送目标</h4>
<template v-if="isPresetReadonly">
<a-form-item label="渠道类型">
<a-tag>{{ presetChannelLabel }}</a-tag>
</a-form-item>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="渠道类型" name="channel_type">
<a-select
v-model:value="form.channel_type"
:options="channelTypes"
:loading="channelTypesLoading"
:disabled="sending"
placeholder="请选择渠道类型"
show-search
option-filter-prop="label"
@change="handleChannelTypeChange"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="账户" name="account_id">
<a-select
v-model:value="form.account_id"
:options="accounts"
:loading="accountsLoading"
:disabled="sending || !form.channel_type"
placeholder="请选择账户"
show-search
option-filter-prop="label"
/>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="账户">
<span class="ch-send-form__readonly-value">{{ presetAccountLabel }}</span>
</a-form-item>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="目标类型" name="target_type">
<a-radio-group
v-model:value="form.target_type"
:options="targetTypeOptions"
:disabled="sending"
option-type="button"
size="small"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="目标" name="target">
<a-tooltip
:title="form.target"
:disabled="!form.target || form.target.length <= 40"
placement="topLeft"
>
<a-input
v-model:value="form.target"
:disabled="sending"
:placeholder="targetPlaceholder"
allow-clear
/>
</a-tooltip>
</a-form-item>
</a-col>
</a-row>
<a-form-item label="目标">
<span class="ch-send-form__readonly-value" :title="form.target">{{ form.target }}</span>
</a-form-item>
<div class="target-type-hint">{{ targetTypeHint }}</div>
<div v-if="targetPatternHint" class="target-pattern-hint">
{{ targetPatternHint }}
</div>
</template>
<template v-else>
<a-form-item label="渠道类型" name="channel_type">
<a-select
v-model:value="form.channel_type"
:options="channelTypes"
:loading="channelTypesLoading"
:disabled="sending"
placeholder="请选择渠道类型"
show-search
option-filter-prop="label"
@change="handleChannelTypeChange"
/>
</a-form-item>
<a-form-item label="账户" name="account_id">
<a-select
v-model:value="form.account_id"
:options="accounts"
:loading="accountsLoading"
:disabled="sending || !form.channel_type"
placeholder="请选择账户"
show-search
option-filter-prop="label"
/>
</a-form-item>
<a-form-item label="目标类型" name="target_type">
<a-radio-group
v-model:value="form.target_type"
:options="targetTypeOptions"
:disabled="sending"
option-type="button"
size="small"
/>
<div class="target-type-hint">{{ targetTypeHint }}</div>
</a-form-item>
<a-form-item label="目标" name="target">
<a-tooltip
:title="form.target"
:disabled="!form.target || form.target.length <= 40"
placement="topLeft"
>
<a-input
v-model:value="form.target"
<a-row :gutter="16">
<a-col :span="isPresetReadonly ? 24 : 12">
<a-form-item label="会话策略" name="conversation_policy">
<a-select
v-model:value="form.conversation_policy"
:options="policyOptions"
:disabled="sending"
:placeholder="targetPlaceholder"
allow-clear
placeholder="请选择会话策略"
/>
</a-tooltip>
<div v-if="targetPatternHint" class="target-pattern-hint">
{{ targetPatternHint }}
</div>
</a-form-item>
</template>
<a-form-item label="会话策略" name="conversation_policy">
<a-select
v-model:value="form.conversation_policy"
:options="policyOptions"
:disabled="sending"
placeholder="请选择会话策略"
/>
</a-form-item>
</a-form-item>
</a-col>
</a-row>
<!-- 消息内容 -->
<h4 class="ch-send-form__section-title">消息内容</h4>
@ -115,7 +117,7 @@
<a-form-item label="内容" name="content">
<a-textarea
v-model:value="form.content"
:auto-size="{ minRows: 4, maxRows: 8 }"
:auto-size="{ minRows: 5, maxRows: 10 }"
:disabled="sending"
placeholder="请输入消息内容(文本 / 图片 / 链接 / @提及)"
show-count
@ -129,9 +131,9 @@
:multiple="true"
:disabled="!canUploadAttachment || sending"
>
<a-button :disabled="!canUploadAttachment || sending">
<a-button :disabled="!canUploadAttachment || sending" size="small">
<template #icon>
<Upload :size="16" />
<Upload :size="14" />
</template>
选择文件上传
</a-button>
@ -197,14 +199,16 @@ import { message } from 'ant-design-vue'
import { Upload, X, KeyRound, FileText, Image as ImageIcon } from 'lucide-vue-next'
import { useUserStore } from '@/stores/user'
import { useMessageSend } from '@/composables/channels/useMessageSend'
import { useChannelTypes } from '@/composables/channels/useChannelTypes'
const props = defineProps({
open: { type: Boolean, default: false },
/**
* 预填数据由会话详情/通讯录发消息入口注入
* 形如 { channelType, accountId, target, targetType, readonly: true }
* 形如 { channelType, accountId, target, targetName, targetType, readonly: true }
* readonly=true 时渠道/账户/目标只读预填用户仅需编辑内容
* targetType='peer_id' 时按对端 ID 触达通讯录直发场景
* targetName 为目标展示名如好友昵称优先在提示中显示
* 不传或为 null 时进入新建消息模式所有字段可编辑
*/
preset: { type: Object, default: null }
@ -213,6 +217,7 @@ const props = defineProps({
const emit = defineEmits(['update:open', 'close', 'success'])
const userStore = useUserStore()
const { getLabel: getChannelTypeLabel } = useChannelTypes()
const {
channelTypes,
channelTypesLoading,
@ -274,8 +279,8 @@ const isPresetReadonly = computed(() => !!props.preset?.readonly)
const presetChannelLabel = computed(() => {
const type = props.preset?.channelType
if (!type) return '-'
const found = channelTypes.value.find((t) => t.value === type)
return found?.label || type
// fallback capability
return getChannelTypeLabel(type) || channelTypes.value.find((t) => t.value === type)?.label || type
})
const presetAccountLabel = computed(() => {
@ -288,8 +293,10 @@ const presetAccountLabel = computed(() => {
const presetHint = computed(() => {
const channel = presetChannelLabel.value
const account = presetAccountLabel.value
const target = form.target || '-'
return `将从「${channel} / ${account}」向「${target}」发送消息`
const targetName = props.preset?.targetName
const targetId = form.target || '-'
const targetLabel = targetName || targetId
return `将从「${channel} / ${account}」向「${targetLabel}」发送消息`
})
// ===== §6=====
@ -583,30 +590,29 @@ const MAX_ATTACHMENT_SIZE = 10 * 1024 * 1024
<style scoped lang="less">
.ch-send-form {
:deep(.ant-modal-body) {
max-height: 60vh;
overflow-y: auto;
}
&__preset-hint {
:deep(.ant-form-item) {
margin-bottom: 16px;
}
&__preset-hint {
margin-bottom: 18px;
}
&__section-title {
margin: 0 0 16px;
margin: 0 0 10px;
font-size: 14px;
font-weight: 600;
color: var(--gray-900);
&:not(:first-of-type) {
margin-top: 24px;
margin-top: 16px;
}
}
&__readonly-value {
display: inline-block;
max-width: 100%;
color: var(--gray-600);
color: var(--gray-700);
font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', Menlo, monospace;
font-size: 13px;
overflow: hidden;

View File

@ -294,7 +294,7 @@ function handleHeaderCheckChange(checked) {
function channelLabel(type) {
if (!type) return '跨渠道'
const opt = channelTypeOptions.find((o) => o.value === type)
const opt = channelTypeOptions.value?.find((o) => o.value === type)
return opt ? opt.label : type
}

View File

@ -371,6 +371,8 @@ const sendPreset = computed(() => {
channelType: sessionDetail.value.channel_type,
accountId: sessionDetail.value.owner_id || undefined,
target: sessionDetail.value.peer_id || sessionDetail.value.session_id || '',
targetName: sessionDetail.value.peer_name || '',
targetType: 'peer_id',
readonly: true
}
})