mirror of
https://github.com/kuaifan/dootask.git
synced 2026-03-28 16:20:43 +00:00
feat: 新增回复消息功能
This commit is contained in:
parent
4c163a8092
commit
3728b1d3a0
@ -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;
|
||||||
|
|||||||
@ -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'];
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
});
|
});
|
||||||
|
|||||||
@ -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");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
26
resources/assets/js/functions/utils.js
vendored
26
resources/assets/js/functions/utils.js
vendored
@ -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
|
||||||
|
|||||||
@ -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"></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>
|
||||||
|
|||||||
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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"></i>
|
||||||
|
<span>{{ $L('回复') }}</span>
|
||||||
|
</li>
|
||||||
|
<li @click="onOperate('forward')">
|
||||||
|
<i class="taskfont"></i>
|
||||||
|
<span>{{ $L('转发') }}</span>
|
||||||
|
</li>
|
||||||
<template v-if="operateHasText">
|
<template v-if="operateHasText">
|
||||||
<li @click="onOperate('copy')">
|
<li @click="onOperate('copy')">
|
||||||
<i class="taskfont"></i>
|
<i class="taskfont"></i>
|
||||||
@ -143,10 +154,6 @@
|
|||||||
<span>{{ $L('新任务') }}</span>
|
<span>{{ $L('新任务') }}</span>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
<li @click="onOperate('forward')">
|
|
||||||
<i class="taskfont"></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"></i>
|
<i class="taskfont"></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: `确定撤回此信息吗?`,
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
5
resources/assets/sass/dark.scss
vendored
5
resources/assets/sass/dark.scss
vendored
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
5
resources/assets/sass/pages/page-manage.scss
vendored
5
resources/assets/sass/pages/page-manage.scss
vendored
@ -148,11 +148,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&.top {
|
|
||||||
.project-h1 {
|
|
||||||
background-color: #EEEFF1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
&.active {
|
&.active {
|
||||||
.project-h1 {
|
.project-h1 {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user