前端页面添加AI自动翻译按钮

Const Dict AI 翻译功能说明

1. 背景

字典配置 页面中,编辑/新增常量字典时,存在以下字段:

  • 常量值
  • 中文描述
  • 英文描述

为了减少手工填写英文描述的成本,在"英文描述"输入框右侧增加了一个 AI翻译 按钮。点击按钮后,可以直接将"中文描述"翻译为英文,并自动回填到"英文描述"字段。


2. 实现目标

本次实现的目标是:

  1. 在常量字典编辑弹框中增加 AI翻译 按钮
  2. 点击按钮后读取当前表单中的 中文描述
  3. 调用 AI 接口生成英文描述
  4. 自动回填到 英文描述
  5. 不将 API Key 写入项目代码或环境变量
  6. 仅在当前页面会话内临时缓存 API Key

3. 页面位置

相关页面与组件:

  • 页面入口:***/ConstDictList.vue
  • 编辑弹框:***/DictForm.vue
  • 本地代理配置:vue.config.js

4. 整体方案

4.1 前端交互

DictForm.vue 中为"英文描述"增加一个 AI翻译 按钮。

点击按钮时:

  1. 先读取 constCnDesc
  2. 如果当前页面内还没有 apiKey,通过 window.prompt 弹窗让用户输入
  3. apiKey 保存到当前 Vue 组件实例的内存变量中
  4. 调用 /openai-api/v1/chat/completions
  5. 解析返回内容并回填 constEnDesc

4.2 为什么不用把 API Key 写进项目

因为这是前端项目:

  • 写进 .env.dev 依然会被注入浏览器代码
  • 写进组件代码风险更高
  • 写进 localStorage / sessionStorage 也容易被长期读取

所以当前实现采用:

  • 首次点击时弹窗输入
  • 只保存在当前页面组件内存里
  • 页面刷新后自动失效

4.3 为什么使用本地代理

如果前端直接请求 其他 AI 服务地址,浏览器容易出现跨域错误:

text 复制代码
blocked by CORS policy

因此改成:

  • 浏览器请求当前站点下的 /openai-api/...
  • vue.config.js 中的 devServer.proxy 代理转发到真实 AI 服务

这样浏览器看到的是"同源请求",可以规避本地开发阶段的跨域限制。


5. 关键代码

5.1 弹框中增加 AI 按钮

文件:DictForm.vue

vue 复制代码
<a-form-item label="英文描述">
  <div class="translate-row">
    <a-input
      :maxLength="200"
      placeholder="请输入英文描述"
      v-decorator="[
        'constEnDesc',
        {
          rules: [
            { required: false, message: '请输入英文描述', trigger: 'change' },
            { pattern: /^(?!;)(?!.*;$)(.*?)+$/, message: '不能以;开头/结尾' },
          ],
        },
      ]"
    >
      <span slot="addonAfter">
        <span :class="isEqual('constEnDesc')">{{ getCount('constEnDesc') }}</span>
      </span>
    </a-input>
    <a-button class="translate-btn" :loading="translationLoading" @click="handleTranslate">
      AI翻译
    </a-button>
  </div>
</a-form-item>

5.2 API Key 仅保存在当前页面内存

js 复制代码
data() {
  return {
    form: this.$form.createForm(this),
    translationLoading: false,
    openAiApiKey: '',
  };
},
methods: {
  getOpenAIApiKey() {
    if (this.openAiApiKey) return this.openAiApiKey;
    const apiKey = window.prompt('请输入 OpenAI API Key(仅保存在当前页面内存中)', '');
    if (!apiKey) return '';
    this.openAiApiKey = apiKey.trim();
    return this.openAiApiKey;
  },
}

说明:

  • openAiApiKey 只存在于当前组件实例内存中
  • 不写入 localStorage
  • 不写入 sessionStorage
  • 不写入 .env.dev

5.3 调用 AI 翻译接口

js 复制代码
const OPENAI_PROXY_PATH = '/openai-api';
const OPENAI_MODEL = process.env.VUE_APP_OPENAI_MODEL || 'gpt-5.5';

