diff --git a/app/components/news/new.vue b/app/components/news/new.vue new file mode 100644 index 0000000..2f6afbd --- /dev/null +++ b/app/components/news/new.vue @@ -0,0 +1,316 @@ + + + + + + diff --git a/app/composables/useArticleApi.ts b/app/composables/useArticleApi.ts new file mode 100644 index 0000000..57a2b4f --- /dev/null +++ b/app/composables/useArticleApi.ts @@ -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 + */ +export const fetchArticleList = async (params: ArticleListParams = {}): Promise => { + 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> + */ +export const fetchRecommendedArticles = async ( + types: string[] = ['solution', 'developKnowledge', 'industryTrend'], + pageSize: number = 5 +): Promise> => { + try { + const result: Record = {} + + // 并发获取各类型文章 + 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 = { + '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 + */ + const getArticles = async (params: ArticleListParams = {}): Promise => { + try { + const response = await fetchArticleList(params) + return response.data || [] + } catch (error) { + console.error('获取文章列表失败:', error) + return [] + } + } + + /** + * 获取推荐文章(按类型分组) + * @param types 文章类型数组 + * @param pageSize 每页数量 + * @returns Promise> + */ + const getRecommendedArticles = async ( + types: string[] = ['solution', 'developKnowledge', 'industryTrend'], + pageSize: number = 5 + ): Promise> => { + return await fetchRecommendedArticles(types, pageSize) + } + + return { + getArticles, + getRecommendedArticles, + getArticleTypeName + } +} diff --git a/app/composables/useImageStyles.ts b/app/composables/useImageStyles.ts new file mode 100644 index 0000000..5835063 --- /dev/null +++ b/app/composables/useImageStyles.ts @@ -0,0 +1,82 @@ +import {nextTick, watch, watchEffect, onMounted, type Ref} from 'vue' + +/** + * 图片样式处理 Composable + * 用于强制处理 v-html 内容中的图片样式 + */ +export const useImageStyles = ( + contentRef: Ref | (() => 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 + } +} diff --git a/app/config/api.ts b/app/config/api.ts new file mode 100644 index 0000000..7a40b3b --- /dev/null +++ b/app/config/api.ts @@ -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 diff --git a/app/pages/news/[id].vue b/app/pages/news/[id].vue new file mode 100644 index 0000000..fed531c --- /dev/null +++ b/app/pages/news/[id].vue @@ -0,0 +1,103 @@ + + + + + diff --git a/public/img/news/banner2.png b/public/img/news/banner2.png new file mode 100644 index 0000000..ac535f1 Binary files /dev/null and b/public/img/news/banner2.png differ