This commit is contained in:
menxipeng
2025-11-30 21:16:56 +08:00
parent 524293bfc5
commit 55e5ffb25f
4 changed files with 139 additions and 31 deletions

View File

@@ -16,7 +16,7 @@ import {
DialogTrigger, DialogTrigger,
} from "../ui/dialog" } from "../ui/dialog"
import { Plus, Download, MapPin, Calendar, Shield, ChevronLeft, ChevronRight, Minus, Plus as PlusIcon, Search } from "lucide-react" import { Plus, Download, MapPin, Calendar, Shield, ChevronLeft, ChevronRight, Minus, Plus as PlusIcon, Search } from "lucide-react"
import { apiGet, apiPost, apiPut } from "../../lib/services/api" import { apiGet, apiPost, apiPut, apiExportFile } from "../../lib/services/api"
// 商户数据类型(根据新接口返回格式定义) // 商户数据类型(根据新接口返回格式定义)
interface ProvinceMerchant { interface ProvinceMerchant {
@@ -98,6 +98,7 @@ export default function EquipmentPage() {
const [loadingStats, setLoadingStats] = useState(false) const [loadingStats, setLoadingStats] = useState(false)
const [merchantSearchForAdd, setMerchantSearchForAdd] = useState("") const [merchantSearchForAdd, setMerchantSearchForAdd] = useState("")
const [merchantSearchForEdit, setMerchantSearchForEdit] = useState("") const [merchantSearchForEdit, setMerchantSearchForEdit] = useState("")
const [isExporting, setIsExporting] = useState(false)
// 获取设备统计数据 // 获取设备统计数据
const fetchEquipmentStats = async () => { const fetchEquipmentStats = async () => {
@@ -534,6 +535,29 @@ export default function EquipmentPage() {
} }
} }
// 导出设备数据
const handleExportEquipment = async () => {
setIsExporting(true)
try {
// 构建导出参数(包含当前搜索条件)
const exportParams = {
equipmentId: equipmentIdSearch.trim() || undefined,
equipmentName: equipmentNameSearch.trim() || undefined,
status: statusFilter !== "all" ? statusFilter : undefined,
}
// 调用导出接口
await apiExportFile('/back/equipment/export', exportParams, `设备列表_${new Date().toISOString().split('T')[0]}.xlsx`)
console.log('导出设备数据成功')
} catch (error) {
console.error('导出设备数据失败:', error)
alert('导出失败:' + (error instanceof Error ? error.message : '未知错误'))
} finally {
setIsExporting(false)
}
}
// 直接使用从API返回的数据不再进行前端过滤 // 直接使用从API返回的数据不再进行前端过滤
const filteredEquipment = equipmentList const filteredEquipment = equipmentList
@@ -1228,9 +1252,14 @@ export default function EquipmentPage() {
<SelectItem value="3"></SelectItem> <SelectItem value="3"></SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
<Button variant="outline" className="w-full sm:w-auto h-9 sm:h-10 text-sm"> <Button
variant="outline"
className="w-full sm:w-auto h-9 sm:h-10 text-sm"
onClick={handleExportEquipment}
disabled={isExporting}
>
<Download className="h-4 w-4 mr-2" /> <Download className="h-4 w-4 mr-2" />
{isExporting ? "导出中..." : "导出"}
</Button> </Button>
</div> </div>
</div> </div>

View File

@@ -15,7 +15,7 @@ import {
DialogTitle, DialogTitle,
} from "../ui/dialog" } from "../ui/dialog"
import { Plus, Search, Filter, Download, Store, Eye, Building2, MapPin, ChevronLeft, ChevronRight, Edit } from "lucide-react" import { Plus, Search, Filter, Download, Store, Eye, Building2, MapPin, ChevronLeft, ChevronRight, Edit } from "lucide-react"
import { apiGet, apiPost, apiPut } from "../../lib/services/api" import { apiGet, apiPost, apiPut, apiExportFile } from "../../lib/services/api"
// 总商户数据类型 // 总商户数据类型
interface TotalMerchant { interface TotalMerchant {
@@ -87,6 +87,7 @@ export default function MerchantsPage() {
countExpire: 0 countExpire: 0
}) })
const [loadingUserEquipmentCount, setLoadingUserEquipmentCount] = useState(false) const [loadingUserEquipmentCount, setLoadingUserEquipmentCount] = useState(false)
const [isExporting, setIsExporting] = useState(false)
const [newMerchant, setNewMerchant] = useState({ const [newMerchant, setNewMerchant] = useState({
name: "", name: "",
@@ -571,6 +572,30 @@ export default function MerchantsPage() {
} }
} }
// 导出商户数据
const handleExportMerchants = async () => {
setIsExporting(true)
try {
// 构建导出参数(包含当前搜索条件)
const exportParams = {
merchantName: merchantNameSearch.trim() || undefined,
contactPerson: contactPersonSearch.trim() || undefined,
contactPhone: contactPhoneSearch.trim() || undefined,
equipmentStatus: statusFilter !== "all" ? statusFilter : undefined,
}
// 调用导出接口
await apiExportFile('/back/merchants/export', exportParams, `商户列表_${new Date().toISOString().split('T')[0]}.xlsx`)
console.log('导出商户数据成功')
} catch (error) {
console.error('导出商户数据失败:', error)
alert('导出失败:' + (error instanceof Error ? error.message : '未知错误'))
} finally {
setIsExporting(false)
}
}
// 提交编辑 // 提交编辑
const handleUpdateMerchant = async () => { const handleUpdateMerchant = async () => {
if (!editMerchant.name || !editMerchant.contact || !editMerchant.phone) { if (!editMerchant.name || !editMerchant.contact || !editMerchant.phone) {
@@ -740,9 +765,13 @@ export default function MerchantsPage() {
</SelectContent> </SelectContent>
</Select> </Select>
<Button variant="outline"> <Button
variant="outline"
onClick={handleExportMerchants}
disabled={isExporting}
>
<Download className="h-4 w-4 mr-2" /> <Download className="h-4 w-4 mr-2" />
{isExporting ? '导出中...' : '导出'}
</Button> </Button>
</div> </div>
</div> </div>

View File

@@ -1,4 +1,5 @@
import { useState, useEffect } from "react"; import { useState, useEffect } from "react";
import { useSearchParams } from "react-router-dom";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
@@ -47,6 +48,7 @@ interface ArchivedWorkOrder {
} }
export default function WorkOrderArchivePage() { export default function WorkOrderArchivePage() {
const [searchParams, setSearchParams] = useSearchParams();
const [archivedOrders, setArchivedOrders] = useState<ArchivedWorkOrder[]>([]); const [archivedOrders, setArchivedOrders] = useState<ArchivedWorkOrder[]>([]);
const [searchTerm, setSearchTerm] = useState(""); const [searchTerm, setSearchTerm] = useState("");
const [statusFilter, setStatusFilter] = useState("all"); const [statusFilter, setStatusFilter] = useState("all");
@@ -81,14 +83,15 @@ export default function WorkOrderArchivePage() {
}; };
// 获取归档工单列表 // 获取归档工单列表
const fetchArchivedOrders = async (pageNum = 1, pageSize = 10) => { const fetchArchivedOrders = async (pageNum = 1, pageSize = 10, searchValue?: string) => {
setLoading(true); setLoading(true);
try { try {
let url = `/back/workOrderArchive/list?pageNum=${pageNum}&pageSize=${pageSize}`; let url = `/back/workOrderArchive/list?pageNum=${pageNum}&pageSize=${pageSize}`;
// 添加工单编号搜索参数 // 添加工单编号搜索参数,优先使用传入的搜索值,否则使用 state 中的 searchTerm
if (searchTerm.trim()) { const searchValueToUse = searchValue !== undefined ? searchValue : searchTerm;
url += `&workOrderNumber=${encodeURIComponent(searchTerm.trim())}`; if (searchValueToUse.trim()) {
url += `&workOrderNumber=${encodeURIComponent(searchValueToUse.trim())}`;
} }
// 添加合格/不合格筛选参数 // 添加合格/不合格筛选参数
@@ -112,9 +115,25 @@ export default function WorkOrderArchivePage() {
} }
}; };
// 从 URL 参数中读取工单号并设置搜索词
useEffect(() => {
const workOrderNumber = searchParams.get('workOrderNumber');
if (workOrderNumber) {
setSearchTerm(workOrderNumber);
// 立即搜索,不等待防抖
fetchArchivedOrders(1, 10, workOrderNumber);
// 清除 URL 参数,避免刷新时重复搜索
setSearchParams({}, { replace: true });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [searchParams]);
useEffect(() => { useEffect(() => {
fetchArchiveCount(); fetchArchiveCount();
fetchArchivedOrders(1, 10); // 只有在没有 URL 参数时才执行初始加载(避免与上面的 useEffect 重复)
if (!searchParams.get('workOrderNumber')) {
fetchArchivedOrders(1, 10);
}
}, []); }, []);
// 监听搜索词和筛选条件变化,重新请求数据 // 监听搜索词和筛选条件变化,重新请求数据

View File

@@ -1,4 +1,5 @@
import React, { useState, useEffect } from "react" import React, { useState, useEffect } from "react"
import { useNavigate } from "react-router-dom"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
import { Button } from "../ui/button" import { Button } from "../ui/button"
import { Input } from "../ui/input" import { Input } from "../ui/input"
@@ -27,6 +28,7 @@ import {
ChevronRight, ChevronRight,
Wrench, Wrench,
Edit, Edit,
FileText,
} from "lucide-react" } from "lucide-react"
import { apiGet, apiPost, apiPut } from "../../lib/services/api" import { apiGet, apiPost, apiPut } from "../../lib/services/api"
@@ -153,6 +155,7 @@ interface WorkOrder {
} }
export default function WorkOrdersPage() { export default function WorkOrdersPage() {
const navigate = useNavigate()
const [searchTerm, setSearchTerm] = useState("") const [searchTerm, setSearchTerm] = useState("")
const [statusFilter, setStatusFilter] = useState("all") const [statusFilter, setStatusFilter] = useState("all")
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false) const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
@@ -502,16 +505,30 @@ export default function WorkOrdersPage() {
</div> </div>
</TableCell> </TableCell>
<TableCell> <TableCell>
<Button <div className="flex items-center space-x-2">
variant="ghost" <Button
size="sm" variant="ghost"
onClick={() => { size="sm"
setEditingWorkOrder(item) onClick={() => {
setIsEditDialogOpen(true) setEditingWorkOrder(item)
}} setIsEditDialogOpen(true)
> }}
<Edit className="h-4 w-4" /> >
</Button> <Edit className="h-4 w-4" />
</Button>
{item.status === "7" && (
<Button
variant="ghost"
size="sm"
onClick={() => {
navigate(`/admin/workorder-archive?workOrderNumber=${encodeURIComponent(item.workOrderNumber)}`)
}}
title="查看归档"
>
<FileText className="h-4 w-4" />
</Button>
)}
</div>
</TableCell> </TableCell>
</TableRow> </TableRow>
)) ))
@@ -540,16 +557,30 @@ export default function WorkOrdersPage() {
<div className="text-sm font-medium text-gray-900">{item.equipmentName}</div> <div className="text-sm font-medium text-gray-900">{item.equipmentName}</div>
<div className="text-xs text-gray-500">: {item.equipmentId}</div> <div className="text-xs text-gray-500">: {item.equipmentId}</div>
</div> </div>
<Button <div className="flex items-center space-x-2">
variant="ghost" <Button
size="sm" variant="ghost"
onClick={() => { size="sm"
setEditingWorkOrder(item) onClick={() => {
setIsEditDialogOpen(true) setEditingWorkOrder(item)
}} setIsEditDialogOpen(true)
> }}
<Edit className="h-4 w-4" /> >
</Button> <Edit className="h-4 w-4" />
</Button>
{item.status === "7" && (
<Button
variant="ghost"
size="sm"
onClick={() => {
navigate(`/admin/workorder-archive?workOrderNumber=${encodeURIComponent(item.workOrderNumber)}`)
}}
title="查看归档"
>
<FileText className="h-4 w-4" />
</Button>
)}
</div>
</div> </div>
<div className="border-t pt-3 space-y-2"> <div className="border-t pt-3 space-y-2">