Browse Source

add:page of goods attr for colors and size

dev
powersir 11 months ago
parent
commit
c72ee97b4f
4 changed files with 604 additions and 48 deletions
  1. +0
    -8
      src/pages/goods/main/attribute/classify.tsx
  2. +281
    -3
      src/pages/goods/main/attribute/colors.tsx
  3. +42
    -34
      src/pages/goods/main/attribute/index.tsx
  4. +281
    -3
      src/pages/goods/main/attribute/size.tsx

+ 0
- 8
src/pages/goods/main/attribute/classify.tsx View File

@@ -282,14 +282,6 @@ export default forwardRef((props, ref) => {
return (
<>
<div>
{/* <div className='flex justify-end content-center'>
<div className="mb-[16px]">
<Button className="ml-5" type='primary' size='large' icon={<PlusOutlined />} onClick={() => {
// seEditData(undefined);
// seEditorVisable(true);
}}> 新增分类 </Button>
</div>
</div> */}
<Table rowKey="id"
scroll={{ x: true }}
columns={columns as ColumnTypes}


+ 281
- 3
src/pages/goods/main/attribute/colors.tsx View File

@@ -1,11 +1,289 @@
import React, { useState, useEffect, useRef, useContext, forwardRef, useImperativeHandle } from 'react';
import { Empty } from 'antd';
import { Space, Table, Button, Input, Select, Divider, Form, Popconfirm } from 'antd';
import type { TableColumnsType } from 'antd';
import type { InputRef } from 'antd';
import type { FormInstance } from 'antd/es/form';
import type { ColumnType, TableProps } from 'antd/es/table';
import { t } from '@/utils/i18n';
import { PlusOutlined, ExclamationCircleFilled, SearchOutlined } from '@ant-design/icons';
import type { GoodsAttrVO, GoodsAttrPageReqVO } from '@/models'
import { antdUtils } from '@/utils/antd';
import { useRequest } from '@/hooks/use-request';
import goodsAttrService from '@/request/service/goods-attr';
import { formatDate } from '@/utils/formatTime';
import { useSetState } from 'ahooks';

const EditableContext = React.createContext<FormInstance<any> | null>(null);

interface EditableRowProps {
index: number;
}

const EditableRow: React.FC<EditableRowProps> = ({ index, ...props }) => {
const [form] = Form.useForm();
return (
<Form form={form} component={false}>
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
</Form>
);
};

interface EditableCellProps {
title: React.ReactNode;
editable: boolean;
children: React.ReactNode;
dataIndex: keyof GoodsAttrVO;
record: GoodsAttrVO;
handleSave: (record: GoodsAttrVO) => void;
}

const EditableCell: React.FC<EditableCellProps> = ({
title,
editable,
children,
dataIndex,
record,
handleSave,
...restProps
}) => {
const [editing, setEditing] = useState(false);
const inputRef = useRef<InputRef>(null);
const form = useContext(EditableContext)!;

useEffect(() => {
if (editing) {
inputRef.current!.focus();
}
}, [editing]);

const toggleEdit = () => {
setEditing(!editing);
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
};

const save = async () => {
try {
const values = await form.validateFields();

toggleEdit();
handleSave({ ...record, ...values });
} catch (errInfo) {
console.log('Save failed:', errInfo);
}
};

let childNode = children;

if (editable) {
childNode = editing ? (
<Form.Item
style={{ margin: 0 }}
name={dataIndex}
rules={[
{
required: true,
message: `${title} is required.`,
},
]}
>
<Input size="middle" ref={inputRef} onPressEnter={save} onBlur={save} />
</Form.Item>
) : (
<div className="editable-cell-value-wrap" style={{ paddingRight: 24 }} onClick={toggleEdit}>
{children}
</div>
);
}

return <td {...restProps}>{childNode}</td>;
};

type EditableTableProps = Parameters<typeof Table>[0];
type ColumnTypes = Exclude<EditableTableProps['columns'], undefined>;

export default forwardRef((props, ref) => {
useImperativeHandle(ref, () => {

addItem
})
const [dataSource, setDataSource] = useState<GoodsAttrVO[]>([]);
const [searchFrom] = Form.useForm();

const [searchState, setSearchState] = useSetState<GoodsAttrPageReqVO>({
pageNo: 1,
pageSize: 10
});
const [total, setTotal] = useState(0)
const searchInput = useRef<InputRef>(null);
const [onSearching, setOnSearching] = useState(false);

const { runAsync: getPageApi } = useRequest(goodsAttrService.getGoodsAttrPageApi, { manual: true });
const { runAsync: updateApi } = useRequest(goodsAttrService.updateGoodsAttrApi, { manual: true });
const { runAsync: verifyApi } = useRequest(goodsAttrService.attrNameEnVerifyUnique, { manual: true });
const { runAsync: deleteApi } = useRequest(goodsAttrService.deleteGoodsAttrApi, { manual: true });

const load = async () => {
const [error, { data }] = await getPageApi(searchFrom.getFieldsValue());
if (!error) {
setDataSource(data.list);
}
};

const deleteItem = async (data: GoodsAttrVO) => {
const [error, { code, msg }] = await deleteApi(data.id);
if (error || code !== 0) {
antdUtils.message?.open({ type: 'error', content: msg ?? '操作失败' })
} else {
antdUtils.message?.open({ type: 'success', content: '删除成功' })
}
await load();
};

const addItem = () => {
console.log("on call add Item")
}

const getColumnSearchProps = (placeholder: string): ColumnType<GoodsAttrVO> => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, close }) => (
<div style={{ padding: 8 }} onKeyDown={(e) => e.stopPropagation()}>
<Input.Search
ref={searchInput}
placeholder={placeholder}
value={selectedKeys[0]}
onChange={(e) => {
setSelectedKeys(e.target.value && e.target.value !== '' ? [e.target.value] : [])
}}
onSearch={(value) => {
if (value === '' && clearFilters) {
clearFilters!!()
}
confirm();
}}
onPressEnter={() => confirm()}
allowClear
style={{ marginBottom: 8, display: 'block' }}
enterButton="搜索"
size="middle"
loading={onSearching}
/>
</div>
),
filterIcon: (filtered: boolean) => (
<SearchOutlined style={{ color: filtered ? 'primaryColor' : undefined }} />
),
onFilterDropdownOpenChange: (visible) => {
if (visible) {
setTimeout(() => searchInput.current?.select(), 100);
}
},
});

const defaultColumns: (ColumnTypes[number] & { editable?: boolean; dataIndex: string })[] = [
{
title: '颜色中文名称',
dataIndex: 'attrNameCn',
key: 'attrNameCn',
align: 'left',
width: '30%',
editable: true,
},
{
title: '颜色英文名称',
dataIndex: 'attrNameEn',
key: 'attrNameEn',
align: 'left',
width: '30%',
editable: true,
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
align: 'center',
render: (value: number) => {
return formatDate(new Date(value), "YYYY-mm-dd HH:MM:SS")
}
},
{
title: t("QkOmYwne" /* 操作 */),
dataIndex: 'operation',
key: 'action',
render: (value: GoodsAttrVO) =>
dataSource.length >= 1 ? (
<Popconfirm title="确认要将该属性删除吗?" onConfirm={() => deleteItem(value)}>
<a>删除</a>
</Popconfirm>
) : null,
},
];

const columns = defaultColumns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
...getColumnSearchProps("请输入颜色名称"),
onCell: (record: GoodsAttrVO) => ({
record,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave,
}),
};
});

