@liujitcn/kratos-taro-app-core 0.0.83 → 0.0.85

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
@@ -12,6 +12,8 @@
12
12
  - 登录阶段支持 TOTP、WebAuthn 和一次性恢复码校验。
13
13
  - 公开 Base MFA 请求及通用密码弹窗、绑定面板和恢复码弹窗,供登录页与业务模块复用同一套绑定界面。
14
14
  - 接口驱动的逻辑路由、访问模式、自绘 tabBar 和稳定 `viewKey`。
15
+ - tab 切换统一使用普通页面路由:H5 替换当前路由,微信端复用页面栈,栈满时替换栈顶,避免整栈重建。
16
+ - runner 在编译配置中移除宿主的原生 `tabBar`,退出后恢复宿主原文件;导航按模块注册的 `viewKey` 解析物理页面,支持业务模块覆盖首页和扩展 tab。
15
17
  - 页面静态资源 export、动态资源公共路径解析,以及 H5 默认导航栏兼容框架。
16
18
  - 首页、登录、协议、WebView、启动/错误状态页面。
17
19
  - H5 与微信小程序共用的模块页面装配 runner。
@@ -28,6 +30,37 @@
28
30
 
29
31
  业务模块只能通过这些 exports 使用底座,不得相对引用 core 源码。RPC 由 `make -C frontend ts-taro-app` 生成,不能手工编辑。
30
32
 
33
+ ## 通用顶部导航
34
+
35
+ 所有自定义顶部导航统一使用公开组件 `KratosNavBar`,页面只提供标题和业务操作,不再定义导航高度、安全区、标题居中、按钮尺寸或微信胶囊避让。普通页与 tabBar 页的标题栏使用相同高度,按钮显隐不会改变标题位置。页面外层只维护水平与底部留白,顶部留白由组件处理;tabBar 页的导航栏还统一预留内容间距。
36
+
37
+ ```tsx
38
+ import { KratosNavBar } from '@liujitcn/kratos-taro-app-core'
39
+
40
+ // 普通页默认带返回,无法返回时回到首页。
41
+ <KratosNavBar title='个人信息' />
42
+ // tabBar 页默认不带返回,也可以不传操作按钮。
43
+ <KratosNavBar title='首页' variant='tab' />
44
+ <KratosNavBar
45
+ title='我的'
46
+ variant='tab'
47
+ action={{ icon: 'settings', label: '设置', onClick: openSettings }}
48
+ />
49
+ ```
50
+
51
+ `back` 可显式控制返回按钮,`onBack` 可覆盖默认返回操作。`action` 包含 `label`、可选 `icon`、可选 `display` 和 `onClick`;`display` 支持 `icon`(纯图标)、`text`(纯文字)和 `both`(图标加文字),默认有图标时显示图标,否则显示文字。图标支持内置 `settings`、`read-all`、`history`,以及自定义图片地址。不传 `action` 时不渲染操作按钮。三种按钮形式共用相同高度;纯图标保持方形,文字与组合按钮按内容增宽,空间不足时省略文字。组件及 `KratosNavBarProps`、`KratosNavBarAction` 类型均从包根公开导出;业务模块应传入翻译后的标题和按钮名称。
52
+
53
+ ```ts
54
+ import type { KratosNavBarAction } from '@liujitcn/kratos-taro-app-core'
55
+
56
+ // 纯图标
57
+ const iconAction: KratosNavBarAction = { icon: 'settings', label: '设置', display: 'icon', onClick: openSettings }
58
+ // 纯文字
59
+ const textAction: KratosNavBarAction = { label: '全部已读', display: 'text', onClick: markAllRead }
60
+ // 图标加文字
61
+ const combinedAction: KratosNavBarAction = { icon: 'history', label: '历史', display: 'both', onClick: openHistory }
62
+ ```
63
+
31
64
  ## Runner 协议
32
65
 
