@liujitcn/kratos-taro-app-core 0.0.80 → 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 +0 -1
- package/package.json +1 -1
- package/src/components/KratosEmpty.tsx +26 -0
- package/src/components/KratosTabBar.scss +1 -14
- package/src/components/KratosTabBar.tsx +1 -13
- package/src/index.ts +2 -0
- package/src/locales/en-US.json +6 -4
- package/src/locales/ja-JP.json +6 -4
- package/src/locales/zh-CN.json +6 -4
- package/src/locales/zh-TW.json +6 -4
- package/src/navigation.ts +42 -16
- package/src/pages.ts +0 -1
- package/src/styles/base.scss +13 -0
- package/src/styles/components.scss +96 -47
- package/src/utils/http.ts +26 -9
- package/src/views/pages/index/index.scss +19 -6
- package/src/views/pages/index/index.tsx +3 -6
- package/src/views/pages/login/login.tsx +0 -4
package/dist/build.mjs
CHANGED
package/package.json
CHANGED
|
@@ -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;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { Image, Text, View } from '@tarojs/components'
|
|
2
2
|
import { resolveModuleIcon, resolveStaticView } from '../module'
|
|
3
3
|
import { APP_MENU_ROOT_ID, navigateAppRoute, useAppMenuBadges, useAppNavigation } from '../navigation'
|
|
4
|
-
import { useI18n } from '../locales'
|
|
5
4
|
import { useThemeStore } from '../stores'
|
|
6
5
|
import { resolveRootMenuId } from '../navigation-tree.mjs'
|
|
7
6
|
import './KratosTabBar.scss'
|
|
@@ -39,19 +38,8 @@ const tabIconSrc = (viewKey: string, active: boolean, theme: string): string =>
|
|
|
39
38
|
export function KratosTabBar({ route }: KratosTabBarProps) {
|
|
40
39
|
const menus = useAppNavigation((state) => state.menus)
|
|
41
40
|
const tabBar = useAppNavigation((state) => state.tabBar)
|
|
42
|
-
const { t } = useI18n()
|
|
43
41
|
const themeStore = useThemeStore()
|
|
44
42
|
|
|
45
|
-
/** 按稳定 viewKey 优先取界面语言标签,未配置的菜单回退后端标题。 */
|
|
46
|
-
const tabBarTitleKeys: Record<string, string> = {
|
|
47
|
-
HOME: 'core.navigation.home',
|
|
48
|
-
APP_ANALYTIC: 'core.navigation.analytics',
|
|
49
|
-
APP_REPORT: 'core.navigation.report',
|
|
50
|
-
MESSAGE_INBOX: 'core.navigation.message',
|
|
51
|
-
PROFILE_HOME: 'core.navigation.my',
|
|
52
|
-
}
|
|
53
|
-
const tabBarTitle = (item: { viewKey: string; title: string }): string =>
|
|
54
|
-
(tabBarTitleKeys[item.viewKey] ? t(tabBarTitleKeys[item.viewKey]) : '') || item.title
|
|
55
43
|
const messageBadge = useAppMenuBadges((state) => state.badges.MESSAGE_INBOX ?? 0)
|
|
56
44
|
const routeMenu = menus.find((menu) => resolveStaticView(menu.viewKey) === route)
|
|
57
45
|
const tabMenuId = routeMenu
|
|
@@ -85,7 +73,7 @@ export function KratosTabBar({ route }: KratosTabBarProps) {
|
|
|
85
73
|
{item.viewKey === 'MESSAGE_INBOX' && messageBadge ? (
|
|
86
74
|
<View className='kratos-tab-bar__badge' aria-hidden='true' />
|
|
87
75
|
) : null}
|
|
88
|
-
<Text className={active ? 'kratos-tab-bar__text--active' : ''}>{
|
|
76
|
+
<Text className={active ? 'kratos-tab-bar__text--active' : ''}>{item.title}</Text>
|
|
89
77
|
</View>
|
|
90
78
|
)
|
|
91
79
|
})}
|
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'
|
package/src/locales/en-US.json
CHANGED
|
@@ -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": "
|
|
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": "
|
|
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",
|
package/src/locales/ja-JP.json
CHANGED
|
@@ -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": "および",
|
package/src/locales/zh-CN.json
CHANGED
|
@@ -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/locales/zh-TW.json
CHANGED
|
@@ -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 =
|
|
226
|
-
|
|
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
|
-
|
|
387
|
-
|
|
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:
|
|
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[]):
|
|
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
|
|
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(
|
|
439
|
-
for (const menu of
|
|
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 =
|
|
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
package/src/styles/base.scss
CHANGED
|
@@ -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:
|
|
185
|
-
font-weight:
|
|
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
|
-
|
|
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
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
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:
|
|
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-
|
|
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
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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
|
-
|
|
14
|
-
|
|
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:
|
|
32
|
-
font-weight:
|
|
33
|
-
|
|
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 =
|
|
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.
|
|
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 ? (
|