| | |
| | | import { Form, Input, Modal, Select, Upload, Button, Image, message, InputNumber } from 'antd'; |
| | | import { allAreaList } from '@/utils/area'; |
| | | import { Cascader, DatePicker, Form, Input, InputNumber, message, Modal, Select } from 'antd'; |
| | | import moment from 'moment'; |
| | | import { forwardRef, useEffect, useImperativeHandle, useState } from 'react'; |
| | | import BraftEditor from '@/components/BraftEditor'; |
| | | import { customRequest } from '@/utils/utils'; |
| | | import { LoadingOutlined, PlusOutlined } from '@ant-design/icons'; |
| | | import { getinfo1 } from './service.js' |
| | | import { classifySelect, deductionTemplatePage, getSite } from './service'; |
| | | |
| | | const formItemLayout = { |
| | | labelCol: { span: 4 }, |
| | | wrapperCol: { span: 12 }, |
| | | }; |
| | | const regionOptions = (allAreaList || []).map((province) => ({ |
| | | label: province.province, |
| | | value: province.value, |
| | | children: (province.data || []).map((city) => ({ |
| | | label: city.city, |
| | | value: city.value, |
| | | children: (city.dataList || []).map((area) => ({ |
| | | label: area.area, |
| | | value: area.value, |
| | | })), |
| | | })), |
| | | })); |
| | | |
| | | const AddEditView = ({ visible, onSave, onUpdate, onCancel, }, ref) => { |
| | | const regionPathMap = (allAreaList || []).reduce((map, province) => { |
| | | (province.data || []).forEach((city) => { |
| | | (city.dataList || []).forEach((area) => { |
| | | map[area.value] = [province.value, city.value, area.value]; |
| | | }); |
| | | }); |
| | | return map; |
| | | }, {}); |
| | | |
| | | const isSuccess = (res) => String(res?.code) === '200' || res?.code === 200; |
| | | |
| | | const AddEditView = ({ visible, onSave, onCancel }, ref) => { |
| | | const [form] = Form.useForm(); |
| | | const [editData, setEditData] = useState({}); |
| | | const [loading, setLoading] = useState(false); |
| | | const [caverImg, setcaverImg] = useState([]) |
| | | const [cityList, setCityList] = useState([]) |
| | | const [classifyList, setClassifyList] = useState([]) |
| | | const [submitting, setSubmitting] = useState(false) |
| | | const [siteOptions, setSiteOptions] = useState([]); |
| | | const [classifyOptions, setClassifyOptions] = useState([]); |
| | | const [submitting, setSubmitting] = useState(false); |
| | | const [priceLoading, setPriceLoading] = useState(false); |
| | | |
| | | useEffect(() => { |
| | | getSite().then((res) => { |
| | | const data = Array.isArray(res?.data) ? res.data : []; |
| | | setSiteOptions( |
| | | data |
| | | .filter((item) => item.siteName) |
| | | .map((item) => ({ label: item.siteName, value: item.siteName, siteId: item.id })), |
| | | ); |
| | | }); |
| | | classifySelect().then((res) => { |
| | | const data = Array.isArray(res?.data) ? res.data : []; |
| | | setClassifyOptions( |
| | | data |
| | | .map((item) => ({ |
| | | label: item.subClassify || item.classifyName || item.name, |
| | | value: item.id, |
| | | })) |
| | | .filter((item) => item.label && item.value !== undefined && item.value !== null), |
| | | ); |
| | | }); |
| | | }, []); |
| | | |
| | | const uploadButton = (text) => { |
| | | return <div> |
| | | <PlusOutlined /> |
| | | <div |
| | | style={{ |
| | | marginTop: 8, |
| | | }} |
| | | > |
| | | {text} |
| | | </div> |
| | | </div> |
| | | }; |
| | | |
| | | |
| | | // 上传安卓用户端前 |
| | | const beforeUploadAndroidUser = (file) => { |
| | | return new Promise(async (resolve, reject) => { |
| | | if (file.name.includes(',')) { |
| | | message.warning('上传文件不能包含英文逗号(,)') |
| | | return Upload.LIST_IGNORE |
| | | } |
| | | setLoading(true) |
| | | resolve(file) |
| | | const updateTotal = (costPrice, infoFee) => { |
| | | const cost = Number(costPrice); |
| | | const fee = Number(infoFee); |
| | | form.setFieldsValue({ |
| | | amount: |
| | | Number.isFinite(cost) && Number.isFinite(fee) ? Number((cost + fee).toFixed(2)) : undefined, |
| | | }); |
| | | }; |
| | | /** |
| | | * 确定按钮事件 |
| | | */ |
| | | const okHandle = async () => { |
| | | if (submitting) { |
| | | return |
| | | |
| | | const loadDeductionPrice = async (values = form.getFieldsValue()) => { |
| | | if (!values.cityName || !values.siteName || !values.classifyName) { |
| | | form.setFieldsValue({ costPrice: undefined, infoFee: undefined, amount: undefined }); |
| | | return; |
| | | } |
| | | setSubmitting(true) |
| | | setPriceLoading(true); |
| | | try { |
| | | const values = await form.validateFields() |
| | | if (caverImg.length == 0) { |
| | | message.error('请先上传图片凭证') |
| | | setSubmitting(false) |
| | | return |
| | | const res = await deductionTemplatePage({ |
| | | cityName: values.cityName, |
| | | siteName: values.siteName, |
| | | classifyName: values.classifyName, |
| | | pageNum: 1, |
| | | pageSize: 1, |
| | | }); |
| | | const template = res?.data?.records?.[0]; |
| | | if (!isSuccess(res) || !template) { |
| | | form.setFieldsValue({ costPrice: undefined, infoFee: undefined, amount: undefined }); |
| | | message.warning(res?.msg || '未找到匹配的扣费模板'); |
| | | return; |
| | | } |
| | | values.pics = caverImg.length > 0 ? caverImg.map(item => item.response).join(',') : null |
| | | values.franchiseeId = editData.id |
| | | values.type = 1 |
| | | // if (editData.id) { //详情 |
| | | // values.id = editData.id; |
| | | // onUpdate(values); |
| | | // } else { //添加 |
| | | // if (caverImg.length == 0) { |
| | | // message.error('请先上传版本文件') |
| | | // return |
| | | // } |
| | | // if (shareImg.length == 0) { |
| | | // message.error('请先上传下载二维码') |
| | | // return |
| | | // } |
| | | // // console.log('caverImg',caverImg) |
| | | // values.androidUrl = caverImg[0].response |
| | | // values.code = shareImg[0].url |
| | | // values.remark = info.content |
| | | console.log('2222222222', values, 'caverImg caverImg', caverImg) |
| | | await onSave(values); |
| | | // } |
| | | form.setFieldsValue({ costPrice: template.costPrice, infoFee: template.infoFee }); |
| | | updateTotal(template.costPrice, template.infoFee); |
| | | } finally { |
| | | setSubmitting(false) |
| | | setPriceLoading(false); |
| | | } |
| | | }; |
| | | |
| | | useImperativeHandle(ref, () => { |
| | | return { |
| | | refreshData: (data) => { |
| | | if (data.modalType == 'edit') { |
| | | getinfo1({ id: data.id }).then(res => { |
| | | setCityList(res.data.list) |
| | | setClassifyList(res.data.list1) |
| | | }) |
| | | } |
| | | form.resetFields(); |
| | | form.setFieldsValue(data); |
| | | setEditData(data); |
| | | setSubmitting(false) |
| | | }, |
| | | clean: () => { |
| | | form.resetFields(); |
| | | setcaverImg([]) |
| | | setSubmitting(false) |
| | | }, |
| | | }; |
| | | }); |
| | | const handleValuesChange = (_, values) => { |
| | | updateTotal(values.costPrice, values.infoFee); |
| | | }; |
| | | |
| | | const okHandle = async () => { |
| | | if (submitting) { |
| | | return; |
| | | } |
| | | setSubmitting(true); |
| | | try { |
| | | const values = await form.validateFields(); |
| | | const payload = { |
| | | ...values, |
| | | franchiseeId: editData.id, |
| | | type: 1, |
| | | completeTime: values.completeTime?.format('YYYY-MM-DD HH:mm:ss'), |
| | | regionId: values.regionPath?.[2], |
| | | amount: values.amount, |
| | | }; |
| | | delete payload.regionPath; |
| | | await onSave(payload); |
| | | } finally { |
| | | setSubmitting(false); |
| | | } |
| | | }; |
| | | |
| | | useImperativeHandle(ref, () => ({ |
| | | refreshData: (data) => { |
| | | form.resetFields(); |
| | | setEditData(data); |
| | | setSubmitting(false); |
| | | if (data.modalType === 'detail') { |
| | | form.setFieldsValue({ |
| | | ...data, |
| | | regionPath: regionPathMap[data.regionId], |
| | | completeTime: data.completeTime ? moment(data.completeTime) : undefined, |
| | | }); |
| | | } |
| | | }, |
| | | clean: () => { |
| | | form.resetFields(); |
| | | setSubmitting(false); |
| | | }, |
| | | })); |
| | | |
| | | const detailMode = editData.modalType === 'detail'; |
| | | |
| | | return ( |
| | | <Modal |
| | | getContainer={false} |
| | | width="45%" |
| | | width={760} |
| | | destroyOnClose |
| | | title={editData.modalType == 'detail' ? '明细详情' : '余额扣除'} |
| | | title={detailMode ? '扣款明细' : '余额扣除'} |
| | | open={visible} |
| | | onCancel={() => onCancel(false)} |
| | | onOk={okHandle} |
| | | footer={(_, { OkBtn, CancelBtn }) => ( |
| | | <div style={{ display: 'flex', justifyContent: 'flex-end', marginTop: 20 }}> |
| | | <Button style={{ marginRight: '20px' }} onClick={() => onCancel(false)}> |
| | | 关闭 |
| | | </Button> |
| | | { |
| | | editData.modalType && editData.modalType != 'detail' && ( |
| | | <Button type="primary" loading={submitting} disabled={submitting} onClick={() => okHandle()}> |
| | | 确认 |
| | | </Button> |
| | | )} |
| | | </div> |
| | | )} |
| | | confirmLoading={submitting || priceLoading} |
| | | okButtonProps={{ hidden: detailMode }} |
| | | > |
| | | { |
| | | editData.modalType == 'detail' ? ( |
| | | <div style={{ marginLeft: '100px' }}> |
| | | <div style={{ display: 'flex', alignItems: 'center', marginBottom: '20px' }}> |
| | | <div style={{ marginRight: '20px', minWidth: '80px' }}>类型</div> |
| | | <div>{editData.type == 1 ? '扣除旧机款' : '充值'}</div> |
| | | </div> |
| | | <div style={{ display: 'flex', alignItems: 'center', marginBottom: '20px' }}> |
| | | <div style={{ marginRight: '20px', minWidth: '80px' }}>金额(元)</div> |
| | | <div>{editData.amount }</div> |
| | | </div> |
| | | <div style={{ display: 'flex', alignItems: 'center', marginBottom: '20px' }}> |
| | | <div style={{ marginRight: '20px', minWidth: '80px' }}>时间</div> |
| | | <div>{editData.createTime.replace(/T/g, ' ')}</div> |
| | | </div> |
| | | {editData.type == 2 && ( |
| | | <> |
| | | <div style={{ display: 'flex', alignItems: 'center', marginBottom: '20px' }}> |
| | | <div style={{ marginRight: '20px', minWidth: '80px' }}>流水单号</div> |
| | | <div>{editData.code}</div> |
| | | </div> |
| | | <div style={{ display: 'flex', alignItems: 'center', marginBottom: '20px' }}> |
| | | <div style={{ marginRight: '20px', minWidth: '80px' }}>支付方式</div> |
| | | <div>{editData.remark || editData.payStr || '拉卡拉支付'}</div> |
| | | </div> |
| | | </> |
| | | )} |
| | | {editData.type == 1 && ( |
| | | <> |
| | | <div style={{ display: 'flex', alignItems: 'center', marginBottom: '20px' }}> |
| | | <div style={{ marginRight: '20px', minWidth: '80px' }}>备注</div> |
| | | <div>{editData.remark}</div> |
| | | </div> |
| | | <div style={{ display: 'flex', alignItems: 'center', marginBottom: '20px' }}> |
| | | <div style={{ marginRight: '20px', minWidth: '80px' }}>凭证</div> |
| | | <div style={{ display: 'flex', flexWrap: 'wrap' }} > |
| | | { |
| | | editData.pics && editData.pics.split(',').map((item, index) => ( |
| | | <div style={{ marginRight: '10px' }}> |
| | | <Image |
| | | key={index} |
| | | width={50} |
| | | src={item} |
| | | /> |
| | | </div> |
| | | )) |
| | | } |
| | | </div> |
| | | </div> |
| | | </> |
| | | )} |
| | | |
| | | |
| | | </div> |
| | | ) : ( |
| | | <Form layout="horizontal" {...formItemLayout} form={form}> |
| | | <Form.Item |
| | | name="amount" |
| | | label="扣除金额" |
| | | rules={[{ required: true, message: '请输入扣除金额' }]} |
| | | > |
| | | <InputNumber min={0} decimalSeparator="," |
| | | precision={2} placeholder="请输入" style={{ width: '280px' }} /> |
| | | </Form.Item> |
| | | <Form.Item |
| | | label="关联城市" |
| | | name="regionId" |
| | | rules={[{ required: true, message: '请选择关联城市' }]} |
| | | > |
| | | <Select style={{ width: '280px' }} options={cityList} fieldNames={{ label: 'name', value: 'id' }} placeholder="请选择" /> |
| | | |
| | | </Form.Item> |
| | | <Form.Item |
| | | label="站点名称" |
| | | name="siteId" |
| | | wrapperCol={{ |
| | | span: 6, |
| | | <Form form={form} layout="vertical" disabled={detailMode} onValuesChange={handleValuesChange}> |
| | | <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', columnGap: 20 }}> |
| | | <Form.Item |
| | | name="orderNo" |
| | | label="订单号" |
| | | rules={[{ required: true, message: '请输入订单号' }]} |
| | | > |
| | | <Input placeholder="请输入" /> |
| | | </Form.Item> |
| | | <Form.Item |
| | | name="workOrderStatus" |
| | | label="服务工单状态" |
| | | rules={[{ required: true, message: '请输入服务工单状态' }]} |
| | | > |
| | | <Input placeholder="请输入" /> |
| | | </Form.Item> |
| | | <Form.Item |
| | | name="userName" |
| | | label="用户姓名" |
| | | rules={[{ required: true, message: '请输入用户姓名' }]} |
| | | > |
| | | <Input placeholder="请输入" /> |
| | | </Form.Item> |
| | | <Form.Item |
| | | name="userAddress" |
| | | label="用户地址" |
| | | rules={[{ required: true, message: '请输入用户地址' }]} |
| | | > |
| | | <Input placeholder="请输入" /> |
| | | </Form.Item> |
| | | <Form.Item |
| | | name="regionPath" |
| | | label="区域" |
| | | rules={[{ required: true, message: '请选择区域' }]} |
| | | > |
| | | <Cascader |
| | | options={regionOptions} |
| | | placeholder="请选择省市县区" |
| | | showSearch |
| | | onChange={(value, selectedOptions = []) => { |
| | | form.setFieldsValue({ |
| | | provinceCode: value?.[0], |
| | | cityCode: value?.[1], |
| | | regionCode: value?.[2], |
| | | provinceName: selectedOptions[0]?.label || '', |
| | | cityName: selectedOptions[1]?.label || '', |
| | | regionName: selectedOptions[2]?.label || '', |
| | | }); |
| | | loadDeductionPrice({ |
| | | ...form.getFieldsValue(), |
| | | cityName: selectedOptions[1]?.label || '', |
| | | }); |
| | | }} |
| | | rules={[{ required: true, message: '请选择站点名称' }]} |
| | | > |
| | | <Select style={{ width: '280px' }} options={classifyList} fieldNames={{ label: 'siteName', value: 'id' }} placeholder="请选择" /> |
| | | |
| | | </Form.Item> |
| | | |
| | | |
| | | <Form.Item |
| | | name="remark" |
| | | label="备注" |
| | | > |
| | | <Input.TextArea placeholder="请输入" style={{ width: '280px' }} /> |
| | | </Form.Item> |
| | | <Form.Item |
| | | name="pics" |
| | | label="图片凭证" |
| | | rules={[{ required: true, message: '请上传图片凭证' }]} |
| | | > |
| | | <Upload |
| | | listType="picture-card" |
| | | maxCount={9} |
| | | beforeUpload={beforeUploadAndroidUser} |
| | | // onChange={(e) => handleChange(e, 1)} |
| | | showUploadList={{ |
| | | showPreviewIcon: false, |
| | | }} |
| | | customRequest={customRequest} |
| | | accept="image/png, image/jpeg, image/jpg" |
| | | fileList={caverImg} |
| | | onChange={({ fileList: newFileList }) => { |
| | | |
| | | console.log('newFileList newFileList', newFileList); |
| | | |
| | | return setcaverImg(newFileList); |
| | | }} |
| | | > |
| | | |
| | | {caverImg.length < 9 && uploadButton('上传图片')} |
| | | </Upload> |
| | | </Form.Item> |
| | | </Form> |
| | | ) |
| | | } |
| | | |
| | | </Modal > |
| | | /> |
| | | </Form.Item> |
| | | <Form.Item |
| | | name="siteName" |
| | | label="站点名称" |
| | | rules={[{ required: true, message: '请选择站点名称' }]} |
| | | > |
| | | <Select |
| | | options={siteOptions} |
| | | placeholder="请选择" |
| | | showSearch |
| | | optionFilterProp="label" |
| | | onChange={(_, option) => { |
| | | form.setFieldsValue({ siteId: option?.siteId }); |
| | | loadDeductionPrice({ ...form.getFieldsValue(), siteName: option?.value || '' }); |
| | | }} |
| | | /> |
| | | </Form.Item> |
| | | <Form.Item name="classifyName" hidden> |
| | | <Input /> |
| | | </Form.Item> |
| | | <Form.Item |
| | | name="classifyId" |
| | | label="品类" |
| | | rules={[{ required: true, message: '请选择品类' }]} |
| | | > |
| | | <Select |
| | | options={classifyOptions} |
| | | placeholder="请选择" |
| | | showSearch |
| | | optionFilterProp="label" |
| | | onChange={(_, option) => { |
| | | form.setFieldsValue({ classifyName: option?.label || '' }); |
| | | loadDeductionPrice({ |
| | | ...form.getFieldsValue(), |
| | | classifyId: option?.value, |
| | | classifyName: option?.label || '', |
| | | }); |
| | | }} |
| | | /> |
| | | </Form.Item> |
| | | <Form.Item |
| | | name="workerName" |
| | | label="师傅" |
| | | rules={[{ required: true, message: '请输入师傅名称' }]} |
| | | > |
| | | <Input placeholder="请输入" /> |
| | | </Form.Item> |
| | | <Form.Item |
| | | name="completeTime" |
| | | label="完成时间" |
| | | rules={[{ required: true, message: '请选择完成时间' }]} |
| | | > |
| | | <DatePicker showTime format="YYYY-MM-DD HH:mm:ss" style={{ width: '100%' }} /> |
| | | </Form.Item> |
| | | <Form.Item |
| | | name="costPrice" |
| | | label="代收代付旧机款" |
| | | rules={[{ required: true, message: '请输入旧机款' }]} |
| | | > |
| | | <InputNumber |
| | | min={0} |
| | | precision={2} |
| | | placeholder="请选择城市、站点和品类后自动带出" |
| | | style={{ width: '100%' }} |
| | | /> |
| | | </Form.Item> |
| | | <Form.Item |
| | | name="infoFee" |
| | | label="信息费用" |
| | | rules={[{ required: true, message: '请输入信息费用' }]} |
| | | > |
| | | <InputNumber |
| | | min={0} |
| | | precision={2} |
| | | placeholder="请选择城市、站点和品类后自动带出" |
| | | style={{ width: '100%' }} |
| | | /> |
| | | </Form.Item> |
| | | <Form.Item |
| | | name="amount" |
| | | label="合计扣款金额" |
| | | rules={[{ required: true, message: '请填写费用' }]} |
| | | > |
| | | <InputNumber disabled precision={2} addonAfter="元" style={{ width: '100%' }} /> |
| | | </Form.Item> |
| | | </div> |
| | | <Form.Item name="remark" label="备注"> |
| | | <Input.TextArea placeholder="请输入" maxLength={500} showCount /> |
| | | </Form.Item> |
| | | <Form.Item name="cityCode" hidden> |
| | | <Input /> |
| | | </Form.Item> |
| | | <Form.Item name="cityName" hidden> |
| | | <Input /> |
| | | </Form.Item> |
| | | <Form.Item name="siteId" hidden> |
| | | <Input /> |
| | | </Form.Item> |
| | | <Form.Item name="provinceCode" hidden> |
| | | <Input /> |
| | | </Form.Item> |
| | | <Form.Item name="provinceName" hidden> |
| | | <Input /> |
| | | </Form.Item> |
| | | <Form.Item name="regionCode" hidden> |
| | | <Input /> |
| | | </Form.Item> |
| | | <Form.Item name="regionName" hidden> |
| | | <Input /> |
| | | </Form.Item> |
| | | </Form> |
| | | </Modal> |
| | | ); |
| | | }; |
| | | |