news 2026/9/5 15:59:37

订单星级评价系统实现:从数据库设计到四星识别

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
订单星级评价系统实现:从数据库设计到四星识别

先交代一个很有意思的引子。前几天在技术群里看到一句吐槽:“是什么样的单主,让我打出了四星评价?”这句话乍一听是用户情绪表达,但放在交易、接单、服务类平台里,它就变成了一条非常典型的产品信号:四星评价没有到“差评”的程度,但又明显低于满分五星。如果你只盯着平均分,四星很容易被淹没;而真正落地过评价系统的人会知道,四星附近往往藏着大量“勉强满意但不愿意推荐”的真实体验问题。

这篇教程不准备讨论具体某个平台的运营策略,而是把这类问题工程化:当你需要开发一套“星级评价系统”时,评价数据表怎么设计?后端如何保证一单一评?四星评价如何被有效识别并进入回访流程?前端星级组件怎么封装?本文会从需求背景、数据库设计、后端接口、前端页面到常见问题排查,完整走一遍实现流程,适合刚接触订单评价模块的开发者,也适合想把“四星评价”从黑盒变成可分析数据的后端工程师。

在整个阅读过程中,本文会一直围绕一个核心目标:不仅让用户能打星,还能让系统从一颗星里挖掘出业务价值。

1. 背景与核心概念

1.1 什么是单主和四星评价场景

在交易、接单或服务类平台中,发起订单的一方通常被叫作“单主”,本质上就是买家、需求方或客户。平台完成一单服务后,买家有机会对本次服务进行评分,这个评分通常以星级形式体现,从 1 星到 5 星。

四星评价的特殊之处在于,它不像 1 星或 2 星那样带有强烈负面情绪,但也意味着用户潜意识里觉得“这次体验没有达到完美标准”。如果平台只做“好评/差评”的简单二分法,四星大概率会被归入“好评”,从而丢失一次改进机会。

从产品层面看,四星评价可能是以下原因造成的:

  • 服务质量达标,但响应速度偏慢。
  • 结果符合预期,但沟通过程中不够顺畅。
  • 整体体验不错,但某个细节让用户不太舒服。
  • 用户对“五星”的定义很严格,习惯默认打四星。

这就是为什么评价系统不能只保存一个总分。一个完整的评价系统,至少要能够回答三类问题:

  1. 谁在什么订单上打了星?
  2. 用户对哪些维度不满意?
  3. 低分或四星评价是否触发了回访和跟进?

1.2 评价系统的核心职责

评价模块听起来简单,无非是一张表加一个提交接口,但真正放到交易闭环里,它通常承担以下职责:

  • 约束评价资格:只有已完成的订单才能评价,且只有买家本人可以评价。
  • 保证唯一性:同一笔订单只能评价一次,不能重复刷评。
  • 采集多维数据:总分之外,还要记录服务、质量、速度等维度分数。
  • 提供统计能力:展示卖家或服务提供方的综合评分、评分分布。
  • 支撑运营动作:将四星以下或特定维度的低分评价转入回访任务。

评价系统的建设难点并不在打分组件,而在数据模型和状态约束。如果订单状态和评价状态没有对齐,就会出现“订单没完成也能评价”“同一个人对同一个订单评价两次”这种数据污染。

1.3 几个容易混淆的概念

在开始写代码前,有必要区分几组概念。

第一,订单评价和商品评价不同。商品评价可以针对同一个 SKU 重复发生,而订单评价是由“单次交易关系”唯一约束的。商品评价通常挂在商品维度,订单评价必须挂订单维度,并且一个订单只能产生一条主评价。

第二,综合评分和维度评分不同。综合评分是用户对整体体验的感受,维度评分可以拆成服务态度、完成质量、响应速度。维度评分的作用是解释综合评分为什么是四星而不是五星。

第三,均分和评分分布不同。均分是平均值,只能反映整体水平;评分分布是 5 星、4 星、3 星及以下的占比。只看均分容易掩盖问题,比如一个 4.8 分的卖家,4 星评价占比可能已经很高,意味着忠实推荐度其实在持续下降。

2. 环境准备与版本说明

本文采用“Spring Boot + Vue + MySQL”这套在企业中普及度很高的组合。版本不需要完全照抄,建议根据你的现有项目情况选择,但示例代码会标明关键配置。

2.1 技术栈清单

