@liujitcn/kratos-uni-app-core 0.0.79 → 0.0.81

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/module.mjs CHANGED
@@ -7,8 +7,10 @@ var corePages = {
7
7
  navigationBarTitleText: ""
8
8
  }
9
9
  },
10
- "pages/login/login": { style: { navigationBarTitleText: "" } },
11
- "pages/login/protocal": { style: { navigationBarTitleText: "" } },
10
+ "pages/login/login": {
11
+ style: { navigationStyle: "custom", navigationBarTitleText: "" }
12
+ },
13
+ "pages/login/protocal": { style: { navigationStyle: "custom", navigationBarTitleText: "" } },
12
14
  "pages/webview/webview": {
13
15
  style: {
14
16
  navigationBarTitleText: "",
@@ -141,6 +143,9 @@ var zh_CN_default = {
141
143
  "core.settings.authorization": "\u6388\u6743\u7BA1\u7406",
142
144
  "core.settings.contact": "\u8054\u7CFB\u6211\u4EEC",
143
145
  "core.settings.feedback": "\u95EE\u9898\u53CD\u9988",
146
+ "core.settings.section_appearance": "\u5916\u89C2",
147
+ "core.settings.section_security": "\u8D26\u6237\u5B89\u5168",
148
+ "core.settings.section_more": "\u66F4\u591A \xB7 \u5C0F\u7A0B\u5E8F\u7AEF",
144
149
  "core.settings.logout_confirm": "\u662F\u5426\u9000\u51FA\u767B\u5F55\uFF1F",
145
150
  "core.settings.logout_failed": "\u9000\u51FA\u767B\u5F55\u5931\u8D25",
146
151
  "core.settings.mfa_bind_success": "\u591A\u56E0\u7D20\u8BA4\u8BC1\u7ED1\u5B9A\u6210\u529F",
@@ -170,7 +175,11 @@ var zh_CN_default = {
170
175
  "common.action.load_more": "\u52A0\u8F7D\u66F4\u591A",
171
176
  "common.status.loading": "\u52A0\u8F7D\u4E2D\u2026",
172
177
  "core.navigation.analytics": "\u5206\u6790",
173
- "core.navigation.report": "\u62A5\u8868"
178
+ "core.navigation.report": "\u62A5\u8868",
179
+ "core.settings.theme": "\u4E3B\u9898\u5916\u89C2",
180
+ "core.theme.auto": "\u8DDF\u968F\u7CFB\u7EDF",
181
+ "core.theme.dark": "\u6697\u9ED1",
182
+ "core.theme.light": "\u660E\u4EAE"
174
183
  };
175
184
 
176
185
  // src/locales/en-US.json
@@ -291,6 +300,9 @@ var en_US_default = {
291
300
  "core.settings.authorization": "Authorization settings",
292
301
  "core.settings.contact": "Contact us",
293
302
  "core.settings.feedback": "Feedback",
303
+ "core.settings.section_appearance": "Appearance",
304
+ "core.settings.section_security": "Account security",
305
+ "core.settings.section_more": "More \xB7 Mini Program",
294
306
  "core.settings.logout_confirm": "Log out now?",
295
307
  "core.settings.logout_failed": "Logout failed",
296
308
  "core.settings.mfa_bind_success": "Multi-factor authentication enabled",
@@ -320,7 +332,11 @@ var en_US_default = {
320
332
  "common.action.load_more": "Load more",
321
333
  "common.status.loading": "Loading\u2026",
322
334
  "core.navigation.analytics": "Analytics",
323
- "core.navigation.report": "Reports"
335
+ "core.navigation.report": "Reports",
336
+ "core.settings.theme": "Theme Appearance",
337
+ "core.theme.auto": "Follow System",
338
+ "core.theme.dark": "Dark",
339
+ "core.theme.light": "Light"
324
340
  };
325
341
 
326
342
  // src/locales/ja-JP.json
@@ -441,6 +457,9 @@ var ja_JP_default = {
441
457
  "core.settings.authorization": "\u8A8D\u53EF\u7BA1\u7406",
442
458
  "core.settings.contact": "\u304A\u554F\u3044\u5408\u308F\u305B",
443
459
  "core.settings.feedback": "\u554F\u984C\u306E\u30D5\u30A3\u30FC\u30C9\u30D0\u30C3\u30AF",
460
+ "core.settings.section_appearance": "\u5916\u89B3",
461
+ "core.settings.section_security": "\u30A2\u30AB\u30A6\u30F3\u30C8\u30BB\u30AD\u30E5\u30EA\u30C6\u30A3",
462
+ "core.settings.section_more": "\u305D\u306E\u4ED6\u30FB\u30DF\u30CB\u30D7\u30ED\u30B0\u30E9\u30E0",
444
463
  "core.settings.logout_confirm": "\u30ED\u30B0\u30A2\u30A6\u30C8\u3057\u307E\u3059\u304B?",
445
464
  "core.settings.logout_failed": "\u30ED\u30B0\u30A2\u30A6\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
446
465
  "core.settings.mfa_bind_success": "\u591A\u8981\u7D20\u8A8D\u8A3C\u3092\u6709\u52B9\u306B\u3057\u307E\u3057\u305F",
@@ -470,7 +489,11 @@ var ja_JP_default = {
470
489
  "common.action.load_more": "\u3055\u3089\u306B\u8AAD\u307F\u8FBC\u3080",
471
490
  "common.status.loading": "\u8AAD\u307F\u8FBC\u307F\u4E2D\u2026",
472
491
  "core.navigation.analytics": "\u5206\u6790",
473
- "core.navigation.report": "\u30EC\u30DD\u30FC\u30C8"
492
+ "core.navigation.report": "\u30EC\u30DD\u30FC\u30C8",
493
+ "core.settings.theme": "\u30C6\u30FC\u30DE\u5916\u89B3",
494
+ "core.theme.auto": "\u30B7\u30B9\u30C6\u30E0\u306B\u5F93\u3046",
495
+ "core.theme.dark": "\u30C0\u30FC\u30AF",
496
+ "core.theme.light": "\u30E9\u30A4\u30C8"
474
497
  };
475
498
 
476
499
  // src/locales/zh-TW.json
@@ -591,6 +614,9 @@ var zh_TW_default = {
591
614
  "core.settings.authorization": "\u6388\u6B0A\u7BA1\u7406",
592
615
  "core.settings.contact": "\u806F\u7D61\u6211\u5011",
593
616
  "core.settings.feedback": "\u554F\u984C\u53CD\u994B",
617
+ "core.settings.section_appearance": "\u5916\u89C0",
618
+ "core.settings.section_security": "\u5E33\u6236\u5B89\u5168",
619
+ "core.settings.section_more": "\u66F4\u591A\u30FB\u5C0F\u7A0B\u5F0F\u7AEF",
594
620
  "core.settings.logout_confirm": "\u662F\u5426\u9000\u51FA\u767B\u5165\uFF1F",
595
621
  "core.settings.logout_failed": "\u9000\u51FA\u767B\u5165\u5931\u6557",
596
622
  "core.settings.mfa_bind_success": "\u591A\u56E0\u7D20\u8A8D\u8B49\u7D81\u5B9A\u6210\u529F",
@@ -620,7 +646,11 @@ var zh_TW_default = {
620
646
  "common.action.load_more": "\u8F09\u5165\u66F4\u591A",
621
647
  "common.status.loading": "\u8F09\u5165\u4E2D\u2026",
622
648
  "core.navigation.analytics": "\u5206\u6790",
623
- "core.navigation.report": "\u5831\u8868"
649
+ "core.navigation.report": "\u5831\u8868",
650
+ "core.settings.theme": "\u4E3B\u984C\u5916\u89C0",
651
+ "core.theme.auto": "\u8DDF\u96A8\u7CFB\u7D71",
652
+ "core.theme.dark": "\u6697\u9ED1",
653
+ "core.theme.light": "\u660E\u4EAE"
624
654
  };
625
655
 
626
656
  // src/locales/generated.ts
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.79",
10
+ "version": "0.0.81",
11
11
  "type": "module",
12
12
  "files": [
13
13
  "dist",
@@ -1,12 +1,14 @@
1
1
  <script setup lang="ts">
2
2
  import { useI18n } from '../locales'
3
+ import { useThemeStore } from '../stores'
3
4
 
4
5
  defineProps<{ title?: string; detail?: string }>()
5
6
  const { t } = useI18n()
7
+ const themeStore = useThemeStore()
6
8
  </script>
7
9
 
8
10
  <template>
9
- <view class="bootstrap-status">
11
+ <view class="bootstrap-status" :class="themeStore.themeClass">
10
12
  <image class="bootstrap-status__logo" src="../static/images/logo_icon.png" mode="aspectFit" />
11
13
  <text class="bootstrap-status__title">{{ title || t('core.status.loading') }}</text>
12
14
  <text v-if="detail" class="bootstrap-status__detail">{{ detail }}</text>
@@ -21,7 +23,7 @@ const { t } = useI18n()
21
23
  justify-content: center;
22
24
  flex-direction: column;
23
25
  padding: 48rpx;
24
- background: #f8f8f8;
26
+ background: var(--kratos-bg-gradient);
25
27
  box-sizing: border-box;
26
28
  }
27
29
  .bootstrap-status__logo {
@@ -30,12 +32,12 @@ const { t } = useI18n()
30
32
  margin-bottom: 32rpx;
31
33
  }
32
34
  .bootstrap-status__title {
33
- color: #222;
35
+ color: var(--kratos-color-text);
34
36
  font-size: 32rpx;
35
37
  }
36
38
  .bootstrap-status__detail {
37
39
  margin-top: 16rpx;
38
- color: #777;
40
+ color: var(--kratos-color-text-muted);
39
41
  font-size: 24rpx;
40
42
  text-align: center;
41
43
  }
@@ -1,6 +1,6 @@
1
1
  <script setup lang="ts">
2
- import { computed, onMounted, ref, watch } from 'vue'
3
- import { useUserStore } from '../stores'
2
+ import { computed, onMounted, ref } from 'vue'
3
+ import { useThemeStore, useUserStore, type ThemeMode } from '../stores'
4
4
  import { navigateToLogin } from '../utils/navigation'
5
5
  import { getLanguageOptions, useI18n, type SupportedLocale } from '../locales'
6
6
  import { isAuthExpiredError } from '../utils/http'
@@ -15,6 +15,7 @@ defineSlots<{
15
15
  }>()
16
16
 
17
17
  const userStore = useUserStore()
18
+ const themeStore = useThemeStore()
18
19
  const { locale, setLocale, t } = useI18n()
19
20
 
20
21
  const logoutLoading = ref(false)
@@ -24,6 +25,17 @@ const mfaEnabled = ref(false)
24
25
  const mfaMethod = ref('totp')
25
26
  const mfaStatusLoading = ref(false)
26
27
  const localeOptions = computed(() => getLanguageOptions())
28
+ const themeModeOptions: ThemeMode[] = ['auto', 'dark', 'light']
29
+ const themeModeLabels = computed(() =>
30
+ themeModeOptions.map((mode) =>
31
+ mode === 'auto'
32
+ ? t('core.theme.auto')
33
+ : mode === 'dark'
34
+ ? t('core.theme.dark')
35
+ : t('core.theme.light'),
36
+ ),
37
+ )
38
+ const themeModeIndex = computed(() => Math.max(0, themeModeOptions.indexOf(themeStore.mode)))
27
39
  const localeLabels = computed(() => localeOptions.value.map((item) => item.native_name))
28
40
  const localeIndex = computed(() =>
29
41
  Math.max(
@@ -32,10 +44,41 @@ const localeIndex = computed(() =>
32
44
  ),
33
45
  )
34
46
 
35
- watch(locale, () => void uni.setNavigationBarTitle({ title: t('core.settings.title') }), {
36
- immediate: true,
47
+ const settingsIcons = computed(() => {
48
+ const primary = themeStore.theme === 'light' ? '#039e8c' : '#28f0d4'
49
+ const warning = themeStore.theme === 'light' ? '#d9880f' : '#ffb648'
50
+ const violet = themeStore.theme === 'light' ? '#5f4fe0' : '#9b8cff'
51
+ const createIcon = (body: string, color: string) =>
52
+ `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.8" stroke-linecap="round" stroke-linejoin="round">${body}</svg>`)}`
53
+
54
+ return {
55
+ theme: createIcon('<path d="M21 12.8A8.5 8.5 0 1 1 11.2 3a6.5 6.5 0 0 0 9.8 9.8Z"/>', primary),
56
+ mfa: createIcon(
57
+ '<path d="M12 3 4 6v6c0 5 3.5 8 8 9 4.5-1 8-4 8-9V6l-8-3Z"/><path d="m9 12 2 2 4-4"/>',
58
+ primary,
59
+ ),
60
+ language: createIcon(
61
+ '<circle cx="12" cy="12" r="9"/><path d="M3 12h18M12 3c3 3 3 15 0 18M12 3c-3 3-3 15 0 18"/>',
62
+ primary,
63
+ ),
64
+ authorization: createIcon(
65
+ '<circle cx="8" cy="15" r="4"/><path d="m11 12 7-7 3 3-7 7"/><path d="m15 8 2 2"/>',
66
+ warning,
67
+ ),
68
+ feedback: createIcon('<path d="M4 5h16v12H8l-4 4z"/>', violet),
69
+ contact: createIcon(
70
+ '<path d="M4 5c0 9 6 15 15 15 1 0 2-1 2-2v-2l-4-2-2 2a11 11 0 0 1-5-5l2-2-2-4H6c-1 0-2 1-2 2Z"/>',
71
+ primary,
72
+ ),
73
+ }
37
74
  })
38
75
 
76
+ /** 处理主题外观选择并持久化。 */
77
+ const onThemeModeChange = (event: { detail: { value: string | number } }) => {
78
+ const nextMode = themeModeOptions[Number(event.detail.value)]
79
+ if (nextMode) themeStore.setMode(nextMode)
80
+ }
81
+
39
82
  /** 处理语言选择并刷新动态本地化数据。 */
40
83
  const onLocaleChange = (event: { detail: { value: string | number } }) => {
41
84
  const nextLocale = localeOptions.value[Number(event.detail.value)]?.language_code as
@@ -107,6 +150,15 @@ const onMfaDisabled = async () => {
107
150
  uni.reLaunch({ url: '/pages/login/login' })
108
151
  }
109
152
 
153
+ /** 返回个人中心,页面栈不可返回时回到首页。 */
154
+ const onBack = () => {
155
+ uni.navigateBack({
156
+ fail: () => {
157
+ uni.reLaunch({ url: '/pages/index/index' })
158
+ },
159
+ })
160
+ }
161
+
110
162
  // 公共设置模块挂载时加载 MFA 状态,兼容页面包装和业务插槽场景。
111
163
  onMounted(() => {
112
164
  if (!userStore.ensureAuthenticated()) {
@@ -126,7 +178,8 @@ const onLogout = () => {
126
178
  // 模态弹窗
127
179
  uni.showModal({
128
180
  content: t('core.settings.logout_confirm'),
129
- confirmColor: '#27BA9B',
181
+ confirmColor: themeStore.theme === 'light' ? '#039e8c' : '#28f0d4',
182
+ cancelColor: themeStore.theme === 'light' ? '#5a7083' : '#8aa0b4',
130
183
  success: async (res) => {
131
184
  if (!res.confirm) {
132
185
  return
@@ -153,37 +206,98 @@ const onLogout = () => {
153
206
  </script>
154
207
 
155
208
  <template>
156
- <view class="viewport">
209
+ <view class="settings-page" :class="themeStore.themeClass">
157
210
  <slot name="extensions" />
158
- <view class="list">
159
- <view v-if="userStore.isAuthenticated()" class="item arrow mfa-item" @tap="onMfaTap">
160
- <text>{{ t('core.settings.mfa_title') }}</text>
161
- <text class="mfa-value">{{ mfaStatusText }}</text>
211
+ <view class="k-page-nav">
212
+ <view class="k-page-nav__back" :aria-label="t('common.action.back')" @tap="onBack"></view>
213
+ <view class="k-page-nav__title">{{ t('core.settings.title') }}</view>
214
+ <view class="k-page-nav__side" aria-hidden="true"></view>
215
+ </view>
216
+ <view class="settings-content">
217
+ <view class="k-set-cat">{{ t('core.settings.section_appearance') }}</view>
218
+ <picker :range="themeModeLabels" :value="themeModeIndex" @change="onThemeModeChange">
219
+ <view class="k-set-row k-set-row--arrow">
220
+ <view class="k-set-row__lead">
221
+ <view class="k-set-row__icon">
222
+ <image class="settings-row__icon" :src="settingsIcons.theme" mode="aspectFit" />
223
+ </view>
224
+ <text class="k-set-row__title">{{ t('core.settings.theme') }}</text>
225
+ </view>
226
+ <text class="k-set-row__value settings-row__value">{{
227
+ themeModeLabels[themeModeIndex]
228
+ }}</text>
229
+ </view>
230
+ </picker>
231
+
232
+ <view class="k-set-cat">{{ t('core.settings.section_security') }}</view>
233
+ <view v-if="userStore.isAuthenticated()" class="k-set-row k-set-row--arrow" @tap="onMfaTap">
234
+ <view class="k-set-row__lead">
235
+ <view class="k-set-row__icon">
236
+ <image class="settings-row__icon" :src="settingsIcons.mfa" mode="aspectFit" />
237
+ </view>
238
+ <text class="k-set-row__title">{{ t('core.settings.mfa_title') }}</text>
239
+ </view>
240
+ <text
241
+ class="k-set-row__value settings-row__value"
242
+ :class="{ 'k-set-row__value--on': mfaEnabled }"
243
+ >{{ mfaStatusText }}</text
244
+ >
162
245
  </view>
163
246
  <picker :range="localeLabels" :value="localeIndex" @change="onLocaleChange">
164
- <view class="item arrow locale-item">
165
- <text>{{ t('common.field.language') }}</text>
166
- <text class="locale-value">{{ localeLabels[localeIndex] }}</text>
247
+ <view class="k-set-row k-set-row--arrow">
248
+ <view class="k-set-row__lead">
249
+ <view class="k-set-row__icon">
250
+ <image class="settings-row__icon" :src="settingsIcons.language" mode="aspectFit" />
251
+ </view>
252
+ <text class="k-set-row__title">{{ t('common.field.language') }}</text>
253
+ </view>
254
+ <text class="k-set-row__value settings-row__value">{{ localeLabels[localeIndex] }}</text>
167
255
  </view>
168
256
  </picker>
169
- </view>
170
- <!-- #ifdef MP-WEIXIN -->
171
- <!-- 列表2 -->
172
- <view class="list">
173
- <button hover-class="none" class="item arrow" open-type="openSetting">
174
- {{ t('core.settings.authorization') }}
257
+
258
+ <!-- #ifdef MP-WEIXIN -->
259
+ <view class="k-set-cat">{{ t('core.settings.section_more') }}</view>
260
+ <button
261
+ hover-class="none"
262
+ class="k-set-row k-set-row--arrow settings-row--button"
263
+ open-type="openSetting"
264
+ >
265
+ <view class="k-set-row__lead">
266
+ <view class="k-set-row__icon">
267
+ <image class="settings-row__icon" :src="settingsIcons.authorization" mode="aspectFit" />
268
+ </view>
269
+ <text class="k-set-row__title">{{ t('core.settings.authorization') }}</text>
270
+ </view>
175
271
  </button>
176
- <button hover-class="none" class="item arrow" open-type="feedback">
177
- {{ t('core.settings.feedback') }}
272
+ <button
273
+ hover-class="none"
274
+ class="k-set-row k-set-row--arrow settings-row--button"
275
+ open-type="feedback"
276
+ >
277
+ <view class="k-set-row__lead">
278
+ <view class="k-set-row__icon">
279
+ <image class="settings-row__icon" :src="settingsIcons.feedback" mode="aspectFit" />
280
+ </view>
281
+ <text class="k-set-row__title">{{ t('core.settings.feedback') }}</text>
282
+ </view>
178
283
  </button>
179
- <button hover-class="none" class="item arrow" open-type="contact">
180
- {{ t('core.settings.contact') }}
284
+ <button
285
+ hover-class="none"
286
+ class="k-set-row k-set-row--arrow settings-row--button"
287
+ open-type="contact"
288
+ >
289
+ <view class="k-set-row__lead">
290
+ <view class="k-set-row__icon">
291
+ <image class="settings-row__icon" :src="settingsIcons.contact" mode="aspectFit" />
292
+ </view>
293
+ <text class="k-set-row__title">{{ t('core.settings.contact') }}</text>
294
+ </view>
181
295
  </button>
182
- </view>
183
- <!-- #endif -->
184
- <!-- 操作按钮 -->
185
- <view class="action" v-if="userStore.isAuthenticated()">
186
- <view @tap="onLogout" class="button">{{ t('common.action.logout') }}</view>
296
+ <!-- #endif -->
297
+
298
+ <view v-if="userStore.isAuthenticated()" class="k-set-logout" @tap="onLogout">{{
299
+ t('common.action.logout')
300
+ }}</view>
187
301
  </view>
188
302
  </view>
189
303
  <MfaManageDialog
@@ -196,101 +310,39 @@ const onLogout = () => {
196
310
  </template>
197
311
 
198
312
  <style scoped lang="scss">
199
- .viewport {
313
+ .settings-page {
200
314
  min-height: 100vh;
201
- padding: 20rpx;
202
315
  box-sizing: border-box;
203
- background-color: #f4f4f4;
316
+ padding: 0 32rpx calc(40rpx + env(safe-area-inset-bottom));
317
+ background: var(--kratos-bg-gradient);
204
318
  }
205
-
206
- /* 列表 */
207
- .list {
208
- padding: 0 20rpx;
209
- background-color: #fff;
210
- margin-bottom: 20rpx;
211
- border-radius: 10rpx;
212
- .item {
213
- line-height: 90rpx;
214
- padding-left: 10rpx;
215
- font-size: 30rpx;
216
- color: #333;
217
- border-top: 1rpx solid #ddd;
218
- position: relative;
219
- text-align: left;
220
- border-radius: 0;
221
- background-color: #fff;
222
- &::after {
223
- width: auto;
224
- height: auto;
225
- left: auto;
226
- border: none;
227
- }
228
- &::after {
229
- right: 5rpx;
230
- }
231
- }
232
- > .item:first-child {
233
- border: none;
234
- }
235
- .arrow::after {
236
- content: '›';
237
- position: absolute;
238
- top: 50%;
239
- color: #ccc;
240
- font-size: 36rpx;
241
- transform: translateY(-50%);
242
- }
319
+ .settings-content {
320
+ padding-bottom: 20rpx;
243
321
  }
244
322
 
245
- .locale-item {
246
- display: flex;
247
- min-width: 0;
248
- justify-content: space-between;
249
- padding-right: 42rpx;
250
-
251
- > text:first-child {
252
- flex-shrink: 0;
253
- }
323
+ /* 行图标图片尺寸(挂在 .k-set-row__icon 内的 image 上) */
324
+ .settings-row__icon {
325
+ width: 38rpx;
326
+ height: 38rpx;
254
327
  }
255
328
 
256
- .locale-value {
257
- flex: 1;
329
+ /* 行尾值文本截断(配合全局 .k-set-row__value 使用) */
330
+ .settings-row__value {
258
331
  min-width: 0;
332
+ max-width: 45%;
259
333
  overflow: hidden;
260
- margin-left: 24rpx;
261
- color: #888;
262
- text-align: right;
263
334
  text-overflow: ellipsis;
264
335
  white-space: nowrap;
265
336
  }
266
337
 
267
- .mfa-item {
268
- display: flex;
269
- align-items: center;
270
- justify-content: space-between;
271
- padding-right: 42rpx;
272
- }
273
-
274
- .mfa-value {
275
- max-width: 55%;
276
- overflow: hidden;
277
- color: #888;
278
- font-size: 26rpx;
279
- text-overflow: ellipsis;
280
- white-space: nowrap;
338
+ /* 小程序 button 皮肤为 .k-set-row:去掉原生边框,保留箭头 ::after */
339
+ .settings-row--button {
340
+ line-height: normal;
281
341
  }
282
342
 
283
- /* 操作按钮 */
284
- .action {
285
- text-align: center;
286
- line-height: 90rpx;
287
- margin-top: 40rpx;
288
- font-size: 32rpx;
289
- color: #333;
290
- .button {
291
- background-color: #fff;
292
- margin-bottom: 20rpx;
293
- border-radius: 10rpx;
294
- }
343
+ .settings-row--button::after {
344
+ position: static;
345
+ border-left: 0;
346
+ border-bottom: 0;
295
347
  }
296
348
  </style>
@@ -2,23 +2,37 @@
2
2
  import { computed, onMounted } from 'vue'
3
3
  import { resolveModuleIcon, resolveStaticView } from '../module'
4
4
  import { APP_MENU_ROOT_ID, useAppMenuBadge, useAppNavigation } from '../navigation'
5
- import { useI18n } from '../locales'
5
+ import { useThemeStore } from '../stores'
6
6
  import { resolveRootMenuId } from '../navigation-tree.mjs'
7
7
 
8
8
  const props = defineProps<{ route: string }>()
9
9
  const { menus, tabBar, navigate } = useAppNavigation()
10
- const { t } = useI18n()
10
+ const themeStore = useThemeStore()
11
11
 
12
- /** 按稳定 viewKey 优先取界面语言标签,未配置的菜单回退后端标题。 */
13
- const tabBarTitleKeys: Record<string, string> = {
14
- HOME: 'core.navigation.home',
15
- APP_ANALYTIC: 'core.navigation.analytics',
16
- APP_REPORT: 'core.navigation.report',
17
- MESSAGE_INBOX: 'core.navigation.message',
18
- PROFILE_HOME: 'core.navigation.my',
12
+ /** 设计稿导航坞线稿图标(viewKey → SVG path)。 */
13
+ const TAB_ICON_PATHS: Record<string, string> = {
14
+ HOME: 'M3 11 12 3l9 8M5 10v10h14V10',
15
+ APP_ANALYTIC: 'M4 20V10M10 20V4M16 20v-7M21 20H3',
16
+ APP_REPORT: 'M6 4h9l5 5v11H6zM14 4v5h5',
17
+ MESSAGE_INBOX: 'M4 5h16v12H8l-4 4z',
18
+ PROFILE_HOME: 'M12 12a4 4 0 1 0 0-8 4 4 0 0 0 0 8ZM4 21c0-4 3.5-6 8-6s8 2 8 6',
19
19
  }
20
- const tabBarTitle = (item: { viewKey: string; title: string }): string =>
21
- (tabBarTitleKeys[item.viewKey] ? t(tabBarTitleKeys[item.viewKey]) : '') || item.title
20
+
21
+ /** 按设计稿线稿生成随主题与激活态取色的图标地址,未知 viewKey 返回空串走后端图标兜底。 */
22
+ const tabIconSrc = (viewKey: string, active: boolean): string => {
23
+ const path = TAB_ICON_PATHS[viewKey]
24
+ if (!path) return ''
25
+ const color = active
26
+ ? themeStore.theme === 'light'
27
+ ? '#039e8c'
28
+ : '#28f0d4'
29
+ : themeStore.theme === 'light'
30
+ ? '#9aabbb'
31
+ : '#4e6378'
32
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="${path}"/></svg>`
33
+ return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`
34
+ }
35
+
22
36
  const activeMenu = computed(() => {
23
37
  const routeMenu = menus.value.find((menu) => resolveStaticView(menu.viewKey) === props.route)
24
38
  if (!routeMenu) return
@@ -36,11 +50,12 @@ onMounted(() => {
36
50
  </script>
37
51
 
38
52
  <template>
39
- <view v-if="visible" class="kratos-tab-bar">
53
+ <view v-if="visible" class="kratos-tab-bar" :class="themeStore.themeClass">
40
54
  <view
41
55
  v-for="item in tabBar"
42
56
  :key="item.id"
43
57
  class="kratos-tab-bar__item"
58
+ :class="{ 'kratos-tab-bar__item--active': activeMenu?.id === item.id }"
44
59
  @tap="navigate(item.path)"
45
60
  >
46
61
  <view
@@ -49,7 +64,13 @@ onMounted(() => {
49
64
  aria-hidden="true"
50
65
  />
51
66
  <image
52
- v-if="resolveModuleIcon(item.icon)"
67
+ v-if="tabIconSrc(item.viewKey, activeMenu?.id === item.id)"
68
+ class="kratos-tab-bar__icon"
69
+ :src="tabIconSrc(item.viewKey, activeMenu?.id === item.id)"
70
+ mode="aspectFit"
71
+ />
72
+ <image
73
+ v-else-if="resolveModuleIcon(item.icon)"
53
74
  class="kratos-tab-bar__icon"
54
75
  :src="
55
76
  resolveModuleIcon(
@@ -65,7 +86,7 @@ onMounted(() => {
65
86
  aria-hidden="true"
66
87
  />
67
88
  <text :class="{ 'kratos-tab-bar__text--active': activeMenu?.id === item.id }">
68
- {{ tabBarTitle(item) }}
89
+ {{ item.title }}
69
90
  </text>
70
91
  </view>
71
92
  </view>
@@ -73,20 +94,22 @@ onMounted(() => {
73
94
 
74
95
  <style scoped>
75
96
  .kratos-tab-bar {
76
- --kratos-tab-bar-color: #333;
77
- --kratos-tab-bar-active-color: #27ba9b;
97
+ --kratos-tab-bar-color: var(--kratos-color-text-dim, #4e6378);
98
+ --kratos-tab-bar-active-color: var(--kratos-color-primary, #28f0d4);
78
99
 
79
100
  position: fixed;
80
- right: 0;
81
- bottom: 0;
82
- left: 0;
101
+ right: 20rpx;
102
+ bottom: env(safe-area-inset-bottom);
103
+ left: 20rpx;
83
104
  z-index: 999;
84
- box-sizing: border-box;
85
105
  display: flex;
86
- height: calc(48px + env(safe-area-inset-bottom));
87
- padding-bottom: env(safe-area-inset-bottom);
88
- border-top: 1px solid #eee;
89
- background: #fff;
106
+ height: 104rpx;
107
+ padding: 0 8rpx;
108
+ border: 1rpx solid var(--kratos-color-border-accent, rgba(120, 230, 215, 0.16));
109
+ border-radius: 30rpx;
110
+ background: var(--kratos-dock-bg, rgba(10, 18, 30, 0.72));
111
+ box-shadow: 0 24rpx 64rpx rgba(0, 0, 0, 0.45);
112
+ backdrop-filter: blur(32rpx);
90
113
  }
91
114
  .kratos-tab-bar__item {
92
115
  position: relative;
@@ -95,11 +118,24 @@ onMounted(() => {
95
118
  align-items: center;
96
119
  justify-content: center;
97
120
  flex-direction: column;
98
- transform: translateY(clamp(1px, env(safe-area-inset-bottom), 7px));
99
121
  color: var(--kratos-tab-bar-color);
100
122
  font-size: 10px;
101
123
  line-height: 12px;
102
124
  }
125
+
126
+ /* 设计稿:激活项顶部霓虹发光条 */
127
+ .kratos-tab-bar__item--active::before {
128
+ position: absolute;
129
+ top: 0;
130
+ left: 50%;
131
+ width: 26px;
132
+ height: 3px;
133
+ border-radius: 3px;
134
+ background: var(--kratos-tab-bar-active-color);
135
+ box-shadow: 0 0 12px var(--kratos-tab-bar-active-color);
136
+ content: '';
137
+ transform: translateX(-50%);
138
+ }
103
139
  .kratos-tab-bar__icon {
104
140
  width: 28px;
105
141
  height: 28px;
@@ -135,7 +171,7 @@ onMounted(() => {
135
171
  height: 7px;
136
172
  border-right: 2px solid var(--kratos-tab-bar-color);
137
173
  border-bottom: 2px solid var(--kratos-tab-bar-color);
138
- background: #fff;
174
+ background: var(--kratos-dock-bg, rgba(10, 18, 30, 0.72));
139
175
  content: '';
140
176
  transform: rotate(45deg);
141
177
  }
@@ -153,6 +189,8 @@ onMounted(() => {
153
189
  }
154
190
  .kratos-tab-bar__text--active {
155
191
  color: var(--kratos-tab-bar-active-color);
192
+ font-weight: 700;
193
+ text-shadow: 0 0 12px currentColor;
156
194
  }
157
195
  .kratos-tab-bar__badge {
158
196
  position: absolute;
@@ -161,8 +199,9 @@ onMounted(() => {
161
199
  width: 10px;
162
200
  height: 10px;
163
201
  box-sizing: border-box;
164
- border: 2px solid #fff;
202
+ border: 2px solid var(--kratos-dock-bg, rgba(10, 18, 30, 0.72));
165
203
  border-radius: 50%;
166
- background: #e5484d;
204
+ background: var(--kratos-color-danger, #ff4d6d);
205
+ box-shadow: 0 0 12px var(--kratos-color-danger, #ff4d6d);
167
206
  }
168
207
  </style>