diff --git a/Server/application/api/controller/BaseController.php b/Server/application/api/controller/BaseController.php index de73bfca..84f69932 100644 --- a/Server/application/api/controller/BaseController.php +++ b/Server/application/api/controller/BaseController.php @@ -6,34 +6,21 @@ use think\Controller; use think\facade\Env; use app\common\service\AuthService; -class BaseController extends Controller { - - +class BaseController extends Controller +{ /** * 令牌 * * @var string */ protected $token = ''; - protected $baseUrl; - protected $authorization = ''; - public function __construct() { + public function __construct() + { parent::__construct(); $this->baseUrl = Env::get('api.wechat_url'); - // 允许跨域 - header('Access-Control-Allow-Origin: *'); - header('Access-Control-Allow-Methods: *'); - header('Access-Control-Allow-Headers: *'); - $this->authorization = AuthService::getSystemAuthorization(); - - } - - - - } diff --git a/Server/application/common/middleware/AllowCrossDomain.php b/Server/application/common/middleware/AllowCrossDomain.php index 6046fe3f..096ce7ca 100644 --- a/Server/application/common/middleware/AllowCrossDomain.php +++ b/Server/application/common/middleware/AllowCrossDomain.php @@ -14,46 +14,46 @@ class AllowCrossDomain */ public function handle($request, \Closure $next) { - // 获取当前请求的域名 - $origin = $request->header('origin'); - - // 当请求使用 credentials 模式时,不能使用通配符 - // 必须指定具体的域名或提取请求中的 Origin - $allowOrigin = '*'; - if ($origin) { - // 如果需要限制特定域名,可以在这里判断 - // 以下是允许的域名列表,如果请求来自这些域名之一,则允许跨域 - $allowDomains = [ /* */ ]; - - // 如果请求来源在允许列表中,直接使用该源 - if (in_array($origin, $allowDomains)) { - $allowOrigin = $origin; - } - } - - // 设置允许的请求头信息 - $allowHeaders = [ - 'Authorization', 'Content-Type', 'If-Match', 'If-Modified-Since', - 'If-None-Match', 'If-Unmodified-Since', 'X-Requested-With', - 'X-Token', 'X-Api-Token', 'Accept', 'Origin' - ]; - - $response = $next($request); - - // 添加跨域响应头 - $response->header([ - 'Access-Control-Allow-Origin' => $allowOrigin, - 'Access-Control-Allow-Headers' => implode(', ', $allowHeaders), - 'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS', - 'Access-Control-Allow-Credentials' => 'true', - 'Access-Control-Max-Age' => '86400', - ]); - - // 对于预检请求,直接返回成功响应 - if ($request->method(true) == 'OPTIONS') { - return response()->code(200); - } - - return $response; +// // 获取当前请求的域名 +// $origin = $request->header('origin'); +// +// // 当请求使用 credentials 模式时,不能使用通配符 +// // 必须指定具体的域名或提取请求中的 Origin +// $allowOrigin = '*'; +// if ($origin) { +// // 如果需要限制特定域名,可以在这里判断 +// // 以下是允许的域名列表,如果请求来自这些域名之一,则允许跨域 +// $allowDomains = [ /* */ ]; +// +// // 如果请求来源在允许列表中,直接使用该源 +// if (in_array($origin, $allowDomains)) { +// $allowOrigin = $origin; +// } +// } +// +// // 设置允许的请求头信息 +// $allowHeaders = [ +// 'Authorization', 'Content-Type', 'If-Match', 'If-Modified-Since', +// 'If-None-Match', 'If-Unmodified-Since', 'X-Requested-With', +// 'X-Token', 'X-Api-Token', 'Accept', 'Origin' +// ]; +// +// $response = $next($request); +// +// // 添加跨域响应头 +// $response->header([ +// 'Access-Control-Allow-Origin' => $allowOrigin, +// 'Access-Control-Allow-Headers' => implode(', ', $allowHeaders), +// 'Access-Control-Allow-Methods' => 'GET, POST, PUT, DELETE, OPTIONS', +// 'Access-Control-Allow-Credentials' => 'true', +// 'Access-Control-Max-Age' => '86400', +// ]); +// +// // 对于预检请求,直接返回成功响应 +// if ($request->method(true) == 'OPTIONS') { +// return response()->code(200); +// } +// +// return $response; } } \ No newline at end of file diff --git a/Server/config/cookie.php b/Server/config/cookie.php index 1de07082..de67bbdf 100644 --- a/Server/config/cookie.php +++ b/Server/config/cookie.php @@ -27,4 +27,6 @@ return [ 'httponly' => '', // 是否使用 setcookie 'setcookie' => true, + // 跨站需要 + 'samesite' => 'None', ]; diff --git a/Server/public/index.php b/Server/public/index.php index 854db7e9..eeaf5d0c 100644 --- a/Server/public/index.php +++ b/Server/public/index.php @@ -14,12 +14,10 @@ namespace think; ////处理跨域预检请求 if($_SERVER['REQUEST_METHOD'] == 'OPTIONS'){ - //允许的源域名 - header("Access-Control-Allow-Origin: *"); - //允许的请求头信息 - header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization"); - //允许的请求类型 - header('Access-Control-Allow-Methods: GET, POST, PUT,DELETE,OPTIONS,PATCH'); + header("Access-Control-Allow-Origin: " . (isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '*')); + header("Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Authorization, Cookie"); + header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS, PATCH'); + header("Access-Control-Allow-Credentials: true"); exit; } @@ -32,4 +30,4 @@ require __DIR__ . '/../thinkphp/base.php'; // 支持事先使用静态方法设置Request对象和Config对象 // 执行应用并响应 -Container::get('app')->run()->send(); +Container::get('app')->run()->send(); \ No newline at end of file diff --git a/Server/route/route.php b/Server/route/route.php index cd50328c..13581e1e 100644 --- a/Server/route/route.php +++ b/Server/route/route.php @@ -12,9 +12,9 @@ use think\facade\Route; // 允许跨域 - header('Access-Control-Allow-Origin: *'); + header('Access-Control-Allow-Origin: ' . (isset($_SERVER['HTTP_ORIGIN']) ? $_SERVER['HTTP_ORIGIN'] : '*')); header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS, PATCH'); - header('Access-Control-Allow-Headers: Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With, X-Token, X-Api-Token'); + header('Access-Control-Allow-Headers: Cookie, Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-Requested-With, X-Token, X-Api-Token'); header('Access-Control-Max-Age: 1728000'); header('Access-Control-Allow-Credentials: true'); diff --git a/SuperAdmin/app/dashboard/page.tsx b/SuperAdmin/app/dashboard/page.tsx index 0f602d49..962f1c9f 100644 --- a/SuperAdmin/app/dashboard/page.tsx +++ b/SuperAdmin/app/dashboard/page.tsx @@ -7,6 +7,7 @@ import { toast } from "sonner" import useAuthCheck from "@/hooks/useAuthCheck" import { getAdminInfo, getGreeting } from "@/lib/utils" import ClientOnly from "@/components/ClientOnly" +import { apiRequest } from '@/lib/api-utils' interface DashboardStats { companyCount: number @@ -28,16 +29,14 @@ export default function DashboardPage() { useAuthCheck() useEffect(() => { - const fetchData = async () => { - setIsLoading(true) + const fetchDashboardData = async () => { try { - // 获取统计信息 - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/dashboard/base`) - const data = await response.json() - if (data.code === 200) { - setStats(data.data) + setIsLoading(true) + const result = await apiRequest('/dashboard/base') + if (result.code === 200 && result.data) { + setStats(result.data) } else { - toast.error(data.msg || "获取统计信息失败") + toast.error(result.msg || "获取仪表盘数据失败") } // 获取用户信息 @@ -49,13 +48,14 @@ export default function DashboardPage() { } } catch (error) { - toast.error("网络错误,请稍后重试") + console.error("获取仪表盘数据失败:", error) + toast.error("网络错误,请稍后再试") } finally { setIsLoading(false) } } - fetchData() + fetchDashboardData() }, []) // 单独处理问候语,避免依赖问题 diff --git a/SuperAdmin/app/dashboard/projects/[id]/edit/page.tsx b/SuperAdmin/app/dashboard/projects/[id]/edit/page.tsx index 09d2b3a4..6bb29aa5 100644 --- a/SuperAdmin/app/dashboard/projects/[id]/edit/page.tsx +++ b/SuperAdmin/app/dashboard/projects/[id]/edit/page.tsx @@ -13,6 +13,7 @@ import Link from "next/link" import { toast, Toaster } from "sonner" import Image from "next/image" import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog" +import { apiRequest } from "@/lib/api-utils" // 为React.use添加类型声明 declare module 'react' { @@ -69,30 +70,14 @@ export default function EditProjectPage({ params }: { params: { id: string } }) useEffect(() => { const fetchProject = async () => { + setIsLoading(true) try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/detail/${id}`) + const result = await apiRequest(`/company/detail/${id}`) - // 检查响应状态 - if (!response.ok) { - toast.error(`获取失败: ${response.status} ${response.statusText}`); - setIsLoading(false); - return; - } - - // 检查内容类型是否为JSON - const contentType = response.headers.get("content-type"); - if (!contentType || !contentType.includes("application/json")) { - toast.error("服务器返回了非JSON格式的数据"); - setIsLoading(false); - return; - } - - const data = await response.json() - - if (data.code === 200) { - setProject(data.data) + if (result.code === 200) { + setProject(result.data) } else { - toast.error(data.msg || "获取项目信息失败") + toast.error(result.msg || "获取项目信息失败") } } catch (error) { toast.error("网络错误,请稍后重试") @@ -106,54 +91,25 @@ export default function EditProjectPage({ params }: { params: { id: string } }) const handleSubmit = async (e: React.FormEvent) => { e.preventDefault() - - if (password && password !== confirmPassword) { - toast.error("两次输入的密码不一致") - return - } - setIsSubmitting(true) try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/update`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - id: parseInt(id), - name: project?.name, - account: project?.account, - memo: project?.memo, - phone: project?.phone, - username: project?.username, - status: project?.status, - ...(password && { password }) - }), + const result = await apiRequest('/company/update', 'POST', { + id: id, + name: project?.name, + account: project?.account, + password: password, + memo: project?.memo, + phone: project?.phone, + username: project?.username, + status: project?.status.toString(), }) - - // 检查响应状态 - if (!response.ok) { - toast.error(`更新失败: ${response.status} ${response.statusText}`); - setIsSubmitting(false); - return; - } - - // 检查内容类型是否为JSON - const contentType = response.headers.get("content-type"); - if (!contentType || !contentType.includes("application/json")) { - toast.error("服务器返回了非JSON格式的数据"); - setIsSubmitting(false); - return; - } - const data = await response.json() - - if (data.code === 200) { - toast.success("更新成功") + if (result.code === 200) { + toast.success("项目更新成功") router.push("/dashboard/projects") } else { - toast.error(data.msg) + toast.error(result.msg || "更新失败") } } catch (error) { toast.error("网络错误,请稍后重试") @@ -177,33 +133,12 @@ export default function EditProjectPage({ params }: { params: { id: string } }) pollingCountRef.current = 0; try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/v1/api/device/add`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - accountId: project.s2_accountId - }), + const result = await apiRequest('/v1/api/device/add', 'POST', { + accountId: project.s2_accountId }) - - // 检查响应状态 - if (!response.ok) { - toast.error(`请求失败: ${response.status} ${response.statusText}`); - return; - } - - // 检查内容类型是否为JSON - const contentType = response.headers.get("content-type"); - if (!contentType || !contentType.includes("application/json")) { - toast.error("服务器返回了非JSON格式的数据"); - return; - } - const data = await response.json() - - if (data.code === 200 && data.data?.qrCode) { - setQrCodeData(data.data.qrCode) + if (result.code === 200 && result.data?.qrCode) { + setQrCodeData(result.data.qrCode) setIsModalOpen(true) // 五秒后开始轮询 @@ -211,7 +146,7 @@ export default function EditProjectPage({ params }: { params: { id: string } }) startPolling(); }, 5000); } else { - toast.error(data.msg || "获取设备二维码失败") + toast.error(result.msg || "获取设备二维码失败") } } catch (error) { toast.error("网络错误,请稍后重试") @@ -248,33 +183,11 @@ export default function EditProjectPage({ params }: { params: { id: string } }) } try { - const accountId = project.s2_accountId; - // 通过URL参数传递accountId - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/devices/add-results?accountId=${accountId}`, { - method: "GET", - headers: { - "Content-Type": "application/json" - } - }); + const result = await apiRequest(`/devices/add-results?accountId=${project.s2_accountId}`) - // 检查响应状态和内容类型 - if (!response.ok) { - console.error("轮询请求失败:", response.status, response.statusText); - return; - } - - // 检查内容类型是否为JSON - const contentType = response.headers.get("content-type"); - if (!contentType || !contentType.includes("application/json")) { - console.error("轮询请求返回的不是JSON格式:", contentType); - return; - } - - const data = await response.json(); - - if (data.code === 200) { + if (result.code === 200) { // 检查是否最后一次轮询且设备未添加 - if (pollingCountRef.current >= MAX_POLLING_COUNT && !data.added) { + if (pollingCountRef.current >= MAX_POLLING_COUNT && !result.data.added) { setPollingStatus("error"); setIsQrCodeBroken(true); stopPolling(); @@ -282,9 +195,9 @@ export default function EditProjectPage({ params }: { params: { id: string } }) } // 检查设备是否已添加成功 - if (data.added) { + if (result.data.added) { setPollingStatus("success"); - setAddedDevice(data.device); + setAddedDevice(result.data.device); stopPolling(); // 刷新设备列表 @@ -293,7 +206,7 @@ export default function EditProjectPage({ params }: { params: { id: string } }) } } else { // 请求失败但继续轮询 - console.error("轮询请求失败:", data.msg); + console.error("轮询请求失败:", result.msg); } } catch (error) { console.error("轮询请求出错:", error); @@ -304,27 +217,12 @@ export default function EditProjectPage({ params }: { params: { id: string } }) // 刷新项目数据的方法 const refreshProjectData = async () => { try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/detail/${id}`) + const result = await apiRequest(`/company/detail/${id}`) - // 检查响应状态 - if (!response.ok) { - toast.error(`刷新失败: ${response.status} ${response.statusText}`); - return; - } - - // 检查内容类型是否为JSON - const contentType = response.headers.get("content-type"); - if (!contentType || !contentType.includes("application/json")) { - toast.error("服务器返回了非JSON格式的数据"); - return; - } - - const data = await response.json() - - if (data.code === 200) { - setProject(data.data) + if (result.code === 200) { + setProject(result.data) } else { - toast.error(data.msg || "刷新项目信息失败") + toast.error(result.msg || "刷新项目信息失败") } } catch (error) { toast.error("网络错误,请稍后重试") diff --git a/SuperAdmin/app/dashboard/projects/[id]/page.tsx b/SuperAdmin/app/dashboard/projects/[id]/page.tsx index 28470f1e..21ced6b8 100644 --- a/SuperAdmin/app/dashboard/projects/[id]/page.tsx +++ b/SuperAdmin/app/dashboard/projects/[id]/page.tsx @@ -13,6 +13,7 @@ import { toast } from "sonner" import { use } from "react" import Image from "next/image" import { Badge } from "@/components/ui/badge" +import { apiRequest } from '@/lib/api-utils' interface ProjectProfile { id: number @@ -69,75 +70,68 @@ export default function ProjectDetailPage({ params }: ProjectDetailPageProps) { const [subUsers, setSubUsers] = useState([]) const [activeTab, setActiveTab] = useState("overview") - useEffect(() => { - const fetchProjectProfile = async () => { + const fetchProject = async () => { + try { setIsLoading(true) - try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/profile/${id}`) - const data = await response.json() - - if (data.code === 200) { - setProfile(data.data) - } else { - toast.error(data.msg || "获取项目信息失败") - } - } catch (error) { - toast.error("网络错误,请稍后重试") - } finally { - setIsLoading(false) + const result = await apiRequest(`/company/profile/${id}`) + if (result.code === 200 && result.data) { + setProfile(result.data) + } else { + toast.error(result.msg || "获取项目信息失败") } + } catch (error) { + console.error("获取项目信息失败:", error) + toast.error("网络错误,请稍后再试") + } finally { + setIsLoading(false) } + } - fetchProjectProfile() + useEffect(() => { + fetchProject() }, [id]) - useEffect(() => { - const fetchDevices = async () => { - if (activeTab === "devices") { - setIsDevicesLoading(true) - try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/devices?companyId=${id}`) - const data = await response.json() - - if (data.code === 200) { - setDevices(data.data) - } else { - toast.error(data.msg || "获取设备列表失败") - } - } catch (error) { - toast.error("网络错误,请稍后重试") - } finally { - setIsDevicesLoading(false) - } + const fetchDevices = async () => { + try { + setIsDevicesLoading(true) + const result = await apiRequest(`/company/devices?companyId=${id}`) + if (result.code === 200 && result.data) { + setDevices(result.data) + } else { + toast.error(result.msg || "获取设备列表失败") } + } catch (error) { + console.error("获取设备列表失败:", error) + toast.error("网络错误,请稍后再试") + } finally { + setIsDevicesLoading(false) } + } + useEffect(() => { fetchDevices() }, [activeTab, id]) useEffect(() => { const fetchSubUsers = async () => { - if (activeTab === "accounts") { - setIsSubUsersLoading(true) - try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/subusers?companyId=${id}`) - const data = await response.json() - - if (data.code === 200) { - setSubUsers(data.data) - } else { - toast.error(data.msg || "获取子账号列表失败") - } - } catch (error) { - toast.error("网络错误,请稍后重试") - } finally { - setIsSubUsersLoading(false) + setIsSubUsersLoading(true) + try { + const result = await apiRequest(`/company/subusers?companyId=${id}`) + + if (result.code === 200) { + setSubUsers(result.data) + } else { + toast.error(result.msg || "获取子账号列表失败") } + } catch (error) { + toast.error("网络错误,请稍后重试") + } finally { + setIsSubUsersLoading(false) } } fetchSubUsers() - }, [activeTab, id]) + }, [id]) if (isLoading) { return
加载中...
diff --git a/SuperAdmin/app/dashboard/projects/new/page.tsx b/SuperAdmin/app/dashboard/projects/new/page.tsx index efb4db6a..8097c7de 100644 --- a/SuperAdmin/app/dashboard/projects/new/page.tsx +++ b/SuperAdmin/app/dashboard/projects/new/page.tsx @@ -12,6 +12,7 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@ import { ArrowLeft } from "lucide-react" import Link from "next/link" import { toast, Toaster } from "sonner" +import { apiRequest } from "@/lib/api-utils" export default function NewProjectPage() { const router = useRouter() @@ -53,29 +54,21 @@ export default function NewProjectPage() { setIsSubmitting(true) try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/add`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - name: formData.name, - account: formData.account, - password: formData.password, - memo: formData.description, - phone: formData.phone, - username: formData.nickname, - status: parseInt(formData.status), - }), + const result = await apiRequest('/company/add', 'POST', { + name: formData.name, + account: formData.account, + password: formData.password, + memo: formData.description, + phone: formData.phone, + username: formData.nickname, + status: parseInt(formData.status), }) - const data = await response.json() - - if (data.code === 200) { - toast.success("创建成功") + if (result.code === 200) { + toast.success("项目创建成功") router.push("/dashboard/projects") } else { - toast.error(data.msg) + toast.error(result.msg || "创建失败") } } catch (error) { toast.error("网络错误,请稍后重试") diff --git a/SuperAdmin/app/dashboard/projects/page.tsx b/SuperAdmin/app/dashboard/projects/page.tsx index 96c66319..2c6fd089 100644 --- a/SuperAdmin/app/dashboard/projects/page.tsx +++ b/SuperAdmin/app/dashboard/projects/page.tsx @@ -19,6 +19,7 @@ import { import { Badge } from "@/components/ui/badge" import { PaginationControls } from "@/components/ui/pagination-controls" import { useTabContext } from "@/app/dashboard/layout" +import { apiRequest } from "@/lib/api-utils" interface Project { id: number @@ -73,15 +74,14 @@ export default function ProjectsPage() { const fetchProjects = async () => { setIsLoading(true) try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/list?page=${currentPage}&limit=${pageSize}`) - const data = await response.json() + const result = await apiRequest(`/company/list?page=${currentPage}&limit=${pageSize}`) - if (data.code === 200) { - setProjects(data.data.list) - setTotalItems(data.data.total) - setTotalPages(Math.ceil(data.data.total / pageSize)) + if (result.code === 200) { + setProjects(result.data.list) + setTotalItems(result.data.total) + setTotalPages(Math.ceil(result.data.total / pageSize)) } else { - toast.error(data.msg || "获取项目列表失败") + toast.error(result.msg || "获取项目列表失败") setProjects([]) setTotalItems(0) setTotalPages(0) @@ -124,33 +124,24 @@ export default function ProjectsPage() { setIsDeleting(true) try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/delete`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - id: deletingProjectId - }), + const result = await apiRequest('/company/delete', 'POST', { + id: deletingProjectId }) - const data = await response.json() - - if (data.code === 200) { + if (result.code === 200) { toast.success("删除成功") // Fetch projects again after delete const fetchProjects = async () => { setIsLoading(true) try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/list?page=${currentPage}&limit=${pageSize}`) - const data = await response.json() - if (data.code === 200) { - setProjects(data.data.list) - setTotalItems(data.data.total) - setTotalPages(Math.ceil(data.data.total / pageSize)) - if (currentPage > Math.ceil(data.data.total / pageSize) && Math.ceil(data.data.total / pageSize) > 0) { - setCurrentPage(Math.ceil(data.data.total / pageSize)); + const result = await apiRequest(`/company/list?page=${currentPage}&limit=${pageSize}`) + if (result.code === 200) { + setProjects(result.data.list) + setTotalItems(result.data.total) + setTotalPages(Math.ceil(result.data.total / pageSize)) + if (currentPage > Math.ceil(result.data.total / pageSize) && Math.ceil(result.data.total / pageSize) > 0) { + setCurrentPage(Math.ceil(result.data.total / pageSize)); } } else { setProjects([]); setTotalItems(0); setTotalPages(0); @@ -160,7 +151,7 @@ export default function ProjectsPage() { } fetchProjects(); } else { - toast.error(data.msg || "删除失败") + toast.error(result.msg || "删除失败") } } catch (error) { toast.error("网络错误,请稍后重试") diff --git a/SuperAdmin/components/projects/project-create.tsx b/SuperAdmin/components/projects/project-create.tsx index 85cb575d..8bc96e8a 100644 --- a/SuperAdmin/components/projects/project-create.tsx +++ b/SuperAdmin/components/projects/project-create.tsx @@ -24,6 +24,7 @@ import { CardTitle, } from "@/components/ui/card" import { toast } from "sonner" +import { apiRequest } from '@/lib/api-utils' const formSchema = z.object({ name: z.string().min(2, "项目名称至少需要2个字符"), @@ -63,35 +64,39 @@ export default function ProjectCreate({ onSuccess }: ProjectCreateProps) { const onSubmit = async (values: z.infer) => { setIsLoading(true) try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/create`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - name: values.name, - account: values.account, - password: values.password, - phone: values.phone || null, - realname: values.realname || null, - nickname: values.nickname || null, - memo: values.memo || null, - }), + // 从localStorage获取token和admin_id + const token = localStorage.getItem('admin_token') + const adminId = localStorage.getItem('admin_id') + + // 设置cookie + if (token && adminId) { + const domain = new URL(process.env.NEXT_PUBLIC_API_BASE_URL || '').hostname + document.cookie = `admin_token=${token}; path=/; domain=${domain}` + document.cookie = `admin_id=${adminId}; path=/; domain=${domain}` + } + + const result = await apiRequest('/company/create', 'POST', { + name: values.name, + account: values.account, + password: values.password, + phone: values.phone || null, + realname: values.realname || null, + nickname: values.nickname || null, + memo: values.memo || null, }) - const data = await response.json() - - if (data.code === 200) { + if (result.code === 200) { toast.success("项目创建成功") form.reset() if (onSuccess) { onSuccess() } } else { - toast.error(data.msg || "创建项目失败") + toast.error(result.msg || "创建项目失败") } } catch (error) { - toast.error("网络错误,请稍后重试") + console.error("创建项目失败:", error) + toast.error("网络错误,请稍后再试") } finally { setIsLoading(false) } diff --git a/SuperAdmin/components/projects/project-detail.tsx b/SuperAdmin/components/projects/project-detail.tsx index 65444fe5..88f71519 100644 --- a/SuperAdmin/components/projects/project-detail.tsx +++ b/SuperAdmin/components/projects/project-detail.tsx @@ -10,6 +10,7 @@ import { ArrowLeft, Edit } from "lucide-react" import { toast } from "sonner" import Image from "next/image" import { Badge } from "@/components/ui/badge" +import { apiRequest } from '@/lib/api-utils' interface ProjectProfile { id: number @@ -63,74 +64,67 @@ export default function ProjectDetail({ projectId, onEdit }: ProjectDetailProps) const [subUsers, setSubUsers] = useState([]) const [activeTab, setActiveTab] = useState("overview") - useEffect(() => { - const fetchProjectProfile = async () => { + const fetchProject = async () => { + try { setIsLoading(true) - try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/profile/${projectId}`) - const data = await response.json() - - if (data.code === 200) { - setProfile(data.data) - } else { - toast.error(data.msg || "获取项目信息失败") - } - } catch (error) { - toast.error("网络错误,请稍后重试") - } finally { - setIsLoading(false) + const result = await apiRequest(`/company/profile/${projectId}`) + if (result.code === 200 && result.data) { + setProfile(result.data) + } else { + toast.error(result.msg || "获取项目信息失败") } + } catch (error) { + console.error("获取项目信息失败:", error) + toast.error("网络错误,请稍后再试") + } finally { + setIsLoading(false) } + } - fetchProjectProfile() + const fetchDevices = async () => { + try { + setIsLoading(true) + const result = await apiRequest(`/company/devices?companyId=${projectId}`) + if (result.code === 200 && result.data) { + setDevices(result.data) + } else { + toast.error(result.msg || "获取设备列表失败") + } + } catch (error) { + console.error("获取设备列表失败:", error) + toast.error("网络错误,请稍后再试") + } finally { + setIsLoading(false) + } + } + + const fetchSubusers = async () => { + try { + setIsLoading(true) + const result = await apiRequest(`/company/subusers?companyId=${projectId}`) + if (result.code === 200 && result.data) { + setSubUsers(result.data) + } else { + toast.error(result.msg || "获取子用户列表失败") + } + } catch (error) { + console.error("获取子用户列表失败:", error) + toast.error("网络错误,请稍后再试") + } finally { + setIsLoading(false) + } + } + + useEffect(() => { + fetchProject() }, [projectId]) useEffect(() => { - const fetchDevices = async () => { - if (activeTab === "devices") { - setIsDevicesLoading(true) - try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/devices?companyId=${projectId}`) - const data = await response.json() - - if (data.code === 200) { - setDevices(data.data) - } else { - toast.error(data.msg || "获取设备列表失败") - } - } catch (error) { - toast.error("网络错误,请稍后重试") - } finally { - setIsDevicesLoading(false) - } - } - } - fetchDevices() }, [activeTab, projectId]) useEffect(() => { - const fetchSubUsers = async () => { - if (activeTab === "accounts") { - setIsSubUsersLoading(true) - try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/subusers?companyId=${projectId}`) - const data = await response.json() - - if (data.code === 200) { - setSubUsers(data.data) - } else { - toast.error(data.msg || "获取子账号列表失败") - } - } catch (error) { - toast.error("网络错误,请稍后重试") - } finally { - setIsSubUsersLoading(false) - } - } - } - - fetchSubUsers() + fetchSubusers() }, [activeTab, projectId]) if (isLoading) { diff --git a/SuperAdmin/components/projects/project-edit.tsx b/SuperAdmin/components/projects/project-edit.tsx index 5b5e85ca..0c5b9c22 100644 --- a/SuperAdmin/components/projects/project-edit.tsx +++ b/SuperAdmin/components/projects/project-edit.tsx @@ -24,6 +24,8 @@ import { CardTitle, } from "@/components/ui/card" import { toast } from "sonner" +import { apiRequest } from '@/lib/api-utils' +import { useRouter } from "next/navigation" const formSchema = z.object({ name: z.string().min(2, "项目名称至少需要2个字符"), @@ -50,6 +52,8 @@ interface ProjectData { export default function ProjectEdit({ projectId, onSuccess }: ProjectEditProps) { const [isLoading, setIsLoading] = useState(false) const [isFetching, setIsFetching] = useState(true) + const [project, setProject] = useState(null) + const router = useRouter() const form = useForm>({ resolver: zodResolver(formSchema), @@ -66,74 +70,54 @@ export default function ProjectEdit({ projectId, onSuccess }: ProjectEditProps) // 获取项目数据 useEffect(() => { const fetchProject = async () => { - setIsFetching(true) try { - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/profile/${projectId}`) - const data = await response.json() - - if (data.code === 200) { - const project = data.data + setIsLoading(true) + const result = await apiRequest(`/company/profile/${projectId}`) + if (result.code === 200 && result.data) { + setProject(result.data) form.reset({ - name: project.name || "", - account: project.account || "", + name: result.data.name || "", + account: result.data.account || "", password: "", confirmPassword: "", - phone: project.phone || "", - memo: project.memo || "", + phone: result.data.phone || "", + memo: result.data.memo || "", }) } else { - toast.error(data.msg || "获取项目信息失败") + toast.error(result.msg || "获取项目信息失败") } } catch (error) { - toast.error("网络错误,请稍后重试") + console.error("获取项目信息失败:", error) + toast.error("网络错误,请稍后再试") } finally { - setIsFetching(false) + setIsLoading(false) } } fetchProject() }, [projectId, form]) - const onSubmit = async (values: z.infer) => { - // 检查密码是否匹配 - if (values.password && values.password !== values.confirmPassword) { - toast.error("两次输入的密码不一致") - return - } - + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() setIsLoading(true) + try { - // 准备请求数据,根据需要添加或移除字段 - const updateData: Record = { - id: parseInt(projectId), - name: values.name, - account: values.account, - memo: values.memo, - phone: values.phone, - } - - // 如果提供了密码,则包含密码字段 - if (values.password) { - updateData.password = values.password - } - - const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/update`, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify(updateData), + const result = await apiRequest('/company/update', 'POST', { + id: projectId, + name: project?.name, + account: project?.account, + memo: project?.memo, + phone: project?.phone, + username: project?.username, + status: project?.status, + ...(form.getValues('password') && { password: form.getValues('password') }) }) - const data = await response.json() - - if (data.code === 200) { - toast.success("项目更新成功") - if (onSuccess) { - onSuccess() - } + if (result.code === 200) { + toast.success("更新成功") + router.push("/dashboard/projects") } else { - toast.error(data.msg || "更新项目失败") + toast.error(result.msg) } } catch (error) { toast.error("网络错误,请稍后重试") @@ -154,7 +138,7 @@ export default function ProjectEdit({ projectId, onSuccess }: ProjectEditProps)
- + ([]) + const [isLoading, setIsLoading] = useState(false) + const router = useRouter() + + const fetchProjects = async () => { + try { + setIsLoading(true) + const params = new URLSearchParams() + params.append('page', '1') + params.append('limit', '10') + + const result = await apiRequest(`/company/list?${params.toString()}`) + if (result.code === 200 && result.data) { + setProjects(result.data) + } else { + toast.error(result.msg || "获取项目列表失败") + } + } catch (error) { + console.error("获取项目列表失败:", error) + toast.error("网络错误,请稍后再试") + } finally { + setIsLoading(false) + } + } + + const handleDelete = async (id: number) => { + try { + setIsLoading(true) + const result = await apiRequest('/company/delete', 'POST', { id }) + if (result.code === 200) { + toast.success("删除成功") + fetchProjects() + } else { + toast.error(result.msg || "删除失败") + } + } catch (error) { + console.error("删除项目失败:", error) + toast.error("网络错误,请稍后再试") + } finally { + setIsLoading(false) + } + } + + useEffect(() => { + fetchProjects() + }, []) + + return ( +
+
+

项目列表

+ +
+
+ + + + 项目名称 + 账号 + 手机号 + 状态 + 创建时间 + 操作 + + + + {projects.map((project) => ( + + {project.name} + {project.account} + {project.phone || '-'} + + + {project.status === 1 ? "正常" : "禁用"} + + + {project.createTime} + +
+ + + +
+
+
+ ))} +
+
+
+
+ ) +} \ No newline at end of file diff --git a/SuperAdmin/lib/api-utils.ts b/SuperAdmin/lib/api-utils.ts index 4a7cba94..4efde424 100644 --- a/SuperAdmin/lib/api-utils.ts +++ b/SuperAdmin/lib/api-utils.ts @@ -3,79 +3,78 @@ import { getConfig } from './config'; /** * API响应接口 */ -export interface ApiResponse { +interface ApiResponse { code: number; msg: string; - data: T | null; + data?: any; } /** * API请求函数 * @param endpoint API端点 * @param method HTTP方法 - * @param data 请求数据 + * @param body 请求数据 + * @param headers 请求头 * @returns API响应 */ -export async function apiRequest( +export async function apiRequest( endpoint: string, - method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET', - data?: any -): Promise> { - // 获取API基础URL - const { apiBaseUrl } = getConfig(); - const url = `${apiBaseUrl}${endpoint}`; + method: string = 'GET', + body?: any, + headers: HeadersInit = {} +): Promise { + const url = `${process.env.NEXT_PUBLIC_API_BASE_URL}${endpoint}`; - // 构建请求头 - const headers: HeadersInit = { - 'Content-Type': 'application/json', - }; + // 从localStorage获取token和admin_id + const token = typeof window !== 'undefined' ? localStorage.getItem('admin_token') : null; + const adminId = typeof window !== 'undefined' ? localStorage.getItem('admin_id') : null; - // 添加认证信息(如果有) - if (typeof window !== 'undefined') { - const token = localStorage.getItem('admin_token'); - if (token) { - // 设置Cookie中的认证信息 - document.cookie = `admin_token=${token}; path=/`; - } + // 设置cookie + if (typeof window !== 'undefined' && token && adminId) { + const domain = new URL(process.env.NEXT_PUBLIC_API_BASE_URL || '').hostname; + document.cookie = `admin_token=${token}; path=/; domain=${domain}`; + document.cookie = `admin_id=${adminId}; path=/; domain=${domain}`; } - - // 构建请求选项 + + const defaultHeaders = { + 'Content-Type': 'application/json', + 'Accept': 'application/json', + ...headers + }; + const options: RequestInit = { method, - headers, - credentials: 'same-origin', // 改为same-origin,避免跨域请求发送Cookie + headers: defaultHeaders, + credentials: 'include', // 允许跨域请求携带cookies + mode: 'cors', // 明确指定使用CORS模式 + ...(body && { body: JSON.stringify(body) }) }; - - // 添加请求体(针对POST、PUT请求) - if (method !== 'GET' && data) { - options.body = JSON.stringify(data); - } - + try { - // 发送请求 const response = await fetch(url, options); + const data = await response.json(); - // 解析响应 - const result = await response.json(); - - // 如果响应状态码不是2xx,或者接口返回的code不是200,抛出错误 - if (!response.ok || (result && result.code !== 200)) { + // 如果接口返回的code不是200,抛出错误 + if (data && data.code !== 200) { // 如果是认证错误,清除登录信息 - if (result.code === 401) { + if (data.code === 401) { if (typeof window !== 'undefined') { localStorage.removeItem('admin_id'); localStorage.removeItem('admin_name'); localStorage.removeItem('admin_account'); localStorage.removeItem('admin_token'); + // 清除cookie + const domain = new URL(process.env.NEXT_PUBLIC_API_BASE_URL || '').hostname; + document.cookie = 'admin_token=; path=/; domain=' + domain + '; expires=Thu, 01 Jan 1970 00:00:00 GMT'; + document.cookie = 'admin_id=; path=/; domain=' + domain + '; expires=Thu, 01 Jan 1970 00:00:00 GMT'; } } - - throw result; // 抛出响应结果作为错误 + throw data; // 抛出响应结果作为错误 } - return result; + return data; } catch (error) { - // 直接抛出错误,由调用方处理 + console.error('API请求失败:', error); throw error; } } \ No newline at end of file