mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-25 08:33:53 +08:00
+17
-12
@@ -41,6 +41,7 @@ import xiaozhi.modules.sys.service.SysDictDataService;
|
|||||||
import xiaozhi.modules.sys.service.SysParamsService;
|
import xiaozhi.modules.sys.service.SysParamsService;
|
||||||
import xiaozhi.modules.sys.service.SysUserService;
|
import xiaozhi.modules.sys.service.SysUserService;
|
||||||
import xiaozhi.modules.sys.vo.SysDictDataItem;
|
import xiaozhi.modules.sys.vo.SysDictDataItem;
|
||||||
|
import xiaozhi.common.utils.JsonUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 登录控制层
|
* 登录控制层
|
||||||
@@ -89,13 +90,13 @@ public class LoginController {
|
|||||||
@Operation(summary = "登录")
|
@Operation(summary = "登录")
|
||||||
public Result<TokenDTO> login(@RequestBody LoginDTO login) {
|
public Result<TokenDTO> login(@RequestBody LoginDTO login) {
|
||||||
String password = login.getPassword();
|
String password = login.getPassword();
|
||||||
|
|
||||||
// 使用工具类解密并验证验证码
|
// 使用工具类解密并验证验证码
|
||||||
String actualPassword = Sm2DecryptUtil.decryptAndValidateCaptcha(
|
String actualPassword = Sm2DecryptUtil.decryptAndValidateCaptcha(
|
||||||
password, login.getCaptchaId(), captchaService, sysParamsService);
|
password, login.getCaptchaId(), captchaService, sysParamsService);
|
||||||
|
|
||||||
login.setPassword(actualPassword);
|
login.setPassword(actualPassword);
|
||||||
|
|
||||||
// 按照用户名获取用户
|
// 按照用户名获取用户
|
||||||
SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername());
|
SysUserDTO userDTO = sysUserService.getByUsername(login.getUsername());
|
||||||
// 判断用户是否存在
|
// 判断用户是否存在
|
||||||
@@ -108,8 +109,6 @@ public class LoginController {
|
|||||||
}
|
}
|
||||||
return sysUserTokenService.createToken(userDTO.getId());
|
return sysUserTokenService.createToken(userDTO.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
@Operation(summary = "注册")
|
@Operation(summary = "注册")
|
||||||
@@ -117,15 +116,15 @@ public class LoginController {
|
|||||||
if (!sysUserService.getAllowUserRegister()) {
|
if (!sysUserService.getAllowUserRegister()) {
|
||||||
throw new RenException(ErrorCode.USER_REGISTER_DISABLED);
|
throw new RenException(ErrorCode.USER_REGISTER_DISABLED);
|
||||||
}
|
}
|
||||||
|
|
||||||
String password = login.getPassword();
|
String password = login.getPassword();
|
||||||
|
|
||||||
// 使用工具类解密并验证验证码
|
// 使用工具类解密并验证验证码
|
||||||
String actualPassword = Sm2DecryptUtil.decryptAndValidateCaptcha(
|
String actualPassword = Sm2DecryptUtil.decryptAndValidateCaptcha(
|
||||||
password, login.getCaptchaId(), captchaService, sysParamsService);
|
password, login.getCaptchaId(), captchaService, sysParamsService);
|
||||||
|
|
||||||
login.setPassword(actualPassword);
|
login.setPassword(actualPassword);
|
||||||
|
|
||||||
// 是否开启手机注册
|
// 是否开启手机注册
|
||||||
Boolean isMobileRegister = sysParamsService
|
Boolean isMobileRegister = sysParamsService
|
||||||
.getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class);
|
.getValueObject(Constant.SysMSMParam.SERVER_ENABLE_MOBILE_REGISTER.getValue(), Boolean.class);
|
||||||
@@ -204,11 +203,11 @@ public class LoginController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String password = dto.getPassword();
|
String password = dto.getPassword();
|
||||||
|
|
||||||
// 使用工具类解密并验证验证码
|
// 使用工具类解密并验证验证码
|
||||||
String actualPassword = Sm2DecryptUtil.decryptAndValidateCaptcha(
|
String actualPassword = Sm2DecryptUtil.decryptAndValidateCaptcha(
|
||||||
password, dto.getCaptchaId(), captchaService, sysParamsService);
|
password, dto.getCaptchaId(), captchaService, sysParamsService);
|
||||||
|
|
||||||
dto.setPassword(actualPassword);
|
dto.setPassword(actualPassword);
|
||||||
|
|
||||||
sysUserService.changePasswordDirectly(userDTO.getId(), dto.getPassword());
|
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("beianIcpNum", sysParamsService.getValue(Constant.SysBaseParam.BEIAN_ICP_NUM.getValue(), true));
|
||||||
config.put("beianGaNum", sysParamsService.getValue(Constant.SysBaseParam.BEIAN_GA_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));
|
config.put("name", sysParamsService.getValue(Constant.SysBaseParam.SERVER_NAME.getValue(), true));
|
||||||
|
|
||||||
// SM2公钥
|
// SM2公钥
|
||||||
String publicKey = sysParamsService.getValue(Constant.SM2_PUBLIC_KEY, true);
|
String publicKey = sysParamsService.getValue(Constant.SM2_PUBLIC_KEY, true);
|
||||||
if (StringUtils.isBlank(publicKey)) {
|
if (StringUtils.isBlank(publicKey)) {
|
||||||
@@ -237,6 +236,12 @@ public class LoginController {
|
|||||||
}
|
}
|
||||||
config.put("sm2PublicKey", publicKey);
|
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);
|
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:
|
- sqlFile:
|
||||||
encoding: utf8
|
encoding: utf8
|
||||||
path: classpath:db/changelog/202511131023.sql
|
path: classpath:db/changelog/202511131023.sql
|
||||||
|
- changeSet:
|
||||||
|
id: 202512031514
|
||||||
|
author: hrz
|
||||||
|
changes:
|
||||||
|
- sqlFile:
|
||||||
|
encoding: utf8
|
||||||
|
path: classpath:db/changelog/202512031514.sql
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
<div class="settings-btn" @click="handleConfigure">
|
<div class="settings-btn" @click="handleConfigure">
|
||||||
{{ $t('home.configureRole') }}
|
{{ $t('home.configureRole') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-btn" @click="handleVoicePrint">
|
<div v-if="featureStatus.voiceprintRecognition" class="settings-btn" @click="handleVoicePrint">
|
||||||
{{ $t('home.voiceprintRecognition') }}
|
{{ $t('home.voiceprintRecognition') }}
|
||||||
</div>
|
</div>
|
||||||
<div class="settings-btn" @click="handleDeviceManage">
|
<div class="settings-btn" @click="handleDeviceManage">
|
||||||
@@ -49,7 +49,15 @@ import i18n from '@/i18n';
|
|||||||
export default {
|
export default {
|
||||||
name: 'DeviceItem',
|
name: 'DeviceItem',
|
||||||
props: {
|
props: {
|
||||||
device: { type: Object, required: true }
|
device: { type: Object, required: true },
|
||||||
|
featureStatus: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
voiceprintRecognition: false,
|
||||||
|
voiceClone: false,
|
||||||
|
knowledgeBase: false
|
||||||
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return { switchValue: false }
|
return { switchValue: false }
|
||||||
|
|||||||
@@ -106,7 +106,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- MCP区域 -->
|
<!-- MCP区域 -->
|
||||||
<div class="mcp-access-point">
|
<div class="mcp-access-point" v-if="featureStatus.mcpAccessPoint">
|
||||||
<div class="mcp-container">
|
<div class="mcp-container">
|
||||||
<!-- 左侧区域 -->
|
<!-- 左侧区域 -->
|
||||||
<div class="mcp-left">
|
<div class="mcp-left">
|
||||||
@@ -171,6 +171,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import Api from '@/apis/api';
|
import Api from '@/apis/api';
|
||||||
import i18n from '@/i18n';
|
import i18n from '@/i18n';
|
||||||
|
import featureManager from '@/utils/featureManager';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
i18n,
|
i18n,
|
||||||
@@ -205,6 +206,11 @@ export default {
|
|||||||
mcpUrl: "",
|
mcpUrl: "",
|
||||||
mcpStatus: "disconnected",
|
mcpStatus: "disconnected",
|
||||||
mcpTools: [],
|
mcpTools: [],
|
||||||
|
|
||||||
|
// 功能状态
|
||||||
|
featureStatus: {
|
||||||
|
mcpAccessPoint: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -249,6 +255,9 @@ export default {
|
|||||||
// 右侧默认指向第一个
|
// 右侧默认指向第一个
|
||||||
this.currentFunction = this.selectedList[0] || null;
|
this.currentFunction = this.selectedList[0] || null;
|
||||||
|
|
||||||
|
// 加载功能状态
|
||||||
|
this.loadFeatureStatus();
|
||||||
|
|
||||||
// 加载MCP数据
|
// 加载MCP数据
|
||||||
this.loadMcpAddress();
|
this.loadMcpAddress();
|
||||||
this.loadMcpTools();
|
this.loadMcpTools();
|
||||||
@@ -259,6 +268,19 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
/**
|
||||||
|
* 加载功能状态
|
||||||
|
*/
|
||||||
|
async loadFeatureStatus() {
|
||||||
|
// 确保featureManager已初始化完成
|
||||||
|
await featureManager.waitForInitialization();
|
||||||
|
|
||||||
|
const config = featureManager.getConfig();
|
||||||
|
this.featureStatus = {
|
||||||
|
mcpAccessPoint: config.mcpAccessPoint || false
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
copyUrl() {
|
copyUrl() {
|
||||||
const textarea = document.createElement('textarea');
|
const textarea = document.createElement('textarea');
|
||||||
textarea.value = this.mcpUrl;
|
textarea.value = this.mcpUrl;
|
||||||
|
|||||||
@@ -26,7 +26,7 @@
|
|||||||
<span class="nav-text">{{ $t("header.smartManagement") }}</span>
|
<span class="nav-text">{{ $t("header.smartManagement") }}</span>
|
||||||
</div>
|
</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">
|
:class="{ 'active-tab': $route.path === '/voice-clone-management' }" @click="goVoiceCloneManagement">
|
||||||
<img loading="lazy" alt="" src="@/assets/header/voice.png" :style="{
|
<img loading="lazy" alt="" src="@/assets/header/voice.png" :style="{
|
||||||
filter:
|
filter:
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
</div>
|
</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':
|
'active-tab':
|
||||||
$route.path === '/voice-clone-management' ||
|
$route.path === '/voice-clone-management' ||
|
||||||
$route.path === '/voice-resource-management',
|
$route.path === '/voice-resource-management',
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
}" />
|
}" />
|
||||||
<span class="nav-text">{{ $t("header.modelConfig") }}</span>
|
<span class="nav-text">{{ $t("header.modelConfig") }}</span>
|
||||||
</div>
|
</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' }"
|
:class="{ 'active-tab': $route.path === '/knowledge-base-management' || $route.path === '/knowledge-file-upload' }"
|
||||||
@click="goKnowledgeBaseManagement">
|
@click="goKnowledgeBaseManagement">
|
||||||
<img loading="lazy" alt="" src="@/assets/header/knowledge_base.png" :style="{
|
<img loading="lazy" alt="" src="@/assets/header/knowledge_base.png" :style="{
|
||||||
@@ -89,7 +89,8 @@
|
|||||||
$route.path === '/server-side-management' ||
|
$route.path === '/server-side-management' ||
|
||||||
$route.path === '/agent-template-management' ||
|
$route.path === '/agent-template-management' ||
|
||||||
$route.path === '/ota-management' ||
|
$route.path === '/ota-management' ||
|
||||||
$route.path === '/user-management',
|
$route.path === '/user-management' ||
|
||||||
|
$route.path === '/feature-management',
|
||||||
}" @visible-change="handleParamDropdownVisibleChange">
|
}" @visible-change="handleParamDropdownVisibleChange">
|
||||||
<span class="el-dropdown-link">
|
<span class="el-dropdown-link">
|
||||||
<img loading="lazy" alt="" src="@/assets/header/param_management.png" :style="{
|
<img loading="lazy" alt="" src="@/assets/header/param_management.png" :style="{
|
||||||
@@ -100,7 +101,8 @@
|
|||||||
$route.path === '/server-side-management' ||
|
$route.path === '/server-side-management' ||
|
||||||
$route.path === '/agent-template-management' ||
|
$route.path === '/agent-template-management' ||
|
||||||
$route.path === '/ota-management' ||
|
$route.path === '/ota-management' ||
|
||||||
$route.path === '/user-management'
|
$route.path === '/user-management' ||
|
||||||
|
$route.path === '/feature-management'
|
||||||
? 'brightness(0) invert(1)'
|
? 'brightness(0) invert(1)'
|
||||||
: 'None',
|
: 'None',
|
||||||
}" />
|
}" />
|
||||||
@@ -129,6 +131,9 @@
|
|||||||
<el-dropdown-item @click.native="goServerSideManagement">
|
<el-dropdown-item @click.native="goServerSideManagement">
|
||||||
{{ $t("header.serverSideManagement") }}
|
{{ $t("header.serverSideManagement") }}
|
||||||
</el-dropdown-item>
|
</el-dropdown-item>
|
||||||
|
<el-dropdown-item @click.native="goFeatureManagement">
|
||||||
|
{{ $t("header.featureManagement") }}
|
||||||
|
</el-dropdown-item>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
</div>
|
</div>
|
||||||
@@ -186,6 +191,7 @@ import userApi from "@/apis/module/user";
|
|||||||
import i18n, { changeLanguage } from "@/i18n";
|
import i18n, { changeLanguage } from "@/i18n";
|
||||||
import { mapActions, mapGetters } from "vuex";
|
import { mapActions, mapGetters } from "vuex";
|
||||||
import ChangePasswordDialog from "./ChangePasswordDialog.vue"; // 引入修改密码弹窗组件
|
import ChangePasswordDialog from "./ChangePasswordDialog.vue"; // 引入修改密码弹窗组件
|
||||||
|
import featureManager from "@/utils/featureManager"; // 引入功能管理工具类
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "HeaderBar",
|
name: "HeaderBar",
|
||||||
@@ -217,6 +223,11 @@ export default {
|
|||||||
label: "label",
|
label: "label",
|
||||||
children: "children",
|
children: "children",
|
||||||
},
|
},
|
||||||
|
// 功能状态
|
||||||
|
featureStatus: {
|
||||||
|
voiceClone: false, // 音色克隆功能状态
|
||||||
|
knowledgeBase: false, // 知识库功能状态
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -286,12 +297,14 @@ export default {
|
|||||||
];
|
];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
async mounted() {
|
||||||
this.fetchUserInfo();
|
this.fetchUserInfo();
|
||||||
this.checkScreenSize();
|
this.checkScreenSize();
|
||||||
window.addEventListener("resize", this.checkScreenSize);
|
window.addEventListener("resize", this.checkScreenSize);
|
||||||
// 从localStorage加载搜索历史
|
// 从localStorage加载搜索历史
|
||||||
this.loadSearchHistory();
|
this.loadSearchHistory();
|
||||||
|
// 等待featureManager初始化完成后再加载功能状态
|
||||||
|
await this.loadFeatureStatus();
|
||||||
},
|
},
|
||||||
//移除事件监听器
|
//移除事件监听器
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
@@ -338,6 +351,20 @@ export default {
|
|||||||
goAgentTemplateManagement() {
|
goAgentTemplateManagement() {
|
||||||
this.$router.push("/agent-template-management");
|
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() {
|
fetchUserInfo() {
|
||||||
userApi.getUserInfo(({ data }) => {
|
userApi.getUserInfo(({ data }) => {
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ export default {
|
|||||||
'header.clearHistory': 'Verlauf löschen',
|
'header.clearHistory': 'Verlauf löschen',
|
||||||
'header.providerManagement': 'Anbieterverwaltung',
|
'header.providerManagement': 'Anbieterverwaltung',
|
||||||
'header.serverSideManagement': 'Serververwaltung',
|
'header.serverSideManagement': 'Serververwaltung',
|
||||||
|
'header.featureManagement': 'Systemfunktionsverwaltung',
|
||||||
'header.changePassword': 'Passwort ändern',
|
'header.changePassword': 'Passwort ändern',
|
||||||
'header.logout': 'Abmelden',
|
'header.logout': 'Abmelden',
|
||||||
'header.searchPlaceholder': 'Nach Namen suchen..',
|
'header.searchPlaceholder': 'Nach Namen suchen..',
|
||||||
@@ -1262,4 +1263,34 @@ export default {
|
|||||||
'knowledgeFileUpload.content': 'Inhalt:',
|
'knowledgeFileUpload.content': 'Inhalt:',
|
||||||
'knowledgeFileUpload.testQuestionRequired': 'Bitte Testfrage eingeben',
|
'knowledgeFileUpload.testQuestionRequired': 'Bitte Testfrage eingeben',
|
||||||
'knowledgeBaseDialog.descriptionRequired': 'Bitte Wissensbasis-Beschreibung 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',
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@ export default {
|
|||||||
'header.clearHistory': 'Clear History',
|
'header.clearHistory': 'Clear History',
|
||||||
'header.providerManagement': 'Provider Management',
|
'header.providerManagement': 'Provider Management',
|
||||||
'header.serverSideManagement': 'Server Management',
|
'header.serverSideManagement': 'Server Management',
|
||||||
|
'header.featureManagement': 'System Feature Management',
|
||||||
'header.changePassword': 'Change Password',
|
'header.changePassword': 'Change Password',
|
||||||
'header.logout': 'Logout',
|
'header.logout': 'Logout',
|
||||||
'header.searchPlaceholder': 'Search by name..',
|
'header.searchPlaceholder': 'Search by name..',
|
||||||
@@ -1262,4 +1263,34 @@ export default {
|
|||||||
'knowledgeFileUpload.content': 'Content:',
|
'knowledgeFileUpload.content': 'Content:',
|
||||||
'knowledgeFileUpload.testQuestionRequired': 'Please enter test question',
|
'knowledgeFileUpload.testQuestionRequired': 'Please enter test question',
|
||||||
'knowledgeBaseDialog.descriptionRequired': 'Please enter knowledge base description',
|
'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',
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@ export default {
|
|||||||
'header.clearHistory': 'Xóa lịch sử',
|
'header.clearHistory': 'Xóa lịch sử',
|
||||||
'header.providerManagement': 'Quản lý nhà cung cấp',
|
'header.providerManagement': 'Quản lý nhà cung cấp',
|
||||||
'header.serverSideManagement': 'Quản lý máy chủ',
|
'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.changePassword': 'Đổi mật khẩu',
|
||||||
'header.logout': 'Đăng xuất',
|
'header.logout': 'Đăng xuất',
|
||||||
'header.searchPlaceholder': 'Tìm kiếm theo tên..',
|
'header.searchPlaceholder': 'Tìm kiếm theo tên..',
|
||||||
@@ -1262,4 +1263,34 @@ export default {
|
|||||||
'knowledgeFileUpload.content': 'Nội dung:',
|
'knowledgeFileUpload.content': 'Nội dung:',
|
||||||
'knowledgeFileUpload.testQuestionRequired': 'Vui lòng nhập câu hỏi kiểm tra',
|
'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',
|
'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',
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@ export default {
|
|||||||
'header.clearHistory': '清空历史',
|
'header.clearHistory': '清空历史',
|
||||||
'header.providerManagement': '字段管理',
|
'header.providerManagement': '字段管理',
|
||||||
'header.serverSideManagement': '服务端管理',
|
'header.serverSideManagement': '服务端管理',
|
||||||
|
'header.featureManagement': '系统功能配置',
|
||||||
'header.changePassword': '修改密码',
|
'header.changePassword': '修改密码',
|
||||||
'header.logout': '退出登录',
|
'header.logout': '退出登录',
|
||||||
'header.searchPlaceholder': '输入名称搜索..',
|
'header.searchPlaceholder': '输入名称搜索..',
|
||||||
@@ -1262,4 +1263,34 @@ export default {
|
|||||||
'knowledgeFileUpload.content': '内容:',
|
'knowledgeFileUpload.content': '内容:',
|
||||||
'knowledgeFileUpload.testQuestionRequired': '请输入测试问题',
|
'knowledgeFileUpload.testQuestionRequired': '请输入测试问题',
|
||||||
'knowledgeBaseDialog.descriptionRequired': '请输入知识库描述',
|
'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': '将语音转换为文本,实现自然语言交互功能',
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -22,6 +22,7 @@ export default {
|
|||||||
'header.clearHistory': '清空歷史',
|
'header.clearHistory': '清空歷史',
|
||||||
'header.providerManagement': '字段管理',
|
'header.providerManagement': '字段管理',
|
||||||
'header.serverSideManagement': '服務端管理',
|
'header.serverSideManagement': '服務端管理',
|
||||||
|
'header.featureManagement': '系統功能配置',
|
||||||
'header.changePassword': '修改密碼',
|
'header.changePassword': '修改密碼',
|
||||||
'header.logout': '退出登錄',
|
'header.logout': '退出登錄',
|
||||||
'header.searchPlaceholder': '輸入名稱搜索..',
|
'header.searchPlaceholder': '輸入名稱搜索..',
|
||||||
@@ -1262,4 +1263,34 @@ export default {
|
|||||||
'knowledgeFileUpload.content': '內容:',
|
'knowledgeFileUpload.content': '內容:',
|
||||||
'knowledgeFileUpload.testQuestionRequired': '請輸入測試問題',
|
'knowledgeFileUpload.testQuestionRequired': '請輸入測試問題',
|
||||||
'knowledgeBaseDialog.descriptionRequired': '請輸入知识库描述',
|
'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': '將語音轉換為文本,實現自然語言交互功能',
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -8,6 +8,7 @@ import store from './store';
|
|||||||
import i18n from './i18n';
|
import i18n from './i18n';
|
||||||
import './styles/global.scss';
|
import './styles/global.scss';
|
||||||
import { register as registerServiceWorker } from './registerServiceWorker';
|
import { register as registerServiceWorker } from './registerServiceWorker';
|
||||||
|
import featureManager from './utils/featureManager';
|
||||||
|
|
||||||
// 创建事件总线,用于组件间通信
|
// 创建事件总线,用于组件间通信
|
||||||
Vue.prototype.$eventBus = new Vue();
|
Vue.prototype.$eventBus = new Vue();
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ const routes = [
|
|||||||
return import('../views/ModelConfig.vue')
|
return import('../views/ModelConfig.vue')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ path: '/params-management',
|
{
|
||||||
|
path: '/params-management',
|
||||||
name: 'ParamsManagement',
|
name: 'ParamsManagement',
|
||||||
component: function () {
|
component: function () {
|
||||||
return import('../views/ParamsManagement.vue')
|
return import('../views/ParamsManagement.vue')
|
||||||
@@ -86,7 +87,8 @@ const routes = [
|
|||||||
title: '参数管理'
|
title: '参数管理'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ path: '/knowledge-base-management',
|
{
|
||||||
|
path: '/knowledge-base-management',
|
||||||
name: 'KnowledgeBaseManagement',
|
name: 'KnowledgeBaseManagement',
|
||||||
component: function () {
|
component: function () {
|
||||||
return import('../views/KnowledgeBaseManagement.vue')
|
return import('../views/KnowledgeBaseManagement.vue')
|
||||||
@@ -96,7 +98,8 @@ const routes = [
|
|||||||
title: '知识库管理'
|
title: '知识库管理'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{ path: '/knowledge-file-upload',
|
{
|
||||||
|
path: '/knowledge-file-upload',
|
||||||
name: 'KnowledgeFileUpload',
|
name: 'KnowledgeFileUpload',
|
||||||
component: function () {
|
component: function () {
|
||||||
return import('../views/KnowledgeFileUpload.vue')
|
return import('../views/KnowledgeFileUpload.vue')
|
||||||
@@ -181,6 +184,18 @@ const routes = [
|
|||||||
return import('../views/TemplateQuickConfig.vue')
|
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({
|
const router = new VueRouter({
|
||||||
base: process.env.VUE_APP_PUBLIC_PATH || '/',
|
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>
|
||||||
@@ -39,8 +39,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<DeviceItem v-for="(item, index) in devices" :key="index" :device="item" @configure="goToRoleConfig"
|
<DeviceItem v-for="(item, index) in devices" :key="index" :device="item" :feature-status="featureStatus"
|
||||||
@deviceManage="handleDeviceManage" @delete="handleDeleteAgent" @chat-history="handleShowChatHistory" />
|
@configure="goToRoleConfig" @deviceManage="handleDeviceManage" @delete="handleDeleteAgent"
|
||||||
|
@chat-history="handleShowChatHistory" />
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -61,6 +62,7 @@ import ChatHistoryDialog from '@/components/ChatHistoryDialog.vue';
|
|||||||
import DeviceItem from '@/components/DeviceItem.vue';
|
import DeviceItem from '@/components/DeviceItem.vue';
|
||||||
import HeaderBar from '@/components/HeaderBar.vue';
|
import HeaderBar from '@/components/HeaderBar.vue';
|
||||||
import VersionFooter from '@/components/VersionFooter.vue';
|
import VersionFooter from '@/components/VersionFooter.vue';
|
||||||
|
import featureManager from '@/utils/featureManager';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HomePage',
|
name: 'HomePage',
|
||||||
@@ -76,15 +78,33 @@ export default {
|
|||||||
skeletonCount: localStorage.getItem('skeletonCount') || 8,
|
skeletonCount: localStorage.getItem('skeletonCount') || 8,
|
||||||
showChatHistory: false,
|
showChatHistory: false,
|
||||||
currentAgentId: '',
|
currentAgentId: '',
|
||||||
currentAgentName: ''
|
currentAgentName: '',
|
||||||
|
// 功能状态
|
||||||
|
featureStatus: {
|
||||||
|
voiceprintRecognition: false,
|
||||||
|
voiceClone: false,
|
||||||
|
knowledgeBase: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
async mounted() {
|
||||||
this.fetchAgentList();
|
this.fetchAgentList();
|
||||||
|
await this.loadFeatureStatus();
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
// 加载功能状态
|
||||||
|
async loadFeatureStatus() {
|
||||||
|
await featureManager.waitForInitialization();
|
||||||
|
const config = featureManager.getConfig();
|
||||||
|
this.featureStatus = {
|
||||||
|
voiceprintRecognition: config.voiceprintRecognition,
|
||||||
|
voiceClone: config.voiceClone,
|
||||||
|
knowledgeBase: config.knowledgeBase
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
showAddDialog() {
|
showAddDialog() {
|
||||||
this.addDeviceDialogVisible = true
|
this.addDeviceDialogVisible = true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ import VersionFooter from "@/components/VersionFooter.vue";
|
|||||||
import i18n, { changeLanguage } from "@/i18n";
|
import i18n, { changeLanguage } from "@/i18n";
|
||||||
import { getUUID, goToPage, showDanger, showSuccess, sm2Encrypt, validateMobile } from "@/utils";
|
import { getUUID, goToPage, showDanger, showSuccess, sm2Encrypt, validateMobile } from "@/utils";
|
||||||
import { mapState } from "vuex";
|
import { mapState } from "vuex";
|
||||||
|
import featureManager from "@/utils/featureManager";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "login",
|
name: "login",
|
||||||
@@ -214,6 +215,13 @@ export default {
|
|||||||
this.$store.dispatch("fetchPubConfig").then(() => {
|
this.$store.dispatch("fetchPubConfig").then(() => {
|
||||||
// 根据配置决定默认登录方式
|
// 根据配置决定默认登录方式
|
||||||
this.isMobileLogin = this.enableMobileRegister;
|
this.isMobileLogin = this.enableMobileRegister;
|
||||||
|
|
||||||
|
// pub-config接口调用完成后,重新初始化featureManager以确保使用最新的配置
|
||||||
|
featureManager.waitForInitialization().then(() => {
|
||||||
|
console.log('featureManager重新初始化完成,使用pub-config配置');
|
||||||
|
}).catch(error => {
|
||||||
|
console.warn('featureManager重新初始化失败:', error);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@@ -107,7 +107,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-column">
|
<div class="form-column">
|
||||||
<div class="model-row">
|
<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">
|
<div class="model-select-wrapper">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="form.model.vadModelId"
|
v-model="form.model.vadModelId"
|
||||||
@@ -125,7 +129,11 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</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">
|
<div class="model-select-wrapper">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="form.model.asrModelId"
|
v-model="form.model.asrModelId"
|
||||||
@@ -277,6 +285,7 @@ import RequestService from "@/apis/httpRequest";
|
|||||||
import FunctionDialog from "@/components/FunctionDialog.vue";
|
import FunctionDialog from "@/components/FunctionDialog.vue";
|
||||||
import HeaderBar from "@/components/HeaderBar.vue";
|
import HeaderBar from "@/components/HeaderBar.vue";
|
||||||
import i18n from "@/i18n";
|
import i18n from "@/i18n";
|
||||||
|
import featureManager from "@/utils/featureManager";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "RoleConfigPage",
|
name: "RoleConfigPage",
|
||||||
@@ -326,6 +335,11 @@ export default {
|
|||||||
isPaused: false,
|
isPaused: false,
|
||||||
currentAudio: null,
|
currentAudio: null,
|
||||||
currentPlayingVoiceId: null,
|
currentPlayingVoiceId: null,
|
||||||
|
// 功能状态
|
||||||
|
featureStatus: {
|
||||||
|
vad: false, // 语言检测活动功能状态
|
||||||
|
asr: false, // 语音识别功能状态
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -980,6 +994,19 @@ export default {
|
|||||||
this.form.chatHistoryConf = 0;
|
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: {
|
watch: {
|
||||||
"form.model.ttsModelId": {
|
"form.model.ttsModelId": {
|
||||||
@@ -1002,7 +1029,7 @@ export default {
|
|||||||
immediate: true,
|
immediate: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mounted() {
|
async mounted() {
|
||||||
const agentId = this.$route.query.agentId;
|
const agentId = this.$route.query.agentId;
|
||||||
if (agentId) {
|
if (agentId) {
|
||||||
this.fetchAgentConfig(agentId);
|
this.fetchAgentConfig(agentId);
|
||||||
@@ -1010,6 +1037,8 @@ export default {
|
|||||||
}
|
}
|
||||||
this.fetchModelOptions();
|
this.fetchModelOptions();
|
||||||
this.fetchTemplates();
|
this.fetchTemplates();
|
||||||
|
// 加载功能状态,确保featureManager已初始化
|
||||||
|
await this.loadFeatureStatus();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user