@liujitcn/kratos-admin-core 0.0.39 → 0.0.41

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,37 @@
1
+ import type { HotPayload, Plugin, WebSocketClient } from "vite";
2
+
3
+ /** 暂缓生成期间的开发热更新,释放后统一刷新以加载完整产物。 */
4
+ export function codegenHmrPlugin(): Plugin {
5
+ return {
6
+ name: "admin-codegen-hmr",
7
+ apply: "serve",
8
+ configureServer(server) {
9
+ const clients = new Set<WebSocketClient>();
10
+ const send = server.ws.send.bind(server.ws);
11
+ let pending = false;
12
+ const release = (client: WebSocketClient) => {
13
+ clients.delete(client);
14
+ if (!clients.size && pending) {
15
+ pending = false;
16
+ send({ type: "full-reload", path: "*" });
17
+ }
18
+ };
19
+ server.ws.send = (payload: HotPayload | string, data?: unknown) => {
20
+ if (clients.size && typeof payload !== "string" && (payload.type === "update" || payload.type === "full-reload")) {
21
+ pending = true;
22
+ return;
23
+ }
24
+ if (typeof payload === "string") send(payload, data);
25
+ else send(payload);
26
+ };
27
+ server.ws.on("admin:codegen-hold", (_data, client) => {
28
+ if (!clients.has(client)) {
29
+ clients.add(client);
30
+ client.socket.once("close", () => release(client));
31
+ }
32
+ client.send("admin:codegen-held", {});
33
+ });
34
+ server.ws.on("admin:codegen-release", (_data, client) => release(client));
35
+ }
36
+ };
37
+ }
package/build/plugins.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { codegenHmrPlugin } from "./codegen-hmr";
1
2
  import { resolve } from "path";
2
3
  import { PluginOption } from "vite";
3
4
  import { VitePWA } from "vite-plugin-pwa";
