Explorar o código

feat(patient): 新增图文资讯页面及相关功能,更新依赖包

mcbaiyun hai 4 semanas
pai
achega
007b495959

+ 22 - 0
package-lock.json

@@ -26,6 +26,7 @@
         "@dcloudio/uni-quickapp-webview": "3.0.0-4070620250821001",
         "@dcloudio/uni-ui": "^1.5.11",
         "@qiun/ucharts": "^2.5.0-20230101",
+        "bignumber.js": "^9.3.1",
         "vue": "3.4.21",
         "vue-i18n": "9.14.5",
         "weapp-qrcode": "^1.0.0"
@@ -36,6 +37,7 @@
         "@dcloudio/uni-cli-shared": "3.0.0-4070620250821001",
         "@dcloudio/uni-stacktracey": "3.0.0-4070620250821001",
         "@dcloudio/vite-plugin-uni": "3.0.0-4070620250821001",
+        "@types/bignumber.js": "^5.0.4",
         "@vue/runtime-core": "3.5.21",
         "@vue/tsconfig": "^0.1.3",
         "sass": "^1.93.2",
@@ -3889,6 +3891,17 @@
         "@babel/types": "^7.28.2"
       }
     },
+    "node_modules/@types/bignumber.js": {
+      "version": "5.0.4",
+      "resolved": "https://registry.npmjs.org/@types/bignumber.js/-/bignumber.js-5.0.4.tgz",
+      "integrity": "sha512-y/nRKmgKRakrBZ4B3/1mhZOEHrKcf6BniqxVlsVrmaG6fN6WzzUlXfsD+KX+Ii4C+093A7h1p6LkOsXktOfQnQ==",
+      "deprecated": "This is a stub types definition. bignumber.js provides its own type definitions, so you do not need this installed.",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "bignumber.js": "*"
+      }
+    },
     "node_modules/@types/estree": {
       "version": "1.0.8",
       "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
@@ -4845,6 +4858,15 @@
         "baseline-browser-mapping": "dist/cli.js"
       }
     },
+    "node_modules/bignumber.js": {
+      "version": "9.3.1",
+      "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
+      "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
+      "license": "MIT",
+      "engines": {
+        "node": "*"
+      }
+    },
     "node_modules/binary-extensions": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",

+ 0 - 1
package.json

@@ -54,7 +54,6 @@
     "@dcloudio/uni-ui": "^1.5.11",
     "@qiun/ucharts": "^2.5.0-20230101",
     "bignumber.js": "^9.3.1",
-    "json-bigint": "^1.0.0",
     "vue": "3.4.21",
     "vue-i18n": "9.14.5",
     "weapp-qrcode": "^1.0.0"

+ 6 - 0
src/pages.json

@@ -114,6 +114,12 @@
 				"navigationBarTitleText": "我的医生"
 			}
 		},
