feat: add invite user binding
This commit is contained in:
@@ -40,6 +40,14 @@ export function Revoke(params) {
|
||||
});
|
||||
}
|
||||
|
||||
export function BindUser(params) {
|
||||
return http.request({
|
||||
url: '/imInvite/bindUser',
|
||||
method: 'POST',
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
export function BatchRevoke(params) {
|
||||
return http.request({
|
||||
url: '/imInvite/batchRevoke',
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<n-modal v-model:show="showModal" preset="card" title="批次详情" class="batch-view-modal">
|
||||
<n-descriptions :column="4" bordered size="small">
|
||||
<n-descriptions-item label="批次ID">{{ detail.batchId || '-' }}</n-descriptions-item>
|
||||
<n-descriptions-item label="总数">{{ detail.quantity || 0 }}</n-descriptions-item>
|
||||
<n-descriptions-item label="有效">{{ detail.activeCount || 0 }}</n-descriptions-item>
|
||||
<n-descriptions-item label="累计注册次数">{{ detail.usageCount || 0 }}</n-descriptions-item>
|
||||
@@ -27,6 +26,16 @@
|
||||
{ title: '邀请码', key: 'code' },
|
||||
{ title: '状态', key: 'status' },
|
||||
{ title: '累计使用次数', key: 'usageCount' },
|
||||
{
|
||||
title: '可用次数',
|
||||
key: 'availableUses',
|
||||
render(row: any) {
|
||||
if (!row.maxUses || Number(row.maxUses) <= 0) {
|
||||
return '不限制';
|
||||
}
|
||||
return `${Math.max(Number(row.maxUses) - Number(row.usageCount || 0), 0)}`;
|
||||
},
|
||||
},
|
||||
{ title: '最后使用时间', key: 'lastUsedAt' },
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<n-modal v-model:show="showModal" preset="card" title="绑定用户" class="bind-user-modal">
|
||||
<n-form :model="searchForm" label-placement="left" label-width="90">
|
||||
<n-grid :cols="2" :x-gap="12">
|
||||
<n-form-item-gi label="用户ID">
|
||||
<n-input v-model:value="searchForm.userId" clearable placeholder="用户ID/编号" />
|
||||
</n-form-item-gi>
|
||||
<n-form-item-gi label="关键词">
|
||||
<n-input v-model:value="searchForm.keyword" clearable placeholder="用户名/昵称/手机号" />
|
||||
</n-form-item-gi>
|
||||
</n-grid>
|
||||
<n-space>
|
||||
<n-button type="primary" :loading="loading" @click="handleSearch">查询</n-button>
|
||||
<n-button @click="handleReset">重置</n-button>
|
||||
</n-space>
|
||||
</n-form>
|
||||
<n-data-table
|
||||
class="user-table"
|
||||
:columns="columns"
|
||||
:data="users"
|
||||
:loading="loading"
|
||||
:row-key="(row) => row.userId"
|
||||
:checked-row-keys="checkedRowKeys"
|
||||
:pagination="pagination"
|
||||
@update:checked-row-keys="handleChecked"
|
||||
@update:page="handlePageChange"
|
||||
/>
|
||||
<template #footer>
|
||||
<n-space justify="end">
|
||||
<n-button @click="showModal = false">取消</n-button>
|
||||
<n-button type="primary" :disabled="!selectedUserId" :loading="submitting" @click="handleSubmit">
|
||||
确认绑定
|
||||
</n-button>
|
||||
</n-space>
|
||||
</template>
|
||||
</n-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, h, reactive, ref } from 'vue';
|
||||
import { NAvatar, useMessage } from 'naive-ui';
|
||||
import { List as UserList } from '@/api/imUser';
|
||||
import { BindUser } from '@/api/im/registrationInvite';
|
||||
import defaultAvatar from '@/assets/images/schoolboy.png';
|
||||
|
||||
const emit = defineEmits(['reload']);
|
||||
const message = useMessage();
|
||||
const showModal = ref(false);
|
||||
const loading = ref(false);
|
||||
const submitting = ref(false);
|
||||
const tenantId = ref(0);
|
||||
const inviteId = ref('');
|
||||
const users = ref<any[]>([]);
|
||||
const checkedRowKeys = ref<string[]>([]);
|
||||
const searchForm = reactive({ userId: '', keyword: '' });
|
||||
const pagination = reactive({ page: 1, pageSize: 10, itemCount: 0, showSizePicker: false });
|
||||
const selectedUserId = computed(() => checkedRowKeys.value[0] || '');
|
||||
|
||||
const columns = [
|
||||
{ type: 'selection', multiple: false },
|
||||
{ title: '编号', key: 'displayNo', width: 90 },
|
||||
{
|
||||
title: '头像',
|
||||
key: 'avatar',
|
||||
width: 70,
|
||||
render(row: any) {
|
||||
return h(NAvatar, { size: 36, src: row.avatar || defaultAvatar });
|
||||
},
|
||||
},
|
||||
{ title: '用户ID', key: 'userId', width: 130 },
|
||||
{ title: '用户名', key: 'username', width: 140 },
|
||||
{
|
||||
title: '昵称',
|
||||
key: 'nickname',
|
||||
width: 140,
|
||||
render(row: any) {
|
||||
return row.nickname || '--';
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '手机号',
|
||||
key: 'phone',
|
||||
width: 150,
|
||||
render(row: any) {
|
||||
return row.phone || '--';
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
async function loadUsers() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const res = await UserList({
|
||||
tenantId: tenantId.value,
|
||||
userId: searchForm.userId,
|
||||
keyword: searchForm.keyword,
|
||||
page: pagination.page,
|
||||
perPage: pagination.pageSize,
|
||||
});
|
||||
users.value = res?.list || [];
|
||||
pagination.itemCount = Number(res?.total || 0);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleChecked(keys: string[]) {
|
||||
checkedRowKeys.value = keys.slice(-1);
|
||||
}
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
pagination.page = page;
|
||||
loadUsers();
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
pagination.page = 1;
|
||||
checkedRowKeys.value = [];
|
||||
loadUsers();
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
searchForm.userId = '';
|
||||
searchForm.keyword = '';
|
||||
handleSearch();
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!selectedUserId.value) {
|
||||
message.warning('请选择要绑定的用户');
|
||||
return;
|
||||
}
|
||||
submitting.value = true;
|
||||
try {
|
||||
await BindUser({ tenantId: tenantId.value, id: inviteId.value, userId: Number(selectedUserId.value) });
|
||||
message.success('绑定成功');
|
||||
showModal.value = false;
|
||||
emit('reload');
|
||||
} finally {
|
||||
submitting.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function openModal(rowTenantId: number, rowInviteId: string) {
|
||||
tenantId.value = rowTenantId;
|
||||
inviteId.value = rowInviteId;
|
||||
searchForm.userId = '';
|
||||
searchForm.keyword = '';
|
||||
users.value = [];
|
||||
checkedRowKeys.value = [];
|
||||
pagination.page = 1;
|
||||
pagination.itemCount = 0;
|
||||
showModal.value = true;
|
||||
}
|
||||
|
||||
defineExpose({ openModal });
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.bind-user-modal {
|
||||
width: min(980px, 94vw);
|
||||
}
|
||||
|
||||
.user-table {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -45,6 +45,7 @@
|
||||
</n-card>
|
||||
<CreateModal ref="createRef" @reload="reloadTable" />
|
||||
<BatchViewModal ref="batchViewRef" />
|
||||
<BindUserModal ref="bindUserRef" @reload="reloadTable" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -69,6 +70,7 @@
|
||||
import { adaTableScrollX } from '@/utils/hotgo';
|
||||
import CreateModal from './CreateModal.vue';
|
||||
import BatchViewModal from './BatchViewModal.vue';
|
||||
import BindUserModal from './BindUserModal.vue';
|
||||
|
||||
const message = useMessage();
|
||||
const dialog = useDialog();
|
||||
@@ -78,6 +80,7 @@
|
||||
const searchFormRef = ref<any>({});
|
||||
const createRef = ref();
|
||||
const batchViewRef = ref();
|
||||
const bindUserRef = ref();
|
||||
const isPlatformAdmin = computed(() => userStore.isCompanyDept);
|
||||
|
||||
const tableColumns = computed(() =>
|
||||
@@ -98,7 +101,7 @@
|
||||
);
|
||||
|
||||
const actionColumn = reactive({
|
||||
width: 300,
|
||||
width: 380,
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
fixed: 'right',
|
||||
@@ -106,6 +109,12 @@
|
||||
return h(TableAction as any, {
|
||||
style: 'button',
|
||||
actions: [
|
||||
{
|
||||
label: '绑定用户',
|
||||
onClick: handleBindUser.bind(null, record),
|
||||
auth: ['/imInvite/bindUser'],
|
||||
ifShow: () => record.status === 'active' && !record.boundUserId && Number(record.usageCount || 0) === 0,
|
||||
},
|
||||
{
|
||||
label: '批次',
|
||||
onClick: handleBatchView.bind(null, record),
|
||||
@@ -168,6 +177,10 @@
|
||||
batchViewRef.value.openModal(Number(record.tenantId || selectedTenantId()), record.batchId);
|
||||
}
|
||||
|
||||
function handleBindUser(record: State) {
|
||||
bindUserRef.value.openModal(Number(record.tenantId || selectedTenantId()), record.id);
|
||||
}
|
||||
|
||||
function handleRevoke(record: State) {
|
||||
dialog.warning({
|
||||
title: '确认撤销',
|
||||
@@ -185,7 +198,7 @@
|
||||
function handleBatchRevoke(record: State) {
|
||||
dialog.warning({
|
||||
title: '确认撤销批次',
|
||||
content: `将撤销批次 ${record.batchId} 下仍有效的邀请码,已过期或已撤销的邀请码不受影响,是否确认?`,
|
||||
content: '将撤销该批次下仍有效的邀请码,已过期或已撤销的邀请码不受影响,是否确认?',
|
||||
positiveText: '确认',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
|
||||
@@ -16,7 +16,10 @@ export class State {
|
||||
public code = '';
|
||||
public status = '';
|
||||
public expiresAt = '';
|
||||
public maxUses = 0;
|
||||
public usageCount = 0;
|
||||
public boundUserId = '';
|
||||
public boundUserLabel = '';
|
||||
public lastUsedAt = '';
|
||||
public createdBy = '';
|
||||
public remark = '';
|
||||
@@ -59,14 +62,6 @@ export const schemas = ref<FormSchema[]>([
|
||||
placeholder: '请选择状态',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'batchId',
|
||||
component: 'NInput',
|
||||
label: '批次ID',
|
||||
componentProps: {
|
||||
placeholder: '请输入批次ID',
|
||||
},
|
||||
},
|
||||
{
|
||||
field: 'createdBy',
|
||||
component: 'NInput',
|
||||
@@ -102,9 +97,26 @@ export const columns = [
|
||||
{ title: '商户', key: 'tenantName', align: 'left', width: 160 },
|
||||
{ title: '邀请码', key: 'code', align: 'left', width: 120 },
|
||||
{ title: '状态', key: 'status', align: 'left', width: 110 },
|
||||
{ title: '批次ID', key: 'batchId', align: 'left', width: 150 },
|
||||
{ title: '到期时间', key: 'expiresAt', align: 'left', width: 190 },
|
||||
{ title: '累计使用次数', key: 'usageCount', align: 'left', width: 130 },
|
||||
{
|
||||
title: '可用次数',
|
||||
key: 'availableUses',
|
||||
align: 'left',
|
||||
width: 110,
|
||||
render(row: State) {
|
||||
return inviteAvailableUsesLabel(row);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '绑定用户',
|
||||
key: 'boundUserLabel',
|
||||
align: 'left',
|
||||
width: 180,
|
||||
render(row: State) {
|
||||
return row.boundUserLabel || row.boundUserId || '--';
|
||||
},
|
||||
},
|
||||
{ title: '最后使用时间', key: 'lastUsedAt', align: 'left', width: 190 },
|
||||
{ title: '创建人', key: 'createdBy', align: 'left', width: 110 },
|
||||
{ title: '创建时间', key: 'createdAt', align: 'left', width: 190 },
|
||||
@@ -128,6 +140,13 @@ export function statusLabel(status: string) {
|
||||
return statusOptions.find((item) => item.value === status)?.label || status;
|
||||
}
|
||||
|
||||
export function inviteAvailableUsesLabel(row: State) {
|
||||
if (!row.maxUses || Number(row.maxUses) <= 0) {
|
||||
return '不限制';
|
||||
}
|
||||
return `${Math.max(Number(row.maxUses) - Number(row.usageCount || 0), 0)}`;
|
||||
}
|
||||
|
||||
export function localRfc3339(value: number | null) {
|
||||
if (!value) {
|
||||
return '';
|
||||
|
||||
Reference in New Issue
Block a user