商户优化

This commit is contained in:
menxipeng
2025-11-27 21:13:11 +08:00
parent 0e99d59f01
commit de3aa54bd2
3 changed files with 208 additions and 42 deletions

View File

@@ -15,7 +15,7 @@ import {
DialogTitle,
DialogTrigger,
} from "../ui/dialog"
import { Plus, Filter, Download, MapPin, Calendar, Shield, ChevronLeft, ChevronRight, Minus, Plus as PlusIcon } from "lucide-react"
import { Plus, Filter, Download, MapPin, Calendar, Shield, ChevronLeft, ChevronRight, Minus, Plus as PlusIcon, Search } from "lucide-react"
import { apiGet, apiPost, apiPut } from "../../lib/services/api"
// 商户数据类型(根据新接口返回格式定义)
@@ -96,6 +96,8 @@ export default function EquipmentPage() {
countExpire: 0
})
const [loadingStats, setLoadingStats] = useState(false)
const [merchantSearchForAdd, setMerchantSearchForAdd] = useState("")
const [merchantSearchForEdit, setMerchantSearchForEdit] = useState("")
// 获取设备统计数据
const fetchEquipmentStats = async () => {
@@ -208,6 +210,20 @@ export default function EquipmentPage() {
}
}
// 过滤商户列表
const filterMerchants = (merchants: ProvinceMerchant[], searchTerm: string) => {
if (!searchTerm.trim()) {
return merchants
}
const lowerSearch = searchTerm.toLowerCase()
return merchants.filter(merchant =>
merchant.merchantName.toLowerCase().includes(lowerSearch) ||
merchant.contactPerson.toLowerCase().includes(lowerSearch) ||
merchant.contactPhone.includes(lowerSearch) ||
(merchant.mallLocation && merchant.mallLocation.toLowerCase().includes(lowerSearch))
)
}
useEffect(() => {
fetchEquipmentStats()
fetchEquipmentTypes()
@@ -531,7 +547,12 @@ export default function EquipmentPage() {
<Download className="h-4 w-4 mr-2" />
</Button>
<Dialog open={isAddEquipmentOpen} onOpenChange={setIsAddEquipmentOpen}>
<Dialog open={isAddEquipmentOpen} onOpenChange={(open) => {
setIsAddEquipmentOpen(open)
if (!open) {
setMerchantSearchForAdd("")
}
}}>
<DialogTrigger asChild>
<Button className="bg-black text-white hover:bg-gray-800 w-full sm:w-auto">
<Plus className="h-4 w-4 mr-2" />
@@ -585,12 +606,18 @@ export default function EquipmentPage() {
<Label htmlFor="merchant" className="text-sm sm:text-base"></Label>
<Select
value={newEquipment.merchantId}
onValueChange={(value) => setNewEquipment({ ...newEquipment, merchantId: value })}
onValueChange={(value) => {
setNewEquipment({ ...newEquipment, merchantId: value })
setMerchantSearchForAdd("")
}}
disabled={loadingMerchants}
onOpenChange={(open) => {
if (open && merchants.length === 0 && !loadingMerchants) {
fetchMerchants()
}
if (!open) {
setMerchantSearchForAdd("")
}
}}
>
<SelectTrigger>
@@ -602,18 +629,37 @@ export default function EquipmentPage() {
: "选择商户"
} />
</SelectTrigger>
<SelectContent className="max-h-[300px] overflow-y-auto">
{merchants.length === 0 && !loadingMerchants ? (
<SelectItem value="no-data" disabled>
</SelectItem>
) : (
merchants.map((merchant) => (
<SelectItem key={merchant.id} value={merchant.merchantsId || merchant.id}>
{merchant.merchantName} - {merchant.contactPerson}
<SelectContent className="max-h-[300px] overflow-hidden">
<div className="p-2 border-b">
<div className="relative">
<Search className="absolute left-2 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input
placeholder="搜索商户名称、联系人、电话..."
value={merchantSearchForAdd}
onChange={(e) => setMerchantSearchForAdd(e.target.value)}
className="pl-8 h-8 text-sm"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
/>
</div>
</div>
<div className="overflow-y-auto max-h-[240px]">
{merchants.length === 0 && !loadingMerchants ? (
<SelectItem value="no-data" disabled>
</SelectItem>
))
)}
) : filterMerchants(merchants, merchantSearchForAdd).length === 0 ? (
<div className="px-2 py-6 text-center text-sm text-gray-500">
</div>
) : (
filterMerchants(merchants, merchantSearchForAdd).map((merchant) => (
<SelectItem key={merchant.id} value={merchant.merchantsId || merchant.id}>
{merchant.merchantName} - {merchant.contactPerson}
</SelectItem>
))
)}
</div>
</SelectContent>
</Select>
</div>
@@ -729,7 +775,12 @@ export default function EquipmentPage() {
</Dialog>
{/* 编辑设备对话框 */}
<Dialog open={isEditEquipmentOpen} onOpenChange={setIsEditEquipmentOpen}>
<Dialog open={isEditEquipmentOpen} onOpenChange={(open) => {
setIsEditEquipmentOpen(open)
if (!open) {
setMerchantSearchForEdit("")
}
}}>
<DialogContent className="max-w-4xl max-h-[95vh] overflow-y-auto w-[95vw] sm:w-full p-4 sm:p-6">
<DialogHeader className="pb-3 sm:pb-4">
<DialogTitle className="text-base sm:text-lg"></DialogTitle>
@@ -777,18 +828,54 @@ export default function EquipmentPage() {
<Label htmlFor="edit-merchant" className="text-sm sm:text-base"></Label>
<Select
value={editEquipment.merchantId}
onValueChange={(value) => setEditEquipment({ ...editEquipment, merchantId: value })}
onValueChange={(value) => {
setEditEquipment({ ...editEquipment, merchantId: value })
setMerchantSearchForEdit("")
}}
disabled={loadingMerchants}
onOpenChange={(open) => {
if (open && merchants.length === 0 && !loadingMerchants) {
fetchMerchants()
}
if (!open) {
setMerchantSearchForEdit("")
}
}}
>
<SelectTrigger className="w-full">
<SelectValue placeholder={loadingMerchants ? "加载中..." : "选择商户"} />
</SelectTrigger>
<SelectContent className="max-h-[300px]">
{merchants.map((merchant) => (
<SelectItem key={merchant.id} value={merchant.merchantsId || merchant.id}>
{merchant.merchantName} - {merchant.contactPerson}
</SelectItem>
))}
<SelectContent className="max-h-[300px] overflow-hidden">
<div className="p-2 border-b">
<div className="relative">
<Search className="absolute left-2 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input
placeholder="搜索商户名称、联系人、电话..."
value={merchantSearchForEdit}
onChange={(e) => setMerchantSearchForEdit(e.target.value)}
className="pl-8 h-8 text-sm"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
/>
</div>
</div>
<div className="overflow-y-auto max-h-[240px]">
{merchants.length === 0 && !loadingMerchants ? (
<div className="px-2 py-6 text-center text-sm text-gray-500">
</div>
) : filterMerchants(merchants, merchantSearchForEdit).length === 0 ? (
<div className="px-2 py-6 text-center text-sm text-gray-500">
</div>
) : (
filterMerchants(merchants, merchantSearchForEdit).map((merchant) => (
<SelectItem key={merchant.id} value={merchant.merchantsId || merchant.id}>
{merchant.merchantName} - {merchant.contactPerson}
</SelectItem>
))
)}
</div>
</SelectContent>
</Select>
</div>

View File

@@ -1,4 +1,4 @@
import { useState, useEffect } from "react"
import { useState, useEffect, useMemo } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
import { Button } from "../ui/button"
import { Input } from "../ui/input"
@@ -316,6 +316,14 @@ export default function MerchantsPage() {
fetchMerchants(1, size, merchantNameSearch, contactPersonSearch, contactPhoneSearch, statusFilter)
}
// 状态筛选变化处理
const handleStatusFilterChange = (status: string) => {
setStatusFilter(status)
setCurrentPage(1) // 重置到第一页
// 状态筛选变化时立即触发 API 请求
fetchMerchants(1, pageSize, merchantNameSearch, contactPersonSearch, contactPhoneSearch, status)
}
// 组件加载时获取数据
useEffect(() => {
fetchUserRole()
@@ -567,8 +575,28 @@ export default function MerchantsPage() {
fetchMerchantEquipments(merchantId)
}
// 直接使用从后端返回的商户列表(搜索和状态筛选已由后端处理)
const filteredMerchants = merchants
// 前端状态筛选
const filteredMerchants = useMemo(() => {
if (statusFilter === "all") {
return merchants
}
return merchants.filter((merchant) => {
switch (statusFilter) {
case "normal":
return (merchant.normalCount || 0) > 0
case "expiring":
return (merchant.expiringCount || 0) > 0
case "expired":
return (merchant.expiredCount || 0) > 0
default:
return true
}
})
}, [merchants, statusFilter])
// 计算过滤后的总数
const filteredTotal = filteredMerchants.length
return (
<div className="space-y-4 sm:space-y-6 p-4 sm:p-6">
@@ -589,7 +617,11 @@ export default function MerchantsPage() {
<Card>
<CardHeader>
<CardTitle></CardTitle>
<CardDescription> - {total} {currentPage} </CardDescription>
<CardDescription>
- {total}
{statusFilter !== "all" && `(当前页显示 ${filteredTotal} 条已过滤)`}
{currentPage}
</CardDescription>
</CardHeader>
<CardContent>
<div className="flex flex-col gap-4 mb-6">
@@ -624,7 +656,7 @@ export default function MerchantsPage() {
</div>
<div className="flex flex-col sm:flex-row gap-4">
<Select value={statusFilter} onValueChange={setStatusFilter}>
<Select value={statusFilter} onValueChange={handleStatusFilterChange}>
<SelectTrigger className="w-full sm:w-48">
<Filter className="h-4 w-4 mr-2" />
<SelectValue placeholder="筛选状态" />
@@ -633,14 +665,14 @@ export default function MerchantsPage() {
<SelectItem value="all">
{loadingUserEquipmentCount ? '' : `(${userEquipmentCount.totalCount})`}
</SelectItem>
<SelectItem value="normal">
{loadingUserEquipmentCount ? '' : `(${userEquipmentCount.count})`}
<SelectItem value="1">
{loadingUserEquipmentCount ? '' : `(${userEquipmentCount.count})`}
</SelectItem>
<SelectItem value="expiring">
{loadingUserEquipmentCount ? '' : `(${userEquipmentCount.countEnd})`}
<SelectItem value="2">
{loadingUserEquipmentCount ? '' : `(${userEquipmentCount.countEnd})`}
</SelectItem>
<SelectItem value="expired">
{loadingUserEquipmentCount ? '' : `(${userEquipmentCount.countExpire})`}
<SelectItem value="3">
{loadingUserEquipmentCount ? '' : `(${userEquipmentCount.countExpire})`}
</SelectItem>
</SelectContent>
</Select>
@@ -700,7 +732,7 @@ export default function MerchantsPage() {
<TableCell>
<div className="flex items-center">
<Building2 className="h-4 w-4 mr-1 text-gray-400" />
{merchant.mallLocation || "无"}
{merchant.mallName || "无"}
</div>
</TableCell>
<TableCell>{merchant.dealerName || "无"}</TableCell>
@@ -789,7 +821,7 @@ export default function MerchantsPage() {
<div className="text-xs text-gray-500 mb-1"></div>
<div className="flex items-center text-sm">
<Building2 className="h-3 w-3 mr-1 text-gray-400" />
{merchant.mallLocation || "无"}
{merchant.mallName || "无"}
</div>
</div>

View File

@@ -695,6 +695,7 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
const [loadingWorkers, setLoadingWorkers] = useState(false)
const [submitting, setSubmitting] = useState(false)
const [isWorkerSelectOpen, setIsWorkerSelectOpen] = useState(false)
const [merchantSearch, setMerchantSearch] = useState("")
// 获取商户列表
const fetchMerchants = async () => {
@@ -711,6 +712,20 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
}
}
// 过滤商户列表
const filterMerchants = (merchants: ProvinceMerchant[], searchTerm: string) => {
if (!searchTerm.trim()) {
return merchants
}
const lowerSearch = searchTerm.toLowerCase()
return merchants.filter(merchant =>
merchant.merchantName.toLowerCase().includes(lowerSearch) ||
merchant.contactPerson.toLowerCase().includes(lowerSearch) ||
merchant.contactPhone.includes(lowerSearch) ||
(merchant.mallLocation && merchant.mallLocation.toLowerCase().includes(lowerSearch))
)
}
// 获取商户设备列表
const fetchMerchantEquipments = async (merchantId: string) => {
setLoadingEquipment(true)
@@ -898,6 +913,7 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
if (response.code === 200) {
alert('工单创建成功!')
setMerchantSearch("")
onClose()
// 可以在这里刷新工单列表或者调用回调函数
} else {
@@ -918,22 +934,53 @@ function CreateWorkOrderForm({ onClose }: { onClose: () => void }) {
<Label htmlFor="merchant" className="text-sm sm:text-base"></Label>
<Select
value={selectedMerchant}
onValueChange={handleMerchantChange}
onValueChange={(value) => {
handleMerchantChange(value)
setMerchantSearch("")
}}
onOpenChange={(open) => {
if (open && merchants.length === 0 && !loadingMerchants) {
fetchMerchants()
}
if (!open) {
setMerchantSearch("")
}
}}
>
<SelectTrigger>
<SelectValue placeholder={loadingMerchants ? "加载中..." : "请先选择商户"} />
</SelectTrigger>
<SelectContent className="max-h-[300px] overflow-y-auto">
{merchants.map((merchant) => (
<SelectItem key={merchant.id} value={merchant.id}>
{merchant.merchantName} - {merchant.contactPerson} - {merchant.mallLocation || "无商场"}
</SelectItem>
))}
<SelectContent className="max-h-[300px] overflow-hidden">
<div className="p-2 border-b">
<div className="relative">
<Search className="absolute left-2 top-1/2 transform -translate-y-1/2 text-gray-400 h-4 w-4" />
<Input
placeholder="搜索商户名称、联系人、电话..."
value={merchantSearch}
onChange={(e) => setMerchantSearch(e.target.value)}
className="pl-8 h-8 text-sm"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
/>
</div>
</div>
<div className="overflow-y-auto max-h-[240px]">
{merchants.length === 0 && !loadingMerchants ? (
<div className="px-2 py-6 text-center text-sm text-gray-500">
</div>
) : filterMerchants(merchants, merchantSearch).length === 0 ? (
<div className="px-2 py-6 text-center text-sm text-gray-500">
</div>
) : (
filterMerchants(merchants, merchantSearch).map((merchant) => (
<SelectItem key={merchant.id} value={merchant.id}>
{merchant.merchantName} - {merchant.contactPerson} - {merchant.mallLocation || "无商场"}
</SelectItem>
))
)}
</div>
</SelectContent>
</Select>
</div>