From cd135141dbe4ca4f9cee362f6d35136ef83bdcf5 Mon Sep 17 00:00:00 2001 From: Default User Date: Fri, 27 Feb 2026 09:42:21 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=87=E6=BB=A4=E6=8E=89type=E4=B8=BAColleag?= =?UTF-8?q?ue=E7=9A=84=E7=94=A8=E6=88=B7=E5=9C=A8=E5=86=85=E9=83=A8?= =?UTF-8?q?=E4=BF=A1=E6=81=AF=E5=9D=97=E4=B8=AD=E7=9A=84=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pages/goods-detail/goods-detail.js | 120 ++++++++++++- pages/goods-detail/goods-detail.wxml | 39 ++++ pages/goods-detail/goods-detail.wxss | 254 +++++++++++++++++++++++++++ server-example/server-mysql.js | 116 ++++++++++++ 4 files changed, 521 insertions(+), 8 deletions(-) diff --git a/pages/goods-detail/goods-detail.js b/pages/goods-detail/goods-detail.js index 782c2f1..297610b 100644 --- a/pages/goods-detail/goods-detail.js +++ b/pages/goods-detail/goods-detail.js @@ -338,6 +338,28 @@ function formatDateTime(dateString) { return dateString; } +// 格式化北京时间的函数 +function formatBeijingTime(dateString) { + if (!dateString) return '未知时间'; + + // 尝试解析日期字符串 + const date = new Date(dateString); + + // 检查是否是有效的日期对象 + if (isNaN(date.getTime())) { + return dateString; + } + + // 转换为北京时间 + const year = date.getFullYear(); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const day = date.getDate().toString().padStart(2, '0'); + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + + return `${year}-${month}-${day} ${hours}:${minutes}`; +} + // 处理净重、件数、规格和价格数据,将逗号分隔的字符串转换为一一对应的数组 function processWeightAndQuantityData(weightSpecString, quantityString, specString, priceString, specStatusString, specPriceChanges = {}) { console.log('===== 处理净重、件数、规格和价格数据 ====='); @@ -1358,7 +1380,12 @@ Page({ // 当前用户信息 currentUserPhone: '', // 当前用户的手机号,用于判断评论是否属于当前用户 // 导航锁状态 - navigating: false // 是否正在导航中,防止多次点击导致多次跳转 + navigating: false, // 是否正在导航中,防止多次点击导致多次跳转 + // 内部信息相关数据 + isInternalUser: false, // 是否为内部人员 + viewerPhone: '', // 浏览人电话号码 + followUpRecords: [], // 跟进记录 + viewerInfo: [] // 浏览用户信息列表 }, // 导航锁机制,防止多次点击导致多次跳转 @@ -1449,11 +1476,69 @@ Page({ currentUserPhone = wx.getStorageSync('phoneNumber'); } - // 设置当前用户的电话号码 - this.setData({ - currentUserPhone: currentUserPhone - }); - console.log('当前用户手机号已设置:', currentUserPhone); + // 4. 检查是否为内部人员(与 index 页面逻辑一致) + if (currentUserPhone) { + const appInstance = getApp(); + appInstance.getPersonnelInfo(currentUserPhone).then(res => { + const isInternalUser = res.success && res.data && res.data.length > 0; + + // 5. 模拟浏览人电话号码和跟进记录(实际项目中应该从API获取) + const viewerPhone = '13800138000'; + const followUpRecords = [ + { time: '2024-01-15 10:30', content: '客户询问商品价格和库存情况' }, + { time: '2024-01-14 14:20', content: '客户表示对商品感兴趣,需要进一步了解' }, + { time: '2024-01-13 09:15', content: '首次联系客户,介绍商品信息' } + ]; + + // 设置当前用户的电话号码和内部人员状态 + this.setData({ + currentUserPhone: currentUserPhone, + isInternalUser: isInternalUser, + viewerPhone: viewerPhone, + followUpRecords: followUpRecords + }); + console.log('当前用户手机号已设置:', currentUserPhone); + console.log('是否为内部人员:', isInternalUser); + }).catch(err => { + console.error('检查personnel表失败:', err); + + // 5. 模拟浏览人电话号码和跟进记录(实际项目中应该从API获取) + const viewerPhone = '13800138000'; + const followUpRecords = [ + { time: '2024-01-15 10:30', content: '客户询问商品价格和库存情况' }, + { time: '2024-01-14 14:20', content: '客户表示对商品感兴趣,需要进一步了解' }, + { time: '2024-01-13 09:15', content: '首次联系客户,介绍商品信息' } + ]; + + // 设置当前用户的电话号码和内部人员状态 + this.setData({ + currentUserPhone: currentUserPhone, + isInternalUser: false, + viewerPhone: viewerPhone, + followUpRecords: followUpRecords + }); + console.log('当前用户手机号已设置:', currentUserPhone); + console.log('是否为内部人员:', false); + }); + } else { + // 5. 模拟浏览人电话号码和跟进记录(实际项目中应该从API获取) + const viewerPhone = '13800138000'; + const followUpRecords = [ + { time: '2024-01-15 10:30', content: '客户询问商品价格和库存情况' }, + { time: '2024-01-14 14:20', content: '客户表示对商品感兴趣,需要进一步了解' }, + { time: '2024-01-13 09:15', content: '首次联系客户,介绍商品信息' } + ]; + + // 设置当前用户的电话号码和内部人员状态 + this.setData({ + currentUserPhone: currentUserPhone, + isInternalUser: false, + viewerPhone: viewerPhone, + followUpRecords: followUpRecords + }); + console.log('当前用户手机号已设置:', currentUserPhone); + console.log('是否为内部人员:', false); + } // 只有在用户未登录且URL参数明确要求登录时才显示登录弹窗 if (!isLoggedIn && needLoginFromUrl) { @@ -2155,11 +2240,30 @@ Page({ displayRegion = extractProvince(displayRegion); } - this.setData({ + // 提取浏览用户信息和跟进记录 + const viewerInfo = product.viewerInfo || []; + + // 处理浏览用户信息,确保格式正确,并过滤掉type为Colleague的用户 + const processedViewerInfo = viewerInfo + .filter(viewer => viewer.type !== 'Colleague') // 过滤掉Colleague类型的用户 + .map(viewer => ({ + userId: viewer.userId, + phoneNumber: viewer.phoneNumber, + nickName: viewer.nickName, + type: viewer.type, + followupRecords: (viewer.followupRecords || []).map(record => ({ + ...record, + // 确保时间显示为北京时间 + createdAt: record.createdAt ? formatBeijingTime(record.createdAt) : '未知时间' + })) + })); + + this.setData({ goodsDetail: formattedGoods, displayRegion: displayRegion, isFavorite: preloadedFavoriteStatus, // 优先使用预加载数据中的收藏状态 - videoCoverUrl: null // 重置封面URL + videoCoverUrl: null, // 重置封面URL + viewerInfo: processedViewerInfo // 设置浏览用户信息 }); // 商品加载完成后检查并提取视频封面 diff --git a/pages/goods-detail/goods-detail.wxml b/pages/goods-detail/goods-detail.wxml index 085d0db..2125cbc 100644 --- a/pages/goods-detail/goods-detail.wxml +++ b/pages/goods-detail/goods-detail.wxml @@ -194,6 +194,45 @@ + + + + + 内部信息 + + + + + + + {{item.nickName || '未知用户'}} + {{item.phoneNumber || '暂无电话'}} + + + + + + + + + + + + 内部信息 + + + 暂无浏览记录 + + + diff --git a/pages/goods-detail/goods-detail.wxss b/pages/goods-detail/goods-detail.wxss index 95bb3d6..17b17a9 100644 --- a/pages/goods-detail/goods-detail.wxss +++ b/pages/goods-detail/goods-detail.wxss @@ -872,6 +872,133 @@ video.slider-media .wx-video-volume-icon { } } +/* 内部信息样式 */ +.internal-info { + margin: 8px 16px; + padding: 16px; + border-radius: 10px; + background: #f0f5ff; + border: 1px solid #d6e4ff; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + animation: fadeIn 0.4s ease-out; + animation-delay: 0.3s; +} + +.internal-header { + margin-bottom: 12px; + padding-bottom: 8px; + border-bottom: 1px solid #d6e4ff; +} + +.internal-title { + font-size: 16px; + font-weight: 600; + color: #1890ff; + display: flex; + align-items: center; +} + +.internal-title::before { + content: '🔒'; + margin-right: 8px; + font-size: 14px; +} + +.internal-content { + display: flex; + flex-direction: column; + gap: 12px; +} + +.internal-item { + display: flex; + flex-direction: column; + gap: 6px; +} + +.internal-label { + font-size: 13px; + color: #595959; + font-weight: 500; +} + +.internal-value { + font-size: 14px; + color: #262626; + font-weight: 600; +} + +/* 跟进记录样式 */ +.follow-up-records { + background: #ffffff; + border-radius: 6px; + padding: 12px; + border: 1px solid #e6f7ff; +} + +.follow-up-item { + margin-bottom: 10px; + padding-bottom: 10px; + border-bottom: 1px solid #f0f0f0; +} + +.follow-up-item:last-child { + margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; +} + +.follow-up-time { + font-size: 12px; + color: #8c8c8c; + margin-bottom: 4px; +} + +.follow-up-content { + font-size: 13px; + color: #595959; + line-height: 1.4; +} + +.no-records { + font-size: 13px; + color: #8c8c8c; + text-align: center; + padding: 20px 0; +} + +/* 浏览人信息样式 */ +.viewer-item { + margin-bottom: 16px; + padding-bottom: 16px; + border-bottom: 1px solid #e6f7ff; +} + +.viewer-item:last-child { + margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; +} + +.viewer-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.viewer-name { + font-size: 14px; + font-weight: 600; + color: #262626; +} + +.viewer-phone { + font-size: 14px; + color: #1890ff; + font-weight: 500; +} + .compare-modal-header { display: flex; justify-content: space-between; @@ -2047,4 +2174,131 @@ video.slider-media .wx-video-volume-icon { to { transform: translateY(0); } +} + +/* 内部信息样式 */ +.internal-info { + margin: 8px 16px; + padding: 16px; + border-radius: 10px; + background: #f0f5ff; + border: 1px solid #d6e4ff; + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08); + animation: fadeIn 0.4s ease-out; + animation-delay: 0.3s; +} + +.internal-header { + margin-bottom: 12px; + padding-bottom: 8px; + border-bottom: 1px solid #d6e4ff; +} + +.internal-title { + font-size: 16px; + font-weight: 600; + color: #1890ff; + display: flex; + align-items: center; +} + +.internal-title::before { + content: '🔒'; + margin-right: 8px; + font-size: 14px; +} + +.internal-content { + display: flex; + flex-direction: column; + gap: 12px; +} + +.internal-item { + display: flex; + flex-direction: column; + gap: 6px; +} + +.internal-label { + font-size: 13px; + color: #595959; + font-weight: 500; +} + +.internal-value { + font-size: 14px; + color: #262626; + font-weight: 600; +} + +/* 跟进记录样式 */ +.follow-up-records { + background: #ffffff; + border-radius: 6px; + padding: 12px; + border: 1px solid #e6f7ff; +} + +.follow-up-item { + margin-bottom: 10px; + padding-bottom: 10px; + border-bottom: 1px solid #f0f0f0; +} + +.follow-up-item:last-child { + margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; +} + +.follow-up-time { + font-size: 12px; + color: #8c8c8c; + margin-bottom: 4px; +} + +.follow-up-content { + font-size: 13px; + color: #595959; + line-height: 1.4; +} + +.no-records { + font-size: 13px; + color: #8c8c8c; + text-align: center; + padding: 20px 0; +} + +/* 浏览人信息样式 */ +.viewer-item { + margin-bottom: 16px; + padding-bottom: 16px; + border-bottom: 1px solid #e6f7ff; +} + +.viewer-item:last-child { + margin-bottom: 0; + padding-bottom: 0; + border-bottom: none; +} + +.viewer-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 10px; +} + +.viewer-name { + font-size: 14px; + font-weight: 600; + color: #262626; +} + +.viewer-phone { + font-size: 14px; + color: #1890ff; + font-weight: 500; } \ No newline at end of file diff --git a/server-example/server-mysql.js b/server-example/server-mysql.js index e8dfda2..795ebe4 100644 --- a/server-example/server-mysql.js +++ b/server-example/server-mysql.js @@ -2568,6 +2568,69 @@ EggbarLike.init({ timestamps: false }); +// 定义用户浏览商品记录模型 +class UserProductView extends Model { } +UserProductView.init({ + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true + }, + user_id: { + type: DataTypes.STRING(50), + allowNull: false, + comment: '用户ID' + }, + product_id: { + type: DataTypes.STRING(100), + allowNull: false, + comment: '产品ID' + }, + created_at: { + type: DataTypes.DATE, + defaultValue: Sequelize.NOW + } +}, { + sequelize, + modelName: 'UserProductView', + tableName: 'user_product_views', + timestamps: false +}); + +// 定义客户历史跟进记录模型 +class UserFollowupHistory extends Model { } +UserFollowupHistory.init({ + id: { + type: DataTypes.INTEGER, + autoIncrement: true, + primaryKey: true + }, + user_id: { + type: DataTypes.STRING(100), + allowNull: false, + comment: '关联的用户ID,对应users表的userId' + }, + followup_content: { + type: DataTypes.TEXT, + allowNull: true, + comment: '跟进内容' + }, + phone_number: { + type: DataTypes.STRING(20), + allowNull: false, + comment: '客户电话号码' + }, + created_at: { + type: DataTypes.DATE, + defaultValue: Sequelize.NOW + } +}, { + sequelize, + modelName: 'UserFollowupHistory', + tableName: 'user_followup_history', + timestamps: false +}); + // 定义模型之间的关联关系 // 用户和商品的一对多关系 (卖家发布商品) @@ -5922,6 +5985,55 @@ app.post('/api/products/detail', async (req, res) => { } }); + // 查询浏览该商品的用户记录 + const userViews = await UserProductView.findAll({ + where: { + product_id: productId + }, + attributes: ['user_id', 'created_at'] + }); + + // 提取浏览用户的user_id列表 + const userIds = userViews.map(view => view.user_id); + + // 查询这些用户的电话号码和跟进记录 + let viewerInfo = []; + if (userIds.length > 0) { + // 查询用户信息,包括followup和type字段 + const users = await User.findAll({ + where: { + userId: { + [Sequelize.Op.in]: userIds + } + }, + attributes: ['userId', 'phoneNumber', 'nickName', 'followup', 'type'] + }); + + // 构建浏览用户信息 + userIds.forEach(userId => { + const user = users.find(u => u.userId === userId); + if (user) { + // 构建跟进记录,使用users表中的followup字段 + const followupRecords = []; + if (user.followup) { + followupRecords.push({ + content: user.followup, + phoneNumber: user.phoneNumber, + createdAt: new Date() // 使用当前时间 + }); + } + + viewerInfo.push({ + userId: userId, + phoneNumber: user.phoneNumber, + nickName: user.nickName, + type: user.type, + followupRecords: followupRecords + }); + } + }); + } + // 对返回的商品数据进行处理 let updatedProduct = { ...product.toJSON() }; @@ -5997,8 +6109,12 @@ app.post('/api/products/detail', async (req, res) => { // 设置收藏人数 - 从favorites表统计得到 updatedProduct.reservedCount = favoriteCount; + // 添加浏览用户信息 + updatedProduct.viewerInfo = viewerInfo; + console.log('商品详情 - 最终返回的毛重值:', updatedProduct.grossWeight, '类型:', typeof updatedProduct.grossWeight); console.log('商品详情 - 返回的收藏人数:', updatedProduct.reservedCount, '类型:', typeof updatedProduct.reservedCount); + console.log('商品详情 - 浏览用户数量:', viewerInfo.length); console.log('商品详情 - producting字段:', updatedProduct.producting, '类型:', typeof updatedProduct.producting); console.log('商品详情 - description字段:', updatedProduct.description, '类型:', typeof updatedProduct.description);