@peng_kai/kit 0.2.5 → 0.2.6-base.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,31 @@
1
+ import { defineAsyncComponent } from 'vue';
2
+ import { useAntdModal } from '../../../antd/hooks/useAntdModal';
3
+
4
+ export { default as Setting2FA, setDeps as setSetting2FADeps } from './src/Setting2FA.vue';
5
+ export { default as Verify2FA, setDeps as setVerify2FADeps } from './src/Verify2FA.vue';
6
+
7
+ export function createSetting2FAModal() {
8
+ return useAntdModal(
9
+ { type: 'modal', is: defineAsyncComponent(() => import('./src/Setting2FA.vue')) },
10
+ { width: 550, footer: null },
11
+ );
12
+ }
13
+
14
+ export function createVerify2FAModal() {
15
+ const verify2FAModal = useAntdModal(
16
+ defineAsyncComponent(() => import('./src/Verify2FA.vue')),
17
+ { title: '双重身份验证', width: 350, footer: null },
18
+ );
19
+
20
+ return {
21
+ PresetComponent: verify2FAModal.PresetComponent,
22
+ open: (requirePassword = false) => {
23
+ return new Promise<{ password: string, security_code: string }>((resolve, reject) => {
24
+ verify2FAModal.open(
25
+ { onConfirm: resolve, requirePassword },
26
+ { onCancel: reject },
27
+ );
28
+ });
29
+ },
30
+ };
31
+ }
@@ -0,0 +1,148 @@
1
+ <script lang="ts">
2
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query';
3
+ import { computed, nextTick } from 'vue';
4
+ import {
5
+ Button as AButton,
6
+ Card as ACard,
7
+ Form as AForm,
8
+ FormItem as AFormItem,
9
+ Input as AInput,
10
+ InputPassword as AInputPassword,
11
+ QRCode as AQrcode,
12
+ } from 'ant-design-vue';
13
+ import { useAntdForm } from '../../../../antd/hooks/useAntdForm';
14
+
15
+ export { setDeps };
16
+
17
+ interface IDeps {
18
+ adminInfoApi: Api.Request
19
+ qrCodeApi: Api.Request
20
+ authSwitchApi: Api.Request
21
+ encryptPassword?: (pwd: string) => string
22
+ }
23
+
24
+ let _deps = {} as Required<IDeps>;
25
+
26
+ function setDeps(deps: IDeps) {
27
+ _deps = {
28
+ encryptPassword: pwd => pwd,
29
+ ...deps,
30
+ };
31
+ }
32
+ </script>
33
+
34
+ <script setup lang="ts">
35
+ const emits = defineEmits<{
36
+ close: []
37
+ }>();
38
+
39
+ const queryClient = useQueryClient();
40
+ const userInfoQry = useQuery({
41
+ queryKey: [_deps.adminInfoApi.id],
42
+ queryFn: () => _deps.adminInfoApi(undefined),
43
+ });
44
+ const enableAuth = computed(() => userInfoQry.data.value?.enable_authenticator);
45
+ const authQRcodeQry = useQuery({
46
+ enabled: computed(() => enableAuth.value === 0),
47
+ queryKey: [_deps.qrCodeApi.id],
48
+ queryFn: () => _deps.qrCodeApi(undefined),
49
+ gcTime: 0,
50
+ });
51
+ const switch2FAMut = useMutation({
52
+ mutationKey: [_deps.authSwitchApi.id],
53
+ mutationFn: _deps.authSwitchApi.setDefaultConfig({ successMessage: true }),
54
+ onSuccess() {
55
+ queryClient.invalidateQueries({
56
+ queryKey: [_deps.adminInfoApi.id],
57
+ exact: false,
58
+ });
59
+ form.$form.resetFields?.();
60
+ },
61
+ });
62
+ const title = computed(() => {
63
+ const _enableAuth = enableAuth.value;
64
+ const title = '双重认证';
65
+
66
+ return _enableAuth === 1 ? `解绑${title}` : _enableAuth === 0 ? `绑定${title}` : title;
67
+ });
68
+
69
+ const form = useAntdForm({
70
+ security_code: {
71
+ value: '',
72
+ rules: [
73
+ { required: true, type: 'string', len: 6, message: '请输入6位数字的安全码' },
74
+ ],
75
+ },
76
+ password: {
77
+ value: '',
78
+ rules: [
79
+ { required: true },
80
+ ],
81
+ },
82
+ });
83
+
84
+ async function onSubmit() {
85
+ await nextTick();
86
+ const state = await form.$form.validate?.()?.catch(() => {});
87
+
88
+ if (!state)
89
+ return;
90
+
91
+ const _enableAuth = enableAuth.value === 1 ? 0 : 1;
92
+ await switch2FAMut.mutateAsync({ requestBody: {
93
+ password: _deps.encryptPassword(state.password),
94
+ security_code: state.security_code,
95
+ enable_authenticator: _enableAuth,
96
+ } });
97
+
98
+ if (_enableAuth)
99
+ emits('close');
100
+ }
101
+ </script>
102
+
103
+ <template>
104
+ <ACard class="antd-cover__card-in-modal bg-$antd-colorBgElevated" :loading="userInfoQry.isLoading.value" :title="title">
105
+ <template #extra>
106
+ <div class="close-btn" @click="emits('close')" />
107
+ </template>
108
+ <div class="flex">
109
+ <AQrcode
110
+ v-if="enableAuth === 0"
111
+ :status="authQRcodeQry.data.value ? 'active' : 'loading'"
112
+ :size="207"
113
+ :value="authQRcodeQry.data.value?.qr_code ?? ''"
114
+ style="flex: none"
115
+ />
116
+
117
+ <AForm v-bind="form.props" class="ml4 flex-1" layout="vertical" @submit="onSubmit()">
118
+ <AFormItem v-bind="form.itemProps.password" label="账户密码">
119
+ <AInputPassword v-model:value="form.state.password" size="large">
120
+ <template #prefix>
121
+ <i class="i-ant-design:lock-outlined" />
122
+ </template>
123
+ </AInputPassword>
124
+ </AFormItem>
125
+ <AFormItem v-bind="form.itemProps.security_code" label="安全码">
126
+ <AInput v-model:value="form.state.security_code" size="large">
127
+ <template #prefix>
128
+ <i class="i-ant-design:safety-outlined" />
129
+ </template>
130
+ </AInput>
131
+ <template #extra>
132
+ <span v-if="enableAuth">请使用身份验证器APP,获取6位数字安全码</span>
133
+ <span v-else>请使用身份验证器APP扫描左侧二维码,获取6位数字安全码</span>
134
+ </template>
135
+ </AFormItem>
136
+ <button class="hidden" type="submit">
137
+ 提交
138
+ </button>
139
+ </AForm>
140
+ </div>
141
+
142
+ <template v-if="userInfoQry.isSuccess.value" #actions>
143
+ <AButton type="primary" @click="onSubmit">
144
+ {{ enableAuth ? '解绑' : '绑定' }}
145
+ </AButton>
146
+ </template>
147
+ </ACard>
148
+ </template>
@@ -0,0 +1,97 @@
1
+ <script lang="ts">
2
+ import { nextTick } from 'vue';
3
+ import {
4
+ Button as AButton,
5
+ Form as AForm,
6
+ FormItem as
7
+ AFormItem,
8
+ Input as AInput,
9
+ InputPassword as AInputPassword,
10
+ } from 'ant-design-vue';
11
+ import { hasToken } from '../../../../utils';
12
+ import { useAntdForm } from '../../../../antd/hooks/useAntdForm';
13
+
14
+ export { setDeps };
15
+
16
+ interface IDeps {
17
+ setting2FA: any
18
+ encryptPassword?: (pwd: string) => string
19
+ }
20
+
21
+ let _deps = {} as Required<IDeps>;
22
+
23
+ function setDeps(deps: IDeps) {
24
+ _deps = {
25
+ encryptPassword: pwd => pwd,
26
+ ...deps,
27
+ };
28
+ }
29
+ </script>
30
+
31
+ <script setup lang="ts">
32
+ const props = withDefaults(defineProps<{
33
+ requirePassword?: boolean
34
+ }>(), {
35
+ requirePassword: false,
36
+ });
37
+
38
+ const emits = defineEmits<{
39
+ confirm: [{ password: string, security_code: string }]
40
+ close: []
41
+ }>();
42
+
43
+ const form = useAntdForm({
44
+ password: {
45
+ value: '',
46
+ rules: [
47
+ { required: true },
48
+ ],
49
+ },
50
+ security_code: {
51
+ value: '',
52
+ rules: [
53
+ { required: true, type: 'string', len: 6, message: '请输入6位数字的安全码' },
54
+ ],
55
+ },
56
+ });
57
+
58
+ async function onSubmit() {
59
+ await nextTick();
60
+ const state = await form.$form.validate?.().catch(err => console.log(err));
61
+
62
+ if (state) {
63
+ emits('confirm', { ...form.state, password: _deps.encryptPassword(state.password) });
64
+ emits('close');
65
+ }
66
+ }
67
+ </script>
68
+
69
+ <template>
70
+ <div>
71
+ <AForm v-bind="form.props" @submit="onSubmit()">
72
+ <AFormItem v-if="props.requirePassword" v-bind="form.itemProps.password">
73
+ <AInputPassword v-model:value="form.state.password" size="large" placeholder="请输入账户密码">
74
+ <template #prefix>
75
+ <i class="i-ant-design:lock-outlined" />
76
+ </template>
77
+ </AInputPassword>
78
+ </AFormItem>
79
+ <AFormItem v-bind="form.itemProps.security_code">
80
+ <AInput v-model:value="form.state.security_code" size="large" placeholder="请输入安全码">
81
+ <template #prefix>
82
+ <i class="i-ant-design:safety-outlined" />
83
+ </template>
84
+ </AInput>
85
+ <template #extra>
86
+ <span>请输入身份验证器上显示的6位数字安全码</span>
87
+ </template>
88
+ </AFormItem>
89
+ <AButton type="primary" size="large" block htmlType="submit">
90
+ 验证
91
+ </AButton>
92
+ <AButton v-if="hasToken()" class="mt2 -mb2" type="link" block @click="_deps.setting2FA?.open?.()">
93
+ 没有安全码?去绑定
94
+ </AButton>
95
+ </AForm>
96
+ </div>
97
+ </template>
@@ -2,15 +2,14 @@ import { usePrevious } from '@vueuse/core';
2
2
  import type { RouteLocationNormalizedLoaded } from 'vue-router';
