fix: 1. 接入入口模块的管理接口,优化样式。

2. 将查看文档评查结果详情对接接口,采用接口的方式进行查询。
This commit is contained in:
2025-11-26 23:37:14 +08:00
parent ae24b82384
commit d5827a2146
13 changed files with 563 additions and 673 deletions
+85 -125
View File
@@ -1,6 +1,6 @@
import { useState, useEffect } from "react";
import { useSearchParams, useNavigate, useLoaderData, useRouteLoaderData } from "@remix-run/react";
import { ActionFunctionArgs, LoaderFunctionArgs, MetaFunction } from "@remix-run/node";
import { useSearchParams, useNavigate, useLoaderData, useRouteLoaderData, useRevalidator } from "@remix-run/react";
import { ClientLoaderFunctionArgs, MetaFunction } from "@remix-run/react";
import { Table } from "~/components/ui/Table";
import { Card } from "~/components/ui/Card";
import { Button } from "~/components/ui/Button";
@@ -45,91 +45,56 @@ interface LoaderData {
pageSize: number;
currentPage: number;
error?: string;
frontendJWT?: string | null;
}
// 加载函数 - 获取入口模块列表
export async function loader({ request }: LoaderFunctionArgs) {
// 🔑 客户端加载函数 - 在浏览器端执行,axios-client 会自动添加 JWT
export async function clientLoader({ request }: ClientLoaderFunctionArgs) {
try {
// 获取用户会话信息
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT } = await getUserSession(request);
const url = new URL(request.url);
const name = url.searchParams.get('name') || undefined;
const area = url.searchParams.get('area') || undefined;
const page = parseInt(url.searchParams.get('page') || '1', 10);
const pageSize = parseInt(url.searchParams.get('pageSize') || '10', 10);
// 构建搜索参数
// 构建搜索参数(注意:API使用page_size而不是pageSize
const searchParams: EntryModuleSearchParams = {
name,
area,
page,
pageSize
page_size: pageSize // API使用page_size
};
const modulesResponse = await getEntryModules(searchParams, frontendJWT);
// ✅ 不需要传递 JWTaxios-client 会自动从 localStorage 读取并添加
const modulesResponse = await getEntryModules(searchParams);
if (modulesResponse.error) {
console.error("获取入口模块失败:", modulesResponse.error);
console.error("❌ [clientLoader] 获取入口模块失败:", modulesResponse.error);
throw new Error(modulesResponse.error);
}
const modulesResult = modulesResponse.data?.modules || [];
return Response.json({
const modulesResult = modulesResponse.data?.modules || [];
const totalCount = modulesResponse.data?.total || modulesResult.length;
return {
modules: modulesResult,
total: modulesResponse.data?.total || modulesResult.length,
total: totalCount,
pageSize,
currentPage: page,
frontendJWT
});
currentPage: page
};
} catch (error) {
console.error("加载入口模块列表失败:", error);
return Response.json(
{
modules: [],
total: 0,
pageSize: 10,
currentPage: 1,
error: error instanceof Error ? error.message : "加载入口模块列表失败"
},
{ status: 500 }
);
return {
modules: [],
total: 0,
pageSize: 10,
currentPage: 1,
error: error instanceof Error ? error.message : "加载入口模块列表失败"
};
}
}
// 动作函数 - 处理删除请求
export async function action({ request }: ActionFunctionArgs) {
// 获取表单数据
const formData = await request.formData();
const id = formData.get("id") as string;
const intent = formData.get("intent") as string;
const { getUserSession } = await import("~/api/login/auth.server");
const { frontendJWT } = await getUserSession(request);
if (intent === "delete" && id) {
try {
const result = await deleteEntryModule(parseInt(id), frontendJWT || undefined);
if (result.error) {
return Response.json({ success: false, error: result.error }, { status: 500 });
}
return Response.json({ success: true });
} catch (error) {
return Response.json(
{ success: false, error: error instanceof Error ? error.message : "删除入口模块失败" },
{ status: 500 }
);
}
}
return Response.json({ success: false, error: "无效的操作" }, { status: 400 });
}
// 地区选项
const AREA_OPTIONS = [
{ value: "", label: "全部地区" },
{ value: "梅州", label: "梅州" },
{ value: "云浮", label: "云浮" },
{ value: "揭阳", label: "揭阳" },
@@ -142,9 +107,11 @@ export default function EntryModulesList() {
const navigate = useNavigate();
const [searchParams, setSearchParams] = useSearchParams();
const [isDeleting, setIsDeleting] = useState(false);
const revalidator = useRevalidator();
// 获取加载器数据
const { modules, total, error, frontendJWT } = useLoaderData<LoaderData>();
const loaderData = useLoaderData<LoaderData>();
const { modules, total, error } = loaderData;
// 获取用户角色并判断权限
const rootData = useRouteLoaderData("root") as { userRole: string };
@@ -218,30 +185,23 @@ export default function EntryModulesList() {
type: "warning",
confirmText: "删除",
cancelText: "取消",
confirmDelay: 4,
confirmDelay: 3,
onConfirm: async () => {
setIsDeleting(true);
try {
const formData = new FormData();
formData.append('id', id.toString());
formData.append('intent', 'delete');
const response = await fetch('/entry-modules', {
method: 'POST',
body: formData
});
const result = await response.json();
// 直接调用 API 删除函数
const result = await deleteEntryModule(id);
if (result.success) {
toastService.success('删除成功!');
// 刷新页面
window.location.reload();
// 重新验证数据,刷新表格
revalidator.revalidate();
} else {
toastService.error(`删除失败: ${result.error || '未知错误'}`);
}
} catch (error) {
console.error('删除入口模块失败:', error);
toastService.error(`删除失败: ${error instanceof Error ? error.message : '未知错误'}`);
} finally {
setIsDeleting(false);
@@ -272,42 +232,36 @@ export default function EntryModulesList() {
// 表格列定义
const columns = [
{
key: 'id',
title: 'ID',
width: '80px',
render: (row: EntryModule) => row.id
},
{
key: 'name',
title: '模块名称',
width: '200px',
render: (row: EntryModule) => (
<span className="font-medium text-gray-900">{row.name}</span>
render: (_: any, record: EntryModule) => (
<span className="font-medium text-gray-900">{record.name}</span>
)
},
{
key: 'description',
title: '描述',
width: '250px',
render: (row: EntryModule) => (
<span className="text-gray-600">{row.description || '-'}</span>
render: (_: any, record: EntryModule) => (
<span className="text-gray-600">{record.description || '-'}</span>
)
},
{
key: 'logo',
title: 'Logo图片',
width: '150px',
render: (row: EntryModule) => {
if (!row.path) {
render: (_: any, record: EntryModule) => {
if (!record.path) {
return <span className="text-gray-400"></span>;
}
const logoUrl = `${DOCUMENT_URL}${row.path}`;
const logoUrl = `${DOCUMENT_URL}${record.path}`;
return (
<div className="flex items-center">
<img
src={logoUrl}
alt={row.name}
alt={record.name}
className="h-8 w-8 object-contain rounded"
onError={(e) => {
(e.target as HTMLImageElement).style.display = 'none';
@@ -330,17 +284,20 @@ export default function EntryModulesList() {
key: 'areas',
title: '适用地区',
width: '200px',
render: (row: EntryModule) => (
render: (_: any, record: EntryModule) => (
<div className="flex flex-wrap gap-1">
{row.areas && row.areas.length > 0 ? (
row.areas.map((area, index) => (
<span
key={index}
className="inline-block px-2 py-1 text-xs bg-blue-100 text-blue-800 rounded"
>
{area}
</span>
))
{record.areas && record.areas.length > 0 ? (
record.areas
.filter(areaConfig => areaConfig.enabled !== false) // 只显示启用的地区
.sort((a, b) => a.sort_order - b.sort_order) // 按排序号排序
.map((areaConfig, index) => (
<span
key={index}
className="inline-block px-2 py-1 text-xs bg-blue-100 text-blue-800 rounded"
>
{areaConfig.area}
</span>
))
) : (
<span className="text-gray-400"></span>
)}
@@ -351,34 +308,34 @@ export default function EntryModulesList() {
key: 'created_at',
title: '创建时间',
width: '180px',
render: (row: EntryModule) =>
row.created_at ? new Date(row.created_at).toLocaleString('zh-CN') : '-'
render: (_: any, record: EntryModule) =>
record.created_at ? new Date(record.created_at).toLocaleString('zh-CN') : '-'
},
{
key: 'actions',
title: '操作',
width: '150px',
render: (row: EntryModule) => (
<div className="flex items-center space-x-2">
<Button
type="link"
size="small"
onClick={() => handleEdit(row.id!)}
width: '180px',
render: (_: any, record: EntryModule) => (
<div className="operations-cell">
<button
onClick={() => handleEdit(record.id!)}
className="operation-btn"
disabled={!hasEditPermission}
title={hasEditPermission ? "编辑" : "无权限"}
title={hasEditPermission ? "编辑入口模块" : "无权限"}
>
</Button>
<Button
type="link"
size="small"
danger
onClick={() => handleDelete(row.id!)}
disabled={isDeleting || !hasEditPermission}
title={hasEditPermission ? "删除" : "无权限"}
>
</Button>
<i className="ri-edit-line"></i> {hasEditPermission ? '编辑' : '查看'}
</button>
{hasEditPermission && (
<button
type="button"
className="operation-btn !text-[--color-error]"
onClick={() => handleDelete(record.id!)}
disabled={isDeleting}
title="删除入口模块"
>
<i className="ri-delete-bin-line"></i>
</button>
)}
</div>
)
}
@@ -406,11 +363,6 @@ export default function EntryModulesList() {
{/* 筛选面板 */}
<FilterPanel onReset={handleReset}>
<SearchFilter
placeholder="请输入入口模块名称"
defaultValue={name}
onSearch={handleNameSearch}
/>
<FilterSelect
label="适用地区"
name="area"
@@ -418,12 +370,20 @@ export default function EntryModulesList() {
options={AREA_OPTIONS}
onChange={handleFilterChange}
/>
<SearchFilter
label="模块名称"
placeholder="请输入入口模块名称"
value={name}
onSearch={handleNameSearch}
className="filter-item-wide"
/>
</FilterPanel>
{/* 表格 */}
<Table
columns={columns}
data={modules || []}
dataSource={modules || []}
rowKey="id"
loading={false}
emptyText="暂无入口模块数据"
/>