@liujitcn/kratos-uni-app-system 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
@@ -8,9 +8,11 @@
8
8
  packages/modules/system
9
9
  ├── src
10
10
  │ ├── api/base/v1 # AI 消息、会话、快捷入口和Provider模型选项接口
11
+ │ ├── components # 可复用的 KratosMyPage 公共个人中心
11
12
  │ ├── rpc # system 相关 Buf 生成类型
12
13
  │ ├── views
13
14
  │ │ ├── pages/my # 个人中心主页
15
+ │ │ │ └── components # MyExtensions 业务扩展的空实现
14
16
  │ │ └── pagesMember # AI、个人资料和默认设置包装页
15
17
  │ ├── index.ts # systemModule 定义和公开入口
16
18
  │ └── pages.ts # 页面样式与分包声明
@@ -29,4 +31,33 @@ packages/modules/system
29
31
  - 允许后续模块按相同物理路由或 `viewKey` 覆盖 system 静态视图。
30
32
  - npm 根入口使用 `dist/index.mjs`;workspace 的 Turbo 任务和 `prepare:modules` 会先构建 system 及其依赖的 core Node 入口,干净安装后不依赖历史 dist。
31
33
 
34
+ ## 个人中心扩展
35
+
36
+ 公共组件 `KratosMyPage` 保留顶部标题与设置、个人信息和 AI 助手入口;AI 卡片继续由应用的 `aiEnabled` 配置控制显隐。唯一业务插槽 `extensions` 位于这些固定内容之后,与它们共用滚动区域、主题和卡片间距。未传入内容时不增加容器或留白,业务卡片、数据请求和权限由项目模块维护。
37
+
38
+ 内置 [我的页面](src/views/pages/my/my.vue) 已使用该组件,并在插槽中挂载 [MyExtensions 空实现](src/views/pages/my/components/MyExtensions.vue)。空实现保留一个 `view` 作为业务内容容器,不显示占位卡片;项目可以按这个结构组织自己的业务内容。
39
+
40
+ 在项目业务模块的 `src/views/pages/my/my.vue` 复用公开组件入口:
41
+
42
+ ```vue
43
+ <script setup lang="ts">
44
+ import KratosMyPage from '@liujitcn/kratos-uni-app-system/components/KratosMyPage.vue'
45
+ import OrderSection from './components/OrderSection.vue'
46
+ import ServiceSection from './components/ServiceSection.vue'
47
+ </script>
48
+
49
+ <template>
50
+ <KratosMyPage>
51
+ <template #extensions>
52
+ <OrderSection />
53
+ <ServiceSection />
54
+ </template>
55
+ </KratosMyPage>
56
+ </template>
57
+ ```
58
+
59
+ 项目模块的 `pages` 声明 `'pages/my/my': { style: { navigationStyle: 'custom', navigationBarTitleText: '' } }`,`views` 声明 `PROFILE_HOME: 'pages/my/my'`,并在宿主 `module-manifest.ts` 中排在 system 模块之后。使用相同的物理路由可以保留微信原生 tab 切换;页面 wrapper 和底部 tabBar 继续由构建插件生成,不在业务页面重复挂载。
60
+
61
+ 插槽可以直接放多个业务卡片,不需要复制公共页面或给顶部入口提供替换内容。需要登录态、用户资料或导航时,通过 core 的公开 store 与导航 API 获取。
62
+
32
63
  模块装配与动态导航约定见 [workspace 文档](../../../README.md)。
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",
@@ -25,6 +25,7 @@
25
25
  "import": "./dist/module.mjs",
26
26
  "default": "./dist/module.mjs"
27
27
  },
28
+ "./components/*.vue": "./src/components/*.vue",
28
29
  "./views/*": "./src/views/*",
29
30
  "./package.json": "./package.json"
30
31
  },
@@ -35,7 +36,7 @@
35
36
  "@dcloudio/uni-app": "3.0.0-5010520260709002",
