|
|
@@ -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"
|