Technology stack:

umi + antd + ts

First look at the effect:

1. Click the + symbol to pop up new product components

2. Add content in the pop-up box

File directory

File parsing

const.ts
Export const formLayout = {labelCol: {span: 6}, xs: {span: 24}}, wrapperCol: {// input control size sm: { span: 16 }, xs: { span: 24 } } }Copy the code

index.tsx

Implementation effect

import React, { useState } from 'react' import { Tooltip } from 'antd' import { PlusCircleOutlined } from '@ant-design/icons' import Import AddShopModal from './addShop' import AddShopModal from './addShop' export default () => {const [state, SetState] = useState({// control whether the item component is visible or editable False}) /** - Open/close the new item component popover - @param visible - @param isEdit -? : Optional -- optional */ const handleToggleAddShop = (visible: Boolean, isEdit? : boolean) => { // ... IsEditAddShop: isEdit, addShopVisible: visible overwrites the value of state setState({... state, isEditAddShop: isEdit, addShopVisible: <div> <Tooltip title=' new product '> <PlusCircleOutlined onClick={() => handleToggleAddShop(true, False)} /> </Tooltip> </div> <AddShopModal isEdit={state.iseditAddshop} // Pass the value of state.iseditAddshop to the newly added product OnClose ={() => handleToggleAddShop(false)} /> </>)}Copy the code
addShop.tsx

Step-by-step analysis

  • File to import

// Create new product component

import React, { useState, useEffect } from ‘react’ 

import { Button, Modal, Form, Input, Select, Upload, message } from ‘antd’ 

import { PlusCircleOutlined, DeleteOutlined, DownloadOutlined, UploadOutlined } from ‘@ant-design/icons’ 

import _ from ‘lodash’ 

import { generate } from ‘shortid’ 

import { Store } from ‘antd/lib/form/interface’ 

Import {formLayout} from ‘./const’ // To define the size and spacing of labels and input controls

import { ShopComponent } from ‘@/interfaces/shop-component’ 

Import {uploadAddShopFile} from ‘@/services/shop-component/addShop

Import {useCommit, useLoading, useModelState} from ‘@/models/shop-component/ addshop-model ‘import {useCommit, useLoading, useModelState} from ‘@/models/shop-component/ addshop-model

Note:

1. Put imported files/plug-ins on the top of the list

2. Internally imported files/plug-ins, etc., are placed under externally imported files/plug-ins (can be separated by one space).

Here is a reference to generate in Shortid, which is used to generate unique keys, so that when we click add parameter, we can continuously add unique items (with different keys), because in React, each item needs a unique key to distinguish different items

FormLayout is used here

The ShopComponent defines the data type name and contract for the Interface Interface

Export Declare Namespace ShopComponent {interface SParamsType {key? : string, name: string, anName: string, value: string, type: string } }Copy the code

Declare here declares the namespace namespace

  • Defines the type of data to be passed from the parent component

    interface InitProps { isEdit: boolean visible: boolean onClose: () => void }

void

  • Text fields and selector options

// Reference the text field component

Const {Option} = Select * from const {Option}

Product pop-up components

Step 1: Initialize the data and define state

Export default function AddShopModal(props: InitProps) {// props accepts data passed by the parent, const initParams: ShopComponent. SParamsType = {/ / define types And initialize ShopComponent key commodity type parameter: the generate (), / / a randomly generated unique id, as a key name: "', anName: '', value: '', type: } const {onClose, visible, isEdit} = props // const [form] = form.useForm () Uploading button to useState(false) for uploading state const [filePath, SetFilePath] = useState("); SetDataParams] = useState([initParams]) // Set item type - add parameter state}Copy the code

Step 2: Write the methods that the component needs to use

1. Close the current popover and clear the list

Function handleClose() {onClose() form.resetFields(); setFilePath("); SetUploading (false) setDataParams([initParams]) setDataParams([initParams]]) setDataParams([initParams]]) setDataParams([initParams]])Copy the code

2. Product type – Add Parameters – Click the add Parameters button, first deep copy (copy of the current array is not the same, different stack, independent), then push (add one or more elements to the end of the array), so as to form a new array

Function handleAddParams() {const newDataParams = _.clonedeep (dataParams) // Deep copy newdataparams.push (initParams) setDataParams(newDataParams) }Copy the code

3. Delete the item type – Add parameter, first deep copy the current array, through the filter, the current click on the array of items filtered out, so that the new array, and delete the selected items

function handleDeleteParams(key: string) { let newDataParams = _.cloneDeep(dataParams) newDataParams = _.filter(newDataParams, item => item.key ! == key) setDataParams(newDataParams) }Copy the code

4. Add or edit the product type – Add the content in the parameter. By traversing each item in the added parameter, the key of the current edited item is compared with the traversed key, if the same, it will be updated to the latest value

/** * @param field * @param value */ function handleChangeParams(key: string, field: string, value: string) { const newDataParams = _.map(dataParams, data => { if(data.key === key) { data[field] = value } return data }) setDataParams(newDataParams) }Copy the code

5. Verify the file before uploading

/** * @param file */ function handleUploadBefore(file: Zip const suffixFile = _. Last (_. Split (name,'.'))) // zip const suffixFile = _. _.isequal (suffixFile, 'zip')) {// check whether suffixFile isEqual to the set format. If not, message.error(' only upload.zip files are supported ') return false} return true}Copy the code

Uploaded files:

6. File upload method

Async function handleUpload(options) {// setUploading(true) Const {file} = options const {success, result}: SetUploading (false) = "await" uploadAddShopFile(file) = "await" Const {name} = file // file name if(! Success) return message. Error (' failed to upload file: ${name} ') const {path} = result // File path form.setfieldsValue ({// set the name of the file returned from the back end in the form filename: Message. success(' file uploaded successfully: ${name} ')}Copy the code

Render item type – Add parameter form content

/** * @param dataParams */ ShopComponent.SParamsType) { const { name, value, key, anName, Type} = dataParams return (<div key={key} > <Input placeholder=' name 'value={anName} onChange={e: React.changeevent) => {const val = _. Get (e, 'target.value', '') // Get the new value handleChangeParams(key, 'anName',) }} /> <Input =' name 'value={name} onChange={e: React.ChangeEvent) => { const val = _.get(e, 'target.value', '') handleChangeParams(key, }} /> <Input placeholder=' default 'value={value} onChange={e: React.ChangeEvent) => { const val = _.get(e, 'target.value', '') handleParamsChange(key, 'value', Val)}} /> <Input =' type 'value={type} hidden={true} /> {<DeleteOutlined onClick={() => handleDeleteParams(key)}></DeleteOutlined> } </div> ) }Copy the code

Note: Each item of the rendered item type – add parameter, requires a different key

8. Submission method

/ / function handleSubmit() {if(! FilePath) return message.error(' Please upload the file first! Form.validatefields ().then((values: Store): Void => {// form validates the value stored in the Store by antd-form void returns no value if(!! Const booValue = _. Some (dataParams, const booValue = _. Some (dataParams, const booValue = _. Item => _.values(item).includes(" ")) // Determine whether there is an empty value, Boolean if(booValue) {message.error(' please add parameters ') return} // When you create multiple parameters, If (dataparams.length! == 1) { const nameArr = _.map(dataParams, item => item.name); if(_.uniq(nameArr).length ! == namearr.length) {// If the name is duplicate, message.error(' parameter name must not be duplicate! ') return}}} const reqParams: ShopComponent. IAddShopParams = {/ / define the form requested data type, have already get the value name: values. The name, alias: values.alias, shopPath: values.shopPath, shopId: values.shopId, description: values.description, filename: values.name, filePath: filePath, params: dataParams } if (! _.isEmpty(externalDetails)) {// If it's an edit, the type of the item stays the same, Reqparams.uuid = externaldetails.uuid delete reqparams.filepath} commit('addOrUpdateExternal',[reqParams, HandleClose]) // Add or edit interfaces})}Copy the code

Page element display:

return ( <Modal title={`${isEdit ? 'Edit' : 'New '} Commodity component '} // Display the title by judging the Boolean value of isEdit. DestroyOnClose // Destroy the child element in Modal when closed width={600} visible={visible} // OnOk ={handleSubmit} onCancel={handleClose} confirmLoading={loading} onOk={handleSubmit} onCancel={handleClose} confirmLoading={loading} <Form Form ={Form} {... FormLayout}> // form control instance created by form.useForm (), < form. Item label=' required Name ='alias' rules={[{required: true, message: 'Please enter the name of the component'}, {Max: 50, message: }]} > <Input placeholder=' /> </ form.item > < form.item label=' Required name='name' Rules ={[{required: true, message: 'Please enter the product component id'}, {Max: 50, message: 'The product component id cannot exceed 50 characters!'}, {// Validation rule pattern: New RegExp('[^ a-za-z1-9 \-]', 'I '), // regular expression message:' Product component identifier must contain no more than 50 characters! ', validator: (rule, value, callback) => { if (rule.pattern.test(value)) { callback(rule.message as string) } callback() } } ]} > <Input Placeholder =' /> </ form.item > < form.item label=' Required name='shopPath' rules={[{required: true, message: }]} > <Input placeholder=' /> </ form.item > < form.item label=' required name='shopId' rules={[ { required: true, message: }]} > <Select placeholder=' allowClear > {_.map(imageList, placeholder) item => <Option key={item.uuid} value={item.uuid}>{item.alias}</Option>)} </Select> </Form.Item> <div> <Button Type ="ghost" shape="round" icon={<PlusCircleOutlined />} onClick={handleAddParams}> Add parameter </Button> <div> <div> Another name < span > < / span > < span > name < / span > < span > default < / span > < / div > {_. The map (dataParams, Item => renderAddParamsForm(item))} </div> </div> < form. item label=' description' Required Name ='description' rules= {[{ required: true, message: }]} > <TextArea rows={5} placeholder=' placeholder '/> </ form. Item> < form. Item label=' filename' required name='filename' rules= {[ { required: true, message: 'Please enter file name'}]} > <Input disabled /> </ form.item > <div> <Upload Accept ='.zip' beforeUpload={file => HandleUploadBefore (file)} customRequest={handleUpload} showUploadList={false} Disabled ={isEdit} // Upload while editing > <Button Type ='primary' icon={<UploadOutlined/>} loading={uploading} disabled={isEdit} > Select Upload files </Button> </Upload> <Button OnClick ={() => window.open("#")} Direct download example file type='primary' icon={<DownloadOutlined/>} > download example file </Button> </div> </Form> </Modal>)Copy the code