Skip to content

前端字段处理

本文档引用文件

目录

  1. 引言
  2. 项目结构分析
  3. i18n字段类型定义与模型
  4. 前端国际化服务实现
  5. 表单组件对i18n字段的处理
  6. API客户端序列化与反序列化
  7. 多语言交互最佳实践

引言

本文档旨在详细说明在PC端和移动端应用中如何处理国际化(i18n)字段。通过分析项目中的关键文件,包括Model.ts、i18n.ts、CustomInput.vue和CustomSelect.vue,阐述i18n字段的类型定义、初始化、验证规则、动态语言切换机制、表单组件的特殊处理逻辑以及API数据的序列化流程。文档将为开发者提供一套完整的前端i18n字段处理方案。

项目结构分析

项目采用模块化结构,主要分为codegen、deno、pc和uni四个部分。其中:

  • codegen:代码生成器,用于自动生成基础CRUD代码。
  • deno:后端服务,基于Deno运行时。
  • pc:PC端前端应用,使用Vue 3 + TypeScript + Vite构建。
  • uni:移动端前端应用,基于uni-app框架。

i18n相关功能分布在pc/src/localesuni/src/locales目录下,而表单组件位于pc/src/componentsuni/src/components中。核心的i18n模型定义在codegen/__out__/pc/src/views/base/i18n/Model.ts中。

Section sources

i18n字段类型定义与模型

Model.ts文件中,i18n字段通过接口扩展的方式进行定义,确保类型安全和可维护性。

类型定义

typescript
/** 国际化 */
interface I18nModel extends I18nModelType {
}

/** 国际化输入 */
interface I18nInput extends I18nInputType {
}

/** 国际化搜索 */
interface I18nSearch extends I18nSearchType {
  is_deleted?: 0 | 1 | null;
}

字段列表

i18nFields数组定义了所有需要国际化的字段名,包括:

  • id:ID
  • lang_id, lang_id_lbl:语言ID及标签
  • menu_id, menu_id_lbl:菜单ID及标签
  • code:编码
  • lbl:名称(核心国际化字段)
  • rem:备注
  • create_usr_id, create_usr_id_lbl:创建人
  • create_time, create_time_lbl:创建时间
  • update_usr_id, update_usr_id_lbl:更新人
  • update_time, update_time_lbl:更新时间
  • is_deleted:软删除标记

查询字段生成

i18nQueryField通过模板字符串将所有字段拼接,用于GraphQL查询,确保一次性获取所有国际化字段。

typescript
export const i18nQueryField = `
  ${ i18nFields.join(" ") }
`;

Section sources

前端国际化服务实现

pc/src/locales/i18n.ts是前端i18n服务的核心,负责管理语言包、动态加载和缓存。

核心功能

  1. 语言包缓存:使用localStorage缓存已加载的语言标签,避免重复请求。
  2. 动态加载:通过initI18ns函数按需从服务器加载指定页面的i18n标签。
  3. 版本控制:通过__i18n_version检查缓存是否过期。
  4. 参数替换:支持{0}{name}等占位符替换。

关键函数

useI18n - 国际化钩子

typescript
export function useI18n(routePath?: string | null) {
  const usrStore = useUsrStore();
  const lang = usrStore.lang;
  return {
    n(code: string, ...args: any[]) {
      return getLbl(lang, code, routePath, ...args);
    },
    nAsync(code: string, ...args: any[]) {
      return getLblAsync(lang, code, routePath, ...args);
    },
    initI18ns(codes: string[]) {
      return initI18ns(lang, codes, routePath);
    },
    ns(code: string, ...args: any[]) {
      return getLbl(lang, code, "", ...args);
    },
    initSysI18ns(codes: string[]) {
      return initI18ns(lang, codes, "");
    },
  };
}
  • n:同步获取标签
  • nAsync:异步获取标签
  • initI18ns:初始化页面级i18n
  • ns:获取系统级i18n标签

getLbl - 获取标签

