提交 42d6ee6f 作者: ZhangLingKun

功能:文章详情

上级 b51f46b7
流水线 #8128 已通过 于阶段
in 6 分 2 秒
......@@ -30,8 +30,10 @@
"env-cmd": "^10.1.0",
"js-base64": "^3.7.5",
"js-cookie": "^3.0.5",
"lodash": "^4.17.21",
"next": "^14.0.4",
"next-redux-wrapper": "^8.1.0",
"query-string": "^8.1.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-infinite-scroll-component": "^6.1.0",
......@@ -39,18 +41,17 @@
"redux": "^4.2.1",
"redux-persist": "^6.0.0",
"styled-components": "^6.1.0",
"swiper": "^11.0.5",
"lodash": "^4.17.21"
"swiper": "^11.0.5"
},
"devDependencies": {
"@types/big.js": "^6.2.0",
"@types/js-cookie": "^3.0.5",
"@types/lodash": "^4.14.202",
"@types/node": "^20",
"@types/react": "^18.2.37",
"@types/react-dom": "^18.2.15",
"@typescript-eslint/eslint-plugin": "^6.10.0",
"@typescript-eslint/parser": "^6.10.0",
"@types/lodash": "^4.14.202",
"autoprefixer": "^10.4.16",
"cross-env": "^7.0.3",
"eslint": "^8",
......
......@@ -53,6 +53,9 @@ dependencies:
next-redux-wrapper:
specifier: ^8.1.0
version: registry.npmmirror.com/next-redux-wrapper@8.1.0(next@14.0.4)(react-redux@8.1.3)(react@18.2.0)
query-string:
specifier: ^8.1.0
version: 8.1.0
react:
specifier: ^18.2.0
version: registry.npmmirror.com/react@18.2.0
......@@ -938,6 +941,11 @@ packages:
dependencies:
ms: 2.1.2
/decode-uri-component@0.4.1:
resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==, tarball: https://registry.npmmirror.com/decode-uri-component/-/decode-uri-component-0.4.1.tgz}
engines: {node: '>=14.16'}
dev: false
/define-data-property@1.1.1:
resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==, tarball: https://registry.npmmirror.com/define-data-property/-/define-data-property-1.1.1.tgz}
engines: {node: '>= 0.4'}
......@@ -1388,6 +1396,11 @@ packages:
to-regex-range: 5.0.1
dev: true
/filter-obj@5.1.0:
resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==, tarball: https://registry.npmmirror.com/filter-obj/-/filter-obj-5.1.0.tgz}
engines: {node: '>=14.16'}
dev: false
/for-each@0.3.3:
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==, tarball: https://registry.npmmirror.com/for-each/-/for-each-0.3.3.tgz}
dependencies:
......@@ -2247,6 +2260,15 @@ packages:
react-is: 16.13.1
dev: true
/query-string@8.1.0:
resolution: {integrity: sha512-BFQeWxJOZxZGix7y+SByG3F36dA0AbTy9o6pSmKFcFz7DAj0re9Frkty3saBn3nHo3D0oZJ/+rx3r8H8r8Jbpw==, tarball: https://registry.npmmirror.com/query-string/-/query-string-8.1.0.tgz}
engines: {node: '>=14.16'}
dependencies:
decode-uri-component: 0.4.1
filter-obj: 5.1.0
split-on-first: 3.0.0
dev: false
/queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==, tarball: https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz}
dev: true
......@@ -2409,6 +2431,11 @@ packages:
resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==, tarball: https://registry.npmmirror.com/source-map-js/-/source-map-js-1.0.2.tgz}
engines: {node: '>=0.10.0'}
/split-on-first@3.0.0:
resolution: {integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==, tarball: https://registry.npmmirror.com/split-on-first/-/split-on-first-3.0.0.tgz}
engines: {node: '>=12'}
dev: false
/streamsearch@1.1.0:
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==, tarball: https://registry.npmmirror.com/streamsearch/-/streamsearch-1.1.0.tgz}
engines: {node: '>=10.0.0'}
......
......@@ -739,3 +739,33 @@ export type LeaseGoodsListType = InterListFunction<
companyName: string;
}
>;
// 项目资讯-新闻详情
export type NewDetailsType = InterFunction<
{ id: number },
{
createTime: string;
id: number;
newsAuthor: string;
newsContents: string;
newsTitle: string;
origin: string;
surfaceImg: string;
updateTime: string;
userAccountId: number;
}
>;
// 查询-招标快讯详情
export type GetInfoById = InterFunction<
{ id: number },
{
createTime: string;
deleted: number;
id: number;
tenderContent: string;
tenderInfoNo: string;
tenderNewsId: number;
tenderPrice: number;
tenderTitle: string;
updateTime: string;
}
>;
......@@ -4,6 +4,7 @@ import {
AppPublishListType,
ForumListType,
GetAppGambitListType,
GetInfoById,
GetPageHomeCategoriesType,
IndustryListPagesType,
LeaseGoodsListType,
......@@ -12,6 +13,7 @@ import {
ListCompanyInfoByCoopIdType,
ListNewsType,
ListTenderInfoType,
NewDetailsType,
RecommendGoodsType,
RequirementsListType,
} from '@/api/interface/home';
......@@ -81,4 +83,12 @@ export class HomeAPI {
// 类型列表
static leaseGoodsList: LeaseGoodsListType = (params) =>
request.post('/pms/app/lease/leaseGoodsList', params);
// 项目资讯-新闻详情
static getNewsDetail: NewDetailsType = (params) =>
request.get('/release/industry-news/details', { params });
// 查询-招标快讯详情
static getInfoById: GetInfoById = (params) =>
request.get('/release/tender/infoById', { params });
}
......@@ -70,7 +70,11 @@ const BreadcrumbView: React.FC = () => {
path: 'course',
children: [{ name: '课程详情', path: 'detail' }],
},
{ name: '行业新闻', path: 'news' },
{
name: '行业新闻',
path: 'news',
children: [{ name: '文章详情', path: 'detail' }],
},
];
// 转换路由
const getCurrentRouter = () => {
......
......@@ -103,6 +103,10 @@ const HomeNewsView = () => {
const handleMore = async () => {
await router.push('/news');
};
// 跳转详情
const handleDetail = async (item: { id: number }, index: number) => {
await router.push(`/news/detail/${item.id}?type=${index + 1}`);
};
// 组件挂载
useEffect(() => {
handleSelect(0).then();
......@@ -148,7 +152,10 @@ const HomeNewsView = () => {
>
{j + 1}
</div>
<div className="item-title text-ellipsis">
<div
className="item-title text-ellipsis"
onClick={() => handleDetail(i, currentIndex)}
>
{i?.gambitName || i?.newsTitle || i?.tenderTitle}
</div>
{/* {currentIndex === 0 && ( */}
......
import React, { useEffect, useState } from 'react';
import { Affix } from 'antd';
import { useRouter } from 'next/router';
import styled from 'styled-components';
import { HomeAPI } from '@/api';
import { InterListType } from '@/api/interface';
......@@ -17,6 +18,8 @@ const indexList = [
];
const HomeTopicView = () => {
// 路由钩子
const router = useRouter();
// 话题列表
const [topicList, setTopicList] = useState<ListType>();
// 获取话题列表
......@@ -29,6 +32,10 @@ const HomeTopicView = () => {
setTopicList(res.result.list || []);
}
};
// 跳转详情
const handleDetail = async (item: ListType[0]) => {
await router.push(`/forum/detail/${item.id}`);
};
// 组件挂载
useEffect(() => {
getListGambit().then();
......@@ -40,7 +47,10 @@ const HomeTopicView = () => {
{topicList?.map((i, j) => (
<div key={j} className="mb-3 flex">
<div className={`${indexList[j]?.value} font-bold`}>{j + 1}</div>
<div className="ml-2 cursor-pointer text-ellipsis hover:text-tag">
<div
className="ml-2 cursor-pointer text-ellipsis hover:text-tag"
onClick={() => handleDetail(i)}
>
{i.gambitName}
</div>
</div>
......
import qs from 'querystring';
import React from 'react';
import { useRouter } from 'next/router';
import styled from 'styled-components';
import { InterListType } from '@/api/interface';
import { ListNewsType } from '@/api/interface/home';
......@@ -6,9 +8,22 @@ import { ListNewsType } from '@/api/interface/home';
// 详情类型
type DetailType = InterListType<ListNewsType>[0];
const NewsListItem: React.FC<{ detail: DetailType }> = ({ detail }) => {
const NewsListItem: React.FC<{ detail: DetailType; full?: Boolean }> = ({
detail,
full = false,
}) => {
// 路由钩子
const router = useRouter();
// 点击详情
const handleDetail = async () => {
const query = qs.stringify({ type: 1 });
await router.push(`/news/detail/${detail?.id}?${query}`);
};
return (
<NewsListWrap>
<NewsListWrap
onClick={handleDetail}
style={{ width: full ? '100%' : '86%' }}
>
<img
className="item-image"
src={detail?.surfaceImg}
......
import React from 'react';
import { Button } from 'antd';
import { useRouter } from 'next/router';
import styled from 'styled-components';
import { InterListType } from '@/api/interface';
import { ListTenderInfoType } from '@/api/interface/home';
// 详情类型
type DetailType = InterListType<ListTenderInfoType>[0];
const NewsTenderItem: React.FC<{ detail: DetailType }> = ({ detail }) => {
const NewsTenderItem: React.FC<{ detail: DetailType; full?: Boolean }> = ({
detail,
full = false,
}) => {
// 路由钩子
const router = useRouter();
// 点击详情
const handleDetail = async () => {
await router.push(`/news/detail/${detail?.id}?type=2`);
};
return (
<NewsTenderWrap>
<NewsTenderWrap
onClick={handleDetail}
style={{ width: full ? '100%' : '86%' }}
>
<div className="title">
<div className="text">{detail?.tenderTitle}</div>
<Button className="action" type="primary">
......@@ -52,6 +65,9 @@ const NewsTenderWrap = styled.div`
font-weight: bold;
color: #333333;
margin-bottom: 0.5rem;
&:hover {
color: #ff552d;
}
}
}
.tags {
......
import React, { useEffect, useState } from 'react';
import { LeftCircleOutlined, RightCircleOutlined } from '@ant-design/icons';
import { Affix, App, Button, Empty } from 'antd';
import { useRouter } from 'next/router';
import styled from 'styled-components';
import { HomeAPI } from '@/api';
import { InterDataType, InterListType } from '@/api/interface';
import {
GetInfoById,
ListNewsType,
ListTenderInfoType,
NewDetailsType,
} from '@/api/interface/home';
import BreadcrumbView from '@/components/breadcrumb';
import HomeNewsView from '@/components/home-comp/home-news';
import LayoutView from '@/components/layout';
import NewsListItem from '@/pages/news/comp/_newsListItem';
import NewsTenderItem from '@/pages/news/comp/_newsTenderItem';
// 新闻类型
type NewsType = InterDataType<NewDetailsType>;
// 投标类型
type TenderType = InterDataType<GetInfoById>;
// 新闻列表类型
type NewsListType = InterListType<ListNewsType>;
// 招投标列表类型
type TenderListType = InterListType<ListTenderInfoType>;
const NewsDetailView = () => {
// app
const { message } = App.useApp();
// 路由钩子
const router = useRouter();
// 新闻详情
const [newsDetail, setNewsDetail] = useState<NewsType>();
// 招投标详情
const [tenderDetail, setTenderDetail] = useState<TenderType>();
// 新闻列表
const [newsList, setNewsList] = useState<NewsListType>([]);
// 招投标列表
const [tenderList, setTenderList] = useState<TenderListType>([]);
// 获取新闻详情
const getNewsDetail = async () => {
const res = await HomeAPI.getNewsDetail({
id: Number(router.query?.id),
});
if (res && res.code === '200') {
setNewsDetail(res.result);
}
};
// 获取招投标项目详情
const getProjectDetail = async () => {
const res = await HomeAPI.getInfoById({
id: Number(router.query?.id),
});
if (res && res.code === '200') {
setTenderDetail(res.result);
}
};
// 热点资讯列表
const getListNews = async () => {
const res = await HomeAPI.getListNews({
pageNo: 1,
pageSize: 999,
});
if (res && res.code === '200') {
setNewsList(res.result.list || []);
}
};
// 获取招标快讯列表
const getListTenderInfo = async () => {
const res = await HomeAPI.getListTenderInfo({
pageNo: 1,
pageSize: 999,
});
if (res && res.code === '200') {
setTenderList(res.result.list || []);
}
};
// 上下切换
const handleNext = async (from: string) => {
const list = router?.query?.type === '1' ? newsList : tenderList;
const index = list?.findIndex((v) => v.id === Number(router.query?.id));
if (from === 'pre') {
if (index === 0) {
await message.info('已经是第一篇了');
return;
}
await router.push(
`/news/detail/${list?.[index - 1].id}?type=${router.query?.type}`,
);
}
if (from === 'nxt') {
if (index === list.length - 1) {
await message.info('已经是最后一篇了');
return;
}
await router.push(
`/news/detail/${list?.[index + 1].id}?type=${router.query?.type}`,
);
}
};
// 组件挂载
useEffect(() => {
if (!router.query?.id) return;
const type = router.query?.type || '1';
if (type === '1') {
Promise.all([getNewsDetail(), getListNews()]).then();
} else {
Promise.all([getProjectDetail(), getListTenderInfo()]).then();
}
}, [router]);
return (
<LayoutView>
<NewsDetailWrap>
{/* 面包屑 */}
<BreadcrumbView />
<div className="wrap flex w-full flex-nowrap">
<div className="content">
{router?.query?.type === '1' && (
<>
<div className="title">{newsDetail?.newsTitle}</div>
<div className="author flex">
<div
className="text text-ellipsis"
title={newsDetail?.newsAuthor}
>
作者:{newsDetail?.newsAuthor}
</div>
<div
className="text text-ellipsis"
title={newsDetail?.origin}
>
来源:{newsDetail?.origin}
</div>
<div title={newsDetail?.createTime}>
发布时间:{newsDetail?.createTime}
</div>
</div>
{newsDetail?.newsContents && (
<div
className="richText"
dangerouslySetInnerHTML={{
__html: newsDetail?.newsContents,
}}
></div>
)}
<img
className="surface"
src={newsDetail?.surfaceImg}
alt="封面图"
/>
</>
)}
{router?.query?.type === '2' && (
<>
<div className="title">{tenderDetail?.tenderTitle}</div>
<div className="price">
招标金额
<span className="num">{tenderDetail?.tenderPrice}万元</span>
</div>
{tenderDetail?.tenderContent && (
<div
className="richText"
dangerouslySetInnerHTML={{
__html: tenderDetail?.tenderContent,
}}
></div>
)}
<div className="action">
<Button>分享</Button>
<Button type="primary">联系合作</Button>
</div>
</>
)}
<div className="footer">
<Button
type={'link'}
icon={<LeftCircleOutlined />}
onClick={() => handleNext('pre')}
>
上一篇
</Button>
<Button
type={'link'}
icon={<RightCircleOutlined />}
onClick={() => handleNext('nxt')}
>
下一篇
</Button>
</div>
</div>
<Affix offsetTop={58}>
<div className="list">
<HomeNewsView />
</div>
</Affix>
</div>
<div className="order">
<div className="title">其他推荐</div>
{router.query?.type === '1' &&
(newsList?.length ? (
newsList
?.slice(0, 10)
?.map((i, j) => <NewsListItem key={j} detail={i} full={true} />)
) : (
<div className="list-empty flex-center">
<Empty />
</div>
))}
{router.query?.type === '2' &&
(tenderList?.length ? (
tenderList
?.slice(0, 10)
?.map((i, j) => (
<NewsTenderItem key={j} detail={i} full={true} />
))
) : (
<div className="list-empty flex-center">
<Empty />
</div>
))}
</div>
</NewsDetailWrap>
</LayoutView>
);
};
export default NewsDetailView;
// 样式
const NewsDetailWrap = styled.div`
position: relative;
max-width: 1190px;
box-sizing: border-box;
padding: 2rem 0 0 0;
margin: 0 auto;
.wrap {
position: relative;
box-sizing: border-box;
.content {
width: calc(70% - 1.5rem);
//min-height: 32rem;
margin-right: 1.5rem;
.title {
font-size: 18px;
font-weight: bold;
margin-bottom: 1rem;
}
.author {
color: #666666;
margin-bottom: 1rem;
.text {
width: 6.8rem;
}
}
.surface {
width: 100%;
margin: 1rem 0;
}
.price {
width: 100%;
height: 1.83rem;
background: #fff8ee;
border-radius: 0.25rem;
border: 0.04rem solid #ffe8e3;
line-height: 1.83rem;
box-sizing: border-box;
padding: 0 1rem;
margin-bottom: 1rem;
.num {
color: #fe3017;
margin-left: 0.5rem;
}
}
.action {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
margin-bottom: 1rem;
.ant-btn:not(:last-child) {
margin-right: 0.5rem;
}
}
.footer {
position: relative;
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 1rem;
}
}
.list {
width: 30%;
}
}
.order {
position: relative;
width: calc(70% - 4rem);
.title {
font-size: 14px;
font-weight: bold;
}
}
`;
......@@ -197,3 +197,9 @@ a {
-moz-border-radius: 2em;
border-radius: 2em;
}
.richText {
width: 100%;
}
.richText,img {
width: 100%;
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论