Commit 8450748c authored by Bill's avatar Bill

fix: 修改加工能力

parent 701a9836
@import '~antd/es/style/themes/default.less';
.anchor-page {
&-header {
background: #FFFFFF;
&-main {
padding: 0 16px;
display: flex;
}
&-left {
flex: 1;
overflow: hidden;
}
&-right {
padding-top: 14px;
flex-shrink: 0;
margin-left: 12px;
}
&-heading {
padding: 14px 0 9px 0;
display: flex;
align-items: center;
&-title {
margin-bottom: 0;
color: rgba(0, 0, 0, 0.85);
font-weight: 600;
font-size: 18px;
line-height: 32px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
&-back {
margin-right: 12px;
font-size: 16px;
cursor: pointer;
&:hover {
color: @primary-color;
}
}
&-anchors {
display: flex;
align-items: center;
}
:global {
.ant-anchor {
background-color: #FFFFFF;
&-link {
padding: 0 16px;
font-size: 14px;
font-weight: 400;
color: @text-color;
&-title {
padding: 16px 0;
border-bottom: 2px solid transparent;
}
&-active,
&:hover {
font-weight: 500;
color: @text-color;
.ant-anchor-link-title {
border-bottom: 2px solid @primary-color;
}
}
}
&-ink {
display: none;
}
}
}
}
&-content {
padding: 16px;
}
}
\ No newline at end of file
/*
* @Author: XieZhiXiong
* @Date: 2021-05-10 11:36:58
* @LastEditors: XieZhiXiong
* @LastEditTime: 2021-05-11 10:16:12
* @Description: 页面公用锚点头部
*/
import React, { useState } from 'react';
import { Anchor } from 'antd';
import { ArrowLeftOutlined } from '@ant-design/icons';
import { history } from 'umi';
import useClientRect from '@/hooks/useClientRect';
import styles from './index.less';
export interface AnchorsItem {
/**
* 跳转标识
*/
key: string,
/**
* 名称
*/
name: React.ReactNode,
}
interface IProps {
/**
* 标题
*/
title: React.ReactNode,
/**
* 右侧拓展部分
*/
extra?: React.ReactNode,
/**
* 锚点数据
*/
anchors: AnchorsItem[],
/**
* 自定义样式
*/
customStyle?: React.CSSProperties,
/**
* 自定义返回事件
*/
onBack?: () => void,
children?: React.ReactNode,
}
const AnchorPage: React.FC<IProps> = (props: IProps) => {
const {
title,
extra,
anchors,
customStyle,
onBack,
children,
} = props;
const firstKey = anchors.length ? `#${anchors[0].key}` : '';
const [current, setCurrent] = useState(firstKey);
const [rect, measuredRef] = useClientRect();
const handleBack = () => {
if (onBack) {
onBack();
return;
}
history.goBack();
};
const handleAnchorChange = (currentActiveLink: string) => {
if (currentActiveLink) {
setCurrent('');
return;
}
if (!currentActiveLink) {
setCurrent(firstKey);
}
};
const handleAnchorClick = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
};
return (
<div
className={styles['anchor-page']}
style={customStyle}
>
<div
className={styles['anchor-page-header']}
ref={measuredRef}
>
<Anchor
showInkInFixed={false}
targetOffset={rect.height}
onChange={handleAnchorChange}
onClick={handleAnchorClick}
>
<div className={styles['anchor-page-header-main']}>
<div className={styles['anchor-page-header-left']}>
<div className={styles['anchor-page-header-heading']}>
<ArrowLeftOutlined
className={styles['anchor-page-header-back']}
onClick={handleBack}
/>
<span className={styles['anchor-page-header-heading-title']}>
{title}
</span>
</div>
<div className={styles['anchor-page-header-content']}>
<div className={styles['anchor-page-header-anchors']}>
{anchors.map((item, index) => (
<Anchor.Link
className={current && index === 0 ? 'ant-anchor-link-active' : null}
key={item.key}
href={`#${item.key}`}
title={item.name}
/>
))}
</div>
</div>
</div>
{extra ? (
<div className={styles['anchor-page-header-right']}>
{extra}
</div>
) : null}
</div>
</Anchor>
</div>
<div className={styles['anchor-page-content']}>
{children}
</div>
</div>
);
};
AnchorPage.defaultProps = {
extra: null,
customStyle: {},
onBack: undefined,
children: null,
};
export default AnchorPage;
.steps {
padding-top: 2px;
overflow: auto;
}
\ No newline at end of file
......@@ -2,76 +2,148 @@
* @Author: XieZhiXiong
* @Date: 2020-09-15 17:48:36
* @LastEditors: XieZhiXiong
* @LastEditTime: 2021-01-22 10:44:04
* @LastEditTime: 2021-05-18 15:08:09
* @Description: 内外部流转记录组件
*/
import React from 'react';
import React, { useState, useEffect } from 'react';
import {
Steps,
Tabs,
} from 'antd';
import MellowCard from '@/components/MellowCard';
import MellowCard, { MellowCardProps } from '@/components/MellowCard';
import ButtonSwitch from '@/components/ButtonSwitch';
import styles from './index.less';
interface AuditProcessProp {
outerVerifyCurrent?: number;
innerVerifyCurrent?: number;
outerVerifySteps?: {
step: number,
stepName: string,
roleName: string,
}[];
innerVerifySteps?: {
step: number,
stepName: string,
roleName: string,
}[];
export interface StepsItem {
/**
* 当前步骤
*/
step?: number,
/**
* 步骤名
*/
stepName?: string,
/**
* 角色名,也可以作为辅助信息使用
*/
roleName: string,
/**
* 当前步骤的状态
*/
status?: 'wait' | 'process' | 'finish' | 'error',
}
interface AuditProcessProp extends MellowCardProps {
/**
* 当前外部流程步骤
*/
outerVerifyCurrent?: number
/**
* 当前内部流程步骤
*/
innerVerifyCurrent?: number
/**
* 当前外部流程
*/
outerVerifySteps?: StepsItem[]
/**
* 当前内部流程
*/
innerVerifySteps?: StepsItem[]
/**
* 自定义item 步骤名键名
*/
customTitleKey?: string
/**
* 自定义item key键名
*/
customKey?: string
};
const AuditProcess: React.FC<AuditProcessProp> = ({
outerVerifyCurrent = 0,
innerVerifyCurrent = 0,
outerVerifySteps = [],
innerVerifySteps = [],
outerVerifySteps,
innerVerifySteps,
customTitleKey,
customKey
}) => (
<MellowCard>
<Tabs onChange={() => {}}>
{
outerVerifySteps?.length > 0 &&
<Tabs.TabPane tab="外部审核流程" key="1">
<Steps style={{ marginTop: 30, overflow: 'auto', padding: '5px 0' }} progressDot current={outerVerifyCurrent}>
{outerVerifySteps.map(item => (
customKey,
...rest
}) => {
const [radioValue, setRadioValue] = useState<('inner' | 'outer')>('inner');
useEffect(() => {
// 这里判断如果只有外部步骤,没有内部步骤的时候,默认设置 radioValue 为 outer
if (Array.isArray(outerVerifySteps) && !Array.isArray(innerVerifySteps)) {
setRadioValue('outer');
}
}, [outerVerifySteps]);
const handleRadioChange = (value: ('inner' | 'outer')) => {
setRadioValue(value);
};
const options = [
(
outerVerifySteps
&& outerVerifySteps.length
? {
label: '外部流转',
value: 'outer',
}
: null
),
(
innerVerifySteps
&& innerVerifySteps.length
? {
label: '内部流转',
value: 'inner',
}
: null
),
].filter(Boolean);
return (
<MellowCard
title="流转进度"
extra={(
<ButtonSwitch
options={options as any[]}
onChange={handleRadioChange}
value={radioValue}
/>
)}
{...rest}
>
{radioValue === 'outer' ? (
<div className={styles.steps}>
<Steps progressDot current={outerVerifyCurrent}>
{outerVerifySteps && outerVerifySteps.map(item => (
<Steps.Step
key={customKey ? item[customKey] : item.step}
title={customTitleKey ? item[customTitleKey] : item.stepName}
description={item.roleName}
status={item["isExecute"] ? "finish" : "wait"}
status={item.status}
/>
))}
</Steps>
</Tabs.TabPane>
}
{
innerVerifySteps?.length > 0 &&
<Tabs.TabPane tab="内部审核流程" key="2">
<Steps style={{ marginTop: 30, overflow: 'auto', padding: '5px 0' }} progressDot current={innerVerifyCurrent}>
{innerVerifySteps.map(item => (
</div>
) : null}
{radioValue === 'inner' ? (
<div className={styles.steps}>
<Steps progressDot current={innerVerifyCurrent}>
{innerVerifySteps && innerVerifySteps.map(item => (
<Steps.Step
key={customKey ? item[customKey] : item.step}
title={customTitleKey ? item[customTitleKey] : item.stepName}
description={item.roleName}
status={item["isExecute"] ? "finish" : "wait"}
status={item.status}
/>
))}
</Steps>
</Tabs.TabPane>
}
</Tabs>
</MellowCard>
);
</div>
) : null}
</MellowCard>
)
};
export default AuditProcess;
.button-switch {
display: inline-block;
:global {
.ant-radio-button-wrapper:hover {
color: #6B778C;
}
.ant-radio-group-solid {
.ant-radio-button-wrapper-checked {
background: #6B778C;
border-color: #6B778C;
&:hover {
background: #6B778C;
border-color: #6B778C;
}
&::after {
background-color: #6B778C;
}
&::before {
background-color: #6B778C;
}
}
}
}
}
/*
* @Author: XieZhiXiong
* @Date: 2021-05-12 11:04:26
* @LastEditors: XieZhiXiong
* @LastEditTime: 2021-05-19 15:59:37
* @Description: 按钮切换器
*/
import React, { useState, useEffect } from 'react';
import { Radio } from 'antd';
import { RadioChangeEvent } from 'antd/lib/radio';
import { SizeType } from 'antd/lib/config-provider/SizeContext';
import styles from './index.less';
export interface OptionItem {
/**
* 名称
*/
label: string,
/**
* 值
*/
value: any,
}
interface IProps {
/**
* 数据
*/
options: OptionItem[],
/**
* 按钮切换触发事件
*/
onChange?: (value: any) => void,
/**
* 值
*/
value?: any,
/**
* Radio.Group size
*/
size?: SizeType,
}
const ButtonSwitch: React.FC<IProps> = (props: IProps) => {
const {
options,
onChange,
value,
size,
} = props;
const first = options.length ? options[0].value : '';
const [radioValue, setRadioValue] = useState(first);
useEffect(() => {
if ('value' in props) {
setRadioValue(value);
}
}, [value]);
const triggerChange = (next: any) => {
if (onChange) {
onChange(next);
}
};
const handleRadioChange = (e: RadioChangeEvent) => {
if (!('value' in props)) {
setRadioValue(e.target.value);
return;
}
triggerChange(e.target.value);
};
return (
<div className={styles['button-switch']}>
<Radio.Group
options={options}
onChange={handleRadioChange}
value={radioValue}
optionType="button"
buttonStyle="solid"
size={size}
/>
</div>
);
};
ButtonSwitch.defaultProps = {
onChange: undefined,
size: 'small',
};
export default ButtonSwitch;
.customize-column {
:global {
.ant-descriptions-item-content {
white-space: break-spaces;
}
}
}
\ No newline at end of file
import React, { CSSProperties } from 'react';
import { Descriptions } from 'antd';
import classNames from 'classnames';
import MellowCard, { MellowCardProps } from '@/components/MellowCard';
import styles from './index.less';
export interface ColumnProps {
span?: number,
contentStyle?: CSSProperties,
labelStyle?: CSSProperties,
}
export interface DataItem {
/**
* 标题
*/
title: React.ReactNode,
/**
* 值
*/
value: React.ReactNode,
/**
* DescriptionItem props
*/
columnProps?: ColumnProps,
}
export interface IProps extends MellowCardProps {
/**
* 数据
*/
data: DataItem[],
/**
* column 列数,默认 3
*/
column?: number
};
const defaultColumnProps: ColumnProps = {
labelStyle: {
width: 104,
},
contentStyle: {
paddingRight: 32,
},
};
const CustomizeColumn: React.FC<IProps> = (props: IProps) => {
const { data, column = 3, ...rest } = props;
const { className } = rest;
const mergeCls = classNames(styles['customize-column'], className);
return (
<MellowCard
bodyStyle={{
paddingBottom: 0,
}}
{...rest}
className={mergeCls}
>
<Descriptions column={column}>
{data.map((item, index) => (
<Descriptions.Item
key={index}
label={item.title}
{...({...defaultColumnProps, ...item.columnProps} || defaultColumnProps)}
>
{item.value}
</Descriptions.Item>
))}
</Descriptions>
</MellowCard>
);
};
CustomizeColumn.defaultProps = {
column: 3,
};
export default CustomizeColumn;
@import '~antd/es/style/themes/default.less';
.desc-progress {
&-statistic {
display: flex;
align-items: center;
justify-content: space-between;
&-item {
&-title {
margin-bottom: @margin-xss;
line-height: @font-size-sm;
font-size: @font-size-sm;
font-weight: 400;
color: @text-color-secondary;
}
&-content {
line-height: @font-size-sm;
font-size: @font-size-sm;
font-weight: 500;
color: @text-color;
}
}
}
}
\ No newline at end of file
/*
* @Author: XieZhiXiong
* @Date: 2021-05-12 14:00:38
* @LastEditors: XieZhiXiong
* @LastEditTime: 2021-05-12 14:37:54
* @Description: 带描述信息的进度条
*/
import React from 'react';
import { Progress } from 'antd';
import { ProgressProps } from 'antd/lib/progress';
import styles from './index.less';
export interface DescriptionsItem {
/**
* 标题
*/
title: React.ReactNode,
/**
* 值
*/
value: React.ReactNode,
/**
* 自定义渲染
*/
customRender?: React.ReactNode,
}
interface IProps extends ProgressProps {
/**
* 描述列表
*/
descriptions: DescriptionsItem[],
}
const DescProgress: React.FC<IProps> = (props: IProps) => {
const {
descriptions,
...rest
} = props;
return (
<div className={styles['desc-progress']}>
<div className={styles['desc-progress-statistic']}>
{descriptions.map((item, index) => (
!item.customRender ? (
<div key={index} className={styles['desc-progress-statistic-item']}>
<div className={styles['desc-progress-statistic-item-title']}>
{item.title}
</div>
<div className={styles['desc-progress-statistic-item-content']}>
{item.value}
</div>
</div>
) : (
item.customRender
)
))}
</div>
<Progress strokeColor="#6B778C" showInfo={false} {...rest} />
</div>
);
};
export default DescProgress;
/*
* @Author: XieZhiXiong
* @Date: 2021-05-18 18:30:50
* @LastEditors: XieZhiXiong
* @LastEditTime: 2021-06-04 13:48:09
* @Description: 内、外部流传记录
*/
import React, { useEffect, useState } from 'react';
import PolymericTable from '@/components/PolymericTable';
import { EditableColumns } from '@/components/PolymericTable/interface';
import MellowCard, { MellowCardProps } from '@/components/MellowCard';
import ButtonSwitch from '@/components/ButtonSwitch';
const PAGE_SIZE = 10;
export interface ListRes {
/**
* 数据
*/
data: { [key: string]: any }[],
/**
* 总计
*/
totalCount: number,
}
export interface FetchListParams {
/**
* 当前页
*/
current: number,
/**
* 当前页数
*/
pageSize: number,
}
interface IProps extends MellowCardProps {
/**
* 外部流转记录数据源,与 fetchOuterList 不能共存
* 如果两个同时存在 outerDataSource 优先
*/
outerDataSource?: { [key: string]: any }[],
/**
* 内部流转记录数据源,与 fetchInnerList 不能共存
* 如果两个同时存在 innerDataSource 优先
*/
innerDataSource?: { [key: string]: any }[],
/**
* 外部流转记录列数据
*/
outerColumns?: EditableColumns[],
/**
* 内部流转记录列数据
*/
innerColumns?: EditableColumns[],
/**
* 外部 rowkey
*/
outerRowkey?: string | (<T = unknown>(record: T, index?: number) => string),
/**
* 内部 rowkey
*/
innerRowkey?: string | (<T = unknown>(record: T, index?: number) => string),
/**
* 获取外部流转记录方法,与 outerDataSource 不能共存
* 如果两个同时存在 outerDataSource 优先
*/
fetchOuterList?: <T extends ListRes>(params: FetchListParams) => Promise<T>,
/**
* 获取内部流转记录方法,与 innerDataSource 不能共存
* 如果两个同时存在 innerDataSource 优先
*/
fetchInnerList?: <T extends ListRes>(params: FetchListParams) => Promise<T>,
}
const FlowRecords: React.FC<IProps> = (props: IProps) => {
const {
outerDataSource,
innerDataSource,
outerColumns,
innerColumns,
outerRowkey,
innerRowkey,
fetchOuterList,
fetchInnerList,
...rest
} = props;
const [outerPage, setOuterPage] = useState(1);
const [outerSize, setOuterSize] = useState(PAGE_SIZE);
const [innerPage, setInnerPage] = useState(1);
const [innerSize, setInnerSize] = useState(PAGE_SIZE);
const [outerList, setOuterList] = useState<ListRes | null>(null);
const [innerList, setInnerList] = useState<ListRes | null>(null);
const [outerLoading, setOuterLoading] = useState(false);
const [innerLoading, setInnerLoading] = useState(false);
const [radioValue, setRadioValue] = useState<('inner' | 'outer')>('inner');
const getOuterList = (params: FetchListParams) => {
if (outerDataSource) {
setOuterList({ data: outerDataSource, totalCount: outerDataSource.length });
return;
}
if (fetchOuterList) {
setOuterLoading(true);
fetchOuterList(params).then(res => {
if (res) {
setOuterList(res);
}
}).finally(() => {
setOuterLoading(false);
});
}
};
const getInnerList = (params: FetchListParams) => {
if (innerDataSource) {
setInnerList({ data: innerDataSource, totalCount: innerDataSource.length });
return;
}
if (fetchInnerList) {
setInnerLoading(true);
fetchInnerList(params).then(res => {
if (res) {
setInnerList(res);
}
}).finally(() => {
setInnerLoading(false);
});
}
};
useEffect(() => {
getOuterList({
current: outerPage,
pageSize: outerSize,
});
}, [outerDataSource]);
useEffect(() => {
getInnerList({
current: innerPage,
pageSize: innerSize,
});
}, [innerDataSource]);
useEffect(() => {
// 这里判断如果只有外部步骤,没有内部步骤的时候,默认设置 radioValue 为 outer
if (
(
Array.isArray(outerDataSource)
|| fetchOuterList
)
&& (
!Array.isArray(innerDataSource)
&& !fetchInnerList
)
) {
setRadioValue('outer');
}
}, [outerDataSource, fetchOuterList]);
const handleOuterPaginationChange = (current: number, pageSize: number) => {
setOuterPage(current);
setOuterSize(pageSize);
getOuterList({
current,
pageSize,
});
};
const handleInnerPaginationChange = (current: number, pageSize: number) => {
setInnerPage(current);
setInnerSize(pageSize);
getInnerList({
current,
pageSize,
});
};
const handleRadioChange = (value: ('inner' | 'outer')) => {
setRadioValue(value);
};
const options = [
(
outerList && outerList.data
? {
label: '外部状态',
value: 'outer',
}
: null
),
(
innerList && innerList.data
? {
label: '内部状态',
value: 'inner',
}
: null
),
].filter(Boolean);
return (
<MellowCard
title="流转记录"
extra={(
<ButtonSwitch
options={options}
onChange={handleRadioChange}
value={radioValue}
/>
)}
{...rest}
>
{radioValue === 'outer' ? (
<PolymericTable
rowKey={outerRowkey}
dataSource={outerList ? outerList.data : []}
columns={outerColumns}
loading={outerLoading}
pagination={(
fetchOuterList
? {
current: outerPage,
pageSize: outerSize,
total: outerList.totalCount,
}
: null
)}
onPaginationChange={handleOuterPaginationChange}
/>
) : null}
{radioValue === 'inner' ? (
<PolymericTable
rowKey={innerRowkey}
dataSource={innerList ? innerList.data : []}
columns={innerColumns}
loading={innerLoading}
pagination={(
fetchInnerList
? {
current: innerPage,
pageSize: innerSize,
total: innerList.totalCount,
}
: null
)}
onPaginationChange={handleInnerPaginationChange}
/>
) : null}
</MellowCard>
);
};
FlowRecords.defaultProps = {
outerDataSource: undefined,
innerDataSource: undefined,
outerColumns: [],
innerColumns: [],
fetchOuterList: undefined,
fetchInnerList: undefined,
};
export default FlowRecords;
/*
* @Author: XieZhiXiong
* @Date: 2021-05-11 09:44:16
* @LastEditors: XieZhiXiong
* @LastEditTime: 2021-05-11 10:14:07
* @Description: 获取 DOM 节点的位置 或是 大小 hook
*/
import { useState, useCallback } from 'react';
interface RectData {
/**
* 元素高度
*/
height: number,
/**
* 元素宽度
*/
width: number,
/**
* 元素顶部距离视口的距离
*/
top: number,
/**
* 元素右侧距离视口的距离
*/
right: number,
/**
* 元素底部距离视口的距离
*/
bottom: number,
/**
* 元素左侧距离视口的距离
*/
left: number,
/**
* 元素距离左上角坐标 (0, 0)的 x 坐标值
*/
x: number,
/**
* 元素距离左上角坐标 (0, 0)的 y 坐标值
*/
y: number,
}
const useClientRect = (): [RectData, any] => {
const [rect, setRect] = useState({
height: 0,
width: 0,
top: 0,
right: 0,
bottom: 0,
left: 0,
x: 0,
y: 0,
});
const ref = useCallback(node => {
if (node !== null) {
setRect(node.getBoundingClientRect());
}
}, []);
return [rect, ref];
}
export default useClientRect;
......@@ -137,7 +137,7 @@ const RightContent: React.FC<{}> = (props) => {
<Dropdown overlay={menuMessage} trigger={['click']} visible={visible}>
<span className={styles.topMessage}>
<Badge count={unreadMsg} size={"small"}>
<BellOutlined onClick={() => setVisible(!visible)}/>
<BellOutlined style={{color: "#fff"}} onClick={() => setVisible(!visible)}/>
</Badge>
</span>
</Dropdown>
......
import React from 'react';
import { ColumnsType } from 'antd/es/table';
import { Link } from 'umi';
import { GetEnhanceSupplierAllDetailsResponse } from '@/services/EnhanceApi';
import { numFormat } from '@/utils/numberFomat'
import moment from 'moment';
/**
* 通知单明细
*/
export const columns: ColumnsType<GetEnhanceSupplierAllDetailsResponse["details"][0] & { isHasTax: 0 | 1, taxRate: number }> = [
{
title: '订单号',
dataIndex: 'orderNo',
render: (text, record) => {
return (
<Link to={`/memberCenter/tranactionAbility/purchaseOrder/orderList/preview?id=${record.orderId}`}>{text}</Link>
)
}
},
{
title: 'ID / 商品名称',
dataIndex: 'id',
render: (text, record) => {
return (
<div style={{display: 'flex', flexDirection: 'column'}}>
<a>{record.productId}</a>
<span style={{marginTop: '8px'}}>{record.productName}</span>
</div>
)
}
},
{
title: '品类',
dataIndex: 'category',
},
{
title: '品牌',
dataIndex: 'brand',
},
{
title: '订单数量/单位',
dataIndex: 'unit',
render: (text, record) => {
const purchaseCount = +record.purchaseCount
return (
<div>
<p>{numFormat((purchaseCount))}</p>
<p>{record.unit}</p>
</div>
)
}
},
{
title: '剩余/加工数量',
dataIndex: 'processNum',
render: (text, record) => {
return (
<div>{record.surplusProcessNum} / {record.processNum}</div>
)
}
},
{
title: '含税/税率',
dataIndex: 'isHasTax',
render: (text, record) => {
return (
<div style={{display: 'flex', flexDirection: 'column'}}>
<a>{record.isHasTax ? '是' : '否'}</a>
<span style={{marginTop: '8px'}}>{record.taxRate}%</span>
</div>
)
}
},
{
title: '加工单价',
dataIndex: 'processPrice',
},
{
title: '加工费',
dataIndex: 'processTotalPrice',
},
{
title: '交期',
dataIndex: 'deliveryDate',
render: (text, record) => {
return moment(text).format('YYYY-MM-DD');
}
},
];
export const productColumn = [
{
title: 'ID / 商品名称',
dataIndex: 'id',
render: (text, record) => {
return (
<div style={{display: 'flex', flexDirection: 'column'}}>
<a>{record.productId}</a>
<span style={{marginTop: '8px'}}>{record.productName}</span>
</div>
)
}
},
{
title: '品类',
dataIndex: 'category',
},
{
title: '品牌',
dataIndex: 'brand',
},
{
title: '单位',
dataIndex: 'unit',
render: (text, record) => {
// const purchaseCount = +record.purchaseCount
return (
<div>
{/* <p>{numFormat((purchaseCount))}</p> */}
<p>{record.unit}</p>
</div>
)
}
},
{
title: '加工数量',
dataIndex: 'processNum',
render: (text, record) => {
return (
<div>{record.processNum}</div>
)
}
},
{
title: '含税/税率',
dataIndex: 'isHasTax',
render: (text, record) => {
return (
<div style={{display: 'flex', flexDirection: 'column'}}>
<span>{record.isHasTax ? '是' : '否'}</span>
<span style={{marginTop: '8px'}}>{record.taxRate}%</span>
</div>
)
}
},
{
title: '加工单价',
dataIndex: 'processPrice',
},
{
title: '加工费',
dataIndex: 'processTotalPrice',
},
{
title: '交期',
dataIndex: 'deliveryDate',
render: (text, record) => {
return moment(text).format('YYYY-MM-DD');
}
},
]
import React from 'react';
import { ColumnsType } from 'antd/es/table';
import moment from 'moment';
import { Badge } from 'antd';
import EyePreview from '@/components/EyePreview';
export const innerFlowColumns = [
{
title: '流转记录',
dataIndex: 'id'
},
{
title: '操作人',
dataIndex: 'operator',
},
{
title: '部门',
dataIndex: 'department'
},
{
title: '职位',
dataIndex: 'jobTitle'
},
{
title: '状态',
dataIndex: 'status'
},
{
title: '操作',
dataIndex: 'operate'
},
{
title: '操作时间',
dataIndex: 'operateTime',
render: (text, record) => {
return moment(text).format('YYYY-MM-DD HH:mm:ss')
}
},
{
title: '审核意见',
dataIndex: 'opinion'
}
]
/**
* 内部流转
*/
export const outerWorkflowRecordsColumn = [
{
title: '序号',
dataIndex: 'id'
},
{
title: '操作角色',
dataIndex: 'roleName',
},
{
title: '状态',
dataIndex: 'status'
},
{
title: '操作',
dataIndex: 'operate'
},
{
title: '操作时间',
dataIndex: 'operateTime',
render: (text, record) => {
return moment(text).format('YYYY-MM-DD HH:mm:ss');
}
},
{
title: '审核意见',
dataIndex: 'opinion'
}
]
import React, { useEffect, useState } from 'react';
import { ColumnsType } from 'antd/es/table';
import { FilterResType } from './useFetchFilterData';
function useColumns<T>(defaultColumns: ColumnsType<T>, actionsList: ColumnsType<T>, filterRes?: FilterResType) {
const [columns, setColumns] = useState<ColumnsType<T>>(() => defaultColumns.concat(actionsList));
/**
* 添加columns 的filter
*/
const setColumnsWithFilterOption = (optionMap: {[key: string]: {text: string, value: string}[]}) => {
const newColumns = [...columns];
const keys = Object.keys(optionMap);
newColumns.forEach((_item: ColumnsType<T>[0] & {dataIndex: string}) => {
if(_item.dataIndex && keys.includes(_item.dataIndex)) {
_item.filters = optionMap[_item.dataIndex];
}
})
setColumns(newColumns);
}
useEffect(() => {
const keys = filterRes && Object.keys(filterRes) || []
if (keys.length === 0) {
return;
}
const map = {};
keys.forEach((_item: keyof FilterResType | string) => {
map[_item] = filterRes![_item].map((_row) => ({ text: _row.label, ..._row }))
})
setColumnsWithFilterOption(map)
}, [defaultColumns, filterRes])
useEffect(() => {
setColumns(() => defaultColumns.concat(actionsList));
}, [defaultColumns])
return { columns, setColumnsWithFilterOption }
}
export default useColumns
import { useMemo } from "react";
import React from 'react';
import moment from "moment";
/**
* 详情页基本信息 列信息
*/
export const useBasicInfoColumnInDetail = <T extends object>(options: { initialValue: T }) => {
const { initialValue } = options;
const basicInfoColumn = useMemo(() => {
return [
{
title: '通知单号',
value: initialValue?.['noticeNo'],
},
{
title: '通知单来源',
value: initialValue?.['source'] === 1 ? '订单加工' : '商品加工'
},
{
title: "单据时间",
value: initialValue?.['createTime'],
},
{
title: "通知摘要",
value: initialValue?.['summary'],
span: 2,
},
{
title: '加工企业',
value: initialValue?.['processName']
},
{
title: '外部状态',
value: initialValue?.['outerStatusName'],
span: 3
},
{
title: '内部状态',
value: initialValue?.['innerStatusName'],
span: 3
}
]
}, [initialValue])
const payInfoColumns = useMemo(() => {
return [
{
title: '配送方式',
value: initialValue?.["deliveryType"] === 1 ? '物流' : '自提'
},
(
initialValue?.["deliveryType"] === 1
? {
title: "收货地址",
value: (
<div>
<div>
<span>{initialValue?.["receiveUserName"]}/ {initialValue?.["receiveUserTel"]}</span>
</div>
<span>{initialValue?.['receiveAddress']}</span>
</div>
)
}
: null
),
(
initialValue?.["deliveryAddress"] !== null
? {
title: initialValue?.["deliveryType"] === 1 ? '发货地址' : '自提地址',
value: (
<div>
<div>
<span>{initialValue?.["deliveryUserName"]}/ {initialValue?.["deliveryUserTel"]}</span>
</div>
<span>{initialValue?.['deliveryAddress']}</span>
</div>
)
}
: null
),
{
title: "交期",
value: (
initialValue?.["deliveryDate"] && moment(initialValue?.["deliveryDate"]).format('YYYY-MM-DD HH:mm:ss')
)
}
].filter(Boolean)
}, [initialValue])
const cacheOtherInfo = useMemo(() => {
const explain = initialValue?.["otherAsk"]?.explain || [];
const list = explain.map((_item) => {
return {
title: _item.name,
value: _item.value,
}
})
return list;
}, [initialValue])
const annexInfo = useMemo(() => {
const annex = initialValue?.["otherAsk"]?.annex || [];
return [{
title: "附件",
value: (
<div style={{display: 'flex', flexDirection: "column"}}>
{
annex.map((_item, key) => {
return (
<a href={_item.value} style={{marginTop: '4px'}} key={key}>{_item.name}</a>
)
})
}
</div>
)
}]
}, [initialValue])
return { basicInfoColumn, payInfoColumns, cacheOtherInfo, annexInfo };
}
import { PublicApi } from '@/services/api';
import React, { useCallback, useState } from 'react';
export type FilterResType = {
innerStatus: {label: string, value: number}[],
outerStatus: {label: string, value: number}[],
}
/**
* 获取列表页,搜索条件
* 暂时写死
*/
function useFetchFilterData(): { filterRes: FilterResType, fetchSelectOptions: () => Promise<FilterResType>} {
const [filterRes, setFilterRes] = useState<FilterResType>({
innerStatus: [],
outerStatus: [],
})
const fetchSelectOptions = useCallback(async () => {
const { data, code } = await PublicApi.getEnhanceSupplierAllOuterAndInner()
if(code === 1000) {
const res = {
innerStatus: data.innerList.map((item: any) => ({label: item.message, value: item.code})),
outerStatus: data.outerList.map((item: any) => ({label: item.message, value: item.code})),
}
setFilterRes(res);
return res;
}
return {
innerStatus: [],
outerStatus: [],
}
}, []);
return { fetchSelectOptions, filterRes}
}
export default useFetchFilterData;
import { useMemo, useState } from "react";
function useGetAnchorHeader(options: { blackList: string[] }) {
const { blackList } = options;
const [headerColumn, setHeaderColumn] = useState(() => {
const temp = [
{
key: 'progress',
name: '流转进度',
},
{
key: 'basicInfo',
name: '基本信息',
},
{
key: 'noticeDetails',
name: '通知单明细'
},
{
key: 'deliveryDetail',
name: '收发货明细'
},
{
key: 'payInfo',
name: '交付信息'
},
{
key: 'otherRequire',
name: '其他要求'
},
{
key: 'annex',
name: '附件'
},
{
key: 'record',
name: '流转记录'
}
]
return temp.filter((item) => !blackList.includes(item.key));
});
const cacheColumns = useMemo(() => headerColumn, [headerColumn]);
return cacheColumns
}
export default useGetAnchorHeader
import React, { useCallback, useEffect, useState } from 'react';
/**
* 获取详情
*/
type ParamsType = {
id: number,
}
type HeadersType = {
[key: string]: any,
}
type Ires = {
code: number,
data: { [key: string]: any },
message: string
}
function useInitialValue<T, P>(api: (params: P, headers?: HeadersType) => Promise<T & Ires>, params: P, headers?: HeadersType): { loading: boolean, initialValue: T, refresh: (params: P) => void} {
const [loading, setLoading] = useState<boolean>(false);
const [initialValue, setInitialValue] = useState<null | Ires["data"]>(null);
const fetchData = useCallback(async (params) => {
setLoading(true)
const { data, code } = await api(params);
setLoading(false)
if (code === 1000) {
setInitialValue(data)
}
}, [headers])
useEffect(() => {
if (!params) {
return;
}
fetchData(params);
}, [])
const refresh = useCallback((params) => {
fetchData(params)
}, [])
return { loading, initialValue: initialValue as T, refresh }
}
export default useInitialValue
import { useCallback, useState } from "react";
function useModal() {
const [visible, setVisible] = useState<boolean>(false);
const toggle = useCallback((status: boolean) => {
setVisible(() => status)
}, [])
return { visible, toggle }
}
export default useModal
import React, { useCallback, useState } from 'react';
import { DataPropsType } from '../../components/MachiningDetail';
import useModal from './useModal';
type ResultData = {
processDataProps: DataPropsType,
handleViewDetail: (record: any) => void,
visible: boolean,
toggle: (flag: boolean) => void
}
const useViewProcessInfo = (): ResultData => {
const [processDataProps, setProcessDataProps] = useState<DataPropsType>({} as any);
const { visible, toggle } = useModal();
const handleViewDetail = useCallback((record: any) => {
const dataProps = {
productId: record.productId,
name: record.productName,
category: record.category,
brand: record.brand,
unitName: record.unit,
processUnitPrice: record.processPrice,
quantity: record.processNum,
isHasTax: (record as any).isHasTax,
taxRate: (record as any).taxRate,
productProps: (record.property as any).specs,
files: (record.property as any).annex
}
toggle(true);
setProcessDataProps(dataProps)
}, [])
return { processDataProps, handleViewDetail, visible, toggle }
}
export default useViewProcessInfo
import React from 'react';
import { Card } from 'antd';
import AppendixItem from './AppendixItem';
interface Iprops {
files: {name: string, value: string}[]
}
const Appendix: React.FC<Iprops> = (props) => {
const { files = [] } = props;
return (
<>
<Card title={"附件"} style={{height: '100%'}} bordered={false}>
{
files.map((item, key) => {
return (
<AppendixItem key={key} name={item.name} url={item.value}/>
)
})
}
</Card>
</>
)
}
export default Appendix
\ No newline at end of file
import React from 'react';
import pdf_icon from '@/asserts/pdf_icon.png';
const styles = {
display: 'flex',
// flexDirection: "row",
alignItem: 'center',
color: '#00B37A',
backgroundColor: '#F4F5F7',
padding: '10px 10px',
cursor: 'pointer',
height: '100%'
}
interface Iprops {
style?: React.CSSProperties,
name: string,
url: string
}
const AppendixItem: React.FC<Iprops> = (props) => {
const handleDownload = (name:string, url: string) => {
let link = document.createElement("a");
link.style.display = "none";
link.href = url;
link.setAttribute("download", name);
document.body.appendChild(link) //a标签插至页面中
link.click();
document.body.removeChild(link);
}
return (
<div style={styles}>
<img src={pdf_icon} style={{width: '20px', marginRight: '15px'}} />
<div onClick={() => handleDownload(props.name, props.url)}>{props.name}</div>
</div>
)
}
export default React.memo(AppendixItem)
\ No newline at end of file
/*
* @Author: your name
* @Date: 2020-10-12 18:27:56
* @LastEditTime: 2020-10-13 10:01:23
* @Description: 附件
*/
import Appendix from './Appendix';
import AppendixItem from './AppendixItem';
export {
Appendix,
AppendixItem
}
\ No newline at end of file
import React from 'react';
import { ColumnsType } from 'antd/es/table';
import moment from 'moment';
import { Badge } from 'antd';
import EyePreview from '@/components/EyePreview';
import { GetEnhanceSupplierAllDetailsResponse } from '@/services/EnhanceApi';
import { Link } from 'umi';
import DescProgress from '@/components/DescProgress';
/**
* 收发货统计, 订单
*/
const statisticsColumns: ColumnsType<GetEnhanceSupplierAllDetailsResponse["details"][0] & { isHasTax: 0 | 1, taxRate: number }> = [
{
title: '订单号',
dataIndex: 'orderNo',
render: (text, record) => {
return (
<Link to={`/memberCenter/tranactionAbility/purchaseOrder/orderList/preview?id=${record.orderId}`}>{text}</Link>
)
}
},
{
title: 'ID / 商品名称',
dataIndex: 'id',
render: (text, record) => {
return (
<div style={{display: 'flex', flexDirection: 'column'}}>
<a>{record.productId}</a>
<span style={{marginTop: '8px'}}>{record.productName}</span>
</div>
)
}
},
{
title: '品类',
dataIndex: 'category',
},
{
title: '品牌',
dataIndex: 'brand',
},
{
title: '加工数量/单位',
render: (text, record) => {
return (
<div style={{display: 'flex', flexDirection: 'column'}}>
<a>{record.processNum}</a>
<span style={{marginTop: '8px'}}>{record.unit}</span>
</div>
)
}
},
{
title: '交期',
dataIndex: 'deliveryDate',
render: (text, record) => {
return moment(text).format('YYYY-MM-DD');
}
},
{
title: '加工单价',
dataIndex: 'processPrice',
},
{
title: '加工费',
dataIndex: 'processTotalPrice',
},
{
title: '已发货/未发货',
dataIndex: 'receiveNum',
render: (text, record) => {
const descOptions = [
{
title: "已发货",
value: record.deliverNum
},
{
title: '未发货',
value: record.notDeliverNum
}
]
return (
<DescProgress descriptions={descOptions} />
)
}
},
{
title: '已收货',
dataIndex: 'receiveNum'
},
{
title: '差异数量',
dataIndex: 'differenceNum'
},
// {
// title: '未发货',
// dataIndex: "notDeliverNum"
// }
];
const productColumns = statisticsColumns.slice(1);
/**
* 收发货详情
*/
const infoOrderColumns = [
{
title: '订单号',
dataIndex: 'orderNo',
render: (text, record) => {
// orderId, orderDetailId
return (
<EyePreview url={`/memberCenter/tranactionAbility/saleOrder/orderList/preview?id=${record.orderId}`}>
{text}
</EyePreview>
)
}
},
{
title: 'ID',
dataIndex: 'productId'
},
{
title: '商品名称',
dataIndex: 'productName'
},
{
title: '品类',
dataIndex: 'category'
},
{
title: '品牌',
dataIndex: 'brand'
},
{
title: '单位',
dataIndex: 'unit'
},
{
title: '加工数量',
dataIndex: 'processNum'
},
{
title: '发货数量',
dataIndex: 'deliverNum'
}
]
const infoProductColumns = infoOrderColumns.slice(1);
export {
statisticsColumns,
productColumns,
infoOrderColumns,
infoProductColumns
}
import MellowCard from '@/components/MellowCard'
import React, { useEffect, useMemo, useState } from 'react'
import { GetEnhanceSupplierAllDetailsResponse } from '@/services/EnhanceApi';
import { statisticsColumns as orderColumns, productColumns, infoOrderColumns, infoProductColumns } from './columns';
import ButtonSwitch from '@/components/ButtonSwitch';
import { Table, Button, Tabs } from 'antd';
import CustomizeColumn from '@/components/CustomizeColumn';
import { Link } from 'umi';
import moment from 'moment';
import { PublicApi } from '@/services/api';
const TabPane = Tabs.TabPane;
type infoDataSourceType = GetEnhanceSupplierAllDetailsResponse["pnoReceiveDeliverDetailDOList"]
interface Iprops {
/**
* 是否是确认生产通知单。
* 用于判断是确认发货,确认收货还是确认回单
*/
mode?: "deliver" | "receive" | "receipt",
/**
* 通知单来源: 1-订单加工 2-商品加工
*/
source: 1 | 2,
/**
* 面板
*/
panelKey?: 'statistics' | 'info',
/**
* 收发货统计跟detail 一样
*/
statisticsDataSource: GetEnhanceSupplierAllDetailsResponse["details"],
/**
* 收发货明细
* infoDataSource
*/
infoDataSource: infoDataSourceType,
/**
* 确认回单,确认发货,确认收货
*/
onConfirm?: (currentInnerStatus: keyof typeof ACTION_TEXT, params: { produceNoticeOrderId: number, pnoReceiveDeliverDetailId: number }) => void,
}
const format = 'YYYY-MM-DD HH:mm:ss';
/**
* isConfirm_deliverStatus_receiveStatus_receiptStatus
*/
const INNER_STATUS_TEXT = {
"1_1_1": '待确认发货',
'2_1_1': '已确认发货',
'2_2_1': '待确认回单',
'2_2_2': '已确认回单'
}
const ACTION_TEXT = {
'1_1_1': '确认发货',
'2_1_1': '确认收货',
'2_2_1': '确认回单'
}
const DeliveryInfo: React.FC<Iprops> = (props: Iprops) => {
const { statisticsDataSource, infoDataSource, source, panelKey, mode, onConfirm } = props;
const [radioValue, setRadioValue] = useState<"statistics" | "info">(panelKey);
const statisticsColumns = useMemo(() => source === 1 ? orderColumns : productColumns, [source]);
const infoColumns = useMemo(() => source === 1 ? infoOrderColumns : infoProductColumns, [source]);
const [activeBatch, setActiveBatch] = useState<number>(1);
const [tabOptions, setTabOptions] = useState<number[]>([]);
useEffect(() => {
setRadioValue(panelKey)
}, [panelKey])
useEffect(() => {
const length = infoDataSource && infoDataSource.length || 0;
if (length === 0) {
return;
}
const options = new Array(length).fill(0).map((item, _key) => _key + 1);
setTabOptions(options);
}, [infoDataSource])
const options = [
{
label: '收发货统计',
value: 'statistics'
},
{
label: "收发货明细",
value: 'info'
}
]
const cacheOptions = useMemo(() => {
if (!infoDataSource || infoDataSource.length === 0) {
return options.filter((_row) => _row.value !== 'info')
}
return options;
}, [infoDataSource])
const handleRadioChange = (value: "statistics" | "info") => {
setRadioValue(value);
}
const handleOnSubmit = async (currentInnerStatus: keyof typeof ACTION_TEXT, params: { produceNoticeOrderId: number, pnoReceiveDeliverDetailId: number }) => {
onConfirm?.(currentInnerStatus, params)
}
const batchColumns = useMemo(() => {
const activeData: infoDataSourceType[0] | null = infoDataSource && infoDataSource.length > 0 && infoDataSource[activeBatch - 1] || null;
const currentInnerStatus = `${activeData?.deliverStatus}_${activeData?.receiveStatus}_${activeData?.receiptStatus}`;
return [
{
title: '发货单号',
value: (
// <Link
// to={`/memberCenter/tranactionAbility/stockSellStorage/bills/detail?id=${activeData?.deliveryId}`}
// >
// {activeData?.deliveryNo}
// </Link>
<a>{activeData?.deliveryNo}</a>
)
},
{
title: "物流单号",
value: (
// <Link to={`/memberCenter/logisticsAbility/logisticsSubmit/orderSubmitSearchList/detail?id=${activeData?.logisticsOrderId}`}>
// {activeData?.logisticsOrderNo}
// </Link>
<a>{activeData?.logisticsOrderNo}</a>
)
},
{
title: "入库单号",
value: (
// <Link to={`/memberCenter/tranactionAbility/stockSellStorage/bills/detail?id=${activeData?.storageId}`}>{activeData?.storageNo}</Link>
<a>{activeData?.storageNo}</a>
)
},
{
title: "内部状态",
value: (
<div style={{display: 'flex', flexDirection: 'row', justifyContent: 'space-between', width: '100%'}}>
<span>{INNER_STATUS_TEXT[currentInnerStatus] || ''}</span>
{
mode && ACTION_TEXT[currentInnerStatus] && activeData?.[`${mode}Status`] === 1 && (
// <a
// onClick={() => handleOnSubmit(currentInnerStatus as "1_1_1", {produceNoticeOrderId: activeData.produceNoticeOrderId, pnoReceiveDeliverDetailId: activeData.id })}
// >
// {ACTION_TEXT[currentInnerStatus]}
// </a>
ACTION_TEXT[currentInnerStatus]
) || null
}
</div>
)
},
{
title: '发货时间',
value: (
activeData?.deliveryTime
&& moment(activeData?.deliveryTime).format(format)
)
},
{
title: '物流公司',
value: (
activeData?.logisticsCompany
)
},
{
title: '入库时间',
value: (
activeData?.storageTime
&& moment(activeData?.storageTime).format(format)
),
}
]
} ,[infoDataSource, activeBatch, mode])
const handleTabOnChange = (value: string) => {
setActiveBatch(+value);
}
return (
<MellowCard
title="收发货统计"
extra={
<ButtonSwitch options={cacheOptions} value={radioValue} onChange={handleRadioChange} />
}
>
{
radioValue === 'statistics' && (
<Table columns={statisticsColumns} rowKey="id" dataSource={statisticsDataSource} />
)
}
{
radioValue === 'info' && infoDataSource?.length > 0 && (
<div>
<Tabs defaultActiveKey="1" activeKey={activeBatch.toString()} onChange={handleTabOnChange}>
{
tabOptions.map((_item) => {
return (
<TabPane tab={`${_item}批次`} key={_item}>
<CustomizeColumn data={batchColumns} column={4} />
<Table columns={infoColumns} rowKey={(record) => `${record.produceNoticeOrderDetailId}-${record.productId}`} dataSource={infoDataSource[activeBatch - 1]?.pnoReceiveDeliverDetailProductBOList}></Table>
</TabPane>
)
})
}
</Tabs>
</div>
)
}
</MellowCard>
)
}
DeliveryInfo.defaultProps = {
panelKey: 'statistics',
mode: null,
onConfirm: null
}
export default DeliveryInfo;
import React, { useState, useEffect } from 'react';
import { Card, Col, Row, Select } from 'antd'
import { PublicApi } from '@/services/api';
const Option = Select.Option;
enum deliveryType {
logistics,
bySelf
}
interface Iprops {
deliveryDate: string,
deliveryType: deliveryType,
receiveAddress: string,
receiveUserName: string,
receiveUserTel: string
deliveryAddress: string,
deliveryUserName: string,
deliveryUserTel: string,
}
const DeliveryInfomation: React.FC<Iprops> = (props) => {
return (
<Card title="交付信息">
<Row>
<Col span={6}>
{/* <OtherRequirement /> */}
<Row style={{marginBottom: '20px'}}>
<Col span={6}>配送方式</Col>
<Col>{props.deliveryType == 1 ? '物流' : '自提'}</Col>
</Row>
<Row>
<Col span={6}>交期</Col>
<Col>{props.deliveryDate}</Col>
</Row>
</Col>
<Col span={9} style={props.deliveryType == 1 ? {} : {display: 'none'}}>
<Row style={{marginBottom: '20px'}}>
<Col span={5}>收货地址</Col>
<Col>{props.receiveUserName} / {props.receiveUserTel}</Col>
</Row>
<Row>
<Col offset={5}>{props.receiveAddress}</Col>
</Row>
</Col>
<Col span={9} style={!props.deliveryUserName ? {display: 'none'} : {}}>
<Row style={{marginBottom: '20px'}}>
<Col span={5}>{props.deliveryType == 2 ? '自提地址' : '发货地址' }</Col>
<Col>{props.deliveryUserTel} / {props.deliveryUserName}</Col>
</Row>
<Row>
<Col offset={5}>{props.deliveryAddress}</Col>
</Row>
</Col>
</Row>
</Card>
)
}
export default DeliveryInfomation;
\ No newline at end of file
......@@ -13,18 +13,24 @@
.menuItem {
font-size: 12px;
font-weight: 500;
color: #303133;
// color: #303133;
line-height: 12px;
padding: 10px 24px;
margin-bottom: 15px;
padding: 18px 0px;
cursor: pointer;
border-left: 2px solid transparent;
// border-left: 2px solid transparent;
transition: all 0.2s ease-in-out;
}
.active {
border-left: 2px solid #00B37A;
.menuTitle {
border-left: 2px solid transparent;
padding-left: 16px;
}
.active {
color: #303133;
border-left: 2px solid #00B37A;
}
}
}
.body {
......@@ -35,13 +41,22 @@
overflow: scroll;
overflow-x: hidden;
.common {
padding: 24px 24px 0 24px;
padding: 24px 24px 0 16px;
.header {
border-left: 2px solid #00B37A;
color: #606266;
padding: 0 10px;
line-height: 14px;
margin-bottom: 24px;
font-size: 14px;
// &:before {
// content: '';
// display: inline-block;
// height: 8px;
// width: 2px;
// background-color: #00B37A;
// }
}
.info {
......@@ -55,4 +70,4 @@
}
}
}
}
\ No newline at end of file
}
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { Drawer, Row, Col } from 'antd'
import { useScroll } from '@umijs/hooks';
import styles from './index.less';
// import useGetClientRect from './useGetClientRect';
import classnames from 'classnames'
/**
* 查看加工明细
*/
export type DataPropsType = {
/**
* 商品id
*/
productId: number
/**
* 商品名
*/
name: string,
/**
* 商品品类
*/
category: string,
/**
* 商品品牌
*/
brand: string,
/**
* j加工数量
*/
quantity: number,
/**
* 单位
*/
unitName: string,
/**
* 加工单价
*/
processUnitPrice: number,
/**
* 商品其他属性
*/
productProps: { name: string, value: string}[],
/**
* 是否含税
*/
isHasTax: 0 | 1,
/**
* 税率
*/
taxRate: number,
/**
* 文件
*/
files: {name: string, value: string}[],
}
interface Iprops {
visible: boolean,
onClose?: (() => void) | null,
dataProps: DataPropsType
}
const MachiningDetail: React.FC<Iprops> = (props: Iprops) => {
const { visible, onClose, dataProps } = props;
const [scroll, ref] = useScroll<HTMLDivElement>();
const [menu, setMenu] = useState<string[]>([])
const [rangeHeight, setRangeHeight] = useState<number[]>([])
const [activeIndex, setActiveIndex] = useState<number>(0);
const activeAndScroll = (index: number) => {
setActiveIndex(index);
ref.current.scrollTop = rangeHeight[index]
}
/**
* @tofix 这里有个bug,就是上面点击了之后,然后滚动,然后再做了一次计算,会有问题
*/
useEffect(() => {
const { top } = scroll;
let activeKey = 0;
for (let i = 0; i < rangeHeight.length - 1; i++) {
if (top >= rangeHeight[i] && top < rangeHeight[i + 1]) {
activeKey = i;
break;
}
activeKey = i + 1;
}
// console.log(activeKey)
setActiveIndex(activeKey)
}, [scroll])
const productsPropsColumns = [
{
title: '商品ID',
dataIndex: 'productId',
},
{
title: '商品名称',
dataIndex: 'name',
},
{
title: '商品品类',
dataIndex: 'category',
},
{
title: '商品品牌',
dataIndex: 'brand',
}
];
const machiningPropsColumns = [
{
title: '单位',
dataIndex: 'unitName'
},
{
title: '加工数量',
dataIndex: 'quantity'
},
{
title: '单价',
dataIndex: 'processUnitPrice',
render: (text, record: DataPropsType) => {
return `¥${record.processUnitPrice}`
}
},
{
title: '是否含税',
dataIndex: 'tax',
render: (text, record: DataPropsType) => {
return (
<div>
{record.isHasTax ? '是' : '否'} / {record.taxRate || 0}%
</div>
)
}
}
]
useEffect(() => {
if (!visible) {
return;
}
const childNodes = ref.current.childNodes;
const menuData = [];
let sum = 0;
const ranges = [0];
childNodes.forEach((_item) => {
const headerChild = _item.childNodes?.[0].textContent;
const offsetHeight = (_item as any).offsetHeight;
menuData.push(headerChild)
sum += offsetHeight;
ranges.push(sum);
})
setMenu(menuData);
setRangeHeight(ranges);
}, [visible, ref])
const renderFiles = () => {
if(!dataProps.files || dataProps.files.length === 0) {
return null
}
return (
<div className={styles.common} >
<div className={styles.header}>{"附件"}</div>
<div className={styles.info}>
{
dataProps.files?.map((_item) => {
return (
<a key={_item.value} href={_item.value} target="_blank">{_item.name}</a>
)
})
}
</div>
</div>
)
}
return (
<Drawer
visible={visible}
onClose={onClose}
width={800}
title="查看加工明细"
bodyStyle={{padding: '0px'}}
>
<div className={styles.container}>
<div className={styles.menu}>
{
menu.map((item,key) => {
return (
<div key={item} className={classnames(styles.menuItem)} onClick={() => activeAndScroll(key)}>
<span className={classnames(styles.menuTitle, { [styles.active]: activeIndex === key })} >
{item}
</span>
</div>
)
})
}
</div>
<div className={styles.body} ref={ref}>
<div className={styles.common}>
<div className={styles.header}>{"基本信息"}</div>
<div className={styles.info}>
{
productsPropsColumns.map((_item) => {
return (
<Row className={styles.infoRow} key={_item.dataIndex}>
<Col className={styles.label} span={4}>{_item.title}</Col>
<Col span={12}>{dataProps?.[_item.dataIndex]}</Col>
</Row>
)
})
}
</div>
</div>
{
dataProps.productProps && dataProps.productProps.map((item, key) => {
return (
<div className={styles.common} key={key}>
<div className={styles.header}>{item.name}</div>
<div className={styles.info}>
<Row className={styles.infoRow}>
<Col className={styles.label} span={4}>{item.name}</Col>
<Col span={12}>{item.value}</Col>
</Row>
</div>
</div>
)
})
}
{
renderFiles()
}
<div className={styles.common} style={{marginBottom: '585px'}}>
<div className={styles.header}>{"加工要求"}</div>
<div className={styles.info}>
{
machiningPropsColumns.map((_row) => {
return (
<Row className={styles.infoRow} key={_row.dataIndex}>
<Col className={styles.label} span={4}>{_row.title}</Col>
<Col span={12}>
{
_row.render && _row.render(dataProps[_row.dataIndex], dataProps)
|| dataProps[_row.dataIndex]
}
</Col>
</Row>
)
})
}
</div>
</div>
</div>
</div>
</Drawer>
)
}
export default MachiningDetail
import React from 'react';
import { Card, Row, Col } from 'antd';
interface Iexpain {
[key: string]: string
}
interface Iprops {
explain: Iexpain[]
}
const OtherRequirement: React.FC<Iprops> = (props) => {
const { explain = [] } = props;
return (
<Card title="其他要求">
{
[0,1,2].map((item) => {
return (
<Row style={{marginBottom: '15px'}} key={item}>
{
explain.slice(item * 2, item * 2 + 2).map((i) => {
return (
<React.Fragment key={i.name} >
<Col span={3}>{i.name}</Col>
<Col span={8}>{i.value}</Col>
</React.Fragment>
)
})
}
</Row>
)
})
}
</Card>
)
}
export default OtherRequirement;
\ No newline at end of file
import React, { useState, useEffect } from 'react';
import { Button, Drawer } from 'antd';
import Content from './content';
import { createFormActions } from '@formily/antd'
const actions = createFormActions();
interface Iprops {
type: string, // veiw | edit,
id: string,
brand: string,
category: string,
name: string,
productProps: any[],
files: IfileProps[],
unitName: string,
quantity?: string,
processUnitPrice?: string
}
interface IfileProps {
name: string,
url: string
}
const DrawerProcessDetail: React.FC<Iprops> = (props) => {
const { id, brand, category, name, productProps, files, unitName, quantity, processUnitPrice} = props;
const [visible, setVisible] = useState<boolean>(false);
const onClose = () => {
setVisible(false)
}
const onOpen = () => {
setVisible(true)
}
return (
<>
<div onClick={onOpen}>
{props.children}
</div>
<Drawer
title="查看加工明细"
width={720}
onClose={onClose}
visible={visible}
bodyStyle={{ padding: '0'}}
footer={
<div
style={{
textAlign: 'right',
}}
>
<Button onClick={onClose} style={{ marginRight: 8 }}>
取消
</Button>
<Button onClick={onClose} type="primary">
确认
</Button>
</div>
}
>
<Content
name={name}
id={id}
brand={brand}
category={category}
unitName={unitName}
productProps={productProps}
quantity={quantity}
processUnitPrice={processUnitPrice}
type={"view"}
files={files}
actions={actions}
/>
</Drawer>
</>
)
}
export default DrawerProcessDetail;
\ No newline at end of file
import React, { useState, useRef, useEffect, useImperativeHandle, useMemo, useCallback } from 'react';
import styles from './content.less';
import { Row, Col } from 'antd';
import classnames from 'classnames';
import NiceForm from '@/components/NiceForm';
import { createFormActions } from '@formily/antd'
import {useScroll } from '@umijs/hooks';
import { AppendixItem } from '../Appendix';
const actions = createFormActions();
const schema = {
type: 'object',
properties: {
layout: {
type: 'object',
'x-component': 'mega-layout',
'x-component-props': {
labelCol: 4,
wrapperCol: 9,
labelAlign: 'left'
},
properties: {
quantity: {
type: 'string',
title: '加工数量',
'x-rules': [
{
required: true,
message: '请填写加工数量'
}
]
},
processUnitPrice: {
type: 'string',
title: '加工单价',
'x-rules': [
{
required: true,
message: '请填写加工单价'
}
]
}
}
}
}
}
interface Iprops {
id: string,
name: string,
category: string,
brand: string,
files: {name: string, url: string}[],
productProps: { name: string, value: string}[],
quantity: number | string,
unitName: string,
processUnitPrice: number | string,
formSubmit: (value: any) => void,
type: 'edit' | 'view',
fileOnChange?: (value: any) => void
ref: any
}
const Content: React.FC<Iprops> = React.forwardRef((props, conftentRef) => {
const { id, name, category, brand, unitName, files, productProps, quantity, processUnitPrice } = props;
const [menu, setMenu] = useState<any>([])
const [scroll, ref] = useScroll<HTMLDivElement>();
const getMenuItemCs = useCallback((minHeight: number, maxHeight: number) => {
return classnames({
[styles.menuItem]: true,
[styles.active]: scroll.top >= minHeight && scroll.top < maxHeight
})
}, [scroll])
useImperativeHandle(conftentRef, () => ({
submit: () => {
actions.submit();
}
}));
const activeAndScroll = (item) => {
ref.current.scrollTop = item.min;
}
useEffect(() => {
let height = 238;
const basic = [{name: '基本信息', min: 0, max: height}]; // 238
let flag = false;
const attributes = productProps && productProps.map((item) => {
const prevHeight = height;
height = height + 112;
flag = true;
return {
name: item.name,
min: prevHeight,
max: height
}
}) || [];
const aboveFileHeight = flag ? height : height - 112;
const maxFileHeight = files.length > 0 && files.reduce((prev, next) => {
return prev += 60;
}, aboveFileHeight) || aboveFileHeight + 128;
const filesMenu = [{name: '附件', min: aboveFileHeight, max: maxFileHeight}];
const progress = [{name: '加工要求', min: maxFileHeight , max: maxFileHeight + 300}];
setMenu(basic.concat(attributes, filesMenu, progress));
}, [productProps, files])
const handleFormSubmit = (value) => {
props.formSubmit(value);
}
return (
<div className={styles.container}>
<div className={styles.menu}>
{
menu.map((item,key) => {
return (
<div key={item.name} className={getMenuItemCs(item.min, item.max)} onClick={() => activeAndScroll(item)}>
{item.name}
</div>
)
})
}
</div>
<div className={styles.body} ref={ref}>
<div className={styles.common} >
<div className={styles.header}>{"基本信息"}</div>
<div className={styles.info}>
<Row className={styles.infoRow}>
<Col className={styles.label} span={4}>商品ID</Col>
<Col span={12}>{id}</Col>
</Row>
<Row className={styles.infoRow}>
<Col className={styles.label} span={4}>商品名称</Col>
<Col span={12}>{name}</Col>
</Row>
<Row className={styles.infoRow}>
<Col className={styles.label} span={4}>商品品类</Col>
<Col span={12}> {category}</Col>
</Row>
<Row className={styles.infoRow}>
<Col className={styles.label} span={4}>商品品牌</Col>
<Col span={12}>{brand}</Col>
</Row>
</div>
</div>
{
productProps && productProps.map((item, key) => {
return (
<div className={styles.common} key={key}>
<div className={styles.header}>{item.name}</div>
<div className={styles.info}>
<Row className={styles.infoRow}>
<Col className={styles.label} span={4}>{item.name}</Col>
<Col span={12}>{item.value}</Col>
</Row>
</div>
</div>
)
})
}
<div className={styles.common} id="附件" >
<div className={styles.header}>{"附件"}</div>
<div className={styles.info}>
{
files.length === 0 && props.type != 'edit'
? <p></p>
: null
}
{
files.map((item,key) => {
return (
<AppendixItem name={item.name} url={item.url} key={key} />
)
})
}
</div>
</div>
<div className={styles.common} >
<div className={styles.header}>{"加工"}</div>
<div className={styles.info}>
<Row className={styles.infoRow}>
<Col className={styles.label} span={4}>单位</Col>
<Col span={12}>{unitName}</Col>
</Row>
</div>
<div className={styles.info} style={{marginBottom: '450px'}}>
{
props.type == 'edit'
? <NiceForm
schema={schema}
actions={actions}
// onSubmit={props.formSubmit}
onSubmit={handleFormSubmit}
initialValues={{
quantity: quantity,
processUnitPrice: processUnitPrice
}}
/>
: (
<>
<Row className={styles.infoRow}>
<Col className={styles.label} span={4}>加工数量</Col>
<Col span={12}>{quantity}</Col>
</Row>
<Row className={styles.infoRow}>
<Col className={styles.label} span={4}>加工单价</Col>
<Col span={12}>{processUnitPrice}</Col>
</Row>
</>
)
}
</div>
</div>
</div>
</div>
)
})
export default React.memo(Content);
\ No newline at end of file
import React, {useEffect, useState} from 'react';
import { ColumnsType } from 'antd/es/table';
import { Table, Row, Col, Popconfirm, Radio } from 'antd';
import styles from './index.less'
import moment from 'moment';
import { history } from 'umi'
// import { ASSIGN_PENDING_RECEIVE_DETAIL, PENDING_DELIVERD_PATH, PENDING_RECEIPT_PATH } from '../../common';
interface Iprops {
tab: string,
columns: ColumnsType<any>,
dataSource: any[],
handleConfirm?: (type: string, params: any) => void
}
const format = 'YYYY-MM-DD HH:mm:ss';
/**
* 只有在待发货生产通知单, 待收货生产通知、待回单生产通知单才能修改他table商品的状态
* 比如确认发货,确认收货、确认回单
* 并且上面三个状态的值为1才能修改
* @param props
*/
// const PENDING_DELIVERD_DETAIL_PATH = PENDING_DELIVERD_PATH + '/detail';
// const PENDING_RECEIPT_DETAIL_PATH = PENDING_RECEIPT_PATH + '/detail';
// //receiptStatus_1
// const MAP_PATH = {
// [PENDING_DELIVERD_DETAIL_PATH]: 'deliverStatus',
// [ASSIGN_PENDING_RECEIVE_DETAIL]: 'receiveStatus',
// [PENDING_RECEIPT_DETAIL_PATH]: 'receiptStatus',
// }
// const TEXT_MAP = {
// 'deliverStatus_1': '确认发货',
// 'receiveStatus_1': '确认收货',
// 'receiptStatus_1': '确认回单'
// }
// 从全部进来, 如果是confirm
const CONFIRM_STATUS_TEXT = {
'1_1_1': '待确认发货',
'2_1_1': '已确认发货',
'2_2_1': '待确认回单',
'2_2_2': '已确认回单'
}
// 如果是 指派通知单
const ASSIGN_STATUS_TEXT = {
'1_1_1': '待确认发货',
'2_1_1': '已确认发货',
'2_2_1': '已确认收货',
'2_2_2': '已确认回单'
}
const DetailTab: React.FC<Iprops> = (props) => {
const { columns, dataSource } = props;
const [activeBatch, setActiveBatch] = useState(0);
const [activeData, setActiveData] = useState<any>({});
// const [currentTarget, setCurrentTarget] = useState("deliverStatus");
const pathname = history.location.pathname;
// const currentPage = MAP_PATH[pathname];
const INNER_TEXT = pathname.includes('confirm') ? CONFIRM_STATUS_TEXT : ASSIGN_STATUS_TEXT;
useEffect(() => {
if(dataSource && dataSource.length > 0) {
setActiveData(dataSource[0])
}
}, [dataSource])
const handleOnChange = (e) => {
setActiveBatch(e.target.value)
setActiveData(dataSource[e.target.value])
}
const confirm = (params, type) => {
!!props.handleConfirm && props.handleConfirm(type, params);
}
return (
<div >
<div >
<Radio.Group value={activeBatch} onChange={handleOnChange}>
{
dataSource && dataSource.map((item, key) => {
return (
<Radio.Button key={item.deliveryBatch} value={key}>{item.deliveryBatch} 批次</Radio.Button>
)
})
}
</Radio.Group>
</div>
<div className={styles.header}>
<Row>
<Col span={6}>
<div className={styles.detailItem}>
<span className={styles.title}>发货单号</span>
<a className={styles.value}>{activeData.deliveryNo}</a>
</div>
<div>
<span className={styles.title}>发货时间</span>
<span className={styles.value}>
{
activeData.deliveryTime
&& moment(activeData.deliveryTime).format(format)
}</span>
</div>
</Col>
<Col span={6}>
<div className={styles.detailItem}>
<span className={styles.title}>物流单号</span>
<a className={styles.value}>{activeData.logisticsOrderNo}</a>
</div>
<div>
<span className={styles.title}>物流公司</span>
<span className={styles.value}>{activeData.logisticsCompany}</span>
</div>
</Col>
<Col span={6}>
<div className={styles.detailItem}>
<span className={styles.title}>入库单号</span>
<span className={styles.value}>{activeData.storageNo}</span>
</div>
<div>
<span className={styles.title}>入库时间</span>
<span className={styles.value}>
{
activeData.storageTime
&& moment(activeData.storageTime).format(format)
}</span>
</div>
</Col>
<Col span={4} className={styles.status}>
<div>
<span className={styles.title}>内部状态</span>
<span className={styles.value}>{
INNER_TEXT[`${activeData.deliverStatus}_${activeData.receiveStatus}_${activeData.receiptStatus}`]
}</span>
</div>
</Col>
{/* <Col span={2} className={styles.action}>
<Popconfirm
title={`是否${TEXT_MAP[`${currentPage}_${activeData[currentPage]}`]}`}
onConfirm={() => confirm(
{
produceNoticeOrderId: activeData.produceNoticeOrderId,
pnoReceiveDeliverDetailId: activeData.id
},
`${currentPage}_${activeData[currentPage]}`
)
}
okText="是"
cancelText="否"
>
<a href="#">
{TEXT_MAP[`${currentPage}_${activeData[currentPage]}`]}
</a>
</Popconfirm>
</Col> */}
</Row>
</div>
<Table
dataSource={activeData.pnoReceiveDeliverDetailProductBOList}
rowKey={"orderNo"}
columns={columns}
/>
</div>
)
}
export default DetailTab;
\ No newline at end of file
import React from 'react';
import { Card, Tabs } from 'antd';
const TabPane = Tabs.TabPane
interface Iprops {};
const ReceiptDeliveryDetailsCard: React.FC<Iprops> = (props) => {
const newChildren = React.Children.map(props.children, (child: any) => {
if(child && child.type) {
return (
<TabPane tab={child.props.tab}>
{child}
</TabPane>
)
} else {
return child
}
})
return (
<Card bodyStyle={{padding: '10px 24px 24px 24px'}}>
<Tabs>
{newChildren}
</Tabs>
</Card>
)
}
export default ReceiptDeliveryDetailsCard
\ No newline at end of file
import React from 'react';
import { Table } from 'antd';
import { ColumnsType } from 'antd/es/table';
interface Iprops {
tab: string,
columns: ColumnsType<any>,
dataSource: any[],
}
const StatisticsTab: React.FC<Iprops> = (props) => {
const { columns, dataSource } = props;
return (
<Table rowKey={"id"} columns={columns} dataSource={dataSource} />
)
}
export default StatisticsTab
\ No newline at end of file
.header {
background: #F7F8FA;
padding: 25px 24px;
margin: 24px 0;
font-size: 12px;
.detailItem {
margin-bottom: 16px;
}
.heightLight {
color: @main-color;
}
.title {
margin-right: 40px;
color: #909399;
}
.status {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.action {
display: flex;
flex-direction: row;
justify-content: flex-end;
align-items: center;
color: #C0C4CC;;
}
}
\ No newline at end of file
import ReceiptDeliveryDetailsCard from './ReceiptDeliveryDetailsCard';
import DetailTab from './DetailTab';
import StatisticsTab from './StatisticsTab';
export {
ReceiptDeliveryDetailsCard,
DetailTab,
StatisticsTab
}
\ No newline at end of file
import React from 'react';
import { Card, Steps } from 'antd';
const Step = Steps.Step;
const customDot = (dot, { status, index }) => (
<span>
{dot}
</span>
);
interface Istatus {
isExecute: number,
roleName: string,
step: number,
taskName: string
}
interface Iprops {
outerTaskList: Istatus[]
}
const StatusStep: React.FC<Iprops> = (props) => {
const {outerTaskList = []} = props;
return (
<div style={{padding: '16px 0'}}>
<h1 >外部状态</h1>
<Steps progressDot={customDot} style={{marginTop: '20px'}}>
{
outerTaskList.map((item: Istatus) => {
return (
<Step title={item.taskName} status={item.isExecute == 1 ? 'finish' : 'wait'} description={item.roleName} key={item.step} />
)
})
}
</Steps>
</div>
)
}
export default StatusStep
\ No newline at end of file
This diff is collapsed.
......@@ -28,21 +28,25 @@ export const SUPPLIER_OUTER_STATUS_COLOR = [
const columns: ColumnsType<any> = [
{
title: '通知单号',
title: '通知单号/摘要',
dataIndex: 'noticeNo',
render: (text, record) => {
const url = '/productionNoticeManage/productionNotice/detail';
return (
<EyePreview url={url + `?id=${record.id}`}>{text}</EyePreview>
<div style={{display: 'flex', flexDirection: 'column'}}>
<EyePreview url={url + `?id=${record.id}`}>{text}</EyePreview>
<span>{record.summary}</span>
</div>
)
}
},
{ title: '通知单摘要', dataIndex: 'summary' },
// { title: '通知单摘要', dataIndex: 'summary' },
{ title: '供应会员', dataIndex: 'supplierName' },
{ title: '加工企业名称', dataIndex: 'processName'},
{
title: '单据时间',
dataIndex: 'createTime',
sorter: (a, b) => moment(a.createTime).valueOf() - moment(b.createTime).valueOf(),
render: (text, record) => {
return moment(text).format('YYYY-MM-DD HH:mm:ss')
}
......@@ -51,7 +55,8 @@ const columns: ColumnsType<any> = [
title: '外部状态',
dataIndex: 'outerStatusName',
render: (text, record) => {
return <StatusTag type={SUPPLIER_OUTER_STATUS_COLOR[record.outerStatus]} title={text}></StatusTag>
const offset = record.outerStatus % SUPPLIER_OUTER_STATUS_COLOR.length;
return <StatusTag type={SUPPLIER_OUTER_STATUS_COLOR[offset] as "default"} title={text}></StatusTag>
}
},
]
......
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