商品列表、分类管理、客户等级
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, {useEffect, useState} from 'react';
|
||||
import {Button, Image, Tag, Typography} from 'antd';
|
||||
import {NodeExpandOutlined} from '@ant-design/icons';
|
||||
import XinTable from '@/components/XinTable';
|
||||
import type { XinTableColumn, XinTableProps } from '@/components/XinTable/typings.ts';
|
||||
import type IProductCategory from '@/domain/iProductCategory.ts';
|
||||
import type { IProductCategoryTree } from '@/domain/iProductCategory.ts';
|
||||
import { CATEGORY_STATUS_MAP } from '@/domain/iProductCategory.ts';
|
||||
import { getCategoryTable } from '@/api/product/category.ts';
|
||||
import {getCategoryTable, getCategoryTree} from '@/api/product/category.ts';
|
||||
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
@@ -34,7 +35,11 @@ function collectAllIds(nodes: IProductCategory[]): number[] {
|
||||
const ProductCategoryPage: React.FC = () => {
|
||||
const [expandedKeys, setExpandedKeys] = useState<number[]>([]);
|
||||
const [allIds, setAllIds] = useState<number[]>([]);
|
||||
const [categoryTree, setCategoryTree] = useState<IProductCategory[]>([]);
|
||||
const [categoryTree, setCategoryTree] = useState<IProductCategoryTree[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
getCategoryTree().then((res) => setCategoryTree(res.data.data ?? []));
|
||||
}, []);
|
||||
|
||||
const columns: XinTableColumn<IProductCategory>[] = [
|
||||
{
|
||||
@@ -63,6 +68,7 @@ const ProductCategoryPage: React.FC = () => {
|
||||
dataIndex: 'sort',
|
||||
valueType: 'digit',
|
||||
hideInSearch: true,
|
||||
initialValue: 0,
|
||||
fieldProps: { min: 0 },
|
||||
align: 'center',
|
||||
},
|
||||
@@ -86,7 +92,7 @@ const ProductCategoryPage: React.FC = () => {
|
||||
},
|
||||
{
|
||||
title: '分类图标',
|
||||
dataIndex: 'icon',
|
||||
dataIndex: 'icon_id',
|
||||
valueType: 'image',
|
||||
fieldProps: {
|
||||
action: '/product/category/upload',
|
||||
@@ -100,8 +106,8 @@ const ProductCategoryPage: React.FC = () => {
|
||||
return (
|
||||
<Image
|
||||
src={url}
|
||||
width={60}
|
||||
height={60}
|
||||
width={32}
|
||||
height={32}
|
||||
style={{ objectFit: 'cover', borderRadius: 4 }}
|
||||
/>
|
||||
);
|
||||
@@ -134,7 +140,6 @@ const ProductCategoryPage: React.FC = () => {
|
||||
handleRequest: async () => {
|
||||
const res = await getCategoryTable();
|
||||
const tree = res.data.data ?? [];
|
||||
setCategoryTree(tree);
|
||||
setAllIds(collectAllIds(tree));
|
||||
return { data: tree, total: tree.length };
|
||||
},
|
||||
|
||||
+78
-20
@@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Drawer,
|
||||
Form,
|
||||
Form, Image,
|
||||
Input,
|
||||
InputNumber,
|
||||
message,
|
||||
@@ -21,7 +21,7 @@ import type IProduct from '@/domain/iProduct.ts';
|
||||
import type { IBatchPriceUpdate, IPriceMatrixRow } from '@/domain/iProduct.ts';
|
||||
import { PRODUCT_STATUS_MAP } from '@/domain/iProduct.ts';
|
||||
import type ICustomerLevel from '@/domain/iCustomerLevel.ts';
|
||||
import type IProductCategory from '@/domain/iProductCategory.ts';
|
||||
import type {IProductCategoryTree} from '@/domain/iProductCategory.ts';
|
||||
import { getLevelOptions } from '@/api/customer/level.ts';
|
||||
import { getSupplierOptions } from '@/api/customer/supplier.ts';
|
||||
import type ISupplier from '@/domain/iSupplier.ts';
|
||||
@@ -36,7 +36,7 @@ const { Title, Text } = Typography;
|
||||
const ProductGoodsPage: React.FC = () => {
|
||||
const [levels, setLevels] = useState<ICustomerLevel[]>([]);
|
||||
const [suppliers, setSuppliers] = useState<ISupplier[]>([]);
|
||||
const [categoryTree, setCategoryTree] = useState<IProductCategory[]>([]);
|
||||
const [categoryTree, setCategoryTree] = useState<IProductCategoryTree[]>([]);
|
||||
|
||||
// ===== 价格矩阵抽屉 =====
|
||||
const [matrixOpen, setMatrixOpen] = useState(false);
|
||||
@@ -161,22 +161,61 @@ const ProductGoodsPage: React.FC = () => {
|
||||
width: 70,
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '商品图片',
|
||||
dataIndex: 'image_ids',
|
||||
valueType: 'image',
|
||||
fieldProps: {
|
||||
action: '/product/goods/upload',
|
||||
maxWidth: 3000,
|
||||
maxHeight: 3000,
|
||||
maxSize: 10,
|
||||
mode: 'multiple',
|
||||
maxCount: 5,
|
||||
changeType: "id"
|
||||
},
|
||||
render: (_, record) => {
|
||||
if (!record.images_arr || record.images_arr?.length <= 0) return '-';
|
||||
return (
|
||||
<Space wrap={false}>
|
||||
{record.images_arr.map(i => (
|
||||
<Image
|
||||
key={i.id}
|
||||
src={i.preview_url}
|
||||
width={60}
|
||||
height={60}
|
||||
style={{ objectFit: 'cover', borderRadius: 4 }}
|
||||
/>
|
||||
))}
|
||||
</Space>
|
||||
);
|
||||
},
|
||||
align: 'center',
|
||||
hideInSearch: true,
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
title: '商品名称',
|
||||
dataIndex: 'name',
|
||||
valueType: 'text',
|
||||
colProps: { span: 24 },
|
||||
fieldProps: {
|
||||
styles: { input: { height: 52, fontSize: 22 } }
|
||||
},
|
||||
required: true,
|
||||
rules: [{ required: true, message: '请输入商品名称' }],
|
||||
},
|
||||
{
|
||||
title: '商品描述',
|
||||
dataIndex: 'remark',
|
||||
valueType: 'text',
|
||||
hideInSearch: true,
|
||||
hideInTable: true,
|
||||
colProps: { span: 24 },
|
||||
},
|
||||
{
|
||||
title: '规格/包规',
|
||||
dataIndex: 'spec',
|
||||
valueType: 'text',
|
||||
hideInSearch: true,
|
||||
align: "center",
|
||||
},
|
||||
{
|
||||
title: '单位',
|
||||
@@ -184,12 +223,14 @@ const ProductGoodsPage: React.FC = () => {
|
||||
valueType: 'text',
|
||||
hideInSearch: true,
|
||||
initialValue: '斤',
|
||||
align: "center",
|
||||
},
|
||||
{
|
||||
title: '分类',
|
||||
dataIndex: 'category_id',
|
||||
valueType: 'treeSelect',
|
||||
required: true,
|
||||
align: "center",
|
||||
rules: [{ required: true, message: '请选择分类' }],
|
||||
fieldProps: {
|
||||
treeData: categoryTree,
|
||||
@@ -207,6 +248,7 @@ const ProductGoodsPage: React.FC = () => {
|
||||
dataIndex: 'supplier_id',
|
||||
valueType: 'select',
|
||||
hideInSearch: true,
|
||||
align: "center",
|
||||
fieldProps: {
|
||||
options: suppliers.map((s) => ({ label: s.name, value: s.id })),
|
||||
showSearch: true,
|
||||
@@ -216,17 +258,33 @@ const ProductGoodsPage: React.FC = () => {
|
||||
},
|
||||
render: (_, record) => record.supplier?.name ?? '-',
|
||||
},
|
||||
{
|
||||
title: '图文详情',
|
||||
dataIndex: 'content',
|
||||
valueType: 'richText',
|
||||
hideInSearch: true,
|
||||
hideInTable: true,
|
||||
colProps: { span: 24 },
|
||||
fieldProps: {
|
||||
height: 400,
|
||||
groupId: 11,
|
||||
placeholder: '输入商品图文详情,支持插入图片',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '等级价格',
|
||||
dataIndex: 'prices',
|
||||
hideInForm: true,
|
||||
hideInSearch: true,
|
||||
width: 370,
|
||||
align: 'center',
|
||||
render: (_, record) => (
|
||||
<Space size={[0, 4]} wrap>
|
||||
<Space wrap>
|
||||
{record.prices?.length
|
||||
? record.prices.map((p) => (
|
||||
<Tag key={p.level_id} color="geekblue">
|
||||
{p.level?.name ?? `等级${p.level_id}`} ¥{p.price}
|
||||
<Tag key={p.id} color="geekblue">
|
||||
{p.level?.name ?? `等级${p.level_id}`}
|
||||
<span style={{color: 'red', marginLeft: 5 }}>¥{p.price ?? '未设定'}</span>
|
||||
</Tag>
|
||||
))
|
||||
: '-'}
|
||||
@@ -258,19 +316,12 @@ const ProductGoodsPage: React.FC = () => {
|
||||
},
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '备注',
|
||||
dataIndex: 'remark',
|
||||
valueType: 'textarea',
|
||||
hideInSearch: true,
|
||||
hideInTable: true,
|
||||
fieldProps: { rows: 2 },
|
||||
},
|
||||
{
|
||||
title: '等级价格设置',
|
||||
dataIndex: 'prices',
|
||||
hideInTable: true,
|
||||
hideInSearch: true,
|
||||
colProps: { span: 24 },
|
||||
fieldRender: () => (
|
||||
<Form.List name="prices">
|
||||
{(fields, { add, remove }) => (
|
||||
@@ -281,7 +332,7 @@ const ProductGoodsPage: React.FC = () => {
|
||||
{...restField}
|
||||
name={[name, 'level_id']}
|
||||
rules={[{ required: true, message: '请选择等级' }]}
|
||||
className="!mb-0"
|
||||
className="mb-0!"
|
||||
>
|
||||
<Select
|
||||
style={{ width: 180 }}
|
||||
@@ -293,7 +344,7 @@ const ProductGoodsPage: React.FC = () => {
|
||||
{...restField}
|
||||
name={[name, 'price']}
|
||||
rules={[{ required: true, message: '请输入单价' }]}
|
||||
className="!mb-0"
|
||||
className="mb-0!"
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
@@ -324,6 +375,13 @@ const ProductGoodsPage: React.FC = () => {
|
||||
</Form.List>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
dataIndex: 'created_at',
|
||||
hideInForm: true,
|
||||
hideInSearch: true,
|
||||
align: 'center',
|
||||
},
|
||||
];
|
||||
|
||||
const tableProps: XinTableProps<IProduct> = {
|
||||
@@ -347,7 +405,7 @@ const ProductGoodsPage: React.FC = () => {
|
||||
},
|
||||
modalProps: {
|
||||
width: 800,
|
||||
styles: {container: {height: '80vh', overflowY: "auto" }}
|
||||
classNames: { body: "overflow-y-auto [scrollbar-width:none] [&::-webkit-scrollbar]:hidden h-[80vh]" },
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user