mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-12 19:35:50 +00:00
feat: 添加查看共同的群
This commit is contained in:
parent
6964158cf6
commit
1ec4796f72
@ -2777,6 +2777,83 @@ class DialogController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} api/dialog/common/list 56. 共同群组群聊
|
||||
*
|
||||
* @apiDescription 需要token身份,按置顶时间、用户在群组中的最后活跃时间倒序排列
|
||||
* @apiVersion 1.0.0
|
||||
* @apiGroup dialog
|
||||
* @apiName common__list
|
||||
*
|
||||
* @apiParam {Number} [target_userid] 目标用户ID(和谁的共同群组,不传则获取自己所有群组)
|
||||
* @apiParam {Number} [page] 当前页数,默认为1
|
||||
* @apiParam {Number} [pagesize] 每页显示条数,默认为20,最大100
|
||||
* @apiParam {String} [only_count] 是否只返回数量,传入 'yes' 则只返回数量不返回列表
|
||||
*
|
||||
* @apiSuccess {Number} ret 返回状态码(1正确、0错误)
|
||||
* @apiSuccess {String} msg 返回信息(错误描述)
|
||||
* @apiSuccess {Object} data 返回数据
|
||||
*
|
||||
* - 当 only_count=yes 时:
|
||||
* @apiSuccess {Number} data.total 群组数量
|
||||
*
|
||||
* - 当获取列表时,返回 Laravel 标准分页格式:
|
||||
* @apiSuccess {Array} data.data 群组列表数据
|
||||
* @apiSuccess {Number} data.current_page 当前页数
|
||||
* @apiSuccess {Number} data.per_page 每页显示条数
|
||||
* @apiSuccess {Number} data.total 总数量
|
||||
* @apiSuccess {String} data.first_page_url 第一页链接
|
||||
* @apiSuccess {String} data.last_page_url 最后页链接
|
||||
* @apiSuccess {String} data.next_page_url 下一页链接
|
||||
* @apiSuccess {String} data.prev_page_url 上一页链接
|
||||
*/
|
||||
public function common__list()
|
||||
{
|
||||
$user = User::auth();
|
||||
//
|
||||
$target_userid = intval(Request::input('target_userid'));
|
||||
$only_count = trim(Request::input('only_count')) === 'yes';
|
||||
|
||||
// 参考getDialogList的查询模式
|
||||
$builder = DB::table('web_socket_dialog_users as u')
|
||||
->select(['d.*', 'u.top_at', 'u.last_at', 'u.mark_unread', 'u.silence', 'u.hide', 'u.color', 'u.updated_at as user_at'])
|
||||
->join('web_socket_dialogs as d', 'u.dialog_id', '=', 'd.id')
|
||||
->where('u.userid', $user->userid)
|
||||
->where('d.type', 'group')
|
||||
->where('d.group_type', 'user')
|
||||
->whereNull('d.deleted_at');
|
||||
|
||||
if ($target_userid) {
|
||||
// 获取与目标用户的共同群组
|
||||
$builder->whereExists(function($query) use ($target_userid) {
|
||||
$query->select(DB::raw(1))
|
||||
->from('web_socket_dialog_users as du2')
|
||||
->whereColumn('du2.dialog_id', 'd.id')
|
||||
->where('du2.userid', $target_userid);
|
||||
});
|
||||
}
|
||||
|
||||
if ($only_count) {
|
||||
// 只返回数量
|
||||
return Base::retSuccess('success', [
|
||||
'total' => $builder->count()
|
||||
]);
|
||||
}
|
||||
|
||||
// 返回分页列表,参考getDialogList的排序逻辑
|
||||
$list = $builder
|
||||
->orderByDesc('u.top_at')
|
||||
->orderByDesc('u.last_at')
|
||||
->paginate(Base::getPaginate(100, 20));
|
||||
|
||||
// 处理分页数据,与getDialogList保持一致的处理方式
|
||||
$list->transform(function ($item) use ($user) {
|
||||
return WebSocketDialog::synthesizeData($item, $user->userid);
|
||||
});
|
||||
|
||||
return Base::retSuccess('success', $list);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} api/dialog/okr/add 56. 创建OKR评论会话
|
||||
*
|
||||
|
||||
@ -6,46 +6,93 @@
|
||||
:mask-closable="false"
|
||||
:footer-hide="true"
|
||||
width="600">
|
||||
<div class="user-detail-body">
|
||||
<UserAvatar
|
||||
:userid="userData.userid"
|
||||
:size="120"
|
||||
:show-state-dot="false"
|
||||
@on-click="onOpenAvatar"/>
|
||||
<ul class="user-select-auto">
|
||||
<li class="user-name">
|
||||
<h1>{{userData.nickname}}</h1>
|
||||
<em v-if="userData.delete_at" class="deleted no-dark-content">{{$L('已删除')}}</em>
|
||||
<em v-else-if="userData.disable_at" class="disabled no-dark-content">{{$L('已离职')}}</em>
|
||||
<div class="user-detail-body">
|
||||
<UserAvatar
|
||||
:userid="userData.userid"
|
||||
:size="120"
|
||||
:show-state-dot="false"
|
||||
@on-click="onOpenAvatar"/>
|
||||
<ul class="user-select-auto">
|
||||
<li class="user-name">
|
||||
<h1>{{userData.nickname}}</h1>
|
||||
<em v-if="userData.delete_at" class="deleted no-dark-content">{{$L('已删除')}}</em>
|
||||
<em v-else-if="userData.disable_at" class="disabled no-dark-content">{{$L('已离职')}}</em>
|
||||
</li>
|
||||
<li v-if="userData.userid != userId && commonDialog.total !== null">
|
||||
<span>{{$L('共同群聊')}}: </span>
|
||||
<a href="javascript:void(0)" @click="commonDialogShow=true">{{ $L('(*)个', commonDialog.total) }}</a>
|
||||
</li>
|
||||
<template v-if="!userData.bot">
|
||||
<li>
|
||||
<span>{{$L('部门')}}: </span>
|
||||
{{userData.department_name || '-'}}
|
||||
</li>
|
||||
<template v-if="!userData.bot">
|
||||
<li>
|
||||
<span>{{$L('部门')}}: </span>
|
||||
{{userData.department_name || '-'}}
|
||||
</li>
|
||||
<li>
|
||||
<span>{{$L('职位/职称')}}: </span>
|
||||
{{userData.profession || '-'}}
|
||||
</li>
|
||||
<li>
|
||||
<span>{{$L('最后在线')}}: </span>
|
||||
{{$A.newDateString(userData.line_at, 'YYYY-MM-DD HH:mm') || '-'}}
|
||||
</li>
|
||||
<li v-if="userData.delete_at">
|
||||
<strong><span>{{$L('删除时间')}}: </span>{{$A.newDateString(userData.delete_at, 'YYYY-MM-DD HH:mm')}}</strong>
|
||||
</li>
|
||||
<li v-else-if="userData.disable_at">
|
||||
<strong><span>{{$L('离职时间')}}: </span>{{$A.newDateString(userData.disable_at, 'YYYY-MM-DD HH:mm')}}</strong>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
<Button icon="md-chatbubbles" :disabled="!!userData.delete_at" @click="onOpenDialog">{{ $L('开始聊天') }}</Button>
|
||||
<li>
|
||||
<span>{{$L('职位/职称')}}: </span>
|
||||
{{userData.profession || '-'}}
|
||||
</li>
|
||||
<li>
|
||||
<span>{{$L('最后在线')}}: </span>
|
||||
{{$A.newDateString(userData.line_at, 'YYYY-MM-DD HH:mm') || '-'}}
|
||||
</li>
|
||||
<li v-if="userData.delete_at">
|
||||
<strong><span>{{$L('删除时间')}}: </span>{{$A.newDateString(userData.delete_at, 'YYYY-MM-DD HH:mm')}}</strong>
|
||||
</li>
|
||||
<li v-else-if="userData.disable_at">
|
||||
<strong><span>{{$L('离职时间')}}: </span>{{$A.newDateString(userData.disable_at, 'YYYY-MM-DD HH:mm')}}</strong>
|
||||
</li>
|
||||
</template>
|
||||
</ul>
|
||||
<Button icon="md-chatbubbles" :disabled="!!userData.delete_at" @click="onOpenDialog">{{ $L('开始聊天') }}</Button>
|
||||
</div>
|
||||
|
||||
<!-- 共同群组 -->
|
||||
<Modal v-model="commonDialogShow" :title="$L('共同群组') + ' (' + $L('(*)个', commonDialog.total) + ')'" :footer-hide="true" width="500">
|
||||
<div class="common-dialog-content">
|
||||
<div v-if="commonDialogLoading > 0 && commonDialog.list.length === 0" class="loading-wrapper">
|
||||
<Loading/>
|
||||
</div>
|
||||
<div v-else-if="commonDialogList.length === 0" class="empty-wrapper">
|
||||
<div class="empty-content">
|
||||
<Icon type="ios-people-outline" size="48"/>
|
||||
<p>{{$L('暂无共同群组')}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="dialog-list">
|
||||
<div
|
||||
v-for="dialog in commonDialogList"
|
||||
:key="dialog.id"
|
||||
class="dialog-item"
|
||||
@click="onOpenCommonDialogChat(dialog)">
|
||||
<div class="dialog-avatar">
|
||||
<EAvatar v-if="dialog.avatar" :src="dialog.avatar" :size="42"></EAvatar>
|
||||
<i v-else-if="dialog.group_type=='department'" class="taskfont icon-avatar department"></i>
|
||||
<i v-else-if="dialog.group_type=='project'" class="taskfont icon-avatar project"></i>
|
||||
<i v-else-if="dialog.group_type=='task'" class="taskfont icon-avatar task"></i>
|
||||
<i v-else-if="dialog.group_type=='okr'" class="taskfont icon-avatar task"></i>
|
||||
<Icon v-else class="icon-avatar" type="ios-people" />
|
||||
</div>
|
||||
<div class="dialog-info">
|
||||
<div class="dialog-name" v-html="transformEmojiToHtml(dialog.name)"></div>
|
||||
<div class="dialog-meta">
|
||||
<span class="member-count">{{$L('(*)人', dialog.people || 0)}}</span>
|
||||
<span v-if="dialog.last_at" class="last-time">{{$A.timeFormat(dialog.last_at)}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<Icon class="enter-icon" type="ios-arrow-forward" />
|
||||
</div>
|
||||
<div v-if="commonDialog.has_more" class="load-more-wrapper">
|
||||
<Button type="primary" @click="loadCommonDialogList(true)" :loading="commonDialogLoading > 0">{{$L('加载更多')}}</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</ModalAlive>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import emitter from "../../../store/events";
|
||||
import transformEmojiToHtml from "../../../utils/emoji";
|
||||
import {mapState} from "vuex";
|
||||
|
||||
export default {
|
||||
@ -58,6 +105,15 @@ export default {
|
||||
},
|
||||
|
||||
showModal: false,
|
||||
|
||||
commonDialog: {
|
||||
total: null,
|
||||
list: [],
|
||||
page: 1,
|
||||
has_more: false,
|
||||
},
|
||||
commonDialogShow: false,
|
||||
commonDialogLoading: 0,
|
||||
}
|
||||
},
|
||||
|
||||
@ -70,16 +126,29 @@ export default {
|
||||
},
|
||||
|
||||
watch: {
|
||||
...mapState(['cacheUserBasic'])
|
||||
...mapState(['cacheUserBasic']),
|
||||
|
||||
commonDialogShow() {
|
||||
if (!this.commonDialogShow || this.commonDialog.list.length > 0) {
|
||||
return;
|
||||
}
|
||||
this.loadCommonDialogList(false);
|
||||
}
|
||||
},
|
||||
|
||||
computed: {
|
||||
isFullscreen({windowWidth}) {
|
||||
return windowWidth < 576
|
||||
},
|
||||
|
||||
commonDialogList() {
|
||||
return this.commonDialog.list || [];
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
transformEmojiToHtml,
|
||||
|
||||
onShow(userid) {
|
||||
if (!/^\d+$/.test(userid)) {
|
||||
return
|
||||
@ -87,13 +156,15 @@ export default {
|
||||
this.$store.dispatch("showSpinner", 600)
|
||||
this.$store.dispatch('getUserData', userid).then(user => {
|
||||
this.userData = user;
|
||||
this.showModal = true
|
||||
this.showModal = true;
|
||||
this.loadCommonDialogCount()
|
||||
}).finally(_ => {
|
||||
this.$store.dispatch("hiddenSpinner")
|
||||
});
|
||||
},
|
||||
|
||||
onHide() {
|
||||
this.commonDialogShow = false;
|
||||
this.showModal = false
|
||||
},
|
||||
|
||||
@ -107,7 +178,65 @@ export default {
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg)
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
loadCommonDialogCount() {
|
||||
const target_userid = this.userData.userid;
|
||||
if (this.commonDialog.userid !== target_userid) {
|
||||
this.commonDialog.total = null;
|
||||
}
|
||||
this.$store.dispatch('call', {
|
||||
url: 'dialog/common/list',
|
||||
data: {
|
||||
target_userid,
|
||||
only_count: 'yes'
|
||||
}
|
||||
}).then(({data}) => {
|
||||
if (target_userid !== this.userData.userid) {
|
||||
return
|
||||
}
|
||||
this.commonDialog = Object.assign(data, {
|
||||
userid: target_userid,
|
||||
list: [],
|
||||
has_more: false,
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
loadCommonDialogList(loadMore = false) {
|
||||
this.commonDialogLoading++;
|
||||
const target_userid = this.userData.userid;
|
||||
this.$store.dispatch('call', {
|
||||
url: 'dialog/common/list',
|
||||
data: {
|
||||
target_userid,
|
||||
page: loadMore ? this.commonDialog.page + 1 : 1
|
||||
}
|
||||
}).then(({data}) => {
|
||||
if (target_userid !== this.userData.userid) {
|
||||
return;
|
||||
}
|
||||
this.commonDialog = {
|
||||
...this.commonDialog,
|
||||
list: loadMore ? [...this.commonDialog.list, ...data.data] : data.data,
|
||||
total: data.total,
|
||||
page: data.current_page,
|
||||
has_more: !!data.next_page_url
|
||||
}
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg || this.$L('加载失败'));
|
||||
}).finally(() => {
|
||||
this.commonDialogLoading--;
|
||||
});
|
||||
},
|
||||
|
||||
onOpenCommonDialogChat(dialog) {
|
||||
this.$store.dispatch("openDialog", dialog.id).then(() => {
|
||||
this.onHide();
|
||||
}).catch(({msg}) => {
|
||||
$A.modalError(msg);
|
||||
});
|
||||
},
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
12
resources/assets/sass/dark.scss
vendored
12
resources/assets/sass/dark.scss
vendored
@ -321,6 +321,18 @@ body.dark-mode-reverse {
|
||||
}
|
||||
}
|
||||
|
||||
.common-dialog-content {
|
||||
.dialog-list {
|
||||
.dialog-item {
|
||||
.dialog-avatar {
|
||||
.icon-avatar {
|
||||
color: #1c1917;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
&:before {
|
||||
background-image: url("../images/file/dark/other.svg");
|
||||
|
||||
@ -82,3 +82,158 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 共同群组弹窗样式
|
||||
.common-dialog-content {
|
||||
margin: -16px -32px 0;
|
||||
|
||||
.loading-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
padding-top: 60px;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
.empty-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: 40px;
|
||||
padding-bottom: 80px;
|
||||
|
||||
.empty-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
color: #999;
|
||||
> i {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-list {
|
||||
padding: 0 12px;
|
||||
overflow-y: auto;
|
||||
max-height: calc(100vh - 310px);
|
||||
|
||||
@media (height <= 900px) {
|
||||
max-height: calc(100vh - 180px);
|
||||
}
|
||||
|
||||
.dialog-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
margin: 4px 0;
|
||||
transition: background-color 0.2s;
|
||||
|
||||
&:hover {
|
||||
background-color: #f5f7fa;
|
||||
}
|
||||
|
||||
.dialog-avatar {
|
||||
flex-shrink: 0;
|
||||
margin-right: 12px;
|
||||
|
||||
.img-avatar,
|
||||
.user-avatar,
|
||||
.icon-avatar {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
margin-right: 2px;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.img-avatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
> img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-avatar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
font-size: 26px;
|
||||
background-color: #61B2F9;
|
||||
color: #ffffff;
|
||||
|
||||
&.department {
|
||||
background-color: #5BC7B0;
|
||||
}
|
||||
|
||||
&.project {
|
||||
background-color: #6E99EB;
|
||||
}
|
||||
|
||||
&.task {
|
||||
background-color: #9B96DF;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.dialog-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
|
||||
.dialog-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #17233d;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.dialog-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
font-size: 12px;
|
||||
color: #808695;
|
||||
|
||||
.member-count {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.last-time {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.enter-icon {
|
||||
flex-shrink: 0;
|
||||
color: #c5c8ce;
|
||||
font-size: 16px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
padding-bottom: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.load-more-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user