后期修改完善,上线版本

This commit is contained in:
2025-11-12 18:11:11 +08:00
parent c54f9c9976
commit 8f57683dd5
98 changed files with 2110 additions and 867 deletions

View File

@ -3,7 +3,7 @@ export default ($axios) => ({
return $axios.get('/tool', { params }) return $axios.get('/tool', { params })
}, },
getCategoryList(params) { getCategoryList(params) {
return $axios.get('/category', { params }) return $axios.get('/category/tree', { params })
}, },
getToolByCategory(params) { getToolByCategory(params) {
return $axios.get('/tool/group', { params }) return $axios.get('/tool/group', { params })
@ -19,5 +19,5 @@ export default ($axios) => ({
}, },
searchToolByWord(word) { searchToolByWord(word) {
return $axios.get('/tool/search', { params:{q: word, limit:50}}) return $axios.get('/tool/search', { params:{q: word, limit:50}})
} },
}) })

View File

@ -44,7 +44,8 @@ export default {
name: "Footer", name: "Footer",
data() { data() {
return { return {
first: [{ first: [
{
name: 'Home', name: 'Home',
path: '/home/list', path: '/home/list',
meta: { meta: {
@ -59,15 +60,10 @@ export default {
}, },
}, },
{ {
name: 'AI Hub', name: 'AI Launches',
path: '/launches',
meta: { meta: {
parent: 'Hub', parent: 'Launches',
}
},
{
name: 'Learn',
meta: {
parent: 'Learn',
} }
}, },
{ {
@ -99,11 +95,6 @@ export default {
goto(path) { goto(path) {
this.$router.push(path); this.$router.push(path);
}, },
/**
* 判断当前导航项是否应该被选中
* @param {Object} item 导航项
* @returns {Boolean} 是否选中
*/
isParentMatch(item) { isParentMatch(item) {
// 首先检查路径匹配逻辑,保持向后兼容性 // 首先检查路径匹配逻辑,保持向后兼容性
const pathMatch = item.path && item.path === this.$route.path; const pathMatch = item.path && item.path === this.$route.path;
@ -159,7 +150,7 @@ export default {
font-size: $larg-font-size; font-size: $larg-font-size;
color: $main-color; color: $main-color;
font-weight: bold; font-weight: bold;
font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
} }
} }
@ -172,14 +163,14 @@ export default {
.bottom-span { .bottom-span {
color: $grey-color; color: $grey-color;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
} }
.navigation-bottom { .navigation-bottom {
margin-bottom: 14px; margin-bottom: 14px;
span { span {
display: inline-block; display: inline-block;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
cursor: pointer; cursor: pointer;
margin-left: 30px; margin-left: 30px;
&:hover { &:hover {

View File

@ -61,7 +61,7 @@ export default {
.loading-text { .loading-text {
margin-top: 15px; margin-top: 15px;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
color: #7B61FF; color: #7B61FF;
} }

View File

@ -10,15 +10,18 @@
<div class="navigation-item pointer" v-for="item in navRoutes" :key="item.path" <div class="navigation-item pointer" v-for="item in navRoutes" :key="item.path"
@click="handleParentClick(item)" @mouseenter="showSubmenu(item)" @mouseleave="hideSubmenu"> @click="handleParentClick(item)" @mouseenter="showSubmenu(item)" @mouseleave="hideSubmenu">
<span <span
:class="{ 'selected-navigation': isParentMatch(item) }">{{ :class="{ 'selected-navigation': isParentMatch(item) }">{{item.meta.navigationName }}
item.meta.navigationName }}
<i class="el-icon-arrow-down" v-if="item.meta.children"></i> <i class="el-icon-arrow-down" v-if="item.meta.children"></i>
</span> </span>
<div v-if="activeMenu === item.path && item.meta.children" class="submenu"> <div v-if="activeMenu === item.path && item.meta.children" class="submenu">
<div v-for="sub in item.children" :key="sub.path" @click.stop="goto(sub.path)" <div v-for="sub in item.children" :key="sub.path" @click.stop="goto(sub.path)"
class="submenu-item pointer"> class="submenu-item pointer">
<img :src="`/logo/${sub.meta.icon}.png`" alt="" /> <div class="wh-24 flex-center mr-10" :style="{ background: sub.meta.color }">
<img :src="`/menu/${sub.meta.icon}.png`" alt="" />
</div>
{{ sub.name }} {{ sub.name }}
<!-- 添加真实的DOM元素作为背景动画 -->
<div class="submenu-item-bg" :style="{ backgroundColor: sub.meta.color || '#f1f5f9' }"></div>
</div> </div>
</div> </div>
</div> </div>
@ -122,11 +125,13 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.mr-10 {
margin-right: 10px;
}
#header-container { #header-container {
width: 100%; width: 100%;
height: $navigationBarHeight;
background: $header-backgroungd; background: $header-backgroungd;
height: 72px;
.navigation-container { .navigation-container {
height: $navigationBarHeight; height: $navigationBarHeight;
@ -137,7 +142,7 @@ export default {
.logo { .logo {
@include flex-center; @include flex-center;
color: $white; color: $white;
//font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
span { span {
font-size: $normal-font-size; font-size: $normal-font-size;
@ -148,7 +153,7 @@ export default {
margin-left: 20px; margin-left: 20px;
padding: 10px; padding: 10px;
position: relative; position: relative;
//font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
span { span {
color: $grey; color: $grey;
@ -159,8 +164,7 @@ export default {
&.selected-navigation { &.selected-navigation {
color: $white; color: $white;
font-weight: 500; font-family: 'Poppins-Bold';
//font-family: 'Poppins-Bold', serif;
} }
} }
@ -168,30 +172,65 @@ export default {
z-index: 10; z-index: 10;
position: absolute; position: absolute;
top: 100%; top: 100%;
left: 50%;
padding: 20px 10px; padding: 20px 10px;
transform: translateX(-50%);
background: $white; background: $white;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
border-radius: 6px; border-radius: 6px;
&-item { .submenu-item {
white-space: nowrap; // 防止文字换行 white-space: nowrap;
padding: 10px; padding: 8px;
color: #000000; color: #1e293b;
@include display-flex; @include display-flex;
font-size: 18px;
position: relative;
overflow: hidden;
z-index: 1;
margin-bottom: 16px;
img { img {
margin-right: 10px; width: 24px;
height: 24px;
transition: transform 0.3s ease;
} }
// 鼠标悬停效果
&:hover { &:hover {
background: #F5F6F9; cursor: pointer;
color: $main-color; img {
transform: scale(1.1); // 放大图片到1.1倍
}
.submenu-item-bg {
left: 0;
}
}
// 真实的DOM元素作为背景动画
.submenu-item-bg {
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
z-index: -1;
transition: left 0.3s ease;
}
&:last-child {
margin-bottom: 0;
} }
} }
} }
} }
} }
} }
@keyframes slideIn {
from {
left: -100%;
}
to {
left: 0;
}
}
</style> </style>

View File

@ -147,12 +147,11 @@ export default {
width: 100%; width: 100%;
.nav-button { .nav-button {
width: 44px; width: 36px;
height: 44px; height: 36px;
cursor: pointer; cursor: pointer;
background-color: #FFFFFF; background: transparent !important;
border-radius: 4px; border-radius: 4px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
flex-shrink: 0; flex-shrink: 0;
display: flex; display: flex;
align-items: center; align-items: center;
@ -163,8 +162,8 @@ export default {
} }
img { img {
width: 44px; width: 36px;
height: 44px; height: 36px;
} }
&.disabled { &.disabled {

View File

@ -151,7 +151,7 @@ export default {
.my-tabs__item.is-disabled { .my-tabs__item.is-disabled {
color: #1e293b; color: #1e293b;
cursor: not-allowed; cursor: not-allowed;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
} }
.my-tabs__close { margin-left: 6px; color: #999; } .my-tabs__close { margin-left: 6px; color: #999; }
.my-tabs__close:hover { color: #f56c6c; } .my-tabs__close:hover { color: #f56c6c; }

View File

@ -84,6 +84,7 @@
background-color: $white; background-color: $white;
color: $main-font-color; color: $main-font-color;
cursor: pointer; cursor: pointer;
font-weight: 'Poppins-Regular';
} }
.pagination-number { .pagination-number {

View File

@ -34,7 +34,7 @@ export default {
:size="24" :size="24"
:disabled="readonly"> :disabled="readonly">
</el-rate> </el-rate>
<div class="rate-num" :class="value ? 'gradient-box' : ''">{{ value }}</div> <div class="rate-num" :class="value ? 'gradient-box' : ''">{{ (value).toFixed(1) }}</div>
</div> </div>
</template> </template>
@ -46,7 +46,7 @@ export default {
} }
} }
.rate-num { .rate-num {
width: 28px; width: 36px;
height: 28px; height: 28px;
line-height: 28px; line-height: 28px;
text-align: center; text-align: center;
@ -54,7 +54,7 @@ export default {
color: #fff; color: #fff;
border-radius: 12px; border-radius: 12px;
margin-left: 8px; margin-left: 8px;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
} }
.gradient-box { .gradient-box {
background: $header-backgroungd; background: $header-backgroungd;

View File

@ -1,5 +1,5 @@
<template> <template>
<div class="search-select-container"> <div class="search-select-container" ref="searchContainer">
<!-- 使用现有的 SearchInput 组件 --> <!-- 使用现有的 SearchInput 组件 -->
<SearchInput <SearchInput
ref="searchInput" ref="searchInput"
@ -9,7 +9,8 @@
/> />
<!-- 搜索结果展示区域 --> <!-- 搜索结果展示区域 -->
<div v-if="searched && hasResults" class="results-list"> <div v-if="searched" class="results-list">
<div v-if="hasResults">
<div <div
v-for="(item, index) in searchResults" v-for="(item, index) in searchResults"
:key="index" :key="index"
@ -20,13 +21,13 @@
<div>{{ item.name || '' }}</div> <div>{{ item.name || '' }}</div>
</div> </div>
</div> </div>
<!-- 无结果提示 --> <!-- 无结果提示 -->
<div v-else-if="searched && !hasResults" class="no-results flex items-center"> <div v-else class="no-results flex items-center">
<img src="/search/icon_alarm.png" alt="" /> <img src="/search/icon_alarm.png" alt="" />
<div>No relevant content was found</div> <div>No relevant content was found</div>
</div> </div>
</div> </div>
</div>
</template> </template>
@ -52,11 +53,32 @@ export default {
return this.searchResults && this.searchResults.length > 0; return this.searchResults && this.searchResults.length > 0;
} }
}, },
mounted() {
// 添加点击事件监听
document.addEventListener('click', this.handleDocumentClick);
},
beforeDestroy() {
// 移除事件监听,避免内存泄漏
document.removeEventListener('click', this.handleDocumentClick);
},
methods: { methods: {
async handleSearch(value) { handleDocumentClick(event) {
if (!value) { // 判断点击是否在搜索容器内部
const searchContainer = this.$refs.searchContainer;
if (searchContainer && !searchContainer.contains(event.target)) {
// 点击在搜索容器外部,清空搜索状态
this.clearSearchState();
}
},
clearSearchState() {
this.searched = false; this.searched = false;
this.searchResults = []; this.searchResults = [];
},
async handleSearch(value) {
if (!value) {
this.clearSearchState();
return; return;
} }
@ -73,8 +95,7 @@ export default {
}, },
selectItem(item) { selectItem(item) {
this.searched = false; this.clearSearchState();
this.searchResults = [];
this.jumpToToolDetail(item); this.jumpToToolDetail(item);
}, },
@ -112,6 +133,7 @@ export default {
} }
.results-list { .results-list {
position: absolute;
padding: 30px 20px; padding: 30px 20px;
margin-top: 10px; margin-top: 10px;
border-radius: 12px; border-radius: 12px;
@ -119,6 +141,8 @@ export default {
overflow-y: auto; overflow-y: auto;
background: white; background: white;
box-shadow: 0 10px 30px #0000000d; box-shadow: 0 10px 30px #0000000d;
width: 100%;
z-index: 999;
.result-item { .result-item {
padding: 20px 30px; padding: 20px 30px;
@ -147,14 +171,16 @@ export default {
.no-results { .no-results {
gap: 30px; gap: 30px;
padding: 10px 16px;
text-align: center;
border-radius: 8px;
margin: 16px auto 0;
color: #1E293B; color: #1E293B;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
@include gradient-border($linear-gradient-start, $linear-gradient-end); width: 100%;
width: fit-content; // 改为自适应内容宽度 cursor: pointer;
padding: 20px 30px;
&:hover {
color: #7C62FF;
background: #F5F6F9;
}
img { img {
width: 24px; width: 24px;

View File

@ -3,7 +3,10 @@
<div id="default-layout"> <div id="default-layout">
<Header></Header> <Header></Header>
<div id="home-container"> <div id="home-container">
<Nuxt /> <keep-alive>
<Nuxt v-if="$route.meta.keepAlive" />
</keep-alive>
<Nuxt v-if="!$route.meta.keepAlive" />
</div> </div>
<Footer></Footer> <Footer></Footer>
@ -59,7 +62,6 @@ export default {
#home-container { #home-container {
width: 100%; width: 100%;
// min-height: 100vh;
background-color: $background-color; background-color: $background-color;
} }

View File

@ -57,9 +57,9 @@ export default {
'normalize.css/normalize.css', // 引入 'normalize.css/normalize.css', // 引入
'@/styles/index.scss', //引入全局样式 '@/styles/index.scss', //引入全局样式
'@/styles/text.scss', '@/styles/text.scss',
'@/styles/font.scss',
'@/styles/flex.scss', '@/styles/flex.scss',
'@/styles/article.scss', '@/styles/article.scss',
'@/styles/font.scss',
], ],
// Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins // Plugins to run before rendering page: https://go.nuxtjs.dev/config-plugins
@ -100,6 +100,24 @@ export default {
// Build Configuration: https://go.nuxtjs.dev/config-build // Build Configuration: https://go.nuxtjs.dev/config-build
build: { build: {
postcss: {
plugins: {
'postcss-px-to-viewport': {
unitToConvert: 'px', // 需要转换的单位
viewportWidth: 1920, // 设计稿基准宽度1470px
unitPrecision: 5, // 转换后的小数位数
propList: ['*'], // 转换所有属性
viewportUnit: 'vw', // 转换后的单位
fontViewportUnit: 'vw', // 字体单位也使用 vw
selectorBlackList: [], // 不转换的选择器
minPixelValue: 1, // 小于等于 1px 不转换
mediaQuery: false, // 不转换媒体查询中的 px
replace: true, // 直接替换原属性
exclude: [/node_modules/], // 忽略 node_modules
landscape: false // 不生成横屏适配
}
}
}
}, },
axios: { axios: {

View File

@ -18,9 +18,11 @@
"axios": "^1.12.2", "axios": "^1.12.2",
"cookie-universal-nuxt": "^2.2.2", "cookie-universal-nuxt": "^2.2.2",
"core-js": "^3.25.3", "core-js": "^3.25.3",
"dayjs": "^1.11.19",
"element-ui": "^2.15.14", "element-ui": "^2.15.14",
"normalize.css": "^8.0.1", "normalize.css": "^8.0.1",
"nuxt": "^2.15.8", "nuxt": "^2.15.8",
"postcss-px-to-viewport": "^1.1.1",
"vue": "^2.7.10", "vue": "^2.7.10",
"vue-server-renderer": "^2.7.10", "vue-server-renderer": "^2.7.10",
"vue-template-compiler": "^2.7.10" "vue-template-compiler": "^2.7.10"

View File

@ -79,14 +79,14 @@
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.input-container { .input-container {
margin-top: 100px; margin-top: 30px;
margin-bottom: 60px; margin-bottom: 30px;
} }
} }
.list { .list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 30px; gap: 30px;
margin-bottom: 60px; margin-bottom: 40px;
} }
</style> </style>

View File

@ -2,19 +2,24 @@
<div id="normal-container" v-loading.fullscreen.lock="fullscreenLoading"> <div id="normal-container" v-loading.fullscreen.lock="fullscreenLoading">
<IntegratedLayout> <IntegratedLayout>
<div class="content"> <div class="content">
<div class="bread-menu">
<span>{{ type ? titleKey[type] : '' }}</span>
<i class="el-icon-arrow-right"></i>
<span class="crumbs gradient-color">{{newsDetail.title || ''}}</span>
</div>
<div class="views-title">{{ newsDetail.title || '' }}</div> <div class="views-title">{{ newsDetail.title || '' }}</div>
<div class="views-header flex-between-center"> <div class="views-header flex-between-center">
<div class="description"> <div class="description">
{{ newsDetail.summary || '' }} {{ newsDetail.summary || '' }}
</div> </div>
<div class="flex flex-col" style="gap: 16px"> <div class="flex flex-col gap-16">
<div class="flex" style="gap: 20px"> <div class="flex gap-20">
<ThumbBtn :like-count="newsDetail.likeCount || 0" :id="newsDetail.id || 0" type="article" @like-success="refreshToolDetail" /> <ThumbBtn :like-count="newsDetail.likeCount || 0" :id="newsDetail.id || 0" type="article" @like-success="refreshToolDetail" />
<CommentBtn :count="commentCount" /> <CommentBtn :count="commentCount" />
</div> </div>
<div class="flex items-center justify-center" style="padding: 0 7px; gap: 8px"> <div class="flex items-center justify-center pad-7 gap-8">
<img src="/ToolDetail/icon_clock1.png" alt="" style="width: 16px; height: 16px" /> <img src="/ToolDetail/icon_clock1.png" alt="" class="img-16" />
<div style="font-size: 14px; color: #869EC2; font-family: 'Poppins-Regular', serif; line-height: 18px"> <div class="date-text">
{{ formatDate(newsDetail.publishTime) }} {{ formatDate(newsDetail.publishTime) }}
</div> </div>
</div> </div>
@ -23,7 +28,7 @@
<div class="container flex justify-between"> <div class="container flex justify-between">
<div class="left-content flex flex-col"> <div class="left-content flex flex-col">
<div class="card preview-wrapper"> <div class="card preview-wrapper">
<img src="/" alt="" style="width: 100%; height: 100%" /> <img :src="newsDetail.coverImage || ''" alt="" class="wh-100" />
</div> </div>
<div class="flex-1"> <div class="flex-1">
<NewsDetail :article="newsDetail" /> <NewsDetail :article="newsDetail" />
@ -34,21 +39,21 @@
<div class="card swiper-box"> <div class="card swiper-box">
<el-carousel :autoplay="false" height="140px"> <el-carousel :autoplay="false" height="140px">
<el-carousel-item v-for="(item, i) in banner" :key="i"> <el-carousel-item v-for="(item, i) in banner" :key="i">
<img :src="item.imageUrl || ''" alt="" style="height: 140px; width: 100%;" /> <img :src="item.imageUrl || ''" alt="" class="swiper-img" />
</el-carousel-item> </el-carousel-item>
</el-carousel> </el-carousel>
</div> </div>
<!--网站导航--> <!--网站导航-->
<div class="card pop-list"> <div class="card pop-list">
<div style="padding: 24px 4px"> <div class="pad-24">
<PopularToolList /> <PopularToolList />
</div> </div>
</div> </div>
<!--文章列表--> <!--文章列表-->
<div class="card"> <div class="card">
<div style="padding: 24px 4px"> <div class="pad-24">
<div class="clearfix"> <div class="clearfix">
<img src="/logo/hot.png" :style="{marginRight: '6px'}" alt=""/> <img src="/logo/hot.png" alt=""/>
Latest Article Latest Article
</div> </div>
<div class="list-scroll"> <div class="list-scroll">
@ -60,9 +65,9 @@
</div> </div>
</div> </div>
</div> </div>
<div style="margin-top: 44px"> <div class="mt-44">
<div class="comment-title">Related news</div> <div class="comment-title">Related news</div>
<div class="flex" style="gap: 20px"> <div class="flex gap-20 justify-between">
<NewsCardItem v-for="it in relatedNewsList" :key="it.id" :item="it" @refresh="goToToolDetail" /> <NewsCardItem v-for="it in relatedNewsList" :key="it.id" :item="it" @refresh="goToToolDetail" />
</div> </div>
</div> </div>
@ -99,6 +104,14 @@ export default {
type: '', type: '',
commentCount: 0, commentCount: 0,
fullscreenLoading: false, fullscreenLoading: false,
titleKey: {
tool: 'AI Tools',
framework: 'Frameworks',
news: 'AI Days News',
observer: 'AI Observer',
analysis: 'In-depth Analysis',
pioneer: 'Pioneer In The Field',
},
} }
}, },
methods: { methods: {
@ -173,7 +186,7 @@ export default {
if (this.news_slug && this.type) { if (this.news_slug && this.type) {
await this.$store.dispatch('getBannerConfig'); await this.$store.dispatch('getBannerConfig');
await this.getNewsDetail(this.news_slug); await this.getNewsDetail(this.news_slug);
await this.getArticleListData(1, 4, this.type); await this.getArticleListData(1, 6, this.type);
await this.getLatestArticleListData(); await this.getLatestArticleListData();
} }
this.fullscreenLoading = false; this.fullscreenLoading = false;
@ -211,34 +224,73 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.gap-16 {
gap: 16px;
}
.gap-20 {
gap: 20px;
}
.pad-24 {
padding: 24px 4px;
}
.date-text {
font-size: 14px; color: #869EC2; font-family: 'Poppins-Regular'; line-height: 18px; white-space: nowrap;
}
.swiper-img {
height: 140px; width: 100%;
}
.img-16 {
height: 16px;
width: 16px;
}
.pad-7 {
padding: 0 7px;
}
.card { .card {
padding: 16px; padding: 16px;
background-color: #FFFFFF; background-color: #FFFFFF;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.08); box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.08);
} }
.mt-44 {
margin-top: 44px;
}
.content { .content {
padding-top: 192px; padding-top: 25px;
padding-bottom: 100px; 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;
}
}
.views-title { .views-title {
font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
font-size: 40px; font-size: 40px;
font-weight: 700; font-weight: 700;
margin-top: 78px;
} }
.views-header { .views-header {
margin-top: 10px; margin-top: 10px;
.description { .description {
margin-right: 350px; margin-right: 350px;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
font-size: 18px; font-size: 18px;
color: #64748B; color: #64748B;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
} }
} }
.container { .container {
gap: 20px; gap: 20px;
margin-top: 114px; margin-top: 30px;
margin-bottom: 40px; margin-bottom: 30px;
.right-content { .right-content {
width: 372px; width: 372px;
display: flex; display: flex;
@ -291,7 +343,10 @@ export default {
margin-bottom: 20px; margin-bottom: 20px;
font-size: $larg-font-size; font-size: $larg-font-size;
font-weight: bold; font-weight: bold;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
img {
margin-right: 6px;
}
} }
.pop-list { .pop-list {
.pop-item { .pop-item {
@ -324,7 +379,7 @@ export default {
} }
.tool-name { .tool-name {
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
color: #64748B; color: #64748B;
} }
} }
@ -344,7 +399,7 @@ export default {
font-size: 30px; font-size: 30px;
font-weight: 600; font-weight: 600;
margin-bottom: 30px; margin-bottom: 30px;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
color: #1E293B; color: #1E293B;
display: flex; display: flex;
align-items: center; align-items: center;
@ -364,7 +419,7 @@ export default {
font-size: 30px; font-size: 30px;
font-weight: 600; font-weight: 600;
margin-bottom: 30px; margin-bottom: 30px;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
color: #1E293B; color: #1E293B;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -79,14 +79,14 @@ export default {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.input-container { .input-container {
margin-top: 100px; margin-top: 60px;
margin-bottom: 60px; margin-bottom: 40px;
} }
} }
.list { .list {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 30px; gap: 30px;
margin-bottom: 60px; margin-bottom: 40px;
} }
</style> </style>

View File

@ -14,20 +14,20 @@
</div> </div>
<div class="bottom-info"> <div class="bottom-info">
<div class="first" style="gap: 50px"> <div class="first flex items-center gap-50">
<div> <div>
<img src="/logo/logo_xs.png" alt="" /> <img src="/logo/logo_xs.png" alt="" />
<span>{{ item.slug || ''}}</span> <span>{{ item.slug || ''}}</span>
</div> </div>
<div class="praise flex items-center"> <div class="praise flex items-center">
<img src="/logo/praise.png" alt="" style="width: 16px; height: 16px" /> <img src="/logo/praise.png" alt="" class="wh-16" />
<span>{{ item.likeCount || 0 }}</span> <span>{{ item.likeCount || 0 }}</span>
</div> </div>
</div> </div>
<div class="time"> <div class="time">
<img src="/logo/clock.png" alt="" /> <img src="/logo/clock.png" alt="" />
<span>{{ item.publishTime || '' }}</span> <span>{{ formatPublishTime(item.publishTime || '') }}</span>
</div> </div>
</div> </div>
</div> </div>
@ -63,12 +63,26 @@ export default {
if (id) { if (id) {
await this.$api.article.recordArticleClick(id); await this.$api.article.recordArticleClick(id);
} }
} },
formatPublishTime(timeString) {
if (!timeString) return '';
const date = new Date(timeString);
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const year = date.getFullYear();
return `${year}-${month}-${day}`;
},
}, },
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.gap-50 {
gap: 50px;
}
.card { .card {
background: $white; background: $white;
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.05); box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.05);

View File

@ -45,11 +45,11 @@
padding-bottom: 100px; padding-bottom: 100px;
.bread-menu { .bread-menu {
font-size: $mid-font-size; font-size: $mid-font-size;
margin: 100px 0; margin: 24px 0 16px;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
.crumbs { .crumbs {
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-weight: 600; font-weight: 600;
} }
} }
@ -57,11 +57,13 @@
.title { .title {
font-weight: bold; font-weight: bold;
font-size: $huge-font-size1; font-size: $huge-font-size1;
font-family: 'Poppins-Bold';
} }
.description { .description {
font-size: $big-font-size; font-size: $big-font-size;
color: $grey-color; color: $grey-color;
font-family: 'Poppins-Medium';
} }
} }
</style> </style>

View File

@ -2,17 +2,17 @@
<div class="text-container"> <div class="text-container">
<div class="top"> <div class="top">
<div class="title"> <div class="title">
About AIToolsFinder About AIProdLaunches
</div> </div>
<div class="little-title"> <div class="little-title">
The Ultimate Global AI Tools Directory, Discover the Best New AI Products The Ultimate Global AI Tools Directory, Discover the Best New AI Products
</div> </div>
<div class="sub-title"> <div class="sub-title">
Welcome to AIToolsFinder your all-in-one gateway to the world of artificial intelligence. Welcome to AIProdLaunches your all-in-one gateway to the world of artificial intelligence.
</div> </div>
</div> </div>
<div class="content"> <div class="content">
Were more than just a directory. AIToolsFinder is designed to help you discover, learn, and apply AI in ways that truly make a difference in your personal and professional life. Whether youre a student exploring new technologies, a designer looking for creative tools, a developer in need of coding assistance, or a business team seeking automation solutionsAIToolsFinder is here to guide you. Were more than just a directory. AIProdLaunches is designed to help you discover, learn, and apply AI in ways that truly make a difference in your personal and professional life. Whether youre a student exploring new technologies, a designer looking for creative tools, a developer in need of coding assistance, or a business team seeking automation solutionsAIProdLaunches is here to guide you.
</div> </div>
<div class="terms-title"> <div class="terms-title">
Heres what youll find with us: Heres what youll find with us:
@ -49,7 +49,7 @@
<div class="item-content"> <div class="item-content">
<span class="item-title">Disclaimer </span> <span class="item-title">Disclaimer </span>
<span class="item-text"> <span class="item-text">
AIToolsFinder is the official and only website under this name. We do not operate paid groups, courses, or third-party affiliates. Please always verify youre on the official site. AIProdLaunches is the official and only website under this name. We do not operate paid groups, courses, or third-party affiliates. Please always verify youre on the official site.
</span> </span>
</div> </div>
</div> </div>
@ -57,7 +57,7 @@
<div class="dot"></div> <div class="dot"></div>
<div class="item-content"> <div class="item-content">
<span class="item-title">Get in Touch </span> <span class="item-title">Get in Touch </span>
<span class="item-text">Got a new AI tool to share, want to collaborate, or have ideas for improving AIToolsFinder? Reach us anytime at </span> <span class="item-text">Got a new AI tool to share, want to collaborate, or have ideas for improving AIProdLaunches? Reach us anytime at </span>
<span class="item-link"> corina@inziqi.com.</span> <span class="item-link"> corina@inziqi.com.</span>
</div> </div>
</div> </div>
@ -89,25 +89,25 @@
color: #3a4a65; color: #3a4a65;
font-size: 20px; font-size: 20px;
font-weight: 600; font-weight: 600;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
margin-top: 30px; margin-top: 30px;
text-align: center; text-align: center;
} }
.content { .content {
color: #64748B; color: #64748B;
font-size: 18px; font-size: 18px;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
} }
.terms-title { .terms-title {
color: #64748B; color: #64748B;
font-size: 18px; font-size: 18px;
font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
margin-top: 30px; margin-top: 30px;
margin-bottom: 20px; margin-bottom: 20px;
font-weight: bold; font-weight: bold;
} }
.little-title { .little-title {
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
font-size: 18px; font-size: 18px;
color: #64748B; color: #64748B;
margin-top: 20px; margin-top: 20px;
@ -133,18 +133,18 @@
.item-title { .item-title {
color: #64748B; color: #64748B;
font-size: 18px; font-size: 18px;
font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
font-weight: bold; font-weight: bold;
} }
.item-text { .item-text {
color: #64748B; color: #64748B;
font-size: 18px; font-size: 18px;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
} }
.item-link { .item-link {
color: #7B61FF; color: #7B61FF;
font-size: 18px; font-size: 18px;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
} }
} }
.bottom-line { .bottom-line {
@ -156,7 +156,7 @@
.bottom-text { .bottom-text {
font-weight: 600; font-weight: 600;
font-size: 28px; font-size: 28px;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
@include text-gradient(90deg, #2563eb, 22%, #7B61FF, 73%); @include text-gradient(90deg, #2563eb, 22%, #7B61FF, 73%);
} }
.bottom-line-left { .bottom-line-left {

View File

@ -5,62 +5,83 @@
Privacy Policy Privacy Policy
</div> </div>
<div class="terms-date"> <div class="terms-date">
Last updated April 4, 2024 Effective Date: October 27, 2025
</div> </div>
</div> </div>
<div class="content"> <div class="content">
<div class="title" style="margin-bottom: 30px">This privacy notice for Futurepedia LLC (doing business as Futurepedia) ("we," "us," or "our") describes how and why we might collect, store, use, and/or share ("process") your information when you use our services ("Services"), such as when you:</div> <div class="title mb-14">
<div class="text text-dot"> 1. Introduction
Visit our website at <span class="text-link">http://www.futurepedia.io/</span> or any website of ours that links to this privacy notice
</div> </div>
<div class="text text-dot" style="margin-bottom: 30px">Engage with us in other related ways, including any sales, marketing, or events</div> <div class="text mb-14">
<div class="title">Questions or concerns?</div> Welcome to AIProdLaunch (we, our, us). We provide a collection of AI tools and resources. Your privacy is important to us. This policy explains how we handle information when you use our website. By visiting or using AIProdLaunch, you agree to this Privacy Policy.
<div class="text text-dot">Reading this privacy notice will help you understand your privacy rights and choices. If you do not agree with our policies and practices, please do not use our Services. If you still have questions or concerns, please contact us at </div> <!--Visit our website at <span class="text-link">http://www.futurepedia.io/</span> or any website of ours that links to this privacy notice-->
<div class="text-link" style="margin-bottom: 40px">contact@futurepedia.io.</div>
<div class="title-dark">SUMMARY OF KEY POINTS</div>
<div class="title" style="margin-bottom: 30px">This summary provides key points from our privacy notice, but you can find out more details about any of these topics by clicking the link following each key point or by using our table of contents below to find the section you are looking for.</div>
<div class="title">What personal information do we process?</div>
<div class="text" style="margin-bottom: 30px">When you visit, use, or navigate our Services, we may process personal information depending on how you interact with us and the Services, the choices you make, and the products and features you use.</div>
<div class="title">Do we process any sensitive personal information? </div>
<div class="text" style="margin-bottom: 30px">We do not process sensitive personal information.</div>
<div class="title">Do we receive any information from third parties?</div>
<div class="text" style="margin-bottom: 30px">We do not receive any information from third parties.</div>
<div class="title">How do we process your information?</div>
<div class="text" style="margin-bottom: 30px">We process your information to provide, improve, and administer our Services, communicate with you, for security and fraud prevention, and to comply with law. We may also process your information for other purposes with your consent. about how we process your information.We process your information to provide, improve, and administer our Services, communicate with you, for security and fraud prevention, and to comply with law. We may also process your information for other purposes with your consent. about how we process your information.</div>
<div class="title-dark">TABLE OF CONTENTS</div>
<div class="text-underline">1. WHAT INFORMATION DO WE COLLECT?</div>
<div class="text-underline">2. HOW DO WE PROCESS YOUR INFORMATION?</div>
<div class="text-underline">1. WHAT INFORMATION DO WE COLLECT?</div>
<div class="title" style="margin-bottom: 25px">Personal information you disclose to us</div>
<div class="title">In Short:</div>
<div class="text" style="margin-bottom: 30px">We collect personal information that you provide to us.
We collect personal information that you voluntarily provide us when you register on the Services, express an interest in obtaining information about us or our products and Services when participating in activities on the Services, or otherwise when you contact us.
</div> </div>
<div class="title">Personal Information Provided by You. </div> <div class="title mb-14">2. Information We Collect</div>
<div class="text" style="margin-bottom: 30px"> <div class="text mb-14">
The personal information we collect depends on the context of your interactions with us and the Services, your choices, and the products and features you use. The personal information we collect may include the following: We do not require accounts and do not collect personal information. We may collect anonymous data to improve our website, such as:
</div> </div>
<div class="text text-dot"> <div class="text text-dot mb-10">
names Browser type and version
</div> </div>
<div class="text text-dot"> <div class="text text-dot mb-10">
email addresses Pages you visit and time spent
</div> </div>
<div class="text text-dot" style="margin-bottom: 30px"> <div class="text text-dot mb-10">
job titles Referring websites
</div> </div>
<div class="title">Sensitive Information.</div> <div class="text text-dot mb-10">
<div class="text" style="margin-bottom: 25px">We do not process sensitive information.</div> Aggregated IP addresses
<div class="text">Social Media Login Data. We may provide you with the option to register with us using your existing social media account details, like your Facebook, Twitter, or other social media accounts. If you choose to register in this way, we will collect the information described in the section called "HOW DO WE HANDLE YOUR SOCIAL LOGINS?" below.</div> </div>
<div class="text-underline">2. HOW DO WE PROCESS YOUR INFORMATION?</div> <div class="text mb-14">
<div class="title">In Short:</div> This information is used only for analytics and site improvement.
<div class="text" style="margin-bottom: 30px"> </div>
We process your information to provide, improve, and administer our Services, communicate with you, for security and fraud prevention, and to comply with law. We may also process your information for other purposes with your consent. <div class="title mb-14">3. Cookies and Analytics</div>
<div class="text mb-14">
We may use cookies or similar technologies to track anonymous usage data, such as with Google Analytics. Cookies help us understand traffic, improve features, and optimize content.
</div>
<div class="text mb-14">
You can disable cookies in your browser, but some features may not work properly.
</div>
<div class="title mb-14">4. Advertising and Affiliate Links</div>
<div class="text mb-14">
We may show third-party ads or include affiliate links. Advertisers may use cookies or tracking tools.
</div>
<div class="text mb-14">
We do not control how third parties use data. Please check their privacy policies if you click their links or ads.
</div>
<div class="title mb-14">5. Third-Party Links and Content</div>
<div class="text mb-14">
Our site may link to external websites, including AI tool providers or platforms like ProductHunt. We are not responsible for their privacy practices or content.
</div>
<div class="title mb-14">6. Data Storage and Security</div>
<div class="text mb-14">
Our servers are located overseas. We implement standard security measures to protect the anonymous data we collect. Since we do not store personal information, your privacy is well protected.
</div>
<div class="title mb-14">7. Your Rights and Choices</div>
<div class="text mb-14">
You can:
</div>
<div class="text text-dot mb-10">
Disable cookies via your browser
</div>
<div class="text text-dot mb-10">
Opt-out of personalized ads through your browser or ad network settings
</div>
<div class="text text-dot mb-10">
Contact us with questions about this Privacy Policy
</div>
<div class="title mb-14">8. Updates to This Policy</div>
<div class="text mb-14">
We may update this policy from time to time. Updated versions will appear on this page with the revision date. Your continued use of AIProdLaunch means you accept the updated policy.
</div>
<div class="title mb-14">9. Contact Us</div>
<div class="text mb-14">
If you have questions, please reach out to:
</div>
<div class="text mb-14">
Email: <span class="text-link">corina@inziqi.com</span>
</div> </div>
<div class="title" style="margin-bottom: 25px">We process your personal information for a variety of reasons, depending on how you interact with our Services, including:</div>
<div class="text text-dot">To facilitate account creation and authentication and otherwise manage user accounts. We may process your information so you can create and log in to your account, as well as keep your account in working order.</div>
<div class="text text-dot">To save or protect an individual's vital interest. We may process your information when necessary to save or protect an individuals vital interest, such as to prevent harm.</div>
</div> </div>
</div> </div>
</template> </template>
@ -79,11 +100,17 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.mb-14 {
margin-bottom: 14px;
}
.mb-10 {
margin-bottom: 10px;
}
.terms { .terms {
.terms-title { .terms-title {
font-size: 40px; font-size: 40px;
font-weight: bold; font-weight: bold;
font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
margin-top: 20px; margin-top: 20px;
text-align: center; text-align: center;
} }
@ -97,32 +124,33 @@
padding-bottom: 50px; padding-bottom: 50px;
.text { .text {
color: #64748B; color: #64748B;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
} }
.title { .title {
font-size: 18px; font-size: 18px;
color: #64748B; color: #64748B;
font-weight: bold; font-weight: bold;
font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
} }
.title-dark { .title-dark {
font-size: 18px; font-size: 18px;
color: #1E293B; color: #1E293B;
font-weight: bold; font-weight: bold;
font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
margin-bottom: 16px; margin-bottom: 16px;
} }
.text-link { .text-link {
color: #7B61FF; color: #7B61FF;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
} }
.text-underline { .text-underline {
text-decoration: underline; text-decoration: underline;
color: #3A4A65; color: #3A4A65;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Regular';
margin-bottom: 20px; margin-bottom: 20px;
} }
.text-dot { .text-dot {
font-family: 'Poppins-Medium';
&::before { &::before {
content: ''; content: '';
display: inline-block; display: inline-block;

View File

@ -5,62 +5,61 @@
Terms Of Service Terms Of Service
</div> </div>
<div class="date terms-date"> <div class="date terms-date">
Last updated April 4, 2024 Effective Date: October 27, 2025
</div> </div>
</div> </div>
<div class="content"> <div class="content">
<div class="title" style="margin-bottom: 30px">This privacy notice for Futurepedia LLC (doing business as Futurepedia) ("we," "us," or "our") describes how and why we might collect, store, use, and/or share ("process") your information when you use our services ("Services"), such as when you:</div> <div class="title mb-14">
<div class="text text-dot"> 1. Acceptance of Terms
Visit our website at <span class="text-link">http://www.futurepedia.io/</span> or any website of ours that links to this privacy notice
</div> </div>
<div class="text text-dot" style="margin-bottom: 30px">Engage with us in other related ways, including any sales, marketing, or events</div> <div class="text mb-14">
<div class="title">Questions or concerns?</div> By accessing or using AIProdLaunch (we, our, us), you agree to comply with these Terms of Service. If you do not agree, please do not use our website.
<div class="text text-dot">Reading this privacy notice will help you understand your privacy rights and choices. If you do not agree with our policies and practices, please do not use our Services. If you still have questions or concerns, please contact us at </div>
<div class="text-link" style="margin-bottom: 40px">contact@futurepedia.io.</div>
<div class="title-dark">SUMMARY OF KEY POINTS</div>
<div class="title" style="margin-bottom: 30px">This summary provides key points from our privacy notice, but you can find out more details about any of these topics by clicking the link following each key point or by using our table of contents below to find the section you are looking for.</div>
<div class="title">What personal information do we process?</div>
<div class="text" style="margin-bottom: 30px">When you visit, use, or navigate our Services, we may process personal information depending on how you interact with us and the Services, the choices you make, and the products and features you use.</div>
<div class="title">Do we process any sensitive personal information? </div>
<div class="text" style="margin-bottom: 30px">We do not process sensitive personal information.</div>
<div class="title">Do we receive any information from third parties?</div>
<div class="text" style="margin-bottom: 30px">We do not receive any information from third parties.</div>
<div class="title">How do we process your information?</div>
<div class="text" style="margin-bottom: 30px">We process your information to provide, improve, and administer our Services, communicate with you, for security and fraud prevention, and to comply with law. We may also process your information for other purposes with your consent. about how we process your information.We process your information to provide, improve, and administer our Services, communicate with you, for security and fraud prevention, and to comply with law. We may also process your information for other purposes with your consent. about how we process your information.</div>
<div class="title-dark">TABLE OF CONTENTS</div>
<div class="text-underline">1. WHAT INFORMATION DO WE COLLECT?</div>
<div class="text-underline">2. HOW DO WE PROCESS YOUR INFORMATION?</div>
<div class="text-underline">1. WHAT INFORMATION DO WE COLLECT?</div>
<div class="title" style="margin-bottom: 25px">Personal information you disclose to us</div>
<div class="title">In Short:</div>
<div class="text" style="margin-bottom: 30px">We collect personal information that you provide to us.
We collect personal information that you voluntarily provide us when you register on the Services, express an interest in obtaining information about us or our products and Services when participating in activities on the Services, or otherwise when you contact us.
</div> </div>
<div class="title">Personal Information Provided by You. </div> <div class="title mb-14">2. Use of the Website</div>
<div class="text" style="margin-bottom: 30px"> <div class="text mb-14">
The personal information we collect depends on the context of your interactions with us and the Services, your choices, and the products and features you use. The personal information we collect may include the following: AIProdLaunch is a website providing information and links to AI tools and resources. You may use the website for personal, non-commercial purposes only. You agree not to:
</div> </div>
<div class="text text-dot"> <div class="text text-dot mb-10">
names Violate any applicable laws or regulations
</div> </div>
<div class="text text-dot"> <div class="text text-dot mb-10">
email addresses Interfere with or disrupt the website or servers
</div> </div>
<div class="text text-dot" style="margin-bottom: 30px"> <div class="text text-dot mb-10">
job titles Attempt to gain unauthorized access to any part of the website
</div> </div>
<div class="title">Sensitive Information.</div> <div class="title mb-14">3. Intellectual Property</div>
<div class="text" style="margin-bottom: 25px">We do not process sensitive information.</div> <div class="text mb-14">
<div class="text">Social Media Login Data. We may provide you with the option to register with us using your existing social media account details, like your Facebook, Twitter, or other social media accounts. If you choose to register in this way, we will collect the information described in the section called "HOW DO WE HANDLE YOUR SOCIAL LOGINS?" below.</div> All content on AIProdLaunch, including text, images, logos, and website design, is owned by us or our licensors. You may view, download, or share content for personal use only. You may not reproduce, distribute, or modify content without permission.
<div class="text-underline">2. HOW DO WE PROCESS YOUR INFORMATION?</div> </div>
<div class="title">In Short:</div> <div class="title mb-14">4. Advertising and Affiliate Links</div>
<div class="text" style="margin-bottom: 30px"> <div class="text mb-14">
We process your information to provide, improve, and administer our Services, communicate with you, for security and fraud prevention, and to comply with law. We may also process your information for other purposes with your consent. AIProdLaunch may display third-party ads or include affiliate links. We do not control third-party websites, ads, or tracking technologies. By using these links or clicking on ads, you acknowledge that any interaction is between you and the third party, and we are not responsible for their content, privacy practices, or transactions.
</div>
<div class="title mb-14">5. Disclaimer of Warranties</div>
<div class="text mb-14">
AIProdLaunch is provided as is and as available. We do not guarantee the accuracy, completeness, or reliability of any content or third-party links. Use of the website is at your own risk.
</div>
<div class="title mb-14">6. Limitation of Liability</div>
<div class="text mb-14">
To the maximum extent permitted by law, AIProdLaunch will not be liable for any direct, indirect, incidental, or consequential damages arising from your use of the website, including any loss of data, revenue, or goodwill.
</div>
<div class="title mb-14">7. Modifications to Terms</div>
<div class="text mb-14">
We may update these Terms of Service from time to time. Updated versions will be posted on this page with the revision date. Your continued use of AIProdLaunch constitutes acceptance of the updated Terms.
</div>
<div class="title mb-14">8. Governing Law</div>
<div class="text mb-14">
These Terms are governed by the laws of the country where the website servers are hosted, without regard to its conflict of law rules.
</div>
<div class="title mb-14">9. Contact Us</div>
<div class="text mb-14">
If you have any questions regarding these Terms of Service, please contact us at:
</div>
<div class="text mb-14">
Email: <span class="text-link">corina@inziqi.com</span>
</div> </div>
<div class="title" style="margin-bottom: 25px">We process your personal information for a variety of reasons, depending on how you interact with our Services, including:</div>
<div class="text text-dot">To facilitate account creation and authentication and otherwise manage user accounts. We may process your information so you can create and log in to your account, as well as keep your account in working order.</div>
<div class="text text-dot">To save or protect an individual's vital interest. We may process your information when necessary to save or protect an individuals vital interest, such as to prevent harm.</div>
</div> </div>
</div> </div>
</template> </template>
@ -79,11 +78,17 @@
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.mb-14 {
margin-bottom: 14px;
}
.mb-10 {
margin-bottom: 10px;
}
.terms { .terms {
.terms-title { .terms-title {
font-size: 40px; font-size: 40px;
font-weight: bold; font-weight: bold;
font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
margin-top: 20px; margin-top: 20px;
text-align: center; text-align: center;
} }
@ -98,29 +103,29 @@
padding-bottom: 50px; padding-bottom: 50px;
.text { .text {
color: #64748B; color: #64748B;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
} }
.title { .title {
font-size: 18px; font-size: 18px;
color: #64748B; color: #64748B;
font-weight: bold; font-weight: bold;
font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
} }
.title-dark { .title-dark {
font-size: 18px; font-size: 18px;
color: #1E293B; color: #1E293B;
font-weight: bold; font-weight: bold;
font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
margin-bottom: 16px; margin-bottom: 16px;
} }
.text-link { .text-link {
color: #7B61FF; color: #7B61FF;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
} }
.text-underline { .text-underline {
text-decoration: underline; text-decoration: underline;
color: #3A4A65; color: #3A4A65;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
margin-bottom: 20px; margin-bottom: 20px;
} }
.text-dot { .text-dot {

View File

@ -88,7 +88,7 @@ export default {
margin: 16px 0; margin: 16px 0;
gap: 20px; gap: 20px;
.tag-item { .tag-item {
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #1E293B; color: #1E293B;
padding: 4px 12px !important; padding: 4px 12px !important;
@include gradient-border($linear-gradient-start, $linear-gradient-end) @include gradient-border($linear-gradient-start, $linear-gradient-end)
@ -99,7 +99,7 @@ export default {
font-weight: 600; font-weight: 600;
margin-bottom: 20px; margin-bottom: 20px;
margin-top: 10px; margin-top: 10px;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
color: #1E293B; color: #1E293B;
display: flex; display: flex;
align-items: center; align-items: center;
@ -115,7 +115,7 @@ export default {
} }
.article-text { .article-text {
color: #64748B; color: #64748B;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
} }
.article-graph { .article-graph {
padding-left: 20px; padding-left: 20px;
@ -138,13 +138,13 @@ export default {
.title { .title {
font-size: 18px; font-size: 18px;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-weight: 600; font-weight: 600;
color: #506179; color: #506179;
} }
.content { .content {
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #64748B; color: #64748B;
margin-top: 18px; margin-top: 18px;
} }

View File

@ -7,14 +7,14 @@
<div class="description"> <div class="description">
{{ newsDetail.summary || '' }} {{ newsDetail.summary || '' }}
</div> </div>
<div class="flex flex-col" style="gap: 16px"> <div class="flex flex-col gap-16">
<div class="flex" style="gap: 20px"> <div class="flex gap-20">
<ThumbBtn :like-count="newsDetail.likeCount || 0" :id="newsDetail.id || 0" type="article" @like-success="refreshToolDetail" /> <ThumbBtn :like-count="newsDetail.likeCount || 0" :id="newsDetail.id || 0" type="article" @like-success="refreshToolDetail" />
<CommentBtn :comment-count="commentCount" /> <CommentBtn :comment-count="commentCount" />
</div> </div>
<div class="flex items-center justify-center" style="padding: 0 7px; gap: 8px"> <div class="flex items-center justify-center gap-8 pad-7">
<img src="/ToolDetail/icon_clock1.png" alt="" style="width: 16px; height: 16px" /> <img src="/ToolDetail/icon_clock1.png" alt="" class="wh-16" />
<div style="font-size: 14px; color: #869EC2; font-family: 'Poppins-Regular', serif; line-height: 18px"> <div class="date-text">
{{ formatDate(newsDetail.publishTime) }} {{ formatDate(newsDetail.publishTime) }}
</div> </div>
</div> </div>
@ -30,7 +30,7 @@
</div> </div>
<div> <div>
<div class="comment-title">Related news</div> <div class="comment-title">Related news</div>
<div class="flex-between-center" style="gap: 20px"> <div class="flex-between-center gap-20">
<NewsCardItem v-for="it in relatedNewsList" :key="it.id" :item="it" @refresh="goToRefreshPage" /> <NewsCardItem v-for="it in relatedNewsList" :key="it.id" :item="it" @refresh="goToRefreshPage" />
</div> </div>
</div> </div>
@ -40,21 +40,21 @@
<div class="card swiper-box"> <div class="card swiper-box">
<el-carousel :autoplay="false" height="140px"> <el-carousel :autoplay="false" height="140px">
<el-carousel-item v-for="item in 4" :key="item"> <el-carousel-item v-for="item in 4" :key="item">
<img style="width: 100%; height: 140px" alt="" src="/" /> <img class="w-100" alt="" src="/" />
</el-carousel-item> </el-carousel-item>
</el-carousel> </el-carousel>
</div> </div>
<!--网站导航--> <!--网站导航-->
<div class="card pop-list"> <div class="card pop-list">
<div style="padding: 24px 4px"> <div class="pad-24">
<PopularToolList /> <PopularToolList />
</div> </div>
</div> </div>
<!--文章列表--> <!--文章列表-->
<div class="card"> <div class="card">
<div style="padding: 24px 4px"> <div class="pad-24">
<div class="clearfix"> <div class="clearfix">
<img src="/logo/hot.png" :style="{marginRight: '6px'}" alt=""/> <img src="/logo/hot.png" class="mr-6" alt=""/>
Latest Article Latest Article
</div> </div>
<div class="list-scroll"> <div class="list-scroll">
@ -174,6 +174,26 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.date-text {
font-size: 14px; color: #869EC2; font-family: 'Poppins-Regular'; line-height: 18px;
}
.gap-8 {
gap: 8px;
}
.pad-7 {
padding: 0 7px;
}
.w-100 {
width: 100%;
height: 140px;
}
.pad-24 {
padding: 24px 4px;
}
.mr-6 {
margin-right: 6px;
}
.card { .card {
padding: 16px; padding: 16px;
background-color: #FFFFFF; background-color: #FFFFFF;
@ -189,12 +209,12 @@ export default {
padding-top: 87px; padding-top: 87px;
padding-bottom: 120px; padding-bottom: 120px;
.gradient-title { .gradient-title {
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
} }
.views-title { .views-title {
font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
font-size: 40px; font-size: 40px;
font-weight: 700; font-weight: 700;
margin-top: 78px; margin-top: 78px;
@ -203,7 +223,7 @@ export default {
margin-top: 10px; margin-top: 10px;
.description { .description {
margin-right: 350px; margin-right: 350px;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
font-size: 18px; font-size: 18px;
color: #64748B; color: #64748B;
} }
@ -262,7 +282,7 @@ export default {
align-items: center; align-items: center;
font-size: $larg-font-size; font-size: $larg-font-size;
font-weight: bold; font-weight: bold;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
} }
.pop-list { .pop-list {
.pop-item { .pop-item {
@ -295,7 +315,7 @@ export default {
} }
.tool-name { .tool-name {
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
color: #64748B; color: #64748B;
} }
} }
@ -319,7 +339,7 @@ export default {
font-size: 30px; font-size: 30px;
font-weight: 600; font-weight: 600;
margin-bottom: 30px; margin-bottom: 30px;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
color: #1E293B; color: #1E293B;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -3,15 +3,15 @@
<div class="input"> <div class="input">
<SearchInput v-model="searchText" placeholder="Please enter the key words" @search="handleTextSearch" /> <SearchInput v-model="searchText" placeholder="Please enter the key words" @search="handleTextSearch" />
</div> </div>
<div class="card flex-1 flex flex-col"> <div class="card flex flex-col flex-1">
<div> <div class="flex-1 pad-10">
<div style="padding: 30px 10px"> <div class="scroll-box">
<div class="daily-content" v-for="(it, i) in groupedArticles" :key="i"> <div class="daily-content" v-for="(it, i) in groupedArticles" :key="i">
<div class="date-title flex items-center"> <div class="date-title flex items-center">
<img src="/about/icon_title_date.png" alt="Daily News" /> <img src="/about/icon_title_date.png" alt="Daily News" />
<div class="gradient-color">{{ it.date }}</div> <div class="gradient-color">{{ it.date }}</div>
</div> </div>
<div class="flex-col flex" style="gap: 40px"> <div class="flex-col flex gap-40">
<ArticleTextListItem v-for="item in it.list" :key="item.id" :item="item" /> <ArticleTextListItem v-for="item in it.list" :key="item.id" :item="item" />
</div> </div>
<div class="diver"></div> <div class="diver"></div>
@ -128,6 +128,16 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.pad-10 {
padding: 10px 0;
}
.gap-40 {
gap: 40px;
}
.scroll-box {
max-height: 1600px;
overflow-y: auto;
}
.card { .card {
padding: 20px; padding: 20px;
background-color: #FFFFFF; background-color: #FFFFFF;
@ -144,7 +154,7 @@ export default {
.daily-content { .daily-content {
.date-title { .date-title {
gap: 8px; gap: 8px;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-size: 24px; font-size: 24px;
font-weight: 600; font-weight: 600;
margin-bottom: 40px; margin-bottom: 40px;

View File

@ -1,16 +1,18 @@
<template> <template>
<div class="article-box" @click="handleClick"> <div class="article-box" @click="handleClick">
<div class="line"></div> <div class="line"></div>
<div style="gap: 20px" class="flex"> <div class="flex gap-20">
<div class="flex flex-col justify-center">
<div class="preview-box"> <div class="preview-box">
<img :src="item.coverImage || ''" alt="" > <img :src="item.coverImage || ''" alt="" class="wh-100">
</div>
<div class="flex items-center gap-6">
<img src="/ToolDetail/icon_clock1.png" alt="" class="wh-16" />
<div class="time">{{ formatPublishTime(item.publishTime || '') }}</div>
</div>
</div> </div>
<div class="content flex flex-col justify-between"> <div class="content flex flex-col justify-between">
<div class="description">{{ item.summary || '' }}</div> <div class="description">{{ item.summary || '' }}</div>
<div class="flex items-center" style="gap: 6px">
<img src="/ToolDetail/icon_clock1.png" alt="" />
<div class="time">{{ item.publishTime || '' }}</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -33,12 +35,35 @@ export default {
methods: { methods: {
handleClick() { handleClick() {
this.$emit('refresh', this.item); this.$emit('refresh', this.item);
} },
formatPublishTime(timeString) {
if (!timeString) return '';
const date = new Date(timeString);
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const year = date.getFullYear();
return `${year}-${month}-${day}`;
},
}, },
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.gap-20 {
gap: 20px;
}
.gap-6 {
gap: 6px;
}
.time {
color: #869EC2;
font-family: 'Poppins-Regular';
font-size: 14px;
line-height: 16px;
}
.article-box { .article-box {
cursor: pointer; cursor: pointer;
&:active { &:active {
@ -46,35 +71,22 @@ export default {
} }
.preview-box { .preview-box {
width: 104px; width: 104px;
height: 104px; height: 52px;
border-radius: 6px; border-radius: 6px;
img { margin-bottom: 12px;
width: 100%;
height: 100%;
}
} }
.content { .content {
height: 104px; height: 104px;
flex: 1; flex: 1;
.description { .description {
color: #1E293B; color: #1E293B;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 3; -webkit-line-clamp: 3;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
} }
img {
width: 16px;
height: 16px;
}
.time {
color: #869EC2;
font-family: 'Poppins-Regular', serif;
font-size: 14px;
line-height: 16px;
}
} }
} }
</style> </style>

View File

@ -73,19 +73,19 @@ export default {
.title { .title {
font-size: 18px; font-size: 18px;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-weight: 600; font-weight: 600;
color: #3A4A65; color: #3A4A65;
} }
.content { .content {
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #64748B; color: #64748B;
margin-top: 20px; margin-top: 20px;
} }
.source { .source {
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #C8CFD7; color: #C8CFD7;
margin-top: 10px; margin-top: 10px;
font-size: 14px; font-size: 14px;

View File

@ -3,19 +3,19 @@
<div class="preview"> <div class="preview">
<img :src="item.coverImage || ''" alt="" /> <img :src="item.coverImage || ''" alt="" />
</div> </div>
<div class="flex flex-col" style="gap: 14px"> <div class="flex flex-col gap-12">
<div class="title" style="height: 60px">{{ item.title || '' }}</div> <div class="title h-60">{{ item.title || '' }}</div>
<div class="description" style="height: 72px"> <div class="description h-72">
{{ item.summary || '' }} {{ item.summary || '' }}
</div> </div>
</div> </div>
<div class="bottom"> <div class="bottom">
<div class="flex items-center" style="gap: 12px"> <div class="flex items-center gap-12">
<div class="circle"></div> <div class="circle"></div>
<div class="text">{{ item.slug || '' }}</div> <div class="text">{{ item.author || '' }}</div>
</div> </div>
<div class="flex items-center" style="gap: 8px"> <div class="flex items-center gap-8">
<img src="/ToolDetail/icon_thumb.png" alt="" style="width: 14px; height: 14px" /> <img src="/ToolDetail/icon_thumb.png" alt="" />
<div class="text">{{ item.likeCount || 0 }}</div> <div class="text">{{ item.likeCount || 0 }}</div>
</div> </div>
</div> </div>
@ -44,6 +44,18 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.gap-12 {
gap: 12px;
}
.gap-8 {
gap: 8px;
}
.h-60 {
height: 60px;
}
.h-72 {
height: 72px;
}
.card { .card {
padding: 20px; padding: 20px;
background-color: #FFFFFF; background-color: #FFFFFF;
@ -82,14 +94,18 @@ export default {
.text { .text {
font-size: 14px; font-size: 14px;
color: #C8CFD7; color: #C8CFD7;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
line-height: 18px; line-height: 18px;
} }
img {
width: 14px;
height: 14px;
}
} }
.title { .title {
font-size: 20px; font-size: 20px;
color: #3A4A65; color: #3A4A65;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-weight: 600; font-weight: 600;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 4; -webkit-line-clamp: 4;
@ -99,7 +115,8 @@ export default {
} }
.description { .description {
color: #64748B; color: #64748B;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
height: auto;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 3; -webkit-line-clamp: 3;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;

View File

@ -7,8 +7,8 @@
<div class="views-header flex-between-center"> <div class="views-header flex-between-center">
<div class="description">Keep up-to-date with the latest AI industry developments. This section provides daily news coverage, focusing on global breakthroughs, frontier research, market movements, and emerging trends across AI sectors.</div> <div class="description">Keep up-to-date with the latest AI industry developments. This section provides daily news coverage, focusing on global breakthroughs, frontier research, market movements, and emerging trends across AI sectors.</div>
</div> </div>
<div class="container flex justify-between"> <div class="container">
<div class="left-content flex flex-col"> <div class="left-content flex flex-col" ref="leftContent">
<div class="card preview-wrapper"> <div class="card preview-wrapper">
<img :src="newConfig.imageUrl || ''" alt="" /> <img :src="newConfig.imageUrl || ''" alt="" />
</div> </div>
@ -16,26 +16,26 @@
<NewsList /> <NewsList />
</div> </div>
</div> </div>
<div class="right-content"> <div class="right-content" ref="rightContent">
<!--轮播图--> <!--轮播图-->
<div class="card swiper-box"> <div class="card swiper-box">
<el-carousel :autoplay="false" height="140px"> <el-carousel :autoplay="false">
<el-carousel-item v-for="(item, i) in banner" :key="i"> <el-carousel-item v-for="(item, i) in banner" :key="i">
<img :src="item.imageUrl || ''" alt="" style="height: 140px; width: 100%;" /> <img :src="item.imageUrl || ''" alt="" class="h-100" />
</el-carousel-item> </el-carousel-item>
</el-carousel> </el-carousel>
</div> </div>
<!--网站导航--> <!--网站导航-->
<div class="card pop-list"> <div class="card pop-list">
<div style="padding: 24px 4px"> <div class="pad-24">
<PopularToolList /> <PopularToolList />
</div> </div>
</div> </div>
<!--文章列表--> <!--文章列表-->
<div class="card"> <div class="card flex-1">
<div style="padding: 24px 4px"> <div class="h-100 flex flex-col pad-24">
<div class="clearfix"> <div class="clearfix">
<img src="/logo/hot.png" :style="{marginRight: '6px'}" alt=""/> <img src="/logo/hot.png" alt=""/>
Latest Article Latest Article
</div> </div>
<div class="list-scroll"> <div class="list-scroll">
@ -115,15 +115,24 @@ export default {
await this.getModuleConfig(); await this.getModuleConfig();
await this.getLatestArticleListData(); await this.getLatestArticleListData();
this.fullscreenLoading = false; this.fullscreenLoading = false;
} },
}, },
mounted() { mounted() {
this.onLoad(); this.onLoad();
} },
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.img-140 {
height: 140px; width: 100%;
}
.pad-24 {
padding: 24px 4px;
}
.h-100 {
height: 100%;
}
.card { .card {
padding: 16px; padding: 16px;
background-color: #FFFFFF; background-color: #FFFFFF;
@ -136,44 +145,58 @@ export default {
overflow-y: auto; overflow-y: auto;
position: relative; position: relative;
.content { .content {
padding-top: 87px; padding-top: 25px;
padding-bottom: 120px; padding-bottom: 100px;
.gradient-title { .gradient-title {
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
} }
.views-title { .views-title {
font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
font-size: 40px; font-size: 40px;
font-weight: 700; font-weight: 700;
margin-top: 78px; margin-top: 25px;
} }
.views-header { .views-header {
margin-top: 10px; margin-top: 10px;
.description { .description {
margin-right: 350px; margin-right: 350px;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
font-size: 18px; font-size: 18px;
color: #64748B; color: #64748B;
} }
} }
.container { .container {
gap: 20px; margin-top: 50px;
margin-top: 114px; display: grid;
grid-template-columns: 1fr 372px;
align-items: start;
grid-gap: 20px;
.right-content { .right-content {
width: 372px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 20px; gap: 20px;
.card:last-child {
flex: 1;
display: flex;
flex-direction: column;
margin: 0;
.list-scroll { .list-scroll {
height: 1500px;
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
height: 1350px; }
} }
.swiper-box { .swiper-box {
height: 174px; height: 174px;
::v-deep .el-carousel { ::v-deep .el-carousel {
.el-carousel__container {
height: 142px;
}
.el-carousel__arrow { .el-carousel__arrow {
opacity: 0 !important; opacity: 0 !important;
transition: none !important; transition: none !important;
@ -212,7 +235,10 @@ export default {
align-items: center; align-items: center;
font-size: $larg-font-size; font-size: $larg-font-size;
font-weight: bold; font-weight: bold;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
img {
margin-right: 6px;
}
} }
.pop-list { .pop-list {
.pop-item { .pop-item {
@ -245,7 +271,7 @@ export default {
} }
.tool-name { .tool-name {
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
color: #64748B; color: #64748B;
} }
} }
@ -256,8 +282,7 @@ export default {
} }
} }
.left-content { .left-content {
flex: 1; height: 100%;
gap: 20px;
.preview-wrapper { .preview-wrapper {
height: 320px; height: 320px;
img { img {
@ -270,7 +295,7 @@ export default {
font-size: 30px; font-size: 30px;
font-weight: 600; font-weight: 600;
margin-bottom: 30px; margin-bottom: 30px;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
color: #1E293B; color: #1E293B;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -7,7 +7,7 @@ export default {
}, },
methods: { methods: {
async getToolsAsyncData() { async getToolsAsyncData() {
const {data: res} = await this.$api.tool.getToolsList({isHot: 1, page: 1, limit: 6}); const {data: res} = await this.$api.tool.getToolsList({isRecommend: 1, page: 1, limit: 6});
const {code, data} = res; const {code, data} = res;
if (code === 0 && data.list) { if (code === 0 && data.list) {
this.pop_tools = data.list; this.pop_tools = data.list;
@ -15,9 +15,9 @@ export default {
}, },
// 跳转工具详情页 // 跳转工具详情页
goToToolDetail(item) { goToToolDetail(item) {
if (item.slug && item.categorySlug) { if (item.slug && item.categoryName && item.categorySlug) {
this.recordToolClick(item); this.recordToolClick(item);
this.$router.push(`/detail?tool_slug=${item.slug}&category_slug=${item.categorySlug}`); this.$router.push(`/detail?tool_slug=${item.slug}&category_slug=${item.categorySlug}&category_name=${item.categoryName}`);
} }
}, },
// 记录工具点击次数 // 记录工具点击次数
@ -36,7 +36,7 @@ export default {
<template> <template>
<div> <div>
<div class="clearfix"> <div class="clearfix">
<img src="/logo/hot.png" :style="{marginRight: '6px'}" alt=""/> <img src="/logo/hot.png" class="mr-6" alt=""/>
Popular Tools Popular Tools
</div> </div>
<div class="line" /> <div class="line" />
@ -54,13 +54,16 @@ export default {
</template> </template>
<style scoped lang="scss"> <style scoped lang="scss">
.mr-6 {
margin-right: 6px;
}
.clearfix { .clearfix {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 20px; margin-bottom: 20px;
font-size: $larg-font-size; font-size: $larg-font-size;
font-weight: bold; font-weight: bold;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
} }
.img-box { .img-box {
@ -103,7 +106,7 @@ export default {
} }
.tool-name { .tool-name {
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
color: #64748B; color: #64748B;
text-align: center; text-align: center;
white-space: nowrap; white-space: nowrap;

View File

@ -0,0 +1,189 @@
<template>
<div class="tool-card" :class="{ 'checkedBg': item.active, 'hovered': isHover && !item.active }"
@click="handleClick"
@mouseenter="handleMouseEnter"
@mouseleave="handleMouseLeave">
<div class="content">
<div class="icon-container">
<img :src="iconBase64 || item.icon || ''" alt="" class="icon-base" />
<img :src="iconSelectedBase64 || item.iconSelected || ''" alt="" class="icon-selected" />
</div>
<span class="text">{{ item.name || '' }}</span>
</div>
</div>
</template>
<script>
export default {
props: {
item: {
type: Object,
required: true
}
},
data() {
return {
iconBase64: '',
iconSelectedBase64: '',
isHover: false
}
},
created() {
// 组件创建时预加载图标
this.preloadIcons();
},
methods: {
// 处理点击事件
handleClick() {
if (this.item.active) {
this.$set(this.item, 'active', false);
} else {
// 先重置所有项,再激活当前项
// 这里我们需要通知父组件来完成全局重置
this.$emit('tool-selected', this.item);
}
},
// 处理鼠标进入事件
handleMouseEnter() {
// 当项已激活时不触发hover效果
if (this.item.active) {
return;
}
this.isHover = true;
},
// 处理鼠标离开事件
handleMouseLeave() {
// 当项已激活时不触发hover效果
if (this.item.active) {
return;
}
this.isHover = false;
},
// 预加载图标并转换为base64
async preloadIcons() {
// 预加载普通图标
if (this.item.icon && this.item.icon !== '') {
try {
this.iconBase64 = await this.convertToBase64(this.item.icon);
} catch (error) {
// console.warn('Failed to load icon:', this.item.icon, error);
}
}
// 预加载选中图标
if (this.item.iconSelected && this.item.iconSelected !== '') {
try {
this.iconSelectedBase64 = await this.convertToBase64(this.item.iconSelected);
} catch (error) {
// console.warn('Failed to load iconSelected:', this.item.iconSelected, error);
}
}
},
// 将图片URL转换为base64
convertToBase64(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'Anonymous'; // 处理跨域问题
img.onload = () => {
try {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = img.width;
canvas.height = img.height;
ctx.drawImage(img, 0, 0);
const dataURL = canvas.toDataURL('image/png');
resolve(dataURL);
} catch (error) {
reject(error);
}
};
img.onerror = (error) => {
reject(error);
};
img.src = url;
});
}
}
}
</script>
<style lang="scss" scoped>
.tool-card {
background: #FFFFFF;
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.05);
border-radius: 12px;
padding: 10px 16px;
font-weight: 600;
font-family: 'Poppins-SemiBold';
cursor: pointer;
width: fit-content;
.content {
@include display-flex;
white-space: nowrap;
max-width: 220px;
.icon-container {
position: relative;
width: 24px;
height: 24px;
margin-right: 5px;
}
.icon-base,
.icon-selected {
position: absolute;
top: 0;
left: 0;
width: 24px;
height: 24px;
transition: opacity 0.2s ease;
}
// 基础状态:显示普通图标,隐藏选中图标
.icon-base {
opacity: 1;
}
.icon-selected {
opacity: 0;
}
.text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
// 激活状态样式
.checkedBg {
color: $white;
background: linear-gradient(90deg, $linear-gradient-start 22%, $linear-gradient-end 73%);
.icon-container {
.icon-base {
opacity: 0 !important;
}
.icon-selected {
opacity: 1 !important;
}
}
}
// 悬停状态样式(非激活时)
.hovered {
color: $white;
background: linear-gradient(90deg, $linear-gradient-start 22%, $linear-gradient-end 73%);
// 悬停状态(非激活时):显示选中图标,隐藏普通图标
.icon-container {
.icon-base {
opacity: 0 !important;
}
.icon-selected {
opacity: 1 !important;
}
}
}
</style>

View File

@ -15,9 +15,9 @@ export default {
}, },
methods: { methods: {
goToToolDetail() { goToToolDetail() {
if (this.config.slug && this.categorySlug) { if (this.config.slug && this.config.categoryName && this.categorySlug) {
this.recordToolClick(this.config); this.recordToolClick(this.config);
this.$router.push(`/detail?tool_slug=${this.config.slug}&category_slug=${this.categorySlug}`); this.$router.push(`/detail?tool_slug=${this.config.slug}&category_slug=${this.categorySlug}&category_name=${this.config.categoryName}`);
} }
}, },
// 记录点击次数 // 记录点击次数
@ -109,7 +109,7 @@ export default {
color: $main-font-color; color: $main-font-color;
font-size: $big-font-size; font-size: $big-font-size;
font-weight: 600; font-weight: 600;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
flex: 1; flex: 1;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
@ -119,7 +119,7 @@ export default {
.text { .text {
color: $grey-color; color: $grey-color;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
margin-top: 4px; margin-top: 4px;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;

View File

@ -1,23 +1,17 @@
<template> <template>
<div class="list"> <div class="list">
<div v-for="item in list" class="tools" @click="checkTool(item)"> <div v-for="(item, index) in list" class="tools">
<span class="tool-card" :class="item.active?'checkedBg':''"> <ToolItem :item="item" @tool-selected="handleToolSelected" />
<span class="content">
<img :src="item.icon || ''" alt="" />
<span>{{ item.name || '' }}</span>
</span>
</span>
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import ToolItem from './ToolItem.vue'
export default { export default {
props: ['list'], props: ['list'],
data() { components: {
return { ToolItem
ischeck: 'check',
}
}, },
created() { created() {
this.list.forEach(item => { this.list.forEach(item => {
@ -25,15 +19,16 @@
}) })
}, },
methods: { methods: {
checkTool(item) { handleToolSelected(selectedItem) {
if (item.active) { // 重置所有项
this.list.forEach(item => {
if (item !== selectedItem) {
this.$set(item, 'active', false); this.$set(item, 'active', false);
} else {
// 否则,先重置所有项,再激活当前项
this.list.forEach(i => this.$set(i, 'active', false));
this.$set(item, 'active', true);
this.$emit('tool-selected', item.name);
} }
});
// 激活当前选中项
this.$set(selectedItem, 'active', true);
this.$emit('tool-selected', selectedItem.name);
} }
} }
} }
@ -46,38 +41,7 @@
gap: 20px; // 网格间距 gap: 20px; // 网格间距
.tools { .tools {
max-width: 300px;
}
.tool-card {
background: #FFFFFF;
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.05);
border-radius: 12px;
padding: 10px 16px;
display: inline-block; display: inline-block;
font-weight: 600;
font-family: 'Poppins-SemiBold', serif;
cursor: pointer;
&:hover {
color: $white;
background: linear-gradient(90deg, $linear-gradient-start 22%, $linear-gradient-end 73%);
}
.content {
@include display-flex;
img {
margin-right: 5px;
width: 24px;
height: 24px;
}
}
}
.tools .checkedBg {
color: $white;
background: linear-gradient(90deg, $linear-gradient-start 22%, $linear-gradient-end 73%);
} }
} }
</style> </style>

View File

@ -3,7 +3,7 @@
<div class="bar-list"> <div class="bar-list">
<div class="top-box"> <div class="top-box">
<div class="title-wrap"> <div class="title-wrap">
<img :src="category_icon || ''" alt="" /> <img :src="tool.blueIcon || ''" alt="" />
<span class="title-text gradient-color"> <span class="title-text gradient-color">
{{tool.categoryName}} {{tool.categoryName}}
</span> </span>
@ -15,14 +15,24 @@
<div v-if="tool.tagList && tool.tagList.length"> <div v-if="tool.tagList && tool.tagList.length">
<ScrollList> <ScrollList>
<div class="tags"> <div class="tags">
<div class="tag-item" v-for="(item,index) in tool.tagList" :key="index"> <div class="tag-item" @click="handleTagClick(item)" :class="{active: item.categorySlug === activeCategorySlug}" v-for="(item,index) in tool.tagList" :key="index">
{{ item }} {{ item.categoryName }}
</div> </div>
</div> </div>
</ScrollList> </ScrollList>
<div class="line"></div> <div class="line"></div>
<div v-if="activeSubCategories.length">
<div class="more pointer" @click="tagGoToViewMore">
View more<i class="el-icon-arrow-right"></i>
</div> </div>
<div> <div class="item-card">
<div v-for="(item, index) in activeSubCategories" :key="index" class="item">
<ToolItemCard :config="item" :categorySlug="item.categorySlug || ''" />
</div>
</div>
</div>
</div>
<div v-else>
<div @click="goToViewMore" class="more pointer" v-if="tool.tagList && tool.tagList.length"> <div @click="goToViewMore" class="more pointer" v-if="tool.tagList && tool.tagList.length">
View more<i class="el-icon-arrow-right"></i> View more<i class="el-icon-arrow-right"></i>
</div> </div>
@ -57,23 +67,49 @@
type: String, type: String,
default: '', default: '',
}, },
category_icon: {
type: String,
default: '',
}
}, },
data() { data() {
return { return {
activeCategorySlug: '',
activeCategoryName: '',
activeSubCategories: [],
} }
}, },
methods: { methods: {
// 查看更多 // 查看更多
goToViewMore() { goToViewMore() {
if (this.categorySlug) { if (this.categorySlug && this.tool.categoryName) {
this.$router.push('/home/more?category_slug=' + this.categorySlug) this.$router.push('/home/more?category_slug=' + this.categorySlug + '&tag_name=' + this.tool.categoryName);
} }
},
handleTagClick(item) {
this.activeCategorySlug = item.categorySlug;
// 设置二级分类列表
this.activeSubCategories = item.tools || [];
this.activeCategoryName = item.categoryName;
},
tagGoToViewMore() {
if (this.activeCategorySlug && this.activeCategoryName) {
this.$router.push('/home/more?category_slug=' + this.activeCategorySlug + '&tag_name=' + this.activeCategoryName);
} }
},
},
watch: {
tool: {
handler(newTool) {
// 检查 tool 中 tagList 是否存在且为数组长度不为0
if (newTool &&
newTool.tagList &&
Array.isArray(newTool.tagList) &&
newTool.tagList.length > 0) {
this.activeCategorySlug = newTool.tagList[0].categorySlug || '';
this.activeCategoryName = newTool.tagList[0].categoryName || '';
this.activeSubCategories = newTool.tagList[0].tools || [];
} }
},
immediate: true // 立即执行,确保组件初始化时也会执行
}
},
} }
</script> </script>
@ -100,7 +136,7 @@
.title-text { .title-text {
font-weight: 600; font-weight: 600;
font-size: $larg-font-size; font-size: $larg-font-size;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
} }
} }
@ -120,10 +156,16 @@
padding: 10px; padding: 10px;
@include gradient-border($linear-gradient-start, $linear-gradient-end); @include gradient-border($linear-gradient-start, $linear-gradient-end);
/* 显示抓取手势 */ /* 显示抓取手势 */
font-family: 'Poppins-SemiBold', sans-serif; font-family: 'Poppins-SemiBold';
color: #64748B; color: #64748B;
font-weight: 600; font-weight: 600;
cursor: pointer;
&.active {
color: #fff;
background: $header-backgroungd;
border: none;
}
} }
.more { .more {
@ -131,7 +173,7 @@
text-align: right; text-align: right;
color: $grey-color; color: $grey-color;
font-size: $mid-font-size; font-size: $mid-font-size;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
&:active { &:active {
opacity: 0.8; opacity: 0.8;

View File

@ -11,19 +11,21 @@
<div class="third-text"> <div class="third-text">
It includes over a <a href="/" class="special">thousand global AI tools</a>, covering writing, images, It includes over a <a href="/" class="special">thousand global AI tools</a>, covering writing, images,
videos, audio, programming, videos, audio, programming,
music, design, chatting, etc., and recommends learning platforms, frameworks and models music, design, chatting, etc, and recommends learning platforms, frameworks and models
</div> </div>
<!-- 修改输入框容器 --> <!-- 修改输入框容器 -->
<div style="margin: 68px auto 62px"> <div class="margin-62">
<SearchSelectInput /> <SearchSelectInput />
</div> </div>
</div> </div>
<div class="card flex"> <div class="card flex">
<div class="left-card card-box"> <div class="left-card card-box">
<el-carousel :autoplay="false" height="354px" autoplay :interval="8000"> <el-carousel :autoplay="false" autoplay :interval="8000">
<el-carousel-item v-for="(item, i) in banner" :key="i"> <el-carousel-item v-for="(item, i) in banner" :key="i">
<img :src="item.imageUrl || ''" alt="" style="height: 354px; width: 100%; border-radius: 12px" /> <div class="img-cover">
<img :src="item.imageUrl || ''" alt="" class="img" />
</div>
</el-carousel-item> </el-carousel-item>
</el-carousel> </el-carousel>
</div> </div>
@ -60,13 +62,31 @@ export default {
}, },
methods: { methods: {
}, },
watch: {
'$route'() {
// 当路由变化时滚动到顶部
window.scrollTo(0, 0);
}
},
mounted() { mounted() {
this.$store.dispatch('getBannerConfig'); this.$store.dispatch('getBannerConfig');
} }
} }
</script> </script>
<style lang="scss" scoped> #home-page { <style lang="scss" scoped>
.img-cover {
height: 100%; width: 100%; padding: 24px 20px 30px;
}
.img {
height: 100%;
width: 100%;
border-radius: 12px;
}
.margin-62 {
margin: 40px auto 40px;
}
#home-page {
flex: 1; flex: 1;
overflow-y: auto; // 必须启用滚动 overflow-y: auto; // 必须启用滚动
position: relative; // 添加相对定位 position: relative; // 添加相对定位
@ -89,26 +109,23 @@ export default {
.first-text { .first-text {
line-height: 90px; line-height: 90px;
font-size: $huge-font-size3; font-size: $huge-font-size3;
font-weight: 900; font-family: 'Poppins-ExtraBold';
font-family: 'Poppins-Bold', serif;
@include text-gradient(90deg, #2563eb, 22%, #7B61FF, 73%); @include text-gradient(90deg, #2563eb, 22%, #7B61FF, 73%);
} }
.second-text { .second-text {
margin: 18px 0; margin: 18px 0;
font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
font-size: $huge-font-size2; font-size: $huge-font-size2;
font-weight: 900;
line-height: 75px; line-height: 75px;
} }
.third-text { .third-text {
width: 716px; width: 716px;
font-weight: 500;
color: $grey-color; color: $grey-color;
font-size: $normal-font-size; font-size: $normal-font-size;
margin-top: 8px; margin-top: 8px;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
.special { .special {
color: $main-color; color: $main-color;
@ -134,6 +151,9 @@ export default {
overflow: hidden; overflow: hidden;
box-sizing: border-box; box-sizing: border-box;
::v-deep .el-carousel { ::v-deep .el-carousel {
.el-carousel__container {
height: 354px;
}
.el-carousel__arrow { .el-carousel__arrow {
opacity: 0 !important; opacity: 0 !important;
transition: none !important; transition: none !important;

View File

@ -5,7 +5,7 @@
</div> </div>
<div class="line"> <div class="line">
</div> </div>
<div class="toolbar" v-for="tool in toolsGroup"> <div class="toolbar" v-for="tool in processedToolsGroup" :key="tool.categoryId">
<Toolbar :tool="tool" :id="`tool-${tool.categoryName}`" :category-slug="tool.categorySlug || ''"></Toolbar> <Toolbar :tool="tool" :id="`tool-${tool.categoryName}`" :category-slug="tool.categorySlug || ''"></Toolbar>
</div> </div>
</div> </div>
@ -22,6 +22,14 @@ export default {
fullscreenLoading: false, fullscreenLoading: false,
categoryList: [], categoryList: [],
toolsGroup: [], toolsGroup: [],
processedToolsGroup: [] // 处理后的工具列表
}
},
computed: {
// 计算属性,用于获取处理后的工具列表
getProcessedTools() {
this.processData();
return this.processedToolsGroup;
} }
}, },
methods: { methods: {
@ -48,6 +56,8 @@ export default {
const {code, data} = res; const {code, data} = res;
if (code === 0 && data.list) { if (code === 0 && data.list) {
this.toolsGroup = data.list; this.toolsGroup = data.list;
// 数据获取完成后处理数据
this.processData();
} }
}, },
async onLoad() { async onLoad() {
@ -55,6 +65,38 @@ export default {
await this.getCategoryAsyncData(); await this.getCategoryAsyncData();
await this.getToolsGroupAsyncData(); await this.getToolsGroupAsyncData();
this.fullscreenLoading = false; this.fullscreenLoading = false;
},
// 处理数据的核心逻辑
processData() {
// 确保工具列表已加载
if (!this.toolsGroup.length) {
return;
}
// 1. 分离一级分类parentId === 0和二级分类parentId !== 0
const mainTools = this.toolsGroup.filter(tool => tool.parentId === 0);
const subTools = this.toolsGroup.filter(tool => tool.parentId !== 0);
// 2. 创建一个映射,方便快速查找一级分类
const mainToolMap = {};
mainTools.forEach(tool => {
// 去除tools属性添加tagList属性
mainToolMap[tool.categoryId] = {
...tool,
tagList: [] // 初始化空的tagList
};
});
// 3. 将二级分类添加到对应的一级分类的tagList中
subTools.forEach(subTool => {
const parentTool = mainToolMap[subTool.parentId];
if (parentTool) {
parentTool.tagList.push(subTool);
}
});
// 4. 获取处理后的工具列表(值的数组)
this.processedToolsGroup = Object.values(mainToolMap);
} }
}, },
created() { created() {

View File

@ -47,6 +47,7 @@ export default {
const slug = to.query.category_slug; const slug = to.query.category_slug;
if (slug) { if (slug) {
this.category_slug = slug; this.category_slug = slug;
this.tagName = to.query.tag_name;
this.getToolsByTag(slug); this.getToolsByTag(slug);
} }
} }
@ -54,9 +55,11 @@ export default {
mounted() { mounted() {
// 组件挂载时也检查一次路由参数 // 组件挂载时也检查一次路由参数
const slug = this.$route.query.category_slug; const slug = this.$route.query.category_slug;
if (slug) { const tagName = this.$route.query.tag_name;
if (slug && tagName) {
this.category_slug = slug; this.category_slug = slug;
this.getToolsByTag(slug); this.getToolsByTag(slug);
this.tagName = tagName;
} }
}, },
} }
@ -64,13 +67,13 @@ export default {
<style scoped lang="scss"> <style scoped lang="scss">
.content { .content {
padding-top: 60px; padding-top: 50px;
padding-bottom: 100px; padding-bottom: 100px;
.tag-item { .tag-item {
display: inline-block; display: inline-block;
padding: 10px; padding: 10px;
border-radius: 12px; border-radius: 12px;
font-family: 'Poppins-SemiBold', sans-serif; font-family: 'Poppins-SemiBold';
color: #fff; color: #fff;
font-weight: 600; font-weight: 600;
background: $header-backgroungd; background: $header-backgroungd;

View File

@ -1,12 +1,12 @@
<template> <template>
<div @click="goToFinanceDetail"> <div>
<div class="finance-item flex"> <div class="finance-item flex">
<div class="dot-container"> <div class="dot-container">
<div class="dot"></div> <div class="dot"></div>
</div> </div>
<div> <div>
<div class="title">2025-2-01</div> <div class="title">{{ item.date || '' }}</div>
<div class="content-text">Koah参与$ 500万美元种子轮融资领投Forerunner 参与投资South Park Commons </div> <div class="content-text">{{ item.info || '' }}</div>
</div> </div>
</div> </div>
<div class="diver-line"></div> <div class="diver-line"></div>
@ -27,13 +27,6 @@ export default {
return { return {
} }
}, },
methods: {
goToFinanceDetail() {
this.$router.push({
path: '/finance-detail',
})
}
}
} }
</script> </script>
@ -60,13 +53,18 @@ export default {
} }
.title { .title {
color: #aebadee6; color: #aebadee6;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
margin-bottom: 12px; margin-bottom: 12px;
} }
.content-text { .content-text {
color: #64748B; color: #64748B;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
font-size: 20px; font-size: 20px;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
text-overflow: ellipsis;
} }
} }
</style> </style>

View File

@ -1,9 +1,9 @@
<template> <template>
<div class="item flex items-center"> <div class="item flex items-center" @click="gotoLink(item.value)" @mouseenter="isHovered = true" @mouseleave="isHovered = false">
<div class="icon flex items-center justify-center"> <div class="icon flex items-center justify-center">
<img :src="item.iconUrl || ''" alt="" /> <img :src="getImageSrc()" alt="" />
</div> </div>
<div class="text">{{ item.name || '' }}</div> <div class="text">{{ item.key || '' }}</div>
</div> </div>
</template> </template>
@ -19,12 +19,30 @@ export default {
}, },
data() { data() {
return { return {
isHovered: false
} }
}, },
methods: {} methods: {
// 跳转链接
gotoLink(url) {
if (!url) {
return false;
}
window.open(url, '_blank');
},
// 获取图标路径
getImageSrc() {
if (this.isHovered && this.item.key) {
return `/launches/link/icon_${this.item.key}_selected.png`;
}
return this.item.key ? `/launches/link/icon_${this.item.key}.png` : '';
}
}
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.item { .item {
border-radius: 12px; border-radius: 12px;
@ -33,11 +51,13 @@ export default {
margin-bottom: 6px; margin-bottom: 6px;
color: #64748B; color: #64748B;
font-size: 18px; font-size: 18px;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
cursor: pointer;
.icon { .icon {
width: 24px; width: 24px;
height: 24px; height: 24px;
border-radius: 4px; border-radius: 4px;
padding: 5px;
box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.08); box-shadow: 0 4px 6px 0 rgba(0, 0, 0, 0.08);
img { img {
width: 14px; width: 14px;

View File

@ -1,16 +1,16 @@
<template> <template>
<div class="item"> <div class="item" @click="goToDetail">
<div class="flex items-center"> <div class="flex items-center">
<img class="icon" :src="item.iconUrl || ''" alt="" /> <img class="icon" :src="item.coverImage || ''" alt="" />
<div class="flex-1 flex flex-col justify-between"> <div class="flex-1 flex flex-col justify-between">
<div class="title">{{ item.title || '' }}</div> <div class="title">{{ item.title || '' }}</div>
<div class="flex items-center" style="gap: 30px"> <div class="flex items-center gap-30">
<div class="flex items-center data" style="gap: 12px"> <div class="flex items-center data gap-12">
<img alt="" src="/launches/detail/icon_thumb.png" style="width: 24px; height: 24px;" /> <img alt="" src="/launches/detail/icon_thumb.png" class="wh-24" />
<div>{{ item.likeCount || 0 }}</div> <div>{{ item.likeCount || 0 }}</div>
</div> </div>
<div class="flex items-center data" style="gap: 12px"> <div class="flex items-center data gap-12">
<img alt="" src="/launches/detail/icon_star.png" style="width: 24px; height: 24px;" /> <img alt="" src="/launches/detail/icon_star.png" class="wh-24" />
<div>{{ item.rating || 0 }}</div> <div>{{ item.rating || 0 }}</div>
</div> </div>
</div> </div>
@ -32,12 +32,30 @@ export default {
return { return {
} }
}, },
methods: {}, methods: {
goToDetail() {
if (this.item && this.item.slug) {
this.$router.push(`/launches/detail?news_slug=${this.item.slug}`);
}
}
},
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.gap-30 {
gap: 30px;
}
.item { .item {
cursor: pointer;
transition: all 0.3s ease;
max-width: 330px;
overflow: hidden;
&:hover {
opacity: 0.8;
}
.icon { .icon {
width: 70px; width: 70px;
height: 70px; height: 70px;
@ -45,19 +63,26 @@ export default {
margin-right: 20px; margin-right: 20px;
} }
.title { .title {
font-family: 'Poppins-SemiBold', serif; flex: 1;
font-family: 'Poppins-SemiBold';
font-size: 24px; font-size: 24px;
color: #1E293B; color: #1E293B;
font-weight: 600; font-weight: 600;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.data { .data {
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #64748B; color: #64748B;
} }
.summary { .summary {
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #64748B; color: #64748B;
margin-top: 12px; margin-top: 12px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
} }
</style> </style>

View File

@ -2,11 +2,19 @@
<div id="normal-container"> <div id="normal-container">
<IntegratedLayout> <IntegratedLayout>
<div class="content"> <div class="content">
<div class="title-text">{{ news_detail.title || '' }}</div> <div class="bread-menu">
<div class="rate-box"> <span>AI Launches</span>
<Rate v-model="news_detail.rating" readonly /> <i class="el-icon-arrow-right"></i>
<div class="flex" style="gap: 20px"> <span class="crumbs gradient-color">{{newsDetail.title || ''}}</span>
<CommentBtn :comment-count="commentCount" /> </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> </div>
<div class="terms-item"> <div class="terms-item">
@ -15,7 +23,7 @@
<span>Introduction: </span> <span>Introduction: </span>
</div> </div>
<div class="item-content"> <div class="item-content">
{{ news_detail.summary || '' }} {{ newsDetail.summary || '' }}
</div> </div>
</div> </div>
<div class="terms-item"> <div class="terms-item">
@ -23,65 +31,54 @@
<img src="/ToolDetail/icon_clock.png" alt=""> <img src="/ToolDetail/icon_clock.png" alt="">
<span>Data update: </span> <span>Data update: </span>
</div> </div>
<div class="item-content">{{ news_detail.publishTime || '' }}</div> <div class="item-content">{{ formatPublishTime(newsDetail.publishTime || '') }}</div>
</div>
<div class="terms-item">
<div class="item-title">
<img src="/ToolDetail/like_icon.png" alt="">
<span>Like: </span>
</div>
<div class="item-content">{{ news_detail.likeCount || 0 }}</div>
</div> </div>
<div class="tags"> <div class="tags">
<div class="tag-item" v-for="(it, index) in tagList" :key="index">{{ it }}</div> <div class="tag-item" v-for="(it, index) in tagList" :key="index">{{ it }}</div>
</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="diver"></div>
<div class="container flex"> <div class="container flex">
<div class="left-content flex-1 flex flex-col"> <div class="left-content flex-1 flex flex-col">
<!--<div class="sketch flex justify-between">-->
<!-- <div class="flex items-center" style="gap: 8px">-->
<!-- <img style="width: 18px; height: 18px" src="/launches/detail/icon_fly.png" alt="" />-->
<!-- <div class="text">This is the <span style="color: #7B61FF">6</span> release of Sketch</div>-->
<!-- </div>-->
<!-- <div class="flex items-center more">-->
<!-- <div>View more</div>-->
<!-- <img src="/launches/detail/icon_arrow.png" alt="" />-->
<!-- </div>-->
<!--</div>-->
<div class="card flex-1"> <div class="card flex-1">
<div class="article-content"> <div class="article-content">
<div v-html="news_detail.content || ''"></div> <div v-html="newsDetail.content || ''"></div>
</div> </div>
</div> </div>
</div> </div>
<div class="right-content"> <div class="right-content">
<div class="card"> <div class="card">
<div class="flex items-center" style="margin-top: 20px"> <div class="flex items-center mt-20">
<img alt="" src="/launches/detail/icon_hourse.png" class="icon" /> <img alt="" src="/launches/detail/icon_hourse.png" class="icon" />
<div class="content-title">Company Information</div> <div class="content-title">Company Information</div>
</div> </div>
<div class="diver-line"></div> <div class="diver-line"></div>
<ProjectItem :item="{name: 'Sketch.com', iconUrl: '/launches/web/sketch.png'}" /> <div v-if="newsDetail.extra && Array.isArray(newsDetail.extra.socialLinks)" class="mt-30">
<ProjectItem :item="{name: 'Github.com', iconUrl: '/launches/web/github.png'}" /> <ProjectItem v-for="(item, index) in newsDetail.extra.socialLinks" :key="index" :item="item" />
<ProjectItem :item="{name: 'Facebook.com', iconUrl: '/launches/web/facebook.png'}" />
<ProjectItem :item="{name: 'Instagram.com', iconUrl: '/launches/web/instagram.png'}" />
<ProjectItem :item="{name: 'Twitter.com', iconUrl: '/launches/web/twitter.png'}" />
<div class="more flex items-center justify-between" style="margin-top: 20px">
<div>A total of 8 projects were released</div>
<img src="/launches/detail/icon_arrow.png" alt="" />
</div> </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>
<div class="card" style="margin-top: 20px"> <div class="card mt-20">
<div class="flex items-center" style="margin-top: 20px"> <div class="flex items-center mt-20">
<img alt="" src="/launches/detail/icon_finance.png" class="icon" /> <img alt="" src="/launches/detail/icon_finance.png" class="icon" />
<div class="content-title">Special Financing</div> <div class="content-title">Special Financing</div>
</div> </div>
<div class="diver-line"></div> <div class="diver-line"></div>
<FinanceItem /> <div class="scroll">
<FinanceItem /> <div v-if="newsDetail.extra && Array.isArray(newsDetail.extra.financing)">
<FinanceItem /> <FinanceItem v-for="(item, index) in newsDetail.extra.financing" :key="index" :item="item" :slug="news_slug" />
<div class="flex justify-end" style="margin-top: 30px"> </div>
<div class="more flex items-center justify-between" style="gap: 14px"> </div>
<div class="flex justify-end mt-30">
<div class="more flex items-center justify-between gap-14" @click="goToFinanceDetail">
<div>View more</div> <div>View more</div>
<img src="/launches/detail/icon_arrow.png" alt="" /> <img src="/launches/detail/icon_arrow.png" alt="" />
</div> </div>
@ -97,29 +94,14 @@
</div> </div>
</div> </div>
<div class="card"> <div class="card">
<div class="flex-between-center list"> <div class="list">
<RelatedTool :item="{ <div class="item" v-for="(item, index) in otherList" :key="index">
title: 'Figma', <RelatedTool :item="item" />
summary: 'Easily create highly interactive prototypes',
likeCount: 123,
rating: 4.5
}" />
<RelatedTool :item="{
title: 'Figma',
summary: 'Easily create highly interactive prototypes',
likeCount: 123,
rating: 4.5
}" />
<RelatedTool :item="{
title: 'Figma',
summary: 'Easily create highly interactive prototypes',
likeCount: 123,
rating: 4.5
}" />
</div> </div>
</div> </div>
</div> </div>
<Comment comment-type="article" :id="news_detail.id" @update:commentCount="handleCommentCountUpdate" /> </div>
<Comment comment-type="article" :id="newsDetail.id" @update:commentCount="handleCommentCountUpdate" />
</div> </div>
</IntegratedLayout> </IntegratedLayout>
</div> </div>
@ -131,24 +113,58 @@ import Comment from "@/pages/ToolDetail/Comment/index.vue";
import RelatedTool from "@/pages/Launches/Detail/RelatedTool.vue"; import RelatedTool from "@/pages/Launches/Detail/RelatedTool.vue";
import ProjectItem from "@/pages/Launches/Detail/ProjectItem.vue"; import ProjectItem from "@/pages/Launches/Detail/ProjectItem.vue";
import FinanceItem from "@/pages/Launches/Detail/FinanceItem.vue"; import FinanceItem from "@/pages/Launches/Detail/FinanceItem.vue";
import ThumbBtn from "@/pages/ToolDetail/components/ThumbBtn.vue";
export default { export default {
components: {FinanceItem, ProjectItem, RelatedTool, CommentBtn, Comment}, components: {ThumbBtn, FinanceItem, ProjectItem, RelatedTool, CommentBtn, Comment},
data() { data() {
return { return {
news_detail: {}, newsDetail: {},
commentCount: 0, commentCount: 0,
news_slug: '', news_slug: '',
otherList: [],
} }
}, },
methods: { methods: {
// 刷新工具详情数据
refreshToolDetail() {
if (this.news_slug) {
this.getNewsDetail(this.news_slug);
}
},
// 获取新闻详情 // 获取新闻详情
async getNewsDetail(newsSlug) { async getNewsDetail(newsSlug) {
const {data: res} = await this.$api.article.getArticleDetail(newsSlug); const {data: res} = await this.$api.article.getArticleDetail(newsSlug);
const {code, data} = res; const {code, data} = res;
if (code === 0 && data) { if (code === 0 && data) {
this.news_detail = {...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) { stringJsonToObject(str) {
// 将json字符串转为对象捕获错误当str为空或者转对象失败时默认返回空数组 // 将json字符串转为对象捕获错误当str为空或者转对象失败时默认返回空数组
@ -161,63 +177,116 @@ export default {
}, },
goToViewMore() { goToViewMore() {
// 返回上一页 // 返回上一页
this.$router.go(-1); this.$router.push('/launches');
}, },
handleCommentCountUpdate(count) { handleCommentCountUpdate(count) {
this.commentCount = 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: { watch: {
'$route'(to, from) { '$route'(to, from) {
// 当路由参数发生变化时重新加载数据 // 当路由参数发生变化时重新加载数据
if (to.query.news_slug !== from.query.news_slug) { if (to.query.news_slug !== from.query.news_slug) {
this.news_slug = to.query.news_slug; this.onLoad();
if (this.news_slug) {
this.getNewsDetail(this.news_slug);
}
} }
} }
}, },
mounted() { mounted() {
this.news_slug = this.$route.query.news_slug; this.onLoad();
if (this.news_slug) {
this.getNewsDetail(this.news_slug);
}
}, },
computed: { computed: {
tagList() { tagList() {
if (!this.news_detail.tags) { if (!this.newsDetail.tags) {
return []; return [];
} }
return this.stringJsonToObject(this.news_detail.tags || '[]'); return this.stringJsonToObject(this.newsDetail.tags || '[]');
} }
}, },
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.mt-24 {
margin-top: 24px;
}
.color-01 {
color: #7B61FF;
}
.mt-20 {
margin-top: 20px;
}
.mt-30 {
margin-top: 30px;
}
.card { .card {
padding: 20px; padding: 20px;
background-color: #FFFFFF; background-color: #FFFFFF;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.08); box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.08);
} }
.scroll {
max-height: 500px; overflow-y: auto; overflow-x: hidden;
}
.content { .content {
padding-top: 100px; padding-top: 25px;
padding-bottom: 100px; 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 { .diver {
margin-top: 20px; margin-top: 30px;
border-top: 4px solid #E2E8F0; border-top: 4px solid #E2E8F0;
margin-bottom: 20px; margin-bottom: 30px;
} }
.title-text { .title-text {
font-size: 34px; font-size: 34px;
color: #1E293B; color: #1E293B;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-weight: 600; font-weight: 600;
margin-top: 80px;
} }
.rate-box { .rate-box {
@ -244,7 +313,7 @@ export default {
align-items: center; align-items: center;
gap: 8px; gap: 8px;
color: #1E293B; color: #1E293B;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
img { img {
width: 24px; width: 24px;
@ -253,7 +322,7 @@ export default {
} }
.item-content { .item-content {
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #64748B; color: #64748B;
} }
} }
@ -269,7 +338,7 @@ export default {
width: 80%; width: 80%;
.tag-item { .tag-item {
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
flex-shrink: 0; flex-shrink: 0;
padding: 4px 12px; padding: 4px 12px;
border-radius: 12px; border-radius: 12px;
@ -290,7 +359,7 @@ export default {
.content-title { .content-title {
margin-left: 6px; margin-left: 6px;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-size: 24px; font-size: 24px;
color: #1E293B; color: #1E293B;
font-weight: 600; font-weight: 600;
@ -306,9 +375,13 @@ export default {
border-radius: 6px; border-radius: 6px;
padding: 8px 15px; padding: 8px 15px;
border: 1px solid #e2e8f0; border: 1px solid #e2e8f0;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #64748B; color: #64748B;
&:active {
opacity: 0.8;
}
img { img {
width: 16px; width: 16px;
height: 16px; height: 16px;
@ -322,7 +395,7 @@ export default {
background-color: #F5F4FF; background-color: #F5F4FF;
border-radius: 12px; border-radius: 12px;
.text { .text {
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #64748B; color: #64748B;
font-size: 18px; font-size: 18px;
} }
@ -332,7 +405,7 @@ export default {
border-radius: 6px; border-radius: 6px;
padding: 8px 15px; padding: 8px 15px;
border: 1px solid #e2e8f0; border: 1px solid #e2e8f0;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #64748B; color: #64748B;
img { img {
@ -344,13 +417,13 @@ export default {
} }
} }
.related { .related {
margin-top: 60px; margin-top: 40px;
.related-title { .related-title {
.title { .title {
margin-bottom: 20px; margin-bottom: 20px;
font-size: 30px; font-size: 30px;
color: #1E293B; color: #1E293B;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-weight: 600; font-weight: 600;
&::before { &::before {
content: ''; content: '';
@ -367,12 +440,19 @@ export default {
text-align: right; text-align: right;
color: $grey-color; color: $grey-color;
font-size: $mid-font-size; font-size: $mid-font-size;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
} }
} }
.list { .list {
gap: 60px; gap: 60px;
margin: 40px 10px 40px 10px; margin: 40px 10px 40px 10px;
display: grid;
grid-template-columns: repeat(3, 1fr);
.item {
width: 100%;
overflow: hidden;
min-height: 110px;
}
} }
} }
} }

View File

@ -2,8 +2,10 @@
<div class="special-finance-item flex"> <div class="special-finance-item flex">
<div class="dot"></div> <div class="dot"></div>
<div class="flex-1 container"> <div class="flex-1 container">
<div class="time">2025-2-1</div> <div class="time">{{ item.date || '' }}</div>
<div class="content">Koah participated in a $5 million seed round, led Forerunner, and participated in The Timeline Of Sketch Commons.</div> <div class="content">
{{ item.info || '' }}
</div>
<div class="tag-list flex flex-wrap"> <div class="tag-list flex flex-wrap">
<div v-for="(it, i) in tag" :key="i" class="tag-item flex items-center"> <div v-for="(it, i) in tag" :key="i" class="tag-item flex items-center">
<img src="/" alt="" /> <img src="/" alt="" />
@ -17,21 +19,15 @@
<script> <script>
export default { export default {
props: {
item: {
type: Object,
default: () => ({})
}
},
data() { data() {
return { return {
tag: [{ tag: [],
name: 'Mask Network',
isLead: true,
}, {
name: 'CatcherVC',
isLead: true,
}, {
name: 'CREDIT SCEND',
isLead: false,
}, {
name: 'Ribbit Capital',
isLead: false,
}],
} }
}, },
methods: {}, methods: {},
@ -42,6 +38,7 @@ export default {
.special-finance-item { .special-finance-item {
border-bottom: 1px solid #E2E8F0; border-bottom: 1px solid #E2E8F0;
gap: 14px; gap: 14px;
margin-bottom: 50px;
.dot { .dot {
width: 8px; width: 8px;
height: 8px; height: 8px;
@ -54,13 +51,13 @@ export default {
.time { .time {
font-size: 16px; font-size: 16px;
color: #7B61FF; color: #7B61FF;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
margin-bottom: 22px; margin-bottom: 22px;
} }
.content { .content {
color: #64748B; color: #64748B;
font-size: 20px; font-size: 20px;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
margin-bottom: 20px; margin-bottom: 20px;
} }
.tag-list { .tag-list {
@ -78,7 +75,7 @@ export default {
span { span {
font-size: 14px; font-size: 14px;
color: #64748B; color: #64748B;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
line-height: 14px; line-height: 14px;
} }
.lead { .lead {

View File

@ -2,11 +2,19 @@
<div id="normal-container"> <div id="normal-container">
<IntegratedLayout> <IntegratedLayout>
<div class="content"> <div class="content">
<div class="title-text">{{ financeDetail.title || '' }}</div> <div class="bread-menu">
<div class="rate-box"> <span>AI Launches</span>
<Rate v-model="financeDetail.rating" readonly /> <i class="el-icon-arrow-right"></i>
<div class="flex" style="gap: 20px"> <span class="crumbs gradient-color">{{newsDetail.title || ''}}</span>
<CommentBtn :comment-count="commentCount" /> </div>
<div class="title-text">{{ newsDetail.title || '' }}</div>
<div class="flex mt-24">
<div>
<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> </div>
<div class="terms-item"> <div class="terms-item">
@ -15,9 +23,7 @@
<span>Introduction: </span> <span>Introduction: </span>
</div> </div>
<div class="item-content"> <div class="item-content">
<div style="padding-right: 70px"> {{ newsDetail.summary || '' }}
{{ financeDetail.summary || '' }}
</div>
</div> </div>
</div> </div>
<div class="terms-item"> <div class="terms-item">
@ -25,22 +31,17 @@
<img src="/ToolDetail/icon_clock.png" alt=""> <img src="/ToolDetail/icon_clock.png" alt="">
<span>Data update: </span> <span>Data update: </span>
</div> </div>
<div class="item-content">{{ financeDetail.publishTime || '' }}</div> <div class="item-content">{{ formatPublishTime(newsDetail.publishTime || '') }}</div>
</div> </div>
<div class="terms-item">
<div class="item-title">
<img src="/ToolDetail/like_icon.png" alt="">
<span>Like: </span>
</div> </div>
<div class="item-content">{{ financeDetail.likeCount || 0 }}</div>
</div> </div>
<div class="tags"> <div class="tags">
<div class="tag-item" v-for="(it, index) in tagList" :key="index">{{ it }}</div> <div class="tag-item" v-for="(it, index) in tagList" :key="index">{{ it }}</div>
</div> </div>
<div class="diver"></div> <div class="diver"></div>
<div class="title">Special Financing</div> <div class="title">Special Financing</div>
<div class="card content-box"> <div class="card content-box" v-if="newsDetail.extra && Array.isArray(newsDetail.extra.financing)">
<SpecialFinanceItem /> <SpecialFinanceItem v-for="(it, i) in newsDetail.extra.financing" :key="i" :item="it" />
</div> </div>
</div> </div>
</IntegratedLayout> </IntegratedLayout>
@ -50,12 +51,15 @@
<script> <script>
import CommentBtn from "@/pages/ToolDetail/components/CommentBtn.vue"; import CommentBtn from "@/pages/ToolDetail/components/CommentBtn.vue";
import SpecialFinanceItem from "@/pages/Launches/FinanceDetail/SpecialFinanceItem.vue"; import SpecialFinanceItem from "@/pages/Launches/FinanceDetail/SpecialFinanceItem.vue";
import ThumbBtn from "@/pages/ToolDetail/components/ThumbBtn.vue";
export default { export default {
components: {CommentBtn, SpecialFinanceItem}, components: {ThumbBtn, CommentBtn, SpecialFinanceItem},
data() { data() {
return { return {
commentCount: 0, commentCount: 0,
news_slug: '',
newsDetail: {},
financeDetail: { financeDetail: {
title: 'The Timeline Of Sketch', title: 'The Timeline Of Sketch',
rating: 1, rating: 1,
@ -63,22 +67,88 @@ export default {
publishTime: 'Sep 03 2025', publishTime: 'Sep 03 2025',
likeCount: 100, likeCount: 100,
}, },
tagList: [
'Sketch',
'Athens',
'Offline Maps',
'AR',
'Navigation',
'Scenic Spots',
'Exploration',
],
} }
}, },
methods: {}, methods: {
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 [];
}
},
// 获取新闻详情
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);
}
}
}
}
},
watch: {
'$route'(to, from) {
// 当路由参数发生变化时重新加载数据
if (to.query.news_slug !== from.query.news_slug) {
this.news_slug = to.query.news_slug;
if (this.news_slug) {
this.getNewsDetail(this.news_slug);
}
}
}
},
mounted() {
this.news_slug = this.$route.query.news_slug;
if (this.news_slug) {
this.getNewsDetail(this.news_slug);
}
},
computed: {
tagList() {
if (!this.newsDetail.tags) {
return [];
}
return this.stringJsonToObject(this.newsDetail.tags || '[]');
}
},
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.mt-24 {
margin-top: 24px;
}
.color-01 {
color: #7B61FF;
}
.card { .card {
padding: 50px 30px; padding: 50px 30px;
background-color: #FFFFFF; background-color: #FFFFFF;
@ -89,7 +159,7 @@ export default {
margin-bottom: 20px; margin-bottom: 20px;
font-size: 30px; font-size: 30px;
color: #1E293B; color: #1E293B;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-weight: 600; font-weight: 600;
&::before { &::before {
content: ''; content: '';
@ -102,9 +172,20 @@ export default {
} }
} }
.content { .content {
padding-top: 180px; padding-top: 25px;
padding-bottom: 100px; 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;
}
}
.content-box { .content-box {
padding: 50px 30px; padding: 50px 30px;
} }
@ -118,9 +199,8 @@ export default {
.title-text { .title-text {
font-size: 34px; font-size: 34px;
color: #1E293B; color: #1E293B;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-weight: 600; font-weight: 600;
margin-top: 80px;
} }
.rate-box { .rate-box {
@ -147,7 +227,7 @@ export default {
align-items: center; align-items: center;
gap: 8px; gap: 8px;
color: #1E293B; color: #1E293B;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
img { img {
width: 24px; width: 24px;
@ -156,7 +236,7 @@ export default {
} }
.item-content { .item-content {
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #64748B; color: #64748B;
} }
} }
@ -172,7 +252,7 @@ export default {
width: 80%; width: 80%;
.tag-item { .tag-item {
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
flex-shrink: 0; flex-shrink: 0;
padding: 4px 12px; padding: 4px 12px;
border-radius: 12px; border-radius: 12px;

View File

@ -5,30 +5,32 @@
<div class="preview-box"> <div class="preview-box">
<img :src="config.coverImage || ''" alt="" /> <img :src="config.coverImage || ''" alt="" />
</div> </div>
<div class="tool-content flex flex-col justify-between"> <div class="tool-content flex flex-1 flex-col justify-between">
<div class="content-top flex items-start"> <div class="content-top flex items-start">
<div class="icon"> <div class="icon">
<img src="/" alt="" /> <img src="/" alt="" />
</div> </div>
<div class="flex-1"> <div class="flex-1">
<div class="title">{{ config.title || '' }}</div> <div class="title">{{ config.title || '' }}</div>
<div class="sub-title" style="padding-right: 30px">{{ config.summary || '' }}</div> <div class="sub-title pr-30">{{ config.summary || '' }}</div>
</div> </div>
</div> </div>
<div class="content-bottom flex items-center"> <div class="content-bottom flex items-center">
<img src="/launches/item/icon_clock.png" alt="" /> <img src="/launches/item/icon_clock.png" alt="" />
<div class="time-style">{{ config.publishTime || '' }}</div> <div class="time-style">{{ formatPublishTime(config.publishTime) }}</div>
</div> </div>
</div> </div>
</div> </div>
<div class="right-content flex"> <div class="right-content flex items-center">
<div class="flex items-center"> <div class="flex items-center btn">
<img :src="hovered ? '/launches/item/icon_thumb_grey.png' : '/launches/item/icon_thumb.png'" alt="" /> <img :src="hovered ? '/launches/item/icon_thumb_grey.png' : '/launches/item/icon_thumb.png'" alt="" />
<div :class="{ 'hover-text': hovered }">{{ config.likeCount || 0 }}</div> <div :class="{ 'hover-text': hovered }">{{ config.likeCount || 0 }}</div>
</div> </div>
<div class="flex items-center"> <div class="flex items-center btn">
<img :src="hovered ? '/launches/item/icon_star_grey.png' : '/launches/item/icon_star.png'" alt="" /> <img :src="hovered ? '/launches/item/icon_star_grey.png' : '/launches/item/icon_star.png'" alt="" />
<div :class="{ 'hover-text': hovered }">{{ config.rating || 0 }}</div> <div :class="{ 'hover-text': hovered }">
{{ (config.rating || 0).toFixed(1) }}
</div>
</div> </div>
</div> </div>
</div> </div>
@ -54,6 +56,21 @@ export default {
}, },
}, },
methods: { methods: {
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} Released in ${year}`;
},
handleMouseEnter() { handleMouseEnter() {
this.hovered = true; this.hovered = true;
}, },
@ -78,7 +95,7 @@ export default {
if (id) { if (id) {
await this.$api.article.recordArticleClick(id); await this.$api.article.recordArticleClick(id);
} }
} },
}, },
mounted() { mounted() {
const itemContent = this.$el; const itemContent = this.$el;
@ -94,19 +111,27 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.pr-30 {
padding-right: 30px;
}
.item-content { .item-content {
border-radius: 12px; border-radius: 12px;
padding: 30px 20px; padding: 25px 20px;
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
cursor: pointer;
&:hover { &:hover {
background-color: #F5F4FF; background-color: #F5F4FF;
.order-num { .order-num {
background: #fff; background: #fff !important;
} }
} }
&:active {
opacity: 0.8;
}
.left-content { .left-content {
gap: 30px; gap: 30px;
.order-num { .order-num {
@ -118,12 +143,13 @@ export default {
text-align: center; text-align: center;
line-height: 36px; line-height: 36px;
font-size: 18px; font-size: 18px;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
transition: background-color 0.3s ease; transition: background-color 0.3s ease;
color: #506179;
} }
.preview-box { .preview-box {
width: 228px; width: 228px;
height: 133px; height: 110px;
border-radius: 6px; border-radius: 6px;
background-color: #FFFFFF; background-color: #FFFFFF;
img { img {
@ -145,15 +171,19 @@ export default {
} }
.title { .title {
font-size: 20px; font-size: 20px;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-weight: 600; font-weight: 600;
line-height: 24px; line-height: 24px;
color: #3A4A65; color: #3A4A65;
} }
.sub-title { .sub-title {
color: #64748B; color: #64748B;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
margin-top: 10px; margin-top: 10px;
max-width: 420px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
} }
.content-bottom { .content-bottom {
@ -164,7 +194,7 @@ export default {
} }
.time-style { .time-style {
color: #64748B; color: #64748B;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
} }
} }
} }
@ -172,9 +202,15 @@ export default {
.right-content { .right-content {
padding-right: 23px; padding-right: 23px;
gap: 33px; gap: 33px;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #E2E8F0; color: #E2E8F0;
height: 24px; max-width: 192px;
.btn {
cursor: pointer;
&:active {
opacity: 0.8;
}
}
img { img {
width: 24px; width: 24px;
height: 24px; height: 24px;

View File

@ -6,7 +6,7 @@
<div v-if="mode === 'Daily'" class="daily-picker"> <div v-if="mode === 'Daily'" class="daily-picker">
<HorizontalDateList> <HorizontalDateList>
<button <button
style="width: 36px" class="bt-d"
v-for="day in dailyDays" v-for="day in dailyDays"
:key="day.dateStr" :key="day.dateStr"
:class="{ 'selected': day.dateStr === selectedDate }" :class="{ 'selected': day.dateStr === selectedDate }"
@ -19,7 +19,7 @@
<div v-else-if="mode === 'Weekly'" class="weekly-picker"> <div v-else-if="mode === 'Weekly'" class="weekly-picker">
<HorizontalDateList> <HorizontalDateList>
<button <button
style="margin: 0 30px; width: 105px" class="bt-w"
v-for="week in weeklyRanges" v-for="week in weeklyRanges"
:key="week.start" :key="week.start"
:class="{ 'selected': week.start === selectedDate[0] && week.end === selectedDate[1] }" :class="{ 'selected': week.start === selectedDate[0] && week.end === selectedDate[1] }"
@ -32,7 +32,7 @@
<div v-else class="monthly-picker"> <div v-else class="monthly-picker">
<HorizontalDateList> <HorizontalDateList>
<button <button
style="margin: 0 9px; width: 55px" class="bt-m"
v-for="(monthAbbr, index) in monthAbbrs" v-for="(monthAbbr, index) in monthAbbrs"
:key="index" :key="index"
:class="{ 'selected': (index + 1) === selectedMonth }" :class="{ 'selected': (index + 1) === selectedMonth }"
@ -47,6 +47,17 @@
<script> <script>
import HorizontalDateList from '@/components/HorizontalDateList.vue'; import HorizontalDateList from '@/components/HorizontalDateList.vue';
// 从 sessionStorage 获取缓存数据的辅助函数
function getCachedData() {
try {
const cachedData = sessionStorage.getItem('launches_search_cache');
return cachedData ? JSON.parse(cachedData) : null;
} catch (e) {
// console.error('获取缓存数据失败', e);
return null;
}
}
export default { export default {
name: 'OptionDates', name: 'OptionDates',
components: { components: {
@ -72,9 +83,34 @@ export default {
} }
}, },
data() { data() {
const cachedData = getCachedData();
let initialSelectedDate = this.mode === 'Daily' ? '' : ['', ''];
let initialSelectedMonth = this.month;
// 改进:优先从缓存中获取月份
if (cachedData && cachedData.currentMonth) {
initialSelectedMonth = cachedData.currentMonth;
}
// 如果有缓存数据,优先使用缓存数据
if (cachedData && cachedData.lastSelectedValue) {
try {
const lastSelectedValue = JSON.parse(cachedData.lastSelectedValue);
if (lastSelectedValue) {
if (this.mode === 'Daily' && typeof lastSelectedValue === 'string') {
initialSelectedDate = lastSelectedValue;
} else if (this.mode !== 'Daily' && Array.isArray(lastSelectedValue)) {
initialSelectedDate = lastSelectedValue;
}
}
} catch (e) {
console.error('解析缓存的lastSelectedValue失败', e);
}
}
return { return {
selectedDate: this.mode === 'Daily' ? '' : ['', ''], // 选中的日期/日期范围 selectedDate: initialSelectedDate,
selectedMonth: this.month // 月模式下选中的月份 selectedMonth: initialSelectedMonth,
}; };
}, },
computed: { computed: {
@ -102,10 +138,60 @@ export default {
} }
}, },
created() { created() {
// 组件创建时自动选中当前日期或周 // 先从props获取月份避免初始为null
this.autoSelectCurrentDate(); this.selectedMonth = this.month;
// 立即尝试获取缓存数据
this.updateFromCache();
// 添加一个小延迟再次尝试确保父组件的loadSearchCache执行完毕
setTimeout(() => {
this.updateFromCache();
// 强制更新UI确保选中状态正确显示
this.$forceUpdate();
}, 100);
}, },
methods: { methods: {
updateFromCache() {
const cachedData = getCachedData();
if (cachedData) {
// 优先从缓存获取currentMonth
if (cachedData.currentMonth) {
this.selectedMonth = cachedData.currentMonth;
}
// 处理lastSelectedValue
if (cachedData.lastSelectedValue) {
try {
const lastSelectedValue = JSON.parse(cachedData.lastSelectedValue);
if (lastSelectedValue) {
if (this.mode === 'Monthly') {
// 月模式下确保selectedMonth和lastSelectedValue一致
let monthToUse = this.selectedMonth;
// 如果缓存中有currentMonth优先使用
if (cachedData.currentMonth) {
monthToUse = cachedData.currentMonth;
this.selectedMonth = monthToUse;
}
// 生成与selectedMonth对应的日期范围
const [start, end] = this.getMonthRange(this.year, monthToUse);
// 使用$nextTick确保DOM更新后再发出事件
this.$nextTick(() => {
this.$emit('select', [start, end]);
this.$emit('month-change', monthToUse);
});
} else if ((this.mode === 'Daily' && typeof lastSelectedValue === 'string') ||
(this.mode !== 'Daily' && Array.isArray(lastSelectedValue))) {
this.$emit('select', lastSelectedValue);
}
}
} catch (e) {
console.error('使用缓存的lastSelectedValue失败', e);
}
}
}
},
// 自动选中当前日期或周 // 自动选中当前日期或周
autoSelectCurrentDate(shouldEmit = true) { autoSelectCurrentDate(shouldEmit = true) {
const now = new Date(); const now = new Date();
@ -162,8 +248,15 @@ export default {
// 处理月模式的选择 // 处理月模式的选择
handleMonthSelect(month) { handleMonthSelect(month) {
// 确保状态立即更新
this.selectedMonth = month; this.selectedMonth = month;
// 强制更新UI
this.$forceUpdate();
const [start, end] = this.getMonthRange(this.year, month); const [start, end] = this.getMonthRange(this.year, month);
// 先发出month-change事件确保父组件的currentMonth先更新
this.$emit('month-change', month);
// 然后发出select事件更新lastSelectedValue包含正确的时间格式
this.$emit('select', [start, end]); this.$emit('select', [start, end]);
}, },
@ -184,7 +277,15 @@ export default {
getMonthRange(year, month) { getMonthRange(year, month) {
const start = new Date(year, month - 1, 1); const start = new Date(year, month - 1, 1);
const end = new Date(year, month, 0); const end = new Date(year, month, 0);
return [this.formatDate(start), this.formatDate(end)]; const formatDateWithTime = (date, isStart = true) => {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const time = isStart ? '00:00:00' : '23:59:59';
return `${year}-${month}-${day} ${time}`;
};
return [formatDateWithTime(start), formatDateWithTime(end, false)];
}, },
// 辅助:获取某年的所有完整周区间(假设周从周日开始,到周六结束) // 辅助:获取某年的所有完整周区间(假设周从周日开始,到周六结束)
@ -253,23 +354,27 @@ export default {
month() { month() {
if (this.mode === 'Daily') { if (this.mode === 'Daily') {
this.selectedDate = ''; this.selectedDate = '';
}
this.autoSelectCurrentDate(); this.autoSelectCurrentDate();
} }
}
}, },
}; };
</script> </script>
<style scoped> <style scoped>
.date-picker-core { .bt-d {
padding: 10px; width: 36px; margin: 0 10px;
}
.bt-w {
margin: 0 30px; width: 105px;
}
.bt-m {
margin: 0 20px; width: 55px;
} }
.picker-container { .picker-container {
display: flex; display: flex;
flex-wrap: wrap; flex-wrap: wrap;
gap: 8px; gap: 8px;
padding: 12px;
border-radius: 4px; border-radius: 4px;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;

View File

@ -9,11 +9,25 @@
</template> </template>
<script> <script>
// 从 sessionStorage 获取缓存数据的辅助函数
function getCachedMode() {
try {
const cachedData = sessionStorage.getItem('launches_search_cache');
if (cachedData) {
const parsedData = JSON.parse(cachedData);
return parsedData.currentMode || 'Daily';
}
} catch (e) {
console.error('获取缓存的模式失败', e);
}
return 'Daily';
}
export default { export default {
props: { props: {
value: { value: {
type: String, type: String,
default: 'Daily' default: getCachedMode,
} }
}, },
data() { data() {
@ -29,22 +43,22 @@ export default {
<style scoped lang="scss"> <style scoped lang="scss">
.switch-box { .switch-box {
padding: 22px 40px; padding: 12px 40px;
border-radius: 12px; border-radius: 12px;
background-color: #fff; background-color: #fff;
box-shadow: 0 10px 30px 0 #0000000d; box-shadow: 0 10px 30px 0 #0000000d;
.diver { .diver {
width: 1px; width: 1px;
height: 36px; height: 29px;
margin-left: 70px; margin-left: 33px;
margin-right: 70px; margin-right: 33px;
border-left-width: 1px; border-left-width: 1px;
border-left-style: solid; border-left-style: solid;
border-left-color: #E2E8F0; border-left-color: #E2E8F0;
} }
.item-text { .item-text {
font-size: 24px; font-size: 24px;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #3A4A65; color: #3A4A65;
&:active { &:active {
opacity: 0.8; opacity: 0.8;
@ -53,7 +67,7 @@ export default {
.active { .active {
color: #7B61FF; color: #7B61FF;
font-weight: 900 !important; font-weight: 900 !important;
font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
} }
} }
</style> </style>

View File

@ -3,7 +3,7 @@
<div class="btn" @click="prevYear"> <div class="btn" @click="prevYear">
<img :src="prevIcon" alt="Previous Year" /> <img :src="prevIcon" alt="Previous Year" />
</div> </div>
<div style="width: 60px;text-align: center; font-family: 'Poppins-Regular', serif">{{ monthName }}</div> <div class="text">{{ monthName }}</div>
<div class="btn" @click="nextYear"> <div class="btn" @click="nextYear">
<img :src="nextIcon" :alt="isNextDisabled ? 'Next Year Disabled' : 'Next Year'" /> <img :src="nextIcon" :alt="isNextDisabled ? 'Next Year Disabled' : 'Next Year'" />
</div> </div>
@ -16,6 +16,20 @@ import IconNext from '@/static/launches/icon_next.png';
import IconNextDisabled from '@/static/launches/icon_next_disabled.png'; import IconNextDisabled from '@/static/launches/icon_next_disabled.png';
import IconPrevDisabled from '@/static/launches/icon_prev_disabled.png'; import IconPrevDisabled from '@/static/launches/icon_prev_disabled.png';
// 从 sessionStorage 获取缓存数据的辅助函数
function getCachedMonth() {
try {
const cachedData = sessionStorage.getItem('launches_search_cache');
if (cachedData) {
const parsedData = JSON.parse(cachedData);
return parsedData.currentMonth || (new Date().getMonth() + 1);
}
} catch (e) {
console.error('获取缓存的月份失败', e);
}
return new Date().getMonth() + 1;
}
export default { export default {
data() { data() {
return { return {
@ -24,7 +38,7 @@ export default {
props: { props: {
value: { value: {
type: Number, type: Number,
default: () => new Date().getMonth() + 1 default: getCachedMonth
} }
}, },
methods: { methods: {
@ -60,15 +74,13 @@ export default {
<style scoped lang="scss"> <style scoped lang="scss">
.box { .box {
gap: 16px;
color: #3A4A65; color: #3A4A65;
font-size: 24px; font-size: 24px;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
.btn { .btn {
width: 48px; width: 36px;
height: 48px; height: 36px;
cursor: pointer; cursor: pointer;
background-color: #FFFFFF;
border-radius: 4px; border-radius: 4px;
&:active { &:active {
@ -80,5 +92,8 @@ export default {
height: 100%; height: 100%;
} }
} }
.text {
width: 60px;text-align: center; font-family: 'Poppins-Regular';
}
} }
</style> </style>

View File

@ -1,9 +1,23 @@
<script> <script>
// 从 sessionStorage 获取缓存数据的辅助函数
function getCachedSortType() {
try {
const cachedData = sessionStorage.getItem('launches_search_cache');
if (cachedData) {
const parsedData = JSON.parse(cachedData);
return parsedData.sortType || 'popular';
}
} catch (e) {
console.error('获取缓存的排序类型失败', e);
}
return 'popular';
}
export default { export default {
props: { props: {
value: { value: {
type: String, type: String,
default: 'popular' default: getCachedSortType
} }
}, },
methods: { methods: {

View File

@ -3,11 +3,25 @@ import IconPrev from '@/static/launches/icon_prev.png';
import IconNext from '@/static/launches/icon_next.png'; import IconNext from '@/static/launches/icon_next.png';
import IconNextDisabled from '@/static/launches/icon_next_disabled.png'; import IconNextDisabled from '@/static/launches/icon_next_disabled.png';
// 从 sessionStorage 获取缓存数据的辅助函数
function getCachedYear() {
try {
const cachedData = sessionStorage.getItem('launches_search_cache');
if (cachedData) {
const parsedData = JSON.parse(cachedData);
return parsedData.currentYear || new Date().getFullYear();
}
} catch (e) {
console.error('获取缓存的年份失败', e);
}
return new Date().getFullYear();
}
export default { export default {
props: { props: {
value: { value: {
type: Number, type: Number,
default: () => new Date().getFullYear() default: getCachedYear
} }
}, },
data() { data() {
@ -45,7 +59,7 @@ export default {
<div class="btn" @click="prevYear"> <div class="btn" @click="prevYear">
<img :src="prevIcon" alt="Previous Year" /> <img :src="prevIcon" alt="Previous Year" />
</div> </div>
<div style="width: 60px;text-align: center; font-family: 'Poppins-Regular', serif">{{ year }}</div> <div class="text">{{ year }}</div>
<div class="btn" @click="nextYear"> <div class="btn" @click="nextYear">
<img :src="nextIcon" :alt="isNextDisabled ? 'Next Year Disabled' : 'Next Year'" /> <img :src="nextIcon" :alt="isNextDisabled ? 'Next Year Disabled' : 'Next Year'" />
</div> </div>
@ -54,15 +68,15 @@ export default {
<style scoped lang="scss"> <style scoped lang="scss">
.box { .box {
gap: 16px; gap: 6px;
color: #3A4A65; color: #3A4A65;
font-size: 24px; font-size: 24px;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
.btn { .btn {
width: 48px; width: 36px;
height: 48px; height: 36px;
cursor: pointer; cursor: pointer;
background-color: #FFFFFF; //background-color: #FFFFFF;
border-radius: 4px; border-radius: 4px;
&:active { &:active {
@ -74,5 +88,8 @@ export default {
height: 100%; height: 100%;
} }
} }
.text {
width: 60px;text-align: center; font-family: 'Poppins-Regular';
}
} }
</style> </style>

View File

@ -2,6 +2,9 @@
<div id="normal-container" class="launches-content" v-loading.fullscreen.lock="fullscreenLoading"> <div id="normal-container" class="launches-content" v-loading.fullscreen.lock="fullscreenLoading">
<IntegratedLayout> <IntegratedLayout>
<div class="content"> <div class="content">
<div class="bread-menu">
<span class="gradient-color crumbs">AI Launches</span>
</div>
<div class="launches-header"> <div class="launches-header">
<h1 class="launches-title-text"> <h1 class="launches-title-text">
AI Launches AI Launches
@ -13,13 +16,13 @@
</div> </div>
<div class="list-header flex-between-center"> <div class="list-header flex-between-center">
<SwitchSort v-model="sortType" /> <SwitchSort v-model="sortType" />
<div class="flex items-center" style="gap: 60px"> <div class="flex items-center gap-40">
<SwitchMonth v-show="currentMode === 'Daily'" v-model="currentMonth" /> <SwitchMonth v-show="currentMode === 'Daily'" v-model="currentMonth" />
<SwitchYear v-model="currentYear" /> <SwitchYear v-model="currentYear" />
</div> </div>
</div> </div>
<div class="card list-container"> <div class="card list-container">
<OptionDates :year="currentYear" :mode="currentMode" :month="currentMonth" @select="handleDateSelect" /> <OptionDates :year="currentYear" :mode="currentMode" :month="currentMonth" @select="handleDateSelect" @month-change="handleMonthChange" />
<div class="diver"></div> <div class="diver"></div>
<div class="list"> <div class="list">
<ListItem v-for="(it, i) in articleList" :key="it.id" :config="it" :sort-index="i + 1" /> <ListItem v-for="(it, i) in articleList" :key="it.id" :config="it" :sort-index="i + 1" />
@ -41,6 +44,8 @@ import SwitchMonth from "@/pages/Launches/components/SwitchMonth.vue";
import ListItem from "@/pages/Launches/components/ListItem.vue"; import ListItem from "@/pages/Launches/components/ListItem.vue";
import OptionDates from "@/pages/Launches/components/OptionDates.vue"; import OptionDates from "@/pages/Launches/components/OptionDates.vue";
const SEARCH_CACHE_KEY = 'launches_search_cache';
export default { export default {
components: { components: {
SwitchDate, SwitchDate,
@ -90,32 +95,176 @@ export default {
console.error('解析时间参数失败', e); console.error('解析时间参数失败', e);
} }
} }
this.saveSearchCache();
// 重新获取文章列表,传入新的排序类型 // 重新获取文章列表,传入新的排序类型
this.getArticleListData(this.currentPage, this.pageSize, startTime, endTime, newVal); this.getArticleListData(this.currentPage, this.pageSize, startTime, endTime, newVal);
} }
}, },
// 监听其他搜索条件变化
currentPage() {
this.saveSearchCache();
},
currentYear() {
this.saveSearchCache();
},
currentMode() {
this.saveSearchCache();
},
currentMonth() {
this.saveSearchCache();
},
lastSelectedValue() {
this.saveSearchCache();
}
}, },
methods: { methods: {
handleMonthChange(month) {
this.currentMonth = month;
// 无论当前模式是什么都更新lastSelectedValue
const [start, end] = this.getMonthRange(this.currentYear, month);
this.lastSelectedValue = JSON.stringify([start, end]);
},
getMonthRange(year, month) {
const start = new Date(year, month - 1, 1);
const end = new Date(year, month, 0);
// 格式化为 yyyy-mm-dd HH:MM:SS
const formatDate = (date, isStart = true) => {
const y = date.getFullYear();
const m = String(date.getMonth() + 1).padStart(2, '0');
const d = String(date.getDate()).padStart(2, '0');
const time = isStart ? '00:00:00' : '23:59:59';
return `${y}-${m}-${d} ${time}`;
};
return [formatDate(start), formatDate(end)];
},
// 保存搜索条件到sessionStorage
saveSearchCache() {
const cacheData = {
currentPage: this.currentPage,
currentYear: this.currentYear,
currentMode: this.currentMode,
currentMonth: this.currentMonth,
sortType: this.sortType,
lastSelectedValue: this.lastSelectedValue
};
try {
sessionStorage.setItem(SEARCH_CACHE_KEY, JSON.stringify(cacheData));
} catch (e) {
console.error('保存搜索缓存失败', e);
}
},
// 从sessionStorage加载搜索条件
loadSearchCache() {
try {
const cachedData = sessionStorage.getItem(SEARCH_CACHE_KEY);
if (cachedData) {
const parsedData = JSON.parse(cachedData);
// 恢复各个搜索条件
if (parsedData.currentPage !== undefined) {
this.currentPage = parsedData.currentPage;
}
if (parsedData.currentYear !== undefined) {
this.currentYear = parsedData.currentYear;
}
if (parsedData.currentMode !== undefined) {
this.currentMode = parsedData.currentMode;
}
if (parsedData.currentMonth !== undefined) {
this.currentMonth = parsedData.currentMonth;
}
if (parsedData.sortType !== undefined) {
this.sortType = parsedData.sortType;
}
if (parsedData.lastSelectedValue !== undefined) {
this.lastSelectedValue = parsedData.lastSelectedValue;
}
// 确保数据同步在月模式下如果currentMonth和lastSelectedValue不一致以currentMonth为准
this.$nextTick(() => {
if (this.currentMode === 'Monthly' && this.currentMonth) {
// 解析lastSelectedValue获取其中的月份
let cachedMonth = null;
try {
if (this.lastSelectedValue) {
const selectedValue = JSON.parse(this.lastSelectedValue);
if (Array.isArray(selectedValue) && selectedValue.length > 0) {
// 从日期字符串中提取月份
const dateParts = selectedValue[0].split('-');
if (dateParts.length >= 2) {
cachedMonth = parseInt(dateParts[1], 10);
}
}
}
} catch (e) {
console.error('解析lastSelectedValue失败', e);
}
// 如果lastSelectedValue中的月份与currentMonth不一致更新lastSelectedValue
if (cachedMonth !== this.currentMonth) {
this.handleMonthChange(this.currentMonth);
}
}
// 从lastSelectedValue中提取时间参数并重新请求数据
let startTime = '';
let endTime = '';
if (this.lastSelectedValue) {
try {
const selectedValue = JSON.parse(this.lastSelectedValue);
if (selectedValue instanceof Array) {
startTime = selectedValue[0];
endTime = selectedValue[1];
} else {
startTime = selectedValue;
endTime = selectedValue;
}
} catch (e) {
console.error('解析时间参数失败', e);
}
}
// 调用数据加载方法
this.getArticleListData(this.currentPage, this.pageSize, startTime, endTime, this.sortType);
});
}
} catch (e) {
console.error('加载搜索缓存失败', e);
}
},
calculateTotalPages() { calculateTotalPages() {
// 当 total 为 0 时 totalPages 为 1 // 当 total 为 0 时 totalPages 为 1
// 否则向上取整计算总页数 // 否则向上取整计算总页数
this.totalPages = this.total === 0 ? 1 : Math.ceil(this.total / this.pageSize); this.totalPages = this.total === 0 ? 1 : Math.ceil(this.total / this.pageSize);
}, },
handleDateSelect(selectedValue) { handleDateSelect(selectedValue) {
const stringValue = JSON.stringify(selectedValue); // 确保selectedValue已经包含时分秒
let valueToSave = selectedValue;
// 处理日期字符串或日期范围数组,添加时分秒
if (typeof selectedValue === 'string' && !selectedValue.includes(' ')) {
// 单个日期,添加时间
valueToSave = selectedValue + ' 00:00:00';
} else if (Array.isArray(selectedValue) && selectedValue.length === 2) {
// 日期范围数组,确保每个日期都有时间
const start = selectedValue[0].includes(' ') ? selectedValue[0] : selectedValue[0] + ' 00:00:00';
const end = selectedValue[1].includes(' ') ? selectedValue[1] : selectedValue[1] + ' 23:59:59';
valueToSave = [start, end];
}
const stringValue = JSON.stringify(valueToSave);
if (this.lastSelectedValue === stringValue) { if (this.lastSelectedValue === stringValue) {
return; return;
} }
this.lastSelectedValue = stringValue; this.lastSelectedValue = stringValue;
// 获取开始和结束时间
let startTime = ''; let startTime = '';
let endTime = ''; let endTime = '';
if (selectedValue instanceof Array) { if (valueToSave instanceof Array) {
startTime = selectedValue[0] + ' 00:00:00'; startTime = valueToSave[0];
endTime = selectedValue[1] + ' 23:59:59'; endTime = valueToSave[1];
} else { } else {
startTime = selectedValue + ' 00:00:00'; startTime = valueToSave;
endTime = selectedValue + ' 23:59:59'; endTime = valueToSave;
} }
this.getArticleListData(this.currentPage, this.pageSize, startTime, endTime, this.sortType); this.getArticleListData(this.currentPage, this.pageSize, startTime, endTime, this.sortType);
}, },
@ -164,12 +313,18 @@ export default {
this.getArticleListData(pageNumber, this.pageSize, startTime, endTime, this.sortType); this.getArticleListData(pageNumber, this.pageSize, startTime, endTime, this.sortType);
}, },
}, },
mounted() {
this.loadSearchCache();
}
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.gap-40 {
gap: 40px;
}
.card { .card {
padding: 60px 30px; padding: 20px 30px;
background-color: #FFFFFF; background-color: #FFFFFF;
border-radius: 12px; border-radius: 12px;
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.08); box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.08);
@ -179,35 +334,47 @@ export default {
overflow-y: auto; overflow-y: auto;
position: relative; position: relative;
.content { .content {
padding-top: 190px; padding-top: 25px;
padding-bottom: 100px; padding-bottom: 100px;
.bread-menu {
font-size: 18px;
font-family: 'Poppins-Medium';
margin-bottom: 25px;
.crumbs {
font-family: 'Poppins-SemiBold';
font-weight: 600;
}
}
.launches-header { .launches-header {
margin-bottom: 114px; margin-bottom: 25px;
.launches-title-text { .launches-title-text {
margin: 0; margin: 0;
font-size: 40px; font-size: 40px;
font-weight: bold; font-weight: bold;
font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
} }
.launches-subtitle-text { .launches-subtitle-text {
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
color: #64748B; color: #64748B;
margin-top: 10px; margin-top: 10px;
} }
} }
.list-header { .list-header {
margin-top: 56px; margin-top: 25px;
} }
.list-container { .list-container {
margin-top: 60px; margin-top: 30px;
margin-bottom: 50px; margin-bottom: 40px;
.diver { .diver {
height: 1px; height: 1px;
border-top-color: #E2E8F0; border-top-color: #E2E8F0;
border-top-style: solid; border-top-style: solid;
border-top-width: 2px; border-top-width: 2px;
margin-top: 40px; margin-top: 15px;
margin-bottom: 40px; margin-bottom: 15px;
} }
.list { .list {
gap: 30px; gap: 30px;

View File

@ -88,8 +88,8 @@ export default {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.input-container { .input-container {
margin-top: 100px; margin-top: 60px;
margin-bottom: 60px; margin-bottom: 40px;
} }
} }
@ -97,7 +97,7 @@ export default {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 30px; gap: 30px;
margin-bottom: 60px; margin-bottom: 40px;
} }
} }
</style> </style>

View File

@ -88,8 +88,8 @@ export default {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.input-container { .input-container {
margin-top: 100px; margin-top: 60px;
margin-bottom: 60px; margin-bottom: 40px;
} }
} }
@ -97,7 +97,7 @@ export default {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 30px; gap: 30px;
margin-bottom: 60px; margin-bottom: 40px;
} }
} }
</style> </style>

View File

@ -88,8 +88,8 @@ export default {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
.input-container { .input-container {
margin-top: 100px; margin-top: 60px;
margin-bottom: 60px; margin-bottom: 40px;
} }
} }
@ -97,7 +97,7 @@ export default {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 30px; gap: 30px;
margin-bottom: 60px; margin-bottom: 40px;
} }
} }
</style> </style>

View File

@ -3,7 +3,7 @@
<IntegratedLayout> <IntegratedLayout>
<div class="content"> <div class="content">
<div class="bread-menu"> <div class="bread-menu">
<span>AI Hub</span> <span>Learn</span>
<i class="el-icon-arrow-right"></i> <i class="el-icon-arrow-right"></i>
<span class="crumbs gradient-color">{{$route.name}}</span> <span class="crumbs gradient-color">{{$route.name}}</span>
</div> </div>
@ -28,13 +28,25 @@ export default {
padding-bottom: 100px; padding-bottom: 100px;
.bread-menu { .bread-menu {
font-size: $mid-font-size; font-size: $mid-font-size;
margin: 100px 0; margin: 24px 0 16px;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
.crumbs { .crumbs {
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-weight: 600; font-weight: 600;
} }
} }
.title {
font-weight: bold;
font-size: $huge-font-size1;
font-family: 'Poppins-Bold';
}
.description {
font-size: $big-font-size;
color: $grey-color;
font-family: 'Poppins-Medium';
}
} }
</style> </style>

View File

@ -6,17 +6,17 @@
</div> </div>
<!--写评论--> <!--写评论-->
<div class="write-comment-box card"> <div class="write-comment-box card">
<div class="flex-between-center" style="margin-bottom: 30px"> <div class="flex-between-center mb-30">
<div class="flex-1"> <div class="flex-1">
<h2 class="content-title">Would you recommend Skywork?</h2> <h2 class="content-title">Would you recommend Skywork?</h2>
<div class="rate-box flex-between-center"> <div class="rate-box flex-between-center">
<div class="flex items-center"> <div class="flex items-center">
<Rate v-model="rate" /> <Rate v-model="rate" />
<span style="margin-left: 30px; display: block">Full score: 5 points</span> <span class="little-text">Full score: 5 points</span>
</div> </div>
<div class="flex items-center" style="gap: 16px" v-if="alarmText"> <div class="flex items-center gap-16" v-if="alarmText">
<img src="/logo/icon_alarm.png" alt="" style="width: 16px; height: 16px;" /> <img src="/logo/icon_alarm.png" alt="" />
<p style="color: #CD0E4AFF">{{ alarmText }}</p> <p class="color-01">{{ alarmText }}</p>
</div> </div>
</div> </div>
</div> </div>
@ -225,6 +225,15 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.mb-30 {
margin-bottom: 30px;
}
.gap-16 {
gap: 16px;
}
.color-01 {
color: #CD0E4AFF;
}
.card { .card {
background-color: #fff; background-color: #fff;
border-radius: 12px; border-radius: 12px;
@ -232,12 +241,12 @@ export default {
box-shadow: 0 10px 30px 0 #0000000d; box-shadow: 0 10px 30px 0 #0000000d;
} }
.comment-content { .comment-content {
padding-top: 50px; padding-top: 40px;
.comment-title { .comment-title {
font-size: 30px; font-size: 30px;
font-weight: 600; font-weight: 600;
margin-bottom: 30px; margin-bottom: 30px;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
color: #1E293B; color: #1E293B;
display: flex; display: flex;
align-items: center; align-items: center;
@ -254,8 +263,16 @@ export default {
.write-comment-box { .write-comment-box {
.rate-box { .rate-box {
margin-top: 14px; margin-top: 14px;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #E2E8F0; color: #E2E8F0;
.little-text {
margin-left: 30px;
display: block;
}
img {
width: 16px;
height: 16px;
}
} }
::v-deep .el-textarea { ::v-deep .el-textarea {
.el-textarea__inner { .el-textarea__inner {
@ -287,7 +304,7 @@ export default {
margin-top: 30px; margin-top: 30px;
color: #fff; color: #fff;
font-size: 18px; font-size: 18px;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
width: fit-content; width: fit-content;
margin-left: auto; margin-left: auto;
cursor: pointer; cursor: pointer;
@ -324,7 +341,7 @@ export default {
} }
.little-text { .little-text {
font-size: 14px; font-size: 14px;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #64748B; color: #64748B;
vertical-align: middle; vertical-align: middle;
margin-left: 8px; margin-left: 8px;
@ -332,7 +349,7 @@ export default {
.content-title { .content-title {
font-size: 24px; font-size: 24px;
font-weight: 600; font-weight: 600;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
color: #1E293B; color: #1E293B;
line-height: 24px; line-height: 24px;
margin: 0; margin: 0;
@ -342,6 +359,6 @@ export default {
text-align: right; text-align: right;
color: $grey-color; color: $grey-color;
font-size: $mid-font-size; font-size: $mid-font-size;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
} }
</style> </style>

View File

@ -6,9 +6,9 @@
</div> </div>
<div class="law-text-box"> <div class="law-text-box">
<div class="law-title flex items-center justify-center"> <div class="law-title flex items-center justify-center">
<div style="width: 130px; height: 2px; border-top: 2px solid #1e293b"></div> <div class="diver"></div>
<span>Special Announcement</span> <span>Special Announcement</span>
<div style="width: 130px; height: 2px; border-top: 2px solid #1e293b"></div> <div class="diver"></div>
</div> </div>
<p class="law-text">Without the explicit written permission of this platform, no unit or individual may copy, reprint, quote, modify, disseminate or use all or part of the content of this website in any way. It is strictly prohibited to establish a mirror image or conduct illegal collection on any unofficially authorized server. For any infringement, this platform will hold the offender legally responsible in accordance with the law.</p> <p class="law-text">Without the explicit written permission of this platform, no unit or individual may copy, reprint, quote, modify, disseminate or use all or part of the content of this website in any way. It is strictly prohibited to establish a mirror image or conduct illegal collection on any unofficially authorized server. For any infringement, this platform will hold the offender legally responsible in accordance with the law.</p>
</div> </div>
@ -17,23 +17,25 @@
<!--表头--> <!--表头-->
<div class="flex-between-center"> <div class="flex-between-center">
<div class="flex-center similar-nav-title-box"> <div class="flex-center similar-nav-title-box">
<img src="/ToolDetail/icon_pack.png" alt="" style="width: 24px; height: 24px" /> <img src="/ToolDetail/icon_pack.png" alt="" />
<span class="similar-nav-title">Similar Tools</span> <span class="similar-nav-title">Similar Tools</span>
</div> </div>
<NuxtLink :to="'/home/more?category_slug=' + categorySlug" class="more pointer"> <NuxtLink :to="'/home/more?category_slug=' + categorySlug + '&tag_name=' + categoryName" class="more pointer">
View more<i class="el-icon-arrow-right"></i> View more<i class="el-icon-arrow-right"></i>
</NuxtLink> </NuxtLink>
</div> </div>
<!--列表--> <!--列表-->
<div class="similar-nav-list"> <div class="similar-nav-list">
<SimilarToolCard v-for="it in filteredOtherTools" :key="it.id" :config="it" /> <div class="item" v-for="it in filteredOtherTools" :key="it.id">
<SimilarToolCard :config="it" />
</div>
</div> </div>
</div> </div>
<!--滚动预览图--> <!--滚动预览图-->
<div class="tools-preview-box"> <div class="tools-preview-box">
<el-carousel autoplay height="300px"> <el-carousel autoplay>
<el-carousel-item v-for="(item, i) in banner" :key="i"> <el-carousel-item v-for="(item, i) in banner" :key="i">
<img :src="item.imageUrl || ''" alt="" style="height: 300px; width: 100%; border-radius: 12px" /> <img :src="item.imageUrl || ''" alt="" class="img" />
</el-carousel-item> </el-carousel-item>
</el-carousel> </el-carousel>
</div> </div>
@ -68,6 +70,10 @@ export default {
type: String, type: String,
default: "", default: "",
}, },
categoryName: {
type: String,
default: "",
}
}, },
data() { data() {
return {} return {}
@ -97,7 +103,7 @@ export default {
if (this.categorySlug) { if (this.categorySlug) {
this.$router.push('/home/more?category_slug=' + this.categorySlug) this.$router.push('/home/more?category_slug=' + this.categorySlug)
} }
} },
}, },
mounted() { mounted() {
this.$store.dispatch('getBannerConfig'); this.$store.dispatch('getBannerConfig');
@ -106,13 +112,16 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.img {
height: 100%; width: 100%; border-radius: 12px;
}
.product-content { .product-content {
padding-top: 50px; //padding-top: 40px;
.product-title { .product-title {
font-size: 30px; font-size: 30px;
font-weight: 600; font-weight: 600;
margin-bottom: 30px; margin-bottom: 30px;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
color: #1E293B; color: #1E293B;
display: flex; display: flex;
align-items: center; align-items: center;
@ -134,7 +143,7 @@ export default {
color: #1E293B; color: #1E293B;
font-size: 24px; font-size: 24px;
font-weight: 600; font-weight: 600;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 12px; margin-bottom: 12px;
@ -155,41 +164,45 @@ export default {
margin-top: 20px; margin-top: 20px;
} }
.content-text { .content-text {
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #64748B; color: #64748B;
} }
.content-text-semi-bold { .content-text-semi-bold {
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-weight: 600; font-weight: 600;
color: #64748B; color: #64748B;
} }
.content-text-url { .content-text-url {
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #7B61FF; color: #7B61FF;
} }
} }
.law-text-box { .law-text-box {
background-color: #FFF8F1; background-color: #FFF8F1;
padding: 50px; padding: 50px;
margin-top: 60px; margin-top: 40px;
.law-title { .law-title {
font-size: 24px; font-size: 24px;
color: #1E293B; color: #1E293B;
font-weight: 600; font-weight: 600;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
text-align: center; text-align: center;
margin-bottom: 22px; margin-bottom: 22px;
gap: 22px; gap: 22px;
.diver {
width: 130px; height: 2px; border-top: 2px solid #1e293b
}
} }
.law-text { .law-text {
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #64748B; color: #64748B;
} }
} }
.similar-nav-box { .similar-nav-box {
margin-top: 60px; margin-top: 40px;
.similar-nav-title-box { .similar-nav-title-box {
padding: 10px 16px; padding: 10px 16px;
@ -197,11 +210,16 @@ export default {
background: $header-backgroungd; background: $header-backgroungd;
gap: 8px; gap: 8px;
img {
width: 24px;
height: 24px;
}
.similar-nav-title { .similar-nav-title {
color: #fff; color: #fff;
font-size: 24px; font-size: 24px;
font-weight: 600; font-weight: 600;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
line-height: 24px; line-height: 24px;
} }
} }
@ -211,7 +229,7 @@ export default {
text-align: right; text-align: right;
color: $grey-color; color: $grey-color;
font-size: $mid-font-size; font-size: $mid-font-size;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
&:active { &:active {
opacity: 0.8; opacity: 0.8;
@ -220,11 +238,16 @@ export default {
.similar-nav-list { .similar-nav-list {
display: grid; display: grid;
//grid-auto-rows: 1fr;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
justify-content: space-between; justify-content: space-between;
gap: 20px; gap: 20px;
margin-top: 30px; margin-top: 30px;
.item {
min-height: 110px;
width: 100%;
overflow: hidden;
}
} }
} }
.tools-preview-box { .tools-preview-box {
@ -232,9 +255,12 @@ export default {
border-radius: 16px; border-radius: 16px;
box-shadow: 0 18px 33px 0 #0000000d; box-shadow: 0 18px 33px 0 #0000000d;
padding: 35px 20px; padding: 35px 20px;
margin-top: 60px; margin-top: 40px;
::v-deep .el-carousel { ::v-deep .el-carousel {
.el-carousel__container {
height: 300px;
}
.el-carousel__arrow { .el-carousel__arrow {
opacity: 0 !important; opacity: 0 !important;
transition: none !important; transition: none !important;

View File

@ -17,14 +17,14 @@ export default {
getImageSrc() { getImageSrc() {
// 如果已经点赞,显示选中的图片 // 如果已经点赞,显示选中的图片
if (this.isActive) { if (this.isActive) {
return '/ToolDetail/icon_comment_selected.png'; return require('/static/ToolDetail/icon_comment_selected.png');
} }
// 如果鼠标悬停或点击,显示高亮的图片 // 如果鼠标悬停或点击,显示高亮的图片
if (this.isHovered) { if (this.isHovered) {
return '/ToolDetail/icon_comment_selected.png'; return require('/static/ToolDetail/icon_comment_selected.png');
} }
// 默认显示普通图片 // 默认显示普通图片
return '/ToolDetail/icon_comment.png'; return require('/static/ToolDetail/icon_comment.png');
} }
}, },
methods: { methods: {
@ -62,7 +62,7 @@ export default {
box-shadow: 0 4px 6px 0 #0000000d; box-shadow: 0 4px 6px 0 #0000000d;
background: #fff; background: #fff;
padding: 5px; padding: 5px;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
font-size: 12px; font-size: 12px;
cursor: pointer; cursor: pointer;
img { img {

View File

@ -8,8 +8,8 @@
{{item.username || ''}} {{item.username || ''}}
</div> </div>
<div class="date-wrap flex-top-left"> <div class="date-wrap flex-top-left">
<img src="/ToolDetail/icon_clock1.png" alt="" style="width: 16px; height: 16px;" /> <img src="/ToolDetail/icon_clock1.png" alt="" />
<span style="line-height: 18px">{{ item.createdAt || '' }}</span> <span class="line-h-18">{{ item.createdAt || '' }}</span>
</div> </div>
<p class="comment-text"> <p class="comment-text">
{{ item.content || '' }} {{ item.content || '' }}
@ -38,6 +38,9 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.line-h-18 {
line-height: 18px;
}
.comment-item-box { .comment-item-box {
gap: 15px; gap: 15px;
padding: 30px 0; padding: 30px 0;
@ -54,22 +57,26 @@ export default {
.comment-author { .comment-author {
font-size: 18px; font-size: 18px;
font-weight: 600; font-weight: 600;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
color: #1E293B; color: #1E293B;
line-height: 27px; line-height: 27px;
height: 27px; height: 27px;
} }
.date-wrap { .date-wrap {
gap: 8px; gap: 8px;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
font-size: 14px; font-size: 14px;
color: #869EC2; color: #869EC2;
margin-top: 3px; margin-top: 3px;
img {
width: 16px;
height: 16px;
}
} }
.comment-text { .comment-text {
margin-bottom: 0; margin-bottom: 0;
margin-top: 13px; margin-top: 13px;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #64748B; color: #64748B;
} }
} }

View File

@ -15,8 +15,8 @@ export default {
}, },
methods: { methods: {
goToToolDetail() { goToToolDetail() {
if (this.config.slug && this.category_slug) { if (this.config.slug && this.config.categoryName && this.category_slug) {
this.$router.push('/detail?tool_slug=' + this.config.slug + '&category_slug=' + this.category_slug); this.$router.push('/detail?tool_slug=' + this.config.slug + '&category_slug=' + this.category_slug + '&category_name=' + this.config.categoryName);
} }
} }
}, },
@ -37,12 +37,12 @@ export default {
<div class="icon"> <div class="icon">
<img :src="config.iconUrl || '/'" alt="" /> <img :src="config.iconUrl || '/'" alt="" />
</div> </div>
<span style="font-size: 18px"> <span class="title-text">
{{ config.name || '' }} {{ config.name || '' }}
</span> </span>
</div> </div>
<div class="text"> <div class="text">
{{ config.memo || '' }} {{ config.memo ? config.memo : '' }}
</div> </div>
</div> </div>
</template> </template>
@ -50,12 +50,12 @@ export default {
<style scoped lang="scss"> <style scoped lang="scss">
.similar-card-container { .similar-card-container {
background: #FFFFFF; background: #FFFFFF;
box-shadow: 0 10px 30px 0 #0000000d; box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.05);
border-radius: 8px; border-radius: 8px;
padding: 16px 16px 10px; padding: 16px;
border: 1px solid #BACFFF; border: 1px solid #E2E8F0;
max-width: 305px; height: 100%;
cursor: pointer; box-sizing: border-box;
&:hover { &:hover {
@include gradient-border($linear-gradient-start, $linear-gradient-end); @include gradient-border($linear-gradient-start, $linear-gradient-end);
@ -68,6 +68,14 @@ export default {
.title { .title {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px;
.title-text {
font-size: 18px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.icon { .icon {
width: 40px; width: 40px;
@ -86,20 +94,23 @@ export default {
color: $main-font-color; color: $main-font-color;
font-size: $big-font-size; font-size: $big-font-size;
font-weight: 600; font-weight: 600;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
white-space: nowrap; flex: 1;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap;
} }
} }
.text { .text {
color: $grey-color; color: $grey-color;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
margin-top: 4px;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box; display: -webkit-box;
-webkit-line-clamp: 2; -webkit-line-clamp: 2;
-webkit-box-orient: vertical; -webkit-box-orient: vertical;
overflow: hidden;
} }
} }
</style> </style>

View File

@ -26,14 +26,14 @@ export default {
getImageSrc() { getImageSrc() {
// 如果已经点赞,显示选中的图片 // 如果已经点赞,显示选中的图片
if (this.isActive) { if (this.isActive) {
return '/ToolDetail/icon_thumb_selected.png'; return require('/static/ToolDetail/icon_thumb_selected.png');
} }
// 如果鼠标悬停或点击,显示高亮的图片 // 如果鼠标悬停或点击,显示高亮的图片
if (this.isHovered) { if (this.isHovered) {
return '/ToolDetail/icon_thumb_selected.png'; return require('/static/ToolDetail/icon_thumb_selected.png');
} }
// 默认显示普通图片 // 默认显示普通图片
return '/ToolDetail/icon_thumb.png'; return require('/static/ToolDetail/icon_thumb.png');
} }
}, },
methods: { methods: {
@ -107,7 +107,7 @@ export default {
box-shadow: 0 4px 6px 0 #0000000d; box-shadow: 0 4px 6px 0 #0000000d;
background: #fff; background: #fff;
padding: 5px; padding: 5px;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
font-size: 12px; font-size: 12px;
cursor: pointer; cursor: pointer;
img { img {

View File

@ -12,22 +12,18 @@
</div> </div>
<!--标题--> <!--标题-->
<p class="title-text">{{ tool_detail.name || '' }}</p> <p class="title-text">{{ tool_detail.name || '' }}</p>
<!--评分-->
<div class="rate-box">
<Rate v-model="tool_detail.rating" readonly />
<div class="flex" style="gap: 20px">
<ThumbBtn
:like-count="tool_detail.likeCount || 0"
:id="tool_detail.id"
type="tool"
@like-success="refreshToolDetail"
/>
<CommentBtn :comment-count="commentCount" />
</div>
</div>
<!--工具内容--> <!--工具内容-->
<div class="tool-content"> <div class="tool-content">
<div class="left-content flex flex-col"> <div class="left-content flex flex-col">
<div class="terms-item">
<div class="item-title">
<img src="/ToolDetail/icon_star.png" alt="">
<span>Score: </span>
</div>
<div class="item-content color-01">
{{ (tool_detail.rating || 0).toFixed(1) }}
</div>
</div>
<div class="terms-item"> <div class="terms-item">
<div class="item-title"> <div class="item-title">
<img src="/ToolDetail/icon_note.png" alt=""> <img src="/ToolDetail/icon_note.png" alt="">
@ -42,22 +38,33 @@
<img src="/ToolDetail/icon_clock.png" alt=""> <img src="/ToolDetail/icon_clock.png" alt="">
<span>Data update: </span> <span>Data update: </span>
</div> </div>
<div class="item-content">{{ tool_detail.updatedAt || '' }}</div> <div class="item-content">{{ formatPublishTime(tool_detail.updatedAt || '') }}</div>
</div> </div>
<div class="tags"> <div class="tags">
<div class="tag-item" v-for="(it, index) in tagList" :key="index">{{ it }}</div> <div class="tag-item" v-for="(it, index) in tagList" :key="index">{{ it.value }}</div>
</div> </div>
</div> </div>
<div class="right-content"> <div class="right-content">
<div class="card-website-view"> <div class="flex flex-top-right mb-20 mr-25">
<iframe :src="tool_detail.url || ''" style="width: 100%; height: 100%; pointer-events: none"></iframe> <div class="flex gap-20">
</div> <ThumbBtn
:like-count="tool_detail.likeCount || 0"
:id="tool_detail.id"
type="tool"
@like-success="refreshToolDetail"
/>
<CommentBtn :comment-count="commentCount" />
<a :href="tool_detail.url || ''" class="link-button"> <a :href="tool_detail.url || ''" class="link-button">
<img src="/ToolDetail/icon_link.png" alt="" style="width: 16px; height: 16px" /> <img src="/ToolDetail/icon_link.png" alt="" class="wh-16" />
<span>Visit website</span> <span>Visit website</span>
</a> </a>
</div> </div>
</div> </div>
<div class="card-website-view">
<iframe :src="tool_detail.url || ''" class="scaled-iframe"></iframe>
</div>
</div>
</div>
<MyTabs v-model="activeName" @tab-click="handleClick"> <MyTabs v-model="activeName" @tab-click="handleClick">
<MyTabPane label="Product information" name="product"> <MyTabPane label="Product information" name="product">
@ -67,6 +74,7 @@
:tool-slug="tool_slug || ''" :tool-slug="tool_slug || ''"
:category-slug="category_slug || ''" :category-slug="category_slug || ''"
:tool_content="tool_detail.description || ''" :tool_content="tool_detail.description || ''"
:category-name="category_name || ''"
/> />
</MyTabPane> </MyTabPane>
<MyTabPane label="Comment" name="comment"></MyTabPane> <MyTabPane label="Comment" name="comment"></MyTabPane>
@ -100,16 +108,18 @@ export default {
data() { data() {
return { return {
activeName: 'product', activeName: 'product',
tool_slug: null, tool_slug: '',
tool_detail: {}, tool_detail: {},
commentCount: 0, commentCount: 0,
other_tools: [], other_tools: [],
category_slug: null, category_slug: '',
category_name: '',
} }
}, },
mounted() { mounted() {
this.tool_slug = this.$route.query.tool_slug; this.tool_slug = this.$route.query.tool_slug;
this.category_slug = this.$route.query.category_slug; this.category_slug = this.$route.query.category_slug;
this.category_name = this.$route.query.category_name || '';
this.getAsyncToolDetailData(); this.getAsyncToolDetailData();
this.getAsyncOtherTools(); this.getAsyncOtherTools();
}, },
@ -119,6 +129,7 @@ export default {
if (to.query.tool_slug !== from.query.tool_slug) { if (to.query.tool_slug !== from.query.tool_slug) {
this.tool_slug = to.query.tool_slug; this.tool_slug = to.query.tool_slug;
this.category_slug = to.query.category_slug; this.category_slug = to.query.category_slug;
this.category_name = to.query.category_name || '';
this.resetAndReloadData(); this.resetAndReloadData();
} }
} }
@ -128,6 +139,21 @@ export default {
async refreshToolDetail() { async refreshToolDetail() {
await this.getAsyncToolDetailData(); await this.getAsyncToolDetailData();
}, },
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}`;
},
// 获取详情数据 // 获取详情数据
async getAsyncToolDetailData() { async getAsyncToolDetailData() {
if (this.tool_slug) { if (this.tool_slug) {
@ -184,22 +210,34 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.gap-20 {
gap: 20px;
}
.mb-20 {
margin-bottom: 20px;
}
.mr-25 {
margin-right: 25px;
}
.color-01 {
color: #7B61FF;
}
.tool-detail { .tool-detail {
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
position: relative; position: relative;
.content { .content {
padding-top: 100px; padding-top: 25px;
padding-bottom: 100px; padding-bottom: 100px;
min-height: 100vh; min-height: 100vh;
.bread-menu { .bread-menu {
font-size: $mid-font-size; font-size: $mid-font-size;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
font-weight: 600; font-weight: 600;
.crumbs { .crumbs {
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-weight: 600; font-weight: 600;
} }
} }
@ -207,37 +245,23 @@ export default {
.title-text { .title-text {
font-size: 34px; font-size: 34px;
color: #1E293B; color: #1E293B;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-weight: 600; font-weight: 600;
margin-top: 55px; margin-top: 16px;
} margin-bottom: 20px;
.rate-box {
margin-top: 20px;
display: flex;
align-items: center;
justify-content: space-between;
.btn-wrapper {
display: flex;
align-items: center;
gap: 20px;
}
} }
.tool-content { .tool-content {
display: flex; display: flex;
margin-top: 20px; margin-top: 20px;
margin-bottom: 20px;
gap: 50px; gap: 50px;
.left-content { .left-content {
padding-top: 12px; gap: 20px;
gap: 50px;
flex: 1; flex: 1;
.terms-item { .terms-item {
margin-bottom: 30px; margin-bottom: 20px;
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
gap: 7px; gap: 7px;
@ -247,7 +271,7 @@ export default {
align-items: center; align-items: center;
gap: 8px; gap: 8px;
color: #1E293B; color: #1E293B;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
img { img {
width: 24px; width: 24px;
@ -256,7 +280,7 @@ export default {
} }
.item-content { .item-content {
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #64748B; color: #64748B;
} }
} }
@ -290,7 +314,14 @@ export default {
border-radius: 16px; border-radius: 16px;
box-shadow: 0 10px 30px 0 #0000000d; box-shadow: 0 10px 30px 0 #0000000d;
padding: 19px 25px; padding: 19px 25px;
margin-bottom: 28px; .scaled-iframe {
width: 1000%;
height: 1000%;
transform: scale(0.1);
transform-origin: 0 0;
pointer-events: none;
border: none;
}
} }
.link-button { .link-button {
@ -300,7 +331,7 @@ export default {
border-radius: 12px; border-radius: 12px;
margin: auto; margin: auto;
color: #fff; color: #fff;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
font-weight: 600; font-weight: 600;
display: flex; display: flex;
align-items: center; align-items: center;

View File

@ -93,7 +93,7 @@ export const routes = [
meta: { meta: {
navigationName: "AI Launches Detail", navigationName: "AI Launches Detail",
hidden: true, hidden: true,
parent: 'Launches', // parent: 'Launches',
}, },
}, },
{ {
@ -103,7 +103,7 @@ export const routes = [
meta: { meta: {
navigationName: "Finance", navigationName: "Finance",
hidden: true, hidden: true,
parent: 'Launches', // parent: 'Launches',
} }
}, },
{ {
@ -122,7 +122,8 @@ export const routes = [
meta: { meta: {
icon: 'tools', icon: 'tools',
hidden: true, // 隐藏于主导航,只在子菜单显示 hidden: true, // 隐藏于主导航,只在子菜单显示
parent: 'Hub' parent: 'Hub',
color: '#FFF6F3',
} }
}, },
{ {
@ -133,6 +134,7 @@ export const routes = [
icon: 'frameworks', icon: 'frameworks',
hidden: true, hidden: true,
parent: 'Hub', parent: 'Hub',
color: '#F3FFF3',
} }
}, },
] ]
@ -144,7 +146,6 @@ export const routes = [
meta: { meta: {
icon: 'tools', icon: 'tools',
hidden: true, hidden: true,
parent: ['Hub', 'DailyNews', 'Learn'],
} }
}, },
{ {
@ -174,6 +175,7 @@ export const routes = [
icon: 'observer', icon: 'observer',
hidden: true, hidden: true,
parent: 'Learn', parent: 'Learn',
color: '#FFFBF3',
} }
}, },
{ {
@ -184,6 +186,7 @@ export const routes = [
icon: 'analysis', icon: 'analysis',
hidden: true, hidden: true,
parent: 'Learn', parent: 'Learn',
color: '#FFF3FE',
} }
}, },
{ {
@ -194,6 +197,7 @@ export const routes = [
icon: 'pioneer', icon: 'pioneer',
hidden: true, hidden: true,
parent: 'Learn', parent: 'Learn',
color: '#FFF3F3',
} }
} }
] ]

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 881 B

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 908 B

After

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 909 B

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 900 B

After

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 705 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 681 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 830 B

BIN
static/menu/analysis.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

BIN
static/menu/frameworks.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
static/menu/observer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
static/menu/pioneer.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

BIN
static/menu/tools.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

View File

@ -1,12 +1,12 @@
.article-content { .article-content {
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
color: #64748B; color: #64748B;
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
font-weight: 600; font-weight: 600;
margin-bottom: 20px; margin-bottom: 20px;
margin-top: 10px; margin-top: 10px;
font-family: 'Poppins-SemiBold', serif; font-family: 'Poppins-SemiBold';
color: #1E293B; color: #1E293B;
display: flex; display: flex;
align-items: center; align-items: center;
@ -50,7 +50,7 @@
p, li, span { p, li, span {
color: #64748B; color: #64748B;
font-family: 'Poppins-Regular', serif; font-family: 'Poppins-Regular';
line-height: 1.7; line-height: 1.7;
} }

View File

@ -1,42 +1,36 @@
@font-face { @font-face {
font-family: 'Poppins-Bold'; font-family: 'Poppins-Bold';
src: url('/static/font/Poppins-Bold.otf') format('opentype'); src: url('../static/font/Poppins-Bold.otf') format('opentype');
font-weight: normal; font-weight: 700 !important;
font-display: swap;
} }
@font-face { @font-face {
font-family: 'Poppins-Regular'; font-family: 'Poppins-Regular';
src: url('/static/font/Poppins-Regular.otf') format('opentype'); src: url('../static/font/Poppins-Regular.otf') format('opentype');
font-weight: normal; font-weight: 400 !important;
font-display: swap;
} }
@font-face { @font-face {
font-family: 'Poppins-Light'; font-family: 'Poppins-Light';
src: url('/static/font/Poppins-Light.otf') format('opentype'); src: url('../static/font/Poppins-Light.otf') format('opentype');
font-weight: normal; font-weight: 300 !important;
font-display: swap;
} }
@font-face { @font-face {
font-family: 'Poppins-Medium'; font-family: 'Poppins-Medium';
src: url('/static/font/Poppins-Medium.otf') format('opentype'); src: url('../static/font/Poppins-Medium.otf') format('opentype');
font-weight: normal; font-weight: 500 !important;
font-display: swap;
} }
@font-face { @font-face {
font-family: 'Poppins-SemiBold'; font-family: 'Poppins-SemiBold';
src: url('/static/font/Poppins-SemiBold.otf') format('opentype'); src: url('../static/font/Poppins-SemiBold.otf') format('opentype');
font-weight: normal; font-weight: 600 !important;
font-display: swap;
} }
@font-face { @font-face {
font-family: 'Poppins-ExtraBold'; font-family: 'Poppins-ExtraBold';
src: url('/static/font/Poppins-ExtraBold.otf') format('opentype'); src: url('../static/font/Poppins-ExtraBold.otf') format('opentype');
font-weight: normal; font-weight: 800 !important;
font-display: swap;
} }

View File

@ -103,7 +103,7 @@ a {
border: none; border: none;
border-radius: 12px; border-radius: 12px;
background-color: #ffffff; background-color: #ffffff;
font-family: 'Poppins-Medium', serif; font-family: 'Poppins-Medium';
/* 白色背景 */ /* 白色背景 */
color: #666666; color: #666666;
/* 文字颜色 */ /* 文字颜色 */
@ -134,3 +134,34 @@ a {
@include container-bg; @include container-bg;
flex: 1; flex: 1;
} }
.wh-100 {
width: 100%;
height: 100%;
}
.wh-16 {
width: 16px;
height: 16px;
}
.wh-24 {
width: 24px;
height: 24px;
}
.wh-12 {
width: 12px;
height: 12px;
}
.wh-14 {
width: 14px;
height: 14px;
}
.gap-12 {
gap: 12px;
}
.gap-16 {
gap: 16px;
}
.gap-20 {
gap: 20px;
}

View File

@ -8,7 +8,7 @@
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
font-size: $huge-font-size1; font-size: $huge-font-size1;
font-family: 'Poppins-Bold', serif; font-family: 'Poppins-Bold';
} }
} }
} }

View File

@ -3564,6 +3564,11 @@ data-view-byte-offset@^1.0.1:
es-errors "^1.3.0" es-errors "^1.3.0"
is-data-view "^1.0.1" is-data-view "^1.0.1"
dayjs@^1.11.19:
version "1.11.19"
resolved "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.19.tgz#15dc98e854bb43917f12021806af897c58ae2938"
integrity sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==
de-indent@^1.0.2: de-indent@^1.0.2:
version "1.0.2" version "1.0.2"
resolved "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d" resolved "https://registry.npmmirror.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
@ -6140,7 +6145,7 @@ nuxt@^2.15.8:
"@nuxt/vue-renderer" "2.18.1" "@nuxt/vue-renderer" "2.18.1"
"@nuxt/webpack" "2.18.1" "@nuxt/webpack" "2.18.1"
object-assign@^4.0.1, object-assign@^4.1.0: object-assign@>=4.0.1, object-assign@^4.0.1, object-assign@^4.1.0:
version "4.1.1" version "4.1.1"
resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" resolved "https://registry.npmmirror.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==
@ -7161,6 +7166,14 @@ postcss-pseudo-class-any-link@^9.0.2:
dependencies: dependencies:
postcss-selector-parser "^6.0.13" postcss-selector-parser "^6.0.13"
postcss-px-to-viewport@^1.1.1:
version "1.1.1"
resolved "https://registry.npmmirror.com/postcss-px-to-viewport/-/postcss-px-to-viewport-1.1.1.tgz#a25ca410b553c9892cc8b525cc710da47bf1aa55"
integrity sha512-2x9oGnBms+e0cYtBJOZdlwrFg/mLR4P1g2IFu7jYKvnqnH/HLhoKyareW2Q/x4sg0BgklHlP1qeWo2oCyPm8FQ==
dependencies:
object-assign ">=4.0.1"
postcss ">=5.0.2"
postcss-reduce-initial@^5.1.2: postcss-reduce-initial@^5.1.2:
version "5.1.2" version "5.1.2"
resolved "https://registry.npmmirror.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz#798cd77b3e033eae7105c18c9d371d989e1382d6" resolved "https://registry.npmmirror.com/postcss-reduce-initial/-/postcss-reduce-initial-5.1.2.tgz#798cd77b3e033eae7105c18c9d371d989e1382d6"
@ -7264,15 +7277,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^
resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" resolved "https://registry.npmmirror.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514"
integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==
postcss@^7.0.36: postcss@>=5.0.2, postcss@^8.2.1, postcss@^8.2.15, postcss@^8.4.14, postcss@^8.4.38:
version "7.0.39"
resolved "https://registry.npmmirror.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309"
integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==
dependencies:
picocolors "^0.2.1"
source-map "^0.6.1"
postcss@^8.2.1, postcss@^8.2.15, postcss@^8.4.14, postcss@^8.4.38:
version "8.5.6" version "8.5.6"
resolved "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c" resolved "https://registry.npmmirror.com/postcss/-/postcss-8.5.6.tgz#2825006615a619b4f62a9e7426cc120b349a8f3c"
integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg== integrity sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==
@ -7281,6 +7286,14 @@ postcss@^8.2.1, postcss@^8.2.15, postcss@^8.4.14, postcss@^8.4.38:
picocolors "^1.1.1" picocolors "^1.1.1"
source-map-js "^1.2.1" source-map-js "^1.2.1"
postcss@^7.0.36:
version "7.0.39"
resolved "https://registry.npmmirror.com/postcss/-/postcss-7.0.39.tgz#9624375d965630e2e1f2c02a935c82a59cb48309"
integrity sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==
dependencies:
picocolors "^0.2.1"
source-map "^0.6.1"
prepend-http@^1.0.0: prepend-http@^1.0.0:
version "1.0.4" version "1.0.4"
resolved "https://registry.npmmirror.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" resolved "https://registry.npmmirror.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"