const handleSave = async (row: GoodsAttrVO) => {
const [error, { code, msg }] = await updateApi(row);
if (!error && code === 0) {
const newData = [...dataSource];
const index = newData.findIndex((item) => row.id === item.id);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row,
});
setDataSource(newData);
} else {
antdUtils.message?.open({ type: 'error', content: msg ?? '更新失败' })
}
};

const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};

useEffect(() => {
load();
}, []);

const onReset = () => {
searchFrom.resetFields()
load()
}

return (
<Empty />
<>
<div>
<Table rowKey="id"
scroll={{ x: true }}
columns={columns as ColumnTypes}
dataSource={dataSource}
components={components}
rowClassName={() => 'editable-row'}
pagination={{
position: ['bottomRight'],
current: searchState.pageNo,
pageSize: searchState.pageSize,
total
}} />
</div>
</>
);
});


+ 42
- 34
src/pages/goods/main/attribute/index.tsx View File

@@ -1,5 +1,6 @@
import React, { useState, useCallback, useRef } from 'react';
import React, { useState, useCallback, useRef, useMemo } from 'react';
import { Tabs, Card, Button } from 'antd';
import { KeepAliveTab, useTabs } from '@/hooks/use-tabs';
import { PlusOutlined } from '@ant-design/icons';
import type { TabsProps } from 'antd';
import Classify from './classify';
@@ -10,57 +11,64 @@ type TabKey = "1" | "2" | "3";