@@ -37,6 +38,7 @@ export const createVitePlugins = (viteEnv: ViteEnv, options: VitePluginOptions =
37
38
  const { VITE_GLOB_APP_TITLE, VITE_REPORT, VITE_DEVTOOLS, VITE_PWA, VITE_CODEINSPECTOR } = viteEnv;
38
39
  const sourcePatterns = options.sourcePatterns;
39
40
  return [
41
+ codegenHmrPlugin(),
40
42
  vue(),
41
43
  // vue 可以使用 jsx/tsx 语法
42
44
  vueJsx(),
@@ -0,0 +1,7 @@
1
+ /** 为 API 和组件自动导入创建跨平台源码匹配规则,包含 npm 包内源码。 */
2
+ export function createSourcePatterns(sourceRoots: string[]): RegExp[] {
3
+ return [...new Set(sourceRoots.map(sourceRoot => sourceRoot.replaceAll("\\", "/")))].map(sourceRoot => {
4
+ const escapedRoot = sourceRoot.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
5
+ return new RegExp(`^${escapedRoot.replaceAll("/", "[/\\\\]")}[/\\\\].*\\.(?:vue|[jt]sx?)(?:\\?.*)?$`);
6
+ });
7
+ }
@@ -0,0 +1,3 @@
1
+ import type { Plugin } from "vite";
2
+ /** 暂缓生成期间的开发热更新,释放后统一刷新以加载完整产物。 */
3
+ export declare function codegenHmrPlugin(): Plugin;
@@ -0,0 +1,2 @@
1
+ /** 为 API 和组件自动导入创建跨平台源码匹配规则,包含 npm 包内源码。 */
2
+ export declare function createSourcePatterns(sourceRoots: string[]): RegExp[];
@@ -4,18 +4,23 @@ interface ProFormItemProps {
4
4
  field: ProFormField;
5
5
  model: Record<string, any>;
6
6
  }
7
- declare var __VLS_127: string, __VLS_128: {
7
+ declare var __VLS_10: string, __VLS_11: {
8
+ field: ProFormField;
9
+ model: Record<string, any>;
10
+ }, __VLS_133: string, __VLS_134: {
8
11
  option: import("element-plus").TransferDataItem;
9
12
  field: ProFormField;
10
13
  model: Record<string, any>;
11
- }, __VLS_173: string, __VLS_174: {
14
+ }, __VLS_179: string, __VLS_180: {
12
15
  field: ProFormField;
13
16
  model: Record<string, any>;
14
17
  };
15
18
  type __VLS_Slots = {} & {
16
- [K in NonNullable<typeof __VLS_127>]?: (props: typeof __VLS_128) => any;
19
+ [K in NonNullable<typeof __VLS_10>]?: (props: typeof __VLS_11) => any;
20
+ } & {
21
+ [K in NonNullable<typeof __VLS_133>]?: (props: typeof __VLS_134) => any;
17
22
  } & {
18
- [K in NonNullable<typeof __VLS_173>]?: (props: typeof __VLS_174) => any;
23
+ [K in NonNullable<typeof __VLS_179>]?: (props: typeof __VLS_180) => any;
19
24
  };
20
25
  declare const __VLS_base: import("vue").DefineComponent<ProFormItemProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<ProFormItemProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
21
26
  declare const __VLS_export: __VLS_WithSlots<typeof __VLS_base, __VLS_Slots>;
@@ -33,6 +33,8 @@ export interface ProFormField {
33
33
  rowBreakBefore?: boolean;
34
34
  /** 自定义插槽名称。 */
35
35
  slotName?: string;
36
+ /** 输入控件旁的附加操作插槽。 */
37
+ suffixSlotName?: string;
36
38
  /** 标题提示文案。 */
37
39
  labelTooltip?: string;
38
40
  /** 字段校验规则。 */
@@ -1,3 +1,8 @@
1
+ import "./vite-env.d.ts";
2
+ import "./typings/global.d.ts";
3
+ import "./typings/utils.d.ts";
4
+ import "../types/generated/auto-imports.d.ts";
5
+ import "../types/generated/components.d.ts";
1
6
  export * from "./modules";
2
7
  export * from "./locales";
3
8
  export { defLanguageService } from "./api/base/v1/language";
@@ -1,4 +1,8 @@
1
1
  import "go-captcha-vue/dist/style.css";
2
- declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
2
+ /** 登录表单属性,验证弹窗以外层登录卡片的中心定位。 */
3
+ interface LoginFormProps {
4
+ dialogAnchor?: HTMLElement;
5
+ }
6
+ declare const __VLS_export: import("vue").DefineComponent<LoginFormProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<LoginFormProps> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
3
7
  declare const _default: typeof __VLS_export;
4
8
  export default _default;
@@ -1,5 +1,9 @@
1
1
  <template>
2
- <el-input v-if="field.component === 'input'" v-model.trim="fieldValue" v-bind="fieldProps" />
2
+ <el-input v-if="field.component === 'input'" v-model.trim="fieldValue" v-bind="fieldProps">
3
+ <template v-if="field.suffixSlotName" #append>
4
+ <slot :name="field.suffixSlotName" :field="field" :model="model" />
5
+ </template>
6
+ </el-input>
3
7
 
4
8
  <el-input
5
9
  v-else-if="field.component === 'password'"
@@ -16,11 +16,21 @@
16
16
  </el-tooltip>
17
17
  </div>
18
18
  </template>
19
- <ProFormItem :field="field" :model="model">
20
- <template v-for="slotName in slotNames" #[slotName]="slotScope">
21
- <slot :name="slotName" v-bind="slotScope" />
22
- </template>
23
- </ProFormItem>
19
+ <div class="pro-form__control">
20
+ <div class="pro-form__input">
21
+ <ProFormItem :field="field" :model="model">
22
+ <template v-for="slotName in slotNames" #[slotName]="slotScope">
23
+ <slot :name="slotName" v-bind="slotScope" />
24
+ </template>
25
+ </ProFormItem>
26
+ </div>
27
+ <slot
28
+ v-if="field.suffixSlotName && field.component !== 'input'"
29
+ :name="field.suffixSlotName"
30
+ :model="model"
31
+ :field="field"
32
+ />
33
+ </div>
24
34
  </el-form-item>
25
35
  </el-col>
26
36
  </template>
@@ -104,6 +114,18 @@ defineExpose({
104
114
  </script>
105
115
 
106
116
  <style scoped lang="scss">
117
+ .pro-form__control {
118
+ display: flex;
119
+ align-items: flex-start;
120
+ gap: 8px;
121
+ width: 100%;
122
+ min-width: 0;
123
+ }
124
+ .pro-form__input {
125
+ flex: 1;
126
+ min-width: 0;
127
+ }
128
+
107
129
  .pro-form__row-break {
108
130
  flex: 0 0 100%;
109
131
  height: 0;
@@ -60,6 +60,8 @@ export interface ProFormField {
60
60
  rowBreakBefore?: boolean;
61
61
  /** 自定义插槽名称。 */
62
62
  slotName?: string;
63
+ /** 输入控件旁的附加操作插槽。 */
64
+ suffixSlotName?: string;
63
65
  /** 标题提示文案。 */
64
66
  labelTooltip?: string;
65
67
  /** 字段校验规则。 */
@@ -1,8 +1,8 @@
1
- /// <reference path="./vite-env.d.ts" />
2
- /// <reference path="./typings/global.d.ts" />
3
- /// <reference path="./typings/utils.d.ts" />
4
- /// <reference path="../types/generated/auto-imports.d.ts" />
5
- /// <reference path="../types/generated/components.d.ts" />
1
+ import "./vite-env.d.ts";
2
+ import "./typings/global.d.ts";
3
+ import "./typings/utils.d.ts";
4
+ import "../types/generated/auto-imports.d.ts";
5
+ import "../types/generated/components.d.ts";
6
6
 
7
7
  export * from "./modules";
8
8
  export * from "./locales";
@@ -96,11 +96,11 @@
96
96
  <ProDialog
97
97
  v-model="behaviorDialogVisible"
98
98
  width="364px"
99
- top="16vh"
99
+ :style="loginDialogStyle"
100
100
  :show-close="false"
101
101
  :show-footer="false"
102
102
  append-to-body
103
- class="behavior-captcha-dialog"
103
+ class="login-verification-dialog behavior-captcha-dialog"
104
104
  >
105
105
  <div v-loading="behaviorLoading" class="behavior-captcha-body">
106
106
  <GoCaptchaSlide
@@ -126,7 +126,15 @@
126
126
  />
127
127
  </div>
128
128
  </ProDialog>
129
- <ProDialog v-model="mfaDialogVisible" :title="t('core.login.mfa_title')" width="360px" :close-on-click-modal="false">
129
+ <ProDialog
130
+ v-model="mfaDialogVisible"
131
+ :title="t('core.login.mfa_title')"
132
+ width="364px"
133
+ :style="loginDialogStyle"
134
+ class="login-verification-dialog"
135
+ append-to-body
136
+ :close-on-click-modal="false"
137
+ >
130
138
  <ProForm
131
139
  ref="mfaLoginFormRef"
132
140
  :model="mfaLoginForm"
@@ -146,7 +154,15 @@
146
154
  </el-button>
147
155
  </template>
148
156
  </ProDialog>
149
- <ProDialog v-model="mfaSetupDialogVisible" :title="t('core.login.mfa_setup_title')" width="520px" :close-on-click-modal="false">
157
+ <ProDialog
158
+ v-model="mfaSetupDialogVisible"
159
+ :title="t('core.login.mfa_setup_title')"
160
+ width="520px"
161
+ :style="loginDialogStyle"
162
+ class="login-verification-dialog"
163
+ append-to-body
164
+ :close-on-click-modal="false"
165
+ >
150
166
  <template v-if="mfaSetupMethod !== 'webauthn'">
151
167
  <MfaSetupPanel :uri="mfaSetupUri" />
152
168
  <ProForm
@@ -166,12 +182,20 @@
166
182
  </el-button>
167
183
  </template>
168
184
  </ProDialog>
169
- <MfaRecoveryCodesDialog v-model="recoveryCodesDialogVisible" :codes="recoveryCodes" @confirm="finishMfaEnrollment" />
185
+ <MfaRecoveryCodesDialog
186
+ v-model="recoveryCodesDialogVisible"
187
+ :codes="recoveryCodes"
188
+ :style="loginDialogStyle"
189
+ class="login-verification-dialog"
190
+ append-to-body
191
+ @confirm="finishMfaEnrollment"
192
+ />
170
193
  </template>
171
194
 
172
195
  <script setup lang="ts">
173
196
  import { computed, ref, reactive, onMounted, watch } from "vue";
174
197
  import { useRoute, useRouter } from "vue-router";
198
+ import { useElementBounding } from "@vueuse/core";
175
199
  import { HOME_URL } from "../../../config";
176
200
  import { getTimeState } from "../../../utils";
177
201
  import { defLoginService } from "../../../api/base/v1/login";
@@ -200,6 +224,19 @@ import "go-captcha-vue/dist/style.css";
200
224
  import { useLocaleStore } from "../../../locales";
201
225
  import { clearPasswordChangeRequired, handlePasswordChangeRequired } from "../../../utils/request";
202
226
 
227
+ /** 登录表单属性,验证弹窗以外层登录卡片的中心定位。 */
228
+ interface LoginFormProps {
229
+ dialogAnchor?: HTMLElement;
230
+ }
231
+
232
+ const props = defineProps<LoginFormProps>();
233
+ const dialogAnchorBounds = useElementBounding(() => props.dialogAnchor);
234
+ const loginDialogStyle = computed(() => ({
235
+ "--login-dialog-center-x": `${dialogAnchorBounds.left.value + dialogAnchorBounds.width.value / 2}px`,
236
+ "--login-dialog-center-y": `${dialogAnchorBounds.top.value + dialogAnchorBounds.height.value / 2}px`,
237
+ "--login-dialog-anchor-width": `${dialogAnchorBounds.width.value}px`
238
+ }));
239
+
203
240
  const router = useRouter();
204
241
  const route = useRoute();
205
242
  const userStore = useUserStore();
@@ -958,6 +995,25 @@ watch(
958
995
  color: var(--el-color-primary);
959
996
  }
960
997
 
998
+ /* 各验证步骤共用登录卡片中心;靠近视口边缘时限制尺寸,内容在弹窗内滚动。 */
999
+ :global(.login-verification-dialog.el-dialog) {
1000
+ --login-dialog-width: min(var(--el-dialog-width), var(--login-dialog-anchor-width), calc(100vw - 32px));
1001
+ --login-dialog-y: clamp(25dvh, var(--login-dialog-center-y), 75dvh);
1002
+
1003
+ position: fixed;
1004
+ top: var(--login-dialog-y);
1005
+ left: clamp(
1006
+ calc(var(--login-dialog-width) / 2 + 16px),
1007
+ var(--login-dialog-center-x),
1008
+ calc(100vw - var(--login-dialog-width) / 2 - 16px)
1009
+ );
1010
+ width: var(--login-dialog-width);
1011
+ max-height: calc(min(var(--login-dialog-y), 100dvh - var(--login-dialog-y)) * 2 - 32px);
1012
+ margin: 0;
1013
+ overflow: auto;
1014
+ translate: -50% -50%;
1015
+ }
1016
+
961
1017
  :global(.behavior-captcha-dialog) {
962
1018
  --go-captcha-theme-text-color: var(--el-text-color-primary);
963
1019
  --go-captcha-theme-bg-color: var(--el-bg-color);
@@ -6,12 +6,12 @@
6
6
  <div class="login-left">
7
7
  <img class="login-left-img" :src="backgroundUrl" :alt="t('core.login.background_alt')" />
8
8
  </div>
9
- <div class="login-form">
9
+ <div ref="loginFormRef" class="login-form">
10
10
  <div ref="loginLogoRef" class="login-logo">
11
11
  <img v-show="showLogoIcon" ref="logoIconRef" class="login-icon" :src="logoUrl" alt="" />
12
12
  <h2 ref="logoTextRef" class="logo-text" :style="{ fontSize: `${logoFontSize}px` }">{{ projectName }}</h2>
13
13
  </div>
14
- <LoginForm />
14
+ <LoginForm :dialog-anchor="loginFormRef" />
15
15
  </div>
16
16
  </div>
17
17
  </div>
@@ -36,6 +36,7 @@ const PREFERRED_MIN_LOGO_FONT_SIZE = 30;
36
36
  const MIN_LOGO_FONT_SIZE = 8;
37
37
  const LOGO_TEXT_GAP = 16;
38
38
 
39
+ const loginFormRef = ref<HTMLElement>();
39
40
  const loginLogoRef = ref<HTMLElement>();
40
41
  const logoIconRef = ref<HTMLImageElement>();
41
42
  const logoTextRef = ref<HTMLElement>();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@liujitcn/kratos-admin-core",
3
3
  "private": false,
4
- "version": "0.0.39",
4
+ "version": "0.0.41",
5
5
  "type": "module",
6
6
  "description": "kratos-admin frontend runtime and base modules",
7
7
  "author": {
@@ -224,7 +224,7 @@
224
224
  "scripts": {
225
225
  "build": "pnpm build:package",
226
226
  "build:package": "node ../../scripts/build-package.mjs .",
227
- "test": "tsc -p tsconfig.test.json && node --test dist/test/tests/modules.test.js dist/test/tests/dict.test.js dist/test/tests/menu.test.js",
227
+ "test": "tsc -p tsconfig.test.json && node --test dist/test/tests/modules.test.js dist/test/tests/dict.test.js dist/test/tests/menu.test.js dist/test/tests/source-patterns.test.js",
228
228
  "type:check": "vue-tsc --noEmit --skipLibCheck"
229
229
  }
230
230
  }
package/vite.config.ts CHANGED
@@ -8,6 +8,7 @@ import dayjs from "dayjs";
8
8
  import { wrapperEnv } from "./build/getEnv";
9
9
  import { createProxy } from "./build/proxy";
10
10
  import { createVitePlugins } from "./build/plugins";
11
+ import { createSourcePatterns } from "./build/source-patterns";
11
12
 
12
13
  const coreRoot = dirname(fileURLToPath(import.meta.url));
13
14
  const viteLogger = createLogger();
@@ -52,9 +53,7 @@ export function defineAdminViteConfig(options: AdminViteConfigOptions = {}) {
52
53
  ...resolvePackageSource(root, packageName)
53
54
  }));
54
55
  const sourceRoots = [resolve(root, "src"), coreSourceRoot, ...modulePackages.map(item => item.sourceRoot)];
55
- const sourcePatterns = [...new Set(sourceRoots)].map(
56
- sourceRoot => new RegExp(`${escapeRegExp(sourceRoot)}[\\/\\\\].*\\.(?:vue|[jt]sx?)(?:\\?.*)?$`)
57
- );
56
+ const sourcePatterns = createSourcePatterns(sourceRoots);
58
57
  const aliases = [
59
58
  { find: "@", replacement: coreSourceRoot },
60
59
  ...createPackageAliases(corePackage),