|
@@ -18,18 +18,18 @@ import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, For
|
|
|
import { Textarea } from '@d8d/shared-ui-components/components/ui/textarea';
|
|
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 } from '../api/goodsClient';
|
|
|
|
|
|
|
+import { goodsClient, goodsClientManager } from '../api/goodsClient';
|
|
|
import { AdminCreateGoodsDto, AdminUpdateGoodsDto } from '@d8d/goods-module/schemas';
|
|
import { AdminCreateGoodsDto, AdminUpdateGoodsDto } from '@d8d/goods-module/schemas';
|
|
|
import { DataTablePagination } from '@d8d/shared-ui-components/components/admin/DataTablePagination';
|
|
import { DataTablePagination } from '@d8d/shared-ui-components/components/admin/DataTablePagination';
|
|
|
import { FileSelector } from '@d8d/file-management-ui';
|
|
import { FileSelector } from '@d8d/file-management-ui';
|
|
|
-import { GoodsCategoryCascadeSelector } from '@d8d/goods-category-management-ui';
|
|
|
|
|
-import { SupplierSelector } from '@d8d/supplier-management-ui';
|
|
|
|
|
-import { MerchantSelector } from '@d8d/merchant-management-ui';
|
|
|
|
|
|
|
+import { GoodsCategoryCascadeSelector } from '@d8d/goods-category-management-ui/components';
|
|
|
|
|
+import { SupplierSelector } from '@d8d/supplier-management-ui/components';
|
|
|
|
|
+import { MerchantSelector } from '@d8d/merchant-management-ui/components';
|
|
|
import { Search, Plus, Edit, Trash2, Package } from 'lucide-react';
|
|
import { Search, Plus, Edit, Trash2, Package } from 'lucide-react';
|
|
|
|
|
|
|
|
-type CreateRequest = InferRequestType<typeof goodsClient.$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.$get, 200>['data'][0];
|
|
|
|
|
|
|
+type GoodsResponse = InferResponseType<typeof goodsClient.index.$get, 200>['data'][0];
|
|
|
|
|
|
|
|
const createFormSchema = AdminCreateGoodsDto;
|
|
const createFormSchema = AdminCreateGoodsDto;
|
|
|
const updateFormSchema = AdminUpdateGoodsDto;
|
|
const updateFormSchema = AdminUpdateGoodsDto;
|
|
@@ -76,7 +76,7 @@ export const GoodsManagement: React.FC = () => {
|
|
|
const { data, isLoading, refetch } = useQuery({
|
|
const { data, isLoading, refetch } = useQuery({
|
|
|
queryKey: ['goods', searchParams],
|
|
queryKey: ['goods', searchParams],
|
|
|
queryFn: async () => {
|
|
queryFn: async () => {
|
|
|
- const res = await goodsClient.$get({
|
|
|
|
|
|
|
+ const res = await goodsClientManager.get().index.$get({
|
|
|
query: {
|
|
query: {
|
|
|
page: searchParams.page,
|
|
page: searchParams.page,
|
|
|
pageSize: searchParams.limit,
|
|
pageSize: searchParams.limit,
|
|
@@ -91,7 +91,7 @@ export const GoodsManagement: React.FC = () => {
|
|
|
// 创建商品
|
|
// 创建商品
|
|
|
const createMutation = useMutation({
|
|
const createMutation = useMutation({
|
|
|
mutationFn: async (data: CreateRequest) => {
|
|
mutationFn: async (data: CreateRequest) => {
|
|
|
- const res = await goodsClient.$post({ json: data });
|
|
|
|
|
|
|
+ const res = await goodsClientManager.get().index.$post({ json: data });
|
|
|
if (res.status !== 201) throw new Error('创建商品失败');
|
|
if (res.status !== 201) throw new Error('创建商品失败');
|
|
|
return await res.json();
|
|
return await res.json();
|
|
|
},
|
|
},
|
|
@@ -109,8 +109,8 @@ export const GoodsManagement: React.FC = () => {
|
|
|
// 更新商品
|
|
// 更新商品
|
|
|
const updateMutation = useMutation({
|
|
const updateMutation = useMutation({
|
|
|
mutationFn: async ({ id, data }: { id: number; data: UpdateRequest }) => {
|
|
mutationFn: async ({ id, data }: { id: number; data: UpdateRequest }) => {
|
|
|
- const res = await goodsClient[':id']['$put']({
|
|
|
|
|
- param: { id: id.toString() },
|
|
|
|
|
|
|
+ const res = await goodsClientManager.get()[':id']['$put']({
|
|
|
|
|
+ param: { id: id },
|
|
|
json: data
|
|
json: data
|
|
|
});
|
|
});
|
|
|
if (res.status !== 200) throw new Error('更新商品失败');
|
|
if (res.status !== 200) throw new Error('更新商品失败');
|
|
@@ -130,8 +130,8 @@ export const GoodsManagement: React.FC = () => {
|
|
|
// 删除商品
|
|
// 删除商品
|
|
|
const deleteMutation = useMutation({
|
|
const deleteMutation = useMutation({
|
|
|
mutationFn: async (id: number) => {
|
|
mutationFn: async (id: number) => {
|
|
|
- const res = await goodsClient[':id']['$delete']({
|
|
|
|
|
- param: { id: id.toString() }
|
|
|
|
|
|
|
+ const res = await goodsClientManager.get()[':id']['$delete']({
|
|
|
|
|
+ param: { id: id }
|
|
|
});
|
|
});
|
|
|
if (res.status !== 204) throw new Error('删除商品失败');
|
|
if (res.status !== 204) throw new Error('删除商品失败');
|
|
|
return id;
|
|
return id;
|
|
@@ -296,6 +296,7 @@ export const GoodsManagement: React.FC = () => {
|
|
|
variant="ghost"
|
|
variant="ghost"
|
|
|
size="icon"
|
|
size="icon"
|
|
|
onClick={() => handleEditGoods(goods)}
|
|
onClick={() => handleEditGoods(goods)}
|
|
|
|
|
+ data-testid="edit-goods-button"
|
|
|
>
|
|
>
|
|
|
<Edit className="h-4 w-4" />
|
|
<Edit className="h-4 w-4" />
|
|
|
</Button>
|
|
</Button>
|
|
@@ -303,6 +304,7 @@ export const GoodsManagement: React.FC = () => {
|
|
|
variant="ghost"
|
|
variant="ghost"
|
|
|
size="icon"
|
|
size="icon"
|
|
|
onClick={() => handleDeleteGoods(goods.id)}
|
|
onClick={() => handleDeleteGoods(goods.id)}
|
|
|
|
|
+ data-testid="delete-goods-button"
|
|
|
>
|
|
>
|
|
|
<Trash2 className="h-4 w-4" />
|
|
<Trash2 className="h-4 w-4" />
|
|
|
</Button>
|
|
</Button>
|
|
@@ -349,7 +351,11 @@ 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 placeholder="请输入商品名称" {...field} />
|
|
|
|
|
|
|
+ <Input
|
|
|
|
|
+ placeholder="请输入商品名称"
|
|
|
|
|
+ data-testid="goods-name-input"
|
|
|
|
|
+ {...field}
|
|
|
|
|
+ />
|
|
|
</FormControl>
|
|
</FormControl>
|
|
|
<FormMessage />
|
|
<FormMessage />
|
|
|
</FormItem>
|
|
</FormItem>
|
|
@@ -364,7 +370,13 @@ 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" {...field} />
|
|
|
|
|
|
|
+ <Input
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ step="0.01"
|
|
|
|
|
+ placeholder="0.00"
|
|
|
|
|
+ data-testid="goods-price-input"
|
|
|
|
|
+ {...field}
|
|
|
|
|
+ />
|
|
|
</FormControl>
|
|
</FormControl>
|
|
|
<FormMessage />
|
|
<FormMessage />
|
|
|
</FormItem>
|
|
</FormItem>
|
|
@@ -378,7 +390,13 @@ 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" {...field} />
|
|
|
|
|
|
|
+ <Input
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ step="0.01"
|
|
|
|
|
+ placeholder="0.00"
|
|
|
|
|
+ data-testid="goods-cost-price-input"
|
|
|
|
|
+ {...field}
|
|
|
|
|
+ />
|
|
|
</FormControl>
|
|
</FormControl>
|
|
|
<FormMessage />
|
|
<FormMessage />
|
|
|
</FormItem>
|
|
</FormItem>
|
|
@@ -457,7 +475,12 @@ 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" placeholder="0" {...field} />
|
|
|
|
|
|
|
+ <Input
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ placeholder="0"
|
|
|
|
|
+ data-testid="goods-stock-input"
|
|
|
|
|
+ {...field}
|
|
|
|
|
+ />
|
|
|
</FormControl>
|
|
</FormControl>
|
|
|
<FormMessage />
|
|
<FormMessage />
|
|
|
</FormItem>
|
|
</FormItem>
|
|
@@ -477,7 +500,7 @@ export const GoodsManagement: React.FC = () => {
|
|
|
onChange={field.onChange}
|
|
onChange={field.onChange}
|
|
|
maxSize={2}
|
|
maxSize={2}
|
|
|
uploadPath="/goods"
|
|
uploadPath="/goods"
|
|
|
- uploadButtonText="上传商品主图"
|
|
|
|
|
|
|
+ title="上传商品主图"
|
|
|
previewSize="medium"
|
|
previewSize="medium"
|
|
|
placeholder="选择商品主图"
|
|
placeholder="选择商品主图"
|
|
|
filterType="image"
|
|
filterType="image"
|
|
@@ -502,7 +525,7 @@ export const GoodsManagement: React.FC = () => {
|
|
|
allowMultiple={true}
|
|
allowMultiple={true}
|
|
|
maxSize={5}
|
|
maxSize={5}
|
|
|
uploadPath="/goods/slide"
|
|
uploadPath="/goods/slide"
|
|
|
- uploadButtonText="上传轮播图"
|
|
|
|
|
|
|
+ title="上传轮播图"
|
|
|
previewSize="small"
|
|
previewSize="small"
|
|
|
placeholder="选择商品轮播图"
|
|
placeholder="选择商品轮播图"
|
|
|
filterType="image"
|
|
filterType="image"
|
|
@@ -684,7 +707,7 @@ export const GoodsManagement: React.FC = () => {
|
|
|
onChange={field.onChange}
|
|
onChange={field.onChange}
|
|
|
maxSize={2}
|
|
maxSize={2}
|
|
|
uploadPath="/goods"
|
|
uploadPath="/goods"
|
|
|
- uploadButtonText="上传商品主图"
|
|
|
|
|
|
|
+ title="上传商品主图"
|
|
|
previewSize="medium"
|
|
previewSize="medium"
|
|
|
placeholder="选择商品主图"
|
|
placeholder="选择商品主图"
|
|
|
filterType="image"
|
|
filterType="image"
|
|
@@ -709,7 +732,7 @@ export const GoodsManagement: React.FC = () => {
|
|
|
allowMultiple={true}
|
|
allowMultiple={true}
|
|
|
maxSize={5}
|
|
maxSize={5}
|
|
|
uploadPath="/goods/slide"
|
|
uploadPath="/goods/slide"
|
|
|
- uploadButtonText="上传轮播图"
|
|
|
|
|
|
|
+ title="上传轮播图"
|
|
|
previewSize="small"
|
|
previewSize="small"
|
|
|
placeholder="选择商品轮播图"
|
|
placeholder="选择商品轮播图"
|
|
|
filterType="image"
|
|
filterType="image"
|