typescript
function getLbl(lang: string, code: string, routePath: string | null, ...args: any[]) {
  // 1. 检查本地缓存
  const key = `${routePath} ${code}`;
  let i18nLbl: string = i18nLbls[lang]?.[key];
  if (i18nLbl) {
    return setLblArgs(i18nLbl, args);
  }
  // 2. 异步从服务器获取并缓存
  const i18nLblRef = ref(setLblArgs(code, args));
  (async function() {
    await nextTick();
    i18nLbls[key] = await n0(lang, routePath, code, { notLoading: true });
    localStorage.setItem(`i18nLblsLang`, JSON.stringify(i18nLblsLang));
    i18nLblRef.value = setLblArgs(i18nLbls[key], args);
  })();
  return i18nLblRef.value;
}

缓存机制

Diagram sources

Section sources

表单组件对i18n字段的处理

CustomInput 组件

CustomInput.vue是基础输入组件,支持i18n字段的显示和编辑。

只读模式处理

vue
<template v-else>
  <div class="custom_input_readonly">
    <span v-if="shouldShowPlaceholder">
      {{ props.readonlyPlaceholder ?? "" }}
    </span>
    <span v-else>
      {{ modelValue ?? "" }}
    </span>
  </div>
</template>
  • readonlytrue时,显示只读文本。
  • 支持通过readonlyPlaceholder显示占位符。

数据绑定

使用v-model双向绑定modelValue,并通过watch同步props变化。

typescript
let modelValue = $ref(props.modelValue);

watch(() => props.modelValue, () => {
  modelValue = props.modelValue;
});

watch(() => modelValue, () => {
  emit("update:modelValue", modelValue);
});

Section sources

CustomSelect 组件

CustomSelect.vue是下拉选择组件,对i18n字段有更复杂的处理。

多语言标签显示

typescript
const modelLabels: string[] = $computed(() => {
  if (!modelValue) return [ "" ];
  if (!props.multiple) {
    const model = data.find((item) => props.optionsMap(item).value === modelValue);
    return [ props.optionsMap(model).label || "" ];
  }
  // 多选逻辑
});
  • 通过optionsMap函数将数据映射为{ label, value }结构,label即为i18n字段。

全选功能

vue
<template #header>
  <el-checkbox v-model="isSelectAll" :indeterminate="isIndeterminate">
    <span>{{ ns("全选") }}</span>
  </el-checkbox>
</template>
  • 使用ns("全选")获取“全选”按钮的多语言文本。

默认值设置

支持多选时“设为默认”功能,按钮文本“设为默认”也应国际化。

vue
<el-button @click.stop="onSetMultipleDefault(item.value)">
  设为默认
</el-button>

异步初始化

typescript
async function initFrame() {
  const codes = ["全选"];
  await initSysI18ns(codes);
}
initFrame();
  • 在组件挂载前预加载所需的i18n标签。

Section sources

API客户端序列化与反序列化

序列化流程

  1. 用户在表单中输入多语言字段。
  2. 组件通过v-model收集数据。
  3. 提交时,数据被序列化为JSON,包含lang_idcodelbl等字段。
  4. 通过GraphQL mutation发送到后端。

反序列化流程

  1. 后端返回包含i18nQueryField的所有字段。
  2. 前端将数据填充到I18nModel类型对象中。
  3. 在模板中直接使用lbl字段显示。

语言偏好传递

  • 通过用户Store(useUsrStore())获取当前语言lang
  • useI18n钩子中自动注入lang参数。
  • API请求头或查询参数中可携带lang以获取特定语言数据。

Diagram sources

多语言交互最佳实践

1. 统一使用useI18n钩子

typescript
const { n, ns, initI18ns } = useI18n();
// 页面级
await initI18ns(["保存", "取消"]);
// 使用
const btnSave = n("保存");

2. 预加载关键标签

在组件setuponMounted中调用initSysI18ns预加载。

3. 处理动态内容

对于动态生成的标签,使用{}占位符:

typescript
n("用户{0}不存在", username);

4. 移动端适配

uni项目中的CustomInputCustomSelect应实现类似逻辑,确保一致性。

5. 错误处理

当i18n标签不存在时,回退到默认值或code本身。

6. 性能优化

  • 合理使用localStorage缓存。
  • 避免在循环中频繁调用n(),可预先获取。