async handleTranslate() {
  const constCnDesc = this.getValue('constCnDesc');
  if (!constCnDesc) {
    message.warning('请先输入中文描述');
    return;
  }
  if (this.translationLoading) return;

  const apiKey = this.getOpenAIApiKey();
  if (!apiKey) {
    message.warning('未配置 OpenAI API Key');
    return;
  }

  this.translationLoading = true;
  try {
    const response = await fetch(`${OPENAI_PROXY_PATH}/v1/chat/completions`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Authorization: `Bearer ${apiKey}`,
      },
      body: JSON.stringify({
        model: OPENAI_MODEL,
        temperature: 0.2,
        messages: [
          {
            role: 'system',
            content:
              'You are a bilingual admin-config translation assistant. Translate Chinese constant descriptions into concise English labels. Keep item order unchanged. If the input contains semicolon-separated items, return the same number of English items separated by semicolons only. Return only the translated text with no explanation, numbering, or quotes.',
          },
          {
            role: 'user',
            content: this.buildTranslatePrompt(),
          },
        ],
      }),
    });

    if (!response.ok) {
      message.error(`AI翻译失败(${response.status})`);
      return;
    }

    const res = await response.json();
    const translatedText = this.normalizeTranslatedText(
      res &&
        res.choices &&
        res.choices[0] &&
        res.choices[0].message &&
        res.choices[0].message.content
    );

    if (!translatedText) {
      message.warning('AI未返回翻译结果');
      return;
    }

    this.form.setFieldsValue({
      constEnDesc: translatedText,
    });

    if (this.getCount('constVal') !== this.getCount('constEnDesc')) {
      message.warning('翻译结果条数与常量值条数不一致,请检查分号分隔内容');
      return;
    }

    message.success('AI翻译成功');
  } catch (error) {
    console.log(error);
    message.error('AI翻译失败');
  } finally {
    this.translationLoading = false;
  }
}

5.4 Prompt 构造

为了让模型知道上下文,当前会把常量名称、中文名、常量值一起发给 AI:

js 复制代码
buildTranslatePrompt() {
  return [
    `constName: ${this.getValue('constName') || ''}`,
    `constCnName: ${this.getValue('constCnName') || ''}`,
    `constVal: ${this.getValue('constVal') || ''}`,
    `delimiter: ;`,
    `text: ${this.getValue('constCnDesc') || ''}`,
  ].join('\n');
}

这样做的好处是:

  • AI 更容易理解这是"后台常量字典"而不是普通句子
  • 对分号分隔的批量描述翻译更稳定
  • 有助于生成更短、更适合作为标签的英文

5.5 响应内容清洗

js 复制代码
normalizeTranslatedText(content) {
  if (!content) return '';
  return String(content)
    .trim()
    .replace(/\r?\n/g, '')
    .replace(/;/g, ';')
    .replace(/^["']|["']$/g, '');
}

用于处理:

  • 换行
  • 中文分号
  • AI 返回时带引号的情况

5.6 代理配置,解决跨域

文件:vue.config.js

js 复制代码
const openAiBaseURL = process.env.VUE_APP_OPENAI_BASE_URL;

devServer: {
  port: 8000,
  proxy: {
    '/openai-api': {
      target: openAiBaseURL,
      changeOrigin: true,
      pathRewrite: { '^/openai-api': '' }
    }
  }
}

说明:

  • 浏览器请求的是 /openai-api/v1/chat/completions
  • 开发服务器再把它转发到真实 AI 服务
  • 这样可以避免浏览器直接请求外部地址时的 CORS 报错

6. 当前交互流程

用户操作流程如下:

  1. 打开 /global-sys-management/const-dict
  2. 点击 新增修改
  3. 输入 中文描述
  4. 点击 AI翻译
  5. 首次点击时浏览器弹出输入框,要求填写 API Key
  6. 翻译成功后,自动回填 英文描述
  7. 若条数不一致,会提示用户检查分号分隔内容

7. 风险与注意事项

7.1 API Key 仍然属于前端侧使用

虽然当前没有把 key 写进项目代码,但它仍然会出现在浏览器运行时请求中,因此:

  • 这不是严格意义上的服务端保密方案
  • 更适合开发、自测、内部使用
  • 如果以后要正式长期使用,建议改为后端代理调用

7.2 依赖本地代理

当前跨域方案依赖 vue.config.js 中的开发代理,因此:

  • 修改代理后需要重启前端 dev 服务
  • 如果目标 AI 服务地址变化,需要同步调整 VUE_APP_OPENAI_BASE_URL

7.3 AI 返回结果不一定 100% 可直接使用

虽然已经通过 prompt 和条数校验进行了约束,但仍然建议用户确认:

  • 翻译结果是否符合业务术语
  • 分号拆分项数是否一致
  • 是否存在过长、过口语化的英文描述

相关推荐
沉浸学习的匿名网友1 小时前
什么是 .gitignore?为什么每个 Git 项目几乎都离不开它?
前端·git
Apifox2 小时前
从 Postman 迁移到 Apifox:Workspace、Collection、Environment 现在可以一起导入了
前端·后端·程序员
cidy_983 小时前
Agent\-Reach 保姆级教程|AI Agent 全网数据源扩展工具(免费无调用费)
前端
乘风gg3 小时前
当 AI 遇到私有组件,Cli 才是 AI Coding 的起点
前端·ai编程·cursor
40岁搬砖工3 小时前
直观高效的 VSCode 略缩图定位注释 MARK
前端
前端开发爱好者4 小时前
支持 110 种文件预览!兼容 Vue、React、Svelte!
前端·javascript·vue.js
陈随易5 小时前
VSCode古法神器fnMap v9开发故事
前端·后端·程序员
vivo互联网技术5 小时前
未来,什么才是 AI“正确的使用方式”
人工智能·ai编程