分销提交

This commit is contained in:
wong
2025-12-18 16:23:01 +08:00
parent 66c8623138
commit 2f00ee176b
3 changed files with 334 additions and 14 deletions

View File

@@ -498,6 +498,166 @@
color: #fa8c16;
}
// 渠道登录入口卡片
.loginEntryCard {
margin: 16px 16px 8px;
padding: 12px 16px;
border-radius: 12px;
background: #f7f9fc;
border: 1px solid #e5e6eb;
display: flex;
flex-direction: column;
gap: 8px;
}
.loginEntryHeader {
display: flex;
align-items: center;
justify-content: space-between;
}
.loginEntryTitle {
display: flex;
align-items: center;
gap: 6px;
}
.loginEntryDot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #1890ff;
}
.loginEntryText {
font-size: 14px;
font-weight: 600;
color: #1f2933;
}
.loginEntryTypeTabs {
display: flex;
gap: 4px;
background: #edf2ff;
padding: 2px;
border-radius: 999px;
}
.loginEntryTypeTab {
border: none;
outline: none;
padding: 4px 10px;
border-radius: 999px;
font-size: 12px;
background: transparent;
color: #4b5563;
cursor: pointer;
&.active {
background: #ffffff;
color: #1890ff;
box-shadow: 0 1px 4px rgba(24, 144, 255, 0.2);
}
}
.loginEntryContent {
display: flex;
align-items: center;
justify-content: space-between;
margin-top: 4px;
}
.loginEntryDesc {
font-size: 12px;
color: #6b7280;
max-width: 70%;
}
// 登录二维码弹窗
.loginQrDialog {
padding: 16px 16px 24px;
background: #fff;
border-top-left-radius: 16px;
border-top-right-radius: 16px;
}
.loginQrHeader {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
h3 {
margin: 0;
font-size: 16px;
font-weight: 600;
}
}
.loginQrTypeSelector {
display: flex;
justify-content: center;
gap: 8px;
margin-bottom: 16px;
}
.loginQrContent {
display: flex;
flex-direction: column;
align-items: center;
gap: 16px;
}
.loginQrLoading {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 32px 0;
font-size: 14px;
color: #6b7280;
}
.loginQrImage {
width: 200px;
height: 200px;
border-radius: 12px;
border: 1px solid #e5e6eb;
padding: 8px;
background: #fff;
}
.loginQrLinkSection {
width: 100%;
margin-top: 8px;
}
.loginQrLinkLabel {
font-size: 13px;
color: #4b5563;
margin-bottom: 6px;
}
.loginQrLinkWrapper {
display: flex;
align-items: center;
gap: 8px;
}
.loginQrLinkInput {
font-size: 12px;
}
.loginQrError {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
padding: 24px 0;
font-size: 14px;
color: #9ca3af;
}
// 加载和空状态
.loading {
text-align: center;

View File

@@ -9,7 +9,7 @@ import {
Select,
Modal,
} from "antd";
import { Tabs, DatePicker, InfiniteScroll, SpinLoading } from "antd-mobile";
import { Tabs, DatePicker, InfiniteScroll, SpinLoading, Popup } from "antd-mobile";
import NavCommon from "@/components/NavCommon";
const { TextArea } = Input;
@@ -70,6 +70,7 @@ import {
fetchWithdrawalList,
reviewWithdrawal,
markAsPaid,
generateLoginQRCode,
} from "./api";
import type {
Channel,
@@ -130,6 +131,16 @@ const DistributionManagement: React.FC = () => {
const [showDatePicker, setShowDatePicker] = useState(false);
const [withdrawalKeyword, setWithdrawalKeyword] = useState("");
// 渠道登录入口二维码相关状态
const [loginQrVisible, setLoginQrVisible] = useState(false);
const [loginQrType, setLoginQrType] = useState<"h5" | "miniprogram">("h5");
const [loginQrLoading, setLoginQrLoading] = useState(false);
const [loginQrData, setLoginQrData] = useState<{
qrCode: string;
url: string;
type: "h5" | "miniprogram";
} | null>(null);
useEffect(() => {
loadData();
}, []);
@@ -164,6 +175,35 @@ const DistributionManagement: React.FC = () => {
}
};
// 生成渠道登录二维码(每次调用都重新请求)
const handleGenerateLoginQRCode = async (type: "h5" | "miniprogram") => {
setLoginQrLoading(true);
try {
const res = await generateLoginQRCode(type);
setLoginQrData(res);
} catch (e: any) {
const errorMsg = e?.message || e?.msg || "生成登录二维码失败";
message.error(errorMsg);
setLoginQrData(null);
} finally {
setLoginQrLoading(false);
}
};
// 打开登录入口弹窗(默认用当前类型重新请求一次)
const handleOpenLoginQrDialog = async () => {
setLoginQrVisible(true);
setLoginQrData(null);
await handleGenerateLoginQRCode(loginQrType);
};
// 切换登录二维码类型H5 / 小程序),每次都重新请求接口生成
const handleLoginQrTypeChange = async (type: "h5" | "miniprogram") => {
setLoginQrType(type);
setLoginQrData(null);
await handleGenerateLoginQRCode(type);
};
const loadChannelList = async () => {
setLoading(true);
try {
@@ -551,6 +591,46 @@ const DistributionManagement: React.FC = () => {
</div>
</div>
{/* 渠道登录入口 */}
<div className={styles.loginEntryCard}>
<div className={styles.loginEntryHeader}>
<div className={styles.loginEntryTitle}>
<span className={styles.loginEntryDot}></span>
<span className={styles.loginEntryText}></span>
</div>
<div className={styles.loginEntryTypeTabs}>
<button
className={`${styles.loginEntryTypeTab} ${
loginQrType === "h5" ? styles.active : ""
}`}
onClick={() => handleLoginQrTypeChange("h5")}
>
H5
</button>
<button
className={`${styles.loginEntryTypeTab} ${
loginQrType === "miniprogram" ? styles.active : ""
}`}
onClick={() => handleLoginQrTypeChange("miniprogram")}
>
</button>
</div>
</div>
<div className={styles.loginEntryContent}>
<div className={styles.loginEntryDesc}>
</div>
<Button
type="primary"
size="small"
onClick={handleOpenLoginQrDialog}
>
</Button>
</div>
</div>
{/* 搜索栏 */}
<div className={styles.searchBar}>
<Input
@@ -1044,6 +1124,91 @@ const DistributionManagement: React.FC = () => {
)}
</div>
{/* 渠道登录入口二维码弹窗 */}
<Popup
visible={loginQrVisible}
onMaskClick={() => setLoginQrVisible(false)}
position="bottom"
bodyStyle={{ borderTopLeftRadius: 16, borderTopRightRadius: 16 }}
>
<div className={styles.loginQrDialog}>
<div className={styles.loginQrHeader}>
<h3></h3>
<Button size="small" onClick={() => setLoginQrVisible(false)}>
</Button>
</div>
<div className={styles.loginQrTypeSelector}>
<button
className={`${styles.loginEntryTypeTab} ${
loginQrType === "h5" ? styles.active : ""
}`}
onClick={() => handleLoginQrTypeChange("h5")}
>
H5
</button>
<button
className={`${styles.loginEntryTypeTab} ${
loginQrType === "miniprogram" ? styles.active : ""
}`}
onClick={() => handleLoginQrTypeChange("miniprogram")}
>
</button>
</div>
<div className={styles.loginQrContent}>
{loginQrLoading ? (
<div className={styles.loginQrLoading}>
<SpinLoading color="primary" style={{ fontSize: 28 }} />
<div>...</div>
</div>
) : loginQrData?.qrCode ? (
<>
<img
src={loginQrData.qrCode}
alt="登录二维码"
className={styles.loginQrImage}
/>
{loginQrData.url && (
<div className={styles.loginQrLinkSection}>
<div className={styles.loginQrLinkLabel}>
{loginQrType === "h5" ? "H5 链接" : "小程序链接"}
</div>
<div className={styles.loginQrLinkWrapper}>
<Input
value={loginQrData.url}
readOnly
className={styles.loginQrLinkInput}
/>
<Button
size="small"
onClick={() => {
navigator.clipboard.writeText(loginQrData.url);
message.success("链接已复制到剪贴板");
}}
>
</Button>
</div>
</div>
)}
</>
) : (
<div className={styles.loginQrError}>
<div></div>
<Button
size="small"
type="primary"
onClick={() => handleGenerateLoginQRCode(loginQrType)}
>
</Button>
</div>
)}
</div>
</div>
</Popup>
{/* 新增/编辑渠道弹窗 */}
<AddChannelModal
visible={addChannelModalVisible}