11111
This commit is contained in:
@@ -594,43 +594,52 @@ const ScenarioList: React.FC = () => {
|
||||
<SpinLoading color="primary" />
|
||||
<div>生成二维码中...</div>
|
||||
</div>
|
||||
) : qrImg ? (
|
||||
<>
|
||||
<img
|
||||
src={qrImg}
|
||||
alt="小程序二维码"
|
||||
className={style["qr-image"]}
|
||||
/>
|
||||
{/* 链接复制区域 */}
|
||||
<div className={style["qr-link-section"]}>
|
||||
<div className={style["link-label"]}>小程序链接</div>
|
||||
<div className={style["link-input-wrapper"]}>
|
||||
<Input
|
||||
value={`https://h5.ckb.quwanzhi.com/#/pages/form/input2?id=${currentTaskId}`}
|
||||
readOnly
|
||||
className={style["link-input"]}
|
||||
placeholder="小程序链接"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
const link = `https://h5.ckb.quwanzhi.com/#/pages/form/input2?id=${currentTaskId}`;
|
||||
navigator.clipboard.writeText(link);
|
||||
Toast.show({
|
||||
content: "链接已复制到剪贴板",
|
||||
position: "top",
|
||||
});
|
||||
}}
|
||||
className={style["copy-button"]}
|
||||
>
|
||||
<CopyOutlined />
|
||||
复制
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
) : (
|
||||
<div className={style["qr-error"]}>二维码生成失败</div>
|
||||
<>
|
||||
{/* 二维码显示区域 */}
|
||||
{qrImg ? (
|
||||
<img
|
||||
src={qrImg}
|
||||
alt="小程序二维码"
|
||||
className={style["qr-image"]}
|
||||
/>
|
||||
) : (
|
||||
<div className={style["qr-error"]}>
|
||||
<QrcodeOutlined style={{ fontSize: 48, color: "#999", marginBottom: 12 }} />
|
||||
<div>二维码生成失败</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* H5链接展示 - 无论二维码是否成功都要显示 */}
|
||||
{currentTaskId && (
|
||||
<div className={style["qr-link-section"]}>
|
||||
<div className={style["link-label"]}>H5链接</div>
|
||||
<div className={style["link-input-wrapper"]}>
|
||||
<Input
|
||||
value={`https://h5.ckb.quwanzhi.com/#/pages/form/input2?id=${currentTaskId}`}
|
||||
readOnly
|
||||
className={style["link-input"]}
|
||||
placeholder="H5链接"
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
const link = `https://h5.ckb.quwanzhi.com/#/pages/form/input2?id=${currentTaskId}`;
|
||||
navigator.clipboard.writeText(link);
|
||||
Toast.show({
|
||||
content: "链接已复制到剪贴板",
|
||||
position: "top",
|
||||
});
|
||||
}}
|
||||
className={style["copy-button"]}
|
||||
>
|
||||
<CopyOutlined />
|
||||
复制
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
SearchOutlined,
|
||||
DeleteOutlined,
|
||||
QrcodeOutlined,
|
||||
CopyOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import { Checkbox, Popup } from "antd-mobile";
|
||||
import styles from "./base.module.scss";
|
||||
@@ -60,11 +61,46 @@ const DistributionSettings: React.FC<DistributionSettingsProps> = ({
|
||||
|
||||
const PAGE_SIZE = 20;
|
||||
|
||||
// 生成H5链接(独立生成,不依赖二维码)
|
||||
const generateH5Url = (channelId: string | number, channelCode?: string): string => {
|
||||
// 生成H5链接,参考列表中的实现
|
||||
// 格式: https://h5.ckb.quwanzhi.com/#/pages/form/input2?id={planId}&channelId={channelId}
|
||||
if (planId) {
|
||||
return `https://h5.ckb.quwanzhi.com/#/pages/form/input2?id=${planId}&channelId=${channelId}`;
|
||||
} else if (channelCode) {
|
||||
// 新建状态,使用渠道code
|
||||
return `https://h5.ckb.quwanzhi.com/#/pages/form/input2?channelCode=${channelCode}`;
|
||||
}
|
||||
return "";
|
||||
};
|
||||
|
||||
// 生成渠道二维码
|
||||
const generateChannelQRCode = async (channelId: string | number, channelCode?: string) => {
|
||||
// 如果已经有二维码,直接返回
|
||||
if (qrCodeMap[channelId]?.qrCode) {
|
||||
return;
|
||||
// 先生成H5链接(无论二维码是否成功都要显示)
|
||||
const h5Url = generateH5Url(channelId, channelCode);
|
||||
|
||||
// 如果已经有二维码数据,只更新H5链接(如果还没有)
|
||||
if (qrCodeMap[channelId]) {
|
||||
if (!qrCodeMap[channelId].url) {
|
||||
setQrCodeMap(prev => ({
|
||||
...prev,
|
||||
[channelId]: { ...prev[channelId], url: h5Url },
|
||||
}));
|
||||
}
|
||||
// 如果已经有二维码,直接返回
|
||||
if (qrCodeMap[channelId].qrCode) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// 初始化,先设置H5链接
|
||||
setQrCodeMap(prev => ({
|
||||
...prev,
|
||||
[channelId]: {
|
||||
qrCode: "",
|
||||
url: h5Url,
|
||||
loading: true,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
// 设置加载状态
|
||||
@@ -85,18 +121,20 @@ const DistributionSettings: React.FC<DistributionSettingsProps> = ({
|
||||
}
|
||||
|
||||
// 调用API生成二维码,参考列表中的实现
|
||||
// 接口返回格式: { code: 200, msg: "获取小程序码成功", data: "data:image/png;base64,..." }
|
||||
const response = await request(
|
||||
`/v1/plan/getWxMinAppCode`,
|
||||
params,
|
||||
"GET"
|
||||
);
|
||||
|
||||
if (response && response.qrCode) {
|
||||
// response 已经是 base64 字符串(因为 request 拦截器返回了 res.data.data)
|
||||
if (response && typeof response === 'string' && response.startsWith('data:image')) {
|
||||
setQrCodeMap(prev => ({
|
||||
...prev,
|
||||
[channelId]: {
|
||||
qrCode: response.qrCode,
|
||||
url: response.url || "",
|
||||
qrCode: response, // base64 图片数据
|
||||
url: h5Url, // H5链接(确保即使失败也有)
|
||||
loading: false,
|
||||
},
|
||||
}));
|
||||
@@ -104,13 +142,19 @@ const DistributionSettings: React.FC<DistributionSettingsProps> = ({
|
||||
throw new Error("二维码生成失败");
|
||||
}
|
||||
} catch (error: any) {
|
||||
// 即使二维码生成失败,也要保留H5链接
|
||||
Toast.show({
|
||||
content: error.message || "生成二维码失败",
|
||||
position: "top",
|
||||
});
|
||||
setQrCodeMap(prev => ({
|
||||
...prev,
|
||||
[channelId]: { ...prev[channelId], loading: false },
|
||||
[channelId]: {
|
||||
...prev[channelId],
|
||||
qrCode: "", // 二维码为空
|
||||
url: h5Url, // H5链接保留
|
||||
loading: false,
|
||||
},
|
||||
}));
|
||||
}
|
||||
};
|
||||
@@ -673,63 +717,93 @@ const DistributionSettings: React.FC<DistributionSettingsProps> = ({
|
||||
<SpinLoading color="primary" style={{ fontSize: 32 }} />
|
||||
<div style={{ fontSize: 14, color: "#666" }}>生成二维码中...</div>
|
||||
</div>
|
||||
) : qrCodeMap[currentQrChannel.id]?.qrCode ? (
|
||||
) : (
|
||||
<>
|
||||
<img
|
||||
src={qrCodeMap[currentQrChannel.id].qrCode}
|
||||
alt="渠道二维码"
|
||||
style={{
|
||||
width: 200,
|
||||
height: 200,
|
||||
border: "1px solid #e5e6eb",
|
||||
borderRadius: 8,
|
||||
padding: 8,
|
||||
background: "#fff",
|
||||
}}
|
||||
/>
|
||||
{qrCodeMap[currentQrChannel.id].url && (
|
||||
{/* 二维码显示区域 */}
|
||||
{qrCodeMap[currentQrChannel.id]?.qrCode ? (
|
||||
<img
|
||||
src={qrCodeMap[currentQrChannel.id].qrCode}
|
||||
alt="渠道二维码"
|
||||
style={{
|
||||
width: 200,
|
||||
height: 200,
|
||||
border: "1px solid #e5e6eb",
|
||||
borderRadius: 8,
|
||||
padding: 8,
|
||||
background: "#fff",
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: 12,
|
||||
padding: "40px 20px",
|
||||
color: "#999",
|
||||
}}>
|
||||
<QrcodeOutlined style={{ fontSize: 48 }} />
|
||||
<div style={{ fontSize: 14 }}>二维码生成失败</div>
|
||||
<Button
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={() => currentQrChannel && generateChannelQRCode(currentQrChannel.id, currentQrChannel.code)}
|
||||
>
|
||||
重新生成
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* H5链接展示 - 无论二维码是否成功都要显示 */}
|
||||
{qrCodeMap[currentQrChannel.id]?.url && (
|
||||
<div style={{
|
||||
width: "100%",
|
||||
padding: "12px",
|
||||
background: "#f5f5f5",
|
||||
borderRadius: 8,
|
||||
marginTop: 16,
|
||||
}}>
|
||||
<div style={{
|
||||
fontSize: 12,
|
||||
fontSize: 14,
|
||||
color: "#666",
|
||||
marginBottom: 8,
|
||||
fontWeight: 500,
|
||||
}}>
|
||||
链接地址
|
||||
H5链接
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: 12,
|
||||
color: "#333",
|
||||
wordBreak: "break-all",
|
||||
display: "flex",
|
||||
gap: 8,
|
||||
alignItems: "center",
|
||||
}}>
|
||||
{qrCodeMap[currentQrChannel.id].url}
|
||||
<Input
|
||||
value={qrCodeMap[currentQrChannel.id].url}
|
||||
readOnly
|
||||
style={{
|
||||
flex: 1,
|
||||
fontSize: 12,
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => {
|
||||
const link = qrCodeMap[currentQrChannel.id].url;
|
||||
navigator.clipboard.writeText(link);
|
||||
Toast.show({
|
||||
content: "链接已复制到剪贴板",
|
||||
position: "top",
|
||||
});
|
||||
}}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<CopyOutlined />
|
||||
复制
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<div style={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
gap: 12,
|
||||
padding: "40px 20px",
|
||||
color: "#999",
|
||||
}}>
|
||||
<QrcodeOutlined style={{ fontSize: 48 }} />
|
||||
<div style={{ fontSize: 14 }}>二维码生成失败</div>
|
||||
<Button
|
||||
size="small"
|
||||
color="primary"
|
||||
onClick={() => currentQrChannel && generateChannelQRCode(currentQrChannel.id, currentQrChannel.code)}
|
||||
>
|
||||
重新生成
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -133,7 +133,7 @@ export const markAsPaid = async (
|
||||
return request(`/v1/distribution/withdrawals/${id}/mark-paid`, data, "POST");
|
||||
};
|
||||
|
||||
// 生成二维码
|
||||
// 生成渠道扫码创建二维码
|
||||
export const generateQRCode = async (
|
||||
type: "h5" | "miniprogram",
|
||||
): Promise<{
|
||||
@@ -143,3 +143,18 @@ export const generateQRCode = async (
|
||||
}> => {
|
||||
return request("/v1/distribution/channel/generate-qrcode", { type }, "POST");
|
||||
};
|
||||
|
||||
// 生成渠道登录二维码
|
||||
export const generateLoginQRCode = async (
|
||||
type: "h5" | "miniprogram" = "h5",
|
||||
): Promise<{
|
||||
type: "h5" | "miniprogram";
|
||||
qrCode: string; // base64 或图片URL
|
||||
url: string; // H5 或小程序落地页URL
|
||||
}> => {
|
||||
return request(
|
||||
"/v1/distribution/channel/generate-login-qrcode",
|
||||
{ type },
|
||||
"POST",
|
||||
);
|
||||
};
|
||||
|
||||
@@ -70,23 +70,11 @@ const AddChannelModal: React.FC<AddChannelModalProps> = ({
|
||||
}
|
||||
}, [editData, visible]);
|
||||
|
||||
// 当弹窗打开或切换到扫码创建时,自动生成二维码
|
||||
// 当弹窗打开时,如果是扫码创建模式,自动生成二维码
|
||||
useEffect(() => {
|
||||
// 只有在弹窗可见、非编辑模式、选择扫码方式、没有二维码数据、且不在加载中时才生成
|
||||
if (visible && !isEdit && createMethod === "scan" && !qrCodeData && !qrCodeLoading && !generatingRef.current) {
|
||||
// 使用 setTimeout 确保状态更新完成
|
||||
const timer = setTimeout(() => {
|
||||
handleGenerateQRCode();
|
||||
}, 100);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [visible, createMethod]);
|
||||
|
||||
// 当二维码类型变化时,重新生成二维码
|
||||
useEffect(() => {
|
||||
if (visible && !isEdit && createMethod === "scan" && qrCodeData && !qrCodeLoading && !generatingRef.current) {
|
||||
// 重置状态后重新生成
|
||||
// 弹窗打开时,如果是扫码创建模式,无论之前是否有数据都重新生成
|
||||
if (visible && !isEdit && createMethod === "scan" && !qrCodeLoading && !generatingRef.current) {
|
||||
// 重置状态后重新生成(无论之前是否有数据)
|
||||
setQrCodeData(null);
|
||||
setScanning(false);
|
||||
// 使用 setTimeout 确保状态更新完成
|
||||
@@ -96,7 +84,22 @@ const AddChannelModal: React.FC<AddChannelModalProps> = ({
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [qrCodeType]);
|
||||
}, [visible]);
|
||||
|
||||
// 当二维码类型变化时,重新生成二维码(无论之前是否成功或失败都要重新生成)
|
||||
useEffect(() => {
|
||||
if (visible && !isEdit && createMethod === "scan" && !qrCodeLoading && !generatingRef.current) {
|
||||
// 重置状态后重新生成(无论之前是否有数据)
|
||||
setQrCodeData(null);
|
||||
setScanning(false);
|
||||
// 使用 setTimeout 确保状态更新完成
|
||||
const timer = setTimeout(() => {
|
||||
handleGenerateQRCode();
|
||||
}, 100);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [qrCodeType, visible, createMethod]);
|
||||
|
||||
// 验证手机号格式
|
||||
const validatePhone = (phone: string): boolean => {
|
||||
@@ -213,20 +216,18 @@ const AddChannelModal: React.FC<AddChannelModalProps> = ({
|
||||
|
||||
// 当切换到扫码创建时,自动生成二维码
|
||||
useEffect(() => {
|
||||
if (visible && createMethod === "scan" && !isEdit && !qrCodeData && !qrCodeLoading) {
|
||||
handleGenerateQRCode();
|
||||
if (visible && createMethod === "scan" && !isEdit && !qrCodeLoading && !generatingRef.current) {
|
||||
// 重置状态后重新生成(无论之前是否有数据)
|
||||
setQrCodeData(null);
|
||||
setScanning(false);
|
||||
const timer = setTimeout(() => {
|
||||
handleGenerateQRCode();
|
||||
}, 100);
|
||||
return () => clearTimeout(timer);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [createMethod, visible]);
|
||||
|
||||
// 当二维码类型变化时,重新生成二维码
|
||||
useEffect(() => {
|
||||
if (visible && createMethod === "scan" && !isEdit && qrCodeData) {
|
||||
handleGenerateQRCode();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [qrCodeType, visible]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={visible}
|
||||
|
||||
Reference in New Issue
Block a user