temp-save
This commit is contained in:
parent
f4ed5f5db5
commit
c1296a21b4
316
app/components/news/new.vue
Normal file
316
app/components/news/new.vue
Normal file
|
|
@ -0,0 +1,316 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import {computed} from "vue";
|
||||||
|
import {useImageStyles} from '~/composables/useImageStyles';
|
||||||
|
|
||||||
|
// 组件属性
|
||||||
|
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 articleData = computed(() => props.articleData)
|
||||||
|
const loading = computed(() => props.loading)
|
||||||
|
const error = computed(() => props.error)
|
||||||
|
|
||||||
|
// 热门标签
|
||||||
|
const hotTags = ref([
|
||||||
|
'景区单车', '政务平台', '共享经济', '共享汽车APP开发', '共享类',
|
||||||
|
'共享汽车软件开发', '共享雨伞APP开发', '共享雨伞软件开发', '共享货车开发'
|
||||||
|
])
|
||||||
|
|
||||||
|
// 重新加载页面方法
|
||||||
|
const reloadPage = () => {
|
||||||
|
if (typeof window !== 'undefined') {
|
||||||
|
window.location.reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用图片样式处理 composable
|
||||||
|
const {initImageStyles} = useImageStyles(
|
||||||
|
() => props.articleData?.content || '',
|
||||||
|
'.article-content',
|
||||||
|
{
|
||||||
|
maxWidth: '100%',
|
||||||
|
margin: '15px 0',
|
||||||
|
display: 'block',
|
||||||
|
boxSizing: 'border-box'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 使用 Nuxt 的 useHead 来管理页面资源
|
||||||
|
useHead({
|
||||||
|
title: computed(() => articleData.value?.title ? `${articleData.value.title} - 创特科技` : '文章详情 - 创特科技'),
|
||||||
|
meta: [
|
||||||
|
{
|
||||||
|
name: 'description',
|
||||||
|
content: computed(() => articleData.value?.content ? articleData.value.content.substring(0, 160) + '...' : '创特科技专业文章')
|
||||||
|
},
|
||||||
|
{name: 'keywords', content: '创特科技,技术文章,共享单车,软件开发,解决方案'}
|
||||||
|
],
|
||||||
|
link: [
|
||||||
|
{rel: 'stylesheet', href: '/news/bootstrap.min.css'},
|
||||||
|
|
||||||
|
{rel: 'stylesheet', href: '/news/main22.css'},
|
||||||
|
|
||||||
|
{rel: 'stylesheet', href: '/news/new_index.css'},
|
||||||
|
{rel: 'stylesheet', href: '/news/float.css'},
|
||||||
|
{rel: 'stylesheet', href: '/news/animate.min.css'},
|
||||||
|
{rel: 'stylesheet', href: '/css/main2.css'},
|
||||||
|
],
|
||||||
|
script: [
|
||||||
|
|
||||||
|
{src: '/news/float.js', defer: true}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 初始化图片样式处理
|
||||||
|
initImageStyles()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<!-- 顶部 -->
|
||||||
|
|
||||||
|
<!-- 文章正文 -->
|
||||||
|
<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="reloadPage">
|
||||||
|
重新加载
|
||||||
|
</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;
|
||||||
|
overflow: hidden; /* 防止内容溢出 */
|
||||||
|
word-wrap: break-word; /* 长单词换行 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content p {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 响应式调整 */
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
.col-md-8, .col-md-4 {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* 标签云样式 */
|
||||||
|
.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>
|
||||||
|
|
||||||
160
app/composables/useArticleApi.ts
Normal file
160
app/composables/useArticleApi.ts
Normal file
|
|
@ -0,0 +1,160 @@
|
||||||
|
// 文章API服务
|
||||||
|
// 导入API配置
|
||||||
|
import {getApiUrl, API_CONFIG} from '~/config/api'
|
||||||
|
|
||||||
|
export interface Article {
|
||||||
|
id: string
|
||||||
|
title: string
|
||||||
|
brief: string | null
|
||||||
|
content: string | null
|
||||||
|
createTime: string
|
||||||
|
code: string | null
|
||||||
|
status: string | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ArticleListResponse {
|
||||||
|
msg: string
|
||||||
|
code: number
|
||||||
|
data: Article[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ArticleListParams {
|
||||||
|
code?: string // 文章类型:solution、developKnowledge、industryTrend
|
||||||
|
orderByColumn?: string
|
||||||
|
isAsc?: string
|
||||||
|
pageNum?: number
|
||||||
|
pageSize?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文章列表
|
||||||
|
* @param params 查询参数
|
||||||
|
* @returns Promise<ArticleListResponse>
|
||||||
|
*/
|
||||||
|
export const fetchArticleList = async (params: ArticleListParams = {}): Promise<ArticleListResponse> => {
|
||||||
|
try {
|
||||||
|
// 构建查询参数
|
||||||
|
const queryParams = new URLSearchParams()
|
||||||
|
|
||||||
|
if (params.code) queryParams.append('code', params.code)
|
||||||
|
if (params.orderByColumn) queryParams.append('orderByColumn', params.orderByColumn)
|
||||||
|
if (params.isAsc) queryParams.append('isAsc', params.isAsc)
|
||||||
|
if (params.pageNum) queryParams.append('pageNum', params.pageNum.toString())
|
||||||
|
if (params.pageSize) queryParams.append('pageSize', params.pageSize.toString())
|
||||||
|
|
||||||
|
const url = `${getApiUrl(API_CONFIG.ENDPOINTS.ARTICLE.LIST)}?${queryParams.toString()}`
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: API_CONFIG.REQUEST.HEADERS,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: ArticleListResponse = await response.json()
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取文章列表失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取推荐文章(按类型分组)
|
||||||
|
* @param types 文章类型数组
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
* @returns Promise<Record<string, Article[]>>
|
||||||
|
*/
|
||||||
|
export const fetchRecommendedArticles = async (
|
||||||
|
types: string[] = ['solution', 'developKnowledge', 'industryTrend'],
|
||||||
|
pageSize: number = 5
|
||||||
|
): Promise<Record<string, Article[]>> => {
|
||||||
|
try {
|
||||||
|
const result: Record<string, Article[]> = {}
|
||||||
|
|
||||||
|
// 并发获取各类型文章
|
||||||
|
const promises = types.map(async (type) => {
|
||||||
|
const response = await fetchArticleList({
|
||||||
|
code: type,
|
||||||
|
orderByColumn: 'createTime',
|
||||||
|
isAsc: 'descending',
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: pageSize
|
||||||
|
})
|
||||||
|
return {type, articles: response.data}
|
||||||
|
})
|
||||||
|
|
||||||
|
const responses = await Promise.all(promises)
|
||||||
|
|
||||||
|
// 整理结果
|
||||||
|
responses.forEach(({type, articles}) => {
|
||||||
|
result[type] = articles
|
||||||
|
})
|
||||||
|
|
||||||
|
return result
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取推荐文章失败:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文章类型映射
|
||||||
|
*/
|
||||||
|
export const ARTICLE_TYPE_MAP: Record<string, string> = {
|
||||||
|
'solution': '解决方案',
|
||||||
|
'developKnowledge': '开发知识',
|
||||||
|
'industryTrend': '行业动态',
|
||||||
|
'aboutUs': '关于我们'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文章类型的中文名称
|
||||||
|
* @param code 文章类型代码
|
||||||
|
* @returns 中文名称
|
||||||
|
*/
|
||||||
|
export const getArticleTypeName = (code: string): string => {
|
||||||
|
return ARTICLE_TYPE_MAP[code] || '未知类型'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文章API Composable
|
||||||
|
* @returns 文章API相关方法
|
||||||
|
*/
|
||||||
|
export const useArticleApi = () => {
|
||||||
|
/**
|
||||||
|
* 获取文章列表
|
||||||
|
* @param params 查询参数
|
||||||
|
* @returns Promise<Article[]>
|
||||||
|
*/
|
||||||
|
const getArticles = async (params: ArticleListParams = {}): Promise<Article[]> => {
|
||||||
|
try {
|
||||||
|
const response = await fetchArticleList(params)
|
||||||
|
return response.data || []
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取文章列表失败:', error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取推荐文章(按类型分组)
|
||||||
|
* @param types 文章类型数组
|
||||||
|
* @param pageSize 每页数量
|
||||||
|
* @returns Promise<Record<string, Article[]>>
|
||||||
|
*/
|
||||||
|
const getRecommendedArticles = async (
|
||||||
|
types: string[] = ['solution', 'developKnowledge', 'industryTrend'],
|
||||||
|
pageSize: number = 5
|
||||||
|
): Promise<Record<string, Article[]>> => {
|
||||||
|
return await fetchRecommendedArticles(types, pageSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
getArticles,
|
||||||
|
getRecommendedArticles,
|
||||||
|
getArticleTypeName
|
||||||
|
}
|
||||||
|
}
|
||||||
82
app/composables/useImageStyles.ts
Normal file
82
app/composables/useImageStyles.ts
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
import {nextTick, watch, watchEffect, onMounted, type Ref} from 'vue'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 图片样式处理 Composable
|
||||||
|
* 用于强制处理 v-html 内容中的图片样式
|
||||||
|
*/
|
||||||
|
export const useImageStyles = (
|
||||||
|
contentRef: Ref<string> | (() => string),
|
||||||
|
containerSelector: string = '.article-content',
|
||||||
|
options: {
|
||||||
|
maxWidth?: string
|
||||||
|
margin?: string
|
||||||
|
display?: string
|
||||||
|
boxSizing?: string
|
||||||
|
} = {}
|
||||||
|
) => {
|
||||||
|
// 默认配置
|
||||||
|
const defaultOptions = {
|
||||||
|
maxWidth: 'calc(100% - 0px)',
|
||||||
|
margin: '15px 0',
|
||||||
|
display: 'block',
|
||||||
|
boxSizing: 'border-box',
|
||||||
|
...options
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 强制处理图片样式
|
||||||
|
*/
|
||||||
|
const forceImageStyles = () => {
|
||||||
|
if (typeof document === 'undefined') return
|
||||||
|
|
||||||
|
const images = document.querySelectorAll(`${containerSelector} img`)
|
||||||
|
images.forEach((img: any) => {
|
||||||
|
// 强制设置样式
|
||||||
|
img.style.maxWidth = defaultOptions.maxWidth
|
||||||
|
img.style.height = 'auto'
|
||||||
|
img.style.width = 'auto'
|
||||||
|
img.style.display = defaultOptions.display
|
||||||
|
img.style.margin = defaultOptions.margin
|
||||||
|
img.style.boxSizing = defaultOptions.boxSizing
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理图片样式的核心方法
|
||||||
|
*/
|
||||||
|
const processImageStyles = () => {
|
||||||
|
nextTick(() => {
|
||||||
|
forceImageStyles()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化图片样式处理
|
||||||
|
*/
|
||||||
|
const initImageStyles = () => {
|
||||||
|
// 组件挂载时处理
|
||||||
|
onMounted(() => {
|
||||||
|
processImageStyles()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听内容变化
|
||||||
|
if (typeof contentRef === 'function') {
|
||||||
|
// 如果是函数,使用 watchEffect 监听
|
||||||
|
watchEffect(() => {
|
||||||
|
contentRef() // 触发函数执行
|
||||||
|
processImageStyles()
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// 如果是 Ref,监听其值
|
||||||
|
watch(contentRef, () => {
|
||||||
|
processImageStyles()
|
||||||
|
}, {deep: true})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
forceImageStyles,
|
||||||
|
processImageStyles,
|
||||||
|
initImageStyles
|
||||||
|
}
|
||||||
|
}
|
||||||
61
app/config/api.ts
Normal file
61
app/config/api.ts
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
/**
|
||||||
|
* API配置文件
|
||||||
|
* 统一管理所有API相关配置
|
||||||
|
*/
|
||||||
|
|
||||||
|
// API基础地址配置
|
||||||
|
export const API_CONFIG = {
|
||||||
|
// 开发环境API地址
|
||||||
|
BASE_URL: 'http://192.168.1.4:4101',
|
||||||
|
|
||||||
|
// API端点配置
|
||||||
|
ENDPOINTS: {
|
||||||
|
// 文章相关API
|
||||||
|
ARTICLE: {
|
||||||
|
LIST: '/app/owArticle/list',
|
||||||
|
GET: '/app/owArticle/get',
|
||||||
|
CREATE: '/app/owArticle/create',
|
||||||
|
UPDATE: '/app/owArticle/update',
|
||||||
|
DELETE: '/app/owArticle/delete'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 请求配置
|
||||||
|
REQUEST: {
|
||||||
|
TIMEOUT: 10000, // 请求超时时间(毫秒)
|
||||||
|
RETRY_COUNT: 3, // 重试次数
|
||||||
|
HEADERS: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} as const
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取完整的API URL
|
||||||
|
* @param endpoint API端点
|
||||||
|
* @returns 完整的API URL
|
||||||
|
*/
|
||||||
|
export const getApiUrl = (endpoint: string): string => {
|
||||||
|
return `${API_CONFIG.BASE_URL}${endpoint}`
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取文章API URL
|
||||||
|
* @param action 操作类型
|
||||||
|
* @param id 文章ID(可选)
|
||||||
|
* @returns 文章API URL
|
||||||
|
*/
|
||||||
|
export const getArticleApiUrl = (action: keyof typeof API_CONFIG.ENDPOINTS.ARTICLE, id?: string | number): string => {
|
||||||
|
const endpoint = API_CONFIG.ENDPOINTS.ARTICLE[action]
|
||||||
|
const baseUrl = getApiUrl(endpoint)
|
||||||
|
|
||||||
|
if (id && (action === 'GET' || action === 'UPDATE' || action === 'DELETE')) {
|
||||||
|
return `${baseUrl}/${id}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出默认配置
|
||||||
|
export default API_CONFIG
|
||||||
103
app/pages/news/[id].vue
Normal file
103
app/pages/news/[id].vue
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
<script lang="ts" setup>
|
||||||
|
// 设置页面布局
|
||||||
|
// 导入API配置
|
||||||
|
import {getArticleApiUrl} from '~/config/api'
|
||||||
|
|
||||||
|
|
||||||
|
// 获取路由参数
|
||||||
|
const route = useRoute()
|
||||||
|
const articleId = route.params.id as string
|
||||||
|
|
||||||
|
// 文章数据
|
||||||
|
const articleData = ref({
|
||||||
|
title: '',
|
||||||
|
publishDate: '',
|
||||||
|
category: '',
|
||||||
|
content: '',
|
||||||
|
prevArticle: {
|
||||||
|
title: '',
|
||||||
|
url: '#'
|
||||||
|
},
|
||||||
|
nextArticle: {
|
||||||
|
title: '',
|
||||||
|
url: '#'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 加载状态
|
||||||
|
const loading = ref(true)
|
||||||
|
const error = ref('')
|
||||||
|
|
||||||
|
// 获取文章详情
|
||||||
|
const fetchArticle = async (id: string) => {
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
error.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] || '暂无分类'
|
||||||
|
|
||||||
|
// 更新文章数据
|
||||||
|
articleData.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)
|
||||||
|
error.value = '获取文章失败,请稍后重试'
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件挂载时获取文章详情
|
||||||
|
onMounted(() => {
|
||||||
|
if (articleId) {
|
||||||
|
fetchArticle(articleId)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 监听路由变化
|
||||||
|
watch(() => route.params.id, (newId) => {
|
||||||
|
if (newId) {
|
||||||
|
fetchArticle(newId as string)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<view>
|
||||||
|
<NewsNew :article-data="articleData" :error="error" :loading="loading"/>
|
||||||
|
</view>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
</style>
|
||||||
BIN
public/img/news/banner2.png
Normal file
BIN
public/img/news/banner2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 MiB |
Loading…
Reference in New Issue
Block a user