@liujitcn/kratos-taro-app-core 0.0.1 → 0.0.18

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 CHANGED
@@ -8,13 +8,14 @@
8
8
  - 统一 HTTP、上传、下载、文件预览与 RSA/AES 密码加密。
9
9
  - Zustand 用户、配置和导航状态。
10
10
  - 接口驱动的逻辑路由、访问模式、自绘 tabBar 和稳定 `viewKey`。
11
- - 打包静态资源公共路径解析,以及 H5 默认导航栏兼容框架。
11
+ - 页面静态资源 export、动态资源公共路径解析,以及 H5 默认导航栏兼容框架。
12
12
  - 首页、登录、协议、WebView、启动/错误状态页面。
13
13
  - H5 与微信小程序共用的模块页面装配 runner。
14
14
 
15
15
  ## 公开入口
16
16
 
17
- - `@liujitcn/kratos-taro-app-core`:启动、模块、导航、状态和常用工具,包含 `resolveBundledAsset()`。
17
+ - `@liujitcn/kratos-taro-app-core`:启动、模块、导航、状态和常用工具,包含动态路径使用的 `resolveBundledAsset()`。
18
+ - `@liujitcn/kratos-taro-app-core/static/*`:页面图片等可由 Webpack 静态分析的打包资源。
18
19
  - `@liujitcn/kratos-taro-app-core/build`:构建期模块描述。
19
20
  - `@liujitcn/kratos-taro-app-core/runner`:Taro 构建装配入口。
20
21
  - `api/*`、`components/*`、`rpc/*`、`styles/*`、`utils/*`、`views/*`:白名单子路径。
package/package.json CHANGED
@@ -1,7 +1,13 @@
1
1
  {
2
2
  "name": "@liujitcn/kratos-taro-app-core",
3
3
  "description": "Runtime, navigation, authentication, and build foundation for Kratos Taro apps.",
4
- "version": "0.0.1",
4
+ "license": "MIT",
5
+ "homepage": "https://github.com/liujitcn/kratos-admin",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git@github.com:liujitcn/kratos-admin.git"
9
+ },
10
+ "version": "0.0.18",
5
11
  "type": "module",
