@peng_kai/kit 0.2.4 → 0.2.6-base.0

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,139 @@
1
+ <script lang="ts">
2
+ import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query';
3
+ import { computed, nextTick } from 'vue';
4
+ import { useAntdForm } from '../../../../antd/hooks/useAntdForm';
5
+
6
+ export { setDeps };
7
+
8
+ interface IDeps {
9
+ adminInfoApi: Api.Request
10
+ qrCodeApi: Api.Request
11
+ authSwitchApi: Api.Request
12
+ encryptPassword?: (pwd: string) => string
13
+ }
14
+
15
+ let _deps = {} as Required<IDeps>;
16
+
17
+ function setDeps(deps: IDeps) {
18
+ _deps = {
19
+ encryptPassword: pwd => pwd,
20
+ ...deps,
21
+ };
22
+ }
23
+ </script>
24
+
25
+ <script setup lang="ts">
26
+ const emits = defineEmits<{
27
+ close: []
28
+ }>();
29
+
30
+ const queryClient = useQueryClient();
31
+ const userInfoQry = useQuery({
32
+ queryKey: [_deps.adminInfoApi.id],
33
+ queryFn: () => _deps.adminInfoApi(undefined),
34
+ });
35
+ const enableAuth = computed(() => userInfoQry.data.value?.enable_authenticator);
36
+ const authQRcodeQry = useQuery({
37
+ enabled: computed(() => enableAuth.value === 0),
38
+ queryKey: [_deps.qrCodeApi.id],
39
+ queryFn: () => _deps.qrCodeApi(undefined),
40
+ gcTime: 0,
41
+ });
42
+ const switch2FAMut = useMutation({
43
+ mutationKey: [_deps.authSwitchApi.id],
44
+ mutationFn: _deps.authSwitchApi.setDefaultConfig({ successMessage: true }),
45
+ onSuccess() {
46
+ queryClient.invalidateQueries({
47
+ queryKey: [_deps.adminInfoApi.id],
48
+ exact: false,
49
+ });
50
+ form.$form.resetFields?.();
51
+ },
52
+ });
53
+ const title = computed(() => {
54
+ const _enableAuth = enableAuth.value;
55
+ const title = '双重认证';
56
+
57
+ return _enableAuth === 1 ? `解绑${title}` : _enableAuth === 0 ? `绑定${title}` : title;
58
+ });
59
+
60
+ const form = useAntdForm({
61
+ security_code: {
62
+ value: '',
63
+ rules: [
64
+ { required: true, type: 'string', len: 6, message: '请输入6位数字的安全码' },
65
+ ],
66
+ },
67
+ password: {
68
+ value: '',
69
+ rules: [
70
+ { required: true },
71
+ ],
72
+ },
73
+ });
74
+
75
+ async function onSubmit() {
76
+ await nextTick();
77
+ const state = await form.$form.validate?.()?.catch(() => {});
78
+
79
+ if (!state)
80
+ return;
81
+
82
+ const _enableAuth = enableAuth.value === 1 ? 0 : 1;
83
+ await switch2FAMut.mutateAsync({ requestBody: {
84
+ password: _deps.encryptPassword(state.password),
85
+ security_code: state.security_code,
86
+ enable_authenticator: _enableAuth,
87
+ } });
88
+
89
+ if (_enableAuth)
90
+ emits('close');
91
+ }
92
+ </script>
93
+
94
+ <template>
95
+ <ACard class="antd-cover__card-in-modal bg-$antd-colorBgElevated" :loading="userInfoQry.isLoading.value" :title="title">
96
+ <template #extra>
97
+ <div class="close-btn" @click="emits('close')" />
98
+ </template>
99
+ <div class="flex">
100
+ <AQrcode
101
+ v-if="enableAuth === 0"
102
+ :status="authQRcodeQry.data.value ? 'active' : 'loading'"
103
+ :size="207"
104
+ :value="authQRcodeQry.data.value?.qr_code ?? ''"
105
+ style="flex: none"
106
+ />
107
+
108
+ <AForm v-bind="form.props" class="ml4 flex-1" layout="vertical" @submit="onSubmit()">
109
+ <AFormItem v-bind="form.itemProps.password" label="账户密码">
110
+ <AInputPassword v-model:value="form.state.password" size="large">
111
+ <template #prefix>
112
+ <i class="i-ant-design:lock-outlined" />
113
+ </template>
114
+ </AInputPassword>
115
+ </AFormItem>
116
+ <AFormItem v-bind="form.itemProps.security_code" label="安全码">
117
+ <AInput v-model:value="form.state.security_code" size="large">
118
+ <template #prefix>
119
+ <i class="i-ant-design:safety-outlined" />
120
+ </template>
121
+ </AInput>
122
+ <template #extra>
123
+ <span v-if="enableAuth">请使用身份验证器APP,获取6位数字安全码</span>
124
+ <span v-else>请使用身份验证器APP扫描左侧二维码,获取6位数字安全码</span>
125
+ </template>
126
+ </AFormItem>
127
+ <button class="hidden" type="submit">
128
+ 提交
129
+ </button>
130
+ </AForm>
131
+ </div>
132
+
133
+ <template v-if="userInfoQry.isSuccess.value" #actions>
134
+ <AButton type="primary" @click="onSubmit">
135
+ {{ enableAuth ? '解绑' : '绑定' }}
136
+ </AButton>
137
+ </template>
138
+ </ACard>
139
+ </template>
@@ -0,0 +1,89 @@
1
+ <script lang="ts">
2
+ import { nextTick } from 'vue';
3
+ import { hasToken } from '../../../../utils';
4
+ import { useAntdForm } from '../../../../antd/hooks/useAntdForm';
5
+
6
+ export { setDeps };
7
+
8
+ interface IDeps {
9
+ setting2FA: any
10
+ encryptPassword?: (pwd: string) => string
11
+ }
12
+
13
+ let _deps = {} as Required<IDeps>;
14
+
15
+ function setDeps(deps: IDeps) {
16
+ _deps = {
17
+ encryptPassword: pwd => pwd,
18
+ ...deps,
19
+ };
20
+ }
21
+ </script>
22
+
23
+ <script setup lang="ts">
24
+ const props = withDefaults(defineProps<{
25
+ requirePassword?: boolean
26
+ }>(), {
27
+ requirePassword: false,
28
+ });
29
+
30
+ const emits = defineEmits<{
31
+ confirm: [{ password: string, security_code: string }]
32
+ close: []
33
+ }>();
34
+
35
+ const form = useAntdForm({
36
+ password: {
37
+ value: '',
38
+ rules: [
39
+ { required: true },
40
+ ],
41
+ },
42
+ security_code: {
43
+ value: '',
44
+ rules: [
45
+ { required: true, type: 'string', len: 6, message: '请输入6位数字的安全码' },
46
+ ],
47
+ },
48
+ });
49
+
50
+ async function onSubmit() {
51
+ await nextTick();
52
+ const state = await form.$form.validate?.().catch(err => console.log(err));
53
+
54
+ if (state) {
55
+ emits('confirm', { ...form.state, password: _deps.encryptPassword(state.password) });
56
+ emits('close');
57
+ }
58
+ }
59
+ </script>
60
+
61
+ <template>
62
+ <div>
63
+ <AForm v-bind="form.props" @submit="onSubmit()">
64
+ <AFormItem v-if="props.requirePassword" v-bind="form.itemProps.password">
65
+ <AInputPassword v-model:value="form.state.password" size="large" placeholder="请输入账户密码">
66
+ <template #prefix>
67
+ <i class="i-ant-design:lock-outlined" />
68
+ </template>
69
+ </AInputPassword>
70
+ </AFormItem>
71
+ <AFormItem v-bind="form.itemProps.security_code">
72
+ <AInput v-model:value="form.state.security_code" size="large" placeholder="请输入安全码">
73
+ <template #prefix>
74
+ <i class="i-ant-design:safety-outlined" />
75
+ </template>
76
+ </AInput>
77
+ <template #extra>
78
+ <span>请输入身份验证器上显示的6位数字安全码</span>
79
+ </template>
80
+ </AFormItem>
81
+ <AButton type="primary" size="large" block htmlType="submit">
82
+ 验证
83
+ </AButton>
84
+ <AButton v-if="hasToken()" class="mt2 -mb2" type="link" block @click="_deps.setting2FA?.open?.()">
85
+ 没有安全码?去绑定
86
+ </AButton>
87
+ </AForm>
88
+ </div>
89
+ </template>
@@ -1,10 +1,10 @@
1
1
  <script lang="ts">
2
2
  import { reactiveComputed } from '@vueuse/core';
3
3
  import { cloneDeep, mapKeys, mapValues } from 'lodash-es';
4
- import dayjs from 'dayjs';
5
4
  import { Button, Card, CheckboxGroup, DatePicker, type DatePickerProps, Form, FormItem, Input, InputNumber, RadioGroup, RangePicker, Select, Textarea } from 'ant-design-vue';
6
5
  import { computed, ref, toRef } from 'vue';
7
6
  import { useMutation, useQuery, useQueryClient } from '@tanstack/vue-query';
7
+ import dayjs from '../../../../libs/dayjs';
8
8
  import { type ItemSchema, useAntdForm } from '../../../../antd';
9
9
 
10
10
  interface IConfigDetail {
@@ -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.4",
4
+ "version": "0.2.6-base.0",
5
5
  "description": "",
6
6
  "author": "",
7
7
  "license": "ISC",