/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import React, { useContext, useEffect, useMemo, useRef, useState } from 'react'; import { Button, Card, Form, Typography, Spin, Modal, Popover, Radio, } from '@douyinfe/semi-ui'; import { IconUserGroup, IconAlertTriangle, IconInfoCircle, IconTick, } from '@douyinfe/semi-icons'; import { useTranslation } from 'react-i18next'; import { API, showError, showSuccess, userIsDistributorUser } from '../helpers'; import { StatusContext } from '../context/Status'; import { UserContext } from '../context/User'; import DOMPurify from 'dompurify'; import DistributorApplyFileUpload from '../components/distributor/DistributorApplyFileUpload'; const { Text } = Typography; /** 代理申请:必填项标题(红 * + 加粗),与资格证书区块一致 */ function ApplyRequiredLabel({ children, className = '' }) { return ( * {children} ); } const applyTrimmedRequiredRule = (t) => ({ validator: (rule, value) => { const v = value == null ? '' : String(value).trim(); if (!v) return Promise.reject(t('必填')); return Promise.resolve(); }, }); const iconLg = 22; /** * 自研圆角提示条(与主卡片 `rounded-2xl` 一致,替代 Semi Banner 直角/风格差异问题) * @param {'danger' | 'info' | 'success'} variant */ function ApplyStatusNotice({ variant = 'info', title, className = '', children, }) { const tone = variant === 'danger' ? { frame: 'border-[var(--semi-color-danger-light-hover)] bg-[var(--semi-color-danger-light-default)]', icon: ( ), } : variant === 'success' ? { frame: 'border-[var(--semi-color-success-light-hover)] bg-[var(--semi-color-success-light-default)]', icon: ( ), } : { frame: 'border-[var(--semi-color-info-light-hover)] bg-[var(--semi-color-info-light-default)]', icon: ( ), }; return (
{tone.icon}
{title ? (
{title}
) : null} {children}
); } export default function DistributorApply() { const { t } = useTranslation(); const formApi = useRef(null); const [statusState] = useContext(StatusContext); const [userState] = useContext(UserContext); const [loading, setLoading] = useState(true); const [submitting, setSubmitting] = useState(false); const [app, setApp] = useState(null); const [urls, setUrls] = useState([]); const [previewUrl, setPreviewUrl] = useState(null); const [applyType, setApplyType] = useState(1); const csImage = ( statusState?.status?.distributor_apply_cs_image_url || '' ).trim(); const showCsColumn = Boolean(csImage); const applyIntroHtmlRaw = ( statusState?.status?.distributor_apply_intro_html || '' ).trim(); const applyIntroHtml = applyIntroHtmlRaw ? DOMPurify.sanitize(applyIntroHtmlRaw, { USE_PROFILES: { html: true } }) : ''; const isPdfUrl = (u) => /\.pdf(\?|$)/i.test(u || ''); const load = async () => { setLoading(true); try { const res = await API.get('/api/distributor/my_application'); const { success, message, data } = res.data; if (!success) { showError(message); return; } setApp(data || null); } catch { showError(t('加载失败')); } finally { setLoading(false); } }; useEffect(() => { load(); }, []); const isDist = useMemo(() => { const u = userState?.user; if (u) return userIsDistributorUser(u); try { const raw = localStorage.getItem('user'); if (raw) return userIsDistributorUser(JSON.parse(raw)); } catch { // ignore } return false; }, [userState?.user]); /** 申请记录为已通过,且当前账号仍是代理:显示「去代理分销」 */ const showApprovedForActiveDistributor = app?.status === 2 && isDist; /** 记录曾为已通过,但账号已非代理(如被降级):应允许重新提交 */ const showReapplyAfterRevoked = app?.status === 2 && !isDist; const shouldPrefillFormFromApp = showReapplyAfterRevoked || app?.status === 3; useEffect(() => { if (!shouldPrefillFormFromApp || !app || loading) return; const tid = window.setTimeout(() => { try { formApi.current?.setValues({ real_name: app.real_name || '', id_card_no: app.id_card_no || '', contact: app.contact || '', }); const at = Number(app.apply_type); setApplyType(at === 2 ? 2 : 1); const raw = app.qualification_urls; if (raw) { const j = typeof raw === 'string' ? JSON.parse(raw) : raw; if (Array.isArray(j) && j.length) setUrls(j.filter(Boolean)); } } catch { // ignore } }, 0); return () => window.clearTimeout(tid); }, [shouldPrefillFormFromApp, app, loading]); const onSubmit = async () => { const api = formApi.current; if (!api) return; try { await api.validate(); } catch { return; } const values = api.getValues(); if (!urls.length) { showError(t('请上传资格证书')); return; } setSubmitting(true); try { const res = await API.post('/api/distributor/application', { apply_type: applyType, real_name: values.real_name, id_card_no: values.id_card_no, contact: values.contact, qualification_urls: urls, }); const { success, message } = res.data; if (success) { showSuccess(t('提交成功')); await load(); } else { showError(message); } } catch { showError(t('提交失败')); } finally { setSubmitting(false); } }; if (isDist) { return (
{t('您已是代理,无需再次申请')}
); } const csPopoverContent = showCsColumn ? (
) : null; const rightBody = app?.status === 1 ? (
{t('您的申请正在审核中,请耐心等待。')}
) : showApprovedForActiveDistributor ? (
{t('申请已通过,请从侧栏进入「代理分销」。')}
) : ( {app?.status === 3 ? ( {app?.reject_reason ? (
{app.reject_reason}
) : (
{t('请根据管理员说明修改资料后重新提交。')}
)}
) : null} {showReapplyAfterRevoked ? ( {t( '您的代理资格已失效(例如已被管理员调整),可修改资料后重新提交审核。', )} ) : null}
(formApi.current = f)} layout='vertical'>
{t('申请类型')} { const v = val && typeof val === 'object' && 'target' in val ? val.target.value : val; setApplyType(Number(v)); }} > {t('个人申请')} {t('企业申请')}
{applyType === 2 ? t('企业名称') : t('姓名')} } rules={[applyTrimmedRequiredRule(t)]} /> {applyType === 2 ? t('统一社会信用代码') : t('身份证')} } rules={[applyTrimmedRequiredRule(t)]} /> {t('联系方式')}} rules={[applyTrimmedRequiredRule(t)]} />
); return (
{t('代理伙伴招募')}
{showCsColumn ? ( ) : null}
{applyIntroHtml ? (
) : null} {t( '请如实填写以下信息,审核通过后即可获得代理资格与邀请分成。', )}
{rightBody}
setPreviewUrl(null)} footer={null} width={Math.min( 960, typeof window !== 'undefined' ? window.innerWidth - 48 : 960, )} > {previewUrl && (isPdfUrl(previewUrl) ? (
) : (
))}
); }