@liujitcn/kratos-uni-app-core 0.0.54 → 0.0.56

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
@@ -655,6 +655,8 @@ var coreModule = defineKratosAppModule({
655
655
  icons: {
656
656
  HOME_DEFAULT: "static/tabs/home_default.png",
657
657
  HOME_SELECTED: "static/tabs/home_selected.png",
658
+ MESSAGE_DEFAULT: "static/tabs/message_default.png",
659
+ MESSAGE_SELECTED: "static/tabs/message_selected.png",
658
660
  USER_DEFAULT: "static/tabs/user_default.png",
659
661
  USER_SELECTED: "static/tabs/user_selected.png"
660
662
  },
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.54",
10
+ "version": "0.0.56",
11
11
  "type": "module",
12
12
  "files": [
13
13
  "dist",
@@ -3,6 +3,8 @@ import type {
3
3
  ConfigService,
4
4
  GetConfigRequest,
5
5
  GetConfigResponse,
6
+ GetI18nCustomRequest,
7
+ GetI18nCustomResponse,
6
8
  } from '../../../rpc/base/v1/config'
7
9
 
8
10
  const CONFIG_URL = '/v1/base/config'
@@ -19,6 +21,15 @@ export class ConfigServiceImpl implements ConfigService {
19
21
  header: { Authorization: 'no-auth' },
20
22
  })
21
23
  }
24
+
25
+ /** 获取当前租户的自定义国际化覆盖项。 */
26
+ GetI18nCustom(request: GetI18nCustomRequest): Promise<GetI18nCustomResponse> {
27
+ return http<GetI18nCustomResponse>({
28
+ url: `${CONFIG_URL}/i18n-custom`,
29
+ method: 'GET',
30
+ data: request,
31
+ })
32
+ }
22
33
  }
23
34
 
24
35
  export const defConfigService = new ConfigServiceImpl()
@@ -1,8 +1,9 @@
1
1
  import { http } from '../../../utils/http'
