mirror of
https://github.com/kuaifan/dootask.git
synced 2026-02-28 12:50:48 +00:00
feat: 支持搜索历史消息
This commit is contained in:
parent
4384a2dd91
commit
d167a91a07
@ -62,6 +62,42 @@ class DialogController extends AbstractController
|
||||
return Base::retSuccess('success', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/search 02. 搜索会话
|
||||
*
|
||||
* @apiDescription 根据消息关键词搜索相关会话,需要token身份
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup dialog
|
||||
* @apiName search
|
||||
*
|
||||
* @apiParam {String} key 消息关键词
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*/
|
||||
public function search()
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$key = trim(Request::input('key'));
|
||||
//
|
||||
$list = WebSocketDialog::select(['web_socket_dialogs.*', 'u.top_at', 'u.mark_unread', 'm.id as search_msg_id'])
|
||||
->join('web_socket_dialog_users as u', 'web_socket_dialogs.id', '=', 'u.dialog_id')
|
||||
->join('web_socket_dialog_msgs as m', 'web_socket_dialogs.id', '=', 'm.dialog_id')
|
||||
->where('u.userid', $user->userid)
|
||||
->where('m.key', 'LIKE', "%{$key}%")
|
||||
->orderByDesc('m.id')
|
||||
->take(20)
|
||||
->get();
|
||||
//
|
||||
$list->transform(function (WebSocketDialog $item) use ($user) {
|
||||
return $item->formatData($user->userid);
|
||||
});
|
||||
//
|
||||
return Base::retSuccess('success', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/one 02. 获取单个会话信息
|
||||
*
|
||||
|
||||
@ -58,29 +58,34 @@ class WebSocketDialog extends AbstractModel
|
||||
*/
|
||||
public function formatData($userid)
|
||||
{
|
||||
// 最后消息
|
||||
$last_msg = WebSocketDialogMsg::whereDialogId($this->id)->orderByDesc('id')->first();
|
||||
$this->last_msg = $last_msg;
|
||||
// 未读信息
|
||||
$unreadBuilder = WebSocketDialogMsgRead::whereDialogId($this->id)->whereUserid($userid)->whereReadAt(null);
|
||||
$this->unread = $unreadBuilder->count();
|
||||
$this->mention = 0;
|
||||
$this->last_umid = 0;
|
||||
if ($this->unread > 0) {
|
||||
$this->mention = $unreadBuilder->clone()->whereMention(1)->count();
|
||||
$this->last_umid = intval($unreadBuilder->clone()->orderByDesc('msg_id')->value('msg_id'));
|
||||
if (isset($this->search_msg_id)) {
|
||||
// 最后消息 (搜索预览消息)
|
||||
$this->last_msg = WebSocketDialogMsg::whereDialogId($this->id)->find($this->search_msg_id);
|
||||
$this->last_at = $this->last_msg?->created_at;
|
||||
} else {
|
||||
// 最后消息
|
||||
$this->last_msg = WebSocketDialogMsg::whereDialogId($this->id)->orderByDesc('id')->first();
|
||||
// 未读信息
|
||||
$unreadBuilder = WebSocketDialogMsgRead::whereDialogId($this->id)->whereUserid($userid)->whereReadAt(null);
|
||||
$this->unread = $unreadBuilder->count();
|
||||
$this->mention = 0;
|
||||
$this->last_umid = 0;
|
||||
if ($this->unread > 0) {
|
||||
$this->mention = $unreadBuilder->clone()->whereMention(1)->count();
|
||||
$this->last_umid = intval($unreadBuilder->clone()->orderByDesc('msg_id')->value('msg_id'));
|
||||
}
|
||||
$this->mark_unread = $this->mark_unread ?? WebSocketDialogUser::whereDialogId($this->id)->whereUserid($userid)->value('mark_unread');
|
||||
// 对话人数
|
||||
$builder = WebSocketDialogUser::whereDialogId($this->id);
|
||||
$this->people = $builder->count();
|
||||
}
|
||||
$this->mark_unread = $this->mark_unread ?? WebSocketDialogUser::whereDialogId($this->id)->whereUserid($userid)->value('mark_unread');
|
||||
// 对话人数
|
||||
$builder = WebSocketDialogUser::whereDialogId($this->id);
|
||||
$this->people = $builder->count();
|
||||
// 对方信息
|
||||
$this->dialog_user = null;
|
||||
$this->group_info = null;
|
||||
$this->top_at = $this->top_at ?? WebSocketDialogUser::whereDialogId($this->id)->whereUserid($userid)->value('top_at');
|
||||
switch ($this->type) {
|
||||
case "user":
|
||||
$dialog_user = $builder->where('userid', '!=', $userid)->first();
|
||||
$dialog_user = WebSocketDialogUser::whereDialogId($this->id)->where('userid', '!=', $userid)->first();
|
||||
if ($dialog_user->userid === 0) {
|
||||
$dialog_user->userid = $userid;
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
* @property string|null $type 消息类型
|
||||
* @property array|mixed $msg 详细消息
|
||||
* @property array|mixed $emoji emoji回复
|
||||
* @property string|null $key 搜索关键词
|
||||
* @property int|null $read 已阅数量
|
||||
* @property int|null $send 发送数量
|
||||
* @property int|null $reply_id 回复ID
|
||||
@ -39,6 +40,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereDialogType($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereEmoji($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereId($value)
|
||||
* @method static \Illuminate\Database\Eloquent\Builder|WebSocketDialogMsg whereKey($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 whereReplyId($value)
|
||||
@ -60,6 +62,7 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
];
|
||||
|
||||
protected $hidden = [
|
||||
'key',
|
||||
'updated_at',
|
||||
];
|
||||
|
||||
@ -335,6 +338,19 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成关键词
|
||||
* @return string
|
||||
*/
|
||||
public function generateMsgKey()
|
||||
{
|
||||
return match ($this->type) {
|
||||
'text' => strip_tags($this->msg['text']),
|
||||
'meeting', 'file' => $this->msg['name'],
|
||||
default => '',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 返回文本预览消息
|
||||
* @param $text
|
||||
@ -464,6 +480,7 @@ class WebSocketDialogMsg extends AbstractModel
|
||||
$dialog->save();
|
||||
$dialogMsg->send = 1;
|
||||
$dialogMsg->dialog_type = $dialog->type;
|
||||
$dialogMsg->key = $dialogMsg->generateMsgKey();
|
||||
$dialogMsg->save();
|
||||
});
|
||||
Task::deliver(new WebSocketDialogMsgTask($dialogMsg->id));
|
||||
|
||||
@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
@error_reporting(E_ALL & ~E_NOTICE & ~E_WARNING);
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddWebSocketDialogMsgsKey extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$isAdd = false;
|
||||
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) use (&$isAdd) {
|
||||
if (!Schema::hasColumn('web_socket_dialog_msgs', 'key')) {
|
||||
$isAdd = true;
|
||||
$table->text('key')->after('emoji')->nullable()->default('')->comment('搜索关键词');
|
||||
}
|
||||
});
|
||||
if ($isAdd) {
|
||||
\App\Models\WebSocketDialogMsg::chunkById(100, function ($lists) {
|
||||
/** @var \App\Models\WebSocketDialogMsg $item */
|
||||
foreach ($lists as $item) {
|
||||
$key = $item->generateMsgKey();
|
||||
if ($key) {
|
||||
$item->key = $key;
|
||||
$item->save();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
Schema::table('web_socket_dialog_msgs', function (Blueprint $table) {
|
||||
$table->dropColumn("key");
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -285,6 +285,10 @@ export default {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
searchMsgId: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
autoFocus: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
@ -315,6 +319,7 @@ export default {
|
||||
userids: [],
|
||||
},
|
||||
|
||||
confirmId: 0,
|
||||
dialogDrag: false,
|
||||
groupInfoShow: false,
|
||||
|
||||
@ -473,7 +478,10 @@ export default {
|
||||
this.allMsgs = this.allMsgList;
|
||||
requestAnimationFrame(this.onToBottom);
|
||||
}
|
||||
this.$store.dispatch("getDialogMsgs", id).catch(_ => {});
|
||||
this.$store.dispatch("getDialogMsgs", id).then(_ => {
|
||||
this.confirmId = id;
|
||||
setTimeout(this.onSearchMsgId, 100)
|
||||
}).catch(_ => {});
|
||||
//
|
||||
this.$store.dispatch('saveInDialog', {
|
||||
uid: this._uid,
|
||||
@ -488,6 +496,10 @@ export default {
|
||||
immediate: true
|
||||
},
|
||||
|
||||
searchMsgId() {
|
||||
this.onSearchMsgId();
|
||||
},
|
||||
|
||||
dialogMsgTransfer: {
|
||||
handler({time, msgFile, msgRecord, msgText}) {
|
||||
if (time > $A.Time()) {
|
||||
@ -675,6 +687,44 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
onSearchMsgId() {
|
||||
if (this.searchMsgId > 0 && this.confirmId === this.dialogId) {
|
||||
this.onPositionId(this.searchMsgId)
|
||||
this.$store.state.searchMsgId = 0
|
||||
}
|
||||
},
|
||||
|
||||
onPositionId(position_id, msg_id = 0) {
|
||||
if (position_id === 0) {
|
||||
return
|
||||
}
|
||||
const index = this.allMsgs.findIndex(item => item.id === position_id)
|
||||
if (index > -1) {
|
||||
this.onToIndex(index)
|
||||
} else {
|
||||
if (msg_id > 0) {
|
||||
this.$store.dispatch("setLoad", {
|
||||
key: `msg-${msg_id}`,
|
||||
delay: 600
|
||||
})
|
||||
}
|
||||
this.preventToBottom = true;
|
||||
this.$store.dispatch("getDialogMoreMsgs", {
|
||||
dialog_id: this.dialogId,
|
||||
position_id
|
||||
}).finally(_ => {
|
||||
const index = this.allMsgs.findIndex(item => item.id === position_id)
|
||||
if (index > -1) {
|
||||
this.onToIndex(index)
|
||||
}
|
||||
if (msg_id > 0) {
|
||||
this.$store.dispatch("cancelLoad", `msg-${msg_id}`)
|
||||
}
|
||||
this.preventToBottom = false;
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
itemClassAdd(index) {
|
||||
return index === this.replyActiveIndex ? 'dialog-shake' : '';
|
||||
},
|
||||
@ -832,12 +882,13 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
onToIndex(index, addOffset) {
|
||||
onToIndex(index) {
|
||||
const scroller = this.$refs.scroller;
|
||||
if (scroller) {
|
||||
scroller.scrollToIndex(index, addOffset);
|
||||
requestAnimationFrame(_ => scroller.scrollToIndex(index, addOffset)) // 确保滚动到
|
||||
scroller.scrollToIndex(index, -100);
|
||||
requestAnimationFrame(_ => scroller.scrollToIndex(index, -100)) // 确保滚动到
|
||||
}
|
||||
requestAnimationFrame(_ => this.replyActiveIndex = index)
|
||||
},
|
||||
|
||||
onToOffset(offset) {
|
||||
@ -1123,31 +1174,7 @@ export default {
|
||||
if (this.operateVisible) {
|
||||
return
|
||||
}
|
||||
const runToIndex = (index) => {
|
||||
this.onToIndex(index, -100)
|
||||
requestAnimationFrame(_ => this.replyActiveIndex = index)
|
||||
}
|
||||
const index = this.allMsgs.findIndex(item => item.id === data.reply_id)
|
||||
if (index > -1) {
|
||||
runToIndex(index)
|
||||
} else {
|
||||
this.$store.dispatch("setLoad", {
|
||||
key: `msg-${data.msg_id}`,
|
||||
delay: 600
|
||||
})
|
||||
this.preventToBottom = true;
|
||||
this.$store.dispatch("getDialogMoreMsgs", {
|
||||
dialog_id: this.dialogId,
|
||||
position_id: data.reply_id
|
||||
}).finally(_ => {
|
||||
const index = this.allMsgs.findIndex(item => item.id === data.reply_id)
|
||||
if (index > -1) {
|
||||
runToIndex(index)
|
||||
}
|
||||
this.$store.dispatch("cancelLoad", `msg-${data.msg_id}`)
|
||||
this.preventToBottom = false;
|
||||
})
|
||||
}
|
||||
this.onPositionId(data.reply_id, data.msg_id)
|
||||
},
|
||||
|
||||
onViewText({target}) {
|
||||
|
||||
@ -33,22 +33,20 @@
|
||||
<ul
|
||||
v-if="tabActive==='dialog'"
|
||||
class="dialog">
|
||||
<li v-if="dialogList.length === 0" class="nothing">
|
||||
{{$L(dialogKey ? `没有任何与"${dialogKey}"相关的会话` : `没有任何会话`)}}
|
||||
</li>
|
||||
<template v-if="dialogList.length === 0">
|
||||
<li v-if="dialogLoad > 0" class="loading"><Loading/></li>
|
||||
<li v-else class="nothing">
|
||||
{{$L(dialogKey ? `没有任何与"${dialogKey}"相关的会话` : `没有任何会话`)}}
|
||||
</li>
|
||||
</template>
|
||||
<li
|
||||
v-else
|
||||
v-for="(dialog, key) in dialogList"
|
||||
:ref="`dialog_${dialog.id}`"
|
||||
:key="key"
|
||||
:class="{
|
||||
top: dialog.top_at,
|
||||
active: dialog.id == dialogId,
|
||||
operate: operateVisible && dialog.id == operateItem.id,
|
||||
completed: $A.dialogCompleted(dialog)
|
||||
}"
|
||||
:data-id="dialog.id"
|
||||
@click="openDialog(dialog.id)"
|
||||
:class="dialogClass(dialog)"
|
||||
@click="openDialog(dialog.id, dialog.search_msg_id)"
|
||||
v-longpress="handleLongpress">
|
||||
<template v-if="dialog.type=='group'">
|
||||
<i v-if="dialog.group_type=='project'" class="taskfont icon-avatar project"></i>
|
||||
@ -141,7 +139,12 @@
|
||||
<div class="msg-dialog-bg-icon"><Icon type="ios-chatbubbles" /></div>
|
||||
<div class="msg-dialog-bg-text">{{$L('选择一个会话开始聊天')}}</div>
|
||||
</div>
|
||||
<DialogWrapper v-if="windowLarge && dialogId > 0" :dialogId="dialogId" @on-active="scrollIntoActive" :auto-focus="$A.isDesktop()"/>
|
||||
<DialogWrapper
|
||||
v-if="windowLarge && dialogId > 0"
|
||||
:dialogId="dialogId"
|
||||
:searchMsgId="dialogSearchMsgId"
|
||||
@on-active="scrollIntoActive"
|
||||
:auto-focus="$A.isDesktop()"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -161,14 +164,17 @@ export default {
|
||||
return {
|
||||
tabActive: 'dialog',
|
||||
|
||||
dialogLoad: 0,
|
||||
dialogKey: '',
|
||||
dialogSearch: [],
|
||||
|
||||
dialogActive: '',
|
||||
dialogType: [
|
||||
{type: '', name: '全部'},
|
||||
{type: 'project', name: '项目'},
|
||||
{type: 'task', name: '任务'},
|
||||
{type: 'user', name: '个人'},
|
||||
],
|
||||
dialogActive: '',
|
||||
dialogKey: '',
|
||||
|
||||
contactsKey: '',
|
||||
contactsLoad: 0,
|
||||
@ -199,14 +205,14 @@ export default {
|
||||
},
|
||||
|
||||
computed: {
|
||||
...mapState(['cacheDialogs', 'loadDialogs', 'dialogId']),
|
||||
...mapState(['cacheDialogs', 'loadDialogs', 'dialogId', 'dialogSearchMsgId']),
|
||||
|
||||
routeName() {
|
||||
return this.$route.name
|
||||
},
|
||||
|
||||
dialogList() {
|
||||
const {dialogActive, dialogKey} = this;
|
||||
const {dialogActive, dialogKey, dialogSearch} = this;
|
||||
if (dialogActive == '' && dialogKey == '') {
|
||||
return this.cacheDialogs.filter(dialog => this.filterDialog(dialog)).sort((a, b) => {
|
||||
if (a.top_at || b.top_at) {
|
||||
@ -215,7 +221,7 @@ export default {
|
||||
return $A.Date(b.last_at) - $A.Date(a.last_at);
|
||||
});
|
||||
}
|
||||
return this.cacheDialogs.filter(dialog => {
|
||||
const list = this.cacheDialogs.filter(dialog => {
|
||||
if (!this.filterDialog(dialog)) {
|
||||
return false;
|
||||
}
|
||||
@ -243,7 +249,11 @@ export default {
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}).sort((a, b) => {
|
||||
})
|
||||
if (dialogSearch.length > 0) {
|
||||
list.push(...dialogSearch.map(item => Object.assign(item, {is_search: true})))
|
||||
}
|
||||
return list.sort((a, b) => {
|
||||
if (a.top_at || b.top_at) {
|
||||
return $A.Date(b.top_at) - $A.Date(a.top_at);
|
||||
}
|
||||
@ -263,7 +273,7 @@ export default {
|
||||
},
|
||||
|
||||
contactsList() {
|
||||
let list = [];
|
||||
const list = [];
|
||||
this.contactsFilter.some(user => {
|
||||
let az = user.az ? user.az.toUpperCase() : "#";
|
||||
let item = list.find(item => item.az == az);
|
||||
@ -342,6 +352,18 @@ export default {
|
||||
$A.reloadUrl();
|
||||
break;
|
||||
}
|
||||
//
|
||||
this.dialogSearch = [];
|
||||
if (val == '') {
|
||||
return;
|
||||
}
|
||||
this.dialogLoad++;
|
||||
setTimeout(() => {
|
||||
if (this.dialogKey == val) {
|
||||
this.searchDialog(val);
|
||||
}
|
||||
this.dialogLoad--;
|
||||
}, 600);
|
||||
},
|
||||
|
||||
contactsKey(val) {
|
||||
@ -412,11 +434,27 @@ export default {
|
||||
this.dialogActive = type
|
||||
},
|
||||
|
||||
openDialog(dialogId) {
|
||||
dialogClass(dialog) {
|
||||
if (this.dialogKey) {
|
||||
return null
|
||||
}
|
||||
return {
|
||||
top: dialog.top_at,
|
||||
active: dialog.id == this.dialogId,
|
||||
operate: this.operateVisible && dialog.id == this.operateItem.id,
|
||||
completed: $A.dialogCompleted(dialog)
|
||||
}
|
||||
},
|
||||
|
||||
openDialog(dialogId, searchMsgId = null) {
|
||||
if (this.operateVisible) {
|
||||
return
|
||||
}
|
||||
this.$store.dispatch("openDialog", dialogId)
|
||||
this.dialogKey = "";
|
||||
this.$store.dispatch("openDialog", {
|
||||
dialog_id: dialogId,
|
||||
search_msg_id: searchMsgId
|
||||
})
|
||||
},
|
||||
|
||||
openContacts(user) {
|
||||
@ -470,6 +508,18 @@ export default {
|
||||
return true;
|
||||
},
|
||||
|
||||
searchDialog(key) {
|
||||
this.dialogLoad++;
|
||||
this.$store.dispatch("call", {
|
||||
url: 'dialog/search',
|
||||
data: {key},
|
||||
}).then(({data}) => {
|
||||
this.dialogSearch = data;
|
||||
}).finally(_ => {
|
||||
this.dialogLoad--;
|
||||
});
|
||||
},
|
||||
|
||||
getContactsList(page) {
|
||||
this.contactsLoad++;
|
||||
this.$store.dispatch("call", {
|
||||
|
||||
6
resources/assets/js/store/actions.js
vendored
6
resources/assets/js/store/actions.js
vendored
@ -2026,6 +2026,12 @@ export default {
|
||||
* @param dialog_id
|
||||
*/
|
||||
openDialog({state, dispatch}, dialog_id) {
|
||||
let search_msg_id;
|
||||
if ($A.isJson(dialog_id)) {
|
||||
search_msg_id = dialog_id.search_msg_id;
|
||||
dialog_id = dialog_id.dialog_id;
|
||||
}
|
||||
state.dialogSearchMsgId = /\d+/.test(search_msg_id) ? search_msg_id : 0;
|
||||
state.dialogId = /\d+/.test(dialog_id) ? dialog_id : 0;
|
||||
},
|
||||
|
||||
|
||||
1
resources/assets/js/store/state.js
vendored
1
resources/assets/js/store/state.js
vendored
@ -71,6 +71,7 @@ const stateData = {
|
||||
|
||||
// 会话聊天
|
||||
dialogId: 0,
|
||||
dialogSearchMsgId: 0,
|
||||
dialogIns: [],
|
||||
dialogMsgs: [],
|
||||
dialogInputCache: $A.getStorageArray("cacheDialogInput"),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user