二手回收后台,师傅端,用户端
刘帅
2026-08-25 462f1f7bb18761e3d4184aee50f6a3b83fb4fb5c
加盟商余额扣除功能
2个文件已修改
775 ■■■■■ 已修改文件
management/src/pages/toJoin/deleteMoney.jsx 526 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/toJoin/service.js 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
management/src/pages/toJoin/deleteMoney.jsx
@@ -1,260 +1,324 @@
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>
  );
};
management/src/pages/toJoin/service.js
@@ -2,173 +2,186 @@
// 列表
export const getList = async (params) => {
    return request(`/franchisee/page`, {
        method: 'POST',
        data: params
    });
}
  return request(`/franchisee/page`, {
    method: 'POST',
    data: params,
  });
};
// 新增
export const add = async (data) => {
    return request('/franchisee/save', {
        method: 'POST',
        data,
    });
}
  return request('/franchisee/save', {
    method: 'POST',
    data,
  });
};
// 虚拟新增
export const virtualRecharge = async (data) => {
    return request('/franchisee/recent/money/virtualRecharge', {
        method: 'POST',
        data,
    });
}
  return request('/franchisee/recent/money/virtualRecharge', {
    method: 'POST',
    data,
  });
};
// 编辑
export const edit = async (data) => {
    return request('/franchisee/update', {
        method: 'POST',
        data,
    });
}
  return request('/franchisee/update', {
    method: 'POST',
    data,
  });
};
// 删除
export const del = async (params) => {
    return request(`/franchisee/batchDelete`, {
        method: 'GET',
        params
    });
}
  return request(`/franchisee/batchDelete`, {
    method: 'GET',
    params,
  });
};
// 详情
export const detail = async (params) => {
    return request(`/franchisee/detail`, {
        method: 'GET',
        params
    });
}
  return request(`/franchisee/detail`, {
    method: 'GET',
    params,
  });
};
// 省市
export const getCity = async () => {
    return request(`/region/addressTree`, {
        method: 'GET'
    });
}
  return request(`/region/addressTree`, {
    method: 'GET',
  });
};
// 获取角色
export const getRoleList = async () => {
    return request('/role/list', {
        method: 'GET',
    });
}
  return request('/role/list', {
    method: 'GET',
  });
};
  // 更改启用状态
  export const enable = async (params) => {
      return request('/franchisee/enable', {
          method: 'GET',
          params
      });
  }
  // 更改启用状态
  export const isDisplay = async (params) => {
    return request('/franchisee/setIsDisplay', {
        method: 'PUT',
        params
    });
}
// 更改启用状态
export const enable = async (params) => {
  return request('/franchisee/enable', {
    method: 'GET',
    params,
  });
};
// 更改启用状态
export const isDisplay = async (params) => {
  return request('/franchisee/setIsDisplay', {
    method: 'PUT',
    params,
  });
};
//站点
export const getSite = async (params) => {
    return request(`/site/list`, {
        method: 'GET',
        params
    });
}
  return request(`/site/list`, {
    method: 'GET',
    params,
  });
};
//加盟商列表余额
export const getMoney = async (params) => {
    return request(`/franchisee/recent/money`, {
        method: 'GET',
        params
    });
}
  return request(`/franchisee/recent/money`, {
    method: 'GET',
    params,
  });
};
///franchisee/recent/money/list扣款记录及充值记录
export const getMoneyList = async (data) => {
    return request(`/franchisee/recent/money/list`, {
        method: 'POST',
        data
    });
}
  return request(`/franchisee/recent/money/list`, {
    method: 'POST',
    data,
  });
};
//加盟商城市和站点
export const getinfo1 = async (params) => {
    return request(`/franchisee/info1`, {
        method: 'GET',
        params
    });
}
  return request(`/franchisee/info1`, {
    method: 'GET',
    params,
  });
};
//扣余额
export const setconsume = async (data) => {
    return request(`/franchisee/recent/money/consume`, {
        method: 'POST',
        data
    });
}
  return request(`/franchisee/recent/money/consume`, {
    method: 'POST',
    data,
  });
};
export const deductionTemplatePage = async (params) => {
  return request('/deductionTemplate/page', {
    method: 'GET',
    params,
  });
};
export const classifySelect = async (params) => {
  return request('/recoveryClassify/select', {
    method: 'GET',
    params,
  });
};
export const buy = async (params) => {
    return request(`/franchisee/code/buyPlatform`, {
        method: 'post',
        params
    });
}
  return request(`/franchisee/code/buyPlatform`, {
    method: 'post',
    params,
  });
};
//查询支付状态
export const getPayStatus = async (params) => {
    return request(`/franchisee/code/check`, {
        method: 'post',
        params
    });
}
  return request(`/franchisee/code/check`, {
    method: 'post',
    params,
  });
};
//申请列表
export const getApplayList = async (data) => {
    return request(`/franchiseeWithdraw/franchiseeWithdrawList`, {
        method: 'post',
        data
    });
}
  return request(`/franchiseeWithdraw/franchiseeWithdrawList`, {
    method: 'post',
    data,
  });
};
//审核提现申请
export const appovaApplay = async (data) => {
    return request(`/franchiseeWithdraw/auditFranchiseeWithdraw`, {
        method: 'post',
        data
    });
}
  return request(`/franchiseeWithdraw/auditFranchiseeWithdraw`, {
    method: 'post',
    data,
  });
};
// 获取二级城市筛选
export const getCityTree = async (data) => {
    return request(`/region/manageaddressTree`, {
        method: 'get',
        data
    });
}
  return request(`/region/manageaddressTree`, {
    method: 'get',
    data,
  });
};
//撤回
export const balanceWithdraw = async (data) => {
    return request(`/franchisee/balanceWithdraw`, {
        method: 'put',
        params:data
    });
}
  return request(`/franchisee/balanceWithdraw`, {
    method: 'put',
    params: data,
  });
};
export const getJoinSetting = async (params) => {
    return request(`/franchiseeSetting/detail`, {
        method: 'get',
        params,
    });
}
  return request(`/franchiseeSetting/detail`, {
    method: 'get',
    params,
  });
};
export const saveJoinSetting = async (data) => {
    return request(`/franchiseeSetting/save`, {
        method: 'post',
        data
    });
}
  return request(`/franchiseeSetting/save`, {
    method: 'post',
    data,
  });
};