Browse Source

Merge pull request #1324 from colinin/wechat-bind

feat(vben5-wechat): Add an integrated module for wechat
pull/1327/head
yx lin 5 months ago
committed by GitHub
parent
commit
a8f7426870
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      apps/vben5/apps/app-antd/package.json
  2. 4
      apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json
  3. 4
      apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json
  4. 89
      apps/vben5/apps/app-antd/src/views/account/my-settings/index.vue
  5. 1
      apps/vben5/packages/@abp/account/src/api/index.ts
  6. 58
      apps/vben5/packages/@abp/account/src/api/useExternalLoginsApi.ts
  7. 12
      apps/vben5/packages/@abp/account/src/components/MySetting.vue
  8. 38
      apps/vben5/packages/@abp/account/src/components/components/BindSettings.vue
  9. 9
      apps/vben5/packages/@abp/account/src/components/components/SecuritySettings.vue
  10. 17
      apps/vben5/packages/@abp/account/src/types/bind.ts
  11. 32
      apps/vben5/packages/@abp/account/src/types/external-logins.ts
  12. 2
      apps/vben5/packages/@abp/account/src/types/index.ts
  13. 1
      apps/vben5/packages/@abp/core/src/utils/index.ts
  14. 42
      apps/vben5/packages/@abp/core/src/utils/uuid.ts
  15. 1
      apps/vben5/packages/@abp/settings/src/components/index.ts
  16. 40
      apps/vben5/packages/@abp/wechat/package.json
  17. 2
      apps/vben5/packages/@abp/wechat/src/api/index.ts
  18. 40
      apps/vben5/packages/@abp/wechat/src/api/useWechatSettingsApi.ts
  19. 22
      apps/vben5/packages/@abp/wechat/src/api/userWorkWeixinJsSdkApi.ts
  20. 62
      apps/vben5/packages/@abp/wechat/src/components/bind-user/index.vue
  21. 2
      apps/vben5/packages/@abp/wechat/src/components/index.ts
  22. 37
      apps/vben5/packages/@abp/wechat/src/components/settings/index.vue
  23. 2
      apps/vben5/packages/@abp/wechat/src/index.ts
  24. 6
      apps/vben5/packages/@abp/wechat/src/types/js-sdk.ts
  25. 6
      apps/vben5/packages/@abp/wechat/tsconfig.json
  26. 1
      apps/vben5/pnpm-workspace.yaml

1
apps/vben5/apps/app-antd/package.json

@ -46,6 +46,7 @@
"@abp/text-templating": "workspace:*",
"@abp/ui": "workspace:*",
"@abp/webhooks": "workspace:*",
"@abp/wechat": "workspace:*",
"@vben-core/shadcn-ui": "workspace:*",
"@vben/access": "workspace:*",
"@vben/common-ui": "workspace:*",

4
apps/vben5/apps/app-antd/src/locales/langs/en-US/abp.json

@ -142,5 +142,9 @@
"title": "Object storage",
"containers": "Containers",
"objects": "Files"
},
"wechat": {
"title": "WeChat",
"settings": "Settings"
}
}

4
apps/vben5/apps/app-antd/src/locales/langs/zh-CN/abp.json

@ -142,5 +142,9 @@
"title": "对象存储",
"containers": "容器管理",
"objects": "文件管理"
},
"wechat": {
"title": "微信集成",
"settings": "微信设置"
}
}

89
apps/vben5/apps/app-antd/src/views/account/my-settings/index.vue

@ -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
apps/vben5/packages/@abp/account/src/api/index.ts

@ -1,4 +1,5 @@
export { useAccountApi } from './useAccountApi';
export { useExternalLoginsApi } from './useExternalLoginsApi';
export { useMySessionApi } from './useMySessionApi';
export { useProfileApi } from './useProfileApi';
export { useScanQrCodeApi } from './useScanQrCodeApi';

58
apps/vben5/packages/@abp/account/src/api/useExternalLoginsApi.ts

@ -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,
};
}

12
apps/vben5/packages/@abp/account/src/components/MySetting.vue

