This commit is contained in:
menxipeng
2025-11-10 21:03:29 +08:00
parent abd5a99502
commit 2000f315cc

View File

@@ -23,12 +23,12 @@ import {
CheckCircle,
User,
MapPin,
MoreHorizontal,
ChevronLeft,
ChevronRight,
Wrench,
Edit,
} from "lucide-react"
import { apiGet, apiPost } from "../../lib/services/api"
import { apiGet, apiPost, apiPut } from "../../lib/services/api"
// 商户数据类型接口
interface ProvinceMerchant {
@@ -157,6 +157,8 @@ export default function WorkOrdersPage() {
const [statusFilter, setStatusFilter] = useState("all")
const [workerFilter, setWorkerFilter] = useState("all")
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
const [editingWorkOrder, setEditingWorkOrder] = useState<WorkOrder | null>(null)
const [workOrders, setWorkOrders] = useState<WorkOrder[]>([])
const [loadingWorkOrders, setLoadingWorkOrders] = useState(false)
const [totalWorkOrders, setTotalWorkOrders] = useState(0)
@@ -218,7 +220,7 @@ export default function WorkOrdersPage() {
const getStatusBadge = (status: string | number) => {
const statusValue = typeof status === 'string' ? parseInt(status) : status
switch (statusValue) {
case WorkOrderStatus.PENDING_ACCEPT:
return <Badge className="bg-gray-100 text-gray-800"></Badge>
@@ -267,7 +269,7 @@ export default function WorkOrdersPage() {
item.equipmentName.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.merchantName.toLowerCase().includes(searchTerm.toLowerCase()) ||
item.workerName.toLowerCase().includes(searchTerm.toLowerCase())
// 使用工单的实际状态进行过滤
const itemStatus = item.status || "1" // 默认为待接单状态
const matchesStatus = statusFilter === "all" || itemStatus === statusFilter
@@ -302,6 +304,27 @@ export default function WorkOrdersPage() {
}} />
</DialogContent>
</Dialog>
{/* 编辑工单对话框 */}
<Dialog open={isEditDialogOpen} onOpenChange={setIsEditDialogOpen}>
<DialogContent className="max-w-2xl max-h-[90vh] overflow-y-auto">
<DialogHeader>
<DialogTitle></DialogTitle>
<DialogDescription></DialogDescription>
</DialogHeader>
{editingWorkOrder && (
<EditWorkOrderForm
workOrder={editingWorkOrder}
onClose={() => {
setIsEditDialogOpen(false)
setEditingWorkOrder(null)
fetchWorkOrders(currentPage, pageSize)
fetchStatusCounts()
}}
/>
)}
</DialogContent>
</Dialog>
</div>
{/* Stats Cards */}
@@ -503,8 +526,15 @@ export default function WorkOrdersPage() {
</div>
</TableCell>
<TableCell>
<Button variant="ghost" size="sm">
<MoreHorizontal className="h-4 w-4" />
<Button
variant="ghost"
size="sm"
onClick={() => {
setEditingWorkOrder(item)
setIsEditDialogOpen(true)
}}
>
<Edit className="h-4 w-4" />
</Button>
</TableCell>
</TableRow>
@@ -687,21 +717,21 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
}
const equipmentType = selectedEquipment.equipmentType
console.log('选择的设备类型:', equipmentType)
// 厨房自动灭火设备 - kitchen_automatic_fire_extinguisher
if (equipmentType === "kitchen_automatic_fire_extinguisher" || equipmentType === "厨房自动灭火设备") {
return ["故障检测", "故障维修", "设备安装", "预防性维护", "设备改造", "设备拆除", "更换药剂"]
return ["故障检测", "故障维修", "设备安装", "设备改造", "设备拆除", "更换药剂"]
}
// 动火离人 - fire_extinguisher
if (equipmentType === "fire_extinguisher" || equipmentType === "动火离人") {
return ["故障维修", "设备安装", "设备改造", "设备拆除"]
}
// 默认返回所有类型
return ["故障检测", "故障维修", "设备安装", "预防性维护", "设备改造", "设备拆除", "更换药剂"]
return ["故障检测", "故障维修", "设备安装", "设备改造", "设备拆除", "更换药剂"]
}
// 切换工人选择
@@ -863,17 +893,17 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
<div className="space-y-2">
<Label htmlFor="type"></Label>
<Select
value={formData.type}
<Select
value={formData.type}
onValueChange={(value) => setFormData({ ...formData, type: value })}
disabled={!formData.equipmentId}
>
<SelectTrigger className="w-full">
<SelectValue placeholder={
!formData.equipmentId
? "请先选择设备"
: getAvailableWorkOrderTypes().length === 0
? "无可用工单类型"
!formData.equipmentId
? "请先选择设备"
: getAvailableWorkOrderTypes().length === 0
? "无可用工单类型"
: "选择类型"
} />
</SelectTrigger>
@@ -965,3 +995,383 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
</form>
)
}
function EditWorkOrderForm({ workOrder, onClose }: { workOrder: WorkOrder; onClose: () => void }) {
const [formData, setFormData] = useState({
type: workOrder.workOrderType || "",
assignee: workOrder.workersId ? workOrder.workersId.split(",") : [] as string[],
description: workOrder.remark || "",
status: workOrder.status || "1",
})
const [selectedMerchant, setSelectedMerchant] = useState("")
const [availableEquipment, setAvailableEquipment] = useState<Equipment[]>([])
const [merchants, setMerchants] = useState<ProvinceMerchant[]>([])
const [workers, setWorkers] = useState<Worker[]>([])
const [loadingWorkers, setLoadingWorkers] = useState(false)
const [submitting, setSubmitting] = useState(false)
const [isWorkerSelectOpen, setIsWorkerSelectOpen] = useState(false)
// 初始化表单数据
useEffect(() => {
// 查找商户
const findMerchant = async () => {
try {
const response = await apiGet('/back/general/provinceMerchants')
if (response.code === 200) {
const merchantList = response.data || []
setMerchants(merchantList)
// 根据商户名称或ID查找对应的商户
const foundMerchant = merchantList.find((m: ProvinceMerchant) =>
m.merchantName === workOrder.merchantName ||
m.merchantsId === workOrder.merchantUserId ||
m.id === workOrder.merchantUserId
)
if (foundMerchant) {
setSelectedMerchant(foundMerchant.id)
// 获取该商户的设备列表
const merchantIdForEquipment = foundMerchant.merchantsId || foundMerchant.id
await fetchMerchantEquipments(merchantIdForEquipment)
}
}
} catch (error) {
console.error('获取商户列表失败:', error)
}
}
// 获取工人列表
const loadWorkers = async () => {
setLoadingWorkers(true)
try {
const response = await apiGet('/back/workers/dis')
if (response.code === 200) {
const workerList = response.data || []
setWorkers(workerList)
// 根据工人名称匹配已选中的工人
if (workOrder.workerName) {
const workerNames = workOrder.workerName.split(",")
const matchedWorkerIds = workerList
.filter((w: Worker) => workerNames.includes(w.name))
.map((w: Worker) => w.id)
setFormData(prev => ({ ...prev, assignee: matchedWorkerIds }))
}
}
} catch (error) {
console.error('获取工人列表失败:', error)
} finally {
setLoadingWorkers(false)
}
}
findMerchant()
loadWorkers()
}, [workOrder])
// 获取商户设备列表
const fetchMerchantEquipments = async (merchantId: string) => {
try {
const response = await apiGet(`/back/equipment/list?pageNum=1&pageSize=9999&merchantId=${merchantId}`)
if (response.code === 200) {
const equipmentList = response.rows || []
setAvailableEquipment(equipmentList)
// 查找当前工单对应的设备
const foundEquipment = equipmentList.find((e: Equipment) =>
e.equipmentId === workOrder.equipmentId
)
if (foundEquipment) {
setFormData(prev => ({ ...prev, type: workOrder.workOrderType || "" }))
}
}
} catch (error) {
console.error('获取设备列表失败:', error)
setAvailableEquipment([])
}
}
// 点击外部关闭下拉框
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 getAvailableWorkOrderTypes = () => {
if (!workOrder.equipmentId) {
return []
}
const selectedEquipment = availableEquipment.find(e => e.equipmentId === workOrder.equipmentId)
if (!selectedEquipment) {
return []
}
const equipmentType = selectedEquipment.equipmentType
// 厨房自动灭火设备
if (equipmentType === "kitchen_automatic_fire_extinguisher" || equipmentType === "厨房自动灭火设备") {
return ["故障检测", "故障维修", "设备安装", "设备改造", "设备拆除", "更换药剂"]
}
// 动火离人
if (equipmentType === "fire_extinguisher" || equipmentType === "动火离人") {
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) => {
e.preventDefault()
if (formData.assignee.length === 0) {
alert('请至少选择一位工人')
return
}
setSubmitting(true)
try {
// 找到选中的商户信息
const selectedMerchantObj = merchants.find(m => m.id === selectedMerchant)
// 找到选中的设备信息
const selectedEquipmentObj = availableEquipment.find(e => e.equipmentId === workOrder.equipmentId)
// 找到所有选中的工人信息
const selectedWorkers = workers.filter(w => formData.assignee.includes(w.id))
if (!selectedMerchantObj || !selectedEquipmentObj || selectedWorkers.length === 0) {
alert('无法获取完整的商户、设备或工人信息')
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 || ""
// 构建API请求数据
const requestData = {
id: workOrder.id,
merchantUserId: selectedMerchantObj.merchantsId || workOrder.merchantUserId,
distributorUserId: distributorUserId || workOrder.distributorUserId,
workersId: workersIds,
workOrderNumber: workOrder.workOrderNumber,
workOrderType: formData.type,
workOrderSubtype: workOrder.workOrderSubtype || "",
equipmentId: workOrder.equipmentId,
equipmentName: selectedEquipmentObj.equipmentName || workOrder.equipmentName,
merchantName: selectedMerchantObj.merchantName || workOrder.merchantName,
merchantLocation: selectedMerchantObj.mallLocation || workOrder.merchantLocation || "",
merchantAddress: selectedMerchantObj.fullAddress || workOrder.merchantAddress,
merchantCity: selectedMerchantObj.city || workOrder.merchantCity || "",
merchantDistrict: selectedMerchantObj.district || workOrder.merchantDistrict || "",
responsiblePerson: selectedMerchantObj.contactPerson || workOrder.responsiblePerson,
responsibleVirtualPhone: selectedMerchantObj.contactPhone || workOrder.responsibleVirtualPhone,
workerVirtualPhone: workerPhones,
workerName: workerNames,
status: formData.status,
createdDate: workOrder.createdDate,
completedDate: workOrder.completedDate || "",
remark: formData.description
}
console.log('更新工单请求数据:', requestData)
const response = await apiPut('/back/orders', requestData)
if (response.code === 200) {
alert('工单更新成功!')
onClose()
} else {
alert(`更新工单失败: ${response.msg || '未知错误'}`)
}
} catch (error) {
console.error('更新工单失败:', error)
alert('更新工单失败,请稍后重试')
} finally {
setSubmitting(false)
}
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div className="space-y-4">
<div className="space-y-2">
<Label></Label>
<div className="p-3 bg-gray-50 rounded-md">
<div className="text-sm font-medium">{workOrder.merchantName}</div>
<div className="text-xs text-gray-500 mt-1">{workOrder.merchantLocation || "无商场"}</div>
<div className="text-xs text-gray-500">{workOrder.merchantAddress}</div>
</div>
</div>
<div className="space-y-2">
<Label></Label>
<div className="p-3 bg-gray-50 rounded-md">
<div className="text-sm font-medium">{workOrder.equipmentName}</div>
<div className="text-xs text-gray-500 mt-1">ID: {workOrder.equipmentId}</div>
</div>
</div>
<div className="space-y-2">
<Label></Label>
<div className="p-3 bg-gray-50 rounded-md">
<div className="text-sm font-medium">{workOrder.workOrderNumber}</div>
</div>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="type"></Label>
<Select
value={formData.type}
onValueChange={(value) => setFormData({ ...formData, type: value })}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="选择类型" />
</SelectTrigger>
<SelectContent className="max-h-[300px]">
{getAvailableWorkOrderTypes().map((type) => (
<SelectItem key={type} value={type}>
{type}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="status"></Label>
<Select
value={formData.status}
onValueChange={(value) => setFormData({ ...formData, status: value })}
>
<SelectTrigger className="w-full">
<SelectValue placeholder="选择状态" />
</SelectTrigger>
<SelectContent className="max-h-[300px]">
<SelectItem value="1"></SelectItem>
<SelectItem value="2"></SelectItem>
<SelectItem value="3"></SelectItem>
<SelectItem value="4"></SelectItem>
<SelectItem value="5"></SelectItem>
<SelectItem value="6"></SelectItem>
<SelectItem value="7"></SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="assignee"></Label>
<div className="relative worker-select-container">
<Button
type="button"
variant="outline"
className="w-full justify-between"
onClick={() => {
setIsWorkerSelectOpen(!isWorkerSelectOpen)
if (!isWorkerSelectOpen && workers.length === 0 && !loadingWorkers) {
// 已经在useEffect中加载了
}
}}
>
<span className="truncate">{loadingWorkers ? "加载中..." : getSelectedWorkersText()}</span>
<ChevronRight className={`h-4 w-4 ml-2 transition-transform ${isWorkerSelectOpen ? 'rotate-90' : ''}`} />
</Button>
{isWorkerSelectOpen && (
<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 ? (
<div className="p-4 text-center text-gray-500"></div>
) : (
<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 className="space-y-2">
<Label htmlFor="description"></Label>
<Textarea
id="description"
placeholder="请描述需要执行的工作内容..."
value={formData.description}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
rows={3}
/>
</div>
<div className="flex justify-end space-x-2">
<Button type="button" variant="outline" onClick={onClose} disabled={submitting}>
</Button>
<Button type="submit" disabled={submitting}>
{submitting ? "更新中..." : "更新工单"}
</Button>
</div>
</form>
)
}