Bläddra i källkod

1.完善农技模块;2.增加摄像头设备监控播放接口

jiuling 11 månader sedan
förälder
incheckning
3fe4b21b86

+ 61 - 1
api/services/device.js

@@ -7,8 +7,68 @@ import {
   Method
 } from '@/utils/request.js';
 import storage from "@/utils/storage.js";
-const request = http.request;
+import api from "@/config/api.js";
+import config from "@/config/config.js";
+// const request = http.request;
 const userInfo = storage.getUserInfo()
+export function loginWvp() {
+  return uni.request({
+    url: `/wvp/api/user/login?username=${config.wvpUsername}&password=${config.wvpPassword}`,
+    method: Method.GET,
+    needToken: false,
+  });
+}
+
+export async function getChannels(deviceId) {
+  try {
+    const loginRes = await loginWvp();
+    console.log("WVP登录结果:", loginRes);
+
+    if (loginRes.code === 0) {
+      console.log("WVP登录成功");
+      storage.setWvpAccessToken(loginRes.data.accessToken);
+      const channelsRes = await uni.request({
+        url: `/wvp/api/device/query/devices/${deviceId}/channels`,
+        method: Method.GET,
+        data: {
+          page: 1,
+          count: 10,
+          online: true,
+        },
+        header: {
+          'Authorization': `access-token ${storage.getWvpAccessToken()}`,
+        },
+      });
+      console.log("获取通道结果:", channelsRes);
+      return channelsRes;
+    } else {
+      console.error("WVP登录失败", loginRes);
+      throw new Error("WVP登录失败");
+    }
+  } catch (error) {
+    console.error("获取通道失败", error);
+    throw error;
+  }
+}
+
+export async function playStart(deviceId, channelId) {
+  return await  uni.request({
+    url: `/wvp/api/play/start/${deviceId}/${channelId}`,  
+    method: Method.GET,
+    header: {
+          'Authorization': `access-token ${storage.getWvpAccessToken()}`,
+    },
+  })
+}
+export async function pause(deviceId, channelId) {
+  return await  uni.request({
+    url: `/wvp/api/play/stop/{deviceId}/{channelId}`,  
+    method: Method.GET,
+    header: {
+          'Authorization': `access-token ${storage.getWvpAccessToken()}`,
+    },
+  })
+}
 /**
  * 获取设备概览数据
  * @param {string} fieldId - 地块ID,可选

+ 13 - 80
api/services/knowledge.js

@@ -6,25 +6,27 @@ import {
 import storage from "@/utils/storage.js";
 const request = http.request;
 
+
 /**
- * 获取农技知识列表
+ * 获取农技知识列表(新)
  * @param params
  */
-export function getTechList(params) {
+export function getKnowledgeList(params) {
   return http.request({
-    url: "uniapp/knowledge/tech",
+    url: "uniapp/knowledge/list",
     method: Method.GET,
     // needToken: true,
     data:params,
   });
 }
+
 /**
- * 获取政策解读列表
+ * 获取农技知识详情(新)
  * @param params
  */
-export function getPolicyList(params) {
+export function getKnowledgeDetail(params) {
   return http.request({
-    url: "uniapp/knowledge/policy",
+    url: "uniapp/knowledge/detail",
     method: Method.GET,
     // needToken: true,
     data:params,
@@ -32,86 +34,17 @@ export function getPolicyList(params) {
 }
 
 /**
- * 获取知识文章详情
- * @param id 文章ID
- */
-export function getArticleDetail(id) {
-  return http.request({
-    url: `uniapp/knowledge/${id}`,
-    method: Method.GET,
-    // needToken: true,
-    headers: {
-      'Accept': 'application/json'
-    }
-  });
-}
-
-/**
- * 获取文章相关图片
- * @param articleId 文章ID
- */
-export function getArticleImages(articleId) {
-  return http.request({
-    url: `uniapp/knowledge/images/${articleId}`,
-    method: Method.GET,
-  });
-}
-
-/**
- * 获取轮播图列表
- */
-export function getCarouselImages() {
-  return http.request({
-    url: "uniapp/knowledge/carousel",
-    method: Method.GET,
-  });
-}
-
-/**
- * 创建文章
- * @param article 文章数据
+ * 增加阅读量(新)
+ * @param params
  */
-export function createArticle(article) {
+export function getKnowledgeView(params) {
   return http.request({
-    url: "uniapp/knowledge",
+    url: "uniapp/knowledge/view",
     method: Method.POST,
     // needToken: true,
-    data: article,
-    headers: {
-      'Content-Type': 'application/json'
-    }
-  });
-}
-
-/**
- * 更新文章
- * @param article 文章数据
- */
-export function updateArticle(article) {
-  return http.request({
-    url: "uniapp/knowledge",
-    method: Method.PUT,
-    // needToken: true,
-    data: article,
-    headers: {
-      'Content-Type': 'application/json'
-    }
-  });
-}
-
-/**
- * 上传文件
- * @param formData 表单数据
- */
-export function uploadFile(formData) {
-  return http.request({
-    url: "uniapp/file/upload",
-    method: Method.POST,
-    needToken: true,
-    data: formData,
+    data:params,
   });
 }
-
 /**
  * 点赞文章
  * @param id 文章ID

+ 1 - 4
config/api.js

@@ -1,12 +1,10 @@
 // 开发环境
 const dev = {
-  dify: "http://localhost:9203",
   serve: "http://localhost:8080",
 };
 // 生产环境
 const prod = {
-  dify: "http://localhost:9203",
-  serve: "http://localhost:8080",
+  serve: "http://121.4.16.100:9000/pro-uniapp",
 };
 
 //默认生产环境
@@ -21,7 +19,6 @@ if (process.env.NODE_ENV == "development") {
 // #ifdef MP-WEIXIN || APP-PLUS
 // api = prod;
 // #endif
-
 export default {
   ...api,
 };

+ 7 - 18
config/config.js

@@ -14,25 +14,14 @@ export default {
   customerServiceEmail: "lili@lili.com", //客服邮箱
   imWebSrc: "https://im.pickmall.cn", //IM地址
   baseWsUrl: "wss://im-api.pickmall.cn/lili/webSocket", // IM WS 地址
-  enableGetClipboard: false, //是否启用粘贴板获取 scanAuthNavigation 中的链接,如果匹配则会跳转到对应页面
-  enableMiniBarStartUpApp: true, //是否在h5中右侧浮空按钮点击启动app
+  /* wvp用户/密码(MD5) */
+  wvpUsername: "admin", // 用户名
+  wvpPassword: "af7b951b3a30e898e2684ffe8d20a961", // 密码(MD5加密)
   /**
    * 如需更换主题请修改此处以及uni.scss中的全局颜色
    */
-  mainColor: "#ff3c2a", // 主题色
-  lightColor: "#ff6b35", // 高亮主题色
-  aiderLightColor: "#ff9f28", // 辅助高亮颜色
-  defaultUserPhoto: "/static/missing-face.png", // 默认用户头像
-  enableFetchMobileLogin: false, // 是否启用获取手机号登录 如果微信小程序提示封禁手机号获取权限 可将此选项设置成false作为备用登录方案
-  // 新增视频流服务器配置
-  streamServer: {
-    // RTMP流服务器地址,用于小程序播放
-    rtmpServer: 'rtmp://121.4.16.100:1935',
-    
-    // HLS流服务器地址,用于小程序播放
-    hlsServer: 'http://121.4.16.100:6080/rtp/34020000001110000001_34020000001320000012/hls.m3u8',
-    
-    // WebSocket-FLV流服务器地址,用于H5播放
-    wsFlvServer: 'ws://121.4.16.100:6080/rtp'
-  }
+  // mainColor: "#ff3c2a", // 主题色
+  // lightColor: "#ff6b35", // 高亮主题色
+  // aiderLightColor: "#ff9f28", // 辅助高亮颜色
+  // defaultUserPhoto: "/static/missing-face.png", // 默认用户头像
 };

+ 3 - 3
main.js

@@ -13,9 +13,9 @@ Object.keys(filters).forEach((key) => {
 })
 
 // 全局挂载变量(通过 Vue.prototype)
-Vue.prototype.$mainColor = config.mainColor
-Vue.prototype.$lightColor = config.lightColor
-Vue.prototype.$aiderLightColor = config.aiderLightColor
+// Vue.prototype.$mainColor = config.mainColor
+// Vue.prototype.$lightColor = config.lightColor
+// Vue.prototype.$aiderLightColor = config.aiderLightColor
 
 // 使用 Vuex store
 Vue.use(store)

+ 6 - 2
manifest.json

@@ -1,6 +1,6 @@
 {
     "name" : "nongxiaoyu",
-    "appid" : "__UNI__1234567",
+    "appid" : "__UNI__D07390E",
     "description" : "农小禹小程序",
     "versionName" : "1.0.0",
     "versionCode" : "100",
@@ -21,7 +21,11 @@
     "vueVersion" : "2",
     "h5" : {
         "devServer" : {
-            "port" : 9000
+            "port" : 9000,
+			"host": "0.0.0.0"
+        },
+        "router" : {
+            "base" : "./"
         }
     }
 }

+ 2 - 2
pages/dashboard/index.vue

@@ -559,7 +559,7 @@
 					this.columns = [cached]; // 结构上是二维数组
 					console.log("从缓存加载字段:", this.columns);
 				} else {
-					this.loadPhots(this.userData.userId); // 异步方法内部要处理赋值 columns
+					this.loadPhots(this.userData.userid); // 异步方法内部要处理赋值 columns
 				}
 			},
 
@@ -639,7 +639,7 @@
 		onShow() {
 			const userInfo = storage.getUserInfo()
 			console.log("userInfo", userInfo);
-			this.userData.nickname = userInfo.nickName || '未登录'
+			this.userData.nickname = userInfo.username || '未登录'
 			this.userData.avatar = userInfo.avatar || '/static/icons/user_icon.png'
 			this.userData.userId = userInfo.userid
 			

+ 70 - 1
pages/device/device-list/detail-camera.vue

@@ -223,7 +223,7 @@
 </template>
 
 <script>
-	import { getDeviceCollectorDetail } from "@/api/services/device.js";
+	import { getDeviceCollectorDetail, getChannels, playStart,pause } from "@/api/services/device.js";
 	import { formatSmartTime, formatDate, getFormattedTime} from '@/utils/dateUtils'
 	import { isPlayableInMiniProgram, buildPlatformStreamUrls } from '@/utils/media-utils'
 	import config from '@/config/config'
@@ -249,6 +249,7 @@ export default {
         deviceType: 'weather' ,// 默认类型,会根据API返回更新
       	deviceTypeId:null,
 		    streamUrl: '',
+        channelId: null, // 当前通道ID
         originalStreamUrl: 'ws://121.4.16.100:6080/rtp/34020000001110000001_34020000001320000012.live.flv',
 
       },
@@ -359,6 +360,7 @@ export default {
 	  		    
 	  		    // 加载设备详情
 	  		    this.fetchDeviceInfo();
+            // this.queryChannels();
 	  		  }
 	    });
   },
@@ -405,6 +407,41 @@ export default {
 		})
     },
     
