123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526 |
- <template>
- <fs-page>
- <!-- 添加一个容器来分割页面 -->
- <div class="digital-human-container">
- <!-- 左侧列表区域 -->
- <div class="list-area">
- <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="getDialogTitle()"
- width="500px"
- :close-on-click-modal="false"
- :destroy-on-close="true"
- >
- <!-- 查看模式使用 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_type) }}
- </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>
- <el-form-item label="头像" prop="avatar_url" :rules="[{ required: true, message: '请上传头像', trigger: 'change' }]">
- <el-upload
- class="avatar-uploader"
- :action="`${apiBaseUrl}/api/system/admin_upload/`"
- :headers="{
- 'Authorization': `JWT ${Session.get('token') || ''}`
- }"
- :data="{
- tenant_id: '1'
- }"
- :show-file-list="false"
- :on-success="handleAvatarSuccess"
- :before-upload="beforeAvatarUpload">
- <img v-if="formData.avatar_url" :src="formData.avatar_url" class="avatar" />
- <el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
- </el-upload>
- </el-form-item>
- <el-form-item label="模板视频" prop="template_video_url">
- <el-upload
- class="video-uploader"
- :action="`${apiBaseUrl}/api/system/admin_upload/`"
- :headers="{
- 'Authorization': `JWT ${Session.get('token') || ''}`
- }"
- :data="{
- tenant_id: '1'
- }"
- :show-file-list="false"
- :on-success="handleVideoSuccess"
- :before-upload="beforeVideoUpload"
- accept="video/*">
- <video
- v-if="formData.template_video_url"
- :src="formData.template_video_url"
- class="video-preview"
- controls>
- </video>
- <div v-else class="video-uploader-placeholder">
- <el-icon class="video-uploader-icon"><Plus /></el-icon>
- <div>点击上传视频</div>
- </div>
- </el-upload>
- </el-form-item>
- <el-form-item label="状态" prop="status">
- <el-switch v-model="formData.status" :active-value="1" :inactive-value="0"></el-switch>
- </el-form-item>
-
- <el-form-item label="语音ID" prop="config.voice_type">
- <el-select v-model="formData.config.voice_type" placeholder="请选择语音">
- <el-option v-for="(item,index) in voiceList" :key="index" :label="item.label" :value="item.value"></el-option>
- <!-- 可以根据实际情况添加更多选项 -->
- </el-select>
- </el-form-item>
-
- <el-form-item label="风格" prop="config.style">
- <el-select v-model="formData.config.style" placeholder="请选择风格">
- <el-option label="专业" value="professional"></el-option>
- <el-option label="友好" value="friendly"></el-option>
- <el-option label="严肃" value="serious"></el-option>
- <!-- 可以根据实际情况添加更多选项 -->
- </el-select>
- </el-form-item>
- <el-form-item label="描述" prop="description">
- <el-input v-model="formData.description" type="textarea" placeholder="请输入描述"></el-input>
- </el-form-item>
-
- </el-form>
-
- <template #footer>
- <div class="dialog-footer">
- <el-button @click="closeForm">{{ formMode === 'view' ? '关闭' : '取消' }}</el-button>
- <el-button v-if="formMode !== 'view'" type="primary" @click="submitForm">提交</el-button>
- </div>
- </template>
- </el-dialog>
- </div>
- </fs-page>
- </template>
- <script lang="ts" setup name="areas">
- import { onMounted, ref, reactive } from 'vue';
- import { useFs } from '@fast-crud/fast-crud';
- import { createCrudOptions } from './crud';
- import { GetPermission, AddObj, UpdateObj, DelObj,GetVoiceList } from './api';
- import * as api from './api'; // Import the full api module
- import { handleColumnPermission } from '/@/utils/columnPermission';
- 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;
- const { crudBinding, crudRef, crudExpose, crudOptions, resetCrudOptions } = useFs({ createCrudOptions });
- // 表单模式:add 或 edit
- const formMode = ref('add');
- const showForm = ref(false);
- const defaultFormData = {
- name: '',
- description: '',
- avatar_url: '',
- status: 1,
- template_video_url: '',
- config: {
- voice_type: '',
- style: 'professional'
- }
- };
- const formData = ref({...defaultFormData});
- // Add formRef reference
- const formRef = ref();
- // 打开添加表单
- const openAddForm = () => {
- formMode.value = 'add';
- formData.value = {...defaultFormData};
- showForm.value = true;
- };
- // 打开编辑表单
- const openEditForm = (row: any) => {
- console.log('编辑行数据:', row);
- formMode.value = 'edit';
- // 深拷贝行数据
- 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;
- };
- // 打开查看表单(只读模式)
- 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;
- };
- // 关闭表单
- const closeForm = () => {
- showForm.value = false;
- };
- // 头像上传成功处理
- const handleAvatarSuccess = (res: any, file: any) => {
- // 根据实际接口返回格式调整
- console.log(res);
- formData.value.avatar_url = res.url;
- };
- // 头像上传前验证
- const beforeAvatarUpload = (file: any) => {
- const isImage = file.type.startsWith('image/');
- const isLt2M = file.size / 1024 / 1024 < 2;
- if (!isImage) {
- ElMessage.error('上传头像图片只能是图片格式!');
- }
- if (!isLt2M) {
- ElMessage.error('上传头像图片大小不能超过 2MB!');
- }
- return isImage && isLt2M;
- };
- // 视频上传成功处理
- const handleVideoSuccess = (res: any, file: any) => {
- // 根据实际接口返回格式调整
- console.log(res);
- formData.value.template_video_url = res.url;
- };
- // 视频上传前验证
- const beforeVideoUpload = (file: any) => {
- const isVideo = file.type.startsWith('video/');
- const isLt50M = file.size / 1024 / 1024 < 50;
- if (!isVideo) {
- ElMessage.error('请上传视频格式文件!');
- }
- if (!isLt50M) {
- ElMessage.error('上传视频大小不能超过 50MB!');
- }
- return isVideo && isLt50M;
- };
- // 提交表单
- const submitForm = async () => {
- try {
- await formRef.value.validate();
-
- // 处理配置对象
- const submitData = {...formData.value};
- if (submitData.config && typeof submitData.config === 'object') {
- // 确保 config 符合类型要求
- const config = {
- voice_type: submitData.config.voice_type || '',
- style: submitData.config.style || ''
- };
- // 将config转为字符串
- submitData.config = JSON.stringify(config) as any; // 使用类型断言解决类型错误
- }
-
- // 在发送请求前,确保config是字符串类型
- if (formMode.value === 'add') {
- await AddObj(submitData);
- ElMessage.success('添加成功');
- } else {
- await UpdateObj(submitData);
- ElMessage.success('更新成功');
- }
-
- closeForm();
- 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();
- };
- const voiceList = ref([
- { label: '龙小夏', value: 'longxiaoxia' },
- { label: '龙小春', value: 'longxiaochun' },
- { label: '龙小瑶', value: 'longxiaoyao' },
- { label: '龙小婧', value: 'longxiaojing' },
- { label: '龙青周', value: 'longqingzhou' },
- { label: '女声清新风格', value: 'female1' },
- { label: '女声稳重风格', value: 'female2' },
- { label: '男声清新风格', value: 'male1' },
- { label: '男声稳重风格', value: 'male2' },
- ]);
- /* 获取语音列表 */
- const getVoiceList = async () => {
- /* const response = await GetVoiceList();
- voiceList.value = response.data; */
- };
- // 页面打开后获取列表数据
- onMounted(async () => {
- // 设置列权限
- const newOptions = await handleColumnPermission(GetPermission, crudOptions);
-
- // 配置新增按钮,点击时打开右侧表单而不是弹窗
- if (newOptions.actionbar?.buttons?.add) {
- newOptions.actionbar.buttons.add.click = () => {
- openAddForm();
- };
- }
-
- //重置crudBinding
- resetCrudOptions(newOptions);
- // 刷新
- crudExpose.doRefresh();
- getVoiceList();
- });
- // 获取对话框标题
- 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>
- .digital-human-container {
- display: flex;
- height: 100%;
- }
- .list-area {
- flex: 1;
- overflow: auto;
- }
- /* 移除侧边栏相关样式,添加弹窗相关样式 */
- .avatar-uploader {
- text-align: center;
- border: 1px dashed #d9d9d9;
- border-radius: 6px;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- transition: border-color 0.3s;
- }
- .avatar-uploader:hover {
- border-color: #409EFF;
- }
- .avatar-uploader-icon {
- font-size: 28px;
- color: #8c939d;
- width: 100px;
- height: 100px;
- line-height: 100px;
- text-align: center;
- }
- .avatar {
- width: 100px;
- height: 100px;
- display: block;
- object-fit: cover;
- }
- .dialog-footer {
- text-align: right;
- }
- .video-uploader {
- text-align: center;
- border: 1px dashed #d9d9d9;
- border-radius: 6px;
- cursor: pointer;
- position: relative;
- overflow: hidden;
- transition: border-color 0.3s;
- }
- .video-uploader:hover {
- border-color: #409EFF;
- }
- .video-uploader-icon {
- font-size: 28px;
- color: #8c939d;
- width: 100px;
- height: 100px;
- line-height: 100px;
- text-align: center;
- }
- .video-preview {
- width: 200px;
- height: auto;
- max-height: 150px;
- display: block;
- object-fit: contain;
- }
- .video-uploader-placeholder {
- text-align: center;
- padding: 20px;
- border: 1px dashed #d9d9d9;
- border-radius: 6px;
- }
- /* 添加删除按钮的危险样式 */
- .text-danger {
- color: #F56C6C;
- }
- .text-danger:hover {
- color: #f78989;
- }
- /* 添加查看模式的样式 */
- .el-descriptions {
- margin-bottom: 20px;
- }
- </style>
|