diff --git a/web/app/dashboard/channels/_components/edit.tsx b/web/app/dashboard/channels/_components/edit.tsx index 4aefcd0..70f1e61 100644 --- a/web/app/dashboard/channels/_components/edit.tsx +++ b/web/app/dashboard/channels/_components/edit.tsx @@ -1,9 +1,11 @@ "use client" -import { useEffect, useState } from "react" +import { useEffect, useMemo, useState } from "react" import { zodResolver } from "@hookform/resolvers/zod" import { Controller, Resolver, useForm, useWatch } from "react-hook-form" import { z } from "zod/v4" +import { CopyIcon, ExternalLinkIcon } from "lucide-react" +import { toast } from "sonner" import { OptionCombobox } from "@/components/option-combobox" import { ProjectDialog } from "@/components/project-dialog" @@ -214,6 +216,7 @@ function ChannelFormBody({ const [wxWorkKFAccounts, setWxWorkKFAccounts] = useState([]) const [wxWorkKFAccountsLoading, setWxWorkKFAccountsLoading] = useState(false) const [wxWorkKFAccountsError, setWxWorkKFAccountsError] = useState("") + const [channelDetail, setChannelDetail] = useState(null) const [currentStatus, setCurrentStatus] = useState(0) const form = useForm< z.input, @@ -249,12 +252,14 @@ function ChannelFormBody({ async function loadDetail() { if (!itemId) { setCurrentStatus(0) + setChannelDetail(null) reset(emptyForm) return } setLoading(true) try { const data = await fetchChannel(itemId) + setChannelDetail(data) setCurrentStatus(data.status) reset(buildForm(data)) } catch (error) { @@ -436,71 +441,74 @@ function ChannelFormBody({ ) : null} {channelType === "web" ? ( -
- - 窗口标题 - - - - - + <> +
+ + 窗口标题 + + + + + - - 窗口副标题 - - - - - + + 窗口副标题 + + + + + - - 主题色 - - - - - + + 主题色 + + + + + - - 挂载位置 - - ( - - )} - /> - - - + + 挂载位置 + + ( + + )} + /> + + + - - 窗口宽度 - - - - - -
+ + 窗口宽度 + + + + + +
+ + ) : null} @@ -516,3 +524,140 @@ function ChannelFormBody({ ) } + +function WebAccessGuide({ channelId }: { channelId: string }) { + const [origin, setOrigin] = useState("") + + useEffect(() => { + setOrigin(window.location.origin) + }, []) + + const accessUrl = useMemo(() => { + if (!origin || !channelId) { + return "" + } + const url = new URL("/kefu/chat/", origin) + url.searchParams.set("channelId", channelId) + return url.toString() + }, [channelId, origin]) + + const testUrl = useMemo(() => { + if (!origin || !channelId) { + return "" + } + const url = new URL("/kefu", origin) + url.searchParams.set("channelId", channelId) + return url.toString() + }, [channelId, origin]) + + const snippet = useMemo(() => { + if (!origin || !channelId) { + return "" + } + return ` +` + }, [channelId, origin]) + + async function copyText(text: string, successMessage: string) { + if (!text) { + return + } + try { + await navigator.clipboard.writeText(text) + toast.success(successMessage) + } catch { + toast.error("复制失败") + } + } + + return ( +
+
+
Web 接入信息
+
+ {channelId + ? "复制链接或嵌入代码即可接入当前 Web 渠道。" + : "保存渠道后生成接入链接和 SDK 代码。"} +
+
+ + {!channelId ? ( +
+ 当前为新建渠道,保存后会生成 channelId。 +
+ ) : ( +
+
+
直接访问链接
+
+ +
+ + +
+
+
+ +
+
+
+ 嵌入式接入代码 +
+ +
+
+              {snippet}
+            
+
+ +
+
接入教程
+
1. 确认该渠道已启用。
+
2. 将嵌入代码粘贴到目标网站 HTML 的 body 结束标签前。
+
3. 发布网站后刷新页面,客服入口会按渠道配置展示。
+
4. 独立页面或二维码场景可直接使用访问链接。
+
+ +
+
+
+ )} +
+ ) +}