分层技术选型说明
后端语言Java 8 或 17JDK 17 建议配合 Spring Boot 3.x,JDK 8 建议使用 Spring Boot 2.7.x
后端框架Spring Boot简化依赖管理和请求处理
ORMMyBatis-Plus 3.5.x提供基础 CRUD 和 LambdaQueryWrapper
数据库MySQL 5.7+ / 8.x本文 SQL 使用 utf8mb4 字符集
前端Vue 3 + Element Plus星级组件使用 el-rate
构建工具Maven 3.6+管理后端依赖
IDEIntelliJ IDEA 或 Eclipse按习惯选择即可

需要注意,Spring Boot 3.x 对 JDK 版本有要求,如果你的环境是 JDK 8,建议继续使用 Spring Boot 2.7.x。下面示例中不会使用过于“新版本限定”的 API,核心逻辑可以平移到老版本。

2.2 项目目录结构设计

为了让代码便于阅读,我按前后端分离的结构来组织项目。你可以直接复用这个目录骨架。

order-review-system ├── backend │ ├── pom.xml │ └── src/main │ ├── java/com/example/review │ │ ├── ReviewApplication.java │ │ ├── common │ │ ├── controller │ │ ├── mapper │ │ ├── service │ │ └── entity │ └── resources │ ├── application.yml │ └── mapper/ReviewMapper.xml ├── frontend │ └── src │ └── views │ └── ReviewForm.vue └── sql └── init.sql

这是一个比较常规的单体项目结构。如果你的业务量比较大,后续可以把“评价提交”和“评价统计”拆成独立服务,但在初期阶段,单体结构足够支撑业务。

2.3 数据库环境检查

在动手写代码前,建议先确认 MySQL 可以正常连接,并创建一个独立数据库。为了避免 emoji 评论写入报错,数据库字符集一定要使用utf8mb4

create database if not exists order_review default character set utf8mb4 collate utf8mb4_general_ci;

后面建表时,也要显式使用utf8mb4,否则一旦用户在评论里输入表情符号,就可能出现Incorrect string value的错误。

3. 数据库设计:把“为什么打四星”拆成可分析的维度

数据库设计是整个评价系统最关键的部分。很多团队最初只建一张评价表,包含订单 ID、用户 ID、分数、内容,但这种设计只能回答“打了几分”,很难回答“到底是哪方面没做好”。

3.1 订单表设计

订单表不是本文重点,但评价系统强依赖订单状态,所以先设计一张精简订单表。实际业务中订单表结构会更复杂,这里只保留评价模块需要的核心字段。

create table `orders` ( id bigint primary key auto_increment, order_no varchar(32) not null comment '订单编号', buyer_id bigint not null comment '买家id,也就是单主', seller_id bigint not null comment '卖家/接单方id', order_status tinyint not null default 1 comment '订单状态 0-已取消 1-进行中 2-已完成', finish_time datetime default null comment '完成时间', create_time datetime not null default current_timestamp comment '下单时间', unique key uk_order_no (order_no), key idx_buyer_status (buyer_id, order_status), key idx_seller_status (seller_id, order_status) ) engine=innodb default charset=utf8mb4 comment='订单表';

订单状态是评价资格判断的依据。后端不能只判断订单 ID 存在,还必须检查当前订单是否处于已完成状态。否则用户完全可以在订单未完成时提交评价,后续履约内容发生变化后,评分会失真。

3.2 评价主表设计

评价主表用于保存每笔订单的主评价信息。核心约束是order_id,必须保证唯一。

create table review ( id bigint primary key auto_increment, order_id bigint not null comment '订单id', buyer_id bigint not null comment '买家id', seller_id bigint not null comment '卖家id', total_score tinyint not null comment '综合评分 1~5', service_score tinyint not null comment '服务态度评分 1~5', quality_score tinyint not null comment '交付质量评分 1~5', speed_score tinyint not null comment '交付速度评分 1~5', comment varchar(500) default null comment '文字评价', anonymous tinyint not null default 0 comment '是否匿名 0-否 1-是', create_time datetime not null default current_timestamp comment '评价时间', unique key uk_order (order_id), key idx_seller_score (seller_id, total_score), key idx_create_time (create_time) ) engine=innodb default charset=utf8mb4 comment='订单评价表';

这里有几个设计细节需要重点说明。

第一,order_id上的唯一索引uk_order是防止重复评价的兜底手段。后端代码可以做幂等判断,但并发请求同时到达时,只有数据库唯一索引才能真正挡住。

第二,buyer_idseller_id可以冗余在评价表中。虽然可以从订单表关联查询,但评价数据往往需要长期独立展示,冗余可以减少不必要的关联查询。

