feat(C-01):路由/代理前缀冲突修复

SPA冲突路由改名+旧路径Navigate重定向(/api-docs→/open-api-docs、/api-detail→/open-api-detail、/article→/post、/user→/user-home);vite代理宽前缀改精确正则白名单;nginx.conf同步同口径;新增86路由回归脚本+Playwright 3项(86/86全绿)。
This commit is contained in:
chunyu
2026-09-15 15:25:59 +08:00
parent 26feb3d277
commit 24f200bfa6
18 changed files with 539 additions and 137 deletions
+9 -9
View File
@@ -193,26 +193,26 @@ const ApiDirectory: React.FC = () => {
const getApiRoute = (item: ApiItem): string | null => {
const pathMap: Record<string, string> = {
// 天气查询
"/api/weather": "/api/weather-details",
"/api/weather": "/open-api/weather-details",
// IP地址定位
"/api/getIpData/": "/api/ip-location",
"/api/getIpData/": "/open-api/ip-location",
// 空气质量
"/api/air-quality/": "/api/air-quality-details",
"/api/air-quality/": "/open-api/air-quality-details",
// 汇率相关
"/api/currency/rates/": "/api-detail/currency",
"/api/currency/currencies/": "/api-detail/currency",
"/api/currency/convert/": "/api-detail/currency",
"/api/currency/rates/": "/open-api-detail/currency",
"/api/currency/currencies/": "/open-api-detail/currency",
"/api/currency/convert/": "/open-api-detail/currency",
// 二维码生成
"/api/qrcode-generator/": "/api/qrcode-generator",
"/api/qrcode-generator/": "/open-api/qrcode-generator",
// 密码生成器
"/api/password-generator/": "/api/password-generator",
"/api/password-generator/": "/open-api/password-generator",
// 工具类
"/api/tool/text-diff": "/utility/text-diff",
"/api/tool/image-compressor": "/utility/image-compressor",
// 短链接
"/api/shorturl/shorten": "/shorturl-detail",
// 百度翻译
"/api/baiduFanyi": "/api-detail/baidu-translate",
"/api/baiduFanyi": "/open-api-detail/baidu-translate",
};
return pathMap[item.url_path] || null;
};
@@ -76,13 +76,13 @@ const ApiDirectoryDesktop: React.FC = () => {
description: api.description,
image: getApiImageUrl(api),
category: api.category_name || 'API接口',
link: `/api-detail/${api.id}`,
link: `/open-api-detail/${api.id}`,
});
} catch (err) {
console.error('记录浏览历史失败:', err);
}
}
navigate(`/api-detail/${api.id}`);
navigate(`/open-api-detail/${api.id}`);
};
const handleFavoriteToggle = async (apiId: number, e: React.MouseEvent) => {
@@ -112,13 +112,13 @@ const ApiDirectoryMobile: React.FC = () => {
description: api.description,
image: getApiImageUrl(api),
category: api.category_name || 'API接口',
link: `/api-detail/${api.id}`,
link: `/open-api-detail/${api.id}`,
});
} catch (err) {
console.error('记录浏览历史失败:', err);
}
}
navigate(`/api-detail/${api.id}`);
navigate(`/open-api-detail/${api.id}`);
};
if (loading) {
+10 -10
View File
@@ -78,7 +78,7 @@ const ArticleDetail: React.FC = () => {
description: article.excerpt || "",
image: article.image || "",
category: article.category || "",
link: `/article/${id}`,
link: `/post/${id}`,
} : null);
useEffect(() => {
@@ -333,12 +333,12 @@ const ArticleDetail: React.FC = () => {
src={article.author_avatar}
size={32}
style={{ cursor: "pointer" }}
onClick={() => navigate(`/user/${article.author_user_id}`)}
onClick={() => navigate(`/user-home/${article.author_user_id}`)}
/>
<span
className="author-name"
style={{ cursor: "pointer" }}
onClick={() => navigate(`/user/${article.author_user_id}`)}
onClick={() => navigate(`/user-home/${article.author_user_id}`)}
>
{article.author_name}
</span>
@@ -419,7 +419,7 @@ const ArticleDetail: React.FC = () => {
<div
key={related.id}
className="related-article-card"
onClick={() => navigate(`/article/${related.id}`)}
onClick={() => navigate(`/post/${related.id}`)}
>
<div className="related-article-cover">
<img src={related.image} alt={related.title} />
@@ -444,14 +444,14 @@ const ArticleDetail: React.FC = () => {
src={article.author_avatar}
size={64}
style={{ cursor: "pointer" }}
onClick={() => navigate(`/user/${article.author_user_id}`)}
onClick={() => navigate(`/user-home/${article.author_user_id}`)}
/>
</div>
<div className="author-card-info">
<div
className="author-card-name"
style={{ cursor: "pointer" }}
onClick={() => navigate(`/user/${article.author_user_id}`)}
onClick={() => navigate(`/user-home/${article.author_user_id}`)}
>
{article.author_name}
</div>
@@ -514,14 +514,14 @@ const ArticleDetail: React.FC = () => {
src={comment.user_avatar}
size={40}
style={{ cursor: "pointer" }}
onClick={() => comment.user_id && navigate(`/user/${comment.user_id}`)}
onClick={() => comment.user_id && navigate(`/user-home/${comment.user_id}`)}
/>
<div className="comment-content">
<div className="comment-header">
<span
className="comment-author"
style={{ cursor: comment.user_id ? "pointer" : "default" }}
onClick={() => comment.user_id && navigate(`/user/${comment.user_id}`)}
onClick={() => comment.user_id && navigate(`/user-home/${comment.user_id}`)}
>
{comment.user_name}
</span>
@@ -554,14 +554,14 @@ const ArticleDetail: React.FC = () => {
src={reply.user_avatar}
size={32}
style={{ cursor: "pointer" }}
onClick={() => reply.user_id && navigate(`/user/${reply.user_id}`)}
onClick={() => reply.user_id && navigate(`/user-home/${reply.user_id}`)}
/>
<div className="reply-content">
<div className="reply-header">
<span
className="reply-author"
style={{ cursor: reply.user_id ? "pointer" : "default" }}
onClick={() => reply.user_id && navigate(`/user/${reply.user_id}`)}
onClick={() => reply.user_id && navigate(`/user-home/${reply.user_id}`)}
>
{reply.user_name}
</span>
+3 -3
View File
@@ -215,7 +215,7 @@ const ArticleManage: React.FC = () => {
],
onClick: ({ key }: { key: string }) => {
switch (key) {
case "view": navigate(`/article/${record.id}`); break;
case "view": navigate(`/post/${record.id}`); break;
case "edit": navigate(`/article-editor?id=${record.id}`); break;
case "copy":
navigator.clipboard?.writeText(`${window.location.origin}/article/${record.id}`);
@@ -239,7 +239,7 @@ const ArticleManage: React.FC = () => {
<div className="am-title-cell">
<div className="am-title-row">
{record.isTop && <Tag color="red" className="am-mini-tag">{t('articleManage.topBadge')}</Tag>}
<a className="am-title-link" onClick={() => navigate(`/article/${record.id}`)}>
<a className="am-title-link" onClick={() => navigate(`/post/${record.id}`)}>
{text}
</a>
</div>
@@ -297,7 +297,7 @@ const ArticleManage: React.FC = () => {
<Button type="link" size="small" icon={<EditOutlined />} onClick={() => navigate(`/article-editor?id=${record.id}`)} />
</Tooltip>
<Tooltip title={t('articleManage.tooltips.view')}>
<Button type="link" size="small" icon={<EyeOutlined />} onClick={() => navigate(`/article/${record.id}`)} />
<Button type="link" size="small" icon={<EyeOutlined />} onClick={() => navigate(`/post/${record.id}`)} />
</Tooltip>
<Dropdown menu={getMoreActions(record)} trigger={["click"]}>
<Button type="link" size="small" icon={<MoreOutlined />} onClick={(e) => e.stopPropagation()} />
+1 -1
View File
@@ -1008,7 +1008,7 @@ const Chat: React.FC = () => {
<div className="chat-header-popover">
<div className="popover-item" onClick={() => {
if (selectedConv) {
navigate(`/user/${friends.find(f => f.nickname === selectedConv.name)?.userId || 0}`);
navigate(`/user-home/${friends.find(f => f.nickname === selectedConv.name)?.userId || 0}`);
}
}}>
<UserOutlined /> {t('chat.viewProfile')}
+2 -2
View File
@@ -50,7 +50,7 @@ const Console: React.FC = () => {
};
const articleColumns = [
{ title: "文章标题", dataIndex: "title", key: "title", render: (text: string, record: ArticleRecord) => <a onClick={() => navigate(`/article/${record.id}`)}>{text}</a> },
{ title: "文章标题", dataIndex: "title", key: "title", render: (text: string, record: ArticleRecord) => <a onClick={() => navigate(`/post/${record.id}`)}>{text}</a> },
{ title: "分类", dataIndex: "category", key: "category" },
{ title: "状态", dataIndex: "status", key: "status", render: (status: string) => statusTag(status) },
{ title: "浏览", dataIndex: "views", key: "views" },
@@ -62,7 +62,7 @@ const Console: React.FC = () => {
key: "action",
render: (_: unknown, record: ArticleRecord) => (
<Space size="small">
<Tooltip title="查看"><Button type="link" icon={<EyeOutlined />} size="small" onClick={() => navigate(`/article/${record.id}`)} /></Tooltip>
<Tooltip title="查看"><Button type="link" icon={<EyeOutlined />} size="small" onClick={() => navigate(`/post/${record.id}`)} /></Tooltip>
<Tooltip title="编辑"><Button type="link" icon={<EditOutlined />} size="small" /></Tooltip>
<Popconfirm title="确定删除?" onConfirm={() => message.success("删除成功")}>
<Button type="link" danger icon={<DeleteOutlined />} size="small" />
+2 -2
View File
@@ -130,11 +130,11 @@ const Favorites: React.FC = () => {
if (item.type === "tool") {
navigate(item.url_path);
} else if (item.type === "article") {
navigate(`/article/${item.id}`);
navigate(`/post/${item.id}`);
} else if (item.type === "course") {
navigate(`/course-learn?id=${item.id}`);
} else if (item.type === "api") {
navigate(`/api-detail/${item.id}`);
navigate(`/open-api-detail/${item.id}`);
}
};
+3 -3
View File
@@ -305,7 +305,7 @@ const UserHome: React.FC = () => {
<div
key={article.id}
className="userhome-article-card"
onClick={() => navigate(`/article/${article.id}`)}
onClick={() => navigate(`/post/${article.id}`)}
>
<div className="userhome-article-cover">
{article.cover_image ? (
@@ -404,7 +404,7 @@ const UserHome: React.FC = () => {
className="userhome-favorite-card"
onClick={() => {
if (item.type === 'tool') navigate(item.url_path);
else if (item.type === 'article') navigate(`/article/${item.id}`);
else if (item.type === 'article') navigate(`/post/${item.id}`);
else if (item.type === 'course') navigate(`/course-learn?id=${item.id}`);
}}
>
@@ -819,7 +819,7 @@ const UserHome: React.FC = () => {
style={{ cursor: 'pointer' }}
onClick={() => {
if (item.type === 'tool') navigate(item.url_path);
else if (item.type === 'article') navigate(`/article/${item.id}`);
else if (item.type === 'article') navigate(`/post/${item.id}`);
else if (item.type === 'course') navigate(`/course-learn?id=${item.id}`);
}}
>