Commit 946cea22 authored by 武广's avatar 武广

Merge branch 'feature/addr20221031' into 'master'

Feature/addr20221031

See merge request !64
parents baa19369 d8defddc
import React, { forwardRef } from 'react';
import { Checkbox } from 'antd';
import { RightOutlined, DownOutlined, LoadingOutlined } from '@ant-design/icons';
import debounce from 'lodash/debounce';
import styles from './index.less';
const CustomTree = forwardRef(props => {
const { treeData } = props;
const onChange = () => {
props.onChange(treeData.key, !treeData.checked, treeData.level, treeData.children);
};
const onVisible = debounce(e => {
e.persist();
if (treeData.isLoading) return;
props.onVisibleChildren(
treeData.key,
!treeData.visibleChildren,
treeData.children,
treeData.checked,
);
}, 100);
const renderIcon = () => {
if (treeData.children && treeData.isLoading) {
return <LoadingOutlined />;
}
if (treeData.isLeaf) {
return (
<div onClick={onVisible} className={styles.CBIcon}>
{treeData.visibleChildren ? <DownOutlined /> : <RightOutlined />}
</div>
);
}
return <span />;
};
return (
<div key={`key${treeData.key}`}>
<div className={styles['ccheck-box']}>
<div className={styles['ccheck-box--expand']}>{renderIcon()}</div>
<Checkbox
onChange={onChange}
indeterminate={!treeData.checked && treeData.indeterminate}
checked={treeData.checked}
/>
<div className={styles['ccheck-box--label']} onClick={onVisible}>
{props.label}
{treeData.isLeaf && treeData.value && treeData.value.length > 0
? `(${treeData.value.length})`
: ''}
</div>
</div>
</div>
);
});
export default CustomTree;
/* eslint-disable no-unused-expressions */
import React, { useState, useEffect, forwardRef } from 'react';
import { Row, Col } from 'antd';
import styles from './index.less';
import CTreeNode from './CTreeNode';
const CustomTree = forwardRef(props => {
const [treeData, setTreeData] = useState([]);
const [childrenList, setChildrenList] = useState([]);
const [initValue, setInitValue] = useState([]);
// 初始化数据状态
const initChildrenStatus = arr =>
arr.map(item => {
const obj = initValue.find(val => val.key === item.key) || {};
item.checked = obj.checked || item.checked || false;
item.indeterminate = obj.indeterminate || item.indeterminate || false;
return item;
});
// 获取子列表
const getChildrenList = list => {
const arr = [];
const findArr = childrens => {
childrens.forEach(node => {
if (node.children && node.children.length) {
node.visibleChildren && arr.push(initChildrenStatus(node.children, node.checked));
findArr(node.children);
}
});
};
findArr(list);
return arr;
};
// 更改属性值
const ChangeVal = (list, key, v, keyName) => {
list.forEach(node => {
if (node.key === key) {
node[keyName] = v;
} else if (node.children && node.children.length) {
ChangeVal(node.children, key, v);
}
});
};
// 获取选中的值
const getCheckValue = (list, isFrist) => {
const arr = [];
const getChildrenCheckValue = childrens => {
childrens.forEach(item => {
if (item.checked) {
arr.push({ ...item, [props.valueKey]: item.key });
} else if (item.children && !isFrist) {
getChildrenCheckValue(item.children);
}
});
};
getChildrenCheckValue(list);
};
// 获取默认值是否选中
const getIsChecked = (key, json) => {
const checkfn = list => {
list.forEach(item => {
if (+item[props.valueKey || 'key'] === +key) {
json.checked = !!item.checked;
json.indeterminate = !item.checked;
json.value = getCheckValue(item.children, 1);
} else if (item.children) {
checkfn(item.children);
}
});
};
props.value && props.value.length && checkfn(props.value, json);
};
// 格式化数据
const filterData = (arr, parentChecked) => {
const list = [];
arr.forEach(item => {
const json = {
label: item.addrName,
key: +item.addrId,
level: item.addrLevel,
value: [],
checked: parentChecked || false,
indeterminate: false,
children: [],
isLeaf: true,
isLoading: false,
};
if (!parentChecked) {
getIsChecked(item.addrId, json);
}
list.push(json);
});
return list;
};
// 更改子复选框状态
const updateChildren = (list, isChecked) => {
list.forEach(item => {
item.indeterminate = false;
item.checked = isChecked;
if (item.children && item.children.length) {
item.value = isChecked ? item.children.map(c => c.key) : [];
updateChildren(item.children, isChecked);
} else {
item.value = [];
}
});
};
// 追加子树
const appendTreeData = (list, key, children, isChecked) =>
list.forEach(node => {
if (node.key === key) {
node.children = children.length ? children : [];
isChecked && (node.value = children);
updateChildren(node.children, isChecked);
} else if (node.children && node.children.length) {
appendTreeData(node.children, key, children, isChecked);
}
});
// 更新子树显示隐藏状态
const updateVisibleChildren = (list, visible) => {
list.forEach(item => {
item.visibleChildren = false;
item.isLoading = false;
if (item.children && item.children.length) {
updateVisibleChildren(item.children, visible);
}
});
};
// 更新显示隐藏状态
const updateVisible = (list, ckey, visible, pkey) => {
let key = '';
list.forEach(node => {
node.isLoading = false;
if (node.key === ckey) {
node.visibleChildren = visible;
key = pkey;
node.isLeaf = node.children.length > 0;
updateVisibleChildren(node.children, visible);
} else if (node.children && node.children.length) {
node.visibleChildren = false;
if (updateVisible(node.children, ckey, visible, node.key) === node.key) {
node.visibleChildren = true;
key = pkey;
} else {
node.visibleChildren = false;
}
}
});
return key;
};
// 异步获取数据
const getSyncLoadChildrens = async (arr, ckey, parentChecked) => {
ChangeVal(arr, ckey, true, 'isLoading');
setTreeData(arr);
const res = await props.loadData({ key: ckey });
if (res && res.length) {
appendTreeData(arr, ckey, filterData(res, parentChecked), parentChecked);
}
};
// 改变子树显示隐藏状态事件
const onVisibleChildren = async (ckey, visible, children, parentChecked) => {
const arr = [...treeData];
if (children && !children.length && typeof props.loadData === 'function') {
await getSyncLoadChildrens(arr, ckey, parentChecked);
}
updateVisible(arr, ckey, visible);
setChildrenList(getChildrenList(arr));
setTreeData(arr);
};
// 更改选中状态
const changeChecked = (list, ckey, isChecked, pkey) => {
let key = '';
list.forEach(node => {
if (node.key === ckey) {
node.checked = isChecked;
key = pkey;
node.children && node.children.length && updateChildren(node.children, isChecked);
} else if (node.children && node.children.length) {
if (changeChecked(node.children, ckey, isChecked, node.key) === node.key) {
if (!isChecked) {
node.checked = false;
} else {
node.checked = node.children.every(item => item.checked);
}
key = pkey;
}
}
if (node.checked) {
node.indeterminate = false;
} else if (node.children && node.children.length) {
node.indeterminate = node.children.some(item => item.checked || item.indeterminate);
}
});
return key;
};
// 获取选中的值
const getCheckTreeValue = list => {
const arr = [];
const getChildrenCheckValue = (childrens, json, values = []) => {
childrens.forEach(item => {
if (item.checked || item.indeterminate) {
const obj = { ...item };
obj.children = [];
json.children.push(obj);
if (!item.checked) {
const valueArr = values.find(val => val.key === item.key) || {};
if (item.children && item.children.length) {
getChildrenCheckValue(item.children, obj, valueArr.children);
} else if (valueArr && valueArr.key) {
obj.children = valueArr.children;
}
}
}
});
};
list.forEach(item => {
if (item.checked || item.indeterminate) {
const obj = { ...item };
obj.children = [];
if (!item.checked) {
const valueArr = props.value.find(val => val.key === item.key) || {};
if (item.children && item.children.length) {
getChildrenCheckValue(item.children, obj, valueArr.children);
} else if (valueArr && valueArr.key) {
obj.children = valueArr.children;
}
}
arr.push(obj);
}
});
return arr;
};
// 切换选中状态事件
const onChange = async (ckey, isChecked, level, children) => {
const datas = [...treeData];
if (isChecked && children && !children.length && typeof props.loadData === 'function') {
await getSyncLoadChildrens(datas, ckey, isChecked);
}
changeChecked(datas, ckey, isChecked);
const values = getCheckTreeValue(datas, isChecked);
if (level === 1) {
onVisibleChildren('', false);
}
props.onChange(values);
};
// 渲染子树
const renderTreeNodes = data =>
data &&
data.map(item => (
<CTreeNode
label={item.label}
onChange={onChange}
onVisibleChildren={onVisibleChildren}
key={item.key}
treeData={item}
/>
));
// 隐藏所有子树
const handleMouseUp = e => {
const isCur = e.path.some(
item =>
item.className &&
typeof item.className === 'string' &&
item.className.indexOf('CustomCTreeWrapper') > -1,
);
if (!isCur) {
onVisibleChildren('', false);
}
};
// 初始化数初始化
const initTreeValue = (arr = [], values = [], parentChecked, level = 1) => {
const getValues = (item, valueObj) => {
if (item.checked && item.children && item.children.length) {
return item.children.map(c => c.key);
}
return (valueObj.children || []).map(c => c.key);
};
arr.forEach(item => {
const valueObj = values.find(val => val.key === item.key) || {};
item.checked =
(typeof parentChecked === 'boolean' && parentChecked) || valueObj.checked || false;
item.indeterminate = valueObj.indeterminate || false;
item.value = getValues(item, valueObj);
item.isLeaf = typeof item.isLeaf === 'boolean' ? item.isLeaf : true;
item.isLoading = false;
item.visibleChildren = item.visibleChildren || false;
item.level = level;
if (item.children && item.children.length) {
initTreeValue(item.children, valueObj.children, item.checked, level + 1);
}
});
};
const getInitValue = () => {
const arr = [];
const getDeepValue = list => {
list.forEach(item => {
const obj = { ...item };
delete obj.children;
arr.push(obj);
if (item.children && item.children.length) {
getDeepValue(item.children);
}
});
};
getDeepValue(props.value);
return arr;
};
useEffect(() => {
const datas = [...treeData];
initTreeValue(datas, props.value);
setTreeData(datas);
const arr = getInitValue();
setInitValue(arr);
}, [props.value]);
useEffect(() => {
const datas = [...props.treeData];
initTreeValue(datas, props.value);
setTreeData(datas);
}, [props.treeData]);
// 委托 - 点击别的地方关闭子层
useEffect(() => {
window.addEventListener('mouseup', handleMouseUp);
return () => {
window.removeEventListener('mouseup', handleMouseUp);
};
}, [treeData]);
return (
<div className={styles['tree-box']} id="my-custom-tree-box">
<Row justify="space-between">
{(treeData.length &&
treeData.map(item => (
<Col span={7} key={`col${item.key}`}>
<div className={`${styles['tree-box--wrapper']} CustomCTreeWrapper`}>
<CTreeNode
label={item.label}
onChange={onChange}
onVisibleChildren={onVisibleChildren}
key={item.key}
treeData={item}
></CTreeNode>
{(item.visibleChildren && childrenList && childrenList.length && (
<div className={styles['tree-children-box']} key="childrens">
{childrenList.map(arr => (
<div
className={styles['tree-children-wrapper']}
key={`children${arr[0].key}`}
>
{renderTreeNodes(arr) || ''}
</div>
))}
</div>
)) ||
''}
</div>
</Col>
))) ||
''}
</Row>
</div>
);
});
CustomTree.CTreeNode = CTreeNode;
export default CustomTree;
.tree-box {
position: relative;
:global(.ant-col) {
display: flex;
}
&--wrapper {
position: relative;
}
}
.ccheck-box {
position: relative;
display: flex;
box-sizing: border-box;
width: max-content;
padding-right: 0;
padding-left: 22px;
&--expand {
position: absolute;
left: 0;
z-index: 1;
cursor: pointer;
}
&--childrens {
position: relative;
left: 100%;
z-index: 2;
display: flex;
flex-direction: column;
box-sizing: border-box;
width: max-content;
padding-left: 4px;
background-color: #fff;
box-shadow: 0 0 3px #ccc;
:global(.ant-checkbox-wrapper) {
margin-left: 0;
}
}
&--label {
padding: 0 10px;
cursor: pointer;
}
}
.CBIcon {
font-size: 12px;
}
.tree-children-box {
position: absolute;
top: 0;
left: 100%;
z-index: 2;
display: flex;
padding: 0 10px;
background-color: #fff;
box-shadow: 0 0 3px #ccc;
}
.tree-children-wrapper {
display: flex;
flex-direction: column;
max-height: 220px;
padding-left: 5px;
overflow-y: auto;
border-left: 1px solid #ccc;
&:first-child {
border: 0;
}
}
......@@ -97,13 +97,16 @@ const LogModal = props => {
useEffect(() => {
if (!props.id) return;
handleSearch();
// 20221108 临时隐藏商品详情,默认切换到审核详情 by liteng
// handleSearch();
bundleOnTabChange('1');
}, [props.id]);
const { visible } = props;
return (
<Modal title="日志详情" visible={visible} footer={null} onCancel={bundleOnCancel} width="800px">
<Tabs type="card" onChange={bundleOnTabChange} activeKey={tabActiveKey}>
<Tabs.TabPane tab="商品详情" key="0">
{/* 20221108 临时隐藏商品详情 by liteng */}
{/* <Tabs.TabPane tab="商品详情" key="0">
<Table
dataSource={tableData.records}
bordered
......@@ -122,7 +125,7 @@ const LogModal = props => {
className={styles.pagination}
/>
)}
</Tabs.TabPane>
</Tabs.TabPane> */}
<Tabs.TabPane tab="审核详情" key="1">
<Table
dataSource={merchantList}
......
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import { Modal, Input, Select, Cascader, Tag, notification } from 'antd';
import { Modal, Input, Select, Cascader, Tag, notification, Tree, Col, Row } from 'antd';
import React, { useState, useEffect } from 'react';
import { el } from 'date-fns/locale';
import { areaList, getAddTemplate, forbiddenAddress } from '../services';
const { Option } = Select;
import CustomTree from '@/components/CustomTree';
const AddAreaModal = props => {
const {
......@@ -13,9 +12,9 @@ const AddAreaModal = props => {
form: { getFieldDecorator, validateFields, resetFields, setFieldsValue },
templateData,
} = props;
const [addList, setAddList] = useState([]);
const [selectedList, setSelectedList] = useState([]);
const [selected, setSelect] = useState([]);
const [treeData, setTreeData] = useState([]);
const formItemLayout = {
labelCol: {
span: 6,
......@@ -24,76 +23,46 @@ const AddAreaModal = props => {
span: 16,
},
};
const getAreaList = async () => {
if (!addList.length) {
const data = await areaList();
const newData = [];
data.map(item =>
newData.push({
label: item.addrName,
value: item.addrId,
addressId: item.addrId,
addressLevel: item.addrLevel,
isLeaf: item.addrLevel === 4,
}),
);
setAddList(newData);
}
};
const onChange = (value, selectedOptions) => {
setSelect([...selectedOptions]);
};
// 加载
const loadData = async selectedOptions => {
const targetOption = selectedOptions.slice(-1)[0];
targetOption.loading = true;
const data = await areaList({ parentId: targetOption.addressId });
if (!data.length) {
targetOption.loading = false;
} else {
const newData = [];
data.map(itemData =>
newData.push({
label: itemData.addrName,
value: itemData.addrId,
addressId: itemData.addrId,
addressLevel: itemData.addrLevel,
isLeaf: false,
}),
);
targetOption.children = newData;
targetOption.loading = false;
setAddList([...addList]);
const filterData = arr =>
arr.map(item => ({
label: item.addrName,
key: Number(item.addrId),
level: item.addrLevel,
children: [],
// value: props.templateData.list.filter(item => item.)
}));
const loadProvice = async () => {
const data = await areaList();
if (data && data.length) {
const arr = filterData(data);
setTreeData(arr);
}
};
// 限制区域删除
const preventDefault = val => {
const newArr = selectedList.filter(item => item !== val);
setSelectedList([...newArr]);
};
const onCancel = () => {
setSelectedList([]);
resetFields();
setSelect([]);
props.onSubmit();
};
const handleOk = async () => {
const { length } = selectedList;
// if (!selectedList.length) {
// notification.error({ message: '请选择限制区域!' });
// return;
// }
const handleOk = () => {
validateFields(async (error, fieldsValue) => {
const newData = [];
selectedList.map(itemData =>
newData.push({
addressId: itemData.addressId,
addressLevel: itemData.addressLevel,
addressName: itemData.addressName,
}),
);
const getValues = (list, level) => {
list.forEach(itemData => {
if (itemData.checked) {
newData.push({
addressId: itemData.key,
addressLevel: itemData.level || level,
addressName: itemData.label,
});
} else if (itemData.children && itemData.children.length) {
getValues(itemData.children, level + 1);
}
});
};
if (!error) {
console.log('fieldsValue :>> ', fieldsValue);
getValues(fieldsValue.list, 1);
if (props.templateData.status) {
const data = await forbiddenAddress({
templateId: props.templateData.id,
......@@ -117,45 +86,38 @@ const AddAreaModal = props => {
}
});
};
// 判断是否重复
const getChilds = select => {
const reslutData = selectedList.filter(
item =>
!select.addressName.includes(item.addressName) &&
!item.addressName.includes(select.addressName),
);
reslutData.push(select);
return reslutData;
const onLoadData = async ({ key }) => {
const res = await areaList({ parentId: key });
return res || [];
};
const onPopupVisibleChange = labels => {
if (!labels && selected?.length) {
const select = selected.slice(-1)[0];
const arr = selected.map(x => x.label);
select.addressName = arr.join('/');
const reslutData = getChilds(select);
// 用中文字符排序
const resultArray = reslutData.sort((param1, param2) =>
param1.addressName.localeCompare(param2.addressName, 'zh'),
);
setSelectedList([...resultArray]);
setSelect([]);
}
};
useEffect(() => {
getAreaList();
if (props.templateData) {
setSelectedList(props.templateData.list);
let arr = [];
const dealData = list =>
list.map(item => {
item.key = +item.addressId;
item.label = item.addressName;
item.indeterminate = !item.isForbidden;
item.checked = item.isForbidden;
item.children && item.children.length && dealData(item.children);
return item;
});
arr = dealData(props.templateData.list || []);
setSelectedList(arr);
}
}, [props.templateData]);
useEffect(() => {
// console.log(selectedList)
}, [selectedList]);
props.visible && loadProvice();
}, [props.visible]);
return (
<Modal
title={props.templateData.status ? '编辑限制区域配送模板' : '添加限制区域配送模板'}
maskClosable={false}
visible={visible}
width="500px"
width="800px"
onCancel={() => onCancel()}
onOk={() => handleOk()}
>
......@@ -167,21 +129,10 @@ const AddAreaModal = props => {
})(<Input placeholder="请填写模板名称" maxLength={20} />)}
</Form.Item>
<Form.Item label="限制配送区域">
{getFieldDecorator('name')(
<Cascader
options={addList}
loadData={loadData}
onChange={(val, label) => onChange(val, label)}
allowClear={false}
changeOnSelect
onDropdownVisibleChange={onPopupVisibleChange}
/>,
)}
{selectedList?.map((selItem, selIndex) => (
<Tag closable key={selItem.addressId} onClose={() => preventDefault(selItem)}>
{selItem.addressName}
</Tag>
))}
{getFieldDecorator('list', {
rules: [{ required: true, message: '请选择限制配送区域!', type: 'array' }],
initialValue: selectedList,
})(<CustomTree treeData={treeData} loadData={onLoadData} valueKey="addressId" />)}
</Form.Item>
</Form>
</Modal>
......
......@@ -55,7 +55,7 @@ const TableList = props => {
),
]}
/>
<AddArea visible={visible} onSubmit={reload} templateData={templateData} />
{visible && <AddArea visible={visible} onSubmit={reload} templateData={templateData} />}
</PageHeaderWrapper>
);
};
......
......@@ -57,6 +57,41 @@ export function toThousands(data, num) {
export const formatTime = (time, crm = 'YYYY-MM-DD HH:mm:ss') => time.format(crm);
export const resetTime = (time, crm = 'YYYY-MM-DD HH:mm:ss') => moment(time, crm);
// 返回传递给他的任意对象的类
export function isClass(o) {
if (o === null) return 'Null';
if (o === undefined) return 'Undefined';
return Object.prototype.toString.call(o).slice(8, -1);
}
// 深拷贝
export function deepClone(obj) {
let result;
const oClass = isClass(obj);
// 确定result的类型
if (oClass === 'Object') {
result = {};
} else if (oClass === 'Array') {
result = [];
} else {
return obj;
}
// eslint-disable-next-line no-restricted-syntax
for (const key in obj) {
if ({}.hasOwnProperty.call(obj, key)) {
const copy = obj[key];
if (isClass(copy) === 'Object') {
result[key] = deepClone(copy); // 递归调用
} else if (isClass(copy) === 'Array') {
result[key] = deepClone(copy);
} else {
result[key] = obj[key];
}
}
}
return result;
}
export const getClientInfo = () => {
if (window.innerHeight !== undefined) {
return {
......
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