Merge branch 'develop' of https://e.coding.net/g-xtcy5189/cunkebao/cunkebao_v3 into develop
This commit is contained in:
@@ -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();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -27,4 +27,6 @@ return [
|
||||
'httponly' => '',
|
||||
// 是否使用 setcookie
|
||||
'setcookie' => true,
|
||||
// 跨站需要
|
||||
'samesite' => 'None',
|
||||
];
|
||||
|
||||
@@ -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();
|
||||
@@ -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');
|
||||
|
||||
|
||||
@@ -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()
|
||||
}, [])
|
||||
|
||||
// 单独处理问候语,避免依赖问题
|
||||
|
||||
@@ -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("网络错误,请稍后重试")
|
||||
|
||||
@@ -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<SubUser[]>([])
|
||||
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 <div className="flex items-center justify-center min-h-screen">加载中...</div>
|
||||
|
||||
@@ -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("网络错误,请稍后重试")
|
||||
|
||||
@@ -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("网络错误,请稍后重试")
|
||||
|
||||
@@ -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<typeof formSchema>) => {
|
||||
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)
|
||||
}
|
||||
|
||||
@@ -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<SubUser[]>([])
|
||||
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) {
|
||||
|
||||
@@ -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<ProjectData | null>(null)
|
||||
const router = useRouter()
|
||||
|
||||
const form = useForm<z.infer<typeof formSchema>>({
|
||||
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<typeof formSchema>) => {
|
||||
// 检查密码是否匹配
|
||||
if (values.password && values.password !== values.confirmPassword) {
|
||||
toast.error("两次输入的密码不一致")
|
||||
return
|
||||
}
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault()
|
||||
setIsLoading(true)
|
||||
|
||||
try {
|
||||
// 准备请求数据,根据需要添加或移除字段
|
||||
const updateData: Record<string, any> = {
|
||||
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)
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form {...form}>
|
||||
<form id="edit-project-form" onSubmit={form.handleSubmit(onSubmit)} className="space-y-8">
|
||||
<form id="edit-project-form" onSubmit={handleSubmit} className="space-y-8">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
|
||||
139
SuperAdmin/components/projects/project-list.tsx
Normal file
139
SuperAdmin/components/projects/project-list.tsx
Normal file
@@ -0,0 +1,139 @@
|
||||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { apiRequest } from '@/lib/api-utils'
|
||||
import { toast } from "sonner"
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
} from "@/components/ui/table"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
import { useRouter } from "next/navigation"
|
||||
|
||||
interface Project {
|
||||
id: number
|
||||
name: string
|
||||
account: string
|
||||
phone: string
|
||||
status: number
|
||||
createTime: string
|
||||
}
|
||||
|
||||
export default function ProjectList() {
|
||||
const [projects, setProjects] = useState<Project[]>([])
|
||||
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 (
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h2 className="text-2xl font-bold">项目列表</h2>
|
||||
<Button onClick={() => router.push('/dashboard/projects/create')}>
|
||||
新建项目
|
||||
</Button>
|
||||
</div>
|
||||
<div className="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>项目名称</TableHead>
|
||||
<TableHead>账号</TableHead>
|
||||
<TableHead>手机号</TableHead>
|
||||
<TableHead>状态</TableHead>
|
||||
<TableHead>创建时间</TableHead>
|
||||
<TableHead>操作</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{projects.map((project) => (
|
||||
<TableRow key={project.id}>
|
||||
<TableCell>{project.name}</TableCell>
|
||||
<TableCell>{project.account}</TableCell>
|
||||
<TableCell>{project.phone || '-'}</TableCell>
|
||||
<TableCell>
|
||||
<Badge variant={project.status === 1 ? "success" : "destructive"}>
|
||||
{project.status === 1 ? "正常" : "禁用"}
|
||||
</Badge>
|
||||
</TableCell>
|
||||
<TableCell>{project.createTime}</TableCell>
|
||||
<TableCell>
|
||||
<div className="space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => router.push(`/dashboard/projects/${project.id}`)}
|
||||
>
|
||||
详情
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => router.push(`/dashboard/projects/${project.id}/edit`)}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => handleDelete(project.id)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -3,79 +3,78 @@ import { getConfig } from './config';
|
||||
/**
|
||||
* API响应接口
|
||||
*/
|
||||
export interface ApiResponse<T = any> {
|
||||
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<T = any>(
|
||||
export async function apiRequest(
|
||||
endpoint: string,
|
||||
method: 'GET' | 'POST' | 'PUT' | 'DELETE' = 'GET',
|
||||
data?: any
|
||||
): Promise<ApiResponse<T>> {
|
||||
// 获取API基础URL
|
||||
const { apiBaseUrl } = getConfig();
|
||||
const url = `${apiBaseUrl}${endpoint}`;
|
||||
method: string = 'GET',
|
||||
body?: any,
|
||||
headers: HeadersInit = {}
|
||||
): Promise<ApiResponse> {
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user