committed by
GitHub
26 changed files with 517 additions and 14 deletions
@ -1,15 +1,98 @@ |
|||
<script lang="ts" setup> |
|||
import { Page } from '@vben/common-ui'; |
|||
import type { BindItem } from '@abp/account'; |
|||
|
|||
import { MySetting } from '@abp/account'; |
|||
import { defineAsyncComponent, ref } from 'vue'; |
|||
|
|||
import { Page, useVbenModal } from '@vben/common-ui'; |
|||
import { useRefresh } from '@vben/hooks'; |
|||
import { $t } from '@vben/locales'; |
|||
|
|||
import { MySetting, useExternalLoginsApi } from '@abp/account'; |
|||
import { message, Modal } from 'ant-design-vue'; |
|||
|
|||
defineOptions({ |
|||
name: 'Vben5AccountMySettings', |
|||
}); |
|||
|
|||
const { bindWorkWeixinApi, getExternalLoginsApi, removeExternalLoginApi } = |
|||
useExternalLoginsApi(); |
|||
const { refresh } = useRefresh(); |
|||
|
|||
const [WechatWorkUserBindModal, weComBindModal] = useVbenModal({ |
|||
connectedComponent: defineAsyncComponent(async () => { |
|||
const component = await import('@abp/wechat'); |
|||
return component.WechatWorkUserBinder; |
|||
}), |
|||
}); |
|||
const externalLogins = ref<BindItem[]>([]); |
|||
|
|||
async function onBindWorkWeixin(code: string) { |
|||
weComBindModal.setState({ submitting: true }); |
|||
try { |
|||
await bindWorkWeixinApi({ code }); |
|||
weComBindModal.close(); |
|||
message.success($t('AbpAccount.BindSuccessfully')); |
|||
refresh(); |
|||
} finally { |
|||
weComBindModal.setState({ submitting: false }); |
|||
} |
|||
} |
|||
|
|||
async function onRemoveBind(provider: string, key: string) { |
|||
Modal.confirm({ |
|||
title: $t('AbpUi.AreYouSure'), |
|||
centered: true, |
|||
content: $t('AbpAccount.CancelBindWarningMessage'), |
|||
async onOk() { |
|||
await removeExternalLoginApi({ |
|||
loginProvider: provider, |
|||
providerKey: key, |
|||
}); |
|||
message.success($t('AbpAccount.CancelBindSuccessfully')); |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
function onBind(provider: string) { |
|||
switch (provider.toLocaleLowerCase()) { |
|||
case 'workweixin': { |
|||
weComBindModal.open(); |
|||
} |
|||
} |
|||
} |
|||
|
|||
async function onClick(provider: string, key?: string) { |
|||
if (key) { |
|||
await onRemoveBind(provider, key); |
|||
return; |
|||
} |
|||
onBind(provider); |
|||
} |
|||
|
|||
async function onInit() { |
|||
const res = await getExternalLoginsApi(); |
|||
externalLogins.value = res.externalLogins.map((item) => { |
|||
const userLogin = res.userLogins.find((x) => x.loginProvider === item.name); |
|||
return { |
|||
title: item.displayName, |
|||
description: userLogin?.providerKey ?? $t('AbpAccount.UnBind'), |
|||
buttons: [ |
|||
{ |
|||
title: userLogin?.providerKey |
|||
? $t('AbpAccount.CancelBind') |
|||
: $t('AbpAccount.Bind'), |
|||
type: 'link', |
|||
click: () => onClick(item.name, userLogin?.providerKey), |
|||
}, |
|||
], |
|||
}; |
|||
}); |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Page> |
|||
<MySetting /> |
|||
<MySetting :bind-items="externalLogins" @on-bind-init="onInit" /> |
|||
<WechatWorkUserBindModal @on-login="onBindWorkWeixin" /> |
|||
</Page> |
|||
</template> |
|||
|
|||
@ -1,4 +1,5 @@ |
|||
export { useAccountApi } from './useAccountApi'; |
|||
export { useExternalLoginsApi } from './useExternalLoginsApi'; |
|||
export { useMySessionApi } from './useMySessionApi'; |
|||
export { useProfileApi } from './useProfileApi'; |
|||
export { useScanQrCodeApi } from './useScanQrCodeApi'; |
|||
|
|||
@ -0,0 +1,58 @@ |
|||
import type { |
|||
ExternalLoginResultDto, |
|||
RemoveExternalLoginInput, |
|||
WorkWeixinLoginBindInput, |
|||
} from '../types/external-logins'; |
|||
|
|||
import { useRequest } from '@abp/request'; |
|||
|
|||
export function useExternalLoginsApi() { |
|||
const { cancel, request } = useRequest(); |
|||
|
|||
/** |
|||
* 绑定企业微信 |
|||
* @param input 绑定参数 |
|||
* @returns { Promise<void> } |
|||
*/ |
|||
async function bindWorkWeixinApi( |
|||
input: WorkWeixinLoginBindInput, |
|||
): Promise<void> { |
|||
return await request(`/api/account/oauth/work-weixin/bind`, { |
|||
method: 'POST', |
|||
data: input, |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 获取外部登录提供者列表 |
|||
* @returns 外部登录提供者列表 |
|||
*/ |
|||
async function getExternalLoginsApi(): Promise<ExternalLoginResultDto> { |
|||
return await request<ExternalLoginResultDto>( |
|||
`/api/account/external-logins`, |
|||
{ |
|||
method: 'GET', |
|||
}, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 移除外部登录提供者 |
|||
* @returns { Promise<void> } |
|||
*/ |
|||
async function removeExternalLoginApi( |
|||
input: RemoveExternalLoginInput, |
|||
): Promise<void> { |
|||
return await request(`/api/account/external-logins/remove`, { |
|||
method: 'DELETE', |
|||
params: input, |
|||
}); |
|||
} |
|||
|
|||
return { |
|||
cancel, |
|||
bindWorkWeixinApi, |
|||
getExternalLoginsApi, |
|||
removeExternalLoginApi, |
|||
}; |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
import type { ButtonType } from 'ant-design-vue/lib/button'; |
|||
|
|||
interface BindButton { |
|||
click: () => Promise<void> | void; |
|||
title: string; |
|||
type?: ButtonType; |
|||
} |
|||
|
|||
interface BindItem { |
|||
buttons?: BindButton[]; |
|||
description?: string; |
|||
enable?: boolean; |
|||
slot?: string; |
|||
title: string; |
|||
} |
|||
|
|||
export type { BindItem }; |
|||
@ -0,0 +1,32 @@ |
|||
interface UserLoginInfoDto { |
|||
loginProvider: string; |
|||
providerDisplayName: string; |
|||
providerKey: string; |
|||
} |
|||
|
|||
interface ExternalLoginInfoDto { |
|||
displayName: string; |
|||
name: string; |
|||
} |
|||
|
|||
interface WorkWeixinLoginBindInput { |
|||
code: string; |
|||
} |
|||
|
|||
interface ExternalLoginResultDto { |
|||
externalLogins: ExternalLoginInfoDto[]; |
|||
userLogins: UserLoginInfoDto[]; |
|||
} |
|||
|
|||
interface RemoveExternalLoginInput { |
|||
loginProvider: string; |
|||
providerKey: string; |
|||
} |
|||
|
|||
export type { |
|||
ExternalLoginInfoDto, |
|||
ExternalLoginResultDto, |
|||
RemoveExternalLoginInput, |
|||
UserLoginInfoDto, |
|||
WorkWeixinLoginBindInput, |
|||
}; |
|||
@ -1,4 +1,6 @@ |
|||
export * from './account'; |
|||
export * from './bind'; |
|||
export * from './external-logins'; |
|||
export * from './profile'; |
|||
export * from './token'; |
|||
export * from './user'; |
|||
|
|||
@ -0,0 +1,42 @@ |
|||
const hexList: string[] = []; |
|||
for (let i = 0; i <= 15; i++) { |
|||
hexList[i] = i.toString(16); |
|||
} |
|||
|
|||
export function buildUUID(): string { |
|||
let uuid = ''; |
|||
for (let i = 1; i <= 36; i++) { |
|||
switch (i) { |
|||
case 9: |
|||
case 14: |
|||
case 19: |
|||
case 24: { |
|||
uuid += '-'; |
|||
|
|||
break; |
|||
} |
|||
case 15: { |
|||
uuid += 4; |
|||
|
|||
break; |
|||
} |
|||
case 20: { |
|||
uuid += hexList[(Math.random() * 4) | 8]; |
|||
|
|||
break; |
|||
} |
|||
default: { |
|||
uuid += hexList[Math.trunc(Math.random() * 16)]; |
|||
} |
|||
} |
|||
} |
|||
return uuid.replaceAll('-', ''); |
|||
} |
|||
|
|||
let unique = 0; |
|||
export function buildShortUUID(prefix = ''): string { |
|||
const time = Date.now(); |
|||
const random = Math.floor(Math.random() * 1_000_000_000); |
|||
unique++; |
|||
return `${prefix}_${random}${unique}${String(time)}`; |
|||
} |
|||
@ -1,3 +1,4 @@ |
|||
export { default as SettingDefinitionTable } from './definitions/SettingDefinitionTable.vue'; |
|||
export { default as SettingForm } from './settings/SettingForm.vue'; |
|||
export { default as SystemSetting } from './settings/SystemSetting.vue'; |
|||
export { default as UserSetting } from './settings/UserSetting.vue'; |
|||
|
|||
@ -0,0 +1,40 @@ |
|||
{ |
|||
"name": "@abp/wechat", |
|||
"version": "9.2.0", |
|||
"homepage": "https://github.com/colinin/abp-next-admin", |
|||
"bugs": "https://github.com/colinin/abp-next-admin/issues", |
|||
"repository": { |
|||
"type": "git", |
|||
"url": "git+https://github.com/colinin/abp-next-admin.git", |
|||
"directory": "packages/@abp/wechat" |
|||
}, |
|||
"license": "MIT", |
|||
"type": "module", |
|||
"sideEffects": [ |
|||
"**/*.css" |
|||
], |
|||
"exports": { |
|||
".": { |
|||
"types": "./src/index.ts", |
|||
"default": "./src/index.ts" |
|||
} |
|||
}, |
|||
"dependencies": { |
|||
"@abp/core": "workspace:*", |
|||
"@abp/features": "workspace:*", |
|||
"@abp/request": "workspace:*", |
|||
"@abp/settings": "workspace:*", |
|||
"@abp/ui": "workspace:*", |
|||
"@ant-design/icons-vue": "catalog:", |
|||
"@vben/access": "workspace:*", |
|||
"@vben/common-ui": "workspace:*", |
|||
"@vben/hooks": "workspace:*", |
|||
"@vben/icons": "workspace:*", |
|||
"@vben/layouts": "workspace:*", |
|||
"@vben/locales": "workspace:*", |
|||
"@wecom/jssdk": "catalog:", |
|||
"ant-design-vue": "catalog:", |
|||
"dayjs": "catalog:", |
|||
"vue": "catalog:*" |
|||
} |
|||
} |
|||
@ -0,0 +1,2 @@ |
|||
export { userWorkWeixinJsSdkApi } from './userWorkWeixinJsSdkApi'; |
|||
export { useWechatSettingsApi } from './useWechatSettingsApi'; |
|||
@ -0,0 +1,40 @@ |
|||
import type { ListResultDto } from '@abp/core'; |
|||
import type { SettingGroup } from '@abp/settings'; |
|||
|
|||
import { useRequest } from '@abp/request'; |
|||
|
|||
export function useWechatSettingsApi() { |
|||
const { cancel, request } = useRequest(); |
|||
|
|||
/** |
|||
* 获取全局设置 |
|||
* @returns 设置数据传输对象列表 |
|||
*/ |
|||
function getGlobalSettingsApi(): Promise<ListResultDto<SettingGroup>> { |
|||
return request<ListResultDto<SettingGroup>>( |
|||
`/api/wechat/setting-management/by-global`, |
|||
{ |
|||
method: 'GET', |
|||
}, |
|||
); |
|||
} |
|||
|
|||
/** |
|||
* 获取租户设置 |
|||
* @returns 设置数据传输对象列表 |
|||
*/ |
|||
function getTenantSettingsApi(): Promise<ListResultDto<SettingGroup>> { |
|||
return request<ListResultDto<SettingGroup>>( |
|||
`/api/wechat/setting-management/by-current-tenant`, |
|||
{ |
|||
method: 'GET', |
|||
}, |
|||
); |
|||
} |
|||
|
|||
return { |
|||
cancel, |
|||
getGlobalSettingsApi, |
|||
getTenantSettingsApi, |
|||
}; |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
import type { AgentConfigDto } from '../types/js-sdk'; |
|||
|
|||
import { useRequest } from '@abp/request'; |
|||
|
|||
export function userWorkWeixinJsSdkApi() { |
|||
const { cancel, request } = useRequest(); |
|||
|
|||
/** |
|||
* 获取企业微信应用配置 |
|||
* @returns 企业微信应用配置Dto |
|||
*/ |
|||
function getAgentConfigApi(): Promise<AgentConfigDto> { |
|||
return request<AgentConfigDto>(`/api/wechat/work/jssdk/agent-config`, { |
|||
method: 'GET', |
|||
}); |
|||
} |
|||
|
|||
return { |
|||
cancel, |
|||
getAgentConfigApi, |
|||
}; |
|||
} |
|||
@ -0,0 +1,62 @@ |
|||
<script setup lang="ts"> |
|||
import { useTemplateRef } from 'vue'; |
|||
|
|||
import { useVbenModal } from '@vben/common-ui'; |
|||
|
|||
import { buildUUID } from '@abp/core'; |
|||
import { WWLoginRedirectType, WWLoginType } from '@wecom/jssdk'; |
|||
import * as ww from '@wecom/jssdk'; |
|||
|
|||
import { userWorkWeixinJsSdkApi } from '../../api/userWorkWeixinJsSdkApi'; |
|||
|
|||
const emits = defineEmits<{ |
|||
/** |
|||
* 用户扫码登录成功回调事件 |
|||
* @params code 企业微信授权码 |
|||
*/ |
|||
(event: 'onLogin', code: string): void; |
|||
}>(); |
|||
const wxLoginRef = useTemplateRef<Element>('wxLogin'); |
|||
|
|||
const { getAgentConfigApi } = userWorkWeixinJsSdkApi(); |
|||
|
|||
const [Modal, modalApi] = useVbenModal({ |
|||
onOpenChange(isOpen) { |
|||
if (isOpen) { |
|||
setTimeout(onInitLogin, 200); |
|||
} |
|||
}, |
|||
}); |
|||
|
|||
async function onInitLogin() { |
|||
try { |
|||
modalApi.setState({ loading: true }); |
|||
const agentConfig = await getAgentConfigApi(); |
|||
ww.createWWLoginPanel({ |
|||
el: wxLoginRef.value!, |
|||
params: { |
|||
login_type: WWLoginType.corpApp, |
|||
appid: agentConfig.corpId, |
|||
agentid: agentConfig.agentId, |
|||
// TODO: 是否应改为可配置式? 企业微信仅允许配置一个回调地址, 生产环境应配合反向代理服务器. |
|||
redirect_uri: window.location.href, |
|||
state: buildUUID(), |
|||
redirect_type: WWLoginRedirectType.callback, |
|||
}, |
|||
onLoginSuccess(res) { |
|||
emits('onLogin', res.code); |
|||
}, |
|||
}); |
|||
} finally { |
|||
modalApi.setState({ loading: false }); |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<Modal :title="$t('AbpAccountOAuth.OAuth:WorkWeixin')"> |
|||
<div ref="wxLogin"></div> |
|||
</Modal> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,2 @@ |
|||
export { default as WechatWorkUserBinder } from './bind-user/index.vue'; |
|||
export { default as WechatSettings } from './settings/index.vue'; |
|||
@ -0,0 +1,37 @@ |
|||
<script setup lang="ts"> |
|||
import type { SettingsUpdateInput } from '@abp/settings'; |
|||
|
|||
import { useAbpStore } from '@abp/core'; |
|||
import { SettingForm, useSettingsApi } from '@abp/settings'; |
|||
|
|||
import { useWechatSettingsApi } from '../../api/useWechatSettingsApi'; |
|||
|
|||
defineOptions({ |
|||
name: 'MaterialInspectSettings', |
|||
}); |
|||
|
|||
const abpStore = useAbpStore(); |
|||
const { getGlobalSettingsApi, getTenantSettingsApi } = useWechatSettingsApi(); |
|||
const { setGlobalSettingsApi, setTenantSettingsApi } = useSettingsApi(); |
|||
|
|||
async function onGet() { |
|||
const getSettingsApi = abpStore.application?.currentTenant.isAvailable |
|||
? getTenantSettingsApi |
|||
: getGlobalSettingsApi; |
|||
const { items } = await getSettingsApi(); |
|||
return items; |
|||
} |
|||
|
|||
async function onSubmit(input: SettingsUpdateInput) { |
|||
const setSettingsApi = abpStore.application?.currentTenant.isAvailable |
|||
? setTenantSettingsApi |
|||
: setGlobalSettingsApi; |
|||
await setSettingsApi(input); |
|||
} |
|||
</script> |
|||
|
|||
<template> |
|||
<SettingForm :get-api="onGet" :submit-api="onSubmit" /> |
|||
</template> |
|||
|
|||
<style scoped></style> |
|||
@ -0,0 +1,2 @@ |
|||
export * from './api'; |
|||
export * from './components'; |
|||
@ -0,0 +1,6 @@ |
|||
interface AgentConfigDto { |
|||
agentId: string; |
|||
corpId: string; |
|||
} |
|||
|
|||
export type { AgentConfigDto }; |
|||
@ -0,0 +1,6 @@ |
|||
{ |
|||
"$schema": "https://json.schemastore.org/tsconfig", |
|||
"extends": "@vben/tsconfig/web.json", |
|||
"include": ["src"], |
|||
"exclude": ["node_modules"] |
|||
} |
|||
Loading…
Reference in new issue