工单类型
This commit is contained in:
@@ -32,7 +32,6 @@ interface UserData {
|
|||||||
userId: string
|
userId: string
|
||||||
userName: string
|
userName: string
|
||||||
nickName: string
|
nickName: string
|
||||||
email: string
|
|
||||||
phonenumber: string
|
phonenumber: string
|
||||||
status: string
|
status: string
|
||||||
loginDate: string
|
loginDate: string
|
||||||
@@ -189,7 +188,6 @@ export default function CompanyPermissionsPage() {
|
|||||||
const matchesSearch =
|
const matchesSearch =
|
||||||
user.nickName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
user.nickName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
user.userName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
user.userName.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
user.email.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
||||||
(user.dept?.deptName || "").toLowerCase().includes(searchTerm.toLowerCase())
|
(user.dept?.deptName || "").toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
|
||||||
const matchesStatus = statusFilter === "all" ||
|
const matchesStatus = statusFilter === "all" ||
|
||||||
@@ -264,10 +262,7 @@ export default function CompanyPermissionsPage() {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>{user.dept?.deptName || '-'}</TableCell>
|
<TableCell>{user.dept?.deptName || '-'}</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div>
|
<div className="text-sm">{user.phonenumber || '-'}</div>
|
||||||
<div className="text-sm">{user.email || '-'}</div>
|
|
||||||
<div className="text-sm text-gray-500">{user.phonenumber || '-'}</div>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="text-sm">
|
<div className="text-sm">
|
||||||
@@ -354,7 +349,7 @@ export default function CompanyPermissionsPage() {
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
|
||||||
<Input
|
<Input
|
||||||
placeholder="搜索用户名、姓名、邮箱或部门..."
|
placeholder="搜索用户名、姓名或部门..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="pl-10"
|
className="pl-10"
|
||||||
@@ -438,7 +433,6 @@ function CreateUserForm({ roles, onClose }: { roles: Role[]; onClose: () => void
|
|||||||
const [formData, setFormData] = useState({
|
const [formData, setFormData] = useState({
|
||||||
username: "",
|
username: "",
|
||||||
name: "",
|
name: "",
|
||||||
email: "",
|
|
||||||
phone: "",
|
phone: "",
|
||||||
role: "",
|
role: "",
|
||||||
department: "",
|
department: "",
|
||||||
@@ -500,7 +494,6 @@ function CreateUserForm({ roles, onClose }: { roles: Role[]; onClose: () => void
|
|||||||
nickName: formData.name,
|
nickName: formData.name,
|
||||||
password: formData.password,
|
password: formData.password,
|
||||||
phonenumber: formData.phone,
|
phonenumber: formData.phone,
|
||||||
email: formData.email,
|
|
||||||
sex: "0",
|
sex: "0",
|
||||||
status: "0",
|
status: "0",
|
||||||
remark: formData.department || "",
|
remark: formData.department || "",
|
||||||
@@ -564,18 +557,6 @@ function CreateUserForm({ roles, onClose }: { roles: Role[]; onClose: () => void
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="email">邮箱</Label>
|
|
||||||
<Input
|
|
||||||
id="email"
|
|
||||||
type="email"
|
|
||||||
value={formData.email}
|
|
||||||
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
|
|
||||||
placeholder="请输入邮箱地址"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="phone">手机号</Label>
|
<Label htmlFor="phone">手机号</Label>
|
||||||
<Input
|
<Input
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {
|
|||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
DialogTrigger,
|
||||||
} from "../ui/dialog"
|
} from "../ui/dialog"
|
||||||
import { Plus, Filter, Download, MapPin, Calendar, Shield, ChevronLeft, ChevronRight } from "lucide-react"
|
import { Plus, Filter, Download, MapPin, Calendar, Shield, ChevronLeft, ChevronRight, Minus, Plus as PlusIcon } from "lucide-react"
|
||||||
import { apiGet, apiPost, apiPut } from "../../lib/services/api"
|
import { apiGet, apiPost, apiPut } from "../../lib/services/api"
|
||||||
|
|
||||||
// 商户数据类型(根据新接口返回格式定义)
|
// 商户数据类型(根据新接口返回格式定义)
|
||||||
@@ -60,6 +60,7 @@ interface Equipment {
|
|||||||
mallName: string
|
mallName: string
|
||||||
installationLocation: string
|
installationLocation: string
|
||||||
installationDate: string
|
installationDate: string
|
||||||
|
nextInspectionDate: string
|
||||||
status: string
|
status: string
|
||||||
remarks: string
|
remarks: string
|
||||||
createdBy: string
|
createdBy: string
|
||||||
@@ -201,6 +202,7 @@ export default function EquipmentPage() {
|
|||||||
merchantId: "",
|
merchantId: "",
|
||||||
location: "",
|
location: "",
|
||||||
installDate: "",
|
installDate: "",
|
||||||
|
nextInspectionDate: "",
|
||||||
status: "normal",
|
status: "normal",
|
||||||
notes: "",
|
notes: "",
|
||||||
})
|
})
|
||||||
@@ -213,10 +215,71 @@ export default function EquipmentPage() {
|
|||||||
merchantId: "",
|
merchantId: "",
|
||||||
location: "",
|
location: "",
|
||||||
installDate: "",
|
installDate: "",
|
||||||
|
nextInspectionDate: "",
|
||||||
status: "normal",
|
status: "normal",
|
||||||
notes: "",
|
notes: "",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 调整下一次检测日期(添加设备)
|
||||||
|
const adjustNextInspectionDate = (years: number) => {
|
||||||
|
if (!newEquipment.nextInspectionDate) {
|
||||||
|
// 如果没有下次检测日期,使用安装日期或当前日期作为基准
|
||||||
|
const baseDate = newEquipment.installDate ? new Date(newEquipment.installDate) : new Date()
|
||||||
|
baseDate.setFullYear(baseDate.getFullYear() + years)
|
||||||
|
const newDateStr = baseDate.toISOString().split('T')[0]
|
||||||
|
|
||||||
|
// 检查是否小于安装日期
|
||||||
|
if (newEquipment.installDate && newDateStr < newEquipment.installDate) {
|
||||||
|
alert('下一次检测时间不能小于安装日期')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setNewEquipment({ ...newEquipment, nextInspectionDate: newDateStr })
|
||||||
|
} else {
|
||||||
|
const currentDate = new Date(newEquipment.nextInspectionDate)
|
||||||
|
currentDate.setFullYear(currentDate.getFullYear() + years)
|
||||||
|
const newDateStr = currentDate.toISOString().split('T')[0]
|
||||||
|
|
||||||
|
// 检查是否小于安装日期
|
||||||
|
if (newEquipment.installDate && newDateStr < newEquipment.installDate) {
|
||||||
|
alert('下一次检测时间不能小于安装日期')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setNewEquipment({ ...newEquipment, nextInspectionDate: newDateStr })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调整下一次检测日期(编辑设备)
|
||||||
|
const adjustEditNextInspectionDate = (years: number) => {
|
||||||
|
if (!editEquipment.nextInspectionDate) {
|
||||||
|
// 如果没有下次检测日期,使用安装日期或当前日期作为基准
|
||||||
|
const baseDate = editEquipment.installDate ? new Date(editEquipment.installDate) : new Date()
|
||||||
|
baseDate.setFullYear(baseDate.getFullYear() + years)
|
||||||
|
const newDateStr = baseDate.toISOString().split('T')[0]
|
||||||
|
|
||||||
|
// 检查是否小于安装日期
|
||||||
|
if (editEquipment.installDate && newDateStr < editEquipment.installDate) {
|
||||||
|
alert('下一次检测时间不能小于安装日期')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setEditEquipment({ ...editEquipment, nextInspectionDate: newDateStr })
|
||||||
|
} else {
|
||||||
|
const currentDate = new Date(editEquipment.nextInspectionDate)
|
||||||
|
currentDate.setFullYear(currentDate.getFullYear() + years)
|
||||||
|
const newDateStr = currentDate.toISOString().split('T')[0]
|
||||||
|
|
||||||
|
// 检查是否小于安装日期
|
||||||
|
if (editEquipment.installDate && newDateStr < editEquipment.installDate) {
|
||||||
|
alert('下一次检测时间不能小于安装日期')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
setEditEquipment({ ...editEquipment, nextInspectionDate: newDateStr })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 打开编辑对话框
|
// 打开编辑对话框
|
||||||
const handleEditEquipment = (equipment: Equipment) => {
|
const handleEditEquipment = (equipment: Equipment) => {
|
||||||
setEditEquipment({
|
setEditEquipment({
|
||||||
@@ -227,6 +290,7 @@ export default function EquipmentPage() {
|
|||||||
merchantId: equipment.merchantId || "",
|
merchantId: equipment.merchantId || "",
|
||||||
location: equipment.installationLocation || "",
|
location: equipment.installationLocation || "",
|
||||||
installDate: equipment.installationDate || "",
|
installDate: equipment.installationDate || "",
|
||||||
|
nextInspectionDate: equipment.nextInspectionDate || "",
|
||||||
status: equipment.status === "1" ? "normal" : equipment.status === "2" ? "expiring" : "expired",
|
status: equipment.status === "1" ? "normal" : equipment.status === "2" ? "expiring" : "expired",
|
||||||
notes: equipment.remarks || "",
|
notes: equipment.remarks || "",
|
||||||
})
|
})
|
||||||
@@ -247,6 +311,16 @@ export default function EquipmentPage() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证下一次检测时间不能小于安装日期
|
||||||
|
if (editEquipment.nextInspectionDate && editEquipment.installDate) {
|
||||||
|
const nextDate = new Date(editEquipment.nextInspectionDate)
|
||||||
|
const installDate = new Date(editEquipment.installDate)
|
||||||
|
if (nextDate < installDate) {
|
||||||
|
alert('下一次检测时间不能小于安装日期')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -258,6 +332,7 @@ export default function EquipmentPage() {
|
|||||||
merchantId: editEquipment.merchantId,
|
merchantId: editEquipment.merchantId,
|
||||||
installationLocation: editEquipment.location,
|
installationLocation: editEquipment.location,
|
||||||
installationDate: editEquipment.installDate,
|
installationDate: editEquipment.installDate,
|
||||||
|
nextInspectionDate: editEquipment.nextInspectionDate,
|
||||||
status: editEquipment.status === 'normal' ? 1 : editEquipment.status === 'expiring' ? 2 : 3,
|
status: editEquipment.status === 'normal' ? 1 : editEquipment.status === 'expiring' ? 2 : 3,
|
||||||
remarks: editEquipment.notes,
|
remarks: editEquipment.notes,
|
||||||
}
|
}
|
||||||
@@ -289,6 +364,16 @@ export default function EquipmentPage() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 验证下一次检测时间不能小于安装日期
|
||||||
|
if (newEquipment.nextInspectionDate && newEquipment.installDate) {
|
||||||
|
const nextDate = new Date(newEquipment.nextInspectionDate)
|
||||||
|
const installDate = new Date(newEquipment.installDate)
|
||||||
|
if (nextDate < installDate) {
|
||||||
|
alert('下一次检测时间不能小于安装日期')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
setIsSubmitting(true)
|
setIsSubmitting(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -300,6 +385,7 @@ export default function EquipmentPage() {
|
|||||||
merchantId: newEquipment.merchantId,
|
merchantId: newEquipment.merchantId,
|
||||||
installationLocation: newEquipment.location,
|
installationLocation: newEquipment.location,
|
||||||
installationDate: newEquipment.installDate,
|
installationDate: newEquipment.installDate,
|
||||||
|
nextInspectionDate: newEquipment.nextInspectionDate,
|
||||||
status: newEquipment.status === 'normal' ? 1 : newEquipment.status === 'expiring' ? 2 : 3,
|
status: newEquipment.status === 'normal' ? 1 : newEquipment.status === 'expiring' ? 2 : 3,
|
||||||
remarks: newEquipment.notes,
|
remarks: newEquipment.notes,
|
||||||
createdBy: "admin" // 可以从用户上下文获取
|
createdBy: "admin" // 可以从用户上下文获取
|
||||||
@@ -322,6 +408,7 @@ export default function EquipmentPage() {
|
|||||||
merchantId: "",
|
merchantId: "",
|
||||||
location: "",
|
location: "",
|
||||||
installDate: "",
|
installDate: "",
|
||||||
|
nextInspectionDate: "",
|
||||||
status: "normal",
|
status: "normal",
|
||||||
notes: "",
|
notes: "",
|
||||||
})
|
})
|
||||||
@@ -421,12 +508,12 @@ export default function EquipmentPage() {
|
|||||||
添加设备
|
添加设备
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent className="max-w-2xl">
|
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>添加新设备</DialogTitle>
|
<DialogTitle>添加新设备</DialogTitle>
|
||||||
<DialogDescription>填写设备基本信息和安装详情</DialogDescription>
|
<DialogDescription>填写设备基本信息和安装详情</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-x-6 gap-y-5 py-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="equipmentName">设备名称</Label>
|
<Label htmlFor="equipmentName">设备名称</Label>
|
||||||
<Input
|
<Input
|
||||||
@@ -515,9 +602,53 @@ export default function EquipmentPage() {
|
|||||||
id="installDate"
|
id="installDate"
|
||||||
type="date"
|
type="date"
|
||||||
value={newEquipment.installDate}
|
value={newEquipment.installDate}
|
||||||
onChange={(e) => setNewEquipment({ ...newEquipment, installDate: e.target.value })}
|
onChange={(e) => {
|
||||||
|
const installDate = e.target.value
|
||||||
|
setNewEquipment({ ...newEquipment, installDate })
|
||||||
|
|
||||||
|
// 自动填充下一次检测时间(安装日期+1年)
|
||||||
|
if (installDate && !newEquipment.nextInspectionDate) {
|
||||||
|
const nextDate = new Date(installDate)
|
||||||
|
nextDate.setFullYear(nextDate.getFullYear() + 1)
|
||||||
|
const nextDateStr = nextDate.toISOString().split('T')[0]
|
||||||
|
setNewEquipment(prev => ({ ...prev, nextInspectionDate: nextDateStr }))
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-span-2 space-y-2">
|
||||||
|
<Label htmlFor="nextInspectionDate">下一次检测时间</Label>
|
||||||
|
<div className="flex gap-2 max-w-md">
|
||||||
|
<Input
|
||||||
|
id="nextInspectionDate"
|
||||||
|
type="date"
|
||||||
|
value={newEquipment.nextInspectionDate}
|
||||||
|
min={newEquipment.installDate || undefined}
|
||||||
|
onChange={(e) => setNewEquipment({ ...newEquipment, nextInspectionDate: e.target.value })}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => adjustNextInspectionDate(-1)}
|
||||||
|
title="上一年"
|
||||||
|
className="px-3"
|
||||||
|
>
|
||||||
|
<Minus className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => adjustNextInspectionDate(1)}
|
||||||
|
title="下一年"
|
||||||
|
className="px-3"
|
||||||
|
>
|
||||||
|
<PlusIcon className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="status">设备状态</Label>
|
<Label htmlFor="status">设备状态</Label>
|
||||||
<Select
|
<Select
|
||||||
@@ -565,12 +696,12 @@ export default function EquipmentPage() {
|
|||||||
|
|
||||||
{/* 编辑设备对话框 */}
|
{/* 编辑设备对话框 */}
|
||||||
<Dialog open={isEditEquipmentOpen} onOpenChange={setIsEditEquipmentOpen}>
|
<Dialog open={isEditEquipmentOpen} onOpenChange={setIsEditEquipmentOpen}>
|
||||||
<DialogContent className="max-w-2xl">
|
<DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
<DialogTitle>编辑设备</DialogTitle>
|
<DialogTitle>编辑设备</DialogTitle>
|
||||||
<DialogDescription>修改设备基本信息和安装详情</DialogDescription>
|
<DialogDescription>修改设备基本信息和安装详情</DialogDescription>
|
||||||
</DialogHeader>
|
</DialogHeader>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-x-6 gap-y-5 py-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="edit-equipmentName">设备名称</Label>
|
<Label htmlFor="edit-equipmentName">设备名称</Label>
|
||||||
<Input
|
<Input
|
||||||
@@ -642,9 +773,53 @@ export default function EquipmentPage() {
|
|||||||
id="edit-installDate"
|
id="edit-installDate"
|
||||||
type="date"
|
type="date"
|
||||||
value={editEquipment.installDate}
|
value={editEquipment.installDate}
|
||||||
onChange={(e) => setEditEquipment({ ...editEquipment, installDate: e.target.value })}
|
onChange={(e) => {
|
||||||
|
const installDate = e.target.value
|
||||||
|
setEditEquipment({ ...editEquipment, installDate })
|
||||||
|
|
||||||
|
// 自动填充下一次检测时间(安装日期+1年)
|
||||||
|
if (installDate && !editEquipment.nextInspectionDate) {
|
||||||
|
const nextDate = new Date(installDate)
|
||||||
|
nextDate.setFullYear(nextDate.getFullYear() + 1)
|
||||||
|
const nextDateStr = nextDate.toISOString().split('T')[0]
|
||||||
|
setEditEquipment(prev => ({ ...prev, nextInspectionDate: nextDateStr }))
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="col-span-2 space-y-2">
|
||||||
|
<Label htmlFor="edit-nextInspectionDate">下一次检测时间</Label>
|
||||||
|
<div className="flex gap-2 max-w-md">
|
||||||
|
<Input
|
||||||
|
id="edit-nextInspectionDate"
|
||||||
|
type="date"
|
||||||
|
value={editEquipment.nextInspectionDate}
|
||||||
|
min={editEquipment.installDate || undefined}
|
||||||
|
onChange={(e) => setEditEquipment({ ...editEquipment, nextInspectionDate: e.target.value })}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => adjustEditNextInspectionDate(-1)}
|
||||||
|
title="上一年"
|
||||||
|
className="px-3"
|
||||||
|
>
|
||||||
|
<Minus className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => adjustEditNextInspectionDate(1)}
|
||||||
|
title="下一年"
|
||||||
|
className="px-3"
|
||||||
|
>
|
||||||
|
<PlusIcon className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="edit-status">设备状态</Label>
|
<Label htmlFor="edit-status">设备状态</Label>
|
||||||
<Select
|
<Select
|
||||||
@@ -842,9 +1017,21 @@ export default function EquipmentPage() {
|
|||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<div className="space-y-1">
|
<div className="space-y-1">
|
||||||
<div className="flex items-center text-sm">
|
{item.installationDate && (
|
||||||
<Calendar className="h-3 w-3 mr-1 text-blue-600" />
|
<div className="flex items-center text-sm">
|
||||||
<span className="text-blue-600">创建: {item.createdAt}</span>
|
<Calendar className="h-3 w-3 mr-1 text-orange-600" />
|
||||||
|
<span className="text-orange-600">上次检测: {item.installationDate}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{item.nextInspectionDate && (
|
||||||
|
<div className="flex items-center text-sm">
|
||||||
|
<Calendar className="h-3 w-3 mr-1 text-green-600" />
|
||||||
|
<span className="text-green-600">下次检测: {item.nextInspectionDate}</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="flex items-center text-xs text-gray-500">
|
||||||
|
<Calendar className="h-3 w-3 mr-1" />
|
||||||
|
创建: {item.createdAt}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs text-gray-500">更新: {item.updatedAt}</div>
|
<div className="text-xs text-gray-500">更新: {item.updatedAt}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -574,7 +574,7 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
|
|||||||
merchantId: "",
|
merchantId: "",
|
||||||
equipmentId: "",
|
equipmentId: "",
|
||||||
type: "",
|
type: "",
|
||||||
assignee: "",
|
assignee: [] as string[], // 改为数组支持多选
|
||||||
description: "",
|
description: "",
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -586,6 +586,7 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
|
|||||||
const [loadingEquipment, setLoadingEquipment] = useState(false)
|
const [loadingEquipment, setLoadingEquipment] = useState(false)
|
||||||
const [loadingWorkers, setLoadingWorkers] = useState(false)
|
const [loadingWorkers, setLoadingWorkers] = useState(false)
|
||||||
const [submitting, setSubmitting] = useState(false)
|
const [submitting, setSubmitting] = useState(false)
|
||||||
|
const [isWorkerSelectOpen, setIsWorkerSelectOpen] = useState(false)
|
||||||
|
|
||||||
// 获取商户列表
|
// 获取商户列表
|
||||||
const fetchMerchants = async () => {
|
const fetchMerchants = async () => {
|
||||||
@@ -637,6 +638,23 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 点击外部关闭下拉框
|
||||||
|
useEffect(() => {
|
||||||
|
const handleClickOutside = (event: MouseEvent) => {
|
||||||
|
if (isWorkerSelectOpen) {
|
||||||
|
const target = event.target as HTMLElement
|
||||||
|
if (!target.closest('.worker-select-container')) {
|
||||||
|
setIsWorkerSelectOpen(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('mousedown', handleClickOutside)
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside)
|
||||||
|
}
|
||||||
|
}, [isWorkerSelectOpen])
|
||||||
|
|
||||||
const handleMerchantChange = (merchantId: string) => {
|
const handleMerchantChange = (merchantId: string) => {
|
||||||
console.log('选择商户,ID:', merchantId)
|
console.log('选择商户,ID:', merchantId)
|
||||||
setSelectedMerchant(merchantId)
|
setSelectedMerchant(merchantId)
|
||||||
@@ -686,9 +704,34 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
|
|||||||
return ["故障检测", "故障维修", "设备安装", "预防性维护", "设备改造", "设备拆除", "更换药剂"]
|
return ["故障检测", "故障维修", "设备安装", "预防性维护", "设备改造", "设备拆除", "更换药剂"]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 切换工人选择
|
||||||
|
const toggleWorkerSelection = (workerId: string) => {
|
||||||
|
setFormData(prev => ({
|
||||||
|
...prev,
|
||||||
|
assignee: prev.assignee.includes(workerId)
|
||||||
|
? prev.assignee.filter(id => id !== workerId)
|
||||||
|
: [...prev.assignee, workerId]
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取选中工人的显示文本
|
||||||
|
const getSelectedWorkersText = () => {
|
||||||
|
if (formData.assignee.length === 0) return "选择维修工人"
|
||||||
|
if (formData.assignee.length === 1) {
|
||||||
|
const worker = workers.find(w => w.id === formData.assignee[0])
|
||||||
|
return worker ? worker.name : "1位工人"
|
||||||
|
}
|
||||||
|
return `已选择 ${formData.assignee.length} 位工人`
|
||||||
|
}
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent) => {
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
|
|
||||||
|
if (formData.assignee.length === 0) {
|
||||||
|
alert('请至少选择一位工人')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
setSubmitting(true)
|
setSubmitting(true)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -696,22 +739,31 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
|
|||||||
const selectedMerchantObj = merchants.find(m => m.id === selectedMerchant)
|
const selectedMerchantObj = merchants.find(m => m.id === selectedMerchant)
|
||||||
// 找到选中的设备信息
|
// 找到选中的设备信息
|
||||||
const selectedEquipmentObj = availableEquipment.find(e => e.equipmentId === formData.equipmentId)
|
const selectedEquipmentObj = availableEquipment.find(e => e.equipmentId === formData.equipmentId)
|
||||||
// 找到选中的工人信息
|
// 找到所有选中的工人信息
|
||||||
const selectedWorkerObj = workers.find(w => w.id === formData.assignee)
|
const selectedWorkers = workers.filter(w => formData.assignee.includes(w.id))
|
||||||
|
|
||||||
if (!selectedMerchantObj || !selectedEquipmentObj || !selectedWorkerObj) {
|
if (!selectedMerchantObj || !selectedEquipmentObj || selectedWorkers.length === 0) {
|
||||||
alert('无法获取完整的商户、设备或工人信息')
|
alert('无法获取完整的商户、设备或工人信息')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 拼接工人ID(使用逗号分隔)
|
||||||
|
const workersIds = selectedWorkers.map(w => w.workersId || "").join(",")
|
||||||
|
// 拼接工人名称(使用逗号分隔)
|
||||||
|
const workerNames = selectedWorkers.map(w => w.name).join(",")
|
||||||
|
// 拼接工人电话(使用逗号分隔)
|
||||||
|
const workerPhones = selectedWorkers.map(w => w.phone).join(",")
|
||||||
|
// 使用第一个工人的经销商ID
|
||||||
|
const distributorUserId = selectedWorkers[0].distributorUserId || ""
|
||||||
|
|
||||||
// 生成工单编号
|
// 生成工单编号
|
||||||
const workOrderNumber = `WO${new Date().getFullYear()}${(new Date().getMonth() + 1).toString().padStart(2, '0')}${new Date().getDate().toString().padStart(2, '0')}${Date.now().toString().slice(-4)}`
|
const workOrderNumber = `WO${new Date().getFullYear()}${(new Date().getMonth() + 1).toString().padStart(2, '0')}${new Date().getDate().toString().padStart(2, '0')}${Date.now().toString().slice(-4)}`
|
||||||
|
|
||||||
// 构建API请求数据
|
// 构建API请求数据
|
||||||
const requestData = {
|
const requestData = {
|
||||||
merchantUserId: selectedMerchantObj.merchantsId || "",
|
merchantUserId: selectedMerchantObj.merchantsId || "",
|
||||||
distributorUserId: selectedWorkerObj.distributorUserId || "",
|
distributorUserId: distributorUserId,
|
||||||
workersId: selectedWorkerObj.workersId || "",
|
workersId: workersIds, // 拼接的工人ID
|
||||||
workOrderNumber: workOrderNumber,
|
workOrderNumber: workOrderNumber,
|
||||||
workOrderType: formData.type,
|
workOrderType: formData.type,
|
||||||
workOrderSubtype: "", // 暂时为空
|
workOrderSubtype: "", // 暂时为空
|
||||||
@@ -724,13 +776,15 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
|
|||||||
merchantDistrict: selectedMerchantObj.district || "",
|
merchantDistrict: selectedMerchantObj.district || "",
|
||||||
responsiblePerson: selectedMerchantObj.contactPerson,
|
responsiblePerson: selectedMerchantObj.contactPerson,
|
||||||
responsibleVirtualPhone: selectedMerchantObj.contactPhone,
|
responsibleVirtualPhone: selectedMerchantObj.contactPhone,
|
||||||
workerVirtualPhone: selectedWorkerObj.phone,
|
workerVirtualPhone: workerPhones, // 拼接的工人电话
|
||||||
workerName: selectedWorkerObj.name,
|
workerName: workerNames, // 拼接的工人名称
|
||||||
createdDate: new Date().toISOString().split('T')[0],
|
createdDate: new Date().toISOString().split('T')[0],
|
||||||
completedDate: ""
|
completedDate: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('创建工单请求数据:', requestData)
|
console.log('创建工单请求数据:', requestData)
|
||||||
|
console.log('选中的工人数量:', selectedWorkers.length)
|
||||||
|
console.log('工人ID拼接结果:', workersIds)
|
||||||
|
|
||||||
const response = await apiPost('/back/orders', requestData)
|
const response = await apiPost('/back/orders', requestData)
|
||||||
|
|
||||||
@@ -834,27 +888,58 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="assignee">分配工人</Label>
|
<Label htmlFor="assignee">分配工人(可多选)</Label>
|
||||||
<Select
|
<div className="relative worker-select-container">
|
||||||
value={formData.assignee}
|
<Button
|
||||||
onValueChange={(value) => setFormData({ ...formData, assignee: value })}
|
type="button"
|
||||||
onOpenChange={(open) => {
|
variant="outline"
|
||||||
if (open && workers.length === 0 && !loadingWorkers) {
|
className="w-full justify-between"
|
||||||
fetchWorkers()
|
onClick={() => {
|
||||||
}
|
setIsWorkerSelectOpen(!isWorkerSelectOpen)
|
||||||
}}
|
if (!isWorkerSelectOpen && workers.length === 0 && !loadingWorkers) {
|
||||||
>
|
fetchWorkers()
|
||||||
<SelectTrigger>
|
}
|
||||||
<SelectValue placeholder={loadingWorkers ? "加载中..." : "选择维修工人"} />
|
}}
|
||||||
</SelectTrigger>
|
>
|
||||||
<SelectContent className="max-h-[300px] overflow-y-auto">
|
<span className="truncate">{loadingWorkers ? "加载中..." : getSelectedWorkersText()}</span>
|
||||||
{workers.map((worker) => (
|
<ChevronRight className={`h-4 w-4 ml-2 transition-transform ${isWorkerSelectOpen ? 'rotate-90' : ''}`} />
|
||||||
<SelectItem key={worker.id} value={worker.id}>
|
</Button>
|
||||||
{worker.name} - {worker.province} ({worker.phone})
|
{isWorkerSelectOpen && (
|
||||||
</SelectItem>
|
<div className="absolute z-50 w-full mt-1 bg-white border rounded-md shadow-lg max-h-[300px] overflow-y-auto">
|
||||||
))}
|
{workers.length === 0 ? (
|
||||||
</SelectContent>
|
<div className="p-4 text-center text-gray-500">暂无工人数据</div>
|
||||||
</Select>
|
) : (
|
||||||
|
<div className="p-2">
|
||||||
|
{workers.map((worker) => (
|
||||||
|
<div
|
||||||
|
key={worker.id}
|
||||||
|
className="flex items-center space-x-2 p-2 hover:bg-gray-100 rounded cursor-pointer"
|
||||||
|
onClick={() => toggleWorkerSelection(worker.id)}
|
||||||
|
>
|
||||||
|
<div className={`w-4 h-4 border rounded flex items-center justify-center ${
|
||||||
|
formData.assignee.includes(worker.id) ? 'bg-blue-600 border-blue-600' : 'border-gray-300'
|
||||||
|
}`}>
|
||||||
|
{formData.assignee.includes(worker.id) && (
|
||||||
|
<svg className="w-3 h-3 text-white" fill="currentColor" viewBox="0 0 20 20">
|
||||||
|
<path fillRule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clipRule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<span className="text-sm flex-1">
|
||||||
|
{worker.name} - {worker.province} ({worker.phone})
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{formData.assignee.length > 0 && (
|
||||||
|
<div className="text-xs text-gray-500 mt-1">
|
||||||
|
已选择: {workers.filter(w => formData.assignee.includes(w.id)).map(w => w.name).join(", ")}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user