36
37
  "@dcloudio/uni-ui": "^1.4.28",
37
38
  "vue": "^3.4.21",
38
- "@liujitcn/kratos-uni-app-core": "^0.0.83"
39
+ "@liujitcn/kratos-uni-app-core": "^0.0.85"
39
40
  },
40
41
  "devDependencies": {
41
42
  "esbuild": "^0.20.2",
@@ -0,0 +1,148 @@
1
+ <script setup lang="ts">
2
+ import { useSettingStore, useThemeStore, useUserStore } from '@liujitcn/kratos-uni-app-core/stores'
3
+ import { computed } from 'vue'
4
+ import { formatSrc } from '@liujitcn/kratos-uni-app-core/utils/index'
5
+ import { navigateToLogin } from '@liujitcn/kratos-uni-app-core/utils/navigation'
6
+ import { navigateAppRoute } from '@liujitcn/kratos-uni-app-core'
7
+ import KratosNavBar from '@liujitcn/kratos-uni-app-core/components/KratosNavBar.vue'
8
+ import defaultAvatar from '@liujitcn/kratos-uni-app-core/static/images/avatar.png'
9
+ import { useI18n } from '@liujitcn/kratos-uni-app-core'
10
+
11
+ defineOptions({ name: 'KratosMyPage' })
12
+
13
+ defineSlots<{
14
+ /** 在个人信息和 AI 助手之后渲染的项目业务内容。 */
15
+ extensions?: () => unknown
16
+ }>()
17
+
18
+ // 公共个人中心统一复用用户、设置和主题状态。
19
+ const userStore = useUserStore()
20
+ const settingStore = useSettingStore()
21
+ const themeStore = useThemeStore()
22
+ const { t } = useI18n()
23
+ const isLoggedIn = computed(() => userStore.isAuthenticated())
24
+ const profile = computed(() => userStore.userInfo)
25
+
26
+ /** 昵称或占位账号首字符,用于渐变头像兜底。 */
27
+ const avatarText = computed(() => (profile.value?.nick_name || 'U').slice(0, 1))
28
+
29
+ /** 个人卡账号行:登录展示账号,未登录提示去登录。 */
30
+ const accountText = computed(() => {
31
+ if (isLoggedIn.value && profile.value) {
32
+ return profile.value.user_name || profile.value.nick_name || ''
33
+ }
34
+ return t('system.profile.login_prompt')
35
+ })
36
+
37
+ /** 打开移动端 AI 助手静态页,未登录时先进入登录流程。 */
38
+ const navigateToAi = () => {
39
+ if (!userStore.ensureAuthenticated()) {
40
+ navigateToLogin()
41
+ return
42
+ }
43
+ navigateAppRoute('app/ai')
44
+ }
45
+
46
+ /** 打开设置页,未登录时先进入登录流程。 */
47
+ const navigateToSettings = () => {
48
+ if (!userStore.ensureAuthenticated()) {
49
+ navigateToLogin()
50
+ return
51
+ }
52
+ navigateAppRoute('app/settings')
53
+ }
54
+
55
+ /** 打开当前用户资料。 */
56
+ const navigateToProfile = () => navigateAppRoute('app/profile')
57
+ </script>
58
+
59
+ <template>
60
+ <scroll-view enable-back-to-top class="my-viewport" scroll-y :class="themeStore.themeClass">
61
+ <view class="my-content">
62
+ <KratosNavBar
63
+ :title="t('core.navigation.my')"
64
+ variant="tab"
65
+ :action="{
66
+ icon: 'settings',
67
+ label: t('core.settings.title'),
68
+ onClick: navigateToSettings,
69
+ }"
70
+ />
71
+
72
+ <view class="my-stack">
73
+ <!-- 个人资料 -->
74
+ <view class="k-glass k-glass--lit k-pfx my-profile" @tap="navigateToProfile">
75
+ <view class="my-pfx-main">
76
+ <image
77
+ v-if="isLoggedIn && profile"
78
+ class="k-pfx__av"
79
+ :src="profile.avatar ? formatSrc(profile.avatar) : defaultAvatar"
80
+ mode="aspectFill"
81
+ ></image>
82
+ <view v-else class="k-pfx__av">{{ avatarText }}</view>
83
+ <view class="my-pfx-meta">
84
+ <view class="k-pfx__nm">
85
+ {{ isLoggedIn && profile ? profile.nick_name : t('system.profile.not_logged_in') }}
86
+ </view>
87
+ <view class="k-pfx__ac">{{ accountText }}</view>
88
+ </view>
89
+ </view>
90
+ <view class="k-pfx__ent">{{ t('system.profile.title') }} ›</view>
91
+ </view>
92
+
93
+ <!-- AI 助手入口 -->
94
+ <view v-if="settingStore.aiEnabled" class="k-aix" @tap="navigateToAi">
95
+ <view class="k-aix__ic">AI</view>
96
+ <view class="my-aix-copy">
97
+ <view class="k-aix__t">{{ t('system.settings.ai_title') }}</view>
98
+ <view class="k-aix__d">{{ t('system.settings.ai_description') }}</view>
99
+ </view>
100
+ <view class="k-aix__b">{{ t('system.settings.go_ask') }}</view>
101
+ </view>
102
+
103
+ <slot name="extensions" />
104
+ </view>
105
+ </view>
106
+ </scroll-view>
107
+ </template>
108
+
109
+ <style lang="scss">
110
+ .my-viewport {
111
+ height: 100%;
112
+ min-height: 100vh;
113
+ box-sizing: border-box;
114
+ background: var(--kratos-bg-gradient);
115
+ }
116
+
117
+ .my-content {
118
+ padding: 0 20rpx calc(40rpx + env(safe-area-inset-bottom));
119
+ }
120
+
121
+ .my-stack {
122
+ display: flex;
123
+ flex-direction: column;
124
+ gap: 24rpx;
125
+ }
126
+
127
+ /* k-pfx 仅定义 flex 排布,卡片内边距交由页面控制 */
128
+ .my-profile {
129
+ padding: 36rpx 30rpx;
130
+ }
131
+
132
+ .my-pfx-main {
133
+ display: flex;
134
+ align-items: center;
135
+ gap: 24rpx;
136
+ min-width: 0;
137
+ }
138
+
139
+ .my-pfx-meta {
140
+ min-width: 0;
141
+ }
142
+
143
+ /* k-aix 中部文案占位伸展 */
144
+ .my-aix-copy {
145
+ flex: 1;
146
+ min-width: 0;
147
+ }
148
+ </style>
@@ -0,0 +1,9 @@
1
+ <script setup lang="ts">
2
+ defineOptions({ name: 'MyExtensions' })
3
+ </script>
4
+
5
+ <template>
6
+ <view>
7
+ <!-- 项目业务扩展的空实现,可在这里添加订单、收藏或统计卡片。 -->
8
+ </view>
9
+ </template>
@@ -1,157 +1,12 @@
1
1
  <script setup lang="ts">
