Merge pull request #2646 from xinnan-tech/WebMenu

uptate:增加功能管理菜单
This commit is contained in:
hrz
2025-12-05 15:41:25 +08:00
committed by GitHub
18 changed files with 1267 additions and 31 deletions
@@ -41,6 +41,7 @@ import xiaozhi.modules.sys.service.SysDictDataService;
import xiaozhi.modules.sys.service.SysParamsService;
import xiaozhi.modules.sys.service.SysUserService;
import xiaozhi.modules.sys.vo.SysDictDataItem;
import xiaozhi.common.utils.JsonUtils;
/**
* 登录控制层
@@ -89,13 +90,13 @@ public class LoginController {
@Operation(summary = "登录")
public Result<TokenDTO> login(@RequestBody LoginDTO login) {
String password = login.getPassword();
// 使用工具类解密并验证验证码
String actualPassword = Sm2DecryptUtil.decryptAndValidateCaptcha(
password, login.getCaptchaId(), captchaService, sysParamsService);
login.setPassword(actualPassword);
// 按照用户名获取用户
SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername());
// 判断用户是否存在
@@ -108,8 +109,6 @@ public class LoginController {
}
return sysUserTokenService.createToken(userDTO.getId());
}
@PostMapping("/register")
@Operation(summary = "注册")
@@ -117,15 +116,15 @@ public class LoginController {
if (!sysUserService.getAllowUserRegister()) {
throw new RenException(ErrorCode.USER_REGISTER_DISABLED);
}
String password = login.getPassword();
// 使用工具类解密并验证验证码
String actualPassword = Sm2DecryptUtil.decryptAndValidateCaptcha(
password, login.getCaptchaId(), captchaService, sysParamsService);
login.setPassword(actualPassword);
// 是否开启手机注册
Boolean isMobileRegister = sysParamsService
.getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class);
@@ -204,11 +203,11 @@ public class LoginController {
}
String password = dto.getPassword();
// 使用工具类解密并验证验证码
String actualPassword = Sm2DecryptUtil.decryptAndValidateCaptcha(
password, dto.getCaptchaId(), captchaService, sysParamsService);
dto.setPassword(actualPassword);
sysUserService.changePasswordDirectly(userDTO.getId(), dto.getPassword());
@@ -229,7 +228,7 @@ public class LoginController {
config.put("beianIcpNum", sysParamsService.getValue(Constant.SysBaseParam.BEIAN_ICP_NUM.getValue(), true));
config.put("beianGaNum", sysParamsService.getValue(Constant.SysBaseParam.BEIAN_GA_NUM.getValue(), true));
config.put("name", sysParamsService.getValue(Constant.SysBaseParam.SERVER_NAME.getValue(), true));
// SM2公钥
String publicKey = sysParamsService.getValue(Constant.SM2_PUBLIC_KEY, true);
if (StringUtils.isBlank(publicKey)) {
@@ -237,6 +236,12 @@ public class LoginController {
}
config.put("sm2PublicKey", publicKey);
// 获取system-web.menu参数配置
String menuConfig = sysParamsService.getValue("system-web.menu", true);
if (StringUtils.isNotBlank(menuConfig)) {
config.put("systemWebMenu", JsonUtils.parseObject(menuConfig, Object.class));
}
return new Result<Map<String, Object>>().ok(config);
}
}
@@ -0,0 +1,6 @@
-- 添加系统功能菜单配置参数
delete from `sys_params` where param_code = 'system-web.menu';
-- 添加系统功能菜单配置参数
INSERT INTO `sys_params` (id, param_code, param_value, value_type, param_type, remark) VALUES
(600, 'system-web.menu', '{"voiceprintRecognition":{"name":"feature.voiceprintRecognition.name","enabled":false,"description":"feature.voiceprintRecognition.description"},"voiceClone":{"name":"feature.voiceClone.name","enabled":false,"description":"feature.voiceClone.description"},"knowledgeBase":{"name":"feature.knowledgeBase.name","enabled":false,"description":"feature.knowledgeBase.description"},"mcpAccessPoint":{"name":"feature.mcpAccessPoint.name","enabled":false,"description":"feature.mcpAccessPoint.description"},"vad":{"name":"feature.vad.name","enabled":false,"description":"feature.vad.description"},"asr":{"name":"feature.asr.name","enabled":false,"description":"feature.asr.description"}}', 'json', 1, '系统功能菜单配置');
@@ -423,3 +423,11 @@ databaseChangeLog:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202511131023.sql
- changeSet:
id: 202512031514
author: hrz
changes:
- sqlFile:
encoding: utf8
path: classpath:db/changelog/202512031514.sql
+10 -2
View File
@@ -23,7 +23,7 @@
<div class="settings-btn" @click="handleConfigure">
{{ $t('home.configureRole') }}
</div>
<div class="settings-btn" @click="handleVoicePrint">
<div v-if="featureStatus.voiceprintRecognition" class="settings-btn" @click="handleVoicePrint">
{{ $t('home.voiceprintRecognition') }}
</div>
<div class="settings-btn" @click="handleDeviceManage">
@@ -49,7 +49,15 @@ import i18n from '@/i18n';
export default {
name: 'DeviceItem',
props: {
device: { type: Object, required: true }
device: { type: Object, required: true },
featureStatus: {
type: Object,
default: () => ({
voiceprintRecognition: false,
voiceClone: false,
knowledgeBase: false
})
}
},
data() {
return { switchValue: false }
@@ -106,7 +106,7 @@
</div>
<!-- MCP区域 -->
<div class="mcp-access-point">
<div class="mcp-access-point" v-if="featureStatus.mcpAccessPoint">
<div class="mcp-container">
<!-- 左侧区域 -->
<div class="mcp-left">
@@ -171,6 +171,7 @@
<script>
import Api from '@/apis/api';
import i18n from '@/i18n';
import featureManager from '@/utils/featureManager';
export default {
i18n,
@@ -205,6 +206,11 @@ export default {
mcpUrl: "",
mcpStatus: "disconnected",
mcpTools: [],
// 功能状态
featureStatus: {
mcpAccessPoint: false
}
}
},
computed: {
@@ -249,6 +255,9 @@ export default {
// 右侧默认指向第一个
this.currentFunction = this.selectedList[0] || null;
// 加载功能状态
this.loadFeatureStatus();
// 加载MCP数据
this.loadMcpAddress();
this.loadMcpTools();
@@ -259,6 +268,19 @@ export default {
}
},
methods: {
/**
* 加载功能状态
*/
async loadFeatureStatus() {
// 确保featureManager已初始化完成
await featureManager.waitForInitialization();
const config = featureManager.getConfig();
this.featureStatus = {
mcpAccessPoint: config.mcpAccessPoint || false
};
},
copyUrl() {
const textarea = document.createElement('textarea');
textarea.value = this.mcpUrl;
+33 -6
View File
@@ -26,7 +26,7 @@
<span class="nav-text">{{ $t("header.smartManagement") }}</span>
</div>
<!-- 普通用户显示音色克隆 -->
<div v-if="!isSuperAdmin" class="equipment-management"
<div v-if="!isSuperAdmin && featureStatus.voiceClone" class="equipment-management"
:class="{ 'active-tab': $route.path === '/voice-clone-management' }" @click="goVoiceCloneManagement">
<img loading="lazy" alt="" src="@/assets/header/voice.png" :style="{
filter:
@@ -38,7 +38,7 @@
</div>
<!-- 超级管理员显示音色克隆下拉菜单 -->
<el-dropdown v-if="isSuperAdmin" trigger="click" class="equipment-management more-dropdown" :class="{
<el-dropdown v-if="isSuperAdmin && featureStatus.voiceClone" trigger="click" class="equipment-management more-dropdown" :class="{
'active-tab':
$route.path === '/voice-clone-management' ||
$route.path === '/voice-resource-management',
@@ -72,7 +72,7 @@
}" />
<span class="nav-text">{{ $t("header.modelConfig") }}</span>
</div>
<div class="equipment-management"
<div v-if="featureStatus.knowledgeBase" class="equipment-management"
:class="{ 'active-tab': $route.path === '/knowledge-base-management' || $route.path === '/knowledge-file-upload' }"
@click="goKnowledgeBaseManagement">
<img loading="lazy" alt="" src="@/assets/header/knowledge_base.png" :style="{
@@ -89,7 +89,8 @@
$route.path === '/server-side-management' ||
$route.path === '/agent-template-management' ||
$route.path === '/ota-management' ||
$route.path === '/user-management',
$route.path === '/user-management' ||
$route.path === '/feature-management',
}" @visible-change="handleParamDropdownVisibleChange">
<span class="el-dropdown-link">
<img loading="lazy" alt="" src="@/assets/header/param_management.png" :style="{
@@ -100,7 +101,8 @@
$route.path === '/server-side-management' ||
$route.path === '/agent-template-management' ||
$route.path === '/ota-management' ||
$route.path === '/user-management'
$route.path === '/user-management' ||
$route.path === '/feature-management'
? 'brightness(0) invert(1)'
: 'None',
}" />
@@ -129,6 +131,9 @@
<el-dropdown-item @click.native="goServerSideManagement">
{{ $t("header.serverSideManagement") }}
</el-dropdown-item>
<el-dropdown-item @click.native="goFeatureManagement">
{{ $t("header.featureManagement") }}
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
@@ -186,6 +191,7 @@ import userApi from "@/apis/module/user";
import i18n, { changeLanguage } from "@/i18n";
import { mapActions, mapGetters } from "vuex";
import ChangePasswordDialog from "./ChangePasswordDialog.vue"; // 引入修改密码弹窗组件
import featureManager from "@/utils/featureManager"; // 引入功能管理工具类
export default {
name: "HeaderBar",
@@ -217,6 +223,11 @@ export default {
label: "label",
children: "children",
},
// 功能状态
featureStatus: {
voiceClone: false, // 音色克隆功能状态
knowledgeBase: false, // 知识库功能状态
},
};
},
computed: {
@@ -286,12 +297,14 @@ export default {
];
},
},
mounted() {
async mounted() {
this.fetchUserInfo();
this.checkScreenSize();
window.addEventListener("resize", this.checkScreenSize);
// 从localStorage加载搜索历史
this.loadSearchHistory();
// 等待featureManager初始化完成后再加载功能状态
await this.loadFeatureStatus();
},
//移除事件监听器
beforeDestroy() {
@@ -338,6 +351,20 @@ export default {
goAgentTemplateManagement() {
this.$router.push("/agent-template-management");
},
// 跳转到功能管理
goFeatureManagement() {
this.$router.push("/feature-management");
},
// 加载功能状态
async loadFeatureStatus() {
// 等待featureManager初始化完成
await featureManager.waitForInitialization();
const config = featureManager.getConfig();
this.featureStatus.voiceClone = config.voiceClone;
this.featureStatus.knowledgeBase = config.knowledgeBase;
},
// 获取用户信息
fetchUserInfo() {
userApi.getUserInfo(({ data }) => {
+31
View File
@@ -22,6 +22,7 @@ export default {
'header.clearHistory': 'Verlauf löschen',
'header.providerManagement': 'Anbieterverwaltung',
'header.serverSideManagement': 'Serververwaltung',
'header.featureManagement': 'Systemfunktionsverwaltung',
'header.changePassword': 'Passwort ändern',
'header.logout': 'Abmelden',
'header.searchPlaceholder': 'Nach Namen suchen..',
@@ -1262,4 +1263,34 @@ export default {
'knowledgeFileUpload.content': 'Inhalt:',
'knowledgeFileUpload.testQuestionRequired': 'Bitte Testfrage eingeben',
'knowledgeBaseDialog.descriptionRequired': 'Bitte Wissensbasis-Beschreibung eingeben',
// Feature Management page text
'featureManagement.selectAll': 'Alle auswählen',
'featureManagement.deselectAll': 'Alle abwählen',
'featureManagement.save': 'Konfiguration speichern',
'featureManagement.reset': 'Zurücksetzen',
'featureManagement.group.featureManagement': 'Funktion/Bereich aktivieren oder deaktivieren',
'featureManagement.group.voiceManagement': 'Für Benutzer bei der Agenten-Konfiguration sichtbar',
'featureManagement.noFeatures': 'Keine Funktionen verfügbar',
'featureManagement.contactAdmin': 'Bitte kontaktieren Sie Administrator zur Funktionskonfiguration',
'featureManagement.saveSuccess': 'Funktionskonfiguration erfolgreich gespeichert',
'featureManagement.resetConfirm': 'Sind Sie sicher, dass Sie alle Funktionskonfigurationen zurücksetzen möchten?',
'featureManagement.confirm': 'Bestätigen',
'featureManagement.cancel': 'Abbrechen',
'featureManagement.resetSuccess': 'Funktionskonfiguration erfolgreich zurückgesetzt',
// Feature names and descriptions
'feature.voiceprintRecognition.name': 'Stimmerkennung',
'feature.voiceprintRecognition.description': 'Benutzeridentität durch Stimmerkennungstechnologie verifizieren, bietet sichere Sprachiinteraktion',
'feature.voiceClone.name': 'Stimmenklon',
'feature.voiceClone.description': 'Spezifische Stimmfarbe mit KI-Technologie klonen, um personalisierte Sprachsynthese zu ermöglichen',
'feature.knowledgeBase.name': 'Wissensbasis',
'feature.knowledgeBase.description': 'Wissensbasissystem aufbauen und verwalten, um professionelle Wissensunterstützung für KI-Assistenten zu bieten',
'feature.mcpAccessPoint.name': 'MCP-Zugangspunkt',
'feature.mcpAccessPoint.description': 'MCP-Protokoll-Zugangspunkte bereitstellen, um Integration externer Tools und Dienste zu unterstützen',
'feature.vad.name': 'Sprachaktivitätserkennung',
'feature.vad.description': 'Sprachaktivität automatisch erkennen, um die Effizienz der Sprachiinteraktion zu optimieren',
'feature.asr.name': 'Spracherkennung',
'feature.asr.description': 'Sprache in Text umwandeln, um natürliche Sprachinteraktionsfunktionalität zu ermöglichen',
}
+31
View File
@@ -22,6 +22,7 @@ export default {
'header.clearHistory': 'Clear History',
'header.providerManagement': 'Provider Management',
'header.serverSideManagement': 'Server Management',
'header.featureManagement': 'System Feature Management',
'header.changePassword': 'Change Password',
'header.logout': 'Logout',
'header.searchPlaceholder': 'Search by name..',
@@ -1262,4 +1263,34 @@ export default {
'knowledgeFileUpload.content': 'Content:',
'knowledgeFileUpload.testQuestionRequired': 'Please enter test question',
'knowledgeBaseDialog.descriptionRequired': 'Please enter knowledge base description',
// Feature Management page text
'featureManagement.selectAll': 'Select All',
'featureManagement.deselectAll': 'Deselect All',
'featureManagement.save': 'Save Configuration',
'featureManagement.reset': 'Reset',
'featureManagement.group.featureManagement': 'Enable/Disable the feature/section',
'featureManagement.group.voiceManagement': 'Visible to users during agent configuration',
'featureManagement.noFeatures': 'No features available',
'featureManagement.contactAdmin': 'Please contact administrator to configure features',
'featureManagement.saveSuccess': 'Feature configuration saved successfully',
'featureManagement.resetConfirm': 'Are you sure you want to reset all feature configurations?',
'featureManagement.confirm': 'Confirm',
'featureManagement.cancel': 'Cancel',
'featureManagement.resetSuccess': 'Feature configuration reset successfully',
// Feature names and descriptions
'feature.voiceprintRecognition.name': 'Voiceprint Recognition',
'feature.voiceprintRecognition.description': 'Verify user identity through voiceprint recognition technology, providing secure voice interaction experience',
'feature.voiceClone.name': 'Voice Clone',
'feature.voiceClone.description': 'Clone specific voice timbre using AI technology to achieve personalized voice synthesis',
'feature.knowledgeBase.name': 'Knowledge Base',
'feature.knowledgeBase.description': 'Build and manage knowledge base system to provide professional knowledge support for AI assistants',
'feature.mcpAccessPoint.name': 'MCP Access Point',
'feature.mcpAccessPoint.description': 'Provide MCP protocol access points to support integration of external tools and services',
'feature.vad.name': 'Voice Activity Detection',
'feature.vad.description': 'Automatically detect voice activity to optimize voice interaction response efficiency',
'feature.asr.name': 'Speech Recognition',
'feature.asr.description': 'Convert speech to text to enable natural language interaction functionality',
}
+31
View File
@@ -22,6 +22,7 @@ export default {
'header.clearHistory': 'Xóa lịch sử',
'header.providerManagement': 'Quản lý nhà cung cấp',
'header.serverSideManagement': 'Quản lý máy chủ',
'header.featureManagement': 'Cấu hình chức năng hệ thống',
'header.changePassword': 'Đổi mật khẩu',
'header.logout': 'Đăng xuất',
'header.searchPlaceholder': 'Tìm kiếm theo tên..',
@@ -1262,4 +1263,34 @@ export default {
'knowledgeFileUpload.content': 'Nội dung:',
'knowledgeFileUpload.testQuestionRequired': 'Vui lòng nhập câu hỏi kiểm tra',
'knowledgeBaseDialog.descriptionRequired': 'Vui lòng nhập mô tả cơ sở kiến thức',
// Feature Management page text
'featureManagement.selectAll': 'Chọn tất cả',
'featureManagement.deselectAll': 'Bỏ chọn tất cả',
'featureManagement.save': 'Lưu cấu hình',
'featureManagement.reset': 'Đặt lại',
'featureManagement.group.featureManagement': 'Có bật tính năng/khối này lên hay không',
'featureManagement.group.voiceManagement': 'Khi cấu hình agent, có hiển thị với người dùng hay không',
'featureManagement.noFeatures': 'Chưa có tính năng nào',
'featureManagement.contactAdmin': 'Vui lòng liên hệ quản trị viên để cấu hình tính năng',
'featureManagement.saveSuccess': 'Cấu hình tính năng đã được lưu thành công',
'featureManagement.resetConfirm': 'Bạn có chắc chắn muốn đặt lại tất cả cấu hình tính năng?',
'featureManagement.confirm': 'Xác nhận',
'featureManagement.cancel': 'Hủy bỏ',
'featureManagement.resetSuccess': 'Cấu hình tính năng đã được đặt lại thành công',
// Feature names and descriptions
'feature.voiceprintRecognition.name': 'Nhận dạng giọng nói',
'feature.voiceprintRecognition.description': 'Xác minh danh tính người dùng thông qua công nghệ nhận dạng giọng nói, cung cấp trải nghiệm tương tác giọng nói an toàn',
'feature.voiceClone.name': 'Nhân bản âm sắc',
'feature.voiceClone.description': 'Sử dụng công nghệ AI để nhân bản âm sắc cụ thể, đạt được tổng hợp giọng nói cá nhân hóa',
'feature.knowledgeBase.name': 'Cơ sở kiến thức',
'feature.knowledgeBase.description': 'Xây dựng và quản lý hệ thống cơ sở kiến thức, cung cấp hỗ trợ kiến thức chuyên môn cho trợ lý AI',
'feature.mcpAccessPoint.name': 'Điểm truy cập MCP',
'feature.mcpAccessPoint.description': 'Cung cấp điểm truy cập giao thức MCP, hỗ trợ tích hợp các công cụ và dịch vụ bên ngoài',
'feature.vad.name': 'Phát hiện hoạt động giọng nói',
'feature.vad.description': 'Tự động phát hiện hoạt động giọng nói, tối ưu hóa hiệu suất phản hồi tương tác giọng nói',
'feature.asr.name': 'Nhận dạng giọng nói',
'feature.asr.description': 'Chuyển đổi giọng nói thành văn bản, thực hiện chức năng tương tác ngôn ngữ tự nhiên',
}
+31
View File
@@ -22,6 +22,7 @@ export default {
'header.clearHistory': '清空历史',
'header.providerManagement': '字段管理',
'header.serverSideManagement': '服务端管理',
'header.featureManagement': '系统功能配置',
'header.changePassword': '修改密码',
'header.logout': '退出登录',
'header.searchPlaceholder': '输入名称搜索..',
@@ -1262,4 +1263,34 @@ export default {
'knowledgeFileUpload.content': '内容:',
'knowledgeFileUpload.testQuestionRequired': '请输入测试问题',
'knowledgeBaseDialog.descriptionRequired': '请输入知识库描述',
// 系统功能配置页面文本
'featureManagement.selectAll': '全选',
'featureManagement.deselectAll': '取消全选',
'featureManagement.save': '保存配置',
'featureManagement.reset': '重置',
'featureManagement.group.featureManagement': '是否开启功能/板块',
'featureManagement.group.voiceManagement': '配置智能体时是否对用户可见',
'featureManagement.noFeatures': '暂无功能',
'featureManagement.contactAdmin': '请联系管理员配置功能',
'featureManagement.saveSuccess': '功能配置保存成功',
'featureManagement.resetConfirm': '确定要重置所有功能配置吗?',
'featureManagement.confirm': '确定',
'featureManagement.cancel': '取消',
'featureManagement.resetSuccess': '功能配置重置成功',
// 功能名称和描述
'feature.voiceprintRecognition.name': '声纹识别',
'feature.voiceprintRecognition.description': '通过声纹识别技术验证用户身份,提供安全的语音交互体验',
'feature.voiceClone.name': '音色克隆',
'feature.voiceClone.description': '使用AI技术克隆特定音色,实现个性化语音合成',
'feature.knowledgeBase.name': '知识库',
'feature.knowledgeBase.description': '构建和管理知识库系统,为AI助手提供专业知识支持',
'feature.mcpAccessPoint.name': 'MCP接入点',
'feature.mcpAccessPoint.description': '提供MCP协议接入点,支持外部工具和服务的集成',
'feature.vad.name': '语音活动检测',
'feature.vad.description': '自动检测语音活动,优化语音交互的响应效率',
'feature.asr.name': '语音识别',
'feature.asr.description': '将语音转换为文本,实现自然语言交互功能',
}
+31
View File
@@ -22,6 +22,7 @@ export default {
'header.clearHistory': '清空歷史',
'header.providerManagement': '字段管理',
'header.serverSideManagement': '服務端管理',
'header.featureManagement': '系統功能配置',
'header.changePassword': '修改密碼',
'header.logout': '退出登錄',
'header.searchPlaceholder': '輸入名稱搜索..',
@@ -1262,4 +1263,34 @@ export default {
'knowledgeFileUpload.content': '內容:',
'knowledgeFileUpload.testQuestionRequired': '請輸入測試問題',
'knowledgeBaseDialog.descriptionRequired': '請輸入知识库描述',
// 功能管理頁面文本
'featureManagement.selectAll': '全選',
'featureManagement.deselectAll': '取消全選',
'featureManagement.save': '儲存配置',
'featureManagement.reset': '重置',
'featureManagement.group.featureManagement': '是否開啟功能/板块',
'featureManagement.group.voiceManagement': '配置智能体時是否對用戶可見',
'featureManagement.noFeatures': '暫無功能',
'featureManagement.contactAdmin': '請聯繫管理員配置功能',
'featureManagement.saveSuccess': '功能配置儲存成功',
'featureManagement.resetConfirm': '確定要重置所有功能配置嗎?',
'featureManagement.confirm': '確定',
'featureManagement.cancel': '取消',
'featureManagement.resetSuccess': '功能配置重置成功',
// 功能名稱和描述
'feature.voiceprintRecognition.name': '聲紋識別',
'feature.voiceprintRecognition.description': '通過聲紋識別技術驗證用戶身份,提供安全的語音交互體驗',
'feature.voiceClone.name': '音色複刻',
'feature.voiceClone.description': '使用AI技術複刻特定音色,實現個性化語音合成',
'feature.knowledgeBase.name': '知識庫',
'feature.knowledgeBase.description': '構建和管理知識庫系統,為AI助手提供專業知識支持',
'feature.mcpAccessPoint.name': 'MCP接入點',
'feature.mcpAccessPoint.description': '提供MCP協議接入點,支持外部工具和服務的整合',
'feature.vad.name': '語音活動檢測',
'feature.vad.description': '自動檢測語音活動,優化語音交互的響應效率',
'feature.asr.name': '語音識別',
'feature.asr.description': '將語音轉換為文本,實現自然語言交互功能',
}
+1
View File
@@ -8,6 +8,7 @@ import store from './store';
import i18n from './i18n';
import './styles/global.scss';
import { register as registerServiceWorker } from './registerServiceWorker';
import featureManager from './utils/featureManager';
// 创建事件总线,用于组件间通信
Vue.prototype.$eventBus = new Vue();
+18 -3
View File
@@ -76,7 +76,8 @@ const routes = [
return import('../views/ModelConfig.vue')
}
},
{ path: '/params-management',
{
path: '/params-management',
name: 'ParamsManagement',
component: function () {
return import('../views/ParamsManagement.vue')
@@ -86,7 +87,8 @@ const routes = [
title: '参数管理'
}
},
{ path: '/knowledge-base-management',
{
path: '/knowledge-base-management',
name: 'KnowledgeBaseManagement',
component: function () {
return import('../views/KnowledgeBaseManagement.vue')
@@ -96,7 +98,8 @@ const routes = [
title: '知识库管理'
}
},
{ path: '/knowledge-file-upload',
{
path: '/knowledge-file-upload',
name: 'KnowledgeFileUpload',
component: function () {
return import('../views/KnowledgeFileUpload.vue')
@@ -181,6 +184,18 @@ const routes = [
return import('../views/TemplateQuickConfig.vue')
}
},
// 功能配置页面路由
{
path: '/feature-management',
name: 'FeatureManagement',
component: function () {
return import('../views/FeatureManagement.vue')
},
meta: {
requiresAuth: true,
title: '功能配置'
}
},
]
const router = new VueRouter({
base: process.env.VUE_APP_PUBLIC_PATH || '/',
@@ -0,0 +1,340 @@
//功能配置工具
import Api from "@/apis/api";
class FeatureManager {
constructor() {
this.defaultFeatures = {
voiceprintRecognition: {
name: 'feature.voiceprintRecognition.name',
enabled: false,
description: 'feature.voiceprintRecognition.description'
},
voiceClone: {
name: 'feature.voiceClone.name',
enabled: false,
description: 'feature.voiceClone.description'
},
knowledgeBase: {
name: 'feature.knowledgeBase.name',
enabled: false,
description: 'feature.knowledgeBase.description'
},
mcpAccessPoint: {
name: 'feature.mcpAccessPoint.name',
enabled: false,
description: 'feature.mcpAccessPoint.description'
},
vad: {
name: 'feature.vad.name',
enabled: false,
description: 'feature.vad.description'
},
asr: {
name: 'feature.asr.name',
enabled: false,
description: 'feature.asr.description'
}
};
this.currentFeatures = { ...this.defaultFeatures }; // 当前内存中的配置
this.initialized = false;
this.initPromise = null;
}
/**
* 等待初始化完成
*/
async waitForInitialization() {
if (!this.initPromise) {
this.initPromise = this.init();
}
await this.initPromise;
return this.initialized;
}
/**
* 初始化功能配置
*/
async init() {
try {
// 从pub-config接口获取配置
const config = await this.getConfigFromPubConfig();
if (config) {
this.currentFeatures = { ...config }; // 保存到内存
this.initialized = true;
return;
}
} catch (error) {
console.warn('从pub-config接口获取配置失败:', error);
}
// pub-config接口失败,使用默认配置
this.currentFeatures = { ...this.defaultFeatures }; // 保存默认配置到内存
this.initialized = true;
}
/**
* 从pub-config接口获取配置
*/
async getConfigFromPubConfig() {
return new Promise((resolve) => {
// 直接调用pub-config接口获取配置
Api.user.getPubConfig((result) => {
// 检查返回结果的结构
if (result && result.status === 200) {
// 检查是否有data字段
if (result.data) {
// 检查是否有code字段,如果有则按照code判断
if (result.data.code !== undefined) {
if (result.data.code === 0 && result.data.data && result.data.data.systemWebMenu) {
try {
let config;
if (typeof result.data.data.systemWebMenu === 'string') {
// 如果是字符串,需要解析JSON
config = JSON.parse(result.data.data.systemWebMenu);
} else {
// 如果已经是对象,直接使用
config = result.data.data.systemWebMenu;
}
// 检查配置中是否包含features对象
if (config && config.features) {
// 确保knowledgeBase功能存在且配置正确
if (!config.features.knowledgeBase) {
console.warn('配置中缺少knowledgeBase功能,合并默认配置');
config.features = { ...this.defaultFeatures, ...config.features };
}
resolve(config.features);
} else {
console.warn('配置中缺少features对象,使用默认配置');
resolve(this.defaultFeatures);
}
} catch (error) {
console.warn('处理systemWebMenu配置失败:', error);
resolve(null);
}
} else {
console.warn('接口返回code不为0或缺少必要数据,使用默认配置');
resolve(null);
}
} else {
// 如果没有code字段,直接检查systemWebMenu
if (result.data && result.data.systemWebMenu) {
try {
let config;
if (typeof result.data.systemWebMenu === 'string') {
// 如果是字符串,需要解析JSON
config = JSON.parse(result.data.systemWebMenu);
} else {
// 如果已经是对象,直接使用
config = result.data.systemWebMenu;
}
// 检查配置中是否包含features对象
if (config && config.features) {
// 确保knowledgeBase功能存在且配置正确
if (!config.features.knowledgeBase) {
console.warn('配置中缺少knowledgeBase功能,合并默认配置');
config.features = { ...this.defaultFeatures, ...config.features };
}
resolve(config.features);
} else {
console.warn('配置中缺少features对象,使用默认配置');
resolve(this.defaultFeatures);
}
} catch (error) {
console.warn('处理systemWebMenu配置失败:', error);
resolve(null);
}
} else {
console.warn('接口返回缺少systemWebMenu数据,使用默认配置');
resolve(null);
}
}
} else {
console.warn('接口返回数据中缺少data字段,使用默认配置');
resolve(null);
}
} else {
console.warn('pub-config接口调用失败,使用默认配置');
resolve(null);
}
});
});
}
/**
* 获取当前配置
*/
getCurrentConfig() {
// 返回内存中的当前配置
return this.currentFeatures;
}
/**
* 保存配置到后端API
*/
async saveConfig(config) {
try {
// 更新内存中的配置
this.currentFeatures = { ...config };
// 异步保存到后端API
this.saveConfigToAPI(config).catch(error => {
console.warn('保存配置到API失败:', error);
});
// 触发配置变更事件
window.dispatchEvent(new CustomEvent('featureConfigChanged', {
detail: config
}));
} catch (error) {
console.error('保存功能配置失败:', error);
}
}
/**
* 保存配置到后端API
*/
async saveConfigToAPI(config) {
return new Promise((resolve) => {
// 直接使用已知的ID(600)更新参数
Api.admin.updateParam(
{
id: 600,
paramCode: 'system-web.menu',
paramValue: JSON.stringify({
features: config,
groups: {
featureManagement: ["voiceprintRecognition", "voiceClone", "knowledgeBase", "mcpAccessPoint"],
voiceManagement: ["vad", "asr"]
}
}),
valueType: 'json',
remark: '系统功能菜单配置'
},
(updateResult) => {
if (updateResult.code === 0) {
resolve();
} else {
// 如果更新失败,可能是参数不存在或其他错误,记录但不阻止保存到localStorage
console.warn('更新参数失败:', updateResult.msg);
resolve(); // 不阻止保存到localStorage
}
},
(error) => {
console.warn('更新参数失败:', error);
resolve(); // 不阻止保存到localStorage
}
);
});
}
/**
* 获取所有功能配置
*/
getAllFeatures() {
return this.getCurrentConfig();
}
/**
* 获取简化的配置对象(用于首页组件)
*/
getConfig() {
const features = this.getAllFeatures();
return {
voiceprintRecognition: features.voiceprintRecognition?.enabled || false,
voiceClone: features.voiceClone?.enabled || false,
knowledgeBase: features.knowledgeBase?.enabled || false,
mcpAccessPoint: features.mcpAccessPoint?.enabled || false,
vad: features.vad?.enabled || false,
asr: features.asr?.enabled || false
};
}
/**
* 获取指定功能的状态
*/
getFeatureStatus(featureKey) {
const features = this.getAllFeatures();
return features[featureKey]?.enabled || false;
}
/**
* 设置功能状态
*/
setFeatureStatus(featureKey, enabled) {
const features = this.getAllFeatures();
if (features[featureKey]) {
features[featureKey].enabled = enabled;
this.saveConfig(features);
return true;
}
return false;
}
/**
* 启用功能
*/
enableFeature(featureKey) {
return this.setFeatureStatus(featureKey, true);
}
/**
* 禁用功能
*/
disableFeature(featureKey) {
return this.setFeatureStatus(featureKey, false);
}
/**
* 切换功能状态
*/
toggleFeature(featureKey) {
const currentStatus = this.getFeatureStatus(featureKey);
return this.setFeatureStatus(featureKey, !currentStatus);
}
/**
* 重置所有功能为默认状态
*/
resetToDefault() {
this.saveConfig(this.defaultFeatures);
}
/**
* 批量更新功能状态
*/
updateFeatures(featureUpdates) {
const features = this.getAllFeatures();
Object.keys(featureUpdates).forEach(featureKey => {
if (features[featureKey]) {
features[featureKey].enabled = featureUpdates[featureKey];
}
});
this.saveConfig(features);
}
/**
* 获取已启用的功能列表
*/
getEnabledFeatures() {
const features = this.getAllFeatures();
return Object.keys(features).filter(key => features[key].enabled);
}
/**
* 检查功能是否启用
*/
isFeatureEnabled(featureKey) {
return this.getFeatureStatus(featureKey);
}
}
// 创建单例实例
const featureManager = new FeatureManager();
export default featureManager;
@@ -0,0 +1,592 @@
<template>
<div class="welcome">
<HeaderBar />
<div class="operation-bar">
<h2 class="page-title">{{ $t('header.featureManagement') }}</h2>
</div>
<div class="main-wrapper">
<div class="content-panel">
<div class="content-area">
<el-card class="feature-card" shadow="never">
<div class="config-header">
<div class="header-icon">
<img loading="lazy" src="@/assets/home/equipment.png" alt="" />
</div>
<div class="header-actions">
<el-button @click="!isSaving && toggleSelectAll()" class="btn-select-all" :disabled="isSaving">
{{ isAllSelected ? $t('featureManagement.deselectAll') : $t('featureManagement.selectAll') }}
</el-button>
<el-button type="primary" class="save-btn" @click="handleSave" :disabled="isSaving">
{{ isSaving ? $t('featureManagement.saving') : $t('featureManagement.save') }}
</el-button>
<el-button class="reset-btn" @click="handleReset" :disabled="isSaving">
{{ $t('featureManagement.reset') }}
</el-button>
</div>
</div>
<div class="divider"></div>
<!-- 功能分组容器 - 左右布局 -->
<div class="feature-groups-container">
<!-- 功能管理分组 -->
<div v-if="featureManagementFeatures.length > 0" class="feature-group">
<h3 class="group-title">{{ $t('featureManagement.group.featureManagement') }}</h3>
<div class="features-grid">
<div
v-for="feature in featureManagementFeatures"
:key="feature.id"
class="feature-card-item"
:class="{ 'feature-enabled': feature.enabled, 'feature-disabled': isSaving }"
@click="!isSaving && toggleFeature(feature)"
>
<div class="feature-header">
<h3 class="feature-name">{{ $t(`feature.${feature.id}.name`) }}</h3>
<el-checkbox
v-model="feature.enabled"
@change="!isSaving && toggleFeature(feature)"
class="feature-checkbox"
:disabled="isSaving"
/>
</div>
<p class="feature-description">{{ $t(`feature.${feature.id}.description`) }}</p>
</div>
</div>
</div>
<!-- 语音管理分组 -->
<div v-if="voiceManagementFeatures.length > 0" class="feature-group">
<h3 class="group-title">{{ $t('featureManagement.group.voiceManagement') }}</h3>
<div class="features-grid">
<div
v-for="feature in voiceManagementFeatures"
:key="feature.id"
class="feature-card-item"
:class="{ 'feature-enabled': feature.enabled, 'feature-disabled': isSaving }"
@click="!isSaving && toggleFeature(feature)"
>
<div class="feature-header">
<h3 class="feature-name">{{ $t(`feature.${feature.id}.name`) }}</h3>
<el-checkbox
v-model="feature.enabled"
@change="!isSaving && toggleFeature(feature)"
class="feature-checkbox"
:disabled="isSaving"
/>
</div>
<p class="feature-description">{{ $t(`feature.${feature.id}.description`) }}</p>
</div>
</div>
</div>
</div>
<div v-if="filteredFeatures.length === 0" class="empty-state">
<el-empty :description="$t('featureManagement.noFeatures')">
<p class="empty-tip">{{ $t('featureManagement.contactAdmin') }}</p>
</el-empty>
</div>
</el-card>
</div>
</div>
</div>
<el-footer>
<VersionFooter />
</el-footer>
</div>
</template>
<script>
import HeaderBar from "@/components/HeaderBar.vue";
import VersionFooter from "@/components/VersionFooter.vue";
import featureManager from "@/utils/featureManager.js";
export default {
name: "FeatureManagement",
components: {
HeaderBar,
VersionFooter
},
data() {
return {
pendingChanges: false,
featureManagementFeatures: [],
voiceManagementFeatures: [],
isSaving: false // 添加保存状态锁定
}
},
computed: {
// 所有功能列表
filteredFeatures() {
return [...this.featureManagementFeatures, ...this.voiceManagementFeatures]
},
// 判断是否所有功能都已选中
isAllSelected() {
const allFeatures = [...this.featureManagementFeatures, ...this.voiceManagementFeatures]
return allFeatures.length > 0 && allFeatures.every(feature => feature.enabled)
}
},
async created() {
// 等待功能配置管理器初始化完成
try {
console.log('等待功能配置管理器初始化...')
await featureManager.waitForInitialization()
console.log('功能配置管理器初始化完成,开始加载功能配置')
await this.loadFeatures()
this.setupConfigChangeListener()
} catch (error) {
console.error('功能配置管理器初始化等待失败:', error)
await this.loadFeatures()
this.setupConfigChangeListener()
}
},
beforeDestroy() {
this.removeConfigChangeListener()
},
methods: {
// 根据ID列表获取功能
async getFeaturesByIds(featureIds) {
try {
const featureConfig = await featureManager.getAllFeatures()
console.log('获取到的功能配置:', JSON.stringify(featureConfig, null, 2))
console.log('请求的功能ID列表:', featureIds)
const result = featureIds.map(id => {
const feature = featureConfig[id]
console.log(`功能 ${id} 的配置:`, feature)
console.log(`功能 ${id} 的启用状态:`, feature?.enabled)
return {
id: id,
name: this.$t(`feature.${id}.name`),
description: this.$t(`feature.${id}.description`),
enabled: feature?.enabled || false
}
})
console.log('最终返回的功能列表:', JSON.stringify(result, null, 2))
return result
} catch (error) {
console.error('获取功能配置失败:', error)
// 如果获取失败,返回默认配置
return featureIds.map(id => ({
id: id,
name: this.$t(`feature.${id}.name`),
description: this.$t(`feature.${id}.description`),
enabled: false
}))
}
},
// 加载功能配置
async loadFeatures() {
// 保存当前用户的选择状态
const currentFeatureStates = {}
const allCurrentFeatures = [...this.featureManagementFeatures, ...this.voiceManagementFeatures]
allCurrentFeatures.forEach(feature => {
currentFeatureStates[feature.id] = feature.enabled
})
// 重新加载配置
this.featureManagementFeatures = await this.getFeaturesByIds(['voiceprintRecognition', 'voiceClone', 'knowledgeBase', 'mcpAccessPoint'])
this.voiceManagementFeatures = await this.getFeaturesByIds(['vad', 'asr'])
// 恢复用户的选择状态(如果存在)
const allFeatures = [...this.featureManagementFeatures, ...this.voiceManagementFeatures]
allFeatures.forEach(feature => {
if (currentFeatureStates.hasOwnProperty(feature.id)) {
feature.enabled = currentFeatureStates[feature.id]
}
})
},
// 切换功能状态
async toggleFeature(feature) {
// 如果正在保存,阻止操作
if (this.isSaving) {
return
}
feature.enabled = !feature.enabled
this.pendingChanges = true
// 不再立即更新到配置管理器,只在保存时统一更新
},
// 保存配置
async handleSave() {
if (!this.pendingChanges) {
this.$message.info({
message: this.$t('featureManagement.noChanges'),
showClose: true
})
return
}
// 设置保存状态,锁定界面
this.isSaving = true
try {
// 获取当前所有功能的状态并保存
const featureUpdates = {}
const allFeatures = [...this.featureManagementFeatures, ...this.voiceManagementFeatures]
allFeatures.forEach(feature => {
featureUpdates[feature.id] = feature.enabled
})
await featureManager.updateFeatures(featureUpdates)
this.pendingChanges = false
this.$message.success({
message: this.$t('featureManagement.saveSuccess'),
showClose: true
})
setTimeout(() => {
this.loadFeatures()
this.$router.go(0)
}, 1000)
} catch (error) {
console.error('保存配置失败:', error)
this.$message.error({
message: this.$t('featureManagement.saveError'),
showClose: true
})
} finally {
// 无论成功与否,都解除保存状态锁定
this.isSaving = false
}
},
// 设置配置变化监听器
setupConfigChangeListener() {
this.configChangeHandler = () => {
console.log('检测到配置变化,重新加载功能列表')
this.loadFeatures()
}
window.addEventListener('featureConfigReloaded', this.configChangeHandler)
},
// 移除配置变化监听器
removeConfigChangeListener() {
if (this.configChangeHandler) {
window.removeEventListener('featureConfigReloaded', this.configChangeHandler)
}
},
// 重置配置
async handleReset() {
try {
await this.$confirm(
this.$t('featureManagement.resetConfirm'),
this.$t('featureManagement.reset'),
{
confirmButtonText: this.$t('featureManagement.confirm'),
cancelButtonText: this.$t('featureManagement.cancel'),
type: 'warning'
}
)
featureManager.resetToDefault()
this.loadFeatures()
this.pendingChanges = false
this.$message.success({
message: this.$t('featureManagement.resetSuccess'),
showClose: true
})
setTimeout(() => {
this.loadFeatures()
this.$router.go(0)
}, 1000)
} catch (error) {
// 用户取消操作
}
},
// 搜索功能(预留接口)
handleSearch() {
// 搜索功能待实现
},
// 全选/取消全选
toggleSelectAll() {
// 如果正在保存,阻止操作
if (this.isSaving) {
return
}
const allFeatures = [...this.featureManagementFeatures, ...this.voiceManagementFeatures]
const newStatus = !this.isAllSelected
allFeatures.forEach(feature => {
feature.enabled = newStatus
})
this.pendingChanges = true
}
}
}
</script>
<style scoped>
.welcome {
min-width: 900px;
min-height: 506px;
height: 100vh;
display: flex;
position: relative;
flex-direction: column;
background-size: cover;
background: linear-gradient(to bottom right, #dce8ff, #e4eeff, #e6cbfd) center;
-webkit-background-size: cover;
-o-background-size: cover;
overflow: hidden;
}
.operation-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 24px;
}
.page-title {
font-size: 24px;
margin: 0;
}
.config-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 0 16px 0;
}
.header-icon {
width: 40px;
height: 40px;
background: #5778ff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 12px;
}
.header-icon img {
width: 20px;
height: 20px;
}
.header-actions {
display: flex;
align-items: center;
gap: 8px;
margin-left: auto;
}
.divider {
height: 1px;
background: #e0e0e0;
margin-bottom: 20px;
}
.btn-select-all {
background: #e6ebff;
color: #5778ff;
border: 1px solid #adbdff;
border-radius: 18px;
padding: 8px 16px;
height: 32px;
font-size: 14px;
}
.btn-select-all:hover {
background: #d0d8ff;
}
.save-btn {
background: #5778ff;
color: white;
border: none;
border-radius: 18px;
padding: 8px 16px;
height: 32px;
font-size: 14px;
}
.save-btn:hover {
background: #4a6ae8;
}
.reset-btn {
background: #e6ebff;
color: #5778ff;
border: 1px solid #adbdff;
border-radius: 18px;
padding: 8px 16px;
height: 32px;
}
.reset-btn:hover {
background: #d0d8ff;
}
.main-wrapper {
margin: 0 22px 5px 22px;
border-radius: 15px;
min-height: calc(100vh - 24vh);
height: auto;
max-height: 80vh;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
position: relative;
background: rgba(237, 242, 255, 0.5);
display: flex;
flex-direction: column;
}
.content-panel {
flex: 1;
display: flex;
overflow: hidden;
height: 100%;
border-radius: 15px;
background: transparent;
border: 1px solid #fff;
}
.content-area {
flex: 1;
height: 100%;
min-width: 600px;
overflow: auto;
background-color: white;
display: flex;
flex-direction: column;
}
.feature-card {
background: white;
flex: 1;
display: flex;
flex-direction: column;
border: none;
box-shadow: none;
overflow: hidden;
}
.feature-card ::v-deep .el-card__body {
padding: 24px;
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 16px;
}
.feature-card-item {
display: flex;
flex-direction: column;
padding: 20px;
border-radius: 12px;
border: 2px solid #e0e0e0;
background-color: white;
cursor: pointer;
transition: all 0.3s ease;
user-select: none;
position: relative;
}
.feature-card-item:hover {
border-color: #9cc6ef;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
transform: translateY(-2px);
}
.feature-card-item.feature-enabled {
border-color: #409eff;
box-shadow: 0 4px 16px rgba(95, 112, 243, 0.2);
transform: translateY(-2px);
}
.feature-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12px;
}
.feature-checkbox ::v-deep .el-checkbox__input {
transform: scale(1.2);
}
.feature-name {
font-size: 18px;
font-weight: 600;
color: #333;
margin: 0;
transition: color 0.3s ease;
}
.feature-description {
font-size: 14px;
line-height: 1.6;
color: #666;
margin: 0 0 12px 0;
transition: color 0.3s ease;
text-align: left;
}
/* 功能分组容器 - 左右布局 */
.feature-groups-container {
display: flex;
gap: 32px;
align-items: flex-start;
position: relative;
}
/* 分组之间的分隔线 */
.feature-groups-container::before {
content: '';
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: 1px;
height: 550px;
background: #e0e0e0;
opacity: 0.5;
transform: translateX(-50%);
}
/* 分组样式 */
.feature-group {
flex: 1;
min-width: 0;
margin-bottom: 32px;
}
.group-title {
font-size: 18px;
font-weight: 600;
color: #303133;
margin-bottom: 12px;
padding-left: 12px;
border-left: 4px solid #5f70f3;
text-align: left;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 16px;
}
</style>
+24 -4
View File
@@ -39,8 +39,9 @@
</template>
<template v-else>
<DeviceItem v-for="(item, index) in devices" :key="index" :device="item" @configure="goToRoleConfig"
@deviceManage="handleDeviceManage" @delete="handleDeleteAgent" @chat-history="handleShowChatHistory" />
<DeviceItem v-for="(item, index) in devices" :key="index" :device="item" :feature-status="featureStatus"
@configure="goToRoleConfig" @deviceManage="handleDeviceManage" @delete="handleDeleteAgent"
@chat-history="handleShowChatHistory" />
</template>
</div>
</div>
@@ -61,6 +62,7 @@ import ChatHistoryDialog from '@/components/ChatHistoryDialog.vue';
import DeviceItem from '@/components/DeviceItem.vue';
import HeaderBar from '@/components/HeaderBar.vue';
import VersionFooter from '@/components/VersionFooter.vue';
import featureManager from '@/utils/featureManager';
export default {
name: 'HomePage',
@@ -76,15 +78,33 @@ export default {
skeletonCount: localStorage.getItem('skeletonCount') || 8,
showChatHistory: false,
currentAgentId: '',
currentAgentName: ''
currentAgentName: '',
// 功能状态
featureStatus: {
voiceprintRecognition: false,
voiceClone: false,
knowledgeBase: false
}
}
},
mounted() {
async mounted() {
this.fetchAgentList();
await this.loadFeatureStatus();
},
methods: {
// 加载功能状态
async loadFeatureStatus() {
await featureManager.waitForInitialization();
const config = featureManager.getConfig();
this.featureStatus = {
voiceprintRecognition: config.voiceprintRecognition,
voiceClone: config.voiceClone,
knowledgeBase: config.knowledgeBase
};
},
showAddDialog() {
this.addDeviceDialogVisible = true
},
+8
View File
@@ -156,6 +156,7 @@ import VersionFooter from "@/components/VersionFooter.vue";
import i18n, { changeLanguage } from "@/i18n";
import { getUUID, goToPage, showDanger, showSuccess, sm2Encrypt, validateMobile } from "@/utils";
import { mapState } from "vuex";
import featureManager from "@/utils/featureManager";
export default {
name: "login",
@@ -214,6 +215,13 @@ export default {
this.$store.dispatch("fetchPubConfig").then(() => {
// 根据配置决定默认登录方式
this.isMobileLogin = this.enableMobileRegister;
// pub-config接口调用完成后,重新初始化featureManager以确保使用最新的配置
featureManager.waitForInitialization().then(() => {
console.log('featureManager重新初始化完成,使用pub-config配置');
}).catch(error => {
console.warn('featureManager重新初始化失败:', error);
});
});
},
methods: {
+32 -3
View File
@@ -107,7 +107,11 @@
</div>
<div class="form-column">
<div class="model-row">
<el-form-item :label="$t('roleConfig.vad')" class="model-item">
<el-form-item
v-if="featureStatus.vad"
:label="$t('roleConfig.vad')"
class="model-item"
>
<div class="model-select-wrapper">
<el-select
v-model="form.model.vadModelId"
@@ -125,7 +129,11 @@
</el-select>
</div>
</el-form-item>
<el-form-item :label="$t('roleConfig.asr')" class="model-item">
<el-form-item
v-if="featureStatus.asr"
:label="$t('roleConfig.asr')"
class="model-item"
>
<div class="model-select-wrapper">
<el-select
v-model="form.model.asrModelId"
@@ -277,6 +285,7 @@ import RequestService from "@/apis/httpRequest";
import FunctionDialog from "@/components/FunctionDialog.vue";
import HeaderBar from "@/components/HeaderBar.vue";
import i18n from "@/i18n";
import featureManager from "@/utils/featureManager";
export default {
name: "RoleConfigPage",
@@ -326,6 +335,11 @@ export default {
isPaused: false,
currentAudio: null,
currentPlayingVoiceId: null,
// 功能状态
featureStatus: {
vad: false, // 语言检测活动功能状态
asr: false, // 语音识别功能状态
},
};
},
methods: {
@@ -980,6 +994,19 @@ export default {
this.form.chatHistoryConf = 0;
}
},
// 加载功能状态
async loadFeatureStatus() {
try {
// 确保featureManager已初始化完成
await featureManager.waitForInitialization();
const config = featureManager.getConfig();
this.featureStatus.voiceprintRecognition = config.voiceprintRecognition || false;
this.featureStatus.vad = config.vad || false;
this.featureStatus.asr = config.asr || false;
} catch (error) {
console.error("加载功能状态失败:", error);
}
},
},
watch: {
"form.model.ttsModelId": {
@@ -1002,7 +1029,7 @@ export default {
immediate: true,
},
},
mounted() {
async mounted() {
const agentId = this.$route.query.agentId;
if (agentId) {
this.fetchAgentConfig(agentId);
@@ -1010,6 +1037,8 @@ export default {
}
this.fetchModelOptions();
this.fetchTemplates();
// 加载功能状态,确保featureManager已初始化
await this.loadFeatureStatus();
},
};
</script>