6
12
  "files": [
7
13
  "dist",
@@ -26,18 +26,21 @@ button::after {
26
26
  border: 0;
27
27
  }
28
28
 
29
+ // 与 uni-app H5 一致,仅在路由页内容溢出时显示系统滚动条。
30
+ .taro_page {
31
+ overflow-y: auto !important;
32
+ }
33
+
29
34
  // Taro H5 的默认确认色与 uni-app H5 不同,统一为基线平台色。
30
35
  .taro-model__confirm {
31
36
  color: #007aff !important;
32
37
  }
33
38
 
34
- // H5 路由页和 ScrollView 同时滚动,隐藏滚动条避免重复占用内容宽度。
35
- .taro_page,
39
+ // H5 ScrollView 使用内部滚动,隐藏其滚动条避免额外占用内容宽度。
36
40
  .taro-scroll-view__scroll-y {
37
41
  scrollbar-width: none;
38
42
  }
39
43
 
40
- .taro_page::-webkit-scrollbar,
41
44
  .taro-scroll-view__scroll-y::-webkit-scrollbar {
42
45
  display: none;
43
46
  width: 0;
@@ -15,6 +15,12 @@ function buildRouteUrl(path: string, query: Record<string, RouteQueryValue> = {}
15
15
  return queryString ? `${path}?${queryString}` : path
16
16
  }
17
17
 
18
+ /** 规范化应用内部路由,避免多余的前导斜杠导致 Taro 跳转失败。 */
19
+ function normalizeRouteUrl(url: string): string {
20
+ return `/${url.replace(/^\/+/, '')}`
21
+ }
22
+
23
+ /** 获取当前页面及其跨端查询参数。 */
18
24
  function getCurrentRouteUrl(): string {
19
25
  const pages = getCurrentPages()
20
26
  const currentPage = pages[pages.length - 1] as
@@ -26,24 +32,21 @@ function getCurrentRouteUrl(): string {
26
32
  | undefined
27
33
  if (!currentPage?.route) return ''
28
34
  return buildRouteUrl(
29
- `/${currentPage.route}`,
30
- currentPage.options ?? currentPage.$taroParams ?? {},
35
+ normalizeRouteUrl(currentPage.route),
36
+ { ...currentPage.$taroParams, ...currentPage.options },
31
37
  )
32
38
  }
33
39
 
34
40
  /** 保存当前页面路由,登录成功后恢复到用户原来的页面。 */
35
41
  export function saveCurrentRoute(): void {
36
42
  const currentRoute = getCurrentRouteUrl()
37
- if (!currentRoute || currentRoute.startsWith(LOGIN_PAGE)) {
38
- Taro.removeStorageSync(LAST_ROUTE_KEY)
39
- return
40
- }
43
+ if (!currentRoute || currentRoute.startsWith(LOGIN_PAGE)) return
41
44
  Taro.setStorageSync(LAST_ROUTE_KEY, currentRoute)
42
45
  }
43
46
 
44
47
  /** 保存指定页面路由,供登录成功后继续原访问目标。 */
45
48
  export function saveLoginRedirectUrl(url: string): void {
46
- const normalizedUrl = url.startsWith('/') ? url : `/${url}`
49
+ const normalizedUrl = normalizeRouteUrl(url)
47
50
  if (!normalizedUrl.startsWith(LOGIN_PAGE)) Taro.setStorageSync(LAST_ROUTE_KEY, normalizedUrl)
48
51
  }
49
52
 
@@ -54,6 +57,17 @@ export function consumeLoginRedirectUrl(): string {
54
57
  return url
55
58
  }
56
59
 
60
+ /** 登录成功后恢复访问目标,并仅在跳转成功后清理保存的地址。 */
61
+ export async function restoreLoginRedirect(): Promise<void> {
62
+ const savedRoute = Taro.getStorageSync<string>(LAST_ROUTE_KEY) || ''
63
+ const targetRoute = savedRoute ? normalizeRouteUrl(savedRoute) : homeTabPage
64
+ if (targetRoute.startsWith(homeTabPage)) Taro.setStorageSync('SwitchTabIndex', true)
65
+ await Taro.reLaunch({ url: targetRoute })
66
+ if (savedRoute && Taro.getStorageSync<string>(LAST_ROUTE_KEY) === savedRoute) {
67
+ Taro.removeStorageSync(LAST_ROUTE_KEY)
68
+ }
69
+ }
70
+
57
71
  /** 跳转到登录页,并在跳转前记录当前页面。 */
58
72
  export function navigateToLogin(redirectUrl?: string): void {
59
73
  if (typeof redirectUrl === 'string' && redirectUrl) saveLoginRedirectUrl(redirectUrl)
@@ -1,6 +1,6 @@
1
1
  .home-page {
2
2
  min-height: 100vh;
3
- padding: 44px 28px 156px;
3
+ padding: 44px 28px 56px;
4
4
  box-sizing: border-box;
5
5
  background-color: #f7f7f8;
6
6
  }
@@ -37,6 +37,7 @@
37
37
  }
38
38
 
39
39
  .home-title {
40
+ margin-top: 8px;
40
41
  color: #fff;
41
42
  font-size: 40px;
42
43
  font-weight: 600;
@@ -1,11 +1,9 @@
1
1
  import { Image, Text, View } from '@tarojs/components'
2
2
  import { useLoad } from '@tarojs/taro'
3
- import { resolveBundledAsset } from '../../../module'
3
+ import defaultLogo from '@liujitcn/kratos-taro-app-core/static/images/logo_icon.png'
4
4
  import { useSettingStore } from '../../../stores'
5
5
  import './index.scss'
6
6
 
7
- const defaultLogo = resolveBundledAsset('static/images/logo_icon.png')
8
-
9
7
  /** 应用首页。 */
10
8
  export default function HomePage() {
11
9
  const settings = useSettingStore((state) => state.data)
@@ -1,12 +1,12 @@
1
1
  import { Button, Image, Input, Text, View } from '@tarojs/components'
2
2
  import Taro, { useLoad } from '@tarojs/taro'
3
3
  import { useMemo, useRef, useState } from 'react'
4
+ import defaultLogo from '@liujitcn/kratos-taro-app-core/static/images/logo_icon.png'
4
5
  import { defLoginService } from '../../../api/base/login'
5
- import { resolveBundledAsset } from '../../../module'
6
6
  import { navigateAppRoute } from '../../../navigation'
7
7
  import type { LoginRequest } from '../../../rpc/base/v1/login'
8
8
  import { useSettingStore, useUserStore } from '../../../stores'
9
- import { consumeLoginRedirectUrl, homeTabPage } from '../../../utils/navigation'
9
+ import { restoreLoginRedirect } from '../../../utils/navigation'
10
10
  import { encryptPassword, PASSWORD_CRYPTO_SCENE } from '../../../utils/passwordCrypto'
11
11
  import BehaviorCaptcha from './components/BehaviorCaptcha'
12
12
  import type {
@@ -17,7 +17,6 @@ import './login.scss'
17
17
 
18
18
  const behaviorCaptchaTypes = new Set(['slide', 'click', 'rotate'])
19
19
  const wechatMiniProvider = 'wechatmini'
20
- const defaultLogo = resolveBundledAsset('static/images/logo_icon.png')
21
20
 
22
21
  type BehaviorPayload = BehaviorCaptchaData & {
23
22
  type?: string
@@ -246,14 +245,12 @@ export default function LoginPage() {
246
245
  return result.confirm
247
246
  }
248
247
 
248
+ /** 完成用户资料加载并恢复登录前页面。 */
249
249
  const loginSuccess = async () => {
250
250
  await userStore.getUserProfile()
251
251
  await Taro.showToast({ icon: 'success', title: '登录成功' })
252
- setTimeout(() => {
253
- const lastRoute = consumeLoginRedirectUrl() || homeTabPage
254
- if (lastRoute.startsWith(homeTabPage)) Taro.setStorageSync('SwitchTabIndex', true)
255
- void Taro.reLaunch({ url: lastRoute })
256
- }, 500)
252
+ await new Promise<void>((resolve) => setTimeout(resolve, 500))
253
+ await restoreLoginRedirect()
257
254
  }
258
255
 
259
256
  const validateLoginForm = async () => {
@@ -1,11 +1,14 @@
1
1
  import { Button, Text, View, WebView } from '@tarojs/components'
2
2
  import Taro, { useLoad } from '@tarojs/taro'
3
- import { useState } from 'react'
3
+ import { useEffect, useState } from 'react'
4
4
  import './webview.scss'
5
5
 
6
6
  /** 统一外链承载页。 */
7
7
  export default function WebViewPage() {
8
8
  const [url, setUrl] = useState('')
9
+ const [iframeLoaded, setIframeLoaded] = useState(false)
10
+ const [iframeTimedOut, setIframeTimedOut] = useState(false)
11
+ const isH5 = process.env.TARO_ENV === 'h5'
9
12
 
10
13
  useLoad((query) => {
11
14
  const nextUrl = decodeURIComponent(query?.url || '')
@@ -14,21 +17,35 @@ export default function WebViewPage() {
14
17
  if (title) void Taro.setNavigationBarTitle({ title })
15
18
  })
16
19
 
17
- if (!url) {
18
- return (
19
- <View className='webview-container'>
20
- <View className='webview-empty'>
21
- <Text className='webview-empty__title'>链接无法打开</Text>
22
- <Text className='webview-empty__desc'>缺少有效链接地址</Text>
23
- </View>
24
- </View>
25
- )
26
- }
20
+ useEffect(() => {
21
+ if (!isH5 || !url) return undefined
22
+ setIframeLoaded(false)
23
+ setIframeTimedOut(false)
24
+ const timer = window.setTimeout(() => setIframeTimedOut(true), 800)
25
+ return () => window.clearTimeout(timer)
26
+ }, [isH5, url])
27
+
28
+ const showFallback = !url || (isH5 && iframeTimedOut && !iframeLoaded)
29
+ const emptyDescription = url
30
+ ? '当前 H5 页面可能被目标站点限制嵌入'
31
+ : '缺少有效链接地址'
27
32
 
28
33
  return (
29
34
  <View className='webview-container'>
30
- <WebView src={url} onMessage={(event) => console.log('收到H5消息:', event.detail)} />
31
- {process.env.TARO_ENV === 'h5' ? (
35
+ {url ? (
36
+ <WebView
37
+ src={url}
38
+ onLoad={() => setIframeLoaded(true)}
39
+ onMessage={(event) => console.log('收到H5消息:', event.detail)}
40
+ />
41
+ ) : null}
42
+ {showFallback ? (
43
+ <View className='webview-empty'>
44
+ <Text className='webview-empty__title'>链接无法打开</Text>
45
+ <Text className='webview-empty__desc'>{emptyDescription}</Text>
46
+ </View>
47
+ ) : null}
48
+ {isH5 && url && iframeTimedOut ? (
32
49
  <Button
33
50
  className='webview-open-button'
34
51
  onClick={() => window.open(url, '_blank', 'noopener,noreferrer')}