47fb8d1bd0
- 用户要求专区白名单入口交互与商品一致,改为右侧抽屉弹出 - UserSelectModal 组件外层由 a-modal 改为 a-drawer,保持内部逻辑不变 - 组件 props 和 emits 完全兼容,调用方式无需更改 - 前端重新构建即可,无需后端改动
189 lines
6.1 KiB
Vue
189 lines
6.1 KiB
Vue
<!-- 白名单用户管理抽屉(右侧弹出:打开只列已添加,搜索手机/姓名昵称追加,单条即时增删) -->
|
||
<template>
|
||
<a-drawer
|
||
:width="820"
|
||
:visible="visible"
|
||
:maskClosable="false"
|
||
title="专区白名单用户"
|
||
placement="right"
|
||
:body-style="{ paddingBottom: '16px' }"
|
||
@update:visible="updateVisible"
|
||
>
|
||
<!-- 搜索可添加的用户 -->
|
||
<div style="margin-bottom: 12px; display: flex; gap: 8px; align-items: center;">
|
||
<a-input-search
|
||
v-model:value="searchText"
|
||
placeholder="搜索手机号码 / 姓名 / 昵称"
|
||
allow-clear
|
||
style="flex: 1; max-width: 360px;"
|
||
@search="searchToAdd"
|
||
@pressEnter="searchToAdd"
|
||
/>
|
||
<a-button type="primary" :loading="searchLoading" @click="searchToAdd">
|
||
搜索
|
||
</a-button>
|
||
</div>
|
||
|
||
<!-- 搜索结果(点击 + 添加) -->
|
||
<div
|
||
v-if="candidates.length > 0"
|
||
style="margin-bottom: 12px; border: 1px dashed #d9d9d9; border-radius: 6px; padding: 8px;"
|
||
>
|
||
<div style="font-size: 12px; color: #999; margin-bottom: 6px;">搜索结果(点击 + 添加)</div>
|
||
<div
|
||
v-for="u in candidates"
|
||
:key="u.userId"
|
||
style="display: flex; align-items: center; padding: 4px 0; gap: 8px; border-bottom: 1px solid #f0f0f0;"
|
||
>
|
||
<span style="flex: 1; font-size: 13px;">{{ u.realName || '-' }}</span>
|
||
<span style="color: #999; font-size: 12px;">{{ u.mobile || '-' }}</span>
|
||
<span style="color: #999; font-size: 12px;">{{ u.nickname || '' }}</span>
|
||
<a-button type="link" size="small" style="padding: 0;" @click="addOne(u)">+ 添加</a-button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 已添加的白名单用户 -->
|
||
<a-divider style="margin: 8px 0;">已添加({{ addedUsers.length }})</a-divider>
|
||
<a-spin :spinning="addedLoading">
|
||
<a-table
|
||
v-if="addedUsers.length > 0"
|
||
row-key="userId"
|
||
:columns="columns"
|
||
:data-source="addedUsers"
|
||
:pagination="false"
|
||
:scroll="{ y: 320 }"
|
||
size="middle"
|
||
>
|
||
<template #bodyCell="{ column, record }">
|
||
<template v-if="column.key === 'action'">
|
||
<a-popconfirm title="确定移出该白名单用户吗?" @confirm="removeOne(record)">
|
||
<a-button type="link" danger size="small">移除</a-button>
|
||
</a-popconfirm>
|
||
</template>
|
||
</template>
|
||
</a-table>
|
||
<a-empty v-else description="暂无白名单用户,请用上方搜索添加" />
|
||
</a-spin>
|
||
</a-drawer>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { ref, watch } from 'vue';
|
||
import { message } from 'ant-design-vue';
|
||
import { listSectionUsers, addSectionUsers, removeSectionUsers } from '@/api/shop/shopZone';
|
||
import { pageUsers } from '@/api/system/user';
|
||
import type { User } from '@/api/system/user/model';
|
||
|
||
const props = defineProps<{
|
||
visible: boolean;
|
||
sectionId?: number;
|
||
}>();
|
||
|
||
const emit = defineEmits<{
|
||
(e: 'update:visible', visible: boolean): void;
|
||
}>();
|
||
|
||
const updateVisible = (value: boolean) => {
|
||
emit('update:visible', value);
|
||
};
|
||
|
||
// 已添加的白名单用户
|
||
const addedUsers = ref<User[]>([]);
|
||
const addedLoading = ref(false);
|
||
|
||
// 搜索结果(候选)
|
||
const searchText = ref('');
|
||
const candidates = ref<User[]>([]);
|
||
const searchLoading = ref(false);
|
||
|
||
const columns = [
|
||
{ title: 'ID', dataIndex: 'userId', key: 'userId', align: 'center', width: 80 },
|
||
{ title: '姓名', dataIndex: 'realName', key: 'realName', align: 'center' },
|
||
{ title: '手机号码', dataIndex: 'mobile', key: 'mobile', align: 'center' },
|
||
{ title: '昵称', dataIndex: 'nickname', key: 'nickname', align: 'center' },
|
||
{ title: '所属部门', dataIndex: 'organizationName', key: 'organizationName', align: 'center' },
|
||
{ title: '操作', key: 'action', align: 'center', width: 90 }
|
||
];
|
||
|
||
/* 加载已添加的白名单 */
|
||
const loadAdded = () => {
|
||
if (!props.sectionId) return;
|
||
addedLoading.value = true;
|
||
listSectionUsers(props.sectionId)
|
||
.then((list: User[]) => {
|
||
addedUsers.value = list || [];
|
||
})
|
||
.catch((e: Error) => {
|
||
message.error(e.message || '加载白名单失败');
|
||
})
|
||
.finally(() => {
|
||
addedLoading.value = false;
|
||
});
|
||
};
|
||
|
||
/* 搜索可添加的用户(排除已添加的) */
|
||
const searchToAdd = () => {
|
||
const kw = searchText.value.trim();
|
||
if (!kw) {
|
||
message.warning('请输入手机号码 / 姓名 / 昵称');
|
||
return;
|
||
}
|
||
searchLoading.value = true;
|
||
pageUsers({ keywords: kw, page: 1, limit: 20 } as any)
|
||
.then((res: any) => {
|
||
const addedIds = new Set(addedUsers.value.map((u) => u.userId));
|
||
candidates.value = (res?.list || []).filter((u: User) => !addedIds.has(u.userId));
|
||
if (candidates.value.length === 0) {
|
||
message.info('没有可添加的新用户');
|
||
}
|
||
})
|
||
.catch((e: Error) => {
|
||
message.error(e.message || '搜索失败');
|
||
})
|
||
.finally(() => {
|
||
searchLoading.value = false;
|
||
});
|
||
};
|
||
|
||
/* 添加单个用户到白名单 */
|
||
const addOne = (u: User) => {
|
||
if (!props.sectionId) return;
|
||
addSectionUsers(props.sectionId, [u.userId as number])
|
||
.then((msg: string) => {
|
||
message.success(msg || '添加成功');
|
||
candidates.value = candidates.value.filter((c) => c.userId !== u.userId);
|
||
loadAdded();
|
||
})
|
||
.catch((e: Error) => {
|
||
message.error(e.message || '添加失败');
|
||
});
|
||
};
|
||
|
||
/* 从白名单移除单个用户 */
|
||
const removeOne = (u: User) => {
|
||
if (!props.sectionId) return;
|
||
removeSectionUsers(props.sectionId, [u.userId as number])
|
||
.then((msg: string) => {
|
||
message.success(msg || '移除成功');
|
||
loadAdded();
|
||
})
|
||
.catch((e: Error) => {
|
||
message.error(e.message || '移除失败');
|
||
});
|
||
};
|
||
|
||
// 打开弹窗时加载已添加名单
|
||
watch(
|
||
() => props.visible,
|
||
(visible) => {
|
||
if (visible) {
|
||
searchText.value = '';
|
||
candidates.value = [];
|
||
loadAdded();
|
||
}
|
||
},
|
||
{ immediate: true }
|
||
);
|
||
</script>
|
||
<style lang="less"></style>
|