第三,total_scoretinyint存储 1 到 5 的整数。如果不允许半星,这个设计是合理的。如果业务允许四星半这种半星评分,就不能再用tinyint,而应该改成decimal(2,1),这一点后面会再提到。

第四,anonymous字段表示用户是否匿名评价。匿名评价展示时不能暴露买家昵称和头像,但后台仍然需要buyer_id来做权限判断。

3.3 维度评价的另外两种设计思路

上面的表把三个维度分作为固定字段放在主表里,适合评价指标相对稳定的场景。如果业务希望后续可以动态增加评价维度,更推荐拆成子表。

维度子表方案如下:

create table review_dimension ( id bigint primary key auto_increment, review_id bigint not null comment '评价主表id', dimension_code varchar(32) not null comment '维度编码 service/quality/speed', dimension_name varchar(50) not null comment '维度名称', score tinyint not null comment '维度评分 1~5', unique key uk_review_dim (review_id, dimension_code) ) engine=innodb default charset=utf8mb4 comment='评价维度表';

两种方案没有绝对优劣:

  • 固定字段方案查询简单,统计三个维度时不需要group by,适合指标确定的小型项目。
  • 子表方案扩展性好,新增一个“包装满意度”维度时无需改主表结构,但统计时代码更复杂。

建议在没有明确扩展需求前,先用固定字段。后续如果要扩展,可以从主表迁移到子表,或者新增一张维度元数据配置表。

3.4 评价字段的最终数据流

到这里,评价数据已经能够回答很多问题:

  • 总评分是多少:看total_score
  • 用户不满意的点在哪里:看三个维度分。
  • 笔订单对应哪笔交易:看order_id
  • 评价是否匿名显示:看anonymous
  • 评价发生在什么时间:看create_time

这套结构既支撑详情展示,也能支撑后续统计报表。接下来开始写后端代码。

4. 后端实现:评价提交与统计接口

4.1 添加 Maven 依赖

后端使用 Spring Boot 搭建,核心依赖如下:

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <scope>runtime</scope> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> </dependencies>

这里使用的 MyBatis-Plus 版本只是一个示例,实际使用时应根据你的项目版本统一。如果项目已经引入 MyBatis-Plus,不要重复引入。

4.2 配置文件

application.yml中配置数据源和 MyBatis-Plus 相关参数。

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/order_review?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

map-underscore-to-camel-case用来把数据库下划线字段自动映射成 Java 驼峰属性,MyBatis-Plus 默认开启,这里再显式写一次以免混淆。

serverTimezone=Asia/Shanghai用来避免数据库连接时出现时区差异导致的时间问题。

4.3 实体类

先创建评价实体类,对应review表。文件路径为src/main/java/com/example/review/entity/Review.java

package com.example.review.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.time.LocalDateTime; @Data @TableName("review") public class Review { @TableId(type = IdType.AUTO) private Long id; private Long orderId; private Long buyerId; private Long sellerId; private Integer totalScore; private Integer serviceScore; private Integer qualityScore; private Integer speedScore; private String comment; private Integer anonymous; private LocalDateTime createTime; }

实体字段和表字段通过驼峰映射自动对应,total_score会映射为totalScore

同样地,订单表实体不需要把所有字段都写出来,只保留评价逻辑需要判断的字段即可。文件路径为src/main/java/com/example/review/entity/Order.java

package com.example.review.entity; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; import lombok.Data; import java.time.LocalDateTime; @Data @TableName("orders") public class Order { @TableId(type = IdType.AUTO) private Long id; private String orderNo; private Long buyerId; private Long sellerId; private Integer orderStatus; private LocalDateTime finishTime; }

这里有一个细节:表名orders中的order在 MySQL 里是保留关键字,所以表名要加上复数形式,或者在建表时使用反引号。实体类使用@TableName("orders")指定表名即可。

4.4 接收参数 DTO

前端提交评价时,不应该直接把实体对象作为入参。原因是实体里包含buyerIdsellerIdcreateTime等后端应该自己决定的字段,如果完全信任前端传入,就存在越权风险。

新增一个请求 DTO,文件路径为src/main/java/com/example/review/dto/ReviewCreateRequest.java

