Const Dict AI 翻译功能说明
1. 背景
在 字典配置 页面中,编辑/新增常量字典时,存在以下字段:
常量值中文描述英文描述
为了减少手工填写英文描述的成本,在"英文描述"输入框右侧增加了一个 AI翻译 按钮。点击按钮后,可以直接将"中文描述"翻译为英文,并自动回填到"英文描述"字段。
2. 实现目标
本次实现的目标是:
- 在常量字典编辑弹框中增加
AI翻译按钮 - 点击按钮后读取当前表单中的
中文描述 - 调用 AI 接口生成英文描述
- 自动回填到
英文描述 - 不将
API Key写入项目代码或环境变量 - 仅在当前页面会话内临时缓存
API Key

3. 页面位置
相关页面与组件:
- 页面入口:
***/ConstDictList.vue - 编辑弹框:
***/DictForm.vue - 本地代理配置:
vue.config.js
4. 整体方案
4.1 前端交互
在 DictForm.vue 中为"英文描述"增加一个 AI翻译 按钮。
点击按钮时:
- 先读取
constCnDesc - 如果当前页面内还没有
apiKey,通过window.prompt弹窗让用户输入 - 将
apiKey保存到当前 Vue 组件实例的内存变量中 - 调用
/openai-api/v1/chat/completions - 解析返回内容并回填
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. 当前交互流程
用户操作流程如下:
- 打开
/global-sys-management/const-dict - 点击
新增或修改 - 输入
中文描述 - 点击
AI翻译 - 首次点击时浏览器弹出输入框,要求填写
API Key - 翻译成功后,自动回填
英文描述 - 若条数不一致,会提示用户检查分号分隔内容
7. 风险与注意事项
7.1 API Key 仍然属于前端侧使用
虽然当前没有把 key 写进项目代码,但它仍然会出现在浏览器运行时请求中,因此:
- 这不是严格意义上的服务端保密方案
- 更适合开发、自测、内部使用
- 如果以后要正式长期使用,建议改为后端代理调用
7.2 依赖本地代理
当前跨域方案依赖 vue.config.js 中的开发代理,因此:
- 修改代理后需要重启前端 dev 服务
- 如果目标 AI 服务地址变化,需要同步调整
VUE_APP_OPENAI_BASE_URL
7.3 AI 返回结果不一定 100% 可直接使用
虽然已经通过 prompt 和条数校验进行了约束,但仍然建议用户确认:
- 翻译结果是否符合业务术语
- 分号拆分项数是否一致
- 是否存在过长、过口语化的英文描述