33
66
  模块必须同时提供运行时定义和 `./build` 导出。构建入口返回模块名称、包根目录与页面清单;runner 根据宿主的静态 `module-manifest.ts` 解析模块顺序,扫描页面并创建事务。
package/dist/runner.mjs CHANGED
@@ -67,6 +67,7 @@ async function main() {
67
67
  if (options.cleanupOnly) return;
68
68
  const originalAppConfig = readFileSync(appConfigFile, "utf8");
69
69
  const manifest = JSON.parse(readFileSync(baseConfigFile, "utf8"));
70
+ delete manifest.tabBar;
70
71
  const modules = await loadBuildModules(hostRoot, resolve(inputDir, "module-manifest.ts"));
71
72
  const pageMap = /* @__PURE__ */ new Map();
72
73
  modules.forEach((module) => {
package/package.json CHANGED
@@ -7,7 +7,7 @@
7
7
  "type": "git",
8
8
  "url": "git@github.com:liujitcn/kratos-admin.git"
9
9
  },
10
- "version": "0.0.83",
10
+ "version": "0.0.85",
11
11
  "type": "module",
12
12
  "files": [
13
13
  "dist",
@@ -0,0 +1,100 @@
1
+ .kratos-nav-bar {
2
+ flex-shrink: 0;
3
+ box-sizing: border-box;
4
+ padding-top: calc(env(safe-area-inset-top) + 24px);
5
+ }
6
+
7
+ .kratos-nav-bar--tab {
8
+ margin-bottom: 16px;
9
+ }
10
+
11
+ .kratos-nav-bar__row {
12
+ position: relative;
13
+ display: flex;
14
+ align-items: center;
15
+ gap: 16px;
16
+ height: 76px;
17
+ }
18
+
19
+ .kratos-nav-bar__title {
20
+ position: absolute;
21
+ top: 50%;
22
+ left: 50%;
23
+ max-width: calc(100% - 190px);
24
+ overflow: hidden;
25
+ color: var(--kratos-color-text);
26
+ font-size: 16PX;
27
+ font-weight: 700;
28
+ line-height: 22PX;
29
+ text-overflow: ellipsis;
30
+ white-space: nowrap;
31
+ transform: translate(-50%, -50%);
32
+ }
33
+
34
+ .kratos-nav-bar__button {
35
+ display: flex;
36
+ flex: 0 0 76px;
37
+ align-items: center;
38
+ justify-content: center;
39
+ box-sizing: border-box;
40
+ width: 76px;
41
+ height: 76px;
42
+ border: 1PX solid var(--kratos-color-border);
43
+ border-radius: 24px;
44
+ background: var(--kratos-color-surface);
45
+ color: var(--kratos-color-text);
46
+ }
47
+
48
+ .kratos-nav-bar__back::before {
49
+ width: 16px;
50
+ height: 16px;
51
+ border-left: 2PX solid var(--kratos-color-primary);
52
+ border-bottom: 2PX solid var(--kratos-color-primary);
53
+ content: '';
54
+ transform: rotate(45deg);
55
+ }
56
+
57
+ .kratos-nav-bar__action {
58
+ margin-left: auto;
59
+ }
60
+
61
+ .kratos-nav-bar__action--label {
62
+ flex: 0 1 auto;
63
+ gap: 10px;
64
+ min-width: 76px;
65
+ max-width: calc(50% - 48px);
66
+ width: auto;
67
+ padding: 0 20px;
68
+ }
69
+
70
+ .kratos-nav-bar__icon {
71
+ flex-shrink: 0;
72
+ width: 34px;
73
+ height: 34px;
74
+ }
75
+
76
+ .kratos-nav-bar__label {
77
+ min-width: 0;
78
+ overflow: hidden;
79
+ font-size: 22px;
80
+ line-height: 28px;
81
+ text-overflow: ellipsis;
82
+ white-space: nowrap;
83
+ }
84
+
85
+ .kratos-nav-bar--weapp {
86
+ padding-top: 44PX;
87
+
88
+ .kratos-nav-bar__row {
89
+ height: 88px;
90
+ }
91
+
92
+ /* 原生胶囊占据右侧,操作按钮与返回按钮统一放在左侧。 */
93
+ .kratos-nav-bar__action {
94
+ margin-left: 0;
95
+ }
96
+
97
+ .kratos-nav-bar__title {
98
+ max-width: calc(100% - 190px - var(--kratos-nav-bar-capsule-gap));
99
+ }
100
+ }
@@ -0,0 +1,87 @@
1
+ import { Image, Text, View } from '@tarojs/components'
2
+ import Taro from '@tarojs/taro'
3
+ import type { CSSProperties } from 'react'
4
+ import { useI18n } from '../locales'
5
+ import { useThemeStore } from '../stores'
6
+ import { getMpCapsuleRightGap } from '../utils'
7
+ import { homeTabPage } from '../utils/navigation'
8
+ import './KratosNavBar.scss'
9
+
10
+ /** KratosNavBarAction 导航栏操作按钮,统一渲染尺寸、图标与无障碍文案。 */
11
+ export interface KratosNavBarAction {
12
+ /** 按钮名称,同时用作可见文字与无障碍文案。 */
13
+ label: string
14
+ /** 内置 settings、read-all、history 图标名称,或自定义图片地址。 */
15
+ icon?: string
16
+ /** 显示方式;默认有图标时显示图标,否则显示文字。 */
17
+ display?: 'icon' | 'text' | 'both'
18
+ /** 点击按钮后执行的业务操作。 */
19
+ onClick: () => void
20
+ }
21
+
22
+ /** KratosNavBarProps 普通页面与 tabBar 页面共用的顶部导航属性。 */
23
+ export interface KratosNavBarProps {
24
+ /** 居中显示的页面标题。 */
25
+ title: string
26
+ /** 普通页面默认显示返回按钮,tabBar 页面默认隐藏返回按钮。 */
27
+ variant?: 'page' | 'tab'
28
+ /** 显式覆盖返回按钮的显隐。 */
29
+ back?: boolean
30
+ /** 可选的页面操作按钮。 */
31
+ action?: KratosNavBarAction
32
+ /** 自定义返回操作;不传时返回上一页,失败后回到首页。 */
33
+ onBack?: () => void
34
+ }
35
+
36
+ const iconPaths: Record<string, string> = {
37
+ settings:
38
+ '<circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.6 1.6 0 0 0 .3 1.8 2 2 0 1 1-2.8 2.8 1.6 1.6 0 0 0-2.7 1.1 2 2 0 1 1-4 0 1.6 1.6 0 0 0-2.6-1.1 2 2 0 1 1-2.8-2.8A1.6 1.6 0 0 0 3 13.9a2 2 0 1 1 0-4 1.6 1.6 0 0 0 1.5-2.7 2 2 0 1 1 2.8-2.8A1.6 1.6 0 0 0 10 4.6a2 2 0 1 1 4 0 1.6 1.6 0 0 0 2.7.6 2 2 0 1 1 2.8 2.8 1.6 1.6 0 0 0-.1 2.6a2.2 2.2 0 1 1 0 4.4Z"/>',
39
+ 'read-all': '<path d="m3 12 4 4L17 6m-5 10 9-9"/>',
40
+ history: '<path d="M4 6h16M4 12h16M4 18h16"/>',
41
+ }
42
+
43
+ /** KratosNavBar 统一页面标题、返回与操作按钮,并处理安全区和微信胶囊避让。 */
44
+ export default function KratosNavBar({ title, variant = 'page', back, action, onBack }: KratosNavBarProps) {
45
+ const { t } = useI18n()
46
+ const themeStore = useThemeStore()
47
+ const capsuleGap = getMpCapsuleRightGap()
48
+ const navStyle = { '--kratos-nav-bar-capsule-gap': `${capsuleGap}px` } as CSSProperties
49
+ const showBack = back ?? variant === 'page'
50
+ const iconPath = action?.icon ? iconPaths[action.icon] : undefined
51
+ const color = themeStore.theme === 'light' ? '#102232' : '#e9f4f6'
52
+ const actionIcon = iconPath
53
+ ? `data:image/svg+xml;charset=utf-8,${encodeURIComponent(`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round">${iconPath}</svg>`)}`
54
+ : action?.icon
55
+ const actionDisplay = action?.display ?? (actionIcon ? 'icon' : 'text')
56
+ const showActionIcon = !!actionIcon && actionDisplay !== 'text'
57
+ const showActionLabel = !actionIcon || actionDisplay !== 'icon'
58
+
59
+ /** goBack 优先执行页面返回操作,否则返回上一页并在失败时回到首页。 */
60
+ function goBack() {
61
+ if (onBack) {
62
+ onBack()
63
+ return
64
+ }
65
+ void Taro.navigateBack().catch(() => Taro.reLaunch({ url: homeTabPage }))
66
+ }
67
+
68
+ return (
69
+ <View
70
+ className={`kratos-nav-bar${variant === 'tab' ? ' kratos-nav-bar--tab' : ''}${process.env.TARO_ENV === 'weapp' ? ' kratos-nav-bar--weapp' : ''}`}
71
+ style={navStyle}
72
+ >
73
+ <View className='kratos-nav-bar__row'>
74
+ {showBack && (
75
+ <View className='kratos-nav-bar__button kratos-nav-bar__back' aria-label={t('common.action.back')} onClick={goBack} />
76
+ )}
77
+ <View className='kratos-nav-bar__title'>{title}</View>
78
+ {action && (
79
+ <View className={`kratos-nav-bar__button kratos-nav-bar__action${showActionLabel ? ' kratos-nav-bar__action--label' : ''}`} aria-label={action.label} onClick={action.onClick}>
80
+ {showActionIcon && <Image className='kratos-nav-bar__icon' src={actionIcon} mode='aspectFit' aria-hidden='true' />}
81
+ {showActionLabel && <Text className='kratos-nav-bar__label'>{action.label}</Text>}
82
+ </View>
83
+ )}
84
+ </View>
85
+ </View>
86
+ )
87
+ }
package/src/index.ts CHANGED
@@ -44,3 +44,6 @@ export { default as PasswordVerifyDialog } from './components/PasswordVerifyDial
44
44
  export type { PasswordVerifyDialogProps } from './components/PasswordVerifyDialog'
45
45
  export { default as KratosEmpty } from './components/KratosEmpty'
46
46
  export type { KratosEmptyProps } from './components/KratosEmpty'
47
+ /** KratosNavBar 普通页面与 tabBar 页面共用的顶部导航栏。 */
48
+ export { default as KratosNavBar } from './components/KratosNavBar'
49
+ export type { KratosNavBarAction, KratosNavBarProps } from './components/KratosNavBar'
package/src/navigation.ts CHANGED
@@ -197,8 +197,6 @@ const defaultMenuTitleKeys: Record<string, string> = {
197
197
  }
198
198
 
199
199
  let tabNavigationTarget: string | undefined
200
- const nativeTabViewKeys = ['HOME', 'PROFILE_HOME']
201
- const resetTabViewKeys = ['MESSAGE_INBOX']
202
200
 
203
201
  function localizedDefaultAppMenus(): AppMenu[] {
204
202
  return defaultAppMenus.map((menu) => ({
@@ -325,7 +323,7 @@ export function navigateAppView(viewKey: string, params: Record<string, string>
325
323
  navigateAppRoute(`${path}${query ? `?${query}` : ''}`)
326
324
  }
327
325
 
328
- /** 自绘 tab 在 H5 直接切换,微信端优先复用原生 tab 或页面栈。 */
326
+ /** 切换自绘 tab:H5 替换路由,微信端复用页面栈,避免重建整个页面栈。 */
329
327
  function navigateTabRoute(url: string): void {
330
328
  const targetRoute = url.slice(1).split('?', 1)[0].replace(/^\/+/, '')
331
329
  const pages = getCurrentPages()
@@ -338,41 +336,17 @@ function navigateTabRoute(url: string): void {
338
336
  if (tabNavigationTarget === targetRoute) tabNavigationTarget = undefined
339
337
  }
340
338
  if (process.env.TARO_ENV === 'h5') {
341
- void Taro.redirectTo({ url }).then(release, () => {
342
- void Taro.reLaunch({ url }).then(release, release)
343
- })
339
+ void Taro.redirectTo({ url }).then(release, release)
344
340
  return
345
341
  }
346
- if (nativeTabViewKeys.some((viewKey) => resolveStaticView(viewKey) === targetRoute)) {
347
- void Taro.switchTab({ url })
348
- .then(release)
349
- .catch(() => {
350
- navigateTabRouteInStack(url, targetRoute, pages, currentIndex, release)
351
- })
352
- return
353
- }
354
- if (resetTabViewKeys.some((viewKey) => resolveStaticView(viewKey) === targetRoute)) {
355
- void Taro.reLaunch({ url }).then(release, release)
342
+ const targetIndex = pages.findIndex((page) => page.route?.replace(/^\/+/, '') === targetRoute)
343
+ if (targetIndex >= 0 && targetIndex < currentIndex) {
344
+ void Taro.navigateBack({ delta: currentIndex - targetIndex }).then(release, release)
356
345
  return
357
346
  }
358
- navigateTabRouteInStack(url, targetRoute, pages, currentIndex, release)
359
- }
360
-
361
- /** 在没有原生 tab 路由的动态菜单上复用页面栈。 */
362
- function navigateTabRouteInStack(
363
- url: string,
364
- targetRoute: string,
365
- pages: ReturnType<typeof getCurrentPages>,
366
- currentIndex: number,
367
- release: () => void,
368
- ): void {
369
- const targetIndex = pages.findIndex((page) => page.route?.replace(/^\/+/, '') === targetRoute)
370
- const navigation =
371
- targetIndex >= 0 && targetIndex < currentIndex
372
- ? Taro.navigateBack({ delta: currentIndex - targetIndex })
373
- : Taro.navigateTo({ url })
374
- void navigation.then(release).catch(() => {
375
- void Taro.reLaunch({ url }).then(release, release)
347
+ void Taro.navigateTo({ url }).then(release, () => {
348
+ // 小程序页面栈满时只替换栈顶,保留其他 tab 的已加载页面。
349
+ void Taro.redirectTo({ url }).then(release, release)
376
350
  })
377
351
  }
378
352
 
package/src/runner.ts CHANGED
@@ -69,6 +69,8 @@ async function main(): Promise<void> {
69
69
 
70
70
  const originalAppConfig = readFileSync(appConfigFile, 'utf8')
71
71
  const manifest = JSON.parse(readFileSync(baseConfigFile, 'utf8')) as AppManifest
72
+ // 所有宿主统一使用 KratosTabBar,原生 tab 声明会限制普通页面跳转并要求额外组件。
73
+ delete manifest.tabBar
72
74
  const modules = await loadBuildModules(hostRoot, resolve(inputDir, 'module-manifest.ts'))
73
75
  const pageMap = new Map<string, PageEntry>()
74
76
  modules.forEach((module) => {
@@ -105,88 +105,6 @@
105
105
  box-shadow: var(--kratos-glow);
106
106
  }
107
107
 
108
- /* 顶栏 */
109
- .k-page-nav {
110
- position: relative;
111
- display: flex;
112
- align-items: center;
113
- justify-content: space-between;
114
- box-sizing: border-box;
115
- height: calc(88px + env(safe-area-inset-top));
116
- padding-top: env(safe-area-inset-top);
117
- }
118
- .k-page-nav--weapp {
119
- height: calc(88px + 44PX);
120
- padding-top: 44PX;
121
- }
122
- .k-page-nav__back,
123
- .k-page-nav__side {
124
- display: flex;
125
- align-items: center;
126
- justify-content: center;
127
- flex: 0 0 76px;
128
- width: 76px;
129
- height: 76px;
130
- }
131
- .k-page-nav__back {
132
- border: 1PX solid var(--kratos-color-border);
133
- border-radius: 24px;
134
- background: var(--kratos-color-surface);
135
- }
136
- .k-page-nav__back::before {
137
- width: 16px;
138
- height: 16px;
139
- border-left: 2PX solid var(--kratos-color-primary);
140
- border-bottom: 2PX solid var(--kratos-color-primary);
141
- content: '';
142
- transform: rotate(45deg);
143
- }
144
- .k-page-nav__title {
145
- position: absolute;
146
- left: 50%;
147
- max-width: calc(100% - 190px);
148
- overflow: hidden;
149
- color: var(--kratos-color-text);
150
- font-size: 16PX;
151
- font-weight: 700;
152
- text-overflow: ellipsis;
153
- transform: translateX(-50%);
154
- white-space: nowrap;
155
- }
156
- .k-top {
157
- position: relative;
158
- display: flex;
159
- align-items: center;
160
- /* 标题绝对居中后,流内子元素(右侧动作按钮)统一靠右排布 */
161
- justify-content: flex-end;
162
- gap: 16px;
163
- /* weapp 原生胶囊避让:页面通过 getMpCapsuleRightGap() 写入该变量,
164
- 其余平台未设置时回退 0,不影响布局。 */
165
- padding-right: var(--mp-capsule-gap, 0px);
166
- }
167
- /* #ifdef weapp */
168
- /* 双写类名提高优先级:页面级 padding 简写(如 .message-header)不得压掉导航带 */
169
- .k-top.k-top {
170
- /* 小程序右上角是原生胶囊:操作按钮靠左,标题行与胶囊同行(同 k-page-nav--weapp 几何) */
171
- justify-content: flex-start;
172
- height: calc(88px + 44PX);
173
- padding-top: 44PX;
174
- padding-right: 0;
175
- }
176
- /* #endif */
177
- .k-top__title {
178
- position: absolute;
179
- left: 50%;
180
- max-width: calc(100% - 190px);
181
- overflow: hidden;
182
- color: var(--kratos-color-text);
183
- font-size: 16PX;
184
- font-weight: 700;
185
- text-overflow: ellipsis;
186
- transform: translateX(-50%);
187
- white-space: nowrap;
188
- }
189
-
190
108
  /* 方形图标按钮 + 角标 */
191
109
  .k-ic {
192
110
  position: relative;
@@ -9,41 +9,10 @@ page {
9
9
  background: var(--kratos-bg-gradient);
10
10
  }
11
11
 
12
- .home-page--weapp .home-content {
13
- padding-top: 0;
14
- }
15
-
16
- /* 小程序端首页标题占导航带:与 k-page-nav--weapp 同几何,标题与胶囊同行 */
17
- .home-page--weapp .home-top {
18
- box-sizing: border-box;
19
- height: calc(88px + 44PX);
20
- padding: 44PX 4px 0;
21
- display: flex;
22
- align-items: center;
23
- justify-content: center;
24
- }
25
-
26
- .home-page--weapp .home-top__title {
27
- margin-top: 0;
28
- }
29
-
30
12
  .home-content {
31
13
  position: relative;
32
14
  z-index: 1;
33
- padding: calc(env(safe-area-inset-top) + 20px) 32px 220px;
34
- }
35
-
36
- .home-top {
37
- padding: 8px 4px 24px;
38
- }
39
-
40
- /* 页面大标题:与其他页面导航标题同规格,向原生导航栏看齐 */
41
- .home-top__title {
42
- margin-top: 14px;
43
- color: var(--kratos-color-text);
44
- font-size: 16PX;
45
- font-weight: 700;
46
- text-align: center;
15
+ padding: 0 20px 220px;
47
16
  }
48
17
 
49
18
  .home-hero {
@@ -5,9 +5,10 @@ import defaultLogo from '@liujitcn/kratos-taro-app-core/static/images/logo_icon.
5
5
  import { useSettingStore, useThemeStore } from '../../../stores'
6
6
  import { formatSrc } from '../../../utils'
7
7
  import './index.scss'
8
+ import KratosNavBar from '../../../components/KratosNavBar'
8
9
  import { useI18n } from '../../../locales'
9
10
 
10
- /** 应用首页。 */
11
+ /** HomePage 展示应用首页,并复用固定高度的 Tab 标题栏。 */
11
12
  export default function HomePage() {
12
13
  const { t } = useI18n()
13
14
  const themeStore = useThemeStore()
@@ -41,15 +42,13 @@ export default function HomePage() {
41
42
  ]
42
43
  return (
43
44
  <View
44
- className={`home-page${process.env.TARO_ENV === 'weapp' ? ' home-page--weapp' : ''} ${
45
+ className={`home-page ${
45
46
  themeStore.theme === 'light' ? 'theme-light' : 'theme-dark'
46
47
  }`}
47
48
  >
48
49
  <View className='k-grid-bg' aria-hidden />
49
50
  <View className='home-content'>
50
- <View className='home-top'>
51
- <View className='home-top__title'>{mainTitle}</View>
52
- </View>
51
+ <KratosNavBar title={mainTitle} variant='tab' />
53
52
 
54
53
  <View className='k-glass k-glass--lit home-hero'>
55
54
  <View className='home-hero__main'>
@@ -1,6 +1,6 @@
1
1
  .protocol-page {
2
2
  min-height: 100vh;
3
- padding: 24px;
3
+ padding: 0 24px 24px;
4
4
  box-sizing: border-box;
5
5
  background: var(--kratos-bg-gradient);
6
6
  }
@@ -3,7 +3,7 @@ import Taro, { useLoad } from '@tarojs/taro'
3
3
  import { useState } from 'react'
4
4
  import { useSettingStore, useThemeStore } from '../../../stores'
5
5
  import { useI18n } from '../../../locales'
6
- import { homeTabPage } from '../../../utils/navigation'
6
+ import KratosNavBar from '../../../components/KratosNavBar'
7
7
  import './protocal.scss'
8
8
 
9
9
  const protocolClassMap: Record<string, string> = {
@@ -39,7 +39,7 @@ function decorateProtocolContent(html: string): string {
39
39
  )
40
40
  }
41
41
 
42
- /** 服务条款与隐私协议页。 */
42
+ /** ProtocolPage 展示服务条款与隐私协议。 */
43
43
  export default function ProtocolPage() {
44
44
  const { t } = useI18n()
45
45
  const themeStore = useThemeStore()
@@ -68,18 +68,9 @@ export default function ProtocolPage() {
68
68
  })
69
69
  })
70
70
 
71
- /** 返回来源页,页面栈为空时回到首页。 */
72
- const onBack = () => {
73
- void Taro.navigateBack({ fail: () => void Taro.reLaunch({ url: homeTabPage }) })
74
- }
75
-
76
71
  return (
77
72
  <View className={`protocol-page ${themeStore.theme === 'light' ? 'theme-light' : 'theme-dark'}`}>
78
- <View className={`k-page-nav ${process.env.TARO_ENV === 'weapp' ? 'k-page-nav--weapp' : ''}`}>
79
- <View className='k-page-nav__back' aria-label={t('common.action.back')} onClick={onBack} />
80
- <View className='k-page-nav__title'>{pageTitle}</View>
81
- <View className='k-page-nav__side' aria-hidden />
82
- </View>
73
+ <KratosNavBar title={pageTitle} />
83
74
  <View className='protocol-card'>
84
75
  <RichText className='protocol-content' nodes={content} />
85
76
  </View>