| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850 |
- <template>
- <div class="role-management">
- <div class="header">
- <h2>Role 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" plain>
- <el-icon><Upload /></el-icon>Import
- </el-button>
- </el-upload>
- <!-- <el-button type="primary" @click="getRoleData">-->
- <!-- select Role-->
- <!-- </el-button>-->
- <el-button type="primary" @click="showAddDialog">
- Create Role
- </el-button>
- </div>
- </div>
-
- <el-table :data="roles" v-loading="loading" style="width: 100%" class="role-table">
- <el-table-column label="Role" min-width="100">
- <template #default="scope">
- <div class="role-info">
- <el-avatar :src="scope.row.avatar" v-if="scope.row.avatar"></el-avatar>
- <el-avatar v-else>{{ scope.row.name.charAt(0) }}</el-avatar>
- <div class="role-details">
- <div class="role-name">{{ scope.row.name }}</div>
- <div class="role-id" hidden>ID: {{ scope.row.id }}</div>
- </div>
- </div>
- </template>
- </el-table-column>
- <!-- <el-table-column prop="description" label="Persona" min-width="200" />-->
- <el-table-column prop="callings" label="callings" width="200">
- <template #default="scope">
- {{ scope.row.callings }}
- </template>
- </el-table-column>
- <el-table-column prop="prompt" label="prompt" width="200">
- <template #default="scope">
- {{ scope.row.prompt }}
- </template>
- </el-table-column>
- <!-- <el-table-column prop="gender" label="Gender" width="100">-->
- <!-- <template #default="scope">-->
- <!-- {{ scope.row.gender === 'male' ? 'Male' : scope.row.gender === 'female' ? 'Female' : 'Other' }}-->
- <!-- </template>-->
- <!-- </el-table-column>-->
- <el-table-column prop="language" label="Language" width="100">
- <template #default="scope">
- {{ scope.row.language === 'zh' ? 'Chinese' :
- scope.row.language === 'en' ? 'English' :
- scope.row.language === 'jp' ? 'Japanese' :
- scope.row.language === 'kr' ? 'Korean' : scope.row.language }}
- </template>
- </el-table-column>
- <el-table-column label="USE VOICE" width="150">
- <template #default="scope">
- <div class="voice-details">
- <div class="voice-name">{{ scope.row.voiceName }}</div>
- <div class="voice-id" hidden>ID: {{ scope.row.voiceId }}</div>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="Actions" width="150">
- <template #default="scope">
- <div class="action-buttons">
- <el-button type="text" @click="editRole(scope.row)">
- <el-icon><Edit /></el-icon>
- </el-button>
- <!-- <el-button type="primary" @click="editRole(scope.row)">-->
- <!-- 推荐-->
- <!-- </el-button>-->
- <el-button type="primary" @click="changeTopFlag(scope.row);onPageChange(currentPage);">
- {{ scope.row.topFlag ===true ? '取消推荐': '推荐' }}
- </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/Edit Role Dialog -->
- <el-dialog
- :title="dialogType === 'add' ? 'Create New Role' : 'Edit Role'"
- v-model="dialogVisible"
- width="500px"
- class="role-dialog"
- >
- <el-form
- ref="voiceFormRef"
- :model="roleForm"
- :rules="rules"
- label-width="120px"
- >
- <el-form-item label="role callings" prop="name">
- <el-input v-model="roleForm.callings" placeholder="Enter callings" />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="dialogVisible = false">Cancel</el-button>
- <el-button type="primary" @click="updateCallings(roleForm.id,roleForm.callings); onPageChange(currentPage);dialogVisible = false">
- {{ dialogType === 'add' ? 'Create' : 'Save Changes' }}
- </el-button>
- </template>
- </el-dialog>
- <!-- Add Dialog -->
- <el-dialog
- :title="'Create New Role'"
- v-model="addDialogVisible"
- width="500px"
- class="role-dialog"
- >
- <el-form
- :model="roleForm"
- :rules="rules"
- label-width="120px"
- >
- <el-form-item label="name" prop="name">
- <el-input v-model="roleForm.name" placeholder="Enter name" />
- </el-form-item>
- <el-form-item label="prompt" prop="prompt">
- <el-input v-model="roleForm.prompt" placeholder="Enter prompt" />
- </el-form-item>
- <el-form-item label="Photo" prop="Photo">
- <el-input v-model="roleForm.photo" placeholder="Enter Photo" />
- </el-form-item>
- <el-form-item label="Language" prop="Language">
- <el-input v-model="roleForm.language" placeholder="Enter Language" />
- </el-form-item>
- <el-form-item label="照片文件">
- <el-upload
- class="avatar-uploader"
- action=""
- :auto-upload="false"
- :on-change="handlePhotoUpload"
- accept="image/*"
- >
- <el-button size="small" type="primary">
- <el-icon><Upload /></el-icon> 上传照片
- </el-button>
- <div v-if="photoFileName" class="photo-file-name">{{ photoFileName }}</div>
- </el-upload>
- </el-form-item>
- <el-form-item label="声音文件">
- <el-upload
- class="voice-uploader"
- action=""
- :auto-upload="false"
- :on-change="handleVoiceUpload"
- accept="audio/*"
- >
- <el-button size="small" type="primary">
- <el-icon><Upload /></el-icon> 上传声音文件
- </el-button>
- <div v-if="voiceFileName" class="voice-file-name">{{ voiceFileName }}</div>
- </el-upload>
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="addDialogVisible = false">Cancel</el-button>
- <el-button type="primary" @click="addCallings(roleForm.id,roleForm.callings); onPageChange(currentPage); addDialogVisible = false">
- {{ '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, Upload, Headset, CircleClose, Loading } from '@element-plus/icons-vue'
- import { ElMessage, ElMessageBox } from 'element-plus'
- import { useStore } from '../store'
- import anycallService from '../service/anycallService.js';
- import Pagination from './util/Pagination.vue'
- import cdnService from "../service/cdnService";
- export default {
- name: 'RoleManagement',
- components: {
- Pagination,
- Edit,
- Delete,
- Upload,
- Headset,
- CircleClose,
- Loading
- },
- data() {
- const store = useStore()
- return {
- currentPage: 1,
- perPage: 10,
- totalItems: store.rolesCount,
- maxDisplayedPages: 5,
- file: null,
- roles: store.roles,
- voices: store.voices,
- loading: false,
- dialogVisible: false,
- addDialogVisible: false,
- dialogType: 'add', // 'add' or 'edit'
- importDialogVisible: false,
- importFileName: '',
- roleForm: {
- id: '',
- name: '',
- description: '',
- gender: '',
- language: '',
- avatar: '',
- voiceId: '',
- photoFileName: '',
- voiceFileName: '',
- clonedVoiceFileName: '',
- isCloning: false,
- clonedVoice: false
- },
- currentRole: null,
- rules: {
- name: [
- {required: true, message: '请输入角色名称', trigger: 'blur'}
- ],
- description: [
- {required: true, message: '请输入角色描述', trigger: 'blur'}
- ],
- gender: [
- {required: true, message: '请选择性别', trigger: 'change'}
- ],
- language: [
- {required: true, message: '请选择语言', trigger: 'change'}
- ],
- voiceFileName: [
- {required: true, message: '请上传原始声音文件', trigger: 'blur'}
- ]
- }
- };
- },
- methods: {
- async handlePhotoUpload(file) {
- // cdnService.uploadVoice(file);
- // this.file = file;
- let formData = {
- file: file.raw
- }
- cdnService.upload(formData).then(res => {
- if (res.data.code === 0) {
- this.photo = res.data.data;
- }else {
- this.photo = '';
- }
- console.log(res.data.data);
- console.log(this.photo);
- });
- },
- handleVoiceUpload(file) {
- let response = cdnService.uploadVoice(file);
- console.log(response.data);
- // this.voiceFileName = file.name;
- // todo 实际项目中应上传文件到服务器
- },
- async onPageChange(page) {
- this.currentPage = page
- // 这里可以添加加载数据的逻辑
- let pageData = {
- page: page,
- size: this.perPage,
- name: ""
- };
- const response = await anycallService.anycallPage(pageData);
- this.roles = response.data.data.content.map(item => ({
- id: item.id || '',
- name: item.name || '',
- prompt: item.prompt || '',
- callings: item.callings || '',
- language: item.language || '',
- photo: item.photo || '',
- voice: item.voice || '',
- voiceName: item.voiceName || '',
- topFlag: item.topFlag || '',
- }));
- this.rolesCount = response.data.data.total;
- },
- async changeTopFlag(row) {
- // console.log(row.id);
- // console.log(row.id);
- // console.log(row.topFlag !== true);
- let data = {
- cloneId: row.id,
- topFlag: row.topFlag !== true
- };
- console.log(data);
- const response = await anycallService.updateTopFlag(data);
- ElMessage.success('推荐状态修改成功')
- },
- async addCallings(id,callings){
- let data = {
- cloneId: id,
- callings: callings
- };
- // const response = await anycallService.updateCallings(data);
- ElMessage.success('添加成功')
- const store = useStore()
- this.roles = store.roles
- },
- async updateCallings(id,callings){
- let data = {
- cloneId: id,
- callings: callings
- };
- const response = await anycallService.updateCallings(data);
- ElMessage.success('callings修改成功')
- const store = useStore()
- this.roles = store.roles
- },
- // 处理Excel上传
- handleExcelUpload(file) {
- this.importFileName = file.name;
- this.importDialogVisible = true;
- return false; // 阻止默认上传
- },
-
- // 确认导入
- confirmImport() {
- // 模拟导入处理
- ElMessage.success(`文件 ${this.importFileName} 导入成功`);
- this.importDialogVisible = false;
- this.importFileName = '';
- // 实际项目中应解析Excel文件并添加角色
- },
-
- // 上传前检查头像
- beforeUploadAvatar(file) {
- const isImage = ['image/jpeg', 'image/png', 'image/gif'].includes(file.type) ||
- file.name.endsWith('.jpg') || file.name.endsWith('.jpeg') ||
- file.name.endsWith('.png') || file.name.endsWith('.gif');
- const isLt5M = file.size / 1024 / 1024 < 5;
-
- if (!isImage) {
- ElMessage.error('请上传图片格式文件!');
- }
- if (!isLt5M) {
- ElMessage.error('上传文件大小不能超过5MB!');
- }
-
- return isImage && isLt5M;
- },
-
- // 上传前检查声音文件 - 只接受原始声音源文件
- 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) {
- // 模拟上传成功,实际应用中应该根据服务器返回设置文件信息
- this.roleForm.voiceFileName = file.name;
- ElMessage.success('原始声音文件上传成功');
-
- // 模拟系统自动克隆过程
- this.roleForm.isCloning = true;
-
- // 模拟克隆过程的延迟
- setTimeout(() => {
- // 生成克隆文件名
- const originalFileName = file.name;
- const clonedFileName = originalFileName.replace('original-', 'cloned-');
-
- // 如果不在表单提交过程中,直接更新表单状态
- if (!this.dialogVisible) {
- this.roleForm.clonedVoiceFileName = clonedFileName;
- this.roleForm.isCloning = false;
- this.roleForm.clonedVoice = true;
- ElMessage.success('已从原始声音源文件成功克隆声音');
- }
- // 如果是在表单提交过程中,更新声音数据中的克隆状态
- else if (this.roleForm.voiceId) {
- const store = useStore();
- store.updateVoice(this.roleForm.voiceId, {
- clonedVoice: clonedFileName,
- isCloning: false
- });
- // 刷新本地数据
- this.voices = store.voices;
- this.roles = store.roles;
- ElMessage.success('角色的声音克隆已完成');
- }
- }, 2000); // 模拟2秒的克隆时间
- },
-
- // 移除声音文件
- removeVoiceFile() {
- this.roleForm.voiceFileName = '';
- this.roleForm.clonedVoiceFileName = '';
- this.roleForm.isCloning = false;
- this.roleForm.clonedVoice = false;
- this.roleForm.voiceId = '';
- },
-
- // 自定义HTTP请求处理器 - 模拟上传过程,阻止实际请求
- handleHttpRequest(options) {
- // 立即阻止所有默认行为,这是最关键的一步
- if (options && options.onSuccess && typeof options.onSuccess === 'function') {
- console.log('Custom HTTP request handler triggered, BLOCKING actual request');
-
- // 创建一个完整的模拟成功响应对象
- const mockResponse = {
- code: 200,
- message: 'Success',
- data: {
- fileName: options.file.name,
- url: '#'
- },
- status: 200,
- statusText: 'OK'
- };
-
- // 直接调用成功回调函数,模拟成功上传
- setTimeout(() => {
- try {
- options.onSuccess(mockResponse, options.file);
- } catch (error) {
- console.error('Error in onSuccess callback:', error);
- }
- }, 100);
-
- // 在所有条件下都返回false,确保阻止默认行为
- return false;
- }
-
- console.error('Invalid options or missing onSuccess callback');
-
- // 即使出错也返回false阻止请求
- return false;
- },
- // 根据ID获取声音
- getVoiceById(voiceId) {
- return this.voices.find(v => v.id === voiceId);
- },
-
- // 播放声音
- playVoice(voiceId, isCloned = false) {
- // 这里是模拟播放声音的逻辑
- // 在实际应用中,应该根据voiceId获取声音文件并播放
- if (isCloned) {
- const voice = this.getVoiceById(voiceId);
- if (voice) {
- ElMessage.success(`正在播放克隆声音: ${voice.clonedVoice}`);
- } else {
- ElMessage.success('正在播放克隆声音');
- }
- } else {
- // 查找对应的声音名称
- const voice = this.getVoiceById(voiceId);
- if (voice) {
- ElMessage.success(`正在播放声音: ${voice.name}`);
- } else {
- ElMessage.success('正在播放原始声音');
- }
- }
- },
-
- // 处理头像上传成功
- handleAvatarUploadSuccess(file) {
- try {
- // 模拟上传成功,实际应用中应该根据服务器返回设置图片URL
- // 使用FileReader读取图片文件,以便在本地预览
- const reader = new FileReader();
- reader.onload = (e) => {
- this.roleForm.avatar = e.target.result;
- };
- reader.readAsDataURL(file);
-
- ElMessage.success('头像上传成功');
- } catch (error) {
- console.error('Error uploading avatar:', error);
- ElMessage.error('处理头像图片失败');
- this.handleUploadError();
- }
- },
-
- // 处理上传错误
- handleUploadError(err, file, fileList) {
- // 阻止默认错误处理
- if (err && err.status === 404) {
- ElMessage.warning('模拟环境中上传端点不可用');
- return false;
- }
- ElMessage.error('上传失败,请重试');
- return false;
- },
-
- // 移除头像
- removeAvatar() {
- this.roleForm.avatar = '';
- ElMessage.success('Avatar removed');
- },
- showAddDialog() {
- this.roleForm = {
- id: '',
- name: '',
- description: '',
- gender: '',
- language: '',
- avatar: '',
- voiceId: '',
- voiceFileName: '',
- clonedVoiceFileName: '',
- isCloning: false,
- clonedVoice: false
- }
- this.addDialogVisible = true
- },
- editRole(role) {
- this.dialogType = 'edit'
- this.currentRole = role
-
- // 初始化表单数据
- this.roleForm = { ...role }
-
- // 如果有voiceId,查找对应的声音文件信息
- if (role.voiceId) {
- const voice = this.getVoiceById(role.voiceId)
- if (voice) {
- this.roleForm.voiceFileName = voice.originalVoice
- this.roleForm.clonedVoiceFileName = voice.clonedVoice
- this.roleForm.clonedVoice = !!voice.clonedVoice
- }
- }
-
- this.dialogVisible = true
- },
- confirmDelete(role) {
- ElMessageBox.confirm(
- `Are you sure you want to delete role "${role.name}"?`,
- 'Warning',
- {
- confirmButtonText: 'Delete',
- cancelButtonText: 'Cancel',
- type: 'warning'
- }
- ).then(() => {
- const store = useStore()
- store.deleteRole(role.id)
- this.roles = store.roles
- ElMessage.success('Role deleted successfully')
- }).catch(() => {})
- },
- submitForm() {
- this.$refs.roleFormRef.validate((valid) => {
- if (valid) {
- const store = useStore()
-
- // 先创建或更新声音数据
- if (this.dialogType === 'add' || !this.roleForm.voiceId) {
- // 创建新声音
- // 检查克隆是否已完成,如果未完成则标记为克隆中
- const isCloning = this.roleForm.isCloning || !this.roleForm.clonedVoice;
- const newVoice = {
- id: Date.now().toString(),
- name: `${this.roleForm.name} Voice`,
- originalVoice: this.roleForm.voiceFileName,
- clonedVoice: isCloning ? null : this.roleForm.clonedVoiceFileName,
- gender: this.roleForm.gender,
- description: `${this.roleForm.name}的声音`,
- uploadTime: new Date().toLocaleDateString(),
- isCloning: isCloning
- }
- store.addVoice(newVoice)
- this.roleForm.voiceId = newVoice.id
- } else {
- // 更新现有声音
- const isUpdatingCloning = this.roleForm.isCloning || !this.roleForm.clonedVoice;
- store.updateVoice(this.roleForm.voiceId, {
- originalVoice: this.roleForm.voiceFileName,
- clonedVoice: isUpdatingCloning ? null : this.roleForm.clonedVoiceFileName,
- gender: this.roleForm.gender,
- isCloning: isUpdatingCloning
- })
- }
-
- // 然后创建或更新角色
- if (this.dialogType === 'add') {
- store.addRole({
- ...this.roleForm,
- id: Date.now().toString(),
- createdAt: new Date().toLocaleString()
- })
- ElMessage.success('角色创建成功')
- } else {
- store.updateRole(this.currentRole.id, this.roleForm)
- ElMessage.success('角色更新成功')
- }
-
- // 更新本地数据
- this.roles = store.roles
- this.voices = store.voices
- this.dialogVisible = false
- }
- })
- }
- }
- }
- </script>
- <style>
- .role-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;
- }
- /* 克隆中状态样式 */
- .loading-container {
- display: flex;
- align-items: center;
- gap: 5px;
- color: #909399;
- }
- .loading-icon {
- animation: rotate 1s linear infinite;
- font-size: 14px;
- }
- .loading-text {
- font-size: 12px;
- }
- @keyframes rotate {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
- }
- .role-table {
- margin-top: 20px;
- border-radius: 8px;
- overflow: hidden;
- }
- .role-info {
- display: flex;
- align-items: center;
- gap: 12px;
- }
- .role-details {
- display: flex;
- flex-direction: column;
- }
- .role-name {
- font-weight: 500;
- }
- .role-id {
- font-size: 12px;
- color: #909399;
- }
- .action-buttons {
- display: flex;
- gap: 10px;
- }
- .delete-btn {
- color: #f56c6c;
- }
- .role-dialog .el-form-item {
- margin-bottom: 20px;
- }
- .avatar-uploader {
- display: inline-block;
- width: 100px;
- height: 100px;
- border-radius: 50%;
- overflow: hidden;
- border: 1px dashed #d9d9d9;
- cursor: pointer;
- position: relative;
- background-color: #f0f2f5;
- display: flex;
- align-items: center;
- justify-content: center;
- }
- .avatar-uploader:hover {
- border-color: #409eff;
- }
- .avatar {
- width: 100%;
- height: 100%;
- object-fit: cover;
- }
- .avatar-actions {
- margin-top: 10px;
- }
- .avatar-uploader .el-button {
- width: 100%;
- height: 100%;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- background-color: transparent;
- border: none;
- }
- .upload-voice {
- margin-bottom: 10px;
- }
- .file-name {
- display: flex;
- align-items: center;
- padding: 8px 12px;
- background-color: #f5f7fa;
- border-radius: 4px;
- margin-top: 5px;
- }
- .file-name .el-button {
- margin-left: auto;
- }
- .cloning-status {
- display: flex;
- align-items: center;
- padding: 8px 12px;
- margin-top: 5px;
- color: #67c23a;
- }
- .cloning-status .el-icon {
- margin-right: 5px;
- animation: spin 1s linear infinite;
- }
- @keyframes spin {
- 0% { transform: rotate(0deg); }
- 100% { transform: rotate(360deg); }
- }
- </style>
|