web-资源中心修改
This commit is contained in:
@@ -12,26 +12,21 @@
|
||||
<!-- 文章头部信息 -->
|
||||
<div class="article-header">
|
||||
<h1 class="article-title">{{ articleData.title }}</h1>
|
||||
<div class="article-meta">
|
||||
<span v-if="articleData.category" class="meta-item">
|
||||
分类:{{ getCategoryLabel(articleData.category) }}
|
||||
</span>
|
||||
<span v-if="articleData.tags && articleData.tags.length" class="meta-item">
|
||||
标签:{{ getTagsString(articleData.tags) }}
|
||||
</span>
|
||||
<span v-if="articleData.author" class="meta-item">
|
||||
作者:{{ articleData.author }}
|
||||
</span>
|
||||
<span v-if="articleData.createTime" class="meta-item">
|
||||
发布时间:{{ formatDate(articleData.createTime) }}
|
||||
</span>
|
||||
<div class="article-meta-info">
|
||||
<div class="meta-item" v-if="articleData.publishTime || articleData.createTime">
|
||||
发布时间:{{ formatDateSimple(articleData.publishTime || articleData.createTime || '') }}
|
||||
</div>
|
||||
<div class="meta-item" v-if="articleData.viewCount !== undefined">
|
||||
浏览次数:{{ articleData.viewCount }}
|
||||
</div>
|
||||
<div class="meta-item" v-if="articleData.source">
|
||||
来源:{{ articleData.source }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 封面图片 -->
|
||||
<div v-if="articleData.coverImage" class="article-cover">
|
||||
<img :src="FILE_DOWNLOAD_URL + articleData.coverImage" class="cover-image" />
|
||||
</div>
|
||||
<!-- 分隔线 -->
|
||||
<div class="separator"></div>
|
||||
|
||||
<!-- 文章内容 -->
|
||||
<div class="article-content ql-editor" v-html="articleData.content"></div>
|
||||
@@ -48,26 +43,21 @@
|
||||
<!-- 文章头部信息 -->
|
||||
<div class="article-header">
|
||||
<h1 class="article-title">{{ articleData.title }}</h1>
|
||||
<div class="article-meta">
|
||||
<span v-if="articleData.category" class="meta-item">
|
||||
分类:{{ getCategoryLabel(articleData.category) }}
|
||||
</span>
|
||||
<span v-if="articleData.tags && articleData.tags.length" class="meta-item">
|
||||
标签:{{ getTagsString(articleData.tags) }}
|
||||
</span>
|
||||
<span v-if="articleData.author" class="meta-item">
|
||||
作者:{{ articleData.author }}
|
||||
</span>
|
||||
<span v-if="articleData.createTime" class="meta-item">
|
||||
发布时间:{{ formatDate(articleData.createTime) }}
|
||||
</span>
|
||||
<div class="article-meta-info">
|
||||
<div class="meta-item" v-if="articleData.publishTime || articleData.createTime">
|
||||
发布时间:{{ formatDateSimple(articleData.publishTime || articleData.createTime || '') }}
|
||||
</div>
|
||||
<div class="meta-item" v-if="articleData.viewCount !== undefined">
|
||||
浏览次数:{{ articleData.viewCount }}
|
||||
</div>
|
||||
<div class="meta-item" v-if="articleData.source">
|
||||
来源:{{ articleData.source }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 封面图片 -->
|
||||
<div v-if="articleData.coverImage" class="article-cover">
|
||||
<img :src="FILE_DOWNLOAD_URL + articleData.coverImage" class="cover-image" />
|
||||
</div>
|
||||
<!-- 分隔线 -->
|
||||
<div class="separator"></div>
|
||||
|
||||
<!-- 文章内容 -->
|
||||
<div class="article-content ql-editor" v-html="articleData.content"></div>
|
||||
@@ -77,25 +67,15 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { ElDialog, ElButton } from 'element-plus';
|
||||
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||
import { ResourceCategory } from '@/types/resource';
|
||||
import { ResourceCategory, Resource, Tag } from '@/types';
|
||||
|
||||
interface ArticleData {
|
||||
title?: string;
|
||||
content?: string;
|
||||
coverImage?: string;
|
||||
category?: string;
|
||||
tags?: Array<{ name?: string }> | string[]; // 支持对象数组或字符串数组
|
||||
author?: string;
|
||||
createTime?: string | Date;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
modelValue?: boolean; // Dialog 模式下的显示状态
|
||||
asDialog?: boolean; // 是否作为 Dialog 使用
|
||||
title?: string; // Dialog 标题
|
||||
width?: string; // Dialog 宽度
|
||||
articleData?: ArticleData; // 文章数据
|
||||
articleData?: Resource; // 文章数据
|
||||
categoryList?: Array<ResourceCategory>; // 分类列表
|
||||
showEditButton?: boolean; // 是否显示编辑按钮
|
||||
}
|
||||
@@ -122,43 +102,17 @@ const visible = computed({
|
||||
set: (val) => props.asDialog ? emit('update:modelValue', val) : undefined
|
||||
});
|
||||
|
||||
// 获取标签字符串
|
||||
function getTagsString(tags: Array<{ name?: string }> | string[]): string {
|
||||
if (!tags || tags.length === 0) return '';
|
||||
|
||||
if (typeof tags[0] === 'string') {
|
||||
return (tags as string[]).join(', ');
|
||||
} else {
|
||||
return (tags as Array<{ name?: string }>)
|
||||
.map(tag => tag.name || '')
|
||||
.filter(name => name)
|
||||
.join(', ');
|
||||
}
|
||||
}
|
||||
|
||||
// 获取分类标签
|
||||
function getCategoryLabel(categoryId: string): string {
|
||||
if (!props.categoryList || !categoryId) return '';
|
||||
|
||||
const category = props.categoryList.find(cat =>
|
||||
cat.id === categoryId || cat.categoryID === categoryId
|
||||
);
|
||||
|
||||
return category?.name || categoryId;
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
function formatDate(date: string | Date): string {
|
||||
// 格式化日期(简单格式:YYYY-MM-DD)
|
||||
function formatDateSimple(date: string | Date): string {
|
||||
if (!date) return '';
|
||||
|
||||
const d = new Date(date);
|
||||
return d.toLocaleDateString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
const year = d.getFullYear();
|
||||
const month = String(d.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(d.getDate()).padStart(2, '0');
|
||||
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
// 关闭处理
|
||||
@@ -189,45 +143,44 @@ defineExpose({
|
||||
.article-show-container {
|
||||
max-width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.article-header {
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
|
||||
.article-title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
color: #303133;
|
||||
margin: 0 0 16px 0;
|
||||
line-height: 1.4;
|
||||
line-height: 28px;
|
||||
color: #141F38;
|
||||
margin: 0 0 30px 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.article-meta {
|
||||
.article-meta-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
color: #909399;
|
||||
font-size: 14px;
|
||||
align-items: center;
|
||||
gap: 48px;
|
||||
margin-top: 30px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background: #909399;
|
||||
border-radius: 50%;
|
||||
margin-right: 8px;
|
||||
}
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 28px;
|
||||
color: #979797;
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: #E9E9E9;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.article-cover {
|
||||
@@ -243,9 +196,11 @@ defineExpose({
|
||||
}
|
||||
|
||||
.article-content {
|
||||
line-height: 1.8;
|
||||
color: #303133;
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 30px;
|
||||
color: #334155;
|
||||
|
||||
// 继承富文本编辑器的样式
|
||||
:deep(img) {
|
||||
|
||||
@@ -1,167 +0,0 @@
|
||||
<template>
|
||||
<div class="party-history-learning">
|
||||
<h2 class="page-title">党史学习</h2>
|
||||
|
||||
<!-- 分类标签 -->
|
||||
<div class="category-tabs">
|
||||
<div
|
||||
class="category-tab"
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
:class="{ active: activeCategory === category.id }"
|
||||
@click="activeCategory = category.id"
|
||||
>
|
||||
{{ category.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 文章列表 -->
|
||||
<div class="article-list">
|
||||
<div class="article-item" v-for="article in articles" :key="article.id" @click="viewArticle(article)">
|
||||
<div class="article-image">
|
||||
<img :src="article.image" :alt="article.title" />
|
||||
</div>
|
||||
<div class="article-content">
|
||||
<h3>{{ article.title }}</h3>
|
||||
<p class="article-summary">{{ article.summary }}</p>
|
||||
<div class="article-meta">
|
||||
<span class="author">{{ article.author }}</span>
|
||||
<span class="date">{{ article.publishDate }}</span>
|
||||
<span class="views">{{ article.views }} 阅读</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const activeCategory = ref(1);
|
||||
const articles = ref<any[]>([]);
|
||||
|
||||
const categories = [
|
||||
{ id: 1, name: '新民主主义革命时期' },
|
||||
{ id: 2, name: '社会主义革命和建设时期' },
|
||||
{ id: 3, name: '改革开放和社会主义现代化建设新时期' },
|
||||
{ id: 4, name: '新时代中国特色社会主义' }
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
// TODO: 加载党史学习文章
|
||||
});
|
||||
|
||||
function viewArticle(article: any) {
|
||||
router.push(`/resource/article/${article.id}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.party-history-learning {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.category-tabs {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 32px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.category-tab {
|
||||
padding: 8px 20px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: #ffe6e6;
|
||||
color: #C62828;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #C62828;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.article-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.article-item {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s;
|
||||
border: 1px solid #e0e0e0;
|
||||
|
||||
&:hover {
|
||||
background: #f9f9f9;
|
||||
}
|
||||
}
|
||||
|
||||
.article-image {
|
||||
width: 240px;
|
||||
height: 160px;
|
||||
flex-shrink: 0;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.article-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.article-summary {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.article-meta {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
font-size: 13px;
|
||||
color: #999;
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
<template>
|
||||
<div class="policy-interpretation">
|
||||
<h2 class="page-title">政策解读</h2>
|
||||
<div class="interpretation-grid">
|
||||
<div class="interpretation-item" v-for="item in interpretations" :key="item.id" @click="viewInterpretation(item)">
|
||||
<div class="item-badge">{{ item.category }}</div>
|
||||
<h3>{{ item.title }}</h3>
|
||||
<p class="item-summary">{{ item.summary }}</p>
|
||||
<div class="item-footer">
|
||||
<span class="item-date">{{ item.publishDate }}</span>
|
||||
<span class="item-views">{{ item.views }} 阅读</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const interpretations = ref<any[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
// TODO: 加载政策解读数据
|
||||
});
|
||||
|
||||
function viewInterpretation(item: any) {
|
||||
router.push(`/resource/interpretation/${item.id}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.policy-interpretation {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.interpretation-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.interpretation-item {
|
||||
padding: 24px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
border-color: #C62828;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.item-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
background: #ffe6e6;
|
||||
color: #C62828;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.item-summary {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.item-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
<template>
|
||||
<div class="red-classic">
|
||||
<h2 class="page-title">红色经典</h2>
|
||||
<div class="classic-grid">
|
||||
<div class="classic-item" v-for="item in classics" :key="item.id" @click="viewClassic(item)">
|
||||
<div class="classic-cover">
|
||||
<img :src="item.cover" :alt="item.title" />
|
||||
<div class="classic-type">{{ item.type }}</div>
|
||||
</div>
|
||||
<div class="classic-info">
|
||||
<h3>{{ item.title }}</h3>
|
||||
<p class="classic-author">{{ item.author }}</p>
|
||||
<p class="classic-description">{{ item.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const classics = ref<any[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
// TODO: 加载红色经典数据
|
||||
});
|
||||
|
||||
function viewClassic(item: any) {
|
||||
router.push(`/resource/classic/${item.id}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.red-classic {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.classic-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.classic-item {
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
}
|
||||
|
||||
.classic-cover {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.classic-type {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 12px;
|
||||
padding: 4px 12px;
|
||||
background: rgba(198, 40, 40, 0.9);
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.classic-info {
|
||||
margin-top: 12px;
|
||||
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.classic-author {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.classic-description {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,104 +1,149 @@
|
||||
<template>
|
||||
<div class="resource-center-page">
|
||||
<!-- 导航栏 -->
|
||||
<div class="resource-nav">
|
||||
<div
|
||||
class="nav-tab"
|
||||
v-for="tab in tabs"
|
||||
:key="tab.key"
|
||||
:class="{ active: activeTab === tab.key }"
|
||||
@click="activeTab = tab.key"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</div>
|
||||
<div class="resource-center-view">
|
||||
<ResourceHead
|
||||
:category-name="currentCategoryName"
|
||||
:show-article-mode="showArticle"
|
||||
/>
|
||||
<div class="search-wrapper">
|
||||
<Search @search="handleSearch" />
|
||||
</div>
|
||||
|
||||
<!-- 内容区域 -->
|
||||
<div class="resource-content">
|
||||
<component :is="currentComponent" />
|
||||
<div class="content-wrapper">
|
||||
<div class="content-container">
|
||||
<ResourceSideBar
|
||||
:active-category-id="currentCategoryId"
|
||||
@category-change="handleCategoryChange"
|
||||
/>
|
||||
<ResourceList
|
||||
v-show="!showArticle"
|
||||
ref="resourceListRef"
|
||||
:category-id="currentCategoryId"
|
||||
:search-keyword="searchKeyword"
|
||||
@resource-click="handleResourceClick"
|
||||
@list-updated="handleListUpdated"
|
||||
/>
|
||||
<ResourceArticle
|
||||
v-show="showArticle"
|
||||
:resource-id="currentResourceId"
|
||||
:category-id="currentCategoryId"
|
||||
:resource-list="resourceList"
|
||||
@resource-change="handleResourceChange"
|
||||
@navigate="handleArticleNavigate"
|
||||
@back-to-list="backToList"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import MediaArchive from './components/MediaArchive.vue';
|
||||
import PartyHistoryLearning from './PartyHistoryLearningView.vue';
|
||||
import PolicySpeech from './components/PolicySpeech.vue';
|
||||
import PolicyInterpretation from './PolicyInterpretationView.vue';
|
||||
import RedClassic from './RedClassicView.vue';
|
||||
import SpecialReport from './SpecialReportView.vue';
|
||||
import WorldCase from './WorldCaseView.vue';
|
||||
import { ref } from 'vue';
|
||||
import { ResourceHead, ResourceSideBar, ResourceList, ResourceArticle } from './components';
|
||||
import { Search } from '@/components/base';
|
||||
import type { Resource, ResourceCategory } from '@/types/resource';
|
||||
|
||||
const activeTab = ref('media');
|
||||
const showArticle = ref(false);
|
||||
const currentCategoryId = ref('party_history');
|
||||
const currentCategoryName = ref('党史学习');
|
||||
const currentResourceId = ref('');
|
||||
const searchKeyword = ref('');
|
||||
const resourceListRef = ref();
|
||||
const resourceList = ref<Resource[]>([]);
|
||||
|
||||
const tabs = [
|
||||
{ key: 'media', label: '媒体档案' },
|
||||
{ key: 'party-history', label: '党史学习' },
|
||||
{ key: 'policy-speech', label: '政策讲话' },
|
||||
{ key: 'policy-interpretation', label: '政策解读' },
|
||||
{ key: 'red-classic', label: '红色经典' },
|
||||
{ key: 'special-report', label: '专题报告' },
|
||||
{ key: 'world-case', label: '世界案例' }
|
||||
];
|
||||
function handleCategoryChange(category: ResourceCategory) {
|
||||
currentCategoryId.value = category.categoryID || '';
|
||||
currentCategoryName.value = category.name || '';
|
||||
searchKeyword.value = '';
|
||||
showArticle.value = false;
|
||||
}
|
||||
|
||||
const componentMap: Record<string, any> = {
|
||||
'media': MediaArchive,
|
||||
'party-history': PartyHistoryLearning,
|
||||
'policy-speech': PolicySpeech,
|
||||
'policy-interpretation': PolicyInterpretation,
|
||||
'red-classic': RedClassic,
|
||||
'special-report': SpecialReport,
|
||||
'world-case': WorldCase
|
||||
};
|
||||
function handleSearch(keyword: string) {
|
||||
searchKeyword.value = keyword;
|
||||
showArticle.value = false;
|
||||
}
|
||||
|
||||
const currentComponent = computed(() => componentMap[activeTab.value]);
|
||||
function handleListUpdated(list: Resource[]) {
|
||||
resourceList.value = list;
|
||||
}
|
||||
|
||||
function handleResourceClick(resource: Resource) {
|
||||
currentResourceId.value = resource.resourceID || '';
|
||||
showArticle.value = true;
|
||||
}
|
||||
|
||||
function handleResourceChange(resourceId: string) {
|
||||
currentResourceId.value = resourceId;
|
||||
// ArticleShowView 会自动重新加载
|
||||
}
|
||||
|
||||
function backToList() {
|
||||
showArticle.value = false;
|
||||
currentResourceId.value = '';
|
||||
}
|
||||
|
||||
// 文章内前后切换时,靠近列表头部或尾部触发列表翻页
|
||||
async function handleArticleNavigate(direction: 'prev' | 'next', resourceId: string) {
|
||||
const list = resourceListRef.value?.getResources?.() || [];
|
||||
const index = list.findIndex((r: any) => r.resourceID === resourceId);
|
||||
if (index === -1) return;
|
||||
const nearHead = index <= 2;
|
||||
const nearTail = index >= list.length - 3;
|
||||
if (nearHead && direction === 'prev') {
|
||||
await resourceListRef.value?.loadPrevPage?.();
|
||||
} else if (nearTail && direction === 'next') {
|
||||
await resourceListRef.value?.loadNextPage?.();
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.resource-center-page {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
<style scoped lang="scss">
|
||||
.resource-center-view {
|
||||
background: #F9F9F9;
|
||||
}
|
||||
|
||||
.resource-nav {
|
||||
background: white;
|
||||
padding: 0 40px;
|
||||
.search-wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
border-bottom: 1px solid #e0e0e0;
|
||||
}
|
||||
|
||||
.nav-tab {
|
||||
padding: 16px 24px;
|
||||
cursor: pointer;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
position: relative;
|
||||
transition: all 0.3s;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
|
||||
&:hover {
|
||||
color: #C62828;
|
||||
}
|
||||
|
||||
&.active {
|
||||
color: #C62828;
|
||||
font-weight: 600;
|
||||
:deep(.resource-search) {
|
||||
width: 1200px;
|
||||
height: 60px;
|
||||
padding: 0;
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 3px;
|
||||
background: #C62828;
|
||||
.search-box {
|
||||
height: 60px;
|
||||
|
||||
input {
|
||||
font-size: 20px;
|
||||
padding: 0 100px 0 40px;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
width: 72px;
|
||||
height: 60px;
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.resource-content {
|
||||
padding: 20px;
|
||||
.content-wrapper {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-bottom: 60px;
|
||||
}
|
||||
|
||||
.content-container {
|
||||
width: 1200px;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 24px;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
<template>
|
||||
<div class="special-report">
|
||||
<h2 class="page-title">专题报告</h2>
|
||||
<div class="report-list">
|
||||
<div class="report-item" v-for="report in reports" :key="report.id" @click="viewReport(report)">
|
||||
<div class="report-banner">
|
||||
<img :src="report.banner" :alt="report.title" />
|
||||
</div>
|
||||
<div class="report-content">
|
||||
<h3>{{ report.title }}</h3>
|
||||
<div class="report-tags">
|
||||
<span class="tag" v-for="tag in report.tags" :key="tag">{{ tag }}</span>
|
||||
</div>
|
||||
<p class="report-summary">{{ report.summary }}</p>
|
||||
<div class="report-footer">
|
||||
<span class="report-speaker">主讲人:{{ report.speaker }}</span>
|
||||
<span class="report-date">{{ report.date }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const reports = ref<any[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
// TODO: 加载专题报告数据
|
||||
});
|
||||
|
||||
function viewReport(report: any) {
|
||||
router.push(`/resource/report/${report.id}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.special-report {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.report-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.report-item {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
padding: 20px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
border-color: #C62828;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.report-banner {
|
||||
width: 320px;
|
||||
height: 200px;
|
||||
flex-shrink: 0;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.report-content {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
h3 {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.report-tags {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 4px 12px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.report-summary {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.report-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: 16px;
|
||||
margin-top: 16px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,181 +0,0 @@
|
||||
<template>
|
||||
<div class="world-case">
|
||||
<h2 class="page-title">世界案例</h2>
|
||||
|
||||
<!-- 地区筛选 -->
|
||||
<div class="region-filter">
|
||||
<div
|
||||
class="region-tab"
|
||||
v-for="region in regions"
|
||||
:key="region.id"
|
||||
:class="{ active: activeRegion === region.id }"
|
||||
@click="activeRegion = region.id"
|
||||
>
|
||||
{{ region.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 案例列表 -->
|
||||
<div class="case-grid">
|
||||
<div class="case-item" v-for="caseItem in cases" :key="caseItem.id" @click="viewCase(caseItem)">
|
||||
<div class="case-image">
|
||||
<img :src="caseItem.image" :alt="caseItem.title" />
|
||||
<div class="case-country">{{ caseItem.country }}</div>
|
||||
</div>
|
||||
<div class="case-content">
|
||||
<h3>{{ caseItem.title }}</h3>
|
||||
<p class="case-summary">{{ caseItem.summary }}</p>
|
||||
<div class="case-footer">
|
||||
<span class="case-category">{{ caseItem.category }}</span>
|
||||
<span class="case-date">{{ caseItem.publishDate }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const activeRegion = ref('all');
|
||||
const cases = ref<any[]>([]);
|
||||
|
||||
const regions = [
|
||||
{ id: 'all', name: '全部' },
|
||||
{ id: 'asia', name: '亚洲' },
|
||||
{ id: 'europe', name: '欧洲' },
|
||||
{ id: 'americas', name: '美洲' },
|
||||
{ id: 'africa', name: '非洲' },
|
||||
{ id: 'oceania', name: '大洋洲' }
|
||||
];
|
||||
|
||||
onMounted(() => {
|
||||
// TODO: 加载世界案例数据
|
||||
});
|
||||
|
||||
function viewCase(caseItem: any) {
|
||||
router.push(`/resource/case/${caseItem.id}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.world-case {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.region-filter {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.region-tab {
|
||||
padding: 8px 20px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
background: #ffe6e6;
|
||||
color: #C62828;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #C62828;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.case-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.case-item {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
border-color: #C62828;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.case-image {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
background: #f5f5f5;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.case-country {
|
||||
position: absolute;
|
||||
bottom: 12px;
|
||||
right: 12px;
|
||||
padding: 4px 12px;
|
||||
background: rgba(198, 40, 40, 0.9);
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.case-content {
|
||||
padding: 20px;
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.case-summary {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.case-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid #f0f0f0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.case-category {
|
||||
color: #C62828;
|
||||
}
|
||||
|
||||
.case-date {
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
<template>
|
||||
<div class="media-archive">
|
||||
<h2 class="page-title">媒体档案</h2>
|
||||
<div class="media-grid">
|
||||
<div class="media-item" v-for="item in mediaList" :key="item.id" @click="viewMedia(item)">
|
||||
<div class="media-thumbnail">
|
||||
<img :src="item.thumbnail" :alt="item.title" />
|
||||
<div class="media-type">{{ item.type }}</div>
|
||||
</div>
|
||||
<div class="media-info">
|
||||
<h3>{{ item.title }}</h3>
|
||||
<p class="media-description">{{ item.description }}</p>
|
||||
<div class="media-meta">
|
||||
<span>{{ item.publishDate }}</span>
|
||||
<span>{{ item.views }} 观看</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const mediaList = ref<any[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
// TODO: 加载媒体档案数据
|
||||
});
|
||||
|
||||
function viewMedia(item: any) {
|
||||
router.push(`/resource/media/${item.id}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.media-archive {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.media-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.media-item {
|
||||
cursor: pointer;
|
||||
transition: transform 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
}
|
||||
|
||||
.media-thumbnail {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 200px;
|
||||
background: #f5f5f5;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.media-type {
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
right: 12px;
|
||||
padding: 4px 12px;
|
||||
background: rgba(198, 40, 40, 0.9);
|
||||
color: white;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.media-info {
|
||||
margin-top: 12px;
|
||||
|
||||
h3 {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.media-description {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.media-meta {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
<template>
|
||||
<div class="policy-speech">
|
||||
<h2 class="page-title">政策讲话</h2>
|
||||
<div class="speech-list">
|
||||
<div class="speech-item" v-for="speech in speeches" :key="speech.id" @click="viewSpeech(speech)">
|
||||
<div class="speech-header">
|
||||
<h3>{{ speech.title }}</h3>
|
||||
<span class="speech-date">{{ speech.date }}</span>
|
||||
</div>
|
||||
<div class="speech-info">
|
||||
<span class="speaker">讲话人:{{ speech.speaker }}</span>
|
||||
<span class="occasion">场合:{{ speech.occasion }}</span>
|
||||
</div>
|
||||
<p class="speech-summary">{{ speech.summary }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const speeches = ref<any[]>([]);
|
||||
|
||||
onMounted(() => {
|
||||
// TODO: 加载政策讲话数据
|
||||
});
|
||||
|
||||
function viewSpeech(speech: any) {
|
||||
router.push(`/resource/speech/${speech.id}`);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.policy-speech {
|
||||
background: white;
|
||||
padding: 40px;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.speech-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.speech-item {
|
||||
padding: 24px;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
border-color: #C62828;
|
||||
box-shadow: 0 2px 8px rgba(198, 40, 40, 0.1);
|
||||
}
|
||||
}
|
||||
|
||||
.speech-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
|
||||
h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #141F38;
|
||||
}
|
||||
}
|
||||
|
||||
.speech-date {
|
||||
font-size: 14px;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.speech-info {
|
||||
display: flex;
|
||||
gap: 24px;
|
||||
margin-bottom: 12px;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.speech-summary {
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<template>
|
||||
<div class="resource-bottom">
|
||||
<div class="separator"></div>
|
||||
<div class="nav-link" :class="{ disabled: !prevResource }" @click="handleNavigate('prev')">
|
||||
<span class="nav-label">上一篇:</span>
|
||||
<span class="nav-title">{{ prevResource?.title || '没有了' }}</span>
|
||||
</div>
|
||||
<div class="nav-link" :class="{ disabled: !nextResource }" @click="handleNavigate('next')">
|
||||
<span class="nav-label">下一篇:</span>
|
||||
<span class="nav-title">{{ nextResource?.title || '没有了' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Resource } from '@/types/resource';
|
||||
|
||||
interface Props {
|
||||
prevResource?: Resource | null;
|
||||
nextResource?: Resource | null;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
navigate: [direction: 'prev' | 'next', resource: Resource];
|
||||
}>();
|
||||
|
||||
function handleNavigate(direction: 'prev' | 'next') {
|
||||
const resource = direction === 'prev' ? props.prevResource : props.nextResource;
|
||||
if (resource) {
|
||||
emit('navigate', direction, resource);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.resource-bottom {
|
||||
margin-top: 80px;
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.separator {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
background: #E9E9E9;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #334155;
|
||||
margin-bottom: 14px;
|
||||
cursor: pointer;
|
||||
transition: color 0.3s;
|
||||
|
||||
&:hover:not(.disabled) {
|
||||
color: #C62828;
|
||||
|
||||
.nav-title {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.nav-label {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.nav-title {
|
||||
color: inherit;
|
||||
transition: text-decoration 0.3s;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<template>
|
||||
<div class="resource-collect-btn">
|
||||
<button
|
||||
class="collect-button"
|
||||
:class="{ collected: isCollected }"
|
||||
@click="handleCollect"
|
||||
>
|
||||
<img src="@/assets/imgs/star-icon.svg" alt="collect" />
|
||||
<span>{{ isCollected ? '已收藏' : '收藏' }}</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
isCollected: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
|
||||
const emit = defineEmits<{
|
||||
collect: [];
|
||||
uncollect: [];
|
||||
}>();
|
||||
|
||||
function handleCollect() {
|
||||
if(props.isCollected) {
|
||||
emit('uncollect');
|
||||
} else {
|
||||
emit('collect');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.resource-collect-btn {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 80px 0;
|
||||
}
|
||||
|
||||
.collect-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
padding: 10px 20px;
|
||||
background: #FFFFFF;
|
||||
border: 1px solid #979797;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
transition: filter 0.3s;
|
||||
}
|
||||
|
||||
span {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
color: #979797;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: #C62828;
|
||||
|
||||
span {
|
||||
color: #C62828;
|
||||
}
|
||||
|
||||
img {
|
||||
filter: brightness(0) saturate(100%) invert(17%) sepia(85%) saturate(3207%) hue-rotate(349deg) brightness(92%) contrast(92%);
|
||||
}
|
||||
}
|
||||
|
||||
&.collected {
|
||||
border-color: #C62828;
|
||||
background: #FFF5F5;
|
||||
|
||||
span {
|
||||
color: #C62828;
|
||||
}
|
||||
|
||||
img {
|
||||
filter: brightness(0) saturate(100%) invert(17%) sepia(85%) saturate(3207%) hue-rotate(349deg) brightness(92%) contrast(92%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
<template>
|
||||
<div class="resource-article">
|
||||
<div class="page-header">
|
||||
<el-button @click="handleBack" :icon="ArrowLeft">返回</el-button>
|
||||
</div>
|
||||
<ArticleShowView
|
||||
v-if="articleData"
|
||||
:as-dialog="false"
|
||||
:article-data="articleData"
|
||||
:category-list="[]"
|
||||
/>
|
||||
<div v-else class="loading">加载中...</div>
|
||||
<ResouceCollect
|
||||
:is-collected="isCollected"
|
||||
@collect="handleCollect"
|
||||
@uncollect="handleUncollect"
|
||||
/>
|
||||
<ResouceBottom
|
||||
:prev-resource="prevResource"
|
||||
:next-resource="nextResource"
|
||||
@navigate="handleNavigate"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
import { ArticleShowView } from '@/views/article';
|
||||
import { ResouceCollect, ResouceBottom } from '@/views/resource-center/components';
|
||||
import { resourceApi } from '@/apis/resource';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import type { Resource } from '@/types/resource';
|
||||
import { ArrowLeft } from '@element-plus/icons-vue';
|
||||
|
||||
interface Props {
|
||||
resourceId?: string;
|
||||
categoryId?: string;
|
||||
resourceList?: Resource[];
|
||||
}
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'resource-change': [resourceId: string];
|
||||
'back-to-list': [];
|
||||
'navigate': [direction: 'prev' | 'next', resourceId: string];
|
||||
}>();
|
||||
|
||||
const articleData = ref<any>(null);
|
||||
const isCollected = ref(false);
|
||||
const prevResource = ref<Resource | null>(null);
|
||||
const nextResource = ref<Resource | null>(null);
|
||||
|
||||
watch(() => props.resourceId, (newId) => {
|
||||
if (newId) {
|
||||
// 进入加载前先置空,避免子组件读取到 null 字段
|
||||
articleData.value = null;
|
||||
loadArticle(newId);
|
||||
updateAdjacentResources(newId);
|
||||
}
|
||||
}, { immediate: true });
|
||||
|
||||
watch(() => props.resourceList, () => {
|
||||
if (props.resourceId) {
|
||||
updateAdjacentResources(props.resourceId);
|
||||
}
|
||||
}, { deep: true });
|
||||
|
||||
async function loadArticle(resourceId: string) {
|
||||
try {
|
||||
const res = await resourceApi.getResourceById(resourceId);
|
||||
if (res.success && res.data) {
|
||||
const resourceVO = res.data;
|
||||
articleData.value = {
|
||||
...resourceVO.resource,
|
||||
tags: resourceVO.tags || []
|
||||
};
|
||||
} else {
|
||||
ElMessage.error('加载文章失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载文章失败:', error);
|
||||
ElMessage.error('加载文章失败');
|
||||
}
|
||||
}
|
||||
|
||||
function updateAdjacentResources(currentResourceId: string) {
|
||||
if (!props.resourceList || props.resourceList.length === 0) {
|
||||
prevResource.value = null;
|
||||
nextResource.value = null;
|
||||
return;
|
||||
}
|
||||
|
||||
const currentIndex = props.resourceList.findIndex(r =>
|
||||
String(r.resourceID) === String(currentResourceId)
|
||||
);
|
||||
|
||||
if (currentIndex !== -1) {
|
||||
prevResource.value = currentIndex > 0 ? props.resourceList[currentIndex - 1] : null;
|
||||
nextResource.value = currentIndex < props.resourceList.length - 1 ? props.resourceList[currentIndex + 1] : null;
|
||||
} else {
|
||||
prevResource.value = null;
|
||||
nextResource.value = null;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleCollect() {
|
||||
try {
|
||||
const resourceID = articleData.value?.resourceID;
|
||||
if (!resourceID) return;
|
||||
|
||||
const res = await resourceApi.incrementCollectCount(resourceID);
|
||||
if (res.success) {
|
||||
isCollected.value = true;
|
||||
ElMessage.success('收藏成功');
|
||||
} else {
|
||||
ElMessage.error('收藏失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('收藏失败:', error);
|
||||
ElMessage.error('收藏失败');
|
||||
}
|
||||
}
|
||||
|
||||
function handleUncollect() {
|
||||
// TODO: 实现取消收藏API
|
||||
isCollected.value = false;
|
||||
ElMessage.success('已取消收藏');
|
||||
}
|
||||
|
||||
function handleNavigate(direction: 'prev' | 'next', resource: Resource) {
|
||||
const resourceId = resource.resourceID;
|
||||
if (resourceId) {
|
||||
emit('resource-change', resourceId);
|
||||
emit('navigate', direction, resourceId);
|
||||
}
|
||||
}
|
||||
|
||||
// 返回列表
|
||||
function handleBack() {
|
||||
emit('back-to-list');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #303133;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
.resource-article {
|
||||
flex: 1;
|
||||
background: #FFFFFF;
|
||||
border-radius: 10px;
|
||||
padding: 40px 60px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,90 @@
|
||||
<template>
|
||||
<div class="resource-head">
|
||||
<div class="head-title">资源中心</div>
|
||||
<div class="breadcrumb">
|
||||
<span class="breadcrumb-label">当前位置:</span>
|
||||
<div class="breadcrumb-nav">
|
||||
<span class="breadcrumb-item">资源中心</span>
|
||||
<span class="separator">/</span>
|
||||
<span class="breadcrumb-item">{{ categoryName }}</span>
|
||||
<span class="separator" v-if="showArticleMode">/</span>
|
||||
<span class="breadcrumb-item active" v-if="showArticleMode">正文</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface Props {
|
||||
categoryName?: string;
|
||||
showArticleMode?: boolean;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
categoryName: '党史学习',
|
||||
showArticleMode: false
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.resource-head {
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
background: url('@/assets/imgs/resource-head-bg.jpg') center/cover;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.head-title {
|
||||
font-family: 'MiSans VF', 'PingFang SC';
|
||||
font-weight: 630;
|
||||
font-size: 40px;
|
||||
line-height: 1.2;
|
||||
letter-spacing: 0.05em;
|
||||
color: #FFFFFF;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.breadcrumb-label {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #F1F1F1;
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.breadcrumb-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.breadcrumb-item {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
color: #FFFFFF;
|
||||
|
||||
&.active {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
}
|
||||
|
||||
.separator {
|
||||
font-family: 'PingFang SC';
|
||||
font-size: 14px;
|
||||
color: #C9CDD4;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
<template>
|
||||
<div class="resource-list">
|
||||
<div class="list-container" ref="listContainerRef">
|
||||
<div
|
||||
v-for="resource in resources"
|
||||
:key="resource.resourceID "
|
||||
class="resource-item"
|
||||
@click="handleResourceClick(resource)"
|
||||
>
|
||||
<div class="resource-cover">
|
||||
<img
|
||||
:src="resource.coverImage ? (FILE_DOWNLOAD_URL + resource.coverImage) : defaultArticleImg"
|
||||
alt="cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="resource-info">
|
||||
<h3 class="resource-title">{{ resource.title }}</h3>
|
||||
<div class="resource-collect">
|
||||
<img src="@/assets/imgs/star-icon.svg" alt="collect" />
|
||||
<span>{{ (resource.collectCount || 0) > 0 ? '已收藏' : '收藏' }}</span>
|
||||
</div>
|
||||
<p class="resource-summary">{{ resource.summary || '暂无简介' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="loading" class="loading-more">加载中...</div>
|
||||
<div v-if="resources.length === 0 && !loading" class="empty">暂无数据</div>
|
||||
</div>
|
||||
|
||||
<div v-if="total > 0" class="pagination-wrapper">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
:page-size="pageSize"
|
||||
:total="total"
|
||||
layout="prev, pager, next, jumper"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, nextTick } from 'vue';
|
||||
import { resourceApi } from '@/apis/resource';
|
||||
import { FILE_DOWNLOAD_URL } from '@/config';
|
||||
import type { Resource, ResourceSearchParams } from '@/types/resource';
|
||||
import type { PageParam } from '@/types';
|
||||
import defaultArticleImg from '@/assets/imgs/article-default.png';
|
||||
|
||||
interface Props {
|
||||
categoryId?: string;
|
||||
searchKeyword?: string;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
'resource-click': [resource: Resource];
|
||||
'list-updated': [resources: Resource[]];
|
||||
}>();
|
||||
|
||||
const resources = ref<Resource[]>([]);
|
||||
const loading = ref(false);
|
||||
const total = ref(0);
|
||||
const currentPage = ref(1);
|
||||
const pageSize = 10;
|
||||
const listContainerRef = ref<HTMLElement>();
|
||||
|
||||
onMounted(() => {
|
||||
loadResources();
|
||||
});
|
||||
|
||||
watch(() => [props.categoryId, props.searchKeyword], () => {
|
||||
currentPage.value = 1;
|
||||
loadResources();
|
||||
}, { deep: true });
|
||||
|
||||
async function loadResources() {
|
||||
if (loading.value) return;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
const filter: ResourceSearchParams = {
|
||||
categoryID: props.categoryId,
|
||||
keyword: props.searchKeyword,
|
||||
// status: 1 // 只加载已发布的
|
||||
};
|
||||
|
||||
const pageParam: PageParam = {
|
||||
page: currentPage.value,
|
||||
size: pageSize
|
||||
};
|
||||
|
||||
const res = await resourceApi.getResourcePage(pageParam, filter);
|
||||
|
||||
if (res.success && res.dataList) {
|
||||
resources.value = res.dataList;
|
||||
total.value = res.pageDomain?.total || 0;
|
||||
|
||||
// 通知父组件列表已更新
|
||||
emit('list-updated', res.dataList);
|
||||
|
||||
// 重置滚动位置到顶部
|
||||
await nextTick();
|
||||
if (listContainerRef.value) {
|
||||
listContainerRef.value.scrollTop = 0;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载资源列表失败:', error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
currentPage.value = page;
|
||||
loadResources();
|
||||
}
|
||||
|
||||
function getResources() {
|
||||
return resources.value;
|
||||
}
|
||||
|
||||
function getPageInfo() {
|
||||
return { currentPage: currentPage.value, total: total.value };
|
||||
}
|
||||
|
||||
async function loadNextPage() {
|
||||
const totalPages = Math.ceil(total.value / pageSize);
|
||||
if (currentPage.value < totalPages) {
|
||||
currentPage.value++;
|
||||
await loadResources();
|
||||
}
|
||||
}
|
||||
|
||||
async function loadPrevPage() {
|
||||
if (currentPage.value > 1) {
|
||||
currentPage.value--;
|
||||
await loadResources();
|
||||
}
|
||||
}
|
||||
|
||||
function handleResourceClick(resource: Resource) {
|
||||
emit('resource-click', resource);
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
loadResources,
|
||||
getResources,
|
||||
getPageInfo,
|
||||
loadNextPage,
|
||||
loadPrevPage
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.resource-list {
|
||||
flex: 1;
|
||||
align-self: stretch;
|
||||
background: #FFFFFF;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.list-container {
|
||||
flex: 1;
|
||||
padding: 30px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.resource-item {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
padding-bottom: 30px;
|
||||
border-bottom: 1px solid #EEEEEE;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
.resource-title {
|
||||
color: #C62828;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.resource-cover {
|
||||
width: 202px;
|
||||
height: 123px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
flex-shrink: 0;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.default-cover {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
}
|
||||
}
|
||||
|
||||
.resource-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.resource-title {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 600;
|
||||
font-size: 28px;
|
||||
line-height: 28px;
|
||||
color: #C62828;
|
||||
margin: 0;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.resource-collect {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
|
||||
img {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
span {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 14px;
|
||||
line-height: 24px;
|
||||
color: #979797;
|
||||
}
|
||||
}
|
||||
|
||||
.resource-summary {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 400;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #334155;
|
||||
margin: 8px 0 0 0;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.loading-more,
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
font-family: 'PingFang SC';
|
||||
font-size: 14px;
|
||||
color: #979797;
|
||||
}
|
||||
|
||||
.pagination-wrapper {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 30px 0;
|
||||
background: #FFFFFF;
|
||||
border-top: 1px solid #EEEEEE;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div class="resource-sidebar">
|
||||
<div class="sidebar-content">
|
||||
<div
|
||||
v-for="category in categories"
|
||||
:key="category.categoryID"
|
||||
class="sidebar-item"
|
||||
:class="{ active: category.categoryID === activeCategoryId }"
|
||||
@click="handleCategoryClick(category)"
|
||||
>
|
||||
<span class="category-name">{{ category.name }}</span>
|
||||
<div v-if="category.categoryID === activeCategoryId" class="active-overlay"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { resourceCategoryApi } from '@/apis/resource';
|
||||
import type { ResourceCategory } from '@/types/resource';
|
||||
|
||||
interface Props {
|
||||
activeCategoryId?: string;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
activeCategoryId: 'party_history'
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
'category-change': [category: ResourceCategory];
|
||||
}>();
|
||||
|
||||
const categories = ref<ResourceCategory[]>([]);
|
||||
|
||||
onMounted(async () => {
|
||||
await loadCategories();
|
||||
});
|
||||
|
||||
async function loadCategories() {
|
||||
try {
|
||||
const res = await resourceCategoryApi.getCategoryList();
|
||||
if (res.success && res.dataList) {
|
||||
categories.value = res.dataList;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载分类失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function handleCategoryClick(category: ResourceCategory) {
|
||||
emit('category-change', category);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.resource-sidebar {
|
||||
width: 180px;
|
||||
background: #FFFFFF;
|
||||
border-radius: 10px;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.sidebar-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.sidebar-item {
|
||||
position: relative;
|
||||
height: 54px;
|
||||
padding: 0 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
|
||||
&:hover {
|
||||
.category-name {
|
||||
color: #C62828;
|
||||
}
|
||||
}
|
||||
|
||||
&.active {
|
||||
.category-name {
|
||||
color: #FFFFFF;
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.active-overlay {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
left: 10px;
|
||||
right: 10px;
|
||||
bottom: 5px;
|
||||
background: #C62828;
|
||||
border-radius: 8px;
|
||||
z-index: 1;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.category-name {
|
||||
font-family: 'PingFang SC';
|
||||
font-weight: 500;
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
color: #334155;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
export { default as ResourceHead } from './ResourceHead.vue';
|
||||
export { default as ResourceSideBar } from './ResourceSideBar.vue';
|
||||
export { default as ResourceList } from './ResourceList.vue';
|
||||
export { default as ResourceArticle } from './ResourceArticle.vue';
|
||||
export { default as ResouceCollect } from './ResouceCollect.vue';
|
||||
export { default as ResouceBottom } from './ResouceBottom.vue';
|
||||
Reference in New Issue
Block a user