工单类型

This commit is contained in:
menxipeng
2025-11-09 21:29:16 +08:00
parent e5f6869e08
commit abd5a99502
3 changed files with 313 additions and 60 deletions

View File

@@ -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

View File

@@ -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>

View File

@@ -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>