| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514 |
- <template>
- <div class="voice-management">
- <div class="header">
- <h2>Voice Management</h2>
- <div class="header-actions">
- <!-- <el-upload-->
- <!-- class="import-btn"-->
- <!-- action="javascript:;"-->
- <!-- :show-file-list="false"-->
- <!-- :before-upload="handleExcelUpload"-->
- <!-- accept=".xlsx,.xls"-->
- <!-- >-->
- <!-- <el-button type="text">-->
- <!-- <el-icon><Upload /></el-icon>Import-->
- <!-- </el-button>-->
- <!-- </el-upload>-->
- <!-- <el-button type="primary" @click="showAddDialog">-->
- <!-- Add Voice-->
- <!-- </el-button>-->
- </div>
- </div>
- <el-table :data="voices" v-loading="loading" style="width: 100%" class="voice-table">
- <el-table-column label="Voice Name" min-width="200">
- <template #default="scope">
- <div class="voice-info">
- <!-- <div class="voice-icon">-->
- <!-- <el-icon><Microphone /></el-icon>-->
- <!-- </div>-->
- <div class="voice-name">{{ scope.row.name }}</div>
- </div>
- </template>
- </el-table-column>
- <!-- <el-table-column prop="description" label="Voice Description" min-width="250" />-->
- <el-table-column prop="gender" label="Gender" width="200">
- <template #default="scope">
- {{ scope.row.gender === '1' ? 'Male' : 'Female' }}
- </template>
- </el-table-column>
- <!-- <el-table-column label="Original Voice" width="150">-->
- <!-- <template #default="scope">-->
- <!-- <el-button type="text" @click="playVoice(scope.row.originalVoice)">-->
- <!-- <el-icon><VideoPlay /></el-icon> 试听-->
- <!-- </el-button>-->
- <!-- </template>-->
- <!-- </el-table-column>-->
- <el-table-column prop="createTime" label="create Date" width="120" >
- <template #default="scope">
- {{ scope.row.ctime}}
- </template>
- </el-table-column>
- <!-- <el-table-column label="Actions" width="150">-->
- <!-- <template #default="scope">-->
- <!-- <div class="action-buttons">-->
- <!-- <el-button type="text" @click="editVoice(scope.row)">-->
- <!-- <el-icon><Edit /></el-icon>-->
- <!-- </el-button>-->
- <!-- <el-button type="text" class="delete-btn" @click="confirmDelete(scope.row)">-->
- <!-- <el-icon><Delete /></el-icon>-->
- <!-- </el-button>-->
- <!-- </div>-->
- <!-- </template>-->
- <!-- </el-table-column>-->
- </el-table>
- <!-- 导入确认对话框 -->
- <el-dialog
- title="导入声音"
- v-model="importDialogVisible"
- width="500px"
- >
- <div v-if="importFileName" class="import-file-info">
- 即将导入文件: {{ importFileName }}
- </div>
- <div v-else>
- 请先选择Excel文件
- </div>
-
- <template #footer>
- <el-button @click="importDialogVisible = false">取消</el-button>
- <el-button
- type="primary"
- @click="confirmImport"
- :disabled="!importFileName"
- >
- 确认导入
- </el-button>
- </template>
- </el-dialog>
- <!-- Add Voice Dialog -->
- <el-dialog
- :title="dialogType === 'add' ? 'Add Voice' : 'Edit Voice'"
- v-model="dialogVisible"
- width="500px"
- class="voice-dialog"
- >
- <el-form
- ref="voiceFormRef"
- :model="voiceForm"
- :rules="rules"
- label-width="120px"
- >
- <el-form-item label="Voice Name" prop="name">
- <el-input v-model="voiceForm.name" placeholder="Enter voice name" />
- </el-form-item>
- <el-form-item label="Description" prop="description">
- <el-input
- v-model="voiceForm.description"
- type="textarea"
- placeholder="Enter voice description"
- rows="3"
- />
- </el-form-item>
- <el-form-item label="Gender" prop="gender">
- <el-select v-model="voiceForm.gender" placeholder="Select gender" style="width: 100%">
- <el-option label="Male" value="male" />
- <el-option label="Female" value="female" />
- </el-select>
- </el-form-item>
- <!-- <el-form-item label="Original Voice" prop="originalVoice">-->
- <!-- <el-upload-->
- <!-- :show-file-list="false"-->
- <!-- :before-upload="beforeUploadVoice"-->
- <!-- :on-success="(response, file) => handleVoiceUploadSuccess(file, 'originalVoice')"-->
- <!-- :on-error="handleUploadError"-->
- <!-- class="upload-voice"-->
- <!-- >-->
- <!-- <el-button plain>-->
- <!-- <el-icon><Upload /></el-icon>-->
- <!-- {{ voiceForm.originalVoice ? 'Change File' : 'Upload File' }}-->
- <!-- </el-button>-->
- <!-- </el-upload>-->
- <!-- <div v-if="voiceForm.originalVoice" class="file-name">-->
- <!-- {{ voiceForm.originalVoice }}-->
- <!-- <el-button type="text" size="small" @click="removeVoiceFile('originalVoice')">-->
- <!-- <el-icon><CircleClose /></el-icon>-->
- <!-- </el-button>-->
- <!-- </div>-->
- <!-- </el-form-item>-->
- <el-form-item label="Cloned Voice" prop="clonedVoice">
- <div v-if="voiceForm.originalVoice && !voiceForm.clonedVoice" class="cloning-status">
- <el-icon><Loading /></el-icon>
- <span>正在从原始声音源文件克隆...</span>
- </div>
- <div v-else-if="voiceForm.clonedVoice" class="file-name">
- {{ voiceForm.clonedVoice }}
- <el-button type="text" size="small" @click="removeVoiceFile('clonedVoice')">
- <el-icon><CircleClose /></el-icon>
- </el-button>
- </div>
- <div v-else>
- <span class="no-clone-hint">请先上传原始声音源文件</span>
- </div>
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="dialogVisible = false">Cancel</el-button>
- <el-button type="primary" @click="submitForm">
- {{ dialogType === 'add' ? 'Add' : 'Save Changes' }}
- </el-button>
- </template>
- </el-dialog>
- <Pagination
- :current-page="currentPage"
- :per-page="perPage"
- :total-items="totalItems"
- :max-displayed-pages="maxDisplayedPages"
- :show-info="true"
- @page-changed="onPageChange"
- />
- </div>
- </template>
- <script>
- import { Edit, Delete, VideoPlay, Upload, Microphone, CircleClose, Loading } from '@element-plus/icons-vue'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import { useStore } from '../store'
- import Pagination from './util/Pagination.vue'
- import anycallService from "../service/anycallService";
- export default {
- name: 'VoiceManagement',
- components: {
- Pagination,
- Edit,
- Delete,
- VideoPlay,
- Upload,
- CircleClose,
- Loading,
- Microphone: {
- template: `<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg" data-v-029747aa="">
- <path fill="currentColor" d="M512 128a128 128 0 0 0-128 128v256a128 128 0 1 0 256 0V256a128 128 0 0 0-128-128zm0 64a64 64 0 0 1 64 64v256a64 64 0 1 1-128 0V256a64 64 0 0 1 64-64zM320 512a32 32 0 0 0-32 32v64a224 224 0 0 0 448 0v-64a32 32 0 0 0-64 0v64a160 160 0 0 1-320 0v-64a32 32 0 0 0-32-32z"></path>
- </svg>`
- }
- },
- data() {
- const store = useStore()
- return {
- currentPage: 1,
- perPage: 10,
- totalItems: store.voicesCount,
- maxDisplayedPages: 5,
- voices: store.voices,
- loading: false,
- dialogVisible: false,
- dialogType: 'add', // 'add' or 'edit'
- importDialogVisible: false,
- importFileName: '',
- voiceForm: {
- id: '',
- description: '',
- gender: '',
- originalVoice: '',
- clonedVoice: '',
- uploadTime: ''
- },
- currentVoice: null,
- rules: {
- name: [
- { required: true, message: 'Please enter voice name', trigger: 'blur' }
- ],
- description: [
- { required: true, message: 'Please enter voice description', trigger: 'blur' }
- ],
- gender: [
- { required: true, message: 'Please select gender', trigger: 'change' }
- ],
- originalVoice: [
- { required: true, message: 'Please upload original voice file', trigger: 'blur' }
- ]
- }
- }
- },
- methods: {
- async onPageChange(page) {
- this.currentPage = page
- // 这里可以添加加载数据的逻辑
- let pageData = {
- page: page,
- size: this.perPage,
- system: true,
- gender: 1
- };
- const response = await anycallService.voiceList(pageData);
- this.voices = response.data.data.content.map(item => ({
- id: item.id || '',
- name: item.name || '',
- gender: item.gender || '',
- ctime: new Date(item.ctime).getFullYear()+'-'+new Date(item.ctime).getMonth()+'-'+new Date(item.ctime).getDay() || '',
- }
- ));
- this.rolesCount = response.data.data.total;
- },
- // 处理Excel上传
- handleExcelUpload(file) {
- this.importDialogVisible = true;
- return false; // 阻止默认上传
- },
-
- // 确认导入
- confirmImport() {
- // 模拟导入处理
- ElMessage.success(`文件 ${this.importFileName} 导入成功`);
- this.importDialogVisible = false;
- this.importFileName = '';
- // 实际项目中应解析Excel文件并添加声音
- },
-
- // Play voice (simulation)
- playVoice(filename) {
- ElMessage.info(`Playing voice file: ${filename} (simulation)`)
- },
-
- // Show add dialog
- showAddDialog() {
- this.dialogType = 'add'
- this.voiceForm = {
- id: '',
- description: '',
- gender: '',
- originalVoice: '',
- clonedVoice: '',
- uploadTime: ''
- }
- this.dialogVisible = true
- },
-
- // Edit voice
- editVoice(voice) {
- this.dialogType = 'edit'
- this.currentVoice = voice
- this.voiceForm = { ...voice }
- this.dialogVisible = true
- },
-
- // Confirm delete
- confirmDelete(voice) {
- ElMessageBox.confirm(
- `Are you sure you want to delete voice "${voice.description}"?`,
- 'Warning',
- {
- confirmButtonText: 'Delete',
- cancelButtonText: 'Cancel',
- type: 'warning'
- }
- ).then(() => {
- const store = useStore()
- store.deleteVoice(voice.id)
- this.voices = store.voices
- ElMessage.success('Voice deleted successfully')
- }).catch(() => {})
- },
-
- // 上传前检查 - 只接受原始声音源文件
- beforeUploadVoice(file) {
- const isAudio = ['audio/mpeg', 'audio/wav', 'audio/mp3'].includes(file.type) || file.name.endsWith('.mp3') || file.name.endsWith('.wav');
- const isLt20M = file.size / 1024 / 1024 < 20;
-
- if (!isAudio) {
- ElMessage.error('请上传音频格式文件!');
- }
- if (!isLt20M) {
- ElMessage.error('上传文件大小不能超过20MB!');
- }
-
- return isAudio && isLt20M;
- },
-
- // 处理上传成功
- handleVoiceUploadSuccess(file, field) {
- // 模拟上传成功,实际应用中应该根据服务器返回设置文件路径
- this.voiceForm[field] = file.name;
- ElMessage.success(`${field === 'originalVoice' ? '原始' : '克隆'} 声音文件上传成功`);
-
- // 如果上传的是原始声音,模拟系统自动克隆过程
- if (field === 'originalVoice' && !this.voiceForm.clonedVoice) {
- // 模拟克隆过程的延迟
- setTimeout(() => {
- // 生成克隆文件名
- const originalFileName = file.name;
- const clonedFileName = originalFileName.replace('original-', 'cloned-');
- this.voiceForm.clonedVoice = clonedFileName;
- ElMessage.success('已从原始声音源文件成功克隆声音');
- }, 2000); // 模拟2秒的克隆时间
- }
- },
-
- // 处理上传错误
- handleUploadError() {
- ElMessage.error('File upload failed');
- },
-
- // 移除已上传的文件
- removeVoiceFile(field) {
- this.voiceForm[field] = '';
- },
-
- // Submit form
- submitForm() {
- this.$refs.voiceFormRef.validate((valid) => {
- if (valid) {
- const store = useStore()
- if (this.dialogType === 'add') {
- store.addVoice({
- ...this.voiceForm,
- id: Date.now().toString(),
- uploadTime: new Date().toLocaleDateString()
- })
- ElMessage.success('Voice added successfully')
- } else {
- store.updateVoice(this.currentVoice.id, this.voiceForm)
- ElMessage.success('Voice updated successfully')
- }
- this.voices = store.voices
- this.dialogVisible = false
- }
- })
- }
- }
- }
- </script>
- <style>
- .voice-management {
- padding: 20px;
- }
- .header {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 20px;
- }
- .header h2 {
- font-size: 24px;
- font-weight: 600;
- margin: 0;
- }
- .header-actions {
- display: flex;
- gap: 10px;
- }
- .import-btn {
- border-color: #dcdfe6;
- }
- .voice-table {
- margin-top: 20px;
- border-radius: 8px;
- overflow: hidden;
- }
- .voice-info {
- display: flex;
- align-items: center;
- gap: 12px;
- }
- .voice-icon {
- width: 40px;
- height: 40px;
- border-radius: 50%;
- background-color: #f0f2f5;
- display: flex;
- align-items: center;
- justify-content: center;
- color: #409eff;
- }
- .voice-details {
- display: flex;
- flex-direction: column;
- }
- .voice-name {
- font-weight: 500;
- }
- .voice-id {
- font-size: 12px;
- color: #909399;
- }
- .action-buttons {
- display: flex;
- gap: 10px;
- }
- .delete-btn {
- color: #f56c6c;
- }
- .upload-box {
- display: flex;
- align-items: center;
- gap: 10px;
- }
- .file-name {
- color: #909399;
- font-size: 14px;
- }
- .voice-dialog .el-form-item {
- margin-bottom: 20px;
- }
- .upload-voice {
- display: inline-block;
- }
- .file-name {
- margin-top: 10px;
- display: flex;
- align-items: center;
- gap: 10px;
- font-size: 14px;
- color: #606266;
- }
- .cloning-status {
- display: flex;
- align-items: center;
- gap: 10px;
- color: #409eff;
- font-size: 14px;
- }
- .no-clone-hint {
- color: #909399;
- font-size: 14px;
- }
- .voice-info {
- display: flex;
- align-items: center;
- gap: 10px;
- }
- .voice-name {
- font-weight: 500;
- }
- </style>
|