1. 优化IM用户列表搜索逻辑,支持长数字用户ID精确匹配 2. 修复前端雪花ID精度丢失问题,改用字符串传输用户ID 3. 新增邀请码批量绑定用户功能,支持搜索绑定指定用户 4. 新增字典配置接口,新增联系我们字典SQL脚本 5. 完善字典数据变更事件推送,优化弹窗关闭与页面刷新逻辑
179 lines
5.4 KiB
Vue
179 lines
5.4 KiB
Vue
<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>
|
||
|