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:
@@ -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) {
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()} />
|
||||
|
||||
@@ -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')}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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}`);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -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}`);
|
||||
}}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user