Commit 70621511 authored by 许佳敏's avatar 许佳敏

Merge branch 'feat-material' into 'v2-220418'

feat: 添加物料审核流程规则配置0418 功能 See merge request linkseeks-design/pro-admin!13
parents 2f374a1f 89b26c9e
......@@ -58,13 +58,13 @@ export interface MellowCardProps extends CardProps {
}
const MellowCard: React.FC<MellowCardProps> = props => {
const { children, blockClassName, fullHeight, ...rest } = props;
const { children, blockClassName, fullHeight, style, ...rest } = props;
const cls = classNames({
'fullHeight': fullHeight,
});
return (
<Wrap className={cls}>
<Wrap className={cls} style={style}>
<Card bordered={false} {...rest}>
{children}
</Card>
......
import React, { useEffect, useState } from 'react';
import AnchorPage from '@/components/AnchorPage';
import NiceForm from '@/components/NiceForm';
import { createFormActions, FormPath } from '@formily/antd';
import { ArrayTable } from '@formily/antd-components'
import { addSchema } from './schemas/add';
import ProcessRadio from './components/processRadio';
import ApplicableMaterial from './components/applicableMaterials';
import SelectMaterial from './components/selectMaterial';
import { Button } from 'antd';
import {
getProductPlatformMaterialProcessBaseList,
getProductPlatformMaterialProcessGet,
getProductPlatformMaterialProcessMemberPage,
postProductPlatformMaterialProcessSave,
postProductPlatformMaterialProcessUpdate
} from '@/services/ProductV2Api';
import { useAsyncSelect } from '@/formSchema/effects/useAsyncSelect';
import { usePageStatus } from '@/hooks/usePageStatus';
const formActions = createFormActions();
type SubmitDataType = {
name?: string,
allMembers: number,
/** 基础物料流程id */
baseProcessId?: number,
members?: {
memberId: number,
roleId: number
}[],
}
/**
* 新增物料审核流程规则
*/
const anchorHeader = [
{
name: '流程规则',
key: 'config'
},
{
name: '流程选择',
key: 'process',
},
{
name: '适用会员',
key: 'apply'
}
]
const Add = () => {
const { id } = usePageStatus();
const { pathname } = location;
const lastKey = pathname.split("/").pop();
const isAdd = lastKey === 'add' && !id;
const isEdit = lastKey === 'edit' && id;
const isEditable = isAdd || isEdit;
const [initialValue, setInitialValue] = useState<null | SubmitDataType>(null);
const [loading, setLoading] = useState<boolean>(false);
useEffect(() => {
if (isAdd) {
setInitialValue({
allMembers: 1 as number,
})
return;
}
async function getInitialValue() {
const { data, code } = await getProductPlatformMaterialProcessGet({ processId: id as string });
if (code !== 1000) {
return;
}
const listData = await getProductPlatformMaterialProcessMemberPage({ processId: id as string });
console.log("listData", listData);
setInitialValue({
...data,
allMembers: data.allMembers ? 1 : 2,
members: listData.data?.map((_item) => {
return {
..._item,
primaryKey: `${_item.memberId}_${_item.roleId}`
}
}) || []
});
}
getInitialValue();
}, [])
const fetchProcess = async () => {
const { data, code } = await getProductPlatformMaterialProcessBaseList();
if (code === 1000) {
return data
}
return [];
}
const handleSubmit = async (values: SubmitDataType) => {
const defaultPostData = {
...values,
allMembers: values.allMembers === 1,
members: values.members?.map((_item) => {
return {
memberId: _item.memberId,
roleId: _item.roleId,
}
})
};
const postData = isAdd
? defaultPostData
: {
...defaultPostData,
id: id
}
setLoading(true)
const service = isAdd
? postProductPlatformMaterialProcessSave
: postProductPlatformMaterialProcessUpdate
const { data, code } = await service(postData as any);
setLoading(false)
if (code === 1000) {
history.back();
}
}
const renderTitle = () => {
if (isAdd) {
return '新增物料审核流程规则'
}
if (isEdit) {
return '编辑物料审核流程规则'
}
return '查看物料审核流程规则'
}
return (
<AnchorPage
title={renderTitle()}
anchors={anchorHeader}
extra={
isEditable && (
<Button
onClick={() => formActions.submit()}
loading={loading}
type="primary"
>
保存
</Button>
) || null
}
>
<NiceForm
onSubmit={handleSubmit}
schema={addSchema}
actions={formActions}
value={initialValue}
editable={isEditable}
components={{
ProcessRadio,
ApplicableMaterial,
ArrayTable,
SelectMaterial,
}}
effects={($, actions) => {
useAsyncSelect('baseProcessId', fetchProcess);
$('onFieldValueChange', 'baseProcessId').subscribe((fieldState) => {
actions.setFieldState('applyCard.selectMaterials', (state) => {
FormPath.setIn(state, 'props.x-component-props', {
processId: fieldState.value
})
})
})
}}
/>
</AnchorPage>
)
}
export default Add;
.container {
display: flex;
flex-direction: row;
.item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-right: 16px;
background: #F5F6F7;
border-radius: 4px;
padding: 8px 16px;
color: #5C626A;
cursor: pointer;
border: 1px solid transparent;
}
.active {
border: 1px solid #00A98F;
color: #00A98F;
}
}
\ No newline at end of file
import { Radio } from 'antd';
import React from 'react';
import styles from './index.less'
import className from 'classnames';
type EnumType = {
title: string,
id: string | number,
}
interface Iprops {
props: {
enum: EnumType[],
},
value: EnumType,
editable: boolean,
mutators: {
change: (id: number | string) => void
},
}
const ApplicableMaterial: React.FC<Iprops> & { isFieldComponent: boolean } = (props: Iprops) => {
const { value, editable, mutators } = props;
const options = props.props?.enum || [];
const handleChange = (_item: EnumType) => {
if (!editable) {
return;
}
mutators.change(_item.id)
}
return (
<div className={styles.container}>
{
options.map((_item) => {
const isChecked = _item.id === +value
return (
<div
key={_item.id}
className={
className(
styles.item,
{ [styles.active]: isChecked }
)
}
onClick={() => handleChange(_item)}
>
<Radio checked={isChecked} />
{_item.title}
</div>
)
})
}
</div>
)
}
ApplicableMaterial.isFieldComponent = true
export default ApplicableMaterial
\ No newline at end of file
.panel {
position: relative;
.more {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: -24px;
cursor: pointer;
}
}
.container {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-right: -16px;
.item {
width: 50%;
padding-right: 16px;
margin-bottom: 8px;
// border: 1px solid #00A98F;
.itemContainer {
background: #F5F6F7;
border: 1px solid transparent;
border-radius: 4px;
padding: 16px;
width: 100%;
display: flex;
flex-direction: row;
align-items: center;
cursor: pointer;
height: 100%;
}
.active {
border: 1px solid #00A98F;
}
.section {
flex: 1;
.info {
display: flex;
flex-direction: row;
justify-content: space-between;
}
}
.description {
margin-top: 8px;
color: #91959B;
font-size: 12px;
}
}
}
\ No newline at end of file
import StatusTag from '@/components/StatusTag';
import { Radio } from 'antd';
import React, { useState } from 'react';
import styles from './index.less';
import className from 'classnames'
import { CaretDownOutlined } from '@ant-design/icons';
type EnumType = {
baseProcessId: number,
processName: string,
processType: string | number,
processTypeName: string,
description: string,
}
interface Iprops {
props: {
enum: EnumType[],
},
value: number | string ,
editable: boolean,
mutators: {
change: (id: string | number) => void
},
}
/**
* 选择物料流程
* @returns
*/
const PAGE_SIZE = 6;
const ProcessRadio: React.FC<Iprops> & { isFieldComponent: boolean } = (props: Iprops) => {
const { value, editable, mutators } = props;
const [page, setPage] = useState<number>(1);
const options = props.props?.enum;
const dataSource = options.slice(0, page * PAGE_SIZE);
const hasMore = dataSource.length < options.length;
const onChange = (_item: EnumType) => {
if (!editable) {
return;
}
mutators.change(_item.baseProcessId)
}
const handleLoadMore = () => {
setPage(page + 1)
}
return (
<div className={styles.panel}>
<div className={styles.container}>
{
dataSource.map((_item) => {
return (
<div
className={styles.item}
key={_item.baseProcessId}
>
<div
className={
className(
styles.itemContainer,
{ [styles.active]: _item.baseProcessId === value }
)
}
key={_item.baseProcessId}
onClick={() => onChange(_item)}
>
{
editable && (
<Radio
checked={_item.baseProcessId === value}
/>
)
}
<div className={styles.section}>
<div className={styles.info}>
<span>{_item.processName}</span>
<StatusTag type={'primary'} title={_item.processTypeName} />
</div>
<span className={styles.description}>{_item.description}</span>
</div>
</div>
</div>
)
})
}
</div>
{
hasMore && (
<div className={styles.more} onClick={handleLoadMore}>
加载更多
<CaretDownOutlined />
</div>
) || null
}
</div>
)
}
ProcessRadio.isFieldComponent = true
export default ProcessRadio;
\ No newline at end of file
.plus {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
margin-bottom: 8px;
padding: 4px;
border: 1px solid #EDEEEF;
border-radius: 4px;
background-color: #EDEEEF;
color: #5C626A;
cursor: pointer;
}
\ No newline at end of file
import { FORM_FILTER_PATH } from '@/formSchema/const';
import { useStateFilterSearchLinkageEffect } from '@/formSchema/effects/useFilterSearch';
import { useToggle } from '@umijs/hooks';
import { message, Table } from 'antd';
import React from 'react';
import { schema } from './schema'
import styles from './index.less';
import { PlusOutlined } from '@ant-design/icons';
import { getProductMaterialProcessPageRelMaterial, getProductPlatformMaterialProcessMemberPage } from '@/services/ProductV2Api';
import TableModal from '@/components/TableModal';
import { getMemberManageAllProviderPage } from '@/services/MemberV2Api';
/**
* 选择指定物料
*/
type ValueType = {
num: string,
name: string,
}
interface Iprops {
value: ValueType[],
mutators: {
change: (data: any) => void,
}
props: {
'x-component-props': {
processId: number,
}
},
editable: boolean
}
const DEFAULT_RETURN_DATA = {
totalCount: 0,
data: []
}
const SelectMaterial: React.FC<Iprops> & { isFieldComponent: boolean} = (props: Iprops) => {
const { state: visible, toggle } = useToggle(false)
const { value, mutators, editable } = props
const xComponentProps = props['props']['x-component-props'];
const columns = [
{
title: '会员id',
dataIndex: 'memberId'
},
{
title: '会员名称',
dataIndex: 'name'
},
{
title: '会员类型',
dataIndex: 'memberTypeName'
},
{
title: '会员角色',
dataIndex: 'roleName',
},
{
title: '会员等级',
dataIndex: 'levelTag',
},
]
const handleOnOk = (selectRow, selectRowRecord) => {
const result = selectRowRecord.map((_item) => {
return {
..._item,
primaryKey: `${_item.memberId}_${_item.roleId}`
}
})
mutators.change(result)
toggle(false);
}
const handleFetchData = async (params) => {
const { data, code } = await getMemberManageAllProviderPage({...params});
if (code === 1000) {
return {
totalCount: data.totalCount,
data: data.data.map((_item) => {
return {
..._item,
primaryKey: `${_item.memberId}_${_item.roleId}`
}
})
}
}
return DEFAULT_RETURN_DATA as any
}
const handleOpen = () => {
toggle(true)
}
const handleDelete = (_row: { memberId: number, roleId: number }) => {
const temp = value.filter((_item) => !(_item.roleId === _row.roleId && _item.memberId === _row.memberId))
mutators.change(temp)
}
const withEdit = editable
? columns.concat([{
title: '操作',
render: (text, record) => {
return <a onClick={() => handleDelete(record)}>删除</a>
}
}] as any)
: columns
return (
<div>
{
editable && (
<div
className={styles.plus}
onClick={handleOpen}
>
<PlusOutlined />
添加
</div>
)
}
<Table
columns={withEdit}
dataSource={value}
rowKey={(_record) => `${_record.memberId}_${_record.roleId}`}
/>
<TableModal
modalType='Drawer'
visible={visible}
onClose={() => toggle(false)}
title={"选择会员"}
columns={columns}
schema={schema}
onOk={handleOnOk}
fetchData={handleFetchData}
tableProps={{
rowKey: 'primaryKey',
}}
customKey="primaryKey"
effects={($, actions) => {
useStateFilterSearchLinkageEffect($, actions, 'name', FORM_FILTER_PATH);
}}
mode={"checkbox"}
value={value}
/>
</div>
)
}
SelectMaterial.isFieldComponent = true
export default SelectMaterial
\ No newline at end of file
import { FORM_FILTER_PATH } from "@/formSchema/const";
import { ISchema } from "@formily/antd";
export const schema: ISchema = {
type: 'object',
properties: {
megaLayout: {
type: 'object',
'x-component': 'mega-layout',
properties: {
orderNo: {
type: 'string',
'x-component': 'Search',
'x-component-props': {
placeholder: '会员名称',
align: 'flex-left',
},
},
[FORM_FILTER_PATH]: {
type: 'object',
'x-component': 'mega-layout',
'x-component-props': {
inline:true,
full: true,
},
properties: {
name: {
type: 'string',
"x-component-props": {
placeholder: '物料名称'
}
},
type: {
type: 'string',
"x-component-props": {
placeholder: '物料规格'
}
},
group: {
type: 'string',
enum: [],
"x-component-props": {
placeholder: '物料组',
allowClear: true,
style: {
width: 150
}
}
},
category: {
type: 'string',
'x-component-props': {
placeholder: '品类'
}
},
brand: {
type: 'string',
'x-component-props': {
placeholder: '品牌'
}
},
submit: {
'x-component': 'Submit',
'x-mega-props': {
span: 1,
},
'x-component-props': {
children: '提交'
},
},
},
},
},
},
},
};
import React, { useEffect, useRef } from 'react';
import { PageHeaderWrapper } from '@ant-design/pro-layout';
import NiceForm from '@/components/NiceForm';
import { StandardTable } from '@linkseeks/god';
import { FORM_FILTER_PATH } from '@/formSchema/const';
import { useStateFilterSearchLinkageEffect } from '@/formSchema/effects/useFilterSearch';
import { querySchema } from './schemas/query';
import { Button, Card, Popconfirm, Space, Switch } from 'antd';
import { createFormActions } from '@formily/antd';
import {
getProductPlatformMaterialProcessDelete,
getProductPlatformMaterialProcessPage,
GetProductPlatformMaterialProcessPageResponseDetail,
postProductPlatformMaterialProcessStatusUpdate
} from '@/services/ProductV2Api';
import { Link } from 'umi';
/**
* 物料审核流程规则配置
*/
const formActions = createFormActions();
const CREATE_URL = '/system/ruleSettingManager/material/detail/add'
const MaterialAuditProcessConfig = () => {
const ref = useRef<any>({});
const handleEnableOrDisable = async (_row: GetProductPlatformMaterialProcessPageResponseDetail) => {
const { code } = await postProductPlatformMaterialProcessStatusUpdate({
processId: _row.processId,
status: _row.status ? 0 : 1
})
if (code === 1000) {
formActions.submit();
}
}
const handleDelete = async (_row: GetProductPlatformMaterialProcessPageResponseDetail) => {
const { code } = await getProductPlatformMaterialProcessDelete({
processId: `${_row.processId}`,
})
if (code === 1000) {
formActions.submit();
}
}
const columns = [
{
title: '流程规则ID',
dataIndex: 'processId',
render: (text, record) => {
return (
<Link to={`/system/ruleSettingManager/material/detail?id=${record.processId}`}>
{record.processId}
</Link>
)
}
},
{
title: '流程规则名称',
dataIndex: 'name',
},
{
title: '状态',
dataIndex: 'status',
render: (text, record) => {
return (
<Switch checked={!!text} onChange={() => handleEnableOrDisable(record)} />
)
}
},
{
title: '操作时间',
dataIndex: 'createTime',
},
{
title: '操作',
dataIndex: 'actions',
render: (text, record) => {
if (record.status === 1) {
return null;
}
return (
<Space>
<Link to={`/system/ruleSettingManager/material/edit?id=${record.processId}`}>
修改
</Link>
{
record.status !== 1 && (
<Popconfirm
title="确认删除?"
onConfirm={() => handleDelete(record)}
okText="是"
cancelText="否"
>
<a >删除</a>
</Popconfirm>
)
}
</Space>
)
}
}
]
const controllerBtns = () => {
return (
<Space>
<Link to={CREATE_URL}>
<Button
type="primary"
>
新增
</Button>
</Link>
</Space>
)
}
const handleSearch = (values: { name: string}) => {
ref.current.reload(values)
};
const fetchListData = async (params) => {
const { code, data } = await getProductPlatformMaterialProcessPage(params);
if (code === 1000) {
return data;
}
return {
totalCount: 0,
data: [],
}
}
return (
<PageHeaderWrapper
title={"物料审核流程规则配置"}
>
<Card>
<StandardTable
tableProps={{
rowKey: 'id',
}}
columns={columns}
currentRef={ref}
fetchTableData={fetchListData}
controlRender={
<NiceForm
components={{ controllerBtns }}
schema={querySchema}
actions={formActions}
onSubmit={handleSearch}
effects={($, actions) => {
useStateFilterSearchLinkageEffect($, actions, 'name', FORM_FILTER_PATH);
// useAsyncInitSelect(
// ['innerStatus', 'outerStatus'],
// fetchSelectOptions,
// );
}}
/>
}
/>
</Card>
</PageHeaderWrapper>
)
}
export default MaterialAuditProcessConfig
\ No newline at end of file
import { ISchema, Schema } from '@formily/antd'
/**
* 新增物料
*/
export const addSchema: ISchema = {
type: 'object',
properties: {
basic: {
type: 'object',
"x-component": 'MellowCardBox',
"x-component-props": {
id: 'basic',
title: '流程规则'
},
properties: {
layout: {
type: 'object',
"x-component": 'mega-layout',
"x-component-props": {
labelAlign: 'left',
labelCol: 4,
wrapperCol: 19,
grid: true,
autoRow: true,
columns: 2,
"responsive": {
"lg": 2,
"m": 1,
"s": 1
}
},
properties: {
name: {
title: '流程规则名称',
type: 'string',
'x-rules': [
{
required: true,
message: '请填写流程规则名称'
}
]
},
}
}
}
},
/** 根据品类动态获取schema */
processCard: {
type: 'object',
"x-component": 'MellowCardBox',
"x-component-props": {
id: 'process',
title: '流程选择',
style: {
"marginTop": '16px'
}
},
properties: {
baseProcessId: {
type: 'string',
enum: [],
'x-component': 'ProcessRadio'
}
}
},
applyCard: {
type: 'object',
"x-component": 'MellowCardBox',
"x-component-props": {
id: 'apply',
title: '适用会员',
style: {
"marginTop": '16px'
}
},
properties: {
allMembers: {
title: '',
type: 'string',
'x-component': 'ApplicableMaterial',
enum: [
{
title: '所有会员',
id: 1,
},
{
title: '特定会员',
id: 2,
},
] as any,
'x-linkages': [
{
type: 'value:visible',
target: 'members',
condition: '{{ $self.value === 2 }}'
},
{
type: 'value:schema',
target: 'members',
condition: `{{ $self.value === 2 }}`,
schema: {
"x-rules": [
{
required: true,
}
]
},
otherwise: {
"x-rules": [
{
required: false,
}
]
}
}
]
},
members: {
title: '',
type: 'string',
'x-component': 'SelectMaterial',
},
// materialGroups: {
// title: '',
// type: 'array',
// 'x-component': 'FormilyTransfer',
// 'x-component-props': {
// }
// }
}
}
},
}
import { getIntl } from 'umi';
import { ISchema } from '@formily/antd';
import { FORM_FILTER_PATH } from '@/formSchema/const';
const intl = getIntl();
export const querySchema: ISchema = {
type: 'object',
properties: {
megaLayout: {
type: 'object',
'x-component': 'mega-layout',
properties: {
topLayout: {
type: 'object',
'x-component': 'Mega-Layout',
'x-component-props': {
grid: true,
},
properties: {
ctl: {
type: 'object',
"x-component": 'controllerBtns'
},
name: {
type: 'string',
'x-component': 'Search',
'x-component-props': {
placeholder: '流程规则名称',
advanced: false
},
},
},
},
},
},
},
};
\ No newline at end of file
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