From 2f00ee176b9afb93b1ebfcbd1c06cff50e94c551 Mon Sep 17 00:00:00 2001 From: wong <106998207@qq.com> Date: Thu, 18 Dec 2025 16:23:01 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=86=E9=94=80=E6=8F=90=E4=BA=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../distribution-management/index.module.scss | 160 +++++++++++++++++ .../distribution-management/index.tsx | 167 +++++++++++++++++- .../distribution/ChannelController.php | 21 +-- 3 files changed, 334 insertions(+), 14 deletions(-) diff --git a/Cunkebao/src/pages/mobile/workspace/distribution-management/index.module.scss b/Cunkebao/src/pages/mobile/workspace/distribution-management/index.module.scss index ecda04dc..ed82753e 100644 --- a/Cunkebao/src/pages/mobile/workspace/distribution-management/index.module.scss +++ b/Cunkebao/src/pages/mobile/workspace/distribution-management/index.module.scss @@ -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; diff --git a/Cunkebao/src/pages/mobile/workspace/distribution-management/index.tsx b/Cunkebao/src/pages/mobile/workspace/distribution-management/index.tsx index 27f91544..b1658bef 100644 --- a/Cunkebao/src/pages/mobile/workspace/distribution-management/index.tsx +++ b/Cunkebao/src/pages/mobile/workspace/distribution-management/index.tsx @@ -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 = () => { + {/* 渠道登录入口 */} +
+
+
+ + 渠道登录入口 +
+
+ + +
+
+
+
+ 生成登录入口二维码,分发给渠道方扫码登录管理后台 +
+ +
+
+ {/* 搜索栏 */}
{ )}
+ {/* 渠道登录入口二维码弹窗 */} + setLoginQrVisible(false)} + position="bottom" + bodyStyle={{ borderTopLeftRadius: 16, borderTopRightRadius: 16 }} + > +
+
+

渠道登录入口

+ +
+
+ + +
+
+ {loginQrLoading ? ( +
+ +
生成二维码中...
+
+ ) : loginQrData?.qrCode ? ( + <> + 登录二维码 + {loginQrData.url && ( +
+
+ {loginQrType === "h5" ? "H5 链接" : "小程序链接"} +
+
+ + +
+
+ )} + + ) : ( +
+
二维码生成失败,请重试
+ +
+ )} +
+
+
+ {/* 新增/编辑渠道弹窗 */} $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); + // 登录入口只需要携带 companyId 参数 + $h5Url = $h5BaseUrl . '/pages/channel/login?companyId=' . urlencode($companyId); // 生成二维码 $qrCode = new QrCode($h5Url); @@ -1042,8 +1035,11 @@ class ChannelController extends BaseController $app = Factory::miniProgram($miniProgramConfig); - // scene参数长度限制为32位,使用token的MD5值 - $scene = substr(md5($token), 0, 32); + // scene 参数直接使用 companyId(字符串),并确保长度不超过32 + $scene = (string)$companyId; + if (strlen($scene) > 32) { + $scene = substr($scene, 0, 32); + } // 调用接口生成小程序码 // 小程序登录页面路径,需要根据实际项目调整 @@ -1064,8 +1060,7 @@ class ChannelController extends BaseController 'data' => [ 'type' => 'miniprogram', 'qrCode' => $imgBase64, - 'scene' => $scene, - 'token' => $token + 'scene' => $scene ] ]); }