|
@@ -5,7 +5,7 @@ import { zhCN } from 'date-fns/locale';
|
|
|
import { toast } from 'sonner';
|
|
import { toast } from 'sonner';
|
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
|
import { useForm } from 'react-hook-form';
|
|
import { useForm } from 'react-hook-form';
|
|
|
-import type { InferRequestType, InferResponseType } from 'hono/client';
|
|
|
|
|
|
|
+import type { InferRequestType } from 'hono/client';
|
|
|
|
|
|
|
|
import { Button } from '@d8d/shared-ui-components/components/ui/button';
|
|
import { Button } from '@d8d/shared-ui-components/components/ui/button';
|
|
|
import { Input } from '@d8d/shared-ui-components/components/ui/input';
|
|
import { Input } from '@d8d/shared-ui-components/components/ui/input';
|
|
@@ -20,7 +20,8 @@ import { Textarea } from '@d8d/shared-ui-components/components/ui/textarea';
|
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@d8d/shared-ui-components/components/ui/select';
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@d8d/shared-ui-components/components/ui/select';
|
|
|
|
|
|
|
|
import { goodsClient, goodsClientManager } from '../api/goodsClient';
|
|
import { goodsClient, goodsClientManager } from '../api/goodsClient';
|
|
|
-import { AdminCreateGoodsDto, AdminUpdateGoodsDto } from '@d8d/goods-module-mt/schemas';
|
|
|
|
|
|
|
+import { AdminCreateGoodsDto, AdminUpdateGoodsDto, AdminGoodsSchema } from '@d8d/goods-module-mt/schemas';
|
|
|
|
|
+import { z } from 'zod';
|
|
|
import { supplierClientManager } from '@d8d/supplier-management-ui-mt/api';
|
|
import { supplierClientManager } from '@d8d/supplier-management-ui-mt/api';
|
|
|
import { merchantClientManager } from '@d8d/merchant-management-ui-mt/api';
|
|
import { merchantClientManager } from '@d8d/merchant-management-ui-mt/api';
|
|
|
import { DataTablePagination } from '@d8d/shared-ui-components/components/admin/DataTablePagination';
|
|
import { DataTablePagination } from '@d8d/shared-ui-components/components/admin/DataTablePagination';
|
|
@@ -33,7 +34,31 @@ import { Search, Plus, Edit, Trash2, Package } from 'lucide-react';
|
|
|
|
|
|
|
|
type CreateRequest = InferRequestType<typeof goodsClient.index.$post>['json'];
|
|
type CreateRequest = InferRequestType<typeof goodsClient.index.$post>['json'];
|
|
|
type UpdateRequest = InferRequestType<typeof goodsClient[':id']['$put']>['json'];
|
|
type UpdateRequest = InferRequestType<typeof goodsClient[':id']['$put']>['json'];
|
|
|
-type GoodsResponse = InferResponseType<typeof goodsClient.index.$get, 200>['data'][0];
|
|
|
|
|
|
|
+
|
|
|
|
|
+// API返回的File类型(日期为字符串格式)
|
|
|
|
|
+type ApiFile = {
|
|
|
|
|
+ id: number;
|
|
|
|
|
+ tenantId: number;
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ type: string | null;
|
|
|
|
|
+ size: number | null;
|
|
|
|
|
+ path: string;
|
|
|
|
|
+ fullUrl: string;
|
|
|
|
|
+ description: string | null;
|
|
|
|
|
+ uploadUserId: number;
|
|
|
|
|
+ uploadTime: string;
|
|
|
|
|
+ lastUpdated: string | null;
|
|
|
|
|
+ createdAt: string;
|
|
|
|
|
+ updatedAt: string;
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+// API返回的商品类型(日期为字符串格式)
|
|
|
|
|
+type GoodsResponse = Omit<z.infer<typeof AdminGoodsSchema>, 'createdAt' | 'updatedAt' | 'slideImages' | 'imageFile'> & {
|
|
|
|
|
+ createdAt: string;
|
|
|
|
|
+ updatedAt: string;
|
|
|
|
|
+ slideImages: ApiFile[] | null;
|
|
|
|
|
+ imageFile: ApiFile | null;
|
|
|
|
|
+};
|
|
|
|
|
|
|
|
const createFormSchema = AdminCreateGoodsDto;
|
|
const createFormSchema = AdminCreateGoodsDto;
|
|
|
const updateFormSchema = AdminUpdateGoodsDto;
|
|
const updateFormSchema = AdminUpdateGoodsDto;
|
|
@@ -52,10 +77,8 @@ export const GoodsManagement: React.FC = () => {
|
|
|
batchSpecs: [] as BatchSpecTemplate[]
|
|
batchSpecs: [] as BatchSpecTemplate[]
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
- const [isVisible, setIsVisible] = useState(false);
|
|
|
|
|
-
|
|
|
|
|
// 创建表单
|
|
// 创建表单
|
|
|
- const createForm = useForm<CreateRequest>({
|
|
|
|
|
|
|
+ const createForm = useForm({
|
|
|
resolver: zodResolver(createFormSchema),
|
|
resolver: zodResolver(createFormSchema),
|
|
|
defaultValues: {
|
|
defaultValues: {
|
|
|
name: '',
|
|
name: '',
|
|
@@ -79,7 +102,7 @@ export const GoodsManagement: React.FC = () => {
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// 更新表单
|
|
// 更新表单
|
|
|
- const updateForm = useForm<UpdateRequest>({
|
|
|
|
|
|
|
+ const updateForm = useForm({
|
|
|
resolver: zodResolver(updateFormSchema),
|
|
resolver: zodResolver(updateFormSchema),
|
|
|
});
|
|
});
|
|
|
|
|
|
|
@@ -101,7 +124,7 @@ export const GoodsManagement: React.FC = () => {
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// 获取供应商列表(用于默认选择第一个)
|
|
// 获取供应商列表(用于默认选择第一个)
|
|
|
- const { data: suppliersData, isLoading: isLoadingSuppliers } = useQuery({
|
|
|
|
|
|
|
+ const { data: suppliersData } = useQuery({
|
|
|
queryKey: ['suppliers-for-goods'],
|
|
queryKey: ['suppliers-for-goods'],
|
|
|
queryFn: async () => {
|
|
queryFn: async () => {
|
|
|
const res = await supplierClientManager.get().index.$get({
|
|
const res = await supplierClientManager.get().index.$get({
|
|
@@ -117,7 +140,7 @@ export const GoodsManagement: React.FC = () => {
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
// 获取商户列表(用于默认选择第一个)
|
|
// 获取商户列表(用于默认选择第一个)
|
|
|
- const { data: merchantsData, isLoading: isLoadingMerchants } = useQuery({
|
|
|
|
|
|
|
+ const { data: merchantsData } = useQuery({
|
|
|
queryKey: ['merchants-for-goods'],
|
|
queryKey: ['merchants-for-goods'],
|
|
|
queryFn: async () => {
|
|
queryFn: async () => {
|
|
|
const res = await merchantClientManager.get().index.$get({
|
|
const res = await merchantClientManager.get().index.$get({
|
|
@@ -436,7 +459,7 @@ export const GoodsManagement: React.FC = () => {
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</TableCell>
|
|
</TableCell>
|
|
|
- <TableCell>¥{goods.price.toFixed(2)}</TableCell>
|
|
|
|
|
|
|
+ <TableCell>{goods.price === 0 ? '面议' : `¥${goods.price.toFixed(2)}`}</TableCell>
|
|
|
<TableCell>{goods.stock}</TableCell>
|
|
<TableCell>{goods.stock}</TableCell>
|
|
|
<TableCell>{goods.salesNum}</TableCell>
|
|
<TableCell>{goods.salesNum}</TableCell>
|
|
|
{/* <TableCell>{goods.supplier?.name || '-'}</TableCell>
|
|
{/* <TableCell>{goods.supplier?.name || '-'}</TableCell>
|
|
@@ -454,7 +477,7 @@ export const GoodsManagement: React.FC = () => {
|
|
|
<Button
|
|
<Button
|
|
|
variant="ghost"
|
|
variant="ghost"
|
|
|
size="icon"
|
|
size="icon"
|
|
|
- onClick={() => handleEditGoods(goods)}
|
|
|
|
|
|
|
+ onClick={() => handleEditGoods(goods as GoodsResponse)}
|
|
|
data-testid="edit-goods-button"
|
|
data-testid="edit-goods-button"
|
|
|
>
|
|
>
|
|
|
<Edit className="h-4 w-4" />
|
|
<Edit className="h-4 w-4" />
|
|
@@ -530,14 +553,33 @@ export const GoodsManagement: React.FC = () => {
|
|
|
<FormItem>
|
|
<FormItem>
|
|
|
<FormLabel>售卖价 <span className="text-red-500">*</span></FormLabel>
|
|
<FormLabel>售卖价 <span className="text-red-500">*</span></FormLabel>
|
|
|
<FormControl>
|
|
<FormControl>
|
|
|
- <Input
|
|
|
|
|
- type="number"
|
|
|
|
|
- step="0.01"
|
|
|
|
|
- placeholder="0.00"
|
|
|
|
|
- data-testid="goods-price-input"
|
|
|
|
|
- {...field}
|
|
|
|
|
- />
|
|
|
|
|
|
|
+ <div className="flex gap-2">
|
|
|
|
|
+ <Input
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ step="1"
|
|
|
|
|
+ placeholder="0.00"
|
|
|
|
|
+ data-testid="goods-price-input"
|
|
|
|
|
+ disabled={(field.value as number) === 0}
|
|
|
|
|
+ value={field.value as number}
|
|
|
|
|
+ onChange={field.onChange}
|
|
|
|
|
+ onBlur={field.onBlur}
|
|
|
|
|
+ name={field.name}
|
|
|
|
|
+ ref={field.ref}
|
|
|
|
|
+ />
|
|
|
|
|
+ <Button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ variant={(field.value as number) === 0 ? "default" : "outline"}
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ className="whitespace-nowrap"
|
|
|
|
|
+ onClick={() => {
|
|
|
|
|
+ field.onChange((field.value as number) === 0 ? 0.01 : 0);
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {(field.value as number) === 0 ? "面议" : "设为面议"}
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
</FormControl>
|
|
</FormControl>
|
|
|
|
|
+ <FormDescription>{(field.value as number) === 0 ? "当前价格:面议" : "输入具体价格或点击设为面议"}</FormDescription>
|
|
|
<FormMessage />
|
|
<FormMessage />
|
|
|
</FormItem>
|
|
</FormItem>
|
|
|
)}
|
|
)}
|
|
@@ -760,8 +802,31 @@ export const GoodsManagement: React.FC = () => {
|
|
|
<FormItem>
|
|
<FormItem>
|
|
|
<FormLabel>售卖价</FormLabel>
|
|
<FormLabel>售卖价</FormLabel>
|
|
|
<FormControl>
|
|
<FormControl>
|
|
|
- <Input type="number" step="0.01" {...field} />
|
|
|
|
|
|
|
+ <div className="flex gap-2">
|
|
|
|
|
+ <Input
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ step="0.01"
|
|
|
|
|
+ disabled={(field.value as number) === 0}
|
|
|
|
|
+ value={field.value as number}
|
|
|
|
|
+ onChange={field.onChange}
|
|
|
|
|
+ onBlur={field.onBlur}
|
|
|
|
|
+ name={field.name}
|
|
|
|
|
+ ref={field.ref}
|
|
|
|
|
+ />
|
|
|
|
|
+ <Button
|
|
|
|
|
+ type="button"
|
|
|
|
|
+ variant={(field.value as number) === 0 ? "default" : "outline"}
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ className="whitespace-nowrap"
|
|
|
|
|
+ onClick={() => {
|
|
|
|
|
+ field.onChange((field.value as number) === 0 ? 0.01 : 0);
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {(field.value as number) === 0 ? "面议" : "设为面议"}
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
</FormControl>
|
|
</FormControl>
|
|
|
|
|
+ <FormDescription>{(field.value as number) === 0 ? "当前价格:面议" : "输入具体价格或点击设为面议"}</FormDescription>
|
|
|
<FormMessage />
|
|
<FormMessage />
|
|
|
</FormItem>
|
|
</FormItem>
|
|
|
)}
|
|
)}
|