mirror of
https://gitee.com/bootx/dax-pay-ui
synced 2026-08-15 01:36:20 +08:00
feat(admin): 用户管理新增三方账号绑定抽屉
- 新增 UserSocialBind.vue 抽屉组件(SocialLogo + 解绑, 无绑定按钮) - 新增 user-social.api.ts, 独立于 SocialApi - UserList.vue 更多菜单增加三方绑定入口(VIEW 权限) - 清理 social.api.ts 中残留的旧 admin 方法 - i18n: iam/user.json 补充 action.socialBind 和 social 段
This commit is contained in:
49
apps/daxpay-admin/src/api/iam/user-social.api.ts
Normal file
49
apps/daxpay-admin/src/api/iam/user-social.api.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import type { Result } from '#/types/web';
|
||||
|
||||
import { defHttp } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 社交账号绑定结果
|
||||
*/
|
||||
export interface SocialBindResult {
|
||||
/** 主键 */
|
||||
id?: string;
|
||||
/** 本地用户ID */
|
||||
userId?: string;
|
||||
/** 终端编码 */
|
||||
clientCode?: string;
|
||||
/** 平台编码 */
|
||||
source?: string;
|
||||
/** 平台用户唯一标识 */
|
||||
openId?: string;
|
||||
/** 平台昵称 */
|
||||
username?: string;
|
||||
/** 平台头像 */
|
||||
avatar?: string;
|
||||
/** 绑定时间 */
|
||||
createTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户三方账号绑定管理 API (管理员)
|
||||
*
|
||||
* 与登录/绑定流程的 SocialApi 完全分离, 归属用户管理域.
|
||||
*/
|
||||
export const UserSocialApi = {
|
||||
/**
|
||||
* 查询指定用户的第三方账号绑定列表
|
||||
* @param userId 目标用户ID
|
||||
*/
|
||||
bindList(userId: string): Promise<Result<SocialBindResult[]>> {
|
||||
return defHttp.get({ url: '/user/admin/social/bind-list', params: { userId } });
|
||||
},
|
||||
|
||||
/**
|
||||
* 解除指定用户的第三方账号绑定
|
||||
* @param userId 目标用户ID
|
||||
* @param source 平台编码
|
||||
*/
|
||||
unbind(userId: string, source: string): Promise<Result<void>> {
|
||||
return defHttp.post({ url: '/user/admin/social/unbind', params: { userId, source } });
|
||||
},
|
||||
};
|
||||
@@ -30,7 +30,17 @@
|
||||
"confirmAssignRole": "Are you sure you want to save the role assignment?",
|
||||
"confirmBan": "Are you sure to ban selected users?",
|
||||
"confirmUnlock": "Are you sure to unlock selected users?",
|
||||
"confirmResetPassword": "Are you sure to reset password for selected users?"
|
||||
"confirmResetPassword": "Are you sure to reset password for selected users?",
|
||||
"socialBind": "Social Bindings"
|
||||
},
|
||||
"social": {
|
||||
"title": "Social Account Bindings",
|
||||
"bound": "Bound",
|
||||
"unbound": "Not Bound",
|
||||
"unbindAction": "Unbind",
|
||||
"unbindConfirm": "Are you sure to unbind this user's {name} account?",
|
||||
"unbindSuccess": "Unbind successful",
|
||||
"noEnabled": "No enabled social platforms"
|
||||
},
|
||||
"password": {
|
||||
"password": "Password",
|
||||
|
||||
@@ -30,7 +30,17 @@
|
||||
"confirmAssignRole": "确定要保存角色分配吗?",
|
||||
"confirmBan": "确定要封禁选中的用户吗?",
|
||||
"confirmUnlock": "确定要解锁选中的用户吗?",
|
||||
"confirmResetPassword": "确定要重置选中用户的密码吗?"
|
||||
"confirmResetPassword": "确定要重置选中用户的密码吗?",
|
||||
"socialBind": "三方绑定"
|
||||
},
|
||||
"social": {
|
||||
"title": "三方账号绑定",
|
||||
"bound": "已绑定",
|
||||
"unbound": "未绑定",
|
||||
"unbindAction": "解绑",
|
||||
"unbindConfirm": "确定要解绑该用户的 {name} 账号吗?",
|
||||
"unbindSuccess": "解绑成功",
|
||||
"noEnabled": "暂无启用的第三方平台"
|
||||
},
|
||||
"password": {
|
||||
"password": "密码",
|
||||
|
||||
@@ -10,9 +10,9 @@
|
||||
|
||||
import { UserApi } from '#/api/iam/user.api';
|
||||
import { BQuery, type QueryField } from '#/components/query';
|
||||
import { PermCodes } from '#/constants/perm-codes';
|
||||
import { clientCodeColorMap, clientCodeI18nMap } from '#/enums/clientCode';
|
||||
import { useMessage } from '#/hooks/useMessage';
|
||||
import { PermCodes } from '#/constants/perm-codes';
|
||||
import { usePermission } from '#/hooks/usePermission';
|
||||
|
||||
import UserAdd from './components/UserAdd.vue';
|
||||
@@ -20,6 +20,7 @@
|
||||
import UserInfo from './components/UserInfo.vue';
|
||||
import UserResetPassword from './components/UserResetPassword.vue';
|
||||
import UserRoleAssign from './components/UserRoleAssign.vue';
|
||||
import UserSocialBind from './components/UserSocialBind.vue';
|
||||
|
||||
/**
|
||||
* Tab 配置项接口
|
||||
@@ -54,6 +55,7 @@
|
||||
const userInfoRef = ref();
|
||||
const userResetPasswordRef = ref();
|
||||
const userRoleAssignRef = ref();
|
||||
const userSocialBindRef = ref();
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false);
|
||||
@@ -207,6 +209,12 @@
|
||||
label: $t('iam.user.action.resetPassword'),
|
||||
disabled: !hasPermission(PermCodes.Iam.UserManager.RESET_PASSWORD),
|
||||
},
|
||||
// 三方绑定
|
||||
{
|
||||
key: 'socialBind',
|
||||
label: $t('iam.user.action.socialBind'),
|
||||
disabled: !hasPermission(PermCodes.Iam.UserManager.VIEW),
|
||||
},
|
||||
{ type: 'divider' },
|
||||
// 封禁
|
||||
{
|
||||
@@ -238,6 +246,11 @@
|
||||
|
||||
break;
|
||||
}
|
||||
case 'socialBind': {
|
||||
handleSocialBind(row);
|
||||
|
||||
break;
|
||||
}
|
||||
case 'unlock': {
|
||||
handleUnlock([row.id]);
|
||||
|
||||
@@ -305,6 +318,13 @@
|
||||
userRoleAssignRef.value?.show(row.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* 三方账号绑定
|
||||
*/
|
||||
function handleSocialBind(row: any) {
|
||||
userSocialBindRef.value?.show(row.id, row.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* 封禁
|
||||
*/
|
||||
@@ -529,6 +549,7 @@
|
||||
<UserInfo ref="userInfoRef" />
|
||||
<UserResetPassword ref="userResetPasswordRef" @ok="queryPage" />
|
||||
<UserRoleAssign ref="userRoleAssignRef" @ok="queryPage" />
|
||||
<UserSocialBind ref="userSocialBindRef" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
<script lang="ts" setup>
|
||||
import type { SocialBindResult } from '#/api/iam/user-social.api';
|
||||
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { SocialApi } from '#/api/iam/social.api';
|
||||
import { UserSocialApi } from '#/api/iam/user-social.api';
|
||||
import { SocialLogo } from '#/components/social';
|
||||
import { useMessage } from '#/hooks/useMessage';
|
||||
|
||||
defineOptions({ name: 'UserSocialBind' });
|
||||
|
||||
const { message, confirm } = useMessage();
|
||||
|
||||
/** 抽屉可见性 */
|
||||
const visible = ref(false);
|
||||
|
||||
/** 加载状态 */
|
||||
const loading = ref(false);
|
||||
|
||||
/** 当前用户ID */
|
||||
const userId = ref('');
|
||||
|
||||
/** 当前用户名(用于标题) */
|
||||
const userName = ref('');
|
||||
|
||||
/** 已启用的平台列表 */
|
||||
const enabledPlatforms = ref<{ source: string }[]>([]);
|
||||
|
||||
/** 目标用户的绑定列表 */
|
||||
const bindList = ref<SocialBindResult[]>([]);
|
||||
|
||||
/** 平台列表(合并启用 + 绑定状态) */
|
||||
const platformList = computed(() =>
|
||||
enabledPlatforms.value.map((p) => ({
|
||||
...p,
|
||||
name: $t(`iam.social.platform.${p.source}`),
|
||||
})),
|
||||
);
|
||||
|
||||
/** 抽屉标题 */
|
||||
const drawerTitle = computed(() =>
|
||||
userName.value ? `${$t('iam.user.social.title')} - ${userName.value}` : $t('iam.user.social.title'),
|
||||
);
|
||||
|
||||
/**
|
||||
* 查找平台是否已绑定
|
||||
*/
|
||||
function findBind(source: string) {
|
||||
return bindList.value.find((item) => item.source === source);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开抽屉
|
||||
* @param id 用户ID
|
||||
* @param name 用户名(用于标题显示)
|
||||
*/
|
||||
async function show(id: number | string, name?: string) {
|
||||
userId.value = String(id);
|
||||
userName.value = name || '';
|
||||
visible.value = true;
|
||||
await fetchData();
|
||||
}
|
||||
|
||||
/**
|
||||
* 拉取数据: 已启用平台 + 目标用户绑定列表
|
||||
*/
|
||||
async function fetchData() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const [{ data: platforms }, { data: binds }] = await Promise.all([
|
||||
SocialApi.enabledList(),
|
||||
UserSocialApi.bindList(userId.value),
|
||||
]);
|
||||
enabledPlatforms.value = platforms ?? [];
|
||||
bindList.value = binds ?? [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解绑第三方账号
|
||||
*/
|
||||
function handleUnbind(source: string, nickname?: string) {
|
||||
confirm({
|
||||
title: $t('common.confirm'),
|
||||
content: $t('iam.user.social.unbindConfirm', { name: nickname || source }),
|
||||
onOk: async () => {
|
||||
await UserSocialApi.unbind(userId.value, source);
|
||||
message.success($t('iam.user.social.unbindSuccess'));
|
||||
await fetchData();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
show,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<a-drawer v-model:open="visible" :title="drawerTitle" :size="640" :destroy-on-close="true">
|
||||
<a-spin :spinning="loading">
|
||||
<a-empty v-if="platformList.length === 0" :description="$t('iam.user.social.noEnabled')" />
|
||||
<div v-else class="space-y-3">
|
||||
<div
|
||||
v-for="platform in platformList"
|
||||
:key="platform.source"
|
||||
class="flex items-center justify-between border-b border-gray-100 py-3 last:border-0"
|
||||
>
|
||||
<div class="flex items-center gap-3">
|
||||
<SocialLogo :source="platform.source" :size="32" />
|
||||
<span class="text-base font-medium">{{ platform.name }}</span>
|
||||
<template v-if="findBind(platform.source)">
|
||||
<a-tag color="green">{{ $t('iam.user.social.bound') }}</a-tag>
|
||||
<span class="text-sm text-gray-500">
|
||||
{{ findBind(platform.source)?.username }}
|
||||
</span>
|
||||
</template>
|
||||
<a-tag v-else color="default">{{ $t('iam.user.social.unbound') }}</a-tag>
|
||||
</div>
|
||||
<div>
|
||||
<a-button
|
||||
v-if="findBind(platform.source)"
|
||||
danger
|
||||
size="small"
|
||||
@click="handleUnbind(platform.source, findBind(platform.source)?.username)"
|
||||
>
|
||||
{{ $t('iam.user.social.unbindAction') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
</a-drawer>
|
||||
</template>
|
||||
Reference in New Issue
Block a user