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.createdAt || '未知时间'}}
+ {{item.content || '无内容'}}
+
+
+ 暂无跟进记录
+
+
+
+
+
+
+
+
+
+ 暂无浏览记录
+
+
+
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);