feat: 新增回复消息功能

This commit is contained in:
kuaifan 2022-06-17 08:14:36 +08:00
parent 4c163a8092
commit 3728b1d3a0
15 changed files with 288 additions and 56 deletions

View File

@ -264,6 +264,7 @@ class DialogController extends AbstractController
* @apiName msg__sendtext * @apiName msg__sendtext
* *
* @apiParam {Number} dialog_id 对话ID * @apiParam {Number} dialog_id 对话ID
* @apiParam {Number} [reply_id] 回复ID
* @apiParam {String} text 消息内容 * @apiParam {String} text 消息内容
* *
* @apiSuccess {Number} ret 返回状态码1正确、0错误 * @apiSuccess {Number} ret 返回状态码1正确、0错误
@ -284,10 +285,14 @@ class DialogController extends AbstractController
} }
// //
$dialog_id = Base::getPostInt('dialog_id'); $dialog_id = Base::getPostInt('dialog_id');
$reply_id = Base::getPostInt('reply_id');
$text = trim(Base::getPostValue('text')); $text = trim(Base::getPostValue('text'));
// //
WebSocketDialog::checkDialog($dialog_id); WebSocketDialog::checkDialog($dialog_id);
// //
if ($reply_id > 0 && !WebSocketDialogMsg::whereId($reply_id)->whereDialogId($dialog_id)->exists()) {
return Base::retError('回复的消息不存在');
}
$text = WebSocketDialogMsg::formatMsg($text, $dialog_id); $text = WebSocketDialogMsg::formatMsg($text, $dialog_id);
$strlen = mb_strlen($text); $strlen = mb_strlen($text);
if ($strlen < 1) { if ($strlen < 1) {
@ -317,10 +322,10 @@ class DialogController extends AbstractController
'height' => -1, 'height' => -1,
'ext' => 'htm', 'ext' => 'htm',
]; ];
return WebSocketDialogMsg::sendMsg($dialog_id, 'file', $fileData, $user->userid); return WebSocketDialogMsg::sendMsg($dialog_id, $reply_id, 'file', $fileData, $user->userid);
} }
// //
return WebSocketDialogMsg::sendMsg($dialog_id, 'text', ['text' => $text], $user->userid); return WebSocketDialogMsg::sendMsg($dialog_id, $reply_id, 'text', ['text' => $text], $user->userid);
} }
/** /**
@ -332,6 +337,7 @@ class DialogController extends AbstractController
* @apiName msg__sendrecord * @apiName msg__sendrecord
* *
* @apiParam {Number} dialog_id 对话ID * @apiParam {Number} dialog_id 对话ID
* @apiParam {Number} [reply_id] 回复ID
* @apiParam {String} base64 语音base64 * @apiParam {String} base64 语音base64
* @apiParam {Number} duration 语音时长(毫秒) * @apiParam {Number} duration 语音时长(毫秒)
* *
@ -344,9 +350,13 @@ class DialogController extends AbstractController
$user = User::auth(); $user = User::auth();
// //
$dialog_id = Base::getPostInt('dialog_id'); $dialog_id = Base::getPostInt('dialog_id');
$reply_id = Base::getPostInt('reply_id');
// //
WebSocketDialog::checkDialog($dialog_id); WebSocketDialog::checkDialog($dialog_id);
// //
if ($reply_id > 0 && !WebSocketDialogMsg::whereId($reply_id)->whereDialogId($dialog_id)->exists()) {
return Base::retError('回复的消息不存在');
}
$path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/"; $path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
$base64 = Base::getPostValue('base64'); $base64 = Base::getPostValue('base64');
$duration = Base::getPostInt('duration'); $duration = Base::getPostInt('duration');
@ -363,7 +373,7 @@ class DialogController extends AbstractController
$recordData = $data['data']; $recordData = $data['data'];
$recordData['size'] *= 1024; $recordData['size'] *= 1024;
$recordData['duration'] = $duration; $recordData['duration'] = $duration;
return WebSocketDialogMsg::sendMsg($dialog_id, 'record', $recordData, $user->userid); return WebSocketDialogMsg::sendMsg($dialog_id, $reply_id, 'record', $recordData, $user->userid);
} }
} }
@ -376,6 +386,7 @@ class DialogController extends AbstractController
* @apiName msg__sendfile * @apiName msg__sendfile
* *
* @apiParam {Number} dialog_id 对话ID * @apiParam {Number} dialog_id 对话ID
* @apiParam {Number} [reply_id] 回复ID
* @apiParam {Number} [image_attachment] 图片是否也存到附件 * @apiParam {Number} [image_attachment] 图片是否也存到附件
* @apiParam {String} [filename] post-文件名称 * @apiParam {String} [filename] post-文件名称
* @apiParam {String} [image64] post-base64图片二选一 * @apiParam {String} [image64] post-base64图片二选一
@ -390,10 +401,14 @@ class DialogController extends AbstractController
$user = User::auth(); $user = User::auth();
// //
$dialog_id = Base::getPostInt('dialog_id'); $dialog_id = Base::getPostInt('dialog_id');
$reply_id = Base::getPostInt('reply_id');
$image_attachment = Base::getPostInt('image_attachment'); $image_attachment = Base::getPostInt('image_attachment');
// //
$dialog = WebSocketDialog::checkDialog($dialog_id); $dialog = WebSocketDialog::checkDialog($dialog_id);
// //
if ($reply_id > 0 && !WebSocketDialogMsg::whereId($reply_id)->whereDialogId($dialog_id)->exists()) {
return Base::retError('回复的消息不存在');
}
$path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/"; $path = "uploads/chat/" . date("Ym") . "/" . $dialog_id . "/";
$image64 = Base::getPostValue('image64'); $image64 = Base::getPostValue('image64');
$fileName = Base::getPostValue('filename'); $fileName = Base::getPostValue('filename');
@ -438,7 +453,7 @@ class DialogController extends AbstractController
} }
} }
// //
$result = WebSocketDialogMsg::sendMsg($dialog_id, 'file', $fileData, $user->userid); $result = WebSocketDialogMsg::sendMsg($dialog_id, $reply_id, 'file', $fileData, $user->userid);
if (Base::isSuccess($result)) { if (Base::isSuccess($result)) {
if (isset($task)) { if (isset($task)) {
$result['data']['task_id'] = $task->id; $result['data']['task_id'] = $task->id;

View File

@ -859,7 +859,7 @@ class UsersController extends AbstractController
} }
$dialog = WebSocketDialog::checkUserDialog($user->userid, $userid); $dialog = WebSocketDialog::checkUserDialog($user->userid, $userid);
if ($dialog) { if ($dialog) {
$res = WebSocketDialogMsg::sendMsg($dialog->id, 'meeting', $data, $user->userid); $res = WebSocketDialogMsg::sendMsg($dialog->id, 0, 'meeting', $data, $user->userid);
if (Base::isSuccess($res)) { if (Base::isSuccess($res)) {
$msgs[] = $res['data']; $msgs[] = $res['data'];
} }
@ -909,7 +909,7 @@ class UsersController extends AbstractController
} }
$dialog = WebSocketDialog::checkUserDialog($user->userid, $userid); $dialog = WebSocketDialog::checkUserDialog($user->userid, $userid);
if ($dialog) { if ($dialog) {
$res = WebSocketDialogMsg::sendMsg($dialog->id, 'meeting', $data, $user->userid); $res = WebSocketDialogMsg::sendMsg($dialog->id, 0, 'meeting', $data, $user->userid);
if (Base::isSuccess($res)) { if (Base::isSuccess($res)) {
$msgs[] = $res['data']; $msgs[] = $res['data'];
} }

View File

@ -22,6 +22,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @property array|mixed $emoji emoji回复 * @property array|mixed $emoji emoji回复
* @property int|null $read 已阅数量 * @property int|null $read 已阅数量
* @property int|null $send 发送数量 * @property int|null $send 发送数量
* @property int|null $reply_id 回复ID
* @property \Illuminate\Support\Carbon|null $created_at * @property \Illuminate\Support\Carbon|null $created_at
* @property \Illuminate\Support\Carbon|null $updated_at * @property \Illuminate\Support\Carbon|null $updated_at
* @property \Illuminate\Support\Carbon|null $deleted_at * @property \Illuminate\Support\Carbon|null $deleted_at
@ -39,6 +40,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereId($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereMsg($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereMsg($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereRead($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereRead($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereReplyId($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereSend($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereSend($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereType($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereType($value)
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereUpdatedAt($value)
@ -239,7 +241,7 @@ class WebSocketDialogMsg extends AbstractModel
} }
$dialog = WebSocketDialog::checkUserDialog($sender, $userid); $dialog = WebSocketDialog::checkUserDialog($sender, $userid);
if ($dialog) { if ($dialog) {
$res = self::sendMsg($dialog->id, $this->type, $this->getOriginal('msg'), $sender); $res = self::sendMsg($dialog->id, 0, $this->type, $this->getOriginal('msg'), $sender);
if (Base::isSuccess($res)) { if (Base::isSuccess($res)) {
$msgs[] = $res['data']; $msgs[] = $res['data'];
} }
@ -420,28 +422,30 @@ class WebSocketDialogMsg extends AbstractModel
/** /**
* 发送消息 * 发送消息
* @param int $dialog_id 会话ID 聊天室ID * @param int $dialog_id 会话ID 聊天室ID
* @param int $reply_id 回复ID
* @param string $type 消息类型 * @param string $type 消息类型
* @param array $msg 发送的消息 * @param array $msg 发送的消息
* @param int $sender 发送的会员ID默认自己0为系统 * @param int $sender 发送的会员ID默认自己0为系统
* @return array * @return array
*/ */
public static function sendMsg($dialog_id, $type, $msg, $sender = 0) public static function sendMsg($dialog_id, $reply_id, $type, $msg, $sender = 0)
{ {
$dialogMsg = self::createInstance([ $dialogMsg = self::createInstance([
'dialog_id' => $dialog_id,
'reply_id' => $reply_id,
'userid' => $sender ?: User::userid(), 'userid' => $sender ?: User::userid(),
'type' => $type, 'type' => $type,
'msg' => $msg, 'msg' => $msg,
'read' => 0, 'read' => 0,
]); ]);
AbstractModel::transaction(function () use ($dialog_id, $msg, $dialogMsg) { AbstractModel::transaction(function () use ($dialogMsg) {
$dialog = WebSocketDialog::find($dialog_id); $dialog = WebSocketDialog::find($dialogMsg->dialog_id);
if (empty($dialog)) { if (empty($dialog)) {
throw new ApiException('获取会话失败'); throw new ApiException('获取会话失败');
} }
$dialog->last_at = Carbon::now(); $dialog->last_at = Carbon::now();
$dialog->save(); $dialog->save();
$dialogMsg->send = 1; $dialogMsg->send = 1;
$dialogMsg->dialog_id = $dialog->id;
$dialogMsg->dialog_type = $dialog->type; $dialogMsg->dialog_type = $dialog->type;
$dialogMsg->save(); $dialogMsg->save();
}); });

View File

@ -0,0 +1,34 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class WebSocketDialogMsgsAddReplyId extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
if (!Schema::hasColumn('web_socket_dialog_msgs', 'reply_id')) {
$table->bigInteger('reply_id')->nullable()->default(0)->after('send')->comment('回复ID');
}
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
$table->dropColumn("reply_id");
});
}
}

