Bladeren bron

✨ feat(goods): 添加商户选择功能和库存字段

- 新增MerchantSelector组件,用于商户选择
- 在商品列表页添加商户列显示
- 在创建和编辑商品表单中添加商户选择器
- 为创建商品表单添加必填的库存字段
- 更新商品编辑表单,添加商户选择功能
yourname 4 maanden geleden
bovenliggende
commit
6bd8d6ea02
2 gewijzigde bestanden met toevoegingen van 139 en 0 verwijderingen
  1. 65 0
      src/client/admin-shadcn/components/MerchantSelector.tsx
  2. 74 0
      src/client/admin-shadcn/pages/Goods.tsx

+ 65 - 0
src/client/admin-shadcn/components/MerchantSelector.tsx

@@ -0,0 +1,65 @@
+import React from 'react';
+import { useQuery } from '@tanstack/react-query';
+import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/client/components/ui/select';
+import { merchantClient } from '@/client/api';
+import type { InferResponseType } from 'hono/client';
+
+type Merchant = InferResponseType<typeof merchantClient.$get, 200>['data'][0];
+
+interface MerchantSelectorProps {
+  value?: number;
+  onChange?: (value: number | undefined) => void;
+  placeholder?: string;
+  disabled?: boolean;
+  allowClear?: boolean;
+}
+
+export const MerchantSelector: React.FC<MerchantSelectorProps> = ({
+  value,
+  onChange,
+  placeholder = '选择商户',
+  disabled = false,
+  allowClear = true,
+}) => {
+  const { data, isLoading } = useQuery({
+    queryKey: ['merchants-select'],
+    queryFn: async () => {
+      const res = await merchantClient.$get({
+        query: { page: 1, pageSize: 100 }
+      });
+      if (res.status !== 200) throw new Error('获取商户列表失败');
+      return await res.json();
+    },
+  });
+
+  const merchants = data?.data || [];
+
+  const handleValueChange = (newValue: string) => {
+    const numValue = newValue === 'null' ? undefined : parseInt(newValue, 10);
+    onChange?.(numValue);
+  };
+
+  return (
+    <Select
+      value={value?.toString() || (allowClear ? 'null' : undefined)}
+      onValueChange={handleValueChange}
+      disabled={disabled}
+    >
+      <SelectTrigger>
+        <SelectValue placeholder={placeholder} />
+      </SelectTrigger>
+      <SelectContent>
+        {allowClear && (
+          <SelectItem value="null">无</SelectItem>
+        )}
+        {merchants.map((merchant) => (
+          <SelectItem key={merchant.id} value={merchant.id.toString()}>
+            {merchant.name || merchant.username}
+          </SelectItem>
+        ))}
+      </SelectContent>
+    </Select>
+  );
+};
+
+export default MerchantSelector;

+ 74 - 0
src/client/admin-shadcn/pages/Goods.tsx

@@ -25,6 +25,7 @@ import ImageSelector from '@/client/admin-shadcn/components/ImageSelector';
 import GoodsCategorySelector from '@/client/admin-shadcn/components/GoodsCategorySelector';
 import GoodsCategoryCascadeSelector from '@/client/admin-shadcn/components/GoodsCategoryCascadeSelector';
 import SupplierSelector from '@/client/admin-shadcn/components/SupplierSelector';
+import MerchantSelector from '@/client/admin-shadcn/components/MerchantSelector';
 import { Search, Plus, Edit, Trash2, Package } from 'lucide-react';
 
 type CreateRequest = InferRequestType<typeof goodsClient.$post>['json'];
@@ -55,6 +56,7 @@ export const GoodsPage = () => {
       categoryId3: 0,
       goodsType: 1,
       supplierId: null,
+      merchantId: null,
       imageFileId: null,
       slideImageIds: [],
       detail: '',
@@ -174,6 +176,7 @@ export const GoodsPage = () => {
       categoryId3: goods.categoryId3,
       goodsType: goods.goodsType,
       supplierId: goods.supplierId,
+      merchantId: goods.merchantId,
       imageFileId: goods.imageFileId,
       slideImageIds: goods.slideImages?.map(img => img.id) || [],
       detail: goods.detail || '',
@@ -252,6 +255,7 @@ export const GoodsPage = () => {
                   <TableHead>库存</TableHead>
                   <TableHead>销量</TableHead>
                   <TableHead>供应商</TableHead>
+                  <TableHead>商户</TableHead>
                   <TableHead>状态</TableHead>
                   <TableHead>创建时间</TableHead>
                   <TableHead className="text-right">操作</TableHead>
@@ -278,6 +282,7 @@ export const GoodsPage = () => {
                     <TableCell>{goods.stock}</TableCell>
                     <TableCell>{goods.salesNum}</TableCell>
                     <TableCell>{goods.supplier?.name || '-'}</TableCell>
+                    <TableCell>{goods.merchant?.name || goods.merchant?.username || '-'}</TableCell>
                     <TableCell>
                       <Badge variant={goods.state === 1 ? 'default' : 'secondary'}>
                         {goods.state === 1 ? '可用' : '不可用'}
@@ -402,6 +407,25 @@ export const GoodsPage = () => {
                     )}
                   />
 
+                  <FormField
+                    control={createForm.control}
+                    name="merchantId"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormLabel>商户</FormLabel>
+                        <FormControl>
+                          <MerchantSelector
+                            value={field.value || undefined}
+                            onChange={field.onChange}
+                          />
+                        </FormControl>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
+                </div>
+
+                <div className="grid grid-cols-2 gap-4">
                   <FormField
                     control={createForm.control}
                     name="goodsType"
@@ -426,6 +450,20 @@ export const GoodsPage = () => {
                       </FormItem>
                     )}
                   />
+
+                  <FormField
+                    control={createForm.control}
+                    name="stock"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormLabel>库存 <span className="text-red-500">*</span></FormLabel>
+                        <FormControl>
+                          <Input type="number" placeholder="0" {...field} />
+                        </FormControl>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
                 </div>
 
                 <FormField
@@ -571,6 +609,42 @@ export const GoodsPage = () => {
 
                 <GoodsCategoryCascadeSelector />
 
+                <div className="grid grid-cols-2 gap-4">
+                  <FormField
+                    control={updateForm.control}
+                    name="supplierId"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormLabel>供应商</FormLabel>
+                        <FormControl>
+                          <SupplierSelector
+                            value={field.value || undefined}
+                            onChange={field.onChange}
+                          />
+                        </FormControl>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
+
+                  <FormField
+                    control={updateForm.control}
+                    name="merchantId"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormLabel>商户</FormLabel>
+                        <FormControl>
+                          <MerchantSelector
+                            value={field.value || undefined}
+                            onChange={field.onChange}
+                          />
+                        </FormControl>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
+                </div>
+
                 <div className="grid grid-cols-2 gap-4">
                   <FormField
                     control={updateForm.control}