package com.example.review.dto; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; import lombok.Data; @Data public class ReviewCreateRequest { @NotNull(message = "订单id不能为空") private Long orderId; @NotNull(message = "综合评分不能为空") @Min(value = 1, message = "综合评分最小为1") @Max(value = 5, message = "综合评分最大为5") private Integer totalScore; @NotNull(message = "服务态度评分不能为空") @Min(value = 1, message = "服务态度评分最小为1") @Max(value = 5, message = "服务态度评分最大为5") private Integer serviceScore; @NotNull(message = "交付质量评分不能为空") @Min(value = 1, message = "交付质量评分最小为1") @Max(value = 5, message = "交付质量评分最大为5") private Integer qualityScore; @NotNull(message = "交付速度评分不能为空") @Min(value = 1, message = "交付速度评分最小为1") @Max(value = 5, message = "交付速度评分最大为5") private Integer speedScore; private String comment; private Integer anonymous; }

通过校验注解可以拦截无效分数,例如用户传入 6 分或 0 分,后端接口会直接返回参数校验异常。

4.5 Mapper 层

为了让代码结构完整,先创建ReviewMapperOrderMapper。文件路径为src/main/java/com/example/review/mapper/ReviewMapper.java

package com.example.review.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.review.entity.Review; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.Map; @Mapper public interface ReviewMapper extends BaseMapper<Review> { Map<String, Object> selectScoreStat(@Param("sellerId") Long sellerId); Map<String, Object> selectDimensionAvg(@Param("sellerId") Long sellerId); }

selectScoreStat用于统计某个卖家的总体评分情况,selectDimensionAvg用于统计各维度平均分。对应的 SQL 写在 XML 里。

OrderMapper只需要继承BaseMapper就行:

package com.example.review.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.review.entity.Order; import org.apache.ibatis.annotations.Mapper; @Mapper public interface OrderMapper extends BaseMapper<Order> { }

MyBatis-Plus 的BaseMapper已经提供了selectByIdinsertupdateById等基础方法,这里不需要额外手写。

4.6 核心业务 Service

评价提交的完整流程图可以拆成下面几步:

  1. 根据orderId查询订单。
  2. 判断订单是否存在,不存在则提示“订单不存在”。
  3. 判断当前登录用户是否是订单中的买家。如果不是,拒绝提交。
  4. 判断订单状态是否为“已完成”。
  5. 判断该订单是否已经被评价过。如果已评价,提示用户不能重复评价。
  6. 构造评价对象并插入数据库。
  7. 如果遇到数据库唯一索引冲突,也视为重复评价。
  8. 如果评分为四星或更低,触发售后回访逻辑。

文件路径为src/main/java/com/example/review/service/ReviewService.java

