@liujitcn/kratos-uni-app-core 0.0.20 → 0.0.23

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/src/module.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  import { corePages } from './pages'
2
+ import type { LocaleMessages, SupportedLocale } from './locales'
3
+
4
+ // Core 语言包由同步脚本生成并注册,供 uni-app 公共页面、请求工具和基础组件使用。
5
+ import { LOCALE_MESSAGES } from './locales/generated'
2
6
 
3
7
  /** bootstrap 可见状态键。 */
4
8
  export type BootstrapViewKey =
@@ -21,6 +25,8 @@ export interface KratosAppModule {
21
25
  pages: Record<string, KratosAppPageConfig>
22
26
  views: Record<string, string>
23
27
  icons?: Record<string, string>
28
+ /** 模块按语言区域贡献的扁平语言包。 */
29
+ messages?: Partial<Record<SupportedLocale, LocaleMessages>>
24
30
  }
25
31
 
26
32
  /** 声明 uni-app 模块。 */
@@ -82,4 +88,5 @@ export const coreModule = defineKratosAppModule({
82
88
  USER_DEFAULT: 'static/tabs/user_default.png',
83
89
  USER_SELECTED: 'static/tabs/user_selected.png',
84
90
  },
91
+ messages: LOCALE_MESSAGES,
85
92
  })
package/src/navigation.ts CHANGED
@@ -5,6 +5,7 @@ import { navigateToLogin } from './utils/navigation'
5
5
  import { resolveStaticView } from './module'
6
6
  import { matchLogicalPath, parseLogicalQuery } from './navigation-pattern.mjs'
7
7
  import { buildMenuTree } from './navigation-tree.mjs'
8
+ import { getCurrentLocale, t } from './locales'
8
9
 
9
10
  /** 移动菜单访问模式。 */
10
11
  export type AppMenuAccess = 'PUBLIC' | 'GUEST_ONLY' | 'AUTHENTICATED'
@@ -45,14 +46,14 @@ const ANONYMOUS_CACHE_KEY = 'kratos-uni-app:navigation:anonymous'
45
46
  const AUTHENTICATED_CACHE_KEY = 'kratos-uni-app:navigation:authenticated'
46
47
  /** 固定移动端菜单根目录编号。 */
47
48
  export const APP_MENU_ROOT_ID = 999
