家政维修平台实战28:用户评价


上一篇我们介绍了评价算法的设计,本篇我们来实现一下用户评价的功能。用户评价的时候要留存评价数据,需要创建一个评价的数据模型,对于相关表需要扩充字段来支持评分的展现。

1 创建数据源

打开我们的数据模型,创建评价表
在这里插入图片描述
修改服务表,增加评分字段
在这里插入图片描述
修改工人表,增加评分对应字段
在这里插入图片描述

2 创建API

表创建好了之后,就需要编写API来实现评价算法,在我们的订单管理里增加评价方法
在这里插入图片描述
输入如下代码

/**
 * ErrorCode 定义
 * 统一管理系统可能返回的错误码和消息。
 */
const ErrorCode = {
    SUCCESS: 0,
    PARAM_ERROR: 1001,          // 参数缺失或无效
    NOT_FOUND: 1002,            // 订单或派工记录未找到
    SYSTEM_ERROR: 1003,         // 通用系统错误
    INVALID_ORDER_STATUS: 1004, // 订单当前主状态不允许评价
    // 更多错误码可以按需添加
};

/**
 * 订单在 `jz_orders` (订单主表) 中可能的主状态枚举。
 */
const ORDER_STATUS_ENUM = {
    PENDING_PAYMENT: '1',
    PENDING_ACCEPTANCE: '2', // 待接单
    ACCEPTED: '3',           // 已接单
    IN_SERVICE: '4',         // 服务中
    PENDING_REVIEW: '5',     // 待评价 (服务已完成,等待用户评价)
    COMPLETED: '6',          // 已完成 (用户已评价或已过评价期)
    CANCELED: '7',
    REFUNDED: '8',
};

/**
 * 订单主表 `jz_orders``dispatchStatus` 字段的枚举。
 */
const DISPATCH_STATUS_ENUM = {
    PENDING_DISPATCH: '1',
    DISPATCHED_PENDING_ACCEPTANCE: '2',
    DISPATCHED_ACCEPTED: '3',
    DISPATCHED_REJECTED: '4',
    DISPATCHED_COMPLETED: '5', // 派单已完成 (工人已完成服务)
};

/**
 * 提交评价函数
 * 用户通过此函数提交对已完成服务的评价。
 *
 * @param {object} params - 入参
 * @param {string} params.orderId - 订单ID (主键 _id)
 * @param {string} params.userId - 提交评价的用户ID (当前登录用户)
 * @param {number} params.overallRating - 整体服务评分 (1-5星)
 * @param {number} params.serviceQualityRating - 服务质量评分 (1-5星)
 * @param {number} params.serviceAttitudeRating - 服务态度评分 (1-5星)
 * @param {number} params.professionalismRating - 专业程度评分 (1-5星)
 * @param {number} params.punctualityRating - 准时程度评分 (1-5星)
 * @param {string} [params.reviewContent] - 评价内容 (可选)
 * @param {Array<string>} [params.reviewPhotos] - 评价照片的URL数组 (可选)
 * @param {boolean} [params.isAnonymous=false] - 是否匿名评价 (可选,默认为false)
 * @param {object} context - 微搭云函数上下文对象
 * @returns {Promise<object>} 评价结果,包含 code, message, data (更新后的订单信息)
 */
