优化程序界面
This commit is contained in:
parent
fed42c7589
commit
dd6ee001f1
1
frontend/components.d.ts
vendored
1
frontend/components.d.ts
vendored
@ -23,6 +23,7 @@ declare module 'vue' {
|
|||||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||||
|
ElTag: typeof import('element-plus/es')['ElTag']
|
||||||
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
|
HelloWorld: typeof import('./src/components/HelloWorld.vue')['default']
|
||||||
RouterLink: typeof import('vue-router')['RouterLink']
|
RouterLink: typeof import('vue-router')['RouterLink']
|
||||||
RouterView: typeof import('vue-router')['RouterView']
|
RouterView: typeof import('vue-router')['RouterView']
|
||||||
|
|||||||
13
frontend/src/api/article.ts
Normal file
13
frontend/src/api/article.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
//进行接口API的统一管理
|
||||||
|
import { request } from "./axios";
|
||||||
|
|
||||||
|
export class article {
|
||||||
|
/**
|
||||||
|
* @description 获取article文章详情
|
||||||
|
* @param {string} id - 内容ID
|
||||||
|
* @return {Promise} 返回请求结果
|
||||||
|
*/
|
||||||
|
static async getArticleDetail(id: string) {
|
||||||
|
return request("/index/articles/getArticleDetail", { id }, "get");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -28,12 +28,4 @@ export class downloadGames {
|
|||||||
return request("/index/program/getDownloadGamesSimpleLists", { cateid }, "get");
|
return request("/index/program/getDownloadGamesSimpleLists", { cateid }, "get");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 获取downloadGames文章详情
|
|
||||||
* @param {string} id - 内容ID
|
|
||||||
* @return {Promise} 返回请求结果
|
|
||||||
*/
|
|
||||||
static async getDownloadGamesDetail(id: string) {
|
|
||||||
return request("/index/program/getDownloadGamesDetail", { id }, "get");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -36,12 +36,4 @@ export class downloadPrograms {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 获取downloadPrograms文章详情
|
|
||||||
* @param {string} id - 内容ID
|
|
||||||
* @return {Promise} 返回请求结果
|
|
||||||
*/
|
|
||||||
static async getDownloadProgramsDetail(id: string) {
|
|
||||||
return request("/index/program/getDownloadProgramsDetail", { id }, "get");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,12 +32,4 @@ export class officeResources {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @description 获取officeResources文章详情
|
|
||||||
* @param {string} id - 内容ID
|
|
||||||
* @return {Promise} 返回请求结果
|
|
||||||
*/
|
|
||||||
static async getOfficeResourcesDetail(id: string) {
|
|
||||||
return request("/index/program/getOfficeResourcesDetail", { id }, "get");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
13
frontend/src/api/resource.ts
Normal file
13
frontend/src/api/resource.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
//进行接口API的统一管理
|
||||||
|
import { request } from "./axios";
|
||||||
|
|
||||||
|
export class resource {
|
||||||
|
/**
|
||||||
|
* @description 获取resource文章详情
|
||||||
|
* @param {string} id - 内容ID
|
||||||
|
* @return {Promise} 返回请求结果
|
||||||
|
*/
|
||||||
|
static async getResourceDetail(id: string) {
|
||||||
|
return request("/index/resources/getResourceDetail", { id }, "get");
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,4 +18,5 @@ export class siteInformation {
|
|||||||
static async getSiteInformationLists(cateid: string) {
|
static async getSiteInformationLists(cateid: string) {
|
||||||
return request("/index/articles/getSiteInformationLists", { cateid }, "get");
|
return request("/index/articles/getSiteInformationLists", { cateid }, "get");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -22,4 +22,5 @@ export class technicalArticles {
|
|||||||
"get"
|
"get"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -33,9 +33,14 @@ const router = createRouter({
|
|||||||
component: () => import("@/views/downloadPrograms/index.vue"),
|
component: () => import("@/views/downloadPrograms/index.vue"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/downloadPrograms/:id",
|
path: "/article",
|
||||||
name: "downloadProgramsDetail",
|
name: "articleDetail",
|
||||||
component: () => import("@/views/downloadPrograms/detail.vue"),
|
component: () => import("@/views/components/detail_article.vue"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/resource",
|
||||||
|
name: "resourceDetail",
|
||||||
|
component: () => import("@/views/components/detail_resource.vue"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: "/downloadGames",
|
path: "/downloadGames",
|
||||||
|
|||||||
353
frontend/src/views/components/detail_article.vue
Normal file
353
frontend/src/views/components/detail_article.vue
Normal file
@ -0,0 +1,353 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } 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 { siteInformation } from "@/api/siteInformation";
|
||||||
|
import { technicalArticles } from "@/api/technicalArticles";
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
|
const articleId = ref(route.query.id as string);
|
||||||
|
const article = ref<any>(null);
|
||||||
|
const loading = ref(true);
|
||||||
|
|
||||||
|
// 获取图片地址
|
||||||
|
const getImageUrl = (imagePath: string) => {
|
||||||
|
if (!imagePath) return "/src/assets/imgs/default.png";
|
||||||
|
return import.meta.env.VITE_API_DOMAIN + imagePath;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
const formatDate = (timestamp: string | number) => {
|
||||||
|
if (!timestamp) return "";
|
||||||
|
const numTimestamp = typeof timestamp === 'string' ? parseInt(timestamp) : timestamp;
|
||||||
|
const date = new Date(numTimestamp < 1e10 ? numTimestamp * 1000 : numTimestamp);
|
||||||
|
return date.toLocaleDateString("zh-CN", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取文章详情
|
||||||
|
const fetchArticleDetail = async () => {
|
||||||
|
if (!articleId.value) {
|
||||||
|
ElMessage.error("文章ID不存在");
|
||||||
|
router.push("/");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
// 根据来源页面调用不同的API
|
||||||
|
const source = route.query.source as string;
|
||||||
|
let response: any;
|
||||||
|
|
||||||
|
if (source === 'siteInformation') {
|
||||||
|
response = await siteInformation.getSiteInformationDetail(articleId.value);
|
||||||
|
} else if (source === 'technicalArticles') {
|
||||||
|
response = await technicalArticles.getTechnicalArticlesDetail(articleId.value);
|
||||||
|
} else {
|
||||||
|
// 默认尝试两个API
|
||||||
|
try {
|
||||||
|
response = await siteInformation.getSiteInformationDetail(articleId.value);
|
||||||
|
} catch {
|
||||||
|
response = await technicalArticles.getTechnicalArticlesDetail(articleId.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.data?.data) {
|
||||||
|
article.value = response.data.data;
|
||||||
|
} else {
|
||||||
|
ElMessage.warning(response.data?.msg || "获取文章详情失败");
|
||||||
|
router.push("/");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error("获取文章详情失败,请稍后重试");
|
||||||
|
console.error("获取文章详情失败:", error);
|
||||||
|
router.push("/");
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchArticleDetail();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Header />
|
||||||
|
<div class="article-detail">
|
||||||
|
<div class="container">
|
||||||
|
<!-- 面包屑导航 -->
|
||||||
|
<div class="breadcrumb-wrapper" v-if="article">
|
||||||
|
<el-breadcrumb separator="/">
|
||||||
|
<el-breadcrumb-item :to="{ path: '/' }">首页</el-breadcrumb-item>
|
||||||
|
<el-breadcrumb-item :to="{ path: '/siteInformation' }">站点资讯</el-breadcrumb-item>
|
||||||
|
<el-breadcrumb-item>详情</el-breadcrumb-item>
|
||||||
|
</el-breadcrumb>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 加载状态 -->
|
||||||
|
<div v-if="loading" class="loading">
|
||||||
|
<el-icon class="is-loading">
|
||||||
|
<Loading />
|
||||||
|
</el-icon>
|
||||||
|
<span>加载中...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 文章不存在 -->
|
||||||
|
<div v-else-if="!article" class="error">
|
||||||
|
<el-empty description="文章不存在或已删除"></el-empty>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 文章内容 -->
|
||||||
|
<div v-else class="article-content">
|
||||||
|
<!-- 文章标题 -->
|
||||||
|
<div class="article-header">
|
||||||
|
<h1 class="article-title">{{ article.title }}</h1>
|
||||||
|
<div class="article-meta">
|
||||||
|
<span class="article-date">
|
||||||
|
<i class="fas fa-calendar"></i>
|
||||||
|
{{ formatDate(article.publishdate || article.created_at) }}
|
||||||
|
</span>
|
||||||
|
<span class="article-views" v-if="article.view">
|
||||||
|
<i class="fas fa-eye"></i>
|
||||||
|
{{ article.view }} 阅读
|
||||||
|
</span>
|
||||||
|
<span class="article-likes" v-if="article.likes">
|
||||||
|
<i class="fas fa-heart"></i>
|
||||||
|
{{ article.likes }} 点赞
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 文章图片 -->
|
||||||
|
<div class="article-image" v-if="article.image">
|
||||||
|
<img :src="getImageUrl(article.image)" :alt="article.title" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 文章正文 -->
|
||||||
|
<div class="article-body">
|
||||||
|
<div
|
||||||
|
class="article-text"
|
||||||
|
v-html="article.content"
|
||||||
|
></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 文章标签 -->
|
||||||
|
<div class="article-tags" v-if="article.tags && article.tags.length">
|
||||||
|
<span class="tag-label">标签:</span>
|
||||||
|
<el-tag
|
||||||
|
v-for="tag in article.tags"
|
||||||
|
:key="tag"
|
||||||
|
size="small"
|
||||||
|
class="article-tag"
|
||||||
|
>
|
||||||
|
{{ tag }}
|
||||||
|
</el-tag>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Footer />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.article-detail {
|
||||||
|
padding-top: 100px;
|
||||||
|
min-height: 100vh;
|
||||||
|
background: #f9fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
max-width: 1200px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-wrapper {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
|
||||||
|
:deep(.el-breadcrumb__inner) {
|
||||||
|
color: #fff !important;
|
||||||
|
font-weight: bolder !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #666;
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 50px 0;
|
||||||
|
color: #666;
|
||||||
|
|
||||||
|
.el-icon {
|
||||||
|
font-size: 24px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
padding: 50px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 40px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-header {
|
||||||
|
border-bottom: 1px solid #eee;
|
||||||
|
padding-bottom: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin: 0 0 20px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-meta {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
|
||||||
|
span {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-image {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-body {
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.8;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-text {
|
||||||
|
:deep(p) {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
line-height: 1.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(img) {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(h1), :deep(h2), :deep(h3), :deep(h4), :deep(h5), :deep(h6) {
|
||||||
|
color: #333;
|
||||||
|
margin: 24px 0 16px 0;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(ul), :deep(ol) {
|
||||||
|
padding-left: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(code) {
|
||||||
|
background: #f6f8fa;
|
||||||
|
padding: 2px 6px;
|
||||||
|
border-radius: 3px;
|
||||||
|
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(pre) {
|
||||||
|
background: #f6f8fa;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 16px 0;
|
||||||
|
|
||||||
|
code {
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(blockquote) {
|
||||||
|
border-left: 4px solid #ddd;
|
||||||
|
padding-left: 16px;
|
||||||
|
margin: 16px 0;
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-tags {
|
||||||
|
margin-top: 40px;
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
|
||||||
|
.tag-label {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-tag {
|
||||||
|
margin-right: 8px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.container {
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-meta {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
420
frontend/src/views/components/detail_resource.vue
Normal file
420
frontend/src/views/components/detail_resource.vue
Normal file
@ -0,0 +1,420 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, computed } from "vue";
|
||||||
|
import { useRoute } 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 resourceId = ref(route.query.id as string);
|
||||||
|
const resourceData = ref<any>(null);
|
||||||
|
const loading = ref(true);
|
||||||
|
const showImageModal = ref(false);
|
||||||
|
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) || ''
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 获取图片地址
|
||||||
|
const getImageUrl = (imagePath: string) => {
|
||||||
|
if (!imagePath) return "/src/assets/imgs/default.png";
|
||||||
|
return import.meta.env.VITE_API_DOMAIN + imagePath;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 下载文件
|
||||||
|
const downloadFile = (downloadPath: string, type: string) => {
|
||||||
|
if (!downloadPath) return ElMessage.warning(`${type}下载链接不存在`);
|
||||||
|
|
||||||
|
const fullUrl = type === '本地' ? import.meta.env.VITE_API_DOMAIN + downloadPath : downloadPath;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = fullUrl;
|
||||||
|
link.target = '_blank';
|
||||||
|
link.download = '';
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
document.body.removeChild(link);
|
||||||
|
ElMessage.success(`${type}下载链接已打开`);
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(`${type}下载失败,请稍后重试`);
|
||||||
|
console.error(`${type}下载失败:`, error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 复制分享码到剪贴板
|
||||||
|
const copyShareCode = async () => {
|
||||||
|
const code = resourceData.value?.code;
|
||||||
|
if (!code) return ElMessage.warning("分享码不存在");
|
||||||
|
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(code);
|
||||||
|
ElMessage.success("分享码已复制到剪贴板");
|
||||||
|
} catch {
|
||||||
|
// 兼容性处理
|
||||||
|
const textArea = document.createElement("textarea");
|
||||||
|
textArea.value = code;
|
||||||
|
document.body.appendChild(textArea);
|
||||||
|
textArea.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(textArea);
|
||||||
|
ElMessage.success("分享码已复制到剪贴板");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 图片模态框操作
|
||||||
|
const openImageModal = (imageSrc: string) => {
|
||||||
|
modalImageSrc.value = imageSrc;
|
||||||
|
showImageModal.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeImageModal = () => {
|
||||||
|
showImageModal.value = false;
|
||||||
|
resetImageState();
|
||||||
|
};
|
||||||
|
|
||||||
|
const resetImageState = () => {
|
||||||
|
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)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const rotateImage = () => {
|
||||||
|
imageRotation.value = (imageRotation.value + 90) % 360;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
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");
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取资源详情
|
||||||
|
const fetchResourceDetail = async () => {
|
||||||
|
if (!resourceId.value) return;
|
||||||
|
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
// 由于所有资源都来自downloadGames页面,统一使用downloadGames API
|
||||||
|
const response = await resourceApi.getResourceDetail(resourceId.value);
|
||||||
|
|
||||||
|
if (response.data?.data) {
|
||||||
|
resourceData.value = response.data.data;
|
||||||
|
} else {
|
||||||
|
ElMessage.warning(response.data?.msg || "获取资源详情失败");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error("获取资源详情失败,请稍后重试");
|
||||||
|
console.error("获取资源详情失败:", error);
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchResourceDetail();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<Header />
|
||||||
|
<div class="main-container">
|
||||||
|
<div class="container">
|
||||||
|
<div class="content" v-if="resourceData">
|
||||||
|
<div class="top-content">
|
||||||
|
<div class="top-content-main">
|
||||||
|
<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>详情</el-breadcrumb-item>
|
||||||
|
</el-breadcrumb>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="main-content">
|
||||||
|
<div class="info-card">
|
||||||
|
<div class="card-content">
|
||||||
|
<div class="card-content-left">
|
||||||
|
<img
|
||||||
|
class="img-cover"
|
||||||
|
: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>
|
||||||
|
</div>
|
||||||
|
<div class="resource-info">
|
||||||
|
<div class="title">
|
||||||
|
{{
|
||||||
|
resourceData.price == 0 || !resourceData.price
|
||||||
|
? "Free"
|
||||||
|
: "¥" + 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>
|
||||||
|
|
||||||
|
<div class="infos-item">
|
||||||
|
<div class="label">所属分类:</div>
|
||||||
|
<div class="value">{{ resourceData.cate }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="infos-item">
|
||||||
|
<div class="label">程序编号:</div>
|
||||||
|
<div class="value">{{ resourceData.number }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="infos-item">
|
||||||
|
<div class="label">查看次数:</div>
|
||||||
|
<div class="value">{{ resourceData.views }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="infos-item">
|
||||||
|
<div class="label">下载次数:</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, '网盘')">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<div class="loading" v-else-if="loading">
|
||||||
|
<el-empty description="正在加载文章详情..."></el-empty>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="error" v-else>
|
||||||
|
<el-empty description="文章不存在或已删除"></el-empty>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 图片放大模态框 -->
|
||||||
|
<div v-if="showImageModal" class="image-modal" @click="closeImageModal">
|
||||||
|
<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>
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
<img
|
||||||
|
:src="modalImageSrc"
|
||||||
|
:alt="resourceData?.title"
|
||||||
|
class="modal-image"
|
||||||
|
:style="{
|
||||||
|
transform: `scale(${imageScale}) rotate(${imageRotation}deg)`,
|
||||||
|
maxWidth: '1200px',
|
||||||
|
maxHeight: '800px'
|
||||||
|
}"
|
||||||
|
@wheel="handleWheel"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Footer />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.main-container {
|
||||||
|
padding-top: 100px; min-height: 100vh; background: #f9fafc;
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 100%; margin: 0;
|
||||||
|
|
||||||
|
.content {
|
||||||
|
.top-content {
|
||||||
|
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;
|
||||||
|
|
||||||
|
.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;
|
||||||
|
|
||||||
|
.card-content {
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-content-right {
|
||||||
|
padding: 20px; display: flex; flex-direction: column; gap: 30px;
|
||||||
|
|
||||||
|
.top-btns { display: flex; justify-content: flex-end; }
|
||||||
|
|
||||||
|
.resource-info {
|
||||||
|
display: flex; flex-direction: column;
|
||||||
|
|
||||||
|
.title {
|
||||||
|
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;
|
||||||
|
|
||||||
|
.label { margin-bottom: 10px; }
|
||||||
|
|
||||||
|
.value {
|
||||||
|
border: 1px #0d6efd dashed; padding: 3px 6px; font-size: 13px;
|
||||||
|
background-color: #0081ff12; border-radius: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-btns {
|
||||||
|
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;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.resource-detail {
|
||||||
|
position: relative; top: -120px; max-width: 1400px; margin: 0 auto;
|
||||||
|
background: white; border-radius: 8px; padding: 30px;
|
||||||
|
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-content {
|
||||||
|
line-height: 1.8; color: #333; font-size: 16px;
|
||||||
|
img { width: 100% !important; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading,
|
||||||
|
.error {
|
||||||
|
margin-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-breadcrumb__inner) {
|
||||||
|
color: #fff !important;
|
||||||
|
font-weight: bolder !important;
|
||||||
|
|
||||||
|
a,
|
||||||
|
&.is-link {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-breadcrumb__separator) {
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图片放大模态框样式
|
||||||
|
.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;
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
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%;
|
||||||
|
|
||||||
|
.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; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-btn { margin-left: 10px; }
|
||||||
|
|
||||||
|
.scale-display {
|
||||||
|
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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -3,9 +3,9 @@
|
|||||||
<div class="resources-page">
|
<div class="resources-page">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="top-content">
|
<div class="top-content">
|
||||||
<h1 class="page-title">办公资源</h1>
|
<h1 class="page-title">游戏资源</h1>
|
||||||
<div class="content flex flex-column align-items-center">
|
<div class="content flex flex-column align-items-center">
|
||||||
<p>发现优质程序与工具</p>
|
<p>发现优质游戏资源</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
@ -40,7 +40,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<router-link
|
<router-link
|
||||||
:to="`/resource/${resource.id}`"
|
:to="`/resource?id=${resource.id}&path=/downloadGames&category=${encodeURIComponent(currentCategoryName)}`"
|
||||||
class="resource-title-link"
|
class="resource-title-link"
|
||||||
>
|
>
|
||||||
<h3 class="resource-title">{{ resource.title }}</h3>
|
<h3 class="resource-title">{{ resource.title }}</h3>
|
||||||
@ -97,6 +97,12 @@ const currentPage = ref(1); // 当前页码
|
|||||||
const pageSize = ref(10); // 每页条数
|
const pageSize = ref(10); // 每页条数
|
||||||
const total = ref(0); // 总条数
|
const total = ref(0); // 总条数
|
||||||
|
|
||||||
|
// 当前选中分类的名称
|
||||||
|
const currentCategoryName = computed(() => {
|
||||||
|
const category = categories.value.find(cat => cat.id === selectedCategory.value);
|
||||||
|
return category?.name || '';
|
||||||
|
});
|
||||||
|
|
||||||
// 获取图片地址
|
// 获取图片地址
|
||||||
const getImageUrl = (imagePath: string) => {
|
const getImageUrl = (imagePath: string) => {
|
||||||
if (!imagePath) return "/src/assets/imgs/default.png";
|
if (!imagePath) return "/src/assets/imgs/default.png";
|
||||||
|
|||||||
@ -1,428 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref, onMounted } from "vue";
|
|
||||||
import { useRoute } from "vue-router";
|
|
||||||
import { ElMessage } from "element-plus";
|
|
||||||
import Header from "@/views/components/header.vue";
|
|
||||||
import Footer from "@/views/components/footer.vue";
|
|
||||||
import { downloadPrograms } from "@/api/downloadPrograms";
|
|
||||||
|
|
||||||
const route = useRoute();
|
|
||||||
const resourceId = ref(route.query.id as string);
|
|
||||||
const resource = ref<any>(null);
|
|
||||||
const loading = ref(true);
|
|
||||||
|
|
||||||
// 获取图片地址
|
|
||||||
const getImageUrl = (imagePath: string) => {
|
|
||||||
if (!imagePath) return "/src/assets/imgs/default.png";
|
|
||||||
return import.meta.env.VITE_API_DOMAIN + imagePath;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 下载文件
|
|
||||||
const downloadFile = (downloadPath: string, type: string) => {
|
|
||||||
if (!downloadPath) {
|
|
||||||
ElMessage.warning(`${type}下载链接不存在`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let fullUrl = downloadPath;
|
|
||||||
|
|
||||||
// 根据下载类型处理URL
|
|
||||||
if (type === '本地') {
|
|
||||||
fullUrl = import.meta.env.VITE_API_DOMAIN + downloadPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 创建一个隐藏的a标签来触发下载
|
|
||||||
const link = document.createElement('a');
|
|
||||||
link.href = fullUrl;
|
|
||||||
link.target = '_blank';
|
|
||||||
link.download = ''; // 让浏览器决定文件名
|
|
||||||
document.body.appendChild(link);
|
|
||||||
link.click();
|
|
||||||
document.body.removeChild(link);
|
|
||||||
|
|
||||||
ElMessage.success(`${type}下载链接已打开`);
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error(`${type}下载失败,请稍后重试`);
|
|
||||||
console.error(`${type}下载失败:`, error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 复制分享码到剪贴板
|
|
||||||
const copyShareCode = async () => {
|
|
||||||
if (!resource.value?.code) {
|
|
||||||
ElMessage.warning("分享码不存在");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
await navigator.clipboard.writeText(resource.value.code);
|
|
||||||
ElMessage.success("分享码已复制到剪贴板");
|
|
||||||
} catch (error) {
|
|
||||||
// 兼容性处理,如果clipboard API不可用
|
|
||||||
const textArea = document.createElement("textarea");
|
|
||||||
textArea.value = resource.value.code;
|
|
||||||
document.body.appendChild(textArea);
|
|
||||||
textArea.select();
|
|
||||||
document.execCommand('copy');
|
|
||||||
document.body.removeChild(textArea);
|
|
||||||
ElMessage.success("分享码已复制到剪贴板");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
if (resourceId.value) {
|
|
||||||
loading.value = true;
|
|
||||||
try {
|
|
||||||
const response: any = await downloadPrograms.getDownloadProgramsDetail(
|
|
||||||
resourceId.value
|
|
||||||
);
|
|
||||||
if (response.data?.data) {
|
|
||||||
resource.value = response.data.data;
|
|
||||||
} else {
|
|
||||||
ElMessage.warning(response.data?.msg || "获取文章详情失败");
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error("获取文章详情失败,请稍后重试");
|
|
||||||
console.error("获取文章详情失败:", error);
|
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Header />
|
|
||||||
<div class="main-container">
|
|
||||||
<div class="container">
|
|
||||||
<div class="content" v-if="resource">
|
|
||||||
<div class="top-content">
|
|
||||||
<div class="top-content-main">
|
|
||||||
<div class="top-content-main-title">{{ resource.title }}</div>
|
|
||||||
<div class="location">
|
|
||||||
<el-breadcrumb separator="/">
|
|
||||||
<el-breadcrumb-item :to="{ path: '/' }"
|
|
||||||
>首页</el-breadcrumb-item
|
|
||||||
>
|
|
||||||
<el-breadcrumb-item :to="{ path: '/downloadPrograms' }"
|
|
||||||
>程序下载</el-breadcrumb-item
|
|
||||||
>
|
|
||||||
<el-breadcrumb-item>详情</el-breadcrumb-item>
|
|
||||||
</el-breadcrumb>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="main-content">
|
|
||||||
<div class="info-card">
|
|
||||||
<div class="card-content">
|
|
||||||
<div class="card-content-left">
|
|
||||||
<img
|
|
||||||
class="img-cover"
|
|
||||||
:src="getImageUrl(resource.icon)"
|
|
||||||
:alt="resource.title"
|
|
||||||
/>
|
|
||||||
</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>
|
|
||||||
</div>
|
|
||||||
<div class="resource-info">
|
|
||||||
<div class="title">
|
|
||||||
{{
|
|
||||||
resource.price == 0 || !resource.price
|
|
||||||
? "Free"
|
|
||||||
: "¥" + resource.price
|
|
||||||
}}
|
|
||||||
</div>
|
|
||||||
<div class="infos">
|
|
||||||
<div class="infos-item">
|
|
||||||
<div class="label">更新时间:</div>
|
|
||||||
<div class="value">{{ resource.update_time }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="infos-item">
|
|
||||||
<div class="label">所属分类:</div>
|
|
||||||
<div class="value">{{ resource.cate }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="infos-item">
|
|
||||||
<div class="label">程序编号:</div>
|
|
||||||
<div class="value">{{ resource.number }}</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="infos-item">
|
|
||||||
<div class="label">查看次数:</div>
|
|
||||||
<div class="value">{{ resource.views }}</div>
|
|
||||||
</div>
|
|
||||||
<div class="infos-item">
|
|
||||||
<div class="label">下载次数:</div>
|
|
||||||
<div class="value">{{ resource.downloads }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="bottom-btns">
|
|
||||||
<!-- 网盘下载按钮 -->
|
|
||||||
<button
|
|
||||||
v-if="resource.url"
|
|
||||||
id="netdiskBtn"
|
|
||||||
class="btn btn-primary"
|
|
||||||
@click="downloadFile(resource.url, '网盘')"
|
|
||||||
>
|
|
||||||
<i class="fa-solid fa-download"></i>
|
|
||||||
网盘下载
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- 本地下载按钮 -->
|
|
||||||
<button
|
|
||||||
v-if="resource.fileurl"
|
|
||||||
id="localBtn"
|
|
||||||
class="btn btn-primary"
|
|
||||||
@click="downloadFile(resource.fileurl, '本地')"
|
|
||||||
>
|
|
||||||
<i class="fa-solid fa-download"></i>
|
|
||||||
本地下载
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- 分享码按钮 -->
|
|
||||||
<button
|
|
||||||
v-if="resource.code"
|
|
||||||
id="codeBtn"
|
|
||||||
class="codebtn"
|
|
||||||
@click="copyShareCode"
|
|
||||||
>
|
|
||||||
<i class="fa-solid fa-download"></i>
|
|
||||||
分享码:{{resource.code}}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="resource-detail" v-if="resource">
|
|
||||||
<div class="resource-content" v-html="resource.content"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="loading" v-else-if="loading">
|
|
||||||
<el-empty description="正在加载文章详情..."></el-empty>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="error" v-else>
|
|
||||||
<el-empty description="文章不存在或已删除"></el-empty>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Footer />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style lang="less" scoped>
|
|
||||||
.main-container {
|
|
||||||
padding-top: 100px;
|
|
||||||
min-height: 100vh;
|
|
||||||
background: #f9fafc;
|
|
||||||
|
|
||||||
.container {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
|
|
||||||
.content {
|
|
||||||
.top-content {
|
|
||||||
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;
|
|
||||||
|
|
||||||
.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: 1200px;
|
|
||||||
/* height: 300px; */
|
|
||||||
margin: 0px 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;
|
|
||||||
|
|
||||||
.card-content-left {
|
|
||||||
padding: 20px;
|
|
||||||
|
|
||||||
.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;
|
|
||||||
|
|
||||||
.top-btns {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
.resource-info {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
|
|
||||||
.title {
|
|
||||||
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;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.value {
|
|
||||||
border: 1px #0d6efd dashed;
|
|
||||||
padding: 3px 6px;
|
|
||||||
font-size: 13px;
|
|
||||||
background-color: #0081ff12;
|
|
||||||
border-radius: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.bottom-btns {
|
|
||||||
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;
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-detail {
|
|
||||||
position: relative;
|
|
||||||
top: -120px;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
background: white;
|
|
||||||
border-radius: 8px;
|
|
||||||
padding: 30px;
|
|
||||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
|
|
||||||
|
|
||||||
.resource-title {
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: bold;
|
|
||||||
color: #333;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-meta {
|
|
||||||
display: flex;
|
|
||||||
gap: 20px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
color: #666;
|
|
||||||
font-size: 14px;
|
|
||||||
|
|
||||||
span {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.resource-content {
|
|
||||||
line-height: 1.8;
|
|
||||||
color: #333;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.loading,
|
|
||||||
.error {
|
|
||||||
margin-top: 50px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-breadcrumb__inner) {
|
|
||||||
color: #fff !important;
|
|
||||||
font-weight: bolder !important;
|
|
||||||
|
|
||||||
a,
|
|
||||||
&.is-link {
|
|
||||||
color: #fff !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.el-breadcrumb__separator) {
|
|
||||||
color: #fff !important;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -3,9 +3,9 @@
|
|||||||
<div class="resources-page">
|
<div class="resources-page">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<div class="top-content">
|
<div class="top-content">
|
||||||
<h1 class="page-title">办公资源</h1>
|
<h1 class="page-title">程序资源</h1>
|
||||||
<div class="content flex flex-column align-items-center">
|
<div class="content flex flex-column align-items-center">
|
||||||
<p>发现优质程序与工具</p>
|
<p>发现优质程序资源</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="main-content">
|
<div class="main-content">
|
||||||
@ -40,7 +40,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<router-link
|
<router-link
|
||||||
:to="`/downloadPrograms/detail?id=${resource.id}`"
|
:to="`/resource?id=${resource.id}&path=/downloadPrograms&category=${encodeURIComponent(currentCategoryName)}`"
|
||||||
class="resource-title-link"
|
class="resource-title-link"
|
||||||
>
|
>
|
||||||
<h3 class="resource-title">{{ resource.title }}</h3>
|
<h3 class="resource-title">{{ resource.title }}</h3>
|
||||||
@ -82,7 +82,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted,computed } from "vue";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import Header from "@/views/components/header.vue";
|
import Header from "@/views/components/header.vue";
|
||||||
import Footer from "@/views/components/footer.vue";
|
import Footer from "@/views/components/footer.vue";
|
||||||
@ -97,6 +97,12 @@ const currentPage = ref(1); // 当前页码
|
|||||||
const pageSize = ref(10); // 每页条数
|
const pageSize = ref(10); // 每页条数
|
||||||
const total = ref(0); // 总条数
|
const total = ref(0); // 总条数
|
||||||
|
|
||||||
|
// 当前选中分类的名称
|
||||||
|
const currentCategoryName = computed(() => {
|
||||||
|
const category = categories.value.find(cat => cat.id === selectedCategory.value);
|
||||||
|
return category?.name || '';
|
||||||
|
});
|
||||||
|
|
||||||
// 获取图片地址
|
// 获取图片地址
|
||||||
const getImageUrl = (imagePath: string) => {
|
const getImageUrl = (imagePath: string) => {
|
||||||
if (!imagePath) return "/src/assets/imgs/default.png";
|
if (!imagePath) return "/src/assets/imgs/default.png";
|
||||||
|
|||||||
@ -40,7 +40,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<router-link
|
<router-link
|
||||||
:to="`/resource/${resource.id}`"
|
:to="`/resource?id=${resource.id}&path=/officeResources&category=${encodeURIComponent(currentCategoryName)}`"
|
||||||
class="resource-title-link"
|
class="resource-title-link"
|
||||||
>
|
>
|
||||||
<h3 class="resource-title">{{ resource.title }}</h3>
|
<h3 class="resource-title">{{ resource.title }}</h3>
|
||||||
@ -97,6 +97,12 @@ const currentPage = ref(1); // 当前页码
|
|||||||
const pageSize = ref(10); // 每页条数
|
const pageSize = ref(10); // 每页条数
|
||||||
const total = ref(0); // 总条数
|
const total = ref(0); // 总条数
|
||||||
|
|
||||||
|
// 当前选中分类的名称
|
||||||
|
const currentCategoryName = computed(() => {
|
||||||
|
const category = categories.value.find(cat => cat.id === selectedCategory.value);
|
||||||
|
return category?.name || '';
|
||||||
|
});
|
||||||
|
|
||||||
// 获取图片地址
|
// 获取图片地址
|
||||||
const getImageUrl = (imagePath: string) => {
|
const getImageUrl = (imagePath: string) => {
|
||||||
if (!imagePath) return "/src/assets/imgs/default.png";
|
if (!imagePath) return "/src/assets/imgs/default.png";
|
||||||
|
|||||||
@ -39,7 +39,7 @@
|
|||||||
<img :src="getImageUrl(article.image)" :alt="article.title">
|
<img :src="getImageUrl(article.image)" :alt="article.title">
|
||||||
</div>
|
</div>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<router-link :to="`/article/${article.id}`" class="article-title-link">
|
<router-link :to="`/article?id=${article.id}&source=siteInformation`" class="article-title-link">
|
||||||
<h3 class="article-title">{{ article.title }}</h3>
|
<h3 class="article-title">{{ article.title }}</h3>
|
||||||
</router-link>
|
</router-link>
|
||||||
<div class="article-meta">
|
<div class="article-meta">
|
||||||
|
|||||||
@ -40,7 +40,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<router-link
|
<router-link
|
||||||
:to="`/article/${article.id}`"
|
:to="`/article?id=${article.id}&source=technicalArticles`"
|
||||||
class="article-title-link"
|
class="article-title-link"
|
||||||
>
|
>
|
||||||
<h3 class="article-title">{{ article.title }}</h3>
|
<h3 class="article-title">{{ article.title }}</h3>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user