Commit 6f12bd52 authored by guang.wu's avatar guang.wu

feat: 添加品牌管理

parent d277d3b9
...@@ -20,6 +20,7 @@ yarn-error.log ...@@ -20,6 +20,7 @@ yarn-error.log
yarn.lock yarn.lock
*bak *bak
.vscode .vscode
.scannerwork
# visual studio code # visual studio code
.history .history
......
...@@ -288,6 +288,12 @@ export default { ...@@ -288,6 +288,12 @@ export default {
name: 'businessInfo', name: 'businessInfo',
component: './businessManage/info', component: './businessManage/info',
}, },
{
title: '商户管理后台-品牌管理',
path: '/brandManage',
name: 'brandManage',
component: './BrandManage',
},
...groupMealRoute, ...groupMealRoute,
{ {
component: './404', component: './404',
......
import { DeleteOutlined, EyeOutlined, PlusOutlined } from '@ant-design/icons';
import { Modal, Upload, notification, Spin } from 'antd';
import React, { useState, useEffect, useRef, forwardRef } from 'react';
import lodash from 'lodash';
import { ReactSortable } from 'react-sortablejs';
import { merchantUpload } from './service.js';
import styles from './style.less';
const MAX_FILE_SIZE = 5;
const UNIT = 1024 * 1024;
const UploadButton = (
<div>
<PlusOutlined />
<div style={{ marginTop: 8 }}>上传图片</div>
</div>
);
const UploadImage = forwardRef((props, ref) => {
const {
name = `${Date.now()}`,
limit = null,
multiple = true,
disabled,
uploadParams,
value,
width = 0,
height = 0,
accept = ['jpg', 'jpeg', 'png'],
} = props;
const [uploadLoading, setUploadLoading] = useState(false);
const [previewVisible, setPreviewVisible] = useState(false);
const [previewImage, setPreviewImage] = useState('');
const [previewTitle, setPreviewTitle] = useState('');
const [fileList, setFileList] = useState([]);
const [activeImgIndex, setActiveImgIndex] = useState(null);
const fileListRef = useRef([]);
useEffect(() => {
const newPictures = (value || []).map((url, ind) => ({
url,
name: url,
uid: `${ind}`,
}));
fileListRef.current = [...newPictures];
setFileList([...newPictures]);
}, [value]);
const handleCancel = () => setPreviewVisible(false);
const handlePreview = async file => {
setPreviewImage(file.url);
setPreviewVisible(true);
setPreviewTitle(file.name || file.url.substring(file.url.lastIndexOf('/') + 1));
};
const bundleChange = imgFile => {
const imgList = imgFile.map(item => item.url);
props.onChange(imgList);
};
const handleRemove = file => {
const freshFiles = fileList?.filter(ele => ele.uid !== file.uid);
bundleChange(freshFiles);
};
const checkFile = file =>
new Promise(resolve => {
const curType = file.name.substr(file.name.lastIndexOf('.') + 1).toLowerCase();
if (!accept.includes(curType)) {
notification.open({
message: file.name,
description: `图片格式须为${accept.join('')}!`,
});
return resolve(null);
}
if (file.size > MAX_FILE_SIZE * UNIT) {
notification.open({
message: file.name,
description: `单个图片大小不能超过${MAX_FILE_SIZE}M!`,
});
return resolve(null);
}
return resolve(file);
});
const imageLoading = (file, ret) =>
new Promise(resolve => {
const reader = new FileReader();
// 监听图片转换完成
reader.addEventListener(
'load',
() => {
const temFile = { uid: file.uid, status: 'done', name: file.name, url: ret };
resolve(temFile);
},
false,
);
reader.readAsDataURL(file);
});
const getImageSize = async file =>
new Promise((resolve, reject) => {
const fileObj = file;
// 获取上传的图片的宽高
const reader = new FileReader();
reader.readAsDataURL(fileObj);
reader.onload = evt => {
const replaceSrc = evt.target.result;
const imageObj = new Image();
imageObj.src = replaceSrc;
imageObj.onload = () => {
const { width: widthPx, height: heightPx } = imageObj;
file.width = widthPx;
file.height = heightPx;
resolve(file);
};
};
reader.onerror = error => reject(error);
});
const validateImageSize = async files => {
const fileAll = files.map(item => getImageSize(item));
const checkFiles = (await Promise.all(fileAll)).filter(item => item !== null);
const checkSize = checkFiles.some(
item => (item.width !== width && width !== 0) || (item.height !== height && height !== 0),
);
if (checkSize) {
notification.warning({ message: '图片尺寸不正确' });
return false;
}
return true;
};
const defaultBeforeUpload = lodash.debounce(
(file, fileArray) =>
// 文件显示
new Promise(async () => {
if (limit && fileListRef.current.length + fileArray.length > limit) {
Modal.warning({
maskClosable: true,
title: '超出上传个数',
});
return Upload.LIST_IGNORE;
}
if (width !== 0 || height !== 0) {
const checkSize = await validateImageSize(fileArray);
if (!checkSize) {
return Upload.LIST_IGNORE;
}
}
const fileAll = fileArray.map(item => checkFile(item));
const checkFiles = (await Promise.all(fileAll)).filter(item => item !== null);
try {
if (checkFiles.length) {
setUploadLoading(true);
const res = await merchantUpload(checkFiles);
if (res?.data) {
const proFiles = (res.data || []).map((urlItem, urlIndex) =>
imageLoading(checkFiles[urlIndex], urlItem),
);
const imagList = await Promise.all(proFiles);
const newFiles = [...fileListRef.current, ...imagList];
bundleChange(newFiles);
} else {
notification.warning({
message: '警告',
description: res?.msg || '上传失败,请重新尝试!',
});
}
setUploadLoading(false);
}
} catch (error) {
setUploadLoading(false);
Modal.warning({
maskClosable: true,
title: '上传失败,请重新尝试!',
});
}
return null;
}),
);
return (
<Spin tip="正在上传..." spinning={uploadLoading} delay={100}>
<div>
{fileList.length > 0 && (
<ReactSortable animation={300} list={fileList} setList={list => bundleChange(list)}>
{fileList.map((item, index) => (
<div
key={item.uid}
className={styles.sortImg}
onMouseEnter={() => setActiveImgIndex(index)}
onMouseLeave={() => setActiveImgIndex(null)}
>
<div style={{ width: '100%', height: '100%', overflow: 'hidden' }}>
<img width="100%" key={item.uid} src={item.url} alt="" />
</div>
{activeImgIndex === index && (
<div className={styles.mask}>
<EyeOutlined className={styles.maskIcon} onClick={() => handlePreview(item)} />
<DeleteOutlined
className={styles.maskIcon}
onClick={() => handleRemove(item)}
/>
</div>
)}
</div>
))}
</ReactSortable>
)}
</div>
<Upload
{...uploadParams}
disabled={Boolean(disabled)}
multiple={multiple}
name={name}
customRequest={() => {}}
listType="picture-card"
beforeUpload={defaultBeforeUpload}
fileList={fileList}
onPreview={handlePreview}
onRemove={handleRemove}
showUploadList={false}
>
{limit !== null && fileList.length >= limit ? null : UploadButton}
</Upload>
<Modal open={previewVisible} title={previewTitle} footer={null} onCancel={handleCancel}>
<img alt="example" style={{ width: '100%' }} src={previewImage} />
</Modal>
</Spin>
);
});
export default UploadImage;
import request from '@/utils/request';
import config from '@/../config/env.config';
const { goodsApi } = config;
export const merchantUpload = async files => {
const params = new FormData();
files.forEach(file => params.append('file', file));
const data = await request.post('/image/api/merchant/upload', {
prefix: goodsApi,
data: params,
});
return data;
};
.sortImg {
position: relative;
display: inline-block;
width: 105px;
height: 105px;
margin-right: 10px;
margin-bottom: 10px;
padding: 9px;
border: 1px solid #ddd;
border-radius: 6px;
}
.mask {
position: absolute;
top: 9px;
right: 9px;
bottom: 9px;
left: 9px;
padding-top: 27%;
text-align: center;
background: #000;
opacity: 0.5;
}
.maskIcon {
margin-right: 5px;
color: #efefef !important;
font-size: 16px;
cursor: pointer;
}
import React from 'react';
import { BetaSchemaForm } from '@ant-design/pro-components';
import { brandInfoColumn } from '../staticData.js';
import { layout } from '@/utils/bll';
const BrandInfo = props => {
const refForm = React.useRef();
const { actionStatus, brandId, supplierId } = props;
const closeModal = v => {
!v && props.onClose();
};
const submitForm = async values => {
console.log(values);
};
const getInfo = () =>
Promise.resolve({
imageList: ['https://img.yzcdn.cn/vant/cat.jpeg', 'https://img.yzcdn.cn/vant/cat.jpeg'],
});
const config = { actionStatus };
return (
<BetaSchemaForm
layoutType="ModalForm"
title="品牌信息"
open={props.visible}
width="600px"
modalProps={{
maskClosable: false,
destroyOnClose: true,
}}
request={getInfo}
formRef={refForm}
onOpenChange={closeModal}
layout="horizontal"
{...layout}
onFinish={submitForm}
columns={brandInfoColumn(config)}
/>
);
};
export default BrandInfo;
import React, { useState, useRef } from 'react';
import { ProTable } from '@ant-design/pro-components';
import { Button } from 'antd';
import { PlusOutlined } from '@ant-design/icons';
import { apiBrandList } from './services';
import { brandColumn } from './staticData.js';
import utilStyle from '@/utils/utils.less';
import BrandInfo from './components/BrandInfo.jsx';
// 品牌管理
const BrandManage = () => {
const refTable = useRef();
const [id, setId] = useState('');
const [supplierId, setSupplierId] = useState('');
const [visible, setVisible] = useState(false);
const [actionStatus, setActionStatus] = useState('add');
// 编辑品牌
const onAction = (record, status) => {
setId(record.brandId);
setSupplierId(record.id);
setActionStatus(status);
setVisible(!0);
};
return (
<>
<ProTable
actionRef={refTable}
search={{
collapsed: false,
span: 8,
className: utilStyle.formSearch,
collapseRender: () => null,
}}
columns={brandColumn({
refTable,
onAction,
})}
request={apiBrandList}
toolBarRender={() => [
<Button
key="add"
icon={<PlusOutlined />}
type="primary"
onClick={() => {
setId('');
setSupplierId('');
setActionStatus('add');
setVisible(!0);
}}
>
添加
</Button>,
]}
rowKey={r => r.id}
bordered
options={false}
/>
<BrandInfo
visible={visible}
actionStatus={actionStatus}
brandId={id}
supplierId={supplierId}
onClose={() => setVisible(false)}
id={id}
/>
</>
);
};
export default BrandManage;
import request from '@/utils/request';
import config from '@/../config/env.config';
const { kdspApi, goodsApi } = config;
/**
* 分页查询所有品牌列表
* http://yapi.quantgroups.com/project/389/interface/api/66404
*/
export async function apiBrandList(params) {
const param = {
...params,
pageNo: params.current,
};
const [data] = await request.post(`${goodsApi}/api/merchants/brands/list`, param, {
emulateJSON: true,
});
if (data) {
return {
total: data.total,
data: data.records,
};
}
return {
total: 0,
data: [],
};
}
/**
* 添加品牌
* http://yapi.quantgroups.com/project/389/interface/api/66389
* */
export async function apiCreateBrand(params) {
return request.post(`${goodsApi}/api/merchants/brands/add`, params);
}
/**
* 编辑品牌
* http://yapi.quantgroups.com/project/389/interface/api/66394
*/
export async function apiEditBrand(params) {
return request.post(`${goodsApi}/api/merchants/brands/edit`, params);
}
/**
* 查询品牌信息
* http://yapi.quantgroups.com/project/389/interface/api/66409
* */
export async function apiBrandDetail(params) {
return request.get(`${goodsApi}/api/merchants/brands/detail`, params);
}
import React from 'react';
import { Button, Input } from 'antd';
import UploadImage from '@/components/UploadImg/index.jsx';
// 品牌审核状态 0待审核 1审核通过 2驳回
export const brandStatusEnum = {
0: '待审核',
1: '审核通过',
2: '驳回',
};
// 品牌审核 1审核通过 2驳回
export const brandAuditEnum = {
1: '审核通过',
2: '驳回',
};
// 操作状态 审核、查看、修改、添加、补充资质
export const brandActionAudit = 'audit';
export const brandActionFind = 'find';
export const brandActionEdit = 'edit';
export const brandActionAdd = 'add';
export const brandActionSupplement = 'supplement';
export const brandBaseColumn = [
{
title: '品牌名称',
dataIndex: 'brandName',
key: 'brandName',
},
{
title: '中文名称',
dataIndex: 'chineseName',
key: 'chineseName',
hideInSearch: true,
align: 'center',
},
{
title: '英文名称',
key: 'englishName',
dataIndex: 'englishName',
hideInSearch: true,
align: 'center',
},
{
title: '品牌缩写或别称',
key: 'alias',
dataIndex: 'alias',
hideInSearch: true,
align: 'center',
},
];
// 品牌列表字段
export const brandColumn = config => {
const { onAction } = config;
return [
...brandBaseColumn,
{
title: '审核状态',
key: 'status',
dataIndex: 'status',
align: 'center',
valueEnum: brandStatusEnum,
},
{
title: '操作',
hideInSearch: true,
dataIndex: 'option',
align: 'center',
width: 200,
render: (_, r) => [
<Button key="check" type="primary" onClick={() => onAction(r, 'edit')}>
修改
</Button>,
<Button key="update" type="primary" ghost onClick={() => onAction(r, 'update')}>
补充资质
</Button>,
],
},
];
};
// 品牌信息字段
export const brandInfoColumn = config => {
const { actionStatus } = config;
const disabled = [brandActionAudit, brandActionFind].includes(actionStatus);
const baseInfo = [
{
title: '资质证书',
dataIndex: 'qualifyUrl',
formItemProps: {
rules: [{ required: true, message: '请选择资质证书' }],
},
fieldProps: {
disabled,
},
renderFormItem: () => <UploadImage limit={1} />,
},
{
title: '授权证书',
dataIndex: 'authorizationUrl',
formItemProps: {
rules: [{ required: true, message: '请选择授权证书' }],
},
fieldProps: {
disabled,
},
renderFormItem: () => <UploadImage limit={1} />,
},
{
title: '品牌名称',
dataIndex: 'name',
maxLength: 50,
fieldProps: {
disabled,
},
formItemProps: {
rules: [{ required: true, message: '请输入品牌名称' }],
},
},
{
title: '中文名称',
dataIndex: 'chineseName',
maxLength: 50,
fieldProps: {
disabled,
},
},
{
title: '英文名称',
dataIndex: 'englishName',
maxLength: 100,
fieldProps: {
disabled,
},
},
{
title: '品牌缩写或别称',
dataIndex: 'alias',
maxLength: 50,
formItemProps: {
rules: [{ required: true, message: '请输入品牌缩写或别称' }],
},
fieldProps: {
disabled,
},
},
{
title: '长方形LOGO上传',
dataIndex: 'horizontalLogo',
formItemProps: {
rules: [{ required: true, message: '请上传长方形LOGO上传' }],
extra: (
<div>
<div>尺寸要求:219*72</div>
<div>
素材要求:1、透明底;2、上下左右最少留白2px,具体以保证整体大小一致,视觉平衡为准
</div>
</div>
),
},
fieldProps: {
disabled,
},
renderFormItem: () => <UploadImage limit={1} width={219} height={72} />,
},
{
title: 'LOGO上传',
dataIndex: 'logo',
formItemProps: {
rules: [{ required: true, message: '请上传LOGO' }],
extra: <span>尺寸要求:192*192</span>,
},
fieldProps: {
disabled,
},
renderFormItem: () => <UploadImage limit={1} width={192} height={192} />,
},
{
title: '',
dataIndex: '',
valueType: 'divider',
hideInForm: ![brandActionAudit, brandActionFind].includes(actionStatus),
formItemProps: {
wrapperCol: { span: 22 },
},
fieldProps: {
children: '品牌审核',
orientation: 'left',
},
},
{
title: '审核结果',
dataIndex: 'status',
valueType: 'radio',
hideInForm: ![brandActionAudit, brandActionFind].includes(actionStatus),
valueEnum: brandAuditEnum,
fieldProps: {
buttonStyle: 'outline',
disabled: [brandActionFind].includes(actionStatus),
onChange: e => {
if (e.target.value === 2) {
config.form.setFieldsValue({ reason: '' });
}
},
},
},
{
valueType: 'dependency',
name: ['status'],
hideInForm: ![brandActionAudit, brandActionFind].includes(actionStatus),
columns: ({ status }) => {
const rejectColumn = [
{
title: '驳回原因',
dataIndex: 'rejectReason',
maxLength: 200,
formItemProps: {
rules: [{ required: true, message: '请输入驳回原因' }],
},
fieldProps: {
placeholder: '请输入驳回原因',
disabled: [brandActionFind].includes(actionStatus),
},
renderFormItem: () => <Input />,
},
];
return +status === 2 ? rejectColumn : [];
},
},
];
return baseInfo;
};
export const layout = {
labelCol: { span: 6 },
wrapperCol: { span: 16 },
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment