feat: expand IM admin controls
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<n-modal
|
||||
v-model:show="showModal"
|
||||
preset="dialog"
|
||||
:show-icon="false"
|
||||
:title="'登录设备 ' + userId"
|
||||
:mask-closable="false"
|
||||
:style="{ width: dialogWidth }"
|
||||
>
|
||||
<BasicTable
|
||||
ref="actionRef"
|
||||
:columns="deviceColumns"
|
||||
:request="loadDataTable"
|
||||
:row-key="(row) => row.deviceId"
|
||||
:scroll-x="scrollX"
|
||||
:resizeHeightOffset="-10000"
|
||||
/>
|
||||
<template #action>
|
||||
<n-space>
|
||||
<n-button @click="showModal = false">关闭</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { BasicTable } from '@/components/Table';
|
||||
import { Devices } from '@/api/imUser';
|
||||
import { deviceColumns, State } from './model';
|
||||
import { adaModalWidth, adaTableScrollX } from '@/utils/hotgo';
|
||||
|
||||
const showModal = ref(false);
|
||||
const actionRef = ref();
|
||||
const tenantId = ref(0);
|
||||
const userId = ref('');
|
||||
const dialogWidth = computed(() => adaModalWidth(1080));
|
||||
const scrollX = computed(() => adaTableScrollX(deviceColumns, 0));
|
||||
|
||||
const loadDataTable = async (res) => {
|
||||
if (!tenantId.value || !userId.value) {
|
||||
return { list: [], page: 1, pageCount: 0, totalCount: 0 };
|
||||
}
|
||||
return await Devices({ tenantId: tenantId.value, userId: userId.value, ...res });
|
||||
};
|
||||
|
||||
function openModal(record: State) {
|
||||
tenantId.value = record.tenantId;
|
||||
userId.value = record.userId;
|
||||
showModal.value = true;
|
||||
setTimeout(() => {
|
||||
actionRef.value?.reload();
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({ openModal });
|
||||
</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="n-layout-page-header">
|
||||
<n-card :bordered="false" title="IM用户管理"></n-card>
|
||||
<n-card :bordered="false" title="IM用户管理" />
|
||||
</div>
|
||||
<n-card :bordered="false" class="proCard">
|
||||
<BasicForm
|
||||
@@ -22,30 +22,34 @@
|
||||
/>
|
||||
</n-card>
|
||||
<ViewModal ref="viewRef" />
|
||||
<DevicesModal ref="devicesRef" />
|
||||
<ResetPasswordModal ref="resetPasswordRef" @reload="reloadTable" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue';
|
||||
import { useMessage } from 'naive-ui';
|
||||
import { useDialog, useMessage } from 'naive-ui';
|
||||
import { BasicTable, TableAction } from '@/components/Table';
|
||||
import { BasicForm, useForm } from '@/components/Form/index';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
import { List } from '@/api/imUser';
|
||||
import { List, RevokeSessions } from '@/api/imUser';
|
||||
import { columns, loadOptions, loadTenantOptions, schemas, State, tenantOptions } from './model';
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
import ViewModal from './view.vue';
|
||||
import DevicesModal from './DevicesModal.vue';
|
||||
import ResetPasswordModal from './ResetPasswordModal.vue';
|
||||
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
const { hasPermission } = usePermission();
|
||||
const userStore = useUserStore();
|
||||
const isPlatformAdmin = computed(() => userStore.isCompanyDept);
|
||||
const actionRef = ref();
|
||||
const searchFormRef = ref<any>({});
|
||||
const viewRef = ref();
|
||||
const devicesRef = ref();
|
||||
const resetPasswordRef = ref();
|
||||
|
||||
const tenantSchema = schemas.value.find((item) => item.field === 'tenantId');
|
||||
@@ -54,7 +58,7 @@
|
||||
}
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 220,
|
||||
width: 330,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
@@ -67,6 +71,17 @@
|
||||
onClick: handleView.bind(null, record),
|
||||
auth: ['/imUser/view'],
|
||||
},
|
||||
{
|
||||
label: '设备',
|
||||
onClick: handleDevices.bind(null, record),
|
||||
auth: ['/imUser/devices'],
|
||||
},
|
||||
{
|
||||
label: '撤销会话',
|
||||
type: 'error',
|
||||
onClick: handleRevokeSessions.bind(null, record),
|
||||
auth: ['/imUser/revokeSessions'],
|
||||
},
|
||||
{
|
||||
label: '重置密码',
|
||||
type: 'warning',
|
||||
@@ -107,6 +122,24 @@
|
||||
viewRef.value.openModal(record);
|
||||
}
|
||||
|
||||
function handleDevices(record: State) {
|
||||
devicesRef.value.openModal(record);
|
||||
}
|
||||
|
||||
function handleRevokeSessions(record: State) {
|
||||
dialog.warning({
|
||||
title: '撤销用户会话',
|
||||
content: '将递增该用户认证版本,并通知 IM 服务清退在线会话,是否确认?',
|
||||
positiveText: '确认',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
await RevokeSessions({ tenantId: record.tenantId, userId: record.userId });
|
||||
message.success('会话撤销事件已提交');
|
||||
reloadTable();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleResetPassword(record: State) {
|
||||
resetPasswordRef.value.openModal(record);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
import { computed, ref } from 'vue';
|
||||
import { computed, h, ref } from 'vue';
|
||||
import { NSwitch } from 'naive-ui';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import { FormSchema } from '@/components/Form';
|
||||
import { defRangeShortcuts } from '@/utils/dateUtil';
|
||||
import { useDictStore } from '@/store/modules/dict';
|
||||
import { List as TenantList } from '@/api/imTenant';
|
||||
import { Status } from '@/api/imUser';
|
||||
import { usePermission } from '@/hooks/web/usePermission';
|
||||
|
||||
const dict = useDictStore();
|
||||
const { hasPermission } = usePermission();
|
||||
const $message = window['$message'];
|
||||
|
||||
export const tenantOptions = ref<any[]>([]);
|
||||
|
||||
@@ -43,6 +48,21 @@ export class State {
|
||||
}
|
||||
}
|
||||
|
||||
export class DeviceState {
|
||||
public tenantId = 0;
|
||||
public tenantName = '';
|
||||
public userId = '';
|
||||
public deviceId = '';
|
||||
public platform = '';
|
||||
public lang = '';
|
||||
public lastSeen = '';
|
||||
public pushProvider = '';
|
||||
public pushPlatform = '';
|
||||
public pushAppVersion = '';
|
||||
public pushTokenConfigured = false;
|
||||
public pushUpdatedAt = '';
|
||||
}
|
||||
|
||||
export function newState(state: State | Record<string, any> | null): State {
|
||||
if (state !== null) {
|
||||
if (state instanceof State) {
|
||||
@@ -159,7 +179,25 @@ export const columns = [
|
||||
align: 'left',
|
||||
width: 110,
|
||||
render(row: State) {
|
||||
return dict.getLabel('ImUserStateOptions', row.state);
|
||||
if (row.state === 20) {
|
||||
return dict.getLabel('ImUserStateOptions', row.state);
|
||||
}
|
||||
return h(NSwitch, {
|
||||
value: row.state === 0,
|
||||
checked: '正常',
|
||||
unchecked: '停用',
|
||||
disabled: !hasPermission(['/imUser/status']),
|
||||
onUpdateValue: async (value) => {
|
||||
const oldState = row.state;
|
||||
row.state = value ? 0 : 10;
|
||||
try {
|
||||
await Status({ tenantId: row.tenantId, userId: row.userId, state: row.state });
|
||||
$message.success('操作成功');
|
||||
} catch {
|
||||
row.state = oldState;
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -200,6 +238,26 @@ export const columns = [
|
||||
},
|
||||
];
|
||||
|
||||
export const deviceColumns = [
|
||||
{ title: '设备ID', key: 'deviceId', align: 'left', width: 240 },
|
||||
{ title: '平台', key: 'platform', align: 'left', width: 120 },
|
||||
{ title: '语言', key: 'lang', align: 'left', width: 90 },
|
||||
{ title: '最后活跃', key: 'lastSeen', align: 'left', width: 180 },
|
||||
{ title: 'Push服务商', key: 'pushProvider', align: 'left', width: 120 },
|
||||
{ title: 'Push平台', key: 'pushPlatform', align: 'left', width: 120 },
|
||||
{ title: 'App版本', key: 'pushAppVersion', align: 'left', width: 120 },
|
||||
{
|
||||
title: 'Push Token',
|
||||
key: 'pushTokenConfigured',
|
||||
align: 'left',
|
||||
width: 120,
|
||||
render(row: DeviceState) {
|
||||
return row.pushTokenConfigured ? '已配置' : '未配置';
|
||||
},
|
||||
},
|
||||
{ title: 'Push更新时间', key: 'pushUpdatedAt', align: 'left', width: 180 },
|
||||
];
|
||||
|
||||
// 加载字典数据选项
|
||||
export function loadOptions() {
|
||||
dict.loadOptions(['ImUserStateOptions']);
|
||||
|
||||
Reference in New Issue
Block a user