feat: 新增IM联系我们字典配置,优化用户搜索与邀请绑定功能

1. 优化IM用户列表搜索逻辑,支持长数字用户ID精确匹配
2. 修复前端雪花ID精度丢失问题,改用字符串传输用户ID
3. 新增邀请码批量绑定用户功能,支持搜索绑定指定用户
4. 新增字典配置接口,新增联系我们字典SQL脚本
5. 完善字典数据变更事件推送,优化弹窗关闭与页面刷新逻辑
This commit is contained in:
Austin
2026-09-21 13:11:08 +08:00
parent 0381984939
commit b4174a6412
12 changed files with 215 additions and 10 deletions
@@ -132,7 +132,8 @@
}
submitting.value = true;
try {
await BindUser({ tenantId: tenantId.value, id: inviteId.value, userId: Number(selectedUserId.value) });
// 雪花ID超出 JS Number 安全整数范围,保持字符串传输避免精度丢失
await BindUser({ tenantId: tenantId.value, id: inviteId.value, userId: selectedUserId.value });
message.success('绑定成功');
showModal.value = false;
emit('reload');
@@ -4,6 +4,23 @@
<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>
@@ -24,7 +41,9 @@
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();
@@ -33,11 +52,15 @@
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 = {
@@ -52,6 +75,9 @@
formValue.expiresAt = Date.now() + 7 * 24 * 3600 * 1000;
formValue.remark = '';
formValue.idempotencyKey = crypto.randomUUID();
formValue.boundUserId = '';
bindUserInput.value = '';
boundUser.value = null;
showModal.value = true;
}
@@ -59,17 +85,61 @@
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 = {
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);
@@ -87,3 +157,22 @@
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>
+2
View File
@@ -515,6 +515,8 @@
windowMinutes: lockoutForm.value.windowMinutes,
});
message.success('已保存,配置即时生效');
policyVisible.value = false;
reloadAll();
} catch (e: any) {
message.error(e.message || '保存失败');
} finally {