@ -1,4 +1,5 @@
<script setup lang="ts">
import type { BindItem } from '../types/bind';
import type { ProfileDto, UpdateProfileDto } from '../types/profile';
import type { UserInfo } from '../types/user';
@ -17,8 +18,8 @@ interface MenuItem {
key: string;
label: string;
}
const props = defineProps<{
bindItems?: BindItem[];
disableAuthenticator?: boolean;
disableBind?: boolean;
disableNotice?: boolean;
@ -26,6 +27,9 @@ const props = defineProps<{
disableSecurity?: boolean;
disableSession?: boolean;
}>();
const emits = defineEmits<{
(event: 'onBindInit'): void;
}>();
const AuthenticatorSettings = defineAsyncComponent(
() => import('./components/AuthenticatorSettings.vue'),
);
@ -191,7 +195,11 @@ onMounted(async () => {
@submit="onUpdateProfile"
@picture-change="onGetProfile"
/>
<BindSettings v-else-if="selectedMenuKeys[0] === 'bind'" />
<BindSettings
v-else-if="selectedMenuKeys[0] === 'bind'"
:items="bindItems"
@on-init="emits('onBindInit')"
/>
<SecuritySettings
v-else-if="selectedMenuKeys[0] === 'security'"
:user-info="getUserInfo"

38
apps/vben5/packages/@abp/account/src/components/components/BindSettings.vue

@ -1,10 +1,44 @@
<script setup lang="ts">
import { Card, Empty } from 'ant-design-vue';
import type { BindItem } from '../../types/bind';
import { onMounted } from 'vue';
import { Button, Card, Empty, List } from 'ant-design-vue';
defineProps<{
items?: BindItem[];
}>();
const emits = defineEmits<{
(event: 'onInit'): void;
}>();
const ListItem = List.Item;
const ListItemMeta = List.Item.Meta;
onMounted(() => {
setTimeout(() => {
emits('onInit');
}, 200);
});
</script>
<template>
<Card :bordered="false" :title="$t('abp.account.settings.bindSettings')">
<Empty />
<Empty v-if="items?.length === 0" />
<List v-else item-layout="horizontal">
<ListItem v-for="item in items" :key="item.title">
<template v-if="item.buttons?.length" #extra>
<Button
v-for="button in item.buttons"
:type="button.type"
@click="button.click"
:key="button.title"
>
{{ button.title }}
</Button>
</template>
<ListItemMeta :description="item.description" :title="item.title" />
</ListItem>
</List>
</Card>
</template>

9
apps/vben5/packages/@abp/account/src/components/components/SecuritySettings.vue

@ -86,13 +86,8 @@ onMounted(onGet);
</template>
<ListItemMeta
:description="$t('abp.account.settings.security.passwordDesc')"
>
<template #title>
<a href="https://www.antdv.com/">{{
$t('abp.account.settings.security.password')
}}</a>
</template>
</ListItemMeta>
:title="$t('abp.account.settings.security.password')"
/>
</ListItem>
<!-- 手机号码 -->
<ListItem>

17
apps/vben5/packages/@abp/account/src/types/bind.ts

@ -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 };

32
apps/vben5/packages/@abp/account/src/types/external-logins.ts

@ -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,
};

2
apps/vben5/packages/@abp/account/src/types/index.ts

@ -1,4 +1,6 @@
export * from './account';
export * from './bind';
export * from './external-logins';
export * from './profile';
export * from './token';
export * from './user';

1
apps/vben5/packages/@abp/core/src/utils/index.ts

@ -5,3 +5,4 @@ export * from './mitt';
export * from './regex';
export * from './string';
export * from './tree';
export * from './uuid';

42
apps/vben5/packages/@abp/core/src/utils/uuid.ts

@ -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
apps/vben5/packages/@abp/settings/src/components/index.ts

@ -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';

40
apps/vben5/packages/@abp/wechat/package.json

@ -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:*"
}
}

2
apps/vben5/packages/@abp/wechat/src/api/index.ts

@ -0,0 +1,2 @@
export { userWorkWeixinJsSdkApi } from './userWorkWeixinJsSdkApi';
export { useWechatSettingsApi } from './useWechatSettingsApi';

40
apps/vben5/packages/@abp/wechat/src/api/useWechatSettingsApi.ts

@ -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,
};
}

22
apps/vben5/packages/@abp/wechat/src/api/userWorkWeixinJsSdkApi.ts

@ -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,
};
}

62
apps/vben5/packages/@abp/wechat/src/components/bind-user/index.vue

@ -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>

2
apps/vben5/packages/@abp/wechat/src/components/index.ts

@ -0,0 +1,2 @@
export { default as WechatWorkUserBinder } from './bind-user/index.vue';
export { default as WechatSettings } from './settings/index.vue';

37
apps/vben5/packages/@abp/wechat/src/components/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>

2
apps/vben5/packages/@abp/wechat/src/index.ts

@ -0,0 +1,2 @@
export * from './api';
export * from './components';

6
apps/vben5/packages/@abp/wechat/src/types/js-sdk.ts

@ -0,0 +1,6 @@
interface AgentConfigDto {
agentId: string;
corpId: string;
}
export type { AgentConfigDto };

6
apps/vben5/packages/@abp/wechat/tsconfig.json

@ -0,0 +1,6 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "@vben/tsconfig/web.json",
"include": ["src"],
"exclude": ["node_modules"]
}

1
apps/vben5/pnpm-workspace.yaml

@ -73,6 +73,7 @@ catalog:
'@vueuse/core': ^13.1.0
'@vueuse/integrations': ^13.1.0
'@vueuse/motion': ^3.0.3
'@wecom/jssdk': ^2.3.1
ant-design-vue: ^4.2.6
archiver: ^7.0.1
autoprefixer: ^10.4.21

Loading…
Cancel
Save