定时任务修正

This commit is contained in:
2025-11-17 15:16:11 +08:00
parent 4b167058b6
commit 6e9057f6ee
16 changed files with 444 additions and 496 deletions

View File

@@ -59,11 +59,12 @@
<div class="stat-views">浏览量</div>
<div class="stat-likes">点赞数</div>
<div class="stat-time">发布时间</div>
<div class="stat-actions">操作</div>
</div>
<div
v-for="(article, index) in hotArticles"
:key="article.resourceID"
:key="article.resource?.resourceID"
class="hot-article-row"
:class="{ 'top-three': index < 3 }"
>
@@ -73,28 +74,50 @@
</div>
</div>
<div class="stat-title">
<span class="article-title" :title="article.title">{{ article.title }}</span>
<span class="article-title" :title="article.resource?.title">{{ article.resource?.title }}</span>
</div>
<div class="stat-tag">
<span v-if="article.tagID" class="tag-name">{{ getTagName(article.tagID) }}</span>
<span v-if="article.resource?.tagID" class="tag-name">{{ getTagName(article.resource.tagID) }}</span>
<span v-else class="tag-empty">-</span>
</div>
<div class="stat-author">
<span>{{ article.author || '-' }}</span>
<span>{{ article.resource?.author || '-' }}</span>
</div>
<div class="stat-views">
<div class="stat-number hot">
<img src="@/assets/imgs/hot.svg" alt="浏览" class="stat-icon" />
{{ formatNumber(article.viewCount) }}
{{ formatNumber(article.resource?.viewCount) }}
</div>
</div>
<div class="stat-likes">
<div class="stat-number">
{{ formatNumber(article.likeCount) }}
{{ formatNumber(article.resource?.likeCount) }}
</div>
</div>
<div class="stat-time">
<span class="time-text">{{ formatDate(article.publishTime) }}</span>
<span class="time-text">{{ formatDate(article.resource?.publishTime) }}</span>
</div>
<div class="stat-actions">
<button
v-if="article.resource?.resourceID && !article.isTopRecommend"
class="add-to-top-btn"
@click="addToTopRecommends(article.resource.resourceID)"
:disabled="!!article.resource?.resourceID && addingToTop.has(article.resource.resourceID)"
>
{{ article.resource?.resourceID && addingToTop.has(article.resource.resourceID) ? '添加中...' : '添加至TOP' }}
</button>
<span v-else-if="article.isTopRecommend" class="already-top">已在TOP</span>
<!-- 添加至思政 -->
<button
v-if="article.resource?.resourceID && !article.isIdeologicalRecommend"
class="add-to-ideological-btn"
@click="addToIdeologicalRecommends(article.resource.resourceID)"
:disabled="!!article.resource?.resourceID && addingToIdeological.has(article.resource.resourceID)"
>
{{ article.resource?.resourceID && addingToIdeological.has(article.resource.resourceID) ? '添加中...' : '添加至思政' }}
</button>
<span v-else-if="article.isIdeologicalRecommend" class="already-ideological">已在思政</span>
</div>
</div>
@@ -338,7 +361,7 @@ import { ElDialog, ElInput, ElButton, ElCheckbox, ElIcon, ElForm, ElFormItem, El
import { Search } from '@element-plus/icons-vue';
import { AdminLayout } from '@/views/admin';
import { resourceRecommendApi, resourceApi, resourceTagApi } from '@/apis/resource';
import type { ResourceRecommendVO, Resource, Tag } from '@/types';
import type { ResourceRecommendVO, Resource, ResourceVO, Tag } from '@/types';
defineOptions({
name: 'ColumnManagementView'
@@ -356,9 +379,15 @@ const topRecommends = ref<ResourceRecommendVO[]>([]);
const ideologicalRecommends = ref<ResourceRecommendVO[]>([]);
// 热门文章列表
const hotArticles = ref<Resource[]>([]);
const hotArticles = ref<ResourceVO[]>([]);
const hotArticleLimit = ref<number>(20);
// 添加至TOP的加载状态
const addingToTop = ref<Set<string>>(new Set());
// 添加至思政的加载状态
const addingToIdeological = ref<Set<string>>(new Set());
// 标签列表
const tags = ref<Tag[]>([]);
@@ -381,7 +410,7 @@ const editForm = ref({
orderNum: 1
});
// 挂载时加载标签和当前tab的数据
// 挂载时加载标签数据和默认tab的数据
onMounted(() => {
loadTags();
loadTabData(activeTab.value);
@@ -445,7 +474,7 @@ async function loadHotArticles() {
loading.value = true;
try {
// 获取已发布的文章,按浏览量排序
const result = await resourceApi.getResourcePage(
const result = await resourceApi.getResourcePageOrderByViewCount(
{
pageNumber: 1,
pageSize: hotArticleLimit.value
@@ -458,7 +487,7 @@ async function loadHotArticles() {
if (result.success && result.pageDomain?.dataList) {
// 按浏览量降序排序
hotArticles.value = result.pageDomain.dataList
.sort((a, b) => (b.viewCount || 0) - (a.viewCount || 0));
.sort((a, b) => (b.resource?.viewCount || 0) - (a.resource?.viewCount || 0));
}
} catch (error) {
console.error('加载热门文章失败:', error);
@@ -510,7 +539,7 @@ async function showAddResourceDialog(recommendType: 1 | 2) {
async function searchResources() {
searchLoading.value = true;
try {
const result = await resourceApi.getResourcePage(
const result = await resourceApi.getResourcePageOrderByViewCount(
{ pageNumber: 1, pageSize: 50 },
{
keyword: searchKeyword.value,
@@ -524,9 +553,9 @@ async function searchResources() {
? topRecommends.value.map(r => r.resourceID)
: ideologicalRecommends.value.map(r => r.resourceID);
availableResources.value = result.pageDomain.dataList.filter(
r => !existingIds.includes(r.resourceID)
);
availableResources.value = result.pageDomain.dataList
.map(vo => vo.resource)
.filter(r => r && !existingIds.includes(r.resourceID)) as Resource[];
}
} catch (error) {
console.error('搜索资源失败:', error);
@@ -655,6 +684,79 @@ async function deleteRecommend(item: ResourceRecommendVO) {
}
}
}
// 检查资源是否已在TOP推荐中
function isInTopRecommends(resourceID?: string): boolean {
if (!resourceID) return false;
return topRecommends.value.some(r => r.resourceID === resourceID);
}
// 检查资源是否已在思政推荐中
function isInIdeologicalRecommends(resourceID?: string): boolean {
if (!resourceID) return false;
return ideologicalRecommends.value.some(r => r.resourceID === resourceID);
}
// 根据ResourceVO获取resourceID
function getResourceID(articleVO: ResourceVO): string | undefined {
return articleVO.resource?.resourceID;
}
// 添加至TOP推荐
async function addToTopRecommends(resourceID?: string) {
if (!resourceID) return;
addingToTop.value.add(resourceID);
try {
await resourceRecommendApi.batchAddRecommends([resourceID], 1);
ElMessage.success('添加至TOP推荐成功');
// 重新加载TOP推荐列表
loadedTabs.value.delete('top-resources');
const topResult = await resourceRecommendApi.getRecommendsByType(1);
if (topResult.success && topResult.dataList) {
topRecommends.value = topResult.dataList.sort((a, b) => (a.orderNum || 0) - (b.orderNum || 0));
}
// 重新加载热门文章统计以更新按钮状态
loadedTabs.value.delete('hot-articles');
await loadHotArticles();
} catch (error) {
console.error('添加至TOP推荐失败:', error);
ElMessage.error('添加至TOP推荐失败');
} finally {
addingToTop.value.delete(resourceID);
}
}
// 添加至思政推荐
async function addToIdeologicalRecommends(resourceID?: string) {
if (!resourceID) return;
addingToIdeological.value.add(resourceID);
try {
await resourceRecommendApi.batchAddRecommends([resourceID], 2);
ElMessage.success('添加至思政推荐成功');
// 重新加载思政推荐列表
loadedTabs.value.delete('ideological');
const ideologicalResult = await resourceRecommendApi.getRecommendsByType(2);
if (ideologicalResult.success && ideologicalResult.dataList) {
ideologicalRecommends.value = ideologicalResult.dataList.sort((a, b) => (a.orderNum || 0) - (b.orderNum || 0));
}
// 重新加载热门文章统计以更新按钮状态
loadedTabs.value.delete('hot-articles');
await loadHotArticles();
} catch (error) {
console.error('添加至思政推荐失败:', error);
ElMessage.error('添加至思政推荐失败');
} finally {
addingToIdeological.value.delete(resourceID);
}
}
</script>
<style lang="scss" scoped>
@@ -906,6 +1008,7 @@ async function deleteRecommend(item: ResourceRecommendVO) {
.filter-controls {
display: flex;
gap: 12px;
min-width: 150px;
}
// 热门文章列表
@@ -918,7 +1021,7 @@ async function deleteRecommend(item: ResourceRecommendVO) {
.statistics-header {
display: grid;
grid-template-columns: 80px 1fr 120px 120px 120px 100px 120px;
grid-template-columns: 80px 1fr 120px 120px 120px 100px 120px 150px;
gap: 16px;
padding: 12px 16px;
background: #F9FAFB;
@@ -931,7 +1034,7 @@ async function deleteRecommend(item: ResourceRecommendVO) {
.hot-article-row {
display: grid;
grid-template-columns: 80px 1fr 120px 120px 120px 100px 120px;
grid-template-columns: 80px 1fr 120px 120px 120px 100px 120px 150px;
gap: 16px;
padding: 16px;
border-bottom: 1px solid rgba(0, 0, 0, 0.05);
@@ -952,6 +1055,13 @@ async function deleteRecommend(item: ResourceRecommendVO) {
align-items: center;
}
.stat-actions {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.rank-badge {
display: flex;
align-items: center;
@@ -1035,6 +1145,62 @@ async function deleteRecommend(item: ResourceRecommendVO) {
font-size: 13px;
color: #6B7280;
}
.add-to-top-btn {
padding: 6px 12px;
background: #E7000B;
border: none;
border-radius: 6px;
color: #FFFFFF;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
&:hover:not(:disabled) {
background: #c00009;
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.already-top {
font-size: 13px;
color: #10B981;
font-weight: 500;
}
.add-to-ideological-btn {
padding: 6px 12px;
background: #E7000B;
border: none;
border-radius: 6px;
color: #FFFFFF;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
white-space: nowrap;
&:hover:not(:disabled) {
background: #c00009;
}
&:disabled {
opacity: 0.6;
cursor: not-allowed;
}
}
.already-ideological {
font-size: 13px;
color: #10B981;
font-weight: 500;
}
}
// 对话框样式