460 lines
11 KiB
Vue
460 lines
11 KiB
Vue
<template>
|
||
<div id="normal-container">
|
||
<IntegratedLayout>
|
||
<div class="content">
|
||
<div class="bread-menu">
|
||
<span>AI Launches</span>
|
||
<i class="el-icon-arrow-right"></i>
|
||
<span class="crumbs gradient-color">{{newsDetail.title || ''}}</span>
|
||
</div>
|
||
<div class="title-text">{{ newsDetail.title || '' }}</div>
|
||
<div class="flex mt-24">
|
||
<div class="flex-1">
|
||
<div class="terms-item">
|
||
<div class="item-title">
|
||
<img src="/ToolDetail/icon_star.png" alt="">
|
||
<span>Score: </span>
|
||
<div class="score color-01">{{ (newsDetail.rating || 0).toFixed(1) }}</div>
|
||
</div>
|
||
</div>
|
||
<div class="terms-item">
|
||
<div class="item-title">
|
||
<img src="/ToolDetail/icon_note.png" alt="">
|
||
<span>Introduction: </span>
|
||
</div>
|
||
<div class="item-content">
|
||
{{ newsDetail.summary || '' }}
|
||
</div>
|
||
</div>
|
||
<div class="terms-item">
|
||
<div class="item-title">
|
||
<img src="/ToolDetail/icon_clock.png" alt="">
|
||
<span>Data update: </span>
|
||
</div>
|
||
<div class="item-content">{{ formatPublishTime(newsDetail.publishTime || '') }}</div>
|
||
</div>
|
||
<div class="tags">
|
||
<div class="tag-item" v-for="(it, index) in tagList" :key="index">{{ it }}</div>
|
||
</div>
|
||
</div>
|
||
<div class="flex gap-20">
|
||
<ThumbBtn :like-count="newsDetail.likeCount || 0" :id="newsDetail.id || 0" type="article" @like-success="refreshToolDetail" />
|
||
<CommentBtn :comment-count="commentCount" />
|
||
</div>
|
||
</div>
|
||
<div class="diver"></div>
|
||
<div class="container flex">
|
||
<div class="left-content flex-1 flex flex-col">
|
||
<div class="card flex-1">
|
||
<div class="article-content">
|
||
<div v-html="newsDetail.content || ''"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="right-content">
|
||
<div class="card">
|
||
<div class="flex items-center mt-20">
|
||
<img alt="" src="/launches/detail/icon_hourse.png" class="icon" />
|
||
<div class="content-title">Company Information</div>
|
||
</div>
|
||
<div class="diver-line"></div>
|
||
<div v-if="newsDetail.extra && Array.isArray(newsDetail.extra.socialLinks)" class="mt-30">
|
||
<ProjectItem v-for="(item, index) in newsDetail.extra.socialLinks" :key="index" :item="item" />
|
||
</div>
|
||
<!--<div class="more flex items-center justify-between mt-20">-->
|
||
<!-- <div>A total of 8 projects were released</div>-->
|
||
<!-- <img src="/launches/detail/icon_arrow.png" alt="" />-->
|
||
<!--</div>-->
|
||
</div>
|
||
<div class="card mt-20">
|
||
<div class="flex items-center mt-20">
|
||
<img alt="" src="/launches/detail/icon_finance.png" class="icon" />
|
||
<div class="content-title">Special Financing</div>
|
||
</div>
|
||
<div class="diver-line"></div>
|
||
<div class="scroll">
|
||
<div v-if="newsDetail.extra && Array.isArray(newsDetail.extra.financing)">
|
||
<FinanceItem v-for="(item, index) in newsDetail.extra.financing" :key="index" :item="item" :slug="news_slug" />
|
||
</div>
|
||
</div>
|
||
<div class="flex justify-end mt-30">
|
||
<div class="more flex items-center justify-between gap-14" @click="goToFinanceDetail">
|
||
<div>View more</div>
|
||
<img src="/launches/detail/icon_arrow.png" alt="" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="related">
|
||
<div class="related-title flex justify-between items-center">
|
||
<div class="title">Related Products</div>
|
||
<div @click="goToViewMore" class="more pointer">
|
||
View more<i class="el-icon-arrow-right"></i>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="list">
|
||
<div class="item" v-for="(item, index) in otherList" :key="index">
|
||
<RelatedTool :item="item" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<Comment comment-type="article" :id="newsDetail.id" @update:commentCount="handleCommentCountUpdate" />
|
||
</div>
|
||
</IntegratedLayout>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import CommentBtn from "@/pages/ToolDetail/components/CommentBtn.vue";
|
||
import Comment from "@/pages/ToolDetail/Comment/index.vue";
|
||
import RelatedTool from "@/pages/Launches/Detail/RelatedTool.vue";
|
||
import ProjectItem from "@/pages/Launches/Detail/ProjectItem.vue";
|
||
import FinanceItem from "@/pages/Launches/Detail/FinanceItem.vue";
|
||
import ThumbBtn from "@/pages/ToolDetail/components/ThumbBtn.vue";
|
||
|
||
export default {
|
||
components: {ThumbBtn, FinanceItem, ProjectItem, RelatedTool, CommentBtn, Comment},
|
||
data() {
|
||
return {
|
||
newsDetail: {},
|
||
commentCount: 0,
|
||
news_slug: '',
|
||
otherList: [],
|
||
}
|
||
},
|
||
methods: {
|
||
// 刷新工具详情数据
|
||
refreshToolDetail() {
|
||
if (this.news_slug) {
|
||
this.getNewsDetail(this.news_slug);
|
||
}
|
||
},
|
||
// 获取新闻详情
|
||
async getNewsDetail(newsSlug) {
|
||
const {data: res} = await this.$api.article.getArticleDetail(newsSlug);
|
||
const {code, data} = res;
|
||
if (code === 0 && data) {
|
||
this.newsDetail = {...data, extra: this.stringJsonToObject(data.extra || '[]')};
|
||
// 处理socialLinks
|
||
if (this.newsDetail.extra && Array.isArray(this.newsDetail.extra.socialLinks)) {
|
||
const socialLinks = this.newsDetail.extra.socialLinks;
|
||
// 查找Website选项的索引
|
||
const websiteIndex = socialLinks.findIndex(item => item && item.key === 'Website');
|
||
// 如果找到Website选项且不在第一个位置,则移到第一个位置
|
||
if (websiteIndex !== -1 && websiteIndex !== 0) {
|
||
const websiteItem = socialLinks.splice(websiteIndex, 1)[0];
|
||
socialLinks.unshift(websiteItem);
|
||
}
|
||
}
|
||
}
|
||
},
|
||
formatPublishTime(timeString) {
|
||
if (!timeString) return '';
|
||
|
||
const date = new Date(timeString);
|
||
const months = [
|
||
'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
|
||
];
|
||
|
||
const month = months[date.getMonth()];
|
||
const day = String(date.getDate()).padStart(2, '0');
|
||
const year = date.getFullYear();
|
||
|
||
return `${month} ${day} ${year}`;
|
||
},
|
||
stringJsonToObject(str) {
|
||
// 将json字符串转为对象,捕获错误,当str为空或者转对象失败时默认返回空数组
|
||
try {
|
||
return JSON.parse(str);
|
||
} catch (e) {
|
||
console.error('Error parsing JSON string:', e);
|
||
return [];
|
||
}
|
||
},
|
||
goToViewMore() {
|
||
// 返回上一页
|
||
this.$router.push('/launches');
|
||
},
|
||
handleCommentCountUpdate(count) {
|
||
this.commentCount = count;
|
||
},
|
||
goToFinanceDetail() {
|
||
if (!this.news_slug) {
|
||
return false;
|
||
}
|
||
this.$router.push('/finance-detail?news_slug=' + this.news_slug)
|
||
},
|
||
async getArticleListData() {
|
||
const params = {page: 1, limit: 4, articleType: 'launches'};
|
||
const {data: res} = await this.$api.article.getArticleList(params);
|
||
const {code, data} = res;
|
||
if (code === 0 && data.list && Array.isArray(data.list)) {
|
||
let processedList = [...data.list];
|
||
// 判断列表中是否有与当前详情id一致的项
|
||
if (this.newsDetail && this.newsDetail.id) {
|
||
const currentIdIndex = processedList.findIndex(item => item && item.id === this.newsDetail.id);
|
||
|
||
if (currentIdIndex !== -1) {
|
||
// 有一致的id,删除该项
|
||
processedList.splice(currentIdIndex, 1);
|
||
} else if (processedList.length > 0) {
|
||
// 没有一致的id,删除最后一个项
|
||
processedList = processedList.slice(0, -1);
|
||
}
|
||
}
|
||
this.otherList = processedList;
|
||
}
|
||
},
|
||
async onLoad() {
|
||
this.news_slug = this.$route.query.news_slug;
|
||
if (this.news_slug) {
|
||
await this.getNewsDetail(this.news_slug);
|
||
await this.getArticleListData();
|
||
}
|
||
}
|
||
},
|
||
watch: {
|
||
'$route'(to, from) {
|
||
// 当路由参数发生变化时重新加载数据
|
||
if (to.query.news_slug !== from.query.news_slug) {
|
||
this.onLoad();
|
||
}
|
||
}
|
||
},
|
||
mounted() {
|
||
this.onLoad();
|
||
},
|
||
computed: {
|
||
tagList() {
|
||
if (!this.newsDetail.tags) {
|
||
return [];
|
||
}
|
||
return this.stringJsonToObject(this.newsDetail.tags || '[]');
|
||
}
|
||
},
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.mt-24 {
|
||
margin-top: 24px;
|
||
}
|
||
.color-01 {
|
||
color: #7B61FF;
|
||
}
|
||
.mt-20 {
|
||
margin-top: 20px;
|
||
}
|
||
.mt-30 {
|
||
margin-top: 30px;
|
||
}
|
||
.card {
|
||
padding: 20px;
|
||
background-color: #FFFFFF;
|
||
border-radius: 12px;
|
||
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.08);
|
||
}
|
||
.scroll {
|
||
max-height: 500px; overflow-y: auto; overflow-x: hidden;
|
||
}
|
||
.content {
|
||
padding-top: 25px;
|
||
padding-bottom: 100px;
|
||
|
||
.bread-menu {
|
||
font-size: $mid-font-size;
|
||
font-family: 'Poppins-Medium';
|
||
margin-bottom: 25px;
|
||
|
||
.crumbs {
|
||
font-family: 'Poppins-SemiBold';
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
|
||
.diver {
|
||
margin-top: 30px;
|
||
border-top: 4px solid #E2E8F0;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.title-text {
|
||
font-size: 34px;
|
||
color: #1E293B;
|
||
font-family: 'Poppins-SemiBold';
|
||
font-weight: 600;
|
||
}
|
||
|
||
.rate-box {
|
||
margin-top: 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
|
||
.btn-wrapper {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 20px;
|
||
}
|
||
}
|
||
|
||
.terms-item {
|
||
margin-bottom: 30px;
|
||
display: flex;
|
||
align-items: flex-start;
|
||
gap: 7px;
|
||
|
||
.item-title {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
color: #1E293B;
|
||
font-family: 'Poppins-Medium';
|
||
|
||
img {
|
||
width: 24px;
|
||
height: 24px;
|
||
}
|
||
}
|
||
|
||
.item-content {
|
||
font-family: 'Poppins-Regular';
|
||
color: #64748B;
|
||
}
|
||
}
|
||
|
||
.tags {
|
||
display: flex;
|
||
overflow-x: auto;
|
||
gap: 12px;
|
||
scrollbar-width: none;
|
||
-ms-overflow-style: none;
|
||
user-select: none;
|
||
flex-wrap: wrap;
|
||
width: 80%;
|
||
|
||
.tag-item {
|
||
font-family: 'Poppins-Medium';
|
||
flex-shrink: 0;
|
||
padding: 4px 12px;
|
||
border-radius: 12px;
|
||
@include gradient-border($linear-gradient-start, $linear-gradient-end);
|
||
}
|
||
}
|
||
|
||
.container {
|
||
gap: 20px;
|
||
|
||
.right-content {
|
||
width: 372px;
|
||
|
||
.diver-line {
|
||
margin-top: 20px;
|
||
border-top: 2px solid #E2E8F0;
|
||
}
|
||
|
||
.content-title {
|
||
margin-left: 6px;
|
||
font-family: 'Poppins-SemiBold';
|
||
font-size: 24px;
|
||
color: #1E293B;
|
||
font-weight: 600;
|
||
}
|
||
|
||
.icon {
|
||
width: 24px;
|
||
height: 24px;
|
||
}
|
||
|
||
.more {
|
||
cursor: pointer;
|
||
border-radius: 6px;
|
||
padding: 8px 15px;
|
||
border: 1px solid #e2e8f0;
|
||
font-family: 'Poppins-Regular';
|
||
color: #64748B;
|
||
|
||
&:active {
|
||
opacity: 0.8;
|
||
}
|
||
|
||
img {
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.left-content {
|
||
.sketch {
|
||
padding: 22px;
|
||
background-color: #F5F4FF;
|
||
border-radius: 12px;
|
||
.text {
|
||
font-family: 'Poppins-Regular';
|
||
color: #64748B;
|
||
font-size: 18px;
|
||
}
|
||
|
||
.more {
|
||
cursor: pointer;
|
||
border-radius: 6px;
|
||
padding: 8px 15px;
|
||
border: 1px solid #e2e8f0;
|
||
font-family: 'Poppins-Regular';
|
||
color: #64748B;
|
||
|
||
img {
|
||
width: 16px;
|
||
height: 16px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.related {
|
||
margin-top: 40px;
|
||
.related-title {
|
||
.title {
|
||
margin-bottom: 20px;
|
||
font-size: 30px;
|
||
color: #1E293B;
|
||
font-family: 'Poppins-SemiBold';
|
||
font-weight: 600;
|
||
&::before {
|
||
content: '';
|
||
display: inline-block;
|
||
width: 6px;
|
||
height: 26px;
|
||
background: $header-backgroungd;
|
||
margin-right: 8px;
|
||
border-radius: 0 6px 6px 0;
|
||
}
|
||
}
|
||
.more {
|
||
display: block;
|
||
text-align: right;
|
||
color: $grey-color;
|
||
font-size: $mid-font-size;
|
||
font-family: 'Poppins-Regular';
|
||
}
|
||
}
|
||
.list {
|
||
gap: 60px;
|
||
margin: 40px 10px 40px 10px;
|
||
display: grid;
|
||
grid-template-columns: repeat(3, 1fr);
|
||
.item {
|
||
width: 100%;
|
||
overflow: hidden;
|
||
min-height: 110px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|