Browse Source

feat: 对接编辑声音

sheldon 2 days ago
parent
commit
e15d99f47a

+ 34 - 0
src/api/modules/voice.ts

@@ -0,0 +1,34 @@
+// anycall
+import { request } from '@/api'
+import type { TAgent } from '@/types/role'
+import type { TVoice } from '@/types/voice'
+
+
+
+// 克隆声音
+
+export function cloneVoice(params: {name: string, gender?: number, audioUrl: string}){
+  return request(`/anycall/cloneVoice`, params)
+}
+
+
+export function updateVoice(params: {
+    "id": string,
+    "name": string,
+    "photoUrl": string,
+    "feature": string
+}){
+  return request(`/voice/update`, params)
+}
+
+export function voiceList(params: {
+  page: number,
+  size: number,
+  gender?: number|null,
+  system?: boolean,
+}){
+  return request<{
+    total: number,
+    content: TVoice[]
+  }>(`/voice/select`, params)
+}

+ 1 - 1
src/components/VoiceSelector.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { voiceList } from '@/api/modules/anycallService'
+import { voiceList } from '@/api/modules/voice'
 // import type { TVoice } from '@/types/voice'
 
 // 定义组件属性

+ 22 - 16
src/types/voice.ts

@@ -1,17 +1,23 @@
 export type TVoice = {
-  ctime?: number;
-  customEdit?: boolean;
-  fromTrained?: boolean;
-  gender?: number;
-  id?: string;
-  name?: string;
-  service?: string;
-  spaceId?: string;
-  status?: number;
-  trial?: {
-    url?: string;
-    duration?: number;
-    srcName?: string;
-  };
-  userId?: string;
-};
+  "id": string|undefined,
+  "name": string,
+  "photoUrl": string,
+  "feature": string
+}
+// export type TVoice = {
+//   ctime?: number;
+//   customEdit?: boolean;
+//   fromTrained?: boolean;
+//   gender?: number;
+//   id?: string;
+//   name?: string;
+//   service?: string;
+//   spaceId?: string;
+//   status?: number;
+//   trial?: {
+//     url?: string;
+//     duration?: number;
+//     srcName?: string;
+//   };
+//   userId?: string;
+// };

+ 0 - 1
src/views/model/global-prompt/components/EditForm.vue

@@ -60,7 +60,6 @@ watch(() => props.modelValue, (newModelValue) => {
       ...formData.value,
       ...newModelValue
     };
-    console.log(newModelValue, formData.value,33344)
   }
 });
 

+ 190 - 0
src/views/voice-management/components/EditForm.vue