+    // 根据设备id获取通道列表
+    queryChannels(){
+      getChannels(this.deviceInfo.deviceId)
+            .then(res => {
+              console.log('获取通道列表:', res);
+              
+              if (res.code === 0 && res.data.total > 0) {
+                const channels = res.data.list;
+                this.deviceInfo.channelId = channels[0].deviceId; // 默认选择第一个通道
+                playStart(this.deviceInfo.deviceId,this.deviceInfo.channelId).then(res=>{
+                  if (res.code !== 0) {
+                    console.error('播放开始失败:', res.message);
+                    uni.showToast({
+                      title: '播放失败: ' + res.message,
+                      icon: 'none'
+                    });
+                    return;
+                  }
+                  console.log('播放开始:', res);
+                  this.deviceInfo.originalStreamUrl = res.data.ws_flv || this.deviceInfo.originalStreamUrl;
+                }).catch(err => {
+                  console.error('播放开始失败:', err);
+                })
+              } else {
+                uni.showToast({
+                      title: '获取通道列表失败: ' + res.message,
+                      icon: 'none'
+                    });
+                  return;
+              }
+            })
+            .catch(err => {
+              console.error('获取通道列表错误:', err);
+            });
+    },
     // 播放/暂停切换
     togglePlayState() {
       if (!this.isPlaying) {
@@ -435,6 +472,20 @@ export default {
         // #ifdef H5
         // 浏览器环境中使用Jessibuca
         setTimeout(() => {
+                //    playStart(this.deviceInfo.deviceId,this.deviceInfo.channelId).then(res=>{
+                //   if (res.code !== 0) {
+                //     console.error('播放开始失败:', res.message);
+                //     uni.showToast({
+                //       title: '播放失败: ' + res.message,
+                //       icon: 'none'
+                //     });
+                //     return;
+                //   }
+                //   console.log('播放开始:', res);
+                //   this.deviceInfo.originalStreamUrl = res.data.ws_flv || this.deviceInfo.originalStreamUrl;
+                // }).catch(err => {
+                //   console.error('播放开始失败:', err);
+                // })
           uni.vibrateShort()
         }, 300)
         // #endif
@@ -443,6 +494,24 @@ export default {
         // #ifdef H5
         if (this.$refs.jessibucaRef) {
           this.$refs.jessibucaRef.pause()
+          pause().then(res => {
+            if (res.code !== 0) {
+              console.error('暂停失败:', res.message)
+              uni.showToast({
+                title: '暂停失败: ' + res.message,
+                icon: 'none'
+              })
+            } else {
+              console.log('视频已暂停')
+              // uni.showToast({
+              //   title: '视频已暂停',
+              //   icon: 'none',
+              //   duration: 1500
+              // })
+            }
+          }).catch(err => {
+            console.error('暂停请求错误:', err)
+          })
         }
         // #endif
         

+ 3 - 2
pages/knowledge/ai-chat/index.vue

@@ -134,7 +134,7 @@
 					this.requestTask.abort();
 				}
 
-				const url = api.dify + '/dify/chat/stream';
+				const url = api.serve + '/uniapp/dify/chat/stream';
 
 				// 添加一个临时的"正在输入"消息
 				const typingMessageIndex = this.chatMessages.push({
@@ -152,7 +152,8 @@
 					data: message,
 					responseType: 'text',
 					header: {
-						'content-type': 'application/json'
+						'content-type': 'application/json',
+						'Authorization': `Bearer ${storage.getAccessToken()}`
 					},
 					success: (res) => {
 						if (res.data) {

+ 0 - 1097
pages/knowledge/article-editor.vue

@@ -1,1097 +0,0 @@
-<template>
-  <view class="container">
-    <!-- H5环境自定义导航栏 -->
-    <view class="h5-custom-navbar" v-if="isH5">
-      <view class="h5-navbar-left" @click="goBack">
-        <view class="h5-back-icon">
-          <view class="h5-arrow-left"></view>
-        </view>
-      </view>
-      <text class="h5-navbar-title">编辑文章</text>
-      <view class="h5-navbar-right"></view>
-    </view>
-    
-    <!-- 表单内容区 -->
-    <view class="article-container" :style="isH5 ? 'margin-top: 90rpx;' : ''">
-      <!-- 加载状态 -->
-      <view class="loading-container" v-if="loading">
-        <view class="loading-spinner"></view>
-        <text class="loading-text">加载中...</text>
-      </view>
-      
-      <!-- 编辑区域 -->
-      <block v-else>
-        <!-- 基本信息区域 -->
-        <view class="form-section">
-          <view class="form-title">基本信息</view>
-          <view class="form-item">
-            <text class="form-label">文章标题</text>
-            <input class="form-input" v-model="articleInfo.title" placeholder="请输入文章标题" />
-          </view>
-          <view class="form-item">
-            <text class="form-label">文章来源</text>
-            <input class="form-input" v-model="articleInfo.source" placeholder="请输入文章来源" />
-          </view>
-          <view class="form-item">
-            <text class="form-label">文章类型</text>
-            <radio-group class="type-group" @change="handleTypeChange">
-              <label class="type-item">
-                <radio value="tech" :checked="articleInfo.category === 'tech'" />
-                <text>农技知识</text>
-              </label>
-              <label class="type-item">
-                <radio value="policy" :checked="articleInfo.category === 'policy'" />
-                <text>政策解读</text>
-              </label>
-            </radio-group>
-          </view>
-          <view class="form-item">
-            <text class="form-label">内容类型</text>
-            <radio-group class="type-group" @change="handleContentTypeChange">
-              <label class="type-item">
-                <radio value="article" :checked="articleInfo.contentType === 'article'" />
-                <text>文章</text>
-              </label>
-              <label class="type-item">
-                <radio value="video" :checked="articleInfo.contentType === 'video'" />
-                <text>视频</text>
-              </label>
-            </radio-group>
-          </view>
-          <view class="form-item" v-if="articleInfo.contentType === 'video'">
-            <text class="form-label">视频链接</text>
-            <input class="form-input" v-model="articleInfo.videoUrl" placeholder="请输入视频链接" />
-          </view>
-          <view class="form-item" v-if="articleInfo.contentType === 'video'">
-            <text class="form-label">视频时长</text>
-            <input class="form-input" v-model="articleInfo.videoDuration" placeholder="请输入视频时长,如12:30" />
-          </view>
-          <view class="form-item">
-            <text class="form-label">文章简介</text>
-            <textarea class="form-textarea" v-model="articleInfo.description" placeholder="请输入文章简介" />
-          </view>
-          <!-- <view class="form-item">
-            <text class="form-label">缩略图</text>
-            <view class="upload-box" @click="chooseThumbnail">
-              <image v-if="articleInfo.thumbnail" :src="articleInfo.thumbnail" mode="aspectFill" class="thumbnail-preview"></image>
-              <view v-else class="upload-placeholder">
-                <text class="upload-icon">+</text>
-                <text>上传缩略图</text>
-              </view>
-            </view>
-          </view> -->
-        </view>
-        
-        <!-- 文章内容编辑区 -->
-        <view class="form-section">
-          <view class="form-title">文章内容</view>
-          
-          <!-- 文章内容构建器 -->
-          <view class="content-builder">
-            <view class="section-controls">
-              <button class="control-btn" @click="addSection('paragraph')">添加段落</button>
-              <button class="control-btn" @click="addSection('title')">添加标题</button>
-              <button class="control-btn" @click="addSection('image')">添加图片</button>
-              <button class="control-btn" @click="addSection('note')">添加提示框</button>
-            </view>
-            
-            <!-- 内容块 -->
-            <view class="content-sections">
-              <view v-for="(section, index) in contentSections" :key="index" class="content-section-item">
-                <!-- 段落 -->
-                <view v-if="section.type === 'paragraph'" class="section-edit-item">
-                  <textarea class="section-textarea" v-model="section.content" placeholder="请输入段落内容"></textarea>
-                  <view class="section-controls">
-                    <text class="delete-btn" @click="deleteSection(index)">删除</text>
-                    <text class="move-btn" @click="moveSection(index, -1)" v-if="index > 0">上移</text>
-                    <text class="move-btn" @click="moveSection(index, 1)" v-if="index < contentSections.length - 1">下移</text>
-                  </view>
-                </view>
-                
-                <!-- 标题 -->
-                <view v-if="section.type === 'title'" class="section-edit-item">
-                  <input class="section-title-input" v-model="section.content" placeholder="请输入标题"></input>
-                  <view class="section-controls">
-                    <text class="delete-btn" @click="deleteSection(index)">删除</text>
-                    <text class="move-btn" @click="moveSection(index, -1)" v-if="index > 0">上移</text>
-                    <text class="move-btn" @click="moveSection(index, 1)" v-if="index < contentSections.length - 1">下移</text>
-                  </view>
-                </view>
-                
-                <!-- 图片 -->
-                <view v-if="section.type === 'image'" class="section-edit-item">
-                  <view class="image-upload-container">
-                    <view class="upload-box" @click="chooseImage(index)">
-                      <image v-if="section.imageUrl" :src="section.imageUrl" mode="aspectFill" class="section-image-preview"></image>
-                      <view v-else class="upload-placeholder">
-                        <text class="upload-icon">+</text>
-                        <text>上传图片</text>
-                      </view>
-                    </view>
-                    <input class="image-caption-input" v-model="section.caption" placeholder="图片说明文字"></input>
-                    <input class="color-input" v-model="section.color" placeholder="颜色代码,例如: #8BC34A"></input>
-                  </view>
-                  <view class="section-controls">
-                    <text class="delete-btn" @click="deleteSection(index)">删除</text>
-                    <text class="move-btn" @click="moveSection(index, -1)" v-if="index > 0">上移</text>
-                    <text class="move-btn" @click="moveSection(index, 1)" v-if="index < contentSections.length - 1">下移</text>
-                  </view>
-                </view>
-                
-                <!-- 提示框 -->
-                <view v-if="section.type === 'note'" class="section-edit-item">
-                  <view class="note-box-edit">
-                    <input class="note-title-input" v-model="section.title" placeholder="提示框标题,例如:注意事项"></input>
-                    <textarea class="note-content-input" v-model="section.content" placeholder="提示框内容"></textarea>
-                  </view>
-                  <view class="section-controls">
-                    <text class="delete-btn" @click="deleteSection(index)">删除</text>
-                    <text class="move-btn" @click="moveSection(index, -1)" v-if="index > 0">上移</text>
-                    <text class="move-btn" @click="moveSection(index, 1)" v-if="index < contentSections.length - 1">下移</text>
-                  </view>
-                </view>
-              </view>
-            </view>
-          </view>
-        </view>
-        
-        <!-- 预览区 -->
-        <view class="preview-section">
-          <view class="form-title">内容预览</view>
-          <view class="article-content">
-            <rich-text :nodes="generatedHtml"></rich-text>
-          </view>
-        </view>
-        
-        <!-- 提交按钮 -->
-        <view class="submit-section">
-          <button class="submit-btn" @click="saveArticle">保存文章</button>
-        </view>
-      </block>
-    </view>
-    
-    <!-- 返回顶部 -->
-    <view class="back-to-top" @click="scrollToTop">
-      <view class="top-arrow"></view>
-      <text class="top-text">顶部</text>
-    </view>
-  </view>
-</template>
-
-<script>
-import { createArticle, updateArticle, getArticleDetail, uploadFile } from '@/api/services/knowledge.js';
-
-export default {
-  data() {
-    return {
-      title: "编辑文章",
-      isH5: false,
-      loading: false,
-      isEdit: false,  // 是否为编辑模式
-      id: null,  // 文章ID
-      
-      // 文章基本信息
-      articleInfo: {
-        id: '',
-        title: '',
-        description: '',
-        source: '农业技术研究院',
-        category: 'tech',  // 默认为农技知识
-        contentType: 'article',  // 默认为文章类型
-        thumbnail: '',  // 缩略图
-        videoUrl: '',  // 视频链接
-        videoDuration: '',  // 视频时长
-        isRecommend: 0,
-        status: 1
-      },
-      
-      // 内容块
-      contentSections: [],
-      
-      // 生成的HTML内容
-      generatedHtml: ''
-    }
-  },
-  
-  onLoad(options) {
-    // 检测是否在H5环境中运行
-    // #ifdef H5
-    this.isH5 = true;
-    // #endif
-    
-    // 设置导航栏标题
-    uni.setNavigationBarTitle({
-      title: '编辑文章'
-    });
-    
-    // 如果有ID参数,则为编辑模式
-    if (options && options.id) {
-      this.id = options.id;
-      this.isEdit = true;
-      this.loadArticleData();
-    }
-  },
-  
-  watch: {
-    // 监听内容区块变化,实时生成HTML
-    contentSections: {
-      handler: function() {
-        console.log("this.generateHtml()",this.generateHtml());
-        
-        this.generateHtml();
-      },
-      deep: true
-    }
-  },
-  
-  methods: {
-    // 加载文章数据(编辑模式)
-    async loadArticleData() {
-      if (!this.id) return;
-      
-      try {
-        this.loading = true;
-        const result = await getArticleDetail(this.id);
-        
-        if (result.data.code === 200 && result.data.data) {
-          const articleData = result.data.data;
-          
-          // 填充基本信息
-          this.articleInfo = {
-            id: articleData.id,
-            title: articleData.title,
-            description: articleData.description,
-            source: articleData.source,
-            category: articleData.type,
-            contentType: articleData.contentType,
-            thumbnail: articleData.image,
-            videoUrl: articleData.videoUrl,
-            videoDuration: articleData.duration,
-            isRecommend: 0,
-            status: 1
-          };
-          
-          // 解析HTML内容为内容块
-          this.parseHtmlToSections(articleData.content);
-        } else {
-          uni.showToast({
-            title: result.data.msg || '获取文章详情失败',
-            icon: 'none'
-          });
-        }
-      } catch (error) {
-        console.error('获取文章详情失败:', error);
-        uni.showToast({
-          title: '网络异常,请稍后重试',
-          icon: 'none'
-        });
-      } finally {
-        this.loading = false;
-      }
-    },
-    
-    // 解析HTML为内容块
-    parseHtmlToSections(html) {
-      if (!html) {
-        this.contentSections = [];
-        return;
-      }
-      
-      // 创建一个临时的DOM元素来解析HTML
-      const parser = new DOMParser();
-      const doc = parser.parseFromString(html, 'text/html');
-      const divs = doc.querySelectorAll('div.content-section');
-      
-      const sections = [];
-      
-      divs.forEach(div => {
-        // 处理段落
-        const paragraph = div.querySelector('p.paragraph');
-        if (paragraph) {
-          sections.push({
-            type: 'paragraph',
-            content: paragraph.textContent
-          });
-        }
-        
-        // 处理标题
-        const title = div.querySelector('h3.section-title');
-        if (title) {
-          sections.push({
-            type: 'title',
-            content: title.textContent
-          });
-        }
-        
-        // 处理图片
-        const imageDiv = div.querySelector('div.section-image');
-        if (imageDiv) {
-          const colorImage = imageDiv.querySelector('div.color-image');
-          const imageLabel = colorImage ? colorImage.querySelector('div.image-label') : null;
-          
-          sections.push({
-            type: 'image',
-            imageUrl: '', // 需要后端提供真实URL
-            caption: imageLabel ? imageLabel.textContent : '',
-            color: colorImage ? colorImage.style.backgroundColor : '#8BC34A'
-          });
-        }
-        
-        // 处理提示框
-        const noteBox = div.querySelector('div.note-box');
-        if (noteBox) {
-          const noteTitle = noteBox.querySelector('div.note-title');
-          const noteContent = noteBox.querySelector('div.note-content');
-          
-          sections.push({
-            type: 'note',
-            title: noteTitle ? noteTitle.textContent : '【注意事项】',
-            content: noteContent ? noteContent.textContent : ''
-          });
-        }
-      });
-      
-      this.contentSections = sections;
-    },
-    
-    // 类型切换处理
-    handleTypeChange(e) {
-      this.articleInfo.category = e.detail.value;
-    },
-    
-    // 内容类型切换处理
-    handleContentTypeChange(e) {
-      this.articleInfo.contentType = e.detail.value;
-    },
-    
-    // 添加内容区块
-    addSection(type) {
-      switch(type) {
-        case 'paragraph':
-          this.contentSections.push({
-            type: 'paragraph',
-            content: ''
-          });
-          break;
-        case 'title':
-          this.contentSections.push({
-            type: 'title',
-            content: ''
-          });
-          break;
-        case 'image':
-          this.contentSections.push({
-            type: 'image',
-            imageUrl: '',
-            caption: '',
-            color: '#8BC34A' // 默认颜色
-          });
-          break;
-        case 'note':
-          this.contentSections.push({
-            type: 'note',
-            title: '【注意事项】',
-            content: ''
-          });
-          break;
-      }
-    },
-    
-    // 删除区块
-    deleteSection(index) {
-      this.contentSections.splice(index, 1);
-    },
-    
-    // 移动区块
-    moveSection(index, direction) {
-      const newIndex = index + direction;
-      
-      if (newIndex >= 0 && newIndex < this.contentSections.length) {
-        const item = this.contentSections[index];
-        this.contentSections.splice(index, 1);
-        this.contentSections.splice(newIndex, 0, item);
-      }
-    },
-    
-    // 选择缩略图
-    chooseThumbnail() {
-      uni.chooseImage({
-        count: 1,
-        success: async (res) => {
-          const tempFilePath = res.tempFilePaths[0];
-		  console.log("res",res);
-		  
-          console.log("tempFilePath",tempFilePath);
-          try {
-            uni.showLoading({ title: '上传中...' });
-            // 调用上传API
-            const uploadResult = await this.uploadImage(tempFilePath);
-            
-            if (uploadResult && uploadResult.url) {
-              this.articleInfo.thumbnail = ""+ uploadResult.url;
-            } else {
-              throw new Error('上传失败');
-            }
-          } catch (error) {
-            console.error('上传图片失败:', error);
-            uni.showToast({
-              title: '上传图片失败',
-              icon: 'none'
-            });
-          } finally {
-            uni.hideLoading();
-          }
-        }
-      });
-    },
-    
-    // 选择内容图片
-    chooseImage(index) {
-      uni.chooseImage({
-        count: 1,
-        success: async (res) => {
-          const tempFilePath = res.tempFilePaths[0];
-          
-          try {
-            uni.showLoading({ title: '上传中...' });
-            // 调用上传API
-            const uploadResult = await this.uploadImage(tempFilePath);
-            
-            if (uploadResult && uploadResult.url) {
-              this.$set(this.contentSections[index], 'imageUrl', uploadResult.url);
-            } else {
-              throw new Error('上传失败');
-            }
-          } catch (error) {
-            console.error('上传图片失败:', error);
-            uni.showToast({
-              title: '上传图片失败',
-              icon: 'none'
-            });
-          } finally {
-            uni.hideLoading();
-          }
-        }
-      });
-    },
-    
-    // 上传图片
-    async uploadImage(filePath) {
-      return new Promise((resolve, reject) => {
-        uni.uploadFile({
-          url: 'http://localhost:9203/file/upload', // 替换为实际的上传API
-          filePath: filePath,
-          name: 'file',
-          success: (uploadRes) => {
-            const data = JSON.parse(uploadRes.data);
-            if (data.code === 200 && data.data) {
-              resolve(data.data);
-            } else {
-              reject(new Error(data.msg || '上传失败'));
-            }
-          },
-          fail: (error) => {
-            reject(error);
-          }
-        });
-      });
-    },
-    
-    // 生成HTML内容
-    generateHtml() {
-      let html = '';
-      
-      this.contentSections.forEach(section => {
-        switch(section.type) {
-          case 'paragraph':
-            html += `<div class="content-section">
-                      <p class="paragraph">${section.content || ''}</p>
-                     </div>`;
-            break;
-            
-          case 'title':
-            html += `<div class="content-section">
-                      <h3 class="section-title">${section.content || ''}</h3>
-                     </div>`;
-            break;
-            
-          case 'image':
-            html += `<div class="content-section">
-                      <div class="section-image">
-                        <div class="color-image" style="background-color: ${section.color || '#8BC34A'};">
-                          <div class="image-label">${section.caption || ''}</div>
-                        </div>
-                      </div>
-                     </div>`;
-            break;
-            
-          case 'note':
-            html += `<div class="content-section">
-                      <div class="note-box">
-                        <div class="note-title">${section.title || '【注意事项】'}</div>
-                        <div class="note-content">${section.content || ''}</div>
-                      </div>
-                     </div>`;
-            break;
-        }
-      });
-      
-      this.generatedHtml = html;
-      return html;
-    },
-    
-    // 验证表单
-    validateForm() {
-      if (!this.articleInfo.title) {
-        uni.showToast({
-          title: '请输入文章标题',
-          icon: 'none'
-        });
-        return false;
-      }
-      
-      if (!this.articleInfo.source) {
-        uni.showToast({
-          title: '请输入文章来源',
-          icon: 'none'
-        });
-        return false;
-      }
-      
-      if (!this.articleInfo.description) {
-        uni.showToast({
-          title: '请输入文章简介',
-          icon: 'none'
-        });
-        return false;
-      }
-      
-      // if (!this.articleInfo.thumbnail) {
-      //   uni.showToast({
-      //     title: '请上传文章缩略图',
-      //     icon: 'none'
-      //   });
-      //   return false;
-      // }
-      
-      if (this.articleInfo.contentType === 'video') {
-        if (!this.articleInfo.videoUrl) {
-          uni.showToast({
-            title: '请输入视频链接',
-            icon: 'none'
-          });
-          return false;
-        }
-      } else {
-        // 文章类型时,需要检查内容是否为空
-        if (this.contentSections.length === 0) {
-          uni.showToast({
-            title: '请添加文章内容',
-            icon: 'none'
-          });
-          return false;
-        }
-      }
-      
-      return true;
-    },
-    
-    // 保存文章
-    async saveArticle() {
-      if (!this.validateForm()) return;
-      
-      try {
-        this.loading = true;
-        
-        // 生成最终的HTML内容
-        const contentHtml = this.generateHtml();
-        console.log("contentHtml",contentHtml);
-        // 构建提交数据
-        const articleData = {
-          id: this.isEdit ? this.id : null,
-          title: this.articleInfo.title,
-          description: this.articleInfo.description,
-          content: contentHtml, // 直接传递原始HTML内容,不进行编码或转义
-          category: this.articleInfo.category,
-          thumbnail: this.articleInfo.thumbnail,
-          contentType: this.articleInfo.contentType,
-          videoUrl: this.articleInfo.videoUrl,
-          videoDuration: this.articleInfo.videoDuration,
-          source: this.articleInfo.source,
-          isRecommend: this.articleInfo.isRecommend,
-          status: this.articleInfo.status
-        };
-        
-        let result;
-        
-        if (this.isEdit) {
-          // 编辑模式
-          result = await updateArticle(articleData);
-        } else {
-          // 新建模式
-          result = await createArticle(articleData);
-        }
-        
-        if (result.data.code === 200) {
-          uni.showToast({
-            title: this.isEdit ? '更新成功' : '发布成功',
-            icon: 'success'
-          });
-          
-          // 成功后返回上一页
-          setTimeout(() => {
-            uni.navigateBack();
-          }, 1500);
-        } else {
-          uni.showToast({
-            title: result.data.msg || '保存失败',
-            icon: 'none'
-          });
-        }
-      } catch (error) {
-        console.error('保存文章失败:', error);
-        uni.showToast({
-          title: '网络异常,请稍后重试',
-          icon: 'none'
-        });
-      } finally {
-        this.loading = false;
-      }
-    },
-    
-    // 返回
-    goBack() {
-      uni.navigateBack();
-    },
-    
-    // 滚动到顶部
-    scrollToTop() {
-      uni.pageScrollTo({
-        scrollTop: 0,
-        duration: 300
-      });
-    }
-  }
-};
-</script>
-
-<style>
-/* 引入原有样式 */
-@font-face {
-  font-family: 'iconfont';
-  src: url('https://at.alicdn.com/t/font_3442238_cosd6rj55jg.ttf') format('truetype');
-}
-
-@font-face {
-  font-family: 'Material Icons';
-  font-style: normal;
-  font-weight: 400;
-  src: url(https://fonts.gstatic.com/s/materialicons/v139/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2');
-}
-
-.icon, .view-icon, .play-icon, .action-icon, .top-icon {
-  font-family: 'iconfont';
-}
-
-.material-icon {
-  font-family: 'Material Icons';
-  font-weight: normal;
-  font-style: normal;
-  font-size: 50rpx;
-  line-height: 1;
-  letter-spacing: normal;
-  text-transform: none;
-  display: inline-block;
-  white-space: nowrap;
-  word-wrap: normal;
-  direction: ltr;
-  -webkit-font-smoothing: antialiased;
-  color: #4CAF50;
-}
-
-/* 容器样式 */
-.container {
-  background-color: #f8f8f8;
-  min-height: 100vh;
-  position: relative;
-  padding-bottom: 120rpx;
-}
-
-/* 文章容器 */
-.article-container {
-  background-color: #fff;
-  border-radius: 0;
-  overflow: hidden;
-}
-
-/* H5导航栏样式 */
-.h5-custom-navbar {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  height: 90rpx;
-  background-color: #fff;
-  display: flex;
-  align-items: center;
-  padding: 0 30rpx;
-  z-index: 100;
-  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.05);
-}
-
-.h5-navbar-left {
-  width: 80rpx;
-  height: 80rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-
-.h5-back-icon {
-  font-size: 40rpx;
-  color: #333;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-
-.h5-navbar-title {
-  flex: 1;
-  text-align: center;
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #333;
-}
-
-.h5-navbar-right {
-  width: 60rpx;
-}
-
-.h5-arrow-left {
-  width: 24rpx;
-  height: 24rpx;
-  border-top: 4rpx solid #333;
-  border-left: 4rpx solid #333;
-  transform: rotate(-45deg);
-}
-
-/* 表单样式 */
-.form-section {
-  padding: 30rpx;
-  margin-bottom: 30rpx;
-  background-color: #fff;
-  border-bottom: 1rpx solid #eee;
-}
-
-.form-title {
-  font-size: 34rpx;
-  font-weight: bold;
-  color: #333;
-  margin-bottom: 30rpx;
-  padding-left: 16rpx;
-  border-left: 8rpx solid #4CAF50;
-}
-
-.form-item {
-  margin-bottom: 30rpx;
-}
-
-.form-label {
-  display: block;
-  font-size: 28rpx;
-  color: #666;
-  margin-bottom: 12rpx;
-}
-
-.form-input {
-  width: 100%;
-  height: 80rpx;
-  border: 1rpx solid #ddd;
-  border-radius: 8rpx;
-  padding: 0 20rpx;
-  font-size: 28rpx;
-  color: #333;
-  background-color: #f9f9f9;
-}
-
-.form-textarea {
-  width: 100%;
-  height: 180rpx;
-  border: 1rpx solid #ddd;
-  border-radius: 8rpx;
-  padding: 20rpx;
-  font-size: 28rpx;
-  color: #333;
-  background-color: #f9f9f9;
-}
-
-.type-group {
-  display: flex;
-  flex-direction: row;
-}
-
-.type-item {
-  margin-right: 60rpx;
-  font-size: 28rpx;
-  color: #333;
-}
-
-/* 上传框样式 */
-.upload-box {
-  width: 200rpx;
-  height: 200rpx;
-  border: 2rpx dashed #ddd;
-  border-radius: 8rpx;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  background-color: #f9f9f9;
-}
-
-.upload-placeholder {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  color: #999;
-  font-size: 24rpx;
-}
-
-.upload-icon {
-  font-size: 60rpx;
-  margin-bottom: 10rpx;
-}
-
-.thumbnail-preview {
-  width: 100%;
-  height: 100%;
-  border-radius: 8rpx;
-}
-
-/* 内容构建器样式 */
-.content-builder {
-  margin-top: 20rpx;
-}
-
-.section-controls {
-  display: flex;
-  flex-wrap: wrap;
-  margin-bottom: 30rpx;
-}
-
-.control-btn {
-  margin-right: 20rpx;
-  margin-bottom: 20rpx;
-  padding: 10rpx 24rpx;
-  background-color: #f5f5f5;
-  color: #333;
-  font-size: 26rpx;
-  border-radius: 30rpx;
-  border: 1rpx solid #e0e0e0;
-}
-
-.content-section-item {
-  margin-bottom: 30rpx;
-  border: 1rpx solid #eee;
-  border-radius: 8rpx;
-  padding: 20rpx;
-}
-
-.section-edit-item {
-  position: relative;
-}
-
-.section-textarea {
-  width: 100%;
-  min-height: 150rpx;
-  border: 1rpx solid #eee;
-  border-radius: 8rpx;
-  padding: 20rpx;
-  font-size: 28rpx;
-  color: #333;
-  margin-bottom: 20rpx;
-  background-color: #f9f9f9;
-}
-
-.section-title-input {
-  width: 100%;
-  height: 80rpx;
-  border: 1rpx solid #eee;
-  border-radius: 8rpx;
-  padding: 0 20rpx;
-  font-size: 30rpx;
-  font-weight: bold;
-  color: #333;
-  margin-bottom: 20rpx;
-  background-color: #f9f9f9;
-}
-
-.section-controls {
-  display: flex;
-  justify-content: flex-end;
-}
-
-.delete-btn, .move-btn {
-  font-size: 24rpx;
-  color: #999;
-  padding: 6rpx 16rpx;
-  margin-left: 20rpx;
-}
-
-.delete-btn {
-  color: #ff5252;
-}
-
-/* 图片上传区域 */
-.image-upload-container {
-  width: 100%;
-  margin-bottom: 20rpx;
-}
-
-.section-image-preview {
-  width: 100%;
-  height: 300rpx;
-  border-radius: 8rpx;
-}
-
-.image-caption-input {
-  width: 100%;
-  height: 70rpx;
-  border: 1rpx solid #eee;
-  border-radius: 8rpx;
-  padding: 0 20rpx;
-  font-size: 26rpx;
-  color: #333;
-  margin-top: 20rpx;
-  background-color: #f9f9f9;
-}
-
-.color-input {
-  width: 100%;
-  height: 70rpx;
-  border: 1rpx solid #eee;
-  border-radius: 8rpx;
-  padding: 0 20rpx;
-  font-size: 26rpx;
-  color: #333;
-  margin-top: 20rpx;
-  background-color: #f9f9f9;
-}
-
-/* 提示框编辑区域 */
-.note-box-edit {
-  border-left: 8rpx solid #8bc34a;
-  padding: 20rpx;
-  background-color: #f1f8e9;
-  margin-bottom: 20rpx;
-  border-radius: 8rpx;
-}
-
-.note-title-input {
-  width: 100%;
-  height: 70rpx;
-  border: 1rpx solid #e0e0e0;
-  border-radius: 8rpx;
-  padding: 0 20rpx;
-  font-size: 28rpx;
-  font-weight: bold;
-  color: #558b2f;
-  margin-bottom: 20rpx;
-  background-color: rgba(255,255,255,0.5);
-}
-
-.note-content-input {
-  width: 100%;
-  height: 150rpx;
-  border: 1rpx solid #e0e0e0;
-  border-radius: 8rpx;
-  padding: 20rpx;
-  font-size: 26rpx;
-  color: #689f38;
-  background-color: rgba(255,255,255,0.5);
-}
-
-/* 预览区域 */
-.preview-section {
-  padding: 30rpx;
-  margin-bottom: 30rpx;
-  background-color: #fff;
-  border-top: 1rpx solid #eee;
-}
-
-.article-content {
-  margin-top: 20rpx;
-  border: 1rpx dashed #ddd;
-  padding: 20rpx;
-  border-radius: 8rpx;
-}
-
-/* 提交按钮区域 */
-.submit-section {
-  padding: 20rpx 30rpx 50rpx;
-}
-
-.submit-btn {
-  width: 100%;
-  height: 90rpx;
-  line-height: 90rpx;
-  background: linear-gradient(135deg, #4CAF50, #388E3C);
-  color: #fff;
-  font-size: 32rpx;
-  font-weight: bold;
-  border-radius: 45rpx;
-  text-align: center;
-  box-shadow: 0 6rpx 16rpx rgba(76, 175, 80, 0.3);
-}
-
-/* 内容预览样式 */
-.content-section {
-  margin-bottom: 40rpx;
-}
-
-.section-title {
-  font-size: 34rpx;
-  font-weight: bold;
-  color: #2e7d32;
-  margin-bottom: 20rpx;
-  display: block;
-}
-
-.paragraph {
-  font-size: 30rpx;
-  line-height: 1.8;
-  color: #333;
-  margin-bottom: 20rpx;
-  display: block;
-}
-
-.section-image {
-  margin: 20rpx 0;
-  width: 100%;
-}
-
-.color-image {
-  width: 100%;
-  height: 300rpx;
-  border-radius: 0;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  position: relative;
-  overflow: hidden;
-}
-
-.image-label {
-  color: white;
-  font-size: 32rpx;
-  font-weight: bold;
-  text-shadow: 0 2px 4px rgba(0,0,0,0.5);
-  z-index: 5;
-}
-
-.note-box {
-  background-color: #f1f8e9;
-  border-left: 8rpx solid #8bc34a;
-  padding: 20
-}
-</style>

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 513 - 344
pages/knowledge/detail.vue


+ 292 - 195
pages/knowledge/index.vue

@@ -25,7 +25,7 @@
       scroll-y
       class="content-container"
       @scrolltolower="loadMore"
-      @refresherrefresh="onRefresh"
+	  @refresherrefresh="onRefresh"
       refresher-enabled
       :refresher-triggered="isRefreshing"
     >
@@ -36,20 +36,20 @@
           <navigator 
             class="content-card"
             v-for="(item, index) in techList"
-            :key="index"
+            :key="`tech-${item.id}-${index}`"
             :url="`/pages/knowledge/detail?id=${item.id}&type=tech`"
             hover-class="card-hover"
           >
             <view class="content-thumbnail">
               <image 
-                :src="item.image" 
+                src="https://img.freepik.com/free-photo/rice-field-with-beautiful-sky_74190-7490.jpg" 
                 mode="aspectFill"
                 class="thumbnail-image"
                 lazy-load
               />
               <!-- 仅视频类型显示时长和播放按钮 -->
               <template v-if="item.contentType === 'video'">
-                <view class="video-duration">{{ item.duration }}</view>
+                <view class="video-duration">{{ item.duration || '00:00' }}</view>
                 <view class="play-button">
                   <view class="play-icon"></view>
                 </view>
@@ -65,9 +65,8 @@
               <view class="info-row">
                 <text class="source-name">{{ item.source }}</text>
                 <view class="view-count">
-                  <!-- <text class="iconfont icon-eye"></text> -->
-				  <image src="../../static/icons/yidu.png" mode="aspectFit"></image>
-                  <text>已读 {{ item.viewCount }}</text>
+                  <image src="@/static/icons/read.png" style="width: 30rpx;height: 30rpx;"></image>
+                  <text>{{ item.viewCount }}</text>
                 </view>
               </view>
             </view>
@@ -79,20 +78,20 @@
           <navigator 
             class="content-card"
             v-for="(item, index) in policyList"
-            :key="index"
+            :key="`policy-${item.id}-${index}`"
             :url="`/pages/knowledge/detail?id=${item.id}&type=policy`"
             hover-class="card-hover"
           >
             <view class="content-thumbnail">
               <image 
-                :src="item.image" 
+                :src="item.imageUrl" 
                 mode="aspectFill"
                 class="thumbnail-image"
                 lazy-load
               />
               <!-- 仅视频类型显示时长和播放按钮 -->
               <template v-if="item.contentType === 'video'">
-                <view class="video-duration">{{ item.duration }}</view>
+                <view class="video-duration">{{ item.duration || '00:00' }}</view>
                 <view class="play-button">
                   <view class="play-icon"></view>
                 </view>
@@ -108,29 +107,24 @@
               <view class="info-row">
                 <text class="source-name">{{ item.source }}</text>
                 <view class="view-count">
-                 <image src="../../static/icons/yidu.png" mode="aspectFit"></image>
-                  <text>已读 {{ item.viewCount }}</text>
+                  <image src="@/static/icons/read.png" style="width: 30rpx;height: 30rpx;"></image>
+                  <text>{{ item.viewCount }}</text>
                 </view>
               </view>
-			  
             </view>
           </navigator>
         </block>
 
-        <!-- 无数据状态 -->
-        <view class="empty-state" v-if="(currentTab === 0 && techList.length === 0) || (currentTab === 1 && policyList.length === 0)">
-          <image src="/static/images/empty.png" mode="aspectFit" class="empty-image"></image>
-          <text class="empty-text">暂无数据</text>
-        </view>
-        
-        <!-- 加载状态 -->
+        <!-- 加载中状态 -->
         <view class="loading-state" v-if="loading">
+          <view class="spinner"></view>
           <text class="loading-text">加载中...</text>
         </view>
-        
-        <!-- 底部加载完成提示 -->
-        <view class="load-all" v-if="(currentTab === 0 && techLoadAll) || (currentTab === 1 && policyLoadAll)">
-          <text class="load-all-text">— 已经到底啦 —</text>
+
+        <!-- 无数据状态 -->
+        <view class="empty-state" v-if="!loading && ((currentTab === 0 && techList.length === 0) || (currentTab === 1 && policyList.length === 0))">
+          <image src="/static/images/empty.png" mode="aspectFit" class="empty-image"></image>
+          <text class="empty-text">暂无数据</text>
         </view>
       </view>
 
@@ -147,222 +141,324 @@
 </template>
 
 <script>
-import {getTechList,getPolicyList} from '@/api/services/knowledge.js';
+	import {getKnowledgeList} from "@/api/services/knowledge.js";
 export default {
   data() {
     return {
       // 当前标签
       currentTab: 0,
-
+      
       // 农技知识列表
       techList: [],
+      
       // 政策解读列表
       policyList: [],
-
+      
       // 分页参数
-      techPage: 1,
-      policyPage: 1,
-      pageSize: 10,
+      techPageParams: {
+        page: 1,
+        pageSize: 10,
+        hasMore: true
+      },
       
-      // 加载状态
-      loading: false,
-      isRefreshing: false,
+      policyPageParams: {
+        page: 1,
+        pageSize: 10,
+        hasMore: true
+      },
       
-      // 是否全部加载完成
-      techLoadAll: false,
-      policyLoadAll: false
-    }
+      // 刷新加载状态
+      isRefreshing: false,
+      loading: false
+    };
   },
-
+  
+  // 监听标签切换
+  // watch: {
+  //   currentTab(newVal) {
+  //     if (newVal === 0 && this.techList.length === 0) {
+  //       this.fetchTechKnowledge();
+  //     } else if (newVal === 1 && this.policyList.length === 0) {
+  //       this.fetchPolicyData();
+  //     }
+  //   }
+  // },
+  
+  // 页面加载完成
   mounted() {
-	  console.log("process.env.NODE_ENV",process.env.NODE_ENV);
-    // 初始化数据加载
-    this.loadInitialData();
+    console.log('Page mounted, current tab:', this.currentTab);
+    // 初始加载数据
+    this.fetchTechKnowledge();
+    
+    // 监听文章阅读量更新事件
+    uni.$on('updateArticleViewCount', this.handleViewCountUpdate);
   },
-
-  methods: {
-    // 初始化数据加载
-    loadInitialData() {
-      if (this.currentTab === 0) {
-        this.loadTechList();
-      } else {
-        this.loadPolicyList();
+  
+  // 页面显示时触发
+  onShow() {
+    // 获取当前页面路由信息,判断是否是从详情页返回
+    const pages = getCurrentPages();
+    if (pages.length > 1) {
+      const prePage = pages[pages.length - 2];
+      // 如果前一个页面是详情页,检查是否需要刷新数据
+      if (prePage && prePage.route && prePage.route.includes('knowledge/detail')) {
+        console.log('从详情页返回,更新阅读量显示');
+        // 由于已经通过事件机制更新了列表,这里只需要触发一下视图更新
+        if (this.currentTab === 0) {
+          this.techList = [...this.techList];
+        } else {
+          this.policyList = [...this.policyList];
+        }
       }
-    },
-    
+    }
+  },
+  
+  // 页面销毁前
+  beforeDestroy() {
+    // 移除事件监听
+    uni.$off('updateArticleViewCount', this.handleViewCountUpdate);
+  },
+  
+  methods: {
     // 处理Tab切换
     handleTabChange(index) {
-      if (this.currentTab === index) {
-        return;
-      }
+      if (this.currentTab === index) return;
       
       this.currentTab = index;
+      console.log('Tab changed to:', index);
       
-      // 切换标签时,如果对应列表为空,则加载数据
+      // 如果切换到的标签没有数据,则加载数据
       if (index === 0 && this.techList.length === 0) {
-        this.loadTechList();
+        this.fetchTechKnowledge();
       } else if (index === 1 && this.policyList.length === 0) {
-        this.loadPolicyList();
+        this.fetchPolicyData();
       }
     },
-
-    // 下拉刷新
-    async onRefresh() {
-      this.isRefreshing = true;
-      
-      if (this.currentTab === 0) {
-        this.techPage = 1;
-        this.techLoadAll = false;
-        await this.loadTechList(true);
-      } else {
-        this.policyPage = 1;
-        this.policyLoadAll = false;
-        await this.loadPolicyList(true);
+    
+    // 获取农技知识数据
+    async fetchTechKnowledge(refresh = false) {
+      if (refresh) {
+        this.techPageParams.page = 1;
+        this.techPageParams.hasMore = true;
       }
       
-      this.isRefreshing = false;
-    },
-
-    // 加载更多
-    loadMore() {
-      if (this.loading) return;
-      
-      if (this.currentTab === 0 && !this.techLoadAll) {
-        this.techPage++;
-        this.loadTechList();
-      } else if (this.currentTab === 1 && !this.policyLoadAll) {
-        this.policyPage++;
-        this.loadPolicyList();
-      }
-    },
-    
-    // 加载农技知识列表
-    async loadTechList(refresh = false) {
-      if (this.loading) return;
+      // 如果没有更多数据则不请求
+      if (!this.techPageParams.hasMore && !refresh) return;
       
       this.loading = true;
       
       try {
-         const result = await getTechList({params: {
-            pageNum: this.techPage,
-            pageSize: this.pageSize
-          }});
-		console.log("result",result);
-        // 请求成功
-        if (result.data.code === 200 && result.data.rows) {
-          const newData = result.data.rows;
-          
-          // 如果是刷新或第一页,则替换列表
-          if (refresh || this.techPage === 1) {
-            this.techList = newData;
-          } else {
-            // 否则追加到列表
-            this.techList = [...this.techList, ...newData];
-          }
-          
-          // 判断是否加载完全部数据
-          if (newData.length < this.pageSize) {
-            this.techLoadAll = true;
-          }
-		  console.log("this.techList",this.techList);
-        } else {
-          // 请求返回错误
-          uni.showToast({
-            title: result.msg || '加载失败',
-            icon: 'none'
-          });
-          
-          // 如果不是第一页,则减回页码
-          if (this.techPage > 1) {
-            this.techPage--;
-          }
-        }
+        // 构建请求参数
+        const params = {
+          page: this.techPageParams.page,
+          pageSize: this.techPageParams.pageSize,
+          category: 'tech'
+        };
+        
+        // 发起请求
+        await getKnowledgeList(params).then(response=>{
+			console.log("response农技",response);
+			// 解析响应
+			if (response.data.data && response.data.code === 200) {
+			  const { list, total } = response.data.data;
+			  
+			  if (refresh) {
+			    this.techList = list || [];
+			  } else {
+			    this.techList = [...this.techList, ...(list || [])];
+			  }
+			  console.log("techList",this.techList);
+			  // 更新分页状态
+			  this.techPageParams.page++;
+			  this.techPageParams.hasMore = this.techList.length < total;
+			} else {
+			  uni.showToast({
+			    title: (response.data && response.data.message) || '获取数据失败',
+			    icon: 'none'
+			  });
+			}
+		}).catch(err => [err, null]);
+           
       } catch (error) {
-        console.error('加载农技知识列表失败:', error);
+        console.error('获取农技知识数据失败:', error);
         uni.showToast({
-          title: '网络异常,请稍后试',
+          title: '网络异常,请稍后试',
           icon: 'none'
         });
-        
-        // 如果不是第一页,则减回页码
-        if (this.techPage > 1) {
-          this.techPage--;
-        }
       } finally {
         this.loading = false;
+        if (refresh) {
+          this.isRefreshing = false;
+        }
+        
+        // 如果加载完成后没有更多数据,并且不是刷新操作,显示提示
+        if (!this.techPageParams.hasMore && !refresh && this.techList.length > 0) {
+          uni.showToast({
+            title: '已加载全部内容',
+            icon: 'none',
+            duration: 2000
+          });
+        }
       }
     },
     
-    // 加载政策解读列表
-    async loadPolicyList(refresh = false) {
-      if (this.loading) return;
+    // 获取政策解读数据
+    async fetchPolicyData(refresh = false) {
+      if (refresh) {
+        this.policyPageParams.page = 1;
+        this.policyPageParams.hasMore = true;
+      }
+      
+      // 如果没有更多数据则不请求
+      if (!this.policyPageParams.hasMore && !refresh) return;
       
       this.loading = true;
       
       try {
-        const result = await getPolicyList({
-          params: {
-            pageNum: this.policyPage,
-            pageSize: this.pageSize
-          }
-        })
+        // 构建请求参数
+        const params = {
+          page: this.policyPageParams.page,
+          pageSize: this.policyPageParams.pageSize,
+          category: 'policy'
+        };
         
-        // 请求成功
-        if (result.data.code === 200 && result.data.rows) {
-          const newData = result.data.rows;
-          
-          // 如果是刷新或第一页,则替换列表
-          if (refresh || this.policyPage === 1) {
-            this.policyList = newData;
-          } else {
-            // 否则追加到列表
-            this.policyList = [...this.policyList, ...newData];
-          }
-          
-          // 判断是否加载完全部数据
-          if (newData.length < this.pageSize) {
-            this.policyLoadAll = true;
-          }
-        } else {
-          // 请求返回错误
-          uni.showToast({
-            title: result.msg || '加载失败',
-            icon: 'none'
-          });
-          
-          // 如果不是第一页,则减回页码
-          if (this.policyPage > 1) {
-            this.policyPage--;
-          }
-        }
+        // 发起请求
+        getKnowledgeList(params).then(response=>{
+			console.log("response农技333",response);
+			// 解析响应
+			if (response.data.data && response.data.code === 200) {
+			  const { list, total } = response.data.data;
+			  
+			  if (refresh) {
+			    this.policyList = list || [];
+			  } else {
+			    this.policyList = [...this.policyList, ...(list || [])];
+			  }
+			  console.log("this.policyList",this.policyList);
+			  // 更新分页状态
+			  this.policyPageParams.page++;
+			  this.policyPageParams.hasMore = this.policyList.length < total;
+			} else {
+			  uni.showToast({
+			    title: (response.data && response.data.message) || '获取数据失败',
+			    icon: 'none'
+			  });
+			}
+		}).catch(err => [err, null]);
       } catch (error) {
-        console.error('加载政策解读列表失败:', error);
+        console.error('获取政策解读数据失败:', error);
         uni.showToast({
-          title: '网络异常,请稍后试',
+          title: '网络异常,请稍后再试',
           icon: 'none'
         });
-        
-        // 如果不是第一页,则减回页码
-        if (this.policyPage > 1) {
-          this.policyPage--;
-        }
       } finally {
         this.loading = false;
+        if (refresh) {
+          this.isRefreshing = false;
+        }
+        
+        // 如果加载完成后没有更多数据,并且不是刷新操作,显示提示
+        if (!this.policyPageParams.hasMore && !refresh && this.policyList.length > 0) {
+          uni.showToast({
+            title: '已加载全部内容',
+            icon: 'none',
+            duration: 2000
+          });
+        }
       }
     },
-
+    
+    // 下拉刷新
+    async onRefresh() {
+      this.isRefreshing = true;
+      
+      if (this.currentTab === 0) {
+        // 刷新农技知识数据
+        await this.fetchTechKnowledge(true);
+      } else {
+        // 刷新政策解读数据
+        await this.fetchPolicyData(true);
+      }
+    },
+    
+    // 加载更多
+    loadMore() {
+      if (this.loading) return;
+      
+      console.log('Loading more content');
+      
+      if (this.currentTab === 0) {
+        // 检查是否还有更多农技知识数据
+        if (!this.techPageParams.hasMore) {
+          uni.showToast({
+            title: '已加载全部内容',
+            icon: 'none',
+            duration: 2000
+          });
+          return;
+        }
+        // 加载更多农技知识数据
+        this.fetchTechKnowledge();
+      } else {
+        // 检查是否还有更多政策解读数据
+        if (!this.policyPageParams.hasMore) {
+          uni.showToast({
+            title: '已加载全部内容',
+            icon: 'none',
+            duration: 2000
+          });
+          return;
+        }
+        // 加载更多政策解读数据
+        this.fetchPolicyData();
+      }
+    },
+    
     // 跳转AI问答
     navigateToAI() {
       uni.navigateTo({
-        url: '/pages/knowledge/ai-chat/index'
-      })
+        url: '/pages/ai-chat/index'
+      });
+    },
+    
+    // 处理文章阅读量更新
+    handleViewCountUpdate(data) {
+      if (!data || !data.id) return;
+      
+      // 更新技术文章列表中的阅读量
+      if (data.type === 'tech' || !data.type) {
+        const index = this.techList.findIndex(item => item.id === data.id);
+        if (index !== -1) {
+          this.techList[index].viewCount = data.viewCount;
+        }
+      }
+      
+      // 更新政策解读列表中的阅读量
+      if (data.type === 'policy') {
+        const index = this.policyList.findIndex(item => item.id === data.id);
+        if (index !== -1) {
+          this.policyList[index].viewCount = data.viewCount;
+        }
+      }
     }
   }
 }
 </script>
 
 <style scoped>
+/* 引入字体图标 */
+@font-face {
+  font-family: 'iconfont';
+  src: url('//at.alicdn.com/t/font_3266846_kxscvlm9qpp.woff2') format('woff2');
+}
 
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 22rpx;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
 
 .icon-robot:before {
   content: "\e64b";
@@ -372,7 +468,7 @@ export default {
   content: "\e614";
 }
 
-/* 页面容器
+/* 页面容器 */
 .page-container {
   min-height: 100vh;
   background-color: #f7f8fa;
@@ -553,13 +649,6 @@ export default {
   align-items: center;
 }
 
-.view-count image {
-  padding-right: 6px;
-  width: 16px;  /* 设置图标宽度 */
-  height: 16px; /* 设置图标高度 */
-  vertical-align: middle;
-}
-
 .view-count .iconfont {
   margin-right: 4rpx;
 }
@@ -586,22 +675,30 @@ export default {
 
 /* 加载中状态 */
 .loading-state {
-  text-align: center;
-  padding: 20rpx 0;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 30rpx 0;
 }
 
-.loading-text {
-  font-size: 24rpx;
-  color: #999;
+.spinner {
+  width: 60rpx;
+  height: 60rpx;
+  border: 4rpx solid rgba(77, 201, 113, 0.2);
+  border-radius: 50%;
+  border-top-color: #4dc971;
+  animation: spin 0.8s linear infinite;
+  margin-bottom: 16rpx;
 }
 
-/* 加载全部状态 */
-.load-all {
-  text-align: center;
-  padding: 30rpx 0;
+@keyframes spin {
+  to {
+    transform: rotate(360deg);
+  }
 }
 
-.load-all-text {
+.loading-text {
   font-size: 24rpx;
   color: #999;
 }

BIN
static/icons/read.png


+ 6 - 0
utils/storage.js

@@ -90,4 +90,10 @@ export default {
 	isLoggedIn() {
 		return !!this.getHasLogin() && !!this.getAccessToken();
 	},
+	setWvpAccessToken(val){
+		uni.setStorageSync('wvp_access_token', val);
+	},
+	getWvpAccessToken() {
+		return uni.getStorageSync('wvp_access_token');
+	},
 };

Vissa filer visades inte eftersom för många filer har ändrats