瀏覽代碼

add: goods attribute

dev
powersir 1 年之前
父節點
當前提交
2fe62d3d17
共有 7 個檔案被更改,包括 456 行新增0 行删除
  1. +18
    -0
      src/models/goods-classify.data.ts
  2. +1
    -0
      src/models/index.ts
  3. +309
    -0
      src/pages/goods/main/attribute/classify.tsx
  4. +11
    -0
      src/pages/goods/main/attribute/colors.tsx
  5. +69
    -0
      src/pages/goods/main/attribute/index.tsx
  6. +11
    -0
      src/pages/goods/main/attribute/size.tsx
  7. +37
    -0
      src/request/service/goods-classify.ts

+ 18
- 0
src/models/goods-classify.data.ts 查看文件

@@ -0,0 +1,18 @@

//GoodsClassifyVO - 商品分类
export interface GoodsClassifyVO {
//分类名称,分类名称,最大长度125
classifyName: string;
createTime: Date;
id: number;
//是否是默认(1: 是, 2: 否)
isDefault: string;
}


export interface GoodsClassifyPageReqVO extends PageParam {
//分类名称
classifyName?: string;
//创建时间
createTime?: string[];
}

+ 1
- 0
src/models/index.ts 查看文件

@@ -14,6 +14,7 @@ export * from './oauth2.data.ts'
export * from './platform.data.ts'
export * from './category.data.ts'
export * from './material-classify.data.ts'
export * from './goods-classify.data.ts'

export interface ResponseDTO<T>{
code: number;


+ 309
- 0
src/pages/goods/main/attribute/classify.tsx 查看文件

@@ -0,0 +1,309 @@
import React, { useState, useEffect, useRef, useContext, MutableRefObject, forwardRef, useImperativeHandle } from 'react';
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, UndoOutlined } from '@ant-design/icons';
import type { GoodsClassifyPageReqVO, GoodsClassifyVO } from '@/models'
import { antdUtils } from '@/utils/antd';
import { useRequest } from '@/hooks/use-request';
import goodsClassifyService from '@/request/service/goods-classify';
import { formatDate } from '@/utils/formatTime';
import { useSetState } from 'ahooks';

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

interface EditableRowProps {
index: number;
}

type ClassifyProps = {
ref: MutableRefObject<any>
}

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 GoodsClassifyVO;
record: GoodsClassifyVO;
handleSave: (record: GoodsClassifyVO) => 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, () => {
add: () => {
console.log("add")
}
})
const [dataSource, setDataSource] = useState<GoodsClassifyVO[]>([]);
const [searchFrom] = Form.useForm();

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

const { runAsync: getPageApi } = useRequest(goodsClassifyService.getGoodsClassifyPageApi, { manual: true });
const { runAsync: updateApi } = useRequest(goodsClassifyService.updateGoodsClassifyApi, { manual: true });
const { runAsync: verifyApi } = useRequest(goodsClassifyService.classifyNameVerifyUnique, { manual: true });
const { runAsync: deleteApi } = useRequest(goodsClassifyService.deleteGoodsClassifyApi, { manual: true });

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

const showDeleteConfirm = (data: GoodsClassifyVO) => {
antdUtils.modal?.confirm({
title: '确认要将该分类删除吗?',
icon: <ExclamationCircleFilled />,
content: '请注意删除以后不可恢复!',
okText: '删除',
okType: 'danger',
cancelText: '取消',
onOk() {
return new Promise(async (resolve) => {
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();
resolve('')
}).catch(() => antdUtils.message?.open({
type: 'error',
content: '操作失败',
}));
},
onCancel() {
},
});
};

const getColumnSearchProps = (placeholder: string): ColumnType<GoodsClassifyVO> => ({
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: 'classifyName',
key: 'classifyName',
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: GoodsClassifyVO) =>
dataSource.length >= 1 ? (
<Popconfirm title="确认要将该分类删除吗?" onConfirm={() => showDeleteConfirm(value)}>
<a>删除</a>
</Popconfirm>
) : null,
},
];

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

const handleSave = async (row: GoodsClassifyVO) => {
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 (
<>
<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}
dataSource={dataSource}
components={components}
rowClassName={() => 'editable-row'}
pagination={{
position: ['bottomRight'],
current: searchState.pageNo,
pageSize: searchState.pageSize,
total
}} />
</div>
</>
);
});


+ 11
- 0
src/pages/goods/main/attribute/colors.tsx 查看文件

@@ -0,0 +1,11 @@
import React, { useState, useEffect, useRef, useContext, forwardRef, useImperativeHandle } from 'react';
import { Empty } from 'antd';

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

})
return (
<Empty />
);
});

+ 69
- 0
src/pages/goods/main/attribute/index.tsx 查看文件

@@ -0,0 +1,69 @@
import React, { useState, useCallback, useRef } from 'react';
import { Tabs, Card, Button } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import type { TabsProps } from 'antd';
import Classify from './classify';
import Colors from './colors';
import Size from './size';

type TabKey = "1" | "2" | "3";

export default () => {
const [currentKey, setCurrentKey] = useState<TabKey>("1");
const [buttonText, setButtonText] = useState("新增分类")
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 onChange = (key: string) => {
setCurrentKey(key as TabKey);
setButtonText(key === "1"? "新增分类": (key === "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>
</div>
</Card>
</>
);
};


+ 11
- 0
src/pages/goods/main/attribute/size.tsx 查看文件

@@ -0,0 +1,11 @@
import React, { useState, useEffect, useRef, useContext, forwardRef, useImperativeHandle } from 'react';
import { Empty } from 'antd';

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

})
return (
<Empty />
);
});

+ 37
- 0
src/request/service/goods-classify.ts 查看文件

@@ -0,0 +1,37 @@
import request from '@/request';
import { GoodsClassifyVO, GoodsClassifyPageReqVO, PageData } from '@/models';

const BASE_URL = '/admin-api/main/classify';

export default {

// 更新商品分类
updateGoodsClassifyApi: (data: GoodsClassifyVO) => {
return request.put(`${BASE_URL}/update`, data);
},

// 创建商品分类
createGoodsClassifyApi: (data: GoodsClassifyVO) => {
return request.post(`${BASE_URL}/create`, data);
},

// 获得商品分类分页
getGoodsClassifyPageApi: (params: GoodsClassifyPageReqVO) => {
return request.get<PageData<GoodsClassifyVO>>(`${BASE_URL}/page`, { params });
},

// 查询商品分类详情
getGoodsClassifyApi: (id: number) => {
return request.get(`${BASE_URL}/get?id=${id}`);
},

// 删除商品分类
deleteGoodsClassifyApi: (id: number) => {
return request.delete(`${BASE_URL}/delete?id=${id}`);
},

// 商品分类名称唯一校验
classifyNameVerifyUnique: (classifyName: string) => {
return request.delete(`${BASE_URL}/classifyNameVerifyUnique?classifyName=${classifyName}`);
},
};

Loading…
取消
儲存