完成菜单单页制作
This commit is contained in:
parent
996a41884f
commit
89aac61880
BIN
frontend/.env.development
Normal file
BIN
frontend/.env.development
Normal file
Binary file not shown.
BIN
frontend/.env.production
Normal file
BIN
frontend/.env.production
Normal file
Binary file not shown.
7
frontend/frontend/.env.development
Normal file
7
frontend/frontend/.env.development
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# 寮€鍙戠幆澧冮厤缃?# API 鍩虹鍦板潃
|
||||||
|
VITE_API_DOMAIN=http://localhost:8000
|
||||||
|
|
||||||
|
# 寮€鍙戠幆澧冩爣璇?NODE_ENV=development
|
||||||
|
|
||||||
|
# 璋冭瘯妯″紡
|
||||||
|
VITE_DEBUG=true
|
||||||
9
frontend/frontend/frontend/.env.production
Normal file
9
frontend/frontend/frontend/.env.production
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
# 鐢熶骇鐜閰嶇疆
|
||||||
|
# API 鍩虹鍦板潃
|
||||||
|
VITE_API_DOMAIN=https://api.yourdomain.com
|
||||||
|
|
||||||
|
# 鐢熶骇鐜鏍囪瘑
|
||||||
|
NODE_ENV=production
|
||||||
|
|
||||||
|
# 璋冭瘯妯″紡
|
||||||
|
VITE_DEBUG=false
|
||||||
@ -4,8 +4,8 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite --mode development",
|
||||||
"build": "vue-tsc -b && vite build",
|
"build": "vite build --mode production",
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
//进行接口API的统一管理
|
//进行接口API的统一管理
|
||||||
import { request } from "./axios";
|
|
||||||
|
|
||||||
export class UserService {
|
export class UserService {
|
||||||
|
|
||||||
|
|||||||
21
frontend/src/api/downloadGames.ts
Normal file
21
frontend/src/api/downloadGames.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//进行接口API的统一管理
|
||||||
|
import { request } from "./axios";
|
||||||
|
|
||||||
|
export class downloadGames {
|
||||||
|
/**
|
||||||
|
* @description 获取downloadGames分类
|
||||||
|
* @return {Promise} 返回请求结果
|
||||||
|
*/
|
||||||
|
static async getDownloadGamesCategory() {
|
||||||
|
return request("/index/program/getDownloadGamesCategory", "get");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取downloadGames文章列表
|
||||||
|
* @param {string} cateid - 分类ID
|
||||||
|
* @return {Promise} 返回请求结果
|
||||||
|
*/
|
||||||
|
static async getDownloadGamesLists(cateid: string) {
|
||||||
|
return request("/index/program/getDownloadGamesLists", { cateid }, "get");
|
||||||
|
}
|
||||||
|
}
|
||||||
21
frontend/src/api/downloadPrograms.ts
Normal file
21
frontend/src/api/downloadPrograms.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//进行接口API的统一管理
|
||||||
|
import { request } from "./axios";
|
||||||
|
|
||||||
|
export class downloadPrograms {
|
||||||
|
/**
|
||||||
|
* @description 获取downloadPrograms分类
|
||||||
|
* @return {Promise} 返回请求结果
|
||||||
|
*/
|
||||||
|
static async getDownloadProgramsCategory() {
|
||||||
|
return request("/index/program/getDownloadProgramsCategory", "get");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取downloadPrograms文章列表
|
||||||
|
* @param {string} cateid - 分类ID
|
||||||
|
* @return {Promise} 返回请求结果
|
||||||
|
*/
|
||||||
|
static async getDownloadProgramsLists(cateid: string) {
|
||||||
|
return request("/index/program/getDownloadProgramsLists", { cateid }, "get");
|
||||||
|
}
|
||||||
|
}
|
||||||
21
frontend/src/api/officeResources.ts
Normal file
21
frontend/src/api/officeResources.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
//进行接口API的统一管理
|
||||||
|
import { request } from "./axios";
|
||||||
|
|
||||||
|
export class officeResources {
|
||||||
|
/**
|
||||||
|
* @description 获取officeResources分类
|
||||||
|
* @return {Promise} 返回请求结果
|
||||||
|
*/
|
||||||
|
static async getOfficeResourcesCategory() {
|
||||||
|
return request("/index/program/getOfficeResourcesCategory", "get");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取officeResources文章列表
|
||||||
|
* @param {string} cateid - 分类ID
|
||||||
|
* @return {Promise} 返回请求结果
|
||||||
|
*/
|
||||||
|
static async getOfficeResourcesLists(cateid: string) {
|
||||||
|
return request("/index/program/getOfficeResourcesLists", { cateid }, "get");
|
||||||
|
}
|
||||||
|
}
|
||||||
25
frontend/src/api/technicalArticles.ts
Normal file
25
frontend/src/api/technicalArticles.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
//进行接口API的统一管理
|
||||||
|
import { request } from "./axios";
|
||||||
|
|
||||||
|
export class technicalArticles {
|
||||||
|
/**
|
||||||
|
* @description 获取technicalArticles分类
|
||||||
|
* @return {Promise} 返回请求结果
|
||||||
|
*/
|
||||||
|
static async getTechnicalArticlesCategory() {
|
||||||
|
return request("/index/articles/getTechnicalArticlesCategory", "get");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description 获取technicalArticles文章列表
|
||||||
|
* @param {string} cateid - 分类ID
|
||||||
|
* @return {Promise} 返回请求结果
|
||||||
|
*/
|
||||||
|
static async getTechnicalArticlesLists(cateid: string) {
|
||||||
|
return request(
|
||||||
|
"/index/articles/getTechnicalArticlesLists",
|
||||||
|
{ cateid },
|
||||||
|
"get"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
frontend/src/assets/imgs/default.png
Normal file
BIN
frontend/src/assets/imgs/default.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.8 KiB |
@ -1,11 +1,80 @@
|
|||||||
<template>
|
<template>
|
||||||
<Header />
|
<Header />
|
||||||
<div class="download-games-page">
|
<div class="resources-page">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="page-title">游戏下载</h1>
|
<div class="top-content">
|
||||||
<div class="content">
|
<h1 class="page-title">办公资源</h1>
|
||||||
<p>这里是游戏下载页面内容。</p>
|
<div class="content flex flex-column align-items-center">
|
||||||
<p>您可以在这里提供各种游戏的下载链接和介绍。</p>
|
<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/${resource.id}`"
|
||||||
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -13,26 +82,137 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 Footer from "@/views/components/footer.vue";
|
||||||
import Header from "../components/header.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 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 fetchResources = async (_page: number = currentPage.value) => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const response: any = await downloadGames.getDownloadGamesLists(
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.download-games-page {
|
.resources-page {
|
||||||
padding: 120px 20px 40px; // 顶部padding考虑固定header高度
|
padding-top: 100px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #f5f5f5;
|
background: #f9fafc;
|
||||||
|
|
||||||
.container {
|
.top-content {
|
||||||
max-width: 1200px;
|
background: linear-gradient(135deg, #1e9fff 0%, #0d8aff 100%);
|
||||||
margin: 0 auto;
|
padding: 80px 40px;
|
||||||
background: #fff;
|
display: flex;
|
||||||
border-radius: 8px;
|
flex-direction: column;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
align-items: center;
|
||||||
padding: 40px;
|
justify-content: center;
|
||||||
|
color: var(--white);
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
color: #333;
|
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@ -40,7 +220,6 @@ import Header from "../components/header.vue";
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
color: #666;
|
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@ -48,17 +227,203 @@ import Header from "../components/header.vue";
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
.main-content {
|
||||||
.download-games-page {
|
width: 1200px;
|
||||||
padding: 100px 15px 20px;
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
gap: 30px;
|
||||||
|
padding: 40px 0;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
.container {
|
.left-menu {
|
||||||
|
background-color: var(--white);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 250px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: flex-start;
|
||||||
|
|
||||||
.page-title {
|
.menu-title {
|
||||||
font-size: 24px;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,80 @@
|
|||||||
<template>
|
<template>
|
||||||
<Header />
|
<Header />
|
||||||
<div class="download-programs-page">
|
<div class="resources-page">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1 class="page-title">程序下载</h1>
|
<div class="top-content">
|
||||||
<div class="content">
|
<h1 class="page-title">办公资源</h1>
|
||||||
<p>这里是程序下载页面内容。</p>
|
<div class="content flex flex-column align-items-center">
|
||||||
<p>您可以在这里提供各种软件程序的下载链接和介绍。</p>
|
<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/${resource.id}`"
|
||||||
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -13,26 +82,137 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
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";
|
||||||
|
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 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 fetchResources = async (_page: number = currentPage.value) => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const response: any = await downloadPrograms.getDownloadProgramsLists(
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.download-programs-page {
|
.resources-page {
|
||||||
padding: 120px 20px 40px; // 顶部padding考虑固定header高度
|
padding-top: 100px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #f5f5f5;
|
background: #f9fafc;
|
||||||
|
|
||||||
.container {
|
.top-content {
|
||||||
max-width: 1200px;
|
background: linear-gradient(135deg, #1e9fff 0%, #0d8aff 100%);
|
||||||
margin: 0 auto;
|
padding: 80px 40px;
|
||||||
background: #fff;
|
display: flex;
|
||||||
border-radius: 8px;
|
flex-direction: column;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
align-items: center;
|
||||||
padding: 40px;
|
justify-content: center;
|
||||||
|
color: var(--white);
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
color: #333;
|
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@ -40,7 +220,6 @@ import Footer from "@/views/components/footer.vue";
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
color: #666;
|
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@ -48,17 +227,203 @@ import Footer from "@/views/components/footer.vue";
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
.main-content {
|
||||||
.download-programs-page {
|
width: 1200px;
|
||||||
padding: 100px 15px 20px;
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
gap: 30px;
|
||||||
|
padding: 40px 0;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
.container {
|
.left-menu {
|
||||||
|
background-color: var(--white);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 250px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: flex-start;
|
||||||
|
|
||||||
.page-title {
|
.menu-title {
|
||||||
font-size: 24px;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,80 @@
|
|||||||
<template>
|
<template>
|
||||||
<Header />
|
<Header />
|
||||||
<div class="office-resources-page">
|
<div class="resources-page">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<div class="top-content">
|
||||||
<h1 class="page-title">办公资源</h1>
|
<h1 class="page-title">办公资源</h1>
|
||||||
<div class="content">
|
<div class="content flex flex-column align-items-center">
|
||||||
<p>这里是办公资源页面内容。</p>
|
<p>发现优质程序与工具</p>
|
||||||
<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/${resource.id}`"
|
||||||
|
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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -13,26 +82,137 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
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";
|
||||||
|
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 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 fetchResources = async (_page: number = currentPage.value) => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const response: any = await officeResources.getOfficeResourcesLists(
|
||||||
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.office-resources-page {
|
.resources-page {
|
||||||
padding: 120px 20px 40px; // 顶部padding考虑固定header高度
|
padding-top: 100px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #f5f5f5;
|
background: #f9fafc;
|
||||||
|
|
||||||
.container {
|
.top-content {
|
||||||
max-width: 1200px;
|
background: linear-gradient(135deg, #1e9fff 0%, #0d8aff 100%);
|
||||||
margin: 0 auto;
|
padding: 80px 40px;
|
||||||
background: #fff;
|
display: flex;
|
||||||
border-radius: 8px;
|
flex-direction: column;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
align-items: center;
|
||||||
padding: 40px;
|
justify-content: center;
|
||||||
|
color: var(--white);
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
color: #333;
|
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@ -40,7 +220,6 @@ import Footer from "@/views/components/footer.vue";
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
color: #666;
|
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@ -48,17 +227,203 @@ import Footer from "@/views/components/footer.vue";
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
.main-content {
|
||||||
.office-resources-page {
|
width: 1200px;
|
||||||
padding: 100px 15px 20px;
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
gap: 30px;
|
||||||
|
padding: 40px 0;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
.container {
|
.left-menu {
|
||||||
|
background-color: var(--white);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 250px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: flex-start;
|
||||||
|
|
||||||
.page-title {
|
.menu-title {
|
||||||
font-size: 24px;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -130,7 +130,7 @@ const fetchCategories = async () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 获取文章列表
|
// 获取文章列表
|
||||||
const fetchArticles = async (page: number = currentPage.value) => {
|
const fetchArticles = async (_page: number = currentPage.value) => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
const response: any = await siteInformation.getSiteInformationLists(
|
const response: any = await siteInformation.getSiteInformationLists(
|
||||||
|
|||||||
@ -1,11 +1,80 @@
|
|||||||
<template>
|
<template>
|
||||||
<Header />
|
<Header />
|
||||||
<div class="technical-articles-page">
|
<div class="articles-page">
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<div class="top-content">
|
||||||
<h1 class="page-title">技术文章</h1>
|
<h1 class="page-title">技术文章</h1>
|
||||||
<div class="content">
|
<div class="content flex flex-column align-items-center">
|
||||||
<p>这里是技术文章页面内容。</p>
|
<p>探索知识与洞见</p>
|
||||||
<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="articles.length === 0" class="no-data">暂无内容</div>
|
||||||
|
<div v-else class="articles-grid">
|
||||||
|
<div
|
||||||
|
v-for="article in articles"
|
||||||
|
:key="article.id"
|
||||||
|
class="article-card"
|
||||||
|
>
|
||||||
|
<div class="article-image">
|
||||||
|
<img :src="getImageUrl(article.image)" :alt="article.title" />
|
||||||
|
</div>
|
||||||
|
<div class="card-header">
|
||||||
|
<router-link
|
||||||
|
:to="`/article/${article.id}`"
|
||||||
|
class="article-title-link"
|
||||||
|
>
|
||||||
|
<h3 class="article-title">{{ article.title }}</h3>
|
||||||
|
</router-link>
|
||||||
|
<div class="article-meta">
|
||||||
|
<span class="article-view">
|
||||||
|
<i class="fa-solid fa-eye"></i>
|
||||||
|
{{ article.view || 0 }}
|
||||||
|
</span>
|
||||||
|
<span class="article-likes">
|
||||||
|
<i class="fa-solid fa-heart"></i>
|
||||||
|
{{ article.likes || 0 }}
|
||||||
|
</span>
|
||||||
|
<span class="article-date">{{
|
||||||
|
formatDate(article.publishdate)
|
||||||
|
}}</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -13,26 +82,142 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from "vue";
|
||||||
|
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";
|
||||||
|
import { technicalArticles } from "@/api/technicalArticles";
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const categories = ref<any[]>([]); // 分类列表
|
||||||
|
const articles = ref<any[]>([]); // 文章列表
|
||||||
|
const selectedCategory = ref<string>(""); // 选中的分类
|
||||||
|
const loading = ref(false); // 加载状态
|
||||||
|
const currentPage = ref(1); // 当前页码
|
||||||
|
const pageSize = ref(10); // 每页条数
|
||||||
|
const total = ref(0); // 总条数
|
||||||
|
|
||||||
|
// 获取图片地址
|
||||||
|
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 technicalArticles.getTechnicalArticlesCategory();
|
||||||
|
|
||||||
|
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;
|
||||||
|
fetchArticles();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
categories.value = [];
|
||||||
|
ElMessage.warning(response.data?.msg || response.msg || "获取分类失败");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取分类失败:", error);
|
||||||
|
categories.value = [];
|
||||||
|
ElMessage.warning("获取分类失败");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取文章列表
|
||||||
|
const fetchArticles = async (_page: number = currentPage.value) => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const response: any = await technicalArticles.getTechnicalArticlesLists(
|
||||||
|
selectedCategory.value
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = response.data?.data;
|
||||||
|
if (data?.articles && Array.isArray(data.articles)) {
|
||||||
|
articles.value = data.articles;
|
||||||
|
total.value = data.total || 0;
|
||||||
|
currentPage.value = data.page || 1;
|
||||||
|
pageSize.value = data.limit || 10;
|
||||||
|
} else {
|
||||||
|
articles.value = [];
|
||||||
|
total.value = 0;
|
||||||
|
ElMessage.warning(response.data?.msg || "获取文章失败");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("获取文章失败:", error);
|
||||||
|
articles.value = [];
|
||||||
|
total.value = 0;
|
||||||
|
ElMessage.warning("获取文章失败");
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 选择分类
|
||||||
|
const selectCategory = (categoryId: string) => {
|
||||||
|
selectedCategory.value = categoryId;
|
||||||
|
currentPage.value = 1; // 切换分类时重置到第一页
|
||||||
|
fetchArticles(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 分页处理
|
||||||
|
const handlePageChange = (page: number) => {
|
||||||
|
currentPage.value = page;
|
||||||
|
fetchArticles(page);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 每页条数变化
|
||||||
|
const handlePageSizeChange = (size: number) => {
|
||||||
|
pageSize.value = size;
|
||||||
|
currentPage.value = 1; // 重置到第一页
|
||||||
|
fetchArticles(1);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 格式化日期
|
||||||
|
const formatDate = (timestamp: string | number) => {
|
||||||
|
if (!timestamp) return "";
|
||||||
|
// 如果是字符串数字,转换为数字
|
||||||
|
const numTimestamp =
|
||||||
|
typeof timestamp === "string" ? parseInt(timestamp) : timestamp;
|
||||||
|
// 如果时间戳是秒格式(10位),转换为毫秒
|
||||||
|
const date = new Date(
|
||||||
|
numTimestamp < 1e10 ? numTimestamp * 1000 : numTimestamp
|
||||||
|
);
|
||||||
|
return date.toLocaleDateString("zh-CN", {
|
||||||
|
year: "numeric",
|
||||||
|
month: "2-digit",
|
||||||
|
day: "2-digit",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 组件挂载时获取数据
|
||||||
|
onMounted(() => {
|
||||||
|
fetchCategories();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
.technical-articles-page {
|
.articles-page {
|
||||||
padding: 120px 20px 40px; // 顶部padding考虑固定header高度
|
padding-top: 100px;
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
background: #f5f5f5;
|
background: #f9fafc;
|
||||||
|
|
||||||
.container {
|
.top-content {
|
||||||
max-width: 1200px;
|
background: linear-gradient(135deg, #1e9fff 0%, #0d8aff 100%);
|
||||||
margin: 0 auto;
|
padding: 80px 40px;
|
||||||
background: #fff;
|
display: flex;
|
||||||
border-radius: 8px;
|
flex-direction: column;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
align-items: center;
|
||||||
padding: 40px;
|
justify-content: center;
|
||||||
|
color: var(--white);
|
||||||
|
|
||||||
.page-title {
|
.page-title {
|
||||||
color: #333;
|
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
@ -40,7 +225,6 @@ import Footer from "@/views/components/footer.vue";
|
|||||||
}
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
color: #666;
|
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
@ -48,17 +232,203 @@ import Footer from "@/views/components/footer.vue";
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
.main-content {
|
||||||
.technical-articles-page {
|
width: 1200px;
|
||||||
padding: 100px 15px 20px;
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
gap: 30px;
|
||||||
|
padding: 40px 0;
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
.container {
|
.left-menu {
|
||||||
|
background-color: var(--white);
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 250px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: flex-start;
|
||||||
|
|
||||||
.page-title {
|
.menu-title {
|
||||||
font-size: 24px;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.articles-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
|
||||||
|
.article-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);
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-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;
|
||||||
|
|
||||||
|
.article-title-link {
|
||||||
|
text-decoration: none;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
&:hover .article-title {
|
||||||
|
color: #007bff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-meta {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
|
||||||
|
.article-view,
|
||||||
|
.article-likes,
|
||||||
|
.article-date {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
padding: 0 16px;
|
||||||
|
|
||||||
|
.article-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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user