Explorar o código

完善AI对话以及农资商城、农品交易

jiuling hai 6 meses
pai
achega
7c7c8ec071

+ 38 - 1
api/services/chart.js

@@ -4,6 +4,7 @@ import {
 } from '@/utils/request.js';
   // 使用storage模块的方法设置登录状态为false
 import storage from "@/utils/storage.js";
+import api from "@/config/api.js";
 const request = http.request;
 
 /**
@@ -17,4 +18,40 @@ export function chartStream(params) {
     // needToken: true,
     data:params,
   });
-}
+}
+
+// 发送流式AI聊天请求(返回fetch Promise)
+export function postChatMessageData(data) {
+	console.log("JSON.stringify(data)",JSON.stringify(data));
+  const baseUrl =api.serve || '';
+  const url = `${baseUrl}/uniapp/dify/chat/stream`;
+  
+  return fetch(url, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      'Accept': 'text/event-stream',
+      'Authorization': `Bearer ${storage.getAccessToken()}`
+    },
+    body: JSON.stringify(data),
+    credentials: 'include', // 确保发送凭证(cookie等)
+    mode: 'cors'           // 允许跨域请求
+  });
+}
+
+// 终止流式请求
+export function stopChatStream(data) {
+  return request({
+    url: '/uniapp/dify/chat/stop',
+    method: 'post',
+    data: data
+  })
+} 
+// 获取下一轮建议问题列表
+export function chatStreamSuggested(data) {
+  return http.request({
+    url: '/uniapp/dify/chat/suggested',
+    method: 'get',
+    params: data
+  })
+} 

+ 54 - 0
api/services/productInfo.js

@@ -0,0 +1,54 @@
+import { http, Method } from '@/utils/request';
+import storage from "@/utils/storage.js";
+const userInfo = storage.getUserInfo()
+/**
+ * 获取农品列表
+ * @param {Object} params - 查询参数
+ * @returns {Promise} - 返回任务列表
+ */
+export function getProductInfoList(params) {
+  return http.request({
+    url: '/base/productInfo/list',
+    method: Method.GET,
+    params: params,
+	needToken: true
+  });
+}
+
+
+/**
+ * 根据ID获取农品详细信息
+ * @param {id} params - 查询参数
+ * @returns {Promise} - 返回任务详情
+*/
+export function getProductInfoById(id) {
+  return http.request({
+    url: `/base/productInfo/${id}`,
+    method: Method.GET,
+	needToken: true
+  });
+}
+
+/**
+ * 发布农品信息
+*/
+export function addProductInfo(params) {
+  return http.request({
+    url: '/base/productInfo',
+    method: Method.POST,
+	needToken: true,
+	data: params
+  });
+}
+
+/**
+ * 修改农品信息
+*/
+export function editProductInfo(params) {
+  return http.request({
+    url: '/base/productInfo',
+    method: Method.PUT,
+	needToken: true,
+	data: params
+  });
+}

+ 193 - 0
components/common/LocationPicker.vue

@@ -0,0 +1,193 @@
+<template>
+  <!-- <view class="form-item">	 -->
+<!--   <view class="item-label">
+      <text class="label-text">{{ label }}</text>
+      <text v-if="required" class="required">*</text>
+    </view> -->
+
+    <!-- 编辑模式:输入框 -->
+    <uni-data-picker
+      v-if="mode === 'edit'"
+      v-model="innerValue"
+      :localdata="localData"
+      :popup-title="popupTitle"
+      @change="onChange"
+    >
+      <u-input
+        :value="getLocationLabel(innerValue)"
+        :placeholder="placeholder"
+        readonly
+        suffix-icon="arrow-down"
+      >
+        <!-- 清除按钮 -->
+        <template slot="suffix">
+          <view 
+            v-if="innerValue"
+            @click.stop="clearAddress"
+            style="padding: 0 8rpx; display: flex; align-items: center;"
+          >
+            <uni-icons type="close" color="#999" size="20"></uni-icons>
+          </view>
+        </template>
+      </u-input>
+    </uni-data-picker>
+
+    <!-- 展示模式:纯文本 -->
+    <text v-else class="location-text">
+      {{ getLocationLabel(innerValue) || "无" }}
+    </text>
+  <!-- </view> -->
+</template>
+
+<script>
+import cityRows from '@/utils/data.json';
+
+export default {
+  name: "LocationPicker",
+  props: {
+	mode: { // 新增模式属性
+	  type: String,
+	  default: "edit" // edit / view
+	},
+    value: { // v-model
+      type: [String, Number],
+      default: ""
+    },
+    label: { // 左侧文字
+      type: String,
+      default: "所在地"
+    },
+    placeholder: {
+      type: String,
+      default: "请选择省市区"
+    },
+    popupTitle: {
+      type: String,
+      default: "请选择省市区"
+    },
+    required: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      innerValue: this.value,
+      localData: [] // 省市区树数据
+    }
+  },
+  watch: {
+    value(newVal) {
+      this.innerValue = newVal
+    },
+    innerValue(newVal) {
+      this.$emit("input", newVal)
+    }
+  },
+  created() {
+    this.localData = this.get_city_tree()
+  },
+  methods: {
+    /** 点击选择后的回调 */
+    onChange(e) {
+      const lastNode = e.detail.value[e.detail.value.length - 1]
+      this.innerValue = lastNode.value // 只存最底层的 code
+    },
+    /** 清空地址 */
+    clearAddress() {
+      this.innerValue = ""
+      this.$emit("clear")
+    },
+    /** 回显文字(递归找路径) */
+    getLocationLabel(value) {
+      if (!value) return ""
+      let label = ""
+      const traverse = (nodes) => {
+        for (const node of nodes) {
+          if (node.value === value) {
+            label = node.text
+            return true
+          }
+          if (node.children && traverse(node.children)) {
+            label = node.text + " - " + label
+            return true
+          }
+        }
+        return false
+      }
+      traverse(this.localData)
+      return label
+    },
+    /** 生成树数据 */
+    get_city_tree() {
+      let res = []
+      if (cityRows.length) {
+        res = this.handleTree(cityRows)
+      }
+      return res
+    },
+    /** 递归组装树 */
+    handleTree(data, parent_code = null) {
+      let res = []
+      let keys = {
+        id: "code",
+        pid: "parent_code",
+        children: "children",
+        text: "name",
+        value: "code"
+      }
+
+      for (let item of data) {
+        if (parent_code === null) {
+          // 顶级
+          if (!item.hasOwnProperty(keys.pid) || item[keys.pid] == parent_code) {
+            let node = {
+              text: item[keys.text],
+              value: item[keys.value],
+              children: this.handleTree(data, item[keys.id])
+            }
+            res.push(node)
+          }
+        } else {
+          // 子级
+          if (item.hasOwnProperty(keys.pid) && item[keys.pid] == parent_code) {
+            let node = {
+              text: item[keys.text],
+              value: item[keys.value],
+              children: this.handleTree(data, item[keys.id])
+            }
+            res.push(node)
+          }
+        }
+      }
+      return res
+    }
+  }
+}
+</script>
+
+<style scoped>
+.form-item {
+  display: flex;
+  flex-direction: column;
+  /* margin-bottom: 20rpx; */
+}
+.item-label {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10rpx;
+}
+.label-text {
+  font-size: 28rpx;
+  color: #333;
+}
+.required {
+  color: red;
+  margin-left: 5rpx;
+}
+.location-text {
+ /* font-size: 28rpx;
+  color: #333; */
+  /* padding: 16rpx 0; */	
+}
+</style>

+ 6 - 5
pages.json

@@ -81,7 +81,8 @@
 				"navigationBarTitleText": "设备列表",
 				"navigationBarBackgroundColor": "#ffffff",
 				"navigationBarTextStyle": "black",
