From 1ec4796f729b8fcb6ac1ef72ac50465ddf25be07 Mon Sep 17 00:00:00 2001 From: kuaifan Date: Mon, 18 Aug 2025 08:33:00 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=9F=A5=E7=9C=8B?= =?UTF-8?q?=E5=85=B1=E5=90=8C=E7=9A=84=E7=BE=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Http/Controllers/Api/DialogController.php | 77 +++++++ .../js/pages/manage/components/UserDetail.vue | 201 ++++++++++++++---- resources/assets/sass/dark.scss | 12 ++ .../sass/pages/components/user-detail.scss | 155 ++++++++++++++ 4 files changed, 409 insertions(+), 36 deletions(-) diff --git a/app/Http/Controllers/Api/DialogController.php b/app/Http/Controllers/Api/DialogController.php index 38fc9ec13..e685f8674 100755 --- a/app/Http/Controllers/Api/DialogController.php +++ b/app/Http/Controllers/Api/DialogController.php @@ -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评论会话 * diff --git a/resources/assets/js/pages/manage/components/UserDetail.vue b/resources/assets/js/pages/manage/components/UserDetail.vue index 796a791a7..c5652fde5 100755 --- a/resources/assets/js/pages/manage/components/UserDetail.vue +++ b/resources/assets/js/pages/manage/components/UserDetail.vue @@ -6,46 +6,93 @@ :mask-closable="false" :footer-hide="true" width="600"> -
- -
    -
  • -

    {{userData.nickname}}

    - {{$L('已删除')}} - {{$L('已离职')}} +
    + + + +
    + + + +
    +
    + +
    +
    +
    + +

    {{$L('暂无共同群组')}}

    +
    +
    +
    +
    +
    + + + + + + +
    +
    +
    +
    + {{$L('(*)人', dialog.people || 0)}} + {{$A.timeFormat(dialog.last_at)}} +
    +
    + +
    +
    + +
    +
    +
    diff --git a/resources/assets/sass/dark.scss b/resources/assets/sass/dark.scss index 1154a32b9..d51006d5a 100644 --- a/resources/assets/sass/dark.scss +++ b/resources/assets/sass/dark.scss @@ -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"); diff --git a/resources/assets/sass/pages/components/user-detail.scss b/resources/assets/sass/pages/components/user-detail.scss index c4514a562..40c68680f 100755 --- a/resources/assets/sass/pages/components/user-detail.scss +++ b/resources/assets/sass/pages/components/user-detail.scss @@ -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; + } +}