2
- import { useSettingStore, useThemeStore, useUserStore } from '@liujitcn/kratos-uni-app-core/stores'
3
- import { computed } from 'vue'
4
- import { formatSrc, getMpCapsuleRightGap } from '@liujitcn/kratos-uni-app-core/utils/index'
5
- import { navigateToLogin } from '@liujitcn/kratos-uni-app-core/utils/navigation'
6
- import { navigateAppRoute } from '@liujitcn/kratos-uni-app-core'
7
- import defaultAvatar from '@liujitcn/kratos-uni-app-core/static/images/avatar.png'
8
- import { useI18n } from '@liujitcn/kratos-uni-app-core'
9
-
10
- // 获取会员信息
11
- const userStore = useUserStore()
12
- const settingStore = useSettingStore()
13
- const themeStore = useThemeStore()
14
- const { t } = useI18n()
15
- const isLoggedIn = computed(() => userStore.isAuthenticated())
16
- const profile = computed(() => userStore.userInfo)
17
-
18
- /** MP-WEIXIN 原生胶囊按钮右侧避让宽度,写入 --mp-capsule-gap 供 .k-top 预留空间。 */
19
- const capsuleGap = getMpCapsuleRightGap()
20
-
21
- /** 昵称或占位账号首字符,用于渐变头像兜底。 */
22
- const avatarText = computed(() => (profile.value?.nick_name || 'U').slice(0, 1))
23
-
24
- /** 设计稿齿轮线稿,随主题取色的 data URI,替代 CSS 手绘图标。 */
25
- const gearIcon = computed(() => {
26
- const color = themeStore.theme === 'light' ? '#102232' : '#e9f4f6'
27
- const svg = `<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"><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"/></svg>`
28
- return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`
29
- })
30
-
31
- /** 个人卡账号行:登录展示账号,未登录提示去登录。 */
32
- const accountText = computed(() => {
33
- if (isLoggedIn.value && profile.value) {
34
- return profile.value.user_name || profile.value.nick_name || ''
35
- }
36
- return t('system.profile.login_prompt')
37
- })
38
-
39
- /** 打开移动端 AI 助手静态页,未登录时先进入登录流程。 */
40
- const navigateToAi = () => {
41
- if (!userStore.ensureAuthenticated()) {
42
- navigateToLogin()
43
- return
44
- }
45
- navigateAppRoute('app/ai')
46
- }
47
-
48
- /** 打开设置页,未登录时先进入登录流程。 */
49
- const navigateToSettings = () => {
50
- if (!userStore.ensureAuthenticated()) {
51
- navigateToLogin()
52
- return
53
- }
54
- navigateAppRoute('app/settings')
55
- }
56
-
57
- /** 打开当前用户资料。 */
58
- const navigateToProfile = () => navigateAppRoute('app/profile')
2
+ import KratosMyPage from '@liujitcn/kratos-uni-app-system/components/KratosMyPage.vue'
3
+ import MyExtensions from './components/MyExtensions.vue'
59
4
  </script>
60
5
 
61
6
  <template>
62
- <scroll-view enable-back-to-top class="my-viewport" scroll-y :class="themeStore.themeClass">
63
- <view class="my-content">
64
- <!-- 顶栏:标题 + 设置入口(Tab 根页,无返回) -->
65
- <view class="k-top" :style="{ '--mp-capsule-gap': `${capsuleGap}px` }">
66
- <view class="k-top__title">{{ t('core.navigation.my') }}</view>
67
- <view class="k-ic" @tap="navigateToSettings">
68
- <image class="my-gear" :src="gearIcon" mode="aspectFit" />
69
- </view>
70
- </view>
71
-
72
- <view class="my-stack">
73
- <!-- 个人资料 -->
74
- <view class="k-glass k-glass--lit k-pfx my-profile" @tap="navigateToProfile">
75
- <view class="my-pfx-main">
76
- <image
77
- v-if="isLoggedIn && profile"
78
- class="k-pfx__av"
79
- :src="profile.avatar ? formatSrc(profile.avatar) : defaultAvatar"
80
- mode="aspectFill"
81
- ></image>
82
- <view v-else class="k-pfx__av">{{ avatarText }}</view>
83
- <view class="my-pfx-meta">
84
- <view class="k-pfx__nm">
85
- {{ isLoggedIn && profile ? profile.nick_name : t('system.profile.not_logged_in') }}
86
- </view>
87
- <view class="k-pfx__ac">{{ accountText }}</view>
88
- </view>
89
- </view>
90
- <view class="k-pfx__ent">{{ t('system.profile.title') }} ›</view>
91
- </view>
92
-
93
- <!-- AI 助手入口 -->
94
- <view v-if="settingStore.aiEnabled" class="k-aix" @tap="navigateToAi">
95
- <view class="k-aix__ic">AI</view>
96
- <view class="my-aix-copy">
97
- <view class="k-aix__t">{{ t('system.settings.ai_title') }}</view>
98
- <view class="k-aix__d">{{ t('system.settings.ai_description') }}</view>
99
- </view>
100
- <view class="k-aix__b">{{ t('system.settings.go_ask') }}</view>
101
- </view>
102
- </view>
103
- </view>
104
- </scroll-view>
7
+ <KratosMyPage>
8
+ <template #extensions>
9
+ <MyExtensions />
10
+ </template>
11
+ </KratosMyPage>
105
12
  </template>
106
-
107
- <style lang="scss">
108
- .my-viewport {
109
- height: 100%;
110
- min-height: 100vh;
111
- box-sizing: border-box;
112
- background: var(--kratos-bg-gradient);
113
- }
114
-
115
- .my-content {
116
- padding: calc(env(safe-area-inset-top) + 24rpx) 32rpx calc(40rpx + env(safe-area-inset-bottom));
117
-
118
- /* #ifdef MP-WEIXIN */
119
- /* k-top 在小程序端自带 44px 状态栏高度的导航带,页面不再额外让出顶部空间 */
120
- padding-top: 0;
121
- /* #endif */
122
- }
123
-
124
- .my-stack {
125
- display: flex;
126
- flex-direction: column;
127
- gap: 24rpx;
128
- margin-top: 16rpx;
129
- }
130
-
131
- .my-gear {
132
- width: 34rpx;
133
- height: 34rpx;
134
- }
135
-
136
- /* k-pfx 仅定义 flex 排布,卡片内边距交由页面控制 */
137
- .my-profile {
138
- padding: 36rpx 30rpx;
139
- }
140
-
141
- .my-pfx-main {
142
- display: flex;
143
- align-items: center;
144
- gap: 24rpx;
145
- min-width: 0;
146
- }
147
-
148
- .my-pfx-meta {
149
- min-width: 0;
150
- }
151
-
152
- /* k-aix 中部文案占位伸展 */
153
- .my-aix-copy {
154
- flex: 1;
155
- min-width: 0;
156
- }
157
- </style>
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import KratosNavBar from '@liujitcn/kratos-uni-app-core/components/KratosNavBar.vue'
2
3
  import { onLoad } from '@dcloudio/uni-app'
3
4
  import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
4
5
  import {
@@ -22,7 +23,7 @@ import { isAuthExpiredError } from '@liujitcn/kratos-uni-app-core/utils/http'
22
23
  import Composer from './components/Composer.vue'
23
24
  import SessionDrawer from './components/SessionDrawer.vue'
24
25
  import WelcomePanel from './components/WelcomePanel.vue'
25
- import { navigateAppRoute, useI18n } from '@liujitcn/kratos-uni-app-core'
26
+ import { useI18n } from '@liujitcn/kratos-uni-app-core'
26
27
  import { useThemeStore } from '@liujitcn/kratos-uni-app-core/stores'
27
28
  import {
28
29
  type AiStreamEvent,
@@ -74,9 +75,6 @@ const MAX_ATTACHMENT_COUNT = 6
74
75
  const STARTER_PROMPT_PAGE_SIZE = 4
75
76
  const { locale, t } = useI18n()
76
77
  const themeStore = useThemeStore()
77
- /** 导航图标随主题取色,uni-icons 的 color 属性不接受 CSS 变量。 */
78
- const navIconColor = computed(() => (themeStore.theme === 'light' ? '#102232' : '#e9f4f6'))
79
-
80
78
  const windowInfo = uni.getWindowInfo()
81
79
  let safeAreaTop =
82
80
  windowInfo.safeArea?.top || windowInfo.statusBarHeight || windowInfo.safeAreaInsets?.top || 0
@@ -362,15 +360,6 @@ const handleMessageAction = (item: ChatMessageItem) => {
362
360
  })
363
361
  }
364
362
 
365
- const navigateBack = () => {
366
- const pages = getCurrentPages()
367
- if (pages.length > 1) {
368
- uni.navigateBack()
369
- return
370
- }
371
- navigateAppRoute('app/home')
372
- }
373
-
374
363
  const handleSend = async () => {
375
364
  if (isSubmitDisabled.value) {
376
365
  return
@@ -1277,20 +1266,11 @@ function showError(error: unknown, fallback: string) {
1277
1266
 
1278
1267
  <template>
1279
1268
  <view class="ai-page" :class="themeStore.themeClass">
1280
- <view class="ai-navbar k-page-nav">
1281
- <view
1282
- class="k-page-nav__back"
1283
- :aria-label="t('common.action.back')"
1284
- @tap="navigateBack"
1285
- ></view>
1286
- <view class="k-page-nav__title">{{ t('system.ai.chat_title') }}</view>
1287
- <view
1288
- class="k-page-nav__side k-ic"
1289
- :aria-label="t('system.ai.history')"
1290
- @tap="toggleSessionDrawer"
1291
- >
1292
- <uni-icons type="bars" size="22" :color="navIconColor" />
1293
- </view>
1269
+ <view class="ai-navbar">
1270
+ <KratosNavBar
1271
+ :title="t('system.ai.chat_title')"
1272
+ :action="{ icon: 'history', label: t('system.ai.history'), onClick: toggleSessionDrawer }"
1273
+ />
1294
1274
  </view>
1295
1275
 
1296
1276
  <scroll-view
@@ -1,9 +1,9 @@
1
1
  <script setup lang="ts">
2
+ import KratosNavBar from '@liujitcn/kratos-uni-app-core/components/KratosNavBar.vue'
2
3
  import { onLoad } from '@dcloudio/uni-app'
3
4
  import { ref } from 'vue'
4
5
  import { navigateAppView, t } from '@liujitcn/kratos-uni-app-core'
5
6
  import { useThemeStore } from '@liujitcn/kratos-uni-app-core/stores'
6
- import { homeTabPage } from '@liujitcn/kratos-uni-app-core/utils/navigation'
7
7
  import { defNotificationService } from '../../../api/base/v1/notification'
8
8
  import type { Notification } from '../../../rpc/base/v1/notification'
9
9
  import { MessageActionType, MessageContentFormat } from '../../../rpc/base/v1/notification'
@@ -52,20 +52,11 @@ function openAction() {
52
52
  }
53
53
  navigateAppView(detail.value.action_target, params)
54
54
  }
55
-
56
- /** 返回消息列表,页面栈为空时回到首页。 */
57
- function onBack() {
58
- uni.navigateBack({ fail: () => uni.reLaunch({ url: homeTabPage }) })
59
- }
60
55
  </script>
61
56
 
62
57
  <template>
63
58
  <view class="detail-page" :class="themeStore.themeClass">
64
- <view class="k-page-nav">
65
- <view class="k-page-nav__back" :aria-label="t('common.action.back')" @tap="onBack"></view>
66
- <view class="k-page-nav__title">{{ t('system.notification.title') }}</view>
67
- <view class="k-page-nav__side" aria-hidden="true"></view>
68
- </view>
59
+ <KratosNavBar :title="t('system.notification.title')" />
69
60
  <template v-if="detail">
70
61
  <text class="detail-title">{{ detail.title }}</text>
71
62
  <view class="detail-meta">
@@ -104,7 +95,7 @@ function onBack() {
104
95
  .detail-page {
105
96
  box-sizing: border-box;
106
97
  min-height: 100vh;
107
- padding: 24rpx 30rpx calc(96rpx + env(safe-area-inset-bottom));
98
+ padding: 0 30rpx calc(96rpx + env(safe-area-inset-bottom));
108
99
  background: var(--kratos-bg-gradient);
109
100
  color: var(--kratos-color-text);
110
101
  }
@@ -1,9 +1,10 @@
1
1
  <script setup lang="ts">
2
+ import KratosEmpty from '@liujitcn/kratos-uni-app-core/components/KratosEmpty.vue'
3
+ import KratosNavBar from '@liujitcn/kratos-uni-app-core/components/KratosNavBar.vue'
2
4
  import { onShow } from '@dcloudio/uni-app'
3
5
  import { computed, ref } from 'vue'
4
- import { KratosEmpty, navigateAppView, t } from '@liujitcn/kratos-uni-app-core'
6
+ import { navigateAppView, t } from '@liujitcn/kratos-uni-app-core'
5
7
  import { useThemeStore } from '@liujitcn/kratos-uni-app-core/stores'
6
- import { getMpCapsuleRightGap } from '@liujitcn/kratos-uni-app-core/utils/index'
7
8
  import { defNotificationService } from '../../../api/base/v1/notification'
8
9
  import type { Notification, NotificationCategory } from '../../../rpc/base/v1/notification'
9
10
  import { NotificationView } from '../../../rpc/base/v1/notification'
@@ -11,8 +12,6 @@ import { notificationUnreadTotal, refreshNotificationSummary } from '../../../no
11
12
  import { resolveMessageCategoryIcon } from './icons'
12
13
 
13
14
  const themeStore = useThemeStore()
14
- /** MP-WEIXIN 原生胶囊按钮右侧避让宽度,写入 --mp-capsule-gap 供 .k-top 预留空间。 */
15
- const capsuleGap = getMpCapsuleRightGap()
16
15
  const items = ref<Notification[]>([])
17
16
  const loading = ref(false)
18
17
  const cursorId = ref(0)
@@ -128,22 +127,19 @@ const emptyHintKey = computed(() => {
128
127
 
129
128
  <template>
130
129
  <view class="message-page" :class="themeStore.themeClass">
131
- <view class="k-top message-header" :style="{ '--mp-capsule-gap': `${capsuleGap}px` }">
132
- <view class="message-header__title">
133
- <text class="message-header__text">{{ t('system.notification.title') }}</text>
134
- <view v-if="notificationUnreadTotal > 0" class="message-unread-dot" aria-hidden="true" />
135
- </view>
136
- <view
137
- v-if="
138
- selectedView !== NotificationView.NOTIFICATION_VIEW_ARCHIVED &&
139
- notificationUnreadTotal > 0
140
- "
141
- class="k-ch k-ch--pri"
142
- @tap="markAllRead"
143
- >
144
- {{ t('system.notification.mark_all_read') }}
145
- </view>
146
- </view>
130
+ <KratosNavBar
131
+ :title="t('system.notification.title')"
132
+ variant="tab"
133
+ :action="
134
+ selectedView !== NotificationView.NOTIFICATION_VIEW_ARCHIVED && notificationUnreadTotal > 0
135
+ ? {
136
+ display: 'text',
137
+ label: t('system.notification.mark_all_read'),
138
+ onClick: markAllRead,
139
+ }
140
+ : undefined
141
+ "
142
+ />
147
143
  <view class="k-chips">
148
144
  <view class="k-ch" :class="{ 'k-ch--pri': categoryId === undefined }" @tap="changeCategory()">
149
145
  {{ t('system.notification.view.inbox') }}
@@ -247,50 +243,10 @@ const emptyHintKey = computed(() => {
247
243
  .message-page {
248
244
  box-sizing: border-box;
249
245
  min-height: 100vh;
250
- padding: calc(16rpx + env(safe-area-inset-top)) 24rpx calc(200rpx + env(safe-area-inset-bottom));
246
+ padding: 0 20rpx calc(200rpx + env(safe-area-inset-bottom));
251
247
  background: var(--kratos-bg-gradient);
252
248
  color: var(--kratos-color-text);
253
249
  }
254
- /* #ifdef MP-WEIXIN */
255
- .message-page {
256
- /* k-top 在小程序端自带 44px 状态栏高度的导航带,页面不再额外让出顶部空间 */
257
- padding-top: 0;
258
- }
259
- /* #endif */
260
- .message-header {
261
- /* 标题绝对居中后头部不再依赖流内内容撑高,保证标题行与筛选行留出间距 */
262
- min-height: 76rpx;
263
- padding: 20rpx 4rpx 24rpx;
264
- }
265
- /* 标题排版与 k-page-nav__title/k-top__title 同规格,绝对居中后文字与红点同行排布 */
266
- .message-header__title {
267
- position: absolute;
268
- left: 50%;
269
- display: flex;
270
- align-items: center;
271
- min-width: 0;
272
- max-width: calc(100% - 190rpx);
273
- overflow: hidden;
274
- font-size: 32rpx;
275
- font-weight: 700;
276
- color: var(--kratos-color-text);
277
- transform: translateX(-50%);
278
- white-space: nowrap;
279
- }
280
- .message-header__text {
281
- min-width: 0;
282
- overflow: hidden;
283
- text-overflow: ellipsis;
284
- }
285
- .message-unread-dot {
286
- flex-shrink: 0;
287
- width: 12rpx;
288
- height: 12rpx;
289
- margin-left: 12rpx;
290
- border-radius: 50%;
291
- background: var(--kratos-color-danger);
292
- box-shadow: 0 0 16rpx var(--kratos-color-danger);
293
- }
294
250
  .message-tabs {
295
251
  display: flex;
296
252
  margin-top: 4rpx;
@@ -1,4 +1,5 @@
1
1
  <script setup lang="ts">
2
+ import KratosNavBar from '@liujitcn/kratos-uni-app-core/components/KratosNavBar.vue'
2
3
  import { defAuthService } from '@liujitcn/kratos-uni-app-core/api/system/app/v1/auth'
3
4
  import { useThemeStore, useUserStore } from '@liujitcn/kratos-uni-app-core/stores'
4
5
  import type { UserProfileForm } from '@liujitcn/kratos-uni-app-core/rpc/system/app/v1/auth'
@@ -17,9 +18,6 @@ const userStore = useUserStore()
17
18
  const themeStore = useThemeStore()
18
19
  const { t } = useI18n()
19
20
 
20
- /** 返回上一页。 */
21
- const onBack = () => uni.navigateBack()
22
-
23
21
  const imgMaxSize = ref(1024 * 1024)
24
22
  const submitting = ref(false)
25
23
  let navigateBackTimer: ReturnType<typeof setTimeout> | undefined
@@ -251,11 +249,7 @@ const onSubmit = async () => {
251
249
  <view class="profile-viewport" :class="themeStore.themeClass">
252
250
  <view class="profile-content">
253
251
  <!-- 导航栏:统一子页头(单一返回 + 标题) -->
254
- <view class="k-page-nav">
255
- <view class="k-page-nav__back" :aria-label="t('common.action.back')" @tap="onBack"></view>
256
- <view class="k-page-nav__title">{{ t('system.profile.title') }}</view>
257
- <view class="k-page-nav__side" aria-hidden="true"></view>
258
- </view>
252
+ <KratosNavBar :title="t('system.profile.title')" />
259
253
 
260
254
  <!-- 头像卡:点击更换头像 -->
261
255
  <view class="k-glass k-glass--lit k-pfx profile-id" @tap="onAvatarChange">
@@ -367,7 +361,7 @@ const onSubmit = async () => {
367
361
  background: var(--kratos-bg-gradient);
368
362
  }
369
363
 
370
- /* k-page-nav 自带安全区顶部内边距,这里只补水平与底部 */
364
+ /* 导航组件统一处理顶部空间,页面只维护内容的水平与底部留白。 */
371
365
  .profile-content {
372
366
  padding: 0 32rpx calc(40rpx + env(safe-area-inset-bottom));
373
367
  }