@@ -0,0 +1,190 @@
+<script setup lang="ts">
+import { ElDialog, ElForm, ElFormItem, ElInput, ElDatePicker, ElButton, ElMessage, ElRadio } from 'element-plus'
+import type { FormInstance, FormRules } from 'element-plus'
+import FaImageUpload from '@/ui/components/FaImageUpload/index.vue'
+import { updateVoice } from '@/api/modules/voice'
+// 定义表单数据类型
+type IFormData  = {
+    "id": string|undefined,
+    "name": string,
+    "photoUrl": string,
+    "feature": string
+}
+
+// 定义组件的属性
+interface Props {
+  visible: boolean
+  modelValue?: IFormData | null
+  mode?: 'create' | 'edit'
+}
+
+// 定义组件的事件
+interface Emits {
+  (e: 'update:visible', value: boolean): void
+  (e: 'cancel'): void
+  (e: 'refresh'): void
+}
+
+// 接收属性和事件
+const props = withDefaults(defineProps<Props>(), {
+  visible: false,
+  modelValue: null,
+  mode: 'create'
+})
+
+const emit = defineEmits<Emits>()
+
+const dialogVisible = computed({
+  get: () => props.visible,
+  set: (value) => {
+    // 这里可以触发一个事件来通知父组件更新 visible 的值
+    emit('update:visible', value);
+  },
+});
+
+
+// 表单引用
+const editFormRef = ref<FormInstance>();
+
+const photos = ref<string[]>([])
+
+// 响应式数据 - 直接定义所有必需字段
+const formData = ref<IFormData>({
+  photoUrl: '',
+  name: '',
+  feature: '',
+  id: undefined
+});
+
+
+
+// 监听 props.modelValue 变化
+watch(() => props.modelValue, (newModelValue) => {
+  if (newModelValue) {
+    formData.value = {
+      ...formData.value,
+      ...newModelValue
+    };
+    photos.value = newModelValue.photoUrl ? [newModelValue.photoUrl] : []
+  }
+});
+
+// 表单验证规则
+const formRules = ref<FormRules>({
+  name: [
+    { required: true, message: '请输入名称', trigger: 'blur' },
+  ],
+  photoUrl: [
+    { required: true, message: '请输入描述', trigger: 'blur' },
+  ],
+})
+
+// 监听可见性变化
+watch(() => props.visible, (newVisible) => {
+  resetForm()
+})
+
+
+// 重置表单
+function resetForm() {
+  console.log('reset')
+  if (editFormRef.value) {
+    editFormRef.value.resetFields()
+  }
+  formData.value = {
+    photoUrl: '',
+    name: '',
+    feature: '',
+    id: undefined
+  }
+}
+
+// 处理确认
+async function handleConfirm() {
+  if (!editFormRef.value) return
+
+  try {
+    await editFormRef.value.validate()
+    const defaultData = {
+      id: '',
+      name: '',
+      photoUrl: '',
+      feature: ''
+    }
+    const avatar =  photos.value?.[0] ?? ''
+    // 字段全后再对齐字段
+    //@ts-ignore
+    const d: any = { ...defaultData, ...formData.value, photoUrl: avatar }
+    console.log(d)
+    const { code } = await updateVoice(d)
+    if (code === 0) {
+      ElMessage.success('操作成功')
+      emit('refresh')
+    }
+    // 关闭对话框
+    emit('update:visible', false)
+  } catch (error) {
+    // 验证失败,不做处理
+  }
+}
+
+
+function handleCancel() {
+  emit('cancel')
+  emit('update:visible', false)
+}
+
+
+function handleClose() {
+  emit('update:visible', false)
+}
+
+
+</script>
+<template>
+  <div>
+
+    <ElDialog :title="mode === 'create' ? '创建声音' : '编辑声音'" v-model="dialogVisible" align-center @close="handleClose"
+      width="800" :z-index="2000">
+      <ElForm ref="editFormRef" :model="formData" :rules="formRules" label-width="120px" class="mt-4 space-y-4 w-full">
+
+        <ElFormItem label="name" prop="name" label-width="120">
+           <ElInput v-model="formData.name" placeholder="Enter name"  />
+        </ElFormItem>
+        <!-- <ElFormItem label="性别" prop="gender">
+          <ElRadioGroup v-model="formData.gender">
+            <ElRadio :value="1">男</ElRadio>
+            <ElRadio :value="2">女</ElRadio>
+            <ElRadio :value="3">其他</ElRadio>
+          </ElRadioGroup>
+        </ElFormItem> -->
+
+
+        <ElFormItem label="feature" prop="feature" label-width="120">
+           <ElInput type="textarea" v-model="formData.feature" placeholder="Enter feature"  />
+        </ElFormItem>
+
+        <!-- <ElFormItem label="use voice" prop="voiceName" label-width="120">
+           <LLMSelector v-model="formData.voiceId" />
+        </ElFormItem> -->
+
+
+        <ElFormItem label="Avatar" prop="avatar" label-width="120">
+          <FaImageUpload
+            v-model="photos"
+            :max-count="1"
+            list-type="avatar"
+          />
+        </ElFormItem>
+
+
+      </ElForm>
+      <template #footer>
+        <div class="flex justify-end space-x-2">
+          <ElButton @click="handleCancel">取消</ElButton>
+          <ElButton type="primary" @click="handleConfirm">确定</ElButton>
+        </div>
+      </template>
+    </ElDialog>
+  </div>
+</template>

+ 11 - 2
src/views/voice-management/components/SearchForm.vue

@@ -2,9 +2,10 @@
 import { Refresh, Search } from '@element-plus/icons-vue'
 import { ElButton, ElCol, ElForm, ElFormItem, ElInput, ElOption, ElRow, ElSelect } from 'element-plus'
 import { ref } from 'vue'