+		{
+			"path": "pages/patient/index/news-detail",
+			"style": {
+				"navigationBarTitleText": "图文资讯"
+			}
+		},
 		{
 			"path": "pages/doctor/index/index",
 			"style": {

+ 7 - 1
src/pages/patient/index/index.vue

@@ -69,7 +69,7 @@
           <text class="card-title">健康资讯</text>
         </view>
         <view class="card-content">
-          <view class="news-item" v-for="(news, index) in newsList" :key="index">
+          <view class="news-item" v-for="(news, index) in newsList" :key="index" @click="onNewsClick(news)">
             <view v-if="news.image" class="news-image-container">
               <image class="news-image" :src="news.image" mode="aspectFill" />
             </view>
@@ -407,6 +407,12 @@ function onItemClick(type: string) {
 function onQrClick() {
   uni.navigateTo({ url: '/pages/public/profile/qr/index' })
 }
+
+const onNewsClick = (news: any) => {
+  uni.navigateTo({
+    url: `/pages/patient/index/news-detail`
+  })
+}
 </script>
 
 <style>

+ 221 - 0
src/pages/patient/index/news-detail.vue

@@ -0,0 +1,221 @@
+<template>
+  <CustomNav title="图文资讯" leftType="back" />
+  <view class="page-container">
+    <view class="news-detail-card">
+      <view class="news-header">
+        <text class="news-title">{{ newsTitle }}</text>
+      </view>
+      <view class="news-content">
+        <view class="news-desc">
+          <view v-for="(item, index) in parsedMarkdown" :key="index" :class="item.type">
+            <text v-if="item.type === 'h1'">{{ item.content }}</text>
+            <text v-if="item.type === 'h2'">{{ item.content }}</text>
+            <text v-if="item.type === 'h3'">{{ item.content }}</text>
+            <text v-if="item.type === 'p'">{{ item.content }}</text>
+            <view v-if="item.type === 'ul'">
+              <view v-for="(li, liIndex) in item.content" :key="liIndex">
+                <text>● </text><text>{{ li }}</text>
+              </view>
+            </view>
+            <text v-if="item.type === 'blockquote'">{{ item.content }}</text>
+            <image v-if="item.type === 'img'" :src="item.src" :alt="item.alt" />
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import CustomNav from '@/components/custom-nav.vue'
+
+// 处理内联markdown
+const processInline = (text: string) => {
+  return text.replace(/\*\*(.*?)\*\*/g, '$1').replace(/\*(.*?)\*/g, '$1');
+}
+
+// 简单的markdown解析函数,返回组件数组
+const markdownToComponents = (markdown: string) => {
+  const blocks = markdown.split(/\n\n+/);
+  const components = [];
+  for (const block of blocks) {
+    const trimmed = block.trim();
+    if (!trimmed) continue;
+    if (trimmed.startsWith('# ')) {
+      components.push({ type: 'h1', content: processInline(trimmed.substring(2)) });
+    } else if (trimmed.startsWith('## ')) {
+      components.push({ type: 'h2', content: processInline(trimmed.substring(3)) });
+    } else if (trimmed.startsWith('### ')) {
+      components.push({ type: 'h3', content: processInline(trimmed.substring(4)) });
+    } else if (trimmed.startsWith('- ')) {
+      const lines = trimmed.split('\n');
+      const items = lines.map(line => processInline(line.startsWith('- ') ? line.substring(2) : line));
+      components.push({ type: 'ul', content: items });
+    } else if (trimmed.startsWith('> ')) {
+      components.push({ type: 'blockquote', content: processInline(trimmed.substring(2)) });
+    } else if (trimmed.match(/!\[.*\]\(.*\)/)) {
+      const imgMatch = trimmed.match(/!\[([^\]]*)\]\(([^)]+)\)/);
+      if (imgMatch) {
+        components.push({ type: 'img', src: imgMatch[2], alt: imgMatch[1] });
+      }
+    } else {
+      components.push({ type: 'p', content: processInline(trimmed) });
+    }
+  }
+  return components;
+}
+
+interface NewsItem {
+  desc: string
+}
+
+const news = ref<NewsItem>({
+  desc: `# 健康饮食指南
+
+## 均衡饮食的重要性
+
+了解均衡饮食的重要性,掌握健康饮食的基本原则。
+
+![健康饮食图片](/static/carousel/BHFIIABBCDJII-5kCEkD6zh9.jpg)
+
+### 什么是均衡饮食?
+
+均衡饮食是指摄入各种营养素的比例适当,既能满足机体需要,又不会造成营养过剩或缺乏。
+
+## 合理的饮食结构
+
+合理的饮食结构应包括以下五大类食物:
+
+- **谷物**:碳水化合物的主要来源,提供能量
+- **蔬菜和水果**:富含维生素、矿物质和纤维素,有助于预防慢性疾病
+- **肉类**:提供优质蛋白质和铁等营养素
+- **奶制品**:钙的重要来源
+- **油脂类**:提供必需脂肪酸
+
+### 饮食建议
+
+1. 每天摄入足够的蔬菜和水果
+2. 控制糖和盐的摄入量
+3. 选择全谷物食品
+4. 适量饮水,保持身体水分平衡
+
+> **温馨提示**:良好的饮食习惯是健康的基础,建议定期咨询专业营养师制定个性化饮食方案。
+`
+})
+
+const newsTitle = computed(() => {
+  const match = news.value.desc.match(/^# (.+)$/m)
+  return match ? match[1] : '图文资讯'
+})
+
+const parsedMarkdown = computed(() => {
+  let desc = news.value.desc
+  const match = desc.match(/^# (.+)$/m)
+  if (match) {
+    desc = desc.replace(/^# .+\n/, '').trimStart()
+  }
+  return markdownToComponents(desc)
+})
+</script>
+
+<style scoped>
+.page-container {
+  min-height: 100vh;
+  background-color: #f5f5f5;
+  padding-top: calc(var(--status-bar-height) + 44px);
+  padding-bottom: 40rpx;
+}
+
+.news-detail-card {
+  background-color: #fff;
+  margin: 20rpx;
+  border-radius: 20rpx;
+  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
+  overflow: hidden;
+}
+
+.news-header {
+  padding: 40rpx;
+  border-bottom: 1rpx solid #eee;
+}
+
+.news-content {
+  padding-inline: 40rpx;
+}
+.news-desc {
+  font-size: 32rpx;
+  color: #666;
+  line-height: 2.0;
+}
+
+.news-title {
+  font-size: 56rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.h1 {
+  border-block: 1rpx solid #0c0c0c5a;
+  font-size: 56rpx;
+  font-weight: bold;
+  color: #333;
+  margin: 40rpx 0 0 0;
+}
+
+.h2 {
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #333;
+  margin: 30rpx 0 0 0;
+  border-bottom: 1rpx solid #0c0c0c15;
+}
+
+.h3 {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin: 25rpx 0 12rpx 0;
+}
+
+.p {
+  margin: 20rpx 0;
+}
+
+.ul {
+  margin: 20rpx 0;
+}
+
+.li {
+  margin-left: 30rpx;
+  margin-bottom: 5rpx;
+  display: flex;
+  align-items: flex-start;
+}
+
+.blockquote {
+  border-left: 4rpx solid #ddd;
+  padding-left: 20rpx;
+  margin: 20rpx 0;
+  color: #666;
+  font-style: italic;
+}
+
+.img {
+  width: 100%;
+  margin: 10rpx 0;
+  border-radius: 8rpx;
+}
+
+.empty-state {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 100rpx 40rpx;
+}
+
+.empty-text {
+  font-size: 32rpx;
+  color: #666;
+}
+</style>

+ 2 - 8
src/utils/jsonBig.ts

@@ -1,17 +1,11 @@
-import JSONBig from 'json-bigint'
-
-const jsonbig = JSONBig({ storeAsString: true })
+const jsonbig = JSON
 
 export function safeJsonParse(raw: string | object | undefined | null) {
   if (typeof raw === 'string') {
     try {
       return jsonbig.parse(raw)
     } catch (err) {
-      try {
-        return JSON.parse(raw)
-      } catch (e) {
-        return raw
-      }
+      return raw
     }
   }
   return raw

+ 1 - 1
tsconfig.json

@@ -19,7 +19,7 @@
       "@/*": ["./src/*"]
     },
     "lib": ["esnext", "dom"],
-    "types": ["@dcloudio/types"]
+    "types": ["@dcloudio/types", "node"]
   },
   "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "types/**/*.d.ts"]
 }