增加程序相关详情页
This commit is contained in:
parent
89aac61880
commit
fed42c7589
4
frontend/components.d.ts
vendored
4
frontend/components.d.ts
vendored
@ -12,10 +12,14 @@ export {}
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||
ElBreadcrumb: typeof import('element-plus/es')['ElBreadcrumb']
|
||||
ElBreadcrumbItem: typeof import('element-plus/es')['ElBreadcrumbItem']
|
||||
ElButton: typeof import('element-plus/es')['ElButton']
|
||||
ElDivider: typeof import('element-plus/es')['ElDivider']
|
||||
ElDropdown: typeof import('element-plus/es')['ElDropdown']
|
||||
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
|
||||
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
|
||||
ElEmpty: typeof import('element-plus/es')['ElEmpty']
|
||||
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||
ElInput: typeof import('element-plus/es')['ElInput']
|
||||
ElPagination: typeof import('element-plus/es')['ElPagination']
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
# 寮€鍙戠幆澧冮厤缃?# API 鍩虹鍦板潃
|
||||
VITE_API_DOMAIN=http://localhost:8000
|
||||
|
||||
# 寮€鍙戠幆澧冩爣璇?NODE_ENV=development
|
||||
|
||||
# 璋冭瘯妯″紡
|
||||
VITE_DEBUG=true
|
||||
@ -1,9 +0,0 @@
|
||||
# 鐢熶骇鐜閰嶇疆
|
||||
# API 鍩虹鍦板潃
|
||||
VITE_API_DOMAIN=https://api.yourdomain.com
|
||||
|
||||
# 鐢熶骇鐜鏍囪瘑
|
||||
NODE_ENV=production
|
||||
|
||||
# 璋冭瘯妯″紡
|
||||
VITE_DEBUG=false
|
||||
@ -18,4 +18,22 @@ export class downloadGames {
|
||||
static async getDownloadGamesLists(cateid: string) {
|
||||
return request("/index/program/getDownloadGamesLists", { cateid }, "get");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取downloadGames文章列表
|
||||
* @param {string} cateid - 分类ID
|
||||
* @return {Promise} 返回请求结果
|
||||
*/
|
||||
static async getDownloadGamesSimpleLists(cateid: string) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
@ -16,6 +16,32 @@ export class downloadPrograms {
|
||||
* @return {Promise} 返回请求结果
|
||||
*/
|
||||
static async getDownloadProgramsLists(cateid: string) {
|
||||
return request("/index/program/getDownloadProgramsLists", { cateid }, "get");
|
||||
return request(
|
||||
"/index/program/getDownloadProgramsLists",
|
||||
{ cateid },
|
||||
"get"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取downloadPrograms文章列表
|
||||
* @param {string} cateid - 分类ID
|
||||
* @return {Promise} 返回请求结果
|
||||
*/
|
||||
static async getDownloadProgramsSimpleLists(cateid: string) {
|
||||
return request(
|
||||
"/index/program/getDownloadProgramsSimpleLists",
|
||||
{ cateid },
|
||||
"get"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取downloadPrograms文章详情
|
||||
* @param {string} id - 内容ID
|
||||
* @return {Promise} 返回请求结果
|
||||
*/
|
||||
static async getDownloadProgramsDetail(id: string) {
|
||||
return request("/index/program/getDownloadProgramsDetail", { id }, "get");
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,4 +18,26 @@ export class officeResources {
|
||||
static async getOfficeResourcesLists(cateid: string) {
|
||||
return request("/index/program/getOfficeResourcesLists", { cateid }, "get");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取officeResources文章列表
|
||||
* @param {string} cateid - 分类ID
|
||||
* @return {Promise} 返回请求结果
|
||||
*/
|
||||
static async getOfficeResourcesSimpleLists(cateid: string) {
|
||||
return request(
|
||||
"/index/program/getOfficeResourcesSimpleLists",
|
||||
{ cateid },
|
||||
"get"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取officeResources文章详情
|
||||
* @param {string} id - 内容ID
|
||||
* @return {Promise} 返回请求结果
|
||||
*/
|
||||
static async getOfficeResourcesDetail(id: string) {
|
||||
return request("/index/program/getOfficeResourcesDetail", { id }, "get");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { createApp } from "vue";
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import "@/assets/less/global.less";
|
||||
import "@/assets/css/all.css"
|
||||
import App from "./App.vue";
|
||||
@ -7,4 +9,4 @@ import { createPinia } from 'pinia'
|
||||
|
||||
const pinia = createPinia()
|
||||
|
||||
createApp(App).use(router).use(pinia).mount("#app");
|
||||
createApp(App).use(router).use(pinia).use(ElementPlus).mount("#app");
|
||||
|
||||
@ -32,6 +32,11 @@ const router = createRouter({
|
||||
name: "downloadPrograms",
|
||||
component: () => import("@/views/downloadPrograms/index.vue"),
|
||||
},
|
||||
{
|
||||
path: "/downloadPrograms/:id",
|
||||
name: "downloadProgramsDetail",
|
||||
component: () => import("@/views/downloadPrograms/detail.vue"),
|
||||
},
|
||||
{
|
||||
path: "/downloadGames",
|
||||
name: "downloadGames",
|
||||
|
||||
@ -131,10 +131,10 @@ const fetchCategories = async () => {
|
||||
};
|
||||
|
||||
// 获取文章列表
|
||||
const fetchResources = async (_page: number = currentPage.value) => {
|
||||
const fetchResources = async (page: number = currentPage.value) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response: any = await downloadGames.getDownloadGamesLists(
|
||||
const response: any = await downloadGames.getDownloadGamesSimpleLists(
|
||||
selectedCategory.value
|
||||
);
|
||||
|
||||
|
||||
428
frontend/src/views/downloadPrograms/detail.vue
Normal file
428
frontend/src/views/downloadPrograms/detail.vue
Normal file
@ -0,0 +1,428 @@
|
||||
<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>
|
||||
@ -40,7 +40,7 @@
|
||||
</div>
|
||||
<div class="card-header">
|
||||
<router-link
|
||||
:to="`/resource/${resource.id}`"
|
||||
:to="`/downloadPrograms/detail?id=${resource.id}`"
|
||||
class="resource-title-link"
|
||||
>
|
||||
<h3 class="resource-title">{{ resource.title }}</h3>
|
||||
@ -131,10 +131,10 @@ const fetchCategories = async () => {
|
||||
};
|
||||
|
||||
// 获取文章列表
|
||||
const fetchResources = async (_page: number = currentPage.value) => {
|
||||
const fetchResources = async (page: number = currentPage.value) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response: any = await downloadPrograms.getDownloadProgramsLists(
|
||||
const response: any = await downloadPrograms.getDownloadProgramsSimpleLists(
|
||||
selectedCategory.value
|
||||
);
|
||||
|
||||
|
||||
@ -131,10 +131,10 @@ const fetchCategories = async () => {
|
||||
};
|
||||
|
||||
// 获取文章列表
|
||||
const fetchResources = async (_page: number = currentPage.value) => {
|
||||
const fetchResources = async (page: number = currentPage.value) => {
|
||||
loading.value = true;
|
||||
try {
|
||||
const response: any = await officeResources.getOfficeResourcesLists(
|
||||
const response: any = await officeResources.getOfficeResourcesSimpleLists(
|
||||
selectedCategory.value
|
||||
);
|
||||
|
||||
|
||||
@ -29,6 +29,9 @@ export default defineConfig({
|
||||
},
|
||||
css: {
|
||||
preprocessorOptions: {
|
||||
less: {
|
||||
// Less options
|
||||
},
|
||||
scss: {
|
||||
additionalData: '@import "./src/assets/scss/main.scss";',
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user