package com.example.review.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.example.review.dto.ReviewCreateRequest; import com.example.review.entity.Order; import com.example.review.entity.Review; import com.example.review.mapper.OrderMapper; import com.example.review.mapper.ReviewMapper; import lombok.RequiredArgsConstructor; import org.springframework.dao.DuplicateKeyException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.HashMap; import java.util.Map; import java.util.Objects; @Service @RequiredArgsConstructor public class ReviewService { private final ReviewMapper reviewMapper; private final OrderMapper orderMapper; @Transactional(rollbackFor = Exception.class) public void createReview(ReviewCreateRequest request, Long currentUserId) { if (currentUserId == null) { throw new IllegalArgumentException("登录状态失效"); } Order order = orderMapper.selectById(request.getOrderId()); if (order == null) { throw new IllegalArgumentException("订单不存在"); } if (!Objects.equals(order.getBuyerId(), currentUserId)) { throw new IllegalArgumentException("只能评价自己下单的订单"); } if (!Objects.equals(order.getOrderStatus(), 2)) { throw new IllegalArgumentException("订单未完成,不能评价"); } Long count = reviewMapper.selectCount( new LambdaQueryWrapper<Review>() .eq(Review::getOrderId, order.getId()) ); if (count != null && count > 0) { throw new IllegalArgumentException("该订单已评价,不能重复评价"); } Review review = new Review(); review.setOrderId(order.getId()); review.setBuyerId(order.getBuyerId()); review.setSellerId(order.getSellerId()); review.setTotalScore(request.getTotalScore()); review.setServiceScore(request.getServiceScore()); review.setQualityScore(request.getQualityScore()); review.setSpeedScore(request.getSpeedScore()); review.setComment(request.getComment()); review.setAnonymous(request.getAnonymous() == null ? 0 : request.getAnonymous()); try { reviewMapper.insert(review); } catch (DuplicateKeyException e) { throw new IllegalArgumentException("该订单已评价,不能重复评价"); } handleFourStarReview(review); } private void handleFourStarReview(Review review) { if (review.getTotalScore() == null || review.getTotalScore() >= 5) { return; } boolean needFollowUp = false; if (review.getServiceScore() <= 3) { needFollowUp = true; } if (review.getQualityScore() <= 3) { needFollowUp = true; } if (review.getSpeedScore() <= 3) { needFollowUp = true; } if (needFollowUp) { // 实际项目中,这里可以写入回访任务表,或者发送 MQ 消息给售后系统。 // 伪代码: // FollowUpTask task = new FollowUpTask(); // task.setOrderId(review.getOrderId()); // task.setReviewId(review.getId()); // task.setType("LOW_DIMENSION_SCORE"); // task.setStatus("PENDING"); // followUpTaskMapper.insert(task); } } }

这里有几个地方值得展开说明。

第一,方法上加了@Transactional,保证订单校验、重复评价判断、评价插入这几个动作在同一事务中。如果中途出现异常,已经执行的插入操作会回滚。

第二,先查一次订单是否已评价,再插入评价,是为了给用户返回更友好的提示。纯粹依赖数据库唯一索引虽然能防止脏数据,但抛出DuplicateKeyException后,接口层需要做一层异常转换,否则用户会看到晦涩的数据库错误。

第三,handleFourStarReview只是一个示例方法。四星评价不一定是负面评价,但在售后运营中,四星评价配合“某个维度低于 3 分”这个条件,通常能识别出一批真实体验有瑕疵的订单。实际落地时可以把判断规则放进消息通知或回访工单系统。

4.7 统计接口

卖家详情页需要展示三个关键数据:综合平均分、评分分布、维度平均分。我们通过自定义 XML 查询来完成。

文件路径为src/main/resources/mapper/ReviewMapper.xml

<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.review.mapper.ReviewMapper"> <select id="selectScoreStat" resultType="map"> SELECT ROUND(AVG(r.total_score), 2) AS avgScore, COUNT(*) AS totalCount, IFNULL(SUM(CASE WHEN r.total_score = 5 THEN 1 ELSE 0 END), 0) AS fiveStarCount, IFNULL(SUM(CASE WHEN r.total_score = 4 THEN 1 ELSE 0 END), 0) AS fourStarCount, IFNULL(SUM(CASE WHEN r.total_score &lt;= 3 THEN 1 ELSE 0 END), 0) AS lowStarCount FROM review r WHERE r.seller_id = #{sellerId} </select> <select id="selectDimensionAvg" resultType="map"> SELECT ROUND(AVG(r.service_score), 2) AS serviceAvg, ROUND(AVG(r.quality_score), 2) AS qualityAvg, ROUND(AVG(r.speed_score), 2) AS speedAvg FROM review r WHERE r.seller_id = #{sellerId} </select> </mapper>

注意 XML 中<=需要写成&lt;=,否则 XML 解析会出错。如果这个 SQL 写不进 XML,也可以改用大于号的条件再取反,但不如转义符直观。

然后在ReviewService中新增统计方法:

public Map<String, Object> statBySeller(Long sellerId) { Map<String, Object> result = new HashMap<>(); result.put("scoreStat", reviewMapper.selectScoreStat(sellerId)); result.put("dimensionAvg", reviewMapper.selectDimensionAvg(sellerId)); return result; }

统计结果大致长这样:

{ "scoreStat": { "totalCount": 120, "avgScore": 4.72, "fiveStarCount": 90, "fourStarCount": 24, "lowStarCount": 6 }, "dimensionAvg": { "serviceAvg": 4.80, "qualityAvg": 4.75, "speedAvg": 4.55 } }

如果发现avgScore不低,但fourStarCount占比明显偏高,说明大量用户对服务存在“基本满意但未达惊喜”的状态,这是提升复购和口碑时需要重点关注的人群。

4.8 Controller 层

文件路径为src/main/java/com/example/review/controller/ReviewController.java

package com.example.review.controller; import com.example.review.dto.ReviewCreateRequest; import com.example.review.service.ReviewService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; import java.util.Map; @RestController @RequestMapping("/api/review") @RequiredArgsConstructor public class ReviewController { private final ReviewService reviewService; @PostMapping public String submit(@RequestBody @Valid ReviewCreateRequest request, @RequestAttribute("currentUserId") Long currentUserId) { reviewService.createReview(request, currentUserId); return "评价成功"; } @GetMapping("/stat") public Map<String, Object> stat(@RequestParam Long sellerId) { return reviewService.statBySeller(sellerId); } }

实际项目里,currentUserId一般由登录拦截器从 Token 中解析后放入RequestAttribute,而不是由前端直接传 userId。这里用@RequestAttribute只是为了演示后端如何获取当前操作人。如果直接接收前端传入的buyerId,会存在越权评价风险。

5. 前端实现:星级评分组件

后端接口准备好之后,前端需要完成两件事:一个是买家提交评价的表单,另一个是卖家评分展示区。

5.1 评价表单页面

使用 Vue 3 + Element Plus 实现评价表单。核心评分组件是el-rate。文件路径为frontend/src/views/ReviewForm.vue

<template> <div class="review-form"> <h3>评价本次订单</h3> <div class="rate-row"> <span class="rate-label">综合评分</span> <el-rate v-model="form.totalScore" :max="5" show-score /> </div> <div class="rate-row"> <span class="rate-label">服务态度</span> <el-rate v-model="form.serviceScore" :max="5" /> </div> <div class="rate-row"> <span class="rate-label">交付质量</span> <el-rate v-model="form.qualityScore" :max="5" /> </div> <div class="rate-row"> <span class="rate-label">交付速度</span> <el-rate v-model="form.speedScore" :max="5" /> </div> <el-input v-model="form.comment" type="textarea" maxlength="500" show-word-limit placeholder="说说这次体验吧,可以给其他用户一些参考" /> <div class="form-footer"> <el-checkbox v-model="form.anonymous">匿名评价</el-checkbox> <el-button type="primary" :loading="submitting" @click="submitReview"> 提交评价 </el-button> </div> </div> </template> <script setup> import { reactive, ref } from 'vue' import { ElMessage } from 'element-plus' import request from '@/utils/request' const props = defineProps({ orderId: { type: Number, required: true } }) const submitting = ref(false) const form = reactive({ orderId: props.orderId, totalScore: 5, serviceScore: 5, qualityScore: 5, speedScore: 5, comment: '', anonymous: false }) async function submitReview() { if (!form.totalScore || !form.serviceScore || !form.qualityScore || !form.speedScore) { ElMessage.warning('请完成所有评分项') return } submitting.value = true
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/5 15:58:33

tinkabot v0.1.0:用@让grok接入群聊的插件助手

tinkabot v0.1.0 这个版本号&#xff0c;很多人第一眼会以为又是一个 AI 对话助手的壳子&#xff0c;但它实际上是一个让 grok 能通过 方式被调用的 bot 插件助手。Lauren Tan 发布的这个项目&#xff0c;解决的不是“再做一个聊天机器人”&#xff0c;而是“怎么把 grok 接入…

作者头像 李华
网站建设 2026/9/5 15:52:16

Skyvern CLI 上手指南:从启动到任务管理,只用终端搞定

Skyvern CLI 上手指南&#xff1a;从启动到任务管理&#xff0c;只用终端搞定 【免费下载链接】skyvern Automate browser based workflows with AI 项目地址: https://gitcode.com/GitHub_Trending/sk/skyvern 不打开网页&#xff0c;在终端敲几条命令&#xff0c;Skyv…

作者头像 李华
网站建设 2026/9/5 15:48:52

从零跑通 Roo Code:AI 编程助手本地部署与配置完整指南

从零跑通 Roo Code&#xff1a;AI 编程助手本地部署与配置完整指南 【免费下载链接】Roo-Code Roo Code gives you a whole dev team of AI agents in your code editor. 项目地址: https://gitcode.com/GitHub_Trending/ro/Roo-Code 装完一个 AI 编程插件&#xff0c;你…

作者头像 李华
网站建设 2026/9/5 15:45:18

Arduino控制SG90 360度连续旋转舵机:脉宽控制与开环校准实战

这次我们来看一个容易被“先入为主”带偏的硬件控制问题&#xff1a;手头的 SG90 舵机&#xff0c;到底能不能当普通 180 度舵机用&#xff1f; 如果你买的型号是 SG90 360 度连续旋转舵机 &#xff0c;那么答案是不建议直接按角度写。它的真实控制方式是 writeMicrosecond…

作者头像 李华
网站建设 2026/9/5 15:44:52

从原理到实践:SG90舵机与Arduino UNO接线、供电与故障排查

很多玩 Arduino 的新手&#xff0c;第一次接触 SG90 时都会有一个直观感受&#xff1a;三根线、一个舵机臂&#xff0c;代码里写一句servo.write(90)&#xff0c;舵机就能转到中间位置。看起来非常简单&#xff0c;但真正上手后&#xff0c;问题往往一口气冒出来&#xff1a;为…

作者头像 李华