增加程序相关详情页

This commit is contained in:
李志强 2025-12-25 17:28:52 +08:00
parent 89aac61880
commit fed42c7589
13 changed files with 519 additions and 27 deletions

View File

@ -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']

View File

@ -1,7 +0,0 @@
# 寮€鍙戠幆澧冮厤缃?# API 鍩虹鍦板潃
VITE_API_DOMAIN=http://localhost:8000
# 寮€鍙戠幆澧冩爣璇?NODE_ENV=development
# 璋冭瘯妯″紡
VITE_DEBUG=true

View File

@ -1,9 +0,0 @@
# 鐢熶骇鐜閰嶇疆
# API 鍩虹鍦板潃
VITE_API_DOMAIN=https://api.yourdomain.com
# 鐢熶骇鐜鏍囪瘑
NODE_ENV=production
# 璋冭瘯妯″紡
VITE_DEBUG=false

View File

@ -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");
}
}

View File

@ -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");
}
}

View File

@ -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");
}
}

View File

@ -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");

View File

@ -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",

View File

@ -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
);

View 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>

View File

@ -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
);

View File

@ -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
);

View File

@ -29,6 +29,9 @@ export default defineConfig({
},
css: {
preprocessorOptions: {
less: {
// Less options
},
scss: {
additionalData: '@import "./src/assets/scss/main.scss";',
},