Files
im_manager/web/src/views/im/registrationInvite/CreateModal.vue
T
Austin b4174a6412 feat: 新增IM联系我们字典配置,优化用户搜索与邀请绑定功能
1. 优化IM用户列表搜索逻辑,支持长数字用户ID精确匹配
2. 修复前端雪花ID精度丢失问题,改用字符串传输用户ID
3. 新增邀请码批量绑定用户功能,支持搜索绑定指定用户
4. 新增字典配置接口,新增联系我们字典SQL脚本
5. 完善字典数据变更事件推送,优化弹窗关闭与页面刷新逻辑
2026-09-21 13:11:08 +08:00

179 lines
5.4 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<n-modal v-model:show="showModal" preset="dialog" :title="isBatch ? '批量生成邀请码' : '生成邀请码'">
<n-form ref="formRef" :model="formValue" :rules="rules" label-placement="left" :label-width="90">
<n-form-item v-if="isBatch" label="生成数量" path="quantity">
<n-input-number v-model:value="formValue.quantity" :min="1" :max="1000" />
</n-form-item>
<n-form-item v-if="isBatch" label="绑定用户" path="boundUserId">
<div class="bind-user-row">
<n-input
v-model:value="bindUserInput"
placeholder="输入用户ID/编号(非必填)"
clearable
:disabled="!!boundUser"
@keydown.enter="searchUser"
/>
<n-button :loading="searching" :disabled="!!boundUser" @click="searchUser">搜索</n-button>
</div>
<div v-if="boundUser" class="bound-user-tag">
<n-avatar :size="28" :src="boundUser.avatar || defaultAvatar" />
<span class="bound-user-name">{{ boundUser.nickname || boundUser.username || boundUser.phone || '--' }}({{ boundUser.userId }})</span>
<n-button text type="error" @click="clearBoundUser">清除</n-button>
</div>
</n-form-item>
<n-form-item label="到期时间" path="expiresAt">
<n-date-picker v-model:value="formValue.expiresAt" type="datetime" clearable />
</n-form-item>
<n-form-item label="备注" path="remark">
<n-input v-model:value="formValue.remark" type="textarea" maxlength="255" show-count />
</n-form-item>
</n-form>
<template #action>
<n-space justify="end">
<n-button @click="closeModal">取消</n-button>
<n-button type="primary" :loading="loading" @click="submit">确认</n-button>
</n-space>
</template>
</n-modal>
</template>
<script lang="ts" setup>
import { reactive, ref } from 'vue';
import { useMessage } from 'naive-ui';
import { BatchCreate, Create } from '@/api/im/registrationInvite';
import { List as UserList } from '@/api/imUser';
import { localRfc3339 } from './model';
import defaultAvatar from '@/assets/images/schoolboy.png';
const emit = defineEmits(['reload']);
const message = useMessage();
const showModal = ref(false);
const loading = ref(false);
const formRef = ref();
const tenantId = ref(0);
const isBatch = ref(false);
const bindUserInput = ref('');
const searching = ref(false);
const boundUser = ref<any>(null);
const formValue = reactive({
quantity: 100,
expiresAt: Date.now() + 7 * 24 * 3600 * 1000,
remark: '',
idempotencyKey: '',
boundUserId: '',
});
const rules = {
quantity: { required: true, type: 'number', min: 1, max: 1000, message: '数量必须在1到1000之间' },
expiresAt: { required: true, type: 'number', message: '请选择到期时间' },
};
function openModal(id: number, batch = false) {
tenantId.value = id;
isBatch.value = batch;
formValue.quantity = 100;
formValue.expiresAt = Date.now() + 7 * 24 * 3600 * 1000;
formValue.remark = '';
formValue.idempotencyKey = crypto.randomUUID();
formValue.boundUserId = '';
bindUserInput.value = '';
boundUser.value = null;
showModal.value = true;
}
function closeModal() {
showModal.value = false;
}
async function searchUser() {
const keyword = bindUserInput.value.trim();
if (!keyword) {
message.warning('请输入用户ID/编号');
return;
}
searching.value = true;
try {
const res = await UserList({
tenantId: tenantId.value,
userId: keyword,
keyword: '',
page: 1,
perPage: 1,
});
const user = res?.list?.[0];
if (!user) {
message.warning('未找到该用户,将不绑定用户');
boundUser.value = null;
formValue.boundUserId = '';
return;
}
boundUser.value = user;
// 雪花ID超出 JS Number 安全整数范围,必须保持字符串传输,避免精度丢失
formValue.boundUserId = String(user.userId || '');
message.success('已选中用户');
} catch (error: any) {
message.error(error?.toString() || '用户查询失败');
boundUser.value = null;
formValue.boundUserId = '';
} finally {
searching.value = false;
}
}
function clearBoundUser() {
boundUser.value = null;
formValue.boundUserId = '';
bindUserInput.value = '';
}
async function submit() {
await formRef.value?.validate();
loading.value = true;
try {
const params: any = {
tenantId: tenantId.value,
expiresAt: localRfc3339(formValue.expiresAt),
remark: formValue.remark,
idempotencyKey: formValue.idempotencyKey,
};
if (isBatch.value) {
if (formValue.boundUserId) {
params.userId = formValue.boundUserId;
}
await BatchCreate({ ...params, quantity: formValue.quantity });
} else {
await Create(params);
}
message.success('生成成功');
closeModal();
emit('reload');
} catch (error: any) {
message.error(error.toString());
} finally {
loading.value = false;
}
}
defineExpose({ openModal });
</script>
<style lang="less" scoped>
.bind-user-row {
display: flex;
gap: 8px;
width: 100%;
}
.bound-user-tag {
display: flex;
align-items: center;
gap: 8px;
margin-top: 8px;
}
.bound-user-name {
flex: 1;
}
</style>