完善资源详情页
This commit is contained in:
parent
5c21f20126
commit
46fc7b2739
445
frontend/src/views/components/ResourceList.vue
Normal file
445
frontend/src/views/components/ResourceList.vue
Normal file
@ -0,0 +1,445 @@
|
||||
<template>
|
||||
<div class="resources-page">
|
||||
<div class="container">
|
||||
<div class="top-content">
|
||||
<h1 class="page-title">{{ config.title }}</h1>
|
||||
<div class="content flex flex-column align-items-center">
|
||||
<p>{{ config.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<!-- 左侧分类导航 -->
|
||||
<div class="left-menu">
|
||||
<div class="menu-title">分类导航</div>
|
||||
<ul class="category-list">
|
||||
<li
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
class="category-item"
|
||||
:class="{ active: selectedCategory === category.id }"
|
||||
@click="selectCategory(category.id)"
|
||||
>
|
||||
{{ category.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容区域 -->
|
||||
<div class="contents">
|
||||
<div v-if="loading" class="loading">加载中...</div>
|
||||
<div v-else-if="resources.length === 0" class="no-data">暂无内容</div>
|
||||
<div v-else class="resources-grid">
|
||||
<div
|
||||
v-for="resource in resources"
|
||||
:key="resource.id"
|
||||
class="resource-card"
|
||||
>
|
||||
<div class="resource-image">
|
||||
<img :src="getImageUrl(resource.icon)" :alt="resource.title" />
|
||||
</div>
|
||||
<div class="card-header">
|
||||
<router-link
|
||||
:to="`/resource?id=${resource.id}&path=${config.routePath}&category=${encodeURIComponent(currentCategoryName)}`"
|
||||
class="resource-title-link"
|
||||
>
|
||||
<h3 class="resource-title">{{ resource.title }}</h3>
|
||||
</router-link>
|
||||
<div class="resource-meta">
|
||||
<span class="resource-view">
|
||||
<i class="fa-solid fa-eye"></i>
|
||||
{{ resource.view || 0 }}
|
||||
</span>
|
||||
<span class="resource-likes">
|
||||
<i class="fas fa-download"></i>
|
||||
{{ resource.likes || 0 }}
|
||||
</span>
|
||||
<span class="resource-date">{{
|
||||
formatDate(resource.create_time)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<div v-if="total > 0" class="pagination-wrapper">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handlePageSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
// Props定义
|
||||
interface ResourceConfig {
|
||||
title: string;
|
||||
description: string;
|
||||
routePath: string;
|
||||
api: {
|
||||
getCategory: () => Promise<any>;
|
||||
getSimpleLists: (cateid: string) => Promise<any>;
|
||||
};
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
config: ResourceConfig;
|
||||
}>();
|
||||
|
||||
// 响应式数据
|
||||
const categories = ref<any[]>([]); // 分类列表
|
||||
const resources = ref<any[]>([]); // 文章列表
|
||||
const selectedCategory = ref<string>(""); // 选中的分类
|
||||
const loading = ref(false); // 加载状态
|
||||
const currentPage = ref(1); // 当前页码
|
||||
const pageSize = ref(10); // 每页条数
|
||||
const total = ref(0); // 总条数
|
||||
|
||||
// 当前选中分类的名称
|
||||
const currentCategoryName = computed(() => {
|
||||
const category = categories.value.find(cat => cat.id === selectedCategory.value);
|
||||
return category?.name || '';
|
||||
});
|
||||
|
||||
// 获取图片地址
|
||||
const getImageUrl = (imagePath: string) => {
|
||||
if (!imagePath) return "/src/assets/imgs/default.png";
|
||||
return import.meta.env.VITE_API_DOMAIN + imagePath;
|
||||
};
|
||||
|
||||
// 获取分类列表
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
const response: any = await props.config.api.getCategory();
|
||||
|
||||
if (
|
||||
response.data &&
|
||||
response.data.data &&
|
||||
Array.isArray(response.data.data)
|
||||
) {
|
||||
categories.value = response.data.data;
|
||||
// 默认选择第一个分类
|
||||
if (categories.value.length > 0) {
|
||||
selectedCategory.value = categories.value[0].id;
|
||||
fetchResources();
|
||||
}
|
||||
} else {
|
||||
categories.value = [];
|
||||
ElMessage.warning(response.data?.msg || response.msg || "获取分类失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取分类失败:", error);
|
||||
categories.value = [];
|
||||
ElMessage.warning("获取分类失败");
|
||||
}
|
||||
};
|
||||
|
||||
// 获取文章列表
|
||||
const fetchResources = async (page: number = currentPage.value) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response: any = await props.config.api.getSimpleLists(selectedCategory.value);
|
||||
|
||||
const data = response.data?.data;
|
||||
if (data?.resources && Array.isArray(data.resources)) {
|
||||
resources.value = data.resources;
|
||||
total.value = data.total || 0;
|
||||
currentPage.value = data.page || 1;
|
||||
pageSize.value = data.limit || 10;
|
||||
} else {
|
||||
resources.value = [];
|
||||
total.value = 0;
|
||||
ElMessage.warning(response.data?.msg || "获取文章失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取文章失败:", error);
|
||||
resources.value = [];
|
||||
total.value = 0;
|
||||
ElMessage.warning("获取文章失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 选择分类
|
||||
const selectCategory = (categoryId: string) => {
|
||||
selectedCategory.value = categoryId;
|
||||
currentPage.value = 1; // 切换分类时重置到第一页
|
||||
fetchResources(1);
|
||||
};
|
||||
|
||||
// 分页处理
|
||||
const handlePageChange = (page: number) => {
|
||||
currentPage.value = page;
|
||||
fetchResources(page);
|
||||
};
|
||||
|
||||
// 每页条数变化
|
||||
const handlePageSizeChange = (size: number) => {
|
||||
pageSize.value = size;
|
||||
currentPage.value = 1; // 重置到第一页
|
||||
fetchResources(1);
|
||||
};
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateTime: string | number) => {
|
||||
if (!dateTime) return "";
|
||||
if (typeof dateTime === "string") {
|
||||
// 如果包含空格,取日期部分
|
||||
return dateTime.split(' ')[0];
|
||||
}
|
||||
// 如果是数字,当作时间戳处理
|
||||
const date = new Date(dateTime < 1e10 ? dateTime * 1000 : dateTime);
|
||||
return date.toLocaleDateString("zh-CN");
|
||||
};
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchCategories();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.resources-page {
|
||||
padding-top: 100px;
|
||||
min-height: 100vh;
|
||||
background: #f9fafc;
|
||||
|
||||
.top-content {
|
||||
background: linear-gradient(135deg, #1e9fff 0%, #0d8aff 100%);
|
||||
padding: 80px 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--white);
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
line-height: 1.6;
|
||||
|
||||
p {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
padding: 40px 0;
|
||||
align-items: flex-start;
|
||||
|
||||
.left-menu {
|
||||
background-color: var(--white);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
width: 250px;
|
||||
flex-shrink: 0;
|
||||
align-self: flex-start;
|
||||
|
||||
.menu-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #1e9fff;
|
||||
}
|
||||
|
||||
.category-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.category-item {
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
border-left: 3px solid transparent;
|
||||
|
||||
&:hover {
|
||||
background: #f0f8ff;
|
||||
border-left-color: #1e9fff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #e6f7ff;
|
||||
border-left-color: #1e9fff;
|
||||
color: #1e9fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contents {
|
||||
background-color: var(--white);
|
||||
// min-height: 400px;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
flex: 1;
|
||||
|
||||
.pagination-wrapper {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
|
||||
.el-pagination {
|
||||
--el-pagination-font-size: 14px;
|
||||
--el-pagination-button-width: 40px;
|
||||
--el-pagination-button-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.loading,
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.resources-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
|
||||
.resource-card {
|
||||
// min-width: 250px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.03);
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.resource-image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 140px;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 16px 16px 12px;
|
||||
|
||||
.resource-title-link {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
|
||||
&:hover .resource-title {
|
||||
color: #007bff;
|
||||
}
|
||||
}
|
||||
|
||||
.resource-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
height: 40px;
|
||||
color: #333;
|
||||
margin: 0 0 8px 0;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
transition: color 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.resource-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
|
||||
.resource-view,
|
||||
.resource-likes,
|
||||
.resource-date {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 0 16px;
|
||||
|
||||
.resource-summary {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 12px 16px 16px;
|
||||
text-align: right;
|
||||
|
||||
.read-more {
|
||||
color: #1e9fff;
|
||||
text-decoration: none;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
color: #0d8aff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@ -1,24 +1,28 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { ref, onMounted, computed, watch } from "vue";
|
||||
import { useRoute, useRouter } from "vue-router";
|
||||
import { ElMessage } from "element-plus";
|
||||
import Header from "@/views/components/header.vue";
|
||||
import Footer from "@/views/components/footer.vue";
|
||||
import { resource as resourceApi } from "@/api/resource";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter(); // 新增:引入useRouter
|
||||
const resourceId = ref(route.query.id as string);
|
||||
const resourceData = ref<any>(null);
|
||||
const prevResource = ref<any>(null);
|
||||
const nextResource = ref<any>(null);
|
||||
const relatedResources = ref<any[]>([]);
|
||||
const loading = ref(true);
|
||||
const showImageModal = ref(false);
|
||||
const modalImageSrc = ref('');
|
||||
const modalImageSrc = ref("");
|
||||
const imageScale = ref(1);
|
||||
const imageRotation = ref(0);
|
||||
|
||||
// 从路由参数获取页面信息
|
||||
const pageInfo = computed(() => ({
|
||||
path: (route.query.path as string) || '',
|
||||
name: (route.query.category as string) || ''
|
||||
path: (route.query.path as string) || "",
|
||||
name: (route.query.category as string) || "",
|
||||
}));
|
||||
|
||||
// 获取图片地址
|
||||
@ -27,17 +31,51 @@ const getImageUrl = (imagePath: string) => {
|
||||
return import.meta.env.VITE_API_DOMAIN + imagePath;
|
||||
};
|
||||
|
||||
// 处理content中的图片URL
|
||||
const processContentImages = (content: string) => {
|
||||
if (!content) return "";
|
||||
const apiDomain = import.meta.env.VITE_API_DOMAIN;
|
||||
|
||||
// 处理图片URL拼接,并将每个图片包装在div中
|
||||
return content.replace(
|
||||
/<img([^>]+)src=["']([^"']+)["']([^>]*?)>/gi,
|
||||
(_match, beforeSrc, src, afterSrc) => {
|
||||
// 如果已经是完整URL(http或https开头),则不处理
|
||||
if (src.startsWith("http://") || src.startsWith("https://")) {
|
||||
return `<div class="content-image-wrapper"><img${beforeSrc}src="${src}"${afterSrc} data-clickable="true" style="width: 100%; height: auto; display: block; box-sizing: border-box; object-fit: fill; transition: opacity 0.3s; cursor: pointer;" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'"></div>`;
|
||||
}
|
||||
// 拼接环境接口
|
||||
const fullSrc = apiDomain + src;
|
||||
return `<div class="content-image-wrapper"><img${beforeSrc}src="${fullSrc}"${afterSrc} data-clickable="true" style="width: 100%; height: auto; display: block; box-sizing: border-box; object-fit: fill; transition: opacity 0.3s; cursor: pointer;" onmouseover="this.style.opacity='0.8'" onmouseout="this.style.opacity='1'"></div>`;
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// 处理content区域的点击事件(用于图片放大)
|
||||
const handleContentClick = (event: Event) => {
|
||||
const target = event.target as HTMLElement;
|
||||
if (target.tagName === "IMG" && target.hasAttribute("data-clickable")) {
|
||||
const imgSrc = target.getAttribute("src");
|
||||
if (imgSrc) {
|
||||
openImageModal(imgSrc);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 下载文件
|
||||
const downloadFile = (downloadPath: string, type: string) => {
|
||||
if (!downloadPath) return ElMessage.warning(`${type}下载链接不存在`);
|
||||
|
||||
const fullUrl = type === '本地' ? import.meta.env.VITE_API_DOMAIN + downloadPath : downloadPath;
|
||||
const fullUrl =
|
||||
type === "本地"
|
||||
? import.meta.env.VITE_API_DOMAIN + downloadPath
|
||||
: downloadPath;
|
||||
|
||||
try {
|
||||
const link = document.createElement('a');
|
||||
const link = document.createElement("a");
|
||||
link.href = fullUrl;
|
||||
link.target = '_blank';
|
||||
link.download = '';
|
||||
link.target = "_blank";
|
||||
link.download = "";
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
@ -62,7 +100,7 @@ const copyShareCode = async () => {
|
||||
textArea.value = code;
|
||||
document.body.appendChild(textArea);
|
||||
textArea.select();
|
||||
document.execCommand('copy');
|
||||
document.execCommand("copy");
|
||||
document.body.removeChild(textArea);
|
||||
ElMessage.success("分享码已复制到剪贴板");
|
||||
}
|
||||
@ -80,14 +118,17 @@ const closeImageModal = () => {
|
||||
};
|
||||
|
||||
const resetImageState = () => {
|
||||
modalImageSrc.value = '';
|
||||
modalImageSrc.value = "";
|
||||
imageScale.value = 1;
|
||||
imageRotation.value = 0;
|
||||
};
|
||||
|
||||
const handleWheel = (event: WheelEvent) => {
|
||||
event.preventDefault();
|
||||
imageScale.value = Math.max(0.1, Math.min(3, imageScale.value + (event.deltaY > 0 ? -0.1 : 0.1)));
|
||||
imageScale.value = Math.max(
|
||||
0.1,
|
||||
Math.min(3, imageScale.value + (event.deltaY > 0 ? -0.1 : 0.1))
|
||||
);
|
||||
};
|
||||
|
||||
const rotateImage = () => {
|
||||
@ -99,58 +140,86 @@ const formatDate = (dateTime: string | number) => {
|
||||
if (!dateTime) return "";
|
||||
if (typeof dateTime === "string") {
|
||||
// 如果包含空格,取日期部分
|
||||
return dateTime.split(' ')[0];
|
||||
return dateTime.split(" ")[0];
|
||||
}
|
||||
// 如果是数字,当作时间戳处理
|
||||
const date = new Date(dateTime < 1e10 ? dateTime * 1000 : dateTime);
|
||||
return date.toLocaleDateString("zh-CN");
|
||||
};
|
||||
|
||||
// 获取资源详情
|
||||
// 获取资源详情(封装成独立函数)
|
||||
const fetchResourceDetail = async () => {
|
||||
if (!resourceId.value) return;
|
||||
|
||||
// 重置状态
|
||||
loading.value = true;
|
||||
try {
|
||||
// 由于所有资源都来自downloadGames页面,统一使用downloadGames API
|
||||
const response:any = await resourceApi.getResourceDetail(resourceId.value);
|
||||
resourceData.value = null;
|
||||
prevResource.value = null;
|
||||
nextResource.value = null;
|
||||
relatedResources.value = [];
|
||||
|
||||
if (response.data?.data) {
|
||||
resourceData.value = response.data.data;
|
||||
try {
|
||||
const response: any = await resourceApi.getResourceDetail(resourceId.value);
|
||||
|
||||
console.log("获取资源详情响应:", response);
|
||||
if (response?.data?.code === 0 && response?.data?.data) {
|
||||
resourceData.value = response.data.data.resource;
|
||||
prevResource.value = response.data.data.prev_resource;
|
||||
nextResource.value = response.data.data.next_resource;
|
||||
relatedResources.value = response.data.data.related_resources || [];
|
||||
console.log("资源数据加载完成:", resourceData.value?.title);
|
||||
} else {
|
||||
ElMessage.warning(response.data?.msg || "获取资源详情失败");
|
||||
console.error("API响应格式错误:", response);
|
||||
ElMessage.error("获取资源详情失败:数据格式错误");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("API请求失败:", error);
|
||||
ElMessage.error("获取资源详情失败,请稍后重试");
|
||||
console.error("获取资源详情失败:", error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
// 滚动到页面顶部
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: "smooth", // 平滑滚动
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 监听路由参数变化
|
||||
watch(
|
||||
() => route.query.id,
|
||||
(newId) => {
|
||||
if (newId) {
|
||||
resourceId.value = newId as string;
|
||||
fetchResourceDetail(); // 重新获取数据
|
||||
}
|
||||
},
|
||||
{ immediate: true } // 立即执行一次
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
fetchResourceDetail();
|
||||
// 移除原有的fetchResourceDetail调用,由watch接管
|
||||
// 页面加载时滚动到顶部
|
||||
window.scrollTo(0, 0);
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Header />
|
||||
<div class="main-container">
|
||||
<div class="container">
|
||||
<div class="content" v-if="resourceData">
|
||||
<div class="content">
|
||||
<div class="top-content">
|
||||
<div class="top-content-main">
|
||||
<div class="top-content-main-title">{{ resourceData.title }}</div>
|
||||
<div class="top-content-main-title">
|
||||
{{ resourceData?.title || "加载中..." }}
|
||||
</div>
|
||||
<div class="location">
|
||||
<el-breadcrumb separator="/">
|
||||
<el-breadcrumb-item :to="{ path: '/' }"
|
||||
>首页</el-breadcrumb-item
|
||||
>
|
||||
<el-breadcrumb-item :to="{ path: pageInfo.path }"
|
||||
>{{ pageInfo.name }}</el-breadcrumb-item
|
||||
>
|
||||
<el-breadcrumb-item :to="{ path: pageInfo.path }">{{
|
||||
pageInfo.name
|
||||
}}</el-breadcrumb-item>
|
||||
<el-breadcrumb-item>详情</el-breadcrumb-item>
|
||||
</el-breadcrumb>
|
||||
</div>
|
||||
@ -162,60 +231,92 @@ onMounted(() => {
|
||||
<div class="card-content-left">
|
||||
<img
|
||||
class="img-cover"
|
||||
:src="getImageUrl(resourceData.icon)"
|
||||
:alt="resourceData.title"
|
||||
@click="openImageModal(getImageUrl(resourceData.icon))"
|
||||
style="cursor: pointer;"
|
||||
:src="getImageUrl(resourceData?.icon)"
|
||||
:alt="resourceData?.title"
|
||||
@click="openImageModal(getImageUrl(resourceData?.icon))"
|
||||
style="cursor: pointer"
|
||||
/>
|
||||
</div>
|
||||
<div class="card-content-right">
|
||||
<div class="top-btns">
|
||||
<button class="btn btn-primary" id="collectBtn"><i class="fa-solid fa-heart"></i> 收藏</button>
|
||||
<button class="btn btn-primary" id="reportBtn" style="margin-left: 20px"><i class="fa-solid fa-flag"></i> 举报</button>
|
||||
<button class="btn btn-primary" id="collectBtn">
|
||||
<i class="fa-solid fa-heart"></i> 收藏
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-primary"
|
||||
id="reportBtn"
|
||||
style="margin-left: 20px"
|
||||
>
|
||||
<i class="fa-solid fa-flag"></i> 举报
|
||||
</button>
|
||||
</div>
|
||||
<div class="resource-info">
|
||||
<div class="title">
|
||||
{{
|
||||
resourceData.price == 0 || !resourceData.price
|
||||
resourceData?.price == 0 || !resourceData?.price
|
||||
? "Free"
|
||||
: "¥" + resourceData.price
|
||||
: "¥" + resourceData?.price
|
||||
}}
|
||||
</div>
|
||||
<div class="infos">
|
||||
<div class="infos-item">
|
||||
<div class="label">更新时间:</div>
|
||||
<div class="value">{{ formatDate(resourceData.update_time || resourceData.create_time) }}</div>
|
||||
<div class="value">
|
||||
{{
|
||||
formatDate(
|
||||
resourceData?.update_time ||
|
||||
resourceData?.create_time
|
||||
)
|
||||
}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="infos-item">
|
||||
<div class="label">所属分类:</div>
|
||||
<div class="value">{{ resourceData.cate }}</div>
|
||||
<div class="value">{{ resourceData?.cate }}</div>
|
||||
</div>
|
||||
|
||||
<div class="infos-item">
|
||||
<div class="label">程序编号:</div>
|
||||
<div class="value">{{ resourceData.number }}</div>
|
||||
<div class="value">{{ resourceData?.number }}</div>
|
||||
</div>
|
||||
|
||||
<div class="infos-item">
|
||||
<div class="label">查看次数:</div>
|
||||
<div class="value">{{ resourceData.views }}</div>
|
||||
<div class="value">{{ resourceData?.views }}</div>
|
||||
</div>
|
||||
<div class="infos-item">
|
||||
<div class="label">下载次数:</div>
|
||||
<div class="value">{{ resourceData.downloads }}</div>
|
||||
<div class="value">{{ resourceData?.downloads }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bottom-btns">
|
||||
<button v-if="resourceData.url" id="netdiskBtn" class="btn btn-primary" @click="downloadFile(resourceData.url, '网盘')">
|
||||
<button
|
||||
v-if="resourceData?.url"
|
||||
id="netdiskBtn"
|
||||
class="btn btn-primary"
|
||||
@click="downloadFile(resourceData?.url, '网盘')"
|
||||
>
|
||||
<i class="fa-solid fa-download"></i> 网盘下载
|
||||
</button>
|
||||
<button v-if="resourceData.fileurl" id="localBtn" class="btn btn-primary" @click="downloadFile(resourceData.fileurl, '本地')">
|
||||
<button
|
||||
v-if="resourceData?.fileurl"
|
||||
id="localBtn"
|
||||
class="btn btn-primary"
|
||||
@click="downloadFile(resourceData?.fileurl, '本地')"
|
||||
>
|
||||
<i class="fa-solid fa-download"></i> 本地下载
|
||||
</button>
|
||||
<button v-if="resourceData.code" id="codeBtn" class="codebtn" @click="copyShareCode">
|
||||
<i class="fa-solid fa-download"></i> 分享码:{{resourceData.code}}
|
||||
<button
|
||||
v-if="resourceData?.code"
|
||||
id="codeBtn"
|
||||
class="codebtn"
|
||||
@click="copyShareCode"
|
||||
>
|
||||
<i class="fa-solid fa-download"></i> 分享码:{{
|
||||
resourceData?.code
|
||||
}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -225,7 +326,124 @@ onMounted(() => {
|
||||
<div class="resource-detail" v-if="resourceData">
|
||||
<div class="resource-detail-title">详情介绍</div>
|
||||
<el-divider />
|
||||
<div class="resource-content" v-html="resourceData.content"></div>
|
||||
<div
|
||||
class="resource-content"
|
||||
v-html="processContentImages(resourceData?.content)"
|
||||
@click="handleContentClick"
|
||||
></div>
|
||||
|
||||
<div class="disclaimers">
|
||||
<div class="disclaimer-item">
|
||||
<div class="disclaimer-title">免责声明:</div>
|
||||
<div class="disclaimer-content">
|
||||
<p>
|
||||
本站非盈利性站点,所有资源仅供学习参考,并不贩卖软件不存在任何商业目的及用途,如果您访问和下载此文件,表示您同意只将此文件用于参考、学习而非其他用途。
|
||||
</p>
|
||||
<p>
|
||||
本站所发布的一切软件资源均来自于互联网仅限用于学习和研究目的;
|
||||
</p>
|
||||
<p>
|
||||
不得将上述内容用于商业或者非法用途,否则一切后果请用户自负;
|
||||
</p>
|
||||
<p>本站所有软件信息来自网络,版权争议与本站无关;</p>
|
||||
<p>
|
||||
您必须在下载后的24个小时之内从您的电脑中彻底删除,如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务;
|
||||
</p>
|
||||
<p>
|
||||
如有侵权请邮件与我们联系处理,我们会及时处理,
|
||||
邮箱:yunzer_cn#163.com(#换成@)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider />
|
||||
<div class="actions">
|
||||
<div style="display: flex; gap: 30px">
|
||||
<!-- 修复:移除多余的} -->
|
||||
<!-- 网盘下载按钮 -->
|
||||
<button
|
||||
v-if="resourceData?.url"
|
||||
id="netdiskBtn"
|
||||
class="btn btn-primary"
|
||||
@click="downloadFile(resourceData?.url, '网盘')"
|
||||
>
|
||||
<i class="fa-solid fa-download"></i>
|
||||
网盘下载
|
||||
</button>
|
||||
|
||||
<!-- 本地下载按钮 -->
|
||||
<button
|
||||
v-if="resourceData?.fileurl"
|
||||
id="localBtn"
|
||||
class="btn btn-primary"
|
||||
@click="downloadFile(resourceData?.fileurl, '本地')"
|
||||
>
|
||||
<i class="fa-solid fa-download"></i>
|
||||
本地下载
|
||||
</button>
|
||||
|
||||
<!-- 分享码按钮 -->
|
||||
<button
|
||||
v-if="resourceData?.code"
|
||||
id="codeBtn"
|
||||
class="codebtn"
|
||||
@click="copyShareCode"
|
||||
>
|
||||
<i class="fa-solid fa-download"></i>
|
||||
分享码:{{ resourceData?.code }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<el-divider />
|
||||
<div class="navigation">
|
||||
<div v-if="prevResource" class="prev" id="prev">
|
||||
<a :href="`/index/resource/detail?id=${prevResource.id}`">
|
||||
<i class="fa fa-arrow-left"></i>
|
||||
上一篇:{{ prevResource.title }}
|
||||
</a>
|
||||
</div>
|
||||
<div v-if="nextResource" class="next" id="next">
|
||||
<a :href="`/index/resource/detail?id=${nextResource.id}`">
|
||||
下一篇:{{ nextResource.title }}
|
||||
<i class="fa fa-arrow-right"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="related-resources">
|
||||
<h3>相关资源</h3>
|
||||
<el-divider />
|
||||
<div class="resources-list">
|
||||
<div
|
||||
v-for="resource in relatedResources"
|
||||
:key="resource.id"
|
||||
class="resource-item"
|
||||
>
|
||||
<div class="item-cover">
|
||||
<img
|
||||
:src="getImageUrl(resource.icon)"
|
||||
:alt="resource.title"
|
||||
/>
|
||||
</div>
|
||||
<div class="item-info">
|
||||
<router-link
|
||||
:to="{
|
||||
path: '/resource',
|
||||
query: {
|
||||
id: resource.id,
|
||||
path: pageInfo.path,
|
||||
category: pageInfo.name,
|
||||
},
|
||||
}"
|
||||
class="resource-title-link"
|
||||
>
|
||||
<h4 class="resource-title">
|
||||
{{ resource.title }}
|
||||
</h4>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="loading" v-else-if="loading">
|
||||
@ -245,21 +463,39 @@ onMounted(() => {
|
||||
<div class="modal-content" @click.stop>
|
||||
<!-- 控制按钮 -->
|
||||
<div class="image-controls">
|
||||
<button class="control-btn" @click="resetImageState" title="重置"><i class="fas fa-expand"></i></button>
|
||||
<button class="control-btn" @click="rotateImage" title="旋转"><i class="fas fa-redo"></i></button>
|
||||
<button class="control-btn" @click="imageScale = Math.max(0.1, imageScale - 0.2)" title="缩小"><i class="fas fa-search-minus"></i></button>
|
||||
<button class="control-btn" @click="resetImageState" title="重置">
|
||||
<i class="fas fa-expand"></i>
|
||||
</button>
|
||||
<button class="control-btn" @click="rotateImage" title="旋转">
|
||||
<i class="fas fa-redo"></i>
|
||||
</button>
|
||||
<button
|
||||
class="control-btn"
|
||||
@click="imageScale = Math.max(0.1, imageScale - 0.2)"
|
||||
title="缩小"
|
||||
>
|
||||
<i class="fas fa-search-minus"></i>
|
||||
</button>
|
||||
<span class="scale-display">{{ Math.round(imageScale * 100) }}%</span>
|
||||
<button class="control-btn" @click="imageScale = Math.min(3, imageScale + 0.2)" title="放大"><i class="fas fa-search-plus"></i></button>
|
||||
<button class="close-btn" @click="closeImageModal" title="关闭"><i class="fas fa-times"></i></button>
|
||||
<button
|
||||
class="control-btn"
|
||||
@click="imageScale = Math.min(3, imageScale + 0.2)"
|
||||
title="放大"
|
||||
>
|
||||
<i class="fas fa-search-plus"></i>
|
||||
</button>
|
||||
<button class="close-btn" @click="closeImageModal" title="关闭">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<img
|
||||
:src="modalImageSrc"
|
||||
:alt="resourceData?.title"
|
||||
:alt="resourceData?.title || '图片'"
|
||||
class="modal-image"
|
||||
:style="{
|
||||
transform: `scale(${imageScale}) rotate(${imageRotation}deg)`,
|
||||
maxWidth: '1200px',
|
||||
maxHeight: '800px'
|
||||
maxHeight: '800px',
|
||||
}"
|
||||
@wheel="handleWheel"
|
||||
/>
|
||||
@ -271,93 +507,294 @@ onMounted(() => {
|
||||
|
||||
<style lang="less" scoped>
|
||||
.main-container {
|
||||
padding-top: 100px; min-height: 100vh; background: #f9fafc;
|
||||
padding-top: 100px;
|
||||
min-height: 100vh;
|
||||
background: #f9fafc;
|
||||
|
||||
.container {
|
||||
width: 100%; margin: 0;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
|
||||
.content {
|
||||
.top-content {
|
||||
width: 100%; height: 400px; background-color: #0081ff; position: relative;
|
||||
width: 100%;
|
||||
height: 400px;
|
||||
background-color: #0081ff;
|
||||
position: relative;
|
||||
|
||||
.top-content-main {
|
||||
max-width: 1200px; margin: 0 auto; padding-top: 50px;
|
||||
display: flex; justify-content: space-between; position: absolute; top: 0;
|
||||
left: 50%; transform: translateX(-50%); width: 100%; z-index: 1;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding-top: 50px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
|
||||
.top-content-main-title { font-size: 30px; font-weight: 700; max-width: 1000px; color: #fff; }
|
||||
.location { color: #fff; display: flex; align-items: center; }
|
||||
.top-content-main-title {
|
||||
font-size: 30px;
|
||||
font-weight: 700;
|
||||
max-width: 1000px;
|
||||
color: #fff;
|
||||
}
|
||||
.location {
|
||||
color: #fff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
.info-card {
|
||||
max-width: 1400px; margin: 0 auto; position: relative; background-color: #fff;
|
||||
border-radius: 8px; box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1); top: -150px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
top: -150px;
|
||||
|
||||
.card-content {
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
.card-content-left {
|
||||
padding: 20px;
|
||||
.img-cover { border-radius: 8px; overflow: hidden; width: 450px; height: auto; background: cover; }
|
||||
.img-cover {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
width: 450px;
|
||||
height: auto;
|
||||
background: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.card-content-right {
|
||||
padding: 20px; display: flex; flex-direction: column; gap: 30px;
|
||||
padding: 20px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 30px;
|
||||
|
||||
.top-btns { display: flex; justify-content: flex-end; }
|
||||
.top-btns {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.resource-info {
|
||||
display: flex; flex-direction: column;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
font-size: 35px; font-weight: 700; color: #42d697; margin-bottom: 15px;
|
||||
font-size: 35px;
|
||||
font-weight: 700;
|
||||
color: #42d697;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.infos {
|
||||
display: flex;
|
||||
|
||||
.infos-item {
|
||||
margin-right: 60px; color: #7d879c; display: flex; flex-direction: column;
|
||||
margin-right: 60px;
|
||||
color: #7d879c;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.label { margin-bottom: 10px; }
|
||||
.label {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.value {
|
||||
border: 1px #0d6efd dashed; padding: 3px 6px; font-size: 13px;
|
||||
background-color: #0081ff12; border-radius: 5px;
|
||||
border: 1px #0d6efd dashed;
|
||||
padding: 3px 6px;
|
||||
font-size: 13px;
|
||||
background-color: #0081ff12;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-btns {
|
||||
display: flex; align-items: center;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.codebtn {
|
||||
color: #0d6efd; margin-left: 20px; padding: 6px 20px; border-radius: 8px;
|
||||
border: 1px solid #0d6efd; cursor: pointer; transition: all 0.3s ease;
|
||||
color: #0d6efd;
|
||||
margin-left: 20px;
|
||||
padding: 6px 20px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #0d6efd;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.resource-detail {
|
||||
position: relative; top: -120px; max-width: 1400px; margin: 0 auto;
|
||||
background: white; border-radius: 8px; padding: 30px;
|
||||
.resource-detail {
|
||||
position: relative;
|
||||
top: -120px;
|
||||
max-width: 1400px;
|
||||
margin: 0 auto;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
padding: 60px;
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||
|
||||
.resource-detail-title { font-size: 2rem; font-weight: bold; color: #333; margin-bottom: 20px; }
|
||||
.resource-detail-title {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.resource-content {
|
||||
line-height: 1.8; color: #333; font-size: 16px;
|
||||
img { width: 100% !important; }
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
line-height: 1.8;
|
||||
color: #333;
|
||||
font-size: 16px;
|
||||
|
||||
:deep(.content-image-wrapper) {
|
||||
margin-bottom: 10px;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
// .content-image-wrapper {
|
||||
// margin-bottom: 10px;
|
||||
// width: 100%;
|
||||
// box-sizing: border-box;
|
||||
|
||||
// img{
|
||||
// width: 100%;
|
||||
// height: auto;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
.disclaimers {
|
||||
color: #b1b1b1;
|
||||
width: 80%;
|
||||
margin: 20px auto;
|
||||
margin-bottom: 20px;
|
||||
margin-bottom: 60px;
|
||||
|
||||
.disclaimer-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.disclaimer-content {
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
#downloadBtn {
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
.codebtn {
|
||||
color: #0d6efd;
|
||||
padding: 15px 30px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #0d6efd;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.navigation {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 30px 0;
|
||||
|
||||
a {
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.related-resources {
|
||||
margin: 40px 0;
|
||||
|
||||
.resources-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 20px;
|
||||
|
||||
.resource-item {
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
transition: transform 0.3s;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-5px); // 新增:悬浮效果
|
||||
}
|
||||
|
||||
.resource-title-link {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
|
||||
&:hover .resource-title {
|
||||
color: #007bff;
|
||||
}
|
||||
}
|
||||
|
||||
.resource-title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #333;
|
||||
margin-bottom: 15px;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.item-cover {
|
||||
margin-bottom: 20px;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 150px;
|
||||
object-fit: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.item-info {
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,38 +819,85 @@ onMounted(() => {
|
||||
|
||||
// 图片放大模态框样式
|
||||
.image-modal {
|
||||
position: fixed; top: 0; left: 0; width: 100%; height: 100%;
|
||||
background: rgba(0, 0, 0, 0.8); display: flex; justify-content: center; align-items: center;
|
||||
z-index: 9999; cursor: pointer;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
cursor: pointer;
|
||||
|
||||
.modal-content {
|
||||
position: relative; cursor: default; display: flex; flex-direction: column; align-items: center;
|
||||
position: relative;
|
||||
cursor: default;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.image-controls {
|
||||
display: flex; align-items: center; gap: 10px; margin-bottom: 20px;
|
||||
background: rgba(255, 255, 255, 0.1); padding: 10px 20px; border-radius: 25px;
|
||||
backdrop-filter: blur(10px); position: fixed; z-index: 9999; top: 90%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
padding: 10px 20px;
|
||||
border-radius: 25px;
|
||||
backdrop-filter: blur(10px);
|
||||
position: fixed;
|
||||
z-index: 9999;
|
||||
top: 90%;
|
||||
|
||||
.control-btn, .close-btn {
|
||||
background: rgba(255, 255, 255, 0.2); border: none; color: white;
|
||||
width: 40px; height: 40px; border-radius: 50%; cursor: pointer;
|
||||
display: flex; align-items: center; justify-content: center; transition: all 0.3s ease;
|
||||
.control-btn,
|
||||
.close-btn {
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: none;
|
||||
color: white;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.3s ease;
|
||||
|
||||
&:hover { background: rgba(255, 255, 255, 0.3); transform: scale(1.1); }
|
||||
i { font-size: 16px; }
|
||||
&:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
transform: scale(1.1);
|
||||
}
|
||||
i {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.close-btn { margin-left: 10px; }
|
||||
.close-btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.scale-display {
|
||||
color: white; font-size: 14px; font-weight: bold; min-width: 50px; text-align: center;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
min-width: 50px;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.modal-image {
|
||||
max-width: 1200px; max-height: 800px; object-fit: contain; border-radius: 8px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); transition: transform 0.3s ease;
|
||||
cursor: grab; &:active { cursor: grabbing; }
|
||||
max-width: 1200px;
|
||||
max-height: 800px;
|
||||
object-fit: contain;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
|
||||
transition: transform 0.3s ease;
|
||||
cursor: grab;
|
||||
&:active {
|
||||
cursor: grabbing;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,437 +1,24 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="resources-page">
|
||||
<div class="container">
|
||||
<div class="top-content">
|
||||
<h1 class="page-title">游戏资源</h1>
|
||||
<div class="content flex flex-column align-items-center">
|
||||
<p>发现优质游戏资源</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<!-- 左侧分类导航 -->
|
||||
<div class="left-menu">
|
||||
<div class="menu-title">分类导航</div>
|
||||
<ul class="category-list">
|
||||
<li
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
class="category-item"
|
||||
:class="{ active: selectedCategory === category.id }"
|
||||
@click="selectCategory(category.id)"
|
||||
>
|
||||
{{ category.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容区域 -->
|
||||
<div class="contents">
|
||||
<div v-if="loading" class="loading">加载中...</div>
|
||||
<div v-else-if="resources.length === 0" class="no-data">暂无内容</div>
|
||||
<div v-else class="resources-grid">
|
||||
<div
|
||||
v-for="resource in resources"
|
||||
:key="resource.id"
|
||||
class="resource-card"
|
||||
>
|
||||
<div class="resource-image">
|
||||
<img :src="getImageUrl(resource.icon)" :alt="resource.title" />
|
||||
</div>
|
||||
<div class="card-header">
|
||||
<router-link
|
||||
:to="`/resource?id=${resource.id}&path=/downloadGames&category=${encodeURIComponent(currentCategoryName)}`"
|
||||
class="resource-title-link"
|
||||
>
|
||||
<h3 class="resource-title">{{ resource.title }}</h3>
|
||||
</router-link>
|
||||
<div class="resource-meta">
|
||||
<span class="resource-view">
|
||||
<i class="fa-solid fa-eye"></i>
|
||||
{{ resource.view || 0 }}
|
||||
</span>
|
||||
<span class="resource-likes">
|
||||
<i class="fas fa-download"></i>
|
||||
{{ resource.likes || 0 }}
|
||||
</span>
|
||||
<span class="resource-date">{{
|
||||
formatDate(resource.create_time)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<div v-if="total > 0" class="pagination-wrapper">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handlePageSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ResourceList :config="resourceConfig" />
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import Header from "@/views/components/header.vue";
|
||||
import Footer from "@/views/components/footer.vue";
|
||||
import ResourceList from "@/views/components/ResourceList.vue";
|
||||
import { downloadGames } from "@/api/downloadGames";
|
||||
|
||||
// 响应式数据
|
||||
const categories = ref<any[]>([]); // 分类列表
|
||||
const resources = ref<any[]>([]); // 文章列表
|
||||
const selectedCategory = ref<string>(""); // 选中的分类
|
||||
const loading = ref(false); // 加载状态
|
||||
const currentPage = ref(1); // 当前页码
|
||||
const pageSize = ref(10); // 每页条数
|
||||
const total = ref(0); // 总条数
|
||||
|
||||
// 当前选中分类的名称
|
||||
const currentCategoryName = computed(() => {
|
||||
const category = categories.value.find(cat => cat.id === selectedCategory.value);
|
||||
return category?.name || '';
|
||||
});
|
||||
|
||||
// 获取图片地址
|
||||
const getImageUrl = (imagePath: string) => {
|
||||
if (!imagePath) return "/src/assets/imgs/default.png";
|
||||
return import.meta.env.VITE_API_DOMAIN + imagePath;
|
||||
};
|
||||
|
||||
// 获取分类列表
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
const response: any = await downloadGames.getDownloadGamesCategory();
|
||||
|
||||
if (
|
||||
response.data &&
|
||||
response.data.data &&
|
||||
Array.isArray(response.data.data)
|
||||
) {
|
||||
categories.value = response.data.data;
|
||||
// 默认选择第一个分类
|
||||
if (categories.value.length > 0) {
|
||||
selectedCategory.value = categories.value[0].id;
|
||||
fetchResources();
|
||||
}
|
||||
} else {
|
||||
categories.value = [];
|
||||
ElMessage.warning(response.data?.msg || response.msg || "获取分类失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取分类失败:", error);
|
||||
categories.value = [];
|
||||
ElMessage.warning("获取分类失败");
|
||||
// 页面配置
|
||||
const resourceConfig = {
|
||||
title: "游戏资源",
|
||||
description: "发现优质游戏资源",
|
||||
routePath: "/downloadGames",
|
||||
api: {
|
||||
getCategory: downloadGames.getDownloadGamesCategory,
|
||||
getSimpleLists: downloadGames.getDownloadGamesSimpleLists
|
||||
}
|
||||
};
|
||||
|
||||
// 获取文章列表
|
||||
const fetchResources = async (page: number = currentPage.value) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response: any = await downloadGames.getDownloadGamesSimpleLists(
|
||||
selectedCategory.value
|
||||
);
|
||||
|
||||
const data = response.data?.data;
|
||||
if (data?.resources && Array.isArray(data.resources)) {
|
||||
resources.value = data.resources;
|
||||
total.value = data.total || 0;
|
||||
currentPage.value = data.page || 1;
|
||||
pageSize.value = data.limit || 10;
|
||||
} else {
|
||||
resources.value = [];
|
||||
total.value = 0;
|
||||
ElMessage.warning(response.data?.msg || "获取文章失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取文章失败:", error);
|
||||
resources.value = [];
|
||||
total.value = 0;
|
||||
ElMessage.warning("获取文章失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 选择分类
|
||||
const selectCategory = (categoryId: string) => {
|
||||
selectedCategory.value = categoryId;
|
||||
currentPage.value = 1; // 切换分类时重置到第一页
|
||||
fetchResources(1);
|
||||
};
|
||||
|
||||
// 分页处理
|
||||
const handlePageChange = (page: number) => {
|
||||
currentPage.value = page;
|
||||
fetchResources(page);
|
||||
};
|
||||
|
||||
// 每页条数变化
|
||||
const handlePageSizeChange = (size: number) => {
|
||||
pageSize.value = size;
|
||||
currentPage.value = 1; // 重置到第一页
|
||||
fetchResources(1);
|
||||
};
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateTime: string | number) => {
|
||||
if (!dateTime) return "";
|
||||
if (typeof dateTime === "string") {
|
||||
// 如果包含空格,取日期部分
|
||||
return dateTime.split(' ')[0];
|
||||
}
|
||||
// 如果是数字,当作时间戳处理
|
||||
const date = new Date(dateTime < 1e10 ? dateTime * 1000 : dateTime);
|
||||
return date.toLocaleDateString("zh-CN");
|
||||
};
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchCategories();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.resources-page {
|
||||
padding-top: 100px;
|
||||
min-height: 100vh;
|
||||
background: #f9fafc;
|
||||
|
||||
.top-content {
|
||||
background: linear-gradient(135deg, #1e9fff 0%, #0d8aff 100%);
|
||||
padding: 80px 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--white);
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
line-height: 1.6;
|
||||
|
||||
p {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
padding: 40px 0;
|
||||
align-items: flex-start;
|
||||
|
||||
.left-menu {
|
||||
background-color: var(--white);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
width: 250px;
|
||||
flex-shrink: 0;
|
||||
align-self: flex-start;
|
||||
|
||||
.menu-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #1e9fff;
|
||||
}
|
||||
|
||||
.category-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.category-item {
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
border-left: 3px solid transparent;
|
||||
|
||||
&:hover {
|
||||
background: #f0f8ff;
|
||||
border-left-color: #1e9fff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #e6f7ff;
|
||||
border-left-color: #1e9fff;
|
||||
color: #1e9fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contents {
|
||||
background-color: var(--white);
|
||||
// min-height: 400px;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
flex: 1;
|
||||
|
||||
.pagination-wrapper {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
|
||||
.el-pagination {
|
||||
--el-pagination-font-size: 14px;
|
||||
--el-pagination-button-width: 40px;
|
||||
--el-pagination-button-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.loading,
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.resources-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
|
||||
.resource-card {
|
||||
// min-width: 250px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.03);
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.resource-image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 140px;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 16px 16px 12px;
|
||||
|
||||
.resource-title-link {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
|
||||
&:hover .resource-title {
|
||||
color: #007bff;
|
||||
}
|
||||
}
|
||||
|
||||
.resource-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
height: 40px;
|
||||
color: #333;
|
||||
margin: 0 0 8px 0;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
transition: color 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.resource-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
|
||||
.resource-view,
|
||||
.resource-likes,
|
||||
.resource-date {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 0 16px;
|
||||
|
||||
.resource-summary {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 12px 16px 16px;
|
||||
text-align: right;
|
||||
|
||||
.read-more {
|
||||
color: #1e9fff;
|
||||
text-decoration: none;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
color: #0d8aff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,437 +1,24 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="resources-page">
|
||||
<div class="container">
|
||||
<div class="top-content">
|
||||
<h1 class="page-title">程序资源</h1>
|
||||
<div class="content flex flex-column align-items-center">
|
||||
<p>发现优质程序资源</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<!-- 左侧分类导航 -->
|
||||
<div class="left-menu">
|
||||
<div class="menu-title">分类导航</div>
|
||||
<ul class="category-list">
|
||||
<li
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
class="category-item"
|
||||
:class="{ active: selectedCategory === category.id }"
|
||||
@click="selectCategory(category.id)"
|
||||
>
|
||||
{{ category.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容区域 -->
|
||||
<div class="contents">
|
||||
<div v-if="loading" class="loading">加载中...</div>
|
||||
<div v-else-if="resources.length === 0" class="no-data">暂无内容</div>
|
||||
<div v-else class="resources-grid">
|
||||
<div
|
||||
v-for="resource in resources"
|
||||
:key="resource.id"
|
||||
class="resource-card"
|
||||
>
|
||||
<div class="resource-image">
|
||||
<img :src="getImageUrl(resource.icon)" :alt="resource.title" />
|
||||
</div>
|
||||
<div class="card-header">
|
||||
<router-link
|
||||
:to="`/resource?id=${resource.id}&path=/downloadPrograms&category=${encodeURIComponent(currentCategoryName)}`"
|
||||
class="resource-title-link"
|
||||
>
|
||||
<h3 class="resource-title">{{ resource.title }}</h3>
|
||||
</router-link>
|
||||
<div class="resource-meta">
|
||||
<span class="resource-view">
|
||||
<i class="fa-solid fa-eye"></i>
|
||||
{{ resource.view || 0 }}
|
||||
</span>
|
||||
<span class="resource-likes">
|
||||
<i class="fas fa-download"></i>
|
||||
{{ resource.likes || 0 }}
|
||||
</span>
|
||||
<span class="resource-date">{{
|
||||
formatDate(resource.create_time)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<div v-if="total > 0" class="pagination-wrapper">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handlePageSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ResourceList :config="resourceConfig" />
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted,computed } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import Header from "@/views/components/header.vue";
|
||||
import Footer from "@/views/components/footer.vue";
|
||||
import ResourceList from "@/views/components/ResourceList.vue";
|
||||
import { downloadPrograms } from "@/api/downloadPrograms";
|
||||
|
||||
// 响应式数据
|
||||
const categories = ref<any[]>([]); // 分类列表
|
||||
const resources = ref<any[]>([]); // 文章列表
|
||||
const selectedCategory = ref<string>(""); // 选中的分类
|
||||
const loading = ref(false); // 加载状态
|
||||
const currentPage = ref(1); // 当前页码
|
||||
const pageSize = ref(10); // 每页条数
|
||||
const total = ref(0); // 总条数
|
||||
|
||||
// 当前选中分类的名称
|
||||
const currentCategoryName = computed(() => {
|
||||
const category = categories.value.find(cat => cat.id === selectedCategory.value);
|
||||
return category?.name || '';
|
||||
});
|
||||
|
||||
// 获取图片地址
|
||||
const getImageUrl = (imagePath: string) => {
|
||||
if (!imagePath) return "/src/assets/imgs/default.png";
|
||||
return import.meta.env.VITE_API_DOMAIN + imagePath;
|
||||
};
|
||||
|
||||
// 获取分类列表
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
const response: any = await downloadPrograms.getDownloadProgramsCategory();
|
||||
|
||||
if (
|
||||
response.data &&
|
||||
response.data.data &&
|
||||
Array.isArray(response.data.data)
|
||||
) {
|
||||
categories.value = response.data.data;
|
||||
// 默认选择第一个分类
|
||||
if (categories.value.length > 0) {
|
||||
selectedCategory.value = categories.value[0].id;
|
||||
fetchResources();
|
||||
}
|
||||
} else {
|
||||
categories.value = [];
|
||||
ElMessage.warning(response.data?.msg || response.msg || "获取分类失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取分类失败:", error);
|
||||
categories.value = [];
|
||||
ElMessage.warning("获取分类失败");
|
||||
// 页面配置
|
||||
const resourceConfig = {
|
||||
title: "程序资源",
|
||||
description: "发现优质程序资源",
|
||||
routePath: "/downloadPrograms",
|
||||
api: {
|
||||
getCategory: downloadPrograms.getDownloadProgramsCategory,
|
||||
getSimpleLists: downloadPrograms.getDownloadProgramsSimpleLists
|
||||
}
|
||||
};
|
||||
|
||||
// 获取文章列表
|
||||
const fetchResources = async (page: number = currentPage.value) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response: any = await downloadPrograms.getDownloadProgramsSimpleLists(
|
||||
selectedCategory.value
|
||||
);
|
||||
|
||||
const data = response.data?.data;
|
||||
if (data?.resources && Array.isArray(data.resources)) {
|
||||
resources.value = data.resources;
|
||||
total.value = data.total || 0;
|
||||
currentPage.value = data.page || 1;
|
||||
pageSize.value = data.limit || 10;
|
||||
} else {
|
||||
resources.value = [];
|
||||
total.value = 0;
|
||||
ElMessage.warning(response.data?.msg || "获取文章失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取文章失败:", error);
|
||||
resources.value = [];
|
||||
total.value = 0;
|
||||
ElMessage.warning("获取文章失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 选择分类
|
||||
const selectCategory = (categoryId: string) => {
|
||||
selectedCategory.value = categoryId;
|
||||
currentPage.value = 1; // 切换分类时重置到第一页
|
||||
fetchResources(1);
|
||||
};
|
||||
|
||||
// 分页处理
|
||||
const handlePageChange = (page: number) => {
|
||||
currentPage.value = page;
|
||||
fetchResources(page);
|
||||
};
|
||||
|
||||
// 每页条数变化
|
||||
const handlePageSizeChange = (size: number) => {
|
||||
pageSize.value = size;
|
||||
currentPage.value = 1; // 重置到第一页
|
||||
fetchResources(1);
|
||||
};
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateTime: string | number) => {
|
||||
if (!dateTime) return "";
|
||||
if (typeof dateTime === "string") {
|
||||
// 如果包含空格,取日期部分
|
||||
return dateTime.split(' ')[0];
|
||||
}
|
||||
// 如果是数字,当作时间戳处理
|
||||
const date = new Date(dateTime < 1e10 ? dateTime * 1000 : dateTime);
|
||||
return date.toLocaleDateString("zh-CN");
|
||||
};
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchCategories();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.resources-page {
|
||||
padding-top: 100px;
|
||||
min-height: 100vh;
|
||||
background: #f9fafc;
|
||||
|
||||
.top-content {
|
||||
background: linear-gradient(135deg, #1e9fff 0%, #0d8aff 100%);
|
||||
padding: 80px 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--white);
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
line-height: 1.6;
|
||||
|
||||
p {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
padding: 40px 0;
|
||||
align-items: flex-start;
|
||||
|
||||
.left-menu {
|
||||
background-color: var(--white);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
width: 250px;
|
||||
flex-shrink: 0;
|
||||
align-self: flex-start;
|
||||
|
||||
.menu-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #1e9fff;
|
||||
}
|
||||
|
||||
.category-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.category-item {
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
border-left: 3px solid transparent;
|
||||
|
||||
&:hover {
|
||||
background: #f0f8ff;
|
||||
border-left-color: #1e9fff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #e6f7ff;
|
||||
border-left-color: #1e9fff;
|
||||
color: #1e9fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contents {
|
||||
background-color: var(--white);
|
||||
// min-height: 400px;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
flex: 1;
|
||||
|
||||
.pagination-wrapper {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
|
||||
.el-pagination {
|
||||
--el-pagination-font-size: 14px;
|
||||
--el-pagination-button-width: 40px;
|
||||
--el-pagination-button-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.loading,
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.resources-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
|
||||
.resource-card {
|
||||
// min-width: 250px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.03);
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.resource-image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 140px;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 16px 16px 12px;
|
||||
|
||||
.resource-title-link {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
|
||||
&:hover .resource-title {
|
||||
color: #007bff;
|
||||
}
|
||||
}
|
||||
|
||||
.resource-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
height: 40px;
|
||||
color: #333;
|
||||
margin: 0 0 8px 0;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
transition: color 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.resource-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
|
||||
.resource-view,
|
||||
.resource-likes,
|
||||
.resource-date {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 0 16px;
|
||||
|
||||
.resource-summary {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 12px 16px 16px;
|
||||
text-align: right;
|
||||
|
||||
.read-more {
|
||||
color: #1e9fff;
|
||||
text-decoration: none;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
color: #0d8aff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,437 +1,24 @@
|
||||
<template>
|
||||
<Header />
|
||||
<div class="resources-page">
|
||||
<div class="container">
|
||||
<div class="top-content">
|
||||
<h1 class="page-title">办公资源</h1>
|
||||
<div class="content flex flex-column align-items-center">
|
||||
<p>发现优质程序与工具</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="main-content">
|
||||
<!-- 左侧分类导航 -->
|
||||
<div class="left-menu">
|
||||
<div class="menu-title">分类导航</div>
|
||||
<ul class="category-list">
|
||||
<li
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
class="category-item"
|
||||
:class="{ active: selectedCategory === category.id }"
|
||||
@click="selectCategory(category.id)"
|
||||
>
|
||||
{{ category.name }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 右侧内容区域 -->
|
||||
<div class="contents">
|
||||
<div v-if="loading" class="loading">加载中...</div>
|
||||
<div v-else-if="resources.length === 0" class="no-data">暂无内容</div>
|
||||
<div v-else class="resources-grid">
|
||||
<div
|
||||
v-for="resource in resources"
|
||||
:key="resource.id"
|
||||
class="resource-card"
|
||||
>
|
||||
<div class="resource-image">
|
||||
<img :src="getImageUrl(resource.icon)" :alt="resource.title" />
|
||||
</div>
|
||||
<div class="card-header">
|
||||
<router-link
|
||||
:to="`/resource?id=${resource.id}&path=/officeResources&category=${encodeURIComponent(currentCategoryName)}`"
|
||||
class="resource-title-link"
|
||||
>
|
||||
<h3 class="resource-title">{{ resource.title }}</h3>
|
||||
</router-link>
|
||||
<div class="resource-meta">
|
||||
<span class="resource-view">
|
||||
<i class="fa-solid fa-eye"></i>
|
||||
{{ resource.view || 0 }}
|
||||
</span>
|
||||
<span class="resource-likes">
|
||||
<i class="fas fa-download"></i>
|
||||
{{ resource.likes || 0 }}
|
||||
</span>
|
||||
<span class="resource-date">{{
|
||||
formatDate(resource.create_time)
|
||||
}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<div v-if="total > 0" class="pagination-wrapper">
|
||||
<el-pagination
|
||||
v-model:current-page="currentPage"
|
||||
v-model:page-size="pageSize"
|
||||
:page-sizes="[10, 20, 50]"
|
||||
:total="total"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="handlePageSizeChange"
|
||||
@current-change="handlePageChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ResourceList :config="resourceConfig" />
|
||||
<Footer />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from "vue";
|
||||
import { ElMessage } from "element-plus";
|
||||
import Header from "@/views/components/header.vue";
|
||||
import Footer from "@/views/components/footer.vue";
|
||||
import ResourceList from "@/views/components/ResourceList.vue";
|
||||
import { officeResources } from "@/api/officeResources";
|
||||
|
||||
// 响应式数据
|
||||
const categories = ref<any[]>([]); // 分类列表
|
||||
const resources = ref<any[]>([]); // 文章列表
|
||||
const selectedCategory = ref<string>(""); // 选中的分类
|
||||
const loading = ref(false); // 加载状态
|
||||
const currentPage = ref(1); // 当前页码
|
||||
const pageSize = ref(10); // 每页条数
|
||||
const total = ref(0); // 总条数
|
||||
|
||||
// 当前选中分类的名称
|
||||
const currentCategoryName = computed(() => {
|
||||
const category = categories.value.find(cat => cat.id === selectedCategory.value);
|
||||
return category?.name || '';
|
||||
});
|
||||
|
||||
// 获取图片地址
|
||||
const getImageUrl = (imagePath: string) => {
|
||||
if (!imagePath) return "/src/assets/imgs/default.png";
|
||||
return import.meta.env.VITE_API_DOMAIN + imagePath;
|
||||
};
|
||||
|
||||
// 获取分类列表
|
||||
const fetchCategories = async () => {
|
||||
try {
|
||||
const response: any = await officeResources.getOfficeResourcesCategory();
|
||||
|
||||
if (
|
||||
response.data &&
|
||||
response.data.data &&
|
||||
Array.isArray(response.data.data)
|
||||
) {
|
||||
categories.value = response.data.data;
|
||||
// 默认选择第一个分类
|
||||
if (categories.value.length > 0) {
|
||||
selectedCategory.value = categories.value[0].id;
|
||||
fetchResources();
|
||||
}
|
||||
} else {
|
||||
categories.value = [];
|
||||
ElMessage.warning(response.data?.msg || response.msg || "获取分类失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取分类失败:", error);
|
||||
categories.value = [];
|
||||
ElMessage.warning("获取分类失败");
|
||||
// 页面配置
|
||||
const resourceConfig = {
|
||||
title: "办公资源",
|
||||
description: "发现优质程序与工具",
|
||||
routePath: "/officeResources",
|
||||
api: {
|
||||
getCategory: officeResources.getOfficeResourcesCategory,
|
||||
getSimpleLists: officeResources.getOfficeResourcesSimpleLists
|
||||
}
|
||||
};
|
||||
|
||||
// 获取文章列表
|
||||
const fetchResources = async (page: number = currentPage.value) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response: any = await officeResources.getOfficeResourcesSimpleLists(
|
||||
selectedCategory.value
|
||||
);
|
||||
|
||||
const data = response.data?.data;
|
||||
if (data?.resources && Array.isArray(data.resources)) {
|
||||
resources.value = data.resources;
|
||||
total.value = data.total || 0;
|
||||
currentPage.value = data.page || 1;
|
||||
pageSize.value = data.limit || 10;
|
||||
} else {
|
||||
resources.value = [];
|
||||
total.value = 0;
|
||||
ElMessage.warning(response.data?.msg || "获取文章失败");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取文章失败:", error);
|
||||
resources.value = [];
|
||||
total.value = 0;
|
||||
ElMessage.warning("获取文章失败");
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 选择分类
|
||||
const selectCategory = (categoryId: string) => {
|
||||
selectedCategory.value = categoryId;
|
||||
currentPage.value = 1; // 切换分类时重置到第一页
|
||||
fetchResources(1);
|
||||
};
|
||||
|
||||
// 分页处理
|
||||
const handlePageChange = (page: number) => {
|
||||
currentPage.value = page;
|
||||
fetchResources(page);
|
||||
};
|
||||
|
||||
// 每页条数变化
|
||||
const handlePageSizeChange = (size: number) => {
|
||||
pageSize.value = size;
|
||||
currentPage.value = 1; // 重置到第一页
|
||||
fetchResources(1);
|
||||
};
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateTime: string | number) => {
|
||||
if (!dateTime) return "";
|
||||
if (typeof dateTime === "string") {
|
||||
// 如果包含空格,取日期部分
|
||||
return dateTime.split(' ')[0];
|
||||
}
|
||||
// 如果是数字,当作时间戳处理
|
||||
const date = new Date(dateTime < 1e10 ? dateTime * 1000 : dateTime);
|
||||
return date.toLocaleDateString("zh-CN");
|
||||
};
|
||||
|
||||
// 组件挂载时获取数据
|
||||
onMounted(() => {
|
||||
fetchCategories();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.resources-page {
|
||||
padding-top: 100px;
|
||||
min-height: 100vh;
|
||||
background: #f9fafc;
|
||||
|
||||
.top-content {
|
||||
background: linear-gradient(135deg, #1e9fff 0%, #0d8aff 100%);
|
||||
padding: 80px 40px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--white);
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.content {
|
||||
line-height: 1.6;
|
||||
|
||||
p {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-content {
|
||||
width: 1200px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
gap: 30px;
|
||||
padding: 40px 0;
|
||||
align-items: flex-start;
|
||||
|
||||
.left-menu {
|
||||
background-color: var(--white);
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
width: 250px;
|
||||
flex-shrink: 0;
|
||||
align-self: flex-start;
|
||||
|
||||
.menu-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 10px;
|
||||
border-bottom: 2px solid #1e9fff;
|
||||
}
|
||||
|
||||
.category-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
|
||||
.category-item {
|
||||
padding: 12px 16px;
|
||||
margin-bottom: 8px;
|
||||
cursor: pointer;
|
||||
border-radius: 6px;
|
||||
transition: all 0.3s ease;
|
||||
border-left: 3px solid transparent;
|
||||
|
||||
&:hover {
|
||||
background: #f0f8ff;
|
||||
border-left-color: #1e9fff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
background: #e6f7ff;
|
||||
border-left-color: #1e9fff;
|
||||
color: #1e9fff;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.contents {
|
||||
background-color: var(--white);
|
||||
// min-height: 400px;
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
flex: 1;
|
||||
|
||||
.pagination-wrapper {
|
||||
margin-top: 30px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
|
||||
.el-pagination {
|
||||
--el-pagination-font-size: 14px;
|
||||
--el-pagination-button-width: 40px;
|
||||
--el-pagination-button-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.loading,
|
||||
.no-data {
|
||||
text-align: center;
|
||||
padding: 60px 20px;
|
||||
color: #666;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.resources-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
|
||||
.resource-card {
|
||||
// min-width: 250px;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 3px 15px rgba(0, 0, 0, 0.03);
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
&:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.resource-image {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 140px;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.card-header {
|
||||
padding: 16px 16px 12px;
|
||||
|
||||
.resource-title-link {
|
||||
text-decoration: none;
|
||||
display: block;
|
||||
|
||||
&:hover .resource-title {
|
||||
color: #007bff;
|
||||
}
|
||||
}
|
||||
|
||||
.resource-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
height: 40px;
|
||||
color: #333;
|
||||
margin: 0 0 8px 0;
|
||||
line-height: 1.4;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-word;
|
||||
transition: color 0.3s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.resource-meta {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
|
||||
.resource-view,
|
||||
.resource-likes,
|
||||
.resource-date {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 0 16px;
|
||||
|
||||
.resource-summary {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
line-height: 1.5;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 3;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
padding: 12px 16px 16px;
|
||||
text-align: right;
|
||||
|
||||
.read-more {
|
||||
color: #1e9fff;
|
||||
text-decoration: none;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
transition: color 0.3s ease;
|
||||
|
||||
&:hover {
|
||||
color: #0d8aff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user