mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-23 23:53:55 +08:00
updata:智控台页面添加语言切换,支持中英繁。
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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> |
|
||||
target="_blank" class="doc-link">{{ $t('functionDialog.howToDeployMcp') }}</a> |
|
||||
<a href="https://github.com/xinnan-tech/xiaozhi-esp32-server/blob/main/docs/mcp-endpoint-integration.md"
|
||||
target="_blank" class="doc-link">如何接入MCP功能</a>
|
||||
target="_blank" class="doc-link">{{ $t('functionDialog.howToIntegrateMcp') }}</a>
|
||||
</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;
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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" }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user