mirror of
https://github.com/kuaifan/dootask.git
synced 2026-04-23 10:18:41 +00:00
feat(dialog): 重构合并转发功能
- 合并转发消息体改为存储 msg_ids + preview,不再存储完整消息列表 - 新增 mergedetail API 按需加载合并转发详情 - 详情展示从 Modal 改为 DrawerOverlay,支持完整消息渲染 - 统一不可转发消息类型过滤(tag/top/todo/notice/word-chain/vote/template) - 合并转发标题改为前端国际化拼接 - DialogWrapper 支持 staticMsgs 静态模式用于详情渲染 - 优化多选操作栏和转发确认界面样式
This commit is contained in:
parent
00a2ea3d2f
commit
6a71964592
@ -2334,6 +2334,9 @@ class DialogController extends AbstractController
|
|||||||
}
|
}
|
||||||
WebSocketDialog::checkDialog($msgs->first()->dialog_id);
|
WebSocketDialog::checkDialog($msgs->first()->dialog_id);
|
||||||
foreach ($msgs as $msg) {
|
foreach ($msgs as $msg) {
|
||||||
|
if (in_array($msg->type, WebSocketDialogMsg::$unforwardableTypes)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$res = $msg->forwardMsg($dialogids, $userids, $user, $show_source, $leave_message);
|
$res = $msg->forwardMsg($dialogids, $userids, $user, $show_source, $leave_message);
|
||||||
if (Base::isSuccess($res)) {
|
if (Base::isSuccess($res)) {
|
||||||
$allMsgs = array_merge($allMsgs, $res['data']['msgs']);
|
$allMsgs = array_merge($allMsgs, $res['data']['msgs']);
|
||||||
@ -2356,12 +2359,12 @@ class DialogController extends AbstractController
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {get} api/dialog/msg/merge-forward 合并转发消息
|
* @api {get} api/dialog/msg/mergeforward 合并转发消息
|
||||||
*
|
*
|
||||||
* @apiDescription 需要token身份
|
* @apiDescription 需要token身份
|
||||||
* @apiVersion 1.0.0
|
* @apiVersion 1.0.0
|
||||||
* @apiGroup dialog
|
* @apiGroup dialog
|
||||||
* @apiName msg__merge_forward
|
* @apiName msg__mergeforward
|
||||||
*
|
*
|
||||||
* @apiParam {Array} msg_ids 消息ID数组(最多100条)
|
* @apiParam {Array} msg_ids 消息ID数组(最多100条)
|
||||||
* @apiParam {Array} dialogids 转发给的对话ID
|
* @apiParam {Array} dialogids 转发给的对话ID
|
||||||
@ -2373,7 +2376,7 @@ class DialogController extends AbstractController
|
|||||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
* @apiSuccess {Object} data 返回数据
|
* @apiSuccess {Object} data 返回数据
|
||||||
*/
|
*/
|
||||||
public function msg__merge_forward()
|
public function msg__mergeforward()
|
||||||
{
|
{
|
||||||
$user = User::auth();
|
$user = User::auth();
|
||||||
//
|
//
|
||||||
@ -2396,6 +2399,57 @@ class DialogController extends AbstractController
|
|||||||
return WebSocketDialogMsg::mergeForwardMsg($msg_ids, $dialogids, $userids, $user, $show_source, $leave_message);
|
return WebSocketDialogMsg::mergeForwardMsg($msg_ids, $dialogids, $userids, $user, $show_source, $leave_message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} api/dialog/msg/mergedetail 合并转发消息详情
|
||||||
|
*
|
||||||
|
* @apiDescription 需要token身份
|
||||||
|
* @apiVersion 1.0.0
|
||||||
|
* @apiGroup dialog
|
||||||
|
* @apiName msg__mergedetail
|
||||||
|
*
|
||||||
|
* @apiParam {Number} msg_id 合并转发消息ID
|
||||||
|
*
|
||||||
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
|
* @apiSuccess {Object} data 返回数据
|
||||||
|
*/
|
||||||
|
public function msg__mergedetail()
|
||||||
|
{
|
||||||
|
User::auth();
|
||||||
|
//
|
||||||
|
$msg_id = intval(Request::input('msg_id'));
|
||||||
|
if ($msg_id <= 0) {
|
||||||
|
return Base::retError('参数错误');
|
||||||
|
}
|
||||||
|
$dialogMsg = WebSocketDialogMsg::find($msg_id);
|
||||||
|
if (!$dialogMsg || $dialogMsg->type !== 'merge-forward') {
|
||||||
|
return Base::retError('消息不存在或已被删除');
|
||||||
|
}
|
||||||
|
WebSocketDialog::checkDialog($dialogMsg->dialog_id);
|
||||||
|
//
|
||||||
|
$msgData = Base::json2array($dialogMsg->getRawOriginal('msg'));
|
||||||
|
$msgIds = $msgData['msg_ids'] ?? [];
|
||||||
|
if (empty($msgIds)) {
|
||||||
|
return Base::retError('消息不存在或已被删除');
|
||||||
|
}
|
||||||
|
$msgs = WebSocketDialogMsg::withTrashed()
|
||||||
|
->whereIn('id', $msgIds)
|
||||||
|
->orderBy('created_at')
|
||||||
|
->get()
|
||||||
|
->map(function ($msg) {
|
||||||
|
return [
|
||||||
|
'id' => $msg->id,
|
||||||
|
'userid' => $msg->userid,
|
||||||
|
'type' => $msg->type,
|
||||||
|
'msg' => $msg->msg,
|
||||||
|
'created_at' => $msg->created_at->toDateTimeString(),
|
||||||
|
];
|
||||||
|
});
|
||||||
|
return Base::retSuccess('success', [
|
||||||
|
'msgs' => $msgs,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {get} api/dialog/msg/emoji emoji回复
|
* @api {get} api/dialog/msg/emoji emoji回复
|
||||||
*
|
*
|
||||||
|
|||||||
@ -500,7 +500,7 @@ class Setting extends AbstractModel
|
|||||||
}
|
}
|
||||||
$limitTime = Carbon::parse($dialogMsg->created_at)->addMinutes($limitNum);
|
$limitTime = Carbon::parse($dialogMsg->created_at)->addMinutes($limitNum);
|
||||||
if ($limitTime->lt(Carbon::now())) {
|
if ($limitTime->lt(Carbon::now())) {
|
||||||
throw new ApiException('已超过' . Doo::translate(Base::forumMinuteDay($limitNum)) . ',' . $error);
|
throw new ApiException('已超过' . Base::forumMinuteDay($limitNum) . ',' . $error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -533,9 +533,17 @@ class WebSocketDialogMsg extends AbstractModel
|
|||||||
return $dialogs;
|
return $dialogs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 不支持转发的消息类型
|
||||||
|
*/
|
||||||
|
public static $unforwardableTypes = ['tag', 'top', 'todo', 'notice', 'word-chain', 'vote', 'template'];
|
||||||
|
|
||||||
public function forwardMsg($dialogids, $userids, $user, $showSource = 1, $leaveMessage = '')
|
public function forwardMsg($dialogids, $userids, $user, $showSource = 1, $leaveMessage = '')
|
||||||
{
|
{
|
||||||
return AbstractModel::transaction(function () use ($dialogids, $user, $userids, $showSource, $leaveMessage) {
|
return AbstractModel::transaction(function () use ($dialogids, $user, $userids, $showSource, $leaveMessage) {
|
||||||
|
if (in_array($this->type, self::$unforwardableTypes)) {
|
||||||
|
throw new ApiException('此类型消息不支持转发');
|
||||||
|
}
|
||||||
$msgData = Base::json2array($this->getRawOriginal('msg'));
|
$msgData = Base::json2array($this->getRawOriginal('msg'));
|
||||||
$forwardData = is_array($msgData['forward_data']) ? $msgData['forward_data'] : [];
|
$forwardData = is_array($msgData['forward_data']) ? $msgData['forward_data'] : [];
|
||||||
$forwardId = $forwardData['id'] ?: $this->id;
|
$forwardId = $forwardData['id'] ?: $this->id;
|
||||||
@ -601,31 +609,35 @@ class WebSocketDialogMsg extends AbstractModel
|
|||||||
throw new ApiException('只能合并转发同一对话的消息');
|
throw new ApiException('只能合并转发同一对话的消息');
|
||||||
}
|
}
|
||||||
WebSocketDialog::checkDialog($dialogId);
|
WebSocketDialog::checkDialog($dialogId);
|
||||||
// 收集发送者生成标题
|
// 过滤不支持转发的消息类型
|
||||||
|
$msgs = $msgs->filter(function ($msg) {
|
||||||
|
return !in_array($msg->type, self::$unforwardableTypes);
|
||||||
|
});
|
||||||
|
if ($msgs->isEmpty()) {
|
||||||
|
throw new ApiException('所选消息均不支持转发');
|
||||||
|
}
|
||||||
|
// 收集发送者信息
|
||||||
$senderIds = $msgs->pluck('userid')->unique()->values()->toArray();
|
$senderIds = $msgs->pluck('userid')->unique()->values()->toArray();
|
||||||
$senderNames = User::whereIn('userid', array_slice($senderIds, 0, 2))
|
$senderNames = User::whereIn('userid', array_slice($senderIds, 0, 2))
|
||||||
->pluck('nickname')
|
->pluck('nickname')
|
||||||
->toArray();
|
->toArray();
|
||||||
$title = implode(Doo::translate('和'), $senderNames);
|
// 组装预览列表(前4条,精简字段)
|
||||||
if (count($senderIds) > 2) {
|
$msgIds = $msgs->pluck('id')->toArray();
|
||||||
$title .= Doo::translate('等人');
|
$preview = [];
|
||||||
}
|
foreach ($msgs->take(4) as $msg) {
|
||||||
$title .= Doo::translate('的聊天记录');
|
$preview[] = [
|
||||||
// 组装消息列表
|
|
||||||
$list = [];
|
|
||||||
foreach ($msgs as $msg) {
|
|
||||||
$list[] = [
|
|
||||||
'userid' => $msg->userid,
|
'userid' => $msg->userid,
|
||||||
'type' => $msg->type,
|
'type' => $msg->type,
|
||||||
'msg' => Base::json2array($msg->getRawOriginal('msg')),
|
'msg' => self::buildPreviewMsg($msg->type, Base::json2array($msg->getRawOriginal('msg'))),
|
||||||
'created_at' => $msg->created_at->toDateTimeString(),
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
// 构建合并转发消息体
|
// 构建合并转发消息体
|
||||||
$msgData = [
|
$msgData = [
|
||||||
'title' => $title,
|
'sender_names' => $senderNames,
|
||||||
'list' => $list,
|
'sender_total' => count($senderIds),
|
||||||
'count' => count($list),
|
'msg_ids' => $msgIds,
|
||||||
|
'preview' => $preview,
|
||||||
|
'count' => count($msgIds),
|
||||||
'forward_data' => [
|
'forward_data' => [
|
||||||
'show' => $showSource,
|
'show' => $showSource,
|
||||||
'leave' => $leaveMessage ? 1 : 0,
|
'leave' => $leaveMessage ? 1 : 0,
|
||||||
@ -652,6 +664,26 @@ class WebSocketDialogMsg extends AbstractModel
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建预览消息(精简字段)
|
||||||
|
* @param string $type
|
||||||
|
* @param array $msg
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private static function buildPreviewMsg($type, $msg)
|
||||||
|
{
|
||||||
|
switch ($type) {
|
||||||
|
case 'text':
|
||||||
|
return ['text' => $msg['text'] ?? ''];
|
||||||
|
case 'file':
|
||||||
|
return ['name' => $msg['name'] ?? '', 'ext' => $msg['ext'] ?? ''];
|
||||||
|
case 'location':
|
||||||
|
return ['title' => $msg['title'] ?? ''];
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除消息
|
* 删除消息
|
||||||
* @param array|int $ids
|
* @param array|int $ids
|
||||||
@ -784,8 +816,7 @@ class WebSocketDialogMsg extends AbstractModel
|
|||||||
return self::previewTemplateMsg($data['msg']);
|
return self::previewTemplateMsg($data['msg']);
|
||||||
|
|
||||||
case 'merge-forward':
|
case 'merge-forward':
|
||||||
$action = Doo::translate("聊天记录");
|
return "[" . Doo::translate("聊天记录") . "]";
|
||||||
return "[{$action}] " . Base::cutStr($data['msg']['title'] ?? '', 50);
|
|
||||||
|
|
||||||
case 'preview':
|
case 'preview':
|
||||||
return $data['msg']['preview'];
|
return $data['msg']['preview'];
|
||||||
|
|||||||
@ -963,3 +963,13 @@ AI建议:采纳(*)建议
|
|||||||
消息内容格式错误
|
消息内容格式错误
|
||||||
AI 调用失败
|
AI 调用失败
|
||||||
AI 返回内容为空
|
AI 返回内容为空
|
||||||
|
|
||||||
|
修改AI自动分析
|
||||||
|
关联不存在
|
||||||
|
只能合并转发同一对话的消息
|
||||||
|
所选消息均不支持转发
|
||||||
|
无法创建任务对话
|
||||||
|
最多转发(*)条消息
|
||||||
|
此类型消息不支持转发
|
||||||
|
没有权限操作此任务
|
||||||
|
请选择要转发的消息
|
||||||
|
|||||||
@ -2329,3 +2329,21 @@ AI 消息助手
|
|||||||
指派
|
指派
|
||||||
关联
|
关联
|
||||||
采纳
|
采纳
|
||||||
|
|
||||||
|
逐条转发
|
||||||
|
合并转发
|
||||||
|
|
||||||
|
AI任务分析
|
||||||
|
关闭后所有项目将不再自动分析任务。
|
||||||
|
关闭后本项目将不再自动分析任务。
|
||||||
|
新建任务后AI自动分析并给出建议。
|
||||||
|
(最多(*)条)
|
||||||
|
最多选择(*)条消息
|
||||||
|
系统已关闭AI任务分析功能。
|
||||||
|
聊天记录
|
||||||
|
确定要解除与任务 #(*) 的关联吗?
|
||||||
|
共(*)条消息
|
||||||
|
已选(*)条
|
||||||
|
(*)的聊天记录
|
||||||
|
(*)和(*)的聊天记录
|
||||||
|
(*)和(*)等人的聊天记录
|
||||||
16
resources/assets/js/functions/web.js
vendored
16
resources/assets/js/functions/web.js
vendored
@ -431,6 +431,20 @@ import {convertLocalResourcePath} from "../components/Replace/utils";
|
|||||||
* @param imgClassName
|
* @param imgClassName
|
||||||
* @returns {string|*}
|
* @returns {string|*}
|
||||||
*/
|
*/
|
||||||
|
getMergeForwardTitle(msg) {
|
||||||
|
const names = msg.sender_names || [];
|
||||||
|
if (names.length === 0) {
|
||||||
|
return $A.L('聊天记录');
|
||||||
|
}
|
||||||
|
if (names.length === 1) {
|
||||||
|
return $A.L('(*)的聊天记录', names[0]);
|
||||||
|
}
|
||||||
|
if (msg.sender_total > 2) {
|
||||||
|
return $A.L('(*)和(*)等人的聊天记录', names[0], names[1]);
|
||||||
|
}
|
||||||
|
return $A.L('(*)和(*)的聊天记录', names[0], names[1]);
|
||||||
|
},
|
||||||
|
|
||||||
getMsgSimpleDesc(data, imgClassName = null) {
|
getMsgSimpleDesc(data, imgClassName = null) {
|
||||||
if (!$A.isJson(data)) {
|
if (!$A.isJson(data)) {
|
||||||
return '';
|
return '';
|
||||||
@ -462,7 +476,7 @@ import {convertLocalResourcePath} from "../components/Replace/utils";
|
|||||||
const notice = data.msg.source === 'api' ? data.msg.notice : $A.L(data.msg.notice);
|
const notice = data.msg.source === 'api' ? data.msg.notice : $A.L(data.msg.notice);
|
||||||
return $A.cutString(notice, 50)
|
return $A.cutString(notice, 50)
|
||||||
case 'merge-forward':
|
case 'merge-forward':
|
||||||
return `[${$A.L('聊天记录')}] ${$A.cutString(data.msg.title || '', 50)}`
|
return `[${$A.L('聊天记录')}] ${$A.cutString($A.getMergeForwardTitle(data.msg), 50)}`
|
||||||
case 'template':
|
case 'template':
|
||||||
return $A.templateMsgSimpleDesc(data.msg)
|
return $A.templateMsgSimpleDesc(data.msg)
|
||||||
case 'preview':
|
case 'preview':
|
||||||
|
|||||||
@ -72,7 +72,8 @@
|
|||||||
@on-error="onError"
|
@on-error="onError"
|
||||||
@on-emoji="onEmoji"
|
@on-emoji="onEmoji"
|
||||||
@on-other="onOther"
|
@on-other="onOther"
|
||||||
@on-show-emoji-user="onShowEmojiUser"/>
|
@on-show-emoji-user="onShowEmojiUser"
|
||||||
|
@on-merge-forward-detail="onMergeForwardDetail"/>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -181,7 +182,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
isSelectableMsg() {
|
isSelectableMsg() {
|
||||||
return !['tag', 'top', 'todo', 'notice'].includes(this.source.type);
|
return !['tag', 'top', 'todo', 'notice', 'word-chain', 'vote', 'template'].includes(this.source.type);
|
||||||
},
|
},
|
||||||
|
|
||||||
classArray() {
|
classArray() {
|
||||||
@ -323,6 +324,10 @@ export default {
|
|||||||
this.dispatch("on-show-emoji-user", data)
|
this.dispatch("on-show-emoji-user", data)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onMergeForwardDetail(data) {
|
||||||
|
this.dispatch("on-merge-forward-detail", data)
|
||||||
|
},
|
||||||
|
|
||||||
dispatch(event, ...arg) {
|
dispatch(event, ...arg) {
|
||||||
if (this.isReply) {
|
if (this.isReply) {
|
||||||
this.$emit(event, ...arg)
|
this.$emit(event, ...arg)
|
||||||
|
|||||||
@ -44,7 +44,7 @@
|
|||||||
<!--投票-->
|
<!--投票-->
|
||||||
<VoteMsg v-else-if="msgData.type === 'vote'" :msg="msgData.msg" :voteData="voteData" @onVote="onVote($event, msgData)"/>
|
<VoteMsg v-else-if="msgData.type === 'vote'" :msg="msgData.msg" :voteData="voteData" @onVote="onVote($event, msgData)"/>
|
||||||
<!--合并转发-->
|
<!--合并转发-->
|
||||||
<MergeForwardMsg v-else-if="msgData.type === 'merge-forward'" :msg="msgData.msg"/>
|
<MergeForwardMsg v-else-if="msgData.type === 'merge-forward'" :msg="msgData.msg" @on-view-detail="onMergeForwardDetail"/>
|
||||||
<!--模板-->
|
<!--模板-->
|
||||||
<TemplateMsg v-else-if="msgData.type === 'template'" :msg="msgData.msg" @viewText="viewText"/>
|
<TemplateMsg v-else-if="msgData.type === 'template'" :msg="msgData.msg" @viewText="viewText"/>
|
||||||
<!--等待-->
|
<!--等待-->
|
||||||
@ -592,6 +592,10 @@ export default {
|
|||||||
this.$emit("on-show-emoji-user", item)
|
this.$emit("on-show-emoji-user", item)
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onMergeForwardDetail(msg) {
|
||||||
|
this.$emit("on-merge-forward-detail", {msgId: this.msgData.id, msgData: msg})
|
||||||
|
},
|
||||||
|
|
||||||
sortEmojiUser(useris) {
|
sortEmojiUser(useris) {
|
||||||
const myList = useris.filter(item => item == this.userId);
|
const myList = useris.filter(item => item == this.userId);
|
||||||
const otherList = useris.filter(item => item != this.userId);
|
const otherList = useris.filter(item => item != this.userId);
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="content-merge-forward" @click="openDetail">
|
<div class="content-merge-forward" @click="openDetail">
|
||||||
<div class="merge-title">{{ msg.title }}</div>
|
<div class="merge-title">{{ mergeTitle }}</div>
|
||||||
<div class="merge-list">
|
<div class="merge-list">
|
||||||
<div v-for="(item, index) in displayList" :key="index" class="merge-item">
|
<div v-for="(item, index) in displayList" :key="index" class="merge-item">
|
||||||
<UserAvatar :userid="item.userid" :show-icon="false" :show-name="true" :size="14"/>
|
<UserAvatar :userid="item.userid" :show-icon="false" :show-name="true" :size="14"/>
|
||||||
@ -8,44 +8,7 @@
|
|||||||
<span class="item-desc" v-html="$A.getMsgSimpleDesc(item)"></span>
|
<span class="item-desc" v-html="$A.getMsgSimpleDesc(item)"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="merge-footer">{{ $L('共') }} {{ msg.count || msg.list.length }} {{ $L('条消息') }}</div>
|
<div class="merge-footer">{{ $L('共(*)条消息', msg.count || 0) }}</div>
|
||||||
|
|
||||||
<!-- 详情弹窗 -->
|
|
||||||
<Modal
|
|
||||||
v-model="detailShow"
|
|
||||||
:title="msg.title"
|
|
||||||
class-name="merge-forward-detail-modal"
|
|
||||||
:mask-closable="true"
|
|
||||||
width="500"
|
|
||||||
@click.native.stop>
|
|
||||||
<Scrollbar class-name="merge-detail-scroller" :style="{maxHeight: '60vh'}">
|
|
||||||
<div class="merge-detail-list">
|
|
||||||
<div v-for="(item, index) in msg.list" :key="index" class="merge-detail-item">
|
|
||||||
<UserAvatar :userid="item.userid" :size="28" :show-name="false"/>
|
|
||||||
<div class="detail-content">
|
|
||||||
<div class="detail-header">
|
|
||||||
<UserAvatar :userid="item.userid" :show-icon="false" :show-name="true" :size="0"/>
|
|
||||||
<span class="detail-time">{{ item.created_at }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="detail-body">
|
|
||||||
<template v-if="item.type === 'text'">
|
|
||||||
<pre v-html="$A.formatTextMsg(item.msg.text)"></pre>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="item.type === 'file'">
|
|
||||||
<span>[{{ $L('文件') }}] {{ item.msg.name }}</span>
|
|
||||||
</template>
|
|
||||||
<template v-else-if="item.type === 'merge-forward'">
|
|
||||||
<span>[{{ $L('聊天记录') }}] {{ item.msg.title }}</span>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span v-html="$A.getMsgSimpleDesc(item)"></span>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Scrollbar>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -58,20 +21,17 @@ export default {
|
|||||||
default: () => ({})
|
default: () => ({})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
detailShow: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
displayList() {
|
displayList() {
|
||||||
if (!this.msg || !this.msg.list) return [];
|
return this.msg?.preview || [];
|
||||||
return this.msg.list.slice(0, 4);
|
},
|
||||||
|
mergeTitle() {
|
||||||
|
return $A.getMergeForwardTitle(this.msg);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
openDetail() {
|
openDetail() {
|
||||||
this.detailShow = true;
|
this.$emit("on-view-detail", this.msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -220,6 +220,7 @@
|
|||||||
@on-emoji="onEmoji"
|
@on-emoji="onEmoji"
|
||||||
@on-other="onOther"
|
@on-other="onOther"
|
||||||
@on-show-emoji-user="onShowEmojiUser"
|
@on-show-emoji-user="onShowEmojiUser"
|
||||||
|
@on-merge-forward-detail="onMergeForwardDetail"
|
||||||
@on-multi-select-toggle="onMultiSelectToggle">
|
@on-multi-select-toggle="onMultiSelectToggle">
|
||||||
<template #header v-if="!isChildComponent">
|
<template #header v-if="!isChildComponent">
|
||||||
<div class="dialog-item head-box">
|
<div class="dialog-item head-box">
|
||||||
@ -233,9 +234,9 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--多选操作栏-->
|
<!--多选操作栏-->
|
||||||
<div v-if="multiSelectMode" class="dialog-multi-select-bar">
|
<div v-if="!isStaticMode && multiSelectMode" class="dialog-multi-select-bar">
|
||||||
<div class="multi-select-info">
|
<div class="multi-select-info">
|
||||||
<span>{{ $L('已选') }} {{ selectedMsgIds.length }} {{ $L('条') }}</span>
|
<span>{{ $L('已选(*)条', selectedMsgIds.length) }}</span>
|
||||||
<span v-if="selectedMsgIds.length >= 100" class="multi-select-max">{{ $L('(最多100条)') }}</span>
|
<span v-if="selectedMsgIds.length >= 100" class="multi-select-max">{{ $L('(最多100条)') }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="multi-select-actions">
|
<div class="multi-select-actions">
|
||||||
@ -245,7 +246,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--底部输入-->
|
<!--底部输入-->
|
||||||
<div v-show="!multiSelectMode" ref="footer" class="dialog-footer" @click="onClickFooter">
|
<div v-if="!isStaticMode" v-show="!multiSelectMode" ref="footer" class="dialog-footer" @click="onClickFooter">
|
||||||
<!--滚动到底部-->
|
<!--滚动到底部-->
|
||||||
<div
|
<div
|
||||||
v-if="scrollTail > 500 || (msgNew > 0 && allMsgs.length > 0)"
|
v-if="scrollTail > 500 || (msgNew > 0 && allMsgs.length > 0)"
|
||||||
@ -673,6 +674,26 @@
|
|||||||
</div>
|
</div>
|
||||||
</DrawerOverlay>
|
</DrawerOverlay>
|
||||||
|
|
||||||
|
<!--合并转发详情-->
|
||||||
|
<DrawerOverlay
|
||||||
|
v-model="mergeForwardShow"
|
||||||
|
placement="right"
|
||||||
|
class-name="dialog-wrapper-list"
|
||||||
|
:size="500">
|
||||||
|
<template v-if="mergeForwardShow">
|
||||||
|
<div v-if="mergeForwardLoading" style="display:flex;align-items:center;justify-content:center;height:100%">
|
||||||
|
<Spin size="large"/>
|
||||||
|
</div>
|
||||||
|
<DialogWrapper
|
||||||
|
v-else
|
||||||
|
:staticMsgs="mergeForwardMsgs"
|
||||||
|
isChildComponent
|
||||||
|
class="inde-list">
|
||||||
|
<div slot="head" class="drawer-title">{{ mergeForwardTitle }}</div>
|
||||||
|
</DialogWrapper>
|
||||||
|
</template>
|
||||||
|
</DrawerOverlay>
|
||||||
|
|
||||||
<!-- 群接龙 -->
|
<!-- 群接龙 -->
|
||||||
<DialogGroupWordChain/>
|
<DialogGroupWordChain/>
|
||||||
|
|
||||||
@ -757,6 +778,11 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
// 静态消息列表(传入时跳过 store 和 API 加载,直接渲染)
|
||||||
|
staticMsgs: {
|
||||||
|
type: Array,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
beforeBack: Function
|
beforeBack: Function
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -858,6 +884,11 @@ export default {
|
|||||||
todoViewMid: 0,
|
todoViewMid: 0,
|
||||||
todoViewId: 0,
|
todoViewId: 0,
|
||||||
|
|
||||||
|
mergeForwardShow: false,
|
||||||
|
mergeForwardData: {},
|
||||||
|
mergeForwardMsgs: [],
|
||||||
|
mergeForwardLoading: false,
|
||||||
|
|
||||||
scrollDisabled: false,
|
scrollDisabled: false,
|
||||||
scrollDirection: null,
|
scrollDirection: null,
|
||||||
scrollAction: 0,
|
scrollAction: 0,
|
||||||
@ -891,18 +922,22 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
|
if (!this.isStaticMode) {
|
||||||
emitter.on('websocketMsg', this.onWebsocketMsg);
|
emitter.on('websocketMsg', this.onWebsocketMsg);
|
||||||
emitter.on('streamMsgData', this.onMsgChange);
|
emitter.on('streamMsgData', this.onMsgChange);
|
||||||
this.keepInterval = setInterval(this.keepIntoInput, 1000)
|
this.keepInterval = setInterval(this.keepIntoInput, 1000)
|
||||||
this.windowTouch && document.addEventListener('selectionchange', this.onSelectionchange);
|
this.windowTouch && document.addEventListener('selectionchange', this.onSelectionchange);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
|
if (!this.isStaticMode) {
|
||||||
this.windowTouch && document.removeEventListener('selectionchange', this.onSelectionchange);
|
this.windowTouch && document.removeEventListener('selectionchange', this.onSelectionchange);
|
||||||
clearInterval(this.keepInterval);
|
clearInterval(this.keepInterval);
|
||||||
emitter.off('streamMsgData', this.onMsgChange);
|
emitter.off('streamMsgData', this.onMsgChange);
|
||||||
emitter.off('websocketMsg', this.onWebsocketMsg);
|
emitter.off('websocketMsg', this.onWebsocketMsg);
|
||||||
this.generateUnreadData(this.dialogId)
|
this.generateUnreadData(this.dialogId)
|
||||||
|
}
|
||||||
//
|
//
|
||||||
if (!this.isChildComponent) {
|
if (!this.isChildComponent) {
|
||||||
this.$store.dispatch('forgetInDialog', {uid: this._uid})
|
this.$store.dispatch('forgetInDialog', {uid: this._uid})
|
||||||
@ -947,7 +982,16 @@ export default {
|
|||||||
|
|
||||||
...mapGetters(['isLoad', 'isMessengerPage', 'getDialogQuote']),
|
...mapGetters(['isLoad', 'isMessengerPage', 'getDialogQuote']),
|
||||||
|
|
||||||
|
isStaticMode() {
|
||||||
|
return this.staticMsgs !== null
|
||||||
|
},
|
||||||
|
|
||||||
|
mergeForwardTitle() {
|
||||||
|
return $A.getMergeForwardTitle(this.mergeForwardData);
|
||||||
|
},
|
||||||
|
|
||||||
isReady() {
|
isReady() {
|
||||||
|
if (this.isStaticMode) return true
|
||||||
return this.dialogId > 0 && this.dialogData.id > 0
|
return this.dialogId > 0 && this.dialogData.id > 0
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -1006,6 +1050,9 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
allMsgList() {
|
allMsgList() {
|
||||||
|
if (this.isStaticMode) {
|
||||||
|
return this.staticMsgs || []
|
||||||
|
}
|
||||||
const array = [];
|
const array = [];
|
||||||
array.push(...this.dialogMsgList.filter(item => this.msgFilter(item)));
|
array.push(...this.dialogMsgList.filter(item => this.msgFilter(item)));
|
||||||
if (this.msgId > 0) {
|
if (this.msgId > 0) {
|
||||||
@ -1276,6 +1323,13 @@ export default {
|
|||||||
watch: {
|
watch: {
|
||||||
dialogId: {
|
dialogId: {
|
||||||
handler(dialog_id, old_id) {
|
handler(dialog_id, old_id) {
|
||||||
|
if (this.isStaticMode) {
|
||||||
|
this.allMsgs = (this.staticMsgs || []).map((item, index) => {
|
||||||
|
if (!item.id) item.id = index + 1
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
this.getDialogBase(dialog_id)
|
this.getDialogBase(dialog_id)
|
||||||
this.generateUnreadData(old_id)
|
this.generateUnreadData(old_id)
|
||||||
//
|
//
|
||||||
@ -3027,7 +3081,7 @@ export default {
|
|||||||
onForward(forwardData) {
|
onForward(forwardData) {
|
||||||
const isMulti = forwardData.msg_ids && forwardData.msg_ids.length > 0;
|
const isMulti = forwardData.msg_ids && forwardData.msg_ids.length > 0;
|
||||||
const url = isMulti
|
const url = isMulti
|
||||||
? (forwardData.forward_mode === 'merge' ? 'dialog/msg/merge-forward' : 'dialog/msg/forward')
|
? (forwardData.forward_mode === 'merge' ? 'dialog/msg/mergeforward' : 'dialog/msg/forward')
|
||||||
: 'dialog/msg/forward';
|
: 'dialog/msg/forward';
|
||||||
const data = {
|
const data = {
|
||||||
dialogids: forwardData.dialogids,
|
dialogids: forwardData.dialogids,
|
||||||
@ -4017,6 +4071,26 @@ export default {
|
|||||||
this.respondShow = true
|
this.respondShow = true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onMergeForwardDetail({msgId, msgData}) {
|
||||||
|
if (this.operateVisible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.mergeForwardData = msgData
|
||||||
|
this.mergeForwardMsgs = []
|
||||||
|
this.mergeForwardLoading = true
|
||||||
|
this.mergeForwardShow = true
|
||||||
|
this.$store.dispatch("call", {
|
||||||
|
url: 'dialog/msg/mergedetail',
|
||||||
|
data: { msg_id: msgId },
|
||||||
|
}).then(({data}) => {
|
||||||
|
this.mergeForwardMsgs = data.msgs || []
|
||||||
|
}).catch(_ => {
|
||||||
|
this.mergeForwardShow = false
|
||||||
|
}).finally(() => {
|
||||||
|
this.mergeForwardLoading = false
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
onOther({event, data}) {
|
onOther({event, data}) {
|
||||||
if (this.operateVisible) {
|
if (this.operateVisible) {
|
||||||
return
|
return
|
||||||
@ -4263,8 +4337,8 @@ export default {
|
|||||||
actionPermission(item, permission) {
|
actionPermission(item, permission) {
|
||||||
switch (permission) {
|
switch (permission) {
|
||||||
case 'forward':
|
case 'forward':
|
||||||
if (['word-chain', 'vote', 'template'].includes(item.type)) {
|
if (['tag', 'top', 'todo', 'notice', 'word-chain', 'vote', 'template'].includes(item.type)) {
|
||||||
return false // 投票、接龙、模板消息 不支持转发
|
return false // 系统消息、投票、接龙、模板消息 不支持转发
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@ -45,7 +45,7 @@
|
|||||||
<UserAvatar :userid="item.userid" :show-icon="false" :show-name="true" :size="16"/>
|
<UserAvatar :userid="item.userid" :show-icon="false" :show-name="true" :size="16"/>
|
||||||
<span class="preview-desc" v-html="$A.getMsgSimpleDesc(item)"></span>
|
<span class="preview-desc" v-html="$A.getMsgSimpleDesc(item)"></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="merge-preview-count">{{ $L('共') }} {{ msgIds.length }} {{ $L('条消息') }}</div>
|
<div class="merge-preview-count">{{ $L('共(*)条消息', msgIds.length) }}</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
@ -101,7 +101,7 @@
|
|||||||
<Icon @click="onAinew" class="radio-icon" :type="ainew ? 'ios-checkmark-circle' : 'ios-radio-button-off'"/>
|
<Icon @click="onAinew" class="radio-icon" :type="ainew ? 'ios-checkmark-circle' : 'ios-radio-button-off'"/>
|
||||||
<span @click="onAinew" class="radio-label">{{ $L('AI开启新会话') }}</span>
|
<span @click="onAinew" class="radio-label">{{ $L('AI开启新会话') }}</span>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!senderHidden" :class="{selected: !sender}">
|
<li v-if="!senderHidden && forwardMode !== 'merge'" :class="{selected: !sender}">
|
||||||
<Icon @click="onSender" class="radio-icon" :type="sender ? 'ios-radio-button-off' : 'ios-checkmark-circle'"/>
|
<Icon @click="onSender" class="radio-icon" :type="sender ? 'ios-radio-button-off' : 'ios-checkmark-circle'"/>
|
||||||
<span @click="onSender" class="radio-label">{{ $L('不显示原发送者信息') }}</span>
|
<span @click="onSender" class="radio-label">{{ $L('不显示原发送者信息') }}</span>
|
||||||
</li>
|
</li>
|
||||||
|
|||||||
@ -656,7 +656,9 @@
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
width: 28px;
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
margin-top: 1px;
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|
||||||
@ -666,16 +668,11 @@
|
|||||||
transition: color 0.2s;
|
transition: color 0.2s;
|
||||||
|
|
||||||
&.checked {
|
&.checked {
|
||||||
color: #2d8cf0;
|
color: $primary-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.multi-selected {
|
|
||||||
background-color: rgba(45, 140, 240, 0.06);
|
|
||||||
border-radius: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dialog-avatar {
|
.dialog-avatar {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@ -2055,12 +2052,13 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.dialog-multi-select-bar {
|
.dialog-multi-select-bar {
|
||||||
|
height: 52px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
padding: 10px 24px;
|
padding: 0 24px;
|
||||||
border-top: 1px solid #e8e8e8;
|
border-top: 1px solid #f4f5f5;
|
||||||
background-color: #fafafa;
|
background-color: #F4F5F7;
|
||||||
|
|
||||||
.multi-select-info {
|
.multi-select-info {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
@ -2076,6 +2074,10 @@
|
|||||||
.multi-select-actions {
|
.multi-select-actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
> button {
|
||||||
|
height: 28px;
|
||||||
|
padding: 0 9px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2916,56 +2918,3 @@ body.window-portrait {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.merge-forward-detail-modal {
|
|
||||||
.merge-detail-scroller {
|
|
||||||
padding: 0 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.merge-detail-list {
|
|
||||||
.merge-detail-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px 0;
|
|
||||||
|
|
||||||
& + .merge-detail-item {
|
|
||||||
border-top: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-content {
|
|
||||||
flex: 1;
|
|
||||||
min-width: 0;
|
|
||||||
|
|
||||||
.detail-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
|
|
||||||
.detail-time {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #999;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-body {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #333;
|
|
||||||
word-wrap: break-word;
|
|
||||||
word-break: break-word;
|
|
||||||
|
|
||||||
pre {
|
|
||||||
display: block;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -76,6 +76,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.reply-item {
|
||||||
|
border-bottom: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +89,8 @@
|
|||||||
|
|
||||||
.ivu-radio-group {
|
.ivu-radio-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 16px;
|
flex-wrap: wrap;
|
||||||
|
gap: 4px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user