388 lines
11 KiB
Vue
388 lines
11 KiB
Vue
<template>
|
||
<div id="normal-container" class="launches-content" v-loading.fullscreen.lock="fullscreenLoading">
|
||
<IntegratedLayout>
|
||
<div class="content">
|
||
<div class="bread-menu">
|
||
<span class="gradient-color crumbs">AI Launches</span>
|
||
</div>
|
||
<div class="launches-header">
|
||
<h1 class="launches-title-text">
|
||
AI Launches
|
||
</h1>
|
||
<p class="launches-subtitle-text">A daily curated selection of the newest AI applications and products. Explore the most talked-about innovations, trending technologies, and hot new releases shaping the AI landscape. Stay ahead by discovering what’s capturing attention in the AI community.</p>
|
||
</div>
|
||
<div class="flex justify-center">
|
||
<SwitchDate v-model="currentMode" />
|
||
</div>
|
||
<div class="list-header flex-between-center">
|
||
<SwitchSort v-model="sortType" />
|
||
<div class="flex items-center gap-40">
|
||
<SwitchMonth v-show="currentMode === 'Daily'" v-model="currentMonth" />
|
||
<SwitchYear v-model="currentYear" />
|
||
</div>
|
||
</div>
|
||
<div class="card list-container">
|
||
<OptionDates :year="currentYear" :mode="currentMode" :month="currentMonth" @select="handleDateSelect" @month-change="handleMonthChange" />
|
||
<div class="diver"></div>
|
||
<div class="list">
|
||
<ListItem v-for="(it, i) in articleList" :key="it.id" :config="it" :sort-index="i + 1" />
|
||
</div>
|
||
</div>
|
||
<div class="flex-bottom-right">
|
||
<Pagination :current-page="currentPage" :total-pages="totalPages" @page-change="handlePageChange" />
|
||
</div>
|
||
</div>
|
||
</IntegratedLayout>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import SwitchDate from "@/pages/Launches/components/SwitchDate.vue";
|
||
import SwitchSort from "@/pages/Launches/components/SwitchSort.vue";
|
||
import SwitchYear from "@/pages/Launches/components/SwitchYear.vue";
|
||
import SwitchMonth from "@/pages/Launches/components/SwitchMonth.vue";
|
||
import ListItem from "@/pages/Launches/components/ListItem.vue";
|
||
import OptionDates from "@/pages/Launches/components/OptionDates.vue";
|
||
|
||
const SEARCH_CACHE_KEY = 'launches_search_cache';
|
||
|
||
export default {
|
||
components: {
|
||
SwitchDate,
|
||
SwitchSort,
|
||
SwitchYear,
|
||
ListItem,
|
||
OptionDates,
|
||
SwitchMonth,
|
||
},
|
||
data() {
|
||
return {
|
||
lastSelectedValue: null,
|
||
currentPage: 1,
|
||
totalPages: 1,
|
||
pageSize: 10,
|
||
total: 0,
|
||
currentYear: new Date().getFullYear(),
|
||
currentMode: 'Daily',
|
||
currentMonth: new Date().getMonth() + 1,
|
||
articleList: [],
|
||
sortType: 'popular',
|
||
fullscreenLoading: false,
|
||
}
|
||
},
|
||
watch: {
|
||
total() {
|
||
this.calculateTotalPages();
|
||
},
|
||
pageSize() {
|
||
this.calculateTotalPages();
|
||
},
|
||
sortType(newVal, oldVal) {
|
||
if (oldVal !== null) { // 避免初始化时触发
|
||
let startTime = '';
|
||
let endTime = '';
|
||
if (this.lastSelectedValue) {
|
||
try {
|
||
const selectedValue = JSON.parse(this.lastSelectedValue);
|
||
if (selectedValue instanceof Array) {
|
||
startTime = selectedValue[0] + ' 00:00:00';
|
||
endTime = selectedValue[1] + ' 23:59:59';
|
||
} else {
|
||
startTime = selectedValue + ' 00:00:00';
|
||
endTime = selectedValue + ' 23:59:59';
|
||
}
|
||
} catch (e) {
|
||
console.error('解析时间参数失败', e);
|
||
}
|
||
}
|
||
this.saveSearchCache();
|
||
// 重新获取文章列表,传入新的排序类型
|
||
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: {
|
||
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() {
|
||
// 当 total 为 0 时 totalPages 为 1
|
||
// 否则向上取整计算总页数
|
||
this.totalPages = this.total === 0 ? 1 : Math.ceil(this.total / this.pageSize);
|
||
},
|
||
handleDateSelect(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) {
|
||
return;
|
||
}
|
||
this.lastSelectedValue = stringValue;
|
||
|
||
// 获取开始和结束时间
|
||
let startTime = '';
|
||
let endTime = '';
|
||
if (valueToSave instanceof Array) {
|
||
startTime = valueToSave[0];
|
||
endTime = valueToSave[1];
|
||
} else {
|
||
startTime = valueToSave;
|
||
endTime = valueToSave;
|
||
}
|
||
this.getArticleListData(this.currentPage, this.pageSize, startTime, endTime, this.sortType);
|
||
},
|
||
// 获取文章列表
|
||
async getArticleListData(page = 1, limit = 10, startTime, endTime, sortType = 'popular') {
|
||
const params = {page, limit, startTime, endTime, articleType: 'launches'};
|
||
if (sortType === 'popular') {
|
||
params.isHot = 1;
|
||
} else {
|
||
params.sortField = 'publish_time';
|
||
params.sortOrder = 'desc';
|
||
}
|
||
this.fullscreenLoading = true;
|
||
const {data: res} = await this.$api.article.getArticleList(params);
|
||
const {code, data} = res;
|
||
if (code === 0 && data.list) {
|
||
this.articleList = data.list;
|
||
this.total = data.total;
|
||
this.calculateTotalPages();
|
||
} else {
|
||
this.articleList = [];
|
||
this.total = 0;
|
||
this.calculateTotalPages();
|
||
}
|
||
this.fullscreenLoading = false;
|
||
},
|
||
handlePageChange(pageNumber) {
|
||
this.currentPage = pageNumber;
|
||
// 从 lastSelectedValue 中提取时间参数
|
||
let startTime = '';
|
||
let endTime = '';
|
||
if (this.lastSelectedValue) {
|
||
try {
|
||
const selectedValue = JSON.parse(this.lastSelectedValue);
|
||
if (selectedValue instanceof Array) {
|
||
startTime = selectedValue[0] + ' 00:00:00';
|
||
endTime = selectedValue[1] + ' 23:59:59';
|
||
} else {
|
||
startTime = selectedValue + ' 00:00:00';
|
||
endTime = selectedValue + ' 23:59:59';
|
||
}
|
||
} catch (e) {
|
||
console.error('解析时间参数失败', e);
|
||
}
|
||
}
|
||
this.getArticleListData(pageNumber, this.pageSize, startTime, endTime, this.sortType);
|
||
},
|
||
},
|
||
mounted() {
|
||
this.loadSearchCache();
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped lang="scss">
|
||
.gap-40 {
|
||
gap: 40px;
|
||
}
|
||
.card {
|
||
padding: 20px 30px;
|
||
background-color: #FFFFFF;
|
||
border-radius: 12px;
|
||
box-shadow: 0 10px 30px 0 rgba(0, 0, 0, 0.08);
|
||
}
|
||
.launches-content {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
position: relative;
|
||
.content {
|
||
padding-top: 25px;
|
||
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 {
|
||
margin-bottom: 25px;
|
||
.launches-title-text {
|
||
margin: 0;
|
||
font-size: 40px;
|
||
font-weight: bold;
|
||
font-family: 'Poppins-Bold';
|
||
}
|
||
.launches-subtitle-text {
|
||
font-family: 'Poppins-Medium';
|
||
color: #64748B;
|
||
margin-top: 10px;
|
||
}
|
||
}
|
||
.list-header {
|
||
margin-top: 25px;
|
||
}
|
||
.list-container {
|
||
margin-top: 30px;
|
||
margin-bottom: 40px;
|
||
.diver {
|
||
height: 1px;
|
||
border-top-color: #E2E8F0;
|
||
border-top-style: solid;
|
||
border-top-width: 2px;
|
||
margin-top: 15px;
|
||
margin-bottom: 15px;
|
||
}
|
||
.list {
|
||
gap: 30px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|