2
- import type {
3
- DownloadFileRequest,
4
- MultiUploadFileResponse,
5
- FileInfo,
2
+ import {
3
+ BaseFileAccessMode,
4
+ type DownloadFileRequest,
5
+ type MultiUploadFileResponse,
6
+ type FileInfo,
6
7
  } from '../../../rpc/base/v1/file'
7
8
 
8
9
  const FILE_URL = '/v1/base/file'
@@ -10,12 +11,17 @@ const FILE_URL = '/v1/base/file'
10
11
  /** 文件服务 */
11
12
  export class FileServiceImpl {
12
13
  /** 多个文件上传 */
13
- MultiUploadFile(files: File[], fileType: string): Promise<MultiUploadFileResponse> {
14
+ MultiUploadFile(
15
+ files: File[],
16
+ fileType: string,
17
+ accessMode = BaseFileAccessMode.BASE_FILE_ACCESS_MODE_AUTHORIZED,
18
+ ): Promise<MultiUploadFileResponse> {
14
19
  const formData = new FormData()
15
20
  files.map((file) => {
16
21
  formData.append(file.name, file)
17
22
  })
18
23
  formData.append('fileType', fileType)
24
+ formData.append('accessMode', String(accessMode))
19
25
  return http<MultiUploadFileResponse>({
20
26
  url: `${FILE_URL}/multi`,
21
27
  method: 'POST',
@@ -27,10 +33,15 @@ export class FileServiceImpl {
27
33
  })
28
34
  }
29
35
  /** 单个文件上传 */
30
- UploadFile(file: File, fileType: string): Promise<FileInfo> {
36
+ UploadFile(
37
+ file: File,
38
+ fileType: string,
39
+ accessMode = BaseFileAccessMode.BASE_FILE_ACCESS_MODE_AUTHORIZED,
40
+ ): Promise<FileInfo> {
31
41
  const formData = new FormData()
32
42
  formData.append('file', file)
33
43
  formData.append('fileType', fileType)
44
+ formData.append('accessMode', String(accessMode))
34
45
  return http<FileInfo>({
35
46
  url: `${FILE_URL}`,
36
47
  method: 'POST',
@@ -9,4 +9,4 @@
9
9
  | `en-US.json` | 英文 Core 语言包。 | uni-app 切换为英文时使用。 |
10
10
  | `ja-JP.json` | 日文 Core 语言包。 | uni-app 切换为日文时使用。 |
11
11
 
12
- 语言包是标准 JSON,不能在文件内直接写注释;公共键使用 `common.` 或 `core.` 命名空间。新增语言后执行仓库根目录的 `make i18n-sync`。语言切换选项固定显示后端 `base_language.native_name`,接口不可用时使用代码内置的原生名称;`common.language.*` 仅用于普通文案和语言迁移初始数据。新增语言的完整文件清单见[国际化语言扩展指南](../../../../../../docs/国际化语言扩展指南.md)。
12
+ 语言包是标准 JSON,不能在文件内直接写注释;公共键使用 `common.` 或 `core.` 命名空间。新增语言后执行仓库根目录的 `make i18n`。语言切换选项固定显示后端 `base_language.native_name`,接口不可用时使用代码内置的原生名称;`common.language.*` 仅用于普通文案和语言迁移初始数据。新增语言的完整文件清单见[国际化语言扩展指南](../../../../../../docs/国际化语言扩展指南.md)。
package/src/module.ts CHANGED
@@ -85,6 +85,8 @@ export const coreModule = defineKratosAppModule({
85
85
  icons: {
86
86
  HOME_DEFAULT: 'static/tabs/home_default.png',
87
87
  HOME_SELECTED: 'static/tabs/home_selected.png',
88
+ MESSAGE_DEFAULT: 'static/tabs/message_default.png',
89
+ MESSAGE_SELECTED: 'static/tabs/message_selected.png',
88
90
  USER_DEFAULT: 'static/tabs/user_default.png',
89
91
  USER_SELECTED: 'static/tabs/user_selected.png',
90
92
  },
@@ -40,8 +40,18 @@ export interface GetConfigResponse {
40
40
  configs: ConfigItem[];
41
41
  /** AI 助手是否可用 */
42
42
  ai_enabled: boolean;
43
+ }
44
+
45
+ /** 当前租户自定义国际化覆盖项查询条件。 */
46
+ export interface GetI18nCustomRequest {
47
+ /** 位置:枚举【BaseConfigSite】 */
48
+ site: BaseConfigSite;
49
+ }
50
+
51
+ /** 当前租户自定义国际化覆盖项查询结果。 */
52
+ export interface GetI18nCustomResponse {
43
53
  /** 全部语言的自定义国际化覆盖项 */
44
- i18n_customs: I18nCustomItem[];
54
+ items: I18nCustomItem[];
45
55
  }
46
56
 
47
57
  /** 全部语言的自定义国际化覆盖项。 */
@@ -58,4 +68,6 @@ export interface I18nCustomItem {
58
68
  export interface ConfigService {
59
69
  /** 获取系统配置 */
60
70
  GetConfig(request: GetConfigRequest): Promise<GetConfigResponse>;
71
+ /** 获取当前租户的自定义国际化覆盖项 */
72
+ GetI18nCustom(request: GetI18nCustomRequest): Promise<GetI18nCustomResponse>;
61
73
  }
@@ -7,6 +7,16 @@
7
7
  /* eslint-disable */
8
8
  import type { BytesValue } from "../../google/protobuf/wrappers";
9
9
 
10
+ /** 文件访问方式。 */
11
+ export enum BaseFileAccessMode {
12
+ /** BASE_FILE_ACCESS_MODE_UNSPECIFIED - 未指定文件访问方式,服务端按授权访问处理。 */
13
+ BASE_FILE_ACCESS_MODE_UNSPECIFIED = 0,
14
+ /** BASE_FILE_ACCESS_MODE_PUBLIC - 允许匿名公开访问。 */
15
+ BASE_FILE_ACCESS_MODE_PUBLIC = 1,
16
+ /** BASE_FILE_ACCESS_MODE_AUTHORIZED - 必须携带有效访问令牌。 */
17
+ BASE_FILE_ACCESS_MODE_AUTHORIZED = 2,
18
+ }
19
+
10
20
  /** 批量上传文件请求参数 */
11
21
  export interface MultiUploadFileRequest {
12
22
  /** 文件名 */
@@ -35,6 +45,8 @@ export interface UploadFileInfo {
35
45
  path: string;
36
46
  /** 二进制内容将通过 base64 编码传输 */
37
47
  content: Uint8Array;
48
+ /** 文件访问方式,未指定时默认需要访问令牌 */
49
+ access_mode: BaseFileAccessMode;
38
50
  }
39
51
 
40
52
  /** 下载文件请求参数 */
@@ -36,7 +36,6 @@ export const useSettingStore = defineStore('setting', () => {
36
36
  aiEnabled.value = false
37
37
  return
38
38
  }
39
- applyCustomLocaleMessages(res.i18n_customs ?? [])
40
39
  const nextData = new Map<string, string>()
41
40
  res.configs.forEach((item) => {
42
41
  nextData.set(item.key, item.value)
@@ -63,9 +62,24 @@ export const useSettingStore = defineStore('setting', () => {
63
62
  }
64
63
  }
65
64
 
65
+ /** 登录成功后加载当前租户的自定义国际化覆盖项。 */
66
+ const loadI18nCustom = async () => {
67
+ const response = await defConfigService.GetI18nCustom({
68
+ site: BaseConfigSite.BASE_CONFIG_SITE_APP,
69
+ })
70
+ applyCustomLocaleMessages(response.items ?? [])
71
+ }
72
+
73
+ /** 清空租户自定义翻译并恢复静态语言包。 */
74
+ const resetI18nCustom = () => {
75
+ applyCustomLocaleMessages([])
76
+ }
77
+
66
78
  return {
67
79
  getData,
68
80
  aiEnabled,
69
81
  loadData,
82
+ loadI18nCustom,
83
+ resetI18nCustom,
70
84
  }
71
85
  })
@@ -20,6 +20,7 @@ import {
20
20
  setTokenExpiresIn,
21
21
  hasValidToken,
22
22
  } from '../../utils/auth'
23
+ import { useSettingStore } from './setting'
23
24
 
24
25
  const AUTH_SILENT_LOGOUT_EVENT = 'auth:silent-logout'
25
26
  let silentLogoutEventHandler: (() => void) | undefined
@@ -74,6 +75,12 @@ export const useUserStore = defineStore(
74
75
  setToken(token_type + ' ' + access_token)
75
76
  setRefreshToken(refresh_token)
76
77
  setTokenExpiresIn(expires_in)
78
+ const settingStore = useSettingStore()
79
+ try {
80
+ await settingStore.loadI18nCustom()
81
+ } catch {
82
+ settingStore.resetI18nCustom()
83
+ }
77
84
  await runUserStoreExtensions('onLogin')
78
85
  }
79
86
 
@@ -223,6 +230,7 @@ export const useUserStore = defineStore(
223
230
  async function clearUserData() {
224
231
  clearToken()
225
232
  userInfo.value = undefined
233
+ useSettingStore().resetI18nCustom()
226
234
  await runUserStoreExtensions('onLogout')
227
235
  }
228
236
 
@@ -230,6 +238,7 @@ export const useUserStore = defineStore(
230
238
  function silentLogout() {
231
239
  clearToken()
232
240
  userInfo.value = undefined
241
+ useSettingStore().resetI18nCustom()
233
242
  uni.removeStorageSync('user')
234
243
  void runUserStoreExtensions('onSilentLogout')
235
244
  }
@@ -248,6 +257,7 @@ export const useUserStore = defineStore(
248
257
  }
249
258
  silentLogoutEventHandler = () => {
250
259
  userInfo.value = undefined
260
+ useSettingStore().resetI18nCustom()
251
261
  void runUserStoreExtensions('onSilentLogout')
252
262
  }
253
263
  uni.$on(AUTH_SILENT_LOGOUT_EVENT, silentLogoutEventHandler)
package/src/utils/file.ts CHANGED
@@ -1,10 +1,18 @@
1
- import type { FileInfo, MultiUploadFileResponse } from '../rpc/base/v1/file'
1
+ import {
2
+ BaseFileAccessMode,
3
+ type FileInfo,
4
+ type MultiUploadFileResponse,
5
+ } from '../rpc/base/v1/file'
2
6
  import { formatSrc } from './index'
3
7
  import { getLocaleRequestHeaders, t } from '../locales'
4
8
  import { getRequestAccessToken } from './http'
5
9
 
6
10
  // 文件上传-兼容小程序端、H5端、App端
7
- export const uploadFile = async (fileType: string, filePath: string): Promise<FileInfo> => {
11
+ export const uploadFile = async (
12
+ fileType: string,
13
+ filePath: string,
14
+ accessMode = BaseFileAccessMode.BASE_FILE_ACCESS_MODE_AUTHORIZED,
15
+ ): Promise<FileInfo> => {
8
16
  const token = await getRequestAccessToken()
9
17
  const res = await uni.uploadFile({
10
18
  url: '/v1/base/file',
@@ -12,6 +20,7 @@ export const uploadFile = async (fileType: string, filePath: string): Promise<Fi
12
20
  filePath: filePath,
13
21
  formData: {
14
22
  fileType: fileType,
23
+ accessMode: String(accessMode),
15
24
  },
16
25
  header: {
17
26
  ...getLocaleRequestHeaders(),
@@ -30,15 +39,20 @@ export const uploadFile = async (fileType: string, filePath: string): Promise<Fi
30
39
  export const uploadFileList = async (
31
40
  fileType: string,
32
41
  filePaths: string[],
42
+ accessMode = BaseFileAccessMode.BASE_FILE_ACCESS_MODE_AUTHORIZED,
33
43
  ): Promise<FileInfo[]> => {
34
44
  const results = await Promise.allSettled(
35
- filePaths.map((filePath) => uploadFile(fileType, filePath)),
45
+ filePaths.map((filePath) => uploadFile(fileType, filePath, accessMode)),
36
46
  )
37
47
  return results.flatMap((result) => (result.status === 'fulfilled' ? [result.value] : []))
38
48
  }
39
49
 
40
50
  // 多文件上传-兼容小程序端、H5端、App端
41
- export const multiUploadFile = async (fileType: string, files: any): Promise<FileInfo[]> => {
51
+ export const multiUploadFile = async (
52
+ fileType: string,
53
+ files: any,
54
+ accessMode = BaseFileAccessMode.BASE_FILE_ACCESS_MODE_AUTHORIZED,
55
+ ): Promise<FileInfo[]> => {
42
56
  const token = await getRequestAccessToken()
43
57
  const res = await uni.uploadFile({
44
58
  url: '/v1/base/file/multi',
@@ -47,6 +61,7 @@ export const multiUploadFile = async (fileType: string, files: any): Promise<Fil
47
61
  files: files,
48
62
  formData: {
49
63
  fileType: fileType,
64
+ accessMode: String(accessMode),
50
65
  },
51
66
  header: {
52
67
  ...getLocaleRequestHeaders(),
package/src/utils/http.ts CHANGED
@@ -72,8 +72,8 @@ type HttpRequestOptions = UniApp.RequestOptions & {
72
72
  // 添加拦截器
73
73
  const httpInterceptor = {
74
74
  // 拦截前触发
75
- invoke(options: UniApp.RequestOptions) {
76
- const authMode = resolveAuthMode(options as HttpRequestOptions, options.url)
75
+ invoke(options: HttpRequestOptions) {
76
+ const authMode = resolveAuthMode(options, options.url)
77
77
  // 1. 相对接口地址只拼接一次基础路径,兼容 HMR 等场景下拦截器重复注册。
78
78
  if (
79
79
  !options.url.startsWith('http') &&
@@ -1,4 +1,11 @@
1
- const baseURL = import.meta.env.VITE_APP_STATIC_URL
1
+ const baseURL = import.meta.env.VITE_APP_STATIC_URL || import.meta.env.VITE_APP_API_URL
2
+
3
+ /** 将后端数据文件地址切换到当前构建配置的静态资源域名。 */
4
+ const rewriteDataFileOrigin = (src: string, staticOrigin: string) => {
5
+ if (!staticOrigin) return src
6
+ const match = src.match(/^https?:\/\/[^/]+(\/data(?:\/|$).*)$/)
7
+ return match ? `${staticOrigin.replace(/\/$/, '')}${match[1]}` : src
8
+ }
2
9
 
3
10
  /**
4
11
  * 解析静态资源访问前缀
@@ -60,12 +67,11 @@ export const formatSrc = (src: string) => {
60
67
  if (!src) {
61
68
  return src
62
69
  }
63
- // 已经是绝对地址时直接返回,避免重复拼接。
70
+ const staticOrigin = resolveStaticOrigin()
71
+ // 后端数据文件即使保存了旧的绝对域名,也统一使用当前静态资源域名。
64
72
  if (/^https?:\/\//.test(src)) {
65
- return src
73
+ return rewriteDataFileOrigin(src, staticOrigin)
66
74
  }
67
-
68
- const staticOrigin = resolveStaticOrigin()
69
75
  if (!staticOrigin) {
70
76
  return src
71
77
  }
@@ -1,15 +1,35 @@
1
1
  <script setup lang="ts">
2
2
  import { onLoad } from '@dcloudio/uni-app'
3
- import { computed } from 'vue'
3
+ import { computed, ref, watch } from 'vue'
4
4
  import { useSettingStore } from '../../../stores'
5
5
  import defaultLogo from '../../../static/images/logo_icon.png'
6
+ import { formatSrc } from '../../../utils'
6
7
  import { useI18n } from '../../../locales'
7
8
 
8
9
  const settingStore = useSettingStore()
9
10
  const { t } = useI18n()
10
11
  const mainTitle = computed(() => settingStore.getData('mainTitle') || t('core.home.main_title'))
11
12
  const subTitle = computed(() => settingStore.getData('subTitle') || t('core.home.sub_title'))
12
- const appLogo = computed(() => settingStore.getData('appLogo') || defaultLogo)
13
+ const configuredAppLogo = computed(() => {
14
+ const configuredLogo = settingStore.getData('appLogo')
15
+ return configuredLogo ? formatSrc(configuredLogo) : defaultLogo
16
+ })
17
+ const appLogo = ref(defaultLogo)
18
+
19
+ watch(
20
+ configuredAppLogo,
21
+ (value) => {
22
+ appLogo.value = value
23
+ },
24
+ { immediate: true },
25
+ )
26
+
27
+ /** Logo 加载失败时回退到本地默认图片。 */
28
+ const handleLogoError = () => {
29
+ if (appLogo.value !== defaultLogo) {
30
+ appLogo.value = defaultLogo
31
+ }
32
+ }
13
33
 
14
34
  onLoad(() => {
15
35
  void settingStore.loadData().catch(() => undefined)
@@ -19,7 +39,7 @@ onLoad(() => {
19
39
  <template>
20
40
  <view class="page">
21
41
  <view class="hero">
22
- <image class="logo" :src="appLogo" mode="aspectFit" />
42
+ <image class="logo" :src="appLogo" mode="aspectFit" @error="handleLogoError" />
23
43
  <view class="hero-copy">
24
44
  <text class="title">{{ mainTitle }}</text>
25
45
  <text class="subtitle">{{ subTitle }}</text>
@@ -7,6 +7,7 @@ import { computed, defineAsyncComponent, nextTick, reactive, ref, watch } from '
7
7
  import { defLoginService } from '../../../api/base/v1/login'
8
8
  import { defMfaService } from '../../../api/base/v1/mfa'
9
9
  import defaultLogo from '../../../static/images/logo_icon.png'
10
+ import { formatSrc } from '../../../utils'
10
11
  import { homeTabPage } from '../../../utils/navigation'
11
12
  import { PASSWORD_CRYPTO_SCENE, encryptPassword } from '../../../utils/passwordCrypto'
12
13
  import { createWebAuthnCredential, getWebAuthnAssertion } from '../../../utils/webauthn'
@@ -41,9 +42,28 @@ const configuredMainTitle = computed(
41
42
  const configuredSubTitle = computed(
42
43
  () => settingStore.getData('subTitle') || t('core.login.default_sub_title'),
43
44
  )
44
- const configuredAppLogo = computed(() => settingStore.getData('appLogo') || defaultLogo)
45
+ const configuredAppLogo = computed(() => {
46
+ const configuredLogo = settingStore.getData('appLogo')
47
+ return configuredLogo ? formatSrc(configuredLogo) : defaultLogo
48
+ })
49
+ const appLogo = ref(defaultLogo)
45
50
  const showTenantCode = computed(() => settingStore.getData('showTenantCode') !== 'false')
46
51
 
52
+ watch(
53
+ configuredAppLogo,
54
+ (value) => {
55
+ appLogo.value = value
56
+ },
57
+ { immediate: true },
58
+ )
59
+
60
+ /** Logo 加载失败时回退到本地默认图片。 */
61
+ const handleLogoError = () => {
62
+ if (appLogo.value !== defaultLogo) {
63
+ appLogo.value = defaultLogo
64
+ }
65
+ }
66
+
47
67
  // 是否同意协议
48
68
  const isAgreePrivacy = ref(false)
49
69
  const isAgreePrivacyShakeY = ref(false)
@@ -867,7 +887,7 @@ onLoad(() => {
867
887
  </picker>
868
888
  <view class="login-hero">
869
889
  <view class="login-logo-shell">
870
- <image :src="configuredAppLogo" />
890
+ <image :src="appLogo" @error="handleLogoError" />
871
891
  </view>
872
892
  <view class="login-hero-copy">
873
893
  <text class="login-title">{{ configuredMainTitle }}</text>