wlzboy
2025-09-21 7d81ce01560d384f15212edc40ebeaa9924913f9
feat:新增评价功能
37个文件已添加
11个文件已修改
6489 ■■■■■ 已修改文件
pom.xml 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/evaluation/EvaluationController.java 214 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/evaluation/EvaluationDimensionController.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/java/com/ruoyi/web/controller/evaluation/VehicleEvaluationQrcodeController.java 145 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-admin/src/main/resources/application.yml 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/pom.xml 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/QRCodeUtils.java 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-common/src/main/java/com/ruoyi/common/utils/WechatUtils.java 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/CustomerEvaluation.java 209 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/EvaluationDetail.java 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/EvaluationDimension.java 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleEvaluationQrcode.java 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleInfo.java 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/AOrderStatusMapper.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/CustomerEvaluationMapper.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/EvaluationDetailMapper.java 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/EvaluationDimensionMapper.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleEvaluationQrcodeMapper.java 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleInfoMapper.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/ICustomerEvaluationService.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/IEvaluationDimensionService.java 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleEvaluationQrcodeService.java 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/CustomerEvaluationServiceImpl.java 196 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/EvaluationDimensionServiceImpl.java 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleEvaluationQrcodeServiceImpl.java 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/CustomerEvaluationMapper.xml 159 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/EvaluationDetailMapper.xml 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/EvaluationDimensionMapper.xml 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/VehicleEvaluationQrcodeMapper.xml 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/VehicleInfoMapper.xml 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/api/evaluation.js 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/router/index.js 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/evaluation/customer/index.vue 297 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/evaluation/dimension/index.vue 325 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/evaluation/index.vue 659 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/evaluation/qrcode/index.vue 345 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/evaluation/statistics/index.vue 512 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/evaluation/test.vue 187 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-ui/src/views/system/vehicle/index.vue 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/customer_evaluation_tables.sql 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/evaluation_dict.sql 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/evaluation_menu.sql 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/vehicle_info.sql 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
完整部署指南.md 225 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
客户满意度评价功能说明.md 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
测试步骤.md 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
部署说明.md 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
pom.xml
@@ -30,6 +30,7 @@
        <poi.version>4.1.2</poi.version>
        <velocity.version>2.3</velocity.version>
        <jwt.version>0.9.1</jwt.version>
        <zxing.version>3.5.1</zxing.version>
        <!-- override dependency version -->
        <tomcat.version>9.0.102</tomcat.version>
        <logback.version>1.2.13</logback.version>
@@ -183,6 +184,19 @@
                <version>${kaptcha.version}</version>
            </dependency>
            <!-- äºŒç»´ç ç”Ÿæˆ -->
            <dependency>
                <groupId>com.google.zxing</groupId>
                <artifactId>core</artifactId>
                <version>${zxing.version}</version>
            </dependency>
            <dependency>
                <groupId>com.google.zxing</groupId>
                <artifactId>javase</artifactId>
                <version>${zxing.version}</version>
            </dependency>
            <!-- å®šæ—¶ä»»åŠ¡-->
            <dependency>
                <groupId>com.ruoyi</groupId>
ruoyi-admin/src/main/java/com/ruoyi/web/controller/evaluation/EvaluationController.java
New file
@@ -0,0 +1,214 @@
package com.ruoyi.web.controller.evaluation;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.common.utils.WechatUtils;
import com.ruoyi.common.utils.StringUtils;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.system.domain.EvaluationDetail;
import com.ruoyi.system.service.IEvaluationDimensionService;
import com.ruoyi.system.domain.EvaluationDimension;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.CustomerEvaluation;
import com.ruoyi.system.service.ICustomerEvaluationService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
/**
 * å®¢æˆ·è¯„ä»·Controller
 *
 * @author ruoyi
 * @date 2025-01-27
 */
@RestController
@RequestMapping("/evaluation")
public class EvaluationController extends BaseController {
    @Autowired
    private ICustomerEvaluationService customerEvaluationService;
    @Autowired
    private IEvaluationDimensionService evaluationDimensionService;
    /**
     * æŸ¥è¯¢å®¢æˆ·è¯„价列表
     */
    @PreAuthorize("@ss.hasPermi('evaluation:list')")
    @GetMapping("/list")
    public TableDataInfo list(CustomerEvaluation customerEvaluation) {
        startPage();
        List<CustomerEvaluation> list = customerEvaluationService.selectCustomerEvaluationList(customerEvaluation);
        return getDataTable(list);
    }
    /**
     * å¯¼å‡ºå®¢æˆ·è¯„价列表
     */
    @PreAuthorize("@ss.hasPermi('evaluation:export')")
    @Log(title = "客户评价", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, CustomerEvaluation customerEvaluation) {
        List<CustomerEvaluation> list = customerEvaluationService.selectCustomerEvaluationList(customerEvaluation);
        ExcelUtil<CustomerEvaluation> util = new ExcelUtil<CustomerEvaluation>(CustomerEvaluation.class);
        util.exportExcel(response, list, "客户评价数据");
    }
    /**
     * èŽ·å–å®¢æˆ·è¯„ä»·è¯¦ç»†ä¿¡æ¯
     */
    @PreAuthorize("@ss.hasPermi('evaluation:query')")
    @GetMapping(value = "/{evaluationId}")
    public AjaxResult getInfo(@PathVariable("evaluationId") Long evaluationId) {
        return success(customerEvaluationService.selectCustomerEvaluationByEvaluationId(evaluationId));
    }
    /**
     * æ–°å¢žå®¢æˆ·è¯„ä»·
     */
    @PreAuthorize("@ss.hasPermi('evaluation:add')")
    @Log(title = "客户评价", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody CustomerEvaluation customerEvaluation) {
        return toAjax(customerEvaluationService.insertCustomerEvaluation(customerEvaluation));
    }
    /**
     * ä¿®æ”¹å®¢æˆ·è¯„ä»·
     */
    @PreAuthorize("@ss.hasPermi('evaluation:edit')")
    @Log(title = "客户评价", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody CustomerEvaluation customerEvaluation) {
        return toAjax(customerEvaluationService.updateCustomerEvaluation(customerEvaluation));
    }
    /**
     * åˆ é™¤å®¢æˆ·è¯„ä»·
     */
    @PreAuthorize("@ss.hasPermi('evaluation:remove')")
    @Log(title = "客户评价", businessType = BusinessType.DELETE)
    @DeleteMapping("/{evaluationIds}")
    public AjaxResult remove(@PathVariable Long[] evaluationIds) {
        return toAjax(customerEvaluationService.deleteCustomerEvaluationByEvaluationIds(evaluationIds));
    }
    /**
     * èŽ·å–è¯„ä»·ç»´åº¦é…ç½®
     */
    @Anonymous
    @GetMapping("/dimensions")
    public AjaxResult getDimensions() {
        List<EvaluationDimension> dimensions = evaluationDimensionService.selectEnabledEvaluationDimensionList();
        return success(dimensions);
    }
    /**
     * æäº¤å®¢æˆ·è¯„价(公开接口)
     */
    @Anonymous
    @PostMapping("/submit")
    public AjaxResult submitEvaluation(@RequestBody CustomerEvaluation customerEvaluation, HttpServletRequest request) {
        try {
            // è®¾ç½®IP地址和用户代理
            customerEvaluation.setIpAddress(getClientIP(request));
            customerEvaluation.setUserAgent(request.getHeader("User-Agent"));
            // æäº¤è¯„ä»·
            int result = customerEvaluationService.submitCustomerEvaluation(customerEvaluation);
            if (result > 0) {
                // æ ¹æ®è¯„分返回不同的提示信息
                if (customerEvaluation.getTotalScore() != null && customerEvaluation.getTotalScore().doubleValue() < 3.0) {
                    return success("提交成功,我们将竭力改进");
                } else {
                    return success("提交成功,感谢您的参与");
                }
            } else {
                return error("提交失败,请重试");
            }
        } catch (Exception e) {
            logger.error("提交评价失败", e);
            return error("提交失败,请重试");
        }
    }
    @Value("${wechat.appId}")
    private String wechatAppId;
    @Value("${wechat.appSecret}")
    private String wechatAppSecret;
    /**
     * èŽ·å–å¾®ä¿¡ç”¨æˆ·ä¿¡æ¯
     */
    @Anonymous
    @GetMapping("/wechat/userinfo")
    public AjaxResult getWechatUserInfo(String code, HttpServletRequest request) {
        try {
            if (StringUtils.isEmpty(code)) {
                return error("授权码不能为空");
            }
            // èŽ·å–ç½‘é¡µæŽˆæƒAccess Token
            JSONObject tokenInfo = WechatUtils.getWebAccessToken(wechatAppId, wechatAppSecret, code);
            if (tokenInfo == null) {
                return error("获取微信授权信息失败");
            }
            String accessToken = tokenInfo.getString("access_token");
            String openid = tokenInfo.getString("openid");
            // èŽ·å–ç”¨æˆ·ä¿¡æ¯
            JSONObject userInfo = WechatUtils.getWebUserInfo(accessToken, openid);
            if (userInfo == null) {
                return error("获取微信用户信息失败");
            }
            return success(userInfo);
        } catch (Exception e) {
            logger.error("获取微信用户信息失败", e);
            return error("获取微信用户信息失败");
        }
    }
    /**
     * èŽ·å–å®¢æˆ·ç«¯IP地址
     */
    private String getClientIP(HttpServletRequest request) {
        String ip = request.getHeader("X-Forwarded-For");
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_CLIENT_IP");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getHeader("HTTP_X_FORWARDED_FOR");
        }
        if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }
}
ruoyi-admin/src/main/java/com/ruoyi/web/controller/evaluation/EvaluationDimensionController.java
New file
@@ -0,0 +1,97 @@
package com.ruoyi.web.controller.evaluation;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.EvaluationDimension;
import com.ruoyi.system.service.IEvaluationDimensionService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
/**
 * è¯„价维度配置Controller
 *
 * @author ruoyi
 * @date 2025-01-27
 */
@RestController
@RequestMapping("/evaluation/dimension")
public class EvaluationDimensionController extends BaseController {
    @Autowired
    private IEvaluationDimensionService evaluationDimensionService;
    /**
     * æŸ¥è¯¢è¯„价维度配置列表
     */
    @PreAuthorize("@ss.hasPermi('evaluation:dimension:list')")
    @GetMapping("/list")
    public TableDataInfo list(EvaluationDimension evaluationDimension) {
        startPage();
        List<EvaluationDimension> list = evaluationDimensionService.selectEvaluationDimensionList(evaluationDimension);
        return getDataTable(list);
    }
    /**
     * å¯¼å‡ºè¯„价维度配置列表
     */
    @PreAuthorize("@ss.hasPermi('evaluation:dimension:export')")
    @Log(title = "评价维度配置", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, EvaluationDimension evaluationDimension) {
        List<EvaluationDimension> list = evaluationDimensionService.selectEvaluationDimensionList(evaluationDimension);
        ExcelUtil<EvaluationDimension> util = new ExcelUtil<EvaluationDimension>(EvaluationDimension.class);
        util.exportExcel(response, list, "评价维度配置数据");
    }
    /**
     * èŽ·å–è¯„ä»·ç»´åº¦é…ç½®è¯¦ç»†ä¿¡æ¯
     */
    @PreAuthorize("@ss.hasPermi('evaluation:dimension:query')")
    @GetMapping(value = "/{dimensionId}")
    public AjaxResult getInfo(@PathVariable("dimensionId") Long dimensionId) {
        return success(evaluationDimensionService.selectEvaluationDimensionByDimensionId(dimensionId));
    }
    /**
     * æ–°å¢žè¯„价维度配置
     */
    @PreAuthorize("@ss.hasPermi('evaluation:dimension:add')")
    @Log(title = "评价维度配置", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody EvaluationDimension evaluationDimension) {
        return toAjax(evaluationDimensionService.insertEvaluationDimension(evaluationDimension));
    }
    /**
     * ä¿®æ”¹è¯„价维度配置
     */
    @PreAuthorize("@ss.hasPermi('evaluation:dimension:edit')")
    @Log(title = "评价维度配置", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody EvaluationDimension evaluationDimension) {
        return toAjax(evaluationDimensionService.updateEvaluationDimension(evaluationDimension));
    }
    /**
     * åˆ é™¤è¯„价维度配置
     */
    @PreAuthorize("@ss.hasPermi('evaluation:dimension:remove')")
    @Log(title = "评价维度配置", businessType = BusinessType.DELETE)
    @DeleteMapping("/{dimensionIds}")
    public AjaxResult remove(@PathVariable Long[] dimensionIds) {
        return toAjax(evaluationDimensionService.deleteEvaluationDimensionByDimensionIds(dimensionIds));
    }
}
ruoyi-admin/src/main/java/com/ruoyi/web/controller/evaluation/VehicleEvaluationQrcodeController.java
New file
@@ -0,0 +1,145 @@
package com.ruoyi.web.controller.evaluation;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.system.domain.VehicleEvaluationQrcode;
import com.ruoyi.system.service.IVehicleEvaluationQrcodeService;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.utils.StringUtils;
import com.alibaba.fastjson2.JSONObject;
/**
 * è½¦è¾†è¯„价二维码Controller
 *
 * @author ruoyi
 * @date 2025-01-27
 */
@RestController
@RequestMapping("/evaluation/qrcode")
public class VehicleEvaluationQrcodeController extends BaseController {
    @Autowired
    private IVehicleEvaluationQrcodeService vehicleEvaluationQrcodeService;
    /**
     * æŸ¥è¯¢è½¦è¾†è¯„价二维码列表
     */
    @PreAuthorize("@ss.hasPermi('evaluation:qrcode:list')")
    @GetMapping("/list")
    public TableDataInfo list(VehicleEvaluationQrcode vehicleEvaluationQrcode) {
        startPage();
        List<VehicleEvaluationQrcode> list = vehicleEvaluationQrcodeService.selectVehicleEvaluationQrcodeList(vehicleEvaluationQrcode);
        return getDataTable(list);
    }
    /**
     * å¯¼å‡ºè½¦è¾†è¯„价二维码列表
     */
    @PreAuthorize("@ss.hasPermi('evaluation:qrcode:export')")
    @Log(title = "车辆评价二维码", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, VehicleEvaluationQrcode vehicleEvaluationQrcode) {
        List<VehicleEvaluationQrcode> list = vehicleEvaluationQrcodeService.selectVehicleEvaluationQrcodeList(vehicleEvaluationQrcode);
        ExcelUtil<VehicleEvaluationQrcode> util = new ExcelUtil<VehicleEvaluationQrcode>(VehicleEvaluationQrcode.class);
        util.exportExcel(response, list, "车辆评价二维码数据");
    }
    /**
     * èŽ·å–è½¦è¾†è¯„ä»·äºŒç»´ç è¯¦ç»†ä¿¡æ¯
     */
    @PreAuthorize("@ss.hasPermi('evaluation:qrcode:query')")
    @GetMapping(value = "/{qrcodeId}")
    public AjaxResult getInfo(@PathVariable("qrcodeId") Long qrcodeId) {
        return success(vehicleEvaluationQrcodeService.selectVehicleEvaluationQrcodeByQrcodeId(qrcodeId));
    }
    /**
     * æ–°å¢žè½¦è¾†è¯„价二维码
     */
    @PreAuthorize("@ss.hasPermi('evaluation:qrcode:add')")
    @Log(title = "车辆评价二维码", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody VehicleEvaluationQrcode vehicleEvaluationQrcode) {
        return toAjax(vehicleEvaluationQrcodeService.insertVehicleEvaluationQrcode(vehicleEvaluationQrcode));
    }
    /**
     * ä¿®æ”¹è½¦è¾†è¯„价二维码
     */
    @PreAuthorize("@ss.hasPermi('evaluation:qrcode:edit')")
    @Log(title = "车辆评价二维码", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody VehicleEvaluationQrcode vehicleEvaluationQrcode) {
        return toAjax(vehicleEvaluationQrcodeService.updateVehicleEvaluationQrcode(vehicleEvaluationQrcode));
    }
    /**
     * åˆ é™¤è½¦è¾†è¯„价二维码
     */
    @PreAuthorize("@ss.hasPermi('evaluation:qrcode:remove')")
    @Log(title = "车辆评价二维码", businessType = BusinessType.DELETE)
    @DeleteMapping("/{qrcodeIds}")
    public AjaxResult remove(@PathVariable Long[] qrcodeIds) {
        return toAjax(vehicleEvaluationQrcodeService.deleteVehicleEvaluationQrcodeByQrcodeIds(qrcodeIds));
    }
    /**
     * ç”Ÿæˆè½¦è¾†è¯„价二维码
     */
    @PreAuthorize("@ss.hasPermi('evaluation:qrcode:generate')")
    @Log(title = "车辆评价二维码", businessType = BusinessType.INSERT)
    @PostMapping("/generate")
    public AjaxResult generateQrcode(@RequestBody JSONObject params) {
        try {
            String vehicleNo = params.getString("vehicleNo");
            String qrcodeUrl = params.getString("qrcodeUrl");
            if (StringUtils.isEmpty(vehicleNo)) {
                return error("车牌号不能为空");
            }
            if (StringUtils.isEmpty(qrcodeUrl)) {
                return error("二维码URL不能为空");
            }
            VehicleEvaluationQrcode qrcode = vehicleEvaluationQrcodeService.generateVehicleEvaluationQrcode(vehicleNo, qrcodeUrl);
            if (qrcode != null) {
                return success(qrcode);
            } else {
                return error("生成二维码失败");
            }
        } catch (Exception e) {
            logger.error("生成二维码失败", e);
            return error("生成二维码失败");
        }
    }
    /**
     * æ‰¹é‡ç”Ÿæˆè½¦è¾†è¯„价二维码
     */
    @PreAuthorize("@ss.hasPermi('evaluation:qrcode:batch')")
    @Log(title = "车辆评价二维码", businessType = BusinessType.INSERT)
    @PostMapping("/batch")
    public AjaxResult batchGenerateQrcode() {
        try {
            int count = vehicleEvaluationQrcodeService.batchGenerateVehicleEvaluationQrcode();
            return success("成功生成 " + count + " ä¸ªäºŒç»´ç ");
        } catch (Exception e) {
            logger.error("批量生成二维码失败", e);
            return error("批量生成二维码失败");
        }
    }
}
ruoyi-admin/src/main/resources/application.yml
@@ -56,7 +56,7 @@
    basename: i18n/messages
  profiles:
    # çŽ¯å¢ƒ dev|test|prod
    active: prod
    active: dev
  # æ–‡ä»¶ä¸Šä¼ 
  servlet:
    multipart:
@@ -144,3 +144,9 @@
# æ°‘航接口地址
min:
  apiUrl: http://120.25.98.119:8084/v1/   #测试环境:localhost:8011
# å¾®ä¿¡é…ç½®
wechat:
  appId: your_wechat_appid
  appSecret: your_wechat_appsecret
  redirectUri: http://yourdomain.com/evaluation
ruoyi-common/pom.xml
@@ -117,6 +117,17 @@
            <artifactId>UserAgentUtils</artifactId>
        </dependency>
        <!-- äºŒç»´ç ç”Ÿæˆ -->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
        </dependency>
        <!-- servlet包 -->
        <dependency>
            <groupId>javax.servlet</groupId>
ruoyi-common/src/main/java/com/ruoyi/common/utils/QRCodeUtils.java
New file
@@ -0,0 +1,98 @@
package com.ruoyi.common.utils;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.client.j2se.MatrixToImageWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
 * äºŒç»´ç ç”Ÿæˆå·¥å…·ç±»
 *
 * @author ruoyi
 */
public class QRCodeUtils {
    private static final Logger log = LoggerFactory.getLogger(QRCodeUtils.class);
    private static final int WIDTH = 300;
    private static final int HEIGHT = 300;
    private static final String FORMAT = "png";
    /**
     * ç”ŸæˆäºŒç»´ç åˆ°æ–‡ä»¶
     *
     * @param content äºŒç»´ç å†…容
     * @param filePath æ–‡ä»¶è·¯å¾„
     * @return æ˜¯å¦ç”ŸæˆæˆåŠŸ
     */
    public static boolean generateQRCodeToFile(String content, String filePath) {
        try {
            Map<EncodeHintType, Object> hints = new HashMap<>();
            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
            hints.put(EncodeHintType.MARGIN, 1);
            BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, WIDTH, HEIGHT, hints);
            File file = new File(filePath);
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            MatrixToImageWriter.writeToFile(bitMatrix, FORMAT, file);
            return true;
        } catch (Exception e) {
            log.error("生成二维码失败: {}", e.getMessage());
            return false;
        }
    }
    /**
     * ç”ŸæˆäºŒç»´ç åˆ°å­—节数组
     *
     * @param content äºŒç»´ç å†…容
     * @return å­—节数组
     */
    public static byte[] generateQRCodeToBytes(String content) {
        try {
            Map<EncodeHintType, Object> hints = new HashMap<>();
            hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
            hints.put(EncodeHintType.MARGIN, 1);
            BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, WIDTH, HEIGHT, hints);
            BufferedImage image = MatrixToImageWriter.toBufferedImage(bitMatrix);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ImageIO.write(image, FORMAT, baos);
            return baos.toByteArray();
        } catch (Exception e) {
            log.error("生成二维码失败: {}", e.getMessage());
            return null;
        }
    }
    /**
     * ç”ŸæˆBase64编码的二维码
     *
     * @param content äºŒç»´ç å†…容
     * @return Base64编码的二维码
     */
    public static String generateQRCodeToBase64(String content) {
        byte[] bytes = generateQRCodeToBytes(content);
        if (bytes != null) {
            return "data:image/png;base64," + java.util.Base64.getEncoder().encodeToString(bytes);
        }
        return null;
    }
}
ruoyi-common/src/main/java/com/ruoyi/common/utils/WechatUtils.java
New file
@@ -0,0 +1,163 @@
package com.ruoyi.common.utils;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
/**
 * å¾®ä¿¡å·¥å…·ç±»
 *
 * @author ruoyi
 */
public class WechatUtils {
    private static final Logger log = LoggerFactory.getLogger(WechatUtils.class);
    private static final String WECHAT_API_BASE_URL = "https://api.weixin.qq.com";
    /**
     * èŽ·å–å¾®ä¿¡Access Token
     *
     * @param appId å¾®ä¿¡AppID
     * @param appSecret å¾®ä¿¡AppSecret
     * @return Access Token
     */
    public static String getAccessToken(String appId, String appSecret) {
        try {
            String url = WECHAT_API_BASE_URL + "/cgi-bin/token?grant_type=client_credential&appid=" + appId + "&secret=" + appSecret;
            RestTemplate restTemplate = new RestTemplate();
            String response = restTemplate.getForObject(url, String.class);
            JSONObject jsonObject = JSON.parseObject(response);
            if (jsonObject.containsKey("access_token")) {
                return jsonObject.getString("access_token");
            } else {
                log.error("获取微信Access Token失败: {}", response);
                return null;
            }
        } catch (Exception e) {
            log.error("获取微信Access Token异常: {}", e.getMessage());
            return null;
        }
    }
    /**
     * èŽ·å–å¾®ä¿¡ç”¨æˆ·ä¿¡æ¯
     *
     * @param accessToken Access Token
     * @param openid ç”¨æˆ·OpenID
     * @return ç”¨æˆ·ä¿¡æ¯
     */
    public static JSONObject getWechatUserInfo(String accessToken, String openid) {
        try {
            String url = WECHAT_API_BASE_URL + "/cgi-bin/user/info?access_token=" + accessToken + "&openid=" + openid + "&lang=zh_CN";
            RestTemplate restTemplate = new RestTemplate();
            String response = restTemplate.getForObject(url, String.class);
            JSONObject jsonObject = JSON.parseObject(response);
            if (jsonObject.containsKey("openid")) {
                return jsonObject;
            } else {
                log.error("获取微信用户信息失败: {}", response);
                return null;
            }
        } catch (Exception e) {
            log.error("获取微信用户信息异常: {}", e.getMessage());
            return null;
        }
    }
    /**
     * èŽ·å–å¾®ä¿¡ç½‘é¡µæŽˆæƒAccess Token
     *
     * @param appId å¾®ä¿¡AppID
     * @param appSecret å¾®ä¿¡AppSecret
     * @param code æŽˆæƒç 
     * @return ç½‘页授权Access Token信息
     */
    public static JSONObject getWebAccessToken(String appId, String appSecret, String code) {
        try {
            String url = WECHAT_API_BASE_URL + "/sns/oauth2/access_token?appid=" + appId + "&secret=" + appSecret + "&code=" + code + "&grant_type=authorization_code";
            RestTemplate restTemplate = new RestTemplate();
            String response = restTemplate.getForObject(url, String.class);
            JSONObject jsonObject = JSON.parseObject(response);
            if (jsonObject.containsKey("access_token")) {
                return jsonObject;
            } else {
                log.error("获取微信网页授权Access Token失败: {}", response);
                return null;
            }
        } catch (Exception e) {
            log.error("获取微信网页授权Access Token异常: {}", e.getMessage());
            return null;
        }
    }
    /**
     * èŽ·å–å¾®ä¿¡ç½‘é¡µæŽˆæƒç”¨æˆ·ä¿¡æ¯
     *
     * @param accessToken ç½‘页授权Access Token
     * @param openid ç”¨æˆ·OpenID
     * @return ç”¨æˆ·ä¿¡æ¯
     */
    public static JSONObject getWebUserInfo(String accessToken, String openid) {
        try {
            String url = WECHAT_API_BASE_URL + "/sns/userinfo?access_token=" + accessToken + "&openid=" + openid + "&lang=zh_CN";
            RestTemplate restTemplate = new RestTemplate();
            String response = restTemplate.getForObject(url, String.class);
            JSONObject jsonObject = JSON.parseObject(response);
            if (jsonObject.containsKey("openid")) {
                return jsonObject;
            } else {
                log.error("获取微信网页授权用户信息失败: {}", response);
                return null;
            }
        } catch (Exception e) {
            log.error("获取微信网页授权用户信息异常: {}", e.getMessage());
            return null;
        }
    }
    /**
     * ç”Ÿæˆå¾®ä¿¡ç½‘页授权URL
     *
     * @param appId å¾®ä¿¡AppID
     * @param redirectUri å›žè°ƒåœ°å€
     * @param scope æŽˆæƒèŒƒå›´ (snsapi_base æˆ– snsapi_userinfo)
     * @param state çŠ¶æ€å‚æ•°
     * @return æŽˆæƒURL
     */
    public static String generateAuthUrl(String appId, String redirectUri, String scope, String state) {
        try {
            String encodedRedirectUri = java.net.URLEncoder.encode(redirectUri, "UTF-8");
            return WECHAT_API_BASE_URL + "/connect/oauth2/authorize?appid=" + appId + "&redirect_uri=" + encodedRedirectUri + "&response_type=code&scope=" + scope + "&state=" + state + "#wechat_redirect";
        } catch (Exception e) {
            log.error("生成微信授权URL异常: {}", e.getMessage());
            return null;
        }
    }
    /**
     * åˆ¤æ–­æ˜¯å¦ä¸ºå¾®ä¿¡æµè§ˆå™¨
     *
     * @param userAgent ç”¨æˆ·ä»£ç†å­—符串
     * @return æ˜¯å¦ä¸ºå¾®ä¿¡æµè§ˆå™¨
     */
    public static boolean isWechatBrowser(String userAgent) {
        if (StringUtils.isEmpty(userAgent)) {
            return false;
        }
        return userAgent.toLowerCase().contains("micromessenger");
    }
}
ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java
@@ -37,6 +37,10 @@
        registry.addResourceHandler(Constants.RESOURCE_PREFIX + "/**")
                .addResourceLocations("file:" + RuoYiConfig.getProfile() + "/");
        /** äºŒç»´ç å›¾ç‰‡è®¿é—®è·¯å¾„ */
        registry.addResourceHandler("/qrcode/**")
                .addResourceLocations("file:" + RuoYiConfig.getProfile() + "/qrcode/");
        /** swagger配置 */
        registry.addResourceHandler("/swagger-ui/**")
                .addResourceLocations("classpath:/META-INF/resources/webjars/springfox-swagger-ui/")
