@liujitcn/kratos-taro-app-core 0.0.81 → 0.0.82

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/dist/build.mjs CHANGED
@@ -7,7 +7,6 @@ var corePages = {
7
7
  "pages/index/index": {
8
8
  style: {
9
9
  navigationStyle: "custom",
10
- navigationBarTextStyle: "white",
11
10
  navigationBarTitleText: ""
12
11
  }
13
12
  },
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.81",
10
+ "version": "0.0.82",
11
11
  "type": "module",
12
12
  "files": [
13
13
  "dist",
@@ -0,0 +1,26 @@
1
+ import type { ReactNode } from 'react'
2
+ import { Text, View } from '@tarojs/components'
3
+
4
+ /** 空态组件属性。 */
5
+ export interface KratosEmptyProps {
6
+ /** 图标节点(如 <UniIcon />),不传则不渲染图标位。 */
7
+ icon?: ReactNode
8
+ /** 主文案。 */
9
+ title?: string
10
+ /** 次级提示文案。 */
11
+ description?: string
12
+ /** 操作区内容(如引导按钮)。 */
13
+ children?: ReactNode
14
+ }
15
+
16
+ /** 标准空态组件:图标 + 主文案 + 提示文案 + 可选操作区。 */
17
+ export default function KratosEmpty({ icon, title, description, children }: KratosEmptyProps) {
18
+ return (
19
+ <View className='k-empty'>
20
+ {icon && <View className='k-empty__icon'>{icon}</View>}
21
+ {title && <Text className='k-empty__title'>{title}</Text>}
22
+ {description && <Text className='k-empty__desc'>{description}</Text>}
23
+ {children}
24
+ </View>
25
+ )
26
+ }
@@ -30,21 +30,8 @@
30
30
  line-height: 12PX;
31
31
  }
32
32
 
33
- /* 设计稿:激活项顶部霓虹发光条 */
34
- .kratos-tab-bar__item--active::before {
35
- position: absolute;
36
- top: 0;
37
- left: 50%;
38
- width: 26px;
39
- height: 3px;
40
- border-radius: 3px;
41
- background: var(--kratos-tab-bar-active-color);
42
- box-shadow: 0 0 12px var(--kratos-tab-bar-active-color);
43
- content: '';
44
- transform: translateX(-50%);
45
- }
46
-
47
33
  .kratos-tab-bar__icon {
34
+ flex-shrink: 0;
48
35
  width: 28PX;
49
36
  height: 28PX;
50
37
  margin-bottom: 4PX;
package/src/index.ts CHANGED
@@ -42,3 +42,5 @@ export * from './utils/navigation'
42
42
  export * from './utils/secretCrypto'
43
43
  export { default as PasswordVerifyDialog } from './components/PasswordVerifyDialog'
44
44
  export type { PasswordVerifyDialogProps } from './components/PasswordVerifyDialog'
45
+ export { default as KratosEmpty } from './components/KratosEmpty'
46
+ export type { KratosEmptyProps } from './components/KratosEmpty'
@@ -34,18 +34,20 @@
34
34
  "core.file.temporary_path_required": "Use a temporary file path when uploading from a Mini Program",
35
35
  "core.file.upload_failed": "Upload failed",
36
36
  "core.home.account_capability": "Account features",
37
- "core.home.account_capability_description": "Profile, app settings, and AI assistant access are included",
37
+ "core.home.account_capability_description": "Sign-in, profile, multi-factor authentication, and app settings out of the box",
38
38
  "core.home.cross_platform": "Cross-platform pages",
39
39
  "core.home.cross_platform_description": "One codebase for H5 and WeChat Mini Program",
40
40
  "core.home.demo_scope": "Overview",
41
+ "core.home.dynamic_config": "Dynamic config",
42
+ "core.home.dynamic_config_description": "Home logo and tagline configurable from the admin console",
41
43
  "core.home.framework": "App framework",
42
44
  "core.home.language": "Language",
43
45
  "core.home.main_title": "Application Home",
46
+ "core.home.message_center": "Message center",
47
+ "core.home.message_center_description": "Unread messages in one inbox with badge reminders",
44
48
  "core.home.state_management": "State management",
45
- "core.home.static_home": "Static home",
46
- "core.home.static_home_description": "This page presents framework details without business APIs",
47
49
  "core.home.style_solution": "Styling",
48
- "core.home.sub_title": "Shared navigation and account experience",
50
+ "core.home.sub_title": "Accounts, messages, and AI assistant in one place",
49
51
  "core.home.tech_stack": "Technology stack",
50
52
  "core.login.agree_prefix": "I have read and agree to the",
51
53
  "core.login.agree_separator": "and",
@@ -34,18 +34,20 @@
34
34
  "core.file.temporary_path_required": "ミニ プログラムをアップロードするには、一時ファイル パスを使用してください。",
35
35
  "core.file.upload_failed": "アップロードに失敗しました",
36
36
  "core.home.account_capability": "アカウントの機能",
37
- "core.home.account_capability_description": "個人情報、アプリケーション設定、スマートアシスタントの入り口が予約されています",
37
+ "core.home.account_capability_description": "ログイン、プロフィール、多要素認証、アプリ設定を同梱",
38
38
  "core.home.cross_platform": "クロスエンド ページ",
39
39
  "core.home.cross_platform_description": "同じページ コード セットが H5 および WeChat ミニ プログラムに適応します",
40
40
  "core.home.demo_scope": "機能概要",
41
+ "core.home.dynamic_config": "動的設定",
42
+ "core.home.dynamic_config_description": "ホームのロゴとキャッチコピーは管理画面から変更可能",
41
43
  "core.home.framework": "アプリケーション フレームワーク",
42
44
  "core.home.language": "開発言語",
43
45
  "core.home.main_title": "アプリケーションホーム",
46
+ "core.home.message_center": "メッセージセンター",
47
+ "core.home.message_center_description": "未読メッセージを一括管理し、バッジでお知らせ",
44
48
  "core.home.state_management": "ステータス管理",
45
- "core.home.static_home": "静的ホームページ",
46
- "core.home.static_home_description": "現在のホームページはフレーム情報を表示するために使用されており、ビジネス インターフェイスには依存していません",
47
49
  "core.home.style_solution": "スタイル スキーム",
48
- "core.home.sub_title": "ユニバーサル ナビゲーションとパーソナル センターのエクスペリエンスを維持します",
50
+ "core.home.sub_title": "アカウント・メッセージ・スマートアシスタントをひとつに",
49
51
  "core.home.tech_stack": "現在のテクノロジースタック",
50
52
  "core.login.agree_prefix": "読んで同意します",
51
53
  "core.login.agree_separator": "および",
@@ -34,18 +34,20 @@
34
34
  "core.file.temporary_path_required": "小程序端请使用文件临时路径上传",
35
35
  "core.file.upload_failed": "上传失败",
36
36
  "core.home.account_capability": "账户能力",
37
- "core.home.account_capability_description": "个人资料、应用设置与智能助手入口已保留",
37
+ "core.home.account_capability_description": "登录、个人资料、多因素认证与应用设置开箱即用",
38
38
  "core.home.cross_platform": "跨端页面",
39
39
  "core.home.cross_platform_description": "同一套页面代码适配 H5 和微信小程序",
40
40
  "core.home.demo_scope": "功能概览",
41
+ "core.home.dynamic_config": "动态配置",
42
+ "core.home.dynamic_config_description": "首页 Logo 与标语支持后台远程配置",
41
43
  "core.home.framework": "应用框架",
42
44
  "core.home.language": "开发语言",
43
45
  "core.home.main_title": "应用首页",
46
+ "core.home.message_center": "消息中心",
47
+ "core.home.message_center_description": "未读消息统一收纳,红点提醒不错过",
44
48
  "core.home.state_management": "状态管理",
45
- "core.home.static_home": "静态首页",
46
- "core.home.static_home_description": "当前首页用于展示框架信息,不依赖业务接口",
47
49
  "core.home.style_solution": "样式方案",
48
- "core.home.sub_title": "保留通用导航与个人中心体验",
50
+ "core.home.sub_title": "账号、消息与智能助手,一站触达",
49
51
  "core.home.tech_stack": "当前技术栈",
50
52
  "core.login.agree_prefix": "我已阅读并同意",
51
53
  "core.login.agree_separator": "和",
@@ -34,18 +34,20 @@
34
34
  "core.file.temporary_path_required": "小程式端請使用檔案臨時路徑上傳",
35
35
  "core.file.upload_failed": "上傳失敗",
36
36
  "core.home.account_capability": "賬戶能力",
37
- "core.home.account_capability_description": "個人資料、應用設定與智慧助手入口已保留",
37
+ "core.home.account_capability_description": "登入、個人資料、多因素認證與應用設定開箱即用",
38
38
  "core.home.cross_platform": "跨端頁面",
39
39
  "core.home.cross_platform_description": "同一套頁面程式碼適配 H5 和微信小程式",
40
40
  "core.home.demo_scope": "功能概覽",
41
+ "core.home.dynamic_config": "動態設定",
42
+ "core.home.dynamic_config_description": "首頁 Logo 與標語支援後台遠端設定",
41
43
  "core.home.framework": "應用框架",
42
44
  "core.home.language": "開發語言",
43
45
  "core.home.main_title": "應用首頁",
46
+ "core.home.message_center": "訊息中心",
47
+ "core.home.message_center_description": "未讀訊息統一查看,紅點提醒不錯過",
44
48
  "core.home.state_management": "狀態管理",
45
- "core.home.static_home": "靜態首頁",
46
- "core.home.static_home_description": "當前首頁用於展示框架資訊,不依賴業務介面",
47
49
  "core.home.style_solution": "樣式方案",
48
- "core.home.sub_title": "保留通用導航與個人中心體驗",
50
+ "core.home.sub_title": "帳號、訊息與智慧助手,一站觸達",
49
51
  "core.home.tech_stack": "當前技術棧",
50
52
  "core.login.agree_prefix": "我已閱讀並同意",
51
53
  "core.login.agree_separator": "和",
package/src/navigation.ts CHANGED
@@ -101,6 +101,16 @@ export const defaultAppMenus: AppMenu[] = [
101
101
  icon: 'MESSAGE_DEFAULT',
102
102
  selectedIcon: 'MESSAGE_SELECTED',
103
103
  },
104
+ {
105
+ id: 99050100,
106
+ parentId: 99040000,
107
+ name: 'AppMessageDetail',
108
+ path: 'app/message/detail',
109
+ viewKey: 'MESSAGE_DETAIL',
110
+ title: '',
111
+ access: 'AUTHENTICATED',
112
+ inTabBar: false,
113
+ },
104
114
  {
105
115
  id: 99090000,
106
116
  parentId: APP_MENU_ROOT_ID,
@@ -222,8 +232,9 @@ export async function initializeAppNavigation(): Promise<void> {
222
232
  const cached = readCachedMenus(cacheKey)
223
233
  useAppNavigation.setState(createNavigationState(filterUnavailableMenus(cached ?? localizedDefaultAppMenus()), false))
224
234
  try {
225
- const nextMenus = filterUnavailableMenus(normalizeMenuResponse(await adapter.list()))
226
- validateMenus(nextMenus)
235
+ const nextMenus = validateMenus(
236
+ filterUnavailableMenus(normalizeMenuResponse(await adapter.list())),
237
+ )
227
238
  Taro.setStorageSync(cacheKey, nextMenus)
228
239
  useAppNavigation.setState(createNavigationState(nextMenus, true))
229
240
  } catch (error) {
@@ -240,8 +251,7 @@ function filterUnavailableMenus(nextMenus: AppMenu[]): AppMenu[] {
240
251
 
241
252
  /** 使用已经校验的整份配置进行原子切换。 */
242
253
  export function installAppNavigation(nextMenus: AppMenu[]): void {
243
- validateMenus(nextMenus)
244
- const snapshot = nextMenus.map((menu) => ({ ...menu }))
254
+ const snapshot = validateMenus(nextMenus).map((menu) => ({ ...menu }))
245
255
  Taro.setStorageSync(resolveCacheKey(), snapshot)
246
256
  useAppNavigation.setState(createNavigationState(snapshot, true))
247
257
  }
@@ -382,9 +392,16 @@ function readCachedMenus(cacheKey: string): AppMenu[] | undefined {
382
392
  const cached = Taro.getStorageSync<unknown>(cacheKey)
383
393
  if (!Array.isArray(cached)) return
384
394
  try {
385
- const cachedMenus = cached as AppMenu[]
386
- validateMenus(cachedMenus)
387
- return cachedMenus
395
+ const cachedMenus = (cached as AppMenu[]).map((menu) => ({
396
+ ...menu,
397
+ title:
398
+ menu.viewKey === 'HOME'
399
+ ? t('core.navigation.home')
400
+ : menu.viewKey === 'MESSAGE_INBOX'
401
+ ? t('core.navigation.message')
402
+ : menu.title,
403
+ }))
404
+ return validateMenus(cachedMenus)
388
405
  } catch {
389
406
  Taro.removeStorageSync(cacheKey)
390
407
  }
@@ -403,7 +420,12 @@ function normalizeMenu(item: BaseMenu): AppMenu {
403
420
  name: item.name,
404
421
  path: item.path,
405
422
  viewKey: app?.view_key ?? '',
406
- title: meta?.title ?? '',
423
+ title:
424
+ app?.view_key === 'HOME'
425
+ ? t('core.navigation.home')
426
+ : app?.view_key === 'MESSAGE_INBOX'
427
+ ? t('core.navigation.message')
428
+ : meta?.title ?? '',
407
429
  access: (app?.access ?? 'AUTHENTICATED') as AppMenuAccess,
408
430
  inTabBar: app?.in_tab_bar ?? false,
409
431
  icon: meta?.icon,
@@ -411,11 +433,17 @@ function normalizeMenu(item: BaseMenu): AppMenu {
411
433
  }
412
434
  }
413
435
 
414
- function validateMenus(nextMenus: AppMenu[]): void {
436
+ function validateMenus(nextMenus: AppMenu[]): AppMenu[] {
437
+ // 多端共用后端时菜单可能携带本应用未注册的视图键,过滤跳过而不是让整份菜单失效。
438
+ const knownMenus = nextMenus.filter((menu) => {
439
+ if (resolveStaticView(menu.viewKey)) return true
440
+ console.warn(t('core.navigation.error.view_key', { viewKey: menu.viewKey }))
441
+ return false
442
+ })
415
443
  const ids = new Set<number>()
416
444
  const names = new Set<string>()
417
445
  const paths = new Set<string>()
418
- for (const menu of nextMenus) {
446
+ for (const menu of knownMenus) {
419
447
  if (!Number.isSafeInteger(menu.id) || !menu.name || !menu.path.startsWith('app/')) {
420
448
  throw new Error(t('core.navigation.error.identity'))
421
449
  }
@@ -425,9 +453,6 @@ function validateMenus(nextMenus: AppMenu[]): void {
425
453
  if (!['PUBLIC', 'GUEST_ONLY', 'AUTHENTICATED'].includes(menu.access)) {
426
454
  throw new Error(t('core.navigation.error.access', { access: menu.access }))
427
455
  }
428
- if (!resolveStaticView(menu.viewKey)) {
429
- throw new Error(t('core.navigation.error.view_key', { viewKey: menu.viewKey }))
430
- }
431
456
  if (ids.has(menu.id) || names.has(menu.name) || paths.has(menu.path)) {
432
457
  throw new Error(t('core.navigation.error.unique'))
433
458
  }
@@ -435,8 +460,8 @@ function validateMenus(nextMenus: AppMenu[]): void {
435
460
  names.add(menu.name)
436
461
  paths.add(menu.path)
437
462
  }
438
- const menuMap = new Map(nextMenus.map((menu) => [menu.id, menu]))
439
- for (const menu of nextMenus) {
463
+ const menuMap = new Map(knownMenus.map((menu) => [menu.id, menu]))
464
+ for (const menu of knownMenus) {
440
465
  if (menu.parentId === undefined) {
441
466
  throw new Error(t('core.navigation.error.parent_missing', { name: menu.name }))
442
467
  }
@@ -457,6 +482,7 @@ function validateMenus(nextMenus: AppMenu[]): void {
457
482
  parentId = parent.parentId
458
483
  }
459
484
  }
460
- const tabCount = nextMenus.filter((menu) => menu.parentId === APP_MENU_ROOT_ID).length
485
+ const tabCount = knownMenus.filter((menu) => menu.parentId === APP_MENU_ROOT_ID).length
461
486
  if (tabCount === 1 || tabCount > 5) throw new Error(t('core.navigation.error.tab_count'))
487
+ return knownMenus
462
488
  }
package/src/pages.ts CHANGED
@@ -5,7 +5,6 @@ export const corePages: Record<string, KratosTaroPageConfig> = {
5
5
  'pages/index/index': {
6
6
  style: {
7
7
  navigationStyle: 'custom',
8
- navigationBarTextStyle: 'white',
9
8
  navigationBarTitleText: '',
10
9
  },
11
10
  },
@@ -131,3 +131,16 @@ html.theme-dark .taro__toast > div:last-child {
131
131
  color: var(--kratos-color-text) !important;
132
132
  }
133
133
  /* #endif */
134
+
135
+ /* H5 隐藏滚动条但保留滚动能力,视觉对齐小程序;小程序端滚动条本就不存在,无副作用。 */
136
+ /* #ifdef h5 */
137
+ ::-webkit-scrollbar {
138
+ width: 0;
139
+ height: 0;
140
+ display: none;
141
+ }
142
+
143
+ * {
144
+ scrollbar-width: none;
145
+ }
146
+ /* #endif */
@@ -19,40 +19,6 @@
19
19
  box-shadow: var(--kratos-glow-soft);
20
20
  }
21
21
 
22
- /* 四角 L 形角标:渲染为真实 view 节点(weapp 伪元素仅支持 view) */
23
- .k-cnr {
24
- position: absolute;
25
- width: 24px;
26
- height: 24px;
27
- border: 3px solid var(--kratos-color-primary);
28
- opacity: 0.5;
29
- pointer-events: none;
30
- }
31
- .k-cnr--tl {
32
- top: 16px;
33
- left: 16px;
34
- border-right: 0;
35
- border-bottom: 0;
36
- }
37
- .k-cnr--tr {
38
- top: 16px;
39
- right: 16px;
40
- border-left: 0;
41
- border-bottom: 0;
42
- }
43
- .k-cnr--bl {
44
- bottom: 16px;
45
- left: 16px;
46
- border-right: 0;
47
- border-top: 0;
48
- }
49
- .k-cnr--br {
50
- bottom: 16px;
51
- right: 16px;
52
- border-left: 0;
53
- border-top: 0;
54
- }
55
-
56
22
  /* 装饰网格背景 */
57
23
  .k-grid-bg {
58
24
  position: absolute;
@@ -181,31 +147,44 @@
181
147
  max-width: calc(100% - 190px);
182
148
  overflow: hidden;
183
149
  color: var(--kratos-color-text);
184
- font-size: 36px;
185
- font-weight: 800;
150
+ font-size: 16PX;
151
+ font-weight: 700;
186
152
  text-overflow: ellipsis;
187
153
  transform: translateX(-50%);
188
154
  white-space: nowrap;
189
155
  }
190
156
  .k-top {
157
+ position: relative;
191
158
  display: flex;
192
159
  align-items: center;
193
- justify-content: space-between;
160
+ /* 标题绝对居中后,流内子元素(右侧动作按钮)统一靠右排布 */
161
+ justify-content: flex-end;
194
162
  gap: 16px;
195
163
  /* weapp 原生胶囊避让:页面通过 getMpCapsuleRightGap() 写入该变量,
196
164
  其余平台未设置时回退 0,不影响布局。 */
197
165
  padding-right: var(--mp-capsule-gap, 0px);
198
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 */
199
177
  .k-top__title {
200
- font-size: 38px;
201
- font-weight: 800;
202
- letter-spacing: -0.3px;
178
+ position: absolute;
179
+ left: 50%;
180
+ max-width: calc(100% - 190px);
181
+ overflow: hidden;
203
182
  color: var(--kratos-color-text);
204
- }
205
- .k-top__t2 {
206
- font-size: 32px;
183
+ font-size: 16PX;
207
184
  font-weight: 700;
208
- color: var(--kratos-color-text);
185
+ text-overflow: ellipsis;
186
+ transform: translateX(-50%);
187
+ white-space: nowrap;
209
188
  }
210
189
 
211
190
  /* 方形图标按钮 + 角标 */
@@ -306,12 +285,20 @@
306
285
  box-shadow: var(--kratos-glow-soft);
307
286
  }
308
287
 
309
- /* 过滤芯片 */
288
+ /* 过滤芯片:单行横向滚动,超出隐藏滚动条 */
310
289
  .k-chips {
311
290
  display: flex;
312
- flex-wrap: wrap;
291
+ flex-wrap: nowrap;
313
292
  gap: 16px;
314
293
  margin-bottom: 24px;
294
+ overflow-x: auto;
295
+ scrollbar-width: none;
296
+ }
297
+ .k-chips::-webkit-scrollbar {
298
+ display: none;
299
+ }
300
+ .k-chips .k-ch {
301
+ flex: 0 0 auto;
315
302
  }
316
303
  .k-ch {
317
304
  display: inline-flex;
@@ -437,6 +424,8 @@
437
424
  align-items: center;
438
425
  justify-content: center;
439
426
  gap: 16px;
427
+ /* 各端 H5 原生 button 默认宽度不一,显式通栏保证双端一致 */
428
+ width: 100%;
440
429
  height: 100px;
441
430
  margin-top: 36px;
442
431
  border-radius: 20px;
@@ -734,15 +723,30 @@
734
723
  background: var(--kratos-color-primary-soft);
735
724
  }
736
725
  .k-msg-item__title {
726
+ display: -webkit-box;
727
+ overflow: hidden;
737
728
  font-size: 25px;
738
729
  font-weight: 700;
739
730
  color: var(--kratos-color-text);
731
+ -webkit-box-orient: vertical;
732
+ -webkit-line-clamp: 2;
733
+ word-break: break-word;
740
734
  }
741
- .k-msg-item__meta {
735
+ .k-msg-item__foot {
736
+ display: flex;
737
+ align-items: center;
738
+ gap: 24px;
742
739
  margin-top: 10px;
740
+ }
741
+ .k-msg-item__meta {
742
+ flex: 1;
743
+ min-width: 0;
744
+ overflow: hidden;
743
745
  font-size: 20px;
744
746
  color: var(--kratos-color-text-muted);
745
747
  font-family: var(--kratos-font-mono);
748
+ text-overflow: ellipsis;
749
+ white-space: nowrap;
746
750
  }
747
751
  .k-msg-item__time {
748
752
  flex-shrink: 0;
@@ -763,3 +767,48 @@
763
767
  color: var(--kratos-color-primary);
764
768
  font-family: var(--kratos-font-mono);
765
769
  }
770
+
771
+ /* ============================================================
772
+ * 标准空态词汇(列表无数据时统一使用)
773
+ * ============================================================ */
774
+ .k-empty {
775
+ display: flex;
776
+ flex-direction: column;
777
+ align-items: center;
778
+ padding: 140px 32px;
779
+ }
780
+ .k-empty__icon {
781
+ display: flex;
782
+ align-items: center;
783
+ justify-content: center;
784
+ width: 112px;
785
+ height: 112px;
786
+ border-radius: 26px;
787
+ border: 1PX solid var(--kratos-color-border-accent);
788
+ background: var(--kratos-color-primary-soft-2);
789
+ color: var(--kratos-color-primary);
790
+ box-shadow: var(--kratos-glow-soft);
791
+ }
792
+ .k-empty__title {
793
+ margin-top: 28px;
794
+ color: var(--kratos-color-text);
795
+ font-size: 27px;
796
+ font-weight: 700;
797
+ }
798
+ .k-empty__desc {
799
+ margin-top: 12px;
800
+ color: var(--kratos-color-text-muted);
801
+ font-size: 22px;
802
+ font-family: var(--kratos-font-mono);
803
+ text-align: center;
804
+ }
805
+ .k-empty__act {
806
+ margin-top: 32px;
807
+ padding: 16px 40px;
808
+ border-radius: 999px;
809
+ border: 1PX solid var(--kratos-color-border-accent);
810
+ background: var(--kratos-color-primary-soft-2);
811
+ color: var(--kratos-color-primary);
812
+ font-size: 24px;
813
+ font-family: var(--kratos-font-mono);
814
+ }
package/src/utils/http.ts CHANGED
@@ -1,4 +1,4 @@
1
- import Taro from '@tarojs/taro'
1
+ import Taro, { getCurrentPages } from '@tarojs/taro'
2
2
  import {
3
3
  clearToken,
4
4
  getRefreshToken,
@@ -262,19 +262,36 @@ async function refreshAccessToken(): Promise<void> {
262
262
  setTokenExpiresIn(data.expires_in)
263
263
  }
264
264
 
265
+ /** 会话过期弹窗等待时长:H5 端路由切换会自动隐藏模态且 Promise 永不结算,超时释放避免提示永久卡死。 */
266
+ const RELOGIN_PROMPT_TIMEOUT = 60_000
267
+
265
268
  async function promptRelogin(): Promise<void> {
269
+ // 401 来自旧会话的在途请求而当前已是有效登录态时直接忽略,避免刚登录成功被弹窗打断。
270
+ if (hasValidToken()) return
271
+ const pages = getCurrentPages()
272
+ // 登录页自身的请求失效只清理残留登录态,避免确认后重载登录页丢失已填写的表单。
273
+ if (pages[pages.length - 1]?.route === 'pages/login/login') {
274
+ if (getToken() || getRefreshToken() || Taro.getStorageSync('user')) silentClearAuthData()
275
+ return
276
+ }
266
277
  if (isPromptingRelogin) return
267
278
  isPromptingRelogin = true
268
279
  try {
269
280
  const theme = useThemeStore.getState().theme
270
- const modal = await Taro.showModal({
271
- title: t('common.title.notice'),
272
- content: t('core.auth.session_expired'),
273
- showCancel: false,
274
- confirmText: t('core.auth.login_again'),
275
- confirmColor: theme === 'light' ? '#039e8c' : '#28f0d4',
276
- })
277
- if (!modal.confirm) return
281
+ const modal = await Promise.race([
282
+ // 按钮文案复用通用确认键:平台对 confirmText 限制 4 个汉字/8 字符,长文案会被整体拒绝。
283
+ Taro.showModal({
284
+ title: t('common.title.notice'),
285
+ content: t('core.auth.session_expired'),
286
+ showCancel: false,
287
+ confirmText: t('common.action.confirm'),
288
+ confirmColor: theme === 'light' ? '#039e8c' : '#28f0d4',
289
+ }).catch(() => undefined as undefined),
290
+ new Promise<undefined>((resolve) =>
291
+ setTimeout(() => resolve(undefined), RELOGIN_PROMPT_TIMEOUT),
292
+ ),
293
+ ])
294
+ if (!modal?.confirm) return
278
295
  await new Promise((resolve) => setTimeout(resolve, 80))
279
296
  silentClearAuthData()
280
297
  saveCurrentRoute()
@@ -10,8 +10,21 @@ page {
10
10
  }
11
11
 
12
12
  .home-page--weapp .home-content {
13
- /* 44PX 是系统导航栏高度,88px 按设计稿转换为 44 物理像素。 */
14
- padding-top: calc(44PX + 88px);
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;
15
28
  }
16
29
 
17
30
  .home-content {
@@ -24,13 +37,13 @@ page {
24
37
  padding: 8px 4px 24px;
25
38
  }
26
39
 
27
- /* 页面大标题 */
40
+ /* 页面大标题:与其他页面导航标题同规格,向原生导航栏看齐 */
28
41
  .home-top__title {
29
42
  margin-top: 14px;
30
43
  color: var(--kratos-color-text);
31
- font-size: 44px;
32
- font-weight: 800;
33
- letter-spacing: -0.5px;
44
+ font-size: 16PX;
45
+ font-weight: 700;
46
+ text-align: center;
34
47
  }
35
48
 
36
49
  .home-hero {
@@ -13,7 +13,7 @@ export default function HomePage() {
13
13
  const themeStore = useThemeStore()
14
14
  const settings = useSettingStore((state) => state.data)
15
15
  const loadData = useSettingStore((state) => state.loadData)
16
- const mainTitle = settings?.get('mainTitle') || t('core.home.main_title')
16
+ const mainTitle = t('core.navigation.home')
17
17
  const subTitle = settings?.get('subTitle') || t('core.home.sub_title')
18
18
  const configuredAppLogo = settings?.get('appLogo')
19
19
  const configuredLogoUrl = configuredAppLogo ? formatSrc(configuredAppLogo) : defaultLogo
@@ -36,7 +36,8 @@ export default function HomePage() {
36
36
  const demos = [
37
37
  [t('core.home.cross_platform'), t('core.home.cross_platform_description')],
38
38
  [t('core.home.account_capability'), t('core.home.account_capability_description')],
39
- [t('core.home.static_home'), t('core.home.static_home_description')],
39
+ [t('core.home.message_center'), t('core.home.message_center_description')],
40
+ [t('core.home.dynamic_config'), t('core.home.dynamic_config_description')],
40
41
  ]
41
42
  return (
42
43
  <View
@@ -51,10 +52,6 @@ export default function HomePage() {
51
52
  </View>
52
53
 
53
54
  <View className='k-glass k-glass--lit home-hero'>
54
- <View className='k-cnr k-cnr--tl' aria-hidden />
55
- <View className='k-cnr k-cnr--tr' aria-hidden />
56
- <View className='k-cnr k-cnr--bl' aria-hidden />
57
- <View className='k-cnr k-cnr--br' aria-hidden />
58
55
  <View className='home-hero__main'>
59
56
  <Image
60
57
  className='home-logo'
@@ -639,10 +639,6 @@ export default function LoginPage() {
639
639
  </View>
640
640
  </View>
641
641
  <View className='k-glass k-glass--lit login-panel'>
642
- <View className='k-cnr k-cnr--tl' aria-hidden />
643
- <View className='k-cnr k-cnr--tr' aria-hidden />
644
- <View className='k-cnr k-cnr--bl' aria-hidden />
645
- <View className='k-cnr k-cnr--br' aria-hidden />
646
642
  {process.env.TARO_ENV === 'h5' ? (
647
643
  <View className='login-form'>
648
644
  {showTenantCode ? (