fix
This commit is contained in:
@@ -7,6 +7,7 @@ import MainLayout from '@/layouts/MainLayout';
|
||||
import Login from '@/pages/Login';
|
||||
import Dashboard from '@/pages/Dashboard';
|
||||
import MerchantList from '@/pages/MerchantList';
|
||||
import RoomAudit from '@/pages/RoomAudit';
|
||||
import UserList from '@/pages/UserList';
|
||||
import OrderList from '@/pages/OrderList';
|
||||
import Finance from '@/pages/Finance';
|
||||
@@ -29,6 +30,7 @@ const App: React.FC = () => (
|
||||
<Route index element={<Navigate to="/dashboard" replace />} />
|
||||
<Route path="dashboard" element={<Dashboard />} />
|
||||
<Route path="merchants" element={<MerchantList />} />
|
||||
<Route path="room-audit" element={<RoomAudit />} />
|
||||
<Route path="users" element={<UserList />} />
|
||||
<Route path="orders" element={<OrderList />} />
|
||||
<Route path="finance" element={<Finance />} />
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import request from '@/utils/request';
|
||||
|
||||
// 房源审核管理
|
||||
export function getAdminRoomList(params: any) {
|
||||
return request.get('/admin/rooms', { params });
|
||||
}
|
||||
|
||||
export function approveRoom(id: number) {
|
||||
return request.put(`/admin/rooms/${id}/approve`);
|
||||
}
|
||||
|
||||
export function rejectRoom(id: number, reason: string) {
|
||||
return request.put(`/admin/rooms/${id}/reject`, { reason });
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
SettingOutlined,
|
||||
LogoutOutlined,
|
||||
UserOutlined,
|
||||
HomeOutlined,
|
||||
} from '@ant-design/icons';
|
||||
import { useAuthStore } from '@/store/auth';
|
||||
|
||||
@@ -19,6 +20,7 @@ const { Header, Sider, Content } = Layout;
|
||||
const menuItems = [
|
||||
{ key: '/dashboard', icon: <DashboardOutlined />, label: '数据概览' },
|
||||
{ key: '/merchants', icon: <ShopOutlined />, label: '商家管理' },
|
||||
{ key: '/room-audit', icon: <HomeOutlined />, label: '房源审核' },
|
||||
{ key: '/users', icon: <TeamOutlined />, label: '用户管理' },
|
||||
{ key: '/orders', icon: <UnorderedListOutlined />, label: '订单管理' },
|
||||
{ key: '/finance', icon: <WalletOutlined />, label: '财务管理' },
|
||||
|
||||
@@ -0,0 +1,163 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Table, Button, Space, Tag, Modal, Input, Select, message, Image } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { getAdminRoomList, approveRoom, rejectRoom } from '@/api/room';
|
||||
|
||||
const typeLabels: Record<string, string> = { hotel: '酒店', homestay: '民宿', apartment: '公寓', hostel: '青旅' };
|
||||
const auditStatusMap: Record<string, { label: string; color: string }> = {
|
||||
pending: { label: '待审核', color: 'orange' },
|
||||
approved: { label: '已通过', color: 'green' },
|
||||
rejected: { label: '已拒绝', color: 'red' },
|
||||
};
|
||||
|
||||
const RoomAudit: React.FC = () => {
|
||||
const [data, setData] = useState<any[]>([]);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [page, setPage] = useState(1);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [filters, setFilters] = useState<{ keyword?: string; auditStatus?: string; type?: string }>({});
|
||||
const [rejectModal, setRejectModal] = useState<{ visible: boolean; id: number | null; reason: string }>({
|
||||
visible: false, id: null, reason: '',
|
||||
});
|
||||
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const res: any = await getAdminRoomList({ page, pageSize: 10, ...filters });
|
||||
setData(res.data?.list || []);
|
||||
setTotal(res.data?.total || 0);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => { fetchData(); }, [page, filters]);
|
||||
|
||||
const handleApprove = async (id: number) => {
|
||||
await approveRoom(id);
|
||||
message.success('审核通过');
|
||||
fetchData();
|
||||
};
|
||||
|
||||
const handleReject = () => {
|
||||
if (!rejectModal.reason.trim()) {
|
||||
message.warning('请输入拒绝原因');
|
||||
return;
|
||||
}
|
||||
rejectRoom(rejectModal.id!, rejectModal.reason).then(() => {
|
||||
message.success('已拒绝');
|
||||
setRejectModal({ visible: false, id: null, reason: '' });
|
||||
fetchData();
|
||||
});
|
||||
};
|
||||
|
||||
const columns: ColumnsType<any> = [
|
||||
{
|
||||
title: '封面', dataIndex: 'coverImage', width: 100,
|
||||
render: (url) => url ? <Image src={url} width={80} height={60} style={{ borderRadius: 4, objectFit: 'cover' }} /> : '-',
|
||||
},
|
||||
{ title: '名称', dataIndex: 'name', width: 160, ellipsis: true },
|
||||
{
|
||||
title: '商家', dataIndex: 'merchant', width: 120, ellipsis: true,
|
||||
render: (m) => m?.shopName || '-',
|
||||
},
|
||||
{ title: '类型', dataIndex: 'type', width: 80, render: (t) => typeLabels[t] || t },
|
||||
{ title: '价格/晚', dataIndex: 'price', width: 100, render: (v) => `¥${v}` },
|
||||
{
|
||||
title: '审核状态', dataIndex: 'auditStatus', width: 100,
|
||||
render: (s) => {
|
||||
const info = auditStatusMap[s] || { label: s, color: 'default' };
|
||||
return <Tag color={info.color}>{info.label}</Tag>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: '拒绝原因', dataIndex: 'auditRejectReason', width: 160, ellipsis: true,
|
||||
render: (v) => v || '-',
|
||||
},
|
||||
{ title: '提交时间', dataIndex: 'createdAt', width: 170 },
|
||||
{
|
||||
title: '操作', width: 180, fixed: 'right',
|
||||
render: (_, r) => (
|
||||
<Space size="small">
|
||||
{r.auditStatus === 'pending' && (
|
||||
<>
|
||||
<Button type="link" size="small" onClick={() => handleApprove(r.id)}>通过</Button>
|
||||
<Button type="link" danger size="small" onClick={() => setRejectModal({ visible: true, id: r.id, reason: '' })}>
|
||||
拒绝
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
{r.auditStatus === 'approved' && <span style={{ color: '#999' }}>已通过</span>}
|
||||
{r.auditStatus === 'rejected' && <span style={{ color: '#999' }}>已拒绝</span>}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 16 }}>
|
||||
<Space>
|
||||
<h2 style={{ margin: 0 }}>房源审核</h2>
|
||||
<Input.Search
|
||||
placeholder="搜索房源名称"
|
||||
allowClear
|
||||
style={{ width: 200 }}
|
||||
onSearch={(v) => { setFilters((f) => ({ ...f, keyword: v || undefined })); setPage(1); }}
|
||||
/>
|
||||
<Select
|
||||
placeholder="审核状态"
|
||||
allowClear
|
||||
style={{ width: 120 }}
|
||||
onChange={(v) => { setFilters((f) => ({ ...f, auditStatus: v })); setPage(1); }}
|
||||
options={[
|
||||
{ label: '待审核', value: 'pending' },
|
||||
{ label: '已通过', value: 'approved' },
|
||||
{ label: '已拒绝', value: 'rejected' },
|
||||
]}
|
||||
/>
|
||||
<Select
|
||||
placeholder="房源类型"
|
||||
allowClear
|
||||
style={{ width: 120 }}
|
||||
onChange={(v) => { setFilters((f) => ({ ...f, type: v })); setPage(1); }}
|
||||
options={[
|
||||
{ label: '酒店', value: 'hotel' },
|
||||
{ label: '民宿', value: 'homestay' },
|
||||
{ label: '公寓', value: 'apartment' },
|
||||
{ label: '青旅', value: 'hostel' },
|
||||
]}
|
||||
/>
|
||||
</Space>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
loading={loading}
|
||||
scroll={{ x: 1200 }}
|
||||
pagination={{ current: page, total, pageSize: 10, onChange: setPage }}
|
||||
/>
|
||||
|
||||
<Modal
|
||||
title="拒绝审核"
|
||||
open={rejectModal.visible}
|
||||
onOk={handleReject}
|
||||
onCancel={() => setRejectModal({ visible: false, id: null, reason: '' })}
|
||||
okText="确认拒绝"
|
||||
cancelText="取消"
|
||||
>
|
||||
<Input.TextArea
|
||||
rows={4}
|
||||
placeholder="请输入拒绝原因"
|
||||
maxLength={500}
|
||||
value={rejectModal.reason}
|
||||
onChange={(e) => setRejectModal((m) => ({ ...m, reason: e.target.value }))}
|
||||
/>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RoomAudit;
|
||||
Reference in New Issue
Block a user