Prechádzať zdrojové kódy

✨ feat(vocabulary): add pronunciation audio file support

- add Volume2 icon import for audio playback
- import FileSelector component for audio file upload
- add pronunciationFileId field to create and update forms
- add audio column in vocabulary table with playback button
- add audio file upload functionality in create and edit modals
- support audio file selection, upload and preview with 10MB size limit
- update form data handling to include pronunciationFileId
- add audio format description and file type filtering
yourname 3 mesiacov pred
rodič
commit
f2b95b78f4
1 zmenil súbory, kde vykonal 79 pridanie a 1 odobranie
  1. 79 1
      src/client/admin-shadcn/pages/Vocabulary.tsx

+ 79 - 1
src/client/admin-shadcn/pages/Vocabulary.tsx

@@ -1,7 +1,7 @@
 import React, { useState } from 'react';
 import { useQuery } from '@tanstack/react-query';
 import { format } from 'date-fns';
-import { Plus, Search, Edit, Trash2 } from 'lucide-react';
+import { Plus, Search, Edit, Trash2, Volume2 } from 'lucide-react';
 import { vocabularyClient } from '@/client/api';
 import type { InferRequestType, InferResponseType } from 'hono/client';
 import { Button } from '@/client/components/ui/button';
@@ -19,6 +19,7 @@ import { Skeleton } from '@/client/components/ui/skeleton';
 import { Switch } from '@/client/components/ui/switch';
 import { DisabledStatus } from '@/share/types';
 import { CreateVocabularyDto, UpdateVocabularyDto } from '@/server/modules/vocabulary/vocabulary.schema';
+import FileSelector from '@/client/admin-shadcn/components/FileSelector';
 
 // 使用RPC方式提取类型
 type CreateVocabularyRequest = InferRequestType<typeof vocabularyClient.$post>['json'];
@@ -52,6 +53,7 @@ export const VocabularyPage = () => {
       meaning: null,
       example: null,
       isDisabled: DisabledStatus.ENABLED,
+      pronunciationFileId: null,
     },
   });
 
@@ -63,6 +65,7 @@ export const VocabularyPage = () => {
       meaning: null,
       example: null,
       isDisabled: undefined,
+      pronunciationFileId: null,
     },
   });
 
@@ -121,6 +124,7 @@ export const VocabularyPage = () => {
       meaning: vocabulary.meaning,
       example: vocabulary.example,
       isDisabled: vocabulary.isDisabled,
+      pronunciationFileId: vocabulary.pronunciationFileId || null,
     });
     setIsModalOpen(true);
   };
@@ -262,6 +266,7 @@ export const VocabularyPage = () => {
                   <TableHead>发音</TableHead>
                   <TableHead>含义</TableHead>
                   <TableHead>例句</TableHead>
+                  <TableHead>音频</TableHead>
                   <TableHead>状态</TableHead>
                   <TableHead>创建时间</TableHead>
                   <TableHead className="text-right">操作</TableHead>
@@ -274,6 +279,21 @@ export const VocabularyPage = () => {
                     <TableCell>{vocabulary.pronunciation || '-'}</TableCell>
                     <TableCell>{vocabulary.meaning || '-'}</TableCell>
                     <TableCell>{vocabulary.example || '-'}</TableCell>
+                    <TableCell>
+                      {vocabulary.pronunciationFile?.fullUrl ? (
+                        <Button
+                          variant="ghost"
+                          size="sm"
+                          onClick={() => window.open(vocabulary.pronunciationFile?.fullUrl, '_blank')}
+                          className="flex items-center gap-1"
+                        >
+                          <Volume2 className="h-4 w-4" />
+                          播放
+                        </Button>
+                      ) : (
+                        <span className="text-muted-foreground text-sm">-</span>
+                      )}
+                    </TableCell>
                     <TableCell>
                       <Badge
                         variant={vocabulary.isDisabled === 1 ? 'secondary' : 'default'}
@@ -391,6 +411,35 @@ export const VocabularyPage = () => {
                   )}
                 />
 
+                <FormField
+                  control={createForm.control}
+                  name="pronunciationFileId"
+                  render={({ field }) => (
+                    <FormItem>
+                      <FormLabel>发音音频文件</FormLabel>
+                      <FormControl>
+                        <FileSelector
+                          value={field.value || undefined}
+                          onChange={(value) => field.onChange(value)}
+                          maxSize={10}
+                          uploadPath="/vocabulary/pronunciation"
+                          uploadButtonText="上传音频文件"
+                          previewSize="medium"
+                          placeholder="选择发音音频文件"
+                          title="选择发音音频文件"
+                          description="上传新音频文件或从已有文件中选择"
+                          filterType="audio"
+                          accept="audio/*"
+                        />
+                      </FormControl>
+                      <FormDescription>
+                        支持 MP3、WAV 等音频格式,最大 10MB
+                      </FormDescription>
+                      <FormMessage />
+                    </FormItem>
+                  )}
+                />
+
                 <FormField
                   control={createForm.control}
                   name="isDisabled"
@@ -484,6 +533,35 @@ export const VocabularyPage = () => {
                   )}
                 />
 
+                <FormField
+                  control={updateForm.control}
+                  name="pronunciationFileId"
+                  render={({ field }) => (
+                    <FormItem>
+                      <FormLabel>发音音频文件</FormLabel>
+                      <FormControl>
+                        <FileSelector
+                          value={field.value || undefined}
+                          onChange={(value) => field.onChange(value)}
+                          maxSize={10}
+                          uploadPath="/vocabulary/pronunciation"
+                          uploadButtonText="上传音频文件"
+                          previewSize="medium"
+                          placeholder="选择发音音频文件"
+                          title="选择发音音频文件"
+                          description="上传新音频文件或从已有文件中选择"
+                          filterType="audio"
+                          accept="audio/*"
+                        />
+                      </FormControl>
+                      <FormDescription>
+                        支持 MP3、WAV 等音频格式,最大 10MB
+                      </FormDescription>
+                      <FormMessage />
+                    </FormItem>
+                  )}
+                />
+
                 <FormField
                   control={updateForm.control}
                   name="isDisabled"