export default () => {
const [currentKey, setCurrentKey] = useState<TabKey>("1");
const [buttonText, setButtonText] = useState("新增分类")
const [buttonText, setButtonText] = useState("新增分类");

const { tabs } = useTabs();

const classifyRef = useRef();
const colorsRef = useRef();
const sizeRef = useRef();

const renderClassify = () => (<Classify ref={classifyRef}/>)
const items: TabsProps['items'] = [
{
key: '1',
label: '商品分类',
children: renderClassify(),
},
{
key: '2',
label: '颜色设置',
children: (<Colors ref={colorsRef}/>),
},
{
key: '3',
label: '尺码设置',
children: (<Size ref={sizeRef}/>),
},
];
const items = useMemo(() => {
return [
{
key: '1',
label: '商品分类',
children: (<Classify ref={classifyRef} />),
},
{
key: '2',
label: '颜色设置',
children: (<Colors ref={colorsRef} />),
},
{
key: '3',
label: '尺码设置',
children: (<Size ref={sizeRef} />),
},
];
}, []);

const onChange = (key: string) => {
setCurrentKey(key as TabKey);
setButtonText(key === "1"? "新增分类": (key === "2" ? "新增颜色" : "新增尺码"));
setButtonText(key === "1" ? "新增分类" : (key === "2" ? "新增颜色" : "新增尺码"));
};

const onAdd = useCallback(()=> {
console.log(classifyRef)
console.log(colorsRef)
console.log(sizeRef)
if(currentKey === "1") {
} else if(currentKey === "2") {
const onAdd = useCallback(() => {
console.log(classifyRef);
console.log(colorsRef);
console.log(sizeRef);
if (currentKey === "1") {
} else if (currentKey === "2") {

} else {

}
}, [classifyRef, colorsRef, sizeRef])
return (
<>
<Card className='dark:bg-[rgb(33,41,70)] bg-white roundle-lg px[12px]'>
<div className='static'>
<Tabs defaultActiveKey="1" items={items} onChange={onChange} />
<div className="mb-[16px] absolute top-[24px] right-[24px]">
<Button className="ml-5" type='primary' size='large' icon={<PlusOutlined />} onClick={onAdd}> {buttonText} </Button>
</div>
<Tabs defaultActiveKey="1" onChange={onChange}
items={items}
tabPosition={"left"}
// tabBarExtraContent={{
// right: <Button className="ml-5" type='primary' size='large' icon={<PlusOutlined />} onClick={onAdd}> {buttonText} </Button>
// }}
>
</Tabs>
</div>
</Card>
</>


+ 281
- 3
src/pages/goods/main/attribute/size.tsx View File

@@ -1,11 +1,289 @@
import React, { useState, useEffect, useRef, useContext, forwardRef, useImperativeHandle } from 'react';
import { Empty } from 'antd';
import { Space, Table, Button, Input, Select, Divider, Form, Popconfirm } from 'antd';
import type { TableColumnsType } from 'antd';
import type { InputRef } from 'antd';
import type { FormInstance } from 'antd/es/form';
import type { ColumnType, TableProps } from 'antd/es/table';
import { t } from '@/utils/i18n';
import { PlusOutlined, ExclamationCircleFilled, SearchOutlined } from '@ant-design/icons';
import type { GoodsAttrVO, GoodsAttrPageReqVO } from '@/models'
import { antdUtils } from '@/utils/antd';
import { useRequest } from '@/hooks/use-request';
import goodsAttrService from '@/request/service/goods-attr';
import { formatDate } from '@/utils/formatTime';
import { useSetState } from 'ahooks';

const EditableContext = React.createContext<FormInstance<any> | null>(null);

interface EditableRowProps {
index: number;
}

const EditableRow: React.FC<EditableRowProps> = ({ index, ...props }) => {
const [form] = Form.useForm();
return (
<Form form={form} component={false}>
<EditableContext.Provider value={form}>
<tr {...props} />
</EditableContext.Provider>
</Form>
);
};

interface EditableCellProps {
title: React.ReactNode;
editable: boolean;
children: React.ReactNode;
dataIndex: keyof GoodsAttrVO;
record: GoodsAttrVO;
handleSave: (record: GoodsAttrVO) => void;
}

const EditableCell: React.FC<EditableCellProps> = ({
title,
editable,
children,
dataIndex,
record,
handleSave,
...restProps
}) => {
const [editing, setEditing] = useState(false);
const inputRef = useRef<InputRef>(null);
const form = useContext(EditableContext)!;

useEffect(() => {
if (editing) {
inputRef.current!.focus();
}
}, [editing]);

const toggleEdit = () => {
setEditing(!editing);
form.setFieldsValue({ [dataIndex]: record[dataIndex] });
};

const save = async () => {
try {
const values = await form.validateFields();

toggleEdit();
handleSave({ ...record, ...values });
} catch (errInfo) {
console.log('Save failed:', errInfo);
}
};

let childNode = children;

if (editable) {
childNode = editing ? (
<Form.Item
style={{ margin: 0 }}
name={dataIndex}
rules={[
{
required: true,
message: `${title} is required.`,
},
]}
>
<Input size="middle" ref={inputRef} onPressEnter={save} onBlur={save} />
</Form.Item>
) : (
<div className="editable-cell-value-wrap" style={{ paddingRight: 24 }} onClick={toggleEdit}>
{children}
</div>
);
}

return <td {...restProps}>{childNode}</td>;
};

type EditableTableProps = Parameters<typeof Table>[0];
type ColumnTypes = Exclude<EditableTableProps['columns'], undefined>;

export default forwardRef((props, ref) => {
useImperativeHandle(ref, () => {

addItem
})
const [dataSource, setDataSource] = useState<GoodsAttrVO[]>([]);
const [searchFrom] = Form.useForm();

const [searchState, setSearchState] = useSetState<GoodsAttrPageReqVO>({
pageNo: 1,
pageSize: 10
});
const [total, setTotal] = useState(0)
const searchInput = useRef<InputRef>(null);
const [onSearching, setOnSearching] = useState(false);

const { runAsync: getPageApi } = useRequest(goodsAttrService.getGoodsAttrPageApi, { manual: true });
const { runAsync: updateApi } = useRequest(goodsAttrService.updateGoodsAttrApi, { manual: true });
const { runAsync: verifyApi } = useRequest(goodsAttrService.attrNameEnVerifyUnique, { manual: true });
const { runAsync: deleteApi } = useRequest(goodsAttrService.deleteGoodsAttrApi, { manual: true });

const load = async () => {
const [error, { data }] = await getPageApi(searchFrom.getFieldsValue());
if (!error) {
setDataSource(data.list);
}
};

const deleteItem = async (data: GoodsAttrVO) => {
const [error, { code, msg }] = await deleteApi(data.id);
if (error || code !== 0) {
antdUtils.message?.open({ type: 'error', content: msg ?? '操作失败' })
} else {
antdUtils.message?.open({ type: 'success', content: '删除成功' })
}
await load();
};

const addItem = () => {
console.log("on call add Item")
}

const getColumnSearchProps = (placeholder: string): ColumnType<GoodsAttrVO> => ({
filterDropdown: ({ setSelectedKeys, selectedKeys, confirm, clearFilters, close }) => (
<div style={{ padding: 8 }} onKeyDown={(e) => e.stopPropagation()}>
<Input.Search
ref={searchInput}
placeholder={placeholder}
value={selectedKeys[0]}
onChange={(e) => {
setSelectedKeys(e.target.value && e.target.value !== '' ? [e.target.value] : [])
}}
onSearch={(value) => {
if (value === '' && clearFilters) {
clearFilters!!()
}
confirm();
}}
onPressEnter={() => confirm()}
allowClear
style={{ marginBottom: 8, display: 'block' }}
enterButton="搜索"
size="middle"
loading={onSearching}
/>
</div>
),
filterIcon: (filtered: boolean) => (
<SearchOutlined style={{ color: filtered ? 'primaryColor' : undefined }} />
),
onFilterDropdownOpenChange: (visible) => {
if (visible) {
setTimeout(() => searchInput.current?.select(), 100);
}
},
});

const defaultColumns: (ColumnTypes[number] & { editable?: boolean; dataIndex: string })[] = [
{
title: '尺码中文名称',
dataIndex: 'attrNameCn',
key: 'attrNameCn',
align: 'left',
width: '30%',
editable: true,
},
{
title: '尺码英文名称',
dataIndex: 'attrNameEn',
key: 'attrNameEn',
align: 'left',
width: '30%',
editable: true,
},
{
title: '创建时间',
dataIndex: 'createTime',
key: 'createTime',
align: 'center',
render: (value: number) => {
return formatDate(new Date(value), "YYYY-mm-dd HH:MM:SS")
}
},
{
title: t("QkOmYwne" /* 操作 */),
dataIndex: 'operation',
key: 'action',
render: (value: GoodsAttrVO) =>
dataSource.length >= 1 ? (
<Popconfirm title="确认要将该属性删除吗?" onConfirm={() => deleteItem(value)}>
<a>删除</a>
</Popconfirm>
) : null,
},
];

const columns = defaultColumns.map((col) => {
if (!col.editable) {
return col;
}
return {
...col,
...getColumnSearchProps("请输入尺码名称"),
onCell: (record: GoodsAttrVO) => ({
record,
editable: col.editable,
dataIndex: col.dataIndex,
title: col.title,
handleSave,
}),
};
});

const handleSave = async (row: GoodsAttrVO) => {
const [error, { code, msg }] = await updateApi(row);
if (!error && code === 0) {
const newData = [...dataSource];
const index = newData.findIndex((item) => row.id === item.id);
const item = newData[index];
newData.splice(index, 1, {
...item,
...row,
});
setDataSource(newData);
} else {
antdUtils.message?.open({ type: 'error', content: msg ?? '更新失败' })
}
};

const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};

useEffect(() => {
load();
}, []);

const onReset = () => {
searchFrom.resetFields()
load()
}

return (
<Empty />
<>
<div>
<Table rowKey="id"
scroll={{ x: true }}
columns={columns as ColumnTypes}
dataSource={dataSource}
components={components}
rowClassName={() => 'editable-row'}
pagination={{
position: ['bottomRight'],
current: searchState.pageNo,
pageSize: searchState.pageSize,
total
}} />
</div>
</>
);
});


Loading…
Cancel
Save