feat: add IM governance admin enhancements

This commit is contained in:
richjohnrambo
2026-08-21 16:06:29 +08:00
parent 543c7b624e
commit 21a5b97ac8
34 changed files with 2621 additions and 135 deletions
+198 -20
View File
@@ -15,21 +15,84 @@
<n-descriptions label-placement="left" bordered :column="settingStore.isMobile ? 1 : 2">
<n-descriptions-item label="群名称">{{ governance.name || '-' }}</n-descriptions-item>
<n-descriptions-item label="业务状态">{{ groupStatusLabel }}</n-descriptions-item>
<n-descriptions-item label="群主ID">{{ governance.ownerUserId || '-' }}</n-descriptions-item>
<n-descriptions-item label="成员数">{{ governance.memberCount || '-' }}</n-descriptions-item>
<n-descriptions-item label="入群审批">{{ governance.joinApproval ? '开启' : '关闭' }}</n-descriptions-item>
<n-descriptions-item label="全员禁言">{{ governance.muteAll ? '开启' : '关闭' }}</n-descriptions-item>
<n-descriptions-item label="成员上传文件">{{ governance.memberUploadFiles ? '允许' : '禁止' }}</n-descriptions-item>
<n-descriptions-item label="成员私聊">{{ governance.memberPrivateChat ? '允许' : '禁止' }}</n-descriptions-item>
<n-descriptions-item label="成员邀请">{{ governance.memberInvite ? '允许' : '禁止' }}</n-descriptions-item>
<n-descriptions-item label="最大成员数">{{ governance.maxMemberCount || '-' }}</n-descriptions-item>
<n-descriptions-item label="最大管理员数">{{ governance.maxAdminCount || '-' }}</n-descriptions-item>
<n-descriptions-item label="版本">{{ governance.version || '-' }}</n-descriptions-item>
<n-descriptions-item label="创建人">{{ governance.createdBy || '-' }}</n-descriptions-item>
<n-descriptions-item label="创建时间">{{ governance.createdAt || '-' }}</n-descriptions-item>
<n-descriptions-item label="解散人">{{ governance.dissolvedBy || '-' }}</n-descriptions-item>
<n-descriptions-item label="解散时间">{{ governance.dissolvedAt || '-' }}</n-descriptions-item>
<n-descriptions-item label="简介" :span="2">{{ governance.description || '-' }}</n-descriptions-item>
<n-descriptions-item label="群主ID">{{
governance.ownerUserId || '-'
}}</n-descriptions-item>
<n-descriptions-item label="成员数">{{
governance.memberCount || '-'
}}</n-descriptions-item>
<n-descriptions-item label="入群审批">
<n-switch
v-model:value="governance.joinApproval"
:checked-value="1"
:unchecked-value="0"
:disabled="!canUpdateGovernance"
/>
</n-descriptions-item>
<n-descriptions-item label="全员禁言">
<n-switch
v-model:value="governance.muteAll"
:checked-value="1"
:unchecked-value="0"
:disabled="!canUpdateGovernance"
/>
</n-descriptions-item>
<n-descriptions-item label="成员上传文件">
<n-switch
v-model:value="governance.memberUploadFiles"
:checked-value="1"
:unchecked-value="0"
:disabled="!canUpdateGovernance"
/>
</n-descriptions-item>
<n-descriptions-item label="成员私聊">
<n-switch
v-model:value="governance.memberPrivateChat"
:checked-value="1"
:unchecked-value="0"
:disabled="!canUpdateGovernance"
/>
</n-descriptions-item>
<n-descriptions-item label="成员邀请">
<n-switch
v-model:value="governance.memberInvite"
:checked-value="1"
:unchecked-value="0"
:disabled="!canUpdateGovernance"
/>
</n-descriptions-item>
<n-descriptions-item label="最大成员数">
<n-input-number
v-model:value="governance.maxMemberCount"
:min="0"
:disabled="!canUpdateGovernance"
/>
</n-descriptions-item>
<n-descriptions-item label="最大管理员数">
<n-input-number
v-model:value="governance.maxAdminCount"
:min="0"
:disabled="!canUpdateGovernance"
/>
</n-descriptions-item>
<n-descriptions-item label="版本">{{
governance.version || '-'
}}</n-descriptions-item>
<n-descriptions-item label="创建人">{{
governance.createdBy || '-'
}}</n-descriptions-item>
<n-descriptions-item label="创建时间">{{
governance.createdAt || '-'
}}</n-descriptions-item>
<n-descriptions-item label="解散人">{{
governance.dissolvedBy || '-'
}}</n-descriptions-item>
<n-descriptions-item label="解散时间">{{
governance.dissolvedAt || '-'
}}</n-descriptions-item>
<n-descriptions-item label="简介" :span="2">{{
governance.description || '-'
}}</n-descriptions-item>
</n-descriptions>
</n-spin>
</n-tab-pane>
@@ -69,6 +132,7 @@
:columns="muteColumns"
:request="muteData"
:row-key="(row) => row.topic + '-' + row.userId"
:actionColumn="muteActionColumn"
:scroll-x="muteScrollX"
:resizeHeightOffset="-10000"
/>
@@ -86,6 +150,22 @@
</n-tabs>
<template #action>
<n-space>
<n-button
v-if="activeTab === 'settings' && canUpdateGovernance"
type="primary"
:loading="submitLoading"
@click="handleSaveGovernance"
>
保存治理配置
</n-button>
<n-button
v-if="activeTab === 'settings' && canDissolve && governance.groupStatus !== 'dissolved'"
type="error"
:loading="submitLoading"
@click="handleDissolve"
>
解散群
</n-button>
<n-button @click="closeForm">关闭</n-button>
</n-space>
</template>
@@ -94,16 +174,21 @@
</template>
<script lang="ts" setup>
import { computed, nextTick, ref, watch } from 'vue';
import { computed, h, nextTick, reactive, ref, watch } from 'vue';
import { useDialog, useMessage } from 'naive-ui';
import { useDictStore } from '@/store/modules/dict';
import { BasicTable } from '@/components/Table';
import { usePermission } from '@/hooks/web/usePermission';
import { BasicTable, TableAction } from '@/components/Table';
import {
AdminLogList,
AnnouncementList,
Dissolve,
Governance,
GovernanceUpdate,
InvitationList,
JoinRequestList,
MuteList,
Unmute,
} from '@/api/imGroup';
import {
adminLogColumns,
@@ -120,24 +205,52 @@
const settingStore = useProjectSettingStore();
const dict = useDictStore();
const message = useMessage();
const dialog = useDialog();
const { hasPermission } = usePermission();
const showModal = ref(false);
const loading = ref(false);
const submitLoading = ref(false);
const activeTab = ref('settings');
const current = ref<State>(newState(null));
const governance = ref<GovernanceState>(new GovernanceState());
const dialogWidth = computed(() => adaModalWidth(1120));
const groupStatusLabel = computed(() => dict.getLabel('ImGroupStatusOptions', governance.value.groupStatus) || '-');
const canUpdateGovernance = computed(() => hasPermission(['/imGroup/governanceUpdate']));
const canDissolve = computed(() => hasPermission(['/imGroup/dissolve']));
const groupStatusLabel = computed(
() => dict.getLabel('ImGroupStatusOptions', governance.value.groupStatus) || '-'
);
const joinRequestRef = ref();
const invitationRef = ref();
const announcementRef = ref();
const muteRef = ref();
const adminLogRef = ref();
const muteActionColumn = reactive({
width: 110,
title: '操作',
key: 'action',
fixed: 'right',
render(record) {
return h(TableAction as any, {
style: 'button',
actions: [
{
label: '解除',
type: 'warning',
ifShow: () => record.status === 'active',
auth: ['/imGroup/unmute'],
onClick: () => handleUnmute(record),
},
],
});
},
});
const joinRequestScrollX = computed(() => adaTableScrollX(joinRequestColumns, 0));
const invitationScrollX = computed(() => adaTableScrollX(invitationColumns, 0));
const announcementScrollX = computed(() => adaTableScrollX(announcementColumns, 0));
const muteScrollX = computed(() => adaTableScrollX(muteColumns, 0));
const muteScrollX = computed(() => adaTableScrollX(muteColumns, muteActionColumn.width));
const adminLogScrollX = computed(() => adaTableScrollX(adminLogColumns, 0));
function baseParams(res) {
@@ -176,13 +289,78 @@
async function loadGovernance() {
loading.value = true;
try {
const res = await Governance({ tenantId: current.value.tenantId, topic: current.value.topic });
const res = await Governance({
tenantId: current.value.tenantId,
topic: current.value.topic,
});
governance.value = Object.assign(new GovernanceState(), res || {});
} finally {
loading.value = false;
}
}
async function handleSaveGovernance() {
submitLoading.value = true;
try {
await GovernanceUpdate({
tenantId: current.value.tenantId,
topic: current.value.topic,
joinApproval: governance.value.joinApproval,
muteAll: governance.value.muteAll,
memberUploadFiles: governance.value.memberUploadFiles,
memberPrivateChat: governance.value.memberPrivateChat,
memberInvite: governance.value.memberInvite,
maxMemberCount: governance.value.maxMemberCount,
maxAdminCount: governance.value.maxAdminCount,
});
message.success('操作成功');
await loadGovernance();
} finally {
submitLoading.value = false;
}
}
function handleDissolve() {
dialog.error({
title: '解散群',
content: '解散后该群将进入不可用状态,并写入群事件等待 IM 服务端同步,是否确认?',
positiveText: '确认',
negativeText: '取消',
onPositiveClick: async () => {
submitLoading.value = true;
try {
await Dissolve({
tenantId: current.value.tenantId,
topic: current.value.topic,
reason: '后台管理员解散',
});
message.success('操作成功');
await loadGovernance();
} finally {
submitLoading.value = false;
}
},
});
}
function handleUnmute(record) {
dialog.warning({
title: '解除禁言',
content: '确定解除该成员禁言?',
positiveText: '确认',
negativeText: '取消',
onPositiveClick: async () => {
await Unmute({
tenantId: current.value.tenantId,
topic: current.value.topic,
userId: record.userId,
});
message.success('操作成功');
muteRef.value?.reload();
},
});
}
function reloadActiveTable() {
const refs = {
joinRequests: joinRequestRef,