Files
cunkebao_v3/SuperAdmin/app/dashboard/projects/[id]/edit/page.tsx

380 lines
13 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client"
import * as React from "react"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import { Label } from "@/components/ui/label"
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { ArrowLeft, Plus, Trash, X } from "lucide-react"
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"
// 为React.use添加类型声明
declare module 'react' {
function use<T>(promise: Promise<T>): T;
function use<T>(value: T): T;
}
interface Device {
id: number
memo: string
imei: string
phone: string
model: string
brand: string
alive: number
createTime: number
}
interface Project {
id: number
name: string
memo: string
companyId: number
createTime: string
account: string
phone: string | null
deviceCount: number
friendCount: number
userCount: number
username: string
status: number
s2_accountId?: number
devices?: Device[]
}
export default function EditProjectPage({ params }: { params: { id: string } }) {
const router = useRouter()
const [isSubmitting, setIsSubmitting] = useState(false)
const [isLoading, setIsLoading] = useState(true)
const [project, setProject] = useState<Project | null>(null)
const [password, setPassword] = useState("")
const [confirmPassword, setConfirmPassword] = useState("")
const [isModalOpen, setIsModalOpen] = useState(false)
const [qrCodeData, setQrCodeData] = useState("")
const [isAddingDevice, setIsAddingDevice] = useState(false)
const { id } = React.use(params)
useEffect(() => {
const fetchProject = async () => {
try {
const response = await fetch(`${process.env.NEXT_PUBLIC_API_BASE_URL}/company/detail/${id}`)
const data = await response.json()
if (data.code === 200) {
setProject(data.data)
} else {
toast.error(data.msg || "获取项目信息失败")
}
} catch (error) {
toast.error("网络错误,请稍后重试")
} finally {
setIsLoading(false)
}
}
fetchProject()
}, [id])
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 data = await response.json()
if (data.code === 200) {
toast.success("更新成功")
router.push("/dashboard/projects")
} else {
toast.error(data.msg)
}
} catch (error) {
toast.error("网络错误,请稍后重试")
} finally {
setIsSubmitting(false)
}
}
const handleAddDevice = async () => {
if (!project?.s2_accountId) {
toast.error("无法添加设备未找到账号ID")
return
}
setIsAddingDevice(true)
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 data = await response.json()
if (data.code === 200 && data.data?.qrCode) {
setQrCodeData(data.data.qrCode)
setIsModalOpen(true)
} else {
toast.error(data.msg || "获取设备二维码失败")
}
} catch (error) {
toast.error("网络错误,请稍后重试")
} finally {
setIsAddingDevice(false)
}
}
const closeModal = () => {
setIsModalOpen(false)
setQrCodeData("")
}
if (isLoading) {
return <div className="flex items-center justify-center min-h-screen">...</div>
}
return (
<div className="space-y-6">
<Toaster richColors position="top-center" />
<div className="flex items-center gap-2">
<Button variant="outline" size="icon" asChild>
<Link href="/dashboard/projects">
<ArrowLeft className="h-4 w-4" />
</Link>
</Button>
<h1 className="text-2xl font-bold"></h1>
</div>
<form onSubmit={handleSubmit}>
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription></CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="space-y-2">
<Label htmlFor="projectName"></Label>
<Input
id="projectName"
value={project?.name || ""}
onChange={(e) => setProject({ ...project, name: e.target.value })}
placeholder="请输入项目名称"
required
/>
</div>
<div className="grid gap-6 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="account"></Label>
<Input
id="account"
value={project?.account || ""}
onChange={(e) => setProject({ ...project, account: e.target.value })}
placeholder="请输入登录的账号"
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="nickname"></Label>
<Input
id="nickname"
value={project?.username || ""}
onChange={(e) => setProject({ ...project, username: e.target.value })}
placeholder="用于账号登录后显示的用户名,可以填真实姓名"
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="phone"></Label>
<Input
id="phone"
type="number"
value={project?.phone || ""}
onChange={(e) => setProject({ ...project, phone: e.target.value as string })}
placeholder="手机号可用于登录"
required
/>
</div>
<div className="space-y-2">
<Label htmlFor="status"></Label>
<select
id="status"
value={project?.status.toString() || "1"}
onChange={(e) => setProject({ ...project, status: parseInt(e.target.value) })}
className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50"
>
<option value="1"></option>
<option value="0"></option>
</select>
</div>
<div className="space-y-2">
<Label htmlFor="password"></Label>
<Input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="不修改请留空"
/>
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword"></Label>
<Input
id="confirmPassword"
type="password"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
placeholder="不修改请留空"
/>
</div>
</div>
<div className="space-y-2">
<Label></Label>
<div className="space-y-3">
{project && project.devices && project.devices.length > 0 && (
<div className="border rounded-md">
<table className="w-full">
<thead className="bg-muted">
<tr>
<th className="text-left p-2"></th>
<th className="text-left p-2">IMEI</th>
<th className="text-left p-2"></th>
<th className="text-left p-2"></th>
<th className="text-left p-2"></th>
<th className="text-left p-2"></th>
<th className="text-left p-2"></th>
</tr>
</thead>
<tbody>
{project.devices.map((device) => (
<tr key={device.id} className="border-t">
<td className="p-2">{device.memo}</td>
<td className="p-2">{device.imei || '-'}</td>
<td className="p-2">{device.phone || '-'}</td>
<td className="p-2">{device.model || '-'}</td>
<td className="p-2">{device.brand || '-'}</td>
<td className="p-2">
<span className={`inline-block px-2 py-1 text-xs rounded-full ${device.alive === 1 ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'}`}>
{device.alive === 1 ? '在线' : '离线'}
</span>
</td>
<td className="p-2">{device.createTime || '-'}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
<Button
type="button"
variant="outline"
onClick={handleAddDevice}
className="flex items-center gap-1"
disabled={isAddingDevice}
>
{isAddingDevice ? "添加中..." : (
<>
<Plus className="h-4 w-4" />
</>
)}
</Button>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="description"></Label>
<Textarea
id="description"
value={project?.memo || ""}
onChange={(e) => setProject({ ...project, memo: e.target.value })}
placeholder="请输入项目介绍(选填)"
rows={4}
/>
</div>
</CardContent>
<CardFooter className="flex justify-end gap-2">
<Button variant="outline" asChild>
<Link href="/dashboard/projects"></Link>
</Button>
<Button type="submit" disabled={isSubmitting}>
{isSubmitting ? "提交中..." : "保存修改"}
</Button>
</CardFooter>
</Card>
</form>
{/* 使用Dialog组件替代自定义模态框 */}
<Dialog open={isModalOpen} onOpenChange={(open) => !open && closeModal()}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription>
使
</DialogDescription>
</DialogHeader>
<div className="flex justify-center p-6">
<div className="border p-4 rounded-lg">
{qrCodeData ? (
<img
src={qrCodeData}
alt="设备二维码"
className="w-64 h-64 object-contain"
/>
) : (
<div className="w-64 h-64 flex items-center justify-center bg-muted">
<p className="text-muted-foreground">...</p>
</div>
)}
</div>
</div>
<DialogFooter className="sm:justify-center">
<Button type="button" onClick={closeModal}>
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</div>
)
}