3
3
  import { defineStore } from 'pinia';
4
4
  import { computed, reactive, readonly, ref, shallowRef, watch } from 'vue';
5
- import type { VNode } from 'vue';
5
+ import type { DeepReadonly, VNode } from 'vue';
6
6
  import { getTitle } from '../defines/route';
7
7
  import { adminPlugin } from '../adminPlugin';
8
8
  import type { TMenu } from './createUseMenuStore';
9
9
 
10
10
  export { createUsePageStore };
11
- export type { TPageState, TUsePageStore, IBreadcrumb };
11
+ export type { TUsePageStore, IBreadcrumb, IPageState };
12
12
 
13
- type TPageState = ReturnType<typeof getPageState>;
14
13
  type TUsePageStore = ReturnType<typeof createUsePageStore>;
15
14
 
16
15
  interface IBreadcrumb {
@@ -29,7 +28,7 @@ function storeSetup() {
29
28
  const currentPageNode = shallowRef<VNode>();
30
29
  const currentPageKey = ref('');
31
30
  const pageCacheList = reactive<string[]>([]);
32
- const pageStateMap = new Map<string, TPageState>();
31
+ const pageStateMap = new Map<string, IPageState>();
33
32
  const currentPageState = computed(() => pageStateMap.get(currentPageKey.value));
34
33
  const previousPageState = usePrevious(currentPageState);
35
34
 
@@ -103,7 +102,14 @@ function storeSetup() {
103
102
  };
104
103
  }
105
104
 
106
- function getPageState(route: RouteLocationNormalizedLoaded) {
105
+ interface IPageState {
106
+ title: string
107
+ icon?: VNode
108
+ breadcrumbs: IBreadcrumb[]
109
+ readonly route: DeepReadonly<RouteLocationNormalizedLoaded>
110
+ }
111
+
112
+ function getPageState(route: RouteLocationNormalizedLoaded): IPageState {
107
113
  const menuStore = adminPlugin.deps.useMenuStore()!;
108
114
  const appName = adminPlugin.meta.appName;
109
115
  const menuPath = menuStore.getMenuPath(route.name as string);
@@ -1,11 +1,14 @@
1
1
  import { defineStore, storeToRefs } from 'pinia';
2
- import { computed, ref, watch } from 'vue';
2
+ import { type ComputedRef, type Ref, computed, ref, watch } from 'vue';
3
+ import type { Simplify } from 'type-fest';
3
4
  import { adminPlugin } from '../adminPlugin';
5
+ import type { IPageState } from './createUsePageStore';
4
6
 
5
7
  export { createUsePageTabStore };
6
8
  export type { TUsePageTabStore };
7
9
 
8
10
  type TUsePageTabStore = ReturnType<typeof createUsePageTabStore>;
11
+ type TTab = Simplify<Pick<IPageState, 'title' | 'icon'> & { key: string }>;
9
12
 
10
13
  function createUsePageTabStore() {
11
14
  return defineStore('appPageTab', () => storeSetup());
@@ -13,14 +16,16 @@ function createUsePageTabStore() {
13
16
 
14
17
  function storeSetup() {
15
18
  const pageStore = adminPlugin.deps.usePageStore!();
16
- const { currentPageKey } = storeToRefs(pageStore);
19
+ const pageRefs = storeToRefs(pageStore);
20
+ const activeTab: Ref<string> = pageRefs.currentPageKey;
17
21
  const tabKeyList = ref<string[]>([]);
18
- const tabList = computed(() => tabKeyList.value.map((key) => {
22
+ const tabList: ComputedRef<TTab[]> = computed(() => tabKeyList.value.map((key) => {
19
23
  const state = pageStore.pageStateMap.get(key);
20
24
 
21
25
  return state ? { key, title: state.title, icon: state.icon } : undefined!;
22
26
  }).filter(tab => !!tab));
23
27
 
28
+ const openTab: (key: string) => void = pageStore.openPage;
24
29
  const closeTab = (key: string) => {
25
30
  const i = tabKeyList.value.indexOf(key);
26
31
 
@@ -30,14 +35,15 @@ function storeSetup() {
30
35
  }
31
36
  };
32
37
 
33
- watch(
34
- () => pageStore.currentPageKey,
35
- (key) => {
36
- if (!tabKeyList.value.includes(key))
37
- tabKeyList.value.push(key);
38
- },
39
- { immediate: true },
40
- );
38
+ watch(activeTab, (key) => {
39
+ if (!tabKeyList.value.includes(key))
40
+ tabKeyList.value.push(key);
41
+ }, { immediate: true });
41
42
 
42
- return { activeTab: currentPageKey, tabList, closeTab, openTab: pageStore.openPage };
43
+ return {
44
+ activeTab,
45
+ tabList,
46
+ closeTab,
47
+ openTab,
48
+ };
43
49
  }
@@ -3,6 +3,6 @@ export { createUseMenuStore } from './createUseMenuStore';
3
3
  export { createUsePageTabStore } from './createUsePageTabStore';
4
4
  export { createUsePermissionStore } from './createUsePermissionStore';
5
5
 
6
- export type { TPageState, TUsePageStore, IBreadcrumb } from './createUsePageStore';
6
+ export type { IPageState, TUsePageStore, IBreadcrumb } from './createUsePageStore';
7
7
  export type { TUseMenuStore } from './createUseMenuStore';
8
8
  export type { TUsePageTabStore } from './createUsePageTabStore';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@peng_kai/kit",
3
3
  "type": "module",
4
- "version": "0.2.5",
4
+ "version": "0.2.6-base.1",
5
5
  "description": "",
6
6
  "author": "",
7
7
  "license": "ISC",