-				"enablePullDownRefresh": false
+				"enablePullDownRefresh": false,
+				"disableScroll": true
 			}
 		},
 		{
@@ -109,10 +110,10 @@
 				},
 				"app-plus": {
 					"titleNView": {
-						"buttons": [{
-							"type": "back",
-							"background": "transparent"
-						}]
+						// "buttons": [{
+						// 	"type": "back",
+						// 	"background": "transparent"
+						// }]
 					}
 				},
 				"mp-weixin": {

+ 11 - 11
pages/activity/activity-detail.vue

@@ -9,11 +9,11 @@
         </view>
         <view class="info-item">
           <text class="info-label">地块名称</text>
-          <text class="info-value">{{ formData.plotName || '未知' }}</text>
+          <text class="info-value">{{ formData.fieldName || '未知' }}</text>
         </view>
         <view class="info-item">
           <text class="info-label">作物名称</text>
-          <text class="info-value">{{ formData.crop || '未知' }}</text>
+          <text class="info-value">{{ formData.growCrops || '未知' }}</text>
         </view>
         <view class="info-item">
           <text class="info-label">负责人</text>
@@ -390,8 +390,8 @@ export default {
         
         // 地块基础信息
         plotId: '',
-        plotName: '',
-        crop: '',
+        fieldName: '',
+        growCrops: '',
         manager: '',
         
         // 任务信息
@@ -515,9 +515,9 @@ export default {
     }
     
     // 设置地块基础信息
-    this.formData.plotName = decodeURIComponent(options.plotName || '未知');
-    this.formData.crop = decodeURIComponent(options.crop || '未知');
-    this.formData.manager = decodeURIComponent(options.manager || '未知');
+    this.formData.fieldName = decodeURIComponent(options.fieldName === 'undefined' ? '未选择地块' : options.fieldName);
+    this.formData.growCrops = decodeURIComponent(options.growCrops === 'undefined' ? '未选择地块' : options.growCrops);
+    this.formData.manager = decodeURIComponent(options.manager === 'undefined' ? '未选择地块' : options.manager);
     this.formData.plotId = parseInt(options.plotId || '1');
     this.formData.farmId = parseInt(options.farmId || '1');
     
@@ -580,11 +580,11 @@ export default {
       getAgriculturalTasksById(taskId).then(res => {
         if (res.data.code === 200) {
           const taskDetail = res.data.data;
-          
           // 设置表单数据
           this.formData = {
-            ...this.formData, // 保留原有的地块信息
+            // ...this.formData, // 保留原有的地块信息
             ...taskDetail, // 合并后端返回的任务信息
+			manager: this.formData.manager,  
             completionStatus: taskDetail.taskStatus, // 将后端的taskStatus映射为前端的completionStatus
           };
           
@@ -1248,8 +1248,8 @@ export default {
         ...(this.formData.id ? { id: this.formData.id } : {}),
 		farmId: this.formData.farmId,
         plotId: this.formData.plotId,
-        fieldName: this.formData.plotName,
-        growCrops: this.formData.crop,
+        fieldName: this.formData.fieldName,
+        growCrops: this.formData.growCrops,
         taskName: this.formData.taskName,
         taskImages: this.formData.taskImages,
         typeName: this.formData.typeNameId,

+ 23 - 13
pages/activity/index.vue

@@ -4,17 +4,17 @@
       <!-- 顶部地块信息卡片 -->
       <view class="plot-info-card">
         <view class="plot-header">
-          <text class="plot-name">{{ plotData.name }}</text>
-          <text class="plot-area">{{ plotData.size }}亩</text>
+          <text class="plot-name">{{ plotData.name || '未选择地块'}}</text>
+          <text class="plot-area">{{ plotData.size || 0 }}亩</text>
         </view>
         <view class="plot-details">
           <view class="plot-item">
             <text class="item-label">作物:</text>
-            <text class="item-value">{{ plotData.growCrops }}</text>
+            <text class="item-value">{{ plotData.growCrops || '未选择地块' }}</text>
           </view>
           <view class="plot-item">
             <text class="item-label">负责人:</text>
-            <text class="item-value">{{ plotData.managerName }}</text>
+            <text class="item-value">{{ plotData.managerName || '未选择地块'}}</text>
           </view>
         </view>
       </view>
@@ -189,7 +189,17 @@ export default {
     
     // 加载任务数据
     loadTaskData() {
-      this.plotData = JSON.parse(storage.getPlots() || '{}')
+      // this.plotData = JSON.parse(storage.getPlots() || '{}')
+	  this.plotData = JSON.parse(storage.getPlots() || '{}')
+	  const plotId = this.plotData?.id;
+	  // 判断地块是否存在且有 id
+	  if (plotId == undefined) {
+	    uni.showToast({
+	      title: '请选择地块!',
+	      icon: 'none'
+	    });
+	    return
+	  }
 	  console.log("this.plotData",this.plotData);
       this.isLoading = true;
       
@@ -197,7 +207,7 @@ export default {
       const params = {
         pageNum: this.pageNum,
         pageSize: this.pageSize,
-        plotId: this.plotData.id
+        plotId: plotId
       };
       
       // 如果不是查询全部,添加状态过滤条件
@@ -268,11 +278,11 @@ export default {
     },
 
     // 下拉刷新
-    refreshData(e) {
+    refreshData(plotId) {
       this.isRefreshing = true;
       this.pageNum = 1;
       this.noMoreData = false;
-      this.loadTaskData();
+      this.loadTaskData(plotId);
     },
 
     // 上拉加载更多
@@ -294,9 +304,9 @@ export default {
         // 已完成任务跳转到查看页面
         mode = 'view';
       }
-      
+      console.log("this.plotData",this.plotData);
       // 使用简化的URL参数传递
-      const url = `/pages/activity/activity-detail?id=${task.id}&mode=${mode}&plotName=${encodeURIComponent(this.plotData.name)}&crop=${encodeURIComponent(this.plotData.growCrops)}&manager=${encodeURIComponent(this.plotData.managerName)}`;
+      const url = `/pages/activity/activity-detail?id=${task.id}&mode=${mode}&fieldName=${encodeURIComponent(this.plotData.name)}&growCrops=${encodeURIComponent(this.plotData.growCrops)}&farmId=${encodeURIComponent(this.plotData.farmId)}&manager=${encodeURIComponent(this.plotData.managerName)}&plotId=${this.plotData.id}`;
       
       uni.navigateTo({
         url: url
@@ -306,7 +316,7 @@ export default {
     // 创建新任务
     createNewTask() {
       // 使用简化的URL参数传递
-      const url = `/pages/activity/activity-detail?id=new&mode=create&plotName=${encodeURIComponent(this.plotData.name)}&crop=${encodeURIComponent(this.plotData.growCrops)}&farmId=${encodeURIComponent(this.plotData.farmId)}&manager=${encodeURIComponent(this.plotData.managerName)}&plotId=${this.plotData.id}`;
+      const url = `/pages/activity/activity-detail?id=new&mode=create&fieldName=${encodeURIComponent(this.plotData.name)}&growCrops=${encodeURIComponent(this.plotData.growCrops)}&farmId=${encodeURIComponent(this.plotData.farmId)}&manager=${encodeURIComponent(this.plotData.managerName)}&plotId=${this.plotData.id}`;
       
       uni.navigateTo({
         url: url
@@ -323,12 +333,12 @@ export default {
   
   // 页面加载时获取数据
   onLoad() {
-    this.loadTaskData();
+	this.loadTaskData();
   },
   
   // 页面显示时刷新数据
   onShow() {
-    this.refreshData();
+	this.refreshData()
   }
 }
 </script>

+ 568 - 202
pages/knowledge/ai-chat/index.vue

@@ -17,16 +17,28 @@
 						<view class="message-content">
 							<view class="message-bubble ai-bubble"
 								:class="{'typing': message.isTyping, 'welcome': message.isWelcome || index === 0}">
+								<!-- 正在输入指示器 -->
 								<view v-if="message.isTyping" class="typing-indicator">
 									<view class="typing-dot"></view>
 									<view class="typing-dot"></view>
 									<view class="typing-dot"></view>
 								</view>
-								<text v-else class="message-text"
+								
+								<!-- 消息内容 -->
+								<text v-else-if="message.content" class="message-text"
 									:class="{'highlight': containsKeywords(message.content)}">
 									{{ message.content }}
 								</text>
 							</view>
+							
+							<!-- 操作按钮区域(仅最后一条AI消息显示) -->
+							<view v-if="index === chatMessages.length - 1 && message.sender === 'ai' && !message.isTyping" class="message-actions">
+								<view class="action-button" @click="regenerateMessage" hover-class="action-button-hover">
+									<text class="action-icon">🔄</text>
+									<text class="action-text">重新生成</text>
+								</view>
+							</view>
+							
 							<text class="message-time">{{ message.time }}</text>
 						</view>
 					</template>
@@ -50,7 +62,7 @@
 		<!-- 底部输入区 -->
 		<view class="input-container" :style="{ paddingBottom: `${isIOS ? safeAreaBottom : 20}rpx` }">
 			<!-- 问题建议区 -->
-			<scroll-view v-if="chatMessages.length <= 3 && !inputMessage" class="suggested-questions" scroll-x>
+			<scroll-view v-if="chatMessages.length <= 3 && !inputMessage && !isProcessing" class="suggested-questions" scroll-x>
 				<view v-for="(question, index) in suggestedQuestions" :key="index" class="question-chip"
 					@click="useQuestion(question)">
 					<text>{{ question }}</text>
@@ -61,20 +73,35 @@
 				<textarea class="message-input" v-model="inputMessage" placeholder="请输入您的问题..." :disabled="isProcessing"
 					auto-height :maxlength="300" :style="{ maxHeight: '120rpx' }" @focus="onInputFocus"
 					@confirm="submitQuestion" />
-				<view class="send-button" :class="{ 'disabled': !inputMessage.trim() || isProcessing }"
+				
+				<!-- 中止按钮(流式输出时显示) -->
+				<view v-if="isProcessing" class="stop-button" @click="stopStreaming" hover-class="button-hover">
+					<text class="stop-icon">⏹</text>
+				</view>
+				
+				<!-- 发送按钮 -->
+				<view v-else class="send-button" :class="{ 'disabled': !inputMessage.trim() }"
 					@click="submitQuestion" hover-class="button-hover">
 					<image class="send-icon-image"
-						:src="inputMessage.trim() && !isProcessing ? '/static/icons/chat.png' : '/static/icons/chat_off.png'"
+						:src="inputMessage.trim() ? '/static/icons/chat.png' : '/static/icons/chat_off.png'"
 						mode="aspectFit"></image>
 				</view>
 			</view>
 		</view>
+		
+		<!-- renderjs 模块容器(用于 H5/App 端 SSE 流式连接) -->
+		<view 
+			:change:prop="renderModule.onDataChange" 
+			:prop="renderjsData"
+			class="renderjs-container"
+		></view>
 	</view>
 </template>
 
 <script>
 	import api from "@/config/api.js";
 	import storage from "@/utils/storage.js";
+	import { chatStreamSuggested } from "@/api/services/chart.js";
 	export default {
 		data() {
 			return {
@@ -89,8 +116,8 @@
 				scrollTop: 0,
 				inputHeight: 110,
 				isProcessing: false,
-				requestTask: null,
 				currentTypingMessage: null, // 当前正在输入的消息索引
+				lastUserQuestion: '', // 保存最后一个用户问题,用于重新生成
 				suggestedQuestions: [
 					'水稻插秧后如何管理?',
 					'果树夏季修剪技巧?',
@@ -101,8 +128,22 @@
 				statusBarHeight: 20,
 				safeAreaBottom: 34,
 				isIOS: false,
-				typingInterval: 100, // 打字速度配置
-				typingTimers: [] // 用于存储定时器
+				// renderjs 通信数据
+				renderjsData: {
+					action: '', // start, stop
+					url: '',
+					data: {},
+					timestamp: 0
+				},
+				// 消息队列和打字机效果
+				messageQueue: [],
+				isTypingEffect: false,
+				typingTimer: null,
+				// Thinking 模式追踪
+				isInThinkingMode: false,
+				sessionId: null,
+				messageId: null,// 消息ID,用于标识当前消息
+				thinkingBuffer: '' // 临时存储 Thinking 内容
 			}
 		},
 		// 设置页面标题
@@ -128,112 +169,169 @@
 			});
 		},
 		methods: {
-			// 处理消息发送的方法
-			makeStreamRequest(message) {
-				if (this.requestTask) {
-					this.requestTask.abort();
-				}
-
-				const url = api.serve + '/uniapp/dify/chat/stream';
-
-				// 添加一个临时的"正在输入"消息
-				const typingMessageIndex = this.chatMessages.push({
-					sender: 'ai',
-					content: '',
-					time: this.getCurrentTime(),
-					isTyping: true
-				}) - 1;
-
-				this.currentTypingMessage = typingMessageIndex;
-
-				this.requestTask = uni.request({
-					url: url,
-					method: 'POST',
-					data: message,
-					responseType: 'text',
-					header: {
-						'content-type': 'application/json',
-						'Authorization': `Bearer ${storage.getAccessToken()}`
-					},
-					success: (res) => {
-						if (res.data) {
-							const lines = res.data.split('\n');
-							this.processSSELines(lines, typingMessageIndex);
-						}
-					},
-					fail: (err) => {
-						console.error('请求失败:', err);
-						this.handleError(err);
-						// 移除"正在输入"消息
-						if (this.currentTypingMessage !== null) {
-							this.chatMessages.splice(this.currentTypingMessage, 1);
-						}
-					},
-					complete: () => {
-						this.requestTask = null;
-						this.isProcessing = false;
-						this.scrollToBottom();
+			// ========== renderjs 通信方法 ==========
+			
+			// renderjs 回调:接收流式数据
+			onStreamData(data) {
+				console.log('收到流式数据:', data);
+				
+				if (data.type === 'thinking') {
+					// 第一个 thinking 类型,开启 Thinking 模式
+					this.isInThinkingMode = true;
+					this.thinkingBuffer = data.content;
+					this.processThinkingContent();
+				} else if (data.type === 'message') {
+					// 判断是否在 Thinking 模式中
+					if (this.isInThinkingMode) {
+						// 仍在 Thinking 区域内,累积内容
+						this.thinkingBuffer += data.content;
+						this.processThinkingContent();
+					} else {
+						// 普通消息内容
+						this.handleMessageData(data.content);
 					}
-				});
+				} else if (data.type === 'end') {
+					// 流式结束
+					console.log("消息id:",data.id);
+					
+					this.finishStreaming(data.id);
+				} else if (data.type === 'error') {
+					// 错误处理
+					this.handleStreamError(data.error);
+				}
 			},
-
-			// 处理 SSE 数据行
-			processSSELines(lines, typingMessageIndex) {
-				let accumulatedText = this.chatMessages[typingMessageIndex]?.content || '';
-
-				lines.forEach(line => {
-					if (!line.trim()) return;
-
-					if (line.startsWith('data:')) {
-						try {
-							const jsonData = JSON.parse(line.slice(5).trim());
-							if (jsonData.eventType === 'AGENT_MESSAGE' && jsonData.answer) {
-								accumulatedText += jsonData.answer;
-								// 使用新的打字机效果更新消息内容
-								this.typewriterEffect(accumulatedText, (text) => {
-									this.$set(this.chatMessages[typingMessageIndex], 'content', text);
-									// 滚动到底部,确保用户看到最新内容
-									this.scrollToBottom();
-								});
-							} else if (jsonData.eventType === 'MESSAGE_END') {
-								// 消息结束时停止打字效果
-								this.currentTypingMessage = null;
-								this.$set(this.chatMessages[typingMessageIndex], 'isTyping', false);
-								// 确保完整内容显示
-							}
-						} catch (e) {
-							console.error('解析数据出错:', e);
-						}
+			
+			// 处理 Thinking 内容(跳过 Thinking,只处理后续内容)
+			processThinkingContent() {
+				if (this.currentTypingMessage === null) return;
+				
+				// 检查是否包含 </details> 结束标签
+				if (this.thinkingBuffer.includes('</details>')) {
+					// Thinking 区域结束,提取 </details> 之后的内容
+					this.isInThinkingMode = false;
+					
+					// 提取 </details> 后面的内容
+					const detailsEndIndex = this.thinkingBuffer.indexOf('</details>');
+					const contentAfterThinking = this.thinkingBuffer.substring(detailsEndIndex + '</details>'.length);
+					
+					// 如果有内容,添加到消息队列
+					if (contentAfterThinking) {
+						this.handleMessageData(contentAfterThinking);
 					}
-				});
+					
+					// 清空缓冲区
+					this.thinkingBuffer = '';
+				}
+				// 如果还在 Thinking 区域内,不做任何显示,继续累积
 			},
-
-			// 实现打字机效果的方法
-			typewriterEffect(text, callback) {
-				let index = 0;
-				const length = text.length;
-				const interval = 50; // 打字间隔时间
-
-				// 清除之前的打字机效果定时器
-				this.typingTimers.forEach(timer => clearTimeout(timer));
-				this.typingTimers = [];
-
-				const type = () => {
-					if (index <= length) {
-						callback(text.slice(0, index));
-						index++;
-						const timer = setTimeout(type, interval);
-						this.typingTimers.push(timer);
+			
+			// 处理消息数据(添加到队列)
+			handleMessageData(text) {
+				if (!text) return;
+				
+				// 将文本按字符添加到队列
+				for (let char of text) {
+					this.messageQueue.push(char);
+				}
+				
+				// 如果打字机效果未启动,则启动
+				if (!this.isTypingEffect) {
+					this.startTypingEffect();
+				}
+			},
+			
+			// 启动打字机效果
+			startTypingEffect() {
+				if (this.isTypingEffect || this.currentTypingMessage === null) return;
+				
+				this.isTypingEffect = true;
+				this.$set(this.chatMessages[this.currentTypingMessage], 'isTyping', false);
+				
+				const processQueue = () => {
+					if (this.messageQueue.length > 0 && this.currentTypingMessage !== null) {
+						// 每次取出一个字符
+						const char = this.messageQueue.shift();
+						const currentContent = this.chatMessages[this.currentTypingMessage].content || '';
+						this.$set(this.chatMessages[this.currentTypingMessage], 'content', currentContent + char);
+						
+						// 每20个字符滚动一次,优化性能
+						if (currentContent.length % 20 === 0) {
+							this.scrollToBottom();
+						}
+						
+						// 继续处理队列
+						this.typingTimer = setTimeout(processQueue, 10);
+					} else if (this.messageQueue.length === 0) {
+						// 队列为空,等待新数据
+						this.typingTimer = setTimeout(processQueue, 50);
 					}
 				};
-
-				type();
+				
+				processQueue();
+			},
+			
+			// 停止打字机效果
+			stopTypingEffect() {
+				this.isTypingEffect = false;
+				if (this.typingTimer) {
+					clearTimeout(this.typingTimer);
+					this.typingTimer = null;
+				}
+				
+				// 清空队列,将剩余内容一次性显示
+				if (this.messageQueue.length > 0 && this.currentTypingMessage !== null) {
+					const remainingText = this.messageQueue.join('');
+					const currentContent = this.chatMessages[this.currentTypingMessage].content || '';
+					this.$set(this.chatMessages[this.currentTypingMessage], 'content', currentContent + remainingText);
+					this.messageQueue = [];
+				}
+			},
+			
+			// 完成流式输出
+			finishStreaming(id) {
+				console.log("消息id2:",id);
+				this.stopTypingEffect();
+				
+				if (this.currentTypingMessage !== null) {
+					this.$set(this.chatMessages[this.currentTypingMessage], 'isTyping', false);
+					this.currentTypingMessage = null;
+				}
+				
+				// 重置 Thinking 模式状态
+				this.isInThinkingMode = false;
+				this.thinkingBuffer = '';
+				
+				this.isProcessing = false;
+				this.scrollToBottom();
+				
+				// 获取下一轮建议问题
+				// this.fetchSuggestedQuestions({user: this.sessionId,messageId:id});
+			},
+			
+			// 处理流式错误
+			handleStreamError(error) {
+				console.error('流式错误:', error);
+				this.stopTypingEffect();
+				
+				// 移除正在输入的消息
+				if (this.currentTypingMessage !== null) {
+					this.chatMessages.splice(this.currentTypingMessage, 1);
+					this.currentTypingMessage = null;
+				}
+				
+				// 重置 Thinking 模式状态
+				this.isInThinkingMode = false;
+				this.thinkingBuffer = '';
+				
+				this.isProcessing = false;
+				this.handleError({ message: error || '网络异常,请稍后重试' });
 			},
 
+			// ========== 用户交互方法 ==========
+			
 			// 发送消息
 			submitQuestion() {
 				if (!storage.getHasLogin()) {
-					// 使用 uni.showModal 显示登录提示框
 					uni.showModal({
 						title: '提示',
 						content: '您还未登录,请先登录',
@@ -241,7 +339,6 @@
 						cancelText: '取消',
 						success: function(res) {
 							if (res.confirm) {
-								// 点击确定按钮,跳转到登录页面
 								uni.navigateTo({
 									url: '/pages/login/index'
 								});
@@ -250,85 +347,155 @@
 					});
 					return;
 				}
+				
 				if (!this.inputMessage.trim() || this.isProcessing) return;
 
-				this.isProcessing = true;
-
+				const question = this.inputMessage.trim();
+				this.lastUserQuestion = question; // 保存问题用于重新生成
+				this.inputMessage = '';
+				
 				// 添加用户消息
 				this.chatMessages.push({
 					sender: 'user',
-					content: this.inputMessage,
-					time: this.getCurrentTime()
+					content: question,
+					time: this.getCurrentTime(),
+					timestamp: Date.now()
 				});
 
-				const message = {
-					query: this.inputMessage,
-					user: 'user_' + Date.now().toString()
+				// 添加 AI 正在输入的消息
+				const typingMessageIndex = this.chatMessages.push({
+					sender: 'ai',
+					content: '',
+					time: this.getCurrentTime(),
+					timestamp: Date.now(),
+					isTyping: true
+				}) - 1;
+
+				this.currentTypingMessage = typingMessageIndex;
+				this.isProcessing = true;
+				this.scrollToBottom();
+				
+				// 通过 renderjs 发起 SSE 请求
+				this.startSSERequest(question);
+			},
+			
+			// 启动 SSE 请求(通过 renderjs)
+			startSSERequest(question) {
+				const url = api.serve + '/uniapp/dify/chat/stream';
+				this.sessionId = Date.now().toString()
+				const requestData = {
+					query: question,
+					user: 'user_' + this.sessionId
 				};
+				
+				// 更新 renderjs 数据,触发 SSE 连接
+				this.renderjsData = {
+					action: 'start',
+					url: url,
+					data: requestData,
+					token: storage.getAccessToken(),
+					timestamp: Date.now()
+				};
+			},
+			
+			// 停止流式输出
+			stopStreaming() {
+				console.log('用户中止流式输出');
+				
+				// 通知 renderjs 停止
+				this.renderjsData = {
+					action: 'stop',
+					timestamp: Date.now()
+				};
+				
+				// 立即停止打字机效果并完成
+				this.finishStreaming();
+				
+				uni.showToast({
+					title: '已中止',
+					icon: 'none',
+					duration: 1500
+				});
+			},
+			
+			// 重新生成回复
+			regenerateMessage() {
+				if (!this.lastUserQuestion || this.isProcessing) return;
+				
+				// 删除最后一条 AI 消息
+				if (this.chatMessages.length > 0 && this.chatMessages[this.chatMessages.length - 1].sender === 'ai') {
+					this.chatMessages.pop();
+				}
+				
+				// 添加新的正在输入消息
+				const typingMessageIndex = this.chatMessages.push({
+					sender: 'ai',
+					content: '',
+					time: this.getCurrentTime(),
+					timestamp: Date.now(),
+					isTyping: true
+				}) - 1;
 
-				this.inputMessage = '';
+				this.currentTypingMessage = typingMessageIndex;
+				this.isProcessing = true;
+				this.messageQueue = [];
+				
+				// 重置 Thinking 模式状态
+				this.isInThinkingMode = false;
+				this.thinkingBuffer = '';
+				
 				this.scrollToBottom();
-				this.makeStreamRequest(message);
+				
+				// 重新发起请求
+				this.startSSERequest(this.lastUserQuestion);
 			},
 
-			// 添加错误处理方法
+			// ========== 工具方法 ==========
+			
+			// 错误处理
 			handleError(error) {
-				let errorMessage = '发生错误'
+				let errorMessage = '发生错误';
 				if (error.errMsg) {
-					errorMessage = error.errMsg
+					errorMessage = error.errMsg;
 				} else if (error.message) {
-					errorMessage = error.message
+					errorMessage = error.message;
 				}
 
 				uni.showToast({
 					title: errorMessage,
 					icon: 'none',
 					duration: 2000
-				})
+				});
 			},
 
-			// 添加防抖函数
+			// 防抖函数
 			debounce(func, wait) {
-				let timeout
+				let timeout;
 				return (...args) => {
-					clearTimeout(timeout)
+					clearTimeout(timeout);
 					timeout = setTimeout(() => {
-						func.apply(this, args)
-					}, wait)
-				}
+						func.apply(this, args);
+					}, wait);
+				};
 			},
+			
+			// 输入框获取焦点
 			onInputFocus() {
-				// 输入框获取焦点时,确保滚动到底部
 				this.$nextTick(() => {
 					this.scrollToBottom();
 				});
 			},
+			
 			// 滚动到底部
 			scrollToBottom() {
 				this.$nextTick(() => {
-					const query = uni.createSelectorQuery().in(this)
+					const query = uni.createSelectorQuery().in(this);
 					query.select('.chat-list').boundingClientRect(data => {
 						if (data) {
-							this.scrollTop = data.height + 1000
+							this.scrollTop = data.height + 1000;
 						}
-					}).exec()
-				})
-			},
-
-			// 错误处理
-			handleError(error) {
-				let errorMessage = '发生错误'
-				if (error.errMsg) {
-					errorMessage = error.errMsg
-				} else if (error.message) {
-					errorMessage = error.message
-				}
-
-				uni.showToast({
-					title: errorMessage,
-					icon: 'none',
-					duration: 2000
-				})
+					}).exec();
+				});
 			},
 			onScroll(e) {
 				// 可以添加滚动事件处理
@@ -341,58 +508,7 @@
 				const minutes = date.getMinutes().toString().padStart(2, '0');
 				return `${hours}:${minutes}`;
 			},
-			/*    submitQuestion() {
-			      if (!this.inputMessage.trim() || this.isProcessing) return;
-			      
-			      this.isProcessing = true;
-			      
-			      // 添加用户消息
-			      this.chatMessages.push({
-			        sender: 'user',
-			        content: this.inputMessage,
-			        time: this.getCurrentTime()
-			      });
-			      
-			      const userQuestion = this.inputMessage;
-			      this.inputMessage = '';
-			      
-			      // 滚动到底部
-			      this.scrollToBottom();
-			      
-			      // 先添加一个"正在输入"的消息
-			      this.chatMessages.push({
-			        sender: 'ai',
-			        content: '正在思考...',
-			        time: this.getCurrentTime(),
-			        isTyping: true
-			      });
-			      
-			      // 模拟AI回复(实际项目中替换为API调用)
-			      setTimeout(() => {
-			        // 删除"正在输入"消息
-			        this.chatMessages.pop();
-			        
-			        // 模拟回复
-			        let aiResponse = "感谢您的提问!作为您的农技助理,我很高兴能帮助您解决农业相关问题。您询问的内容我已收到,我们团队正在研究适合的解决方案。";
-			        
-			        if (userQuestion.includes('水稻')) {
-			          aiResponse = "水稻种植需要注意水肥管理和病虫害防治。目前南方地区水稻插秧时间已到,建议您选择抗病性强的品种,如'中优84'。插秧后7天开始浅水促根,分蘖期保持3-5cm水层。";
-			        } else if (userQuestion.includes('蔬菜')) {
-			          aiResponse = "夏季蔬菜种植需注意遮阳和水分管理。建议种植耐热品种如空心菜、苋菜、茄子等。可以采用遮阳网减少强光照射,早晚浇水避开高温时段。";
-			        } else if (userQuestion.includes('果树')) {
-			          aiResponse = "果树现在应注意夏季修剪和病虫害防治。柑橘类果树可以进行夏季修剪,去除徒长枝和内膛枝。同时注意柑橘黄龙病和炭疽病的预防,建议定期喷施药剂保护。";
-			        }
-			        
-			        this.chatMessages.push({
-			          sender: 'ai',
-			          content: aiResponse,
-			          time: this.getCurrentTime()
-			        });
-			        
-			        this.scrollToBottom();
-			        this.isProcessing = false;
-			      }, 2000);
-			    }, */
+			
 			useQuestion(question) {
 				this.inputMessage = question;
 			},
@@ -467,21 +583,210 @@
 				return date1.getDate() === date2.getDate() &&
 					date1.getMonth() === date2.getMonth() &&
 					date1.getFullYear() === date2.getFullYear();
+			},
+			
+			// 获取下一轮建议问题列表
+			async fetchSuggestedQuestions(data) {
+				console.log("获取下一轮建议问题参数",data);
+				try {
+					const response = await chatStreamSuggested(data);
+					
+					if (response && response.data) {
+						// 假设接口返回的数据格式为 { data: ["问题1", "问题2", ...] }
+						if (Array.isArray(response.data) && response.data.length > 0) {
+							this.suggestedQuestions = response.data;
+						} else if (response.data.questions && Array.isArray(response.data.questions)) {
+							// 或者接口返回 { data: { questions: [...] } }
+							this.suggestedQuestions = response.data.questions;
+						}
+					}
+				} catch (error) {
+					console.error('获取建议问题失败:', error);
+					// 失败时保持默认建议问题,不影响用户体验
+				}
 			}
 		},
 		// 组件销毁时清理资源
 		beforeDestroy() {
-			// 清理所有打字机效果的定时器
-			this.typingTimers.forEach(timer => clearTimeout(timer));
-			// 清理请求
-			if (this.requestTask) {
-				this.requestTask.abort();
-				this.requestTask = null;
+			// 停止打字机效果
+			this.stopTypingEffect();
+			
+			// 通知 renderjs 停止连接
+			if (this.isProcessing) {
+				this.renderjsData = {
+					action: 'stop',
+					timestamp: Date.now()
+				};
 			}
+			
+			// 清理消息队列
+			this.messageQueue = [];
 		}
 	}
 </script>
 
+<!-- renderjs 模块:处理 H5/App 端的 SSE 流式连接 -->
+<script module="renderModule" lang="renderjs">
+	export default {
+		data() {
+			return {
+				eventSource: null,
+				reader: null,
+				isReading: false
+			};
+		},
+		methods: {
+			// 监听 prop 变化
+			onDataChange(newValue, oldValue, ownerInstance, instance) {
+				if (!newValue || !newValue.action) return;
+				
+				if (newValue.action === 'start') {
+					this.startSSE(newValue, ownerInstance);
+				} else if (newValue.action === 'stop') {
+					this.stopSSE();
+				}
+			},
+			
+			// 启动 SSE 连接
+			async startSSE(config, ownerInstance) {
+				// 先停止之前的连接
+				this.stopSSE();
+				
+				const { url, data, token } = config;
+				
+				try {
+					// 使用 fetch API 建立 SSE 连接
+					const response = await fetch(url, {
+						method: 'POST',
+						headers: {
+							'Content-Type': 'application/json',
+							'Accept': 'text/event-stream',
+							'Authorization': `Bearer ${token}`
+						},
+						body: JSON.stringify(data)
+					});
+					
+					if (!response.ok) {
+						throw new Error(`HTTP error! status: ${response.status}`);
+					}
+					
+					// 获取 reader
+					this.reader = response.body.getReader();
+					const decoder = new TextDecoder('utf-8');
+					this.isReading = true;
+					
+					let buffer = '';
+					this.eventType    = '';          // 当前事件名
+					this.dataBuffer   = [];          // 当前事件的所有 data 行
+					
+					// 读取流式数据
+					while (this.isReading) {
+						const { done, value } = await this.reader.read();
+						
+						if (done) {
+							console.log('SSE 流结束');
+							ownerInstance.callMethod('onStreamData', { type: 'end' , id: this.messageId});
+							break;
+						}
+						
+						// 解码数据
+						buffer += decoder.decode(value, { stream: true });
+						
+						// 按行处理
+						const lines = buffer.split('\n');
+						buffer = lines.pop() || ''; // 保留最后不完整的行						
+						// buffer = lines.pop(); // 保留最后不完整的行
+						
+						for (const line of lines) {
+							this.processLine(line, ownerInstance);
+						}
+					}
+					
+				} catch (error) {
+					console.error('SSE 连接错误:', error);
+					ownerInstance.callMethod('onStreamData', { 
+						type: 'error', 
+						error: error.message || '连接失败' 
+					});
+				} finally {
+					this.stopSSE();
+				}	
+			},
+			// 处理单行数据
+			processLine(line, ownerInstance) {
+				// console.log("处理单行数据:",line);
+				if (!line.trim()) return;
+				
+				// 解析 SSE 格式
+				if (line.startsWith('event:')) {
+					// event: message
+					return;
+				}
+				
+				if (line.startsWith('data:') || line != '') {
+					let data = line;
+					if (line.startsWith('data:') ){
+						data = data.substring(5).trim();
+					}
+					 			
+					if (!data || data.includes("ping") ) return;
+					
+					// 过滤掉 MESSAGE_END 等元数据事件(JSON 格式)
+					if (data.startsWith('{') && data.includes('"eventType"')) {
+						try {
+							const jsonData = JSON.parse(data);
+							// 如果是 MESSAGE_END 事件,通知结束
+							if (jsonData.eventType === 'MESSAGE_END' || jsonData.event === 'message_end') {
+								console.log('收到 MESSAGE_END 事件,流式结束');
+								this.messageId = jsonData.id
+								ownerInstance.callMethod('onStreamData', { type: 'end' , id: this.messageId });
+								return;
+							}
+							// 其他元数据事件也忽略
+							return;
+						} catch (e) {
+							// 不是 JSON,继续处理
+						}
+					}
+					
+					// 检查是否是 Thinking 内容
+					if (data.includes('<details') && data.includes('<summary>')) {
+						ownerInstance.callMethod('onStreamData', {
+							type: 'thinking',
+							content: data
+						});
+					} else {
+						// 普通消息内容
+						ownerInstance.callMethod('onStreamData', {
+							type: 'message',
+							content: data
+						});
+					}
+				}
+			},
+			
+			// 停止 SSE 连接
+			stopSSE() {
+				this.isReading = false;
+				
+				if (this.reader) {
+					try {
+						this.reader.cancel();
+					} catch (e) {
+						console.error('关闭 reader 失败:', e);
+					}
+					this.reader = null;
+				}
+				
+				if (this.eventSource) {
+					this.eventSource.close();
+					this.eventSource = null;
+				}
+			}
+		}
+	};
+</script>
+
 <style>
 	/* 容器样式 */
 	.container {
@@ -585,6 +890,38 @@
 		font-size: 22rpx;
 		color: #999;
 	}
+	
+	/* 消息操作按钮 */
+	.message-actions {
+		display: flex;
+		gap: 16rpx;
+		margin-top: 12rpx;
+	}
+	
+	.action-button {
+		display: flex;
+		align-items: center;
+		padding: 8rpx 16rpx;
+		background-color: #f5f5f5;
+		border-radius: 20rpx;
+		border: 1rpx solid #e0e0e0;
+		transition: all 0.2s;
+	}
+	
+	.action-button-hover {
+		background-color: #e8f5e9;
+		border-color: #a5d6a7;
+	}
+	
+	.action-icon {
+		font-size: 24rpx;
+		margin-right: 6rpx;
+	}
+	
+	.action-text {
+		font-size: 24rpx;
+		color: #666;
+	}
 
 	/* 输入区域 */
 	.input-container {
@@ -638,6 +975,25 @@
 		background-image: none;
 		opacity: 1;
 	}
+	
+	/* 中止按钮 */
+	.stop-button {
+		margin-left: 16rpx;
+		width: 76rpx;
+		height: 76rpx;
+		border-radius: 50%;
+		background-color: #ff5252;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		transition: all 0.2s ease;
+		align-self: center;
+	}
+	
+	.stop-icon {
+		font-size: 36rpx;
+		color: white;
+	}
 
 	.button-hover {
 		transform: scale(0.95);
@@ -810,4 +1166,14 @@
 	.arrow-left {
 		display: none;
 	}
+	
+	/* renderjs 容器(隐藏) */
+	.renderjs-container {
+		display: none;
+		width: 0;
+		height: 0;
+		opacity: 0;
+		position: absolute;
+		pointer-events: none;
+	}
 </style>

+ 17 - 8
pages/knowledge/detail.vue

@@ -3,11 +3,11 @@
     <!-- H5环境自定义导航栏 -->
     <view class="h5-custom-navbar" v-if="isH5">
 		
-      <view class="h5-navbar-left" @click="goBack">
+<!--      <view class="h5-navbar-left" @click="goBack">
         <view class="h5-back-icon">
           <view class="h5-arrow-left"></view>
         </view>
-      </view>
+      </view> -->
       <text class="h5-navbar-title">农业知识</text>
       <view class="h5-navbar-right"></view>
     </view>
@@ -31,6 +31,7 @@
         @change="handleSwiperChange">
         <swiper-item v-for="(slide, index) in carouselImages" :key="index">
           <view class="swiper-item-container" :style="{ backgroundColor: slide.color }">
+			<image class="swiper-bg" :src="slide.url" mode="aspectFill" />
             <view class="pattern-overlay"></view>
             <view class="agri-icon" v-html="slide.icon"></view>
             <view class="swiper-overlay">
@@ -50,7 +51,7 @@
         <view class="article-meta">
           <text class="source">{{ articleInfo.source }}</text>
           <text class="dot">·</text>
-          <text class="date">{{ formatDate(articleInfo.publishDate) }}</text>
+          <text class="date">{{ articleInfo.publishDate }}</text>
           <view class="views">
             <image src="@/static/icons/read.png" style="width: 30rpx;height: 30rpx;"></image>
             <text class="view-count">{{ articleInfo.viewCount }}</text>
@@ -177,14 +178,13 @@ export default {
           for (let i = matches.length - 1; i >= 0; i--) {
             if (imageIndex < this.knowledgeImages.length) {
               const image = this.knowledgeImages[imageIndex];
+			  console.log("image打打符:",image);
               // 根据是否有真实图片URL来决定显示方式
-              imageHtml = image.url && !image.url.includes('example.com') ? 
+              imageHtml = image.url? 
               `
                 <div class="content-image-wrapper">
                   <div class="content-image-real" style="background-image: url('${image.url}');">
-                    <div class="image-text-overlay">
-                      <div class="image-text">${image.title || '农业知识图解'}</div>
-                    </div>
+                    
                   </div>
                 </div>
               ` : 
@@ -225,8 +225,9 @@ export default {
           // 如果有对应的图片,在标题后插入图片
           if (i - 1 < this.knowledgeImages.length) {
             const image = this.knowledgeImages[i - 1];
+			console.log("image打打符:",image);
             // 根据是否有真实图片URL来决定显示方式
-            newContent += image.url && !image.url.includes('example.com') ? 
+            newContent += image.url ? 
             `
               <div class="content-image-wrapper">
                 <div class="content-image-real" style="background-image: url('${image.url}');">
@@ -306,6 +307,7 @@ export default {
 			    }else{
 					this.knowledgeImages.push(image)
 				}
+				console.log("this.carouselImages",this.carouselImages);
 			});
 		       console.log('文章图片数据:', this.knowledgeImages);
 		     }
@@ -588,6 +590,13 @@ export default {
   height: 100%;
   display: block;
 }
+.swiper-bg {
+  width: 100%;
+  height: 100%;
+  position: absolute;
+  top: 0;
+  left: 0;
+}
 
 .swiper-overlay {
   position: absolute;

+ 1 - 1
pages/knowledge/index.vue

@@ -42,7 +42,7 @@
           >
             <view class="content-thumbnail">
               <image 
-                src="https://img.freepik.com/free-photo/rice-field-with-beautiful-sky_74190-7490.jpg" 
+                :src="item.imageUrl" 
                 mode="aspectFill"
                 class="thumbnail-image"
                 lazy-load

+ 2 - 2
pages/service/mall-detail.vue

@@ -253,13 +253,13 @@ export default {
     handleConsult() {
       uni.showModal({
         title: '咨询服务',
-        content: '您可以通过以下方式联系我们:\n1. 拨打客服热线:400-xxx-xxxx\n2. 在线客服(工作时间:9:00-18:00)',
+        content: '您可以通过以下方式联系我们:\n1. 拨打客服热线:400-888-8888\n2. 在线客服(工作时间:9:00-18:00)',
         confirmText: '拨打电话',
         cancelText: '取消',
         success: (res) => {
           if (res.confirm) {
             uni.makePhoneCall({
-              phoneNumber: '400-xxx-xxxx'
+              phoneNumber: '400-888-8888'
             });
           }
         }

+ 1 - 0
pages/service/mall.vue

@@ -183,6 +183,7 @@ export default {
 		const params = {
 		  pageNum: this.pageNum,
 		  pageSize: this.pageSize,
+		  status: 1 // 已上架
 		  // productCategory: this.activeCategory,
 		  // recommend:1 // 查询推荐商品
 		};

+ 143 - 129
pages/service/my-publish.vue

@@ -48,17 +48,17 @@
           >
             <view class="card-content">
               <view class="product-image">
-                <image :src="item.image" mode="aspectFill"></image>
+                <image :src="getImageUrl(item.imageUrl)" mode="aspectFill"></image>
               </view>
               <view class="product-info">
                 <view class="product-header">
-                  <view class="product-name">{{ item.name }}</view>
-                  <view class="price-info">¥{{ item.price }}/{{ item.unit }}</view>
+                  <view class="product-name">{{ item.title }}</view>
+                  <view class="price-info">¥{{ item.price || 0 }}/{{ getUnitLabel(item.unit) }}</view>
                 </view>
                 <view class="product-desc">{{ item.description }}</view>
                 <view class="product-footer">
                   <view class="publish-time">{{ item.publishTime }}</view>
-                  <view class="status-tag" :class="item.status">
+                  <view class="status-tag" :class="getStatusClassName(item.status)">
                     {{ getStatusText(item.status) }}
                   </view>
                 </view>
@@ -79,8 +79,8 @@
             class="search-input" 
             placeholder="搜索收购信息" 
             placeholder-style="color: #999;"
-            v-model="purchaseSearchKeyword"
-            @confirm="handlePurchaseSearch"
+            v-model="searchKeyword"
+            @confirm="handleSearch"
           />
         </view>
       </view>
@@ -96,17 +96,17 @@
           >
             <view class="card-content">
               <view class="product-image">
-                <image :src="getPurchaseImage(item)" mode="aspectFill"></image>
+                <image :src="getImageUrl(item.imageUrl)" mode="aspectFill"></image>
               </view>
               <view class="product-info">
                 <view class="product-header">
                   <view class="product-name">{{ item.title }}</view>
-                  <view class="price-info">¥{{ item.budgetPrice }} 元/{{ item.unit }}</view>
+                  <view class="price-info">¥{{ item.price || 0 }}/{{ getUnitLabel(item.unit) }}</view>
                 </view>
                 <view class="product-desc">需求数量:{{ item.quantity }}</view>
                 <view class="product-footer">
                   <view class="publish-time">{{ item.publishTime }}</view>
-                  <view class="status-tag" :class="item.status">
+                  <view class="status-tag" :class="getStatusClassName(item.status)">
                     {{ getStatusText(item.status) }}
                   </view>
                 </view>
@@ -151,155 +151,181 @@
 </template>
 
 <script>
+	import {
+		getProductInfoList
+	} from '@/api/services/productInfo.js';
+	import dictMixin from '@/utils/mixins/dictMixin';
+	import storage from "@/utils/storage.js";
 export default {
+	mixins: [dictMixin],
   data() {
     return {
       activeTab: 'published',
       searchKeyword: '',
       purchaseSearchKeyword: '',
       showPublishModal: false,
-      
+      pageNum: 1,
+      pageSize: 10,
+      noMoreData: false,
+      isLoading: false,
+      isRefreshing: false,
+	  dictTypeList: ['agricultural_unit'],
+	  currentUserInfo: storage.getUserInfo(),
       // 我的发布列表
-      publishedProductsList: [
-        {
-          id: 1,
-          name: '有机苹果',
-          description: '新鲜采摘,香甜可口',
-          price: '8.5',
-          unit: '斤',
-          image: '/static/images/products/corn-seeds-new.jpg',
-          publishTime: '今天',
-          status: 'approved'
-        },
-        {
-          id: 2,
-          name: '优质大米',
-          description: '当季新米,粒粒饱满',
-          price: '6.8',
-          unit: '斤',
-          image: '/static/images/products/rice-seeds.jpg',
-          publishTime: '昨天',
-          status: 'pending'
-        },
-        {
-          id: 3,
-          name: '新鲜蔬菜包',
-          description: '时令蔬菜,营养丰富',
-          price: '25',
-          unit: '份',
-          image: '/static/images/products/organic-fertilizer-new.jpg',
-          publishTime: '2天前',
-          status: 'approved'
-        },
-        {
-          id: 4,
-          name: '土鸡蛋',
-          description: '散养土鸡,营养价值高',
-          price: '2.5',
-          unit: '个',
-          image: '/static/images/products/seeds-packets.jpg',
-          publishTime: '3天前',
-          status: 'rejected'
-        }
-      ],
-      
+      publishedProductsList: [],
       // 收购信息列表
-      purchaseList: [
-        {
-          id: 1,
-          title: '高价收购优质土豆',
-          category: '蔬菜',
-          quantity: '1000斤',
-          budgetPrice: '3.5',
-          unit: '斤',
-          contact: '张**',
-          location: '本地',
-          publishTime: '今天',
-          status: 'approved'
-        },
-        {
-          id: 2,
-          title: '大量收购新鲜玉米',
-          category: '粮食',
-          quantity: '5000斤',
-          budgetPrice: '2.8',
-          unit: '斤',
-          contact: '李**',
-          location: '周边',
-          publishTime: '昨天',
-          status: 'pending'
-        },
-        {
-          id: 3,
-          title: '收购有机蔬菜',
-          category: '蔬菜',
-          quantity: '500斤',
-          budgetPrice: '8.0',
-          unit: '斤',
-          contact: '王**',
-          location: '本地',
-          publishTime: '2天前',
-          status: 'rejected'
-        }
-      ]
+      purchaseList: [],
+	  productsList: [], // 二和一存储
     }
   },
   
   computed: {
     filteredPublishedProducts() {
-      let products = this.publishedProductsList;
+      // let products = this.publishedProductsList;
+      let products = this.productsList;
       
-      if (this.searchKeyword.trim()) {
-        const keyword = this.searchKeyword.trim().toLowerCase();
-        products = products.filter(item => 
-          item.name.toLowerCase().includes(keyword) || 
-          item.description.toLowerCase().includes(keyword)
-        );
-      }
+      // if (this.searchKeyword.trim()) {
+      //   const keyword = this.searchKeyword.trim().toLowerCase();
+      //   products = products.filter(item => 
+      //     item.name.toLowerCase().includes(keyword) || 
+      //     item.description.toLowerCase().includes(keyword)
+      //   );
+      // }
       
       return products;
     },
     
     filteredPurchaseList() {
-      let list = this.purchaseList;
+      // let list = this.purchaseList;
+      let list = this.productsList;
       
-      if (this.purchaseSearchKeyword.trim()) {
-        const keyword = this.purchaseSearchKeyword.trim().toLowerCase();
-        list = list.filter(item => 
-          item.title.toLowerCase().includes(keyword) || 
-          item.category.toLowerCase().includes(keyword)
-        );
-      }
+      // if (this.purchaseSearchKeyword.trim()) {
+      //   const keyword = this.purchaseSearchKeyword.trim().toLowerCase();
+      //   list = list.filter(item => 
+      //     item.title.toLowerCase().includes(keyword) || 
+      //     item.category.toLowerCase().includes(keyword)
+      //   );
+      // }
       
       return list;
     }
   },
-  
+  onShow() {
+  	// console.log("my-publish",this.formData);
+  	this.loadMyPublishInfo()
+  },
+  // 页面加载时获取数据
+  onLoad() {
+  	this.loadMyPublishInfo();
+  },
   methods: {
+	  // 上拉加载更多
+	  loadMore() {
+	  	if (!this.isLoading && !this.noMoreData) {
+	  		this.pageNum++;
+	  		this.loadMyPublishInfo();
+	  	}
+	  },
+	  getImageUrl(item) {
+	  	// 默认返回url或path
+	  	if (item == null) return
+	  	const imageUrls = item.split(',');
+	  	// console.log("imageUrls", imageUrls);
+	  	return imageUrls[0];
+	  },
+	  getUnitLabel(value) {
+	  	const unit = this.dictData.agricultural_unit.find(u => u.dictValue == value)
+	  	return unit ? unit.dictLabel : ''
+	  },
+	  loadMyPublishInfo(keyword) {
+	  	this.isLoading = true;
+	  	uni.showLoading({
+	  		title: '加载中'
+	  	});
+	  	// 构建查询参数
+	  	const params = {
+	  		pageNum: this.pageNum,
+	  		pageSize: this.pageSize,
+			createBy: this.currentUserInfo.username,
+			type: 0 // 默认查询出售信息
+	  	};
+	  	if (keyword != null && keyword != '') {
+	  		params.searchKeyword = keyword
+	  	}
+	  
+	  	// 分类逻辑
+	  	if (this.activeTab !== 'published' ) {
+	  		// 默认场景:推荐查询
+	  		params.type = 1;
+	  	}
+	  
+	  	getProductInfoList(params).then(res => {
+	  		if (res.data.code === 200) {
+	  			const {
+	  				rows,
+	  				total
+	  			} = res.data;
+	  			if (this.pageNum === 1) {
+	  				this.productsList = rows;
+	  			} else {
+	  				this.productsList = [...this.productsList, ...rows];
+	  			}
+	  			// 判断是否还有更多数据
+	  			this.noMoreData = this.productsList.length >= total;
+	  			uni.hideLoading();
+	  		} else {
+	  			uni.showToast({
+	  				title: res.data.msg || '获取农品列表失败',
+	  				icon: 'none'
+	  			});
+	  		}
+	  	}).catch(err => {
+	  		console.error('获取农品列表失败:', err);
+	  		uni.showToast({
+	  			title: '获取农品列表失败',
+	  			icon: 'none'
+	  		});
+	  	}).finally(() => {
+	  		this.isLoading = false;
+	  		this.isRefreshing = false;
+	  	});
+	  },
     switchTab(tab) {
       this.activeTab = tab;
+	  // this.publishedProductsList = [] // 切换时置空数据数组
+	  // this.purchaseList = [] 
+	  this.productsList = []
+	  // 滚动到当前分类位置
+	  this.loadMyPublishInfo(this.searchKeyword.trim())
     },
     
     getStatusText(status) {
       const statusMap = {
-        'pending': '审核中',
-        'approved': '已上架',
-        'rejected': '已下架'
+        1: '审核中',
+        2: '已上架',
+        3: '已下架',
+		4: '未通过'
       };
       return statusMap[status] || '未知';
     },
+	getStatusClassName(status) {
+	  const statusMapClassName = {
+	    1: 'pending',
+	    2: 'approved',
+	    3: 'rejected',
+		4: 'rejected'
+	  };
+	  return statusMapClassName[status] || '未知';
+	},
     
     handleSearch() {
-      // 搜索逻辑已在computed中处理
-    },
-    
-    handlePurchaseSearch() {
-      // 搜索逻辑已在computed中处理
+	  const keyword = this.searchKeyword.trim().toLowerCase();
+	  this.loadMyPublishInfo(keyword)
     },
     
     navigateToDetail(product) {
       uni.navigateTo({
-        url: `/pages/service/sales-detail?id=${product.id}&type=sale&name=${encodeURIComponent(product.name)}&source=myPublish&status=${product.status}`
+        url: `/pages/service/sales-detail?id=${product.id}&type=sale&name=${encodeURIComponent(product.title)}&source=myPublish&status=${product.status}`
       });
     },
     
@@ -322,18 +348,6 @@ export default {
         url: '/pages/service/purchase-publish'
       });
     },
-    
-    // 获取收购信息对应的图片
-    getPurchaseImage(item) {
-      // 根据分类返回对应的默认图片
-      const categoryImages = {
-        '蔬菜': '/static/images/products/organic-fertilizer-new.jpg',
-        '粮食': '/static/images/products/corn-seeds-new.jpg',
-        '水果': '/static/images/products/rice-seeds.jpg',
-        '其他': '/static/images/products/agriculture-tools.jpg'
-      };
-      return categoryImages[item.category] || categoryImages['其他'];
-    }
   }
 }
 </script>

+ 948 - 703
pages/service/purchase-publish.vue

@@ -1,710 +1,955 @@
 <template>
-  <view class="purchase-publish-container">
-    <!-- 表单内容 -->
-    <view class="form-container">
-      <!-- 收购标题 -->
-      <view class="form-item">
-        <view class="item-label">
-          <text class="label-text">收购标题</text>
-          <text class="required">*</text>
-        </view>
-        <view class="item-content">
-          <input
-            class="form-input"
-            v-model="formData.title"
-            placeholder="请输入收购标题"
-            placeholder-style="color: #999;"
-            maxlength="30"
-            @input="onTitleInput"
-          />
-          <view class="char-count">{{ titleLength }}/30</view>
-        </view>
-      </view>
-
-      <!-- 收购品类 -->
-      <view class="form-item">
-        <view class="item-label">
-          <text class="label-text">收购品类</text>
-          <text class="required">*</text>
-        </view>
-        <view class="item-content">
-          <view class="category-selector" @click="showCategoryPicker = true">
-            <text class="selector-text" :class="{ placeholder: !formData.category }">
-              {{ formData.category || '请选择收购品类' }}
-            </text>
-            <text class="arrow-icon">></text>
-          </view>
-        </view>
-      </view>
-
-      <!-- 收购数量 -->
-      <view class="form-item">
-        <view class="item-label">
-          <text class="label-text">收购数量</text>
-          <text class="required">*</text>
-        </view>
-        <view class="item-content">
-          <view class="input-with-unit">
-            <input
-              class="form-input"
-              v-model="formData.quantity"
-              placeholder="请输入收购数量"
-              placeholder-style="color: #999;"
-              type="number"
-            />
-            <text class="unit-text">斤</text>
-          </view>
-        </view>
-      </view>
-
-      <!-- 单价预算 -->
-      <view class="form-item">
-        <view class="item-label">
-          <text class="label-text">单价预算</text>
-          <text class="required">*</text>
-        </view>
-        <view class="item-content">
-          <view class="input-with-unit">
-            <text class="currency-symbol">¥</text>
-            <input
-              class="form-input"
-              v-model="formData.budgetPrice"
-              placeholder="请输入单价预算"
-              placeholder-style="color: #999;"
-              type="digit"
-            />
-            <text class="unit-text">元/斤</text>
-          </view>
-        </view>
-      </view>
-
-      <!-- 补充说明 -->
-      <view class="form-item">
-        <view class="item-label">
-          <text class="label-text">补充说明</text>
-        </view>
-        <view class="item-content">
-          <textarea
-            class="form-textarea"
-            v-model="formData.description"
-            placeholder="请描述交货要求、时间等补充信息"
-            placeholder-style="color: #999;"
-            maxlength="200"
-            auto-height
-            @input="onDescInput"
-          />
-          <view class="char-count">{{ descLength }}/200</view>
-        </view>
-      </view>
-
-      <!-- 上传参考图片 -->
-      <view class="form-item">
-        <view class="item-label">
-          <text class="label-text">参考图片</text>
-          <text class="optional">(最多2张)</text>
-        </view>
-        <view class="item-content">
-          <view class="image-upload-area">
-            <view 
-              class="image-item"
-              v-for="(image, index) in formData.images"
-              :key="index"
-            >
-              <image class="uploaded-image" :src="image" mode="aspectFill"></image>
-              <view class="image-delete" @click="removeImage(index)">×</view>
-            </view>
-            <view 
-              class="image-upload-btn"
-              v-if="formData.images.length < 2"
-              @click="chooseImage"
-            >
-              <text class="upload-icon">+</text>
-              <text class="upload-text">上传图片</text>
-            </view>
-          </view>
-        </view>
-      </view>
-
-      <!-- 联系人信息 -->
-      <view class="form-item">
-        <view class="item-label">
-          <text class="label-text">联系人信息</text>
-        </view>
-        <view class="item-content">
-          <view class="contact-input-row">
-            <text class="contact-label">联系人:</text>
-            <input
-              class="contact-input"
-              v-model="formData.contactName"
-              placeholder="请输入联系人姓名"
-              placeholder-style="color: #999;"
-              maxlength="10"
-            />
-          </view>
-          <view class="contact-input-row">
-            <text class="contact-label">电话:</text>
-            <input
-              class="contact-input"
-              v-model="formData.contactPhone"
-              placeholder="请输入联系电话"
-              placeholder-style="color: #999;"
-              type="number"
-              maxlength="11"
-            />
-          </view>
-        </view>
-      </view>
-    </view>
-
-    <!-- 底部提交按钮 -->
-    <view class="bottom-action-bar">
-      <view class="submit-btn" @click="submitForm">
-        <text class="btn-text">{{ isEditMode ? '保存修改' : '提交发布' }}</text>
-      </view>
-    </view>
-
-    <!-- 品类选择弹窗 -->
-    <view class="picker-modal" v-if="showCategoryPicker" @click="showCategoryPicker = false">
-      <view class="picker-content" @click.stop>
-        <view class="picker-header">
-          <text class="picker-title">选择收购品类</text>
-          <text class="picker-close" @click="showCategoryPicker = false">×</text>
-        </view>
-        <view class="picker-options">
-          <view 
-            class="picker-option"
-            v-for="category in categoryOptions"
-            :key="category"
-            @click="selectCategory(category)"
-          >
-            <text class="option-text">{{ category }}</text>
-          </view>
-        </view>
-      </view>
-    </view>
-  </view>
+	<view class="purchase-publish-container">
+		<!-- 表单内容 -->
+		<view class="form-container">
+			<!-- 收购标题 -->
+			<view class="form-item">
+				<view class="item-label">
+					<text class="label-text">收购标题</text>
+					<text class="required">*</text>
+				</view>
+				<view class="item-content">
+					<input class="form-input" v-model="formData.title" placeholder="请输入收购标题"
+						placeholder-style="color: #999;" maxlength="30" @input="onTitleInput" />
+					<view class="char-count">{{ titleLength }}/30</view>
+				</view>
+			</view>
+
+			<!-- 收购品类 -->
+			<view class="form-item">
+				<view class="item-label">
+					<text class="label-text">收购品类</text>
+					<text class="required">*</text>
+				</view>
+				<view class="item-content">
+					<view class="category-selector" @click="showCategoryPicker = true">
+						<text class="selector-text" :class="{ placeholder: !formData.categoryId }">
+							<!-- {{ formData.categoryLabel || '请选择产品分类' }} -->
+							{{getDictLabel('agricultural_category',formData.categoryId) || '请选择产品分类' }}
+						</text>
+						<text class="arrow-icon">></text>
+					</view>
+				</view>
+			</view>
+
+			<view class="form-item">
+				<!-- 所在地 -->
+				<view class="item-label">
+					<text class="label-text">所在地</text>
+					<text class="required">*</text>
+				</view>
+				<LocationPicker v-model="formData.location"  mode="edit" />
+			</view>
+
+			<!-- 收购数量 -->
+			<view class="form-item">
+				<view class="item-label">
+					<text class="label-text">收购数量</text>
+					<text class="required">*</text>
+				</view>
+				<view class="item-content">
+					<view class="input-with-unit">
+						<input class="form-input" v-model="formData.quantity" placeholder="请输入收购数量"
+							placeholder-style="color: #999;" type="number" />
+						<!-- <text class="unit-text">斤</text> -->
+					</view>
+				</view>
+			</view>
+			
+			<!-- 单位-->
+			<view class="form-item">
+				<view class="item-label">
+					<text class="label-text">单位</text>
+					<text class="required">*</text>
+				</view>
+				<view class="item-content">
+					<view class="category-selector" @click="showUnitPicker = true">
+						<text class="selector-text" :class="{ placeholder: !formData.unit }">
+							<!-- {{ formData.dictLabel || '请选择价格单位' }} -->
+							{{ getDictLabel('agricultural_unit',formData.unit) || '请选择价格单位'}}
+						</text>
+						<text class="arrow-icon">></text>
+					</view>
+				</view>
+			</view>
+
+			<!-- 单价预算 -->
+			<view class="form-item">
+				<view class="item-label">
+					<text class="label-text">单价预算</text>
+					<text class="required">*</text>
+				</view>
+				<view class="item-content">
+					<view class="input-with-unit">
+						<text class="currency-symbol">¥</text>
+						<input class="form-input" v-model="formData.price" placeholder="请输入单价预算"
+							placeholder-style="color: #999;" type="digit" />
+						<!-- <text class="unit-text">元/斤</text> -->
+					</view>
+				</view>
+			</view>
+
+			
+			<!-- 补充说明 -->
+			<view class="form-item">
+				<view class="item-label">
+					<text class="label-text">补充说明</text>
+				</view>
+				<view class="item-content">
+					<textarea class="form-textarea" v-model="formData.description" placeholder="请描述交货要求、时间等补充信息"
+						placeholder-style="color: #999;" maxlength="200" auto-height @input="onDescInput" />
+					<view class="char-count">{{ descLength }}/200</view>
+				</view>
+			</view>
+
+			<!-- 上传参考图片 -->
+			<view class="form-item">
+				<view class="item-label">
+					<text class="label-text">参考图片</text>
+					<text class="optional">(最多6张)</text>
+				</view>
+				<view class="item-content">
+					<view class="image-upload-area">
+						<view class="image-item" v-for="(image, index) in formData.images" :key="index">
+							<image class="uploaded-image" :src="image.url" mode="aspectFill"></image>
+							<view class="image-delete" @click="removeImage(index)">×</view>
+						</view>
+						<view class="image-upload-btn" v-if="formData.images.length < 6" @click="chooseImage">
+							<text class="upload-icon">+</text>
+							<text class="upload-text">上传图片</text>
+						</view>
+					</view>
+				</view>
+			</view>
+
+			<!-- 联系人信息 -->
+			<view class="form-item">
+				<view class="item-label">
+					<text class="label-text">联系人信息</text>
+				</view>
+				<view class="item-content">
+					<view class="contact-input-row">
+						<text class="contact-label">联系人:</text>
+						<input class="contact-input" v-model="formData.contactName" placeholder="请输入联系人姓名"
+							placeholder-style="color: #999;" maxlength="10" />
+					</view>
+					<view class="contact-input-row">
+						<text class="contact-label">电话:</text>
+						<input class="contact-input" v-model="formData.contactPhone" placeholder="请输入联系电话"
+							placeholder-style="color: #999;" type="number" maxlength="11" />
+					</view>
+				</view>
+			</view>
+		</view>
+
+		<!-- 底部提交按钮 -->
+		<view class="bottom-action-bar">
+			<view class="submit-btn" @click="submitForm">
+				<text class="btn-text">{{ isEditMode ? '保存修改' : '提交发布' }}</text>
+			</view>
+		</view>
+
+		<!-- 品类选择弹窗 -->
+		<view class="picker-modal" v-if="showCategoryPicker" @click="showCategoryPicker = false">
+			<view class="picker-content" @click.stop>
+				<view class="picker-header">
+					<text class="picker-title">选择收购品类</text>
+					<text class="picker-close" @click="showCategoryPicker = false">×</text>
+				</view>
+				<view class="picker-options">
+					<view class="picker-option" 
+					v-for="category in dictDataOptions.agricultural_category" 
+					:key="category.dictCode"
+					@click="selectCategory(category)"
+					:class="{ 'active': category.dictValue == formData.categoryId }">
+						<text class="option-text">{{ category.dictLabel }}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+		<!-- 单位选择弹窗 -->
+		<view class="picker-modal" v-if="showUnitPicker" @click="showUnitPicker = false">
+			<view class="picker-content" @click.stop>
+				<view class="picker-header">
+					<text class="picker-title">选择价格单位</text>
+					<text class="picker-close" @click="showUnitPicker = false">×</text>
+				</view>
+				<view class="picker-options">
+					<view class="picker-option"
+					v-for="unit in dictDataOptions.agricultural_unit"
+					:key="unit.dictCode"
+					@click="selectUnit(unit)"
+					:class="{ 'active': unit.dictValue == formData.unit }">
+						<text class="option-text">{{ unit.dictLabel }}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
 </template>
 
 <script>
-export default {
-  data() {
-    return {
-      isEditMode: false, // 是否为编辑模式
-      editItemId: '', // 编辑的收购信息ID
-      formData: {
-        title: '',
-        category: '',
-        quantity: '',
-        budgetPrice: '',
-        description: '',
-        images: [],
-        contactName: '',
-        contactPhone: ''
-      },
-      categoryOptions: ['蔬菜', '水果', '粮食', '其他'],
-      showCategoryPicker: false,
-      titleLength: 0,
-      descLength: 0
-    }
-  },
-  
-  onLoad(options) {
-    // 检查是否为编辑模式
-    if (options.action === 'edit' && options.id) {
-      this.isEditMode = true;
-      this.editItemId = options.id;
-      
-      // 设置页面标题
-      uni.setNavigationBarTitle({
-        title: '编辑收购信息'
-      });
-      
-      // 加载收购信息数据
-      this.loadPurchaseData();
-    } else {
-      // 新建模式
-      this.isEditMode = false;
-      uni.setNavigationBarTitle({
-        title: '发布收购信息'
-      });
-    }
-  },
-  
-  methods: {
-    // 加载收购信息数据(编辑模式)
-    loadPurchaseData() {
-      // 模拟从API加载数据,实际应用中需要根据ID调用相应的API
-      uni.showLoading({ title: '加载中...' });
-      
-      setTimeout(() => {
-        // 收购信息的模拟数据
-        const mockData = {
-          title: '高价收购优质土豆',
-          category: '蔬菜',
-          quantity: '1000',
-          budgetPrice: '3.5',
-          description: '大量收购优质土豆,要求新鲜无病害,无青皮,规格统一。支持长期合作,价格优惠,现金结算。有货源的农户欢迎联系,我们提供上门收购服务。',
-          images: [
-            '/static/images/products/agriculture-tools.jpg'
-          ],
-          contactName: '李先生',
-          contactPhone: '13856785678'
-        };
-        
-        // 填充表单数据
-        this.formData = { ...mockData };
-        
-        // 更新字符计数
-        this.titleLength = this.formData.title.length;
-        this.descLength = this.formData.description.length;
-        
-        uni.hideLoading();
-      }, 1000);
-    },
-    
-    // 标题输入处理
-    onTitleInput(e) {
-      this.titleLength = e.detail.value.length;
-    },
-    
-    // 描述输入处理
-    onDescInput(e) {
-      this.descLength = e.detail.value.length;
-    },
-    
-    // 选择品类
-    selectCategory(category) {
-      this.formData.category = category;
-      this.showCategoryPicker = false;
-    },
-    
-    // 选择图片
-    chooseImage() {
-      const remainingCount = 2 - this.formData.images.length;
-      uni.chooseImage({
-        count: remainingCount,
-        sizeType: ['compressed'],
-        sourceType: ['album', 'camera'],
-        success: (res) => {
-          this.formData.images = this.formData.images.concat(res.tempFilePaths);
-        },
-        fail: (err) => {
-          console.error('选择图片失败:', err);
-        }
-      });
-    },
-    
-    // 删除图片
-    removeImage(index) {
-      this.formData.images.splice(index, 1);
-    },
-    
-    // 表单验证
-    validateForm() {
-      if (!this.formData.title.trim()) {
-        uni.showToast({
-          title: '请输入收购标题',
-          icon: 'none'
-        });
-        return false;
-      }
-      
-      if (!this.formData.category) {
-        uni.showToast({
-          title: '请选择收购品类',
-          icon: 'none'
-        });
-        return false;
-      }
-      
-      if (!this.formData.quantity || this.formData.quantity <= 0) {
-        uni.showToast({
-          title: '请输入有效的收购数量',
-          icon: 'none'
-        });
-        return false;
-      }
-      
-      if (!this.formData.budgetPrice || this.formData.budgetPrice <= 0) {
-        uni.showToast({
-          title: '请输入有效的单价预算',
-          icon: 'none'
-        });
-        return false;
-      }
-      
-      if (!this.formData.contactName.trim()) {
-        uni.showToast({
-          title: '请输入联系人姓名',
-          icon: 'none'
-        });
-        return false;
-      }
-      
-      if (!this.formData.contactPhone.trim()) {
-        uni.showToast({
-          title: '请输入联系电话',
-          icon: 'none'
-        });
-        return false;
-      }
-      
-      // 简单的手机号格式验证
-      const phoneRegex = /^1[3-9]\d{9}$/;
-      if (!phoneRegex.test(this.formData.contactPhone)) {
-        uni.showToast({
-          title: '请输入正确的手机号码',
-          icon: 'none'
-        });
-        return false;
-      }
-      
-      return true;
-    },
-    
-    // 提交表单
-    submitForm() {
-      if (!this.validateForm()) {
-        return;
-      }
-      
-      // 显示加载提示
-      const loadingTitle = this.isEditMode ? '保存中...' : '提交中...';
-      uni.showLoading({
-        title: loadingTitle
-      });
-      
-      // 模拟提交
-      setTimeout(() => {
-        uni.hideLoading();
-        
-        if (this.isEditMode) {
-          // 编辑模式 - 保存修改
-          uni.showModal({
-            title: '保存成功',
-            content: '您的修改已保存成功!',
-            showCancel: false,
-            success: () => {
-              uni.navigateBack();
-            }
-          });
-        } else {
-          // 新建模式 - 提交发布
-          uni.showToast({
-            title: '发布成功',
-            icon: 'success'
-          });
-          
-          // 返回上一页
-          setTimeout(() => {
-            uni.navigateBack();
-          }, 1500);
-        }
-      }, 2000);
-    }
-  }
-}
+	import LocationPicker from "@/components/common/LocationPicker.vue"
+	import api from "@/config/api.js";
+	import storage from "@/utils/storage.js";
+	import {
+		addProductInfo,
+		getProductInfoById,
+		editProductInfo
+	} from '@/api/services/productInfo.js';
+	import dictMixin from '@/utils/mixins/dictMixin';
+	import {
+		getFormattedTime
+	} from '@/utils/dateUtils'
+	export default {
+		mixins: [dictMixin],
+		components: {
+			LocationPicker
+		},
+		data() {
+			return {
+				dictTypeList: ['agricultural_unit', 'agricultural_category'],
+				isEditMode: false, // 是否为编辑模式
+				editItemId: '', // 编辑的收购信息ID
+				formData: {
+					title: '',
+					categoryId: '',
+					price: '',
+					description: '',
+					imageUrl: '',
+					images: [],
+					unit: '',
+					quantity: '',
+					contactName: '',
+					contactPhone: '',
+					location: '',
+					publishTime: getFormattedTime(), // 发布时间
+					userId: storage.getUserInfo().userid,
+					type: 1, // 收购
+					status: 1, // 审核中
+				},
+				// categoryOptions: ['蔬菜', '水果', '粮食', '其他'],
+				dictDataOptions: [],
+				showCategoryPicker: false,
+				showUnitPicker: false,
+				titleLength: 0,
+				descLength: 0
+			}
+		},
+
+		onLoad(options) {
+			this.dictDataOptions = this.dictData
+			// 检查是否为编辑模式
+			if (options.action === 'edit' && options.id) {
+				this.isEditMode = true;
+				this.editItemId = options.id;
+
+				// 设置页面标题
+				uni.setNavigationBarTitle({
+					title: '编辑收购信息'
+				});
+
+				// 加载收购信息数据
+				this.loadPurchaseData();
+			} else {
+				// 新建模式
+				this.isEditMode = false;
+				uni.setNavigationBarTitle({
+					title: '发布收购信息'
+				});
+			}
+		},
+
+		methods: {
+			// 加载收购信息数据(编辑模式)
+			loadPurchaseData() {
+				// 模拟从API加载数据,实际应用中需要根据ID调用相应的API
+				uni.showLoading({
+					title: '加载中...'
+				});
+
+				getProductInfoById(this.editItemId).then(res=>{
+					if (res.data.code === 200) {
+						  // const { data } = res.data;
+						 // this.formData = data
+						 Object.assign(this.formData, res.data.data)  // 保持响应式
+						 // 更新字符计数
+						 this.nameLength = this.formData.title.length;
+						 this.descLength = this.formData.description.length;
+						 
+						  console.log("this.goodsDetail", this.formData);
+						  // 处理图片数据
+						  if (this.formData.imageUrl) {
+						    try {
+							   this.formData.images = this.formData.imageUrl.split(',').map(url => ({ url,status: 'success' }))
+						      console.log('解析后的图片数据:', this.formData.images);
+						    } catch (e) {
+						      console.error('解析图片数据失败:', e);
+						      this.formData.images = [];
+								}
+						  } else {
+						    this.formData.images = [];
+						  }
+						  uni.hideLoading();
+						} else {
+						  uni.showToast({
+						    title: res.data.msg || '获取农品信息失败',
+						    icon: 'none'
+						  });
+						}
+				})
+			},
+
+			// 标题输入处理
+			onTitleInput(e) {
+				this.titleLength = e.detail.value.length;
+			},
+
+			// 描述输入处理
+			onDescInput(e) {
+				this.descLength = e.detail.value.length;
+			},
+
+			// 选择分类
+			selectCategory(category) {
+				this.formData.categoryId = category.dictValue;
+				this.formData.categoryLabel = category.dictLabel;
+				this.showCategoryPicker = false;
+			},
+			// 选择单位
+			selectUnit(unit) {
+				this.formData.unit = unit.dictValue;
+				this.formData.dictLabel = unit.dictLabel;
+				this.showUnitPicker = false;
+			},
+			getDictLabel(dictKey, value) {
+			   if (!this.dictData || !this.dictData[dictKey]) {
+			     return ''
+			   }
+			   const list = this.dictData[dictKey] || []
+			   const item = list.find(u => u.dictValue == value)
+			   return item ? item.dictLabel : ''
+			 },
+
+			// 选择图片
+			chooseImage() {
+			  uni.chooseImage({
+			    count: 6 - this.formData.images.length,
+			    sizeType: ['original', 'compressed'],
+			    sourceType: ['album', 'camera'],
+			    success: (res) => {
+			      console.log('选择图片成功:', res);
+			      
+			      // 验证文件类型和大小
+			      const validFiles = [];
+			      const invalidFiles = [];
+			      const maxSize = 5 * 1024 * 1024; // 5MB 最大限制
+			      
+			      // 检查每个文件
+			      res.tempFiles.forEach((file, index) => {
+			        // 检查文件类型
+			        const isImage = /\.(jpg|jpeg|png|gif)$/i.test(file.name);
+			        // 检查文件大小
+			        const isValidSize = file.size <= maxSize;
+			        
+			        if (isImage && isValidSize) {
+			          validFiles.push(res.tempFilePaths[index]);
+			        } else {
+			          invalidFiles.push({
+			            path: file.path,
+			            size: file.size,
+			            reason: !isImage ? '文件格式不支持' : '文件大于5MB'
+			          });
+			        }
+			      });
+			      
+			      // 显示无效文件提示
+			      if (invalidFiles.length > 0) {
+			        uni.showToast({
+			          title: `${invalidFiles.length}个文件无效,请检查格式和大小`,
+			          icon: 'none',
+			          duration: 2000
+			        });
+			      }
+			      
+			      // 如果有有效文件,则上传
+			      if (validFiles.length > 0) {
+			        this.uploadImages(validFiles);
+			      }
+			    },
+			    fail: (err) => {
+			      console.error('选择图片失败:', err);
+			      uni.showToast({
+			        title: '选择图片失败',
+			        icon: 'none'
+			      });
+			    }
+			  });
+			},
+			
+			// 上传图片到服务器
+			uploadImages(tempFilePaths) {
+			  uni.showLoading({
+			    title: '上传中...',
+			    mask: true
+			  });
+			  
+			  // 上传成功的图片计数
+			  let successCount = 0;
+			  let failCount = 0;
+			  const totalFiles = tempFilePaths.length;
+			  const newImages = [];
+			  
+			  // 遍历处理每张图片
+			  tempFilePaths.forEach((path, index) => {
+			    // 调用上传API
+			    uni.uploadFile({
+			      // url: api.serve + '/base/tasks/uploadTaskImage', 
+			      url: api.serve + '/file/upload', 
+			      filePath: path,
+			      name: 'file', // 文件参数名称,需要与后端接口匹配
+			      formData: {
+			        type: 'task', // 标识文件类型,用于后端区分不同业务的文件
+			        // directory: '/opt/app/nongxiaoyu/uploadImage' // 指定保存目录
+			      },
+				  header: {
+				  	'Authorization': `Bearer ${storage.getAccessToken()}`
+				  },
+			      success: (res) => {
+			                      try {
+			            const response = JSON.parse(res.data);
+						uni.showToast({
+						  title: `返回: ${response.data}`,
+						  icon: 'none',
+						});
+			            if (response.code === 200) {
+			              // 获取返回的URL
+			              const imageUrl = response.data.url;
+			              console.log('上传成功,返回的图片URL:', imageUrl);
+			              
+			              // 上传成功,将图片信息添加到数组
+			              newImages.push({
+			                url: imageUrl, // 保存原始URL,在显示时会通过getImageUrl方法处理
+			                path: path, // 保存本地路径用于预览
+			                status: 'success',
+			                fileName: response.data.fileName || '' // 保存文件名,如果后端返回的话
+			              });
+			              successCount++;
+			          } else {
+			            failCount++;
+			            console.error('上传失败:', response.msg);
+			          }
+			        } catch (e) {
+			          failCount++;
+					  uni.showToast({
+					    title: `解析响应失败: ${e}`,
+					    icon: 'none',
+					  });
+			          console.error('解析响应失败:', e);
+			        }
+			      },
+			      fail: (err) => {
+			        failCount++;
+			        console.error('上传请求失败:', err);
+					uni.showToast({
+					  title: `上传请求失败: ${err}`,
+					  icon: 'none',
+					});
+			      },
+			      complete: () => {
+			        // 当所有文件都已处理完成
+			        if (successCount + failCount === totalFiles) {
+			          if (newImages.length > 0) {
+			            // 将新上传的图片添加到已有图片列表
+			            this.formData.images = [...this.formData.images, ...newImages];
+			            // 更新taskImages字段,将图片URL用逗号连接
+			           this.formData.imageUrl = this.formData.images.map(item => item.url).join(',');
+					   this.$set(this.formData, 'images', this.formData.images)  // 确保视图刷新
+			            // 显示成功提示
+			            uni.hideLoading();
+			            uni.showToast({
+			              title: `成功上传${successCount}张图片`,
+			              icon: 'success'
+			            });
+			          } else {
+			            // 全部失败
+						uni.showToast({
+						  title: `图片上传: ${newImages}`,
+						  icon: 'none',
+						});
+			            uni.hideLoading();
+			            // uni.showToast({
+			            //   title: '图片上传失败',
+			            //   icon: 'none',
+			            // });
+			          }
+			        }
+			      }
+			    });
+			  });
+			},
+			
+			removeImage(index) {
+			  uni.showModal({
+			    title: '确认删除',
+			    content: '确定要删除这张图片吗?',
+			    success: (res) => {
+			      if (res.confirm) {
+			        this.formData.images.splice(index, 1);
+			        
+			        // 更新taskImages字段
+			       this.formData.imageUrl = this.formData.images.map(item => item.url).join(',');
+				   this.$set(this.formData, 'images', this.formData.images)  // 确保视图刷新
+			        uni.showToast({
+			          title: '已删除',
+			          icon: 'none'
+			        });
+			      }
+			    }
+			  });
+			},
+
+			// 表单验证
+			validateForm() {
+				if (!this.formData.title.trim()) {
+					uni.showToast({
+						title: '请输入收购标题',
+						icon: 'none'
+					});
+					return false;
+				}
+
+				if (!this.formData.categoryId) {
+					uni.showToast({
+						title: '请选择收购品类',
+						icon: 'none'
+					});
+					return false;
+				}
+
+				if (!this.formData.quantity || this.formData.quantity <= 0) {
+					uni.showToast({
+						title: '请输入有效的收购数量',
+						icon: 'none'
+					});
+					return false;
+				}
+
+				if (!this.formData.price || this.formData.price <= 0) {
+					uni.showToast({
+						title: '请输入有效的单价预算',
+						icon: 'none'
+					});
+					return false;
+				}
+
+				if (!this.formData.contactName.trim()) {
+					uni.showToast({
+						title: '请输入联系人姓名',
+						icon: 'none'
+					});
+					return false;
+				}
+
+				if (!this.formData.contactPhone.trim()) {
+					uni.showToast({
+						title: '请输入联系电话',
+						icon: 'none'
+					});
+					return false;
+				}
+
+				// 简单的手机号格式验证
+				const phoneRegex = /^1[3-9]\d{9}$/;
+				if (!phoneRegex.test(this.formData.contactPhone)) {
+					uni.showToast({
+						title: '请输入正确的手机号码',
+						icon: 'none'
+					});
+					return false;
+				}
+
+				return true;
+			},
+
+			// 提交表单
+			submitForm() {
+				if (!this.validateForm()) {
+					return;
+				}
+
+				// 显示加载提示
+				const loadingTitle = this.isEditMode ? '保存中...' : '提交中...';
+				uni.showLoading({
+					title: loadingTitle
+				});
+
+				uni.hideLoading();
+				
+				if (this.isEditMode) {
+					editProductInfo(this.formData).then(res=>{
+						if(res.data.code === 200){
+							uni.showModal({
+								title: '保存成功',
+								content: '您的修改已保存成功!',
+								showCancel: false,
+								success: () => {
+									uni.navigateBack();
+								}
+							});
+						}else{
+							uni.showToast({
+								title: res.data.msg || '提交失败,请稍后重试',
+								icon: 'none'
+							});
+						}
+					}).catch(err => {
+						console.error("提交异常:", err);
+						uni.showToast({
+							title: '网络错误,请检查后重试',
+							icon: 'none'
+						});
+					});
+					
+				} else {
+					// 新建模式 - 提交审核
+					console.log("this.formData",this.formData);
+					// const data = {
+					// 	userId: this.currentUserInfo.userid,
+					// 	type: 0 ,// 出售
+					// 	status: 1 ,// 审核中
+					// }
+					// this.formData = {...this.formData , ...data}
+					addProductInfo(this.formData).then(res=>{
+						console.log("新增出售农产品",res);
+						if(res.data.code === 200){
+							uni.showModal({
+								title: '提交成功',
+								content: '您的收购农产品信息已提交审核,审核通过后将在收购页面展示。',
+								showCancel: false,
+								success: () => {
+									uni.navigateBack();
+								}
+							});
+						}else{
+							uni.showToast({
+								title: res.data.msg || '提交失败,请稍后重试',
+								icon: 'none'
+							});
+						}
+					}).catch(err => {
+						console.error("提交异常:", err);
+						uni.showToast({
+							title: '网络错误,请检查后重试',
+							icon: 'none'
+						});
+					});
+					
+				}
+			}
+		}
+	}
 </script>
 
 <style lang="scss">
-.purchase-publish-container {
-  min-height: 100vh;
-  background-color: #f5f5f5;
-  padding-bottom: calc(128rpx + env(safe-area-inset-bottom));
-}
-
-.form-container {
-  padding: 20rpx;
-}
-
-.form-item {
-  background-color: #fff;
-  border-radius: 12rpx;
-  padding: 24rpx;
-  margin-bottom: 16rpx;
-  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
-}
-
-.item-label {
-  display: flex;
-  align-items: center;
-  margin-bottom: 16rpx;
-}
-
-.label-text {
-  font-size: 28rpx;
-  color: #333;
-  font-weight: bold;
-}
-
-.required {
-  color: #ff4d4f;
-  font-size: 28rpx;
-  margin-left: 4rpx;
-}
-
-.optional {
-  color: #999;
-  font-size: 24rpx;
-  margin-left: 8rpx;
-}
-
-.item-content {
-  position: relative;
-}
-
-.form-input {
-  width: 100%;
-  height: 44rpx;
-  font-size: 28rpx;
-  color: #333;
-  line-height: 44rpx;
-}
-
-.form-textarea {
-  width: 100%;
-  min-height: 120rpx;
-  font-size: 28rpx;
-  color: #333;
-  line-height: 1.5;
-}
-
-.char-count {
-  position: absolute;
-  right: 0;
-  bottom: -4rpx;
-  font-size: 24rpx;
-  color: #999;
-}
-
-// 品类选择器
-.category-selector {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  padding: 12rpx 0;
-  border-bottom: 1rpx solid #f0f0f0;
-}
-
-.selector-text {
-  font-size: 28rpx;
-  color: #333;
-  
-  &.placeholder {
-    color: #999;
-  }
-}
-
-.arrow-icon {
-  font-size: 24rpx;
-  color: #999;
-}
-
-// 带单位的输入框
-.input-with-unit {
-  display: flex;
-  align-items: center;
-  border-bottom: 1rpx solid #f0f0f0;
-  padding: 12rpx 0;
-}
-
-.currency-symbol {
-  font-size: 28rpx;
-  color: #333;
-  margin-right: 8rpx;
-}
-
-.unit-text {
-  font-size: 28rpx;
-  color: #666;
-  margin-left: 8rpx;
-  white-space: nowrap;
-}
-
-// 图片上传
-.image-upload-area {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 16rpx;
-}
-
-.image-item {
-  position: relative;
-  width: 160rpx;
-  height: 160rpx;
-  border-radius: 8rpx;
-  overflow: hidden;
-}
-
-.uploaded-image {
-  width: 100%;
-  height: 100%;
-}
-
-.image-delete {
-  position: absolute;
-  top: -8rpx;
-  right: -8rpx;
-  width: 32rpx;
-  height: 32rpx;
-  background-color: #ff4d4f;
-  color: #fff;
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  font-size: 20rpx;
-  font-weight: bold;
-}
-
-.image-upload-btn {
-  width: 160rpx;
-  height: 160rpx;
-  border: 2rpx dashed #ddd;
-  border-radius: 8rpx;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  background-color: #fafafa;
-}
-
-.upload-icon {
-  font-size: 48rpx;
-  color: #999;
-  margin-bottom: 8rpx;
-}
-
-.upload-text {
-  font-size: 24rpx;
-  color: #999;
-}
-
-// 联系人信息
-.contact-input-row {
-  display: flex;
-  align-items: center;
-  margin-bottom: 20rpx;
-  
-  &:last-child {
-    margin-bottom: 0;
-  }
-}
-
-.contact-label {
-  font-size: 28rpx;
-  color: #333;
-  width: 120rpx;
-  font-weight: bold;
-}
-
-.contact-input {
-  flex: 1;
-  height: 44rpx;
-  font-size: 28rpx;
-  color: #333;
-  line-height: 44rpx;
-  border-bottom: 1rpx solid #f0f0f0;
-  padding: 12rpx 0;
-}
-
-// 底部提交按钮
-.bottom-action-bar {
-  position: fixed;
-  bottom: 0;
-  left: 0;
-  right: 0;
-  width: 100%;
-  background-color: #fff;
-  border-top: 1rpx solid #f0f0f0;
-  padding: 20rpx 30rpx;
-  padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
-  z-index: 1000;
-  box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.05);
-  box-sizing: border-box;
-}
-
-.submit-btn {
-  width: 100%;
-  height: 88rpx;
-  background-color: #4CAF50;
-  border-radius: 12rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  transition: background-color 0.2s ease;
-  box-sizing: border-box;
-  
-  &:active {
-    background-color: #45a049;
-  }
-  
-  .btn-text {
-    font-size: 32rpx;
-    color: #fff;
-    font-weight: bold;
-  }
-}
-
-// 品类选择弹窗
-.picker-modal {
-  position: fixed;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  background-color: rgba(0, 0, 0, 0.5);
-  z-index: 2000;
-  display: flex;
-  align-items: flex-end;
-}
-
-.picker-content {
-  width: 100%;
-  background-color: #fff;
-  border-radius: 24rpx 24rpx 0 0;
-  padding: 0;
-}
-
-.picker-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 30rpx;
-  border-bottom: 1rpx solid #f0f0f0;
-}
-
-.picker-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #333;
-}
-
-.picker-close {
-  font-size: 36rpx;
-  color: #999;
-}
-
-.picker-options {
-  padding: 20rpx 0;
-}
-
-.picker-option {
-  padding: 24rpx 30rpx;
-  border-bottom: 1rpx solid #f8f8f8;
-  transition: background-color 0.2s ease;
-  
-  &:last-child {
-    border-bottom: none;
-  }
-  
-  &:active {
-    background-color: #f5f5f5;
-  }
-}
-
-.option-text {
-  font-size: 30rpx;
-  color: #333;
-}
-</style> 
+	.purchase-publish-container {
+		min-height: 100vh;
+		background-color: #f5f5f5;
+		padding-bottom: calc(128rpx + env(safe-area-inset-bottom));
+	}
+
+	.form-container {
+		padding: 20rpx;
+	}
+
+	.form-item {
+		background-color: #fff;
+		border-radius: 12rpx;
+		padding: 24rpx;
+		margin-bottom: 16rpx;
+		box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+	}
+
+	.item-label {
+		display: flex;
+		align-items: center;
+		margin-bottom: 16rpx;
+	}
+
+	.label-text {
+		font-size: 28rpx;
+		color: #333;
+		font-weight: bold;
+	}
+
+	.required {
+		color: #ff4d4f;
+		font-size: 28rpx;
+		margin-left: 4rpx;
+	}
+
+	.optional {
+		color: #999;
+		font-size: 24rpx;
+		margin-left: 8rpx;
+	}
+
+	.item-content {
+		position: relative;
+	}
+
+	.form-input {
+		width: 100%;
+		height: 44rpx;
+		font-size: 28rpx;
+		color: #333;
+		line-height: 44rpx;
+	}
+
+	.form-textarea {
+		width: 100%;
+		min-height: 120rpx;
+		font-size: 28rpx;
+		color: #333;
+		line-height: 1.5;
+	}
+
+	.char-count {
+		position: absolute;
+		right: 0;
+		bottom: -4rpx;
+		font-size: 24rpx;
+		color: #999;
+	}
+
+	// 品类选择器
+	.category-selector {
+		display: flex;
+		align-items: center;
+		justify-content: space-between;
+		padding: 12rpx 0;
+		border-bottom: 1rpx solid #f0f0f0;
+	}
+
+	.selector-text {
+		font-size: 28rpx;
+		color: #333;
+
+		&.placeholder {
+			color: #999;
+		}
+	}
+
+	.arrow-icon {
+		font-size: 24rpx;
+		color: #999;
+	}
+
+	// 带单位的输入框
+	.input-with-unit {
+		display: flex;
+		align-items: center;
+		border-bottom: 1rpx solid #f0f0f0;
+		padding: 12rpx 0;
+	}
+
+	.currency-symbol {
+		font-size: 28rpx;
+		color: #333;
+		margin-right: 8rpx;
+	}
+
+	.unit-text {
+		font-size: 28rpx;
+		color: #666;
+		margin-left: 8rpx;
+		white-space: nowrap;
+	}
+
+	// 图片上传
+	.image-upload-area {
+		display: flex;
+		flex-wrap: wrap;
+		gap: 16rpx;
+	}
+
+	.image-item {
+		position: relative;
+		width: 160rpx;
+		height: 160rpx;
+		border-radius: 8rpx;
+		overflow: hidden;
+	}
+
+	.uploaded-image {
+		width: 100%;
+		height: 100%;
+	}
+
+	.image-delete {
+		position: absolute;
+		top: -8rpx;
+		right: -8rpx;
+		width: 32rpx;
+		height: 32rpx;
+		background-color: #ff4d4f;
+		color: #fff;
+		border-radius: 50%;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		font-size: 20rpx;
+		font-weight: bold;
+	}
+
+	.image-upload-btn {
+		width: 160rpx;
+		height: 160rpx;
+		border: 2rpx dashed #ddd;
+		border-radius: 8rpx;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+		background-color: #fafafa;
+	}
+
+	.upload-icon {
+		font-size: 48rpx;
+		color: #999;
+		margin-bottom: 8rpx;
+	}
+
+	.upload-text {
+		font-size: 24rpx;
+		color: #999;
+	}
+
+	// 联系人信息
+	.contact-input-row {
+		display: flex;
+		align-items: center;
+		margin-bottom: 20rpx;
+
+		&:last-child {
+			margin-bottom: 0;
+		}
+	}
+
+	.contact-label {
+		font-size: 28rpx;
+		color: #333;
+		width: 120rpx;
+		font-weight: bold;
+	}
+
+	.contact-input {
+		flex: 1;
+		height: 44rpx;
+		font-size: 28rpx;
+		color: #333;
+		line-height: 44rpx;
+		border-bottom: 1rpx solid #f0f0f0;
+		padding: 12rpx 0;
+	}
+
+	// 底部提交按钮
+	.bottom-action-bar {
+		position: fixed;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		width: 100%;
+		background-color: #fff;
+		border-top: 1rpx solid #f0f0f0;
+		padding: 20rpx 30rpx;
+		padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
+		z-index: 1000;
+		box-shadow: 0 -2rpx 8rpx rgba(0, 0, 0, 0.05);
+		box-sizing: border-box;
+	}
+
+	.submit-btn {
+		width: 100%;
+		height: 88rpx;
+		background-color: #4CAF50;
+		border-radius: 12rpx;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		transition: background-color 0.2s ease;
+		box-sizing: border-box;
+
+		&:active {
+			background-color: #45a049;
+		}
+
+		.btn-text {
+			font-size: 32rpx;
+			color: #fff;
+			font-weight: bold;
+		}
+	}
+
+	// 品类选择弹窗
+	.picker-modal {
+		position: fixed;
+		top: 0;
+		left: 0;
+		width: 100%;
+		height: 100%;
+		background-color: rgba(0, 0, 0, 0.5);
+		z-index: 2000;
+		display: flex;
+		align-items: flex-end;
+	}
+
+	.picker-content {
+		width: 100%;
+		background-color: #fff;
+		border-radius: 24rpx 24rpx 0 0;
+		padding: 0;
+	}
+
+	.picker-header {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		padding: 30rpx;
+		border-bottom: 1rpx solid #f0f0f0;
+	}
+
+	.picker-title {
+		font-size: 32rpx;
+		font-weight: bold;
+		color: #333;
+	}
+
+	.picker-close {
+		font-size: 36rpx;
+		color: #999;
+	}
+
+	.picker-options {
+		padding: 20rpx 0;
+	}
+
+	.picker-option {
+		padding: 24rpx 30rpx;
+		border-bottom: 1rpx solid #f8f8f8;
+		transition: background-color 0.2s ease;
+
+		&:last-child {
+			border-bottom: none;
+		}
+
+		&:active {
+			background-color: #f5f5f5;
+		}
+	}
+
+	.option-text {
+		font-size: 30rpx;
+		color: #333;
+	}
+</style>

+ 218 - 100
pages/service/sales-detail.vue

@@ -29,8 +29,8 @@
     <view class="title-section">
       <view class="title-content">
         <text class="product-title">{{ productInfo.title }}</text>
-        <view class="type-tag" :class="productInfo.type">
-          {{ productInfo.type === 'sale' ? '出售' : '收购' }}
+        <view class="type-tag" :class="productInfo.type === 0 ? 'sale' : 'purchase'">
+          {{ productInfo.type === 0 ? '出售' : '收购' }}
         </view>
       </view>
     </view>
@@ -39,11 +39,11 @@
     <view class="info-card">
       <view class="info-row">
         <text class="info-label">分类</text>
-        <text class="info-value">{{ productInfo.category }}</text>
+        <text class="info-value">{{ getDictLabel('agricultural_category',productInfo.categoryId) }}</text>
       </view>
       <view class="info-row">
-        <text class="info-label">{{ productInfo.type === 'sale' ? '单价' : '收购价' }}</text>
-        <text class="info-value price">¥{{ productInfo.price }}/{{ productInfo.unit }}</text>
+        <text class="info-label">{{ productInfo.type === 0 ? '单价' : '收购价' }}</text>
+        <text class="info-value price">¥{{ productInfo.price }}/{{ getDictLabel('agricultural_unit',productInfo.unit) }}</text>
       </view>
       <view class="info-row">
         <text class="info-label">数量</text>
@@ -51,14 +51,19 @@
       </view>
       <view class="info-row">
         <text class="info-label">所在地</text>
-        <text class="info-value location">{{ productInfo.location }}</text>
+        <!-- <text class="info-value location">{{ productInfo.location }}</text> -->
+		<LocationPicker class="info-value location"
+		  v-model="productInfo.location"
+				mode="view"
+		/>
       </view>
+	  
     </view>
 
     <!-- 补充说明区域 -->
     <view class="description-card">
       <view class="card-title">
-        <text>{{ productInfo.type === 'sale' ? '产品详情' : '收购要求' }}</text>
+        <text>{{ productInfo.type === 0 ? '产品详情' : '收购要求' }}</text>
       </view>
       <view class="description-content">
         <text class="description-text">
@@ -75,11 +80,11 @@
       <view class="publisher-info">
         <view class="info-row">
           <text class="info-label">联系人</text>
-          <text class="info-value">{{ productInfo.publisher.name }}</text>
+          <text class="info-value">{{ productInfo.contactName }}</text>
         </view>
         <view class="info-row">
           <text class="info-label">联系电话</text>
-          <text class="info-value phone">{{ productInfo.publisher.phone }}</text>
+          <text class="info-value phone">{{ productInfo.contactPhone }}</text>
         </view>
         <view class="info-row">
           <text class="info-label">发布时间</text>
@@ -87,24 +92,36 @@
         </view>
       </view>
     </view>
+	
+	<!-- 审批意见 -->
+	<view class="description-card" v-if="productInfo.status === 4">
+	  <view class="card-title">
+	    <text>审核结果</text>
+	  </view>
+	  <view class="description-content" style="border-left: 2px solid red">
+	    <text class="description-text" >
+	      {{ productInfo.remark || '无' }}
+	    </text>
+	  </view>
+	</view>
 
     <!-- 底部操作按钮 -->
     <view class="action-buttons" v-if="shouldShowActionButtons">
-      <!-- 当前用户发布的内容 -->
-      <template v-if="isOwnProduct">
-        <!-- 已上架状态:显示编辑和下架 -->
-        <template v-if="productStatus === 'approved'">
-          <button class="action-btn edit-btn" @click="editProduct">
+      <!-- 当前用户发布的内容 
+      <!-- <template v-if="isOwnProduct">
+        <!-- 已上架状态:显示编辑和下架 
+        <template v-if="productStatus === '2'">
+          <!-- <button class="action-btn edit-btn" @click="editProduct">
             编辑
-          </button>
+          </button> 
           <button class="action-btn remove-btn" @click="removeProduct">
             下架
           </button>
         </template>
         
-        <!-- 审核中状态:显示编辑和撤销 -->
-        <template v-else-if="productStatus === 'pending'">
-          <button class="action-btn edit-btn" @click="editProduct">
+        <!-- 审核中状态:显示编辑和撤销 
+        <template v-else-if="productStatus === '1'">
+          <!-- <button class="action-btn edit-btn" @click="editProduct">
             编辑
           </button>
           <button class="action-btn cancel-btn" @click="cancelProduct">
@@ -112,8 +129,37 @@
           </button>
         </template>
         
-        <!-- 已下架状态:不显示任何按钮 -->
-      </template>
+        <!-- 已下架状态:不显示任何按钮 
+		<template v-else-if="productStatus === '3'">
+		  <button class="action-btn edit-btn" @click="editProduct">
+		    编辑
+		  </button>
+		  <button class="action-btn cancel-btn" @click="cancelProduct">
+		    发布
+		  </button>
+		</template>
+		<!-- 未通过 
+		<template v-else-if="productStatus === '4'">
+		  <button class="action-btn edit-btn" @click="editProduct">
+		    编辑
+		  </button>
+		  <button class="action-btn cancel-btn" @click="cancelProduct">
+		    删除
+		  </button>
+		</template>
+      </template> -->
+	  <!-- 当前用户发布的内容 -->
+	  <template v-if="isOwnProduct">
+	    <button
+	      v-for="(btn, index) in actionMap[productStatus] || []"
+	      :key="index"
+	      class="action-btn"
+	      :class="btn.class"
+	      @click="handleAction(btn.action)"
+	    >
+	      {{ btn.label }}
+	    </button>
+	  </template>
       
       <!-- 他人发布的内容 -->
       <template v-else>
@@ -126,35 +172,63 @@
 </template>
 
 <script>
+	import LocationPicker from "@/components/common/LocationPicker.vue"
+	import {
+		getProductInfoById,editProductInfo
+	} from '@/api/services/productInfo.js';
+	import dictMixin from '@/utils/mixins/dictMixin';
 export default {
+	mixins: [dictMixin],
+	components: { LocationPicker },
   data() {
     return {
+	  dictTypeList: ['agricultural_category','agricultural_unit'],
       currentImageIndex: 0,
       source: '', // 页面来源,myPublish表示来自我的发布页面
-      productStatus: '', // 产品状态:pending, approved, rejected
+      productStatus: '', // 产品状态:1-pending, 2-approved, 3-rejected
       productInfo: {
         id: '',
-        type: 'sale', // sale: 出售, purchase: 收购
+        type: '0', // 0-sale: 出售, 1-purchase: 收购
         title: '',
-        category: '',
+        categoryId: '',
         price: '',
         quantity: '',
         unit: '',
         location: '',
         description: '',
-        publisher: {
-          name: '',
-          phone: '',
-          userId: ''
-        },
+        contactName:'',
+		contactPhone:'',
         publishTime: '',
-        images: []
+        images: [],
+		remark: ''
       },
       imageList: [],
-      isOwnProduct: false // 是否为当前用户发布的产品
+      isOwnProduct: false ,// 是否为当前用户发布的产品
+	  // 状态按钮映射
+	      actionMap: {
+	        '1': [ // 审核中
+	          // { label: '编辑', class: 'edit-btn', action: 'editProduct' }, 
+	          // { label: '撤销', class: 'cancel-btn', action: 'cancelProduct' }
+	        ],
+	        '2': [ // 已上架
+	          // { label: '编辑', class: 'edit-btn', action: 'editProduct' }, 
+	          { label: '下架', class: 'remove-btn', action: 'removeProduct' }
+	        ],
+	        '3': [ // 已下架
+	          { label: '编辑', class: 'edit-btn', action: 'editProduct' },
+	          { label: '发布', class: 'cancel-btn', action: 'publishProduct' }
+	        ],
+	        '4': [ // 未通过
+	          { label: '编辑', class: 'edit-btn', action: 'editProduct' },
+	          // { label: '删除', class: 'cancel-btn', action: 'cancelProduct' }
+	        ]
+	      }
     }
   },
-  
+  onShow() {
+  	console.log("onshow",this.productInfo);
+	this.loadProductDetail(this.productInfo.id)
+  },
   onLoad(options) {
     // 获取页面参数
     if (options.id) {
@@ -178,64 +252,56 @@ export default {
   },
   
   methods: {
+	// getUnitLabel(value) {
+	// 	console.log("this.dictData",this.dictData);
+	//   	const unit = this.dictData.agricultural_category.find(u => u.dictValue == value)
+	//   	return unit ? unit.dictLabel : ''
+	// },
+	 handleAction(action) {
+	    this[action] && this[action](); // 动态调用 editProduct/removeProduct 等方法
+	  },
+	 getDictLabel(dictKey, value) {
+	    if (!this.dictData || !this.dictData[dictKey]) {
+	      return ''
+	    }
+	    const list = this.dictData[dictKey] || []
+	    const item = list.find(u => u.dictValue == value)
+	    return item ? item.dictLabel : ''
+	  },
     // 加载产品详情
-    loadProductDetail(id, type) {
+    loadProductDetail(id) {
       // 根据类型模拟不同的数据结构
-      let mockData;
-      
-      if (type === 'purchase') {
-        // 收购信息的数据结构
-        mockData = {
-          id: id,
-          type: 'purchase',
-          title: '高价收购优质土豆',
-          category: '蔬菜',
-          price: '3.5',
-          quantity: '1000',
-          unit: '斤',
-          location: '山东·烟台',
-          description: '大量收购优质土豆,要求新鲜无病害,无青皮,规格统一。支持长期合作,价格优惠,现金结算。有货源的农户欢迎联系,我们提供上门收购服务。',
-          publisher: {
-            name: '李**',
-            phone: '138****5678',
-            userId: 'user456'
-          },
-          publishTime: '2025-01-21 10:30',
-          images: [
-            '/static/images/products/agriculture-tools.jpg',
-            '/static/images/products/organic-fertilizer-new.jpg',
-            '/static/images/products/plastic-film.jpg'
-          ]
-        };
-      } else {
-        // 出售信息的数据结构
-        mockData = {
-          id: id,
-          type: type || 'sale',
-          title: '2024年红富士苹果',
-          category: '水果',
-          price: '8.5',
-          quantity: '1000',
-          unit: '斤',
-          location: '山东·烟台',
-          description: '自家果园种植的红富士苹果,个大味甜,果形端正,色泽鲜艳。采用有机种植方式,无农药残留,口感清脆香甜。现大量上市,欢迎各地客商前来洽谈合作。支持批发零售,量大价优。',
-          publisher: {
-            name: '张**',
-            phone: '188****1234',
-            userId: 'user123'
-          },
-          publishTime: '2025-01-21 14:20',
-          images: [
-            '/static/images/products/corn-seeds-new.jpg',
-            '/static/images/products/rice-seeds.jpg',
-            '/static/images/products/seeds-packets.jpg',
-            '/static/images/products/greenhouse-film.jpg'
-          ]
-        };
-      }
       
-      this.productInfo = mockData;
-      this.imageList = mockData.images;
+      uni.showLoading({
+      	title: '加载中'
+      });
+      getProductInfoById(id).then(res=>{
+      	if (res.data.code === 200) {
+      		  const { data } = res.data;
+      		 this.productInfo = data
+      		  console.log("this.goodsDetail", this.productInfo);
+      		  // 处理图片数据
+      		  if (this.productInfo.imageUrl) {
+      		    try {
+      			  this.imageList = this.productInfo.imageUrl.split(',')
+      		      console.log('解析后的图片数据:', this.imageList);
+      		    } catch (e) {
+      		      console.error('解析图片数据失败:', e);
+      		      this.imageList = [];
+				}
+      		  } else {
+      		    this.imageList = [];
+      		  }
+      		  uni.hideLoading();
+      		} else {
+      		  uni.showToast({
+      		    title: res.data.msg || '获取农品信息失败',
+      		    icon: 'none'
+      		  });
+      		}
+	  })
+     
+      // this.imageList = mockData.images;
       
       // 判断是否为当前用户发布的产品
       // 如果来源是我的发布页面,则表示是自己的产品
@@ -257,7 +323,7 @@ export default {
     
     // 编辑产品
     editProduct() {
-      if (this.productInfo.type === 'purchase') {
+      if (this.productInfo.type === 1) {
         // 收购信息跳转到收购编辑页面
         uni.navigateTo({
           url: `/pages/service/purchase-publish?action=edit&id=${this.productInfo.id}`
@@ -270,6 +336,49 @@ export default {
       }
     },
     
+	publishProduct(){
+		uni.showModal({
+		  title: '确认发布审核',
+		  content: `确定要发布这条信息吗?审核通过后将在${this.productInfo.type === 0 ? '销售' : '收购'}页面展示`,
+		  confirmText: '确认发布',
+		  cancelText: '取消',
+		  success: (res) => {
+		    if (res.confirm) {
+		      // 执行下架操作
+		      this.handlePublishProduct();
+		    }
+		  }
+		});
+	},
+	
+	// 处理发布操作
+	handlePublishProduct() {
+	  // 模拟下架API调用
+	  uni.showLoading({ title: '发布中...' });
+	  const data = {
+		  id: this.productInfo.id,
+		  status: 1 // 审核中
+	  }
+	  editProductInfo(data).then(res=>{
+		  uni.hideLoading();
+		  if(res.data.code === 200){
+			  uni.showToast({
+			    title: '发布成功',
+			    icon: 'success'
+			  });
+			// 返回上一页
+			setTimeout(() => {
+			  uni.navigateBack();
+			}, 1000);
+		  }else{
+			uni.showToast({
+				title: res.data.msg || '发布失败,请稍后重试',
+				icon: 'none'
+			});
+		}
+	  })
+	},
+	
     // 下架产品
     removeProduct() {
       uni.showModal({
@@ -290,19 +399,28 @@ export default {
     handleRemoveProduct() {
       // 模拟下架API调用
       uni.showLoading({ title: '下架中...' });
-      
-      setTimeout(() => {
-        uni.hideLoading();
-        uni.showToast({
-          title: '下架成功',
-          icon: 'success'
-        });
-        
-        // 返回上一页
-        setTimeout(() => {
-          uni.navigateBack();
-        }, 1500);
-      }, 1000);
+	  const data = {
+		  id: this.productInfo.id,
+		  status: 3 // 下架
+	  }
+      editProductInfo(data).then(res=>{
+		  uni.hideLoading();
+		  if(res.data.code === 200){
+			  uni.showToast({
+			    title: '下架成功',
+			    icon: 'success'
+			  });
+			// 返回上一页
+			setTimeout(() => {
+			  uni.navigateBack();
+			}, 1500);
+		  }else{
+			uni.showToast({
+				title: res.data.msg || '下架失败,请稍后重试',
+				icon: 'none'
+			});
+		}
+	  })
     },
     
     // 联系发布者

+ 417 - 196
pages/service/sales-publish.vue

@@ -9,7 +9,7 @@
 					<text class="required">*</text>
 				</view>
 				<view class="item-content">
-					<input class="form-input" v-model="formData.productName" placeholder="请输入产品名称"
+					<input class="form-input" v-model="formData.title" placeholder="请输入产品名称"
 						placeholder-style="color: #999;" maxlength="20" @input="onNameInput" />
 					<view class="char-count">{{ nameLength }}/20</view>
 				</view>
@@ -23,14 +23,25 @@
 				</view>
 				<view class="item-content">
 					<view class="category-selector" @click="showCategoryPicker = true">
-						<text class="selector-text" :class="{ placeholder: !formData.category }">
-							{{ formData.category || '请选择产品分类' }}
+						<text class="selector-text" :class="{ placeholder: !formData.categoryId }">
+							<!-- {{ formData.categoryLabel || '请选择产品分类' }} -->
+							{{getDictLabel('agricultural_category',formData.categoryId) || '请选择产品分类' }}
 						</text>
 						<text class="arrow-icon">></text>
 					</view>
 				</view>
 			</view>
-
+			<view class="form-item">
+			<!-- 所在地 -->
+			<view class="item-label">
+				<text class="label-text">所在地</text>
+				<text class="required">*</text>
+			</view>
+			<LocationPicker
+				v-model="formData.location"
+				mode="edit"
+			/>
+			</view>
 			<!-- 销售价格 -->
 			<view class="form-item">
 				<view class="item-label">
@@ -42,11 +53,47 @@
 						<text class="currency-symbol">¥</text>
 						<input class="form-input" v-model="formData.price" placeholder="请输入销售价格"
 							placeholder-style="color: #999;" type="digit" />
-						<text class="unit-text">元/斤</text>
+						<!-- <text class="unit-text">元/斤</text> -->
+					</view>
+				</view>
+			</view>
+			
+			<!-- 单位-->
+			<view class="form-item">
+				<view class="item-label">
+					<text class="label-text">单位</text>
+					<text class="required">*</text>
+				</view>
+				<view class="item-content">
+					<view class="category-selector" @click="showUnitPicker = true">
+						<text class="selector-text" :class="{ placeholder: !formData.unit }">
+							<!-- {{ formData.dictLabel || '请选择价格单位' }} -->
+							{{ getDictLabel('agricultural_unit',formData.unit) || '请选择价格单位'}}
+						</text>
+						<text class="arrow-icon">></text>
 					</view>
 				</view>
 			</view>
 
+			<!-- 收购数量 -->
+			  <view class="form-item">
+				<view class="item-label">
+				  <text class="label-text">产品数量</text>
+				  <text class="required">*</text>
+				</view>
+				<view class="item-content">
+				  <view class="input-with-unit">
+					<input
+					  class="form-input"
+					  v-model="formData.quantity"
+					  placeholder="请输入产品数量"
+					  placeholder-style="color: #999;"
+					  type="number"
+					/>
+					<!-- <text class="unit-text">斤</text> -->
+				  </view>
+				</view>
+			  </view>
 			<!-- 产品简介 -->
 			<view class="form-item">
 				<view class="item-label">
@@ -64,15 +111,15 @@
 				<view class="item-label">
 					<text class="label-text">产品图片</text>
 					<text class="required">*</text>
-					<text class="optional">(最多3张)</text>
+					<text class="optional">(最多6张)</text>
 				</view>
 				<view class="item-content">
 					<view class="image-upload-area">
 						<view class="image-item" v-for="(image, index) in formData.images" :key="index">
-							<image class="uploaded-image" :src="image" mode="aspectFill"></image>
+							<image class="uploaded-image" :src="image.url" mode="aspectFill"></image>
 							<view class="image-delete" @click="removeImage(index)">×</view>
 						</view>
-						<view class="image-upload-btn" v-if="formData.images.length < 3" @click="chooseImage">
+						<view class="image-upload-btn" v-if="formData.images.length < 6" @click="chooseImage">
 							<text class="upload-icon">+</text>
 							<text class="upload-text">上传图片</text>
 						</view>
@@ -81,7 +128,7 @@
 			</view>
 
 			<!-- 产地信息 -->
-			<view class="form-item">
+<!-- 			<view class="form-item">
 				<view class="item-label">
 					<text class="label-text">产地信息</text>
 				</view>
@@ -95,21 +142,8 @@
 						<text class="contact-value">{{ locationInfo.field }}</text>
 					</view>
 				</view>
-			</view>
-			<uni-data-picker v-slot:default="{data, error, options}" :localdata="localData" popup-title="请选择省市区"
-				@change="onchange" @nodeclick="onnodeclick">
-				<view class="selectedAddress">
-					<view v-if="data.length == 0 && curLocation">{{ curLocation }}</view>
-					<view v-if="data.length" class="selected">
-						<view v-for="(item,index) in data" :key="index" class="selected-item">
-							<text>{{item.text}} </text>
-						</view>
-					</view>
-					<view class="addrlocation">
-						<uni-icons type="location" color="#ec4149" size="24"></uni-icons>
-					</view>
-				</view>
-			</uni-data-picker>
+			</view> -->
+		
 			<!-- 联系人信息 -->
 			<view class="form-item">
 				<view class="item-label">
@@ -145,9 +179,30 @@
 					<text class="picker-close" @click="showCategoryPicker = false">×</text>
 				</view>
 				<view class="picker-options">
-					<view class="picker-option" v-for="category in categoryOptions" :key="category"
-						@click="selectCategory(category)">
-						<text class="option-text">{{ category }}</text>
+					<view class="picker-option" 
+					v-for="category in dictDataOptions.agricultural_category" 
+					:key="category.dictCode"
+					@click="selectCategory(category)"
+					:class="{ 'active': category.dictValue == formData.categoryId }">
+						<text class="option-text">{{ category.dictLabel }}</text>
+					</view>
+				</view>
+			</view>
+		</view>
+		<!-- 单位选择弹窗 -->
+		<view class="picker-modal" v-if="showUnitPicker" @click="showUnitPicker = false">
+			<view class="picker-content" @click.stop>
+				<view class="picker-header">
+					<text class="picker-title">选择价格单位</text>
+					<text class="picker-close" @click="showUnitPicker = false">×</text>
+				</view>
+				<view class="picker-options">
+					<view class="picker-option"
+					v-for="unit in dictDataOptions.agricultural_unit"
+					:key="unit.dictCode"
+					@click="selectUnit(unit)"
+					:class="{ 'active': unit.dictValue == formData.unit }">
+						<text class="option-text">{{ unit.dictLabel }}</text>
 					</view>
 				</view>
 			</view>
@@ -156,47 +211,69 @@
 </template>
 
 <script>
-	import cityRows from '@/utils/data.json';
+	import LocationPicker from "@/components/common/LocationPicker.vue"
+	import api from "@/config/api.js";
+	import storage from "@/utils/storage.js";
+	import { addProductInfo ,getProductInfoById,editProductInfo} from '@/api/services/productInfo.js';
+	import dictMixin from '@/utils/mixins/dictMixin';
+	import {
+		getFormattedTime
+	} from '@/utils/dateUtils'
 	export default {
+		mixins: [dictMixin],
+		components: { LocationPicker },
 		data() {
 			return {
+				dictTypeList: ['agricultural_unit','agricultural_category'],
 				isEditMode: false, // 是否为编辑模式
 				editProductId: '', // 编辑的产品ID
-				productType: 'sale', // 产品类型:sale(销售)或 purchase(收购)
+				productType: '0', // 产品类型:0-sale(销售)或 1-purchase(收购)
 				formData: {
-					productName: '',
-					category: '',
+					title: '',
+					categoryId: '',
 					price: '',
 					description: '',
+					imageUrl: '',
 					images: [],
+					unit:'',
+					quantity: '',
 					contactName: '',
-					contactPhone: ''
+					contactPhone: '',
+					location: '',
+					publishTime: getFormattedTime(), // 发布时间
+					userId: storage.getUserInfo().userid,
+					type: 0 ,// 出售
+					status: 1 ,// 审核中
 				},
 				locationInfo: {
 					address: '张家村',
 					field: '水稻田A区'
 				},
-				categoryOptions: ['蔬菜', '水果', '粮食', '畜产品', '水产品', '中药材'],
+				currentUserInfo: storage.getUserInfo(),
+				// categoryOptions: ['蔬菜', '水果', '粮食', '畜产品', '水产品', '中药材'],
+				dictDataOptions: [],
 				showCategoryPicker: false,
+				showUnitPicker: false,
 				nameLength: 0,
 				descLength: 0,
-				localData: [], //省市区地址
-				curLocation: uni.getStorageSync('location_address')
+				// localData: [], //省市区地址
 			}
 		},
-
+		
 		onLoad(options) {
-			this.localData = this.get_city_tree()
 
+			this.dictDataOptions = this.dictData
+			
+			console.log("this.dictDataOptions",this.dictDataOptions);
 			// 检查是否为编辑模式
 			if (options.action === 'edit' && options.id) {
 				this.isEditMode = true;
 				this.editProductId = options.id;
-				this.productType = options.type || 'sale';
+				this.productType = options.type || '0';
 
 				// 设置页面标题
 				uni.setNavigationBarTitle({
-					title: this.productType === 'sale' ? '编辑销售信息' : '编辑收购信息'
+					title: this.productType === '0' ? '编辑销售信息' : '编辑收购信息'
 				});
 
 				// 加载产品数据
@@ -211,126 +288,43 @@
 		},
 
 		methods: {
-			onchange(e) {
-			    const value = e.detail.value
-				console.log("value",value);
-			},
-			onnodeclick(node) {
-				console.log("node",node);
-				console.log("curLocation",this.curLocation);
-			},
-			// 省市区数据树生成
-			get_city_tree() {
-				let res = []
-				if (cityRows.length) {
-					// 递归生成
-					res = this.handleTree(cityRows)
-				}
-				return res
-			},
-
-
-			handleTree(data, parent_code = null) {
-				let res = []
-
-				let keys = {
-					id: 'code',
-					pid: 'parent_code',
-					children: 'children',
-
-					text: 'name',
-					value: 'code'
-				}
-
-				let oneItemDEMO = {
-					text: '',
-					value: '',
-					children: []
-				}
-				let oneItem = {}
-
-				// 循环
-				for (let index in data) {
-					// 判断
-					if (parent_code === null) {
-						// 顶级菜单 - 省
-						if (!data[index].hasOwnProperty(keys.pid) || data[index][keys.pid] == parent_code) {
-							// 不存在parent_code,或者已匹配
-							oneItem = JSON.parse(JSON.stringify(oneItemDEMO))
-							oneItem.text = data[index][keys.text]
-							oneItem.value = data[index][keys.value]
-
-							// 递归下去
-							oneItem.children = this.handleTree(data, data[index][keys.id])
-							res.push(oneItem)
-						}
-					} else {
-						// 非顶级菜单 - 市、区、街道
-						if (data[index].hasOwnProperty(keys.pid) && data[index][keys.pid] == parent_code) {
-							// 已匹配
-							oneItem = JSON.parse(JSON.stringify(oneItemDEMO))
-							oneItem.text = data[index][keys.text]
-							oneItem.value = data[index][keys.value]
-
-							// 递归下去
-							oneItem.children = this.handleTree(data, data[index][keys.id])
-							res.push(oneItem)
-
-						}
-					}
-				}
-				return res
-			},
 			// 加载产品数据(编辑模式)
 			loadProductData() {
 				// 模拟从API加载数据,实际应用中需要根据ID和类型调用相应的API
 				uni.showLoading({
 					title: '加载中...'
 				});
-
-				setTimeout(() => {
-					let mockData;
-
-					if (this.productType === 'sale') {
-						// 销售信息的模拟数据
-						mockData = {
-							productName: '2024年红富士苹果',
-							category: '水果',
-							price: '8.5',
-							description: '自家果园种植的红富士苹果,个大味甜,果形端正,色泽鲜艳。采用有机种植方式,无农药残留,口感清脆香甜。',
-							images: [
-								'/static/images/products/corn-seeds-new.jpg',
-								'/static/images/products/rice-seeds.jpg'
-							],
-							contactName: '张先生',
-							contactPhone: '18812341234'
-						};
-					} else {
-						// 收购信息的模拟数据
-						mockData = {
-							productName: '高价收购优质土豆',
-							category: '蔬菜',
-							price: '3.5',
-							description: '大量收购优质土豆,要求新鲜无病害,无青皮,规格统一。支持长期合作,价格优惠,现金结算。',
-							images: [
-								'/static/images/products/agriculture-tools.jpg'
-							],
-							contactName: '李先生',
-							contactPhone: '13856785678'
-						};
-					}
-
-					// 填充表单数据
-					this.formData = {
-						...mockData
-					};
-
-					// 更新字符计数
-					this.nameLength = this.formData.productName.length;
-					this.descLength = this.formData.description.length;
-
-					uni.hideLoading();
-				}, 1000);
+				
+				getProductInfoById(this.editProductId).then(res=>{
+					if (res.data.code === 200) {
+						  // const { data } = res.data;
+						 // this.formData = data
+						 Object.assign(this.formData, res.data.data)  // 保持响应式
+						 // 更新字符计数
+						 this.nameLength = this.formData.title.length;
+						 this.descLength = this.formData.description.length;
+						 
+						  console.log("this.goodsDetail", this.formData);
+						  // 处理图片数据
+						  if (this.formData.imageUrl) {
+						    try {
+							   this.formData.images = this.formData.imageUrl.split(',').map(url => ({ url,status: 'success' }))
+						      console.log('解析后的图片数据:', this.formData.images);
+						    } catch (e) {
+						      console.error('解析图片数据失败:', e);
+						      this.formData.images = [];
+								}
+						  } else {
+						    this.formData.images = [];
+						  }
+						  uni.hideLoading();
+						} else {
+						  uni.showToast({
+						    title: res.data.msg || '获取农品信息失败',
+						    icon: 'none'
+						  });
+						}
+				})
 			},
 
 			// 产品名称输入处理
@@ -345,34 +339,209 @@
 
 			// 选择分类
 			selectCategory(category) {
-				this.formData.category = category;
+				this.formData.categoryId = category.dictValue;
+				this.formData.categoryLabel = category.dictLabel;
 				this.showCategoryPicker = false;
 			},
+			// 选择单位
+			selectUnit(unit) {
+				this.formData.unit = unit.dictValue;
+				this.formData.dictLabel = unit.dictLabel;
+				this.showUnitPicker = false;
+			},
+			
+			getDictLabel(dictKey, value) {
+			   if (!this.dictData || !this.dictData[dictKey]) {
+			     return ''
+			   }
+			   const list = this.dictData[dictKey] || []
+			   const item = list.find(u => u.dictValue == value)
+			   return item ? item.dictLabel : ''
+			 },
 
 			// 选择图片
 			chooseImage() {
-				const remainingCount = 3 - this.formData.images.length;
-				uni.chooseImage({
-					count: remainingCount,
-					sizeType: ['compressed'],
-					sourceType: ['album', 'camera'],
-					success: (res) => {
-						this.formData.images = this.formData.images.concat(res.tempFilePaths);
-					},
-					fail: (err) => {
-						console.error('选择图片失败:', err);
-					}
-				});
+			  uni.chooseImage({
+			    count: 6 - this.formData.images.length,
+			    sizeType: ['original', 'compressed'],
+			    sourceType: ['album', 'camera'],
+			    success: (res) => {
+			      console.log('选择图片成功:', res);
+			      
+			      // 验证文件类型和大小
+			      const validFiles = [];
+			      const invalidFiles = [];
+			      const maxSize = 5 * 1024 * 1024; // 5MB 最大限制
+			      
+			      // 检查每个文件
+			      res.tempFiles.forEach((file, index) => {
+			        // 检查文件类型
+			        const isImage = /\.(jpg|jpeg|png|gif)$/i.test(file.name);
+			        // 检查文件大小
+			        const isValidSize = file.size <= maxSize;
+			        
+			        if (isImage && isValidSize) {
+			          validFiles.push(res.tempFilePaths[index]);
+			        } else {
+			          invalidFiles.push({
+			            path: file.path,
+			            size: file.size,
+			            reason: !isImage ? '文件格式不支持' : '文件大于5MB'
+			          });
+			        }
+			      });
+			      
+			      // 显示无效文件提示
+			      if (invalidFiles.length > 0) {
+			        uni.showToast({
+			          title: `${invalidFiles.length}个文件无效,请检查格式和大小`,
+			          icon: 'none',
+			          duration: 2000
+			        });
+			      }
+			      
+			      // 如果有有效文件,则上传
+			      if (validFiles.length > 0) {
+			        this.uploadImages(validFiles);
+			      }
+			    },
+			    fail: (err) => {
+			      console.error('选择图片失败:', err);
+			      uni.showToast({
+			        title: '选择图片失败',
+			        icon: 'none'
+			      });
+			    }
+			  });
+			},
+			
+			// 上传图片到服务器
+			uploadImages(tempFilePaths) {
+			  uni.showLoading({
+			    title: '上传中...',
+			    mask: true
+			  });
+			  
+			  // 上传成功的图片计数
+			  let successCount = 0;
+			  let failCount = 0;
+			  const totalFiles = tempFilePaths.length;
+			  const newImages = [];
+			  
+			  // 遍历处理每张图片
+			  tempFilePaths.forEach((path, index) => {
+			    // 调用上传API
+			    uni.uploadFile({
+			      // url: api.serve + '/base/tasks/uploadTaskImage', 
+			      url: api.serve + '/file/upload', 
+			      filePath: path,
+			      name: 'file', // 文件参数名称,需要与后端接口匹配
+			      formData: {
+			        type: 'task', // 标识文件类型,用于后端区分不同业务的文件
+			        // directory: '/opt/app/nongxiaoyu/uploadImage' // 指定保存目录
+			      },
+				  header: {
+				  	'Authorization': `Bearer ${storage.getAccessToken()}`
+				  },
+			      success: (res) => {
+			                      try {
+			            const response = JSON.parse(res.data);
+						uni.showToast({
+						  title: `返回: ${response.data}`,
+						  icon: 'none',
+						});
+			            if (response.code === 200) {
+			              // 获取返回的URL
+			              const imageUrl = response.data.url;
+			              console.log('上传成功,返回的图片URL:', imageUrl);
+			              
+			              // 上传成功,将图片信息添加到数组
+			              newImages.push({
+			                url: imageUrl, // 保存原始URL,在显示时会通过getImageUrl方法处理
+			                path: path, // 保存本地路径用于预览
+			                status: 'success',
+			                fileName: response.data.fileName || '' // 保存文件名,如果后端返回的话
+			              });
+			              successCount++;
+			          } else {
+			            failCount++;
+			            console.error('上传失败:', response.msg);
+			          }
+			        } catch (e) {
+			          failCount++;
+					  uni.showToast({
+					    title: `解析响应失败: ${e}`,
+					    icon: 'none',
+					  });
+			          console.error('解析响应失败:', e);
+			        }
+			      },
+			      fail: (err) => {
+			        failCount++;
+			        console.error('上传请求失败:', err);
+					uni.showToast({
+					  title: `上传请求失败: ${err}`,
+					  icon: 'none',
+					});
+			      },
+			      complete: () => {
+			        // 当所有文件都已处理完成
+			        if (successCount + failCount === totalFiles) {
+			          if (newImages.length > 0) {
+			            // 将新上传的图片添加到已有图片列表
+			            this.formData.images = [...this.formData.images, ...newImages];
+			            // 更新taskImages字段,将图片URL用逗号连接
+			           this.formData.imageUrl = this.formData.images.map(item => item.url).join(',');
+					   this.$set(this.formData, 'images', this.formData.images)  // 确保视图刷新
+			            // 显示成功提示
+			            uni.hideLoading();
+			            uni.showToast({
+			              title: `成功上传${successCount}张图片`,
+			              icon: 'success'
+			            });
+			          } else {
+			            // 全部失败
+						uni.showToast({
+						  title: `图片上传: ${newImages}`,
+						  icon: 'none',
+						});
+			            uni.hideLoading();
+			            // uni.showToast({
+			            //   title: '图片上传失败',
+			            //   icon: 'none',
+			            // });
+			          }
+			        }
+			      }
+			    });
+			  });
 			},
 
-			// 删除图片
 			removeImage(index) {
-				this.formData.images.splice(index, 1);
+			  uni.showModal({
+			    title: '确认删除',
+			    content: '确定要删除这张图片吗?',
+			    success: (res) => {
+			      if (res.confirm) {
+					  console.log("删除钱:",this.formData.images,index);
+			        this.formData.images.splice(index, 1);
+			        
+			        // 更新taskImages字段
+			       this.formData.imageUrl = this.formData.images.map(item => item.url).join(',');
+				   this.$set(this.formData, 'images', this.formData.images)  // 确保视图刷新
+			        console.log("删除后:",this.formData.imageUrl);
+			        uni.showToast({
+			          title: '已删除',
+			          icon: 'none'
+			        });
+			      }
+			    }
+			  });
 			},
 
 			// 表单验证
 			validateForm() {
-				if (!this.formData.productName.trim()) {
+				if (!this.formData.title.trim()) {
 					uni.showToast({
 						title: '请输入产品名称',
 						icon: 'none'
@@ -380,7 +549,7 @@
 					return false;
 				}
 
-				if (!this.formData.category) {
+				if (!this.formData.categoryId) {
 					uni.showToast({
 						title: '请选择产品分类',
 						icon: 'none'
@@ -395,8 +564,16 @@
 					});
 					return false;
 				}
+				
+				if (!this.formData.quantity || this.formData.quantity <= 0) {
+				  uni.showToast({
+				    title: '请输入有效的收购数量',
+				    icon: 'none'
+				  });
+				  return false;
+				}
 
-				if (this.formData.images.length === 0) {
+				if (this.formData.imageUrl.length === 0) {
 					uni.showToast({
 						title: '请至少上传一张产品图片',
 						icon: 'none'
@@ -445,32 +622,68 @@
 					title: loadingTitle
 				});
 
-				// 模拟提交
-				setTimeout(() => {
-					uni.hideLoading();
-
-					if (this.isEditMode) {
-						// 编辑模式 - 保存修改
-						uni.showModal({
-							title: '保存成功',
-							content: '您的修改已保存成功!',
-							showCancel: false,
-							success: () => {
-								uni.navigateBack();
-							}
+				uni.hideLoading();
+				
+				if (this.isEditMode) {
+					editProductInfo(this.formData).then(res=>{
+						if(res.data.code === 200){
+							uni.showModal({
+								title: '保存成功',
+								content: '您的修改已保存成功!',
+								showCancel: false,
+								success: () => {
+									uni.navigateBack();
+								}
+							});
+						}else{
+							uni.showToast({
+								title: res.data.msg || '提交失败,请稍后重试',
+								icon: 'none'
+							});
+						}
+					}).catch(err => {
+						console.error("提交异常:", err);
+						uni.showToast({
+							title: '网络错误,请检查后重试',
+							icon: 'none'
 						});
-					} else {
-						// 新建模式 - 提交审核
-						uni.showModal({
-							title: '提交成功',
-							content: '您的农产品信息已提交审核,审核通过后将在销售页面展示。',
-							showCancel: false,
-							success: () => {
-								uni.navigateBack();
-							}
+					});
+					
+				} else {
+					// 新建模式 - 提交审核
+					console.log("this.formData",this.formData);
+					// const data = {
+					// 	userId: this.currentUserInfo.userid,
+					// 	type: 0 ,// 出售
+					// 	status: 1 ,// 审核中
+					// }
+					// this.formData = {...this.formData , ...data}
+					addProductInfo(this.formData).then(res=>{
+						console.log("新增出售农产品",res);
+						if(res.data.code === 200){
+							uni.showModal({
+								title: '提交成功',
+								content: '您的农产品信息已提交审核,审核通过后将在销售页面展示。',
+								showCancel: false,
+								success: () => {
+									uni.navigateBack();
+								}
+							});
+						}else{
+							uni.showToast({
+								title: res.data.msg || '提交失败,请稍后重试',
+								icon: 'none'
+							});
+						}
+					}).catch(err => {
+						console.error("提交异常:", err);
+						uni.showToast({
+							title: '网络错误,请检查后重试',
+							icon: 'none'
 						});
-					}
-				}, 2000);
+					});
+					
+				}
 			}
 		}
 	}
@@ -791,9 +1004,17 @@
 		}
 
 		&:active {
-			background-color: #f5f5f5;
+			// background-color: #f5f5f5;
+			background-color: #f0f0f0;
+			color: #007aff;
+			font-weight: bold;
 		}
 	}
+	.picker-option.active {
+	  background-color: #f0f0f0;
+	  color: #007aff;
+	  font-weight: bold;
+	}
 
 	.option-text {
 		font-size: 30rpx;

+ 523 - 533
pages/service/sales.vue

@@ -1,540 +1,530 @@
 <template>
-  <view class="sales-container">
-    <!-- 顶部Tab标签页 -->
-    <view class="tab-header">
-      <view class="tab-list">
-        <view 
-          class="tab-item"
-          :class="{ active: activeTab === 'all' }"
-          @click="switchTab('all')"
-        >
-          <text class="tab-text">全部</text>
-        </view>
-        <view 
-          class="tab-item"
-          :class="{ active: activeTab === 'sale' }"
-          @click="switchTab('sale')"
-        >
-          <text class="tab-text">出售信息</text>
-        </view>
-        <view 
-          class="tab-item"
-          :class="{ active: activeTab === 'purchase' }"
-          @click="switchTab('purchase')"
-        >
-          <text class="tab-text">收购信息</text>
-        </view>
-      </view>
-      <view class="tab-indicator" :style="{ left: getIndicatorPosition() }"></view>
-    </view>
-
-    <!-- 搜索筛选 -->
-    <view class="search-section">
-      <view class="search-box">
-        <image class="search-icon" src="/static/icons/search.png" mode="aspectFit"></image>
-        <input 
-          class="search-input" 
-          placeholder="搜索农产品信息" 
-          placeholder-style="color: #999;"
-          v-model="searchKeyword"
-          @confirm="handleSearch"
-        />
-      </view>
-    </view>
-
-    <!-- 全部农产品信息列表 -->
-    <view class="content-container">
-      <view class="info-list">
-        <view 
-          class="info-card"
-          v-for="item in filteredAllInfo" 
-          :key="`${item.type}_${item.id}`"
-          @click="navigateToDetail(item)"
-        >
-          <view class="card-content">
-            <!-- 封面图片 -->
-            <view class="info-image">
-              <image :src="item.image" mode="aspectFill"></image>
-            </view>
-            
-            <!-- 信息内容 -->
-            <view class="info-content">
-              <!-- 类型标签和标题 -->
-              <view class="info-header">
-                <view class="type-tag" :class="item.type">
-                  {{ item.type === 'sale' ? '出售' : '收购' }}
-                </view>
-                <view class="info-title">{{ item.title }}</view>
-              </view>
-              
-              <!-- 简要说明 -->
-              <view class="info-desc">{{ item.description }}</view>
-              
-              <!-- 底部信息 -->
-              <view class="info-footer">
-                <view class="publish-info">
-                  <text class="publisher">{{ item.publisher }}</text>
-                  <text class="publish-time">{{ item.publishTime }}</text>
-                </view>
-              </view>
-            </view>
-          </view>
-        </view>
-      </view>
-    </view>
-
-    <!-- 浮动发布按钮 -->
-    <view class="floating-btn" @click="navigateToMyPublish">
-      <view class="btn-icon">+</view>
-      <text class="btn-text">我的发布</text>
-    </view>
-  </view>
+	<view class="sales-container">
+		<!-- 顶部Tab标签页 -->
+		<view class="tab-header">
+			<view class="tab-list">
+				<view class="tab-item" :class="{ active: activeTab === 'all' }" @click="switchTab('all')">
+					<text class="tab-text">全部</text>
+				</view>
+				<view class="tab-item" :class="{ active: activeTab === 'sale' }" @click="switchTab('sale')">
+					<text class="tab-text">出售信息</text>
+				</view>
+				<view class="tab-item" :class="{ active: activeTab === 'purchase' }" @click="switchTab('purchase')">
+					<text class="tab-text">收购信息</text>
+				</view>
+			</view>
+			<view class="tab-indicator" :style="{ left: getIndicatorPosition() }"></view>
+		</view>
+
+		<!-- 搜索筛选 -->
+		<view class="search-section">
+			<view class="search-box">
+				<image class="search-icon" src="/static/icons/search.png" mode="aspectFit"></image>
+				<input class="search-input" placeholder="搜索农产品信息" placeholder-style="color: #999;"
+					v-model="searchKeyword" @confirm="handleSearch" />
+			</view>
+		</view>
+
+		<!-- 全部农产品信息列表 -->
+		<scroll-view v-if="allInfoList.length > 0" scroll-y style="height: 100vh;" @scrolltolower="loadMore">
+			<view class="content-container">
+				<view class="info-list">
+					<view class="info-card" v-for="item in filteredAllInfo" :key="`${item.type}_${item.id}`"
+						@click="navigateToDetail(item)">
+						<view class="card-content">
+							<!-- 封面图片 -->
+							<view class="info-image">
+								<image :src="getImageUrl(item.imageUrl)" mode="aspectFill"></image>
+							</view>
+
+							<!-- 信息内容 -->
+							<view class="info-content">
+								<!-- 类型标签和标题 -->
+								<view class="info-header">
+									<view class="type-tag" :class="item.type === 0 ? 'sale' : 'purchase'">
+										{{ item.type === 0 ? '出售' : '收购' }}
+									</view>
+									<view class="info-title"> {{ item.title }}
+										¥{{ item.price || 0 }}/{{ getUnitLabel(item.unit) }}</view>
+								</view>
+
+								<!-- 简要说明 -->
+								<view class="info-desc">{{ item.description }}</view>
+
+								<!-- 底部信息 -->
+								<view class="info-footer">
+									<view class="publish-info">
+										<text class="publisher">{{ item.contactName }}</text>
+										<text class="publish-time">{{ item.publishTime }}</text>
+									</view>
+								</view>
+							</view>
+						</view>
+					</view>
+				</view>
+				<!-- 没有更多数据提示 -->
+				<view class="no-more-data" v-if="noMoreData">
+					<text>没有更多数据了</text>
+				</view>
+			</view>
+		</scroll-view>
+
+		<!-- 浮动发布按钮 -->
+		<view class="floating-btn" @click="navigateToMyPublish">
+			<view class="btn-icon">+</view>
+			<text class="btn-text">我的发布</text>
+		</view>
+	</view>
 </template>
 
 <script>
-export default {
-  data() {
-    return {
-      activeTab: 'all', // 当前活跃的标签页
-      searchKeyword: '',
-      
-      // 全部农产品信息列表(出售+收购)
-      allInfoList: [
-        // 出售信息
-        {
-          id: 1,
-          type: 'sale',
-          title: '优质红薯 ¥2.5/斤',
-          description: '自家种植,香甜可口,现挖现卖',
-          image: '/static/images/products/corn-seeds-new.jpg',
-          publisher: '李**',
-          publishTime: '今天',
-          price: '2.5',
-          unit: '斤'
-        },
-        {
-          id: 2,
-          type: 'sale', 
-          title: '有机苹果 ¥8.5/斤',
-          description: '新鲜采摘,无农药残留,口感清脆',
-          image: '/static/images/products/rice-seeds.jpg',
-          publisher: '张**',
-          publishTime: '2小时前',
-          price: '8.5',
-          unit: '斤'
-        },
-        {
-          id: 3,
-          type: 'sale',
-          title: '土鸡蛋 ¥2.0/个',
-          description: '散养土鸡,营养丰富,蛋黄金黄',
-          image: '/static/images/products/seeds-packets.jpg',
-          publisher: '王**',
-          publishTime: '昨天',
-          price: '2.0',
-          unit: '个'
-        },
-        {
-          id: 4,
-          type: 'sale',
-          title: '新鲜玉米 ¥3.0/斤',
-          description: '当季玉米,粒粒饱满,甜度高',
-          image: '/static/images/products/greenhouse-film.jpg',
-          publisher: '陈**',
-          publishTime: '2天前',
-          price: '3.0',
-          unit: '斤'
-        },
-        // 收购信息
-        {
-          id: 5,
-          type: 'purchase',
-          title: '高价收购优质土豆 ¥3.5/斤',
-          description: '大量收购,要求新鲜无病害,长期合作',
-          image: '/static/images/products/agriculture-tools.jpg',
-          publisher: '刘**',
-          publishTime: '今天',
-          quantity: '1000斤',
-          budgetPrice: '3.5',
-          unit: '斤'
-        },
-        {
-          id: 6,
-          type: 'purchase',
-          title: '收购新鲜玉米 ¥2.8/斤',
-          description: '需求量大,价格优惠,现金结算',
-          image: '/static/images/products/organic-fertilizer-new.jpg',
-          publisher: '周**',
-          publishTime: '5小时前',
-          quantity: '5000斤',
-          budgetPrice: '2.8',
-          unit: '斤'
-        },
-        {
-          id: 7,
-          type: 'purchase',
-          title: '收购有机蔬菜 ¥8.0/斤',
-          description: '要求有机认证,品质优良,长期收购',
-          image: '/static/images/products/plastic-film.jpg',
-          publisher: '赵**',
-          publishTime: '昨天',
-          quantity: '500斤',
-          budgetPrice: '8.0',
-          unit: '斤'
-        },
-        {
-          id: 8,
-          type: 'sale',
-          title: '优质大米 ¥6.8/斤',
-          description: '当季新米,粒粒饱满,口感香甜',
-          image: '/static/images/products/fertilizer.jpg',
-          publisher: '孙**',
-          publishTime: '3天前',
-          price: '6.8',
-          unit: '斤'
-        }
-      ]
-    }
-  },
-  
-  computed: {
-    filteredAllInfo() {
-      let list = this.allInfoList;
-      
-      // 按类型筛选
-      if (this.activeTab === 'sale') {
-        list = list.filter(item => item.type === 'sale');
-      } else if (this.activeTab === 'purchase') {
-        list = list.filter(item => item.type === 'purchase');
-      }
-      
-      // 按搜索关键词筛选
-      if (this.searchKeyword.trim()) {
-        const keyword = this.searchKeyword.trim().toLowerCase();
-        list = list.filter(item => 
-          item.title.toLowerCase().includes(keyword) || 
-          item.description.toLowerCase().includes(keyword) ||
-          item.publisher.toLowerCase().includes(keyword)
-        );
-      }
-      
-      return list;
-    }
-  },
-  
-  methods: {
-    // 切换Tab标签页
-    switchTab(tab) {
-      this.activeTab = tab;
-    },
-    
-    // 获取指示器位置
-    getIndicatorPosition() {
-      const positions = {
-        'all': '0%',
-        'sale': '33.33%', 
-        'purchase': '66.66%'
-      };
-      return positions[this.activeTab] || '0%';
-    },
-    
-    // 搜索处理
-    handleSearch() {
-      // 搜索逻辑已在computed中处理
-    },
-    
-    // 导航到详情页
-    navigateToDetail(item) {
-      uni.navigateTo({
-        url: `/pages/service/sales-detail?id=${item.id}&type=${item.type}&title=${encodeURIComponent(item.title)}`
-      });
-    },
-    
-    // 导航到我的发布页面
-    navigateToMyPublish() {
-      uni.navigateTo({
-        url: '/pages/service/my-publish'
-      });
-    }
-  }
-}
+	import {
+		getProductInfoList
+	} from '@/api/services/productInfo.js';
+	import dictMixin from '@/utils/mixins/dictMixin';
+	export default {
+		mixins: [dictMixin],
+		data() {
+			return {
+				dictTypeList: ['agricultural_unit'],
+				activeTab: 'all', // 当前活跃的标签页
+				searchKeyword: '',
+				pageNum: 1,
+				pageSize: 10,
+				noMoreData: false,
+				isLoading: false,
+				isRefreshing: false,
+				// 全部农产品信息列表(出售+收购)
+				allInfoList: []
+			}
+		},
+
+		computed: {
+			filteredAllInfo() {
+				let list = this.allInfoList;
+
+				// 按类型筛选
+				if (this.activeTab === 'sale') {
+					list = list.filter(item => item.type === 0);
+				} else if (this.activeTab === 'purchase') {
+					list = list.filter(item => item.type === 1);
+				}
+
+				// 按搜索关键词筛选
+				// if (this.searchKeyword.trim()) {
+				// 	const keyword = this.searchKeyword.trim().toLowerCase();
+				// 	list = list.filter(item =>
+				// 		item.title.toLowerCase().includes(keyword) ||
+				// 		item.description.toLowerCase().includes(keyword) ||
+				// 		item.publisher.toLowerCase().includes(keyword)
+				// 	);
+				// }
+
+				return list;
+			}
+		},
+		// 页面加载时获取数据
+		onLoad() {
+			this.loadProductInfo();
+		},
+		methods: {
+			// 上拉加载更多
+			loadMore() {
+				if (!this.isLoading && !this.noMoreData) {
+					this.pageNum++;
+					this.loadProductInfo();
+				}
+			},
+			getImageUrl(item) {
+				// 默认返回url或path
+				if (item == null) return
+				const imageUrls = item.split(',');
+				// console.log("imageUrls", imageUrls);
+				return imageUrls[0];
+			},
+			getUnitLabel(value) {
+				const unit = this.dictData.agricultural_unit.find(u => u.dictValue == value)
+				return unit ? unit.dictLabel : ''
+			},
+			loadProductInfo(keyword) {
+				this.isLoading = true;
+				uni.showLoading({
+					title: '加载中'
+				});
+				// 构建查询参数
+				const params = {
+					pageNum: this.pageNum,
+					pageSize: this.pageSize,
+					status: 2 // 查询 已上架
+				};
+				if (keyword != null && keyword != '') {
+					params.searchKeyword = keyword
+				}
+
+				// 分类逻辑
+				if (this.activeTab !== 'all' ) {
+					// 默认场景:推荐查询
+					params.type = this.activeTab === 'sale' ? 0 : 1;
+				}
+
+				getProductInfoList(params).then(res => {
+					if (res.data.code === 200) {
+						const {
+							rows,
+							total
+						} = res.data;
+						if (this.pageNum === 1) {
+							this.allInfoList = rows;
+						} else {
+							this.allInfoList = [...this.allInfoList, ...rows];
+						}
+						// 判断是否还有更多数据
+						this.noMoreData = this.allInfoList.length >= total;
+						uni.hideLoading();
+					} else {
+						uni.showToast({
+							title: res.data.msg || '获取农品列表失败',
+							icon: 'none'
+						});
+					}
+				}).catch(err => {
+					console.error('获取农品列表失败:', err);
+					uni.showToast({
+						title: '获取农品列表失败',
+						icon: 'none'
+					});
+				}).finally(() => {
+					this.isLoading = false;
+					this.isRefreshing = false;
+				});
+			},
+			// 切换Tab标签页
+			switchTab(tab) {
+				this.activeTab = tab;
+				this.allInfoList = [] // 切换时置空数据数组
+				// 滚动到当前分类位置
+				this.loadProductInfo(this.searchKeyword.trim())
+			},
+
+			// 获取指示器位置
+			getIndicatorPosition() {
+				const positions = {
+					'all': '0%',
+					'sale': '33.33%',
+					'purchase': '66.66%'
+				};
+				return positions[this.activeTab] || '0%';
+			},
+
+			// 搜索处理
+			handleSearch() {
+				// 搜索逻辑已在computed中处理
+				const keyword = this.searchKeyword.trim().toLowerCase();
+				this.loadProductInfo(keyword)
+			},
+
+			// 导航到详情页
+			navigateToDetail(item) {
+				uni.navigateTo({
+					url: `/pages/service/sales-detail?id=${item.id}&type=${item.type}&title=${encodeURIComponent(item.title)}`
+				});
+			},
+
+			// 导航到我的发布页面
+			navigateToMyPublish() {
+				uni.navigateTo({
+					url: '/pages/service/my-publish'
+				});
+			}
+		}
+	}
 </script>
 
 <style lang="scss">
-.sales-container {
-  min-height: 100vh;
-  background-color: #f5f5f5;
-  padding-bottom: 120rpx;
-}
-
-.tab-header {
-  background-color: #fff;
-  position: relative;
-  border-bottom: 1rpx solid #f0f0f0;
-}
-
-.tab-list {
-  display: flex;
-  align-items: center;
-}
-
-.tab-item {
-  flex: 1;
-  text-align: center;
-  padding: 32rpx 0;
-  position: relative;
-  
-  .tab-text {
-    font-size: 30rpx;
-    color: #666;
-    font-weight: 500;
-    transition: color 0.3s ease;
-  }
-  
-  &.active .tab-text {
-    color: #4CAF50;
-    font-weight: bold;
-  }
-}
-
-.tab-indicator {
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  width: 33.33%;
-  height: 4rpx;
-  background-color: #4CAF50;
-  transition: left 0.3s ease;
-}
-
-
-
-.search-section {
-  background-color: #fff;
-  padding: 20rpx;
-  border-bottom: 1rpx solid #f0f0f0;
-}
-
-.search-box {
-  flex: 1;
-  display: flex;
-  align-items: center;
-  background-color: #f8f8f8;
-  border-radius: 32rpx;
-  padding: 16rpx 24rpx;
-}
-
-.search-icon {
-  width: 32rpx;
-  height: 32rpx;
-  margin-right: 16rpx;
-}
-
-.search-input {
-  flex: 1;
-  font-size: 28rpx;
-  line-height: 1.5;
-}
-
-.filter-btn {
-  display: flex;
-  align-items: center;
-  padding: 16rpx 24rpx;
-  background-color: #f8f8f8;
-  border-radius: 24rpx;
-  font-size: 28rpx;
-  color: #666;
-  
-  .filter-icon {
-    margin-left: 8rpx;
-    font-size: 20rpx;
-  }
-}
-
-.category-tags {
-  background-color: #fff;
-  border-bottom: 1rpx solid #f0f0f0;
-}
-
-.tags-scroll {
-  white-space: nowrap;
-}
-
-.tags-list {
-  display: inline-flex;
-  padding: 0 20rpx;
-}
-
-.tag-item {
-  flex-shrink: 0;
-  padding: 20rpx 32rpx;
-  margin-right: 8rpx;
-  font-size: 28rpx;
-  color: #666;
-  border-radius: 32rpx;
-  transition: all 0.2s ease;
-  
-  &.active {
-    color: #4CAF50;
-    background-color: #f0fdf4;
-    font-weight: bold;
-  }
-}
-
-// 内容容器
-.content-container {
-  padding: 20rpx;
-}
-
-.info-list {
-  display: flex;
-  flex-direction: column;
-  gap: 16rpx;
-}
-
-// 统一信息卡片样式
-.info-card {
-  background-color: #fff;
-  border-radius: 12rpx;
-  overflow: hidden;
-  box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
-  transition: transform 0.2s ease;
-  
-  &:active {
-    transform: scale(0.98);
-  }
-}
-
-.card-content {
-  display: flex;
-  padding: 20rpx;
-  gap: 20rpx;
-}
-
-// 封面图片
-.info-image {
-  width: 160rpx;
-  height: 160rpx;
-  border-radius: 8rpx;
-  overflow: hidden;
-  flex-shrink: 0;
-  
-  image {
-    width: 100%;
-    height: 100%;
-  }
-}
-
-// 信息内容
-.info-content {
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-  justify-content: space-between;
-}
-
-// 信息头部
-.info-header {
-  display: flex;
-  align-items: flex-start;
-  margin-bottom: 12rpx;
-  gap: 12rpx;
-}
-
-.type-tag {
-  padding: 6rpx 12rpx;
-  border-radius: 12rpx;
-  font-size: 20rpx;
-  font-weight: bold;
-  flex-shrink: 0;
-  
-  &.sale {
-    background-color: #e8f5e8;
-    color: #4CAF50;
-  }
-  
-  &.purchase {
-    background-color: #e6f7ff;
-    color: #1890ff;
-  }
-}
-
-.info-title {
-  font-size: 28rpx;
-  font-weight: bold;
-  color: #333;
-  flex: 1;
-  line-height: 1.4;
-}
-
-// 简要说明
-.info-desc {
-  font-size: 26rpx;
-  color: #666;
-  margin-bottom: 16rpx;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-  line-height: 1.4;
-}
-
-// 底部信息
-.info-footer {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-
-.publish-info {
-  display: flex;
-  align-items: center;
-  gap: 16rpx;
-}
-
-.publisher {
-  font-size: 24rpx;
-  color: #4CAF50;
-  font-weight: bold;
-}
-
-.publish-time {
-  font-size: 24rpx;
-  color: #999;
-}
-
-// 浮动发布按钮
-.floating-btn {
-  position: fixed;
-  bottom: 120rpx;
-  right: 30rpx;
-  width: 140rpx;
-  height: 140rpx;
-  background-color: #4CAF50;
-  border-radius: 70rpx;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  box-shadow: 0 8rpx 24rpx rgba(76, 175, 80, 0.3);
-  z-index: 1000;
-  transition: transform 0.2s ease;
-  
-  &:active {
-    transform: scale(0.95);
-  }
-  
-  .btn-icon {
-    font-size: 56rpx;
-    color: #fff;
-    font-weight: 300;
-    line-height: 1;
-    margin-bottom: 2rpx;
-  }
-  
-  .btn-text {
-    font-size: 22rpx;
-    color: #fff;
-    font-weight: bold;
-  }
-}
-</style> 
+	.sales-container {
+		min-height: 100vh;
+		background-color: #f5f5f5;
+		padding-bottom: 120rpx;
+	}
+
+	.tab-header {
+		background-color: #fff;
+		position: relative;
+		border-bottom: 1rpx solid #f0f0f0;
+	}
+
+	.tab-list {
+		display: flex;
+		align-items: center;
+	}
+
+	.tab-item {
+		flex: 1;
+		text-align: center;
+		padding: 32rpx 0;
+		position: relative;
+
+		.tab-text {
+			font-size: 30rpx;
+			color: #666;
+			font-weight: 500;
+			transition: color 0.3s ease;
+		}
+
+		&.active .tab-text {
+			color: #4CAF50;
+			font-weight: bold;
+		}
+	}
+
+	.tab-indicator {
+		position: absolute;
+		bottom: 0;
+		left: 0;
+		width: 33.33%;
+		height: 4rpx;
+		background-color: #4CAF50;
+		transition: left 0.3s ease;
+	}
+
+
+
+	.search-section {
+		background-color: #fff;
+		padding: 20rpx;
+		border-bottom: 1rpx solid #f0f0f0;
+	}
+
+	.search-box {
+		flex: 1;
+		display: flex;
+		align-items: center;
+		background-color: #f8f8f8;
+		border-radius: 32rpx;
+		padding: 16rpx 24rpx;
+	}
+
+	.search-icon {
+		width: 32rpx;
+		height: 32rpx;
+		margin-right: 16rpx;
+	}
+
+	.search-input {
+		flex: 1;
+		font-size: 28rpx;
+		line-height: 1.5;
+	}
+
+	.filter-btn {
+		display: flex;
+		align-items: center;
+		padding: 16rpx 24rpx;
+		background-color: #f8f8f8;
+		border-radius: 24rpx;
+		font-size: 28rpx;
+		color: #666;
+
+		.filter-icon {
+			margin-left: 8rpx;
+			font-size: 20rpx;
+		}
+	}
+
+	.category-tags {
+		background-color: #fff;
+		border-bottom: 1rpx solid #f0f0f0;
+	}
+
+	.tags-scroll {
+		white-space: nowrap;
+	}
+
+	.tags-list {
+		display: inline-flex;
+		padding: 0 20rpx;
+	}
+
+	.tag-item {
+		flex-shrink: 0;
+		padding: 20rpx 32rpx;
+		margin-right: 8rpx;
+		font-size: 28rpx;
+		color: #666;
+		border-radius: 32rpx;
+		transition: all 0.2s ease;
+
+		&.active {
+			color: #4CAF50;
+			background-color: #f0fdf4;
+			font-weight: bold;
+		}
+	}
+
+	// 内容容器
+	.content-container {
+		padding: 20rpx;
+	}
+
+	.info-list {
+		display: flex;
+		flex-direction: column;
+		gap: 16rpx;
+	}
+
+	// 统一信息卡片样式
+	.info-card {
+		background-color: #fff;
+		border-radius: 12rpx;
+		overflow: hidden;
+		box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
+		transition: transform 0.2s ease;
+
+		&:active {
+			transform: scale(0.98);
+		}
+	}
+
+	.card-content {
+		display: flex;
+		padding: 20rpx;
+		gap: 20rpx;
+	}
+
+	// 封面图片
+	.info-image {
+		width: 160rpx;
+		height: 160rpx;
+		border-radius: 8rpx;
+		overflow: hidden;
+		flex-shrink: 0;
+
+		image {
+			width: 100%;
+			height: 100%;
+		}
+	}
+
+	// 信息内容
+	.info-content {
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+		justify-content: space-between;
+	}
+
+	// 信息头部
+	.info-header {
+		display: flex;
+		align-items: flex-start;
+		margin-bottom: 12rpx;
+		gap: 12rpx;
+	}
+
+	.type-tag {
+		padding: 6rpx 12rpx;
+		border-radius: 12rpx;
+		font-size: 20rpx;
+		font-weight: bold;
+		flex-shrink: 0;
+
+		&.sale {
+			background-color: #e8f5e8;
+			color: #4CAF50;
+		}
+
+		&.purchase {
+			background-color: #e6f7ff;
+			color: #1890ff;
+		}
+	}
+
+	.info-title {
+		font-size: 28rpx;
+		font-weight: bold;
+		color: #333;
+		flex: 1;
+		line-height: 1.4;
+	}
+
+	// 简要说明
+	.info-desc {
+		font-size: 26rpx;
+		color: #666;
+		margin-bottom: 16rpx;
+		overflow: hidden;
+		text-overflow: ellipsis;
+		white-space: nowrap;
+		line-height: 1.4;
+	}
+
+	// 底部信息
+	.info-footer {
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+	}
+
+	.publish-info {
+		display: flex;
+		align-items: center;
+		gap: 16rpx;
+	}
+
+	.publisher {
+		font-size: 24rpx;
+		color: #4CAF50;
+		font-weight: bold;
+	}
+
+	.publish-time {
+		font-size: 24rpx;
+		color: #999;
+	}
+
+	// 浮动发布按钮
+	.floating-btn {
+		position: fixed;
+		bottom: 120rpx;
+		right: 30rpx;
+		width: 140rpx;
+		height: 140rpx;
+		background-color: #4CAF50;
+		border-radius: 70rpx;
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+		box-shadow: 0 8rpx 24rpx rgba(76, 175, 80, 0.3);
+		z-index: 1000;
+		transition: transform 0.2s ease;
+
+		&:active {
+			transform: scale(0.95);
+		}
+
+		.btn-icon {
+			font-size: 56rpx;
+			color: #fff;
+			font-weight: 300;
+			line-height: 1;
+			margin-bottom: 2rpx;
+		}
+
+		.btn-text {
+			font-size: 22rpx;
+			color: #fff;
+			font-weight: bold;
+		}
+	}
+	.no-more-data{
+		text-align: center;
+		padding: 30rpx 0;
+	}
+	
+	.no-more-data text {
+	  font-size: 24rpx;
+	  color: #999;
+	  margin-left: 10rpx;
+	}
+</style>

+ 4 - 2
pages/user/index.vue

@@ -75,7 +75,7 @@
         </view>
 				<view v-if="isLogin" class="function-item" @click="handleLogout">
 					<view class="left">
-						<text class="function-icon">退</text>
+						<!-- <text class="function-icon">退</text> -->
 						<text>退出登录</text>
 					</view>
 					<text class="arrow">></text>
@@ -197,7 +197,7 @@
     },
 			handleContact() {
 				uni.makePhoneCall({
-					phoneNumber: '400-xxx-xxxx' // 替换为实际的客服电话
+					phoneNumber: '400-888-8888' // 替换为实际的客服电话
 				})
 			},
 			navigateToAbout() {
@@ -223,6 +223,8 @@
 								storage.setAccessToken("");
 								storage.setUserInfo({});
 								storage.setHasLogin(false)
+								storage.removeCurrentPlot()
+								storage.removeUserPlots()
 								this.isLogin = false;
 								this.userInfo = {
 									nickname: '游客',

+ 6 - 0
utils/storage.js

@@ -12,6 +12,12 @@ export default {
 	setDict(type,val){
 		uni.setStorageSync(type, val);
 	},
+	removeCurrentPlot(){
+		uni.removeStorageSync(CURRENT_PLOT);
+	},
+	removeUserPlots(){
+		uni.removeStorageSync(CURRENT_USER_PLOTS_LIST);
+	},
 	removeDict(type){
 		uni.removeStorageSync(type);
 	},

+ 20 - 0
vue.config.js

@@ -0,0 +1,20 @@
+
+module.exports = {
+    /**
+     *  此处为发行h5,微信小程序,app中删除console 
+     *  如需显示console 需要注释此处重新运行
+     */
+    chainWebpack: (config) => {
+        // 发行或运行时启用了压缩时会生效
+        config.optimization.minimizer('terser').tap((args) => {
+            const compress = args[0].terserOptions.compress
+            // 非 App 平台移除 console 代码(包含所有 console 方法,如 log,debug,info...)
+            compress.drop_console = true
+            compress.pure_funcs = [
+                '__f__', // App 平台 vue 移除日志代码
+                // 'console.debug' // 可移除指定的 console 方法
+            ]
+            return args
+        })
+    }
+}