mirror of
https://github.com/kuaifan/dootask.git
synced 2026-09-07 12:38:57 +00:00
feat(task): 实现消息合并转发功能,支持批量选择和转发消息
This commit is contained in:
parent
4b32472d64
commit
984b98e4fc
@ -2311,6 +2311,7 @@ class DialogController extends AbstractController
|
|||||||
{
|
{
|
||||||
$user = User::auth();
|
$user = User::auth();
|
||||||
//
|
//
|
||||||
|
$msg_ids = Request::input('msg_ids');
|
||||||
$msg_id = intval(Request::input("msg_id"));
|
$msg_id = intval(Request::input("msg_id"));
|
||||||
$dialogids = Request::input('dialogids');
|
$dialogids = Request::input('dialogids');
|
||||||
$userids = Request::input('userids');
|
$userids = Request::input('userids');
|
||||||
@ -2321,6 +2322,30 @@ class DialogController extends AbstractController
|
|||||||
return Base::retError("请选择对话或成员");
|
return Base::retError("请选择对话或成员");
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
|
// 支持批量逐条转发
|
||||||
|
if (!empty($msg_ids) && is_array($msg_ids)) {
|
||||||
|
if (count($msg_ids) > 100) {
|
||||||
|
return Base::retError("最多转发100条消息");
|
||||||
|
}
|
||||||
|
$allMsgs = [];
|
||||||
|
$msgs = WebSocketDialogMsg::whereIn('id', $msg_ids)->orderBy('created_at')->get();
|
||||||
|
if ($msgs->isEmpty()) {
|
||||||
|
return Base::retError("消息不存在或已被删除");
|
||||||
|
}
|
||||||
|
WebSocketDialog::checkDialog($msgs->first()->dialog_id);
|
||||||
|
foreach ($msgs as $msg) {
|
||||||
|
$res = $msg->forwardMsg($dialogids, $userids, $user, $show_source, $leave_message);
|
||||||
|
if (Base::isSuccess($res)) {
|
||||||
|
$allMsgs = array_merge($allMsgs, $res['data']['msgs']);
|
||||||
|
}
|
||||||
|
// 留言只在第一条时发送,后续不再重复
|
||||||
|
$leave_message = '';
|
||||||
|
}
|
||||||
|
return Base::retSuccess('转发成功', [
|
||||||
|
'msgs' => $allMsgs
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
//
|
||||||
$msg = WebSocketDialogMsg::whereId($msg_id)->first();
|
$msg = WebSocketDialogMsg::whereId($msg_id)->first();
|
||||||
if (empty($msg)) {
|
if (empty($msg)) {
|
||||||
return Base::retError("消息不存在或已被删除");
|
return Base::retError("消息不存在或已被删除");
|
||||||
@ -2330,6 +2355,47 @@ class DialogController extends AbstractController
|
|||||||
return $msg->forwardMsg($dialogids, $userids, $user, $show_source, $leave_message);
|
return $msg->forwardMsg($dialogids, $userids, $user, $show_source, $leave_message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} api/dialog/msg/merge-forward 合并转发消息
|
||||||
|
*
|
||||||
|
* @apiDescription 需要token身份
|
||||||
|
* @apiVersion 1.0.0
|
||||||
|
* @apiGroup dialog
|
||||||
|
* @apiName msg__merge_forward
|
||||||
|
*
|
||||||
|
* @apiParam {Array} msg_ids 消息ID数组(最多100条)
|
||||||
|
* @apiParam {Array} dialogids 转发给的对话ID
|
||||||
|
* @apiParam {Array} userids 转发给的成员ID
|
||||||
|
* @apiParam {Number} show_source 是否显示原发送者信息
|
||||||
|
* @apiParam {String} leave_message 转发留言
|
||||||
|
*
|
||||||
|
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||||
|
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||||
|
* @apiSuccess {Object} data 返回数据
|
||||||
|
*/
|
||||||
|
public function msg__merge_forward()
|
||||||
|
{
|
||||||
|
$user = User::auth();
|
||||||
|
//
|
||||||
|
$msg_ids = Request::input('msg_ids');
|
||||||
|
$dialogids = Request::input('dialogids');
|
||||||
|
$userids = Request::input('userids');
|
||||||
|
$show_source = intval(Request::input("show_source"));
|
||||||
|
$leave_message = Request::input('leave_message');
|
||||||
|
//
|
||||||
|
if (empty($dialogids) && empty($userids)) {
|
||||||
|
return Base::retError("请选择对话或成员");
|
||||||
|
}
|
||||||
|
if (empty($msg_ids) || !is_array($msg_ids)) {
|
||||||
|
return Base::retError("请选择要转发的消息");
|
||||||
|
}
|
||||||
|
if (count($msg_ids) > 100) {
|
||||||
|
return Base::retError("最多转发100条消息");
|
||||||
|
}
|
||||||
|
//
|
||||||
|
return WebSocketDialogMsg::mergeForwardMsg($msg_ids, $dialogids, $userids, $user, $show_source, $leave_message);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {get} api/dialog/msg/emoji emoji回复
|
* @api {get} api/dialog/msg/emoji emoji回复
|
||||||
*
|
*
|
||||||
|
|||||||
@ -492,6 +492,47 @@ class WebSocketDialogMsg extends AbstractModel
|
|||||||
* @param string $leaveMessage 转发留言
|
* @param string $leaveMessage 转发留言
|
||||||
* @return mixed
|
* @return mixed
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* 收集目标对话
|
||||||
|
* @param array|int $userids 转发给的成员ID
|
||||||
|
* @param array|int $dialogids 转发给的对话ID
|
||||||
|
* @param User $user 当前用户
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private static function collectTargetDialogs($userids, $dialogids, $user)
|
||||||
|
{
|
||||||
|
$dialogs = [];
|
||||||
|
if ($userids) {
|
||||||
|
if (!is_array($userids)) {
|
||||||
|
$userids = [$userids];
|
||||||
|
}
|
||||||
|
foreach ($userids as $userid) {
|
||||||
|
if (!User::whereUserid($userid)->exists()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$dialog = WebSocketDialog::checkUserDialog($user, $userid);
|
||||||
|
if ($dialog) {
|
||||||
|
$dialogs[$dialog->id] = $dialog;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($dialogids) {
|
||||||
|
if (!is_array($dialogids)) {
|
||||||
|
$dialogids = [$dialogids];
|
||||||
|
}
|
||||||
|
foreach ($dialogids as $dialogid) {
|
||||||
|
if (isset($dialogs[$dialogid])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$dialog = WebSocketDialog::find($dialogid);
|
||||||
|
if ($dialog) {
|
||||||
|
$dialogs[$dialog->id] = $dialog;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $dialogs;
|
||||||
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@ -513,35 +554,7 @@ class WebSocketDialogMsg extends AbstractModel
|
|||||||
'leave' => $leaveMessage ? 1 : 0, // 是否留言(用于判断是否发给AI)
|
'leave' => $leaveMessage ? 1 : 0, // 是否留言(用于判断是否发给AI)
|
||||||
];
|
];
|
||||||
$msgs = [];
|
$msgs = [];
|
||||||
$dialogs = [];
|
$dialogs = self::collectTargetDialogs($userids, $dialogids, $user);
|
||||||
if ($userids) {
|
|
||||||
if (!is_array($userids)) {
|
|
||||||
$userids = [$userids];
|
|
||||||
}
|
|
||||||
foreach ($userids as $userid) {
|
|
||||||
if (!User::whereUserid($userid)->exists()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$dialog = WebSocketDialog::checkUserDialog($user, $userid);
|
|
||||||
if ($dialog) {
|
|
||||||
$dialogs[$dialog->id] = $dialog;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($dialogids) {
|
|
||||||
if (!is_array($dialogids)) {
|
|
||||||
$dialogids = [$dialogids];
|
|
||||||
}
|
|
||||||
foreach ($dialogids as $dialogid) {
|
|
||||||
if (isset($dialogs[$dialogid])) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
$dialog = WebSocketDialog::find($dialogid);
|
|
||||||
if ($dialog) {
|
|
||||||
$dialogs[$dialog->id] = $dialog;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
foreach ($dialogs as $dialog) {
|
foreach ($dialogs as $dialog) {
|
||||||
$res = self::sendMsg('forward-' . $forwardId, $dialog->id, $this->type, $msgData, $user->userid);
|
$res = self::sendMsg('forward-' . $forwardId, $dialog->id, $this->type, $msgData, $user->userid);
|
||||||
if (Base::isSuccess($res)) {
|
if (Base::isSuccess($res)) {
|
||||||
@ -564,6 +577,81 @@ class WebSocketDialogMsg extends AbstractModel
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合并转发消息
|
||||||
|
* @param array $msgIds 消息ID数组
|
||||||
|
* @param array|int $dialogids 转发给的对话ID
|
||||||
|
* @param array|int $userids 转发给的成员ID
|
||||||
|
* @param User $user 当前用户
|
||||||
|
* @param int $showSource 是否显示原发送者信息
|
||||||
|
* @param string $leaveMessage 转发留言
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public static function mergeForwardMsg($msgIds, $dialogids, $userids, $user, $showSource = 1, $leaveMessage = '')
|
||||||
|
{
|
||||||
|
return AbstractModel::transaction(function () use ($msgIds, $dialogids, $userids, $user, $showSource, $leaveMessage) {
|
||||||
|
// 查询并验证所有消息
|
||||||
|
$msgs = self::whereIn('id', $msgIds)->orderBy('created_at')->get();
|
||||||
|
if ($msgs->isEmpty()) {
|
||||||
|
throw new ApiException('消息不存在或已被删除');
|
||||||
|
}
|
||||||
|
// 验证所有消息属于同一对话
|
||||||
|
$dialogId = $msgs->first()->dialog_id;
|
||||||
|
if ($msgs->pluck('dialog_id')->unique()->count() > 1) {
|
||||||
|
throw new ApiException('只能合并转发同一对话的消息');
|
||||||
|
}
|
||||||
|
WebSocketDialog::checkDialog($dialogId);
|
||||||
|
// 收集发送者生成标题
|
||||||
|
$senderIds = $msgs->pluck('userid')->unique()->values()->toArray();
|
||||||
|
$senderNames = User::whereIn('userid', array_slice($senderIds, 0, 2))
|
||||||
|
->pluck('nickname')
|
||||||
|
->toArray();
|
||||||
|
$title = implode(Doo::translate('和'), $senderNames);
|
||||||
|
if (count($senderIds) > 2) {
|
||||||
|
$title .= Doo::translate('等人');
|
||||||
|
}
|
||||||
|
$title .= Doo::translate('的聊天记录');
|
||||||
|
// 组装消息列表
|
||||||
|
$list = [];
|
||||||
|
foreach ($msgs as $msg) {
|
||||||
|
$list[] = [
|
||||||
|
'userid' => $msg->userid,
|
||||||
|
'type' => $msg->type,
|
||||||
|
'msg' => Base::json2array($msg->getRawOriginal('msg')),
|
||||||
|
'created_at' => $msg->created_at->toDateTimeString(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
// 构建合并转发消息体
|
||||||
|
$msgData = [
|
||||||
|
'title' => $title,
|
||||||
|
'list' => $list,
|
||||||
|
'count' => count($list),
|
||||||
|
'forward_data' => [
|
||||||
|
'show' => $showSource,
|
||||||
|
'leave' => $leaveMessage ? 1 : 0,
|
||||||
|
],
|
||||||
|
];
|
||||||
|
$dialogs = self::collectTargetDialogs($userids, $dialogids, $user);
|
||||||
|
// 发送到每个目标对话
|
||||||
|
$result = [];
|
||||||
|
foreach ($dialogs as $dialog) {
|
||||||
|
$res = self::sendMsg(null, $dialog->id, 'merge-forward', $msgData, $user->userid);
|
||||||
|
if (Base::isSuccess($res)) {
|
||||||
|
$result[] = $res['data'];
|
||||||
|
}
|
||||||
|
if ($leaveMessage) {
|
||||||
|
$res = self::sendMsg(null, $dialog->id, 'text', ['text' => $leaveMessage], $user->userid);
|
||||||
|
if (Base::isSuccess($res)) {
|
||||||
|
$result[] = $res['data'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Base::retSuccess('转发成功', [
|
||||||
|
'msgs' => $result
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 删除消息
|
* 删除消息
|
||||||
* @param array|int $ids
|
* @param array|int $ids
|
||||||
@ -695,6 +783,10 @@ class WebSocketDialogMsg extends AbstractModel
|
|||||||
case 'template':
|
case 'template':
|
||||||
return self::previewTemplateMsg($data['msg']);
|
return self::previewTemplateMsg($data['msg']);
|
||||||
|
|
||||||
|
case 'merge-forward':
|
||||||
|
$action = Doo::translate("聊天记录");
|
||||||
|
return "[{$action}] " . Base::cutStr($data['msg']['title'] ?? '', 50);
|
||||||
|
|
||||||
case 'preview':
|
case 'preview':
|
||||||
return $data['msg']['preview'];
|
return $data['msg']['preview'];
|
||||||
|
|
||||||
@ -1262,6 +1354,9 @@ class WebSocketDialogMsg extends AbstractModel
|
|||||||
$msg['height'] = $imageSize[1];
|
$msg['height'] = $imageSize[1];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if ($type === 'merge-forward') {
|
||||||
|
$mtype = 'merge-forward';
|
||||||
|
}
|
||||||
if ($push_silence === null) {
|
if ($push_silence === null) {
|
||||||
$push_silence = !in_array($type, ["text", "file", "record", "meeting"]);
|
$push_silence = !in_array($type, ["text", "file", "record", "meeting"]);
|
||||||
}
|
}
|
||||||
|
|||||||
2
resources/assets/js/functions/web.js
vendored
2
resources/assets/js/functions/web.js
vendored
@ -461,6 +461,8 @@ import {convertLocalResourcePath} from "../components/Replace/utils";
|
|||||||
case 'notice':
|
case 'notice':
|
||||||
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':
|
||||||
|
return `[${$A.L('聊天记录')}] ${$A.cutString(data.msg.title || '', 50)}`
|
||||||
case 'template':
|
case 'template':
|
||||||
return $A.templateMsgSimpleDesc(data.msg)
|
return $A.templateMsgSimpleDesc(data.msg)
|
||||||
case 'preview':
|
case 'preview':
|
||||||
|
|||||||
@ -40,6 +40,9 @@
|
|||||||
{{source.msg.source === 'api' ? source.msg.notice : $L(source.msg.notice)}}
|
{{source.msg.source === 'api' ? source.msg.notice : $L(source.msg.notice)}}
|
||||||
</div>
|
</div>
|
||||||
<template v-else>
|
<template v-else>
|
||||||
|
<div v-if="multiSelectMode && isSelectableMsg" class="dialog-multi-check" @click.stop="onMultiSelectToggle">
|
||||||
|
<Icon :type="isSelected ? 'ios-checkmark-circle' : 'ios-radio-button-off'" :class="{checked: isSelected}"/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
class="dialog-avatar"
|
class="dialog-avatar"
|
||||||
@pointerdown="handleOperation">
|
@pointerdown="handleOperation">
|
||||||
@ -132,6 +135,14 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
multiSelectMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
selectedMsgIdsSet: {
|
||||||
|
type: Set,
|
||||||
|
default: () => new Set()
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
@ -165,12 +176,22 @@ export default {
|
|||||||
return this.simpleView || this.msgId > 0
|
return this.simpleView || this.msgId > 0
|
||||||
},
|
},
|
||||||
|
|
||||||
|
isSelected() {
|
||||||
|
return this.multiSelectMode && this.selectedMsgIdsSet.has(this.source.id);
|
||||||
|
},
|
||||||
|
|
||||||
|
isSelectableMsg() {
|
||||||
|
return !['tag', 'top', 'todo', 'notice'].includes(this.source.type);
|
||||||
|
},
|
||||||
|
|
||||||
classArray() {
|
classArray() {
|
||||||
return {
|
return {
|
||||||
'dialog-item': true,
|
'dialog-item': true,
|
||||||
'reply-item': this.isReply,
|
'reply-item': this.isReply,
|
||||||
'unread-start': this.isUnreadStart,
|
'unread-start': this.isUnreadStart,
|
||||||
'self': this.isRightMsg,
|
'self': this.isRightMsg,
|
||||||
|
'multi-select-mode': this.multiSelectMode,
|
||||||
|
'multi-selected': this.isSelected,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -262,6 +283,10 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onMultiSelectToggle() {
|
||||||
|
this.dispatch("on-multi-select-toggle", this.source.id)
|
||||||
|
},
|
||||||
|
|
||||||
onViewReply(data) {
|
onViewReply(data) {
|
||||||
this.dispatch("on-view-reply", data)
|
this.dispatch("on-view-reply", data)
|
||||||
},
|
},
|
||||||
|
|||||||
@ -43,6 +43,8 @@
|
|||||||
<WordChainMsg v-else-if="msgData.type === 'word-chain'" :msg="msgData.msg" :msgId="msgData.id" :unfoldWordChainData="unfoldWordChainData" @unfoldWordChain="unfoldWordChain(msgData)" @onWordChain="onWordChain"/>
|
<WordChainMsg v-else-if="msgData.type === 'word-chain'" :msg="msgData.msg" :msgId="msgData.id" :unfoldWordChainData="unfoldWordChainData" @unfoldWordChain="unfoldWordChain(msgData)" @onWordChain="onWordChain"/>
|
||||||
<!--投票-->
|
<!--投票-->
|
||||||
<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"/>
|
||||||
<!--模板-->
|
<!--模板-->
|
||||||
<TemplateMsg v-else-if="msgData.type === 'template'" :msg="msgData.msg" @viewText="viewText"/>
|
<TemplateMsg v-else-if="msgData.type === 'template'" :msg="msgData.msg" @viewText="viewText"/>
|
||||||
<!--等待-->
|
<!--等待-->
|
||||||
@ -190,6 +192,7 @@ import MeetingMsg from "./meet.vue";
|
|||||||
import WordChainMsg from "./word-chain.vue";
|
import WordChainMsg from "./word-chain.vue";
|
||||||
import VoteMsg from "./vote.vue";
|
import VoteMsg from "./vote.vue";
|
||||||
import TemplateMsg from "./template";
|
import TemplateMsg from "./template";
|
||||||
|
import MergeForwardMsg from "./merge-forward.vue";
|
||||||
import LoadMsg from "./load.vue";
|
import LoadMsg from "./load.vue";
|
||||||
import UnknownMsg from "./unknown.vue";
|
import UnknownMsg from "./unknown.vue";
|
||||||
import emitter from "../../../../store/events";
|
import emitter from "../../../../store/events";
|
||||||
@ -208,6 +211,7 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
UnknownMsg,
|
UnknownMsg,
|
||||||
LoadMsg,
|
LoadMsg,
|
||||||
|
MergeForwardMsg,
|
||||||
TemplateMsg,
|
TemplateMsg,
|
||||||
VoteMsg,
|
VoteMsg,
|
||||||
WordChainMsg,
|
WordChainMsg,
|
||||||
|
|||||||
@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<div class="content-merge-forward" @click="openDetail">
|
||||||
|
<div class="merge-title">{{ msg.title }}</div>
|
||||||
|
<div class="merge-list">
|
||||||
|
<div v-for="(item, index) in displayList" :key="index" class="merge-item">
|
||||||
|
<UserAvatar :userid="item.userid" :show-icon="false" :show-name="true" :size="14"/>
|
||||||
|
<span class="item-colon">:</span>
|
||||||
|
<span class="item-desc" v-html="$A.getMsgSimpleDesc(item)"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="merge-footer">{{ $L('共') }} {{ msg.count || msg.list.length }} {{ $L('条消息') }}</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>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "MergeForwardMsg",
|
||||||
|
props: {
|
||||||
|
msg: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
detailShow: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
displayList() {
|
||||||
|
if (!this.msg || !this.msg.list) return [];
|
||||||
|
return this.msg.list.slice(0, 4);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
openDetail() {
|
||||||
|
this.detailShow = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -201,7 +201,7 @@
|
|||||||
:data-sources="allMsgs"
|
:data-sources="allMsgs"
|
||||||
:data-component="msgItem"
|
:data-component="msgItem"
|
||||||
|
|
||||||
:extra-props="{dialogData, operateVisible, operateItem, pointerMouse, isMyDialog, msgId, unreadOne, scrollIng, readEnabled}"
|
:extra-props="{dialogData, operateVisible, operateItem, pointerMouse, isMyDialog, msgId, unreadOne, scrollIng, readEnabled, multiSelectMode, selectedMsgIdsSet}"
|
||||||
:estimate-size="dialogData.type=='group' ? 105 : 77"
|
:estimate-size="dialogData.type=='group' ? 105 : 77"
|
||||||
:keeps="dialogMsgKeep"
|
:keeps="dialogMsgKeep"
|
||||||
:disabled="scrollDisabled"
|
:disabled="scrollDisabled"
|
||||||
@ -219,7 +219,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-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">
|
||||||
<div v-if="loadIng > 0 || prevId > 0" class="loading" :class="{filled: allMsgs.length === 0}">
|
<div v-if="loadIng > 0 || prevId > 0" class="loading" :class="{filled: allMsgs.length === 0}">
|
||||||
@ -231,8 +232,20 @@
|
|||||||
</VirtualList>
|
</VirtualList>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!--多选操作栏-->
|
||||||
|
<div v-if="multiSelectMode" class="dialog-multi-select-bar">
|
||||||
|
<div class="multi-select-info">
|
||||||
|
<span>{{ $L('已选') }} {{ selectedMsgIds.length }} {{ $L('条') }}</span>
|
||||||
|
<span v-if="selectedMsgIds.length >= 100" class="multi-select-max">{{ $L('(最多100条)') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="multi-select-actions">
|
||||||
|
<Button type="primary" size="small" :disabled="selectedMsgIds.length === 0" @click="onMultiForward">{{ $L('转发') }}</Button>
|
||||||
|
<Button size="small" @click="onMultiSelectCancel">{{ $L('取消') }}</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!--底部输入-->
|
<!--底部输入-->
|
||||||
<div ref="footer" class="dialog-footer" @click="onClickFooter">
|
<div 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)"
|
||||||
@ -355,6 +368,10 @@
|
|||||||
<i class="taskfont"></i>
|
<i class="taskfont"></i>
|
||||||
<span>{{ $L('转发') }}</span>
|
<span>{{ $L('转发') }}</span>
|
||||||
</li>
|
</li>
|
||||||
|
<li v-if="actionPermission(operateItem, 'forward')" @click="onOperate('multiSelect')">
|
||||||
|
<i class="taskfont"></i>
|
||||||
|
<span>{{ $L('多选') }}</span>
|
||||||
|
</li>
|
||||||
<li v-if="operateItem.userid == userId" @click="onOperate('withdraw')">
|
<li v-if="operateItem.userid == userId" @click="onOperate('withdraw')">
|
||||||
<i class="taskfont"></i>
|
<i class="taskfont"></i>
|
||||||
<span>{{ $L('撤回') }}</span>
|
<span>{{ $L('撤回') }}</span>
|
||||||
@ -503,7 +520,9 @@
|
|||||||
:title="$L('转发')"
|
:title="$L('转发')"
|
||||||
:confirm-title="$L('确认转发')"
|
:confirm-title="$L('确认转发')"
|
||||||
:multiple-max="50"
|
:multiple-max="50"
|
||||||
:msg-detail="operateItem"
|
:msg-detail="multiSelectMode ? null : operateItem"
|
||||||
|
:msg-ids="multiSelectMode ? selectedMsgIds : []"
|
||||||
|
:msg-list="multiSelectMsgList"
|
||||||
:before-submit="onForward"/>
|
:before-submit="onForward"/>
|
||||||
|
|
||||||
<!-- 设置待办 -->
|
<!-- 设置待办 -->
|
||||||
@ -801,6 +820,9 @@ export default {
|
|||||||
operateStyles: {},
|
operateStyles: {},
|
||||||
operateItem: {},
|
operateItem: {},
|
||||||
|
|
||||||
|
multiSelectMode: false,
|
||||||
|
selectedMsgIds: [],
|
||||||
|
|
||||||
recordState: '',
|
recordState: '',
|
||||||
pointerMouse: false,
|
pointerMouse: false,
|
||||||
|
|
||||||
@ -946,6 +968,15 @@ export default {
|
|||||||
return this.dialogData.group_type === 'user'
|
return this.dialogData.group_type === 'user'
|
||||||
},
|
},
|
||||||
|
|
||||||
|
selectedMsgIdsSet() {
|
||||||
|
return new Set(this.selectedMsgIds);
|
||||||
|
},
|
||||||
|
|
||||||
|
multiSelectMsgList() {
|
||||||
|
if (!this.multiSelectMode || this.selectedMsgIds.length === 0) return [];
|
||||||
|
return this.allMsgs.filter(m => this.selectedMsgIdsSet.has(m.id));
|
||||||
|
},
|
||||||
|
|
||||||
dialogList() {
|
dialogList() {
|
||||||
return this.cacheDialogs.filter(dialog => {
|
return this.cacheDialogs.filter(dialog => {
|
||||||
return !(dialog.name === undefined || dialog.dialog_delete === 1);
|
return !(dialog.name === undefined || dialog.dialog_delete === 1);
|
||||||
@ -1254,6 +1285,7 @@ export default {
|
|||||||
window.localStorage.removeItem('__cache:vote__')
|
window.localStorage.removeItem('__cache:vote__')
|
||||||
window.localStorage.removeItem('__cache:unfoldWordChain__')
|
window.localStorage.removeItem('__cache:unfoldWordChain__')
|
||||||
//
|
//
|
||||||
|
this.onMultiSelectCancel()
|
||||||
this.handlerMsgTransfer()
|
this.handlerMsgTransfer()
|
||||||
},
|
},
|
||||||
immediate: true
|
immediate: true
|
||||||
@ -2993,20 +3025,27 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
onForward(forwardData) {
|
onForward(forwardData) {
|
||||||
|
const isMulti = forwardData.msg_ids && forwardData.msg_ids.length > 0;
|
||||||
|
const url = isMulti
|
||||||
|
? (forwardData.forward_mode === 'merge' ? 'dialog/msg/merge-forward' : 'dialog/msg/forward')
|
||||||
|
: 'dialog/msg/forward';
|
||||||
|
const data = {
|
||||||
|
dialogids: forwardData.dialogids,
|
||||||
|
userids: forwardData.userids,
|
||||||
|
show_source: forwardData.sender ? 1 : 0,
|
||||||
|
leave_message: forwardData.message
|
||||||
|
};
|
||||||
|
if (isMulti) {
|
||||||
|
data.msg_ids = forwardData.msg_ids;
|
||||||
|
} else {
|
||||||
|
data.msg_id = forwardData.msg_id;
|
||||||
|
}
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
this.$store.dispatch("call", {
|
this.$store.dispatch("call", {url, data}).then(({data, msg}) => {
|
||||||
url: 'dialog/msg/forward',
|
|
||||||
data: {
|
|
||||||
dialogids: forwardData.dialogids,
|
|
||||||
userids: forwardData.userids,
|
|
||||||
msg_id: forwardData.msg_id,
|
|
||||||
show_source: forwardData.sender ? 1 : 0,
|
|
||||||
leave_message: forwardData.message
|
|
||||||
}
|
|
||||||
}).then(({data, msg}) => {
|
|
||||||
this.$store.dispatch("saveDialogMsg", data.msgs);
|
this.$store.dispatch("saveDialogMsg", data.msgs);
|
||||||
this.$store.dispatch("updateDialogLastMsg", data.msgs);
|
this.$store.dispatch("updateDialogLastMsg", data.msgs);
|
||||||
$A.messageSuccess(msg);
|
$A.messageSuccess(msg);
|
||||||
|
if (isMulti) this.onMultiSelectCancel();
|
||||||
resolve()
|
resolve()
|
||||||
}).catch(({msg}) => {
|
}).catch(({msg}) => {
|
||||||
$A.modalError(msg);
|
$A.modalError(msg);
|
||||||
@ -3015,6 +3054,27 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
onMultiSelectToggle(msgId) {
|
||||||
|
const index = this.selectedMsgIds.indexOf(msgId);
|
||||||
|
if (index > -1) {
|
||||||
|
this.selectedMsgIds.splice(index, 1);
|
||||||
|
} else if (this.selectedMsgIds.length < 100) {
|
||||||
|
this.selectedMsgIds.push(msgId);
|
||||||
|
} else {
|
||||||
|
$A.messageWarning(this.$L('最多选择100条消息'));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onMultiForward() {
|
||||||
|
if (this.selectedMsgIds.length === 0) return;
|
||||||
|
this.$refs.forwarder.onSelection();
|
||||||
|
},
|
||||||
|
|
||||||
|
onMultiSelectCancel() {
|
||||||
|
this.multiSelectMode = false;
|
||||||
|
this.selectedMsgIds = [];
|
||||||
|
},
|
||||||
|
|
||||||
onActivity(activity) {
|
onActivity(activity) {
|
||||||
if (this.msgActivity === false) {
|
if (this.msgActivity === false) {
|
||||||
if (activity) {
|
if (activity) {
|
||||||
@ -3135,6 +3195,10 @@ export default {
|
|||||||
|
|
||||||
// 长按触发消息操作
|
// 长按触发消息操作
|
||||||
case "operateMsg":
|
case "operateMsg":
|
||||||
|
if (this.multiSelectMode && $A.isJson(data) && data.id) {
|
||||||
|
this.onMultiSelectToggle(data.id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.operateVisible = $A.isJson(data) && this.operateItem.id === data.id;
|
this.operateVisible = $A.isJson(data) && this.operateItem.id === data.id;
|
||||||
this.operateItem = $A.isJson(data) ? data : {};
|
this.operateItem = $A.isJson(data) ? data : {};
|
||||||
this.operateCopys = []
|
this.operateCopys = []
|
||||||
@ -3330,6 +3394,11 @@ export default {
|
|||||||
this.$refs.forwarder.onSelection()
|
this.$refs.forwarder.onSelection()
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "multiSelect":
|
||||||
|
this.multiSelectMode = true;
|
||||||
|
this.selectedMsgIds = [this.operateItem.id];
|
||||||
|
break;
|
||||||
|
|
||||||
case "withdraw":
|
case "withdraw":
|
||||||
this.onWithdraw()
|
this.onWithdraw()
|
||||||
break;
|
break;
|
||||||
|
|||||||
@ -28,7 +28,42 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="twice-affirm-body-extend">
|
<div class="twice-affirm-body-extend">
|
||||||
<div class="forwarder-wrapper-body">
|
<div class="forwarder-wrapper-body">
|
||||||
<div v-if="msgDetail" class="dialog-wrapper inde-list">
|
<!--多选转发方式-->
|
||||||
|
<div v-if="isMultiMode" class="forward-mode-select">
|
||||||
|
<RadioGroup v-model="forwardMode" size="small">
|
||||||
|
<Radio label="one-by-one">{{ $L('逐条转发') }}</Radio>
|
||||||
|
<Radio label="merge">{{ $L('合并转发') }}</Radio>
|
||||||
|
</RadioGroup>
|
||||||
|
</div>
|
||||||
|
<!--多选消息预览-->
|
||||||
|
<div v-if="isMultiMode" class="dialog-wrapper inde-list">
|
||||||
|
<Scrollbar class-name="dialog-scroller">
|
||||||
|
<template v-if="forwardMode === 'merge'">
|
||||||
|
<div class="merge-forward-preview">
|
||||||
|
<div class="merge-preview-title">{{ $L('聊天记录') }}</div>
|
||||||
|
<div v-for="(item, index) in previewMsgList" :key="item.id" class="merge-preview-item">
|
||||||
|
<UserAvatar :userid="item.userid" :show-icon="false" :show-name="true" :size="16"/>
|
||||||
|
<span class="preview-desc" v-html="$A.getMsgSimpleDesc(item)"></span>
|
||||||
|
</div>
|
||||||
|
<div class="merge-preview-count">{{ $L('共') }} {{ msgIds.length }} {{ $L('条消息') }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<DialogItem
|
||||||
|
v-for="item in previewMsgList"
|
||||||
|
:key="item.id"
|
||||||
|
:source="item"
|
||||||
|
@on-view-text="onViewText"
|
||||||
|
@on-view-file="onViewFile"
|
||||||
|
@on-down-file="onDownFile"
|
||||||
|
@on-emoji="onEmoji"
|
||||||
|
@on-other="onOther"
|
||||||
|
simpleView/>
|
||||||
|
</template>
|
||||||
|
</Scrollbar>
|
||||||
|
</div>
|
||||||
|
<!--单条消息预览-->
|
||||||
|
<div v-else-if="msgDetail" class="dialog-wrapper inde-list">
|
||||||
<Scrollbar class-name="dialog-scroller">
|
<Scrollbar class-name="dialog-scroller">
|
||||||
<DialogItem
|
<DialogItem
|
||||||
:source="msgDetail"
|
:source="msgDetail"
|
||||||
@ -127,6 +162,16 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
// 多选消息ID数组
|
||||||
|
msgIds: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
// 多选消息详情列表
|
||||||
|
msgList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
@ -135,6 +180,7 @@ export default {
|
|||||||
loading: false,
|
loading: false,
|
||||||
|
|
||||||
message: '', // 留言
|
message: '', // 留言
|
||||||
|
forwardMode: 'one-by-one', // 转发方式: one-by-one | merge
|
||||||
|
|
||||||
ainew: $A.getStorageBoolean('forwarder.ainew', true), // 是否AI开启新会话
|
ainew: $A.getStorageBoolean('forwarder.ainew', true), // 是否AI开启新会话
|
||||||
sender: $A.getStorageBoolean('forwarder.sender', true), // 是否隐藏原发送者信息
|
sender: $A.getStorageBoolean('forwarder.sender', true), // 是否隐藏原发送者信息
|
||||||
@ -144,6 +190,15 @@ export default {
|
|||||||
computed: {
|
computed: {
|
||||||
...mapState(['cacheUserBasic']),
|
...mapState(['cacheUserBasic']),
|
||||||
|
|
||||||
|
isMultiMode() {
|
||||||
|
return this.msgIds && this.msgIds.length > 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
previewMsgList() {
|
||||||
|
if (!this.isMultiMode) return [];
|
||||||
|
return this.msgList.slice(0, this.forwardMode === 'merge' ? 4 : 10);
|
||||||
|
},
|
||||||
|
|
||||||
aiUser({forwardTo, cacheUserBasic}) {
|
aiUser({forwardTo, cacheUserBasic}) {
|
||||||
const users = forwardTo.filter(item => item.type !== 'group');
|
const users = forwardTo.filter(item => item.type !== 'group');
|
||||||
return users.filter(user => {
|
return users.filter(user => {
|
||||||
@ -214,6 +269,9 @@ export default {
|
|||||||
const data = {
|
const data = {
|
||||||
message: this.message,
|
message: this.message,
|
||||||
}
|
}
|
||||||
|
if (this.isMultiMode) {
|
||||||
|
data.forward_mode = this.forwardMode;
|
||||||
|
}
|
||||||
if (!this.senderHidden) {
|
if (!this.senderHidden) {
|
||||||
data.sender = this.sender
|
data.sender = this.sender
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,7 +22,9 @@
|
|||||||
|
|
||||||
:dialog-id="forwardDialogId"
|
:dialog-id="forwardDialogId"
|
||||||
:forward-to="forwardTo"
|
:forward-to="forwardTo"
|
||||||
:msg-detail="msgDetail"/>
|
:msg-detail="msgDetail"
|
||||||
|
:msg-ids="msgIds"
|
||||||
|
:msg-list="msgList"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -72,6 +74,16 @@ export default {
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
// 多选消息ID数组
|
||||||
|
msgIds: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
|
// 多选消息详情列表
|
||||||
|
msgList: {
|
||||||
|
type: Array,
|
||||||
|
default: () => []
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
@ -117,7 +129,9 @@ export default {
|
|||||||
//
|
//
|
||||||
data.dialogids = selects.filter(value => $A.leftExists(value, 'd:')).map(value => value.replace('d:', ''));
|
data.dialogids = selects.filter(value => $A.leftExists(value, 'd:')).map(value => value.replace('d:', ''));
|
||||||
data.userids = selects.filter(value => !$A.leftExists(value, 'd:'));
|
data.userids = selects.filter(value => !$A.leftExists(value, 'd:'));
|
||||||
if (this.msgDetail) {
|
if (this.msgIds && this.msgIds.length > 0) {
|
||||||
|
data.msg_ids = this.msgIds;
|
||||||
|
} else if (this.msgDetail) {
|
||||||
data.msg_id = this.msgDetail.id;
|
data.msg_id = this.msgDetail.id;
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
|
|||||||
@ -651,6 +651,31 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-multi-check {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 28px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.ivu-icon {
|
||||||
|
font-size: 22px;
|
||||||
|
color: #c5c5c5;
|
||||||
|
transition: color 0.2s;
|
||||||
|
|
||||||
|
&.checked {
|
||||||
|
color: #2d8cf0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.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;
|
||||||
@ -954,6 +979,58 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content-merge-forward {
|
||||||
|
background: #f7f7f7;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
min-width: 200px;
|
||||||
|
max-width: 300px;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: #efefef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merge-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merge-list {
|
||||||
|
.merge-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 22px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
.item-colon {
|
||||||
|
margin: 0 2px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-desc {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.merge-footer {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.content-file {
|
.content-file {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
@ -1977,6 +2054,31 @@
|
|||||||
background-color: rgba(255, 255, 255, 0.8);
|
background-color: rgba(255, 255, 255, 0.8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dialog-multi-select-bar {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 10px 24px;
|
||||||
|
border-top: 1px solid #e8e8e8;
|
||||||
|
background-color: #fafafa;
|
||||||
|
|
||||||
|
.multi-select-info {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #515a6e;
|
||||||
|
|
||||||
|
.multi-select-max {
|
||||||
|
color: #ed4014;
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.multi-select-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dialog-footer {
|
.dialog-footer {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 0 24px;
|
padding: 0 24px;
|
||||||
@ -2814,3 +2916,56 @@ 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -79,6 +79,55 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.forward-mode-select {
|
||||||
|
padding-bottom: 12px;
|
||||||
|
|
||||||
|
.ivu-radio-group {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.merge-forward-preview {
|
||||||
|
background: #f7f7f7;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
|
||||||
|
.merge-preview-title {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merge-preview-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #666;
|
||||||
|
line-height: 22px;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
|
||||||
|
.preview-desc {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.merge-preview-count {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid #e8e8e8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.leave-message {
|
.leave-message {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 2;
|
z-index: 2;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user