123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149 |
- <template>
- <div class="software-intro-container">
- <!-- 加载状态 -->
- <div v-if="isLoading" class="loading-message">加载中...</div>
- <!-- 错误信息 -->
- <div v-else-if="error" class="error-message">{{ error }}</div>
- <!-- 正常展示内容 -->
- <div v-else class="content-wrapper">
- <h1 class="title">{{ softwareIntro.title }}</h1>
- <div v-for="(paragraph, index) in softwareIntro.introParagraphs" :key="index" class="paragraph">
- <p v-html="paragraph"></p>
- </div>
- </div>
- </div>
- </template>
- <script setup lang="ts">
- import { ref, onMounted, defineProps } from 'vue';
- import axios from 'axios';
- // 定义 targetId 为 prop
- const props = defineProps({
- targetId: {
- type: Number,
- default: 1
- }
- });
- // 存储介绍内容
- const softwareIntro = ref({
- title: '',
- introParagraphs: []
- });
- // 加载状态
- const isLoading = ref(true);
- // 错误信息
- const error = ref('');
- // 判断是否为标题段落
- const isTitle = (paragraph: string) => {
- return /^[^\s].*[::]$/.test(paragraph);
- };
- const imageBaseUrl = ref(import.meta.env.VITE_IMAGE_BASE_URL);
- // 处理段落,将图片路径转换为 <img> 标签
- const processParagraph = (paragraph: string) => {
- // 假设图片路径是相对路径
- const imgRegex = /([^\s]+(\.jpg|\.jpeg|\.png|\.gif))/g;
- return paragraph.replace(imgRegex, `<img src="${imageBaseUrl.value}$1" alt="图片" class="intro-image">`);
- };
- onMounted(async () => {
- try {
- // 从后端获取介绍数据
- const response = await axios.get(`https://soilgd.com:5000/software-intro/${props.targetId}`);
- const { title, intro } = response.data;
- // 保留 \r\n 换行符
- const processedIntro = intro.replace(/<p>/g, '').replace(/<\/p>/g, '\r\n').replace(/<br>/g, '');
- softwareIntro.value.introParagraphs = processedIntro.split('\r\n').filter((paragraph: string) => paragraph.trim()!== '');
- softwareIntro.value.title = title;
- } catch (err: any) {
- error.value = err.message || '请求数据时发生错误';
- } finally {
- isLoading.value = false;
- }
- });
- </script>
- <style scoped lang="scss">
- * {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- }
- body {
- background-color: white;
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; /* 更美观的字体 */
- padding: 0;
- }
- .software-intro-container {
- width: 95%;
- margin: 0 auto;
- padding: 20px;
- background-color: #fff;
- border-radius: 8px; /* 圆角 */
- box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 阴影 */
- height: 80vh; /* 设置容器高度为视口高度的 80% */
- overflow-y: auto; /* 当内容超出容器高度时,显示垂直滚动条 */
- scrollbar-width: none; /* Firefox */
- -ms-overflow-style: none; /* Internet Explorer 10+ */
- }
- .loading-message {
- text-align: center;
- font-size: 18px;
- color: #999;
- padding: 20px;
- }
- .error-message {
- text-align: center;
- font-size: 18px;
- color: #ff0000; /* 红色错误信息 */
- padding: 20px;
- }
- .title {
- font-size: 36px;
- text-align: center;
- margin-bottom: 20px;
- color: #333; /* 深灰色标题 */
- }
- .paragraph {
- margin-bottom: 20px;
- }
- p {
- font-size: 16px;
- line-height: 1.8; /* 增大行高 */
- color: #666; /* 灰色文字 */
- }
- .intro-image {
- max-width: 100%;
- height: auto;
- border-radius: 4px; /* 图片圆角 */
- box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 图片阴影 */
- margin-bottom: 10px;
- }
- /* 响应式设计 */
- @media (max-width: 768px) {
- .software-intro-container {
- padding: 15px;
- height: 90vh; /* 在小屏幕上,调整容器高度为视口高度的 90% */
- }
- .title {
- font-size: 28px;
- }
- p {
- font-size: 14px;
- }
- }
- </style>
|