module.exports = async function (params, context) {
    const {
        orderId,
        userId,
        overallRating,
        serviceQualityRating,
        serviceAttitudeRating,
        professionalismRating,
        punctualityRating,
        reviewContent = '',
        reviewPhotos = [],
        isAnonymous = false
    } = params;

    // 1. 参数验证
    if (!orderId || !userId ||
        overallRating === undefined ||
        serviceQualityRating === undefined ||
        serviceAttitudeRating === undefined ||
        professionalismRating === undefined ||
        punctualityRating === undefined
    ) {
        return { code: ErrorCode.PARAM_ERROR, message: '必填参数缺失或无效:orderId, userId, 完整评分数据' };
    }

    // 验证评分是否在有效范围内 (1-5)
    const ratings = [overallRating, serviceQualityRating, serviceAttitudeRating, professionalismRating, punctualityRating];
    if (ratings.some(r => r < 1 || r > 5)) {
        return { code: ErrorCode.PARAM_ERROR, message: '评分必须在1到5之间' };
    }

    let order = null;
    let service = null;
    let provider = null;

    try {
        // 2. 查找订单的当前状态,并获取 service_id 和 assignedWorkerId
        order = await context.callModel({
            dataSourceName: "jz_orders", // 订单表模型标识
            methodName: "wedaGetItemV2",
            params: {
                filter: {
                    where: {
                        _id: { $eq: orderId }
                    }
                },
                select: {
                    "$master": true,
                    "serviceId": true, // 获取关联的服务ID
                    "assignedWorkerId": true // 获取被指派的工人ID
                }
            }
        });

        if (!order || Object.keys(order).length === 0) {
            return { code: ErrorCode.NOT_FOUND, message: '订单未找到' };
        }

        // 验证订单是否处于“待评价”状态
        if (order.status !== ORDER_STATUS_ENUM.PENDING_REVIEW) {
            return {
                code: ErrorCode.INVALID_ORDER_STATUS,
                message: `订单当前状态不允许评价。订单必须处于“待评价”状态。当前状态码: ${order.status}`
            };
        }

        const serviceId = order.serviceId && order.serviceId._id; // 假设 serviceId 是一个引用类型,需要取 _id
        const providerId = order.assignedWorkerId && order.assignedWorkerId._id; // 假设 assignedWorkerId 是一个引用类型,需要取 _id

        if (!serviceId || !providerId) {
            return { code: ErrorCode.SYSTEM_ERROR, message: '订单关联的服务或工人信息不完整' };
        }

        // 3. 查找服务信息 (用于后续更新服务的平均分)
        service = await context.callModel({
            dataSourceName: "jz_services", // 服务表模型标识
            methodName: "wedaGetItemV2",
            params: {
                filter: {
                    where: {
                        _id: { $eq: serviceId }
                    }
                },
                select: { "_id": true, "avg_rating": true, "total_reviews": true }
            }
        });

        // 4. 查找服务提供者信息 (用于后续更新工人的平均分)
        provider = await context.callModel({
            dataSourceName: "jz_fwssq", // 服务提供者表模型标识
            methodName: "wedaGetItemV2",
            params: {
                filter: {
                    where: {
                        _id: { $eq: providerId }
                    }
                },
                select: { "_id": true, "avg_rating": true, "total_reviews": true }
            }
        });

        if (!service || !provider || Object.keys(service).length === 0 || Object.keys(provider).length === 0) {
    return { code: ErrorCode.NOT_FOUND, message: '关联的服务或服务提供者未找到' };
}

        // 5. 插入新的评价记录到 `Reviews` 表
        const newReviewData = {
            orderId: { _id: orderId }, // 关联订单
            userId: { _id: userId },   // 关联用户
            serviceId: { _id: serviceId }, // 关联服务
            providerId: { _id: providerId }, // 关联服务提供者
            overall_rating: overallRating,
            service_quality_rating: serviceQualityRating,
            service_attitude_rating: serviceAttitudeRating,
            professionalism_rating: professionalismRating,
            punctuality_rating: punctualityRating,
            review_content: reviewContent,
            review_photos: reviewPhotos,
            is_anonymous: isAnonymous,
            review_date: Date.now()
        };

        await context.callModel({
            dataSourceName: "Reviews", // 评价表模型标识
            methodName: "wedaCreateV2",
            params: {
                data: newReviewData
            }
        });

        // 6. 更新 `ServiceProviders` 表中的评分和评价数
        // 重新计算该工人的平均评分和总评价数
        const providerReviews = await context.callModel({
            dataSourceName: "Reviews", // 数据模型标识,对应你之前设计的 Reviews 表
            methodName: "wedaGetRecordsV2", // 获取所有相关评价记录的方法
            params: {
                filter: {
                    relateWhere: {
                        providerId: {
                            where: {
                                _id: {
                                    $eq: providerId
                                }
                            }
                        }
                    }
                },
                select: {
                    "overall_rating": true // 只选择评分字段,减少数据传输量
                },
                getCount: true // 获取总条数,wedaGetRecordsV2 返回的数据结构中会包含 total 字段
                // 如果数据量巨大,不建议一次性获取所有记录。
                // 可以考虑分批获取或利用数据库的聚合功能(如果微搭的callModel支持)
                // 或者在 Reviews 表中维护 providerId 相关的冗余统计字段,通过触发器或定期任务更新
            }
        });


        let newProviderAvgRating = 0;
        let newProviderTotalReviews = providerReviews.total; // total属性表示总条数
        if (newProviderTotalReviews > 0) {
            const sumProviderRatings = providerReviews.records.reduce((sum, r) => sum + r.overall_rating, 0);
            newProviderAvgRating = parseFloat((sumProviderRatings / newProviderTotalReviews).toFixed(1)); // 保留一位小数
        }

        await context.callModel({
            dataSourceName: "jz_fwssq",
            methodName: "wedaUpdateV2",
            params: {
                data: {
                    avg_rating: newProviderAvgRating,
                    total_reviews: newProviderTotalReviews
                },
                filter: {
                    where: { _id: { $eq: providerId } }
                }
            }
        });

        // 7. 更新 `Services` 表中的评分和评价数
        // 重新计算该服务的平均评分和总评价数
        const serviceReviews = await context.callModel({
            dataSourceName: "Reviews",
            methodName: "wedaGetRecordsV2", // 获取所有相关评价
            params: {
                filter: {
                    relateWhere: {
                        serviceId: {
                            where: {
                                _id: {
                                    $eq: serviceId
                                }
                            }
                        }
                    }
                },
                select: { "overall_rating": true },
                getCount:true
            }
        });

        let newServiceAvgRating = 0;
        let newServiceTotalReviews = serviceReviews.total;
        if (newServiceTotalReviews > 0) {
            const sumServiceRatings = serviceReviews.records.reduce((sum, r) => sum + r.overall_rating, 0);
            newServiceAvgRating = parseFloat((sumServiceRatings / newServiceTotalReviews).toFixed(1)); // 保留一位小数
        }

        await context.callModel({
            dataSourceName: "jz_services",
            methodName: "wedaUpdateV2",
            params: {
                data: {
                    avg_rating: newServiceAvgRating,
                    total_reviews: newServiceTotalReviews
                },
                filter: {
                    where: { _id: { $eq: serviceId } }
                }
            }
        });

        // 8. 更新 `jz_orders` (订单主表) 的状态为“已完成”
        const updateOrderParams = {
            status: ORDER_STATUS_ENUM.COMPLETED,      // 订单主状态更新为 '6' (已完成)
            dispatchStatus: DISPATCH_STATUS_ENUM.DISPATCHED_COMPLETED, // 派单状态也更新为已完成
            completionTime: Date.now(),               // 订单完成时间
        };

        await context.callModel({
            dataSourceName: "jz_orders",
            methodName: "wedaUpdateV2",
            params: {
                data: updateOrderParams,
                filter: {
                    where: { _id: { $eq: orderId } }
                }
            }
        });

        // 9. 在 `order_status_logs` (订单状态日志表) 插入日志记录
        const logDescription = `用户 (ID: ${userId}) 已提交订单 (ID: ${orderId}) 评价。订单主状态由 "${order.status}" 变为 "${ORDER_STATUS_ENUM.COMPLETED}"`;

        await context.callModel({
            dataSourceName: "order_status_logs", // 订单状态日志表模型标识
            methodName: "wedaCreateV2",
            params: {
                data: {
                    orderId: { _id: orderId },
                    eventType: '10', // 事件类型
                    eventDescription: logDescription,
                    operatorRole: '用户', // 操作角色为用户
                    operatorId: userId, // 操作人ID为用户ID
                    fssj: Date.now(), // 发生时间
                }
            }
        });

        // 10. 返回评价结果
        const updatedOrder = await context.callModel({
            dataSourceName: "jz_orders",
            methodName: "wedaGetItemV2",
            params: {
                filter: {
                    where: { _id: { $eq: orderId } }
                },
                select: { "$master": true, "status": true, "dispatchStatus": true }
            }
        });

        return {
            code: ErrorCode.SUCCESS,
            message: '订单评价成功',
            data: updatedOrder // 返回更新后的订单信息
        };

    } catch (error) {
        console.error("submitReview error:", error);
        return {
            code: ErrorCode.SYSTEM_ERROR,
            message: `系统错误: ${error.message}`
        };
    }
};

添加入参
在这里插入图片描述
点击方法测试,测试我们的API,测试通过后点击出参自动映射完成API的编写
在这里插入图片描述

在这里插入图片描述

3 搭建评价页面

打开我们的小程序,点击新建页面
在这里插入图片描述
输入页面的名称
在这里插入图片描述
添加表单容器,数据表选择评价表
在这里插入图片描述
选中页面组件,添加一个URL参数
在这里插入图片描述
选择关联订单ID,选中值绑定为我们的URL参数
在这里插入图片描述
选择评价用户ID,选中值绑定为我们全局变量的用户信息的数据标识
在这里插入图片描述
选中表单容器,将表单的提交方法改为我们的API的方法
在这里插入图片描述
表单提交之后增加一个返回上一页的方法
在这里插入图片描述
切换到我的订单页面,增加一个评价按钮
在这里插入图片描述
给按钮设置点击事件,打开我们的评价页面,并且传入订单的数据标识
在这里插入图片描述

总结

本篇我们介绍了用户评价的一个实现过程,创建了评价的数据源,增加了API实现了评价的业务逻辑,搭建了前端界面完成了交互。后续我们就继续完善我们用户端的功能,实现一下售后的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

低代码布道师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值