updata:智控台页面添加语言切换,支持中英繁。

This commit is contained in:
rainv123
2025-09-11 17:10:50 +08:00
parent a9b0775469
commit 98a1483032
41 changed files with 3800 additions and 1061 deletions
@@ -6,24 +6,24 @@
style="width: 40px;height: 40px;border-radius: 50%;background: #5778ff;display: flex;align-items: center;justify-content: center;">
<img src="@/assets/home/equipment.png" alt="" style="width: 18px;height: 15px;" />
</div>
添加设备
{{ $t('device.dialogTitle') }}
</div>
<div style="height: 1px;background: #e8f0ff;" />
<div style="margin: 22px 15px;">
<div style="font-weight: 400;font-size: 14px;text-align: left;color: #3d4566;">
<div style="color: red;display: inline-block;">*</div>
<span style="font-size: 11px"> 验证码</span>
<span style="font-size: 11px"> {{ $t('device.verificationCode') }}</span>
</div>
<div class="input-46" style="margin-top: 12px;">
<el-input placeholder="请输入设备播报的6位数验证码.." v-model="deviceCode" @keyup.enter.native="confirm" />
<el-input :placeholder="$t('device.verificationCodePlaceholder')" v-model="deviceCode" @keyup.enter.native="confirm" />
</div>
</div>
<div style="display: flex;margin: 15px 15px;gap: 7px;">
<div class="dialog-btn" @click="confirm">
确定
{{ $t('device.confirmButton') }}
</div>
<div class="dialog-btn" style="background: #e6ebff;border: 1px solid #adbdff;color: #5778ff;" @click="cancel">
取消
{{ $t('device.cancelButton') }}
</div>
</div>
</el-dialog>
@@ -47,7 +47,7 @@ export default {
methods: {
confirm() {
if (!/^\d{6}$/.test(this.deviceCode)) {
this.$message.error('请输入6位数字验证码');
this.$message.error(this.$t('device.input6DigitCode'));
return;
}
this.loading = true;
@@ -58,13 +58,13 @@ export default {
if (data.code === 0) {
this.$emit('refresh');
this.$message.success({
message: '设备绑定成功',
message: this.$t('device.bindSuccess'),
showClose: true
});
this.closeDialog();
} else {
this.$message.error({
message: data.msg || '绑定失败',
message: data.msg || this.$t('device.bindFailed'),
showClose: true
});
}
@@ -3,8 +3,8 @@
custom-class="custom-dialog" :show-close="false" class="center-dialog">
<div style="margin: 0 18px; text-align: left; padding: 10px; border-radius: 10px;">
<div style="font-size: 30px; color: #3d4566; margin-top: -10px; margin-bottom: 10px; text-align: center;">
添加模型
</div>
{{ $t('modelConfigDialog.addModel') }}
</div>
<button class="custom-close-btn" @click="handleClose">
×
@@ -12,14 +12,14 @@
<!-- 模型信息部分 -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<div style="font-size: 20px; font-weight: bold; color: #3d4566;">模型信息</div>
<div style="font-size: 20px; font-weight: bold; color: #3d4566;">{{ $t('modelConfigDialog.modelInfo') }}</div>
<div style="display: flex; align-items: center; gap: 20px;">
<div style="display: flex; align-items: center;">
<span style="margin-right: 8px;">是否启用</span>
<span style="margin-right: 8px;">{{ $t('modelConfigDialog.enable') }}</span>
<el-switch v-model="formData.isEnabled" class="custom-switch"></el-switch>
</div>
<div style="display: none; align-items: center;">
<span style="margin-right: 8px;">设为默认</span>
<span style="margin-right: 8px;">{{ $t('modelConfigDialog.setDefault') }}</span>
<el-switch v-model="formData.isDefault" class="custom-switch"></el-switch>
</div>
</div>
@@ -28,38 +28,38 @@
<div style="height: 2px; background: #e9e9e9; margin-bottom: 22px;"></div>
<el-form :model="formData" label-width="100px" label-position="left" class="custom-form">
<div style="display: flex; gap: 20px; margin-bottom: 0;">
<el-form-item label="模型名称" prop="modelName" style="flex: 1;">
<el-input v-model="formData.modelName" placeholder="请输入模型名称" class="custom-input-bg"></el-input>
<el-form-item :label="$t('modelConfigDialog.modelName')" prop="modelName" style="flex: 1;">
<el-input v-model="formData.modelName" :placeholder="$t('modelConfigDialog.enterModelName')" class="custom-input-bg"></el-input>
</el-form-item>
<el-form-item label="模型编码" prop="modelCode" style="flex: 1;">
<el-input v-model="formData.modelCode" placeholder="请输入模型编码" class="custom-input-bg"></el-input>
<el-form-item :label="$t('modelConfigDialog.modelCode')" prop="modelCode" style="flex: 1;">
<el-input v-model="formData.modelCode" :placeholder="$t('modelConfigDialog.enterModelCode')" class="custom-input-bg"></el-input>
</el-form-item>
</div>
<div style="display: flex; gap: 20px; margin-bottom: 0;">
<el-form-item label="供应器" prop="supplier" style="flex: 1;">
<el-select v-model="formData.supplier" placeholder="请选择" class="custom-select custom-input-bg"
<el-form-item :label="$t('modelConfigDialog.supplier')" prop="supplier" style="flex: 1;">
<el-select v-model="formData.supplier" :placeholder="$t('modelConfigDialog.selectSupplier')" class="custom-select custom-input-bg"
style="width: 100%;" @focus="loadProviders" filterable>
<el-option v-for="item in providers" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="排序号" prop="sortOrder" style="flex: 1;">
<el-input v-model="formData.sort" type="number" placeholder="请输入排序号" class="custom-input-bg"></el-input>
<el-form-item :label="$t('modelConfigDialog.sortOrder')" prop="sortOrder" style="flex: 1;">
<el-input v-model="formData.sort" type="number" :placeholder="$t('modelConfigDialog.enterSortOrder')" class="custom-input-bg"></el-input>
</el-form-item>
</div>
<el-form-item label="文档地址" prop="docLink" style="margin-bottom: 27px;">
<el-input v-model="formData.docLink" placeholder="请输入文档地址" class="custom-input-bg"></el-input>
<el-form-item :label="$t('modelConfigDialog.docLink')" prop="docLink" style="margin-bottom: 27px;">
<el-input v-model="formData.docLink" :placeholder="$t('modelConfigDialog.enterDocLink')" class="custom-input-bg"></el-input>
</el-form-item>
<el-form-item label="备注" prop="remark" class="prop-remark">
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入模型备注" :autosize="{ minRows: 3, maxRows: 5 }"
<el-form-item :label="$t('modelConfigDialog.remark')" prop="remark" class="prop-remark">
<el-input v-model="formData.remark" type="textarea" :rows="3" :placeholder="$t('modelConfigDialog.enterRemark')" :autosize="{ minRows: 3, maxRows: 5 }"
class="custom-input-bg"></el-input>
</el-form-item>
</el-form>
<div style="font-size: 20px; font-weight: bold; color: #3d4566; margin-bottom: 15px;">调用信息</div>
<div style="font-size: 20px; font-weight: bold; color: #3d4566; margin-bottom: 15px;">{{ $t('modelConfigDialog.callInfo') }}</div>
<div style="height: 2px; background: #e9e9e9; margin-bottom: 22px;"></div>
<el-form :model="formData.configJson" label-width="auto" label-position="left" class="custom-form">
@@ -83,7 +83,7 @@
class="save-btn"
:loading="saving"
:disabled="saving">
保存
{{ $t('modelConfigDialog.save') }}
</el-button>
</div>
</el-dialog>
@@ -6,23 +6,23 @@
style="width: 40px;height: 40px;border-radius: 50%;background: #5778ff;display: flex;align-items: center;justify-content: center;">
<img loading="lazy" src="@/assets/home/equipment.png" alt="" style="width: 18px;height: 15px;" />
</div>
添加智能体
{{ $t('addAgentDialog.title') }}
</div>
<div style="height: 1px;background: #e8f0ff;" />
<div style="margin: 22px 15px;">
<div style="font-weight: 400;text-align: left;color: #3d4566;">
<div style="color: red;display: inline-block;">*</div> 智能体名称
<div style="color: red;display: inline-block;">*</div> {{ $t('addAgentDialog.agentName') }}
</div>
<div class="input-46" style="margin-top: 12px;">
<el-input ref="inputRef" placeholder="请输入智能体名称.." v-model="wisdomBodyName" @keyup.enter.native="confirm" />
<el-input ref="inputRef" :placeholder="$t('addAgentDialog.placeholder')" v-model="wisdomBodyName" @keyup.enter.native="confirm" />
</div>
</div>
<div style="display: flex;margin: 15px 15px;gap: 7px;">
<div class="dialog-btn" @click="confirm">
确定
{{ $t('addAgentDialog.confirm') }}
</div>
<div class="dialog-btn" style="background: #e6ebff;border: 1px solid #adbdff;color: #5778ff;" @click="cancel">
取消
{{ $t('addAgentDialog.cancel') }}
</div>
</div>
</el-dialog>
@@ -50,12 +50,12 @@ export default {
},
confirm() {
if (!this.wisdomBodyName.trim()) {
this.$message.error('请输入智能体名称');
this.$message.error(this.$t('addAgentDialog.nameRequired'));
return;
}
Api.agent.addAgent(this.wisdomBodyName, (res) => {
this.$message.success({
message: '添加成功',
message: this.$t('addAgentDialog.addSuccess'),
showClose: true
});
this.$emit('confirm', res);
+31 -31
View File
@@ -1,54 +1,54 @@
<template>
<el-dialog
title="CDN资源缓存状态"
:title="$t('cache.dialogTitle')"
:visible.sync="visible"
width="70%"
:before-close="handleClose"
>
<div v-if="isLoading" class="loading-container">
<p>正在加载缓存信息...</p>
<p>{{ $t('cache.loading') }}</p>
</div>
<div v-else>
<div v-if="!cacheAvailable" class="no-cache-message">
<i class="el-icon-warning-outline"></i>
<p>您的浏览器不支持Cache API或Service Worker未安装</p>
<el-button type="primary" @click="refreshPage">刷新页面</el-button>
<p>{{ $t('cache.notSupported') }}</p>
<el-button type="primary" @click="refreshPage">{{ $t('cache.refreshPage') }}</el-button>
</div>
<div v-else>
<el-alert
v-if="cacheData.totalCached === 0"
title="未发现缓存的CDN资源"
type="warning"
:closable="false"
show-icon
>
<p>Service Worker可能尚未完成初始化或缓存尚未建立请刷新页面或等待一会后再试</p>
</el-alert>
v-if="cacheData.totalCached === 0"
:title="$t('cache.noCachedResources')"
type="warning"
:closable="false"
show-icon
>
<p>{{ $t('cache.noCachedResourcesDesc') }}</p>
</el-alert>
<div v-else>
<el-alert
title="CDN资源缓存状态"
:title="$t('cache.cdnCacheStatus')"
type="success"
:closable="false"
show-icon
>
共发现 {{ cacheData.totalCached }} 个缓存资源
{{ $t('cache.totalCachedResources').replace('{count}', cacheData.totalCached) }}
</el-alert>
<h3>JavaScript 资源 ({{ cacheData.js.length }})</h3>
<h3>{{ $t('cache.jsResources').replace('{count}', cacheData.js.length) }}</h3>
<el-table :data="cacheData.js" stripe style="width: 100%">
<el-table-column prop="url" label="URL" width="auto" show-overflow-tooltip />
<el-table-column prop="cached" label="状态" width="100">
<el-table-column prop="cached" :label="$t('cache.status')" width="100">
<template slot-scope="scope">
<el-tag type="success" v-if="scope.row.cached">已缓存</el-tag>
<el-tag type="danger" v-else>未缓存</el-tag>
<el-tag type="success" v-if="scope.row.cached">{{ $t('cache.cached') }}</el-tag>
<el-tag type="danger" v-else>{{ $t('cache.notCached') }}</el-tag>
</template>
</el-table-column>
</el-table>
<h3>CSS 资源 ({{ cacheData.css.length }})</h3>
<h3>{{ $t('cache.cssResources').replace('{count}', cacheData.css.length) }}</h3>
<el-table :data="cacheData.css" stripe style="width: 100%">
<el-table-column prop="url" label="URL" width="auto" show-overflow-tooltip />
<el-table-column prop="cached" label="状态" width="100">
@@ -63,9 +63,9 @@
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="handleClose">关闭</el-button>
<el-button type="primary" @click="refreshCache">刷新缓存状态</el-button>
<el-button type="danger" @click="clearCache">清除缓存</el-button>
<el-button @click="handleClose">{{ $t('button.close') }}</el-button>
<el-button type="primary" @click="refreshCache">{{ $t('cache.refreshStatus') }}</el-button>
<el-button type="danger" @click="clearCache">{{ $t('cache.clearCache') }}</el-button>
</span>
</el-dialog>
</template>
@@ -138,29 +138,29 @@ export default {
async refreshCache() {
this.loadCacheData();
this.$message.success('正在刷新缓存状态');
this.$message.success(this.$t('cache.refreshingStatus'));
},
async clearCache() {
this.$confirm('确定要清除所有缓存吗?', '警告', {
confirmButtonText: '确定',
cancelButtonText: '取消',
this.$confirm(this.$t('cache.confirmClear'), this.$t('message.warning'), {
confirmButtonText: this.$t('button.ok'),
cancelButtonText: this.$t('button.cancel'),
type: 'warning'
}).then(async () => {
try {
const success = await clearAllCaches();
if (success) {
this.$message.success('缓存已清除');
this.$message.success(this.$t('cache.clearedSuccess'));
await this.loadCacheData();
} else {
this.$message.error('清除缓存失败');
this.$message.error(this.$t('cache.clearFailed'));
}
} catch (error) {
console.error('清除缓存失败:', error);
this.$message.error('清除缓存失败');
this.$message.error(this.$t('cache.clearFailed'));
}
}).catch(() => {
this.$message.info('已取消清除');
this.$message.info(this.$t('cache.clearCanceled'));
});
},
@@ -204,4 +204,4 @@ h3 {
margin-bottom: 10px;
font-weight: 500;
}
</style>
</style>
@@ -8,38 +8,38 @@
<img loading="lazy" src="@/assets/login/shield.png" alt=""
style="width: 19px;height: 23px; filter: brightness(0) invert(1);" />
</div>
修改密码
{{ $t('changePassword.title') }}
</div>
<div style="height: 1px;background: #e8f0ff;" />
<div style="margin: 22px 15px;">
<div style="font-weight: 400;font-size: 14px;text-align: left;color: #3d4566;">
<div style="color: red;display: inline-block;">*</div>
旧密码
{{ $t('changePassword.oldPasswordLabel') }}
</div>
<div class="input-46" style="margin-top: 12px;">
<el-input placeholder="请输入旧密码" v-model="oldPassword" type="password" show-password />
<el-input :placeholder="$t('changePassword.oldPasswordPlaceholder')" v-model="oldPassword" type="password" show-password />
</div>
<div style="font-weight: 400;font-size: 14px;text-align: left;color: #3d4566;margin-top: 12px;">
<div style="color: red;display: inline-block;">*</div>
新密码
{{ $t('changePassword.newPasswordLabel') }}
</div>
<div class="input-46" style="margin-top: 12px;">
<el-input placeholder="请输入新密码" v-model="newPassword" type="password" show-password />
<el-input :placeholder="$t('changePassword.newPasswordPlaceholder')" v-model="newPassword" type="password" show-password />
</div>
<div style="font-weight: 400;font-size: 14px;text-align: left;color: #3d4566;margin-top: 12px;">
<div style="color: red;display: inline-block;">*</div>
确认新密码
{{ $t('changePassword.confirmPasswordLabel') }}
</div>
<div class="input-46" style="margin-top: 12px;">
<el-input placeholder="请再次输入新密码" v-model="confirmNewPassword" type="password" show-password />
<el-input :placeholder="$t('changePassword.confirmPasswordPlaceholder')" v-model="confirmNewPassword" type="password" show-password />
</div>
</div>
<div style="display: flex;margin: 15px 15px;gap: 7px;">
<div class="dialog-btn" @click="confirm">
确定
{{ $t('changePassword.confirmButton') }}
</div>
<div class="dialog-btn" style="background: #e6ebff;border: 1px solid #adbdff;color: #5778ff;" @click="cancel">
取消
{{ $t('changePassword.cancelButton') }}
</div>
</div>
</el-dialog>
@@ -49,6 +49,7 @@
<script>
import userApi from '@/apis/module/user';
import { mapActions } from 'vuex';
import { changeLanguage } from '@/i18n';
export default {
name: 'ChangePasswordDialog',
@@ -78,15 +79,15 @@ export default {
...mapActions(['logout']), // 引入Vuex的logout action
confirm() {
if (!this.oldPassword.trim() || !this.newPassword.trim() || !this.confirmNewPassword.trim()) {
this.$message.error('请填写所有字段');
this.$message.error(this.$t('changePassword.allFieldsRequired'));
return;
}
if (this.newPassword !== this.confirmNewPassword) {
this.$message.error('两次输入的新密码不一致');
this.$message.error(this.$t('changePassword.passwordsNotMatch'));
return;
}
if (this.newPassword === this.oldPassword) {
this.$message.error('新密码不能与旧密码相同');
this.$message.error(this.$t('changePassword.newPasswordSameAsOld'));
return;
}
@@ -94,7 +95,7 @@ export default {
userApi.changePassword(this.oldPassword, this.newPassword, (res) => {
if (res.data.code === 0) {
this.$message.success({
message: '密码修改成功,请重新登录',
message: this.$t('changePassword.passwordChangedSuccessfully'),
showClose: true
});
this.logout().then(() => {
@@ -102,10 +103,10 @@ export default {
this.$emit('update:visible', false);
});
} else {
this.$message.error(res.data.msg || '密码修改失败');
this.$message.error(res.data.msg || this.$t('changePassword.changeFailed'));
}
}, (err) => {
this.$message.error(err.msg || '密码修改失败');
this.$message.error(err.msg || this.$t('changePassword.changeFailed'));
});
this.$emit('input', false);
},
@@ -1,5 +1,5 @@
<template>
<el-dialog :title="'与' + agentName + '的聊天记录' + (currentMacAddress ? '[' + currentMacAddress + ']' : '')"
<el-dialog :title="$t('chatHistory.with') + agentName + $t('chatHistory.dialogTitle') + (currentMacAddress ? '[' + currentMacAddress + ']' : '')"
:visible.sync="dialogVisible" width="80%" :before-close="handleClose" custom-class="chat-history-dialog">
<div class="chat-container">
<div class="session-list" @scroll="handleScroll">
@@ -11,8 +11,8 @@
<div class="message-count">{{ session.chatCount > 99 ? '99' : session.chatCount }}</div>
</div>
</div>
<div v-if="loading" class="loading">加载中...</div>
<div v-if="!hasMore" class="no-more">没有更多记录了</div>
<div v-if="loading" class="loading">{{ $t('chatHistory.loading') }}</div>
<div v-if="!hasMore" class="no-more">{{ $t('chatHistory.noMoreRecords') }}</div>
</div>
<div class="chat-content">
<div v-if="currentSessionId" class="messages">
@@ -32,7 +32,7 @@
</div>
</div>
<div v-else class="no-session-selected">
请选择会话查看聊天记录
{{ $t('chatHistory.selectSession') }}
</div>
</div>
</div>
@@ -230,9 +230,9 @@ export default {
const minutes = date.getMinutes().toString().padStart(2, '0');
if (date >= today) {
return `今天 ${hours}:${minutes}`;
return `${this.$t('chatHistory.today')} ${hours}:${minutes}`;
} else if (date >= yesterday) {
return `昨天 ${hours}:${minutes}`;
return `${this.$t('chatHistory.yesterday')} ${hours}:${minutes}`;
} else {
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
+30 -18
View File
@@ -14,36 +14,37 @@
</div>
</div>
<div class="device-name">
语言模型{{ device.llmModelName }}
{{ $t('home.languageModel') }}{{ device.llmModelName }}
</div>
<div class="device-name">
音色模型{{ device.ttsModelName }} ({{ device.ttsVoiceName }})
{{ $t('home.voiceModel') }}{{ device.ttsModelName }} ({{ device.ttsVoiceName }})
</div>
<div style="display: flex;gap: 10px;align-items: center;">
<div class="settings-btn" @click="handleConfigure">
配置角色
<div :class="['settings-btn', {'settings-btn-en': isEnglish}]" @click="handleConfigure">
{{ $t('home.configureRole') }}
</div>
<div class="settings-btn" @click="handleVoicePrint">
声纹识别
<div :class="['settings-btn', {'settings-btn-en': isEnglish}]" @click="handleVoicePrint">
{{ $t('home.voiceprintRecognition') }}
</div>
<div class="settings-btn" @click="handleDeviceManage">
设备管理({{ device.deviceCount }})
<div :class="['settings-btn', {'settings-btn-en': isEnglish}]" @click="handleDeviceManage">
{{ $t('home.deviceManagement') }}({{ device.deviceCount }})
</div>
<div class="settings-btn" @click="handleChatHistory"
:class="{ 'disabled-btn': device.memModelId === 'Memory_nomem' }">
<el-tooltip v-if="device.memModelId === 'Memory_nomem'" content="请先在“配置角色”界面开启记忆" placement="top">
<span>聊天记录</span>
<div :class="['settings-btn', {'settings-btn-en': isEnglish, 'disabled-btn': device.memModelId === 'Memory_nomem'}]" @click="handleChatHistory">
<el-tooltip v-if="device.memModelId === 'Memory_nomem'" :content="$t('home.enableMemory')" placement="top">
<span>{{ $t('home.chatHistory') }}</span>
</el-tooltip>
<span v-else>聊天记录</span>
<span v-else>{{ $t('home.chatHistory') }}</span>
</div>
</div>
<div class="version-info">
<div>最近对话{{ formattedLastConnectedTime }}</div>
<div>{{ $t('home.lastConversation') }}{{ formattedLastConnectedTime }}</div>
</div>
</div>
</template>
<script>
import i18n from '@/i18n';
export default {
name: 'DeviceItem',
props: {
@@ -54,23 +55,27 @@ export default {
},
computed: {
formattedLastConnectedTime() {
if (!this.device.lastConnectedAt) return '暂未对话';
if (!this.device.lastConnectedAt) return this.$t('home.noConversation');
const lastTime = new Date(this.device.lastConnectedAt);
const now = new Date();
const diffMinutes = Math.floor((now - lastTime) / (1000 * 60));
if (diffMinutes <= 1) {
return '刚刚';
return this.$t('home.justNow');
} else if (diffMinutes < 60) {
return `${diffMinutes}分钟前`;
return this.$t('home.minutesAgo', { minutes: diffMinutes });
} else if (diffMinutes < 24 * 60) {
const hours = Math.floor(diffMinutes / 60);
const minutes = diffMinutes % 60;
return `${hours}小时${minutes > 0 ? minutes + '分钟' : ''}`;
return this.$t('home.hoursAgo', { hours, minutes });
} else {
return this.device.lastConnectedAt;
}
},
// 判断是否为英文
isEnglish() {
return i18n.locale === 'en';
}
},
methods: {
@@ -125,6 +130,13 @@ export default {
border-radius: 14px;
}
.settings-btn.settings-btn-en {
font-size: 11px;
height: 39px;
min-width: 65px;
padding: 0 16px;
}
.version-info {
display: flex;
justify-content: space-between;
@@ -1,19 +1,19 @@
<template>
<el-dialog :title="title" :visible.sync="dialogVisible" width="30%" @close="handleClose">
<el-form :model="form" :rules="rules" ref="form" label-width="100px">
<el-form-item label="字典标签" prop="dictLabel">
<el-input v-model="form.dictLabel" placeholder="请输入字典标签"></el-input>
<el-form :model="form" :rules="rules" ref="form" label-width="auto">
<el-form-item :label="$t('dictDataDialog.dictLabel')" prop="dictLabel">
<el-input v-model="form.dictLabel" :placeholder="$t('dictDataDialog.dictLabelPlaceholder')"></el-input>
</el-form-item>
<el-form-item label="字典值" prop="dictValue">
<el-input v-model="form.dictValue" placeholder="请输入字典值"></el-input>
<el-form-item :label="$t('dictDataDialog.dictValue')" prop="dictValue">
<el-input v-model="form.dictValue" :placeholder="$t('dictDataDialog.dictValuePlaceholder')"></el-input>
</el-form-item>
<el-form-item label="排序" prop="sort">
<el-form-item :label="$t('dictDataDialog.sort')" prop="sort">
<el-input-number v-model="form.sort" :min="0" :max="999" style="width: 100%;"></el-input-number>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button type="primary" @click="handleSave"> </el-button>
<el-button @click="handleClose">{{ $t('button.cancel') }}</el-button>
<el-button type="primary" @click="handleSave">{{ $t('button.confirm') }}</el-button>
</div>
</el-dialog>
</template>
@@ -28,7 +28,7 @@ export default {
},
title: {
type: String,
default: '新增字典数据'
default: () => this.$t('dictDataDialog.addDictData')
},
dictData: {
type: Object,
@@ -50,8 +50,8 @@ export default {
sort: 0
},
rules: {
dictLabel: [{ required: true, message: '请输入字典标签', trigger: 'blur' }],
dictValue: [{ required: true, message: '请输入字典值', trigger: 'blur' }]
dictLabel: [{ required: true, message: this.$t('dictDataDialog.requiredDictLabel'), trigger: 'blur' }],
dictValue: [{ required: true, message: this.$t('dictDataDialog.requiredDictValue'), trigger: 'blur' }]
}
}
},
@@ -1,16 +1,16 @@
<template>
<el-dialog :title="title" :visible.sync="dialogVisible" width="30%" @close="handleClose">
<el-form :model="form" :rules="rules" ref="form" label-width="120px">
<el-form-item label="字典类型名称" prop="dictName">
<el-input v-model="form.dictName" placeholder="请输入字典类型名称"></el-input>
<el-form :model="form" :rules="rules" ref="form" label-width="auto">
<el-form-item :label="$t('dictTypeDialog.dictName')" prop="dictName">
<el-input v-model="form.dictName" :placeholder="$t('dictTypeDialog.dictNamePlaceholder')"></el-input>
</el-form-item>
<el-form-item label="字典类型编码" prop="dictType">
<el-input v-model="form.dictType" placeholder="请输入字典类型编码"></el-input>
<el-form-item :label="$t('dictTypeDialog.dictType')" prop="dictType">
<el-input v-model="form.dictType" :placeholder="$t('dictTypeDialog.dictTypePlaceholder')"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleClose"> </el-button>
<el-button type="primary" @click="handleSave"> </el-button>
<el-button @click="handleClose">{{ $t('button.cancel') }}</el-button>
<el-button type="primary" @click="handleSave">{{ $t('button.confirm') }}</el-button>
</div>
</el-dialog>
</template>
@@ -25,7 +25,7 @@ export default {
},
title: {
type: String,
default: '新增字典类型'
default: () => this.$t('dictTypeDialog.addDictType')
},
dictTypeData: {
type: Object,
@@ -41,8 +41,8 @@ export default {
dictType: ''
},
rules: {
dictName: [{ required: true, message: '请输入字典类型名称', trigger: 'blur' }],
dictType: [{ required: true, message: '请输入字典类型编码', trigger: 'blur' }]
dictName: [{ required: true, message: this.$t('dictTypeDialog.requiredDictName'), trigger: 'blur' }],
dictType: [{ required: true, message: this.$t('dictTypeDialog.requiredDictType'), trigger: 'blur' }]
}
}
},
@@ -1,33 +1,33 @@
<template>
<el-dialog :visible.sync="dialogVisible" width="900px" @close="handleClose" class="compact-dialog" :append-to-body="true">
<el-form :model="voiceForm" :rules="rules" ref="voiceForm" label-width="80px">
<el-form :model="voiceForm" :rules="rules" ref="voiceForm" label-width="auto">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="音色编码" prop="voiceCode">
<el-input v-model="voiceForm.voiceCode" placeholder="请输入内容" class="compact-input"></el-input>
<el-form-item :label="$t('editVoiceDialog.voiceCode')" prop="voiceCode">
<el-input v-model="voiceForm.voiceCode" :placeholder="$t('editVoiceDialog.voiceCodePlaceholder')" class="compact-input"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="音色名称" prop="voiceName">
<el-input v-model="voiceForm.voiceName" placeholder="请输入内容" class="compact-input"></el-input>
<el-form-item :label="$t('editVoiceDialog.voiceName')" prop="voiceName">
<el-input v-model="voiceForm.voiceName" :placeholder="$t('editVoiceDialog.voiceNamePlaceholder')" class="compact-input"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="语言类型" prop="languageType">
<el-input v-model="voiceForm.languageType" placeholder="请输入内容" class="compact-input"></el-input>
<el-form-item :label="$t('editVoiceDialog.languageType')" prop="languageType">
<el-input v-model="voiceForm.languageType" :placeholder="$t('editVoiceDialog.languageTypePlaceholder')" class="compact-input"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="排序号" prop="sortNumber">
<el-form-item :label="$t('editVoiceDialog.sortNumber')" prop="sortNumber">
<el-input-number v-model="voiceForm.sortNumber" :min="1" :controls="false" class="compact-number"></el-input-number>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="voiceForm.remark" type="textarea" :rows="2" placeholder="请输入内容" class="compact-textarea"
<el-form-item :label="$t('editVoiceDialog.remark')" prop="remark">
<el-input v-model="voiceForm.remark" type="textarea" :rows="2" :placeholder="$t('editVoiceDialog.remarkPlaceholder')" class="compact-textarea"
></el-input>
<div class="audio-controls">
<div class="audio-player">
@@ -38,14 +38,14 @@
class="custom-audio"
></audio>
</div>
<el-button type="primary" size="mini" class="preview-btn">生成试听</el-button>
<el-button type="primary" size="mini" class="preview-btn">{{ $t('editVoiceDialog.generatePreview') }}</el-button>
</div>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="handleSave">保存</el-button>
<el-button type="primary" @click="handleClose">关闭</el-button>
<el-button type="primary" @click="handleSave">{{ $t('button.save') }}</el-button>
<el-button @click="handleClose">{{ $t('button.close') }}</el-button>
</div>
</el-dialog>
</template>
@@ -59,8 +59,8 @@ export default {
type: Object,
default: () => ({
voiceCode: 'wawaxiaohe',
voiceName: '湾湾小何',
languageType: '中文',
voiceName: this.$t('editVoiceDialog.defaultVoiceName'),
languageType: this.$t('editVoiceDialog.defaultLanguageType'),
sortNumber: 123
})
}
@@ -72,8 +72,8 @@ export default {
audioUrl: 'http://music.163.com/song/media/outer/url?id=447925558.mp3',
generatedAudio: null,
rules: {
voiceCode: [{ required: true, message: '请输入音色编码', trigger: 'blur' }],
voiceName: [{ required: true, message: '请输入音色名称', trigger: 'blur' }]
voiceCode: [{ required: true, message: this.$t('editVoiceDialog.requiredVoiceCode'), trigger: 'blur' }],
voiceName: [{ required: true, message: this.$t('editVoiceDialog.requiredVoiceName'), trigger: 'blur' }]
}
}
},
@@ -1,37 +1,37 @@
<template>
<el-dialog :title="title" :visible.sync="dialogVisible" :close-on-click-modal="false" @close="handleClose" @open="handleOpen">
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
<el-form-item label="固件名称" prop="firmwareName">
<el-input v-model="form.firmwareName" placeholder="请输入固件名称(板子+版本号)"></el-input>
<el-form ref="form" :model="form" :rules="rules" label-width="auto">
<el-form-item :label="$t('firmwareDialog.firmwareName')" prop="firmwareName">
<el-input v-model="form.firmwareName" :placeholder="$t('firmwareDialog.firmwareNamePlaceholder')"></el-input>
</el-form-item>
<el-form-item label="固件类型" prop="type">
<el-select v-model="form.type" placeholder="请选择固件类型" style="width: 100%;" filterable :disabled="isTypeDisabled">
<el-form-item :label="$t('firmwareDialog.firmwareType')" prop="type">
<el-select v-model="form.type" :placeholder="$t('firmwareDialog.firmwareTypePlaceholder')" style="width: 100%;" filterable :disabled="isTypeDisabled">
<el-option v-for="item in firmwareTypes" :key="item.key" :label="item.name" :value="item.key"></el-option>
</el-select>
</el-form-item>
<el-form-item label="版本号" prop="version">
<el-input v-model="form.version" placeholder="请输入版本号(x.x.x格式)"></el-input>
<el-form-item :label="$t('firmwareDialog.version')" prop="version">
<el-input v-model="form.version" :placeholder="$t('firmwareDialog.versionPlaceholder')"></el-input>
</el-form-item>
<el-form-item label="固件文件" prop="firmwarePath">
<el-form-item :label="$t('firmwareDialog.firmwareFile')" prop="firmwarePath">
<el-upload ref="upload" class="upload-demo" action="#" :http-request="handleUpload"
:before-upload="beforeUpload" :accept="'.bin,.apk'" :limit="1" :multiple="false" :auto-upload="true"
:on-remove="handleRemove">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传固件文件(.bin/.apk)且不超过100MB</div>
<el-button size="small" type="primary">{{ $t('firmwareDialog.clickUpload') }}</el-button>
<div slot="tip" class="el-upload__tip">{{ $t('firmwareDialog.uploadTip') }}</div>
</el-upload>
<el-progress v-if="isUploading || uploadStatus === 'success'" :percentage="uploadProgress"
:status="uploadStatus"></el-progress>
<div class="hint-text">
<span>温馨提示请上传合并前的xiaozhi.bin文件而不是合并后的merged-binary.bin文件</span>
<span>{{ $t('firmwareDialog.uploadHint') }}</span>
</div>
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input type="textarea" v-model="form.remark" placeholder="请输入备注信息"></el-input>
<el-form-item :label="$t('firmwareDialog.remark')" prop="remark">
<el-input type="textarea" v-model="form.remark" :placeholder="$t('firmwareDialog.remarkPlaceholder')"></el-input>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="handleCancel"> </el-button>
<el-button type="primary" @click="handleSubmit"> </el-button>
<el-button @click="handleCancel">{{ $t('button.cancel') }}</el-button>
<el-button type="primary" @click="handleSubmit">{{ $t('button.confirm') }}</el-button>
</div>
</el-dialog>
</template>
@@ -68,17 +68,17 @@ export default {
dialogVisible: this.visible,
rules: {
firmwareName: [
{ required: true, message: '请输入固件名称(板子+版本号)', trigger: 'blur' }
{ required: true, message: this.$t('firmwareDialog.requiredFirmwareName'), trigger: 'blur' }
],
type: [
{ required: true, message: '请选择固件类型', trigger: 'change' }
{ required: true, message: this.$t('firmwareDialog.requiredFirmwareType'), trigger: 'change' }
],
version: [
{ required: true, message: '请输入版本号', trigger: 'blur' },
{ pattern: /^\d+\.\d+\.\d+$/, message: '版本号格式不正确,请输入x.x.x格式', trigger: 'blur' }
{ required: true, message: this.$t('firmwareDialog.requiredVersion'), trigger: 'blur' },
{ pattern: /^\d+\.\d+\.\d+$/, message: this.$t('firmwareDialog.versionFormatError'), trigger: 'blur' }
],
firmwarePath: [
{ required: false, message: '请上传固件文件', trigger: 'change' }
{ required: false, message: this.$t('firmwareDialog.requiredFirmwareFile'), trigger: 'change' }
]
}
}
@@ -115,7 +115,7 @@ export default {
if (valid) {
// 如果是新增模式且没有上传文件,则提示错误
if (!this.form.id && !this.form.firmwarePath) {
this.$message.error('请上传固件文件')
this.$message.error(this.$t('firmwareDialog.requiredFirmwareFile'))
return
}
// 提交成功后将关闭对话框的逻辑交给父组件处理
@@ -128,13 +128,13 @@ export default {
const isValidType = ['.bin', '.apk'].some(ext => file.name.toLowerCase().endsWith(ext))
if (!isValidType) {
this.$message.error('只能上传.bin/.apk格式的固件文件!')
return false
}
if (!isValidSize) {
this.$message.error('固件文件大小不能超过100MB!')
return false
}
this.$message.error(this.$t('firmwareDialog.invalidFileType'))
return false
}
if (!isValidSize) {
this.$message.error(this.$t('firmwareDialog.invalidFileSize'))
return false
}
return true
},
handleUpload(options) {
@@ -158,14 +158,14 @@ export default {
this.form.size = file.size
this.uploadProgress = 100
this.uploadStatus = 'success'
this.$message.success('固件文件上传成功')
this.$message.success(this.$t('firmwareDialog.uploadSuccess'))
// 延迟2秒后隐藏进度条
setTimeout(() => {
this.isUploading = false
}, 2000)
} else {
this.uploadStatus = 'exception'
this.$message.error(res.msg || '文件上传失败')
this.$message.error(res.msg || this.$t('firmwareDialog.uploadFailed'))
this.isUploading = false
}
}, (progressEvent) => {
@@ -3,7 +3,7 @@
<!-- 自定义标题区域 -->
<div class="custom-header">
<div class="header-left">
<h3 class="bold-title">功能管理</h3>
<h3 class="bold-title">{{ $t('functionDialog.title') }}</h3>
</div>
<button class="custom-close-btn" @click="closeDialog">×</button>
</div>
@@ -12,8 +12,8 @@
<!-- 左侧未选功能 -->
<div class="function-column">
<div class="column-header">
<h4 class="column-title">未选功能</h4>
<el-button type="text" @click="selectAll" class="select-all-btn">全选</el-button>
<h4 class="column-title">{{ $t('functionDialog.unselectedFunctions') }}</h4>
<el-button type="text" @click="selectAll" class="select-all-btn">{{ $t('functionDialog.selectAll') }}</el-button>
</div>
<div class="function-list">
<div v-if="unselected.length">
@@ -27,7 +27,7 @@
</div>
</div>
<div v-else style="display: flex; justify-content: center; align-items: center;">
<el-empty description="没有更多的插件了" />
<el-empty :description="$t('functionDialog.noMorePlugins')" />
</div>
</div>
</div>
@@ -35,8 +35,8 @@
<!-- 中间已选功能 -->
<div class="function-column">
<div class="column-header">
<h4 class="column-title">已选功能</h4>
<el-button type="text" @click="deselectAll" class="select-all-btn">全选</el-button>
<h4 class="column-title">{{ $t('functionDialog.selectedFunctions') }}</h4>
<el-button type="text" @click="deselectAll" class="select-all-btn">{{ $t('functionDialog.selectAll') }}</el-button>
</div>
<div class="function-list">
<div v-if="selectedList.length > 0">
@@ -50,19 +50,19 @@
</div>
</div>
<div v-else style="display: flex; justify-content: center; align-items: center;">
<el-empty description="请选择插件功能" />
<el-empty :description="$t('functionDialog.pleaseSelectPlugin')" />
</div>
</div>
</div>
<!-- 右侧参数配置 -->
<div class="params-column">
<h4 v-if="currentFunction" class="column-title">参数配置 - {{ currentFunction.name }}</h4>
<h4 v-if="currentFunction" class="column-title">{{ $t('functionDialog.paramConfig') }} - {{ currentFunction.name }}</h4>
<div v-if="currentFunction" class="params-container">
<el-form :model="currentFunction" class="param-form">
<!-- 遍历 fieldsMeta而不是 params keys -->
<div v-if="currentFunction.fieldsMeta.length == 0">
<el-empty :description="currentFunction.name + ' 无需配置参数'" />
<el-empty :description="currentFunction.name + $t('functionDialog.noNeedToConfig')" />
</div>
<el-form-item v-for="field in currentFunction.fieldsMeta" :key="field.key" :label="field.label"
class="param-item" :class="{ 'textarea-field': field.type === 'array' || field.type === 'json' }">
@@ -95,7 +95,7 @@
</el-form-item>
</el-form>
</div>
<div v-else class="empty-tip">请选择已配置的功能进行参数设置</div>
<div v-else class="empty-tip">{{ $t('functionDialog.pleaseSelectFunctionForParam') }}</div>
</div>
</div>
@@ -105,22 +105,22 @@
<!-- 左侧区域 -->
<div class="mcp-left">
<div class="mcp-header">
<h3 class="bold-title">MCP接入点</h3>
<h3 class="bold-title">{{ $t('functionDialog.mcpAccessPoint') }}</h3>
</div>
<div class="url-header">
<div class="address-desc">
<span>以下是智能体的MCP接入点地址</span>
<span>{{ $t('functionDialog.mcpAddressDesc') }}</span>
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/docs/mcp-endpoint-enable.md"
target="_blank" class="doc-link">如何部署MCP接入点</a> &nbsp;&nbsp;|&nbsp;&nbsp;
target="_blank" class="doc-link">{{ $t('functionDialog.howToDeployMcp') }}</a> &nbsp;&nbsp;|&nbsp;&nbsp;
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/docs/mcp-endpoint-integration.md"
target="_blank" class="doc-link">如何接入MCP功能</a> &nbsp;
target="_blank" class="doc-link">{{ $t('functionDialog.howToIntegrateMcp') }}</a> &nbsp;
</div>
</div>
<el-input v-model="mcpUrl" readonly class="url-input">
<template #suffix>
<el-button @click="copyUrl" class="inner-copy-btn" icon="el-icon-document-copy">
复制
</el-button>
{{ $t('functionDialog.copy') }}
</el-button>
</template>
</el-input>
</div>
@@ -128,17 +128,17 @@
<!-- 右侧区域 -->
<div class="mcp-right">
<div class="mcp-header">
<h3 class="bold-title">接入点状态</h3>
<h3 class="bold-title">{{ $t('functionDialog.accessPointStatus') }}</h3>
</div>
<div class="status-container">
<span class="status-indicator" :class="mcpStatus"></span>
<span class="status-text">{{
mcpStatus === 'connected' ? '已连接' :
mcpStatus === 'loading' ? '加载中...' : '未连接'
mcpStatus === 'connected' ? $t('functionDialog.connected') :
mcpStatus === 'loading' ? $t('functionDialog.loading') : $t('functionDialog.disconnected')
}}</span>
<button class="refresh-btn" @click="refreshStatus">
<span class="refresh-icon"></span>
<span>刷新</span>
<span>{{ $t('functionDialog.refresh') }}</span>
</button>
</div>
<div class="mcp-tools-list">
@@ -148,7 +148,7 @@
</el-button>
</div>
<div v-else class="no-tools">
<span>暂无可用工具</span>
<span>{{ $t('functionDialog.noAvailableTools') }}</span>
</div>
</div>
</div>
@@ -156,16 +156,19 @@
</div>
<div class="drawer-footer">
<el-button @click="closeDialog">取消</el-button>
<el-button type="primary" @click="saveSelection">保存配置</el-button>
<el-button @click="closeDialog">{{ $t('functionDialog.cancel') }}</el-button>
<el-button type="primary" @click="saveSelection">{{ $t('functionDialog.saveConfig') }}</el-button>
</div>
</el-drawer>
</template>
<script>
import Api from '@/apis/api';
import i18n from '@/i18n';
export default {
i18n,
props: {
value: Boolean,
functions: {
@@ -264,9 +267,9 @@ export default {
try {
const successful = document.execCommand('copy');
if (successful) {
this.$message.success('已复制到剪贴板');
this.$message.success(this.$t('functionDialog.copiedToClipboard'));
} else {
this.$message.error('复制失败,请手动复制');
this.$message.error(this.$t('functionDialog.copyFailed'));
}
} catch (err) {
this.$message.error('复制失败,请手动复制');
@@ -327,7 +330,7 @@ export default {
const obj = JSON.parse(text);
this.handleParamChange(this.currentFunction, key, obj);
} catch {
this.$message.error(`${this.currentFunction.name}${key}字段格式错误:JSON格式有误`);
this.$message.error(`${this.currentFunction.name}${this.$t('functionDialog.jsonFormatError')}`);
}
},
handleFunctionClick(func) {
@@ -409,7 +412,7 @@ export default {
fieldRemark(field) {
let description = (field && field.label) ? field.label : '';
if (field.default) {
description += `默认值${field.default}`;
description += `${this.$t('functionDialog.defaultValue')}${field.default}`;
}
return description;
},
+92 -39
View File
@@ -9,52 +9,52 @@
<!-- 中间导航菜单 -->
<div class="header-center">
<div class="equipment-management"
<div class="equipment-management"
:class="{ 'active-tab': $route.path === '/home' || $route.path === '/role-config' || $route.path === '/device-management' }"
@click="goHome">
<img loading="lazy" alt="" src="@/assets/header/robot.png"
:style="{ filter: $route.path === '/home' || $route.path === '/role-config' || $route.path === '/device-management' ? 'brightness(0) invert(1)' : 'None' }" />
智能体管理
<img loading="lazy" alt="" src="@/assets/header/robot.png"
:style="{ filter: $route.path === '/home' || $route.path === '/role-config' || $route.path === '/device-management' ? 'brightness(0) invert(1)' : 'None' }" />
<span class="nav-text">{{ $t('header.smartManagement') }}</span>
</div>
<div v-if="isSuperAdmin" class="equipment-management" :class="{ 'active-tab': $route.path === '/model-config' }"
@click="goModelConfig">
<img loading="lazy" alt="" src="@/assets/header/model_config.png"
<img loading="lazy" alt="" src="@/assets/header/model_config.png"
:style="{ filter: $route.path === '/model-config' ? 'brightness(0) invert(1)' : 'None' }" />
模型配置
<span class="nav-text">{{ $t('header.modelConfig') }}</span>
</div>
<div v-if="isSuperAdmin" class="equipment-management"
<div v-if="isSuperAdmin" class="equipment-management"
:class="{ 'active-tab': $route.path === '/user-management' }" @click="goUserManagement">
<img loading="lazy" alt="" src="@/assets/header/user_management.png"
<img loading="lazy" alt="" src="@/assets/header/user_management.png"
:style="{ filter: $route.path === '/user-management' ? 'brightness(0) invert(1)' : 'None' }" />
用户管理
<span class="nav-text">{{ $t('header.userManagement') }}</span>
</div>
<div v-if="isSuperAdmin" class="equipment-management"
<div v-if="isSuperAdmin" class="equipment-management"
:class="{ 'active-tab': $route.path === '/ota-management' }" @click="goOtaManagement">
<img loading="lazy" alt="" src="@/assets/header/firmware_update.png"
<img loading="lazy" alt="" src="@/assets/header/firmware_update.png"
:style="{ filter: $route.path === '/ota-management' ? 'brightness(0) invert(1)' : 'None' }" />
OTA管理
<span class="nav-text">{{ $t('header.otaManagement') }}</span>
</div>
<el-dropdown v-if="isSuperAdmin" trigger="click" class="equipment-management more-dropdown"
<el-dropdown v-if="isSuperAdmin" trigger="click" class="equipment-management more-dropdown"
:class="{ 'active-tab': $route.path === '/dict-management' || $route.path === '/params-management' || $route.path === '/provider-management' || $route.path === '/server-side-management' }"
@visible-change="handleParamDropdownVisibleChange">
<span class="el-dropdown-link">
<img loading="lazy" alt="" src="@/assets/header/param_management.png"
<img loading="lazy" alt="" src="@/assets/header/param_management.png"
:style="{ filter: $route.path === '/dict-management' || $route.path === '/params-management' || $route.path === '/provider-management' || $route.path === '/server-side-management' ? 'brightness(0) invert(1)' : 'None' }" />
参数字典
<span class="nav-text">{{ $t('header.paramDictionary') }}</span>
<i class="el-icon-arrow-down el-icon--right" :class="{ 'rotate-down': paramDropdownVisible }"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="goParamManagement">
参数管理
{{ $t('header.paramManagement') }}
</el-dropdown-item>
<el-dropdown-item @click.native="goDictManagement">
字典管理
{{ $t('header.dictManagement') }}
</el-dropdown-item>
<el-dropdown-item @click.native="goProviderManagement">
字段管理
{{ $t('header.providerManagement') }}
</el-dropdown-item>
<el-dropdown-item @click.native="goServerSideManagement">
服务端管理
{{ $t('header.serverSideManagement') }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
@@ -63,11 +63,31 @@
<!-- 右侧元素 -->
<div class="header-right">
<div class="search-container" v-if="$route.path === '/home' && !(isSuperAdmin && isSmallScreen)">
<el-input v-model="search" placeholder="输入名称搜索.." class="custom-search-input"
<el-input v-model="search" :placeholder="$t('header.searchPlaceholder')" class="custom-search-input"
@keyup.enter.native="handleSearch">
<i slot="suffix" class="el-icon-search search-icon" @click="handleSearch"></i>
</el-input>
</div>
<!-- 语言切换下拉菜单 -->
<el-dropdown trigger="click" class="language-dropdown" @visible-change="handleLanguageDropdownVisibleChange">
<span class="el-dropdown-link">
<i class="el-icon-document"></i>
<i class="el-icon-arrow-down el-icon--right" :class="{ 'rotate-down': languageDropdownVisible }"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="changeLanguage('zh_CN')">
{{ $t('language.zhCN') }}
</el-dropdown-item>
<el-dropdown-item @click.native="changeLanguage('zh_TW')">
{{ $t('language.zhTW') }}
</el-dropdown-item>
<el-dropdown-item @click.native="changeLanguage('en')">
{{ $t('language.en') }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<img loading="lazy" alt="" src="@/assets/home/avatar.png" class="avatar-img" />
<el-dropdown trigger="click" class="user-dropdown" @visible-change="handleUserDropdownVisibleChange">
<span class="el-dropdown-link">
@@ -75,8 +95,8 @@
<i class="el-icon-arrow-down el-icon--right" :class="{ 'rotate-down': userDropdownVisible }"></i>
</span>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item @click.native="showChangePasswordDialog">修改密码</el-dropdown-item>
<el-dropdown-item @click.native="handleLogout">退出登录</el-dropdown-item>
<el-dropdown-item @click.native="showChangePasswordDialog">{{ $t('header.changePassword') }}</el-dropdown-item>
<el-dropdown-item @click.native="handleLogout">{{ $t('header.logout') }}</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
@@ -91,6 +111,8 @@
import userApi from '@/apis/module/user';
import { mapActions, mapGetters } from 'vuex';
import ChangePasswordDialog from './ChangePasswordDialog.vue'; // 引入修改密码弹窗组件
import { changeLanguage } from '@/i18n';
import i18n from '@/i18n';
export default {
name: 'HeaderBar',
@@ -108,6 +130,7 @@ export default {
isChangePasswordDialogVisible: false, // 控制修改密码弹窗的显示
userDropdownVisible: false,
paramDropdownVisible: false,
languageDropdownVisible: false,
isSmallScreen: false
}
},
@@ -115,6 +138,10 @@ export default {
...mapGetters(['getIsSuperAdmin']),
isSuperAdmin() {
return this.getIsSuperAdmin;
},
// 获取当前语言
currentLanguage() {
return i18n.locale || 'zh_CN';
}
},
mounted() {
@@ -182,7 +209,7 @@ export default {
} catch (error) {
console.error('正则表达式创建失败:', error);
this.$message.error({
message: '搜索关键词格式不正确',
message: this.$t('message.error'),
showClose: true
});
}
@@ -197,13 +224,13 @@ export default {
// 调用 Vuex 的 logout action
await this.logout();
this.$message.success({
message: '退出登录成功',
message: this.$t('message.success'),
showClose: true
});
} catch (error) {
console.error('退出登录失败:', error);
this.$message.error({
message: '退出登录失败,请重试',
message: this.$t('message.error'),
showClose: true
});
}
@@ -215,6 +242,19 @@ export default {
handleParamDropdownVisibleChange(visible) {
this.paramDropdownVisible = visible;
},
// 监听语言下拉菜单的可见状态变化
handleLanguageDropdownVisibleChange(visible) {
this.languageDropdownVisible = visible;
},
// 切换语言
changeLanguage(lang) {
changeLanguage(lang);
this.languageDropdownVisible = false;
this.$message.success({
message: this.$t('message.success'),
showClose: true
});
},
// 使用 mapActions 引入 Vuex 的 logout action
...mapActions(['logout'])
@@ -229,7 +269,7 @@ export default {
height: 63px !important;
min-width: 900px;
/* 设置最小宽度防止过度压缩 */
overflow: hidden;
overflow: visible;
}
.header-container {
@@ -261,7 +301,7 @@ export default {
align-items: center;
gap: 25px;
position: absolute;
left: 50%;
left: 44%;
transform: translateX(-50%);
}
@@ -304,29 +344,29 @@ export default {
}
.search-container {
margin-right: 15px;
min-width: 150px;
flex-grow: 1;
max-width: 220px;
margin-right: 5px;
flex: 0.9;
min-width: 60px;
max-width: none;
}
.custom-search-input>>>.el-input__inner {
height: 30px;
border-radius: 15px;
height: 18px;
border-radius: 9px;
background-color: #fff;
border: 1px solid #e4e6ef;
padding-left: 15px;
font-size: 12px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding-left: 8px;
font-size: 9px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
width: 100%;
}
.search-icon {
cursor: pointer;
color: #909399;
margin-right: 8px;
font-size: 14px;
line-height: 30px;
margin-right: 3px;
font-size: 9px;
line-height: 18px;
}
.custom-search-input::v-deep .el-input__suffix-inner {
@@ -345,6 +385,11 @@ export default {
flex-shrink: 0;
}
.language-dropdown {
flex-shrink: 0;
margin-right: 5px;
}
.more-dropdown {
padding-right: 20px;
}
@@ -364,6 +409,14 @@ export default {
transition: transform 0.3s ease;
}
/* 导航文本样式 - 支持中英文换行 */
.nav-text {
white-space: normal;
text-align: center;
max-width: 80px;
line-height: 1.2;
}
/* 响应式调整 */
@media (max-width: 1200px) {
.header-center {
@@ -1,9 +1,9 @@
<template>
<el-dialog title="手动添加设备" :visible="visible" @close="handleClose" width="30%" center>
<el-dialog :title="$t('manualAddDeviceDialog.title')" :visible="visible" @close="handleClose" width="30%" center>
<div class="dialog-content">
<el-form :model="deviceForm" :rules="rules" ref="deviceForm" label-width="100px">
<el-form-item label="设备型号" prop="board">
<el-select v-model="deviceForm.board" placeholder="请选择设备型号" style="width: 100%">
<el-form :model="deviceForm" :rules="rules" ref="deviceForm" label-width="auto">
<el-form-item :label="$t('manualAddDeviceDialog.deviceType')" prop="board">
<el-select v-model="deviceForm.board" :placeholder="$t('manualAddDeviceDialog.deviceTypePlaceholder')" style="width: 100%">
<el-option
v-for="item in firmwareTypes"
:key="item.key"
@@ -12,17 +12,17 @@
</el-option>
</el-select>
</el-form-item>
<el-form-item label="固件版本" prop="appVersion">
<el-input v-model="deviceForm.appVersion" placeholder="请输入固件版本"></el-input>
<el-form-item :label="$t('manualAddDeviceDialog.firmwareVersion')" prop="appVersion">
<el-input v-model="deviceForm.appVersion" :placeholder="$t('manualAddDeviceDialog.firmwareVersionPlaceholder')"></el-input>
</el-form-item>
<el-form-item label="Mac地址" prop="macAddress">
<el-input v-model="deviceForm.macAddress" placeholder="请输入Mac地址"></el-input>
<el-form-item :label="$t('manualAddDeviceDialog.macAddress')" prop="macAddress">
<el-input v-model="deviceForm.macAddress" :placeholder="$t('manualAddDeviceDialog.macAddressPlaceholder')"></el-input>
</el-form-item>
</el-form>
</div>
<div style="display: flex;margin: 15px 15px;gap: 7px;">
<div class="dialog-btn" @click="submitForm">确定</div>
<div class="dialog-btn cancel-btn" @click="cancel">取消</div>
<div class="dialog-btn" @click="submitForm">{{ $t('manualAddDeviceDialog.confirm') }}</div>
<div class="dialog-btn cancel-btn" @click="cancel">{{ $t('manualAddDeviceDialog.cancel') }}</div>
</div>
</el-dialog>
</template>
@@ -41,9 +41,9 @@ export default {
const validateMac = (rule, value, callback) => {
const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
if (!value) {
callback(new Error('请输入Mac地址'));
callback(new Error(this.$t('manualAddDeviceDialog.requiredMacAddress')));
} else if (!macRegex.test(value)) {
callback(new Error('请输入正确的Mac地址格式,例如:00:1A:2B:3C:4D:5E'));
callback(new Error(this.$t('manualAddDeviceDialog.invalidMacAddress')));
} else {
callback();
}
@@ -58,10 +58,10 @@ export default {
firmwareTypes: [],
rules: {
board: [
{ required: true, message: '请选择设备型号', trigger: 'change' }
{ required: true, message: this.$t('manualAddDeviceDialog.requiredDeviceType'), trigger: 'change' }
],
appVersion: [
{ required: true, message: '请输入固件版本', trigger: 'blur' }
{ required: true, message: this.$t('manualAddDeviceDialog.requiredFirmwareVersion'), trigger: 'blur' }
],
macAddress: [
{ required: true, validator: validateMac, trigger: 'blur' }
@@ -79,7 +79,7 @@ export default {
this.firmwareTypes = res.data;
} catch (error) {
console.error('获取固件类型失败:', error);
this.$message.error(error.message || '获取固件类型失败');
this.$message.error(error.message || this.$t('manualAddDeviceDialog.getFirmwareTypeFailed'));
}
},
submitForm() {
@@ -97,11 +97,11 @@ export default {
Api.device.manualAddDevice(params, ({ data }) => {
if (data.code === 0) {
this.$message.success('设备添加成功');
this.$message.success(this.$t('manualAddDeviceDialog.addSuccess'));
this.$emit('refresh');
this.closeDialog();
} else {
this.$message.error(data.msg || '添加失败');
this.$message.error(data.msg || this.$t('manualAddDeviceDialog.addFailed'));
}
});
},
@@ -149,10 +149,10 @@ export default {
}
::v-deep .el-dialog__body {
padding: 20px 6px;
padding: 20px 20px;
}
::v-deep .el-form-item {
margin-bottom: 20px;
}
</style>
</style>
@@ -3,7 +3,7 @@
class="center-dialog" >
<div style="margin: 0 18px; text-align: left; padding: 10px; border-radius: 10px;">
<div style="font-size: 30px; color: #3d4566; margin-top: -10px; margin-bottom: 10px; text-align: center;">
{{ modelData.duplicateMode ? '创建副本' : '修改模型' }}
{{ modelData.duplicateMode ? $t('modelConfigDialog.duplicateModel') : $t('modelConfigDialog.editModel') }}
</div>
<button class="custom-close-btn" @click="dialogVisible = false">
@@ -11,14 +11,14 @@
</button>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<div style="font-size: 20px; font-weight: bold; color: #3d4566;">模型信息</div>
<div style="font-size: 20px; font-weight: bold; color: #3d4566;">{{ $t('modelConfigDialog.modelInfo') }}</div>
<div style="display: flex; align-items: center; gap: 20px;">
<div style="display: flex; align-items: center;">
<span style="margin-right: 8px;">是否启用</span>
<span style="margin-right: 8px;">{{ $t('modelConfigDialog.enable') }}</span>
<el-switch v-model="form.isEnabled" :active-value="1" :inactive-value="0" class="custom-switch"></el-switch>
</div>
<div style="display: none; align-items: center;">
<span style="margin-right: 8px;">设为默认</span>
<span style="margin-right: 8px;">{{ $t('modelConfigDialog.setDefault') }}</span>
<el-switch v-model="form.isDefault" :active-value="1" :inactive-value="0" class="custom-switch"></el-switch>
</div>
</div>
@@ -26,39 +26,39 @@
<div style="height: 2px; background: #e9e9e9; margin-bottom: 22px;"></div>
<el-form :model="form" ref="form" label-width="100px" label-position="left" class="custom-form">
<el-form :model="form" ref="form" label-width="auto" label-position="left" class="custom-form">
<div style="display: flex; gap: 20px; margin-bottom: 0;">
<el-form-item label="模型名称" prop="name" style="flex: 1;">
<el-input v-model="form.modelName" placeholder="请输入模型名称" class="custom-input-bg"></el-input>
<el-form-item :label="$t('modelConfigDialog.modelName')" prop="name" style="flex: 1;">
<el-input v-model="form.modelName" :placeholder="$t('modelConfigDialog.enterModelName')" class="custom-input-bg"></el-input>
</el-form-item>
<el-form-item label="模型编码" prop="code" style="flex: 1;">
<el-input v-model="form.modelCode" placeholder="请输入模型编码" class="custom-input-bg"></el-input>
<el-form-item :label="$t('modelConfigDialog.modelCode')" prop="code" style="flex: 1;">
<el-input v-model="form.modelCode" :placeholder="$t('modelConfigDialog.enterModelCode')" class="custom-input-bg"></el-input>
</el-form-item>
</div>
<div style="display: flex; gap: 20px; margin-bottom: 0;">
<el-form-item label="供应器" prop="supplier" style="flex: 1;">
<el-select v-model="form.configJson.type" placeholder="请选择" class="custom-select custom-input-bg"
<el-form-item :label="$t('modelConfigDialog.supplier')" prop="supplier" style="flex: 1;">
<el-select v-model="form.configJson.type" :placeholder="$t('modelConfigDialog.selectSupplier')" class="custom-select custom-input-bg"
style="width: 100%;" @focus="loadProviders" filterable>
<el-option v-for="item in providers" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="排序号" prop="sort" style="flex: 1;">
<el-input v-model.number="form.sort" type="number" placeholder="请输入排序号" class="custom-input-bg"></el-input>
<el-form-item :label="$t('modelConfigDialog.sortOrder')" prop="sort" style="flex: 1;">
<el-input v-model.number="form.sort" type="number" :placeholder="$t('modelConfigDialog.enterSortOrder')" class="custom-input-bg"></el-input>
</el-form-item>
</div>
<el-form-item label="文档地址" prop="docUrl" style="margin-bottom: 27px;">
<el-input v-model="form.docLink" placeholder="请输入文档地址" class="custom-input-bg"></el-input>
<el-form-item :label="$t('modelConfigDialog.docLink')" prop="docUrl" style="margin-bottom: 27px;">
<el-input v-model="form.docLink" :placeholder="$t('modelConfigDialog.enterDocLink')" class="custom-input-bg"></el-input>
</el-form-item>
<el-form-item label="备注" prop="remark" class="prop-remark">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入模型备注" :autosize="{ minRows: 3, maxRows: 5 }"
<el-form-item :label="$t('modelConfigDialog.remark')" prop="remark" class="prop-remark">
<el-input v-model="form.remark" type="textarea" :rows="3" :placeholder="$t('modelConfigDialog.enterRemark')" :autosize="{ minRows: 3, maxRows: 5 }"
class="custom-input-bg"></el-input>
</el-form-item>
</el-form>
<div style="font-size: 20px; font-weight: bold; color: #3d4566; margin-bottom: 15px;">调用信息</div>
<div style="font-size: 20px; font-weight: bold; color: #3d4566; margin-bottom: 15px;">{{ $t('modelConfigDialog.callInfo') }}</div>
<div style="height: 2px; background: #e9e9e9; margin-bottom: 22px;"></div>
<el-form :model="form.configJson" ref="callInfoForm" label-width="auto" class="custom-form">
@@ -67,7 +67,7 @@
<el-form-item v-for="field in row" :key="field.prop" :label="field.label" :prop="field.prop"
style="flex: 1;">
<template v-if="field.type === 'json-textarea'">
<el-input v-model="fieldJsonMap[field.prop]" type="textarea" :rows="3" placeholder="请输入JSON格式变量(示例:{'key':'value'})"
<el-input v-model="fieldJsonMap[field.prop]" type="textarea" :rows="3" :placeholder="$t('modelConfigDialog.enterJsonExample')"
class="custom-input-bg" @change="(val) => handleJsonChange(field.prop, val)"></el-input>
</template>
<el-input v-else v-model="form.configJson[field.prop]" :placeholder="field.placeholder" :type="field.type"
@@ -85,7 +85,7 @@
class="save-btn"
:loading="saving"
:disabled="saving">
保存
{{ $t('modelConfigDialog.save') }}
</el-button>
</div>
</el-dialog>
+20 -20
View File
@@ -19,23 +19,23 @@
</button>
</div>
<el-form :model="form" :rules="rules" ref="form" label-width="110px" label-position="left" class="param-form">
<el-form-item label="参数编码" prop="paramCode" class="form-item">
<el-input v-model="form.paramCode" placeholder="请输入参数编码" class="custom-input"></el-input>
<el-form :model="form" :rules="rules" ref="form" label-width="auto" label-position="left" class="param-form">
<el-form-item :label="$t('paramDialog.paramCode')" prop="paramCode" class="form-item">
<el-input v-model="form.paramCode" :placeholder="$t('paramDialog.paramCodePlaceholder')" class="custom-input"></el-input>
</el-form-item>
<el-form-item label="参数值" prop="paramValue" class="form-item">
<el-input v-model="form.paramValue" placeholder="请输入参数值" class="custom-input"></el-input>
<el-form-item :label="$t('paramDialog.paramValue')" prop="paramValue" class="form-item">
<el-input v-model="form.paramValue" :placeholder="$t('paramDialog.paramValuePlaceholder')" class="custom-input"></el-input>
</el-form-item>
<el-form-item label="值类型" prop="valueType" class="form-item">
<el-select v-model="form.valueType" placeholder="请选择值类型" class="custom-select">
<el-option v-for="item in valueTypeOptions" :key="item.value" :label="item.label" :value="item.value"/>
<el-form-item :label="$t('paramDialog.valueType')" prop="valueType" class="form-item">
<el-select v-model="form.valueType" :placeholder="$t('paramDialog.valueTypePlaceholder')" class="custom-select">
<el-option v-for="item in valueTypeOptions" :key="item.value" :label="$t(`paramDialog.${item.value}Type`)" :value="item.value"/>
</el-select>
</el-form-item>
<el-form-item label="备注" prop="remark" class="form-item remark-item">
<el-input type="textarea" v-model="form.remark" placeholder="请输入备注" :rows="3" class="custom-textarea"></el-input>
<el-form-item :label="$t('paramDialog.remark')" prop="remark" class="form-item remark-item">
<el-input type="textarea" v-model="form.remark" :placeholder="$t('paramDialog.remarkPlaceholder')" :rows="3" class="custom-textarea"></el-input>
</el-form-item>
</el-form>
@@ -46,10 +46,10 @@
class="save-btn"
:loading="saving"
:disabled="saving">
保存
{{ $t('paramDialog.save') }}
</el-button>
<el-button @click="cancel" class="cancel-btn">
取消
{{ $t('paramDialog.cancel') }}
</el-button>
</div>
</div>
@@ -83,21 +83,21 @@ export default {
dialogKey: Date.now(),
saving: false,
valueTypeOptions: [
{ value: 'string', label: '字符串(string)' },
{ value: 'number', label: '数字(number)' },
{ value: 'boolean', label: '布尔值(boolean)' },
{ value: 'array', label: '数组(array)' },
{ value: 'json', label: 'JSON对象(json)' }
{ value: 'string' },
{ value: 'number' },
{ value: 'boolean' },
{ value: 'array' },
{ value: 'json' }
],
rules: {
paramCode: [
{ required: true, message: "请输入参数编码", trigger: "blur" }
{ required: true, message: this.$t('paramDialog.requiredParamCode'), trigger: "blur" }
],
paramValue: [
{ required: true, message: "请输入参数值", trigger: "blur" }
{ required: true, message: this.$t('paramDialog.requiredParamValue'), trigger: "blur" }
],
valueType: [
{ required: true, message: "请选择值类型", trigger: "change" }
{ required: true, message: this.$t('paramDialog.requiredValueType'), trigger: "change" }
]
}
};
@@ -9,44 +9,44 @@
<button class="custom-close-btn" @click="handleClose">×</button>
<el-form :model="form" label-width="100px" :rules="rules" ref="form" class="custom-form">
<el-form :model="form" label-width="auto" :rules="rules" ref="form" class="custom-form">
<div style="display: flex; gap: 20px; margin-bottom: 20px;">
<el-form-item label="类别" prop="modelType" style="flex: 1;">
<el-select v-model="form.modelType" placeholder="请选择类别" class="custom-input-bg" style="width: 100%;">
<el-form-item :label="$t('providerDialog.category')" prop="modelType" style="flex: 1;">
<el-select v-model="form.modelType" :placeholder="$t('providerDialog.selectCategory')" class="custom-input-bg" style="width: 100%;">
<el-option v-for="item in modelTypes" :key="item.value" :label="item.label" :value="item.value">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="编码" prop="providerCode" style="flex: 1;">
<el-input v-model="form.providerCode" placeholder="请输入供应器编码" class="custom-input-bg"></el-input>
<el-form-item :label="$t('providerDialog.code')" prop="providerCode" style="flex: 1;">
<el-input v-model="form.providerCode" :placeholder="$t('providerDialog.inputCode')" class="custom-input-bg"></el-input>
</el-form-item>
</div>
<div style="display: flex; gap: 20px; margin-bottom: 20px;">
<el-form-item label="名称" prop="name" style="flex: 1;">
<el-input v-model="form.name" placeholder="请输入供应器名称" class="custom-input-bg"></el-input>
<el-form-item :label="$t('providerDialog.name')" prop="name" style="flex: 1;">
<el-input v-model="form.name" :placeholder="$t('providerDialog.inputName')" class="custom-input-bg"></el-input>
</el-form-item>
<el-form-item label="排序" prop="sort" style="flex: 1;">
<el-form-item :label="$t('providerDialog.sort')" prop="sort" style="flex: 1;">
<el-input-number v-model="form.sort" :min="0" controls-position="right" class="custom-input-bg"
style="width: 100%;"></el-input-number>
</el-form-item>
</div>
<div style="font-size: 20px; font-weight: bold; color: #3d4566; margin-bottom: 15px;">
字段配置
{{ $t('providerDialog.fieldConfig') }}
<div style="display: inline-block; float: right;">
<el-button type="primary" @click="addField" size="small" style="background: #5bc98c; border: none;"
:disabled="hasIncompleteFields">
添加
{{ $t('providerDialog.add') }}
</el-button>
<el-button type="primary" @click="toggleSelectAllFields" size="small"
style="background: #5f70f3; border: none; margin-left: 10px;">
{{ isAllFieldsSelected ? '取消全选' : '全选' }}
{{ isAllFieldsSelected ? $t('providerDialog.deselectAll') : $t('providerDialog.selectAll') }}
</el-button>
<el-button type="danger" @click="batchRemoveFields" size="small"
style="background: red; border: none; margin-left: 10px;">
批量删除
{{ $t('providerDialog.batchDelete') }}
</el-button>
</div>
</div>
@@ -54,40 +54,40 @@
<div class="fields-container">
<el-table :data="form.fields" style="width: 100%;" border size="medium" :key="tableKey">
<el-table-column label="选择" align="center" width="50">
<el-table-column :label="$t('modelConfig.select')" align="center" width="50">
<template slot-scope="scope">
<el-checkbox v-model="scope.row.selected" @change="handleFieldSelectChange"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="字段key">
<el-table-column :label="$t('providerDialog.fieldKey')">
<template slot-scope="scope">
<template v-if="scope.row.editing">
<el-input v-model="scope.row.key" placeholder="字段key"></el-input>
<el-input v-model="scope.row.key" :placeholder="$t('providerDialog.fieldKey')"></el-input>
</template>
<template v-else>
{{ scope.row.key }}
</template>
</template>
</el-table-column>
<el-table-column label="字段标签">
<el-table-column :label="$t('providerDialog.fieldLabel')">
<template slot-scope="scope">
<template v-if="scope.row.editing">
<el-input v-model="scope.row.label" placeholder="字段标签"></el-input>
<el-input v-model="scope.row.label" :placeholder="$t('providerDialog.fieldLabel')"></el-input>
</template>
<template v-else>
{{ scope.row.label }}
</template>
</template>
</el-table-column>
<el-table-column label="字段类型">
<el-table-column :label="$t('providerDialog.fieldType')">
<template slot-scope="scope">
<template v-if="scope.row.editing">
<el-select v-model="scope.row.type" placeholder="类型">
<el-option label="字符串" value="string"></el-option>
<el-option label="数字" value="number"></el-option>
<el-option label="布尔值" value="boolean"></el-option>
<el-option label="字典" value="dict"></el-option>
<el-option label="分号分割的列表" value="array"></el-option>
<el-select v-model="scope.row.type" :placeholder="$t('providerDialog.type')">
<el-option :label="$t('providerDialog.stringType')" value="string"></el-option>
<el-option :label="$t('providerDialog.numberType')" value="number"></el-option>
<el-option :label="$t('providerDialog.booleanType')" value="boolean"></el-option>
<el-option :label="$t('providerDialog.dictType')" value="dict"></el-option>
<el-option :label="$t('providerDialog.arrayType')" value="array"></el-option>
</el-select>
</template>
<template v-else>
@@ -95,26 +95,26 @@
</template>
</template>
</el-table-column>
<el-table-column label="默认值">
<el-table-column :label="$t('providerDialog.defaultValue')">
<template slot-scope="scope">
<template v-if="scope.row.editing">
<el-input v-model="scope.row.default" placeholder="请输入默认值"></el-input>
<el-input v-model="scope.row.default" :placeholder="$t('providerDialog.inputDefaultValue')"></el-input>
</template>
<template v-else>
{{ scope.row.default }}
</template>
</template>
</el-table-column>
<el-table-column label="操作" width="150" align="center">
<el-table-column :label="$t('providerDialog.operation')" width="150" align="center">
<template slot-scope="scope">
<el-button v-if="!scope.row.editing" type="primary" size="mini" @click="startEditing(scope.row)">
编辑
{{ $t('providerDialog.edit') }}
</el-button>
<el-button v-else type="success" size="mini" @click="stopEditing(scope.row)">
完成
{{ $t('providerDialog.complete') }}
</el-button>
<el-button type="danger" size="mini" @click="removeField(scope.$index)">
删除
{{ $t('providerDialog.delete') }}
</el-button>
</template>
</el-table-column>
@@ -124,7 +124,7 @@
</div>
<div style="display: flex; justify-content: center;">
<el-button type="primary" @click="submit" class="save-btn" :loading="saving">保存</el-button>
<el-button type="primary" @click="submit" class="save-btn" :loading="saving">{{ $t('providerDialog.save') }}</el-button>
</div>
</el-dialog>
</template>
@@ -140,15 +140,19 @@ export default {
data() {
return {
saving: false,
rules: {
modelType: [{ required: true, message: '请选择类别', trigger: 'change' }],
providerCode: [{ required: true, message: '请输入供应器编码', trigger: 'blur' }],
name: [{ required: true, message: '请输入供应器名称', trigger: 'blur' }]
},
isAllFieldsSelected: false,
tableKey: 0 // 用于强制表格重新渲染
};
},
computed: {
rules() {
return {
modelType: [{ required: true, message: this.$t('providerDialog.requiredCategory'), trigger: 'change' }],
providerCode: [{ required: true, message: this.$t('providerDialog.requiredCode'), trigger: 'blur' }],
name: [{ required: true, message: this.$t('providerDialog.requiredName'), trigger: 'blur' }]
};
}
},
computed: {
hasIncompleteFields() {
return this.form.fields && this.form.fields.some(field =>
@@ -159,11 +163,11 @@ export default {
methods: {
getTypeLabel(type) {
const typeMap = {
'string': '字符串',
'number': '数字',
'boolean': '布尔值',
'dict': '字典',
'array': '分号分割的列表'
'string': this.$t('providerDialog.stringType'),
'number': this.$t('providerDialog.numberType'),
'boolean': this.$t('providerDialog.booleanType'),
'dict': this.$t('providerDialog.dictType'),
'array': this.$t('providerDialog.arrayType')
};
return typeMap[type];
},
@@ -212,7 +216,7 @@ export default {
addField() {
if (this.hasIncompleteFields) {
this.$message.warning({
message: '请先完成当前字段的编辑',
message: this.$t('providerDialog.completeFieldEdit'),
showClose: true
});
return;
@@ -230,47 +234,47 @@ export default {
},
removeField(index) {
this.$confirm('确定要删除该字段吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
this.$confirm(this.$t('providerDialog.confirmDeleteField'), this.$t('common.warning'), {
confirmButtonText: this.$t('common.confirm'),
cancelButtonText: this.$t('common.cancel'),
type: 'warning'
}).then(() => {
this.form.fields = this.form.fields.filter((_, i) => i !== index);
this.updateSelectAllStatus();
this.forceTableRerender();
this.$message.success({
message: '删除成功',
showClose: true
});
message: this.$t('common.deleteSuccess'),
showClose: true
});
}).catch(() => {
this.$message.info({
message: '已取消删除',
showClose: true
this.$message.info({
message: this.$t('common.deleteCancelled'),
showClose: true
});
});
});
},
batchRemoveFields() {
const selectedFields = this.form.fields.filter(field => field.selected);
if (selectedFields.length === 0) {
this.$message.warning({
message: '请先选择要删除的字段',
message: this.$t('providerDialog.selectFieldsToDelete'),
showClose: true
});
return;
}
this.$confirm(`确定要删除选中的 ${selectedFields.length} 个字段吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
this.$confirm(this.$t('providerDialog.confirmBatchDeleteFields', { count: selectedFields.length }), this.$t('common.warning'), {
confirmButtonText: this.$t('common.confirm'),
cancelButtonText: this.$t('common.cancel'),
type: 'warning'
}).then(() => {
this.form.fields = this.form.fields.filter(field => !field.selected);
this.isAllFieldsSelected = false;
this.forceTableRerender();
this.$message.success({
message: `成功删除 ${selectedFields.length} 个字段`,
showClose: true
});
message: this.$t('providerDialog.batchDeleteFieldsSuccess', { count: selectedFields.length }),
showClose: true
});
}).catch(() => {
this.$message.info({
message: '已取消删除',
@@ -293,12 +297,12 @@ export default {
if (valid) {
const editingField = this.form.fields.find(field => field.editing);
if (editingField) {
this.$message.warning({
message: '请先完成当前字段的编辑',
showClose: true
});
return;
}
this.$message.warning({
message: this.$t('providerDialog.completeFieldEdit'),
showClose: true
});
return;
}
this.form.fields = this.form.fields.map(field => ({
...field,
+44 -44
View File
@@ -7,72 +7,72 @@
<div class="scroll-wrapper">
<div class="table-container" ref="tableContainer" @scroll="handleScroll">
<el-table v-loading="loading" :data="filteredTtsModels" style="width: 100%;" class="data-table"
header-row-class-name="table-header" :fit="true" element-loading-text="拼命加载中"
header-row-class-name="table-header" :fit="true" :element-loading-text="$t('voicePrint.loading')"
element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.8)">
<el-table-column label="选择" width="50" align="center">
<el-table-column :label="$t('ttsModel.select')" width="50" align="center">
<template slot-scope="scope">
<el-checkbox v-model="scope.row.selected"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="音色编码" align="center">
<el-table-column :label="$t('ttsModel.voiceCode')" align="center">
<template slot-scope="scope">
<el-input v-if="scope.row.editing" v-model="scope.row.voiceCode"></el-input>
<span v-else>{{ scope.row.voiceCode }}</span>
</template>
</el-table-column>
<el-table-column label="音色名称" align="center">
<el-table-column :label="$t('ttsModel.voiceName')" align="center">
<template slot-scope="scope">
<el-input v-if="scope.row.editing" v-model="scope.row.voiceName"></el-input>
<span v-else>{{ scope.row.voiceName }}</span>
</template>
</el-table-column>
<el-table-column label="语言类型" align="center">
<el-table-column :label="$t('ttsModel.languageType')" align="center">
<template slot-scope="scope">
<el-input v-if="scope.row.editing" v-model="scope.row.languageType"></el-input>
<span v-else>{{ scope.row.languageType }}</span>
</template>
</el-table-column>
<el-table-column v-if="!showReferenceColumns" label="试听" align="center" class-name="audio-column">
<el-table-column v-if="!showReferenceColumns" :label="$t('ttsModel.preview')" align="center" class-name="audio-column">
<template slot-scope="scope">
<div class="custom-audio-container">
<el-input v-if="scope.row.editing" v-model="scope.row.voiceDemo" placeholder="请输入MP3地址"
<el-input v-if="scope.row.editing" v-model="scope.row.voiceDemo" :placeholder="$t('ttsModel.enterMp3Url')"
class="audio-input">
</el-input>
<AudioPlayer v-else-if="isValidAudioUrl(scope.row.voiceDemo)" :audioUrl="scope.row.voiceDemo" />
</div>
</template>
</el-table-column>
<el-table-column v-if="!showReferenceColumns" label="备注" align="center">
<el-table-column v-if="!showReferenceColumns" :label="$t('ttsModel.remark')" align="center">
<template slot-scope="scope">
<el-input v-if="scope.row.editing" type="textarea" :rows="1" autosize v-model="scope.row.remark"
placeholder="这里是备注" class="remark-input"></el-input>
:placeholder="$t('ttsModel.enterRemark')" class="remark-input"></el-input>
<span v-else>{{ scope.row.remark }}</span>
</template>
</el-table-column>
<el-table-column v-if="showReferenceColumns" label="克隆音频路径" align="center">
<el-table-column v-if="showReferenceColumns" :label="$t('ttsModel.cloneAudioPath')" align="center">
<template slot-scope="scope">
<el-input v-if="scope.row.editing" v-model="scope.row.referenceAudio" placeholder="这里是克隆音频路径"></el-input>
<el-input v-if="scope.row.editing" v-model="scope.row.referenceAudio" :placeholder="$t('ttsModel.enterCloneAudioPath')"></el-input>
<span v-else>{{ scope.row.referenceAudio }}</span>
</template>
</el-table-column>
<el-table-column v-if="showReferenceColumns" label="克隆音频文本" align="center">
<el-table-column v-if="showReferenceColumns" :label="$t('ttsModel.cloneAudioText')" align="center">
<template slot-scope="scope">
<el-input v-if="scope.row.editing" v-model="scope.row.referenceText" placeholder="这里是克隆音频对应文本"></el-input>
<el-input v-if="scope.row.editing" v-model="scope.row.referenceText" :placeholder="$t('ttsModel.enterCloneAudioText')"></el-input>
<span v-else>{{ scope.row.referenceText }}</span>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="150">
<el-table-column :label="$t('ttsModel.operation')" align="center" width="150">
<template slot-scope="scope">
<template v-if="!scope.row.editing">
<el-button type="text" size="mini" @click="startEdit(scope.row)" class="edit-btn">
编辑
</el-button>
<el-button type="text" size="mini" @click="deleteRow(scope.row)" class="delete-btn">
删除
</el-button>
{{ $t('ttsModel.edit') }}
</el-button>
<el-button type="text" size="mini" @click="deleteRow(scope.row)" class="delete-btn">
{{ $t('ttsModel.delete') }}
</el-button>
</template>
<el-button v-else type="success" size="mini" @click="saveEdit(scope.row)" class="save-Tts">保存
</el-button>
<el-button v-else type="success" size="mini" @click="saveEdit(scope.row)" class="save-Tts">{{ $t('ttsModel.save') }}
</el-button>
</template>
</el-table-column>
</el-table>
@@ -87,13 +87,13 @@
</div>
<div class="action-buttons">
<el-button type="primary" size="mini" @click="toggleSelectAll" style="background: #606ff3;border: None">
{{ selectAll ? '取消全选' : '全选' }}
{{ selectAll ? $t('ttsModel.deselectAll') : $t('ttsModel.selectAll') }}
</el-button>
<el-button type="primary" size="mini" @click="addNew" style="background: #5bc98c;border: None;">
新增
{{ $t('ttsModel.add') }}
</el-button>
<el-button type="primary" size="mini" @click="deleteRow(filteredTtsModels.filter(row => row.selected))"
style="background: red;border:None">删除
style="background: red;border:None">{{ $t('ttsModel.delete') }}
</el-button>
</div>
</el-dialog>
@@ -205,7 +205,7 @@ export default {
.map(item => ({
id: item.id || '',
voiceCode: item.ttsVoice || '',
voiceName: item.name || '未命名音色',
voiceName: item.name || this.$t('ttsModel.unnamedVoice'),
languageType: item.languages || '',
remark: item.remark || '',
referenceAudio: item.referenceAudio || '',
@@ -219,7 +219,7 @@ export default {
this.total = data.total;
} else {
this.$message.error({
message: data.msg || '获取音色列表失败',
message: data.msg || this.$t('ttsModel.getVoiceListFailed'),
showClose: true
});
}
@@ -227,7 +227,7 @@ export default {
}, (err) => {
console.error('加载失败:', err);
this.$message.error({
message: '加载音色数据失败',
message: this.$t('ttsModel.loadVoiceDataFailed'),
showClose: true
});
this.loading = false;
@@ -343,7 +343,7 @@ export default {
saveEdit(row) {
if (!row.voiceCode || !row.voiceName || !row.languageType) {
this.$message.error({
message: '音色编码、音色名称和语言类型不能为空',
message: this.$t('ttsModel.voiceCodeNameLanguageRequired'),
showClose: true
});
return;
@@ -390,7 +390,7 @@ export default {
delete row.originalData;
}
this.$message.error({
message: '操作失败,请重试',
message: this.$t('ttsModel.operationFailed'),
showClose: true
});
}
@@ -399,7 +399,7 @@ export default {
handleResponse(res, row) {
if (res.code === 0) {
this.$message.success({
message: row.id ? '修改成功' : '保存成功',
message: row.id ? this.$t('ttsModel.updateSuccess') : this.$t('ttsModel.saveSuccess'),
showClose: true
});
row.editing = false;
@@ -413,9 +413,9 @@ export default {
delete row.originalData;
}
this.$message.error({
message: res.msg || (row.id ? '修改失败' : '保存失败'),
showClose: true
});
message: res.msg || (row.id ? this.$t('ttsModel.updateFailed') : this.$t('ttsModel.saveFailed')),
showClose: true
});
}
},
@@ -429,7 +429,7 @@ export default {
addNew() {
const hasEditing = this.ttsModels.some(row => row.editing);
if (hasEditing) {
this.$message.warning('请先完成当前编辑再新增');
this.$message.warning(this.$t('ttsModel.finishEditingFirst'));
return;
}
@@ -440,7 +440,7 @@ export default {
const newRow = {
voiceCode: '',
voiceName: '',
languageType: '中文',
languageType: this.$t('editVoiceDialog.defaultLanguageType'),
voiceDemo: '',
remark: '',
referenceAudio: '',
@@ -458,34 +458,34 @@ export default {
const voices = Array.isArray(row) ? row : [row];
if (Array.isArray(row) && row.length === 0) {
this.$message.warning("请先选择需要删除的音色");
this.$message.warning(this.$t('ttsModel.selectVoiceToDelete'));
return;
}
const voiceCount = voices.length;
this.$confirm(`确定要删除选中的${voiceCount}个音色吗?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
this.$confirm(this.$t('ttsModel.confirmDeleteVoice', {count: voiceCount}), this.$t('ttsModel.warning'), {
confirmButtonText: this.$t('common.confirm'),
cancelButtonText: this.$t('common.cancel'),
type: "warning",
distinguishCancelAndClose: true
}).then(() => {
const ids = voices.map(voice => voice.id);
if (ids.some(id => !id)) {
this.$message.error("存在无效的音色ID");
this.$message.error(this.$t('ttsModel.invalidVoiceId'));
return;
}
Api.timbre.deleteVoice(ids, ({ data }) => {
if (data.code === 0) {
this.$message.success({
message: `成功删除${voiceCount}个参数`,
message: this.$t('ttsModel.deleteVoiceSuccess', {count: voiceCount}),
showClose: true
});
this.loadData(); // 刷新参数列表
} else {
this.$message.error({
message: data.msg || '删除失败,请重试',
message: data.msg || this.$t('ttsModel.deleteFailed'),
showClose: true
});
}
@@ -494,13 +494,13 @@ export default {
if (action === 'cancel') {
this.$message({
type: 'info',
message: '已取消删除操作',
message: this.$t('ttsModel.deleteCancelled'),
duration: 1000
});
} else {
this.$message({
type: 'info',
message: '操作已关闭',
message: this.$t('ttsModel.operationClosed'),
duration: 1000
});
}
@@ -11,9 +11,9 @@
</button>
</div>
<el-form :model="form" :rules="rules" ref="form" label-width="110px" label-position="left" class="param-form">
<el-form-item label="声纹向量" prop="audioId" class="form-item">
<el-select v-model="form.audioId" placeholder="请选择一条语言消息" class="custom-select">
<el-form :model="form" :rules="rules" ref="form" label-width="auto" label-position="left" class="param-form">
<el-form-item :label="$t('voicePrintDialog.voicePrintVector')" prop="audioId" class="form-item">
<el-select v-model="form.audioId" :placeholder="$t('voicePrintDialog.selectVoiceMessage')" class="custom-select">
<el-option v-for="item in valueTypeOptions" :key="item.audioId" :label="item.content" :value="item.audioId">
<span style="float: left">{{ item.content }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">
@@ -24,22 +24,22 @@
</el-select>
</el-form-item>
<el-form-item label="姓名" prop="sourceName" class="form-item">
<el-input v-model="form.sourceName" placeholder="请输入姓名" class="custom-input"></el-input>
<el-form-item :label="$t('voicePrintDialog.name')" prop="sourceName" class="form-item">
<el-input v-model="form.sourceName" :placeholder="$t('voicePrintDialog.enterName')" class="custom-input"></el-input>
</el-form-item>
<el-form-item label="描述" prop="introduce" class="form-item remark-item">
<el-input type="textarea" v-model="form.introduce" placeholder="请输入描述" :rows="3" class="custom-textarea"
<el-form-item :label="$t('voicePrintDialog.description')" prop="introduce" class="form-item remark-item">
<el-input type="textarea" v-model="form.introduce" :placeholder="$t('voicePrintDialog.enterDescription')" :rows="3" class="custom-textarea"
maxlength="100" show-word-limit></el-input>
</el-form-item>
</el-form>
<div class="dialog-footer">
<el-button type="primary" @click="submit" class="save-btn" :loading="saving" :disabled="saving">
保存
{{ $t('voicePrintDialog.save') }}
</el-button>
<el-button @click="cancel" class="cancel-btn">
取消
{{ $t('voicePrintDialog.cancel') }}
</el-button>
</div>
</div>
@@ -83,13 +83,13 @@ export default {
],
rules: {
introduce: [
{ required: true, message: "请输入描述", trigger: "blur" }
{ required: true, message: '请输入描述', trigger: "blur" }
],
sourceName: [
{ required: true, message: "请输入姓名", trigger: "blur" }
{ required: true, message: '请输入名称', trigger: "blur" }
],
audioId: [
{ required: true, message: "请选择音频向量", trigger: "change" }
{ required: true, message: '请选择音频向量', trigger: "change" }
]
}
};