工单
This commit is contained in:
@@ -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)
|
||||
@@ -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>
|
||||
@@ -692,7 +722,7 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
|
||||
|
||||
// 厨房自动灭火设备 - kitchen_automatic_fire_extinguisher
|
||||
if (equipmentType === "kitchen_automatic_fire_extinguisher" || equipmentType === "厨房自动灭火设备") {
|
||||
return ["故障检测", "故障维修", "设备安装", "预防性维护", "设备改造", "设备拆除", "更换药剂"]
|
||||
return ["故障检测", "故障维修", "设备安装", "设备改造", "设备拆除", "更换药剂"]
|
||||
}
|
||||
|
||||
// 动火离人 - fire_extinguisher
|
||||
@@ -701,7 +731,7 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
|
||||
}
|
||||
|
||||
// 默认返回所有类型
|
||||
return ["故障检测", "故障维修", "设备安装", "预防性维护", "设备改造", "设备拆除", "更换药剂"]
|
||||
return ["故障检测", "故障维修", "设备安装", "设备改造", "设备拆除", "更换药剂"]
|
||||
}
|
||||
|
||||
// 切换工人选择
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user