import envConfig from "../../env"; import { Comment, CommentResponse } from "./article"; interface IApp { globalData: { token: string; userInfo: any; rawCardData: any[]; processedCardsData: any[]; // 确保这里有定义 }; processedCardsData: any[]; // 也要在这里定义,因为您直接将其挂载在 App 实例上 } const app = getApp(); // 假设 Comment 和 CommentResponse 已经正确定义 // 比如在 article.ts 文件中 // export interface Comment { /* ... */ } // export interface CommentResponse { success: boolean; message?: string; data: Comment[] | any; } Component({ data: { // 明确声明类型,解决 TypeScript 的 never[] 报错 comments: [] as Comment[], loading: true, error: null as string | null, totalCommentsCount: 0, // *** 新增:存储所有评论的总数 (包括回复) *** cardReviewData: null as any | null, showReplyInput: false, currentReplyId: '', replyContent: '', }, properties: { id: { type: String, value: '' } }, lifetimes: { attached() { // 组件被挂载时执行 const id = this.properties.id; this.getCardDetail(id); this.loadCardReviewData(id); } }, methods: { onShareAppMessage: function (res) { // 1. 获取文评数据,添加容错(避免数据为空导致报错) const { cardReviewData = {} } = this.data; const { article_title = "未命名文评", // 文评标题默认值 total_voters = 0, // 总投票人数默认值 vote_type = "单选", // 投票类型(单选/多选)默认值 options = [] // 选项列表默认空数组 } = cardReviewData; // 2. 处理选项数据:拼接“选项名+投票数”(如“方案A15票/方案B13票”) const optionText = options.length ? options.map(opt => `${opt.name || '未命名选项'}${opt.votes || 0}票`).join('/') : "暂无选项数据"; // 无选项时的默认提示 // 3. 拼接最终分享标题(整合所有关键信息) const shareTitle = `${article_title}(已有${total_voters}人投票:${optionText})`; // 4. 返回分享配置(不设置imageUrl,微信会自动使用页面截图作为分享图) return { title: shareTitle, // 整合了关键数据的标题 path: `/pages/articledetail/articledetail?id=${cardReviewData.article_id || ''}`, // 跳转路径(容错:避免id为空) // 不设置imageUrl:微信会自动截取当前页面顶部区域作为分享图(确保页面顶部有文评相关内容) success: (res) => { console.log('分享成功', res); // 可选:分享成功后给用户提示 wx.showToast({ title: '分享成功', icon: 'success', duration: 1500 }); }, fail: (res) => { console.log('分享失败', res); wx.showToast({ title: '分享失败', icon: 'none', duration: 1500 }); } }; }, // *** 递归计算评论总数的辅助函数 *** calculateTotalComments(comments: Comment[]): number { let count = 0; for (const comment of comments) { // 1. 计入当前评论(顶级评论或回复) count++; // 2. 递归计入子评论 if (comment.replies && comment.replies.length > 0) { count += this.calculateTotalComments(comment.replies); } } return count; }, getCardDetail(id: string) { console.log("正在加载文章ID:", id); // 1. 验证和转换 ID const articleId = parseInt(id, 10); if (isNaN(articleId) || articleId <= 0) { this.setData({ loading: false, error: "文章ID格式错误" }); return; } this.setData({ loading: true, error: null }); // 2. 调用获取评论的方法 this.getComments(articleId) .then(commentsData => { console.log("获取到commentsData:",commentsData) // *** 核心修改:计算总评论数 *** const totalCount = this.calculateTotalComments(commentsData); console.log(`文章ID ${articleId} 评论获取成功,总评论数: ${totalCount}`); this.setData({ comments: commentsData, // 存储树形结构的数据 totalCommentsCount: totalCount, // 存储总数 loading: false, }); console.log(this.data.comments[0]) }) .catch(error => { console.error(`获取评论失败: ${error.message}`); this.setData({ loading: false, error: error.message || "获取评论失败,请检查网络" }); }); }, getComments(articleId: number): Promise { return new Promise((resolve, reject) => { wx.request({ url: `${envConfig.apiBaseUrl}/comment/get`, method: "POST", data: { articleId: articleId }, success: (res) => { const response = res.data as CommentResponse; if (response.success && Array.isArray(response.data)) { resolve(response.data as Comment[]); } else { reject(new Error(response.message || "服务器返回数据异常")); } }, fail: (err) => { reject(new Error("网络请求失败,请检查连接")); } }); }); }, hasReplies(replies:Comment[]) { console.log('检测子评论:', { isArray: Array.isArray(replies), // 是否为数组 length: replies ? replies.length : '无数据', // 长度 rawData: replies // 原始数据 }); // 双重校验:1. 是标准数组 2. 长度大于0(避免空数组或非数组) return Array.isArray(replies) && replies.length > 0; }, loadCardReviewData(id: string) { // 1. 检查 app 实例是否存在 const app = getApp(); if (!app) { console.error('无法获取 App 实例,请确保 App 已启动。'); return; } // 2. 检查 app.processedCardsData 是否存在且是数组 if (Array.isArray(app.processedCardsData) && app.processedCardsData.length > 0) { console.log('app详情页获取到的文章数据:', app.globalData.processedCardsData); // ⭐️ 核心修复:先对字符串 id 进行 trim(),再进行 Number() 转换 const targetId = Number(id.trim()); // 打印调试信息,确认转换后的数值 console.log(`调试:properties.id(原始): "${id}"`); console.log(`调试:目标查找 ID (数值, trim后): ${targetId}`); const cardData = app.processedCardsData.find(item => { // 确保比较的是数值 === 数值 return item.article_id === targetId; }); if (cardData) { console.log(`从 app.ts 找到文评数据,ID: ${id}`); this.setData({ cardReviewData: cardData }); console.log("当前界面中cardReviewData:",this.data.cardReviewData) } else { console.log(`app.ts 中未找到 ID 为 ${id} 的文评数据。`); } } else { console.warn('App.processedCardsData 不存在或格式错误,无法加载文评数据。'); if (app.processedCardsData === undefined || app.processedCardsData === null) { console.warn('确认 app.ts 中 processedCardsData 是否在 App({}) 根部定义!'); } } }, formatIsoDateTime(isoString: string): string { console.log("调用formatIsoDateTime:",isoString) if (!isoString) return ''; // 处理ISO格式字符串(如2025-09-25T17:00:08+08:00) const date = new Date(isoString); // 提取日期部分 const year = date.getFullYear(); const month = this.padZero(date.getMonth() + 1); // 月份从0开始 const day = this.padZero(date.getDate()); // 提取时间部分 const hours = this.padZero(date.getHours()); const minutes = this.padZero(date.getMinutes()); // 格式化为 xx年xx月xx日 xx:xx return `${year}年${month}月${day}日 ${hours}:${minutes}`; }, // 数字补零辅助函数 padZero(num: number): string { return num < 10 ? `0${num}` : num.toString(); }, // 修复核心:单选/多选逻辑(基于单个 cardReviewData 对象) selectOption(e: WechatMiniprogram.TouchEvent) { const { optionId, cardId } = e.currentTarget.dataset as { optionId: number; cardId: number }; const { cardReviewData, maxSelectCount } = this.data; // 1. 基础校验:卡片不存在/投票已结束,直接返回 if (!cardReviewData || cardReviewData.article_id !== cardId) { console.error("未找到对应的卡片数据"); return; } if (cardReviewData.is_ended) { wx.showToast({ title: '投票已结束,无法选择', icon: 'none' }); return; } // 2. 深拷贝卡片数据(避免直接修改原数据) const newCardReviewData = JSON.parse(JSON.stringify(cardReviewData)); // 确保 selectedOptions 始终是数组 if (!Array.isArray(newCardReviewData.selectedOptions)) { newCardReviewData.selectedOptions = []; } const currentOptions = newCardReviewData.options || []; // 3. 多选逻辑(保持不变) if (newCardReviewData.vote_type === 'multiple') { const optionIndex = currentOptions.findIndex((o: any) => o.id === optionId); if (optionIndex === -1) return; const isAlreadySelected = newCardReviewData.selectedOptions.includes(optionId); if (isAlreadySelected) { // 取消选择:移除已选ID + 取消选项选中状态 newCardReviewData.selectedOptions = newCardReviewData.selectedOptions .filter(id => id !== optionId); currentOptions[optionIndex].isSelected = false; } else { // 新增选择:检查最大选择数限制 if (newCardReviewData.selectedOptions.length >= maxSelectCount) { wx.showToast({ title: `最多只能选择${maxSelectCount}项`, icon: 'none' }); return; } newCardReviewData.selectedOptions.push(optionId); currentOptions[optionIndex].isSelected = true; } // 4. 单选逻辑(核心修改:支持取消选择) } else if (newCardReviewData.vote_type === 'single') { // 4.1 判断当前点击的选项是否已选中 const isCurrentSelected = newCardReviewData.selectedOptions.includes(optionId); if (isCurrentSelected) { // 👉 情况1:已选中 → 取消选择(清空所有选中状态) currentOptions.forEach((option: any) => { option.isSelected = false; // 取消所有选项的选中状态 }); newCardReviewData.selectedOptions = []; // 清空已选列表 } else { // 👉 情况2:未选中 → 切换选择(取消其他选项,选中当前) currentOptions.forEach((option: any) => { option.isSelected = (option.id === optionId); // 只选中当前选项 }); newCardReviewData.selectedOptions = [optionId]; // 更新已选列表 } } // 5. 同步更新页面数据 this.setData({ cardReviewData: newCardReviewData }, () => { console.log(`卡片 ${cardId} 当前选中项:`, newCardReviewData.selectedOptions); }); }, submitVote(e: WechatMiniprogram.TouchEvent) { const { cardId } = e.currentTarget.dataset as { cardId: number }; const { cardReviewData } = this.data; // 从全局获取用户ID(需确保App.globalData中已存储uid,若从接口获取需调整) console.log("全局用户uid:",getApp().globalData.userInfo.uid) const uid = getApp().globalData.userInfo.uid; // 1. 基础校验:数据不存在/投票已结束/无用户ID if (!cardReviewData || cardReviewData.article_id !== cardId) { wx.showToast({ title: '未找到投票数据', icon: 'none' }); return; } if (cardReviewData.is_ended) { wx.showToast({ title: '投票已结束,无法提交', icon: 'none' }); return; } if (!uid) { wx.showToast({ title: '用户未登录,请先登录', icon: 'none' }); return; } // 2. 处理已选选项:确保是数组且非空 const selectedOptions = Array.isArray(cardReviewData.selectedOptions) ? cardReviewData.selectedOptions : []; if (selectedOptions.length === 0) { wx.showToast({ title: '请至少选择一个选项', icon: 'none' }); return; } // 3. 核心:根据投票类型动态构建请求参数 let voteParams: { uid: string; articleId: number; optionId?: number; optionIds?: number[] } = { uid: uid, // 从全局获取用户ID articleId: cardId // 投票卡片ID(与article_id一致) }; // 单选:添加optionId(取已选列表第一个值,因单选最多一个) if (cardReviewData.vote_type === 'single') { voteParams.optionId = selectedOptions[0]; // 单选已选列表仅1项,直接取第0个 } // 多选:添加optionIds(直接传入已选数组) else if (cardReviewData.vote_type === 'multiple') { voteParams.optionIds = selectedOptions; } console.log("投票发送:",voteParams) // 4. 发送投票请求(替换为实际接口地址,适配后端要求) wx.request({ url: `${envConfig.apiBaseUrl}/article/vote`, // 实际投票提交接口 method: 'POST', header: { 'Content-Type': 'application/json', // 若需Token验证,添加Token头(根据后端要求) // 'Authorization': `Bearer ${getApp().globalData.token}` }, data: voteParams, // 动态构建的差异化参数 success: (res) => { // 假设后端返回格式:{ success: boolean; message: string } const response = res.data; if (response.success) { wx.showToast({ title: '投票提交成功', icon: 'success' }); // 投票成功后:可更新卡片状态(如标记为已投票、禁用选项) this.setData({ ['cardReviewData.is_voted']: true, // 标记用户已投票(需卡片数据支持) ['cardReviewData.is_ended']: response.data?.is_ended || cardReviewData.is_ended // 若后端返回投票结束状态,同步更新 }); // // 通知父组件投票成功(如需) // this.triggerEvent('voteSuccess', { // cardId: cardId, // selectedOptions: selectedOptions, // voteParams: voteParams, // timestamp: Date.now() // }); } else { wx.showToast({ title: response.message || '投票提交失败', icon: 'none' }); console.error('投票提交失败:', response.message); } }, fail: (err) => { wx.showToast({ title: '网络错误,投票失败', icon: 'none' }); console.error('投票请求失败:', err); } }); }, }, })