mirror of
https://github.com/kuaifan/dootask.git
synced 2025-12-13 12:02:51 +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评论会话
|
* @api {post} api/dialog/okr/add 56. 创建OKR评论会话
|
||||||
*
|
*
|
||||||
|
|||||||
@ -18,6 +18,10 @@
|
|||||||
<em v-if="userData.delete_at" class="deleted no-dark-content">{{$L('已删除')}}</em>
|
<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>
|
<em v-else-if="userData.disable_at" class="disabled no-dark-content">{{$L('已离职')}}</em>
|
||||||
</li>
|
</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">
|
<template v-if="!userData.bot">
|
||||||
<li>
|
<li>
|
||||||
<span>{{$L('部门')}}: </span>
|
<span>{{$L('部门')}}: </span>
|
||||||
@ -41,11 +45,54 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<Button icon="md-chatbubbles" :disabled="!!userData.delete_at" @click="onOpenDialog">{{ $L('开始聊天') }}</Button>
|
<Button icon="md-chatbubbles" :disabled="!!userData.delete_at" @click="onOpenDialog">{{ $L('开始聊天') }}</Button>
|
||||||
</div>
|
</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>
|
</ModalAlive>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import emitter from "../../../store/events";
|
import emitter from "../../../store/events";
|
||||||
|
import transformEmojiToHtml from "../../../utils/emoji";
|
||||||
import {mapState} from "vuex";
|
import {mapState} from "vuex";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
@ -58,6 +105,15 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
showModal: false,
|
showModal: false,
|
||||||
|
|
||||||
|
commonDialog: {
|
||||||
|
total: null,
|
||||||
|
list: [],
|
||||||
|
page: 1,
|
||||||
|
has_more: false,
|
||||||
|
},
|
||||||
|
commonDialogShow: false,
|
||||||
|
commonDialogLoading: 0,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -70,16 +126,29 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
...mapState(['cacheUserBasic'])
|
...mapState(['cacheUserBasic']),
|
||||||
|
|
||||||
|
commonDialogShow() {
|
||||||
|
if (!this.commonDialogShow || this.commonDialog.list.length > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.loadCommonDialogList(false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
isFullscreen({windowWidth}) {
|
isFullscreen({windowWidth}) {
|
||||||
return windowWidth < 576
|
return windowWidth < 576
|
||||||
},
|
},
|
||||||
|
|
||||||
|
commonDialogList() {
|
||||||
|
return this.commonDialog.list || [];
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
transformEmojiToHtml,
|
||||||
|
|
||||||
onShow(userid) {
|
onShow(userid) {
|
||||||
if (!/^\d+$/.test(userid)) {
|
if (!/^\d+$/.test(userid)) {
|
||||||
return
|
return
|
||||||
@ -87,13 +156,15 @@ export default {
|
|||||||
this.$store.dispatch("showSpinner", 600)
|
this.$store.dispatch("showSpinner", 600)
|
||||||
this.$store.dispatch('getUserData', userid).then(user => {
|
this.$store.dispatch('getUserData', userid).then(user => {
|
||||||
this.userData = user;
|
this.userData = user;
|
||||||
this.showModal = true
|
this.showModal = true;
|
||||||
|
this.loadCommonDialogCount()
|
||||||
}).finally(_ => {
|
}).finally(_ => {
|
||||||
this.$store.dispatch("hiddenSpinner")
|
this.$store.dispatch("hiddenSpinner")
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onHide() {
|
onHide() {
|
||||||
|
this.commonDialogShow = false;
|
||||||
this.showModal = false
|
this.showModal = false
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -107,7 +178,65 @@ export default {
|
|||||||
}).catch(({msg}) => {
|
}).catch(({msg}) => {
|
||||||
$A.modalError(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>
|
</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 {
|
.file-icon {
|
||||||
&:before {
|
&:before {
|
||||||
background-image: url("../images/file/dark/other.svg");
|
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