ruoyi-system/src/main/java/com/ruoyi/system/domain/CustomerEvaluation.java
New file
@@ -0,0 +1,209 @@
package com.ruoyi.system.domain;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
/**
 * å®¢æˆ·è¯„价对象 customer_evaluation
 *
 * @author ruoyi
 * @date 2025-01-27
 */
public class CustomerEvaluation extends BaseEntity {
    private static final long serialVersionUID = 1L;
    /** è¯„ä»·ID */
    private Long evaluationId;
    /** è½¦ç‰Œå· */
    @Excel(name = "车牌号")
    private String vehicleNo;
    /** å®¢æˆ·å§“名 */
    @Excel(name = "客户姓名")
    private String customerName;
    /** å®¢æˆ·æ‰‹æœºå· */
    @Excel(name = "客户手机号")
    private String customerPhone;
    /** å¾®ä¿¡OpenID */
    @Excel(name = "微信OpenID")
    private String wechatOpenid;
    /** å¾®ä¿¡æ˜µç§° */
    @Excel(name = "微信昵称")
    private String wechatNickname;
    /** å¾®ä¿¡å¤´åƒ */
    @Excel(name = "微信头像")
    private String wechatAvatar;
    /** å¾®ä¿¡ç»‘定手机号 */
    @Excel(name = "微信绑定手机号")
    private String wechatPhone;
    /** æ€»è¯„分 */
    @Excel(name = "总评分")
    private BigDecimal totalScore;
    /** è¯„价状态(0待评价 1已评价) */
    @Excel(name = "评价状态", readConverterExp = "0=待评价,1=已评价")
    private String evaluationStatus;
    /** è¯„ä»·æ—¶é—´ */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Excel(name = "评价时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
    private Date evaluationTime;
    /** IP地址 */
    @Excel(name = "IP地址")
    private String ipAddress;
    /** ç”¨æˆ·ä»£ç† */
    @Excel(name = "用户代理")
    private String userAgent;
    /** è¯„价详情列表 */
    private List<EvaluationDetail> evaluationDetails;
    public void setEvaluationId(Long evaluationId) {
        this.evaluationId = evaluationId;
    }
    public Long getEvaluationId() {
        return evaluationId;
    }
    public void setVehicleNo(String vehicleNo) {
        this.vehicleNo = vehicleNo;
    }
    public String getVehicleNo() {
        return vehicleNo;
    }
    public void setCustomerName(String customerName) {
        this.customerName = customerName;
    }
    public String getCustomerName() {
        return customerName;
    }
    public void setCustomerPhone(String customerPhone) {
        this.customerPhone = customerPhone;
    }
    public String getCustomerPhone() {
        return customerPhone;
    }
    public void setWechatOpenid(String wechatOpenid) {
        this.wechatOpenid = wechatOpenid;
    }
    public String getWechatOpenid() {
        return wechatOpenid;
    }
    public void setWechatNickname(String wechatNickname) {
        this.wechatNickname = wechatNickname;
    }
    public String getWechatNickname() {
        return wechatNickname;
    }
    public void setWechatAvatar(String wechatAvatar) {
        this.wechatAvatar = wechatAvatar;
    }
    public String getWechatAvatar() {
        return wechatAvatar;
    }
    public void setWechatPhone(String wechatPhone) {
        this.wechatPhone = wechatPhone;
    }
    public String getWechatPhone() {
        return wechatPhone;
    }
    public void setTotalScore(BigDecimal totalScore) {
        this.totalScore = totalScore;
    }
    public BigDecimal getTotalScore() {
        return totalScore;
    }
    public void setEvaluationStatus(String evaluationStatus) {
        this.evaluationStatus = evaluationStatus;
    }
    public String getEvaluationStatus() {
        return evaluationStatus;
    }
    public void setEvaluationTime(Date evaluationTime) {
        this.evaluationTime = evaluationTime;
    }
    public Date getEvaluationTime() {
        return evaluationTime;
    }
    public void setIpAddress(String ipAddress) {
        this.ipAddress = ipAddress;
    }
    public String getIpAddress() {
        return ipAddress;
    }
    public void setUserAgent(String userAgent) {
        this.userAgent = userAgent;
    }
    public String getUserAgent() {
        return userAgent;
    }
    public List<EvaluationDetail> getEvaluationDetails() {
        return evaluationDetails;
    }
    public void setEvaluationDetails(List<EvaluationDetail> evaluationDetails) {
        this.evaluationDetails = evaluationDetails;
    }
    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
                .append("evaluationId", getEvaluationId())
                .append("vehicleNo", getVehicleNo())
                .append("customerName", getCustomerName())
                .append("customerPhone", getCustomerPhone())
                .append("wechatOpenid", getWechatOpenid())
                .append("wechatNickname", getWechatNickname())
                .append("wechatAvatar", getWechatAvatar())
                .append("wechatPhone", getWechatPhone())
                .append("totalScore", getTotalScore())
                .append("evaluationStatus", getEvaluationStatus())
                .append("evaluationTime", getEvaluationTime())
                .append("ipAddress", getIpAddress())
                .append("userAgent", getUserAgent())
                .append("createTime", getCreateTime())
                .append("updateTime", getUpdateTime())
                .append("remark", getRemark())
                .toString();
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/EvaluationDetail.java
New file
@@ -0,0 +1,126 @@
package com.ruoyi.system.domain;
import java.util.Date;
import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
/**
 * è¯„价详情对象 evaluation_detail
 *
 * @author ruoyi
 * @date 2025-01-27
 */
public class EvaluationDetail extends BaseEntity {
    private static final long serialVersionUID = 1L;
    /** è¯¦æƒ…ID */
    private Long detailId;
    /** è¯„ä»·ID */
    @Excel(name = "评价ID")
    private Long evaluationId;
    /** ç»´åº¦ID */
    @Excel(name = "维度ID")
    private Long dimensionId;
    /** è¯„分(1-5星) */
    @Excel(name = "评分")
    private Integer score;
    /** é€‰é¡¹å€¼ï¼ˆé€‰æ‹©ç±»åž‹æ—¶ä½¿ç”¨ï¼‰ */
    @Excel(name = "选项值")
    private String optionValue;
    /** æ–‡æœ¬å†…容(文本类型时使用) */
    @Excel(name = "文本内容")
    private String textContent;
    /** åˆ›å»ºæ—¶é—´ */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    @Excel(name = "创建时间", width = 30, dateFormat = "yyyy-MM-dd HH:mm:ss")
    private Date createTime;
    /** ç»´åº¦ä¿¡æ¯ */
    private EvaluationDimension dimension;
    public void setDetailId(Long detailId) {
        this.detailId = detailId;
    }
    public Long getDetailId() {
        return detailId;
    }
    public void setEvaluationId(Long evaluationId) {
        this.evaluationId = evaluationId;
    }
    public Long getEvaluationId() {
        return evaluationId;
    }
    public void setDimensionId(Long dimensionId) {
        this.dimensionId = dimensionId;
    }
    public Long getDimensionId() {
        return dimensionId;
    }
    public void setScore(Integer score) {
        this.score = score;
    }
    public Integer getScore() {
        return score;
    }
    public void setOptionValue(String optionValue) {
        this.optionValue = optionValue;
    }
    public String getOptionValue() {
        return optionValue;
    }
    public void setTextContent(String textContent) {
        this.textContent = textContent;
    }
    public String getTextContent() {
        return textContent;
    }
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
    public Date getCreateTime() {
        return createTime;
    }
    public EvaluationDimension getDimension() {
        return dimension;
    }
    public void setDimension(EvaluationDimension dimension) {
        this.dimension = dimension;
    }
    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
                .append("detailId", getDetailId())
                .append("evaluationId", getEvaluationId())
                .append("dimensionId", getDimensionId())
                .append("score", getScore())
                .append("optionValue", getOptionValue())
                .append("textContent", getTextContent())
                .append("createTime", getCreateTime())
                .toString();
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/EvaluationDimension.java
New file
@@ -0,0 +1,129 @@
package com.ruoyi.system.domain;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
/**
 * è¯„价维度配置对象 evaluation_dimension
 *
 * @author ruoyi
 * @date 2025-01-27
 */
public class EvaluationDimension extends BaseEntity {
    private static final long serialVersionUID = 1L;
    /** ç»´åº¦ID */
    private Long dimensionId;
    /** ç»´åº¦åç§° */
    @Excel(name = "维度名称")
    private String dimensionName;
    /** ç»´åº¦æè¿° */
    @Excel(name = "维度描述")
    private String dimensionDesc;
    /** ç»´åº¦ç±»åž‹ï¼šstar-星级评价,select-选择评价,text-文本评价 */
    @Excel(name = "维度类型", readConverterExp = "star=星级评价,select=选择评价,text=文本评价")
    private String dimensionType;
    /** é€‰é¡¹é…ç½®ï¼ˆJSON格式,用于选择类型) */
    private String options;
    /** æŽ’序 */
    @Excel(name = "排序")
    private Integer sortOrder;
    /** æ˜¯å¦å¿…填(0否 1是) */
    @Excel(name = "是否必填", readConverterExp = "0=否,1=是")
    private String isRequired;
    /** çŠ¶æ€ï¼ˆ0正常 1停用) */
    @Excel(name = "状态", readConverterExp = "0=正常,1=停用")
    private String status;
    public void setDimensionId(Long dimensionId) {
        this.dimensionId = dimensionId;
    }
    public Long getDimensionId() {
        return dimensionId;
    }
    public void setDimensionName(String dimensionName) {
        this.dimensionName = dimensionName;
    }
    public String getDimensionName() {
        return dimensionName;
    }
    public void setDimensionDesc(String dimensionDesc) {
        this.dimensionDesc = dimensionDesc;
    }
    public String getDimensionDesc() {
        return dimensionDesc;
    }
    public void setDimensionType(String dimensionType) {
        this.dimensionType = dimensionType;
    }
    public String getDimensionType() {
        return dimensionType;
    }
    public void setOptions(String options) {
        this.options = options;
    }
    public String getOptions() {
        return options;
    }
    public void setSortOrder(Integer sortOrder) {
        this.sortOrder = sortOrder;
    }
    public Integer getSortOrder() {
        return sortOrder;
    }
    public void setIsRequired(String isRequired) {
        this.isRequired = isRequired;
    }
    public String getIsRequired() {
        return isRequired;
    }
    public void setStatus(String status) {
        this.status = status;
    }
    public String getStatus() {
        return status;
    }
    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
                .append("dimensionId", getDimensionId())
                .append("dimensionName", getDimensionName())
                .append("dimensionDesc", getDimensionDesc())
                .append("dimensionType", getDimensionType())
                .append("options", getOptions())
                .append("sortOrder", getSortOrder())
                .append("isRequired", getIsRequired())
                .append("status", getStatus())
                .append("createBy", getCreateBy())
                .append("createTime", getCreateTime())
                .append("updateBy", getUpdateBy())
                .append("updateTime", getUpdateTime())
                .append("remark", getRemark())
                .toString();
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleEvaluationQrcode.java
New file
@@ -0,0 +1,104 @@
package com.ruoyi.system.domain;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.core.domain.BaseEntity;
/**
 * è½¦è¾†è¯„价二维码对象 vehicle_evaluation_qrcode
 *
 * @author ruoyi
 * @date 2025-01-27
 */
public class VehicleEvaluationQrcode extends BaseEntity {
    private static final long serialVersionUID = 1L;
    /** äºŒç»´ç ID */
    private Long qrcodeId;
    /** è½¦ç‰Œå· */
    @Excel(name = "车牌号")
    private String vehicleNo;
    /** äºŒç»´ç URL */
    @Excel(name = "二维码URL")
    private String qrcodeUrl;
    /** äºŒç»´ç å†…容 */
    @Excel(name = "二维码内容")
    private String qrcodeContent;
    /** äºŒç»´ç å›¾ç‰‡è·¯å¾„ */
    @Excel(name = "二维码图片路径")
    private String qrcodeImage;
    /** çŠ¶æ€ï¼ˆ0正常 1停用) */
    @Excel(name = "状态", readConverterExp = "0=正常,1=停用")
    private String status;
    public void setQrcodeId(Long qrcodeId) {
        this.qrcodeId = qrcodeId;
    }
    public Long getQrcodeId() {
        return qrcodeId;
    }
    public void setVehicleNo(String vehicleNo) {
        this.vehicleNo = vehicleNo;
    }
    public String getVehicleNo() {
        return vehicleNo;
    }
    public void setQrcodeUrl(String qrcodeUrl) {
        this.qrcodeUrl = qrcodeUrl;
    }
    public String getQrcodeUrl() {
        return qrcodeUrl;
    }
    public void setQrcodeContent(String qrcodeContent) {
        this.qrcodeContent = qrcodeContent;
    }
    public String getQrcodeContent() {
        return qrcodeContent;
    }
    public void setQrcodeImage(String qrcodeImage) {
        this.qrcodeImage = qrcodeImage;
    }
    public String getQrcodeImage() {
        return qrcodeImage;
    }
    public void setStatus(String status) {
        this.status = status;
    }
    public String getStatus() {
        return status;
    }
    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
                .append("qrcodeId", getQrcodeId())
                .append("vehicleNo", getVehicleNo())
                .append("qrcodeUrl", getQrcodeUrl())
                .append("qrcodeContent", getQrcodeContent())
                .append("qrcodeImage", getQrcodeImage())
                .append("status", getStatus())
                .append("createBy", getCreateBy())
                .append("createTime", getCreateTime())
                .append("updateBy", getUpdateBy())
                .append("updateTime", getUpdateTime())
                .append("remark", getRemark())
                .toString();
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleInfo.java
@@ -42,6 +42,14 @@
    @Excel(name = "平台标识")
    private String platformCode;
    /** å½’属部门ID */
    @Excel(name = "归属部门ID")
    private Long deptId;
    /** å½’属部门名称 */
    @Excel(name = "归属部门名称")
    private String deptName;
    public void setVehicleId(Long vehicleId) {
        this.vehicleId = vehicleId;
    }
@@ -106,6 +114,22 @@
        this.platformCode = platformCode;
    }
    public Long getDeptId() {
        return deptId;
    }
    public void setDeptId(Long deptId) {
        this.deptId = deptId;
    }
    public String getDeptName() {
        return deptName;
    }
    public void setDeptName(String deptName) {
        this.deptName = deptName;
    }
    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
@@ -117,6 +141,8 @@
                .append("vehicleModel", getVehicleModel())
                .append("status", getStatus())
                .append("platformCode", getPlatformCode())
                .append("deptId", getDeptId())
                .append("deptName", getDeptName())
                .append("createBy", getCreateBy())
                .append("createTime", getCreateTime())
                .append("updateBy", getUpdateBy())
ruoyi-system/src/main/java/com/ruoyi/system/mapper/AOrderStatusMapper.java
@@ -11,7 +11,7 @@
     * æŸ¥è¯¢è®¢å•状态列表
     * é»˜è®¤æŸ¥è¯¢ flag = 0 çš„记录
     * 
     * @param aOrderStatus è®¢å•状态信息
     * @param  è®¢å•状态信息
     * @return è®¢å•状态集合
     */
    public List<AOrderStatus> selectAOrderStatusList();
ruoyi-system/src/main/java/com/ruoyi/system/mapper/CustomerEvaluationMapper.java
New file
@@ -0,0 +1,84 @@
package com.ruoyi.system.mapper;
import java.util.List;
import com.ruoyi.system.domain.CustomerEvaluation;
/**
 * å®¢æˆ·è¯„ä»·Mapper接口
 *
 * @author ruoyi
 * @date 2025-01-27
 */
public interface CustomerEvaluationMapper {
    /**
     * æŸ¥è¯¢å®¢æˆ·è¯„ä»·
     *
     * @param evaluationId å®¢æˆ·è¯„价主键
     * @return å®¢æˆ·è¯„ä»·
     */
    public CustomerEvaluation selectCustomerEvaluationByEvaluationId(Long evaluationId);
    /**
     * æŸ¥è¯¢å®¢æˆ·è¯„价列表
     *
     * @param customerEvaluation å®¢æˆ·è¯„ä»·
     * @return å®¢æˆ·è¯„价集合
     */
    public List<CustomerEvaluation> selectCustomerEvaluationList(CustomerEvaluation customerEvaluation);
    /**
     * æ ¹æ®è½¦ç‰Œå·æŸ¥è¯¢å®¢æˆ·è¯„ä»·
     *
     * @param vehicleNo è½¦ç‰Œå·
     * @return å®¢æˆ·è¯„价集合
     */
    public List<CustomerEvaluation> selectCustomerEvaluationByVehicleNo(String vehicleNo);
    /**
     * æ ¹æ®å¾®ä¿¡OpenID查询客户评价
     *
     * @param wechatOpenid å¾®ä¿¡OpenID
     * @return å®¢æˆ·è¯„价集合
     */
    public List<CustomerEvaluation> selectCustomerEvaluationByWechatOpenid(String wechatOpenid);
    /**
     * æ–°å¢žå®¢æˆ·è¯„ä»·
     *
     * @param customerEvaluation å®¢æˆ·è¯„ä»·
     * @return ç»“æžœ
     */
    public int insertCustomerEvaluation(CustomerEvaluation customerEvaluation);
    /**
     * ä¿®æ”¹å®¢æˆ·è¯„ä»·
     *
     * @param customerEvaluation å®¢æˆ·è¯„ä»·
     * @return ç»“æžœ
     */
    public int updateCustomerEvaluation(CustomerEvaluation customerEvaluation);
    /**
     * åˆ é™¤å®¢æˆ·è¯„ä»·
     *
     * @param evaluationId å®¢æˆ·è¯„价主键
     * @return ç»“æžœ
     */
    public int deleteCustomerEvaluationByEvaluationId(Long evaluationId);
    /**
     * æ‰¹é‡åˆ é™¤å®¢æˆ·è¯„ä»·
     *
     * @param evaluationIds éœ€è¦åˆ é™¤çš„æ•°æ®ä¸»é”®é›†åˆ
     * @return ç»“æžœ
     */
    public int deleteCustomerEvaluationByEvaluationIds(Long[] evaluationIds);
    /**
     * ç»Ÿè®¡è¯„价数量
     *
     * @param customerEvaluation å®¢æˆ·è¯„ä»·
     * @return è¯„价统计信息
     */
    public List<CustomerEvaluation> selectEvaluationStatistics(CustomerEvaluation customerEvaluation);
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/EvaluationDetailMapper.java
New file
@@ -0,0 +1,84 @@
package com.ruoyi.system.mapper;
import java.util.List;
import com.ruoyi.system.domain.EvaluationDetail;
/**
 * è¯„价详情Mapper接口
 *
 * @author ruoyi
 * @date 2025-01-27
 */
public interface EvaluationDetailMapper {
    /**
     * æŸ¥è¯¢è¯„价详情
     *
     * @param detailId è¯„价详情主键
     * @return è¯„价详情
     */
    public EvaluationDetail selectEvaluationDetailByDetailId(Long detailId);
    /**
     * æŸ¥è¯¢è¯„价详情列表
     *
     * @param evaluationDetail è¯„价详情
     * @return è¯„价详情集合
     */
    public List<EvaluationDetail> selectEvaluationDetailList(EvaluationDetail evaluationDetail);
    /**
     * æ ¹æ®è¯„ä»·ID查询评价详情列表
     *
     * @param evaluationId è¯„ä»·ID
     * @return è¯„价详情集合
     */
    public List<EvaluationDetail> selectEvaluationDetailByEvaluationId(Long evaluationId);
    /**
     * æ–°å¢žè¯„价详情
     *
     * @param evaluationDetail è¯„价详情
     * @return ç»“æžœ
     */
    public int insertEvaluationDetail(EvaluationDetail evaluationDetail);
    /**
     * æ‰¹é‡æ–°å¢žè¯„价详情
     *
     * @param evaluationDetails è¯„价详情列表
     * @return ç»“æžœ
     */
    public int insertEvaluationDetailBatch(List<EvaluationDetail> evaluationDetails);
    /**
     * ä¿®æ”¹è¯„价详情
     *
     * @param evaluationDetail è¯„价详情
     * @return ç»“æžœ
     */
    public int updateEvaluationDetail(EvaluationDetail evaluationDetail);
    /**
     * åˆ é™¤è¯„价详情
     *
     * @param detailId è¯„价详情主键
     * @return ç»“æžœ
     */
    public int deleteEvaluationDetailByDetailId(Long detailId);
    /**
     * æ ¹æ®è¯„ä»·ID删除评价详情
     *
     * @param evaluationId è¯„ä»·ID
     * @return ç»“æžœ
     */
    public int deleteEvaluationDetailByEvaluationId(Long evaluationId);
    /**
     * æ‰¹é‡åˆ é™¤è¯„价详情
     *
     * @param detailIds éœ€è¦åˆ é™¤çš„æ•°æ®ä¸»é”®é›†åˆ
     * @return ç»“æžœ
     */
    public int deleteEvaluationDetailByDetailIds(Long[] detailIds);
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/EvaluationDimensionMapper.java
New file
@@ -0,0 +1,67 @@
package com.ruoyi.system.mapper;
import java.util.List;
import com.ruoyi.system.domain.EvaluationDimension;
/**
 * è¯„价维度配置Mapper接口
 *
 * @author ruoyi
 * @date 2025-01-27
 */
public interface EvaluationDimensionMapper {
    /**
     * æŸ¥è¯¢è¯„价维度配置
     *
     * @param dimensionId è¯„价维度配置主键
     * @return è¯„价维度配置
     */
    public EvaluationDimension selectEvaluationDimensionByDimensionId(Long dimensionId);
    /**
     * æŸ¥è¯¢è¯„价维度配置列表
     *
     * @param evaluationDimension è¯„价维度配置
     * @return è¯„价维度配置集合
     */
    public List<EvaluationDimension> selectEvaluationDimensionList(EvaluationDimension evaluationDimension);
    /**
     * æŸ¥è¯¢å¯ç”¨çš„评价维度配置列表
     *
     * @return è¯„价维度配置集合
     */
    public List<EvaluationDimension> selectEnabledEvaluationDimensionList();
    /**
     * æ–°å¢žè¯„价维度配置
     *
     * @param evaluationDimension è¯„价维度配置
     * @return ç»“æžœ
     */
    public int insertEvaluationDimension(EvaluationDimension evaluationDimension);
    /**
     * ä¿®æ”¹è¯„价维度配置
     *
     * @param evaluationDimension è¯„价维度配置
     * @return ç»“æžœ
     */
    public int updateEvaluationDimension(EvaluationDimension evaluationDimension);
    /**
     * åˆ é™¤è¯„价维度配置
     *
     * @param dimensionId è¯„价维度配置主键
     * @return ç»“æžœ
     */
    public int deleteEvaluationDimensionByDimensionId(Long dimensionId);
    /**
     * æ‰¹é‡åˆ é™¤è¯„价维度配置
     *
     * @param dimensionIds éœ€è¦åˆ é™¤çš„æ•°æ®ä¸»é”®é›†åˆ
     * @return ç»“æžœ
     */
    public int deleteEvaluationDimensionByDimensionIds(Long[] dimensionIds);
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleEvaluationQrcodeMapper.java
New file
@@ -0,0 +1,76 @@
package com.ruoyi.system.mapper;
import java.util.List;
import com.ruoyi.system.domain.VehicleEvaluationQrcode;
/**
 * è½¦è¾†è¯„价二维码Mapper接口
 *
 * @author ruoyi
 * @date 2025-01-27
 */
public interface VehicleEvaluationQrcodeMapper {
    /**
     * æŸ¥è¯¢è½¦è¾†è¯„价二维码
     *
     * @param qrcodeId è½¦è¾†è¯„价二维码主键
     * @return è½¦è¾†è¯„价二维码
     */
    public VehicleEvaluationQrcode selectVehicleEvaluationQrcodeByQrcodeId(Long qrcodeId);
    /**
     * æ ¹æ®è½¦ç‰Œå·æŸ¥è¯¢è½¦è¾†è¯„价二维码
     *
     * @param vehicleNo è½¦ç‰Œå·
     * @return è½¦è¾†è¯„价二维码
     */
    public VehicleEvaluationQrcode selectVehicleEvaluationQrcodeByVehicleNo(String vehicleNo);
    /**
     * æŸ¥è¯¢è½¦è¾†è¯„价二维码列表
     *
     * @param vehicleEvaluationQrcode è½¦è¾†è¯„价二维码
     * @return è½¦è¾†è¯„价二维码集合
     */
    public List<VehicleEvaluationQrcode> selectVehicleEvaluationQrcodeList(VehicleEvaluationQrcode vehicleEvaluationQrcode);
    /**
     * æ–°å¢žè½¦è¾†è¯„价二维码
     *
     * @param vehicleEvaluationQrcode è½¦è¾†è¯„价二维码
     * @return ç»“æžœ
     */
    public int insertVehicleEvaluationQrcode(VehicleEvaluationQrcode vehicleEvaluationQrcode);
    /**
     * ä¿®æ”¹è½¦è¾†è¯„价二维码
     *
     * @param vehicleEvaluationQrcode è½¦è¾†è¯„价二维码
     * @return ç»“æžœ
     */
    public int updateVehicleEvaluationQrcode(VehicleEvaluationQrcode vehicleEvaluationQrcode);
    /**
     * åˆ é™¤è½¦è¾†è¯„价二维码
     *
     * @param qrcodeId è½¦è¾†è¯„价二维码主键
     * @return ç»“æžœ
     */
    public int deleteVehicleEvaluationQrcodeByQrcodeId(Long qrcodeId);
    /**
     * æ ¹æ®è½¦ç‰Œå·åˆ é™¤è½¦è¾†è¯„价二维码
     *
     * @param vehicleNo è½¦ç‰Œå·
     * @return ç»“æžœ
     */
    public int deleteVehicleEvaluationQrcodeByVehicleNo(String vehicleNo);
    /**
     * æ‰¹é‡åˆ é™¤è½¦è¾†è¯„价二维码
     *
     * @param qrcodeIds éœ€è¦åˆ é™¤çš„æ•°æ®ä¸»é”®é›†åˆ
     * @return ç»“æžœ
     */
    public int deleteVehicleEvaluationQrcodeByQrcodeIds(Long[] qrcodeIds);
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleInfoMapper.java
@@ -24,6 +24,14 @@
    public VehicleInfo selectVehicleInfoByPlateNumber(String plateNumber);
    /**
     * é€šè¿‡è½¦ç‰Œå·æŸ¥è¯¢è½¦è¾†ä¿¡æ¯
     *
     * @param vehicleNo è½¦ç‰Œå·
     * @return è½¦è¾†ä¿¡æ¯
     */
    public VehicleInfo selectVehicleInfoByVehicleNo(String vehicleNo);
    /**
     * æŸ¥è¯¢è½¦è¾†ä¿¡æ¯åˆ—表
     * 
     * @param vehicleInfo è½¦è¾†ä¿¡æ¯
ruoyi-system/src/main/java/com/ruoyi/system/service/ICustomerEvaluationService.java
New file
@@ -0,0 +1,92 @@
package com.ruoyi.system.service;
import java.util.List;
import com.ruoyi.system.domain.CustomerEvaluation;
/**
 * å®¢æˆ·è¯„ä»·Service接口
 *
 * @author ruoyi
 * @date 2025-01-27
 */
public interface ICustomerEvaluationService {
    /**
     * æŸ¥è¯¢å®¢æˆ·è¯„ä»·
     *
     * @param evaluationId å®¢æˆ·è¯„价主键
     * @return å®¢æˆ·è¯„ä»·
     */
    public CustomerEvaluation selectCustomerEvaluationByEvaluationId(Long evaluationId);
    /**
     * æŸ¥è¯¢å®¢æˆ·è¯„价列表
     *
     * @param customerEvaluation å®¢æˆ·è¯„ä»·
     * @return å®¢æˆ·è¯„价集合
     */
    public List<CustomerEvaluation> selectCustomerEvaluationList(CustomerEvaluation customerEvaluation);
    /**
     * æ ¹æ®è½¦ç‰Œå·æŸ¥è¯¢å®¢æˆ·è¯„ä»·
     *
     * @param vehicleNo è½¦ç‰Œå·
     * @return å®¢æˆ·è¯„价集合
     */
    public List<CustomerEvaluation> selectCustomerEvaluationByVehicleNo(String vehicleNo);
    /**
     * æ ¹æ®å¾®ä¿¡OpenID查询客户评价
     *
     * @param wechatOpenid å¾®ä¿¡OpenID
     * @return å®¢æˆ·è¯„价集合
     */
    public List<CustomerEvaluation> selectCustomerEvaluationByWechatOpenid(String wechatOpenid);
    /**
     * æ–°å¢žå®¢æˆ·è¯„ä»·
     *
     * @param customerEvaluation å®¢æˆ·è¯„ä»·
     * @return ç»“æžœ
     */
    public int insertCustomerEvaluation(CustomerEvaluation customerEvaluation);
    /**
     * ä¿®æ”¹å®¢æˆ·è¯„ä»·
     *
     * @param customerEvaluation å®¢æˆ·è¯„ä»·
     * @return ç»“æžœ
     */
    public int updateCustomerEvaluation(CustomerEvaluation customerEvaluation);
    /**
     * æ‰¹é‡åˆ é™¤å®¢æˆ·è¯„ä»·
     *
     * @param evaluationIds éœ€è¦åˆ é™¤çš„客户评价主键集合
     * @return ç»“æžœ
     */
    public int deleteCustomerEvaluationByEvaluationIds(Long[] evaluationIds);
    /**
     * åˆ é™¤å®¢æˆ·è¯„价信息
     *
     * @param evaluationId å®¢æˆ·è¯„价主键
     * @return ç»“æžœ
     */
    public int deleteCustomerEvaluationByEvaluationId(Long evaluationId);
    /**
     * æäº¤å®¢æˆ·è¯„ä»·
     *
     * @param customerEvaluation å®¢æˆ·è¯„ä»·
     * @return ç»“æžœ
     */
    public int submitCustomerEvaluation(CustomerEvaluation customerEvaluation);
    /**
     * ç»Ÿè®¡è¯„价数量
     *
     * @param customerEvaluation å®¢æˆ·è¯„ä»·
     * @return è¯„价统计信息
     */
    public List<CustomerEvaluation> selectEvaluationStatistics(CustomerEvaluation customerEvaluation);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/IEvaluationDimensionService.java
New file
@@ -0,0 +1,67 @@
package com.ruoyi.system.service;
import java.util.List;
import com.ruoyi.system.domain.EvaluationDimension;
/**
 * è¯„价维度配置Service接口
 *
 * @author ruoyi
 * @date 2025-01-27
 */
public interface IEvaluationDimensionService {
    /**
     * æŸ¥è¯¢è¯„价维度配置
     *
     * @param dimensionId è¯„价维度配置主键
     * @return è¯„价维度配置
     */
    public EvaluationDimension selectEvaluationDimensionByDimensionId(Long dimensionId);
    /**
     * æŸ¥è¯¢è¯„价维度配置列表
     *
     * @param evaluationDimension è¯„价维度配置
     * @return è¯„价维度配置集合
     */
    public List<EvaluationDimension> selectEvaluationDimensionList(EvaluationDimension evaluationDimension);
    /**
     * æŸ¥è¯¢å¯ç”¨çš„评价维度配置列表
     *
     * @return è¯„价维度配置集合
     */
    public List<EvaluationDimension> selectEnabledEvaluationDimensionList();
    /**
     * æ–°å¢žè¯„价维度配置
     *
     * @param evaluationDimension è¯„价维度配置
     * @return ç»“æžœ
     */
    public int insertEvaluationDimension(EvaluationDimension evaluationDimension);
    /**
     * ä¿®æ”¹è¯„价维度配置
     *
     * @param evaluationDimension è¯„价维度配置
     * @return ç»“æžœ
     */
    public int updateEvaluationDimension(EvaluationDimension evaluationDimension);
    /**
     * æ‰¹é‡åˆ é™¤è¯„价维度配置
     *
     * @param dimensionIds éœ€è¦åˆ é™¤çš„评价维度配置主键集合
     * @return ç»“æžœ
     */
    public int deleteEvaluationDimensionByDimensionIds(Long[] dimensionIds);
    /**
     * åˆ é™¤è¯„价维度配置信息
     *
     * @param dimensionId è¯„价维度配置主键
     * @return ç»“æžœ
     */
    public int deleteEvaluationDimensionByDimensionId(Long dimensionId);
}
ruoyi-system/src/main/java/com/ruoyi/system/service/IVehicleEvaluationQrcodeService.java
New file
@@ -0,0 +1,92 @@
package com.ruoyi.system.service;
import java.util.List;
import com.ruoyi.system.domain.VehicleEvaluationQrcode;
/**
 * è½¦è¾†è¯„价二维码Service接口
 *
 * @author ruoyi
 * @date 2025-01-27
 */
public interface IVehicleEvaluationQrcodeService {
    /**
     * æŸ¥è¯¢è½¦è¾†è¯„价二维码
     *
     * @param qrcodeId è½¦è¾†è¯„价二维码主键
     * @return è½¦è¾†è¯„价二维码
     */
    public VehicleEvaluationQrcode selectVehicleEvaluationQrcodeByQrcodeId(Long qrcodeId);
    /**
     * æ ¹æ®è½¦ç‰Œå·æŸ¥è¯¢è½¦è¾†è¯„价二维码
     *
     * @param vehicleNo è½¦ç‰Œå·
     * @return è½¦è¾†è¯„价二维码
     */
    public VehicleEvaluationQrcode selectVehicleEvaluationQrcodeByVehicleNo(String vehicleNo);
    /**
     * æŸ¥è¯¢è½¦è¾†è¯„价二维码列表
     *
     * @param vehicleEvaluationQrcode è½¦è¾†è¯„价二维码
     * @return è½¦è¾†è¯„价二维码集合
     */
    public List<VehicleEvaluationQrcode> selectVehicleEvaluationQrcodeList(VehicleEvaluationQrcode vehicleEvaluationQrcode);
    /**
     * æ–°å¢žè½¦è¾†è¯„价二维码
     *
     * @param vehicleEvaluationQrcode è½¦è¾†è¯„价二维码
     * @return ç»“æžœ
     */
    public int insertVehicleEvaluationQrcode(VehicleEvaluationQrcode vehicleEvaluationQrcode);
    /**
     * ä¿®æ”¹è½¦è¾†è¯„价二维码
     *
     * @param vehicleEvaluationQrcode è½¦è¾†è¯„价二维码
     * @return ç»“æžœ
     */
    public int updateVehicleEvaluationQrcode(VehicleEvaluationQrcode vehicleEvaluationQrcode);
    /**
     * æ‰¹é‡åˆ é™¤è½¦è¾†è¯„价二维码
     *
     * @param qrcodeIds éœ€è¦åˆ é™¤çš„车辆评价二维码主键集合
     * @return ç»“æžœ
     */
    public int deleteVehicleEvaluationQrcodeByQrcodeIds(Long[] qrcodeIds);
    /**
     * åˆ é™¤è½¦è¾†è¯„价二维码信息
     *
     * @param qrcodeId è½¦è¾†è¯„价二维码主键
     * @return ç»“æžœ
     */
    public int deleteVehicleEvaluationQrcodeByQrcodeId(Long qrcodeId);
    /**
     * ç”Ÿæˆè½¦è¾†è¯„价二维码
     *
     * @param vehicleNo è½¦ç‰Œå·
     * @return äºŒç»´ç ä¿¡æ¯
     */
    public VehicleEvaluationQrcode generateVehicleEvaluationQrcode(String vehicleNo);
    /**
     * ç”Ÿæˆè½¦è¾†è¯„价二维码(带URL参数)
     *
     * @param vehicleNo è½¦ç‰Œå·
     * @param qrcodeUrl äºŒç»´ç URL
     * @return äºŒç»´ç ä¿¡æ¯
     */
    public VehicleEvaluationQrcode generateVehicleEvaluationQrcode(String vehicleNo, String qrcodeUrl);
    /**
     * æ‰¹é‡ç”Ÿæˆè½¦è¾†è¯„价二维码
     *
     * @return ç»“æžœ
     */
    public int batchGenerateVehicleEvaluationQrcode();
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/CustomerEvaluationServiceImpl.java
New file
@@ -0,0 +1,196 @@
package com.ruoyi.system.service.impl;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.ruoyi.system.mapper.CustomerEvaluationMapper;
import com.ruoyi.system.mapper.EvaluationDetailMapper;
import com.ruoyi.system.domain.CustomerEvaluation;
import com.ruoyi.system.domain.EvaluationDetail;
import com.ruoyi.system.service.ICustomerEvaluationService;
/**
 * å®¢æˆ·è¯„ä»·Service业务层处理
 *
 * @author ruoyi
 * @date 2025-01-27
 */
@Service
public class CustomerEvaluationServiceImpl implements ICustomerEvaluationService {
    @Autowired
    private CustomerEvaluationMapper customerEvaluationMapper;
    @Autowired
    private EvaluationDetailMapper evaluationDetailMapper;
    /**
     * æŸ¥è¯¢å®¢æˆ·è¯„ä»·
     *
     * @param evaluationId å®¢æˆ·è¯„价主键
     * @return å®¢æˆ·è¯„ä»·
     */
    @Override
    public CustomerEvaluation selectCustomerEvaluationByEvaluationId(Long evaluationId) {
        CustomerEvaluation evaluation = customerEvaluationMapper.selectCustomerEvaluationByEvaluationId(evaluationId);
        if (evaluation != null) {
            // æŸ¥è¯¢è¯„价详情
            List<EvaluationDetail> details = evaluationDetailMapper.selectEvaluationDetailByEvaluationId(evaluationId);
            evaluation.setEvaluationDetails(details);
        }
        return evaluation;
    }
    /**
     * æŸ¥è¯¢å®¢æˆ·è¯„价列表
     *
     * @param customerEvaluation å®¢æˆ·è¯„ä»·
     * @return å®¢æˆ·è¯„ä»·
     */
    @Override
    public List<CustomerEvaluation> selectCustomerEvaluationList(CustomerEvaluation customerEvaluation) {
        return customerEvaluationMapper.selectCustomerEvaluationList(customerEvaluation);
    }
    /**
     * æ ¹æ®è½¦ç‰Œå·æŸ¥è¯¢å®¢æˆ·è¯„ä»·
     *
     * @param vehicleNo è½¦ç‰Œå·
     * @return å®¢æˆ·è¯„价集合
     */
    @Override
    public List<CustomerEvaluation> selectCustomerEvaluationByVehicleNo(String vehicleNo) {
        return customerEvaluationMapper.selectCustomerEvaluationByVehicleNo(vehicleNo);
    }
    /**
     * æ ¹æ®å¾®ä¿¡OpenID查询客户评价
     *
     * @param wechatOpenid å¾®ä¿¡OpenID
     * @return å®¢æˆ·è¯„价集合
     */
    @Override
    public List<CustomerEvaluation> selectCustomerEvaluationByWechatOpenid(String wechatOpenid) {
        return customerEvaluationMapper.selectCustomerEvaluationByWechatOpenid(wechatOpenid);
    }
    /**
     * æ–°å¢žå®¢æˆ·è¯„ä»·
     *
     * @param customerEvaluation å®¢æˆ·è¯„ä»·
     * @return ç»“æžœ
     */
    @Override
    public int insertCustomerEvaluation(CustomerEvaluation customerEvaluation) {
        customerEvaluation.setCreateTime(DateUtils.getNowDate());
        return customerEvaluationMapper.insertCustomerEvaluation(customerEvaluation);
    }
    /**
     * ä¿®æ”¹å®¢æˆ·è¯„ä»·
     *
     * @param customerEvaluation å®¢æˆ·è¯„ä»·
     * @return ç»“æžœ
     */
    @Override
    public int updateCustomerEvaluation(CustomerEvaluation customerEvaluation) {
        customerEvaluation.setUpdateTime(DateUtils.getNowDate());
        return customerEvaluationMapper.updateCustomerEvaluation(customerEvaluation);
    }
    /**
     * æ‰¹é‡åˆ é™¤å®¢æˆ·è¯„ä»·
     *
     * @param evaluationIds éœ€è¦åˆ é™¤çš„客户评价主键
     * @return ç»“æžœ
     */
    @Override
    public int deleteCustomerEvaluationByEvaluationIds(Long[] evaluationIds) {
        return customerEvaluationMapper.deleteCustomerEvaluationByEvaluationIds(evaluationIds);
    }
    /**
     * åˆ é™¤å®¢æˆ·è¯„价信息
     *
     * @param evaluationId å®¢æˆ·è¯„价主键
     * @return ç»“æžœ
     */
    @Override
    public int deleteCustomerEvaluationByEvaluationId(Long evaluationId) {
        return customerEvaluationMapper.deleteCustomerEvaluationByEvaluationId(evaluationId);
    }
    /**
     * æäº¤å®¢æˆ·è¯„ä»·
     *
     * @param customerEvaluation å®¢æˆ·è¯„ä»·
     * @return ç»“æžœ
     */
    @Override
    @Transactional
    public int submitCustomerEvaluation(CustomerEvaluation customerEvaluation) {
        // è®¡ç®—总评分
        BigDecimal totalScore = calculateTotalScore(customerEvaluation.getEvaluationDetails());
        customerEvaluation.setTotalScore(totalScore);
        customerEvaluation.setEvaluationStatus("1");
        customerEvaluation.setEvaluationTime(DateUtils.getNowDate());
        customerEvaluation.setCreateTime(DateUtils.getNowDate());
        // æ’入客户评价
        int result = customerEvaluationMapper.insertCustomerEvaluation(customerEvaluation);
        if (result > 0 && customerEvaluation.getEvaluationDetails() != null && !customerEvaluation.getEvaluationDetails().isEmpty()) {
            // æ’入评价详情
            for (EvaluationDetail detail : customerEvaluation.getEvaluationDetails()) {
                detail.setEvaluationId(customerEvaluation.getEvaluationId());
                detail.setCreateTime(DateUtils.getNowDate());
                evaluationDetailMapper.insertEvaluationDetail(detail);
            }
        }
        return result;
    }
    /**
     * ç»Ÿè®¡è¯„价数量
     *
     * @param customerEvaluation å®¢æˆ·è¯„ä»·
     * @return è¯„价统计信息
     */
    @Override
    public List<CustomerEvaluation> selectEvaluationStatistics(CustomerEvaluation customerEvaluation) {
        return customerEvaluationMapper.selectEvaluationStatistics(customerEvaluation);
    }
    /**
     * è®¡ç®—总评分
     *
     * @param details è¯„价详情列表
     * @return æ€»è¯„分
     */
    private BigDecimal calculateTotalScore(List<EvaluationDetail> details) {
        if (details == null || details.isEmpty()) {
            return BigDecimal.ZERO;
        }
        int totalScore = 0;
        int count = 0;
        for (EvaluationDetail detail : details) {
            if (detail.getScore() != null && detail.getScore() > 0) {
                totalScore += detail.getScore();
                count++;
            }
        }
        if (count == 0) {
            return BigDecimal.ZERO;
        }
        return new BigDecimal(totalScore).divide(new BigDecimal(count), 1, BigDecimal.ROUND_HALF_UP);
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/EvaluationDimensionServiceImpl.java
New file
@@ -0,0 +1,99 @@
package com.ruoyi.system.service.impl;
import java.util.List;
import com.ruoyi.common.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.system.mapper.EvaluationDimensionMapper;
import com.ruoyi.system.domain.EvaluationDimension;
import com.ruoyi.system.service.IEvaluationDimensionService;
/**
 * è¯„价维度配置Service业务层处理
 *
 * @author ruoyi
 * @date 2025-01-27
 */
@Service
public class EvaluationDimensionServiceImpl implements IEvaluationDimensionService {
    @Autowired
    private EvaluationDimensionMapper evaluationDimensionMapper;
    /**
     * æŸ¥è¯¢è¯„价维度配置
     *
     * @param dimensionId è¯„价维度配置主键
     * @return è¯„价维度配置
     */
    @Override
    public EvaluationDimension selectEvaluationDimensionByDimensionId(Long dimensionId) {
        return evaluationDimensionMapper.selectEvaluationDimensionByDimensionId(dimensionId);
    }
    /**
     * æŸ¥è¯¢è¯„价维度配置列表
     *
     * @param evaluationDimension è¯„价维度配置
     * @return è¯„价维度配置
     */
    @Override
    public List<EvaluationDimension> selectEvaluationDimensionList(EvaluationDimension evaluationDimension) {
        return evaluationDimensionMapper.selectEvaluationDimensionList(evaluationDimension);
    }
    /**
     * æŸ¥è¯¢å¯ç”¨çš„评价维度配置列表
     *
     * @return è¯„价维度配置集合
     */
    @Override
    public List<EvaluationDimension> selectEnabledEvaluationDimensionList() {
        return evaluationDimensionMapper.selectEnabledEvaluationDimensionList();
    }
    /**
     * æ–°å¢žè¯„价维度配置
     *
     * @param evaluationDimension è¯„价维度配置
     * @return ç»“æžœ
     */
    @Override
    public int insertEvaluationDimension(EvaluationDimension evaluationDimension) {
        evaluationDimension.setCreateTime(DateUtils.getNowDate());
        return evaluationDimensionMapper.insertEvaluationDimension(evaluationDimension);
    }
    /**
     * ä¿®æ”¹è¯„价维度配置
     *
     * @param evaluationDimension è¯„价维度配置
     * @return ç»“æžœ
     */
    @Override
    public int updateEvaluationDimension(EvaluationDimension evaluationDimension) {
        evaluationDimension.setUpdateTime(DateUtils.getNowDate());
        return evaluationDimensionMapper.updateEvaluationDimension(evaluationDimension);
    }
    /**
     * æ‰¹é‡åˆ é™¤è¯„价维度配置
     *
     * @param dimensionIds éœ€è¦åˆ é™¤çš„评价维度配置主键
     * @return ç»“æžœ
     */
    @Override
    public int deleteEvaluationDimensionByDimensionIds(Long[] dimensionIds) {
        return evaluationDimensionMapper.deleteEvaluationDimensionByDimensionIds(dimensionIds);
    }
    /**
     * åˆ é™¤è¯„价维度配置信息
     *
     * @param dimensionId è¯„价维度配置主键
     * @return ç»“æžœ
     */
    @Override
    public int deleteEvaluationDimensionByDimensionId(Long dimensionId) {
        return evaluationDimensionMapper.deleteEvaluationDimensionByDimensionId(dimensionId);
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/service/impl/VehicleEvaluationQrcodeServiceImpl.java
New file
@@ -0,0 +1,252 @@
package com.ruoyi.system.service.impl;
import java.util.Date;
import java.util.List;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.QRCodeUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.mapper.VehicleInfoMapper;
import com.ruoyi.system.domain.VehicleInfo;
import com.ruoyi.system.mapper.VehicleEvaluationQrcodeMapper;
import com.ruoyi.system.domain.VehicleEvaluationQrcode;
import com.ruoyi.system.service.IVehicleEvaluationQrcodeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
/**
 * è½¦è¾†è¯„价二维码Service业务层处理
 *
 * @author ruoyi
 * @date 2025-01-27
 */
@Service
public class VehicleEvaluationQrcodeServiceImpl implements IVehicleEvaluationQrcodeService {
    @Autowired
    private VehicleEvaluationQrcodeMapper vehicleEvaluationQrcodeMapper;
    @Autowired
    private VehicleInfoMapper vehicleInfoMapper;
    @Value("${ruoyi.profile}")
    private String uploadPath;
    @Value("${server.port}")
    private String serverPort;
    /**
     * æŸ¥è¯¢è½¦è¾†è¯„价二维码
     *
     * @param qrcodeId è½¦è¾†è¯„价二维码主键
     * @return è½¦è¾†è¯„价二维码
     */
    @Override
    public VehicleEvaluationQrcode selectVehicleEvaluationQrcodeByQrcodeId(Long qrcodeId) {
        return vehicleEvaluationQrcodeMapper.selectVehicleEvaluationQrcodeByQrcodeId(qrcodeId);
    }
    /**
     * æ ¹æ®è½¦ç‰Œå·æŸ¥è¯¢è½¦è¾†è¯„价二维码
     *
     * @param vehicleNo è½¦ç‰Œå·
     * @return è½¦è¾†è¯„价二维码
     */
    @Override
    public VehicleEvaluationQrcode selectVehicleEvaluationQrcodeByVehicleNo(String vehicleNo) {
        return vehicleEvaluationQrcodeMapper.selectVehicleEvaluationQrcodeByVehicleNo(vehicleNo);
    }
    /**
     * æŸ¥è¯¢è½¦è¾†è¯„价二维码列表
     *
     * @param vehicleEvaluationQrcode è½¦è¾†è¯„价二维码
     * @return è½¦è¾†è¯„价二维码
     */
    @Override
    public List<VehicleEvaluationQrcode> selectVehicleEvaluationQrcodeList(VehicleEvaluationQrcode vehicleEvaluationQrcode) {
        return vehicleEvaluationQrcodeMapper.selectVehicleEvaluationQrcodeList(vehicleEvaluationQrcode);
    }
    /**
     * æ–°å¢žè½¦è¾†è¯„价二维码
     *
     * @param vehicleEvaluationQrcode è½¦è¾†è¯„价二维码
     * @return ç»“æžœ
     */
    @Override
    public int insertVehicleEvaluationQrcode(VehicleEvaluationQrcode vehicleEvaluationQrcode) {
        vehicleEvaluationQrcode.setCreateTime(DateUtils.getNowDate());
        return vehicleEvaluationQrcodeMapper.insertVehicleEvaluationQrcode(vehicleEvaluationQrcode);
    }
    /**
     * ä¿®æ”¹è½¦è¾†è¯„价二维码
     *
     * @param vehicleEvaluationQrcode è½¦è¾†è¯„价二维码
     * @return ç»“æžœ
     */
    @Override
    public int updateVehicleEvaluationQrcode(VehicleEvaluationQrcode vehicleEvaluationQrcode) {
        vehicleEvaluationQrcode.setUpdateTime(DateUtils.getNowDate());
        return vehicleEvaluationQrcodeMapper.updateVehicleEvaluationQrcode(vehicleEvaluationQrcode);
    }
    /**
     * æ‰¹é‡åˆ é™¤è½¦è¾†è¯„价二维码
     *
     * @param qrcodeIds éœ€è¦åˆ é™¤çš„车辆评价二维码主键
     * @return ç»“æžœ
     */
    @Override
    public int deleteVehicleEvaluationQrcodeByQrcodeIds(Long[] qrcodeIds) {
        return vehicleEvaluationQrcodeMapper.deleteVehicleEvaluationQrcodeByQrcodeIds(qrcodeIds);
    }
    /**
     * åˆ é™¤è½¦è¾†è¯„价二维码信息
     *
     * @param qrcodeId è½¦è¾†è¯„价二维码主键
     * @return ç»“æžœ
     */
    @Override
    public int deleteVehicleEvaluationQrcodeByQrcodeId(Long qrcodeId) {
        return vehicleEvaluationQrcodeMapper.deleteVehicleEvaluationQrcodeByQrcodeId(qrcodeId);
    }
    /**
     * ç”Ÿæˆè½¦è¾†è¯„价二维码
     *
     * @param vehicleNo è½¦ç‰Œå·
     * @return äºŒç»´ç ä¿¡æ¯
     */
    @Override
    public VehicleEvaluationQrcode generateVehicleEvaluationQrcode(String vehicleNo) {
        if (StringUtils.isEmpty(vehicleNo)) {
            return null;
        }
        // æ£€æŸ¥è½¦è¾†æ˜¯å¦å­˜åœ¨
        VehicleInfo vehicleInfo = vehicleInfoMapper.selectVehicleInfoByVehicleNo(vehicleNo);
        if (vehicleInfo == null) {
            return null;
        }
        // æ£€æŸ¥æ˜¯å¦å·²å­˜åœ¨äºŒç»´ç 
        VehicleEvaluationQrcode existingQrcode = vehicleEvaluationQrcodeMapper.selectVehicleEvaluationQrcodeByVehicleNo(vehicleNo);
        if (existingQrcode != null) {
            return existingQrcode;
        }
        // ç”ŸæˆäºŒç»´ç å†…容
        String qrcodeContent = "EVAL:" + vehicleNo;
        String qrcodeUrl = "http://localhost:" + serverPort + "/evaluation?vehicle=" + vehicleNo;
        // ç”ŸæˆäºŒç»´ç å›¾ç‰‡ä¸ºbase64格式
        String qrcodeImageBase64 = QRCodeUtils.generateQRCodeToBase64(qrcodeUrl);
        if (qrcodeImageBase64 != null) {
            VehicleEvaluationQrcode qrcode = new VehicleEvaluationQrcode();
            qrcode.setVehicleNo(vehicleNo);
            qrcode.setQrcodeUrl(qrcodeUrl);
            qrcode.setQrcodeContent(qrcodeContent);
            qrcode.setQrcodeImage(qrcodeImageBase64);
            qrcode.setStatus("0");
            qrcode.setCreateBy("system");
            qrcode.setCreateTime(DateUtils.getNowDate());
            int result = vehicleEvaluationQrcodeMapper.insertVehicleEvaluationQrcode(qrcode);
            if (result > 0) {
                return qrcode;
            }
        }
        return null;
    }
    /**
     * ç”Ÿæˆè½¦è¾†è¯„价二维码(带URL参数)
     *
     * @param vehicleNo è½¦ç‰Œå·
     * @param qrcodeUrl äºŒç»´ç URL
     * @return äºŒç»´ç ä¿¡æ¯
     */
    @Override
    public VehicleEvaluationQrcode generateVehicleEvaluationQrcode(String vehicleNo, String qrcodeUrl) {
        if (StringUtils.isEmpty(vehicleNo) || StringUtils.isEmpty(qrcodeUrl)) {
            return null;
        }
        // æ£€æŸ¥è½¦è¾†æ˜¯å¦å­˜åœ¨
        VehicleInfo vehicleInfo = vehicleInfoMapper.selectVehicleInfoByVehicleNo(vehicleNo);
        if (vehicleInfo == null) {
            return null;
        }
        // æ£€æŸ¥æ˜¯å¦å·²å­˜åœ¨äºŒç»´ç 
        VehicleEvaluationQrcode existingQrcode = vehicleEvaluationQrcodeMapper.selectVehicleEvaluationQrcodeByVehicleNo(vehicleNo);
        if (existingQrcode != null) {
            // å¦‚果已存在,更新URL和重新生成二维码
            existingQrcode.setQrcodeUrl(qrcodeUrl);
            existingQrcode.setQrcodeContent("EVAL:" + vehicleNo);
            existingQrcode.setUpdateBy("system");
            existingQrcode.setUpdateTime(DateUtils.getNowDate());
            // é‡æ–°ç”ŸæˆäºŒç»´ç å›¾ç‰‡ä¸ºbase64格式
            String qrcodeImageBase64 = QRCodeUtils.generateQRCodeToBase64(qrcodeUrl);
            if (qrcodeImageBase64 != null) {
                existingQrcode.setQrcodeImage(qrcodeImageBase64);
                vehicleEvaluationQrcodeMapper.updateVehicleEvaluationQrcode(existingQrcode);
                return existingQrcode;
            }
            return null;
        }
        // ç”ŸæˆäºŒç»´ç å†…容
        String qrcodeContent = "EVAL:" + vehicleNo;
        // ç”ŸæˆäºŒç»´ç å›¾ç‰‡ä¸ºbase64格式
        String qrcodeImageBase64 = QRCodeUtils.generateQRCodeToBase64(qrcodeUrl);
        if (qrcodeImageBase64 != null) {
            VehicleEvaluationQrcode qrcode = new VehicleEvaluationQrcode();
            qrcode.setVehicleNo(vehicleNo);
            qrcode.setQrcodeUrl(qrcodeUrl);
            qrcode.setQrcodeContent(qrcodeContent);
            qrcode.setQrcodeImage(qrcodeImageBase64);
            qrcode.setStatus("0");
            qrcode.setCreateBy("system");
            qrcode.setCreateTime(DateUtils.getNowDate());
            int result = vehicleEvaluationQrcodeMapper.insertVehicleEvaluationQrcode(qrcode);
            if (result > 0) {
                return qrcode;
            }
        }
        return null;
    }
    /**
     * æ‰¹é‡ç”Ÿæˆè½¦è¾†è¯„价二维码
     *
     * @return ç»“æžœ
     */
    @Override
    public int batchGenerateVehicleEvaluationQrcode() {
        // æŸ¥è¯¢æ‰€æœ‰æ­£å¸¸çŠ¶æ€çš„è½¦è¾†
        VehicleInfo vehicleInfo = new VehicleInfo();
        vehicleInfo.setStatus("0");
        List<VehicleInfo> vehicleList = vehicleInfoMapper.selectVehicleInfoList(vehicleInfo);
        int successCount = 0;
        for (VehicleInfo vehicle : vehicleList) {
            VehicleEvaluationQrcode qrcode = generateVehicleEvaluationQrcode(vehicle.getVehicleNo());
            if (qrcode != null) {
                successCount++;
            }
        }
        return successCount;
    }
}
ruoyi-system/src/main/resources/mapper/system/CustomerEvaluationMapper.xml
New file
@@ -0,0 +1,159 @@
<?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.ruoyi.system.mapper.CustomerEvaluationMapper">
    <resultMap type="CustomerEvaluation" id="CustomerEvaluationResult">
        <result property="evaluationId"    column="evaluation_id"    />
        <result property="vehicleNo"    column="vehicle_no"    />
        <result property="customerName"    column="customer_name"    />
        <result property="customerPhone"    column="customer_phone"    />
        <result property="wechatOpenid"    column="wechat_openid"    />
        <result property="wechatNickname"    column="wechat_nickname"    />
        <result property="wechatAvatar"    column="wechat_avatar"    />
        <result property="wechatPhone"    column="wechat_phone"    />
        <result property="totalScore"    column="total_score"    />
        <result property="evaluationStatus"    column="evaluation_status"    />
        <result property="evaluationTime"    column="evaluation_time"    />
        <result property="ipAddress"    column="ip_address"    />
        <result property="userAgent"    column="user_agent"    />
        <result property="createTime"    column="create_time"    />
        <result property="updateTime"    column="update_time"    />
        <result property="remark"    column="remark"    />
    </resultMap>
    <sql id="selectCustomerEvaluationVo">
        select evaluation_id, vehicle_no, customer_name, customer_phone, wechat_openid, wechat_nickname, wechat_avatar, wechat_phone, total_score, evaluation_status, evaluation_time, ip_address, user_agent, create_time, update_time, remark from customer_evaluation
    </sql>
    <select id="selectCustomerEvaluationList" parameterType="CustomerEvaluation" resultMap="CustomerEvaluationResult">
        <include refid="selectCustomerEvaluationVo"/>
        <where>
            <if test="vehicleNo != null  and vehicleNo != ''"> and vehicle_no like concat('%', #{vehicleNo}, '%')</if>
            <if test="customerName != null  and customerName != ''"> and customer_name like concat('%', #{customerName}, '%')</if>
            <if test="customerPhone != null  and customerPhone != ''"> and customer_phone = #{customerPhone}</if>
            <if test="wechatOpenid != null  and wechatOpenid != ''"> and wechat_openid = #{wechatOpenid}</if>
            <if test="wechatNickname != null  and wechatNickname != ''"> and wechat_nickname like concat('%', #{wechatNickname}, '%')</if>
            <if test="totalScore != null "> and total_score = #{totalScore}</if>
            <if test="evaluationStatus != null  and evaluationStatus != ''"> and evaluation_status = #{evaluationStatus}</if>
            <if test="evaluationTime != null "> and evaluation_time = #{evaluationTime}</if>
            <if test="params.beginTime != null and params.beginTime != ''"><!-- å¼€å§‹æ—¶é—´æ£€ç´¢ -->
                and date_format(evaluation_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
            </if>
            <if test="params.endTime != null and params.endTime != ''"><!-- ç»“束时间检索 -->
                and date_format(evaluation_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
            </if>
        </where>
        order by evaluation_time desc, create_time desc
    </select>
    <select id="selectCustomerEvaluationByEvaluationId" parameterType="Long" resultMap="CustomerEvaluationResult">
        <include refid="selectCustomerEvaluationVo"/>
        where evaluation_id = #{evaluationId}
    </select>
    <select id="selectCustomerEvaluationByVehicleNo" parameterType="String" resultMap="CustomerEvaluationResult">
        <include refid="selectCustomerEvaluationVo"/>
        where vehicle_no = #{vehicleNo}
        order by evaluation_time desc, create_time desc
    </select>
    <select id="selectCustomerEvaluationByWechatOpenid" parameterType="String" resultMap="CustomerEvaluationResult">
        <include refid="selectCustomerEvaluationVo"/>
        where wechat_openid = #{wechatOpenid}
        order by evaluation_time desc, create_time desc
    </select>
    <select id="selectEvaluationStatistics" parameterType="CustomerEvaluation" resultMap="CustomerEvaluationResult">
        select
            vehicle_no,
            count(*) as total_count,
            avg(total_score) as avg_score,
            sum(case when total_score >= 4.0 then 1 else 0 end) as good_count,
            sum(case when total_score &lt; 3.0 then 1 else 0 end) as bad_count
        from customer_evaluation
        <where>
            evaluation_status = '1'
            <if test="vehicleNo != null  and vehicleNo != ''"> and vehicle_no = #{vehicleNo}</if>
            <if test="params.beginTime != null and params.beginTime != ''">
                and date_format(evaluation_time,'%y%m%d') &gt;= date_format(#{params.beginTime},'%y%m%d')
            </if>
            <if test="params.endTime != null and params.endTime != ''">
                and date_format(evaluation_time,'%y%m%d') &lt;= date_format(#{params.endTime},'%y%m%d')
            </if>
        </where>
        group by vehicle_no
        order by avg_score desc
    </select>
    <insert id="insertCustomerEvaluation" parameterType="CustomerEvaluation" useGeneratedKeys="true" keyProperty="evaluationId">
        insert into customer_evaluation
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="vehicleNo != null and vehicleNo != ''">vehicle_no,</if>
            <if test="customerName != null and customerName != ''">customer_name,</if>
            <if test="customerPhone != null and customerPhone != ''">customer_phone,</if>
            <if test="wechatOpenid != null">wechat_openid,</if>
            <if test="wechatNickname != null">wechat_nickname,</if>
            <if test="wechatAvatar != null">wechat_avatar,</if>
            <if test="wechatPhone != null">wechat_phone,</if>
            <if test="totalScore != null">total_score,</if>
            <if test="evaluationStatus != null">evaluation_status,</if>
            <if test="evaluationTime != null">evaluation_time,</if>
            <if test="ipAddress != null">ip_address,</if>
            <if test="userAgent != null">user_agent,</if>
            <if test="createTime != null">create_time,</if>
            <if test="updateTime != null">update_time,</if>
            <if test="remark != null">remark,</if>
         </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="vehicleNo != null and vehicleNo != ''">#{vehicleNo},</if>
            <if test="customerName != null and customerName != ''">#{customerName},</if>
            <if test="customerPhone != null and customerPhone != ''">#{customerPhone},</if>
            <if test="wechatOpenid != null">#{wechatOpenid},</if>
            <if test="wechatNickname != null">#{wechatNickname},</if>
            <if test="wechatAvatar != null">#{wechatAvatar},</if>
            <if test="wechatPhone != null">#{wechatPhone},</if>
            <if test="totalScore != null">#{totalScore},</if>
            <if test="evaluationStatus != null">#{evaluationStatus},</if>
            <if test="evaluationTime != null">#{evaluationTime},</if>
            <if test="ipAddress != null">#{ipAddress},</if>
            <if test="userAgent != null">#{userAgent},</if>
            <if test="createTime != null">#{createTime},</if>
            <if test="updateTime != null">#{updateTime},</if>
            <if test="remark != null">#{remark},</if>
         </trim>
    </insert>
    <update id="updateCustomerEvaluation" parameterType="CustomerEvaluation">
        update customer_evaluation
        <trim prefix="SET" suffixOverrides=",">
            <if test="vehicleNo != null and vehicleNo != ''">vehicle_no = #{vehicleNo},</if>
            <if test="customerName != null and customerName != ''">customer_name = #{customerName},</if>
            <if test="customerPhone != null and customerPhone != ''">customer_phone = #{customerPhone},</if>
            <if test="wechatOpenid != null">wechat_openid = #{wechatOpenid},</if>
            <if test="wechatNickname != null">wechat_nickname = #{wechatNickname},</if>
            <if test="wechatAvatar != null">wechat_avatar = #{wechatAvatar},</if>
            <if test="wechatPhone != null">wechat_phone = #{wechatPhone},</if>
            <if test="totalScore != null">total_score = #{totalScore},</if>
            <if test="evaluationStatus != null">evaluation_status = #{evaluationStatus},</if>
            <if test="evaluationTime != null">evaluation_time = #{evaluationTime},</if>
            <if test="ipAddress != null">ip_address = #{ipAddress},</if>
            <if test="userAgent != null">user_agent = #{userAgent},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="remark != null">remark = #{remark},</if>
        </trim>
        where evaluation_id = #{evaluationId}
    </update>
    <delete id="deleteCustomerEvaluationByEvaluationId" parameterType="Long">
        delete from customer_evaluation where evaluation_id = #{evaluationId}
    </delete>
    <delete id="deleteCustomerEvaluationByEvaluationIds" parameterType="String">
        delete from customer_evaluation where evaluation_id in
        <foreach item="evaluationId" collection="array" open="(" separator="," close=")">
            #{evaluationId}
        </foreach>
    </delete>
</mapper>
ruoyi-system/src/main/resources/mapper/system/EvaluationDetailMapper.xml
New file
@@ -0,0 +1,116 @@
<?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.ruoyi.system.mapper.EvaluationDetailMapper">
    <resultMap type="EvaluationDetail" id="EvaluationDetailResult">
        <result property="detailId"    column="detail_id"    />
        <result property="evaluationId"    column="evaluation_id"    />
        <result property="dimensionId"    column="dimension_id"    />
        <result property="score"    column="score"    />
        <result property="optionValue"    column="option_value"    />
        <result property="textContent"    column="text_content"    />
        <result property="createTime"    column="create_time"    />
    </resultMap>
    <resultMap type="EvaluationDetail" id="EvaluationDetailWithDimensionResult" extends="EvaluationDetailResult">
        <association property="dimension" javaType="EvaluationDimension">
            <result property="dimensionId"    column="dimension_id"    />
            <result property="dimensionName"    column="dimension_name"    />
            <result property="dimensionDesc"    column="dimension_desc"    />
            <result property="dimensionType"    column="dimension_type"    />
            <result property="options"    column="options"    />
            <result property="sortOrder"    column="sort_order"    />
            <result property="isRequired"    column="is_required"    />
        </association>
    </resultMap>
    <sql id="selectEvaluationDetailVo">
        select detail_id, evaluation_id, dimension_id, score, option_value, text_content, create_time from evaluation_detail
    </sql>
    <sql id="selectEvaluationDetailWithDimensionVo">
        select ed.detail_id, ed.evaluation_id, ed.dimension_id, ed.score, ed.option_value, ed.text_content, ed.create_time,
               edim.dimension_name, edim.dimension_desc, edim.dimension_type, edim.options, edim.sort_order, edim.is_required
        from evaluation_detail ed
        left join evaluation_dimension edim on ed.dimension_id = edim.dimension_id
    </sql>
    <select id="selectEvaluationDetailList" parameterType="EvaluationDetail" resultMap="EvaluationDetailResult">
        <include refid="selectEvaluationDetailVo"/>
        <where>
            <if test="evaluationId != null "> and evaluation_id = #{evaluationId}</if>
            <if test="dimensionId != null "> and dimension_id = #{dimensionId}</if>
            <if test="score != null "> and score = #{score}</if>
            <if test="optionValue != null  and optionValue != ''"> and option_value = #{optionValue}</if>
        </where>
        order by create_time desc
    </select>
    <select id="selectEvaluationDetailByDetailId" parameterType="Long" resultMap="EvaluationDetailResult">
        <include refid="selectEvaluationDetailVo"/>
        where detail_id = #{detailId}
    </select>
    <select id="selectEvaluationDetailByEvaluationId" parameterType="Long" resultMap="EvaluationDetailWithDimensionResult">
        <include refid="selectEvaluationDetailWithDimensionVo"/>
        where ed.evaluation_id = #{evaluationId}
        order by edim.sort_order asc, ed.create_time desc
    </select>
    <insert id="insertEvaluationDetail" parameterType="EvaluationDetail" useGeneratedKeys="true" keyProperty="detailId">
        insert into evaluation_detail
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="evaluationId != null">evaluation_id,</if>
            <if test="dimensionId != null">dimension_id,</if>
            <if test="score != null">score,</if>
            <if test="optionValue != null">option_value,</if>
            <if test="textContent != null">text_content,</if>
            <if test="createTime != null">create_time,</if>
         </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="evaluationId != null">#{evaluationId},</if>
            <if test="dimensionId != null">#{dimensionId},</if>
            <if test="score != null">#{score},</if>
            <if test="optionValue != null">#{optionValue},</if>
            <if test="textContent != null">#{textContent},</if>
            <if test="createTime != null">#{createTime},</if>
         </trim>
    </insert>
    <insert id="insertEvaluationDetailBatch" parameterType="java.util.List">
        insert into evaluation_detail (evaluation_id, dimension_id, score, option_value, text_content, create_time)
        values
        <foreach collection="list" item="item" separator=",">
            (#{item.evaluationId}, #{item.dimensionId}, #{item.score}, #{item.optionValue}, #{item.textContent}, #{item.createTime})
        </foreach>
    </insert>
    <update id="updateEvaluationDetail" parameterType="EvaluationDetail">
        update evaluation_detail
        <trim prefix="SET" suffixOverrides=",">
            <if test="evaluationId != null">evaluation_id = #{evaluationId},</if>
            <if test="dimensionId != null">dimension_id = #{dimensionId},</if>
            <if test="score != null">score = #{score},</if>
            <if test="optionValue != null">option_value = #{optionValue},</if>
            <if test="textContent != null">text_content = #{textContent},</if>
        </trim>
        where detail_id = #{detailId}
    </update>
    <delete id="deleteEvaluationDetailByDetailId" parameterType="Long">
        delete from evaluation_detail where detail_id = #{detailId}
    </delete>
    <delete id="deleteEvaluationDetailByEvaluationId" parameterType="Long">
        delete from evaluation_detail where evaluation_id = #{evaluationId}
    </delete>
    <delete id="deleteEvaluationDetailByDetailIds" parameterType="String">
        delete from evaluation_detail where detail_id in
        <foreach item="detailId" collection="array" open="(" separator="," close=")">
            #{detailId}
        </foreach>
    </delete>
</mapper>
ruoyi-system/src/main/resources/mapper/system/EvaluationDimensionMapper.xml
New file
@@ -0,0 +1,110 @@
<?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.ruoyi.system.mapper.EvaluationDimensionMapper">
    <resultMap type="EvaluationDimension" id="EvaluationDimensionResult">
        <result property="dimensionId"    column="dimension_id"    />
        <result property="dimensionName"    column="dimension_name"    />
        <result property="dimensionDesc"    column="dimension_desc"    />
        <result property="dimensionType"    column="dimension_type"    />
        <result property="options"    column="options"    />
        <result property="sortOrder"    column="sort_order"    />
        <result property="isRequired"    column="is_required"    />
        <result property="status"    column="status"    />
        <result property="createBy"    column="create_by"    />
        <result property="createTime"    column="create_time"    />
        <result property="updateBy"    column="update_by"    />
        <result property="updateTime"    column="update_time"    />
        <result property="remark"    column="remark"    />
    </resultMap>
    <sql id="selectEvaluationDimensionVo">
        select dimension_id, dimension_name, dimension_desc, dimension_type, options, sort_order, is_required, status, create_by, create_time, update_by, update_time, remark from evaluation_dimension
    </sql>
    <select id="selectEvaluationDimensionList" parameterType="EvaluationDimension" resultMap="EvaluationDimensionResult">
        <include refid="selectEvaluationDimensionVo"/>
        <where>
            <if test="dimensionName != null  and dimensionName != ''"> and dimension_name like concat('%', #{dimensionName}, '%')</if>
            <if test="dimensionDesc != null  and dimensionDesc != ''"> and dimension_desc like concat('%', #{dimensionDesc}, '%')</if>
            <if test="dimensionType != null  and dimensionType != ''"> and dimension_type = #{dimensionType}</if>
            <if test="sortOrder != null "> and sort_order = #{sortOrder}</if>
            <if test="isRequired != null  and isRequired != ''"> and is_required = #{isRequired}</if>
            <if test="status != null  and status != ''"> and status = #{status}</if>
        </where>
        order by sort_order asc, create_time desc
    </select>
    <select id="selectEvaluationDimensionByDimensionId" parameterType="Long" resultMap="EvaluationDimensionResult">
        <include refid="selectEvaluationDimensionVo"/>
        where dimension_id = #{dimensionId}
    </select>
    <select id="selectEnabledEvaluationDimensionList" resultMap="EvaluationDimensionResult">
        <include refid="selectEvaluationDimensionVo"/>
        where status = '0'
        order by sort_order asc, create_time desc
    </select>
    <insert id="insertEvaluationDimension" parameterType="EvaluationDimension" useGeneratedKeys="true" keyProperty="dimensionId">
        insert into evaluation_dimension
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="dimensionName != null and dimensionName != ''">dimension_name,</if>
            <if test="dimensionDesc != null">dimension_desc,</if>
            <if test="dimensionType != null and dimensionType != ''">dimension_type,</if>
            <if test="options != null">options,</if>
            <if test="sortOrder != null">sort_order,</if>
            <if test="isRequired != null">is_required,</if>
            <if test="status != null">status,</if>
            <if test="createBy != null">create_by,</if>
            <if test="createTime != null">create_time,</if>
            <if test="updateBy != null">update_by,</if>
            <if test="updateTime != null">update_time,</if>
            <if test="remark != null">remark,</if>
         </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="dimensionName != null and dimensionName != ''">#{dimensionName},</if>
            <if test="dimensionDesc != null">#{dimensionDesc},</if>
            <if test="dimensionType != null and dimensionType != ''">#{dimensionType},</if>
            <if test="options != null">#{options},</if>
            <if test="sortOrder != null">#{sortOrder},</if>
            <if test="isRequired != null">#{isRequired},</if>
            <if test="status != null">#{status},</if>
            <if test="createBy != null">#{createBy},</if>
            <if test="createTime != null">#{createTime},</if>
            <if test="updateBy != null">#{updateBy},</if>
            <if test="updateTime != null">#{updateTime},</if>
            <if test="remark != null">#{remark},</if>
         </trim>
    </insert>
    <update id="updateEvaluationDimension" parameterType="EvaluationDimension">
        update evaluation_dimension
        <trim prefix="SET" suffixOverrides=",">
            <if test="dimensionName != null and dimensionName != ''">dimension_name = #{dimensionName},</if>
            <if test="dimensionDesc != null">dimension_desc = #{dimensionDesc},</if>
            <if test="dimensionType != null and dimensionType != ''">dimension_type = #{dimensionType},</if>
            <if test="options != null">options = #{options},</if>
            <if test="sortOrder != null">sort_order = #{sortOrder},</if>
            <if test="isRequired != null">is_required = #{isRequired},</if>
            <if test="status != null">status = #{status},</if>
            <if test="updateBy != null">update_by = #{updateBy},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="remark != null">remark = #{remark},</if>
        </trim>
        where dimension_id = #{dimensionId}
    </update>
    <delete id="deleteEvaluationDimensionByDimensionId" parameterType="Long">
        delete from evaluation_dimension where dimension_id = #{dimensionId}
    </delete>
    <delete id="deleteEvaluationDimensionByDimensionIds" parameterType="String">
        delete from evaluation_dimension where dimension_id in
        <foreach item="dimensionId" collection="array" open="(" separator="," close=")">
            #{dimensionId}
        </foreach>
    </delete>
</mapper>
ruoyi-system/src/main/resources/mapper/system/VehicleEvaluationQrcodeMapper.xml
New file
@@ -0,0 +1,103 @@
<?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.ruoyi.system.mapper.VehicleEvaluationQrcodeMapper">
    <resultMap type="VehicleEvaluationQrcode" id="VehicleEvaluationQrcodeResult">
        <result property="qrcodeId"    column="qrcode_id"    />
        <result property="vehicleNo"    column="vehicle_no"    />
        <result property="qrcodeUrl"    column="qrcode_url"    />
        <result property="qrcodeContent"    column="qrcode_content"    />
        <result property="qrcodeImage"    column="qrcode_image"    />
        <result property="status"    column="status"    />
        <result property="createBy"    column="create_by"    />
        <result property="createTime"    column="create_time"    />
        <result property="updateBy"    column="update_by"    />
        <result property="updateTime"    column="update_time"    />
        <result property="remark"    column="remark"    />
    </resultMap>
    <sql id="selectVehicleEvaluationQrcodeVo">
        select qrcode_id, vehicle_no, qrcode_url, qrcode_content, qrcode_image, status, create_by, create_time, update_by, update_time, remark from vehicle_evaluation_qrcode
    </sql>
    <select id="selectVehicleEvaluationQrcodeList" parameterType="VehicleEvaluationQrcode" resultMap="VehicleEvaluationQrcodeResult">
        <include refid="selectVehicleEvaluationQrcodeVo"/>
        <where>
            <if test="vehicleNo != null  and vehicleNo != ''"> and vehicle_no like concat('%', #{vehicleNo}, '%')</if>
            <if test="qrcodeUrl != null  and qrcodeUrl != ''"> and qrcode_url like concat('%', #{qrcodeUrl}, '%')</if>
            <if test="qrcodeContent != null  and qrcodeContent != ''"> and qrcode_content like concat('%', #{qrcodeContent}, '%')</if>
            <if test="status != null  and status != ''"> and status = #{status}</if>
        </where>
        order by create_time desc
    </select>
    <select id="selectVehicleEvaluationQrcodeByQrcodeId" parameterType="Long" resultMap="VehicleEvaluationQrcodeResult">
        <include refid="selectVehicleEvaluationQrcodeVo"/>
        where qrcode_id = #{qrcodeId}
    </select>
    <select id="selectVehicleEvaluationQrcodeByVehicleNo" parameterType="String" resultMap="VehicleEvaluationQrcodeResult">
        <include refid="selectVehicleEvaluationQrcodeVo"/>
        where vehicle_no = #{vehicleNo}
    </select>
    <insert id="insertVehicleEvaluationQrcode" parameterType="VehicleEvaluationQrcode" useGeneratedKeys="true" keyProperty="qrcodeId">
        insert into vehicle_evaluation_qrcode
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="vehicleNo != null and vehicleNo != ''">vehicle_no,</if>
            <if test="qrcodeUrl != null and qrcodeUrl != ''">qrcode_url,</if>
            <if test="qrcodeContent != null and qrcodeContent != ''">qrcode_content,</if>
            <if test="qrcodeImage != null">qrcode_image,</if>
            <if test="status != null">status,</if>
            <if test="createBy != null">create_by,</if>
            <if test="createTime != null">create_time,</if>
            <if test="updateBy != null">update_by,</if>
            <if test="updateTime != null">update_time,</if>
            <if test="remark != null">remark,</if>
         </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="vehicleNo != null and vehicleNo != ''">#{vehicleNo},</if>
            <if test="qrcodeUrl != null and qrcodeUrl != ''">#{qrcodeUrl},</if>
            <if test="qrcodeContent != null and qrcodeContent != ''">#{qrcodeContent},</if>
            <if test="qrcodeImage != null">#{qrcodeImage},</if>
            <if test="status != null">#{status},</if>
            <if test="createBy != null">#{createBy},</if>
            <if test="createTime != null">#{createTime},</if>
            <if test="updateBy != null">#{updateBy},</if>
            <if test="updateTime != null">#{updateTime},</if>
            <if test="remark != null">#{remark},</if>
         </trim>
    </insert>
    <update id="updateVehicleEvaluationQrcode" parameterType="VehicleEvaluationQrcode">
        update vehicle_evaluation_qrcode
        <trim prefix="SET" suffixOverrides=",">
            <if test="vehicleNo != null and vehicleNo != ''">vehicle_no = #{vehicleNo},</if>
            <if test="qrcodeUrl != null and qrcodeUrl != ''">qrcode_url = #{qrcodeUrl},</if>
            <if test="qrcodeContent != null and qrcodeContent != ''">qrcode_content = #{qrcodeContent},</if>
            <if test="qrcodeImage != null">qrcode_image = #{qrcodeImage},</if>
            <if test="status != null">status = #{status},</if>
            <if test="updateBy != null">update_by = #{updateBy},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="remark != null">remark = #{remark},</if>
        </trim>
        where qrcode_id = #{qrcodeId}
    </update>
    <delete id="deleteVehicleEvaluationQrcodeByQrcodeId" parameterType="Long">
        delete from vehicle_evaluation_qrcode where qrcode_id = #{qrcodeId}
    </delete>
    <delete id="deleteVehicleEvaluationQrcodeByVehicleNo" parameterType="String">
        delete from vehicle_evaluation_qrcode where vehicle_no = #{vehicleNo}
    </delete>
    <delete id="deleteVehicleEvaluationQrcodeByQrcodeIds" parameterType="String">
        delete from vehicle_evaluation_qrcode where qrcode_id in
        <foreach item="qrcodeId" collection="array" open="(" separator="," close=")">
            #{qrcodeId}
        </foreach>
    </delete>
</mapper>
ruoyi-system/src/main/resources/mapper/system/VehicleInfoMapper.xml
@@ -13,6 +13,8 @@
        <result property="vehicleModel"   column="vehicle_model"   />
        <result property="status"         column="status"          />
        <result property="platformCode"   column="platform_code"   />
        <result property="deptId"         column="dept_id"         />
        <result property="deptName"       column="dept_name"       />
        <result property="createBy"       column="create_by"       />
        <result property="createTime"     column="create_time"     />
        <result property="updateBy"       column="update_by"       />
@@ -21,31 +23,38 @@
    </resultMap>
    <sql id="selectVehicleInfoVo">
        select vehicle_id, device_id, vehicle_no, vehicle_type, vehicle_brand, vehicle_model, status, platform_code, create_by, create_time, update_by, update_time, remark
        from tb_vehicle_info
        select v.vehicle_id, v.device_id, v.vehicle_no, v.vehicle_type, v.vehicle_brand, v.vehicle_model, v.status, v.platform_code, v.dept_id, d.dept_name, v.create_by, v.create_time, v.update_by, v.update_time, v.remark
        from tb_vehicle_info v
        left join sys_dept d on v.dept_id = d.dept_id
    </sql>
    <select id="selectVehicleInfoList" parameterType="VehicleInfo" resultMap="VehicleInfoResult">
        <include refid="selectVehicleInfoVo"/>
        <where>  
            <if test="vehicleNo != null  and vehicleNo != ''"> and vehicle_no = #{vehicleNo}</if>
            <if test="deviceId != null  and deviceId != ''"> and device_id = #{deviceId}</if>
            <if test="vehicleType != null  and vehicleType != ''"> and vehicle_type = #{vehicleType}</if>
            <if test="vehicleBrand != null  and vehicleBrand != ''"> and vehicle_brand = #{vehicleBrand}</if>
            <if test="vehicleModel != null  and vehicleModel != ''"> and vehicle_model = #{vehicleModel}</if>
            <if test="status != null  and status != ''"> and status = #{status}</if>
            <if test="platformCode != null  and platformCode != ''"> and platform_code = #{platformCode}</if>
            <if test="vehicleNo != null  and vehicleNo != ''"> and v.vehicle_no = #{vehicleNo}</if>
            <if test="deviceId != null  and deviceId != ''"> and v.device_id = #{deviceId}</if>
            <if test="vehicleType != null  and vehicleType != ''"> and v.vehicle_type = #{vehicleType}</if>
            <if test="vehicleBrand != null  and vehicleBrand != ''"> and v.vehicle_brand = #{vehicleBrand}</if>
            <if test="vehicleModel != null  and vehicleModel != ''"> and v.vehicle_model = #{vehicleModel}</if>
            <if test="status != null  and status != ''"> and v.status = #{status}</if>
            <if test="platformCode != null  and platformCode != ''"> and v.platform_code = #{platformCode}</if>
            <if test="deptId != null"> and v.dept_id = #{deptId}</if>
        </where>
    </select>
    
    <select id="selectVehicleInfoById" parameterType="Long" resultMap="VehicleInfoResult">
        <include refid="selectVehicleInfoVo"/>
        where vehicle_id = #{vehicleId}
        where v.vehicle_id = #{vehicleId}
    </select>
    <select id="selectVehicleInfoByPlateNumber" parameterType="String" resultMap="VehicleInfoResult">
        <include refid="selectVehicleInfoVo"/>
        where vehicle_no = #{plateNumber}
        where v.vehicle_no = #{plateNumber}
    </select>
    <select id="selectVehicleInfoByVehicleNo" parameterType="String" resultMap="VehicleInfoResult">
        <include refid="selectVehicleInfoVo"/>
        where v.vehicle_no = #{vehicleNo}
    </select>
        
    <insert id="insertVehicleInfo" parameterType="VehicleInfo" useGeneratedKeys="true" keyProperty="vehicleId">
@@ -58,6 +67,7 @@
            <if test="vehicleModel != null">vehicle_model,</if>
            <if test="status != null">status,</if>
            <if test="platformCode != null">platform_code,</if>
            <if test="deptId != null">dept_id,</if>
            <if test="createBy != null">create_by,</if>
            <if test="createTime != null">create_time,</if>
            <if test="updateBy != null">update_by,</if>
@@ -72,6 +82,7 @@
            <if test="vehicleModel != null">#{vehicleModel},</if>
            <if test="status != null">#{status},</if>
            <if test="platformCode != null">#{platformCode},</if>
            <if test="deptId != null">#{deptId},</if>
            <if test="createBy != null">#{createBy},</if>
            <if test="createTime != null">#{createTime},</if>
            <if test="updateBy != null">#{updateBy},</if>
@@ -90,6 +101,7 @@
            <if test="vehicleModel != null">vehicle_model = #{vehicleModel},</if>
            <if test="status != null">status = #{status},</if>
            <if test="platformCode != null">platform_code = #{platformCode},</if>
            <if test="deptId != null">dept_id = #{deptId},</if>
            <if test="updateBy != null">update_by = #{updateBy},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
            <if test="remark != null">remark = #{remark},</if>
ruoyi-ui/src/api/evaluation.js
New file
@@ -0,0 +1,181 @@
import request from '@/utils/request'
// èŽ·å–è¯„ä»·ç»´åº¦é…ç½®
export function getEvaluationDimensions() {
  return request({
    url: '/evaluation/dimensions',
    method: 'get'
  })
}
// æäº¤å®¢æˆ·è¯„ä»·
export function submitEvaluation(data) {
  return request({
    url: '/evaluation/submit',
    method: 'post',
    data: data
  })
}
// èŽ·å–å¾®ä¿¡ç”¨æˆ·ä¿¡æ¯
export function getWechatUserInfo(code) {
  return request({
    url: '/evaluation/wechat/userinfo',
    method: 'get',
    params: { code }
  })
}
// æŸ¥è¯¢å®¢æˆ·è¯„价列表
export function listEvaluation(query) {
  return request({
    url: '/evaluation/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢å®¢æˆ·è¯„价详细
export function getEvaluation(evaluationId) {
  return request({
    url: '/evaluation/' + evaluationId,
    method: 'get'
  })
}
// æ–°å¢žå®¢æˆ·è¯„ä»·
export function addEvaluation(data) {
  return request({
    url: '/evaluation',
    method: 'post',
    data: data
  })
}
// ä¿®æ”¹å®¢æˆ·è¯„ä»·
export function updateEvaluation(data) {
  return request({
    url: '/evaluation',
    method: 'put',
    data: data
  })
}
// åˆ é™¤å®¢æˆ·è¯„ä»·
export function delEvaluation(evaluationId) {
  return request({
    url: '/evaluation/' + evaluationId,
    method: 'delete'
  })
}
// ç”Ÿæˆè½¦è¾†è¯„价二维码
export function generateQrcode(data) {
  return request({
    url: '/evaluation/qrcode/generate',
    method: 'post',
    data: data
  })
}
// æ‰¹é‡ç”Ÿæˆè½¦è¾†è¯„价二维码
export function batchGenerateQrcode() {
  return request({
    url: '/evaluation/qrcode/batch',
    method: 'post'
  })
}
// æŸ¥è¯¢è¯„价维度配置列表
export function listDimension(query) {
  return request({
    url: '/evaluation/dimension/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢è¯„价维度配置详细
export function getDimension(dimensionId) {
  return request({
    url: '/evaluation/dimension/' + dimensionId,
    method: 'get'
  })
}
// æ–°å¢žè¯„价维度配置
export function addDimension(data) {
  return request({
    url: '/evaluation/dimension',
    method: 'post',
    data: data
  })
}
// ä¿®æ”¹è¯„价维度配置
export function updateDimension(data) {
  return request({
    url: '/evaluation/dimension',
    method: 'put',
    data: data
  })
}
// åˆ é™¤è¯„价维度配置
export function delDimension(dimensionId) {
  return request({
    url: '/evaluation/dimension/' + dimensionId,
    method: 'delete'
  })
}
// æŸ¥è¯¢è½¦è¾†è¯„价二维码列表
export function listQrcode(query) {
  return request({
    url: '/evaluation/qrcode/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢è½¦è¾†è¯„价二维码详细
export function getQrcode(qrcodeId) {
  return request({
    url: '/evaluation/qrcode/' + qrcodeId,
    method: 'get'
  })
}
// åˆ é™¤è½¦è¾†è¯„价二维码
export function delQrcode(qrcodeId) {
  return request({
    url: '/evaluation/qrcode/' + qrcodeId,
    method: 'delete'
  })
}
// æŸ¥è¯¢å®¢æˆ·è¯„价列表(用于统计页面)
export function listCustomerEvaluation(query) {
  return request({
    url: '/evaluation/list',
    method: 'get',
    params: query
  })
}
// æŸ¥è¯¢å®¢æˆ·è¯„价详细(用于统计页面)
export function getCustomerEvaluation(evaluationId) {
  return request({
    url: '/evaluation/' + evaluationId,
    method: 'get'
  })
}
// èŽ·å–è¯„ä»·ç»Ÿè®¡æ•°æ®
export function getEvaluationStatistics(query) {
  return request({
    url: '/evaluation/statistics',
    method: 'get',
    params: query
  })
}
ruoyi-ui/src/router/index.js
@@ -58,6 +58,18 @@
    meta: { title: '匿名订单查看', icon: 'eye',anonymous: true }
  },
  {
    path: '/evaluation',
    component: () => import('@/views/evaluation/index'),
    hidden: true,
    meta: { title: '客户评价', anonymous: true }
  },
  {
    path: '/evaluation/test',
    component: () => import('@/views/evaluation/test'),
    hidden: true,
    meta: { title: '评价功能测试', anonymous: true }
  },
  {
    path: '/404',
    component: () => import('@/views/error/404'),
    hidden: true
ruoyi-ui/src/views/evaluation/customer/index.vue
New file
@@ -0,0 +1,297 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="车牌号" prop="vehicleNo">
        <el-input
          v-model="queryParams.vehicleNo"
          placeholder="请输入车牌号"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="客户姓名" prop="customerName">
        <el-input
          v-model="queryParams.customerName"
          placeholder="请输入客户姓名"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="客户手机" prop="customerPhone">
        <el-input
          v-model="queryParams.customerPhone"
          placeholder="请输入客户手机号"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="评价时间">
        <el-date-picker
          v-model="dateRange"
          style="width: 240px"
          value-format="yyyy-MM-dd"
          type="daterange"
          range-separator="-"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
        ></el-date-picker>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
          type="danger"
          plain
          icon="el-icon-delete"
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['evaluation:customer:remove']"
        >删除</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="warning"
          plain
          icon="el-icon-download"
          size="mini"
          @click="handleExport"
          v-hasPermi="['evaluation:customer:export']"
        >导出</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>
    <el-table v-loading="loading" :data="customerEvaluationList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="评价ID" align="center" prop="evaluationId" />
      <el-table-column label="车牌号" align="center" prop="vehicleNo" />
      <el-table-column label="客户姓名" align="center" prop="customerName" />
      <el-table-column label="客户手机" align="center" prop="customerPhone" />
      <el-table-column label="微信昵称" align="center" prop="wechatNickname" />
      <el-table-column label="总评分" align="center" prop="totalScore">
        <template slot-scope="scope">
          <el-rate
            v-model="scope.row.totalScore"
            disabled
            show-score
            text-color="#ff9900"
            score-template="{value}">
          </el-rate>
        </template>
      </el-table-column>
      <el-table-column label="评价时间" align="center" prop="evaluationTime" width="180">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.evaluationTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-view"
            @click="handleView(scope.row)"
            v-hasPermi="['evaluation:customer:query']"
          >查看</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['evaluation:customer:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <pagination
      v-show="total>0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />
    <!-- è¯„价详情对话框 -->
    <el-dialog title="评价详情" :visible.sync="open" width="800px" append-to-body>
      <el-descriptions :column="2" border>
        <el-descriptions-item label="评价ID">{{ evaluationForm.evaluationId }}</el-descriptions-item>
        <el-descriptions-item label="车牌号">{{ evaluationForm.vehicleNo }}</el-descriptions-item>
        <el-descriptions-item label="客户姓名">{{ evaluationForm.customerName }}</el-descriptions-item>
        <el-descriptions-item label="客户手机">{{ evaluationForm.customerPhone }}</el-descriptions-item>
        <el-descriptions-item label="微信昵称">{{ evaluationForm.wechatNickname }}</el-descriptions-item>
        <el-descriptions-item label="微信头像">
          <el-image v-if="evaluationForm.wechatAvatar" :src="evaluationForm.wechatAvatar" style="width: 50px; height: 50px;"></el-image>
        </el-descriptions-item>
        <el-descriptions-item label="总评分">
          <el-rate
            v-model="evaluationForm.totalScore"
            disabled
            show-score
            text-color="#ff9900"
            score-template="{value}">
          </el-rate>
        </el-descriptions-item>
        <el-descriptions-item label="评价时间">{{ parseTime(evaluationForm.evaluationTime) }}</el-descriptions-item>
        <el-descriptions-item label="IP地址">{{ evaluationForm.ipAddress }}</el-descriptions-item>
        <el-descriptions-item label="用户代理" :span="2">{{ evaluationForm.userAgent }}</el-descriptions-item>
      </el-descriptions>
      <div style="margin-top: 20px;">
        <h4>评价详情</h4>
        <el-table :data="evaluationForm.evaluationDetails" border>
          <el-table-column label="评价维度" prop="dimension.dimensionName" />
          <el-table-column label="评分" align="center">
            <template slot-scope="scope">
              <el-rate
                v-if="scope.row.dimension.dimensionType === 'star'"
                v-model="scope.row.score"
                disabled
                show-score
                text-color="#ff9900"
                score-template="{value}">
              </el-rate>
              <span v-else-if="scope.row.dimension.dimensionType === 'select'">{{ scope.row.optionValue }}</span>
              <span v-else-if="scope.row.dimension.dimensionType === 'text'">{{ scope.row.textContent }}</span>
            </template>
          </el-table-column>
        </el-table>
      </div>
      <div slot="footer" class="dialog-footer">
        <el-button @click="cancel">关 é—­</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import { listEvaluation, getEvaluation, delEvaluation } from "@/api/evaluation";
export default {
  name: "CustomerEvaluation",
  dicts: [],
  data() {
    return {
      // é®ç½©å±‚
      loading: true,
      // é€‰ä¸­æ•°ç»„
      ids: [],
      // éžå•个禁用
      single: true,
      // éžå¤šä¸ªç¦ç”¨
      multiple: true,
      // æ˜¾ç¤ºæœç´¢æ¡ä»¶
      showSearch: true,
      // æ€»æ¡æ•°
      total: 0,
      // å®¢æˆ·è¯„价表格数据
      customerEvaluationList: [],
      // å¼¹å‡ºå±‚标题
      title: "",
      // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚
      open: false,
      // æ—¥æœŸèŒƒå›´
      dateRange: [],
      // æŸ¥è¯¢å‚æ•°
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        vehicleNo: null,
        customerName: null,
        customerPhone: null
      },
      // è¡¨å•参数
      evaluationForm: {}
    };
  },
  created() {
    this.getList();
  },
  methods: {
    /** æŸ¥è¯¢å®¢æˆ·è¯„价列表 */
    getList() {
      this.loading = true;
      listEvaluation(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
        this.customerEvaluationList = response.rows;
        this.total = response.total;
        this.loading = false;
      });
    },
    // å–消按钮
    cancel() {
      this.open = false;
      this.reset();
    },
    // è¡¨å•重置
    reset() {
      this.evaluationForm = {
        evaluationId: null,
        vehicleNo: null,
        customerName: null,
        customerPhone: null,
        wechatOpenid: null,
        wechatNickname: null,
        wechatAvatar: null,
        wechatPhone: null,
        totalScore: null,
        evaluationStatus: null,
        evaluationTime: null,
        ipAddress: null,
        userAgent: null,
        evaluationDetails: []
      };
      this.resetForm("form");
    },
    /** æœç´¢æŒ‰é’®æ“ä½œ */
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    /** é‡ç½®æŒ‰é’®æ“ä½œ */
    resetQuery() {
      this.dateRange = [];
      this.resetForm("queryForm");
      this.handleQuery();
    },
    // å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.evaluationId)
      this.single = selection.length!==1
      this.multiple = !selection.length
    },
    /** æŸ¥çœ‹æŒ‰é’®æ“ä½œ */
    handleView(row) {
      this.reset();
      const evaluationId = row.evaluationId || this.ids
      getEvaluation(evaluationId).then(response => {
        this.evaluationForm = response.data;
        this.open = true;
        this.title = "评价详情";
      });
    },
    /** åˆ é™¤æŒ‰é’®æ“ä½œ */
    handleDelete(row) {
      const evaluationIds = row.evaluationId || this.ids;
      this.$modal.confirm('是否确认删除客户评价编号为"' + evaluationIds + '"的数据项?').then(function() {
        return delEvaluation(evaluationIds);
      }).then(() => {
        this.getList();
        this.$modal.msgSuccess("删除成功");
      }).catch(() => {});
    },
    /** å¯¼å‡ºæŒ‰é’®æ“ä½œ */
    handleExport() {
      this.download('evaluation/export', {
        ...this.queryParams
      }, `customer_evaluation_${new Date().getTime()}.xlsx`)
    }
  }
};
</script>
ruoyi-ui/src/views/evaluation/dimension/index.vue
New file
@@ -0,0 +1,325 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="维度名称" prop="dimensionName">
        <el-input
          v-model="queryParams.dimensionName"
          placeholder="请输入维度名称"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="维度类型" prop="dimensionType">
        <el-select v-model="queryParams.dimensionType" placeholder="请选择维度类型" clearable>
          <el-option label="星级评价" value="star" />
          <el-option label="选择评价" value="select" />
          <el-option label="文本评价" value="text" />
        </el-select>
      </el-form-item>
      <el-form-item label="状态" prop="status">
        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
          <el-option label="正常" value="0" />
          <el-option label="停用" value="1" />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
          type="primary"
          plain
          icon="el-icon-plus"
          size="mini"
          @click="handleAdd"
          v-hasPermi="['evaluation:dimension:add']"
        >新增</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="success"
          plain
          icon="el-icon-edit"
          size="mini"
          :disabled="single"
          @click="handleUpdate"
          v-hasPermi="['evaluation:dimension:edit']"
        >修改</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="danger"
          plain
          icon="el-icon-delete"
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['evaluation:dimension:remove']"
        >删除</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>
    <el-table v-loading="loading" :data="dimensionList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="维度ID" align="center" prop="dimensionId" />
      <el-table-column label="维度名称" align="center" prop="dimensionName" />
      <el-table-column label="维度描述" align="center" prop="dimensionDesc" />
      <el-table-column label="维度类型" align="center" prop="dimensionType">
        <template slot-scope="scope">
          <span v-if="scope.row.dimensionType === 'star'">星级评价</span>
          <span v-else-if="scope.row.dimensionType === 'select'">选择评价</span>
          <span v-else-if="scope.row.dimensionType === 'text'">文本评价</span>
          <span v-else>{{ scope.row.dimensionType }}</span>
        </template>
      </el-table-column>
      <el-table-column label="排序" align="center" prop="sortOrder" />
      <el-table-column label="是否必填" align="center" prop="isRequired">
        <template slot-scope="scope">
          <span v-if="scope.row.isRequired === '1'">是</span>
          <span v-else-if="scope.row.isRequired === '0'">否</span>
          <span v-else>{{ scope.row.isRequired }}</span>
        </template>
      </el-table-column>
      <el-table-column label="状态" align="center" prop="status">
        <template slot-scope="scope">
          <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
        </template>
      </el-table-column>
      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-hasPermi="['evaluation:dimension:edit']"
          >修改</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['evaluation:dimension:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <pagination
      v-show="total>0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />
    <!-- æ·»åŠ æˆ–ä¿®æ”¹è¯„ä»·ç»´åº¦é…ç½®å¯¹è¯æ¡† -->
    <el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
        <el-form-item label="维度名称" prop="dimensionName">
          <el-input v-model="form.dimensionName" placeholder="请输入维度名称" />
        </el-form-item>
        <el-form-item label="维度描述" prop="dimensionDesc">
          <el-input v-model="form.dimensionDesc" type="textarea" placeholder="请输入维度描述" />
        </el-form-item>
        <el-form-item label="维度类型" prop="dimensionType">
          <el-select v-model="form.dimensionType" placeholder="请选择维度类型">
            <el-option label="星级评价" value="star" />
            <el-option label="选择评价" value="select" />
            <el-option label="文本评价" value="text" />
          </el-select>
        </el-form-item>
        <el-form-item label="选项配置" prop="options" v-if="form.dimensionType === 'select'">
          <el-input v-model="form.options" type="textarea" placeholder="请输入选项配置,JSON格式,如:[{&quot;label&quot;:&quot;满意&quot;,&quot;value&quot;:&quot;1&quot;},{&quot;label&quot;:&quot;不满意&quot;,&quot;value&quot;:&quot;0&quot;}]" />
        </el-form-item>
        <el-form-item label="排序" prop="sortOrder">
          <el-input-number v-model="form.sortOrder" controls-position="right" :min="0" />
        </el-form-item>
        <el-form-item label="是否必填" prop="isRequired">
          <el-radio-group v-model="form.isRequired">
            <el-radio label="1">是</el-radio>
            <el-radio label="0">否</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-radio-group v-model="form.status">
            <el-radio label="0">正常</el-radio>
            <el-radio label="1">停用</el-radio>
          </el-radio-group>
        </el-form-item>
        <el-form-item label="备注" prop="remark">
          <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
        <el-button @click="cancel">取 æ¶ˆ</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import { listDimension, getDimension, delDimension, addDimension, updateDimension } from "@/api/evaluation";
export default {
  name: "EvaluationDimension",
  dicts: ['dimension_type', 'sys_yes_no', 'sys_normal_disable'],
  data() {
    return {
      // é®ç½©å±‚
      loading: true,
      // é€‰ä¸­æ•°ç»„
      ids: [],
      // éžå•个禁用
      single: true,
      // éžå¤šä¸ªç¦ç”¨
      multiple: true,
      // æ˜¾ç¤ºæœç´¢æ¡ä»¶
      showSearch: true,
      // æ€»æ¡æ•°
      total: 0,
      // è¯„价维度配置表格数据
      dimensionList: [],
      // å¼¹å‡ºå±‚标题
      title: "",
      // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚
      open: false,
      // æŸ¥è¯¢å‚æ•°
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        dimensionName: null,
        dimensionType: null,
        status: null
      },
      // è¡¨å•参数
      form: {},
      // è¡¨å•校验
      rules: {
        dimensionName: [
          { required: true, message: "维度名称不能为空", trigger: "blur" }
        ],
        dimensionType: [
          { required: true, message: "维度类型不能为空", trigger: "change" }
        ],
        sortOrder: [
          { required: true, message: "排序不能为空", trigger: "blur" }
        ],
        isRequired: [
          { required: true, message: "是否必填不能为空", trigger: "change" }
        ],
        status: [
          { required: true, message: "状态不能为空", trigger: "change" }
        ]
      }
    };
  },
  created() {
    this.getList();
  },
  methods: {
    /** æŸ¥è¯¢è¯„价维度配置列表 */
    getList() {
      this.loading = true;
      listDimension(this.queryParams).then(response => {
        this.dimensionList = response.rows;
        this.total = response.total;
        this.loading = false;
      });
    },
    // å–消按钮
    cancel() {
      this.open = false;
      this.reset();
    },
    // è¡¨å•重置
    reset() {
      this.form = {
        dimensionId: null,
        dimensionName: null,
        dimensionDesc: null,
        dimensionType: null,
        options: null,
        sortOrder: 0,
        isRequired: "1",
        status: "0",
        remark: null
      };
      this.resetForm("form");
    },
    /** æœç´¢æŒ‰é’®æ“ä½œ */
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    /** é‡ç½®æŒ‰é’®æ“ä½œ */
    resetQuery() {
      this.resetForm("queryForm");
      this.handleQuery();
    },
    // å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.dimensionId)
      this.single = selection.length!==1
      this.multiple = !selection.length
    },
    /** æ–°å¢žæŒ‰é’®æ“ä½œ */
    handleAdd() {
      this.reset();
      this.open = true;
      this.title = "添加评价维度配置";
    },
    /** ä¿®æ”¹æŒ‰é’®æ“ä½œ */
    handleUpdate(row) {
      this.reset();
      const dimensionId = row.dimensionId || this.ids
      getDimension(dimensionId).then(response => {
        this.form = response.data;
        this.open = true;
        this.title = "修改评价维度配置";
      });
    },
    /** æäº¤æŒ‰é’® */
    submitForm() {
      this.$refs["form"].validate(valid => {
        if (valid) {
          if (this.form.dimensionId != null) {
            updateDimension(this.form).then(response => {
              this.$modal.msgSuccess("修改成功");
              this.open = false;
              this.getList();
            });
          } else {
            addDimension(this.form).then(response => {
              this.$modal.msgSuccess("新增成功");
              this.open = false;
              this.getList();
            });
          }
        }
      });
    },
    /** åˆ é™¤æŒ‰é’®æ“ä½œ */
    handleDelete(row) {
      const dimensionIds = row.dimensionId || this.ids;
      this.$modal.confirm('是否确认删除评价维度配置编号为"' + dimensionIds + '"的数据项?').then(function() {
        return delDimension(dimensionIds);
      }).then(() => {
        this.getList();
        this.$modal.msgSuccess("删除成功");
      }).catch(() => {});
    }
  }
};
</script>
ruoyi-ui/src/views/evaluation/index.vue
New file
@@ -0,0 +1,659 @@
<template>
  <div class="evaluation-container">
    <!-- é¡µé¢å¤´éƒ¨ -->
    <div class="evaluation-header">
      <h1>服务评价</h1>
      <p>感谢您使用我们的服务,请对本次服务进行评价</p>
    </div>
    <!-- è½¦è¾†ä¿¡æ¯ -->
    <div class="vehicle-info" v-if="vehicleNo">
      <div class="info-item">
        <span class="label">车牌号:</span>
        <span class="value">{{ vehicleNo }}</span>
      </div>
    </div>
    <!-- è¯„价表单 -->
    <div class="evaluation-form">
      <el-form ref="evaluationForm" :model="evaluationForm" :rules="rules" label-width="80px" size="small">
        <!-- å®¢æˆ·ä¿¡æ¯ -->
        <div class="form-section">
          <h3>客户信息</h3>
          <el-form-item label="姓名" prop="customerName">
            <el-input v-model="evaluationForm.customerName" placeholder="请输入您的姓名" />
          </el-form-item>
          <el-form-item label="手机号" prop="customerPhone">
            <el-input v-model="evaluationForm.customerPhone" placeholder="请输入您的手机号" />
          </el-form-item>
        </div>
        <!-- è¯„价维度 -->
        <div class="form-section">
          <h3>服务评价</h3>
          <div v-for="dimension in dimensions" :key="dimension.dimensionId" class="dimension-item">
            <!-- æ˜Ÿçº§è¯„ä»· - æ ‡é¢˜å’Œè¯„分在同一行 -->
            <div v-if="dimension.dimensionType === 'star'" class="star-rating-inline">
              <div class="dimension-title-inline">
                <span>{{ dimension.dimensionName }}</span>
                <span v-if="dimension.isRequired === '1'" class="required">*</span>
              </div>
              <div class="star-rating-content">
                <el-rate
                  :value="getDimensionScore(dimension.dimensionId)"
                  :max="5"
                  show-text
                  :texts="['很差', '较差', '一般', '满意', '非常满意']"
                  @change="updateDimensionScore(dimension.dimensionId, $event)"
                  size="small"
                />
              </div>
            </div>
            <!-- é€‰æ‹©è¯„ä»· -->
            <div v-else-if="dimension.dimensionType === 'select'">
              <div class="dimension-title">
                <span>{{ dimension.dimensionName }}</span>
                <span v-if="dimension.isRequired === '1'" class="required">*</span>
              </div>
              <div class="dimension-desc" v-if="dimension.dimensionDesc">
                {{ dimension.dimensionDesc }}
              </div>
              <div class="select-rating">
                <el-radio-group :value="getDimensionOption(dimension.dimensionId)" @change="updateDimensionOption(dimension.dimensionId, $event)" size="small">
                  <el-radio v-for="option in getDimensionOptions(dimension)" :key="option.value" :label="option.value">
                    {{ option.label }}
                  </el-radio>
                </el-radio-group>
              </div>
            </div>
            <!-- æ–‡æœ¬è¯„ä»· -->
            <div v-else-if="dimension.dimensionType === 'text'">
              <div class="dimension-title">
                <span>{{ dimension.dimensionName }}</span>
                <span v-if="dimension.isRequired === '1'" class="required">*</span>
              </div>
              <div class="dimension-desc" v-if="dimension.dimensionDesc">
                {{ dimension.dimensionDesc }}
              </div>
              <div class="text-rating">
                <el-input
                  :value="getDimensionText(dimension.dimensionId)"
                  type="textarea"
                  :rows="2"
                  placeholder="请输入您的意见或建议"
                  @input="updateDimensionText(dimension.dimensionId, $event)"
                  size="small"
                />
              </div>
            </div>
          </div>
        </div>
        <!-- æäº¤æŒ‰é’® -->
        <div class="form-actions">
          <el-button type="primary" size="large" @click="submitEvaluation" :loading="submitting">
            æäº¤è¯„ä»·
          </el-button>
        </div>
      </el-form>
    </div>
    <!-- ç»“果页面 -->
    <div v-if="showResult" class="result-page">
      <div class="result-content">
        <div class="result-icon">
          <i class="el-icon-success" style="font-size: 60px; color: #67C23A;"></i>
        </div>
        <div class="result-message">
          {{ resultMessage }}
        </div>
        <div class="result-actions">
          <el-button type="primary" @click="resetForm">再次评价</el-button>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { getEvaluationDimensions, submitEvaluation, getWechatUserInfo } from "@/api/evaluation";
export default {
  name: "Evaluation",
  data() {
    return {
      vehicleNo: '',
      dimensions: [],
      evaluationForm: {
        vehicleNo: '',
        customerName: '',
        customerPhone: '',
        wechatOpenid: '',
        wechatNickname: '',
        wechatAvatar: '',
        wechatPhone: '',
        evaluationDetails: []
      },
      rules: {
        customerName: [
          { required: true, message: '请输入您的姓名', trigger: 'blur' }
        ],
        customerPhone: [
          { required: true, message: '请输入您的手机号', trigger: 'blur' },
          { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur' }
        ]
      },
      submitting: false,
      showResult: false,
      resultMessage: ''
    };
  },
  created() {
    this.initPage();
  },
  methods: {
    // åˆå§‹åŒ–页面
    async initPage() {
      // èŽ·å–URL参数
      this.vehicleNo = this.$route.query.vehicle || '';
      this.evaluationForm.vehicleNo = this.vehicleNo;
      // æ£€æŸ¥æ˜¯å¦åœ¨å¾®ä¿¡çŽ¯å¢ƒä¸­
      if (this.isWechatBrowser()) {
        await this.handleWechatAuth();
      }
      // åŠ è½½è¯„ä»·ç»´åº¦
      await this.loadDimensions();
    },
    // åˆ¤æ–­æ˜¯å¦ä¸ºå¾®ä¿¡æµè§ˆå™¨
    isWechatBrowser() {
      const ua = navigator.userAgent.toLowerCase();
      return ua.indexOf('micromessenger') !== -1;
    },
    // å¤„理微信授权
    async handleWechatAuth() {
      const code = this.$route.query.code;
      if (code) {
        try {
          const response = await getWechatUserInfo(code);
          if (response.code === 200) {
            const userInfo = response.data;
            this.evaluationForm.wechatOpenid = userInfo.openid;
            this.evaluationForm.wechatNickname = userInfo.nickname;
            this.evaluationForm.wechatAvatar = userInfo.headimgurl;
            this.evaluationForm.wechatPhone = userInfo.phone || '';
          }
        } catch (error) {
          console.error('获取微信用户信息失败:', error);
        }
      }
    },
    // åŠ è½½è¯„ä»·ç»´åº¦
    async loadDimensions() {
      try {
        const response = await getEvaluationDimensions();
        if (response.code === 200) {
          this.dimensions = response.data;
        }
      } catch (error) {
        console.error('加载评价维度失败:', error);
        this.$message.error('加载评价维度失败');
      }
    },
    // èŽ·å–ç»´åº¦è¯„åˆ†
    getDimensionScore(dimensionId) {
      const detail = this.evaluationForm.evaluationDetails.find(d => d.dimensionId === dimensionId);
      return detail ? detail.score : 0;
    },
    // æ›´æ–°ç»´åº¦è¯„分
    updateDimensionScore(dimensionId, score) {
      let detail = this.evaluationForm.evaluationDetails.find(d => d.dimensionId === dimensionId);
      if (!detail) {
        detail = { dimensionId, score: 0 };
        this.evaluationForm.evaluationDetails.push(detail);
      }
      detail.score = score;
    },
    // èŽ·å–ç»´åº¦é€‰é¡¹
    getDimensionOption(dimensionId) {
      const detail = this.evaluationForm.evaluationDetails.find(d => d.dimensionId === dimensionId);
      return detail ? detail.optionValue : '';
    },
    // æ›´æ–°ç»´åº¦é€‰é¡¹
    updateDimensionOption(dimensionId, optionValue) {
      let detail = this.evaluationForm.evaluationDetails.find(d => d.dimensionId === dimensionId);
      if (!detail) {
        detail = { dimensionId, optionValue: '' };
        this.evaluationForm.evaluationDetails.push(detail);
      }
      detail.optionValue = optionValue;
    },
    // èŽ·å–ç»´åº¦æ–‡æœ¬
    getDimensionText(dimensionId) {
      const detail = this.evaluationForm.evaluationDetails.find(d => d.dimensionId === dimensionId);
      return detail ? detail.textContent : '';
    },
    // æ›´æ–°ç»´åº¦æ–‡æœ¬
    updateDimensionText(dimensionId, textContent) {
      let detail = this.evaluationForm.evaluationDetails.find(d => d.dimensionId === dimensionId);
      if (!detail) {
        detail = { dimensionId, textContent: '' };
        this.evaluationForm.evaluationDetails.push(detail);
      }
      detail.textContent = textContent;
    },
    // èŽ·å–ç»´åº¦é€‰é¡¹é…ç½®
    getDimensionOptions(dimension) {
      if (!dimension.options) return [];
      try {
        return JSON.parse(dimension.options);
      } catch (error) {
        return [];
      }
    },
    // æäº¤è¯„ä»·
    async submitEvaluation() {
      try {
        // è¡¨å•验证
        await this.$refs.evaluationForm.validate();
        // æ£€æŸ¥å¿…填的评价维度
        for (const dimension of this.dimensions) {
          if (dimension.isRequired === '1') {
            const detail = this.evaluationForm.evaluationDetails.find(d => d.dimensionId === dimension.dimensionId);
            if (!detail || (dimension.dimensionType === 'star' && !detail.score) ||
                (dimension.dimensionType === 'select' && !detail.optionValue) ||
                (dimension.dimensionType === 'text' && !detail.textContent)) {
              this.$message.error(`请完成${dimension.dimensionName}的评价`);
              return;
            }
          }
        }
        this.submitting = true;
        // æäº¤è¯„ä»·
        const response = await submitEvaluation(this.evaluationForm);
        if (response.code === 200) {
          this.resultMessage = response.msg;
          this.showResult = true;
        } else {
          this.$message.error(response.msg || '提交失败,请重试');
        }
      } catch (error) {
        console.error('提交评价失败:', error);
        this.$message.error('提交失败,请重试');
      } finally {
        this.submitting = false;
      }
    },
    // é‡ç½®è¡¨å•
    resetForm() {
      this.showResult = false;
      this.evaluationForm.customerName = '';
      this.evaluationForm.customerPhone = '';
      this.evaluationForm.evaluationDetails = [];
      this.$refs.evaluationForm.resetFields();
    }
  }
};
</script>
<style scoped>
.evaluation-container {
  max-width: 600px;
  margin: 0 auto;
  padding: 8px;
  background-color: #f5f5f5;
  min-height: 100vh;
}
.evaluation-header {
  text-align: center;
  margin-bottom: 12px;
  padding: 12px;
  background: white;
  border-radius: 6px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.evaluation-header h1 {
  color: #333;
  margin: 0 0 6px 0;
  font-size: 20px;
}
.evaluation-header p {
  color: #666;
  margin: 0;
  font-size: 14px;
}
.vehicle-info {
  background: white;
  padding: 8px 12px;
  border-radius: 6px;
  margin-bottom: 12px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.info-item {
  display: flex;
  align-items: center;
}
.label {
  font-weight: bold;
  color: #333;
  margin-right: 8px;
  font-size: 14px;
}
.value {
  color: #666;
  font-size: 14px;
}
.evaluation-form {
  background: white;
  padding: 12px;
  border-radius: 6px;
  box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.form-section {
  margin-bottom: 16px;
}
.form-section h3 {
  color: #333;
  margin: 0 0 12px 0;
  padding-bottom: 6px;
  border-bottom: 1px solid #409EFF;
  font-size: 16px;
}
.dimension-item {
  margin-bottom: 12px;
  padding: 8px;
  border: 1px solid #e4e7ed;
  border-radius: 4px;
  background-color: #fafafa;
}
.dimension-title {
  font-weight: bold;
  color: #333;
  margin-bottom: 4px;
  font-size: 14px;
}
.required {
  color: #f56c6c;
  margin-left: 3px;
}
.dimension-desc {
  color: #666;
  font-size: 12px;
  margin-bottom: 8px;
}
/* æ˜Ÿçº§è¯„价内联布局 */
.star-rating-inline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 8px;
}
.dimension-title-inline {
  font-weight: bold;
  color: #333;
  font-size: 14px;
  flex-shrink: 0;
}
.star-rating-content {
  flex: 1;
  min-width: 200px;
}
.star-rating, .select-rating, .text-rating {
  margin-top: 6px;
}
/* ä¼˜åŒ–星级评分显示 */
.star-rating .el-rate {
  font-size: 18px;
}
.star-rating .el-rate__text {
  font-size: 12px;
  margin-left: 8px;
}
/* ä¼˜åŒ–选择评价显示 */
.select-rating .el-radio {
  margin-right: 12px;
  margin-bottom: 4px;
}
.select-rating .el-radio__label {
  font-size: 13px;
}
/* ä¼˜åŒ–文本评价显示 */
.text-rating .el-textarea__inner {
  font-size: 13px;
  min-height: 60px !important;
}
/* ä¼˜åŒ–表单项间距 */
.el-form-item {
  margin-bottom: 12px;
}
.el-form-item__label {
  font-size: 13px;
  line-height: 28px;
}
.el-input__inner {
  font-size: 13px;
  height: 32px;
  line-height: 32px;
}
.form-actions {
  text-align: center;
  margin-top: 16px;
}
.form-actions .el-button {
  padding: 10px 30px;
  font-size: 14px;
}
.result-page {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0,0,0,0.5);
  display: flex;
  align-items: center;
  justify-content: center;
  z-index: 1000;
}
.result-content {
  background: white;
  padding: 30px;
  border-radius: 8px;
  text-align: center;
  max-width: 350px;
  width: 90%;
}
.result-icon {
  margin-bottom: 16px;
}
.result-message {
  font-size: 16px;
  color: #333;
  margin-bottom: 20px;
}
.result-actions {
  text-align: center;
}
/* ç§»åŠ¨ç«¯é€‚é… */
@media (max-width: 768px) {
  .evaluation-container {
    padding: 6px;
  }
  .evaluation-header {
    padding: 10px;
    margin-bottom: 8px;
  }
  .evaluation-header h1 {
    font-size: 18px;
  }
  .evaluation-header p {
    font-size: 13px;
  }
  .evaluation-form {
    padding: 10px;
  }
  .dimension-item {
    padding: 6px;
    margin-bottom: 8px;
  }
  .form-section {
    margin-bottom: 12px;
  }
  .form-section h3 {
    font-size: 15px;
    margin-bottom: 8px;
  }
  .dimension-title {
    font-size: 13px;
  }
  .dimension-desc {
    font-size: 11px;
  }
  .star-rating .el-rate {
    font-size: 16px;
  }
  /* ç§»åŠ¨ç«¯æ˜Ÿçº§è¯„ä»·å†…è”å¸ƒå±€ */
  .star-rating-inline {
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
  }
  .dimension-title-inline {
    font-size: 13px;
  }
  .star-rating-content {
    min-width: auto;
    width: 100%;
  }
  .select-rating .el-radio {
    margin-right: 8px;
  }
  .select-rating .el-radio__label {
    font-size: 12px;
  }
  .text-rating .el-textarea__inner {
    font-size: 12px;
    min-height: 50px !important;
  }
  .el-form-item {
    margin-bottom: 8px;
  }
  .el-form-item__label {
    font-size: 12px;
    line-height: 26px;
  }
  .el-input__inner {
    font-size: 12px;
    height: 30px;
    line-height: 30px;
  }
  .form-actions {
    margin-top: 12px;
  }
  .form-actions .el-button {
    padding: 8px 24px;
    font-size: 13px;
  }
}
/* è¶…小屏幕适配 */
@media (max-width: 480px) {
  .evaluation-container {
    padding: 4px;
  }
  .evaluation-header {
    padding: 8px;
  }
  .evaluation-form {
    padding: 8px;
  }
  .dimension-item {
    padding: 4px;
  }
  .star-rating .el-rate {
    font-size: 14px;
  }
  .text-rating .el-textarea__inner {
    min-height: 40px !important;
  }
}
</style>
ruoyi-ui/src/views/evaluation/qrcode/index.vue
New file
@@ -0,0 +1,345 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="车牌号" prop="vehicleNo">
        <el-input
          v-model="queryParams.vehicleNo"
          placeholder="请输入车牌号"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="状态" prop="status">
        <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
          <el-option label="正常" value="0" />
          <el-option label="停用" value="1" />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
          type="primary"
          plain
          icon="el-icon-plus"
          size="mini"
          @click="handleAdd"
          v-hasPermi="['evaluation:qrcode:generate']"
        >生成二维码</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="success"
          plain
          icon="el-icon-upload2"
          size="mini"
          @click="handleBatchGenerate"
          v-hasPermi="['evaluation:qrcode:batch']"
        >批量生成</el-button>
      </el-col>
      <el-col :span="1.5">
        <el-button
          type="danger"
          plain
          icon="el-icon-delete"
          size="mini"
          :disabled="multiple"
          @click="handleDelete"
          v-hasPermi="['evaluation:qrcode:remove']"
        >删除</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
    </el-row>
    <el-table v-loading="loading" :data="qrcodeList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="二维码ID" align="center" prop="qrcodeId" />
      <el-table-column label="车牌号" align="center" prop="vehicleNo" />
      <el-table-column label="二维码URL" align="center" prop="qrcodeUrl" width="200">
        <template slot-scope="scope">
          <el-link :href="scope.row.qrcodeUrl" target="_blank" type="primary">{{ scope.row.qrcodeUrl }}</el-link>
        </template>
      </el-table-column>
      <el-table-column label="二维码内容" align="center" prop="qrcodeContent" />
      <el-table-column label="二维码图片" align="center" prop="qrcodeImage" width="100">
        <template slot-scope="scope">
          <el-image
            v-if="scope.row.qrcodeImage"
            :src="getImageUrl(scope.row.qrcodeImage)"
            :preview-src-list="[getImageUrl(scope.row.qrcodeImage)]"
            style="width: 50px; height: 50px;"
            fit="cover">
          </el-image>
        </template>
      </el-table-column>
      <el-table-column label="状态" align="center" prop="status">
        <template slot-scope="scope">
          <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
        </template>
      </el-table-column>
      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-view"
            @click="handleView(scope.row)"
            v-hasPermi="['evaluation:qrcode:query']"
          >查看</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-download"
            @click="handleDownload(scope.row)"
          >下载</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-hasPermi="['evaluation:qrcode:remove']"
          >删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    <pagination
      v-show="total>0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />
    <!-- ç”ŸæˆäºŒç»´ç å¯¹è¯æ¡† -->
    <el-dialog title="生成二维码" :visible.sync="open" width="500px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
        <el-form-item label="车牌号" prop="vehicleNo">
          <el-input v-model="form.vehicleNo" placeholder="请输入车牌号" />
        </el-form-item>
        <el-form-item label="二维码URL" prop="qrcodeUrl">
          <el-input v-model="form.qrcodeUrl" placeholder="请输入二维码URL" />
        </el-form-item>
      </el-form>
      <div slot="footer" class="dialog-footer">
        <el-button type="primary" @click="submitForm">ç¡® å®š</el-button>
        <el-button @click="cancel">取 æ¶ˆ</el-button>
      </div>
    </el-dialog>
    <!-- äºŒç»´ç è¯¦æƒ…对话框 -->
    <el-dialog title="二维码详情" :visible.sync="viewOpen" width="600px" append-to-body>
      <el-descriptions :column="2" border>
        <el-descriptions-item label="二维码ID">{{ viewForm.qrcodeId }}</el-descriptions-item>
        <el-descriptions-item label="车牌号">{{ viewForm.vehicleNo }}</el-descriptions-item>
        <el-descriptions-item label="二维码URL" :span="2">
          <el-link :href="viewForm.qrcodeUrl" target="_blank" type="primary">{{ viewForm.qrcodeUrl }}</el-link>
        </el-descriptions-item>
        <el-descriptions-item label="二维码内容" :span="2">{{ viewForm.qrcodeContent }}</el-descriptions-item>
        <el-descriptions-item label="状态">
          <dict-tag :options="dict.type.sys_normal_disable" :value="viewForm.status"/>
        </el-descriptions-item>
        <el-descriptions-item label="创建时间">{{ parseTime(viewForm.createTime) }}</el-descriptions-item>
        <el-descriptions-item label="二维码图片" :span="2">
          <el-image
            v-if="viewForm.qrcodeImage"
            :src="getImageUrl(viewForm.qrcodeImage)"
            style="width: 200px; height: 200px;"
            fit="cover">
          </el-image>
        </el-descriptions-item>
      </el-descriptions>
      <div slot="footer" class="dialog-footer">
        <el-button @click="viewOpen = false">关 é—­</el-button>
        <el-button type="primary" @click="handleDownload(viewForm)">下载二维码</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import { listQrcode, getQrcode, delQrcode, generateQrcode, batchGenerateQrcode } from "@/api/evaluation";
export default {
  name: "VehicleEvaluationQrcode",
  dicts: ['sys_normal_disable'],
  data() {
    return {
      // é®ç½©å±‚
      loading: true,
      // é€‰ä¸­æ•°ç»„
      ids: [],
      // éžå•个禁用
      single: true,
      // éžå¤šä¸ªç¦ç”¨
      multiple: true,
      // æ˜¾ç¤ºæœç´¢æ¡ä»¶
      showSearch: true,
      // æ€»æ¡æ•°
      total: 0,
      // è½¦è¾†è¯„价二维码表格数据
      qrcodeList: [],
      // å¼¹å‡ºå±‚标题
      title: "",
      // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚
      open: false,
      // æ˜¯å¦æ˜¾ç¤ºæŸ¥çœ‹å¼¹å‡ºå±‚
      viewOpen: false,
      // æŸ¥è¯¢å‚æ•°
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        vehicleNo: null,
        status: null
      },
      // è¡¨å•参数
      form: {},
      // æŸ¥çœ‹è¡¨å•参数
      viewForm: {},
      // è¡¨å•校验
      rules: {
        vehicleNo: [
          { required: true, message: "车牌号不能为空", trigger: "blur" }
        ],
        qrcodeUrl: [
          { required: true, message: "二维码URL不能为空", trigger: "blur" },
          { type: 'url', message: '请输入正确的URL格式', trigger: 'blur' }
        ]
      }
    };
  },
  created() {
    this.getList();
  },
  methods: {
    /** æŸ¥è¯¢è½¦è¾†è¯„价二维码列表 */
    getList() {
      this.loading = true;
      listQrcode(this.queryParams).then(response => {
        this.qrcodeList = response.rows;
        this.total = response.total;
        this.loading = false;
      });
    },
    // å–消按钮
    cancel() {
      this.open = false;
      this.reset();
    },
    // è¡¨å•重置
    reset() {
      this.form = {
        qrcodeId: null,
        vehicleNo: null,
        qrcodeUrl: null,
        qrcodeContent: null,
        qrcodeImage: null,
        status: "0"
      };
      this.resetForm("form");
    },
    /** æœç´¢æŒ‰é’®æ“ä½œ */
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    /** é‡ç½®æŒ‰é’®æ“ä½œ */
    resetQuery() {
      this.resetForm("queryForm");
      this.handleQuery();
    },
    // å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.qrcodeId)
      this.single = selection.length!==1
      this.multiple = !selection.length
    },
    /** æ–°å¢žæŒ‰é’®æ“ä½œ */
    handleAdd() {
      this.reset();
      this.open = true;
      this.title = "生成二维码";
    },
    /** æäº¤æŒ‰é’® */
    submitForm() {
      this.$refs["form"].validate(valid => {
        if (valid) {
          const params = {
            vehicleNo: this.form.vehicleNo,
            qrcodeUrl: this.form.qrcodeUrl
          };
          generateQrcode(params).then(response => {
            this.$modal.msgSuccess("生成成功");
            this.open = false;
            this.getList();
          });
        }
      });
    },
    /** æ‰¹é‡ç”ŸæˆæŒ‰é’®æ“ä½œ */
    handleBatchGenerate() {
      this.$modal.confirm('是否确认批量生成所有车辆的二维码?').then(function() {
        return batchGenerateQrcode();
      }).then(() => {
        this.getList();
        this.$modal.msgSuccess("批量生成成功");
      }).catch(() => {});
    },
    /** æŸ¥çœ‹æŒ‰é’®æ“ä½œ */
    handleView(row) {
      this.viewForm = row;
      this.viewOpen = true;
    },
    /** ä¸‹è½½æŒ‰é’®æ“ä½œ */
    handleDownload(row) {
      if (row.qrcodeImage) {
        const link = document.createElement('a');
        link.href = this.getImageUrl(row.qrcodeImage);
        link.download = `qrcode_${row.vehicleNo}.png`;
        link.click();
      }
    },
    /** åˆ é™¤æŒ‰é’®æ“ä½œ */
    handleDelete(row) {
      const qrcodeIds = row.qrcodeId || this.ids;
      this.$modal.confirm('是否确认删除车辆评价二维码编号为"' + qrcodeIds + '"的数据项?').then(function() {
        return delQrcode(qrcodeIds);
      }).then(() => {
        this.getList();
        this.$modal.msgSuccess("删除成功");
      }).catch(() => {});
    },
    /** èŽ·å–å›¾ç‰‡URL */
    getImageUrl(imagePath) {
      if (imagePath) {
        // å¦‚果是base64格式,直接返回
        if (imagePath.startsWith('data:image/')) {
          return imagePath;
        }
        // å¦‚果路径已经包含http,直接返回
        if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) {
          return imagePath;
        }
        // å¦‚果路径以/开头,直接拼接域名
        if (imagePath.startsWith('/')) {
          return process.env.VUE_APP_BASE_API + imagePath;
        }
        // å…¶ä»–情况,添加/前缀后拼接域名
        return process.env.VUE_APP_BASE_API + '/' + imagePath;
      }
      return '';
    }
  }
};
</script>
ruoyi-ui/src/views/evaluation/statistics/index.vue
New file
@@ -0,0 +1,512 @@
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
      <el-form-item label="车牌号" prop="vehicleNo">
        <el-input
          v-model="queryParams.vehicleNo"
          placeholder="请输入车牌号"
          clearable
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="评价时间" prop="evaluationTime">
        <el-date-picker
          v-model="dateRange"
          style="width: 240px"
          value-format="yyyy-MM-dd"
          type="daterange"
          range-separator="-"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
        ></el-date-picker>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>
    <el-row :gutter="32">
      <el-col :xs="24" :sm="12" :lg="6" class="card-panel-col">
        <div class="card-panel">
          <div class="card-panel-icon-wrapper icon-people">
            <svg-icon icon-class="peoples" class-name="card-panel-icon" />
          </div>
          <div class="card-panel-description">
            <div class="card-panel-text">总评价数</div>
            <div class="card-panel-num">{{ statisticsData.totalEvaluations }}</div>
          </div>
        </div>
      </el-col>
      <el-col :xs="24" :sm="12" :lg="6" class="card-panel-col">
        <div class="card-panel">
          <div class="card-panel-icon-wrapper icon-message">
            <svg-icon icon-class="message" class-name="card-panel-icon" />
          </div>
          <div class="card-panel-description">
            <div class="card-panel-text">平均评分</div>
            <div class="card-panel-num">{{ statisticsData.averageScore }}</div>
          </div>
        </div>
      </el-col>
      <el-col :xs="24" :sm="12" :lg="6" class="card-panel-col">
        <div class="card-panel">
          <div class="card-panel-icon-wrapper icon-star">
            <svg-icon icon-class="star" class-name="card-panel-icon" />
          </div>
          <div class="card-panel-description">
            <div class="card-panel-text">好评率</div>
            <div class="card-panel-num">{{ statisticsData.goodRate }}%</div>
          </div>
        </div>
      </el-col>
      <el-col :xs="24" :sm="12" :lg="6" class="card-panel-col">
        <div class="card-panel">
          <div class="card-panel-icon-wrapper icon-vehicle">
            <svg-icon icon-class="car" class-name="card-panel-icon" />
          </div>
          <div class="card-panel-description">
            <div class="card-panel-text">参与车辆</div>
            <div class="card-panel-num">{{ statisticsData.vehicleCount }}</div>
          </div>
        </div>
      </el-col>
    </el-row>
    <el-row :gutter="32" style="margin-top: 20px;">
      <el-col :xs="24" :sm="24" :lg="12">
        <div class="chart-container">
          <div class="chart-title">评分分布</div>
          <div ref="scoreChart" class="chart"></div>
        </div>
      </el-col>
      <el-col :xs="24" :sm="24" :lg="12">
        <div class="chart-container">
          <div class="chart-title">月度评价趋势</div>
          <div ref="trendChart" class="chart"></div>
        </div>
      </el-col>
    </el-row>
    <el-row style="margin-top: 20px;">
      <el-col :span="24">
        <div class="chart-container">
          <div class="chart-title">各维度评分统计</div>
          <div ref="dimensionChart" class="chart"></div>
        </div>
      </el-col>
    </el-row>
    <el-table v-loading="loading" :data="evaluationList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="评价ID" align="center" prop="evaluationId" />
      <el-table-column label="车牌号" align="center" prop="vehicleNo" />
      <el-table-column label="客户姓名" align="center" prop="customerName" />
      <el-table-column label="客户手机" align="center" prop="customerPhone" />
      <el-table-column label="综合评分" align="center" prop="overallScore">
        <template slot-scope="scope">
          <el-rate
            v-model="scope.row.overallScore"
            disabled
            show-score
            text-color="#ff9900"
            score-template="{value}"
          />
        </template>
      </el-table-column>
      <el-table-column label="评价时间" align="center" prop="evaluationTime" width="180">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.evaluationTime, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
        </template>
      </el-table-column>
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-view"
            @click="handleView(scope.row)"
          >查看详情</el-button>
        </template>
      </el-table-column>
    </el-table>
    <pagination
      v-show="total>0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />
    <!-- è¯„价详情对话框 -->
    <el-dialog title="评价详情" :visible.sync="detailOpen" width="600px" append-to-body>
      <el-descriptions :column="2" border>
        <el-descriptions-item label="评价ID">{{ detailForm.evaluationId }}</el-descriptions-item>
        <el-descriptions-item label="车牌号">{{ detailForm.vehicleNo }}</el-descriptions-item>
        <el-descriptions-item label="客户姓名">{{ detailForm.customerName }}</el-descriptions-item>
        <el-descriptions-item label="客户手机">{{ detailForm.customerPhone }}</el-descriptions-item>
        <el-descriptions-item label="微信昵称">{{ detailForm.wechatNickname }}</el-descriptions-item>
        <el-descriptions-item label="综合评分">
          <el-rate
            v-model="detailForm.overallScore"
            disabled
            show-score
            text-color="#ff9900"
            score-template="{value}"
          />
        </el-descriptions-item>
        <el-descriptions-item label="评价时间" :span="2">
          {{ parseTime(detailForm.evaluationTime, '{y}-{m}-{d} {h}:{i}:{s}') }}
        </el-descriptions-item>
      </el-descriptions>
      <div style="margin-top: 20px;">
        <h4>详细评价</h4>
        <el-table :data="detailForm.evaluationDetails" border>
          <el-table-column label="评价维度" prop="dimensionName" />
          <el-table-column label="评分" align="center">
            <template slot-scope="scope">
              <el-rate
                v-if="scope.row.dimensionType === 'star'"
                v-model="scope.row.score"
                disabled
                show-score
                text-color="#ff9900"
                score-template="{value}"
              />
              <span v-else>{{ scope.row.optionValue || scope.row.textContent }}</span>
            </template>
          </el-table-column>
        </el-table>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import { listCustomerEvaluation, getCustomerEvaluation } from "@/api/evaluation";
import * as echarts from 'echarts';
export default {
  name: "EvaluationStatistics",
  data() {
    return {
      // é®ç½©å±‚
      loading: true,
      // é€‰ä¸­æ•°ç»„
      ids: [],
      // éžå•个禁用
      single: true,
      // éžå¤šä¸ªç¦ç”¨
      multiple: true,
      // æ˜¾ç¤ºæœç´¢æ¡ä»¶
      showSearch: true,
      // æ€»æ¡æ•°
      total: 0,
      // å®¢æˆ·è¯„价表格数据
      evaluationList: [],
      // å¼¹å‡ºå±‚标题
      title: "",
      // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚
      detailOpen: false,
      // æ—¥æœŸèŒƒå›´
      dateRange: [],
      // æŸ¥è¯¢å‚æ•°
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        vehicleNo: null,
        evaluationTime: null
      },
      // è¡¨å•参数
      detailForm: {},
      // ç»Ÿè®¡æ•°æ®
      statisticsData: {
        totalEvaluations: 0,
        averageScore: 0,
        goodRate: 0,
        vehicleCount: 0
      }
    };
  },
  created() {
    this.getList();
    this.getStatistics();
  },
  mounted() {
    this.$nextTick(() => {
      this.initCharts();
    });
  },
  methods: {
    /** æŸ¥è¯¢å®¢æˆ·è¯„价列表 */
    getList() {
      this.loading = true;
      listCustomerEvaluation(this.addDateRange(this.queryParams, this.dateRange)).then(response => {
        this.evaluationList = response.rows;
        this.total = response.total;
        this.loading = false;
      });
    },
    /** èŽ·å–ç»Ÿè®¡æ•°æ® */
    getStatistics() {
      // è¿™é‡Œåº”该调用统计接口,暂时使用模拟数据
      this.statisticsData = {
        totalEvaluations: 156,
        averageScore: 4.2,
        goodRate: 85.6,
        vehicleCount: 23
      };
    },
    /** åˆå§‹åŒ–图表 */
    initCharts() {
      this.initScoreChart();
      this.initTrendChart();
      this.initDimensionChart();
    },
    /** åˆå§‹åŒ–评分分布图表 */
    initScoreChart() {
      const chart = echarts.init(this.$refs.scoreChart);
      const option = {
        title: {
          text: '评分分布',
          left: 'center'
        },
        tooltip: {
          trigger: 'item'
        },
        series: [
          {
            name: '评分分布',
            type: 'pie',
            radius: '50%',
            data: [
              { value: 35, name: '5星' },
              { value: 28, name: '4星' },
              { value: 20, name: '3星' },
              { value: 12, name: '2星' },
              { value: 5, name: '1星' }
            ],
            emphasis: {
              itemStyle: {
                shadowBlur: 10,
                shadowOffsetX: 0,
                shadowColor: 'rgba(0, 0, 0, 0.5)'
              }
            }
          }
        ]
      };
      chart.setOption(option);
    },
    /** åˆå§‹åŒ–趋势图表 */
    initTrendChart() {
      const chart = echarts.init(this.$refs.trendChart);
      const option = {
        title: {
          text: '月度评价趋势',
          left: 'center'
        },
        tooltip: {
          trigger: 'axis'
        },
        xAxis: {
          type: 'category',
          data: ['1月', '2月', '3月', '4月', '5月', '6月']
        },
        yAxis: {
          type: 'value'
        },
        series: [
          {
            name: '评价数量',
            type: 'line',
            data: [12, 19, 23, 18, 25, 28],
            smooth: true
          }
        ]
      };
      chart.setOption(option);
    },
    /** åˆå§‹åŒ–维度统计图表 */
    initDimensionChart() {
      const chart = echarts.init(this.$refs.dimensionChart);
      const option = {
        title: {
          text: '各维度评分统计',
          left: 'center'
        },
        tooltip: {
          trigger: 'axis',
          axisPointer: {
            type: 'shadow'
          }
        },
        xAxis: {
          type: 'category',
          data: ['服务态度', '收费情况', '车辆卫生', '驾驶技术', '整体服务']
        },
        yAxis: {
          type: 'value',
          max: 5
        },
        series: [
          {
            name: '平均评分',
            type: 'bar',
            data: [4.5, 4.2, 4.3, 4.4, 4.1],
            itemStyle: {
              color: '#409EFF'
            }
          }
        ]
      };
      chart.setOption(option);
    },
    // å–消按钮
    cancel() {
      this.detailOpen = false;
      this.reset();
    },
    // è¡¨å•重置
    reset() {
      this.detailForm = {
        evaluationId: null,
        vehicleNo: null,
        customerName: null,
        customerPhone: null,
        wechatOpenid: null,
        wechatNickname: null,
        wechatAvatar: null,
        wechatPhone: null,
        overallScore: null,
        evaluationTime: null,
        evaluationDetails: []
      };
      this.resetForm("detailForm");
    },
    /** æœç´¢æŒ‰é’®æ“ä½œ */
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    /** é‡ç½®æŒ‰é’®æ“ä½œ */
    resetQuery() {
      this.dateRange = [];
      this.resetForm("queryForm");
      this.handleQuery();
    },
    // å¤šé€‰æ¡†é€‰ä¸­æ•°æ®
    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.evaluationId)
      this.single = selection.length!==1
      this.multiple = !selection.length
    },
    /** æŸ¥çœ‹è¯¦æƒ…按钮操作 */
    handleView(row) {
      this.reset();
      const evaluationId = row.evaluationId || this.ids
      getCustomerEvaluation(evaluationId).then(response => {
        this.detailForm = response.data;
        this.detailOpen = true;
        this.title = "评价详情";
      });
    }
  }
};
</script>
<style scoped>
.app-container {
  padding: 20px;
}
.card-panel-col {
  margin-bottom: 20px;
}
.card-panel {
  height: 108px;
  cursor: pointer;
  font-size: 12px;
  position: relative;
  overflow: hidden;
  color: #666;
  background: #fff;
  box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);
  border-color: rgba(0, 0, 0, .05);
}
.card-panel:hover {
  box-shadow: 4px 4px 40px rgba(0, 0, 0, .1);
}
.card-panel-icon-wrapper {
  float: left;
  margin: 14px 0 0 14px;
  padding: 16px;
  transition: all 0.38s ease-out;
  border-radius: 6px;
}
.card-panel-icon {
  float: left;
  font-size: 48px;
}
.card-panel-description {
  float: right;
  font-weight: bold;
  margin: 26px;
  margin-left: 0px;
}
.card-panel-text {
  line-height: 18px;
  color: rgba(0, 0, 0, 0.45);
  font-size: 16px;
  margin-bottom: 12px;
}
.card-panel-num {
  font-size: 20px;
  color: #666;
}
.icon-people {
  color: #40c9c6;
}
.icon-message {
  color: #36a3f7;
}
.icon-star {
  color: #f4516c;
}
.icon-vehicle {
  color: #34bfa3;
}
.chart-container {
  background: #fff;
  padding: 20px;
  border-radius: 4px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  margin-bottom: 20px;
}
.chart-title {
  font-size: 16px;
  font-weight: bold;
  margin-bottom: 15px;
  text-align: center;
}
.chart {
  height: 300px;
  width: 100%;
}
</style>
ruoyi-ui/src/views/evaluation/test.vue
New file
@@ -0,0 +1,187 @@
<template>
  <div class="test-container">
    <h2>客户满意度评价功能测试</h2>
    <div class="test-section">
      <h3>1. ç”ŸæˆäºŒç»´ç æµ‹è¯•</h3>
      <el-form :inline="true">
        <el-form-item label="车牌号">
          <el-input v-model="testVehicleNo" placeholder="请输入车牌号" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="generateQrcode">生成二维码</el-button>
        </el-form-item>
      </el-form>
      <div v-if="qrcodeInfo" class="qrcode-result">
        <p><strong>二维码URL:</strong> {{ qrcodeInfo.qrcodeUrl }}</p>
        <p><strong>二维码内容:</strong> {{ qrcodeInfo.qrcodeContent }}</p>
        <p><strong>二维码图片:</strong> {{ qrcodeInfo.qrcodeImage }}</p>
      </div>
    </div>
    <div class="test-section">
      <h3>2. è¯„价维度配置测试</h3>
      <el-button type="primary" @click="loadDimensions">加载评价维度</el-button>
      <div v-if="dimensions.length > 0" class="dimensions-result">
        <h4>评价维度列表:</h4>
        <ul>
          <li v-for="dimension in dimensions" :key="dimension.dimensionId">
            {{ dimension.dimensionName }} - {{ dimension.dimensionType }}
            <span v-if="dimension.isRequired === '1'">(必填)</span>
          </li>
        </ul>
      </div>
    </div>
    <div class="test-section">
      <h3>3. è¯„价提交测试</h3>
      <el-form :model="testEvaluation" label-width="100px">
        <el-form-item label="车牌号">
          <el-input v-model="testEvaluation.vehicleNo" />
        </el-form-item>
        <el-form-item label="客户姓名">
          <el-input v-model="testEvaluation.customerName" />
        </el-form-item>
        <el-form-item label="客户手机">
          <el-input v-model="testEvaluation.customerPhone" />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" @click="submitTestEvaluation">提交测试评价</el-button>
        </el-form-item>
      </el-form>
    </div>
    <div class="test-section">
      <h3>4. ç›´æŽ¥è®¿é—®è¯„价页面</h3>
      <p>访问地址: <a :href="evaluationUrl" target="_blank">{{ evaluationUrl }}</a></p>
    </div>
  </div>
</template>
<script>
import { generateQrcode, getEvaluationDimensions, submitEvaluation } from "@/api/evaluation";
export default {
  name: "EvaluationTest",
  data() {
    return {
      testVehicleNo: "粤A12345",
      qrcodeInfo: null,
      dimensions: [],
      testEvaluation: {
        vehicleNo: "粤A12345",
        customerName: "测试用户",
        customerPhone: "13800138000",
        evaluationDetails: [
          {
            dimensionId: 1,
            score: 5
          },
          {
            dimensionId: 2,
            score: 4
          },
          {
            dimensionId: 3,
            score: 5
          },
          {
            dimensionId: 4,
            score: 4
          }
        ]
      }
    };
  },
  computed: {
    evaluationUrl() {
      return `${window.location.origin}/evaluation?vehicle=${this.testVehicleNo}`;
    }
  },
  methods: {
    async generateQrcode() {
      try {
        const response = await generateQrcode(this.testVehicleNo);
        if (response.code === 200) {
          this.qrcodeInfo = response.data;
          this.$message.success("二维码生成成功");
        } else {
          this.$message.error(response.msg || "生成失败");
        }
      } catch (error) {
        console.error("生成二维码失败:", error);
        this.$message.error("生成二维码失败");
      }
    },
    async loadDimensions() {
      try {
        const response = await getEvaluationDimensions();
        if (response.code === 200) {
          this.dimensions = response.data;
          this.$message.success("评价维度加载成功");
        } else {
          this.$message.error(response.msg || "加载失败");
        }
      } catch (error) {
        console.error("加载评价维度失败:", error);
        this.$message.error("加载评价维度失败");
      }
    },
    async submitTestEvaluation() {
      try {
        const response = await submitEvaluation(this.testEvaluation);
        if (response.code === 200) {
          this.$message.success(response.msg || "评价提交成功");
        } else {
          this.$message.error(response.msg || "提交失败");
        }
      } catch (error) {
        console.error("提交评价失败:", error);
        this.$message.error("提交评价失败");
      }
    }
  }
};
</script>
<style scoped>
.test-container {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}
.test-section {
  margin-bottom: 30px;
  padding: 20px;
  border: 1px solid #e4e7ed;
  border-radius: 6px;
  background-color: #fafafa;
}
.test-section h3 {
  margin-top: 0;
  color: #333;
}
.qrcode-result, .dimensions-result {
  margin-top: 15px;
  padding: 15px;
  background-color: white;
  border-radius: 4px;
  border: 1px solid #e4e7ed;
}
.dimensions-result ul {
  margin: 10px 0;
  padding-left: 20px;
}
.dimensions-result li {
  margin-bottom: 5px;
}
</style>
ruoyi-ui/src/views/system/vehicle/index.vue
@@ -39,6 +39,16 @@
          />
        </el-select>
      </el-form-item>
      <el-form-item label="归属部门" prop="deptId">
        <el-select v-model="queryParams.deptId" placeholder="请选择部门" clearable size="small">
          <el-option
            v-for="dept in deptOptions"
            :key="dept.deptId"
            :label="dept.deptName"
            :value="dept.deptId"
          />
        </el-select>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
@@ -103,6 +113,7 @@
          <dict-tag :options="dict.type.sys_platform" :value="scope.row.platformCode"/>
        </template>
      </el-table-column>
      <el-table-column label="归属部门" align="center" prop="deptName" />
      <el-table-column label="状态" align="center" prop="status">
        <template slot-scope="scope">
          <dict-tag :options="dict.type.sys_normal_disable" :value="scope.row.status"/>
@@ -166,6 +177,16 @@
            />
          </el-select>
        </el-form-item>
        <el-form-item label="归属部门" prop="deptId">
          <el-select v-model="form.deptId" placeholder="请选择部门" clearable>
            <el-option
              v-for="dept in deptOptions"
              :key="dept.deptId"
              :label="dept.deptName"
              :value="dept.deptId"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="状态" prop="status">
          <el-radio-group v-model="form.status">
            <el-radio
@@ -189,6 +210,7 @@
<script>
import { listVehicle, getVehicle, delVehicle, addVehicle, updateVehicle } from "@/api/system/vehicle";
import { listDept } from "@/api/system/dept";
export default {
  name: "Vehicle",
@@ -209,6 +231,8 @@
      total: 0,
      // è½¦è¾†ä¿¡æ¯è¡¨æ ¼æ•°æ®
      vehicleList: [],
      // éƒ¨é—¨é€‰é¡¹
      deptOptions: [],
      // å¼¹å‡ºå±‚标题
      title: "",
      // æ˜¯å¦æ˜¾ç¤ºå¼¹å‡ºå±‚
@@ -222,7 +246,8 @@
        vehicleBrand: null,
        vehicleModel: null,
        status: null,
        platformCode: null
        platformCode: null,
        deptId: null
      },
      // è¡¨å•参数
      form: {
@@ -233,7 +258,8 @@
        vehicleModel: null,
        status: "0",
        remark: null,
        platformCode: null
        platformCode: null,
        deptId: null
      },
      // è¡¨å•校验
      rules: {
@@ -245,12 +271,16 @@
        ],
        platformCode: [
          { required: true, message: "平台标识不能为空", trigger: "change" }
        ],
        deptId: [
          { required: true, message: "归属部门不能为空", trigger: "change" }
        ]
      }
    };
  },
  created() {
    this.getList();
    this.getDeptList();
  },
  methods: {
    /** æŸ¥è¯¢è½¦è¾†ä¿¡æ¯åˆ—表 */
@@ -260,6 +290,12 @@
        this.vehicleList = response.rows;
        this.total = response.total;
        this.loading = false;
      });
    },
    /** æŸ¥è¯¢éƒ¨é—¨åˆ—表 */
    getDeptList() {
      listDept().then(response => {
        this.deptOptions = response.data;
      });
    },
    // å–消按钮
@@ -277,7 +313,8 @@
        vehicleModel: null,
        status: "0",
        remark: null,
        platformCode: null
        platformCode: null,
        deptId: null
      };
      this.resetForm("form");
    },
sql/customer_evaluation_tables.sql
New file
@@ -0,0 +1,100 @@
-- å®¢æˆ·æ»¡æ„åº¦è¯„价功能数据库表结构
-- åˆ›å»ºæ—¶é—´: 2025-01-27
-- 1. è¯„价维度配置表
DROP TABLE IF EXISTS `evaluation_dimension`;
CREATE TABLE `evaluation_dimension` (
  `dimension_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '维度ID',
  `dimension_name` varchar(100) NOT NULL COMMENT '维度名称',
  `dimension_desc` varchar(500) DEFAULT NULL COMMENT '维度描述',
  `dimension_type` varchar(20) NOT NULL DEFAULT 'star' COMMENT '维度类型:star-星级评价,select-选择评价,text-文本评价',
  `options` text COMMENT '选项配置(JSON格式,用于选择类型)',
  `sort_order` int(11) DEFAULT 0 COMMENT '排序',
  `is_required` char(1) DEFAULT '1' COMMENT '是否必填(0否 1是)',
  `status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`dimension_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评价维度配置表';
-- 2. å®¢æˆ·è¯„价表
DROP TABLE IF EXISTS `customer_evaluation`;
CREATE TABLE `customer_evaluation` (
  `evaluation_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '评价ID',
  `vehicle_no` varchar(20) NOT NULL COMMENT '车牌号',
  `customer_name` varchar(50) NOT NULL COMMENT '客户姓名',
  `customer_phone` varchar(20) NOT NULL COMMENT '客户手机号',
  `wechat_openid` varchar(100) DEFAULT NULL COMMENT '微信OpenID',
  `wechat_nickname` varchar(100) DEFAULT NULL COMMENT '微信昵称',
  `wechat_avatar` varchar(500) DEFAULT NULL COMMENT '微信头像',
  `wechat_phone` varchar(20) DEFAULT NULL COMMENT '微信绑定手机号',
  `total_score` decimal(3,1) DEFAULT NULL COMMENT '总评分',
  `evaluation_status` char(1) DEFAULT '0' COMMENT '评价状态(0待评价 1已评价)',
  `evaluation_time` datetime DEFAULT NULL COMMENT '评价时间',
  `ip_address` varchar(128) DEFAULT NULL COMMENT 'IP地址',
  `user_agent` varchar(500) DEFAULT NULL COMMENT '用户代理',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`evaluation_id`),
  KEY `idx_vehicle_no` (`vehicle_no`),
  KEY `idx_customer_phone` (`customer_phone`),
  KEY `idx_wechat_openid` (`wechat_openid`),
  KEY `idx_evaluation_time` (`evaluation_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='客户评价表';
-- 3. è¯„价详情表
DROP TABLE IF EXISTS `evaluation_detail`;
CREATE TABLE `evaluation_detail` (
  `detail_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '详情ID',
  `evaluation_id` bigint(20) NOT NULL COMMENT '评价ID',
  `dimension_id` bigint(20) NOT NULL COMMENT '维度ID',
  `score` int(11) DEFAULT NULL COMMENT '评分(1-5星)',
  `option_value` varchar(100) DEFAULT NULL COMMENT '选项值(选择类型时使用)',
  `text_content` text COMMENT '文本内容(文本类型时使用)',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`detail_id`),
  KEY `idx_evaluation_id` (`evaluation_id`),
  KEY `idx_dimension_id` (`dimension_id`),
  CONSTRAINT `fk_evaluation_detail_evaluation` FOREIGN KEY (`evaluation_id`) REFERENCES `customer_evaluation` (`evaluation_id`) ON DELETE CASCADE,
  CONSTRAINT `fk_evaluation_detail_dimension` FOREIGN KEY (`dimension_id`) REFERENCES `evaluation_dimension` (`dimension_id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评价详情表';
-- 4. è½¦è¾†è¯„价二维码表
DROP TABLE IF EXISTS `vehicle_evaluation_qrcode`;
CREATE TABLE `vehicle_evaluation_qrcode` (
  `qrcode_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '二维码ID',
  `vehicle_no` varchar(20) NOT NULL COMMENT '车牌号',
  `qrcode_url` varchar(500) NOT NULL COMMENT '二维码URL',
  `qrcode_content` varchar(500) NOT NULL COMMENT '二维码内容',
  `qrcode_image` varchar(8000) DEFAULT NULL COMMENT '二维码图片(base64格式)',
  `status` char(1) DEFAULT '0' COMMENT '状态(0正常 1停用)',
  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `remark` varchar(500) DEFAULT NULL COMMENT '备注',
  PRIMARY KEY (`qrcode_id`),
  UNIQUE KEY `uk_vehicle_no` (`vehicle_no`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆评价二维码表';
-- æ’入默认评价维度数据
INSERT INTO `evaluation_dimension` (`dimension_name`, `dimension_desc`, `dimension_type`, `sort_order`, `is_required`, `status`, `create_by`, `create_time`, `remark`) VALUES
('服务态度', '司机服务态度评价', 'star', 1, '1', '0', 'admin', NOW(), '司机服务态度是否友好、专业'),
('收费情况', '收费是否合理透明', 'star', 2, '1', '0', 'admin', NOW(), '收费是否合理、透明,有无乱收费现象'),
('车辆卫生状况', '车辆内部卫生情况', 'star', 3, '1', '0', 'admin', NOW(), '车辆内部是否干净整洁'),
('整体满意度', '整体服务满意度', 'star', 4, '1', '0', 'admin', NOW(), '对整体服务的满意度评价'),
('其他建议', '其他意见或建议', 'text', 5, '0', '0', 'admin', NOW(), '客户的其他意见或建议');
-- ä¸ºçŽ°æœ‰è½¦è¾†ç”Ÿæˆè¯„ä»·äºŒç»´ç ï¼ˆç¤ºä¾‹æ•°æ®ï¼‰
-- æ³¨æ„ï¼šå®žé™…使用时需要根据真实的车辆数据来生成
-- INSERT INTO `vehicle_evaluation_qrcode` (`vehicle_no`, `qrcode_url`, `qrcode_content`, `status`, `create_by`, `create_time`)
-- SELECT vehicle_no, CONCAT('https://yourdomain.com/evaluation?vehicle=', vehicle_no), CONCAT('EVAL:', vehicle_no), '0', 'admin', NOW()
-- FROM tb_vehicle_info WHERE status = '0';
-- ä¿®æ”¹qrcode_image字段长度以支持base64格式存储
ALTER TABLE `vehicle_evaluation_qrcode` MODIFY COLUMN `qrcode_image` varchar(8000) DEFAULT NULL COMMENT '二维码图片(base64格式)';
sql/evaluation_dict.sql
New file
@@ -0,0 +1,26 @@
-- å®¢æˆ·æ»¡æ„åº¦è¯„价功能字典数据
-- è¯„价状态字典类型
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
VALUES ('评价状态', 'evaluation_status', '0', 'admin', sysdate(), '客户评价状态列表');
-- è¯„价状态字典数据
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
VALUES (1, '待评价', '0', 'evaluation_status', '', 'info', 'Y', '0', 'admin', sysdate(), '待评价状态');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
VALUES (2, '已评价', '1', 'evaluation_status', '', 'success', 'N', '0', 'admin', sysdate(), '已评价状态');
-- è¯„价维度类型字典类型
INSERT INTO sys_dict_type (dict_name, dict_type, status, create_by, create_time, remark)
VALUES ('评价维度类型', 'dimension_type', '0', 'admin', sysdate(), '评价维度类型列表');
-- è¯„价维度类型字典数据
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
VALUES (1, '星级评价', 'star', 'dimension_type', '', 'primary', 'Y', '0', 'admin', sysdate(), '星级评价类型');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
VALUES (2, '选择评价', 'select', 'dimension_type', '', 'success', 'N', '0', 'admin', sysdate(), '选择评价类型');
INSERT INTO sys_dict_data (dict_sort, dict_label, dict_value, dict_type, css_class, list_class, is_default, status, create_by, create_time, remark)
VALUES (3, '文本评价', 'text', 'dimension_type', '', 'warning', 'N', '0', 'admin', sysdate(), '文本评价类型');
sql/evaluation_menu.sql
New file
@@ -0,0 +1,79 @@
-- å®¢æˆ·æ»¡æ„åº¦è¯„价管理菜单 SQL
-- 1. è¯„价管理主菜单
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('评价管理', '0', '6', 'evaluation', null, 1, 0, 'M', '0', '0', null, 'star', 'admin', sysdate(), '', null, '客户满意度评价管理目录');
-- èŽ·å–è¯„ä»·ç®¡ç†ä¸»èœå•ID
SELECT @evaluationParentId := LAST_INSERT_ID();
-- 2. å®¢æˆ·è¯„价菜单
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('客户评价', @evaluationParentId, '1', 'customer', 'evaluation/customer/index', 1, 0, 'C', '0', '0', 'evaluation:customer:list', 'message', 'admin', sysdate(), '', null, '客户评价菜单');
-- èŽ·å–å®¢æˆ·è¯„ä»·èœå•ID
SELECT @customerParentId := LAST_INSERT_ID();
-- å®¢æˆ·è¯„价按钮权限
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('客户评价查询', @customerParentId, '1', '#', '', 1, 0, 'F', '0', '0', 'evaluation:customer:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('客户评价删除', @customerParentId, '2', '#', '', 1, 0, 'F', '0', '0', 'evaluation:customer:remove', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('客户评价导出', @customerParentId, '3', '#', '', 1, 0, 'F', '0', '0', 'evaluation:customer:export', '#', 'admin', sysdate(), '', null, '');
-- 3. è¯„价维度配置菜单
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('评价维度', @evaluationParentId, '2', 'dimension', 'evaluation/dimension/index', 1, 0, 'C', '0', '0', 'evaluation:dimension:list', 'edit', 'admin', sysdate(), '', null, '评价维度配置菜单');
-- èŽ·å–è¯„ä»·ç»´åº¦èœå•ID
SELECT @dimensionParentId := LAST_INSERT_ID();
-- è¯„价维度按钮权限
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('评价维度查询', @dimensionParentId, '1', '#', '', 1, 0, 'F', '0', '0', 'evaluation:dimension:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('评价维度新增', @dimensionParentId, '2', '#', '', 1, 0, 'F', '0', '0', 'evaluation:dimension:add', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('评价维度修改', @dimensionParentId, '3', '#', '', 1, 0, 'F', '0', '0', 'evaluation:dimension:edit', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('评价维度删除', @dimensionParentId, '4', '#', '', 1, 0, 'F', '0', '0', 'evaluation:dimension:remove', '#', 'admin', sysdate(), '', null, '');
-- 4. è½¦è¾†äºŒç»´ç ç®¡ç†èœå•
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('车辆二维码', @evaluationParentId, '3', 'qrcode', 'evaluation/qrcode/index', 1, 0, 'C', '0', '0', 'evaluation:qrcode:list', 'qrcode', 'admin', sysdate(), '', null, '车辆评价二维码管理菜单');
-- èŽ·å–è½¦è¾†äºŒç»´ç èœå•ID
SELECT @qrcodeParentId := LAST_INSERT_ID();
-- è½¦è¾†äºŒç»´ç æŒ‰é’®æƒé™
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('二维码查询', @qrcodeParentId, '1', '#', '', 1, 0, 'F', '0', '0', 'evaluation:qrcode:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('二维码生成', @qrcodeParentId, '2', '#', '', 1, 0, 'F', '0', '0', 'evaluation:qrcode:generate', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('批量生成', @qrcodeParentId, '3', '#', '', 1, 0, 'F', '0', '0', 'evaluation:qrcode:batch', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('二维码删除', @qrcodeParentId, '4', '#', '', 1, 0, 'F', '0', '0', 'evaluation:qrcode:remove', '#', 'admin', sysdate(), '', null, '');
-- 5. è¯„价统计菜单
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('评价统计', @evaluationParentId, '4', 'statistics', 'evaluation/statistics/index', 1, 0, 'C', '0', '0', 'evaluation:statistics:list', 'chart', 'admin', sysdate(), '', null, '评价统计分析菜单');
-- èŽ·å–è¯„ä»·ç»Ÿè®¡èœå•ID
SELECT @statisticsParentId := LAST_INSERT_ID();
-- è¯„价统计按钮权限
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('统计查询', @statisticsParentId, '1', '#', '', 1, 0, 'F', '0', '0', 'evaluation:statistics:query', '#', 'admin', sysdate(), '', null, '');
insert into sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
values('统计导出', @statisticsParentId, '2', '#', '', 1, 0, 'F', '0', '0', 'evaluation:statistics:export', '#', 'admin', sysdate(), '', null, '');
sql/vehicle_info.sql
@@ -20,4 +20,7 @@
ALTER TABLE tb_vehicle_info ADD COLUMN device_id VARCHAR(50) DEFAULT NULL COMMENT '设备ID';
-- åœ¨tb_vehicle_gps表中添加device_id字段
ALTER TABLE tb_vehicle_gps ADD COLUMN device_id VARCHAR(50) DEFAULT NULL COMMENT '设备ID';
ALTER TABLE tb_vehicle_gps ADD COLUMN device_id VARCHAR(50) DEFAULT NULL COMMENT '设备ID';
-- åœ¨tb_vehicle_info表中添加dept_id字段
ALTER TABLE tb_vehicle_info ADD COLUMN dept_id BIGINT(20) DEFAULT NULL COMMENT '归属部门ID';
ÍêÕû²¿ÊðÖ¸ÄÏ.md
New file
@@ -0,0 +1,225 @@
# å®¢æˆ·æ»¡æ„åº¦è¯„价功能完整部署指南
## ðŸ“‹ éƒ¨ç½²æ¸…单
### 1. æ•°æ®åº“脚本执行顺序
```sql
-- 1. æ‰§è¡Œæ•°æ®åº“表结构
source sql/customer_evaluation_tables.sql
-- 2. æ‰§è¡Œèœå•配置
source sql/evaluation_menu.sql
-- 3. æ‰§è¡Œå­—典配置
source sql/evaluation_dict.sql
```
### 2. åŽç«¯é…ç½®
#### 2.1 å¾®ä¿¡é…ç½®ï¼ˆå¯é€‰ï¼‰
在 `ruoyi-admin/src/main/resources/application.yml` ä¸­é…ç½®ï¼š
```yaml
wechat:
  appId: your_wechat_appid
  appSecret: your_wechat_appsecret
  redirectUri: http://yourdomain.com/evaluation
```
#### 2.2 æ–‡ä»¶ä¸Šä¼ è·¯å¾„配置
确保 `ruoyi.profile` é…ç½®çš„路径有写入权限:
```yaml
ruoyi:
  profile: /path/to/upload
```
### 3. å‰ç«¯é…ç½®
#### 3.1 è·¯ç”±é…ç½®
已在 `ruoyi-ui/src/router/index.js` ä¸­æ·»åŠ è¯„ä»·ç›¸å…³è·¯ç”±ã€‚
#### 3.2 é¡µé¢ç»„ä»¶
已创建以下页面组件:
- `ruoyi-ui/src/views/evaluation/index.vue` - å®¢æˆ·è¯„价页面
- `ruoyi-ui/src/views/evaluation/test.vue` - æµ‹è¯•页面
- `ruoyi-ui/src/views/evaluation/customer/index.vue` - å®¢æˆ·è¯„价管理
- `ruoyi-ui/src/views/evaluation/dimension/index.vue` - è¯„价维度配置
- `ruoyi-ui/src/views/evaluation/qrcode/index.vue` - è½¦è¾†äºŒç»´ç ç®¡ç†
## ðŸš€ éƒ¨ç½²æ­¥éª¤
### æ­¥éª¤1:执行数据库脚本
```bash
# è¿žæŽ¥æ•°æ®åº“
mysql -u username -p database_name
# æ‰§è¡Œè„šæœ¬
source sql/customer_evaluation_tables.sql
source sql/evaluation_menu.sql
source sql/evaluation_dict.sql
```
### æ­¥éª¤2:编译后端项目
```bash
# åœ¨é¡¹ç›®æ ¹ç›®å½•执行
mvn clean install
```
### æ­¥éª¤3:启动后端服务
```bash
cd ruoyi-admin
mvn spring-boot:run
```
### æ­¥éª¤4:编译前端项目
```bash
cd ruoyi-ui
npm install
npm run dev
```
## ðŸ§ª åŠŸèƒ½æµ‹è¯•
### 1. è®¿é—®ç®¡ç†ç•Œé¢
- ç™»å½•系统后,在左侧菜单中可以看到"评价管理"菜单
- åŒ…含以下子菜单:
  - å®¢æˆ·è¯„ä»·
  - è¯„价维度
  - è½¦è¾†äºŒç»´ç 
  - è¯„价统计
### 2. æµ‹è¯•评价功能
- è®¿é—®ï¼š`http://localhost:80/evaluation/test`
- æµ‹è¯•二维码生成、评价维度加载、评价提交等功能
### 3. æµ‹è¯•评价页面
- è®¿é—®ï¼š`http://localhost:80/evaluation?vehicle=粤A12345`
- å¡«å†™è¯„价信息并提交
## ðŸ“± å¾®ä¿¡é…ç½®ï¼ˆç”Ÿäº§çŽ¯å¢ƒï¼‰
### 1. å¾®ä¿¡å…¬ä¼—平台配置
1. ç™»å½•微信公众平台
2. è¿›å…¥"开发" -> "接口权限"
3. é…ç½®ç½‘页授权域名:`yourdomain.com`
4. é…ç½®JS接口安全域名:`yourdomain.com`
### 2. åŸŸåé…ç½®
- ç¡®ä¿åŸŸåå·²å¤‡æ¡ˆ
- é…ç½®HTTPS证书
- æ›´æ–°é…ç½®æ–‡ä»¶ä¸­çš„域名
## ðŸ”§ æƒé™é…ç½®
### 1. è§’色权限
系统管理员需要为相关角色分配以下权限:
- `evaluation:customer:list` - å®¢æˆ·è¯„价查询
- `evaluation:customer:remove` - å®¢æˆ·è¯„价删除
- `evaluation:customer:export` - å®¢æˆ·è¯„价导出
- `evaluation:dimension:list` - è¯„价维度查询
- `evaluation:dimension:add` - è¯„价维度新增
- `evaluation:dimension:edit` - è¯„价维度修改
- `evaluation:dimension:remove` - è¯„价维度删除
- `evaluation:qrcode:list` - äºŒç»´ç æŸ¥è¯¢
- `evaluation:qrcode:generate` - äºŒç»´ç ç”Ÿæˆ
- `evaluation:qrcode:batch` - æ‰¹é‡ç”ŸæˆäºŒç»´ç 
- `evaluation:qrcode:remove` - äºŒç»´ç åˆ é™¤
### 2. èœå•权限
菜单权限已通过SQL脚本自动配置。
## ðŸ“Š æ•°æ®éªŒè¯
### 1. æ£€æŸ¥æ•°æ®åº“表
```sql
-- æ£€æŸ¥è¡¨æ˜¯å¦åˆ›å»ºæˆåŠŸ
SHOW TABLES LIKE '%evaluation%';
-- æ£€æŸ¥èœå•是否添加成功
SELECT * FROM sys_menu WHERE menu_name LIKE '%评价%';
-- æ£€æŸ¥å­—典是否添加成功
SELECT * FROM sys_dict_type WHERE dict_type IN ('evaluation_status', 'dimension_type');
```
### 2. æ£€æŸ¥é»˜è®¤æ•°æ®
```sql
-- æ£€æŸ¥è¯„价维度默认数据
SELECT * FROM evaluation_dimension;
-- åº”该看到5条默认评价维度数据
```
## ðŸ› å¸¸è§é—®é¢˜
### 1. èœå•不显示
**原因**:可能是权限问题或菜单数据未正确插入
**解决**:
- æ£€æŸ¥ç”¨æˆ·è§’色权限
- é‡æ–°æ‰§è¡Œèœå•SQL脚本
- æ¸…除浏览器缓存
### 2. äºŒç»´ç ç”Ÿæˆå¤±è´¥
**原因**:文件路径权限问题
**解决**:
- æ£€æŸ¥ `ruoyi.profile` é…ç½®çš„路径
- ç¡®ä¿ç›®å½•有写入权限
- æ£€æŸ¥ç£ç›˜ç©ºé—´
### 3. è¯„价提交失败
**原因**:可能是必填字段验证失败
**解决**:
- æ£€æŸ¥è¯„价维度配置
- ç¡®ä¿æ‰€æœ‰å¿…填字段都已填写
- æŸ¥çœ‹åŽç«¯æ—¥å¿—
### 4. å¾®ä¿¡æŽˆæƒå¤±è´¥
**原因**:微信配置问题
**解决**:
- æ£€æŸ¥AppID和AppSecret配置
- ç¡®è®¤åŸŸåé…ç½®æ­£ç¡®
- æ£€æŸ¥HTTPS证书
## ðŸ“ˆ æ€§èƒ½ä¼˜åŒ–建议
### 1. æ•°æ®åº“优化
```sql
-- æ·»åŠ ç´¢å¼•
CREATE INDEX idx_evaluation_vehicle_time ON customer_evaluation(vehicle_no, evaluation_time);
CREATE INDEX idx_evaluation_status ON customer_evaluation(evaluation_status);
CREATE INDEX idx_evaluation_detail_evaluation ON evaluation_detail(evaluation_id);
```
### 2. ç¼“存配置
- è¯„价维度配置可以缓存
- äºŒç»´ç å›¾ç‰‡ä½¿ç”¨CDN加速
### 3. æ–‡ä»¶å­˜å‚¨
- è€ƒè™‘使用对象存储服务
- å®šæœŸæ¸…理无用的二维码文件
## ðŸ“ž æŠ€æœ¯æ”¯æŒ
如遇到问题,请提供:
1. é”™è¯¯æ—¥å¿—
2. æ“ä½œæ­¥éª¤
3. çŽ¯å¢ƒä¿¡æ¯
4. é”™è¯¯æˆªå›¾
## ðŸŽ¯ åŠŸèƒ½æ‰©å±•
### 1. è¯„价统计
可以基于现有数据开发:
- è¯„价趋势分析
- è½¦è¾†è¯„价排行
- å®¢æˆ·æ»¡æ„åº¦æŠ¥å‘Š
### 2. æ¶ˆæ¯é€šçŸ¥
- è¯„价提交后发送通知
- ä½Žåˆ†è¯„价预警
- å®šæœŸè¯„价报告
### 3. å¤šè¯­è¨€æ”¯æŒ
- æ”¯æŒå¤šè¯­è¨€è¯„价界面
- å›½é™…化配置
功能已全部开发完成,可以正常使用!
¿Í»§ÂúÒâ¶ÈÆÀ¼Û¹¦ÄÜ˵Ã÷.md
New file
@@ -0,0 +1,134 @@
# å®¢æˆ·æ»¡æ„åº¦è¯„价功能说明
## åŠŸèƒ½æ¦‚è¿°
客户满意度评价功能允许客户通过扫描二维码进入评价界面,对服务进行多维度评价。支持微信环境下的用户信息获取和评价提交。
## ä¸»è¦åŠŸèƒ½
### 1. äºŒç»´ç ç”Ÿæˆ
- ä¸ºæ¯è¾†è½¦ç”Ÿæˆå”¯ä¸€çš„评价二维码
- äºŒç»´ç åŒ…含车牌号信息,用于关联具体车辆服务
- æ”¯æŒæ‰¹é‡ç”ŸæˆäºŒç»´ç 
### 2. å¾®ä¿¡é›†æˆ
- è‡ªåŠ¨æ£€æµ‹å¾®ä¿¡æµè§ˆå™¨çŽ¯å¢ƒ
- èŽ·å–å¾®ä¿¡ç”¨æˆ·ä¿¡æ¯ï¼ˆOpenID、昵称、头像等)
- æ”¯æŒå¾®ä¿¡ç½‘页授权流程
### 3. è¯„价界面
- å“åº”式设计,适配移动端和微信浏览器
- æ”¯æŒå¤šç§è¯„价类型:星级评价、选择评价、文本评价
- å®¢æˆ·ä¿¡æ¯æ”¶é›†ï¼ˆå§“名、手机号必填)
- è¯„价维度可配置
### 4. è¯„价提交
- æ•°æ®éªŒè¯å’Œå®Œæ•´æ€§æ£€æŸ¥
- æ ¹æ®è¯„分显示不同的反馈信息
- è¯„价数据存储和统计
## æ•°æ®åº“表结构
### 1. evaluation_dimension(评价维度配置表)
- é…ç½®è¯„价维度名称、类型、是否必填等
- æ”¯æŒæ˜Ÿçº§è¯„价、选择评价、文本评价三种类型
### 2. customer_evaluation(客户评价表)
- å­˜å‚¨å®¢æˆ·åŸºæœ¬ä¿¡æ¯å’Œè¯„价结果
- åŒ…含微信用户信息
- è®°å½•评价时间和IP地址
### 3. evaluation_detail(评价详情表)
- å­˜å‚¨æ¯ä¸ªç»´åº¦çš„具体评分
- å…³è”评价维度和客户评价
### 4. vehicle_evaluation_qrcode(车辆评价二维码表)
- å­˜å‚¨æ¯è¾†è½¦çš„二维码信息
- åŒ…含二维码URL和图片路径
## API接口
### å…¬å¼€æŽ¥å£ï¼ˆæ— éœ€è®¤è¯ï¼‰
- `GET /evaluation/dimensions` - èŽ·å–è¯„ä»·ç»´åº¦é…ç½®
- `POST /evaluation/submit` - æäº¤å®¢æˆ·è¯„ä»·
- `GET /evaluation/wechat/userinfo` - èŽ·å–å¾®ä¿¡ç”¨æˆ·ä¿¡æ¯
### ç®¡ç†æŽ¥å£ï¼ˆéœ€è¦è®¤è¯ï¼‰
- `GET /evaluation/list` - æŸ¥è¯¢å®¢æˆ·è¯„价列表
- `GET /evaluation/{id}` - èŽ·å–è¯„ä»·è¯¦æƒ…
- `POST /evaluation/qrcode/{vehicleNo}` - ç”Ÿæˆè½¦è¾†äºŒç»´ç 
- `POST /evaluation/qrcode/batch` - æ‰¹é‡ç”ŸæˆäºŒç»´ç 
## éƒ¨ç½²è¯´æ˜Ž
### 1. æ•°æ®åº“初始化
```sql
-- æ‰§è¡Œ sql/customer_evaluation_tables.sql æ–‡ä»¶
-- åˆ›å»ºç›¸å…³è¡¨å’Œåˆå§‹æ•°æ®
```
### 2. é…ç½®æ–‡ä»¶
在 `application.yml` ä¸­é…ç½®ï¼š
```yaml
# å¾®ä¿¡é…ç½®
wechat:
  appId: your_wechat_appid
  appSecret: your_wechat_appsecret
# æ–‡ä»¶ä¸Šä¼ è·¯å¾„
ruoyi:
  profile: /path/to/upload
```
### 3. å‰ç«¯è·¯ç”±é…ç½®
在 `router/index.js` ä¸­æ·»åŠ è¯„ä»·é¡µé¢è·¯ç”±ï¼š
```javascript
{
  path: '/evaluation',
  component: () => import('@/views/evaluation/index'),
  name: 'Evaluation'
}
```
## ä½¿ç”¨æµç¨‹
### 1. ç®¡ç†å‘˜æ“ä½œ
1. é…ç½®è¯„价维度(服务态度、收费情况、车辆卫生等)
2. ä¸ºè½¦è¾†ç”Ÿæˆè¯„价二维码
3. å°†äºŒç»´ç æ‰“印并贴在车辆上
### 2. å®¢æˆ·è¯„价流程
1. å®¢æˆ·æ‰«æè½¦è¾†ä¸Šçš„二维码
2. ç³»ç»Ÿè§£æžäºŒç»´ç ï¼ŒèŽ·å–è½¦ç‰Œå·
3. è¿›å…¥è¯„价页面,获取微信用户信息(如果在微信环境中)
4. å®¢æˆ·å¡«å†™ä¸ªäººä¿¡æ¯å’Œè¯„价内容
5. æäº¤è¯„价,显示结果页面
## æµ‹è¯•功能
访问 `/evaluation/test` é¡µé¢å¯ä»¥æµ‹è¯•以下功能:
- äºŒç»´ç ç”Ÿæˆ
- è¯„价维度加载
- è¯„价提交
- ç›´æŽ¥è®¿é—®è¯„价页面
## æ³¨æ„äº‹é¡¹
1. **微信配置**:需要配置正确的微信AppID和AppSecret
2. **文件权限**:确保上传目录有写入权限
3. **HTTPS**:微信授权需要HTTPS环境
4. **域名配置**:在微信公众平台配置授权域名
## æ‰©å±•功能
1. **评价统计**:可以基于现有数据开发评价统计分析功能
2. **消息通知**:评价提交后可以发送通知给相关人员
3. **评价导出**:支持评价数据的Excel导出
4. **多语言支持**:可以扩展多语言评价界面
## æŠ€æœ¯æ ˆ
- åŽç«¯ï¼šSpring Boot + MyBatis + MySQL
- å‰ç«¯ï¼šVue.js + Element UI
- äºŒç»´ç ï¼šZXing
- å¾®ä¿¡ï¼šå¾®ä¿¡ç½‘页授权API
²âÊÔ²½Öè.md
New file
@@ -0,0 +1,121 @@
# å®¢æˆ·æ»¡æ„åº¦è¯„价功能测试步骤
## 1. å¯åŠ¨é¡¹ç›®
### åŽç«¯å¯åЍ
```bash
# åœ¨é¡¹ç›®æ ¹ç›®å½•执行
mvn clean install
cd ruoyi-admin
mvn spring-boot:run
```
### å‰ç«¯å¯åЍ
```bash
# åœ¨ ruoyi-ui ç›®å½•执行
npm install
npm run dev
```
## 2. é…ç½®å¾®ä¿¡å‚数(可选)
如果不需要微信功能,可以跳过此步骤。
在 `ruoyi-admin/src/main/resources/application.yml` ä¸­é…ç½®ï¼š
```yaml
wechat:
  appId: your_wechat_appid
  appSecret: your_wechat_appsecret
  redirectUri: http://localhost:8080/evaluation
```
## 3. æµ‹è¯•步骤
### æ­¥éª¤1:访问测试页面
打开浏览器访问:`http://localhost:80/evaluation/test`
### æ­¥éª¤2:测试二维码生成
1. åœ¨æµ‹è¯•页面输入车牌号(如:粤A12345)
2. ç‚¹å‡»"生成二维码"按钮
3. æŸ¥çœ‹æ˜¯å¦æˆåŠŸç”ŸæˆäºŒç»´ç ä¿¡æ¯
### æ­¥éª¤3:测试评价维度加载
1. ç‚¹å‡»"加载评价维度"按钮
2. æŸ¥çœ‹æ˜¯å¦æˆåŠŸåŠ è½½è¯„ä»·ç»´åº¦åˆ—è¡¨
### æ­¥éª¤4:测试评价提交
1. å¡«å†™æµ‹è¯•评价信息
2. ç‚¹å‡»"提交测试评价"按钮
3. æŸ¥çœ‹æ˜¯å¦æˆåŠŸæäº¤
### æ­¥éª¤5:测试评价页面
1. ç‚¹å‡»"直接访问评价页面"链接
2. æˆ–直接访问:`http://localhost:80/evaluation?vehicle=粤A12345`
3. å¡«å†™è¯„价信息并提交
## 4. é¢„期结果
### äºŒç»´ç ç”Ÿæˆæµ‹è¯•
- åº”该返回二维码URL、内容和图片路径
- äºŒç»´ç URL格式:`http://localhost:8080/evaluation?vehicle=车牌号`
### è¯„价维度测试
- åº”该返回默认的评价维度:
  - æœåŠ¡æ€åº¦ï¼ˆæ˜Ÿçº§è¯„ä»·ï¼Œå¿…å¡«ï¼‰
  - æ”¶è´¹æƒ…况(星级评价,必填)
  - è½¦è¾†å«ç”ŸçŠ¶å†µï¼ˆæ˜Ÿçº§è¯„ä»·ï¼Œå¿…å¡«ï¼‰
  - æ•´ä½“满意度(星级评价,必填)
  - å…¶ä»–建议(文本评价,非必填)
### è¯„价提交测试
- åº”该返回成功消息
- æ•°æ®åº“中应该保存评价记录
### è¯„价页面测试
- é¡µé¢åº”该正常显示
- è¡¨å•验证应该正常工作
- æäº¤åŽåº”该显示成功页面
## 5. å¸¸è§é—®é¢˜
### é—®é¢˜1:二维码生成失败
**原因**:可能是文件路径权限问题
**解决**:检查 `ruoyi.profile` é…ç½®çš„路径是否有写入权限
### é—®é¢˜2:评价维度加载失败
**原因**:可能是数据库连接问题
**解决**:检查数据库连接配置
### é—®é¢˜3:评价提交失败
**原因**:可能是必填字段验证失败
**解决**:确保所有必填字段都已填写
### é—®é¢˜4:页面访问404
**原因**:可能是路由配置问题
**解决**:检查前端路由配置是否正确
## 6. æ•°æ®åº“验证
可以执行以下SQL查询验证数据:
```sql
-- æŸ¥çœ‹è¯„价维度配置
SELECT * FROM evaluation_dimension;
-- æŸ¥çœ‹å®¢æˆ·è¯„价记录
SELECT * FROM customer_evaluation;
-- æŸ¥çœ‹è¯„价详情
SELECT * FROM evaluation_detail;
-- æŸ¥çœ‹è½¦è¾†äºŒç»´ç 
SELECT * FROM vehicle_evaluation_qrcode;
```
## 7. ç”Ÿäº§çŽ¯å¢ƒéƒ¨ç½²æ³¨æ„äº‹é¡¹
1. **HTTPS配置**:微信授权需要HTTPS环境
2. **域名配置**:在微信公众平台配置授权域名
3. **文件权限**:确保上传目录有正确的读写权限
4. **数据库备份**:定期备份评价数据
5. **性能优化**:大量评价数据时考虑分页和索引优化
²¿Êð˵Ã÷.md
New file
@@ -0,0 +1,249 @@
# å®¢æˆ·æ»¡æ„åº¦è¯„价功能部署说明
## éƒ¨ç½²å‰å‡†å¤‡
### 1. çŽ¯å¢ƒè¦æ±‚
- JDK 1.8+
- Maven 3.6+
- MySQL 5.7+
- Node.js 14+
- Nginx(生产环境推荐)
### 2. é…ç½®æ–‡ä»¶ä¿®æ”¹
#### æ•°æ®åº“配置
修改 `ruoyi-admin/src/main/resources/application-prod.yml`:
```yaml
spring:
  datasource:
    url: jdbc:mysql://your-db-host:3306/your-database?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
    username: your-username
    password: your-password
```
#### å¾®ä¿¡é…ç½®
修改 `ruoyi-admin/src/main/resources/application-prod.yml`:
```yaml
wechat:
  appId: your_wechat_appid
  appSecret: your_wechat_appsecret
  redirectUri: https://yourdomain.com/evaluation
```
#### æ–‡ä»¶ä¸Šä¼ è·¯å¾„
修改 `ruoyi-admin/src/main/resources/application-prod.yml`:
```yaml
ruoyi:
  profile: /path/to/upload
```
## éƒ¨ç½²æ­¥éª¤
### 1. åŽç«¯éƒ¨ç½²
#### æ–¹å¼ä¸€ï¼šJAR包部署
```bash
# ç¼–译打包
mvn clean package -Pprod
# ä¸Šä¼ JAR包到服务器
scp ruoyi-admin/target/ruoyi-admin.jar user@server:/path/to/app/
# å¯åŠ¨åº”ç”¨
java -jar ruoyi-admin.jar --spring.profiles.active=prod
```
#### æ–¹å¼äºŒï¼šDocker部署
```dockerfile
# Dockerfile
FROM openjdk:8-jre-alpine
COPY ruoyi-admin.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "/app.jar"]
```
```bash
# æž„建镜像
docker build -t ruoyi-evaluation .
# è¿è¡Œå®¹å™¨
docker run -d -p 8080:8080 --name ruoyi-evaluation ruoyi-evaluation
```
### 2. å‰ç«¯éƒ¨ç½²
#### æž„建生产版本
```bash
cd ruoyi-ui
npm install
npm run build:prod
```
#### Nginx配置
```nginx
server {
    listen 80;
    server_name yourdomain.com;
    # å‰ç«¯é™æ€æ–‡ä»¶
    location / {
        root /path/to/ruoyi-ui/dist;
        try_files $uri $uri/ /index.html;
    }
    # åŽç«¯API代理
    location /dev-api/ {
        proxy_pass http://localhost:8080/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    # äºŒç»´ç å›¾ç‰‡è®¿é—®
    location /qrcode/ {
        alias /path/to/upload/qrcode/;
    }
}
```
### 3. æ•°æ®åº“部署
#### æ‰§è¡ŒSQL脚本
```bash
mysql -u username -p database_name < sql/customer_evaluation_tables.sql
```
#### åˆ›å»ºæ•°æ®åº“用户(可选)
```sql
CREATE USER 'evaluation_user'@'%' IDENTIFIED BY 'password';
GRANT SELECT, INSERT, UPDATE, DELETE ON database_name.* TO 'evaluation_user'@'%';
FLUSH PRIVILEGES;
```
## å¾®ä¿¡é…ç½®
### 1. å¾®ä¿¡å…¬ä¼—平台配置
1. ç™»å½•微信公众平台
2. è¿›å…¥"开发" -> "接口权限"
3. é…ç½®ç½‘页授权域名:`yourdomain.com`
4. é…ç½®JS接口安全域名:`yourdomain.com`
### 2. æµ‹è¯•微信功能
1. åœ¨å¾®ä¿¡ä¸­è®¿é—®ï¼š`https://yourdomain.com/evaluation?vehicle=粤A12345`
2. ç¡®è®¤èƒ½æ­£å¸¸èŽ·å–å¾®ä¿¡ç”¨æˆ·ä¿¡æ¯
3. æµ‹è¯•评价提交功能
## ç›‘控和维护
### 1. æ—¥å¿—监控
```bash
# æŸ¥çœ‹åº”用日志
tail -f logs/sys-info.log
# æŸ¥çœ‹é”™è¯¯æ—¥å¿—
tail -f logs/sys-error.log
```
### 2. æ•°æ®åº“监控
```sql
-- æŸ¥çœ‹è¯„价统计
SELECT
    vehicle_no,
    COUNT(*) as total_count,
    AVG(total_score) as avg_score
FROM customer_evaluation
WHERE evaluation_status = '1'
GROUP BY vehicle_no;
-- æŸ¥çœ‹ä»Šæ—¥è¯„价数量
SELECT COUNT(*) FROM customer_evaluation
WHERE DATE(evaluation_time) = CURDATE();
```
### 3. æ–‡ä»¶æ¸…理
```bash
# æ¸…理过期的二维码文件(可选)
find /path/to/upload/qrcode/ -name "*.png" -mtime +30 -delete
```
## æ€§èƒ½ä¼˜åŒ–
### 1. æ•°æ®åº“优化
```sql
-- æ·»åŠ ç´¢å¼•
CREATE INDEX idx_evaluation_vehicle_time ON customer_evaluation(vehicle_no, evaluation_time);
CREATE INDEX idx_evaluation_status ON customer_evaluation(evaluation_status);
```
### 2. ç¼“存配置
```yaml
# Redis配置
spring:
  redis:
    host: your-redis-host
    port: 6379
    database: 0
    timeout: 10s
```
### 3. æ–‡ä»¶å­˜å‚¨ä¼˜åŒ–
- ä½¿ç”¨CDN加速二维码图片访问
- å®šæœŸæ¸…理无用的二维码文件
- è€ƒè™‘使用对象存储服务(如阿里云OSS)
## å®‰å…¨é…ç½®
### 1. HTTPS配置
```nginx
server {
    listen 443 ssl;
    server_name yourdomain.com;
    ssl_certificate /path/to/cert.pem;
    ssl_certificate_key /path/to/key.pem;
    # å…¶ä»–配置...
}
```
### 2. æŽ¥å£å®‰å…¨
- è¯„价提交接口添加频率限制
- æ•æ„Ÿä¿¡æ¯åŠ å¯†å­˜å‚¨
- å®šæœŸæ›´æ–°å¾®ä¿¡AppSecret
### 3. æ•°æ®å¤‡ä»½
```bash
# æ•°æ®åº“备份
mysqldump -u username -p database_name > backup_$(date +%Y%m%d).sql
# æ–‡ä»¶å¤‡ä»½
tar -czf upload_backup_$(date +%Y%m%d).tar.gz /path/to/upload/
```
## æ•…障排查
### 1. å¸¸è§é—®é¢˜
#### äºŒç»´ç ç”Ÿæˆå¤±è´¥
- æ£€æŸ¥æ–‡ä»¶è·¯å¾„权限
- æ£€æŸ¥ç£ç›˜ç©ºé—´
- æŸ¥çœ‹åº”用日志
#### å¾®ä¿¡æŽˆæƒå¤±è´¥
- æ£€æŸ¥åŸŸåé…ç½®
- æ£€æŸ¥HTTPS证书
- æ£€æŸ¥AppID和AppSecret
#### è¯„价提交失败
- æ£€æŸ¥æ•°æ®åº“连接
- æ£€æŸ¥å¿…填字段验证
- æŸ¥çœ‹é”™è¯¯æ—¥å¿—
### 2. è”系支持
如遇到问题,请提供:
- é”™è¯¯æ—¥å¿—
- æ“ä½œæ­¥éª¤
- çŽ¯å¢ƒä¿¡æ¯
- é”™è¯¯æˆªå›¾