Merge pull request #741 from xinnan-tech/web-api-timbre

Web api timbre
This commit is contained in:
hrz
2025-04-11 21:25:34 +08:00
committed by GitHub
6 changed files with 199 additions and 183 deletions
@@ -115,7 +115,6 @@ export default {
}, },
// 删除 // 删除
deleteParam(ids, callback) { deleteParam(ids, callback) {
console.log(4444,ids )
RequestService.sendRequest() RequestService.sendRequest()
.url(`${getServiceUrl()}/admin/params/delete`) .url(`${getServiceUrl()}/admin/params/delete`)
.method('POST') .method('POST')
+9 -19
View File
@@ -1,11 +1,8 @@
// timbre.js
import {getServiceUrl} from '../api'; import {getServiceUrl} from '../api';
import RequestService from '../httpRequest'; import RequestService from '../httpRequest';
export default { export default {
/** // 获取音色
* 获取音色数据
*/
getVoiceList(params, callback) { getVoiceList(params, callback) {
const queryParams = new URLSearchParams({ const queryParams = new URLSearchParams({
ttsModelId: params.ttsModelId, ttsModelId: params.ttsModelId,
@@ -28,9 +25,7 @@ export default {
}); });
}).send(); }).send();
}, },
/** // 音色保存
* 音色保存
*/
saveVoice(params, callback) { saveVoice(params, callback) {
RequestService.sendRequest() RequestService.sendRequest()
.url(`${getServiceUrl()}/ttsVoice`) .url(`${getServiceUrl()}/ttsVoice`)
@@ -54,15 +49,12 @@ export default {
}); });
}).send(); }).send();
}, },
/** // 音色删除
* 音色删除 deleteVoice(ids, callback) {
*/
deleteVoice(id, callback) {
console.log('尝试删除音色,ID:', id);
RequestService.sendRequest() RequestService.sendRequest()
.url(`${getServiceUrl()}/ttsVoice`) .url(`${getServiceUrl()}/ttsVoice/delete`)
.method('DELETE') .method('POST')
.data({ ids: [id] }) .data(ids)
.success((res) => { .success((res) => {
RequestService.clearRequestTime() RequestService.clearRequestTime()
callback(res); callback(res);
@@ -70,13 +62,11 @@ export default {
.fail((err) => { .fail((err) => {
console.error('删除音色失败:', err); console.error('删除音色失败:', err);
RequestService.reAjaxFun(() => { RequestService.reAjaxFun(() => {
this.deleteVoice(id, callback); this.deleteVoice(ids, callback);
}); });
}).send(); }).send();
}, },
/** // 音色修改
* 音色修改
*/
updateVoice(params, callback) { updateVoice(params, callback) {
RequestService.sendRequest() RequestService.sendRequest()
.url(`${getServiceUrl()}/ttsVoice/${params.id}`) .url(`${getServiceUrl()}/ttsVoice/${params.id}`)
+88 -110
View File
@@ -1,7 +1,7 @@
<template> <template>
<el-dialog <el-dialog
:visible.sync="localVisible" :visible.sync="localVisible"
width="75%" width="85%"
@close="handleClose" @close="handleClose"
:show-close="false" :show-close="false"
:append-to-body="true" :append-to-body="true"
@@ -13,8 +13,7 @@
<div <div
class="table-container" class="table-container"
ref="tableContainer" ref="tableContainer"
@scroll="handleScroll" @scroll="handleScroll">
>
<el-table <el-table
v-loading="loading" v-loading="loading"
:data="filteredTtsModels" :data="filteredTtsModels"
@@ -45,7 +44,7 @@
<span v-else>{{ scope.row.languageType }}</span> <span v-else>{{ scope.row.languageType }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="试听" align="center" min-width="225" class-name="audio-column"> <el-table-column label="试听" align="center" min-width="100px" class-name="audio-column">
<template slot-scope="scope"> <template slot-scope="scope">
<div class="custom-audio-container"> <div class="custom-audio-container">
<el-input <el-input
@@ -53,13 +52,13 @@
v-model="scope.row.voiceDemo" v-model="scope.row.voiceDemo"
placeholder="请输入MP3地址" placeholder="请输入MP3地址"
size="mini" size="mini"
class="audio-input" class="audio-input">
></el-input> </el-input>
<AudioPlayer v-else-if="isValidAudioUrl(scope.row.voiceDemo)" :audioUrl="scope.row.voiceDemo"/> <AudioPlayer v-else-if="isValidAudioUrl(scope.row.voiceDemo)" :audioUrl="scope.row.voiceDemo"/>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="备注" align="center" min-width="120"> <el-table-column label="备注" align="center">
<template slot-scope="scope"> <template slot-scope="scope">
<el-input <el-input
v-if="scope.row.editing" v-if="scope.row.editing"
@@ -75,10 +74,10 @@
<el-table-column label="操作" align="center" width="150"> <el-table-column label="操作" align="center" width="150">
<template slot-scope="scope"> <template slot-scope="scope">
<template v-if="!scope.row.editing"> <template v-if="!scope.row.editing">
<el-button type="primary" size="mini" @click="startEdit(scope.row)" <el-button type="text" size="mini" @click="startEdit(scope.row)" class="edit-btn">
style="background: #5cca8e;border:None">编辑 编辑
</el-button> </el-button>
<el-button type="primary" size="mini" @click="deleteRow(scope.row)" style="background: red;border:None"> <el-button type="text" size="mini" @click="deleteRow(scope.row)" class="delete-btn">
删除 删除
</el-button> </el-button>
</template> </template>
@@ -105,10 +104,14 @@
<el-button type="primary" size="mini" @click="toggleSelectAll" style="background: #606ff3;border: None"> <el-button type="primary" size="mini" @click="toggleSelectAll" style="background: #606ff3;border: None">
{{ selectAll ? '取消全选' : '全选' }} {{ selectAll ? '取消全选' : '全选' }}
</el-button> </el-button>
<el-button type="primary" size="mini" @click="addNew" style="background: #f6cf79;border: None; color: #000012"> <el-button type="primary" size="mini" @click="addNew" style="background: #5bc98c;border: None;">
新增 新增
</el-button> </el-button>
<el-button type="primary" size="mini" @click="batchDelete" style="background: red;border:None">删除</el-button> <el-button type="primary"
size="mini"
@click="deleteRow(filteredTtsModels.filter(row => row.selected))"
style="background: red;border:None">删除
</el-button>
</div> </div>
</el-dialog> </el-dialog>
</template> </template>
@@ -419,105 +422,57 @@ export default {
}, },
deleteRow(row) { deleteRow(row) {
this.$confirm("确定要删除该音色吗?", "警告", { // 处理单个音色或音色数组
confirmButtonText: "确定", const voices = Array.isArray(row) ? row : [row];
cancelButtonText: "取消",
type: "warning", if (Array.isArray(row) && row.length === 0) {
}) this.$message.warning("请先选择需要删除的音色");
.then(() => { return;
Api.timbre.deleteVoice(row.id, (response) => { }
if (response.code === 0) {
this.$message.success({
message: "删除成功", const voiceCount = voices.length;
showClose: true this.$confirm(`确定要删除选中的${voiceCount}个音色吗?`, "警告", {
}); confirmButtonText: "确定",
this.loadData(); // 刷新数据 cancelButtonText: "取消",
} else { type: "warning",
this.$message.error({ distinguishCancelAndClose: true
message: response.msg || "删除失败", }).then(() => {
showClose: true const ids = voices.map(voice => voice.id);
}); if (ids.some(id => !id)) {
this.$message.error("存在无效的音色ID");
return;
} }
});
})
.catch(() => {
this.$message.info("已取消删除");
});
},
batchDelete() { Api.timbre.deleteVoice(ids, ({data}) => {
const selectedRows = this.filteredTtsModels.filter(row => row.selected); if (data.code === 0) {
if (selectedRows.length === 0) { this.$message.success({
this.$message.warning("请先选择需要删除的音色"); message: `成功删除${voiceCount}个参数`,
return; showClose: true
}
this.$confirm(`确定要删除选中的${selectedRows.length}个音色吗?`, "警告", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(async () => {
const loading = this.$loading({
lock: true,
text: "正在删除中...",
spinner: "el-icon-loading",
background: "rgba(0, 0, 0, 0.7)",
});
try {
const results = await Promise.all(
selectedRows.map((row) => {
return new Promise((resolve) => {
Api.timbre.deleteVoice(row.id, (response) => {
if (response.code === 0) {
resolve({ success: true, id: row.id });
} else {
resolve({
success: false,
id: row.id,
msg: response.msg || '删除失败'
});
}
});
}); });
}) this.loadData(); // 刷新参数列表
);
const successCount = results.filter(r => r.success).length;
const failCount = results.length - successCount;
if (failCount === 0) {
this.$message.success({
message: `成功删除${successCount}个音色`,
showClose: true
});
} else if (successCount === 0) {
this.$message.error({
message: '删除失败,请重试',
showClose: true
});
} else { } else {
this.$message.warning({ this.$message.error({
message: `成功删除${successCount}个音色,${failCount}个删除失败`, message: data.msg || '删除失败,请重试',
showClose: true showClose: true
}); });
} }
this.loadData(); // 刷新数据
} catch (error) {
console.error('批量删除出错:', error);
this.$message.error({
message: '删除过程中发生错误',
showClose: true
});
} finally {
loading.close();
}
})
.catch(() => {
this.$message.info("已取消删除");
}); });
}).catch(action => {
if (action === 'cancel') {
this.$message({
type: 'info',
message: '已取消删除操作',
duration: 1000
});
} else {
this.$message({
type: 'info',
message: '操作已关闭',
duration: 1000
});
}
});
}, },
isValidAudioUrl(url) { isValidAudioUrl(url) {
@@ -532,7 +487,7 @@ export default {
::v-deep .el-dialog { ::v-deep .el-dialog {
border-radius: 8px !important; border-radius: 8px !important;
overflow: hidden; overflow: hidden;
top: 8vh !important; top: 1vh !important;
} }
::v-deep .el-dialog__header { ::v-deep .el-dialog__header {
@@ -628,13 +583,13 @@ export default {
.table-container { .table-container {
flex: 1; flex: 1;
overflow-y: scroll; overflow-y: scroll;
scrollbar-width: none; /* Firefox */ scrollbar-width: none;
padding-right: 15px; /* 为滚动条留出空间 */ padding-right: 15px;
width: calc(100% - 16px); /* 减去滚动条宽度 */ width: calc(100% - 16px);
} }
.table-container::-webkit-scrollbar { .table-container::-webkit-scrollbar {
display: none; /* Chrome/Safari */ display: none;
} }
/* 自定义滚动条 */ /* 自定义滚动条 */
@@ -684,10 +639,33 @@ export default {
display: none; display: none;
} }
/* 音频播放器容器样式 */
.custom-audio-container {
width: 280px;
margin: auto;
}
/* 新增按钮组样式 */ /* 新增按钮组样式 */
.action-buttons { .action-buttons {
bottom: 20px; bottom: 20px;
padding-top: 10px; padding-top: 10px;
} }
.edit-btn,
.delete-btn,
.save-btn {
margin: 0 8px;
color: #7079aa !important;
transition: all 0.3s;
}
.edit-btn:hover,
.delete-btn:hover,
.save-btn:hover {
color: #5f70f3 !important;
transform: scale(1.05);
}
.save-btn {
color: #5cca8e !important;
}
</style> </style>
+81 -47
View File
@@ -3,7 +3,7 @@
<HeaderBar /> <HeaderBar />
<div class="operation-bar"> <div class="operation-bar">
<h2 class="page-title">模型配置</h2> <h2 class="page-title">{{ modelTypeText }}</h2>
<!-- <div class="right-operations">--> <!-- <div class="right-operations">-->
<!-- <el-button plain size="small" @click="handleImport" style="background: #7b9de5; color: white;">--> <!-- <el-button plain size="small" @click="handleImport" style="background: #7b9de5; color: white;">-->
<!-- <img loading="lazy" alt="" src="@/assets/model/inner_conf.png">--> <!-- <img loading="lazy" alt="" src="@/assets/model/inner_conf.png">-->
@@ -14,6 +14,21 @@
<!-- 导出配置--> <!-- 导出配置-->
<!-- </el-button>--> <!-- </el-button>-->
<!-- </div>--> <!-- </div>-->
<div class="action-group">
<div class="search-group">
<el-input
placeholder="请输入模型名称查询"
v-model="search"
class="search-input"
clearable
@keyup.enter.native="handleSearch"
style="width: 240px"
/>
<el-button class="btn-search" @click="handleSearch">
搜索
</el-button>
</div>
</div>
</div> </div>
<!-- 主体内容 --> <!-- 主体内容 -->
@@ -44,23 +59,6 @@
<!-- 右侧内容 --> <!-- 右侧内容 -->
<div class="content-area"> <div class="content-area">
<div class="title-bar">
<div class="title-wrapper">
<h2 class="model-title">{{ modelTypeText }}</h2>
<el-button type="primary" size="small" @click="addModel" class="add-btn">
添加
</el-button>
</div>
<div class="action-group">
<div class="search-group">
<el-input placeholder="请输入模型名称查询" v-model="search" size="small" class="search-input" clearable @keyup.enter.native="handleSearch" />
<el-button type="primary" size="small" class="search-btn" @click="handleSearch">
查询
</el-button>
</div>
</div>
</div>
<el-table ref="modelTable" style="width: 100%" :header-cell-style="{ background: 'transparent' }" <el-table ref="modelTable" style="width: 100%" :header-cell-style="{ background: 'transparent' }"
:data="modelList" class="data-table" header-row-class-name="table-header" :data="modelList" class="data-table" header-row-class-name="table-header"
:header-cell-class-name="headerCellClassName" @selection-change="handleSelectionChange"> :header-cell-class-name="headerCellClassName" @selection-change="handleSelectionChange">
@@ -103,9 +101,13 @@
<div class="table-footer"> <div class="table-footer">
<div class="batch-actions"> <div class="batch-actions">
<el-button size="mini" @click="selectAll" style="width: 75px; background: #606ff3">{{ isAllSelected ? <el-button size="mini" type="primary" @click="selectAll" >
'取消全选' : '全选' {{ isAllSelected ?
}}</el-button> '取消全选' : '全选' }}
</el-button>
<el-button type="success" size="mini" @click="addModel" class="add-btn">
新增
</el-button>
<el-button size="mini" type="danger" icon="el-icon-delete" @click="batchDelete"> <el-button size="mini" type="danger" icon="el-icon-delete" @click="batchDelete">
删除 删除
</el-button> </el-button>
@@ -456,7 +458,7 @@ export default {
} }
.main-wrapper { .main-wrapper {
margin: 5px 20px; margin: 5px 22px;
border-radius: 15px; border-radius: 15px;
min-height: 600px; min-height: 600px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
@@ -517,7 +519,7 @@ export default {
justify-content: flex-end; justify-content: flex-end;
padding-right: 12px !important; padding-right: 12px !important;
width: fit-content; width: fit-content;
margin: 8px 0px 8px auto; margin: 8px 0 8px auto;
min-width: unset; min-width: unset;
} }
@@ -567,12 +569,6 @@ export default {
flex-wrap: nowrap; flex-wrap: nowrap;
} }
.model-title {
font-size: 18px;
color: #303133;
margin: 0;
}
.action-group { .action-group {
display: flex; display: flex;
align-items: center; align-items: center;
@@ -581,28 +577,42 @@ export default {
.search-group { .search-group {
display: flex; display: flex;
gap: 8px; gap: 10px;
} }
.search-input { .search-input {
width: 240px; width: 240px;
} }
::v-deep .search-input .el-input__inner::placeholder { .btn-search {
color: black; background: linear-gradient(135deg, #6b8cff, #a966ff);
opacity: 0.6; border: none;
color: white;
} }
::v-deep .search-input .el-input__inner { ::v-deep .search-input .el-input__inner {
background: transparent; border-radius: 4px;
border: 1px solid #DCDFE6;
background-color: white;
transition: border-color 0.2s;
} }
.search-btn { ::v-deep .search-input .el-input__inner:focus {
background: linear-gradient(135deg, #6B8CFF, #A966FF); border-color: #6b8cff;
outline: none;
}
.btn-search {
background: linear-gradient(135deg, #6b8cff, #a966ff);
border: none; border: none;
color: white; color: white;
} }
.btn-search:hover {
opacity: 0.9;
transform: translateY(-1px);
}
.data-table { .data-table {
border-radius: 6px; border-radius: 6px;
overflow: hidden; overflow: hidden;
@@ -647,20 +657,45 @@ export default {
width: 100%; width: 100%;
} }
.add-btn {
background: #cce5f9;
width: 75px;
border: none;
color: black;
padding: 8px 16px;
}
.title-wrapper { .title-wrapper {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
} }
.batch-actions .el-button {
min-width: 72px;
height: 32px;
padding: 7px 12px 7px 10px;
font-size: 12px;
border-radius: 4px;
line-height: 1;
font-weight: 500;
border: none;
transition: all 0.3s ease;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
}
.batch-actions .el-button:hover {
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.batch-actions .el-button--primary {
background: #5f70f3 !important;
color: white;
}
.batch-actions .el-button--success {
background: #5bc98c;
color: white;
}
.batch-actions .el-button--danger {
background: #fd5b63;
color: white;
}
.batch-actions .el-button:first-child { .batch-actions .el-button:first-child {
background: linear-gradient(135deg, #409EFF, #6B8CFF); background: linear-gradient(135deg, #409EFF, #6B8CFF);
border: none; border: none;
@@ -686,7 +721,6 @@ export default {
::v-deep .el-table .custom-selection-header .cell .el-checkbox__inner { ::v-deep .el-table .custom-selection-header .cell .el-checkbox__inner {
display: none !important; display: none !important;
/* 使表头复选框不可见 */
} }
::v-deep .el-table .custom-selection-header .cell::before { ::v-deep .el-table .custom-selection-header .cell::before {
@@ -741,8 +775,8 @@ export default {
} }
::v-deep .el-checkbox__input.is-checked .el-checkbox__inner { ::v-deep .el-checkbox__input.is-checked .el-checkbox__inner {
background-color: #409EFF !important; background-color: #5f70f3;
border-color: #409EFF !important; border-color: #5f70f3;
} }
.voice-management-btn { .voice-management-btn {
@@ -31,11 +31,12 @@
<div class="table_bottom"> <div class="table_bottom">
<div class="ctrl_btn"> <div class="ctrl_btn">
<el-button size="mini" type="primary" class="select-all-btn" <el-button size="mini" type="primary" class="select-all-btn" @click="handleSelectAll">
@click="handleSelectAll">全选</el-button> {{ isAllSelected ? '取消全选' : '全选' }}
</el-button>
<el-button size="mini" type="success" @click="showAddDialog">新增</el-button> <el-button size="mini" type="success" @click="showAddDialog">新增</el-button>
<el-button size="mini" type="danger" icon="el-icon-delete" <el-button size="mini" type="danger" icon="el-icon-delete"
@click="deleteParam($refs.paramsTable.selection)">删除</el-button> @click="deleteParam($refs.paramsTable.selection)">删除</el-button>
</div> </div>
<div class="custom-pagination"> <div class="custom-pagination">
<button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst"> <button class="pagination-btn" :disabled="currentPage === 1" @click="goFirst">
@@ -82,6 +83,7 @@ export default {
total: 0, total: 0,
dialogVisible: false, dialogVisible: false,
dialogTitle: "新增参数", dialogTitle: "新增参数",
isAllSelected: false,
paramForm: { paramForm: {
id: null, id: null,
paramCode: "", paramCode: "",
@@ -136,7 +138,12 @@ export default {
this.fetchParams(); this.fetchParams();
}, },
handleSelectAll() { handleSelectAll() {
this.$refs.paramsTable.toggleAllSelection(); if (this.isAllSelected) {
this.$refs.paramsTable.clearSelection();
} else {
this.$refs.paramsTable.toggleAllSelection();
}
this.isAllSelected = !this.isAllSelected;
}, },
showAddDialog() { showAddDialog() {
this.dialogTitle = "新增参数"; this.dialogTitle = "新增参数";
+10 -2
View File
@@ -41,7 +41,9 @@
<div class="table_bottom"> <div class="table_bottom">
<div class="ctrl_btn"> <div class="ctrl_btn">
<el-button size="mini" type="primary" class="select-all-btn" @click="handleSelectAll">全选</el-button> <el-button size="mini" type="primary" class="select-all-btn" @click="handleSelectAll">
{{ isAllSelected ? '取消全选' : '全选' }}
</el-button>
<el-button size="mini" type="success" icon="el-icon-circle-check" @click="batchEnable">启用</el-button> <el-button size="mini" type="success" icon="el-icon-circle-check" @click="batchEnable">启用</el-button>
<el-button size="mini" type="warning" @click="batchDisable"><i <el-button size="mini" type="warning" @click="batchDisable"><i
class="el-icon-remove-outline rotated-icon"></i>禁用</el-button> class="el-icon-remove-outline rotated-icon"></i>禁用</el-button>
@@ -89,6 +91,7 @@ export default {
currentPage: 1, currentPage: 1,
pageSize: 5, pageSize: 5,
total: 0, total: 0,
isAllSelected: false
}; };
}, },
created() { created() {
@@ -135,7 +138,12 @@ export default {
this.fetchUsers(); this.fetchUsers();
}, },
handleSelectAll() { handleSelectAll() {
this.$refs.userTable.toggleAllSelection(); if (this.isAllSelected) {
this.$refs.userTable.clearSelection();
} else {
this.$refs.userTable.toggleAllSelection();
}
this.isAllSelected = !this.isAllSelected;
}, },
batchDelete() { batchDelete() {
const selectedUsers = this.$refs.userTable.selection; const selectedUsers = this.$refs.userTable.selection;