商户优化
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user