11111
This commit is contained in:
@@ -594,43 +594,52 @@ const ScenarioList: React.FC = () => {
|
|||||||
<SpinLoading color="primary" />
|
<SpinLoading color="primary" />
|
||||||
<div>生成二维码中...</div>
|
<div>生成二维码中...</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>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
SearchOutlined,
|
SearchOutlined,
|
||||||
DeleteOutlined,
|
DeleteOutlined,
|
||||||
QrcodeOutlined,
|
QrcodeOutlined,
|
||||||
|
CopyOutlined,
|
||||||
} from "@ant-design/icons";
|
} from "@ant-design/icons";
|
||||||
import { Checkbox, Popup } from "antd-mobile";
|
import { Checkbox, Popup } from "antd-mobile";
|
||||||
import styles from "./base.module.scss";
|
import styles from "./base.module.scss";
|
||||||
@@ -60,11 +61,46 @@ const DistributionSettings: React.FC<DistributionSettingsProps> = ({
|
|||||||
|
|
||||||
const PAGE_SIZE = 20;
|
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) => {
|
const generateChannelQRCode = async (channelId: string | number, channelCode?: string) => {
|
||||||
// 如果已经有二维码,直接返回
|
// 先生成H5链接(无论二维码是否成功都要显示)
|
||||||
if (qrCodeMap[channelId]?.qrCode) {
|
const h5Url = generateH5Url(channelId, channelCode);
|
||||||
return;
|
|
||||||
|
// 如果已经有二维码数据,只更新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生成二维码,参考列表中的实现
|
// 调用API生成二维码,参考列表中的实现
|
||||||
|
// 接口返回格式: { code: 200, msg: "获取小程序码成功", data: "data:image/png;base64,..." }
|
||||||
const response = await request(
|
const response = await request(
|
||||||
`/v1/plan/getWxMinAppCode`,
|
`/v1/plan/getWxMinAppCode`,
|
||||||
params,
|
params,
|
||||||
"GET"
|
"GET"
|
||||||
);
|
);
|
||||||
|
|
||||||
if (response && response.qrCode) {
|
// response 已经是 base64 字符串(因为 request 拦截器返回了 res.data.data)
|
||||||
|
if (response && typeof response === 'string' && response.startsWith('data:image')) {
|
||||||
setQrCodeMap(prev => ({
|
setQrCodeMap(prev => ({
|
||||||
...prev,
|
...prev,
|
||||||
[channelId]: {
|
[channelId]: {
|
||||||
qrCode: response.qrCode,
|
qrCode: response, // base64 图片数据
|
||||||
url: response.url || "",
|
url: h5Url, // H5链接(确保即使失败也有)
|
||||||
loading: false,
|
loading: false,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@@ -104,13 +142,19 @@ const DistributionSettings: React.FC<DistributionSettingsProps> = ({
|
|||||||
throw new Error("二维码生成失败");
|
throw new Error("二维码生成失败");
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
// 即使二维码生成失败,也要保留H5链接
|
||||||
Toast.show({
|
Toast.show({
|
||||||
content: error.message || "生成二维码失败",
|
content: error.message || "生成二维码失败",
|
||||||
position: "top",
|
position: "top",
|
||||||
});
|
});
|
||||||
setQrCodeMap(prev => ({
|
setQrCodeMap(prev => ({
|
||||||
...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 }} />
|
<SpinLoading color="primary" style={{ fontSize: 32 }} />
|
||||||
<div style={{ fontSize: 14, color: "#666" }}>生成二维码中...</div>
|
<div style={{ fontSize: 14, color: "#666" }}>生成二维码中...</div>
|
||||||
</div>
|
</div>
|
||||||
) : qrCodeMap[currentQrChannel.id]?.qrCode ? (
|
) : (
|
||||||
<>
|
<>
|
||||||
<img
|
{/* 二维码显示区域 */}
|
||||||
src={qrCodeMap[currentQrChannel.id].qrCode}
|
{qrCodeMap[currentQrChannel.id]?.qrCode ? (
|
||||||
alt="渠道二维码"
|
<img
|
||||||
style={{
|
src={qrCodeMap[currentQrChannel.id].qrCode}
|
||||||
width: 200,
|
alt="渠道二维码"
|
||||||
height: 200,
|
style={{
|
||||||
border: "1px solid #e5e6eb",
|
width: 200,
|
||||||
borderRadius: 8,
|
height: 200,
|
||||||
padding: 8,
|
border: "1px solid #e5e6eb",
|
||||||
background: "#fff",
|
borderRadius: 8,
|
||||||
}}
|
padding: 8,
|
||||||
/>
|
background: "#fff",
|
||||||
{qrCodeMap[currentQrChannel.id].url && (
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<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={{
|
<div style={{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
padding: "12px",
|
marginTop: 16,
|
||||||
background: "#f5f5f5",
|
|
||||||
borderRadius: 8,
|
|
||||||
}}>
|
}}>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontSize: 12,
|
fontSize: 14,
|
||||||
color: "#666",
|
color: "#666",
|
||||||
marginBottom: 8,
|
marginBottom: 8,
|
||||||
|
fontWeight: 500,
|
||||||
}}>
|
}}>
|
||||||
链接地址
|
H5链接
|
||||||
</div>
|
</div>
|
||||||
<div style={{
|
<div style={{
|
||||||
fontSize: 12,
|
display: "flex",
|
||||||
color: "#333",
|
gap: 8,
|
||||||
wordBreak: "break-all",
|
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>
|
</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");
|
return request(`/v1/distribution/withdrawals/${id}/mark-paid`, data, "POST");
|
||||||
};
|
};
|
||||||
|
|
||||||
// 生成二维码
|
// 生成渠道扫码创建二维码
|
||||||
export const generateQRCode = async (
|
export const generateQRCode = async (
|
||||||
type: "h5" | "miniprogram",
|
type: "h5" | "miniprogram",
|
||||||
): Promise<{
|
): Promise<{
|
||||||
@@ -143,3 +143,18 @@ export const generateQRCode = async (
|
|||||||
}> => {
|
}> => {
|
||||||
return request("/v1/distribution/channel/generate-qrcode", { type }, "POST");
|
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]);
|
}, [editData, visible]);
|
||||||
|
|
||||||
// 当弹窗打开或切换到扫码创建时,自动生成二维码
|
// 当弹窗打开时,如果是扫码创建模式,自动生成二维码
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// 只有在弹窗可见、非编辑模式、选择扫码方式、没有二维码数据、且不在加载中时才生成
|
// 弹窗打开时,如果是扫码创建模式,无论之前是否有数据都重新生成
|
||||||
if (visible && !isEdit && createMethod === "scan" && !qrCodeData && !qrCodeLoading && !generatingRef.current) {
|
if (visible && !isEdit && createMethod === "scan" && !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) {
|
|
||||||
// 重置状态后重新生成
|
|
||||||
setQrCodeData(null);
|
setQrCodeData(null);
|
||||||
setScanning(false);
|
setScanning(false);
|
||||||
// 使用 setTimeout 确保状态更新完成
|
// 使用 setTimeout 确保状态更新完成
|
||||||
@@ -96,7 +84,22 @@ const AddChannelModal: React.FC<AddChannelModalProps> = ({
|
|||||||
return () => clearTimeout(timer);
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// 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 => {
|
const validatePhone = (phone: string): boolean => {
|
||||||
@@ -213,20 +216,18 @@ const AddChannelModal: React.FC<AddChannelModalProps> = ({
|
|||||||
|
|
||||||
// 当切换到扫码创建时,自动生成二维码
|
// 当切换到扫码创建时,自动生成二维码
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (visible && createMethod === "scan" && !isEdit && !qrCodeData && !qrCodeLoading) {
|
if (visible && createMethod === "scan" && !isEdit && !qrCodeLoading && !generatingRef.current) {
|
||||||
handleGenerateQRCode();
|
// 重置状态后重新生成(无论之前是否有数据)
|
||||||
|
setQrCodeData(null);
|
||||||
|
setScanning(false);
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
handleGenerateQRCode();
|
||||||
|
}, 100);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [createMethod, visible]);
|
}, [createMethod, visible]);
|
||||||
|
|
||||||
// 当二维码类型变化时,重新生成二维码
|
|
||||||
useEffect(() => {
|
|
||||||
if (visible && createMethod === "scan" && !isEdit && qrCodeData) {
|
|
||||||
handleGenerateQRCode();
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [qrCodeType, visible]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
open={visible}
|
open={visible}
|
||||||
|
|||||||
@@ -213,7 +213,8 @@ Route::group('v1/', function () {
|
|||||||
Route::put(':id', 'app\cunkebao\controller\distribution\ChannelController@update'); // 编辑渠道
|
Route::put(':id', 'app\cunkebao\controller\distribution\ChannelController@update'); // 编辑渠道
|
||||||
Route::delete(':id', 'app\cunkebao\controller\distribution\ChannelController@delete'); // 删除渠道
|
Route::delete(':id', 'app\cunkebao\controller\distribution\ChannelController@delete'); // 删除渠道
|
||||||
Route::post(':id/toggle-status', 'app\cunkebao\controller\distribution\ChannelController@toggleStatus'); // 禁用/启用渠道
|
Route::post(':id/toggle-status', 'app\cunkebao\controller\distribution\ChannelController@toggleStatus'); // 禁用/启用渠道
|
||||||
Route::post('generate-qrcode', 'app\cunkebao\controller\distribution\ChannelController@generateQrCode'); // 生成渠道二维码
|
Route::post('generate-qrcode', 'app\cunkebao\controller\distribution\ChannelController@generateQrCode'); // 生成渠道注册二维码
|
||||||
|
Route::post('generate-login-qrcode', 'app\cunkebao\controller\distribution\ChannelController@generateLoginQrCode'); // 生成渠道登录二维码
|
||||||
});
|
});
|
||||||
// 提现申请管理
|
// 提现申请管理
|
||||||
Route::group('withdrawals', function () {
|
Route::group('withdrawals', function () {
|
||||||
|
|||||||
@@ -864,7 +864,7 @@ class ChannelController extends BaseController
|
|||||||
if ($type === 'h5') {
|
if ($type === 'h5') {
|
||||||
// 生成H5二维码
|
// 生成H5二维码
|
||||||
// 获取H5页面URL(需要根据实际项目配置)
|
// 获取H5页面URL(需要根据实际项目配置)
|
||||||
$h5BaseUrl = Env::get('h5.base_url', $this->request->domain());
|
$h5BaseUrl = Env::get('rpc.H5_FORM_URL', 'https://h5.ckb.quwanzhi.com/#');
|
||||||
// 确保URL格式正确(去除末尾斜杠)
|
// 确保URL格式正确(去除末尾斜杠)
|
||||||
$h5BaseUrl = rtrim($h5BaseUrl, '/');
|
$h5BaseUrl = rtrim($h5BaseUrl, '/');
|
||||||
$h5Url = $h5BaseUrl . '/pages/channel/add?token=' . urlencode($token);
|
$h5Url = $h5BaseUrl . '/pages/channel/add?token=' . urlencode($token);
|
||||||
@@ -970,6 +970,144 @@ class ChannelController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成渠道登录二维码(H5或小程序码)
|
||||||
|
* 通用登录二维码,不绑定特定渠道,用户扫码后输入手机号和密码登录
|
||||||
|
* @return \think\response\Json
|
||||||
|
*/
|
||||||
|
public function generateLoginQrCode()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// 获取参数
|
||||||
|
$type = $this->request->param('type', 'h5'); // h5 或 miniprogram
|
||||||
|
|
||||||
|
$companyId = $this->getUserInfo('companyId');
|
||||||
|
|
||||||
|
// 参数验证
|
||||||
|
if (!in_array($type, ['h5', 'miniprogram'])) {
|
||||||
|
return json([
|
||||||
|
'code' => 400,
|
||||||
|
'success' => false,
|
||||||
|
'msg' => '类型参数错误,必须为 h5 或 miniprogram',
|
||||||
|
'data' => null
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成登录token(只包含公司ID,有效期1小时)
|
||||||
|
// 用户扫码后需要自己输入手机号和密码
|
||||||
|
$tokenData = [
|
||||||
|
'companyId' => $companyId,
|
||||||
|
'expireTime' => time() + 3600 // 1小时后过期
|
||||||
|
];
|
||||||
|
$token = base64_encode(json_encode($tokenData));
|
||||||
|
|
||||||
|
if ($type === 'h5') {
|
||||||
|
// 生成H5登录二维码
|
||||||
|
$h5BaseUrl = Env::get('rpc.H5_FORM_URL', 'https://h5.ckb.quwanzhi.com/#');
|
||||||
|
$h5BaseUrl = rtrim($h5BaseUrl, '/');
|
||||||
|
// H5登录页面路径,需要根据实际项目调整
|
||||||
|
$h5Url = $h5BaseUrl . '/pages/channel/login?token=' . urlencode($token);
|
||||||
|
|
||||||
|
// 生成二维码
|
||||||
|
$qrCode = new QrCode($h5Url);
|
||||||
|
$qrCode->setSize(300);
|
||||||
|
$qrCode->setMargin(10);
|
||||||
|
$qrCode->setWriterByName('png');
|
||||||
|
$qrCode->setEncoding('UTF-8');
|
||||||
|
$qrCode->setErrorCorrectionLevel(ErrorCorrectionLevel::HIGH);
|
||||||
|
|
||||||
|
// 转换为base64
|
||||||
|
$qrCodeBase64 = 'data:image/png;base64,' . base64_encode($qrCode->writeString());
|
||||||
|
|
||||||
|
return json([
|
||||||
|
'code' => 200,
|
||||||
|
'success' => true,
|
||||||
|
'msg' => '生成H5登录二维码成功',
|
||||||
|
'data' => [
|
||||||
|
'type' => 'h5',
|
||||||
|
'qrCode' => $qrCodeBase64,
|
||||||
|
'url' => $h5Url
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// 生成小程序登录码
|
||||||
|
try {
|
||||||
|
// 从环境变量获取小程序配置
|
||||||
|
$miniProgramConfig = [
|
||||||
|
'app_id' => Env::get('weChat.appidMiniApp', 'wx789850448e26c91d'),
|
||||||
|
'secret' => Env::get('weChat.secretMiniApp', 'd18f75b3a3623cb40da05648b08365a1'),
|
||||||
|
'response_type' => 'array'
|
||||||
|
];
|
||||||
|
|
||||||
|
$app = Factory::miniProgram($miniProgramConfig);
|
||||||
|
|
||||||
|
// scene参数长度限制为32位,使用token的MD5值
|
||||||
|
$scene = substr(md5($token), 0, 32);
|
||||||
|
|
||||||
|
// 调用接口生成小程序码
|
||||||
|
// 小程序登录页面路径,需要根据实际项目调整
|
||||||
|
$response = $app->app_code->getUnlimit($scene, [
|
||||||
|
'page' => 'pages/channel/login', // 请确保小程序里存在该页面
|
||||||
|
'width' => 430,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 成功时返回的是 StreamResponse
|
||||||
|
if ($response instanceof \EasyWeChat\Kernel\Http\StreamResponse) {
|
||||||
|
$img = $response->getBody()->getContents();
|
||||||
|
$imgBase64 = 'data:image/png;base64,' . base64_encode($img);
|
||||||
|
|
||||||
|
return json([
|
||||||
|
'code' => 200,
|
||||||
|
'success' => true,
|
||||||
|
'msg' => '生成小程序登录码成功',
|
||||||
|
'data' => [
|
||||||
|
'type' => 'miniprogram',
|
||||||
|
'qrCode' => $imgBase64,
|
||||||
|
'scene' => $scene,
|
||||||
|
'token' => $token
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果不是流响应,而是数组(错误信息),则解析错误返回
|
||||||
|
if (is_array($response) && isset($response['errcode']) && $response['errcode'] != 0) {
|
||||||
|
$errMsg = isset($response['errmsg']) ? $response['errmsg'] : '微信接口返回错误';
|
||||||
|
return json([
|
||||||
|
'code' => 500,
|
||||||
|
'success' => false,
|
||||||
|
'msg' => '生成小程序登录码失败:' . $errMsg,
|
||||||
|
'data' => $response
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他未知格式
|
||||||
|
return json([
|
||||||
|
'code' => 500,
|
||||||
|
'success' => false,
|
||||||
|
'msg' => '生成小程序登录码失败:响应格式错误',
|
||||||
|
'data' => $response
|
||||||
|
]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return json([
|
||||||
|
'code' => 500,
|
||||||
|
'success' => false,
|
||||||
|
'msg' => '生成小程序登录码失败:' . $e->getMessage(),
|
||||||
|
'data' => null
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return json([
|
||||||
|
'code' => $e->getCode() ?: 500,
|
||||||
|
'success' => false,
|
||||||
|
'msg' => '生成登录二维码失败:' . $e->getMessage(),
|
||||||
|
'data' => null
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扫码提交渠道信息(H5和小程序共用)
|
* 扫码提交渠道信息(H5和小程序共用)
|
||||||
* GET请求:返回预填信息
|
* GET请求:返回预填信息
|
||||||
|
|||||||
Reference in New Issue
Block a user