48
- const defaultMenus: AppMenu[] = [
49
+ const createDefaultMenus = (): AppMenu[] => [
49
50
  {
50
51
  id: 99901,
51
52
  parentId: APP_MENU_ROOT_ID,
52
53
  name: 'AppHome',
53
54
  path: 'app/home',
54
55
  viewKey: 'HOME',
55
- title: '首页',
56
+ title: t('core.navigation.home'),
56
57
  access: 'PUBLIC',
57
58
  inTabBar: true,
58
59
  icon: 'HOME_DEFAULT',
@@ -64,7 +65,7 @@ const defaultMenus: AppMenu[] = [
64
65
  name: 'AppMy',
65
66
  path: 'app/my',
66
67
  viewKey: 'PROFILE_HOME',
67
- title: '我的',
68
+ title: t('core.navigation.my'),
68
69
  access: 'PUBLIC',
69
70
  inTabBar: true,
70
71
  icon: 'USER_DEFAULT',
@@ -76,7 +77,7 @@ const defaultMenus: AppMenu[] = [
76
77
  name: 'AppLogin',
77
78
  path: 'app/login',
78
79
  viewKey: 'LOGIN',
79
- title: '登录',
80
+ title: t('common.action.login'),
80
81
  access: 'GUEST_ONLY',
81
82
  inTabBar: false,
82
83
  },
@@ -86,7 +87,7 @@ const defaultMenus: AppMenu[] = [
86
87
  name: 'AppProtocol',
87
88
  path: 'app/protocol/:type',
88
89
  viewKey: 'PROTOCOL',
89
- title: '协议详情',
90
+ title: t('core.navigation.protocol'),
90
91
  access: 'PUBLIC',
91
92
  inTabBar: false,
92
93
  },
@@ -96,7 +97,7 @@ const defaultMenus: AppMenu[] = [
96
97
  name: 'AppProfile',
97
98
  path: 'app/profile',
98
99
  viewKey: 'PROFILE',
99
- title: '个人信息',
100
+ title: t('system.profile.title'),
100
101
  access: 'AUTHENTICATED',
101
102
  inTabBar: false,
102
103
  },
@@ -106,7 +107,7 @@ const defaultMenus: AppMenu[] = [
106
107
  name: 'AppSettings',
107
108
  path: 'app/settings',
108
109
  viewKey: 'SETTINGS',
109
- title: '设置',
110
+ title: t('system.settings.title'),
110
111
  access: 'AUTHENTICATED',
111
112
  inTabBar: false,
112
113
  },
@@ -116,7 +117,7 @@ const defaultMenus: AppMenu[] = [
116
117
  name: 'AppAi',
117
118
  path: 'app/ai',
118
119
  viewKey: 'AI',
119
- title: 'AI 助手',
120
+ title: t('system.ai.chat_title'),
120
121
  access: 'AUTHENTICATED',
121
122
  inTabBar: false,
122
123
  },
@@ -132,8 +133,9 @@ const defaultMenus: AppMenu[] = [
132
133
  },
133
134
  ]
134
135
 
135
- const menus = ref<AppMenu[]>(defaultMenus)
136
+ const menus = ref<AppMenu[]>([])
136
137
  const ready = ref(false)
138
+ let tabNavigationTarget: string | undefined
137
139
  let adapter: AppNavigationAdapter = {
138
140
  list: () => defBaseMenuService.ListBaseMenu(),
139
141
  }
@@ -147,7 +149,7 @@ export function setAppNavigationAdapter(nextAdapter: AppNavigationAdapter): void
147
149
  export async function initializeAppNavigation(): Promise<void> {
148
150
  const cacheKey = resolveCacheKey()
149
151
  const cached = readCachedMenus(cacheKey)
150
- menus.value = cached ?? defaultMenus
152
+ menus.value = cached ?? createDefaultMenus()
151
153
  try {
152
154
  const nextMenus = normalizeMenuResponse(await adapter.list())
153
155
  validateMenus(nextMenus)
@@ -207,13 +209,31 @@ export function navigateAppRoute(rawRoute: string, options: { replace?: boolean
207
209
  .map(([key, value]) => `${encodeURIComponent(key)}=${encodeURIComponent(value)}`)
208
210
  .join('&')
209
211
  const url = `/${resolved.physicalRoute}${query ? `?${query}` : ''}`
210
- if (options.replace || resolved.menu.inTabBar) {
212
+ if (resolved.menu.inTabBar) {
213
+ navigateTabRoute(url)
214
+ return
215
+ }
216
+ if (options.replace) {
211
217
  uni.reLaunch({ url })
212
218
  return
213
219
  }
214
220
  uni.navigateTo({ url, fail: () => uni.reLaunch({ url }) })
215
221
  }
216
222
 
223
+ /** 切换 tab 页面,避免当前页重复跳转和并发重建页面栈。 */
224
+ function navigateTabRoute(url: string): void {
225
+ const targetRoute = url.slice(1).split('?', 1)[0]
226
+ const pages = getCurrentPages()
227
+ const currentPage = pages[pages.length - 1]
228
+ if (currentPage?.route === targetRoute || tabNavigationTarget) return
229
+
230
+ tabNavigationTarget = targetRoute
231
+ const release = () => {
232
+ if (tabNavigationTarget === targetRoute) tabNavigationTarget = undefined
233
+ }
234
+ uni.reLaunch({ url, success: release, fail: release, complete: release })
235
+ }
236
+
217
237
  /** 获取导航响应式状态。 */
218
238
  export function useAppNavigation() {
219
239
  const menuTree = computed<AppMenuNode[]>(() => buildMenuTree(menus.value, APP_MENU_ROOT_ID))
@@ -235,7 +255,8 @@ export function launchAppStatus(state: import('./module').BootstrapViewKey, deta
235
255
  }
236
256
 
237
257
  function resolveCacheKey(): string {
238
- return hasValidToken() ? AUTHENTICATED_CACHE_KEY : ANONYMOUS_CACHE_KEY
258
+ const identity = hasValidToken() ? AUTHENTICATED_CACHE_KEY : ANONYMOUS_CACHE_KEY
259
+ return `${identity}:${getCurrentLocale()}`
239
260
  }
240
261
 
241
262
  function readCachedMenus(cacheKey: string): AppMenu[] | undefined {
@@ -252,15 +273,16 @@ function readCachedMenus(cacheKey: string): AppMenu[] | undefined {
252
273
 
253
274
  function normalizeMenuResponse(response: unknown): AppMenu[] {
254
275
  if (Array.isArray(response)) return response.map(normalizeMenu)
255
- if (!response || typeof response !== 'object') throw new Error('菜单响应不是对象')
276
+ if (!response || typeof response !== 'object')
277
+ throw new Error(t('core.navigation.error.response_object'))
256
278
  const record = response as Record<string, unknown>
257
279
  const list = record.items ?? record.list ?? record.data
258
- if (!Array.isArray(list)) throw new Error('菜单响应缺少扁平列表')
280
+ if (!Array.isArray(list)) throw new Error(t('core.navigation.error.list_missing'))
259
281
  return list.map(normalizeMenu)
260
282
  }
261
283
 
262
284
  function normalizeMenu(value: unknown): AppMenu {
263
- if (!value || typeof value !== 'object') throw new Error('菜单项不是对象')
285
+ if (!value || typeof value !== 'object') throw new Error(t('core.navigation.error.item_object'))
264
286
  const item = value as Record<string, unknown>
265
287
  const meta = (item.meta ?? {}) as Record<string, unknown>
266
288
  const app = (meta.app ?? item.app ?? {}) as Record<string, unknown>
@@ -292,15 +314,19 @@ function validateMenus(nextMenus: AppMenu[]): void {
292
314
  const paths = new Set<string>()
293
315
  for (const menu of nextMenus) {
294
316
  if (!Number.isSafeInteger(menu.id) || !menu.name || !menu.path.startsWith('app/')) {
295
- throw new Error('菜单 id、name 或 app/ 路径无效')
317
+ throw new Error(t('core.navigation.error.identity'))
318
+ }
319
+ if (!menu.name.startsWith('App')) {
320
+ throw new Error(t('core.navigation.error.name_prefix', { name: menu.name }))
296
321
  }
297
- if (!menu.name.startsWith('App')) throw new Error(`菜单名称必须使用 App 前缀:${menu.name}`)
298
322
  if (!['PUBLIC', 'GUEST_ONLY', 'AUTHENTICATED'].includes(menu.access)) {
299
- throw new Error(`菜单访问模式无效:${menu.access}`)
323
+ throw new Error(t('core.navigation.error.access', { access: menu.access }))
324
+ }
325
+ if (!resolveStaticView(menu.viewKey)) {
326
+ throw new Error(t('core.navigation.error.view_key', { viewKey: menu.viewKey }))
300
327
  }
301
- if (!resolveStaticView(menu.viewKey)) throw new Error(`未注册 viewKey:${menu.viewKey}`)
302
328
  if (ids.has(menu.id) || names.has(menu.name) || paths.has(menu.path)) {
303
- throw new Error('菜单 id、name、path 必须唯一')
329
+ throw new Error(t('core.navigation.error.unique'))
304
330
  }
305
331
  ids.add(menu.id)
306
332
  names.add(menu.name)
@@ -308,23 +334,28 @@ function validateMenus(nextMenus: AppMenu[]): void {
308
334
  }
309
335
  const menuMap = new Map(nextMenus.map((menu) => [menu.id, menu]))
310
336
  for (const menu of nextMenus) {
311
- if (menu.parentId === undefined) throw new Error(`菜单缺少父级编号:${menu.name}`)
337
+ if (menu.parentId === undefined) {
338
+ throw new Error(t('core.navigation.error.parent_missing', { name: menu.name }))
339
+ }
312
340
  if (menu.parentId === APP_MENU_ROOT_ID) {
313
- if (!menu.inTabBar) throw new Error(`移动端二级菜单必须作为 tab:${menu.name}`)
341
+ if (!menu.inTabBar) throw new Error(t('core.navigation.error.root_tab', { name: menu.name }))
314
342
  continue
315
343
  }
316
- if (menu.inTabBar) throw new Error(`只有移动端二级菜单可以作为 tab:${menu.name}`)
344
+ if (menu.inTabBar) throw new Error(t('core.navigation.error.child_tab', { name: menu.name }))
317
345
  const visited = new Set<number>([menu.id])
318
346
  let parentId = menu.parentId
319
347
  while (parentId !== APP_MENU_ROOT_ID) {
320
- if (visited.has(parentId)) throw new Error(`移动端菜单存在循环父级:${menu.name}`)
348
+ if (visited.has(parentId))
349
+ throw new Error(t('core.navigation.error.cycle', { name: menu.name }))
321
350
  visited.add(parentId)
322
351
  const parent = menuMap.get(parentId)
323
- if (!parent) throw new Error(`移动端菜单父级不存在:${menu.name}`)
324
- if (parent.parentId === undefined) throw new Error(`移动端菜单父级编号无效:${menu.name}`)
352
+ if (!parent) throw new Error(t('core.navigation.error.parent_not_found', { name: menu.name }))
353
+ if (parent.parentId === undefined) {
354
+ throw new Error(t('core.navigation.error.parent_invalid', { name: menu.name }))
355
+ }
325
356
  parentId = parent.parentId
326
357
  }
327
358
  }
328
359
  const tabCount = nextMenus.filter((menu) => menu.parentId === APP_MENU_ROOT_ID).length
329
- if (tabCount === 1 || tabCount > 5) throw new Error('tabBar 只能配置 0 或 2-5 项')
360
+ if (tabCount === 1 || tabCount > 5) throw new Error(t('core.navigation.error.tab_count'))
330
361
  }
package/src/pages.ts CHANGED
@@ -6,11 +6,11 @@ export const corePages: Record<string, KratosAppPageConfig> = {
6
6
  style: {
7
7
  navigationStyle: 'custom',
8
8
  navigationBarTextStyle: 'white',
9
- navigationBarTitleText: '首页',
9
+ navigationBarTitleText: '',
10
10
  },
11
11
  },
12
- 'pages/login/login': { style: { navigationBarTitleText: '登录' } },
13
- 'pages/login/protocal': { style: { navigationBarTitleText: '协议详情' } },
12
+ 'pages/login/login': { style: { navigationBarTitleText: '' } },
13
+ 'pages/login/protocal': { style: { navigationBarTitleText: '' } },
14
14
  'pages/webview/webview': {
15
15
  style: {
16
16
  navigationBarTitleText: '',
@@ -5,7 +5,18 @@
5
5
  // source: base/v1/config.proto
6
6
 
7
7
  /* eslint-disable */
8
- import type { BaseConfigSite } from "./enum";
8
+
9
+ /** 系统配置位置。 */
10
+ export enum BaseConfigSite {
11
+ /** BASE_CONFIG_SITE_UNSPECIFIED - 未指定系统配置位置。 */
12
+ BASE_CONFIG_SITE_UNSPECIFIED = 0,
13
+ /** BASE_CONFIG_SITE_SYSTEM - 系统内使用。 */
14
+ BASE_CONFIG_SITE_SYSTEM = 1,
15
+ /** BASE_CONFIG_SITE_ADMIN - 管理端。 */
16
+ BASE_CONFIG_SITE_ADMIN = 2,
17
+ /** BASE_CONFIG_SITE_APP - 移动端。 */
18
+ BASE_CONFIG_SITE_APP = 3,
19
+ }
9
20
 
10
21
  /** 获取系统配置条件 */
11
22
  export interface GetConfigRequest {
@@ -15,6 +26,8 @@ export interface GetConfigRequest {
15
26
 
16
27
  /** 系统配置项 */
17
28
  export interface ConfigItem {
29
+ /** 配置ID */
30
+ id: number;
18
31
  /** 配置key */
19
32
  key: string;
20
33
  /** 配置value */
@@ -0,0 +1,31 @@
1
+ // Code generated by protoc-gen-ts_proto. DO NOT EDIT.
2
+ // versions:
3
+ // protoc-gen-ts_proto v2.11.8
4
+ // protoc unknown
5
+ // source: base/v1/language.proto
6
+
7
+ /* eslint-disable */
8
+
9
+ /** 查询语言选项请求参数。 */
10
+ export interface OptionLanguageRequest {
11
+ }
12
+
13
+ /** 语言选择项。 */
14
+ export interface LanguageItem {
15
+ /** 标准语言代码 */
16
+ language_code: string;
17
+ /** 本地语言名称 */
18
+ native_name: string;
19
+ }
20
+
21
+ /** 当前支持的语言选项查询结果。 */
22
+ export interface OptionLanguageResponse {
23
+ /** 当前支持的语言列表 */
24
+ languages: LanguageItem[];
25
+ }
26
+
27
+ /** 语言公共服务。 */
28
+ export interface LanguageService {
29
+ /** 查询当前支持的语言选项。 */
30
+ OptionLanguage(request: OptionLanguageRequest): Promise<OptionLanguageResponse>;
31
+ }
@@ -7,7 +7,20 @@
7
7
  /* eslint-disable */
8
8
  import type { PasswordCrypto } from "../../common/v1/types";
9
9
  import type { Empty } from "../../google/protobuf/empty";
10
- import type { PasswordCryptoScene } from "./enum";
10
+
11
+ /** 密码加密场景枚举。 */
12
+ export enum PasswordCryptoScene {
13
+ /** PASSWORD_CRYPTO_SCENE_UNSPECIFIED - 未指定密码加密场景。 */
14
+ PASSWORD_CRYPTO_SCENE_UNSPECIFIED = 0,
15
+ /** PASSWORD_CRYPTO_SCENE_LOGIN - 登录密码加密场景。 */
16
+ PASSWORD_CRYPTO_SCENE_LOGIN = 1,
17
+ /** PASSWORD_CRYPTO_SCENE_CREATE_BASE_USER - 新增后台用户密码加密场景。 */
18
+ PASSWORD_CRYPTO_SCENE_CREATE_BASE_USER = 2,
19
+ /** PASSWORD_CRYPTO_SCENE_RESET_BASE_USER_PASSWORD - 重置后台用户密码加密场景。 */
20
+ PASSWORD_CRYPTO_SCENE_RESET_BASE_USER_PASSWORD = 3,
21
+ /** PASSWORD_CRYPTO_SCENE_UPDATE_USER_PASSWORD - 个人修改密码加密场景。 */
22
+ PASSWORD_CRYPTO_SCENE_UPDATE_USER_PASSWORD = 4,
23
+ }
11
24
 
12
25
  /** 验证码获取条件 */
13
26
  export interface CaptchaRequest {
@@ -12,3 +12,4 @@ export default pinia
12
12
  // 模块统一导出
13
13
  export * from './modules/user'
14
14
  export * from './modules/setting'
15
+ export * from './modules/locale'
@@ -0,0 +1,28 @@
1
+ import { defineStore } from 'pinia'
2
+ import { computed } from 'vue'
3
+ import {
4
+ getCurrentLocale,
5
+ getLanguageOptions,
6
+ getSupportedLocales,
7
+ localeState,
8
+ setCurrentLocale,
9
+ type SupportedLocale,
10
+ } from '../../locales'
11
+
12
+ /** 移动端语言状态。 */
13
+ export const useLocaleStore = defineStore('locale', () => {
14
+ const locale = computed(() => localeState.value)
15
+
16
+ /** 切换当前语言并持久化。 */
17
+ const setLocale = async (value: SupportedLocale) => {
18
+ await setCurrentLocale(value)
19
+ }
20
+
21
+ return {
22
+ locale,
23
+ currentLocale: getCurrentLocale,
24
+ languageOptions: computed(() => getLanguageOptions()),
25
+ supportedLocales: computed(() => getSupportedLocales()),
26
+ setLocale,
27
+ }
28
+ })
@@ -1,12 +1,13 @@
1
1
  import { defConfigService } from '../../api/base/config'
2
2
  import { defineStore } from 'pinia'
3
3
  import { ref } from 'vue'
4
- import { BaseConfigSite } from '../../rpc/base/v1/enum'
4
+ import { BaseConfigSite } from '../../rpc/base/v1/config'
5
+ import { t } from '../../locales'
5
6
 
6
7
  const REQUIRED_APP_CONFIGS = [
7
- { key: 'serviceProtocol', name: '服务条款' },
8
- { key: 'privacyProtocol', name: '隐私协议' },
9
- { key: 'captchaType', name: '登录验证码类型' },
8
+ { key: 'serviceProtocol', nameKey: 'core.protocol.service' },
9
+ { key: 'privacyProtocol', nameKey: 'core.protocol.privacy' },
10
+ { key: 'captchaType', nameKey: 'core.login.captcha_type_name' },
10
11
  ] as const
11
12
 
12
13
  export const useSettingStore = defineStore('setting', () => {
@@ -26,7 +27,7 @@ export const useSettingStore = defineStore('setting', () => {
26
27
 
27
28
  const request = (async () => {
28
29
  const res = await defConfigService.GetConfig({
29
- site: BaseConfigSite.APP,
30
+ site: BaseConfigSite.BASE_CONFIG_SITE_APP,
30
31
  })
31
32
  const nextData = new Map<string, string>()
32
33
  res.configs.forEach((item) => {
@@ -35,7 +36,11 @@ export const useSettingStore = defineStore('setting', () => {
35
36
 
36
37
  const missingConfigs = REQUIRED_APP_CONFIGS.filter(({ key }) => !nextData.get(key))
37
38
  if (missingConfigs.length) {
38
- throw new Error(`移动端配置缺失:${missingConfigs.map(({ name }) => name).join('、')}`)
39
+ throw new Error(
40
+ t('core.config.missing', {
41
+ names: missingConfigs.map(({ nameKey }) => t(nameKey)).join(', '),
42
+ }),
43
+ )
39
44
  }
40
45
 
41
46
  data.value = nextData
package/src/utils/file.ts CHANGED
@@ -1,8 +1,11 @@
1
1
  import type { FileInfo, MultiUploadFileResponse } from '../rpc/base/v1/file'
2
2
  import { formatSrc } from './index'
3
+ import { getLocaleRequestHeaders, t } from '../locales'
4
+ import { getRequestAccessToken } from './http'
3
5
 
4
6
  // 文件上传-兼容小程序端、H5端、App端
5
7
  export const uploadFile = async (fileType: string, filePath: string): Promise<FileInfo> => {
8
+ const token = await getRequestAccessToken()
6
9
  const res = await uni.uploadFile({
7
10
  url: '/v1/base/file',
8
11
  name: 'file',
@@ -10,11 +13,16 @@ export const uploadFile = async (fileType: string, filePath: string): Promise<Fi
10
13
  formData: {
11
14
  fileType: fileType,
12
15
  },
16
+ header: {
17
+ ...getLocaleRequestHeaders(),
18
+ 'source-client': 'miniapp',
19
+ ...(token ? { Authorization: token } : {}),
20
+ },
13
21
  })
14
22
  if (res.statusCode === 200) {
15
23
  return JSON.parse(res.data) as FileInfo
16
24
  } else {
17
- throw new Error('上传失败')
25
+ throw new Error(t('core.file.upload_failed'))
18
26
  }
19
27
  }
20
28
 
@@ -23,35 +31,15 @@ export const uploadFileList = async (
23
31
  fileType: string,
24
32
  filePaths: string[],
25
33
  ): Promise<FileInfo[]> => {
26
- const fileInfoArr: FileInfo[] = []
27
- // 使用 Promise.all 等待所有上传完成
28
- await Promise.all(
29
- filePaths.map(async (filePath) => {
30
- try {
31
- const res = await uni.uploadFile({
32
- url: '/v1/base/file',
33
- name: 'file',
34
- filePath: filePath,
35
- formData: {
36
- fileType: fileType,
37
- },
38
- })
39
- if (res.statusCode === 200) {
40
- const data = JSON.parse(res.data) as FileInfo
41
- fileInfoArr.push(data)
42
- } else {
43
- console.error('上传失败,状态码:', res.statusCode)
44
- }
45
- } catch (error) {
46
- console.error('上传异常:', error)
47
- }
48
- }),
34
+ const results = await Promise.allSettled(
35
+ filePaths.map((filePath) => uploadFile(fileType, filePath)),
49
36
  )
50
- return fileInfoArr
37
+ return results.flatMap((result) => (result.status === 'fulfilled' ? [result.value] : []))
51
38
  }
52
39
 
53
40
  // 多文件上传-兼容小程序端、H5端、App端
54
41
  export const multiUploadFile = async (fileType: string, files: any): Promise<FileInfo[]> => {
42
+ const token = await getRequestAccessToken()
55
43
  const res = await uni.uploadFile({
56
44
  url: '/v1/base/file/multi',
57
45
  name: 'file',
@@ -60,12 +48,17 @@ export const multiUploadFile = async (fileType: string, files: any): Promise<Fil
60
48
  formData: {
61
49
  fileType: fileType,
62
50
  },
51
+ header: {
52
+ ...getLocaleRequestHeaders(),
53
+ 'source-client': 'miniapp',
54
+ ...(token ? { Authorization: token } : {}),
55
+ },
63
56
  })
64
57
  if (res.statusCode === 200) {
65
58
  const data = JSON.parse(res.data) as MultiUploadFileResponse
66
59
  return data.files
67
60
  } else {
68
- await uni.showToast({ icon: 'error', title: '上传失败' })
61
+ await uni.showToast({ icon: 'error', title: t('core.file.upload_failed') })
69
62
  return []
70
63
  }
71
64
  }
package/src/utils/http.ts CHANGED
@@ -16,6 +16,7 @@ import {
16
16
  shouldRefreshToken,
17
17
  } from './auth'
18
18
  import { saveCurrentRoute } from './navigation'
19
+ import { getLocaleRequestHeaders, t } from '../locales'
19
20
 
20
21
  const apiBasePath = import.meta.env.VITE_APP_BASE_API || '/api'
21
22
  const apiTargetUrl = import.meta.env.VITE_APP_API_URL || ''
@@ -35,6 +36,7 @@ const SESSION_URL = '/v1/base/session'
35
36
  const REFRESH_TOKEN_URL = '/v1/base/token'
36
37
  const CAPTCHA_URL = '/v1/base/captcha'
37
38
  const CONFIG_URL = '/v1/base/config'
39
+ const LANGUAGE_URL = '/v1/base/language'
38
40
  const PASSWORD_PUBLIC_KEY_URL = '/v1/base/password-public-key'
39
41
  const LEGACY_AUTH_URL = '/auth'
40
42
  const LEGACY_REFRESH_TOKEN_URL = `${LEGACY_AUTH_URL}/token`
@@ -45,6 +47,7 @@ const NO_AUTH_URL_SET = new Set([
45
47
  REFRESH_TOKEN_URL,
46
48
  CAPTCHA_URL,
47
49
  CONFIG_URL,
50
+ LANGUAGE_URL,
48
51
  PASSWORD_PUBLIC_KEY_URL,
49
52
  LEGACY_AUTH_URL,
50
53
  LEGACY_CAPTCHA_URL,
@@ -54,6 +57,7 @@ const AUTH_EXPIRED_EXCLUDED_URL_SET = new Set([
54
57
  SESSION_URL,
55
58
  CAPTCHA_URL,
56
59
  CONFIG_URL,
60
+ LANGUAGE_URL,
57
61
  PASSWORD_PUBLIC_KEY_URL,
58
62
  LEGACY_AUTH_URL,
59
63
  LEGACY_CAPTCHA_URL,
@@ -87,6 +91,7 @@ const httpInterceptor = {
87
91
  options.header = {
88
92
  ...options.header,
89
93
  'source-client': 'miniapp',
94
+ ...getLocaleRequestHeaders(),
90
95
  }
91
96
  // 4. 添加 token 请求头标识
92
97
  const accessToken = getToken()
@@ -219,7 +224,7 @@ export const http = <T>(options: HttpRequestOptions) => {
219
224
  // 其他错误 -> 根据后端错误信息轻提示
220
225
  void uni.showToast({
221
226
  icon: 'none',
222
- title: responseData.message || '请求错误',
227
+ title: responseData.message || t('common.message.request_error'),
223
228
  })
224
229
  reject(res)
225
230
  }
@@ -228,7 +233,7 @@ export const http = <T>(options: HttpRequestOptions) => {
228
233
  fail(err) {
229
234
  void uni.showToast({
230
235
  icon: 'none',
231
- title: '网络错误,换个网络试试',
236
+ title: t('common.message.network_error'),
232
237
  })
233
238
  reject(err)
234
239
  },
@@ -326,6 +331,7 @@ async function refreshAccessToken() {
326
331
  data: { refresh_token: refreshToken },
327
332
  header: {
328
333
  'source-client': 'miniapp',
334
+ ...getLocaleRequestHeaders(),
329
335
  },
330
336
  success: resolve,
331
337
  fail: reject,
@@ -377,10 +383,10 @@ async function promptRelogin() {
377
383
  isPromptingRelogin = true
378
384
  try {
379
385
  const modalRes = await uni.showModal({
380
- title: '提示',
381
- content: '当前页面已失效,请重新登录',
386
+ title: t('common.title.notice'),
387
+ content: t('core.auth.session_expired'),
382
388
  showCancel: false,
383
- confirmText: '重新登录',
389
+ confirmText: t('core.auth.login_again'),
384
390
  })
385
391
 
386
392
  if (!modalRes.confirm) {
@@ -405,7 +411,7 @@ function handleAuthExpiredByMode(authMode: AuthMode, url: string, responseData:
405
411
  if (authMode !== 'optional') {
406
412
  void uni.showToast({
407
413
  icon: 'none',
408
- title: responseData.message || '请求错误',
414
+ title: responseData.message || t('common.message.request_error'),
409
415
  })
410
416
  }
411
417
  }