View File

@ -76,6 +76,32 @@ module.exports = {
return list; return list;
}, },
/**
* 消息简单描述
* @param data
* @returns {string|*}
*/
msgSimpleDesc(data) {
if ($A.isJson(data)) {
switch (data.type) {
case 'text':
return $A.getMsgTextPreview(data.msg.text)
case 'record':
return `[${$A.L('语音')}]`
case 'meeting':
return `[${$A.L('会议')}] ${data.msg.name}`
case 'file':
if (data.msg.type == 'img') {
return `[${$A.L('图片')}]`
}
return `[${$A.L('文件')}] ${data.msg.name}`
default:
return `[${$A.L('未知的消息')}]`
}
}
return '';
},
/** /**
* 阻止滑动穿透 * 阻止滑动穿透
* @param el * @param el

View File

@ -1,6 +1,13 @@
<template> <template>
<div class="chat-input-box" :class="boxClass" v-clickoutside="hidePopover"> <div class="chat-input-box" :class="boxClass" v-clickoutside="hidePopover">
<div class="chat-input-wrapper" @click.stop="focus"> <div class="chat-input-wrapper" @click.stop="focus">
<!-- 回复 -->
<div v-if="replyItem.id" class="chat-reply">
<UserAvatar :userid="replyItem.userid" :show-icon="false" :show-name="true" :tooltip-disabled="true"/>
<div class="reply-desc">{{formatMsgDesc(replyItem)}}</div>
<i class="taskfont" @click.stop="onCancelReply">&#xe6e5;</i>
</div>
<!-- 输入框 --> <!-- 输入框 -->
<div <div
ref="editor" ref="editor"
@ -125,7 +132,7 @@ import touchmouse from "../../../../directives/touchmouse";
import TransferDom from "../../../../directives/transfer-dom"; import TransferDom from "../../../../directives/transfer-dom";
import clickoutside from "../../../../directives/clickoutside"; import clickoutside from "../../../../directives/clickoutside";
import {Store} from "le5le-store"; import {Store} from "le5le-store";
import {scrollPreventThrough} from "../../../../functions/utils"; import {scrollPreventThrough, msgSimpleDesc} from "../../../../functions/utils";
export default { export default {
name: 'ChatInput', name: 'ChatInput',
@ -179,6 +186,10 @@ export default {
type: String, type: String,
default: "top" default: "top"
}, },
replyItem: {
type: Object,
default: () => ({})
},
}, },
data() { data() {
return { return {
@ -813,6 +824,10 @@ export default {
} }
}, },
onCancelReply() {
this.$emit('on-cancel-reply')
},
onToolbar(action) { onToolbar(action) {
this.hidePopover(); this.hidePopover();
switch (action) { switch (action) {
@ -1041,7 +1056,11 @@ export default {
e.preventDefault() e.preventDefault()
this.$emit('on-file', postFiles) this.$emit('on-file', postFiles)
} }
} },
formatMsgDesc(data) {
return msgSimpleDesc(data);
},
} }
} }
</script> </script>

View File

@ -17,8 +17,6 @@
</template> </template>
<script> <script>
import {mapState} from "vuex";
export default { export default {
name: 'DialogUpload', name: 'DialogUpload',
props: { props: {
@ -26,6 +24,10 @@ export default {
type: Number, type: Number,
default: 0 default: 0
}, },
replyId: {
type: [Number, String],
default: 0
},
maxSize: { maxSize: {
type: Number, type: Number,
default: 204800 default: 204800
@ -73,6 +75,7 @@ export default {
params() { params() {
return { return {
dialog_id: this.dialogId, dialog_id: this.dialogId,
reply_id: this.replyId,
} }
} }
}, },

View File

@ -9,6 +9,11 @@
class="dialog-head" class="dialog-head"
:class="headClass" :class="headClass"
v-longpress="{callback: handleLongpress, delay: 300}"> v-longpress="{callback: handleLongpress, delay: 300}">
<!--回复-->
<div v-if="replyData" class="dialog-reply no-dark-content">
<UserAvatar :userid="replyData.userid" :show-icon="false" :show-name="true" :tooltip-disabled="true"/>
<div class="reply-desc">{{formatMsgDesc(replyData)}}</div>
</div>
<!--详情--> <!--详情-->
<div class="dialog-content" :class="contentClass"> <div class="dialog-content" :class="contentClass">
<!--文本--> <!--文本-->
@ -121,7 +126,7 @@ import WCircle from "../../../components/WCircle";
import {mapState} from "vuex"; import {mapState} from "vuex";
import {Store} from "le5le-store"; import {Store} from "le5le-store";
import longpress from "../../../directives/longpress"; import longpress from "../../../directives/longpress";
import {textMsgFormat} from "../../../functions/utils"; import {textMsgFormat, msgSimpleDesc} from "../../../functions/utils";
export default { export default {
name: "DialogView", name: "DialogView",
@ -167,14 +172,17 @@ export default {
}, },
computed: { computed: {
...mapState(['audioPlaying', 'windowActive']), ...mapState(['dialogMsgs', 'audioPlaying', 'windowActive']),
viewClass() { viewClass() {
const {msgData, operateAction, operateEnter} = this; const {msgData, replyData, operateAction, operateEnter} = this;
const array = []; const array = [];
if (msgData.type) { if (msgData.type) {
array.push(msgData.type) array.push(msgData.type)
} }
if (replyData) {
array.push('reply-view')
}
if (operateAction) { if (operateAction) {
array.push('operate-action') array.push('operate-action')
if (operateEnter) { if (operateEnter) {
@ -193,9 +201,9 @@ export default {
}, },
headClass() { headClass() {
const {type, msg, emoji} = this.msgData; const {reply_id, type, msg, emoji} = this.msgData;
const array = []; const array = [];
if ($A.arrayLength(emoji) === 0) { if (reply_id === 0 && $A.arrayLength(emoji) === 0) {
if (type === 'text') { if (type === 'text') {
if (/^<img\s+class="emoticon"[^>]*?>$/.test(msg.text) if (/^<img\s+class="emoticon"[^>]*?>$/.test(msg.text)
|| /^\s*<p>\s*([\uD800-\uDBFF][\uDC00-\uDFFF]){1,3}\s*<\/p>\s*$/.test(msg.text)) { || /^\s*<p>\s*([\uD800-\uDBFF][\uDC00-\uDFFF]){1,3}\s*<\/p>\s*$/.test(msg.text)) {
@ -221,6 +229,14 @@ export default {
} }
} }
return classArray; return classArray;
},
replyData() {
const {reply_id} = this.msgData;
if (reply_id > 0) {
return this.dialogMsgs.find(item => item.id === reply_id) || null;
}
return null;
} }
}, },
@ -329,6 +345,10 @@ export default {
return {}; return {};
}, },
formatMsgDesc(data) {
return msgSimpleDesc(data)
},
playRecord() { playRecord() {
if (this.operateVisible) { if (this.operateVisible) {
return return

View File

@ -100,6 +100,7 @@
ref="chatUpload" ref="chatUpload"
class="chat-upload" class="chat-upload"
:dialog-id="dialogId" :dialog-id="dialogId"
:reply-id="replyItem.id"
@on-progress="chatFile('progress', $event)" @on-progress="chatFile('progress', $event)"
@on-success="chatFile('success', $event)" @on-success="chatFile('success', $event)"
@on-error="chatFile('error', $event)"/> @on-error="chatFile('error', $event)"/>
@ -107,6 +108,7 @@
ref="input" ref="input"
v-model="msgText" v-model="msgText"
:dialog-id="dialogId" :dialog-id="dialogId"
:reply-item="replyItem"
:emoji-bottom="windowSmall" :emoji-bottom="windowSmall"
:maxlength="200000" :maxlength="200000"
@on-focus="onEventFocus" @on-focus="onEventFocus"
@ -117,6 +119,7 @@
@on-record="sendRecord" @on-record="sendRecord"
@on-record-state="onRecordState" @on-record-state="onRecordState"
@on-emoji-visible-change="onEventEmojiVisibleChange" @on-emoji-visible-change="onEventEmojiVisibleChange"
@on-cancel-reply="onCancelReply"
:placeholder="$L('输入消息...')"/> :placeholder="$L('输入消息...')"/>
</div> </div>
@ -133,6 +136,14 @@
<DropdownMenu slot="list"> <DropdownMenu slot="list">
<DropdownItem name="action"> <DropdownItem name="action">
<ul class="operate-action"> <ul class="operate-action">
<li @click="onOperate('reply')">
<i class="taskfont">&#xe6eb;</i>
<span>{{ $L('回复') }}</span>
</li>
<li @click="onOperate('forward')">
<i class="taskfont">&#xe638;</i>
<span>{{ $L('转发') }}</span>
</li>
<template v-if="operateHasText"> <template v-if="operateHasText">
<li @click="onOperate('copy')"> <li @click="onOperate('copy')">
<i class="taskfont">&#xe77f;</i> <i class="taskfont">&#xe77f;</i>
@ -143,10 +154,6 @@
<span>{{ $L('新任务') }}</span> <span>{{ $L('新任务') }}</span>
</li> </li>
</template> </template>
<li @click="onOperate('forward')">
<i class="taskfont">&#xe638;</i>
<span>{{ $L('转发') }}</span>
</li>
<template v-if="operateItem.userid == userId"> <template v-if="operateItem.userid == userId">
<li @click="onOperate('withdraw')"> <li @click="onOperate('withdraw')">
<i class="taskfont">&#xe637;</i> <i class="taskfont">&#xe637;</i>
@ -317,6 +324,8 @@ export default {
recordState: '', recordState: '',
wrapperStart: 0, wrapperStart: 0,
replyItem: {},
} }
}, },
@ -442,9 +451,7 @@ export default {
}) })
// //
if (this.autoFocus) { if (this.autoFocus) {
this.$nextTick(_ => { this.inputFocus()
this.$refs.input.focus()
})
} }
} }
}, },
@ -520,7 +527,7 @@ export default {
this.msgText = ''; this.msgText = '';
} }
if (msgText == '') { if (msgText == '') {
this.$refs.input.focus(); this.inputFocus();
return; return;
} }
msgText = msgText.replace(/<\/span> <\/p>$/, "</span></p>") msgText = msgText.replace(/<\/span> <\/p>$/, "</span></p>")
@ -532,6 +539,7 @@ export default {
let tempMsg = { let tempMsg = {
id: tempId, id: tempId,
dialog_id: this.dialogData.id, dialog_id: this.dialogData.id,
reply_id: this.replyItem.id,
type: 'text', type: 'text',
userid: this.userId, userid: this.userId,
msg: { msg: {
@ -548,6 +556,7 @@ export default {
url: 'dialog/msg/sendtext', url: 'dialog/msg/sendtext',
data: { data: {
dialog_id: this.dialogId, dialog_id: this.dialogId,
reply_id: this.replyItem.id,
text: msgText, text: msgText,
}, },
method: 'post' method: 'post'
@ -572,6 +581,7 @@ export default {
this.tempMsgs.push({ this.tempMsgs.push({
id: tempId, id: tempId,
dialog_id: this.dialogData.id, dialog_id: this.dialogData.id,
reply_id: this.replyItem.id,
type: 'loading', type: 'loading',
userid: this.userId, userid: this.userId,
msg, msg,
@ -581,6 +591,7 @@ export default {
url: 'dialog/msg/sendrecord', url: 'dialog/msg/sendrecord',
data: Object.assign(msg, { data: Object.assign(msg, {
dialog_id: this.dialogId, dialog_id: this.dialogId,
reply_id: this.replyItem.id,
}), }),
method: 'post' method: 'post'
}).then(({data}) => { }).then(({data}) => {
@ -618,6 +629,12 @@ export default {
} }
}, },
inputFocus() {
this.$nextTick(_ => {
this.$refs.input.focus()
})
},
onRecordState(state) { onRecordState(state) {
this.recordState = state; this.recordState = state;
}, },
@ -690,6 +707,7 @@ export default {
this.tempMsgs.push({ this.tempMsgs.push({
id: file.tempId, id: file.tempId,
dialog_id: this.dialogData.id, dialog_id: this.dialogData.id,
reply_id: this.replyItem.id,
type: 'loading', type: 'loading',
userid: this.userId, userid: this.userId,
msg: { }, msg: { },
@ -717,6 +735,7 @@ export default {
this.$store.dispatch("saveDialogMsg", data); this.$store.dispatch("saveDialogMsg", data);
this.$store.dispatch("increaseTaskMsgNum", this.dialogId); this.$store.dispatch("increaseTaskMsgNum", this.dialogId);
this.$store.dispatch("updateDialogLastMsg", data); this.$store.dispatch("updateDialogLastMsg", data);
this.onCancelReply();
this.onActive(); this.onActive();
}, },
@ -936,6 +955,10 @@ export default {
} }
break; break;
case "reply":
this.onReply()
break;
case "forward": case "forward":
this.onForward('open') this.onForward('open')
break; break;
@ -959,6 +982,15 @@ export default {
}) })
}, },
onReply() {
this.replyItem = this.operateItem;
this.inputFocus()
},
onCancelReply() {
this.replyItem = {};
},
onWithdraw() { onWithdraw() {
$A.modalConfirm({ $A.modalConfirm({
content: `确定撤回此信息吗?`, content: `确定撤回此信息吗?`,

View File

@ -72,7 +72,7 @@
<div v-if="dialog.last_msg.userid == userId" class="last-self">{{$L('')}}</div> <div v-if="dialog.last_msg.userid == userId" class="last-self">{{$L('')}}</div>
<UserAvatar v-else :userid="dialog.last_msg.userid" :show-name="true" :show-icon="false" tooltip-disabled/> <UserAvatar v-else :userid="dialog.last_msg.userid" :show-name="true" :show-icon="false" tooltip-disabled/>
</template> </template>
<div class="last-text">{{formatLastMsg(dialog.last_msg)}}</div> <div class="last-text">{{formatMsgDesc(dialog.last_msg)}}</div>
</div> </div>
</div> </div>
<Badge class="dialog-num" :count="$A.getDialogUnread(dialog)"/> <Badge class="dialog-num" :count="$A.getDialogUnread(dialog)"/>
@ -138,7 +138,7 @@
<div class="msg-dialog-bg-icon"><Icon type="ios-chatbubbles" /></div> <div class="msg-dialog-bg-icon"><Icon type="ios-chatbubbles" /></div>
<div class="msg-dialog-bg-text">{{$L('选择一个会话开始聊天')}}</div> <div class="msg-dialog-bg-text">{{$L('选择一个会话开始聊天')}}</div>
</div> </div>
<DialogWrapper v-if="windowLarge && dialogId > 0" :dialogId="dialogId" @on-active="scrollIntoActive" auto-focus/> <DialogWrapper v-if="windowLarge && dialogId > 0" :dialogId="dialogId" @on-active="scrollIntoActive" :auto-focus="$A.isDesktop()"/>
</div> </div>
</div> </div>
</div> </div>
@ -149,6 +149,7 @@ import {mapState} from "vuex";
import DialogWrapper from "./components/DialogWrapper"; import DialogWrapper from "./components/DialogWrapper";
import ScrollerY from "../../components/ScrollerY"; import ScrollerY from "../../components/ScrollerY";
import longpress from "../../directives/longpress"; import longpress from "../../directives/longpress";
import {msgSimpleDesc} from "../../functions/utils";
export default { export default {
components: {ScrollerY, DialogWrapper}, components: {ScrollerY, DialogWrapper},
@ -527,25 +528,8 @@ export default {
} }
}, },
formatLastMsg(data) { formatMsgDesc(data) {
if ($A.isJson(data)) { return msgSimpleDesc(data);
switch (data.type) {
case 'text':
return $A.getMsgTextPreview(data.msg.text)
case 'record':
return `[${this.$L('语音')}]`
case 'meeting':
return `[${this.$L('会议')}] ${data.msg.name}`
case 'file':
if (data.msg.type == 'img') {
return `[${this.$L('图片')}]`
}
return `[${this.$L('文件')}] ${data.msg.name}`
default:
return `[${this.$L('未知的消息')}]`
}
}
return '';
}, },
lastMsgReadDone(data) { lastMsgReadDone(data) {

View File

@ -164,6 +164,11 @@ body.dark-mode-reverse {
.dialog-view { .dialog-view {
.dialog-head { .dialog-head {
background-color: #e1e1e1; background-color: #e1e1e1;
.dialog-reply {
.reply-desc {
color: #ffffff;
}
}
.dialog-content { .dialog-content {
.content-text, .content-text,
.content-record, .content-record,

View File

@ -57,6 +57,56 @@
vertical-align: middle; vertical-align: middle;
width: 100%; width: 100%;
.chat-reply {
position: relative;
padding: 0 48px 8px 24px;
&:after {
content: "";
position: absolute;
top: 0;
left: 12px;
bottom: 8px;
width: 3px;
border-radius: 2px;
transform: scaleX(0.8);
transform-origin: left center;
background-color: rgba($primary-color, 0.7);
}
.common-avatar {
font-weight: 500;
font-size: 13px;
color: $primary-color;
}
.reply-desc {
font-size: 13px;
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.taskfont {
position: absolute;
right: 0;
top: 0;
bottom: 8px;
z-index: 1;
font-size: 16px;
width: 48px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: transform 0.3s ease;
&:hover {
transform: rotate(-90deg);
color: $primary-title-color;
}
}
}
.ql-container { .ql-container {
display: block; display: block;
float: left; float: left;
@ -206,6 +256,9 @@
> div { > div {
flex: 1; flex: 1;
width: 100%; width: 100%;
display: flex;
flex-direction: column;
justify-content: flex-end;
} }
} }
} }

View File

@ -239,6 +239,38 @@
background-color: transparent !important; background-color: transparent !important;
} }
.dialog-reply {
position: relative;
padding-left: 9px;
margin-bottom: 4px;
&:after {
content: "";
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: 3px;
border-radius: 2px;
transform: scaleX(0.8);
transform-origin: left center;
background-color: rgba($primary-color, 0.7);
}
.common-avatar {
font-weight: 500;
font-size: 13px;
color: $primary-color;
}
.reply-desc {
font-size: 13px;
word-break: break-all;
text-overflow: ellipsis;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
}
.dialog-content { .dialog-content {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
@ -668,6 +700,16 @@
background-color: $primary-color; background-color: $primary-color;
border-radius: 8px 2px 8px 8px; border-radius: 8px 2px 8px 8px;
.dialog-reply {
color: #ffffff;
.common-avatar {
color: #ffffff;
}
&:after {
background-color: #ffffff;
}
}
.dialog-content { .dialog-content {
.content-text { .content-text {
color: #ffffff; color: #ffffff;

View File

@ -148,11 +148,6 @@
} }
} }
} }
&.top {
.project-h1 {
background-color: #EEEFF1;
}
}
&.active { &.active {
.project-h1 { .project-h1 {
background-color: #ffffff; background-color: #ffffff;

View File

@ -239,10 +239,10 @@
pointer-events: none; pointer-events: none;
} }
&.top { &.top {
background-color: #EEEFF1; background-color: #F4F5F7;
} }
&.active { &.active {
background-color: #F4F5F7; background-color: #EEEFF1;
} }
&.operate { &.operate {
&:before { &:before {