文档:增加功能列表

后端 + 前端:完善展示分类功能
This commit is contained in:
YunaiV 2019-05-05 20:19:38 +08:00
parent 83a3689088
commit dcc58fbba4
9 changed files with 480 additions and 276 deletions

View File

@ -27,6 +27,12 @@ class PicturesWall extends React.Component {
],
};
componentDidMount() {
if (this.props.urls) {
this.setUrls(this.props.urls);
}
}
handleCancel = () => this.setState({ previewVisible: false })
handlePreview = (file) => {
@ -137,6 +143,14 @@ class PicturesWall extends React.Component {
return urls;
};
getUrl = () => {
let urls = this.getUrls();
if (urls && urls.length > 0) {
return urls[0];
}
return undefined;
};
setUrls = (urls) => {
// let urls = this.props.urls;
if (urls) {
@ -188,7 +202,7 @@ class PicturesWall extends React.Component {
PicturesWall.propTypes = {
maxLength: Number, // 最大照片墙图片数量
// urls: String[], // 初始图片列表
urls: Array, // 初始图片列表
};
export default PicturesWall;

View File

@ -69,7 +69,6 @@ export default {
});
},
* add({ payload }, { call, put }) {
const { callback, body } = payload;
// 显示加载中
yield put({
type: 'changeModalLoading',
@ -77,6 +76,7 @@ export default {
});
// 请求
const { callback, body } = payload;
const response = yield call(addAdmin, body);
// 响应
if (response.code === 0) {

View File

@ -5,61 +5,129 @@ export default {
namespace: 'productCategoryList',
state: {
// 分页列表相关
list: [],
listLoading: false,
// 添加 or 修改表单相关
modalVisible: false,
modalType: undefined, // 'add' or 'update' 表单
formVals: {}, // 当前表单值
modalLoading: false,
},
effects: {
*add({ payload }, { call, put }) {
const { callback, body } = payload;
const response = yield call(productCategoryAdd, body);
if (callback) {
callback(response);
}
yield put({
type: 'tree',
payload: {},
});
},
*update({ payload }, { call, put }) {
const { callback, body } = payload;
const response = yield call(productCategoryUpdate, body);
if (callback) {
callback(response);
}
yield put({
type: 'tree',
payload: {},
});
},
*updateStatus({ payload }, { call, put }) {
const { callback, body } = payload;
const response = yield call(productCategoryUpdateStatus, body);
if (callback) {
callback(response);
}
yield put({
type: 'tree',
payload: {},
});
},
*delete({ payload }, { call, put }) {
const response = yield call(productCategoryDelete, payload);
message.info('删除成功!');
yield put({
type: 'tree',
payload: {},
});
},
// 查询列表
*tree({ payload }, { call, put }) {
// 显示加载中
yield put({
type: 'changeListLoading',
payload: true,
});
// 请求
const { queryParams } = payload;
// 响应
const response = yield call(productCategoryTree, queryParams);
message.info('查询成功!');
yield put({
type: 'treeSuccess',
payload: {
list: response.data,
},
});
// 隐藏加载中
yield put({
type: 'changeListLoading',
payload: false,
});
},
*add({ payload }, { call, put }) {
// 显示加载中
yield put({
type: 'changeModalLoading',
payload: true,
});
// 请求
const { callback, body } = payload;
const response = yield call(productCategoryAdd, body);
// 响应
if (response.code === 0) {
if (callback) {
callback(response);
}
// 刷新列表
yield put({
type: 'tree',
payload: {},
});
}
// 隐藏加载中
yield put({
type: 'changeModalLoading',
payload: false,
});
},
*update({ payload }, { call, put }) {
// 显示加载中
yield put({
type: 'changeModalLoading',
payload: true,
});
// 请求
const { callback, body } = payload;
const response = yield call(productCategoryUpdate, body);
// 响应
if (response.code === 0) {
if (callback) {
callback(response);
}
// 刷新列表
yield put({
type: 'tree',
payload: {},
});
}
// 隐藏加载中
yield put({
type: 'changeModalLoading',
payload: false,
});
},
*updateStatus({ payload }, { call, put }) {
// 请求
const { callback, body } = payload;
// 响应
const response = yield call(productCategoryUpdateStatus, body);
if(response.code === 0) {
message.info('更新状态成功!');
if (callback) {
callback(response);
}
yield put({
type: 'tree',
payload: {},
});
}
},
*delete({ payload }, { call, put }) {
// 响应
const response = yield call(productCategoryDelete, payload);
if(response.code === 0) {
message.info('删除成功!');
if (callback) {
callback(response);
}
yield put({
type: 'tree',
payload: {},
});
}
},
},
@ -70,5 +138,25 @@ export default {
...payload,
};
},
// 修改加载中的状态
changeModalLoading(state, { payload }) {
return {
...state,
modalLoading: payload,
};
},
changeListLoading(state, { payload }) {
return {
...state,
listLoading: payload,
};
},
// 设置所有属性
setAll(state, { payload }) {
return {
...state,
...payload,
};
}
},
};

View File

@ -213,7 +213,7 @@ const AddOrUpdateForm = Form.create()(props => {
// 清空表单
form.resetFields();
// 提示
message.success('添加成功');
message.success('新建成功');
// 关闭弹窗
handleModalVisible();
},
@ -232,7 +232,7 @@ const AddOrUpdateForm = Form.create()(props => {
// 清空表单
form.resetFields();
// 提示
message.success('更新成功');
message.success('编辑成功');
// 关闭弹窗
handleModalVisible();
},
@ -242,7 +242,7 @@ const AddOrUpdateForm = Form.create()(props => {
});
};
const title = modalType === 'add' ? '新建管理员' : '更新管理员';
const title = modalType === 'add' ? '新建' : '更新';
return (
<Modal
destroyOnClose
@ -389,11 +389,6 @@ const RoleAssignModal = Form.create()(props => {
// 主界面
@Form.create()
class AdminList extends PureComponent {
state = {
// 分配角色弹窗
modalRoleVisible: false,
modalRoleRow: {},
};
componentDidMount() {
const { dispatch } = this.props;
@ -461,6 +456,7 @@ class AdminList extends PureComponent {
dispatch,
handleModalVisible: this.handleModalVisible, // Function
};
// 分配角色 Modal 属性
const roleAssignModal = {
loading: roleAssignLoading,
@ -485,7 +481,7 @@ class AdminList extends PureComponent {
type="primary"
onClick={() => this.handleModalVisible(true, 'add', {})}
>
新建管理
新建
</Button>
</div>
</div>
@ -500,4 +496,4 @@ class AdminList extends PureComponent {
}
}
export default AdminList;
export default AdminList;

View File

@ -3,37 +3,208 @@
import React, { PureComponent, Fragment } from 'react';
import { connect } from 'dva';
import moment from 'moment';
import {Card, Form, Input, Select, Button, Modal, message, Table, Divider, InputNumber} from 'antd';
import {Card, Form, Input, Select, Button, Modal, message, Table, Divider, InputNumber, TreeSelect, Spin} from 'antd';
import PageHeaderWrapper from '@/components/PageHeaderWrapper';
import styles from './ProductCategoryList.less';
import PicturesWall from "../../components/Image/PicturesWall";
const FormItem = Form.Item;
const { Option } = Select;
const status = ['未知', '开启', '禁用'];
// 添加 form 表单
const CreateForm = Form.create()(props => {
const { modalVisible, form, handleAdd, handleModalVisible, modalType, initValues } = props;
// 列表
function List({ dataSource, loading, dispatch,
handleModalVisible}) {
function handleStatus(record) {
Modal.confirm({
title: record.status === 1 ? '确认禁用?' : '确认开启?',
content: `${record.name}`,
onOk() {
dispatch({
type: 'productCategoryList/updateStatus',
payload: {
body: {
id: record.id,
status: record.status === 1 ? 2 : 1,
}
},
});
},
onCancel() {},
});
}
function handleDelete(record) {
Modal.confirm({
title: `确认删除?`,
content: `${record.name}`,
onOk() {
dispatch({
type: 'productCategoryList/delete',
payload: {
id: record.id,
},
});
},
onCancel() {},
});
}
const columns = [
// {
// title: 'id',
// dataIndex: 'id',
// render: text => <strong>{text}</strong>,
// },
{
title: '分类名称',
dataIndex: 'name',
},
{
title: '图片',
dataIndex: 'picUrl',
render(val) {
return <img width={120} src={val} />;
},
},
{
title: '排序值',
dataIndex: 'sort',
},
{
title: '状态',
dataIndex: 'status',
render(val) {
return <span>{status[val]}</span>;
},
},
{
title: '描述',
dataIndex: 'description',
},
{
title: '创建时间',
dataIndex: 'createTime',
render: val => <span>{moment(val).format('YYYY-MM-DD')}</span>,
},
{
title: '操作',
render: (text, record) => {
const statusText = record.status === 1 ? '禁用' : '开启';
return (
<Fragment>
<a onClick={() => handleModalVisible(true, 'update', record)}>编辑</a>
<Divider type="vertical"/>
<a className={styles.tableDelete} onClick={() => handleStatus(record)}>
{statusText}
</a>
{
record.status === 2 ? (
<span>
<Divider type="vertical"/>
<a className={styles.tableDelete} onClick={() => handleDelete(record)}>
删除
</a>
</span>
) : ''
}
</Fragment>
);
}
},
];
return (
<Table
defaultExpandAllRows={true}
columns={columns}
loading={loading}
rowKey="id"
dataSource={dataSource} />
)
}
// 新建 form 表单
const AddOrUpdateForm = Form.create()(props => {
const { dispatch, loading, modalVisible, form, handleModalVisible, modalType, categoryTree, formVals } = props;
let picturesWall = null;
const okHandle = () => {
form.validateFields((err, fieldsValue) => {
form.validateFields((err, fields) => {
if (err) return;
form.resetFields();
handleAdd({
fields: fieldsValue,
modalType,
initValues,
});
if (modalType === 'add') {
dispatch({
type: 'productCategoryList/add',
payload: {
body: {
...fields,
picUrl: picturesWall ? picturesWall.getUrl() : undefined,
},
callback: () => {
// 清空表单
form.resetFields();
// 提示
message.success('新建成功');
// 关闭弹窗
handleModalVisible();
},
},
});
} else {
dispatch({
type: 'productCategoryList/update',
payload: {
body: {
...formVals,
...fields,
picUrl: picturesWall ? picturesWall.getUrl() : undefined,
},
callback: () => {
// 清空表单
form.resetFields();
// 提示
message.success('编辑成功');
// 关闭弹窗
handleModalVisible();
},
},
});
}
});
};
const selectStyle = {
width: 200,
};
function onPidChange(value) {
formVals.pid = parseInt(value);
}
const title = modalType === 'add' ? '添加一个 Resource' : '更新一个 Resource';
const okText = modalType === 'add' ? '添加' : '更新';
// 处理分类筛选
const buildSelectTree = (list) => {
return list.map(item => {
let children = [];
// if (item.children) { // 暂时不允许添加二级分类。
// children = buildSelectTree(item.children);
// }
return {
title: item.name,
value: item.id,
key: item.id,
children,
};
});
};
let categoryTreeSelect = buildSelectTree(categoryTree);
categoryTreeSelect.unshift({
title: '无父分类',
value: 0,
key: 0,
children: [],
});
const title = modalType === 'add' ? '新建分类' : '编辑分类';
const okText = modalType === 'add' ? '新建' : '编辑';
return (
<Modal
destroyOnClose
@ -43,51 +214,61 @@ const CreateForm = Form.create()(props => {
okText={okText}
onCancel={() => handleModalVisible()}
>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="分类名">
{form.getFieldDecorator('name', {
rules: [{ required: true, message: '请输入分类名!', min: 2 }],
initialValue: initValues.name,
})(<Input placeholder="请输入" />)}
</FormItem>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="分类图片">
{form.getFieldDecorator('picUrl', {
initialValue: initValues.handler,
})(<Input placeholder="请输入" />)}
</FormItem>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="父分类编号">
{form.getFieldDecorator('pid', {
rules: [{ required: true, message: '请输入父分类编号!' }],
initialValue: initValues.pid,
})(<Input placeholder="请输入" />)}
</FormItem>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="排序值">
{form.getFieldDecorator('sort', {
rules: [{ required: true, message: '请输入排序值!' }],
initialValue: initValues.sort,
})(<InputNumber placeholder="请输入" />)}
</FormItem>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="描述">
{form.getFieldDecorator('description', {
rules: [{ required: true, message: '请输入描述!' }],
initialValue: initValues.description,
})(<Input.TextArea placeholder="请输入" />)}
</FormItem>
<Spin spinning={loading}>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="分类名称">
{form.getFieldDecorator('name', {
rules: [{ required: true, message: '请输入分类名称!', min: 2 }],
initialValue: formVals.name,
})(<Input placeholder="请输入" />)}
</FormItem>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="父分类">
{form.getFieldDecorator('pid', {
rules: [{ required: true, message: '请选择父分类!' }],
initialValue: formVals.pid,
})(
<TreeSelect
showSearch
style={{ width: 300 }}
dropdownStyle={{ maxHeight: 400, overflow: 'auto' }}
treeData={categoryTreeSelect}
placeholder="选择父分类"
onChange={onPidChange}
/>
)}
</FormItem>
{
formVals.pid > 0 ? (
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="分类图片"
extra="建议尺寸480*480PX大小不超过 2M" required={true}>
<PicturesWall urls={formVals.picUrl ? [formVals.picUrl] : undefined} ref={(node) => picturesWall = node} maxLength={1} />
</FormItem>
) : ''
}
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="排序值">
{form.getFieldDecorator('sort', {
rules: [{ required: true, message: '请输入排序值!' }],
initialValue: formVals.sort,
})(<InputNumber placeholder="请输入" />)}
</FormItem>
<FormItem labelCol={{ span: 5 }} wrapperCol={{ span: 15 }} label="描述">
{form.getFieldDecorator('description', {
rules: [{ required: true, message: '请输入描述!' }],
initialValue: formVals.description,
})(<Input.TextArea placeholder="请输入" />)}
</FormItem>
</Spin>
</Modal>
);
});
@connect(({ productCategoryList, loading }) => ({
productCategoryList,
list: productCategoryList.list,
loading: loading.models.productCategoryList,
@connect(({ productCategoryList, }) => ({
...productCategoryList,
// list: productCategoryList.list,
// loading: loading.models.productCategoryList,
}))
@Form.create()
class ProductCategoryList extends PureComponent {
state = {
modalVisible: false,
modalType: 'add', //add update
initValues: {},
};
componentDidMount() {
const { dispatch } = this.props;
@ -97,162 +278,41 @@ class ProductCategoryList extends PureComponent {
});
}
handleModalVisible = (flag, modalType, initValues) => {
this.setState({
modalVisible: !!flag,
initValues: initValues || {},
modalType: modalType || 'add',
handleModalVisible = (modalVisible, modalType, record) => {
const { dispatch } = this.props;
dispatch({
type: 'productCategoryList/setAll',
payload: {
modalVisible,
modalType,
formVals: record || {}
},
});
};
handleAdd = ({ fields, modalType, initValues }) => {
const { dispatch } = this.props;
if (modalType === 'add') {
dispatch({
type: 'productCategoryList/add',
payload: {
body: {
...fields,
},
callback: () => {
message.success('添加成功');
this.handleModalVisible();
},
},
});
} else {
dispatch({
type: 'productCategoryList/update',
payload: {
body: {
...initValues,
...fields,
},
callback: () => {
message.success('更新成功');
this.handleModalVisible();
},
},
});
}
};
handleStatus(row) {
const { dispatch, data } = this.props;
const title = row.status === 1 ? '确认禁用?' : '确认开启?';
const updateStatus = row.status === 1 ? 2 : 1;
Modal.confirm({
title: `${title}`,
content: `${row.name}`,
onOk() {
dispatch({
type: 'productCategoryList/updateStatus',
payload: {
body: {
id: row.id,
status: updateStatus,
}
},
});
},
onCancel() {},
});
}
handleDelete(row) {
const { dispatch } = this.props;
Modal.confirm({
title: `确认删除?`,
content: `${row.displayName}`,
onOk() {
dispatch({
type: 'productCategoryList/delete',
payload: {
id: row.id,
},
});
},
onCancel() {},
});
}
render() {
const { list } = this.props;
const { modalVisible, modalType, initValues } = this.state;
const parentMethods = {
handleAdd: this.handleAdd,
handleModalVisible: this.handleModalVisible,
modalType,
initValues,
const { dispatch,
list,listLoading,
modalVisible, modalType, formVals, modalLoading,} = this.props;
// 列表属性
const listProps = {
dataSource: list,
dispatch,
loading: listLoading,
handleModalVisible: this.handleModalVisible, // Function
};
const that = this;
const columns = [
{
title: 'id',
dataIndex: 'id',
render: text => <strong>{text}</strong>,
},
{
title: '分类名',
dataIndex: 'name',
},
{
title: '分类图片',
dataIndex: 'picUrl',
render(val) {
return <img width={120} src={val} />;
},
},
{
title: '排序值',
dataIndex: 'sort',
},
{
title: '创建时间',
dataIndex: 'createTime',
render: val => <span>{moment(val).format('YYYY-MM-DD')}</span>,
},
{
title: '状态',
dataIndex: 'status',
render(val) {
return <span>{status[val]}</span>;
},
},
{
title: '描述',
dataIndex: 'description',
},
{
title: '操作',
render: (text, record) => {
const statusText = record.status === 1 ? '禁用' : '开启';
return (
<Fragment>
<a onClick={() => this.handleModalVisible(true, 'update', record)}>更新</a>
<Divider type="vertical"/>
<a className={styles.tableDelete} onClick={() => this.handleStatus(record)}>
{statusText}
</a>
{
record.status === 2 ? (
<span>
<Divider type="vertical"/>
<a className={styles.tableDelete} onClick={() => this.handleDelete(record)}>
删除
</a>
</span>
) : ''
}
</Fragment>
);
}
},
];
// 添加 or 编辑表单属性
const addOrUpdateFormProps = {
modalVisible,
modalType,
formVals,
dispatch,
loading: modalLoading,
categoryTree: list,
handleModalVisible: this.handleModalVisible, // Function
};
return (
<PageHeaderWrapper>
@ -264,13 +324,14 @@ class ProductCategoryList extends PureComponent {
type="primary"
onClick={() => this.handleModalVisible(true, 'add', {})}
>
新建商品分类
新建分类
</Button>
</div>
</div>
<Table defaultExpandAllRows={true} columns={columns} dataSource={list} rowKey="id" />
<List {...listProps} />
</Card>
<CreateForm {...parentMethods} modalVisible={modalVisible} />
<AddOrUpdateForm {...addOrUpdateFormProps} />
</PageHeaderWrapper>
);
}

View File

@ -0,0 +1,30 @@
如下是 onemall 的功能列表。
* 当功能被完成时,会标记已完成。
* 未完成的功能,欢迎一起来开发。
- [ ] 首页
- 商品相关
- [ ] 分类列表
- [ ] 商品搜索
- [ ] 商品列表(基于分类)
- [ ] 商品列表(基于促销活动)
- [ ] 商品详情
- [ ] 商品收藏
- 订单相关
- [ ] 下单(直接购买)
- [ ] 下单(购物车购买)
- [ ] 下单(拼团)
- [ ] 订单列表
- [ ] 订单详情
- [ ] 支付
- [ ] 退款
- [ ] 购物车
- [ ] 收获地址
- 营销相关
- [ ] 优惠劵
- [ ] 优惠码
- 用户相关
- [ ] 登陆
- [ ] 注册
- [ ] 个人信息

View File

@ -1,5 +1,10 @@
如下是 onemall 的功能列表。
* 当功能被完成时,会标记已完成。
* 未完成的功能,欢迎一起来开发。
- [ ] 概述 TODO
- [ ] 数据分析
- [ ] 数据分析
- [ ] TODO 未开始
- [ ] 店铺资产
- [ ] TODO 未开始
@ -14,9 +19,9 @@
- [ ] 订单评价
- [ ] 会员管理
- [ ] 会员资料
- TODO 需要补充
- TODO 需要补充
- [ ] 营销管理
- [ ] 广告管理
- [ ] 广告管理
- [ ] 优惠劵
- [ ] 优惠码
- [ ] 商品推荐
@ -29,5 +34,5 @@
- [ ] 权限管理
- [ ] 短信管理
- [ ] 短信模板
- [ ] 发送日志
- [ ] 操作日志
- [ ] 发送日志
- [ ] 操作日志

View File

@ -14,6 +14,7 @@ public enum ProductErrorCodeEnum {
PRODUCT_CATEGORY_STATUS_EQUALS(1002001003, "商品分类已经是该状态"),
PRODUCT_CATEGORY_DELETE_ONLY_DISABLE(1002001004, "只有关闭的商品分类才可以删除"),
PRODUCT_CATEGORY_MUST_ENABLE(1002001005, "只有开启的商品分类,才可以使用"),
PRODUCT_CATEGORY_PARENT_CAN_NOT_BE_LEVEL2(1002001005, "父分类必须是一级分类"),
// ========== PRODUCT SPU + SKU 模块 ==========
PRODUCT_SKU_ATTR_CANT_NOT_DUPLICATE(1003002000, "一个 Sku 下,不能有重复的规格"),
@ -46,4 +47,4 @@ public enum ProductErrorCodeEnum {
return message;
}
}
}

View File

@ -47,11 +47,8 @@ public class ProductCategoryServiceImpl implements ProductCategoryService {
@Override
public CommonResult<ProductCategoryBO> addProductCategory(Integer adminId, ProductCategoryAddDTO productCategoryAddDTO) {
// 校验父分类是否存在
if (!ProductCategoryConstants.PID_ROOT.equals(productCategoryAddDTO.getPid())
&& productCategoryMapper.selectById(productCategoryAddDTO.getPid()) == null) {
return ServiceExceptionUtil.error(ProductErrorCodeEnum.PRODUCT_CATEGORY_PARENT_NOT_EXISTS.getCode());
}
// 校验父分类
validParent(productCategoryAddDTO.getPid());
// 保存到数据库
ProductCategoryDO productCategory = ProductCategoryConvert.INSTANCE.convert(productCategoryAddDTO)
.setStatus(ProductCategoryConstants.STATUS_ENABLE);
@ -65,10 +62,8 @@ public class ProductCategoryServiceImpl implements ProductCategoryService {
@Override
public CommonResult<Boolean> updateProductCategory(Integer adminId, ProductCategoryUpdateDTO productCategoryUpdateDTO) {
// 校验分类是否存在
if (productCategoryMapper.selectById(productCategoryUpdateDTO.getId()) == null) {
return ServiceExceptionUtil.error(ProductErrorCodeEnum.PRODUCT_CATEGORY_NOT_EXISTS.getCode());
}
// 校验父分类
validParent(productCategoryUpdateDTO.getPid());
// 校验不能设置自己为父分类
if (productCategoryUpdateDTO.getId().equals(productCategoryUpdateDTO.getPid())) {
return ServiceExceptionUtil.error(ProductErrorCodeEnum.PRODUCT_CATEGORY_PARENT_NOT_SELF.getCode());
@ -153,4 +148,18 @@ public class ProductCategoryServiceImpl implements ProductCategoryService {
|| ProductCategoryConstants.STATUS_DISABLE.equals(status);
}
private void validParent(Integer pid) {
if (!ProductCategoryConstants.PID_ROOT.equals(pid)) {
ProductCategoryDO parentCategory = productCategoryMapper.selectById(pid);
// 校验父分类是否存在
if (parentCategory == null) {
throw ServiceExceptionUtil.exception(ProductErrorCodeEnum.PRODUCT_CATEGORY_PARENT_NOT_EXISTS.getCode());
}
// 父分类必须是一级分类
if (!ProductCategoryConstants.PID_ROOT.equals(parentCategory.getPid())) {
throw ServiceExceptionUtil.exception((ProductErrorCodeEnum.PRODUCT_CATEGORY_PARENT_CAN_NOT_BE_LEVEL2.getCode()));
}
}
}
}