存客宝 - 修复添加设备二维码有时会错误的问题

This commit is contained in:
柳清爽
2025-04-30 17:15:11 +08:00
parent af1b3b6988
commit 4935d8c8c0
7 changed files with 125 additions and 75 deletions

View File

@@ -7,6 +7,7 @@ import { Card } from "@/components/ui/card";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Alert, AlertDescription } from "@/components/ui/alert"; import { Alert, AlertDescription } from "@/components/ui/alert";
import { AlertCircle } from "lucide-react"; import { AlertCircle } from "lucide-react";
import { toast } from "@/components/ui/use-toast";
interface ContactData { interface ContactData {
mobile: number; mobile: number;

View File

@@ -134,13 +134,9 @@ export default function DeviceDetailPage() {
} else if (serverData.taskConfig) { } else if (serverData.taskConfig) {
try { try {
// 解析taskConfig字段 // 解析taskConfig字段
let taskConfig = serverData.taskConfig const taskConfig = JSON.parse(serverData.taskConfig || '{}');
if (typeof taskConfig === 'string') {
taskConfig = JSON.parse(taskConfig)
}
if (taskConfig) { if (taskConfig) {
console.log('解析的taskConfig:', taskConfig);
formattedDevice.features = { formattedDevice.features = {
autoAddFriend: Boolean(taskConfig.autoAddFriend), autoAddFriend: Boolean(taskConfig.autoAddFriend),
autoReply: Boolean(taskConfig.autoReply), autoReply: Boolean(taskConfig.autoReply),

View File

@@ -171,26 +171,37 @@ export default function DevicesPage() {
setIsLoadingQRCode(true) setIsLoadingQRCode(true)
setQrCodeImage("") // 清空当前二维码 setQrCodeImage("") // 清空当前二维码
// 获取保存的accountId
const accountId = localStorage.getItem('s2_accountId')
if (!accountId) {
toast({
title: "获取二维码失败",
description: "未获取到用户信息,请重新登录",
variant: "destructive",
})
return
}
// 发起请求获取二维码 - 直接使用fetch避免api工具添加基础URL // 发起请求获取二维码 - 直接使用fetch避免api工具添加基础URL
const response = await fetch('http://yi.54word.com/v1/api/device/add', { const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/api/device/add`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Accept': 'application/json' 'Accept': 'application/json'
}, },
body: JSON.stringify({}) body: JSON.stringify({
accountId: accountId
})
}) })
// 保存原始响应文本以便调试 // 保存原始响应文本以便调试
const responseText = await response.text(); const responseText = await response.text();
console.log("原始响应内容:", responseText);
// 尝试将响应解析为JSON // 尝试将响应解析为JSON
let result; let result;
try { try {
result = JSON.parse(responseText); result = JSON.parse(responseText);
} catch (e) { } catch (e) {
console.error("响应不是有效的JSON:", e);
toast({ toast({
title: "获取二维码失败", title: "获取二维码失败",
description: "服务器返回的数据格式无效", description: "服务器返回的数据格式无效",
@@ -199,25 +210,19 @@ export default function DevicesPage() {
return; return;
} }
console.log("二维码响应数据:", result);
if (result && result.code === 200) { if (result && result.code === 200) {
// 尝试多种可能的返回数据结构 // 尝试多种可能的返回数据结构
let qrcodeData = null; let qrcodeData = null;
if (result.data?.qrCode) { if (result.data?.qrCode) {
qrcodeData = result.data.qrCode; qrcodeData = result.data.qrCode;
console.log("找到二维码数据在 result.data.qrCode");
} else if (result.data?.qrcode) { } else if (result.data?.qrcode) {
qrcodeData = result.data.qrcode; qrcodeData = result.data.qrcode;
console.log("找到二维码数据在 result.data.qrcode");
} else if (result.data?.image) { } else if (result.data?.image) {
qrcodeData = result.data.image; qrcodeData = result.data.image;
console.log("找到二维码数据在 result.data.image");
} else if (result.data?.url) { } else if (result.data?.url) {
// 如果返回的是URL而不是base64 // 如果返回的是URL而不是base64
qrcodeData = result.data.url; qrcodeData = result.data.url;
console.log("找到二维码URL在 result.data.url");
setQrCodeImage(qrcodeData); setQrCodeImage(qrcodeData);
toast({ toast({
@@ -229,9 +234,7 @@ export default function DevicesPage() {
} else if (typeof result.data === 'string') { } else if (typeof result.data === 'string') {
// 如果data直接是字符串 // 如果data直接是字符串
qrcodeData = result.data; qrcodeData = result.data;
console.log("二维码数据直接在 result.data 字符串中");
} else { } else {
console.error("无法找到二维码数据:", result);
toast({ toast({
title: "获取二维码失败", title: "获取二维码失败",
description: "返回数据格式不正确", description: "返回数据格式不正确",
@@ -242,7 +245,6 @@ export default function DevicesPage() {
// 检查数据是否为空 // 检查数据是否为空
if (!qrcodeData) { if (!qrcodeData) {
console.error("二维码数据为空");
toast({ toast({
title: "获取二维码失败", title: "获取二维码失败",
description: "服务器返回的二维码数据为空", description: "服务器返回的二维码数据为空",
@@ -251,16 +253,12 @@ export default function DevicesPage() {
return; return;
} }
console.log("处理前的二维码数据:", qrcodeData);
// 检查是否已经是完整的data URL // 检查是否已经是完整的data URL
if (qrcodeData.startsWith('data:image')) { if (qrcodeData.startsWith('data:image')) {
console.log("数据已包含data:image前缀");
setQrCodeImage(qrcodeData); setQrCodeImage(qrcodeData);
} }
// 检查是否是URL // 检查是否是URL
else if (qrcodeData.startsWith('http')) { else if (qrcodeData.startsWith('http')) {
console.log("数据是HTTP URL");
setQrCodeImage(qrcodeData); setQrCodeImage(qrcodeData);
} }
// 尝试作为base64处理 // 尝试作为base64处理
@@ -268,7 +266,6 @@ export default function DevicesPage() {
try { try {
// 确保base64字符串没有空格等干扰字符 // 确保base64字符串没有空格等干扰字符
const cleanedBase64 = qrcodeData.trim(); const cleanedBase64 = qrcodeData.trim();
console.log("处理后的base64数据:", cleanedBase64.substring(0, 30) + "...");
// 直接以图片src格式设置 // 直接以图片src格式设置
setQrCodeImage(`data:image/png;base64,${cleanedBase64}`); setQrCodeImage(`data:image/png;base64,${cleanedBase64}`);
@@ -276,10 +273,9 @@ export default function DevicesPage() {
// 预加载图片,确认是否有效 // 预加载图片,确认是否有效
const img = new Image(); const img = new Image();
img.onload = () => { img.onload = () => {
console.log("二维码图片加载成功"); // 图片加载成功
}; };
img.onerror = (e) => { img.onerror = (e) => {
console.error("二维码图片加载失败:", e);
toast({ toast({
title: "二维码加载失败", title: "二维码加载失败",
description: "服务器返回的数据无法显示为图片", description: "服务器返回的数据无法显示为图片",
@@ -288,7 +284,6 @@ export default function DevicesPage() {
}; };
img.src = `data:image/png;base64,${cleanedBase64}`; img.src = `data:image/png;base64,${cleanedBase64}`;
} catch (e) { } catch (e) {
console.error("处理base64数据出错:", e);
toast({ toast({
title: "获取二维码失败", title: "获取二维码失败",
description: "图片数据处理失败", description: "图片数据处理失败",
@@ -303,7 +298,6 @@ export default function DevicesPage() {
description: "请使用手机扫描新的二维码添加设备", description: "请使用手机扫描新的二维码添加设备",
}); });
} else { } else {
console.error("获取二维码失败:", result);
toast({ toast({
title: "获取二维码失败", title: "获取二维码失败",
description: result?.msg || "请稍后重试", description: result?.msg || "请稍后重试",
@@ -311,7 +305,6 @@ export default function DevicesPage() {
}); });
} }
} catch (error) { } catch (error) {
console.error("获取二维码失败", error);
toast({ toast({
title: "获取二维码失败", title: "获取二维码失败",
description: "请检查网络连接后重试", description: "请检查网络连接后重试",
@@ -343,7 +336,6 @@ export default function DevicesPage() {
try { try {
setIsSubmittingImei(true); setIsSubmittingImei(true);
console.log("正在添加设备IMEI:", deviceImei, "设备名称:", deviceName);
// 使用api.post发送请求到/v1/devices // 使用api.post发送请求到/v1/devices
const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/devices`, { const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/devices`, {
@@ -359,18 +351,14 @@ export default function DevicesPage() {
}) })
}); });
console.log("添加设备响应状态:", response.status);
// 保存原始响应文本以便调试 // 保存原始响应文本以便调试
const responseText = await response.text(); const responseText = await response.text();
console.log("原始响应内容:", responseText);
// 尝试将响应解析为JSON // 尝试将响应解析为JSON
let result; let result;
try { try {
result = JSON.parse(responseText); result = JSON.parse(responseText);
} catch (e) { } catch (e) {
console.error("响应不是有效的JSON:", e);
toast({ toast({
title: "添加设备失败", title: "添加设备失败",
description: "服务器返回的数据格式无效", description: "服务器返回的数据格式无效",
@@ -379,8 +367,6 @@ export default function DevicesPage() {
return; return;
} }
console.log("添加设备响应:", result);
if (result && result.code === 200) { if (result && result.code === 200) {
toast({ toast({
title: "设备添加成功", title: "设备添加成功",
@@ -395,7 +381,6 @@ export default function DevicesPage() {
// 刷新设备列表 // 刷新设备列表
loadDevices(1, true); loadDevices(1, true);
} else { } else {
console.error("添加设备失败:", result);
toast({ toast({
title: "添加设备失败", title: "添加设备失败",
description: result?.msg || "请检查设备信息是否正确", description: result?.msg || "请检查设备信息是否正确",
@@ -403,7 +388,6 @@ export default function DevicesPage() {
}); });
} }
} catch (error) { } catch (error) {
console.error("添加设备请求失败:", error);
toast({ toast({
title: "请求失败", title: "请求失败",
description: "网络错误,请稍后重试", description: "网络错误,请稍后重试",
@@ -675,7 +659,7 @@ export default function DevicesPage() {
</DialogHeader> </DialogHeader>
<Tabs defaultValue="scan" value={activeTab} onValueChange={setActiveTab} className="mt-4"> <Tabs defaultValue="scan" value={activeTab} onValueChange={setActiveTab} className="mt-4">
<TabsList className="grid grid-cols-2 w-full"> {/* <TabsList className="grid grid-cols-2 w-full">
<TabsTrigger value="scan" className="flex items-center"> <TabsTrigger value="scan" className="flex items-center">
<QrCode className="h-4 w-4 mr-2" /> <QrCode className="h-4 w-4 mr-2" />
扫码添加 扫码添加
@@ -684,7 +668,7 @@ export default function DevicesPage() {
<Smartphone className="h-4 w-4 mr-2" /> <Smartphone className="h-4 w-4 mr-2" />
手动添加 手动添加
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList> */}
<TabsContent value="scan" className="space-y-4 py-4"> <TabsContent value="scan" className="space-y-4 py-4">
<div className="flex flex-col items-center justify-center p-6 space-y-4"> <div className="flex flex-col items-center justify-center p-6 space-y-4">

View File

@@ -20,18 +20,23 @@ const menuItems = [
export default function ProfilePage() { export default function ProfilePage() {
const router = useRouter() const router = useRouter()
const { isAuthenticated, user, logout } = useAuth()
const [showLogoutDialog, setShowLogoutDialog] = useState(false) const [showLogoutDialog, setShowLogoutDialog] = useState(false)
const [userInfo, setUserInfo] = useState<any>(null)
// 处理身份验证状态将路由重定向逻辑移至useEffect // 从localStorage获取用户信息
useEffect(() => { useEffect(() => {
if (!isAuthenticated) { const userInfoStr = localStorage.getItem('userInfo')
router.push("/login") if (userInfoStr) {
setUserInfo(JSON.parse(userInfoStr))
} }
}, [isAuthenticated, router]) }, [])
const handleLogout = () => { const handleLogout = () => {
logout() // 使用AuthProvider中的logout方法删除本地存的用户信息 // 清除本地存的用户信息
localStorage.removeItem('token')
localStorage.removeItem('token_expired')
localStorage.removeItem('s2_accountId')
localStorage.removeItem('userInfo')
setShowLogoutDialog(false) setShowLogoutDialog(false)
router.push("/login") router.push("/login")
} }
@@ -57,14 +62,14 @@ export default function ProfilePage() {
<Card className="p-6"> <Card className="p-6">
<div className="flex items-center space-x-4"> <div className="flex items-center space-x-4">
<Avatar className="w-20 h-20"> <Avatar className="w-20 h-20">
<AvatarImage src={user?.avatar || ""} /> <AvatarImage src={userInfo?.avatar || ""} />
<AvatarFallback>{user?.username ? user.username.slice(0, 2) : "用户"}</AvatarFallback> <AvatarFallback>{userInfo?.username ? userInfo.username.slice(0, 2) : "用户"}</AvatarFallback>
</Avatar> </Avatar>
<div className="flex-1"> <div className="flex-1">
<h2 className="text-xl font-semibold text-blue-600">{user?.username || "用户"}</h2> <h2 className="text-xl font-semibold text-blue-600">{userInfo?.username || "用户"}</h2>
<p className="text-gray-500"> <p className="text-gray-500">
: <ClientOnly fallback="加载中..."> : <ClientOnly fallback="加载中...">
{user?.account || Math.floor(10000000 + Math.random() * 90000000).toString()} {userInfo?.account || Math.floor(10000000 + Math.random() * 90000000).toString()}
</ClientOnly> </ClientOnly>
</p> </p>
<div className="mt-2"> <div className="mt-2">

View File

@@ -9,6 +9,7 @@ import { BasicSettings } from "../components/basic-settings"
import { GroupSelector } from "../components/group-selector" import { GroupSelector } from "../components/group-selector"
import { ContentSelector } from "../components/content-selector" import { ContentSelector } from "../components/content-selector"
import type { WechatGroup, ContentLibrary } from "@/types/group-sync" import type { WechatGroup, ContentLibrary } from "@/types/group-sync"
import { toast } from "@/components/ui/use-toast"
const steps = [ const steps = [
{ id: 1, title: "步骤 1", subtitle: "基础设置" }, { id: 1, title: "步骤 1", subtitle: "基础设置" },
@@ -46,11 +47,39 @@ export default function NewGroupSyncPage() {
setFormData((prev) => ({ ...prev, contentLibraries })) setFormData((prev) => ({ ...prev, contentLibraries }))
} }
const handleSave = () => { const handleSubmit = async (formData: any) => {
// 这里可以添加保存逻辑例如API调用 try {
console.log("保存表单数据:", formData) const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/api/group-sync`, {
router.push("/workspace/group-sync") method: 'POST',
} headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify(formData)
});
const data = await response.json();
if (data.code === 200) {
toast({
title: "创建成功",
description: "群同步计划已创建",
});
router.push('/workspace/group-sync');
} else {
toast({
title: "创建失败",
description: data.msg || "请稍后重试",
variant: "destructive",
});
}
} catch (error) {
toast({
title: "创建失败",
description: "网络错误,请稍后重试",
variant: "destructive",
});
}
};
const handleCancel = () => { const handleCancel = () => {
router.push("/workspace/group-sync") router.push("/workspace/group-sync")
@@ -81,7 +110,7 @@ export default function NewGroupSyncPage() {
isEnabled: formData.isEnabled, isEnabled: formData.isEnabled,
}} }}
onNext={handleBasicSettingsNext} onNext={handleBasicSettingsNext}
onSave={handleSave} onSave={handleSubmit}
onCancel={handleCancel} onCancel={handleCancel}
/> />
)} )}
@@ -92,7 +121,7 @@ export default function NewGroupSyncPage() {
onGroupsChange={handleGroupsChange} onGroupsChange={handleGroupsChange}
onPrevious={() => setCurrentStep(1)} onPrevious={() => setCurrentStep(1)}
onNext={() => setCurrentStep(3)} onNext={() => setCurrentStep(3)}
onSave={handleSave} onSave={handleSubmit}
onCancel={handleCancel} onCancel={handleCancel}
/> />
)} )}
@@ -103,7 +132,7 @@ export default function NewGroupSyncPage() {
onLibrariesChange={handleLibrariesChange} onLibrariesChange={handleLibrariesChange}
onPrevious={() => setCurrentStep(2)} onPrevious={() => setCurrentStep(2)}
onNext={() => setCurrentStep(4)} onNext={() => setCurrentStep(4)}
onSave={handleSave} onSave={handleSubmit}
onCancel={handleCancel} onCancel={handleCancel}
/> />
)} )}
@@ -118,7 +147,7 @@ export default function NewGroupSyncPage() {
<Button type="button" variant="outline" onClick={() => setCurrentStep(3)}> <Button type="button" variant="outline" onClick={() => setCurrentStep(3)}>
</Button> </Button>
<Button type="button" onClick={handleSave}> <Button type="button" onClick={handleSubmit}>
</Button> </Button>
<Button type="button" variant="outline" onClick={handleCancel}> <Button type="button" variant="outline" onClick={handleCancel}>

View File

@@ -15,6 +15,7 @@ import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs"
import { Slider } from "@/components/ui/slider" import { Slider } from "@/components/ui/slider"
import { Badge } from "@/components/ui/badge" import { Badge } from "@/components/ui/badge"
import { TrafficPoolSelector } from "@/app/components/traffic-pool-selector" import { TrafficPoolSelector } from "@/app/components/traffic-pool-selector"
import { toast } from "@/components/ui/use-toast"
// 模拟数据 // 模拟数据
const planDetails = { const planDetails = {
@@ -123,11 +124,39 @@ export default function EditTrafficDistributionPage({ params }: { params: { id:
} }
} }
const handleSubmit = () => { const handleSubmit = async (formData: any) => {
// 这里处理表单提交逻辑 try {
console.log("提交表单数据:", formData) const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/api/traffic-distribution/${params.id}`, {
router.push(`/workspace/traffic-distribution/${params.id}`) method: 'PUT',
} headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${localStorage.getItem('token')}`
},
body: JSON.stringify(formData)
});
const data = await response.json();
if (data.code === 200) {
toast({
title: "保存成功",
description: "流量分配计划已更新",
});
router.push('/workspace/traffic-distribution');
} else {
toast({
title: "保存失败",
description: data.msg || "请稍后重试",
variant: "destructive",
});
}
} catch (error) {
toast({
title: "保存失败",
description: "网络错误,请稍后重试",
variant: "destructive",
});
}
};
const isStep1Valid = formData.name && formData.source const isStep1Valid = formData.name && formData.source
const isStep2Valid = formData.targetGroups.length > 0 || formData.targetDevices.length > 0 const isStep2Valid = formData.targetGroups.length > 0 || formData.targetDevices.length > 0
@@ -463,7 +492,7 @@ export default function EditTrafficDistributionPage({ params }: { params: { id:
<ArrowLeft className="mr-2 h-4 w-4" /> <ArrowLeft className="mr-2 h-4 w-4" />
</Button> </Button>
<Button onClick={handleSubmit} disabled={!isStep3Valid}> <Button onClick={() => handleSubmit(formData)} disabled={!isStep3Valid}>
</Button> </Button>
</div> </div>

View File

@@ -6,7 +6,6 @@ use app\common\model\User as UserModel;
use app\common\util\JwtUtil; use app\common\util\JwtUtil;
use Exception; use Exception;
use library\ResponseHelper; use library\ResponseHelper;
use think\response\Json;
use think\Validate; use think\Validate;
/** /**
@@ -24,12 +23,16 @@ class PasswordLoginController extends BaseController
*/ */
protected function getUserProfileWithAccountAndType(string $account, int $typeId): UserModel protected function getUserProfileWithAccountAndType(string $account, int $typeId): UserModel
{ {
$user = UserModel::where(function ($query) use ($account) { $user = UserModel::where(
$query->where('phone', $account)->whereOr('account', $account); function ($query) use ($account) {
}) $query->where('phone', $account)->whereOr('account', $account);
->where(function ($query) use ($typeId) { }
$query->where('status', 1)->where('typeId', $typeId); )
})->find(); ->where(
function ($query) use ($typeId) {
$query->where('status', 1)->where('typeId', $typeId);
}
)->find();
return $user; return $user;
} }
@@ -54,7 +57,10 @@ class PasswordLoginController extends BaseController
throw new \Exception('账号或密码错误', 403); throw new \Exception('账号或密码错误', 403);
} }
return $user->toArray(); return array_merge($user->toArray(), [
'lastLoginIp' => $this->request->ip(),
'lastLoginTime' => time()
]);
} }
/** /**
@@ -100,8 +106,8 @@ class PasswordLoginController extends BaseController
$member = $this->getUser($account, $password, $typeId); $member = $this->getUser($account, $password, $typeId);
// 生成JWT令牌 // 生成JWT令牌
$token = JwtUtil::createToken($member, 7200); $token = JwtUtil::createToken($member, 86400);
$token_expired = time() + 7200; $token_expired = time() + 86400;
return compact('member', 'token', 'token_expired'); return compact('member', 'token', 'token_expired');
} }
@@ -109,7 +115,7 @@ class PasswordLoginController extends BaseController
/** /**
* 用户登录 * 用户登录
* *
* @return Json * @return \think\response\Json
*/ */
public function index() public function index()
{ {