@liujitcn/kratos-uni-app-core 0.0.29 → 0.0.32
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.
- package/README.md +30 -7
- package/dist/module.mjs +164 -16
- package/dist/vite.mjs +4 -5
- package/package.json +3 -1
- package/src/api/base/{config.ts → v1/config.ts} +6 -2
- package/src/api/base/{file.ts → v1/file.ts} +6 -2
- package/src/api/base/{language.ts → v1/language.ts} +2 -2
- package/src/api/base/{login.ts → v1/login.ts} +3 -3
- package/src/api/base/v1/mfa.ts +127 -0
- package/src/api/base/{oauth.ts → v1/oauth.ts} +3 -3
- package/src/api/system/{auth.ts → app/v1/auth.ts} +8 -15
- package/src/api/system/{base_area.ts → app/v1/base_area.ts} +2 -2
- package/src/api/system/app/v1/base_dict.ts +21 -0
- package/src/api/{base/menu.ts → system/app/v1/base_menu.ts} +2 -2
- package/src/bootstrap.ts +1 -1
- package/src/components/KratosSettingsPage.vue +292 -0
- package/src/components/KratosTabBar.vue +71 -1
- package/src/components/MfaManageDialog.vue +469 -0
- package/src/components/MfaRecoveryCodesDialog.vue +192 -0
- package/src/components/MfaSetupPanel.vue +160 -0
- package/src/components/PasswordVerifyDialog.vue +277 -0
- package/src/index.ts +2 -0
- package/src/locales/README.md +2 -1
- package/src/locales/en-US.json +41 -4
- package/src/locales/index.ts +10 -1
- package/src/locales/ja-JP.json +41 -4
- package/src/locales/zh-CN.json +41 -4
- package/src/locales/zh-TW.json +41 -4
- package/src/navigation.ts +115 -53
- package/src/rpc/base/v1/login.ts +37 -4
- package/src/rpc/base/v1/mfa.ts +180 -0
- package/src/rpc/base/v1/oauth.ts +25 -0
- package/src/rpc/system/app/v1/auth.ts +7 -0
- package/src/rpc/system/common/v1/common.ts +35 -0
- package/src/stores/modules/setting.ts +1 -1
- package/src/stores/modules/user.ts +29 -10
- package/src/utils/clipboard.ts +33 -0
- package/src/utils/http.ts +10 -13
- package/src/utils/passwordCrypto.ts +1 -1
- package/src/utils/webauthn.ts +101 -0
- package/src/views/pages/login/login.vue +299 -13
- package/src/vite.ts +4 -7
- package/src/api/system/base_dict.ts +0 -24
|
@@ -1,14 +1,19 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { useSettingStore, useUserStore } from '../../../stores'
|
|
3
3
|
import type { LoginRequest } from '../../../rpc/base/v1/login'
|
|
4
|
+
import { LoginStatus, type LoginResponse } from '../../../rpc/base/v1/login'
|
|
4
5
|
import { onLoad } from '@dcloudio/uni-app'
|
|
5
|
-
import { computed, defineAsyncComponent, reactive, ref, watch } from 'vue'
|
|
6
|
-
import { defLoginService } from '../../../api/base/login'
|
|
6
|
+
import { computed, defineAsyncComponent, nextTick, reactive, ref, watch } from 'vue'
|
|
7
|
+
import { defLoginService } from '../../../api/base/v1/login'
|
|
8
|
+
import { defMfaService } from '../../../api/base/v1/mfa'
|
|
7
9
|
import defaultLogo from '../../../static/images/logo_icon.png'
|
|
8
10
|
import { homeTabPage } from '../../../utils/navigation'
|
|
9
11
|
import { PASSWORD_CRYPTO_SCENE, encryptPassword } from '../../../utils/passwordCrypto'
|
|
12
|
+
import { createWebAuthnCredential, getWebAuthnAssertion } from '../../../utils/webauthn'
|
|
10
13
|
import { navigateAppRoute } from '../../../navigation'
|
|
11
14
|
import { getLanguageOptions, useI18n, type SupportedLocale } from '../../../locales'
|
|
15
|
+
import MfaRecoveryCodesDialog from '../../../components/MfaRecoveryCodesDialog.vue'
|
|
16
|
+
import MfaSetupPanel from '../../../components/MfaSetupPanel.vue'
|
|
12
17
|
|
|
13
18
|
// go-captcha-uni 仅在 H5 端使用,小程序端不支持 defineAsyncComponent,需按平台裁剪。
|
|
14
19
|
// #ifdef H5
|
|
@@ -43,6 +48,21 @@ const showTenantCode = computed(() => settingStore.getData('showTenantCode') !==
|
|
|
43
48
|
const isAgreePrivacy = ref(false)
|
|
44
49
|
const isAgreePrivacyShakeY = ref(false)
|
|
45
50
|
const loading = ref(false)
|
|
51
|
+
const mfaVisible = ref(false)
|
|
52
|
+
const mfaLoading = ref(false)
|
|
53
|
+
const mfaChallengeId = ref('')
|
|
54
|
+
const mfaCode = ref('')
|
|
55
|
+
const mfaRecoveryCode = ref('')
|
|
56
|
+
const mfaMethod = ref('totp')
|
|
57
|
+
const mfaWebAuthnOptionsJson = ref('')
|
|
58
|
+
const mfaSetupVisible = ref(false)
|
|
59
|
+
const mfaSetupTicket = ref('')
|
|
60
|
+
const mfaSetupUri = ref('')
|
|
61
|
+
const mfaSetupCode = ref('')
|
|
62
|
+
const mfaSetupMethod = ref('totp')
|
|
63
|
+
const mfaSetupWebAuthnOptionsJson = ref('')
|
|
64
|
+
const mfaRecoveryCodesVisible = ref(false)
|
|
65
|
+
const mfaRecoveryCodes = ref<string[]>([])
|
|
46
66
|
|
|
47
67
|
watch(locale, () => void uni.setNavigationBarTitle({ title: t('common.action.login') }), {
|
|
48
68
|
immediate: true,
|
|
@@ -77,6 +97,23 @@ const onOpenPrivacyContract = () => {
|
|
|
77
97
|
navigateAppRoute('app/protocol/privacy')
|
|
78
98
|
}
|
|
79
99
|
|
|
100
|
+
/** 提取请求失败时后端返回的可展示消息。 */
|
|
101
|
+
const getWechatErrorMessage = (error: unknown, fallback: string) => {
|
|
102
|
+
if (error instanceof Error && error.message) {
|
|
103
|
+
return error.message
|
|
104
|
+
}
|
|
105
|
+
if (!error || typeof error !== 'object') {
|
|
106
|
+
return fallback
|
|
107
|
+
}
|
|
108
|
+
const response = error as {
|
|
109
|
+
data?: {
|
|
110
|
+
message?: string
|
|
111
|
+
error?: { message?: string }
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return response.data?.message || response.data?.error?.message || fallback
|
|
115
|
+
}
|
|
116
|
+
|
|
80
117
|
// #ifdef MP-WEIXIN
|
|
81
118
|
const miniBinding = ref(false)
|
|
82
119
|
const miniCaptchaImage = ref('')
|
|
@@ -185,10 +222,13 @@ const wxLogin = async () => {
|
|
|
185
222
|
void refreshMiniCaptcha()
|
|
186
223
|
return
|
|
187
224
|
}
|
|
188
|
-
await loginSuccess()
|
|
225
|
+
if (await handleLoginResponse(session)) await loginSuccess()
|
|
189
226
|
} catch (error) {
|
|
190
227
|
if (!isWechatUnboundError(error)) {
|
|
191
|
-
await uni.showToast({
|
|
228
|
+
await uni.showToast({
|
|
229
|
+
icon: 'none',
|
|
230
|
+
title: getWechatErrorMessage(error, t('core.login.wechat_failed')),
|
|
231
|
+
})
|
|
192
232
|
return
|
|
193
233
|
}
|
|
194
234
|
miniBinding.value = true
|
|
@@ -221,7 +261,7 @@ const bindMiniAccount = async () => {
|
|
|
221
261
|
PASSWORD_CRYPTO_SCENE.PASSWORD_CRYPTO_SCENE_LOGIN,
|
|
222
262
|
)
|
|
223
263
|
const code = (await wx.login()).code
|
|
224
|
-
await userStore.bindOauthSession({
|
|
264
|
+
const response = await userStore.bindOauthSession({
|
|
225
265
|
provider: wechatMiniProvider,
|
|
226
266
|
code,
|
|
227
267
|
tenant_code: showTenantCode.value ? miniForm.tenant_code : '0000',
|
|
@@ -230,18 +270,135 @@ const bindMiniAccount = async () => {
|
|
|
230
270
|
captcha_code: miniForm.captcha_code,
|
|
231
271
|
captcha_id: miniForm.captcha_id,
|
|
232
272
|
})
|
|
233
|
-
await loginSuccess()
|
|
273
|
+
if (await handleLoginResponse(response)) await loginSuccess()
|
|
234
274
|
} catch (error) {
|
|
235
275
|
await refreshMiniCaptcha()
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
276
|
+
await uni.showToast({
|
|
277
|
+
icon: 'none',
|
|
278
|
+
title: getWechatErrorMessage(error, t('core.login.login_failed')),
|
|
279
|
+
})
|
|
239
280
|
} finally {
|
|
240
281
|
loading.value = false
|
|
241
282
|
}
|
|
242
283
|
}
|
|
243
284
|
// #endif
|
|
244
285
|
|
|
286
|
+
// 处理登录接口返回的 MFA 状态。
|
|
287
|
+
const handleLoginResponse = async (response: LoginResponse) => {
|
|
288
|
+
if (response.status === LoginStatus.LOGIN_STATUS_MFA_REQUIRED) {
|
|
289
|
+
mfaChallengeId.value = response.mfa_challenge_id
|
|
290
|
+
mfaCode.value = ''
|
|
291
|
+
mfaRecoveryCode.value = ''
|
|
292
|
+
mfaMethod.value = response.mfa_method || 'totp'
|
|
293
|
+
mfaWebAuthnOptionsJson.value = response.mfa_webauthn_options_json || ''
|
|
294
|
+
mfaVisible.value = true
|
|
295
|
+
return false
|
|
296
|
+
}
|
|
297
|
+
if (response.status === LoginStatus.LOGIN_STATUS_MFA_ENROLLMENT_REQUIRED) {
|
|
298
|
+
mfaSetupTicket.value = response.mfa_setup_ticket
|
|
299
|
+
mfaSetupMethod.value = response.mfa_method || 'totp'
|
|
300
|
+
await beginMfaSetup(response.mfa_setup_ticket)
|
|
301
|
+
return false
|
|
302
|
+
}
|
|
303
|
+
return true
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 校验登录阶段 MFA。
|
|
307
|
+
const verifyMfaLogin = async () => {
|
|
308
|
+
if (
|
|
309
|
+
mfaLoading.value ||
|
|
310
|
+
!mfaChallengeId.value ||
|
|
311
|
+
(mfaMethod.value !== 'webauthn' && !mfaCode.value && !mfaRecoveryCode.value)
|
|
312
|
+
)
|
|
313
|
+
return
|
|
314
|
+
mfaLoading.value = true
|
|
315
|
+
try {
|
|
316
|
+
const webauthnResponseJson =
|
|
317
|
+
mfaMethod.value === 'webauthn' && !mfaRecoveryCode.value
|
|
318
|
+
? await getWebAuthnAssertion(mfaWebAuthnOptionsJson.value)
|
|
319
|
+
: ''
|
|
320
|
+
const response = await userStore.verifyMfa({
|
|
321
|
+
challenge_id: mfaChallengeId.value,
|
|
322
|
+
code: mfaRecoveryCode.value ? '' : mfaCode.value,
|
|
323
|
+
recovery_code: mfaRecoveryCode.value,
|
|
324
|
+
webauthn_response_json: webauthnResponseJson,
|
|
325
|
+
})
|
|
326
|
+
mfaVisible.value = false
|
|
327
|
+
if (await handleLoginResponse(response)) await loginSuccess()
|
|
328
|
+
} catch (error) {
|
|
329
|
+
await uni.showToast({
|
|
330
|
+
icon: 'none',
|
|
331
|
+
title: getWechatErrorMessage(error, t('core.login.login_failed')),
|
|
332
|
+
})
|
|
333
|
+
} finally {
|
|
334
|
+
mfaLoading.value = false
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// 开始强制绑定 MFA。
|
|
339
|
+
const beginMfaSetup = async (ticket: string) => {
|
|
340
|
+
mfaLoading.value = true
|
|
341
|
+
try {
|
|
342
|
+
const response = await defMfaService.BeginMfaEnrollment({ setup_ticket: ticket })
|
|
343
|
+
mfaSetupTicket.value = response.setup_ticket
|
|
344
|
+
mfaSetupUri.value = response.otpauth_uri
|
|
345
|
+
mfaSetupCode.value = ''
|
|
346
|
+
mfaSetupMethod.value = response.method || 'totp'
|
|
347
|
+
mfaSetupWebAuthnOptionsJson.value = response.webauthn_options_json || ''
|
|
348
|
+
mfaSetupVisible.value = true
|
|
349
|
+
} finally {
|
|
350
|
+
mfaLoading.value = false
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// 确认强制绑定 MFA。
|
|
355
|
+
const confirmMfaSetup = async () => {
|
|
356
|
+
if (
|
|
357
|
+
mfaLoading.value ||
|
|
358
|
+
!mfaSetupTicket.value ||
|
|
359
|
+
(mfaSetupMethod.value !== 'webauthn' && !mfaSetupCode.value)
|
|
360
|
+
)
|
|
361
|
+
return
|
|
362
|
+
mfaLoading.value = true
|
|
363
|
+
try {
|
|
364
|
+
const webauthnResponseJson =
|
|
365
|
+
mfaSetupMethod.value === 'webauthn'
|
|
366
|
+
? await createWebAuthnCredential(mfaSetupWebAuthnOptionsJson.value)
|
|
367
|
+
: ''
|
|
368
|
+
const response = await defMfaService.ConfirmMfaEnrollment({
|
|
369
|
+
setup_ticket: mfaSetupTicket.value,
|
|
370
|
+
code: mfaSetupCode.value,
|
|
371
|
+
webauthn_response_json: webauthnResponseJson,
|
|
372
|
+
})
|
|
373
|
+
mfaSetupVisible.value = false
|
|
374
|
+
mfaRecoveryCodes.value = response.recovery_codes
|
|
375
|
+
mfaRecoveryCodesVisible.value = true
|
|
376
|
+
} catch (error) {
|
|
377
|
+
await uni.showToast({
|
|
378
|
+
icon: 'none',
|
|
379
|
+
title: getWechatErrorMessage(error, t('core.login.login_failed')),
|
|
380
|
+
})
|
|
381
|
+
} finally {
|
|
382
|
+
mfaLoading.value = false
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/** 确认已保存恢复码,刷新验证码并重新进入登录流程。 */
|
|
387
|
+
const finishMfaEnrollment = async () => {
|
|
388
|
+
mfaRecoveryCodesVisible.value = false
|
|
389
|
+
await nextTick()
|
|
390
|
+
mfaRecoveryCodes.value = []
|
|
391
|
+
await uni.showToast({ icon: 'none', title: t('core.login.mfa_setup_success') })
|
|
392
|
+
// #ifdef H5
|
|
393
|
+
await loadPageCaptcha()
|
|
394
|
+
// #endif
|
|
395
|
+
// 小程序没有可复用的账号密码会话,绑定完成后重新走微信登录以进入 MFA 挑战。
|
|
396
|
+
// #ifdef MP-WEIXIN
|
|
397
|
+
miniBinding.value = false
|
|
398
|
+
await wxLogin()
|
|
399
|
+
// #endif
|
|
400
|
+
}
|
|
401
|
+
|
|
245
402
|
// #ifdef H5
|
|
246
403
|
const captcha_base64 = ref() // 验证码图片Base64字符串
|
|
247
404
|
const defaultCaptchaImageWidth = 170
|
|
@@ -504,6 +661,7 @@ const verifyCaptchaToken = async (captchaCode: string) => {
|
|
|
504
661
|
})
|
|
505
662
|
return result.captcha_token
|
|
506
663
|
}
|
|
664
|
+
|
|
507
665
|
// 执行真正的账号登录流程。
|
|
508
666
|
const submitLogin = async (captchaCode: string) => {
|
|
509
667
|
const password = await encryptPassword(
|
|
@@ -540,8 +698,8 @@ const verifyBehaviorCaptcha = async (captchaCode: string, reset: () => void) =>
|
|
|
540
698
|
try {
|
|
541
699
|
const captchaToken = await verifyCaptchaToken(captchaCode)
|
|
542
700
|
behaviorDialogVisible.value = false
|
|
543
|
-
await submitLogin(captchaToken)
|
|
544
|
-
await loginSuccess()
|
|
701
|
+
const response = await submitLogin(captchaToken)
|
|
702
|
+
if (await handleLoginResponse(response)) await loginSuccess()
|
|
545
703
|
} catch {
|
|
546
704
|
reset()
|
|
547
705
|
await refreshCaptcha()
|
|
@@ -586,8 +744,8 @@ const onSubmit = async () => {
|
|
|
586
744
|
return
|
|
587
745
|
}
|
|
588
746
|
try {
|
|
589
|
-
await submitLogin(form.value.captcha_code)
|
|
590
|
-
await loginSuccess()
|
|
747
|
+
const response = await submitLogin(form.value.captcha_code)
|
|
748
|
+
if (await handleLoginResponse(response)) await loginSuccess()
|
|
591
749
|
} catch {
|
|
592
750
|
form.value.captcha_code = ''
|
|
593
751
|
await refreshCaptcha()
|
|
@@ -849,6 +1007,65 @@ onLoad(() => {
|
|
|
849
1007
|
}}</text>
|
|
850
1008
|
</view>
|
|
851
1009
|
</view>
|
|
1010
|
+
<view v-if="mfaVisible" class="mfa-mask">
|
|
1011
|
+
<view class="mfa-panel">
|
|
1012
|
+
<text class="mfa-title">{{ t('core.login.mfa_title') }}</text>
|
|
1013
|
+
<input
|
|
1014
|
+
v-if="mfaMethod !== 'webauthn'"
|
|
1015
|
+
v-model="mfaCode"
|
|
1016
|
+
class="login-input"
|
|
1017
|
+
type="number"
|
|
1018
|
+
maxlength="8"
|
|
1019
|
+
:placeholder="t('core.login.mfa_code')"
|
|
1020
|
+
/>
|
|
1021
|
+
<input
|
|
1022
|
+
v-model="mfaRecoveryCode"
|
|
1023
|
+
class="login-input"
|
|
1024
|
+
:placeholder="t('core.login.mfa_recovery_code')"
|
|
1025
|
+
/>
|
|
1026
|
+
<button
|
|
1027
|
+
class="login-button login-button-primary"
|
|
1028
|
+
:loading="mfaLoading"
|
|
1029
|
+
@tap="verifyMfaLogin"
|
|
1030
|
+
>
|
|
1031
|
+
{{
|
|
1032
|
+
mfaMethod === 'webauthn' && !mfaRecoveryCode
|
|
1033
|
+
? t('core.login.mfa_webauthn_action')
|
|
1034
|
+
: t('common.action.confirm')
|
|
1035
|
+
}}
|
|
1036
|
+
</button>
|
|
1037
|
+
</view>
|
|
1038
|
+
</view>
|
|
1039
|
+
<view v-if="mfaSetupVisible" class="mfa-mask">
|
|
1040
|
+
<view class="mfa-panel">
|
|
1041
|
+
<text class="mfa-title">{{ t('core.login.mfa_setup_title') }}</text>
|
|
1042
|
+
<MfaSetupPanel :uri="mfaSetupUri" />
|
|
1043
|
+
<input
|
|
1044
|
+
v-if="mfaSetupMethod !== 'webauthn'"
|
|
1045
|
+
v-model="mfaSetupCode"
|
|
1046
|
+
class="login-input"
|
|
1047
|
+
type="number"
|
|
1048
|
+
maxlength="8"
|
|
1049
|
+
:placeholder="t('core.login.mfa_code')"
|
|
1050
|
+
/>
|
|
1051
|
+
<button
|
|
1052
|
+
class="login-button login-button-primary"
|
|
1053
|
+
:loading="mfaLoading"
|
|
1054
|
+
@tap="confirmMfaSetup"
|
|
1055
|
+
>
|
|
1056
|
+
{{
|
|
1057
|
+
mfaSetupMethod === 'webauthn'
|
|
1058
|
+
? t('core.login.mfa_webauthn_action')
|
|
1059
|
+
: t('common.action.confirm')
|
|
1060
|
+
}}
|
|
1061
|
+
</button>
|
|
1062
|
+
</view>
|
|
1063
|
+
</view>
|
|
1064
|
+
<MfaRecoveryCodesDialog
|
|
1065
|
+
v-model="mfaRecoveryCodesVisible"
|
|
1066
|
+
:codes="mfaRecoveryCodes"
|
|
1067
|
+
@confirm="finishMfaEnrollment"
|
|
1068
|
+
/>
|
|
852
1069
|
</view>
|
|
853
1070
|
</view>
|
|
854
1071
|
</template>
|
|
@@ -870,12 +1087,17 @@ onLoad(() => {
|
|
|
870
1087
|
top: calc(24rpx + env(safe-area-inset-top));
|
|
871
1088
|
right: 28rpx;
|
|
872
1089
|
z-index: 2;
|
|
1090
|
+
max-width: calc(100% - 56rpx);
|
|
873
1091
|
}
|
|
874
1092
|
|
|
875
1093
|
.login-locale__value {
|
|
1094
|
+
max-width: 240rpx;
|
|
1095
|
+
overflow: hidden;
|
|
876
1096
|
padding: 12rpx 16rpx;
|
|
877
1097
|
color: #16806d;
|
|
878
1098
|
font-size: 24rpx;
|
|
1099
|
+
text-overflow: ellipsis;
|
|
1100
|
+
white-space: nowrap;
|
|
879
1101
|
}
|
|
880
1102
|
|
|
881
1103
|
.login-hero {
|
|
@@ -1026,6 +1248,70 @@ onLoad(() => {
|
|
|
1026
1248
|
}
|
|
1027
1249
|
}
|
|
1028
1250
|
}
|
|
1251
|
+
.mfa-mask {
|
|
1252
|
+
position: fixed;
|
|
1253
|
+
inset: 0;
|
|
1254
|
+
z-index: 20;
|
|
1255
|
+
display: flex;
|
|
1256
|
+
align-items: center;
|
|
1257
|
+
justify-content: center;
|
|
1258
|
+
padding: 40rpx;
|
|
1259
|
+
background: rgba(15, 23, 42, 0.46);
|
|
1260
|
+
}
|
|
1261
|
+
.mfa-panel {
|
|
1262
|
+
display: flex;
|
|
1263
|
+
flex-direction: column;
|
|
1264
|
+
width: 100%;
|
|
1265
|
+
max-width: 620rpx;
|
|
1266
|
+
padding: 36rpx;
|
|
1267
|
+
background: #fff;
|
|
1268
|
+
border-radius: 24rpx;
|
|
1269
|
+
box-sizing: border-box;
|
|
1270
|
+
}
|
|
1271
|
+
.mfa-title {
|
|
1272
|
+
margin-bottom: 22rpx;
|
|
1273
|
+
font-size: 34rpx;
|
|
1274
|
+
font-weight: 700;
|
|
1275
|
+
color: #172a35;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
.mfa-panel .login-input {
|
|
1279
|
+
width: 100%;
|
|
1280
|
+
height: 88rpx;
|
|
1281
|
+
margin-bottom: 20rpx;
|
|
1282
|
+
padding: 0 30rpx;
|
|
1283
|
+
color: #1f2937;
|
|
1284
|
+
font-size: 28rpx;
|
|
1285
|
+
background: #f9fbfa;
|
|
1286
|
+
border: 1px solid #e5e7eb;
|
|
1287
|
+
border-radius: 24rpx;
|
|
1288
|
+
box-sizing: border-box;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
.mfa-panel .login-button {
|
|
1292
|
+
display: flex;
|
|
1293
|
+
align-items: center;
|
|
1294
|
+
justify-content: center;
|
|
1295
|
+
width: 100%;
|
|
1296
|
+
height: 88rpx;
|
|
1297
|
+
margin-top: 12rpx;
|
|
1298
|
+
padding: 0;
|
|
1299
|
+
color: #fff;
|
|
1300
|
+
font-size: 30rpx;
|
|
1301
|
+
font-weight: 500;
|
|
1302
|
+
background: transparent;
|
|
1303
|
+
border: 0;
|
|
1304
|
+
border-radius: 24rpx;
|
|
1305
|
+
box-shadow: 0 18rpx 36rpx rgba(40, 187, 156, 0.24);
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
.mfa-panel .login-button::after {
|
|
1309
|
+
border: 0;
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
.mfa-panel .login-button-primary {
|
|
1313
|
+
background: linear-gradient(135deg, #34c8aa 0%, #28bb9c 100%);
|
|
1314
|
+
}
|
|
1029
1315
|
|
|
1030
1316
|
.login-behavior-mask {
|
|
1031
1317
|
position: fixed;
|
package/src/vite.ts
CHANGED
|
@@ -5,7 +5,6 @@ import {
|
|
|
5
5
|
readFileSync,
|
|
6
6
|
readdirSync,
|
|
7
7
|
rmdirSync,
|
|
8
|
-
rmSync,
|
|
9
8
|
statSync,
|
|
10
9
|
unlinkSync,
|
|
11
10
|
writeFileSync,
|
|
@@ -39,7 +38,6 @@ type BuildTransaction = {
|
|
|
39
38
|
originalPages: string
|
|
40
39
|
generatedFiles: string[]
|
|
41
40
|
generatedStaticFiles: string[]
|
|
42
|
-
generatedStatic?: boolean
|
|
43
41
|
ownerPid?: number
|
|
44
42
|
ready?: boolean
|
|
45
43
|
}
|
|
@@ -368,13 +366,9 @@ function restoreBuildTransaction(transaction: BuildTransaction, transactionFile:
|
|
|
368
366
|
removeEmptyParents(dirname(file), transaction.inputDir)
|
|
369
367
|
}
|
|
370
368
|
removeGeneratedStaticFiles(
|
|
371
|
-
transaction.generatedStaticFiles
|
|
369
|
+
transaction.generatedStaticFiles,
|
|
372
370
|
resolve(transaction.inputDir, 'static'),
|
|
373
371
|
)
|
|
374
|
-
// 兼容旧事务:旧格式仅在 static 原本不存在时记录整个目录由插件生成。
|
|
375
|
-
if (transaction.generatedStatic && transaction.generatedStaticFiles === undefined) {
|
|
376
|
-
rmSync(resolve(transaction.inputDir, 'static'), { recursive: true, force: true })
|
|
377
|
-
}
|
|
378
372
|
writeFileSync(transaction.pagesFile, transaction.originalPages)
|
|
379
373
|
if (existsSync(transactionFile)) unlinkSync(transactionFile)
|
|
380
374
|
}
|
|
@@ -600,10 +594,13 @@ function appendPage(manifest: PagesManifest, page: ScannedPage): void {
|
|
|
600
594
|
function createPageWrapper(page: ScannedPage): string {
|
|
601
595
|
const source = page.source.replace(/\\/g, '/')
|
|
602
596
|
return `<script setup lang="ts">
|
|
597
|
+
import { onShow } from '@dcloudio/uni-app'
|
|
598
|
+
import { setAppPageTitle } from '@liujitcn/kratos-uni-app-core'
|
|
603
599
|
import KratosPage from ${JSON.stringify(source)}
|
|
604
600
|
import KratosTabBar from '@liujitcn/kratos-uni-app-core/components/KratosTabBar.vue'
|
|
605
601
|
|
|
606
602
|
defineOptions({ inheritAttrs: false })
|
|
603
|
+
onShow(() => setAppPageTitle(${JSON.stringify(page.route)}))
|
|
607
604
|
</script>
|
|
608
605
|
|
|
609
606
|
<template>
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { http } from '../../utils/http'
|
|
2
|
-
import type { BaseDictForm, BaseDictService } from '../../rpc/system/app/v1/base_dict'
|
|
3
|
-
|
|
4
|
-
const BASE_DICT_URL = '/v1/app/base/dict'
|
|
5
|
-
|
|
6
|
-
/** 字典查询请求兼容结构,支持 code 和旧版 value。 */
|
|
7
|
-
type GetBaseDictRequestCompat = {
|
|
8
|
-
code?: string
|
|
9
|
-
value?: string
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
/** 字典服务 */
|
|
13
|
-
export class BaseDictServiceImpl implements BaseDictService {
|
|
14
|
-
/** 查询单个字典 */
|
|
15
|
-
GetBaseDict(request: GetBaseDictRequestCompat): Promise<BaseDictForm> {
|
|
16
|
-
const code = request.code ?? request.value ?? ''
|
|
17
|
-
return http<BaseDictForm>({
|
|
18
|
-
url: `${BASE_DICT_URL}/${code}`,
|
|
19
|
-
method: 'GET',
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const defBaseDictService = new BaseDictServiceImpl()
|