私域操盘手 - 计划任务列表
This commit is contained in:
@@ -154,6 +154,7 @@ export default function Home() {
|
||||
color: "bg-orange-100 text-orange-600",
|
||||
value: 167,
|
||||
growth: 10,
|
||||
sceneId: 7,
|
||||
},
|
||||
]
|
||||
|
||||
@@ -209,7 +210,11 @@ export default function Home() {
|
||||
{scenarioFeatures
|
||||
.sort((a, b) => b.value - a.value)
|
||||
.map((scenario) => (
|
||||
<Link href={`/scenarios/${scenario.id}`} key={scenario.id} className="block flex-1">
|
||||
<div
|
||||
key={scenario.id}
|
||||
className="block flex-1 cursor-pointer"
|
||||
onClick={() => router.push(`/scenarios/${scenario.id}?id=${scenario.sceneId || getSceneIdFromType(scenario.id)}`)}
|
||||
>
|
||||
<div className="flex flex-col items-center text-center space-y-2">
|
||||
<div className={`w-12 h-12 rounded-full ${scenario.color} flex items-center justify-center`}>
|
||||
<img src={scenario.icon || "/placeholder.svg"} alt={scenario.name} className="w-6 h-6" />
|
||||
@@ -219,7 +224,7 @@ export default function Home() {
|
||||
{scenario.name}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</Card>
|
||||
@@ -236,3 +241,18 @@ export default function Home() {
|
||||
)
|
||||
}
|
||||
|
||||
function getSceneIdFromType(type: string): number {
|
||||
const typeToIdMap: Record<string, number> = {
|
||||
'douyin': 1,
|
||||
'xiaohongshu': 2,
|
||||
'weixinqun': 3,
|
||||
'gongzhonghao': 4,
|
||||
'kuaishou': 5,
|
||||
'weibo': 6,
|
||||
'haibao': 7,
|
||||
'phone': 8,
|
||||
'api': 9
|
||||
};
|
||||
return typeToIdMap[type] || 1;
|
||||
}
|
||||
|
||||
|
||||
@@ -123,14 +123,82 @@ export default function NewAcquisitionPlan() {
|
||||
// 根据标签和计划名称自动判断场景
|
||||
const scenario = formData.scenario || determineScenario(formData.planName, formData.tags)
|
||||
|
||||
console.log("计划已创建:", { ...formData, scenario })
|
||||
toast({
|
||||
title: "创建成功",
|
||||
description: "获客计划已创建完成",
|
||||
})
|
||||
// 准备请求数据
|
||||
const requestData = {
|
||||
sceneId: getSceneIdFromScenario(scenario), // 获取场景ID
|
||||
name: formData.planName,
|
||||
status: formData.enabled ? 1 : 0,
|
||||
reqConf: JSON.stringify({
|
||||
remarkType: formData.remarkType,
|
||||
remarkKeyword: formData.remarkKeyword,
|
||||
greeting: formData.greeting,
|
||||
addFriendTimeStart: formData.addFriendTimeStart,
|
||||
addFriendTimeEnd: formData.addFriendTimeEnd,
|
||||
addFriendInterval: formData.addFriendInterval,
|
||||
maxDailyFriends: formData.maxDailyFriends,
|
||||
selectedDevices: formData.selectedDevices,
|
||||
}),
|
||||
msgConf: JSON.stringify({
|
||||
messageInterval: formData.messageInterval,
|
||||
messagePlans: formData.messagePlans,
|
||||
}),
|
||||
tagConf: JSON.stringify({
|
||||
tags: formData.tags,
|
||||
importedTags: formData.importedTags,
|
||||
})
|
||||
}
|
||||
|
||||
// 跳转到首页
|
||||
router.push("/")
|
||||
// 调用API创建获客计划
|
||||
fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/plan/scenes/create`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
},
|
||||
body: JSON.stringify(requestData)
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.code === 200) {
|
||||
toast({
|
||||
title: "创建成功",
|
||||
description: "获客计划已创建完成",
|
||||
})
|
||||
// 跳转到首页
|
||||
router.push("/")
|
||||
} else {
|
||||
toast({
|
||||
title: "创建失败",
|
||||
description: data.msg || "服务器错误,请稍后重试",
|
||||
variant: "destructive"
|
||||
})
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("创建获客计划失败:", error);
|
||||
toast({
|
||||
title: "创建失败",
|
||||
description: "网络错误,请稍后重试",
|
||||
variant: "destructive"
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 将场景类型转换为场景ID
|
||||
const getSceneIdFromScenario = (scenario: string): number => {
|
||||
const scenarioMap: Record<string, number> = {
|
||||
'douyin': 1,
|
||||
'xiaohongshu': 2,
|
||||
'weixinqun': 3,
|
||||
'gongzhonghao': 4,
|
||||
'kuaishou': 5,
|
||||
'weibo': 6,
|
||||
'haibao': 7,
|
||||
'phone': 8,
|
||||
'order': 9,
|
||||
'other': 10
|
||||
}
|
||||
return scenarioMap[scenario] || 1 // 默认返回1
|
||||
}
|
||||
|
||||
const handlePrev = () => {
|
||||
@@ -206,7 +274,7 @@ export default function NewAcquisitionPlan() {
|
||||
case 3:
|
||||
return <MessageSettings formData={formData} onChange={setFormData} onNext={handleNext} onPrev={handlePrev} />
|
||||
case 4:
|
||||
return <TagSettings formData={formData} onComplete={handleSave} onPrev={handlePrev} onChange={setFormData} />
|
||||
return <TagSettings formData={formData} onNext={handleSave} onPrev={handlePrev} onChange={setFormData} />
|
||||
default:
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import { useState, useEffect } from "react"
|
||||
import { ChevronLeft, Copy, Link, HelpCircle } from "lucide-react"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { useRouter } from "next/navigation"
|
||||
@@ -35,6 +35,7 @@ const getChannelName = (channel: string) => {
|
||||
return channelMap[channel] || channel
|
||||
}
|
||||
|
||||
// 恢复Task接口定义
|
||||
interface Task {
|
||||
id: string
|
||||
name: string
|
||||
@@ -50,6 +51,21 @@ interface Task {
|
||||
trend: { date: string; customers: number }[]
|
||||
}
|
||||
|
||||
interface PlanItem {
|
||||
id: number;
|
||||
name: string;
|
||||
status: number;
|
||||
statusText: string;
|
||||
createTime: number;
|
||||
createTimeFormat: string;
|
||||
deviceCount: number;
|
||||
customerCount: number;
|
||||
addedCount: number;
|
||||
passRate: number;
|
||||
lastExecutionTime: string;
|
||||
nextExecutionTime: string;
|
||||
}
|
||||
|
||||
interface DeviceStats {
|
||||
active: number
|
||||
}
|
||||
@@ -77,6 +93,23 @@ export default function ChannelPage({ params }: { params: { channel: string } })
|
||||
const channel = params.channel
|
||||
const channelName = getChannelName(params.channel)
|
||||
|
||||
// 从URL query参数获取场景ID
|
||||
const [sceneId, setSceneId] = useState<number | null>(null);
|
||||
|
||||
// 获取URL中的查询参数
|
||||
useEffect(() => {
|
||||
// 从URL获取id参数
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const idParam = urlParams.get('id');
|
||||
|
||||
if (idParam && !isNaN(Number(idParam))) {
|
||||
setSceneId(Number(idParam));
|
||||
} else {
|
||||
// 如果没有传递有效的ID,使用函数获取默认ID
|
||||
setSceneId(getSceneIdFromChannel(channel));
|
||||
}
|
||||
}, [channel]);
|
||||
|
||||
const initialTasks = [
|
||||
{
|
||||
id: "1",
|
||||
@@ -114,7 +147,9 @@ export default function ChannelPage({ params }: { params: { channel: string } })
|
||||
},
|
||||
]
|
||||
|
||||
const [tasks, setTasks] = useState<Task[]>(initialTasks)
|
||||
const [tasks, setTasks] = useState<Task[]>([])
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
|
||||
const [deviceStats, setDeviceStats] = useState<DeviceStats>({
|
||||
active: 5,
|
||||
@@ -144,7 +179,7 @@ export default function ChannelPage({ params }: { params: { channel: string } })
|
||||
toast({
|
||||
title: "计划已复制",
|
||||
description: `已成功复制"${taskToCopy.name}"`,
|
||||
variant: "success",
|
||||
variant: "default",
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -156,7 +191,7 @@ export default function ChannelPage({ params }: { params: { channel: string } })
|
||||
toast({
|
||||
title: "计划已删除",
|
||||
description: `已成功删除"${taskToDelete.name}"`,
|
||||
variant: "success",
|
||||
variant: "default",
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -167,7 +202,7 @@ export default function ChannelPage({ params }: { params: { channel: string } })
|
||||
toast({
|
||||
title: newStatus === "running" ? "计划已启动" : "计划已暂停",
|
||||
description: `已${newStatus === "running" ? "启动" : "暂停"}获客计划`,
|
||||
variant: "success",
|
||||
variant: "default",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -192,10 +227,95 @@ export default function ChannelPage({ params }: { params: { channel: string } })
|
||||
toast({
|
||||
title: "已复制",
|
||||
description: withParams ? "接口地址(含示例参数)已复制到剪贴板" : "接口地址已复制到剪贴板",
|
||||
variant: "success",
|
||||
variant: "default",
|
||||
})
|
||||
}
|
||||
|
||||
// 修改API数据处理部分
|
||||
useEffect(() => {
|
||||
const fetchPlanList = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
// 如果sceneId还未确定,则等待
|
||||
if (sceneId === null) return;
|
||||
|
||||
// 调用API获取计划列表
|
||||
const response = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/plan/list?id=${sceneId}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.code === 1 && data.data && data.data.list) {
|
||||
// 将API返回的数据转换为前端展示格式
|
||||
const transformedTasks = data.data.list.map((item: PlanItem) => {
|
||||
// 确保返回的对象完全符合Task类型
|
||||
const status: "running" | "paused" | "completed" =
|
||||
item.status === 1 ? "running" : "paused";
|
||||
|
||||
return {
|
||||
id: item.id.toString(),
|
||||
name: item.name,
|
||||
status: status,
|
||||
stats: {
|
||||
devices: item.deviceCount,
|
||||
acquired: item.customerCount,
|
||||
added: item.addedCount,
|
||||
},
|
||||
lastUpdated: item.createTimeFormat,
|
||||
executionTime: item.lastExecutionTime || "--",
|
||||
nextExecutionTime: item.nextExecutionTime || "--",
|
||||
trend: Array.from({ length: 7 }, (_, i) => ({
|
||||
date: `2月${String(i + 1)}日`,
|
||||
customers: Math.floor(Math.random() * 20) + 10, // 模拟数据
|
||||
})),
|
||||
};
|
||||
});
|
||||
|
||||
// 使用类型断言解决类型冲突
|
||||
setTasks(transformedTasks as Task[]);
|
||||
setError(null);
|
||||
} else {
|
||||
setError(data.msg || "获取计划列表失败");
|
||||
// 如果API返回错误,使用初始数据
|
||||
setTasks(initialTasks);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error("获取计划列表失败:", err);
|
||||
setError("网络错误,无法获取计划列表");
|
||||
// 出错时使用初始数据
|
||||
setTasks(initialTasks);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPlanList();
|
||||
}, [channel, initialTasks, sceneId]);
|
||||
|
||||
// 辅助函数:根据渠道获取场景ID
|
||||
const getSceneIdFromChannel = (channel: string): number => {
|
||||
const channelMap: Record<string, number> = {
|
||||
'douyin': 1,
|
||||
'xiaohongshu': 2,
|
||||
'weixinqun': 3,
|
||||
'gongzhonghao': 4,
|
||||
'kuaishou': 5,
|
||||
'weibo': 6,
|
||||
'haibao': 7,
|
||||
'phone': 8,
|
||||
'api': 9
|
||||
};
|
||||
return channelMap[channel] || 6;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex-1 bg-gradient-to-b from-blue-50 to-white min-h-screen">
|
||||
<header className="sticky top-0 z-10 bg-white/80 backdrop-blur-sm border-b">
|
||||
@@ -210,7 +330,21 @@ export default function ChannelPage({ params }: { params: { channel: string } })
|
||||
</header>
|
||||
|
||||
<div className="p-4 max-w-7xl mx-auto">
|
||||
{tasks.length > 0 ? (
|
||||
{loading ? (
|
||||
// 添加加载状态
|
||||
<div className="text-center py-12 bg-white rounded-lg shadow-sm">
|
||||
<div className="w-8 h-8 border-2 border-blue-600 border-t-transparent rounded-full animate-spin mx-auto mb-4"></div>
|
||||
<div className="text-gray-500">加载计划中...</div>
|
||||
</div>
|
||||
) : error ? (
|
||||
// 添加错误提示
|
||||
<div className="text-center py-12 bg-white rounded-lg shadow-sm">
|
||||
<div className="text-red-500 mb-4">{error}</div>
|
||||
<Button variant="outline" onClick={() => window.location.reload()}>
|
||||
重新加载
|
||||
</Button>
|
||||
</div>
|
||||
) : tasks.length > 0 ? (
|
||||
tasks.map((task) => (
|
||||
<div key={task.id} className="mb-6">
|
||||
<ScenarioAcquisitionCard
|
||||
@@ -254,7 +388,7 @@ export default function ChannelPage({ params }: { params: { channel: string } })
|
||||
toast({
|
||||
title: "已复制",
|
||||
description: "API密钥已复制到剪贴板",
|
||||
variant: "success",
|
||||
variant: "default",
|
||||
})
|
||||
}}
|
||||
>
|
||||
|
||||
@@ -81,4 +81,14 @@ Route::group('v1/', function () {
|
||||
|
||||
});
|
||||
|
||||
// 计划任务相关路由
|
||||
Route::group('plan', function () {
|
||||
// 添加计划任务
|
||||
Route::post('add', 'app\cunkebao\controller\Plan@index');
|
||||
// 获取计划任务列表
|
||||
Route::get('list', 'app\cunkebao\controller\Plan@getList');
|
||||
});
|
||||
|
||||
})->middleware(['jwt']);
|
||||
|
||||
return [];
|
||||
@@ -3,6 +3,9 @@
|
||||
namespace app\cunkebao\controller;
|
||||
|
||||
use think\Controller;
|
||||
use think\Db;
|
||||
use think\facade\Request;
|
||||
use library\ResponseHelper;
|
||||
|
||||
/**
|
||||
* 获客场景控制器
|
||||
@@ -10,12 +13,242 @@ use think\Controller;
|
||||
class Plan extends Controller
|
||||
{
|
||||
/**
|
||||
* 计划任务
|
||||
* 添加计划任务
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
try {
|
||||
// 获取表单数据
|
||||
$data = [
|
||||
'name' => Request::post('name', ''),
|
||||
'sceneId' => Request::post('sceneId', 0),
|
||||
'status' => Request::post('status', 0),
|
||||
'reqConf' => Request::post('reqConf', ''),
|
||||
'msgConf' => Request::post('msgConf', ''),
|
||||
'tagConf' => Request::post('tagConf', ''),
|
||||
'createTime' => time(),
|
||||
'updateTime' => time()
|
||||
];
|
||||
|
||||
// 验证必填字段
|
||||
if (empty($data['name'])) {
|
||||
return ResponseHelper::error('计划名称不能为空', 400);
|
||||
}
|
||||
|
||||
if (empty($data['sceneId'])) {
|
||||
return ResponseHelper::error('场景ID不能为空', 400);
|
||||
}
|
||||
|
||||
// 验证数据格式
|
||||
if (!$this->validateJson($data['reqConf'])) {
|
||||
return ResponseHelper::error('好友申请设置格式不正确', 400);
|
||||
}
|
||||
|
||||
if (!$this->validateJson($data['msgConf'])) {
|
||||
return ResponseHelper::error('消息设置格式不正确', 400);
|
||||
}
|
||||
|
||||
if (!$this->validateJson($data['tagConf'])) {
|
||||
return ResponseHelper::error('标签设置格式不正确', 400);
|
||||
}
|
||||
|
||||
// 插入数据库
|
||||
$result = Db::name('friend_plan')->insert($data);
|
||||
|
||||
if ($result) {
|
||||
return ResponseHelper::success([], '添加计划任务成功');
|
||||
} else {
|
||||
return ResponseHelper::error('添加计划任务失败', 500);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('系统错误: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划任务列表
|
||||
*
|
||||
* @return \think\response\Json
|
||||
*/
|
||||
public function getList()
|
||||
{
|
||||
try {
|
||||
// 获取分页参数
|
||||
$id = Request::param('id', 1);
|
||||
$page = Request::param('page', 1);
|
||||
$pageSize = Request::param('pageSize', 10);
|
||||
|
||||
// 构建查询条件
|
||||
$where = [];
|
||||
|
||||
// 过滤已删除的记录
|
||||
$where[] = ['deleteTime', 'null'];
|
||||
|
||||
// 查询总数
|
||||
$total = Db::name('friend_plan')->where('sceneId', $id)->count();
|
||||
|
||||
// 查询列表数据
|
||||
$list = Db::name('friend_plan')
|
||||
->where('sceneId', $id)
|
||||
->field('id, name, status, createTime, updateTime, sceneId')
|
||||
->order('createTime desc')
|
||||
->page($page, $pageSize)
|
||||
->select();
|
||||
// 遍历列表,获取每个计划的统计信息
|
||||
foreach ($list as &$item) {
|
||||
// 获取计划的统计信息
|
||||
$stats = $this->getPlanStats($item['id']);
|
||||
|
||||
// 合并统计信息到结果中
|
||||
$item = array_merge($item, $stats);
|
||||
|
||||
// 格式化状态为文字描述
|
||||
$item['statusText'] = $item['status'] == 1 ? '进行中' : '已暂停';
|
||||
|
||||
// 格式化时间
|
||||
$item['createTimeFormat'] = date('Y-m-d H:i', $item['createTime']);
|
||||
|
||||
// 获取最近一次执行时间
|
||||
$lastExecution = $this->getLastExecution($item['id']);
|
||||
$item['lastExecutionTime'] = $lastExecution['lastTime'] ?? '';
|
||||
$item['nextExecutionTime'] = $lastExecution['nextTime'] ?? '';
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
$result = [
|
||||
'total' => $total,
|
||||
'list' => $list,
|
||||
'page' => $page,
|
||||
'pageSize' => $pageSize
|
||||
];
|
||||
|
||||
return ResponseHelper::success($result);
|
||||
} catch (\Exception $e) {
|
||||
return ResponseHelper::error('获取数据失败: ' . $e->getMessage(), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划的统计信息
|
||||
*
|
||||
* @param int $planId 计划ID
|
||||
* @return array
|
||||
*/
|
||||
private function getPlanStats($planId)
|
||||
{
|
||||
try {
|
||||
// 获取设备数
|
||||
$deviceCount = $this->getDeviceCount($planId);
|
||||
|
||||
// 获取已获客数
|
||||
$customerCount = $this->getCustomerCount($planId);
|
||||
|
||||
// 获取已添加数
|
||||
$addedCount = 1; //$this->getAddedCount($planId);
|
||||
|
||||
// 计算通过率
|
||||
$passRate = $customerCount > 0 ? round(($addedCount / $customerCount) * 100) : 0;
|
||||
|
||||
return [
|
||||
'deviceCount' => $deviceCount,
|
||||
'customerCount' => $customerCount,
|
||||
'addedCount' => $addedCount,
|
||||
'passRate' => $passRate
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
return [
|
||||
'deviceCount' => 0,
|
||||
'customerCount' => 0,
|
||||
'addedCount' => 0,
|
||||
'passRate' => 0
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划使用的设备数
|
||||
*
|
||||
* @param int $planId 计划ID
|
||||
* @return int
|
||||
*/
|
||||
private function getDeviceCount($planId)
|
||||
{
|
||||
try {
|
||||
// 获取计划
|
||||
$plan = Db::name('friend_plan')->where('id', $planId)->find();
|
||||
if (!$plan) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// 解析reqConf
|
||||
$reqConf = json_decode($plan['reqConf'], true);
|
||||
|
||||
// 返回设备数量
|
||||
return isset($reqConf['selectedDevices']) ? count($reqConf['selectedDevices']) : 0;
|
||||
} catch (\Exception $e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划的已获客数
|
||||
*
|
||||
* @param int $planId 计划ID
|
||||
* @return int
|
||||
*/
|
||||
private function getCustomerCount($planId)
|
||||
{
|
||||
// 模拟数据,实际应从相关表获取
|
||||
return rand(10, 50);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划的已添加数
|
||||
*
|
||||
* @param int $planId 计划ID
|
||||
* @return int
|
||||
*/
|
||||
private function getAddedCount($planId)
|
||||
{
|
||||
// 模拟数据,实际应从相关表获取
|
||||
$customerCount = $this->getCustomerCount($planId);
|
||||
return rand(5, $customerCount);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取计划的最近一次执行时间
|
||||
*
|
||||
* @param int $planId 计划ID
|
||||
* @return array
|
||||
*/
|
||||
private function getLastExecution($planId)
|
||||
{
|
||||
// 模拟数据,实际应从执行记录表获取
|
||||
$now = time();
|
||||
$lastTime = $now - rand(3600, 86400);
|
||||
$nextTime = $now + rand(3600, 86400);
|
||||
|
||||
return [
|
||||
'lastTime' => date('Y-m-d H:i', $lastTime),
|
||||
'nextTime' => date('Y-m-d H:i:s', $nextTime)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证JSON格式是否正确
|
||||
*
|
||||
* @param string $string
|
||||
* @return bool
|
||||
*/
|
||||
private function validateJson($string)
|
||||
{
|
||||
if (empty($string)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
json_decode($string);
|
||||
return (json_last_error() == JSON_ERROR_NONE);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user