|
@@ -4,19 +4,80 @@
|
|
|
<div class="digital-human-container">
|
|
|
<!-- 左侧列表区域 -->
|
|
|
<div class="list-area">
|
|
|
- <fs-crud ref="crudRef" v-bind="crudBinding" @edit-digital-human="openEditForm"> </fs-crud>
|
|
|
+ <fs-crud ref="crudRef" v-bind="crudBinding" @add-digital-human="openAddForm" @edit-digital-human="openEditForm">
|
|
|
+ <!-- 自定义行操作按钮 -->
|
|
|
+ <template #cell-rowHandle-middle="scope">
|
|
|
+ <!-- 查看按钮 -->
|
|
|
+ <el-button
|
|
|
+ size="small"
|
|
|
+ type="text"
|
|
|
+ @click="openViewForm(scope.row)"
|
|
|
+ >
|
|
|
+ 查看<el-icon><View /></el-icon>
|
|
|
+ </el-button>
|
|
|
+
|
|
|
+ <!-- 编辑按钮 -->
|
|
|
+ <el-button
|
|
|
+ size="small"
|
|
|
+ type="text"
|
|
|
+ @click="openEditForm(scope.row)"
|
|
|
+ >
|
|
|
+ 编辑<el-icon><Edit /></el-icon>
|
|
|
+ </el-button>
|
|
|
+
|
|
|
+ <!-- 删除按钮 -->
|
|
|
+ <el-button
|
|
|
+ size="small"
|
|
|
+ type="text"
|
|
|
+ @click="handleDelete(scope.row)"
|
|
|
+ class="text-danger"
|
|
|
+ >
|
|
|
+ 删除<el-icon><Delete /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ </template>
|
|
|
+ </fs-crud>
|
|
|
</div>
|
|
|
|
|
|
<!-- 将侧边栏表单改为弹窗 -->
|
|
|
<el-dialog
|
|
|
v-model="showForm"
|
|
|
- :title="formMode === 'add' ? '新增数字人' : '编辑数字人'"
|
|
|
+ :title="getDialogTitle()"
|
|
|
width="500px"
|
|
|
:close-on-click-modal="false"
|
|
|
:destroy-on-close="true"
|
|
|
>
|
|
|
- <!-- 添加表单内容 -->
|
|
|
- <el-form ref="formRef" :model="formData" label-width="100px" class="form-content">
|
|
|
+ <!-- 查看模式使用 el-descriptions 展示数据 -->
|
|
|
+ <el-descriptions v-if="formMode === 'view'" :column="1" border>
|
|
|
+ <el-descriptions-item label="名称">{{ formData.name }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="头像">
|
|
|
+ <img v-if="formData.avatar_url" :src="formData.avatar_url" class="avatar" />
|
|
|
+ <span v-else>无头像</span>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="模板视频">
|
|
|
+ <video
|
|
|
+ v-if="formData.template_video_url"
|
|
|
+ :src="formData.template_video_url"
|
|
|
+ class="video-preview"
|
|
|
+ controls>
|
|
|
+ </video>
|
|
|
+ <span v-else>无视频</span>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="状态">
|
|
|
+ <el-tag :type="formData.status === 1 ? 'success' : 'info'">
|
|
|
+ {{ formData.status === 1 ? '启用' : '禁用' }}
|
|
|
+ </el-tag>
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="语音ID">
|
|
|
+ {{ getVoiceName(formData.config.voice_id) }}
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="风格">
|
|
|
+ {{ getStyleName(formData.config.style) }}
|
|
|
+ </el-descriptions-item>
|
|
|
+ <el-descriptions-item label="描述">{{ formData.description || '无' }}</el-descriptions-item>
|
|
|
+ </el-descriptions>
|
|
|
+
|
|
|
+ <!-- 添加/编辑表单内容 -->
|
|
|
+ <el-form v-else ref="formRef" :model="formData" label-width="100px" class="form-content">
|
|
|
<el-form-item label="名称" prop="name" :rules="[{ required: true, message: '请输入名称', trigger: 'blur' }]">
|
|
|
<el-input v-model="formData.name" placeholder="请输入数字人名称"></el-input>
|
|
|
</el-form-item>
|
|
@@ -91,8 +152,8 @@
|
|
|
|
|
|
<template #footer>
|
|
|
<div class="dialog-footer">
|
|
|
- <el-button @click="closeForm">取消</el-button>
|
|
|
- <el-button type="primary" @click="submitForm">提交</el-button>
|
|
|
+ <el-button @click="closeForm">{{ formMode === 'view' ? '关闭' : '取消' }}</el-button>
|
|
|
+ <el-button v-if="formMode !== 'view'" type="primary" @click="submitForm">提交</el-button>
|
|
|
</div>
|
|
|
</template>
|
|
|
</el-dialog>
|
|
@@ -104,11 +165,11 @@
|
|
|
import { onMounted, ref, reactive } from 'vue';
|
|
|
import { useFs } from '@fast-crud/fast-crud';
|
|
|
import { createCrudOptions } from './crud';
|
|
|
-import { GetPermission, AddObj, UpdateObj } from './api';
|
|
|
+import { GetPermission, AddObj, UpdateObj, DelObj } from './api';
|
|
|
import * as api from './api'; // Import the full api module
|
|
|
import { handleColumnPermission } from '/@/utils/columnPermission';
|
|
|
-import { ElMessage } from 'element-plus';
|
|
|
-import { Close, Plus } from '@element-plus/icons-vue';
|
|
|
+import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
+import { Close, Plus, Edit, Delete, View } from '@element-plus/icons-vue';
|
|
|
import { Session } from '/@/utils/storage';
|
|
|
|
|
|
const apiBaseUrl = import.meta.env.VITE_API_URL;
|
|
@@ -118,7 +179,7 @@ const { crudBinding, crudRef, crudExpose, crudOptions, resetCrudOptions } = useF
|
|
|
// 表单模式:add 或 edit
|
|
|
const formMode = ref('add');
|
|
|
const showForm = ref(false);
|
|
|
-const formData = ref({
|
|
|
+const defaultFormData = {
|
|
|
name: '',
|
|
|
description: '',
|
|
|
avatar_url: '',
|
|
@@ -128,7 +189,9 @@ const formData = ref({
|
|
|
voice_id: 'xiaoming_voice_premium',
|
|
|
style: 'professional'
|
|
|
}
|
|
|
-});
|
|
|
+};
|
|
|
+
|
|
|
+const formData = ref({...defaultFormData});
|
|
|
|
|
|
// Add formRef reference
|
|
|
const formRef = ref();
|
|
@@ -136,33 +199,53 @@ const formRef = ref();
|
|
|
// 打开添加表单
|
|
|
const openAddForm = () => {
|
|
|
formMode.value = 'add';
|
|
|
- formData.value = {
|
|
|
- name: '',
|
|
|
- description: '',
|
|
|
- avatar_url: '',
|
|
|
- status: 1,
|
|
|
- template_video_url: '',
|
|
|
- config: {
|
|
|
- voice_id: 'xiaoming_voice_premium',
|
|
|
- style: 'professional'
|
|
|
- }
|
|
|
- };
|
|
|
+ formData.value = {...defaultFormData};
|
|
|
showForm.value = true;
|
|
|
};
|
|
|
|
|
|
// 打开编辑表单
|
|
|
const openEditForm = (row: any) => {
|
|
|
+ console.log('编辑行数据:', row);
|
|
|
formMode.value = 'edit';
|
|
|
- formData.value = JSON.parse(JSON.stringify(row)); // 深拷贝
|
|
|
+ // 深拷贝行数据
|
|
|
+ const rowData = JSON.parse(JSON.stringify(row));
|
|
|
|
|
|
- // 确保config对象存在
|
|
|
- if (!formData.value.config) {
|
|
|
- formData.value.config = {
|
|
|
- voice_id: 'xiaoming_voice_premium',
|
|
|
- style: 'professional'
|
|
|
- };
|
|
|
+ // 确保 config 是对象格式
|
|
|
+ if (typeof rowData.config === 'string') {
|
|
|
+ try {
|
|
|
+ rowData.config = JSON.parse(rowData.config);
|
|
|
+ } catch (e) {
|
|
|
+ console.error('解析config失败:', e);
|
|
|
+ rowData.config = {...defaultFormData.config};
|
|
|
+ }
|
|
|
+ } else if (!rowData.config) {
|
|
|
+ rowData.config = {...defaultFormData.config};
|
|
|
}
|
|
|
|
|
|
+ formData.value = rowData;
|
|
|
+ showForm.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+// 打开查看表单(只读模式)
|
|
|
+const openViewForm = (row: any) => {
|
|
|
+ console.log('查看行数据:', row);
|
|
|
+ formMode.value = 'view';
|
|
|
+ // 深拷贝行数据
|
|
|
+ const rowData = JSON.parse(JSON.stringify(row));
|
|
|
+
|
|
|
+ // 确保 config 是对象格式
|
|
|
+ if (typeof rowData.config === 'string') {
|
|
|
+ try {
|
|
|
+ rowData.config = JSON.parse(rowData.config);
|
|
|
+ } catch (e) {
|
|
|
+ console.error('解析config失败:', e);
|
|
|
+ rowData.config = {...defaultFormData.config};
|
|
|
+ }
|
|
|
+ } else if (!rowData.config) {
|
|
|
+ rowData.config = {...defaultFormData.config};
|
|
|
+ }
|
|
|
+
|
|
|
+ formData.value = rowData;
|
|
|
showForm.value = true;
|
|
|
};
|
|
|
|
|
@@ -217,21 +300,62 @@ const beforeVideoUpload = (file: any) => {
|
|
|
const submitForm = async () => {
|
|
|
try {
|
|
|
await formRef.value.validate();
|
|
|
+
|
|
|
+ // 处理配置对象
|
|
|
+ const submitData = {...formData.value};
|
|
|
+ if (submitData.config && typeof submitData.config === 'object') {
|
|
|
+ // 确保 config 符合类型要求
|
|
|
+ const config = {
|
|
|
+ voice_id: submitData.config.voice_id || '',
|
|
|
+ style: submitData.config.style || ''
|
|
|
+ };
|
|
|
+ // 将config转为字符串
|
|
|
+ submitData.config = JSON.stringify(config) as any; // 使用类型断言解决类型错误
|
|
|
+ }
|
|
|
+
|
|
|
+ // 在发送请求前,确保config是字符串类型
|
|
|
if (formMode.value === 'add') {
|
|
|
- await AddObj(formData.value);
|
|
|
+ await AddObj(submitData);
|
|
|
ElMessage.success('添加成功');
|
|
|
} else {
|
|
|
- await UpdateObj(formData.value);
|
|
|
+ await UpdateObj(submitData);
|
|
|
ElMessage.success('更新成功');
|
|
|
}
|
|
|
+
|
|
|
closeForm();
|
|
|
- // 刷新列表
|
|
|
- fetchData();
|
|
|
+ crudExpose.doRefresh();
|
|
|
} catch (error) {
|
|
|
console.error('表单提交错误:', error);
|
|
|
+ ElMessage.error('提交失败,请检查表单信息');
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+// 处理删除
|
|
|
+const handleDelete = (row: any) => {
|
|
|
+ ElMessageBox.confirm(
|
|
|
+ `确定要删除数字人 "${row.name}" 吗?`,
|
|
|
+ '删除确认',
|
|
|
+ {
|
|
|
+ confirmButtonText: '确定',
|
|
|
+ cancelButtonText: '取消',
|
|
|
+ type: 'warning',
|
|
|
+ }
|
|
|
+ )
|
|
|
+ .then(async () => {
|
|
|
+ try {
|
|
|
+ await DelObj(row.id);
|
|
|
+ ElMessage.success('删除成功');
|
|
|
+ crudExpose.doRefresh();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('删除失败:', error);
|
|
|
+ ElMessage.error('删除失败,请稍后重试');
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .catch(() => {
|
|
|
+ // 用户取消删除操作
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
// Define the fetchData function
|
|
|
const fetchData = () => {
|
|
|
crudExpose.doRefresh();
|
|
@@ -254,6 +378,38 @@ onMounted(async () => {
|
|
|
// 刷新
|
|
|
crudExpose.doRefresh();
|
|
|
});
|
|
|
+
|
|
|
+// 获取对话框标题
|
|
|
+const getDialogTitle = () => {
|
|
|
+ if (formMode.value === 'add') return '新增数字人';
|
|
|
+ if (formMode.value === 'edit') return '编辑数字人';
|
|
|
+ return '查看数字人';
|
|
|
+};
|
|
|
+
|
|
|
+// 语音ID映射
|
|
|
+const voiceOptions = [
|
|
|
+ { label: '小明(高级)', value: 'xiaoming_voice_premium' },
|
|
|
+ { label: '小红', value: 'xiaohong_voice' }
|
|
|
+];
|
|
|
+
|
|
|
+// 风格映射
|
|
|
+const styleOptions = [
|
|
|
+ { label: '专业', value: 'professional' },
|
|
|
+ { label: '友好', value: 'friendly' },
|
|
|
+ { label: '严肃', value: 'serious' }
|
|
|
+];
|
|
|
+
|
|
|
+// 获取语音名称
|
|
|
+const getVoiceName = (voiceId: string) => {
|
|
|
+ const option = voiceOptions.find(item => item.value === voiceId);
|
|
|
+ return option ? option.label : voiceId;
|
|
|
+};
|
|
|
+
|
|
|
+// 获取风格名称
|
|
|
+const getStyleName = (style: string) => {
|
|
|
+ const option = styleOptions.find(item => item.value === style);
|
|
|
+ return option ? option.label : style;
|
|
|
+};
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
@@ -326,10 +482,11 @@ onMounted(async () => {
|
|
|
}
|
|
|
|
|
|
.video-preview {
|
|
|
- width: 100px;
|
|
|
- height: 100px;
|
|
|
+ width: 200px;
|
|
|
+ height: auto;
|
|
|
+ max-height: 150px;
|
|
|
display: block;
|
|
|
- object-fit: cover;
|
|
|
+ object-fit: contain;
|
|
|
}
|
|
|
|
|
|
.video-uploader-placeholder {
|
|
@@ -338,4 +495,17 @@ onMounted(async () => {
|
|
|
border: 1px dashed #d9d9d9;
|
|
|
border-radius: 6px;
|
|
|
}
|
|
|
+
|
|
|
+/* 添加删除按钮的危险样式 */
|
|
|
+.text-danger {
|
|
|
+ color: #F56C6C;
|
|
|
+}
|
|
|
+.text-danger:hover {
|
|
|
+ color: #f78989;
|
|
|
+}
|
|
|
+
|
|
|
+/* 添加查看模式的样式 */
|
|
|
+.el-descriptions {
|
|
|
+ margin-bottom: 20px;
|
|
|
+}
|
|
|
</style>
|