This commit is contained in:
menxipeng
2025-10-21 23:23:46 +08:00
parent e77d28abf7
commit 0d32f2d187
6 changed files with 561 additions and 183 deletions

View File

@@ -745,7 +745,7 @@ export default function EquipmentPage() {
<div className="space-y-1">
<div className="flex items-center space-x-2">
<Shield className="h-4 w-4 text-blue-600" />
<span className="font-medium text-blue-600">{item.equipmentId}</span>
<span className="font-medium text-blue-600">{item.equId}</span>
</div>
<div className="text-sm font-medium">{item.equipmentName}</div>
<div className="text-xs text-gray-500">: {equipmentTypeDisplay}</div>

View File

@@ -1,4 +1,4 @@
import React, { useState } from "react"
import { useState, useEffect } from "react"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../ui/card"
import { Button } from "../ui/button"
import { Input } from "../ui/input"
@@ -16,121 +16,165 @@ import {
DialogTrigger,
} from "../ui/dialog"
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../ui/table"
import { Plus, Edit, Trash2, Phone } from "lucide-react"
interface ValueAddedService {
id: string
title: string
description: string
status: "active" | "inactive"
assignedDealers: string[]
createdAt: string
updatedAt: string
}
interface Dealer {
id: string
name: string
phone: string
region: string
}
import { Plus, Edit, Trash2, Phone, ChevronLeft, ChevronRight } from "lucide-react"
import { getServiceList, createService, updateService, deleteService } from "@/lib/services/service"
import { ValueAddedService } from "@/lib/types/service"
import { getUserByRoleKey } from "@/lib/services/user"
import type { User } from "@/lib/types/user"
export default function ValueAddedServicesPage() {
const [services, setServices] = useState<ValueAddedService[]>([
{
id: "1",
title: "消防设备年检服务",
description: "提供专业的消防设备年度检测服务,确保设备符合安全标准",
status: "active",
assignedDealers: ["dealer1", "dealer2"],
createdAt: "2024-01-15",
updatedAt: "2024-01-15",
},
{
id: "2",
title: "紧急维修服务",
description: "24小时紧急维修服务快速响应设备故障",
status: "active",
assignedDealers: ["dealer1", "dealer3"],
createdAt: "2024-01-10",
updatedAt: "2024-01-20",
},
])
const [dealers] = useState<Dealer[]>([
{ id: "dealer1", name: "华东经销商", phone: "138****1234", region: "华东区" },
{ id: "dealer2", name: "华南经销商", phone: "139****5678", region: "华南区" },
{ id: "dealer3", name: "华北经销商", phone: "137****9012", region: "华北区" },
])
const [services, setServices] = useState<ValueAddedService[]>([])
const [adminUsers, setAdminUsers] = useState<User[]>([])
const [commonUsers, setCommonUsers] = useState<User[]>([])
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false)
const [isEditDialogOpen, setIsEditDialogOpen] = useState(false)
const [selectedService, setSelectedService] = useState<ValueAddedService | null>(null)
const [loading, setLoading] = useState(false)
const [pageNum, setPageNum] = useState(1)
const [pageSize, setPageSize] = useState(10)
const [total, setTotal] = useState(0)
const [formData, setFormData] = useState({
title: "",
serviceTitle: "",
description: "",
status: "active" as "active" | "inactive",
assignedDealers: [] as string[],
status: "1",
assignedAdmin: "",
assignedCommon: "",
})
const handleCreateService = () => {
const newService: ValueAddedService = {
id: Date.now().toString(),
title: formData.title,
description: formData.description,
status: formData.status,
assignedDealers: formData.assignedDealers,
createdAt: new Date().toISOString().split("T")[0],
updatedAt: new Date().toISOString().split("T")[0],
useEffect(() => {
loadServices()
loadUsers()
}, [pageNum, pageSize])
const loadServices = async () => {
setLoading(true)
try {
const response = await getServiceList({ pageNum, pageSize })
if (response.code === 200) {
setServices(response.rows)
setTotal(response.total)
}
setServices([...services, newService])
} catch (error) {
console.error("加载服务列表失败:", error)
} finally {
setLoading(false)
}
}
const loadUsers = async () => {
try {
const [adminResponse, commonResponse] = await Promise.all([
getUserByRoleKey("admin"),
getUserByRoleKey("common"),
])
if (adminResponse.code === 200 && adminResponse.data) {
setAdminUsers(adminResponse.data)
}
if (commonResponse.code === 200 && commonResponse.data) {
setCommonUsers(commonResponse.data)
}
} catch (error) {
console.error("加载用户列表失败:", error)
}
}
const handleCreateService = async () => {
setLoading(true)
try {
const response = await createService(formData)
if (response.code === 200) {
setIsCreateDialogOpen(false)
resetForm()
loadServices()
} else {
alert(response.msg || "创建失败")
}
} catch (error) {
console.error("创建服务失败:", error)
alert("创建服务失败")
} finally {
setLoading(false)
}
}
const handleEditService = () => {
const handleEditService = async () => {
if (!selectedService) return
const updatedServices = services.map((service) =>
service.id === selectedService.id
? {
...service,
title: formData.title,
description: formData.description,
status: formData.status,
assignedDealers: formData.assignedDealers,
updatedAt: new Date().toISOString().split("T")[0],
}
: service,
)
setServices(updatedServices)
setLoading(true)
try {
const response = await updateService({
id: selectedService.id,
...formData,
})
if (response.code === 200) {
setIsEditDialogOpen(false)
resetForm()
loadServices()
} else {
alert(response.msg || "更新失败")
}
} catch (error) {
console.error("更新服务失败:", error)
alert("更新服务失败")
} finally {
setLoading(false)
}
}
const handleDeleteService = (id: string) => {
setServices(services.filter((service) => service.id !== id))
const handleDeleteService = async (id: string) => {
if (!confirm("确定要删除该服务吗?")) return
setLoading(true)
try {
const response = await deleteService(id)
if (response.code === 200) {
loadServices()
} else {
alert(response.msg || "删除失败")
}
} catch (error) {
console.error("删除服务失败:", error)
alert("删除服务失败")
} finally {
setLoading(false)
}
}
const toggleServiceStatus = (id: string) => {
const updatedServices = services.map((service) =>
service.id === id
? {
...service,
status: service.status === "active" ? ("inactive" as const) : ("active" as const),
updatedAt: new Date().toISOString().split("T")[0],
const toggleServiceStatus = async (service: ValueAddedService) => {
setLoading(true)
try {
const newStatus = service.status === "1" ? "0" : "1"
const response = await updateService({
id: service.id,
serviceTitle: service.serviceTitle,
description: service.description,
status: newStatus,
assignedAdmin: service.assignedAdmin || "",
assignedCommon: service.assignedCommon || "",
})
if (response.code === 200) {
loadServices()
} else {
alert(response.msg || "更新状态失败")
}
} catch (error) {
console.error("更新状态失败:", error)
alert("更新状态失败")
} finally {
setLoading(false)
}
: service,
)
setServices(updatedServices)
}
const resetForm = () => {
setFormData({
title: "",
serviceTitle: "",
description: "",
status: "active",
assignedDealers: [],
status: "1",
assignedAdmin: "",
assignedCommon: "",
})
setSelectedService(null)
}
@@ -138,16 +182,37 @@ export default function ValueAddedServicesPage() {
const openEditDialog = (service: ValueAddedService) => {
setSelectedService(service)
setFormData({
title: service.title,
serviceTitle: service.serviceTitle,
description: service.description,
status: service.status,
assignedDealers: service.assignedDealers,
assignedAdmin: service.assignedAdmin || "",
assignedCommon: service.assignedCommon || "",
})
setIsEditDialogOpen(true)
}
const getDealerName = (dealerId: string) => {
return dealers.find((dealer) => dealer.id === dealerId)?.name || "未知经销商"
const getUserName = (userId: string, userList: User[]) => {
const user = userList.find((u) => u.userId === userId)
return user ? (user.nickName || user.userName) : "未知用户"
}
const getUsersByIds = (ids: string) => {
if (!ids) return []
return ids.split(",").filter(Boolean)
}
const totalPages = Math.ceil(total / pageSize)
const handlePrevPage = () => {
if (pageNum > 1) {
setPageNum(pageNum - 1)
}
}
const handleNextPage = () => {
if (pageNum < totalPages) {
setPageNum(pageNum + 1)
}
}
return (
@@ -175,8 +240,8 @@ export default function ValueAddedServicesPage() {
<Label htmlFor="title"></Label>
<Input
id="title"
value={formData.title}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
value={formData.serviceTitle}
onChange={(e) => setFormData({ ...formData, serviceTitle: e.target.value })}
placeholder="请输入服务标题"
/>
</div>
@@ -191,48 +256,95 @@ export default function ValueAddedServicesPage() {
/>
</div>
<div>
<Label></Label>
<div className="grid grid-cols-1 gap-2 mt-2">
{dealers.map((dealer) => (
<div key={dealer.id} className="flex items-center space-x-2 p-2 border rounded">
<Label></Label>
<div className="grid grid-cols-1 gap-2 mt-2 max-h-48 overflow-y-auto border rounded p-2">
{adminUsers.map((user) => {
const assignedIds = formData.assignedAdmin.split(",").filter(Boolean)
const userId = user.userId
return (
<div key={userId} className="flex items-center space-x-2 p-2 border rounded">
<input
type="checkbox"
id={dealer.id}
checked={formData.assignedDealers.includes(dealer.id)}
id={`admin-${userId}`}
checked={assignedIds.includes(userId)}
onChange={(e) => {
const ids = formData.assignedAdmin.split(",").filter(Boolean)
if (e.target.checked) {
setFormData({
...formData,
assignedDealers: [...formData.assignedDealers, dealer.id],
assignedAdmin: [...ids, userId].join(","),
})
} else {
setFormData({
...formData,
assignedDealers: formData.assignedDealers.filter((id) => id !== dealer.id),
assignedAdmin: ids.filter((id) => id !== userId).join(","),
})
}
}}
/>
<label htmlFor={dealer.id} className="flex-1 cursor-pointer">
<label htmlFor={`admin-${userId}`} className="flex-1 cursor-pointer">
<div className="flex justify-between">
<span className="font-medium">{dealer.name}</span>
<span className="text-sm text-gray-500">{dealer.region}</span>
</div>
<div className="text-sm text-gray-500 flex items-center">
<span className="font-medium">{user.nickName || user.userName}</span>
<span className="text-sm text-gray-500 flex items-center">
<Phone className="h-3 w-3 mr-1" />
{dealer.phone}
{user.phonenumber}
</span>
</div>
</label>
</div>
))}
)
})}
</div>
</div>
<div>
<Label></Label>
<div className="grid grid-cols-1 gap-2 mt-2 max-h-48 overflow-y-auto border rounded p-2">
{commonUsers.map((user) => {
const assignedIds = formData.assignedCommon.split(",").filter(Boolean)
const userId = user.userId
return (
<div key={userId} className="flex items-center space-x-2 p-2 border rounded">
<input
type="checkbox"
id={`common-${userId}`}
checked={assignedIds.includes(userId)}
onChange={(e) => {
const ids = formData.assignedCommon.split(",").filter(Boolean)
if (e.target.checked) {
setFormData({
...formData,
assignedCommon: [...ids, userId].join(","),
})
} else {
setFormData({
...formData,
assignedCommon: ids.filter((id) => id !== userId).join(","),
})
}
}}
/>
<label htmlFor={`common-${userId}`} className="flex-1 cursor-pointer">
<div className="flex justify-between">
<span className="font-medium">{user.nickName || user.userName}</span>
<span className="text-sm text-gray-500 flex items-center">
<Phone className="h-3 w-3 mr-1" />
{user.phonenumber}
</span>
</div>
</label>
</div>
)
})}
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)}>
<Button variant="outline" onClick={() => setIsCreateDialogOpen(false)} disabled={loading}>
</Button>
<Button onClick={handleCreateService}></Button>
<Button onClick={handleCreateService} disabled={loading}>
{loading ? "创建中..." : "创建服务"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
@@ -251,56 +363,125 @@ export default function ValueAddedServicesPage() {
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
<TableHead></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{services.map((service) => (
{loading ? (
<TableRow>
<TableCell colSpan={7} className="text-center py-8">
...
</TableCell>
</TableRow>
) : services.length === 0 ? (
<TableRow>
<TableCell colSpan={7} className="text-center py-8 text-gray-500">
</TableCell>
</TableRow>
) : (
services.map((service) => (
<TableRow key={service.id}>
<TableCell className="font-medium">{service.title}</TableCell>
<TableCell className="font-medium">{service.serviceTitle}</TableCell>
<TableCell className="max-w-xs truncate">{service.description}</TableCell>
<TableCell>
<div className="flex items-center space-x-2">
<Switch
checked={service.status === "active"}
onCheckedChange={() => toggleServiceStatus(service.id)}
checked={service.status === "1"}
onCheckedChange={() => toggleServiceStatus(service)}
disabled={loading}
/>
<Badge variant={service.status === "active" ? "default" : "secondary"}>
{service.status === "active" ? "上架" : "下架"}
<Badge variant={service.status === "1" ? "default" : "secondary"}>
{service.status === "1" ? "上架" : "下架"}
</Badge>
</div>
</TableCell>
<TableCell>
<div className="flex flex-wrap gap-1">
{service.assignedDealers.slice(0, 2).map((dealerId) => (
<Badge key={dealerId} variant="outline" className="text-xs">
{getDealerName(dealerId)}
{getUsersByIds(service.assignedAdmin || "").length > 0 ? (
<>
{getUsersByIds(service.assignedAdmin).slice(0, 2).map((userId) => (
<Badge key={userId} variant="outline" className="text-xs">
{getUserName(userId, adminUsers)}
</Badge>
))}
{service.assignedDealers.length > 2 && (
{getUsersByIds(service.assignedAdmin).length > 2 && (
<Badge variant="outline" className="text-xs">
+{service.assignedDealers.length - 2}
+{getUsersByIds(service.assignedAdmin).length - 2}
</Badge>
)}
</>
) : (
<span className="text-xs text-gray-400">-</span>
)}
</div>
</TableCell>
<TableCell>{service.createdAt}</TableCell>
<TableCell>
<div className="flex flex-wrap gap-1">
{getUsersByIds(service.assignedCommon || "").length > 0 ? (
<>
{getUsersByIds(service.assignedCommon).slice(0, 2).map((userId) => (
<Badge key={userId} variant="outline" className="text-xs">
{getUserName(userId, commonUsers)}
</Badge>
))}
{getUsersByIds(service.assignedCommon).length > 2 && (
<Badge variant="outline" className="text-xs">
+{getUsersByIds(service.assignedCommon).length - 2}
</Badge>
)}
</>
) : (
<span className="text-xs text-gray-400">-</span>
)}
</div>
</TableCell>
<TableCell>{service.createdTime || "-"}</TableCell>
<TableCell>
<div className="flex space-x-2">
<Button variant="outline" size="sm" onClick={() => openEditDialog(service)}>
<Button variant="outline" size="sm" onClick={() => openEditDialog(service)} disabled={loading}>
<Edit className="h-4 w-4" />
</Button>
<Button variant="outline" size="sm" onClick={() => handleDeleteService(service.id)}>
<Button variant="outline" size="sm" onClick={() => handleDeleteService(service.id)} disabled={loading}>
<Trash2 className="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
))}
))
)}
</TableBody>
</Table>
{/* Pagination */}
<div className="flex items-center justify-between mt-4">
<div className="text-sm text-gray-600">
{total} {pageNum} / {totalPages || 1}
</div>
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
onClick={handlePrevPage}
disabled={pageNum === 1 || loading}
>
<ChevronLeft className="h-4 w-4 mr-1" />
</Button>
<Button
variant="outline"
size="sm"
onClick={handleNextPage}
disabled={pageNum >= totalPages || loading}
>
<ChevronRight className="h-4 w-4 ml-1" />
</Button>
</div>
</div>
</CardContent>
</Card>
@@ -316,8 +497,8 @@ export default function ValueAddedServicesPage() {
<Label htmlFor="edit-title"></Label>
<Input
id="edit-title"
value={formData.title}
onChange={(e) => setFormData({ ...formData, title: e.target.value })}
value={formData.serviceTitle}
onChange={(e) => setFormData({ ...formData, serviceTitle: e.target.value })}
placeholder="请输入服务标题"
/>
</div>
@@ -332,48 +513,95 @@ export default function ValueAddedServicesPage() {
/>
</div>
<div>
<Label></Label>
<div className="grid grid-cols-1 gap-2 mt-2">
{dealers.map((dealer) => (
<div key={dealer.id} className="flex items-center space-x-2 p-2 border rounded">
<Label></Label>
<div className="grid grid-cols-1 gap-2 mt-2 max-h-48 overflow-y-auto border rounded p-2">
{adminUsers.map((user) => {
const assignedIds = formData.assignedAdmin.split(",").filter(Boolean)
const userId = user.userId
return (
<div key={userId} className="flex items-center space-x-2 p-2 border rounded">
<input
type="checkbox"
id={`edit-${dealer.id}`}
checked={formData.assignedDealers.includes(dealer.id)}
id={`edit-admin-${userId}`}
checked={assignedIds.includes(userId)}
onChange={(e) => {
const ids = formData.assignedAdmin.split(",").filter(Boolean)
if (e.target.checked) {
setFormData({
...formData,
assignedDealers: [...formData.assignedDealers, dealer.id],
assignedAdmin: [...ids, userId].join(","),
})
} else {
setFormData({
...formData,
assignedDealers: formData.assignedDealers.filter((id) => id !== dealer.id),
assignedAdmin: ids.filter((id) => id !== userId).join(","),
})
}
}}
/>
<label htmlFor={`edit-${dealer.id}`} className="flex-1 cursor-pointer">
<label htmlFor={`edit-admin-${userId}`} className="flex-1 cursor-pointer">
<div className="flex justify-between">
<span className="font-medium">{dealer.name}</span>
<span className="text-sm text-gray-500">{dealer.region}</span>
</div>
<div className="text-sm text-gray-500 flex items-center">
<span className="font-medium">{user.nickName || user.userName}</span>
<span className="text-sm text-gray-500 flex items-center">
<Phone className="h-3 w-3 mr-1" />
{dealer.phone}
{user.phonenumber}
</span>
</div>
</label>
</div>
))}
)
})}
</div>
</div>
<div>
<Label></Label>
<div className="grid grid-cols-1 gap-2 mt-2 max-h-48 overflow-y-auto border rounded p-2">
{commonUsers.map((user) => {
const assignedIds = formData.assignedCommon.split(",").filter(Boolean)
const userId = user.userId
return (
<div key={userId} className="flex items-center space-x-2 p-2 border rounded">
<input
type="checkbox"
id={`edit-common-${userId}`}
checked={assignedIds.includes(userId)}
onChange={(e) => {
const ids = formData.assignedCommon.split(",").filter(Boolean)
if (e.target.checked) {
setFormData({
...formData,
assignedCommon: [...ids, userId].join(","),
})
} else {
setFormData({
...formData,
assignedCommon: ids.filter((id) => id !== userId).join(","),
})
}
}}
/>
<label htmlFor={`edit-common-${userId}`} className="flex-1 cursor-pointer">
<div className="flex justify-between">
<span className="font-medium">{user.nickName || user.userName}</span>
<span className="text-sm text-gray-500 flex items-center">
<Phone className="h-3 w-3 mr-1" />
{user.phonenumber}
</span>
</div>
</label>
</div>
)
})}
</div>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setIsEditDialogOpen(false)}>
<Button variant="outline" onClick={() => setIsEditDialogOpen(false)} disabled={loading}>
</Button>
<Button onClick={handleEditService}></Button>
<Button onClick={handleEditService} disabled={loading}>
{loading ? "保存中..." : "保存修改"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>

View File

@@ -0,0 +1,49 @@
import {
ServiceListParams,
ServiceListResponse,
ServiceCreateParams,
ServiceUpdateParams,
ServiceResponse
} from '@/lib/types/service'
import { apiGet, apiPost, apiPut, apiDelete } from './api'
export async function getServiceList(params: ServiceListParams): Promise<ServiceListResponse> {
try {
const queryParams = new URLSearchParams({
pageSize: params.pageSize.toString(),
pageNum: params.pageNum.toString(),
})
return await apiGet<ServiceListResponse>(`/back/services/list?${queryParams}`)
} catch (error) {
console.error('Failed to fetch services:', error)
throw error
}
}
export async function createService(data: ServiceCreateParams): Promise<ServiceResponse> {
try {
return await apiPost<ServiceResponse>('/back/services/', data)
} catch (error) {
console.error('Failed to create service:', error)
throw error
}
}
export async function updateService(data: ServiceUpdateParams): Promise<ServiceResponse> {
try {
return await apiPut<ServiceResponse>('/back/services/', data)
} catch (error) {
console.error('Failed to update service:', error)
throw error
}
}
export async function deleteService(id: string): Promise<ServiceResponse> {
try {
return await apiDelete<ServiceResponse>(`/back/services/${id}`)
} catch (error) {
console.error('Failed to delete service:', error)
throw error
}
}

12
src/lib/services/user.ts Normal file
View File

@@ -0,0 +1,12 @@
import { UserByRoleResponse } from '@/lib/types/user'
import { apiGet } from './api'
export async function getUserByRoleKey(roleKey: string): Promise<UserByRoleResponse> {
try {
return await apiGet<UserByRoleResponse>(`/back/user/getUserByRoleKey?roleKey=${roleKey}`)
} catch (error) {
console.error(`Failed to fetch users with role ${roleKey}:`, error)
throw error
}
}

45
src/lib/types/service.ts Normal file
View File

@@ -0,0 +1,45 @@
export interface ValueAddedService {
id: string
serviceTitle: string
description: string
status: string
assignedAdmin: string
assignedCommon: string
createTime?: string
updateTime?: string
}
export interface ServiceListParams {
pageSize: number
pageNum: number
}
export interface ServiceListResponse {
code: number
msg: string
rows: ValueAddedService[]
total: number
}
export interface ServiceCreateParams {
serviceTitle: string
description: string
status: string
assignedAdmin: string
assignedCommon: string
}
export interface ServiceUpdateParams {
id: string
serviceTitle: string
description: string
status: string
assignedAdmin: string
assignedCommon: string
}
export interface ServiceResponse {
code: number
msg: string
data?: any
}

44
src/lib/types/user.ts Normal file
View File

@@ -0,0 +1,44 @@
export interface User {
createBy: string
createTime: string
updateBy: string | null
updateTime: string | null
remark: string | null
userId: string
deptId: string | null
userName: string
nickName: string
email: string
phonenumber: string
sex: string
avatar: string
password: string
status: string
delFlag: string
loginIp: string
loginDate: string
pwdUpdateDate: string | null
dept: any
roles: any[]
roleIds: any
postIds: any
roleId: string | null
roleName: string | null
provinceCode: string
parentId: string | null
type: string | null
mallId: string | null
businessLicense: string | null
businessType: string | null
address: string | null
provinceAddress: string | null
shopAddress: string | null
admin: boolean
}
export interface UserByRoleResponse {
msg: string
code: number
data: User[]
}