ct/app/components/news/new.vue
2025-10-13 09:32:00 +08:00

473 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<script lang="ts" setup>
import {onMounted, ref} from "vue";
// 导入API配置
import {getArticleApiUrl} from '~/config/api'
// 组件属性
interface Props {
articleData?: {
title: string
publishDate: string
category: string
content: string
prevArticle: {
title: string
url: string
}
nextArticle: {
title: string
url: string
}
}
loading?: boolean
error?: string
}
const props = withDefaults(defineProps<Props>(), {
articleData: () => ({
title: '',
publishDate: '',
category: '',
content: '',
prevArticle: {
title: '',
url: '#'
},
nextArticle: {
title: '',
url: '#'
}
}),
loading: true,
error: ''
})
// 内部文章数据(当没有外部传入时使用)
const internalArticleData = ref({
title: '',
publishDate: '',
category: '',
content: '',
prevArticle: {
title: '',
url: '#'
},
nextArticle: {
title: '',
url: '#'
}
})
// 内部加载状态
const internalLoading = ref(true)
const internalError = ref('')
// 使用外部传入的数据或内部数据
const articleData = computed(() => props.articleData || internalArticleData.value)
const loading = computed(() => props.loading !== undefined ? props.loading : internalLoading.value)
const error = computed(() => props.error || internalError.value)
// 获取文章详情
const fetchArticle = async (id: number) => {
try {
internalLoading.value = true
internalError.value = ''
const response = await fetch(getArticleApiUrl('GET', id))
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const res = await response.json()
const data = res.data
console.log('api请求数据', data)
// 分类映射配置
const categoryMapping: Record<string, string> = {
'solution': '解决方案',
'developKnowledge': '开发知识',
'industryTrend': '行业动态',
'aboutUs': '关于我们'
}
data.code = categoryMapping[data.code] || '暂无分类'
// 更新内部文章数据
internalArticleData.value = {
title: data.title || '暂无标题',
publishDate: data.createTime || '暂无日期',
category: data.code || '暂无分类',
content: data.content || '暂无内容',
prevArticle: data.prevArticle || {
title: '暂无上一篇',
url: '#'
},
nextArticle: data.nextArticle || {
title: '暂无下一篇',
url: '#'
}
}
} catch (err) {
console.error('获取文章失败:', err)
internalError.value = '获取文章失败,请稍后重试'
} finally {
internalLoading.value = false
}
}
// 热门标签
const hotTags = ref([
'景区单车', '政务平台', '共享经济', '共享汽车APP开发', '共享类',
'共享汽车软件开发', '共享雨伞APP开发', '共享雨伞软件开发', '共享货车开发'
])
onMounted(() => {
// 加载CSS样式文件
loadCSSFiles()
// 加载JavaScript文件
loadJSFiles()
// 如果没有外部传入的文章数据则获取文章详情使用id=1进行测试
if (!props.articleData || !props.articleData.title) {
fetchArticle(1)
}
})
const loadJSFiles = () => {
const jsFiles = [
'/news/jquery.1.11.3.min.js',
'/news/bootstrap.min.js',
'/news/float.js',
]
jsFiles.forEach(src => {
// 检查是否已经加载过该JS文件
const existingScript = document.querySelector(`script[src="${src}"]`)
if (!existingScript) {
const script = document.createElement('script')
script.src = src
script.type = 'text/javascript'
document.head.appendChild(script)
}
})
}
const loadCSSFiles = () => {
const cssFiles = [
'/news/bootstrap.min.css',
'/news/main22.css',
'/news/new_index.css',
'/news/float.css',
'/news/animate.min.css'
]
cssFiles.forEach(href => {
// 检查是否已经加载过该CSS文件
const existingLink = document.querySelector(`link[href="${href}"]`)
if (!existingLink) {
const link = document.createElement('link')
link.rel = 'stylesheet'
link.href = href
link.type = 'text/css'
document.head.appendChild(link)
}
})
}
</script>
<template>
<view>
<!-- 顶部 -->
<nav class="navbar navbar-default navbar-fixed-top" style="background: rgba(255,255,255,1);">
<div class="container">
<div class="navbar-header">
<button class="navbar-toggle" data-target="#mynav" data-toggle="collapse">
<span>创特科技</span>
</button>
<a href="/"><img
class="img-responsive"
src="/news/top_logo.png"
style='margin-top:4px'></a>
</div>
<div id="mynav" class="collapse navbar-collapse">
<ul class="nav navbar-nav">
<li class="hidden-xs hidden-sm"><a href="/">首页 <span class="sr-only">(current)</span></a>
</li>
<li class="dropdown">
<a data-toggle="dropdown" role="button">解决方案 <span class="caret"/></a>
<ul class="dropdown-menu">
<li><a class="menu-group" href="javascript:void(0);">共享</a></li>
<li><a href="/sharedSolutions/bike">共享单车</a></li>
<li><a href="/sharedSolutions/carShare">共享汽车</a></li>
<li><a href="/sharedSolutions/sharedbed">共享陪护床</a></li>
<li><a href="/sharedSolutions/scooter">共享滑板车</a></li>
<li><a href="/sharedSolutions/eBike">共享助力车</a></li>
</ul>
</li>
<li class="dropdown">
<a
aria-expanded="false" aria-haspopup="true" class="dropdown-toggle"
data-toggle="dropdown" href="/article/437#" role="button">定制开发 <span
class="caret"/></a>
<ul class="dropdown-menu">
<li><a href="/softwareDevelopment/app">APP开发</a></li>
<li class="divider" role="separator"/>
<li><a href="/softwareDevelopment/miniprogram">小程序开发</a></li>
</ul>
</li>
<li><a href="/about">关于创特 </a></li>
</ul>
<ul class="nav navbar-nav navbar-right">
<li><a href="/">电话0755-85225123
<!-- <span class="hidden-xs hidden-sm"> 0755-85225123 / </span> 18123752516 --></a></li>
</ul>
</div><!-- /.navbar-collapse -->
</div>
</nav>
<!-- 文章正文 -->
<section class="container" style="margin-top: 88px;">
<div class="row">
<!-- 左边部分 -->
<div class="col-md-8 wow fadeInLeft animated" style="visibility: visible; animation-name: fadeInLeft;">
<!-- 加载状态 -->
<div v-if="loading" class="loading-container" style="text-align: center; padding: 50px;">
<div
class="loading-spinner"
style="border: 4px solid #f3f3f3; border-top: 4px solid #ff8200; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: 0 auto;"/>
<p style="margin-top: 20px; color: #666;">正在加载文章...</p>
</div>
<!-- 错误提示 -->
<div v-else-if="error" class="error-container" style="text-align: center; padding: 50px; color: #ff6b6b;">
<p>{{ error }}</p>
<button
style="margin-top: 20px; padding: 10px 20px; background: #ff8200; color: white; border: none; border-radius: 4px; cursor: pointer;"
@click="() => { if (!props.articleData || !props.articleData.title) fetchArticle(1) }">
重新加载
</button>
</div>
<!-- 文章内容 -->
<div v-else class="row">
<div class="col-xs-12">
<h2>{{ articleData.title }}</h2>
<p id="label" style="border-bottom: 1px solid #ddd; margin-top: 20px;">
<span>{{ articleData.publishDate }}</span>
<span class="pull-right">分类:{{ articleData.category }}</span>
</p>
<div class="article-content" v-html="articleData.content"/>
</div>
</div>
<!-- 上一篇/下一篇 -->
<div class="nextorpre" style="margin-top: 38px; border-top: 1px solid #ddd; padding-top: 8px;">
<li class="pull-left">上一篇:
<a :href="articleData.prevArticle.url">{{ articleData.prevArticle.title }}</a>
</li>
<li class="pull-right">下一篇:
<a :href="articleData.nextArticle.url">{{ articleData.nextArticle.title }}</a>
</li>
</div>
<br><br><br><br>
</div><!--左边部分-->
<!-- 右边部分 -->
<aside class="col-md-4" style="border-left: 1px solid #eee; background: #fefefe;">
<!-- 热门标签 -->
<div
class="widget tags wow fadeInRight animated"
style="margin-bottom: 30px !important; visibility: visible; animation-name: fadeInRight;">
<h4>热门标签</h4>
<ul class="tag-cloud">
<li v-for="tag in hotTags" :key="tag">
<a class="btn btn-xs" href="#" @click.prevent>{{ tag }}</a>
</li>
</ul>
</div><!-- 热门标签 -->
<!-- 推荐文章 -->
<div
class="sy_news animated" data-wow-delay="200ms"
style="visibility: visible; animation-delay: 200ms; animation-name: fadeInDown;">
<RecommendedArticles
:articles-per-type="3"
:show-title="false"
:show-types="['solution', 'developKnowledge', 'industryTrend']"
/>
</div>
</aside><!-- 右边部分 -->
</div><!--/.row-->
</section><!--/#blog-->
</view>
</template>
<style scoped>
/* 文章内容样式 */
.article-content {
margin-top: 20px;
line-height: 1.8;
font-size: 14px;
color: #333;
}
.article-content p {
margin-bottom: 15px;
}
.article-content img {
max-width: 100%;
height: auto;
display: block;
margin: 15px 0;
}
/* 文本截断样式 */
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* 响应式调整 */
@media (max-width: 1024px) {
.grid-cols-4 {
grid-template-columns: 1fr;
}
.col-md-8, .col-md-4 {
width: 100%;
margin-bottom: 20px;
}
}
/* 悬停效果 */
.hover\:bg-gray-50:hover {
background-color: rgb(249 250 251);
}
.dark .hover\:bg-gray-50:hover {
background-color: rgb(55 65 81);
}
.hover\:bg-gray-100:hover {
background-color: rgb(243 244 246);
}
.dark .hover\:bg-gray-100:hover {
background-color: rgb(75 85 99);
}
/* 标签云样式 */
.tag-cloud li {
display: inline-block;
margin: 2px;
}
.tag-cloud a {
display: inline-block;
padding: 4px 8px;
background-color: #f5f5f5;
color: #666;
text-decoration: none;
border-radius: 3px;
font-size: 12px;
transition: all 0.3s ease;
}
.tag-cloud a:hover {
background-color: #ff8200;
color: #fff;
}
/* 推荐文章样式 */
.sy_news ul li {
padding: 8px 0;
border-bottom: 1px solid #f0f0f0;
}
.sy_news ul li:last-child {
border-bottom: none;
}
.sy_news ul li a {
color: #333;
text-decoration: none;
font-size: 13px;
line-height: 1.4;
display: block;
transition: color 0.3s ease;
}
.sy_news ul li a:hover {
color: #ff8200;
}
.sy_news ul li a span {
color: #999;
font-size: 12px;
}
/* 上一篇下一篇样式 */
.nextorpre {
display: flex;
justify-content: space-between;
align-items: center;
}
.nextorpre li {
list-style: none;
margin: 0;
}
.nextorpre a {
color: #ff8200;
text-decoration: none;
}
.nextorpre a:hover {
text-decoration: underline;
}
/* 加载动画 */
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* 加载状态样式 */
.loading-container {
min-height: 200px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
/* 错误状态样式 */
.error-container {
min-height: 200px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
</style>