-type TSearchParams = {
+export type TSearchParams = {
   name: string,
   type: number
+  gender: number|null
 }
 interface Props {
   modelValue: TSearchParams
@@ -61,8 +62,16 @@ function handleClear() {
           <ElFormItem label="名称" prop="name" label-width="120">
             <ElInput v-model="searchParams.name" placeholder="请输入" clearable @clear="handleClear" />
           </ElFormItem>
+          <ElFormItem label="性别" prop="gender" label-width="120">
+            <ElSelect v-model="searchParams.gender" style="width: 120px;">
+              <ElOption :value="-1" label="全部">全部</ElOption>
+              <!-- <ElOption :value="0" label="全部">全部</ElOption> -->
+              <ElOption :value="1" label="女">女</ElOption>
+              <ElOption :value="2" label="男">男</ElOption>
+            </ElSelect>
+          </ElFormItem>
           <ElFormItem label="类型" prop="type" label-width="120">
-            <ElSelect v-model="searchParams.type" style="width: 120px">
+            <ElSelect v-model="searchParams.type" style="width: 120px;">
               <ElOption :value="1" label="系统">系统</ElOption>
               <ElOption :value="2" label="系统克隆">系统克隆</ElOption>
               <ElOption :value="3" label="系统克隆">用户</ElOption>

+ 41 - 9
src/views/voice-management/index.vue

@@ -6,10 +6,12 @@ defineOptions({
 import { Plus } from '@element-plus/icons-vue'
 import { ElButton, ElDialog, ElEmpty, ElInput, ElOption, ElPagination, ElSelect, ElTable, ElTableColumn, ElTag } from 'element-plus'
 import SearchForm from './components/SearchForm.vue'
+import type { TSearchParams } from './components/SearchForm.vue'
+import EditForm from './components/EditForm.vue'
 import AudioFileUploader from '@/components/Uploader/AudioFileUploader.vue'
 import type { TVoice } from '@/types/voice'
 import { toast } from 'vue-sonner'
-import { voiceList, cloneVoice } from '@/api/modules/anycallService'
+import { voiceList, cloneVoice } from '@/api/modules/voice'
 import { formatDateGeneral } from '@/utils'
 
 const tableRef = ref()
@@ -19,11 +21,20 @@ const router = useRouter()
 const route = useRoute()
 
 // 搜索参数
-const searchParams = ref<{name: string, type: number}>({
+const searchParams = ref<TSearchParams>({
   name: '',
-  type: 1
+  type: 1,
+  gender: null
 })
 const dataList = ref<TVoice[]>([]);
+const editFormVisible = ref(false)
+const editMode = ref<'create' | 'edit'>('create')
+const currentData = ref<{
+    "id": string|undefined,
+    "name": string,
+    "photoUrl": string,
+    "feature": string
+}|null>(null)
 
 const currentAudio = ref<{
     src: string;
@@ -60,17 +71,14 @@ const updateUrlParams = (page: number, size: number) => {
   })
 }
 
-
-const  handleRecommend = (id: string) => {
-
-}
-
 async function fetchData() {
   loading.value = true
+  const gender = searchParams.value.gender === -1 ? null : searchParams.value.gender
   const res = await voiceList({
-    gender: 1,
+    gender,
     system: searchParams.value.type === 1 ? true : false,
     name: searchParams.value.name,
+    type: searchParams.value.type,
     page: pagination.value.page,
     size: pagination.value.size,
   })
@@ -104,6 +112,7 @@ function handleReset () {
   searchParams.value = {
     name: '',
     type: 1,
+    gender: null,
   }
   pagination.value.page = 1 // 重置时重置到第一页
   updateUrlParams(1, pagination.value.size)
@@ -152,6 +161,18 @@ const handleClone = async (res:  { src: string, srcName: string, duration: numbe
   }
 }
 
+const handleEdit = (data: TVoice) => {
+  editMode.value = 'edit'
+  currentData.value = {
+    id: data.id,
+    name: data.name ?? '',
+    photoUrl: data.photoUrl ?? '',
+    feature: data.feature ?? ''
+  }
+
+  editFormVisible.value = true
+}
+
 onMounted(async () => {
   await fetchData()
 })
@@ -178,6 +199,11 @@ onMounted(async () => {
       >
         <ElTableColumn label="ID" prop="id" width="280" />
         <ElTableColumn label="Name" prop="name" min-width="200"/>
+        <ElTableColumn label="Avatar" prop="photoUrl">
+          <template #default="{row}">
+            <ElImage :src="row.photoUrl" fit="cover" class="w-12 h-12 rounded" />
+          </template>
+        </ElTableColumn>
         <ElTableColumn label="Gender" prop="gender">
           <template #default="{row}">
             {{ row.gender === 1 ? '男':"女" }}
@@ -193,6 +219,11 @@ onMounted(async () => {
             {{ formatDateGeneral(row.ctime) }}
           </template>
         </ElTableColumn>
+        <ElTableColumn fixed="right" label="操作" min-width="240">
+          <template #default="{row}">
+            <ElButton link type="primary" size="small" @click="handleEdit(row)">编辑</ElButton>
+          </template>
+        </ElTableColumn>
       </ElTable>
       <div class="p-4">
         <ElPagination
@@ -206,6 +237,7 @@ onMounted(async () => {
         />
       </div>
     </FaPageMain>
+    <EditForm v-model="currentData" v-model:visible="editFormVisible" :mode="editMode" @refresh="fetchData"></EditForm>
   </div>
 </template>
 <style scoped>