wlzboy
2025-11-13 0bf199cf85a36157113d217363bb96314956b75f
feat: 修复小程序上传附件
7个文件已修改
458 ■■■■ 已修改文件
app/components/AttachmentUpload.vue 99 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/utils/request.js 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleMileageStats.java 141 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleMileageStatsMapper.java 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-system/src/main/resources/mapper/system/VehicleMileageStatsMapper.xml 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/vehicle_mileage_stats.sql 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
app/components/AttachmentUpload.vue
@@ -67,8 +67,10 @@
</template>
<script>
  import { getAttachmentList, uploadAttachmentFromWechat, deleteAttachment, getWechatAccessToken } from '@/api/task'
  import { getAttachmentList, deleteAttachment } from '@/api/task'
  import { formatDateTime } from '@/utils/common'
  import { getToken } from '@/utils/auth'
  import config from '@/config'
  
  export default {
    name: 'AttachmentUpload',
@@ -106,16 +108,10 @@
          { label: '系安全带', value: '6' }
        ],
        selectedCategoryIndex: 0,
        tempImagePath: null,
        isWechatMiniProgram: false
        tempImagePath: null
      }
    },
    mounted() {
      // 检测是否是微信小程序环境
      // #ifdef MP-WEIXIN
      this.isWechatMiniProgram = true
      // #endif
      // 自动加载附件列表
      if (this.autoLoad && this.taskId) {
        this.loadAttachmentList()
@@ -189,95 +185,20 @@
        const that = this
        const category = this.categoryList[this.selectedCategoryIndex].value
        
        // 微信小程序环境:先获取AccessToken,再上传到微信服务器,最后提交mediaId到后端
        // #ifdef MP-WEIXIN
        if (this.isWechatMiniProgram) {
          uni.showLoading({
            title: '上传中...'
          })
          // 第一步:从后端获取AccessToken
          getWechatAccessToken().then(tokenResponse => {
            // 接口返回格式:{"msg":"token值","code":200}
            console.log('获取AccessToken成功:', tokenResponse)
            const accessToken = tokenResponse.msg || tokenResponse.data || tokenResponse
            if (!accessToken) {
              uni.hideLoading()
              that.$modal.showToast('获取AccessToken失败')
              console.error('获取AccessToken失败,响应数据:', tokenResponse)
              return
            }
            console.log('获取到AccessToken:', accessToken)
            // 第二步:上传到微信服务器
            const uploadUrl = `https://api.weixin.qq.com/cgi-bin/media/upload?access_token=${accessToken}&type=image`
            uni.uploadFile({
              url: uploadUrl,
              filePath: that.tempImagePath,
              name: 'media',
              success: function(res) {
                console.log('微信上传响应:', res)
                try {
                  const data = JSON.parse(res.data)
                  if (data.media_id) {
                    // 第三步:提交mediaId到后端
                    uploadAttachmentFromWechat(that.taskId, data.media_id, category).then(response => {
                      uni.hideLoading()
                      that.$modal.showToast('上传成功')
                      that.closeUploadDialog()
                      that.loadAttachmentList()
                      that.$emit('uploaded', response)
                    }).catch(error => {
                      uni.hideLoading()
                      console.error('提交mediaId失败:', error)
                      that.$modal.showToast('上传失败:' + (error.msg || '请重试'))
                      that.$emit('error', error)
                    })
                  } else {
                    uni.hideLoading()
                    const errMsg = data.errmsg || '未知错误'
                    console.error('微信返回错误:', data)
                    that.$modal.showToast('微信上传失败:' + errMsg)
                  }
                } catch (e) {
                  uni.hideLoading()
                  console.error('解析微信响应失败:', e, res.data)
                  that.$modal.showToast('上传失败:响应解析错误')
                }
              },
              fail: function(err) {
                uni.hideLoading()
                console.error('上传到微信失败:', err)
                that.$modal.showToast('上传失败:' + (err.errMsg || '请检查网络'))
                that.$emit('error', err)
              }
            })
          }).catch(error => {
            uni.hideLoading()
            console.error('获取AccessToken失败:', error)
            that.$modal.showToast('获取AccessToken失败')
            that.$emit('error', error)
          })
          return
        }
        // #endif
        // 非微信小程序环境:直接上传到后端服务器
        // 统一直接上传到后端服务器
        uni.showLoading({
          title: '上传中...'
        })
        
        uni.uploadFile({
          url: that.$baseUrl + '/task/attachment/upload/' + that.taskId,
          url: config.baseUrl + '/task/attachment/upload/' + that.taskId,
          filePath: that.tempImagePath,
          name: 'file',
          formData: {
            'category': category
          },
          header: {
            'Authorization': 'Bearer ' + uni.getStorageSync('token')
            'Authorization': 'Bearer ' + getToken()
          },
          success: function(uploadRes) {
            uni.hideLoading()
@@ -301,7 +222,7 @@
          fail: function(err) {
            uni.hideLoading()
            console.error('上传失败:', err)
            that.$modal.showToast('上传失败')
            that.$modal.showToast('上传失败:' + (err.errMsg || '请检查网络'))
            that.$emit('error', err)
          }
        })
@@ -318,7 +239,7 @@
          let imageUrl = item.fileUrl
          // 如果没有fileUrl,则使用下载接口
          if (!imageUrl) {
            imageUrl = this.$baseUrl + '/task/attachment/download/' + item.attachmentId
            imageUrl = config.baseUrl + '/task/attachment/download/' + item.attachmentId
          }
          
          // 微信小程序中预览图片
@@ -405,7 +326,7 @@
        }
        // 使用下载接口
        if (item.attachmentId) {
          return this.$baseUrl + '/task/attachment/download/' + item.attachmentId
          return config.baseUrl + '/task/attachment/download/' + item.attachmentId
        }
        // 默认占位图
        return '/static/images/default-image.png'
app/utils/request.js
@@ -11,8 +11,10 @@
  // 是否需要设置 token
  const isToken = (config.headers || {}).isToken === false
  config.header = config.header || {}
  if (getToken() && !isToken) {
    config.header['Authorization'] = 'Bearer ' + getToken()
  const token = getToken()
  // 只有当token存在且不为空字符串时才添加到header
  if (token && token.trim() !== '' && !isToken) {
    config.header['Authorization'] = 'Bearer ' + token
  }
  // get请求映射params参数
  if (config.params) {
ruoyi-framework/src/main/java/com/ruoyi/framework/web/service/TokenService.java
@@ -76,9 +76,13 @@
            }
            catch (Exception e)
            {
                log.error("获取用户信息异常'{}'", e.getMessage());
                log.error("获取用户信息异常,token: [{}], 错误: {}", token.length() > 50 ? token.substring(0, 50) + "..." : token, e.getMessage());
            }
        }
        else
        {
            log.warn("请求未携带有效token,Header: {}", request.getHeader(header));
        }
        return null;
    }
ruoyi-system/src/main/java/com/ruoyi/system/domain/VehicleMileageStats.java
@@ -0,0 +1,141 @@
package com.ruoyi.system.domain;
import java.math.BigDecimal;
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;
/**
 * 车辆里程统计对象 vehicle_mileage_stats
 *
 * @author ruoyi
 * @date 2025-01-15
 */
public class VehicleMileageStats extends BaseEntity
{
    private static final long serialVersionUID = 1L;
    /** 统计ID */
    private Long statsId;
    /** 车辆ID */
    @Excel(name = "车辆ID")
    private Long vehicleId;
    /** 统计日期 */
    @JsonFormat(pattern = "yyyy-MM-dd")
    @Excel(name = "统计日期", width = 30, dateFormat = "yyyy-MM-dd")
    private Date statsDate;
    /** 总里程(公里) */
    @Excel(name = "总里程(公里)")
    private BigDecimal totalMileage;
    /** 任务里程(公里) */
    @Excel(name = "任务里程(公里)")
    private BigDecimal taskMileage;
    /** 非任务里程(公里) */
    @Excel(name = "非任务里程(公里)")
    private BigDecimal nonTaskMileage;
    /** 任务里程占比(%) */
    @Excel(name = "任务里程占比(%)")
    private BigDecimal taskMileageRatio;
    /** GPS点数 */
    @Excel(name = "GPS点数")
    private Integer gpsCount;
    public void setStatsId(Long statsId)
    {
        this.statsId = statsId;
    }
    public Long getStatsId()
    {
        return statsId;
    }
    public void setVehicleId(Long vehicleId)
    {
        this.vehicleId = vehicleId;
    }
    public Long getVehicleId()
    {
        return vehicleId;
    }
    public void setStatsDate(Date statsDate)
    {
        this.statsDate = statsDate;
    }
    public Date getStatsDate()
    {
        return statsDate;
    }
    public void setTotalMileage(BigDecimal totalMileage)
    {
        this.totalMileage = totalMileage;
    }
    public BigDecimal getTotalMileage()
    {
        return totalMileage;
    }
    public void setTaskMileage(BigDecimal taskMileage)
    {
        this.taskMileage = taskMileage;
    }
    public BigDecimal getTaskMileage()
    {
        return taskMileage;
    }
    public void setNonTaskMileage(BigDecimal nonTaskMileage)
    {
        this.nonTaskMileage = nonTaskMileage;
    }
    public BigDecimal getNonTaskMileage()
    {
        return nonTaskMileage;
    }
    public void setTaskMileageRatio(BigDecimal taskMileageRatio)
    {
        this.taskMileageRatio = taskMileageRatio;
    }
    public BigDecimal getTaskMileageRatio()
    {
        return taskMileageRatio;
    }
    public void setGpsCount(Integer gpsCount)
    {
        this.gpsCount = gpsCount;
    }
    public Integer getGpsCount()
    {
        return gpsCount;
    }
    @Override
    public String toString() {
        return new ToStringBuilder(this,ToStringStyle.MULTI_LINE_STYLE)
            .append("statsId", getStatsId())
            .append("vehicleId", getVehicleId())
            .append("statsDate", getStatsDate())
            .append("totalMileage", getTotalMileage())
            .append("taskMileage", getTaskMileage())
            .append("nonTaskMileage", getNonTaskMileage())
            .append("taskMileageRatio", getTaskMileageRatio())
            .append("gpsCount", getGpsCount())
            .append("createTime", getCreateTime())
            .append("updateTime", getUpdateTime())
            .toString();
    }
}
ruoyi-system/src/main/java/com/ruoyi/system/mapper/VehicleMileageStatsMapper.java
@@ -0,0 +1,72 @@
package com.ruoyi.system.mapper;
import java.util.Date;
import java.util.List;
import com.ruoyi.system.domain.VehicleMileageStats;
import org.apache.ibatis.annotations.Param;
/**
 * 车辆里程统计Mapper接口
 *
 * @author ruoyi
 * @date 2025-01-15
 */
public interface VehicleMileageStatsMapper
{
    /**
     * 查询车辆里程统计
     *
     * @param statsId 车辆里程统计主键
     * @return 车辆里程统计
     */
    public VehicleMileageStats selectVehicleMileageStatsByStatsId(Long statsId);
    /**
     * 查询车辆里程统计列表
     *
     * @param vehicleMileageStats 车辆里程统计
     * @return 车辆里程统计集合
     */
    public List<VehicleMileageStats> selectVehicleMileageStatsList(VehicleMileageStats vehicleMileageStats);
    /**
     * 新增车辆里程统计
     *
     * @param vehicleMileageStats 车辆里程统计
     * @return 结果
     */
    public int insertVehicleMileageStats(VehicleMileageStats vehicleMileageStats);
    /**
     * 修改车辆里程统计
     *
     * @param vehicleMileageStats 车辆里程统计
     * @return 结果
     */
    public int updateVehicleMileageStats(VehicleMileageStats vehicleMileageStats);
    /**
     * 删除车辆里程统计
     *
     * @param statsId 车辆里程统计主键
     * @return 结果
     */
    public int deleteVehicleMileageStatsByStatsId(Long statsId);
    /**
     * 批量删除车辆里程统计
     *
     * @param statsIds 需要删除的数据主键集合
     * @return 结果
     */
    public int deleteVehicleMileageStatsByStatsIds(Long[] statsIds);
    /**
     * 查询指定车辆和日期的统计记录
     *
     * @param vehicleId 车辆ID
     * @param statsDate 统计日期
     * @return 统计记录
     */
    public VehicleMileageStats selectByVehicleIdAndDate(@Param("vehicleId") Long vehicleId, @Param("statsDate") Date statsDate);
}
ruoyi-system/src/main/resources/mapper/system/VehicleMileageStatsMapper.xml
@@ -0,0 +1,97 @@
<?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.VehicleMileageStatsMapper">
    <resultMap type="VehicleMileageStats" id="VehicleMileageStatsResult">
        <result property="statsId"    column="stats_id"    />
        <result property="vehicleId"    column="vehicle_id"    />
        <result property="statsDate"    column="stats_date"    />
        <result property="totalMileage"    column="total_mileage"    />
        <result property="taskMileage"    column="task_mileage"    />
        <result property="nonTaskMileage"    column="non_task_mileage"    />
        <result property="taskMileageRatio"    column="task_mileage_ratio"    />
        <result property="gpsCount"    column="gps_count"    />
        <result property="createTime"    column="create_time"    />
        <result property="updateTime"    column="update_time"    />
    </resultMap>
    <sql id="selectVehicleMileageStatsVo">
        select stats_id, vehicle_id, stats_date, total_mileage, task_mileage, non_task_mileage,
               task_mileage_ratio, gps_count, create_time, update_time
        from vehicle_mileage_stats
    </sql>
    <select id="selectVehicleMileageStatsList" parameterType="VehicleMileageStats" resultMap="VehicleMileageStatsResult">
        <include refid="selectVehicleMileageStatsVo"/>
        <where>
            <if test="vehicleId != null "> and vehicle_id = #{vehicleId}</if>
            <if test="statsDate != null "> and stats_date = #{statsDate}</if>
        </where>
        order by stats_date desc, vehicle_id
    </select>
    <select id="selectVehicleMileageStatsByStatsId" parameterType="Long" resultMap="VehicleMileageStatsResult">
        <include refid="selectVehicleMileageStatsVo"/>
        where stats_id = #{statsId}
    </select>
    <select id="selectByVehicleIdAndDate" resultMap="VehicleMileageStatsResult">
        <include refid="selectVehicleMileageStatsVo"/>
        where vehicle_id = #{vehicleId} and stats_date = #{statsDate}
    </select>
    <insert id="insertVehicleMileageStats" parameterType="VehicleMileageStats" useGeneratedKeys="true" keyProperty="statsId">
        insert into vehicle_mileage_stats
        <trim prefix="(" suffix=")" suffixOverrides=",">
            <if test="vehicleId != null">vehicle_id,</if>
            <if test="statsDate != null">stats_date,</if>
            <if test="totalMileage != null">total_mileage,</if>
            <if test="taskMileage != null">task_mileage,</if>
            <if test="nonTaskMileage != null">non_task_mileage,</if>
            <if test="taskMileageRatio != null">task_mileage_ratio,</if>
            <if test="gpsCount != null">gps_count,</if>
            <if test="createTime != null">create_time,</if>
            <if test="updateTime != null">update_time,</if>
         </trim>
        <trim prefix="values (" suffix=")" suffixOverrides=",">
            <if test="vehicleId != null">#{vehicleId},</if>
            <if test="statsDate != null">#{statsDate},</if>
            <if test="totalMileage != null">#{totalMileage},</if>
            <if test="taskMileage != null">#{taskMileage},</if>
            <if test="nonTaskMileage != null">#{nonTaskMileage},</if>
            <if test="taskMileageRatio != null">#{taskMileageRatio},</if>
            <if test="gpsCount != null">#{gpsCount},</if>
            <if test="createTime != null">#{createTime},</if>
            <if test="updateTime != null">#{updateTime},</if>
         </trim>
    </insert>
    <update id="updateVehicleMileageStats" parameterType="VehicleMileageStats">
        update vehicle_mileage_stats
        <trim prefix="SET" suffixOverrides=",">
            <if test="vehicleId != null">vehicle_id = #{vehicleId},</if>
            <if test="statsDate != null">stats_date = #{statsDate},</if>
            <if test="totalMileage != null">total_mileage = #{totalMileage},</if>
            <if test="taskMileage != null">task_mileage = #{taskMileage},</if>
            <if test="nonTaskMileage != null">non_task_mileage = #{nonTaskMileage},</if>
            <if test="taskMileageRatio != null">task_mileage_ratio = #{taskMileageRatio},</if>
            <if test="gpsCount != null">gps_count = #{gpsCount},</if>
            <if test="createTime != null">create_time = #{createTime},</if>
            <if test="updateTime != null">update_time = #{updateTime},</if>
        </trim>
        where stats_id = #{statsId}
    </update>
    <delete id="deleteVehicleMileageStatsByStatsId" parameterType="Long">
        delete from vehicle_mileage_stats where stats_id = #{statsId}
    </delete>
    <delete id="deleteVehicleMileageStatsByStatsIds" parameterType="String">
        delete from vehicle_mileage_stats where stats_id in
        <foreach item="statsId" collection="array" open="(" separator="," close=")">
            #{statsId}
        </foreach>
    </delete>
</mapper>
sql/vehicle_mileage_stats.sql
@@ -0,0 +1,37 @@
-- 车辆里程统计表
DROP TABLE IF EXISTS `vehicle_mileage_stats`;
CREATE TABLE `vehicle_mileage_stats` (
  `stats_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '统计ID',
  `vehicle_id` bigint(20) NOT NULL COMMENT '车辆ID',
  `stats_date` date NOT NULL COMMENT '统计日期',
  `total_mileage` decimal(10,2) DEFAULT '0.00' COMMENT '总里程(公里)',
  `task_mileage` decimal(10,2) DEFAULT '0.00' COMMENT '任务里程(公里)',
  `non_task_mileage` decimal(10,2) DEFAULT '0.00' COMMENT '非任务里程(公里)',
  `task_mileage_ratio` decimal(5,2) DEFAULT '0.00' COMMENT '任务里程占比(%)',
  `gps_count` int(11) DEFAULT '0' COMMENT 'GPS点数',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  PRIMARY KEY (`stats_id`),
  UNIQUE KEY `uk_vehicle_date` (`vehicle_id`,`stats_date`),
  KEY `idx_stats_date` (`stats_date`),
  KEY `idx_vehicle_id` (`vehicle_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆里程统计表';
-- 车辆里程统计明细表
DROP TABLE IF EXISTS `vehicle_mileage_stats_detail`;
CREATE TABLE `vehicle_mileage_stats_detail` (
  `detail_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '明细ID',
  `stats_id` bigint(20) NOT NULL COMMENT '统计ID',
  `vehicle_id` bigint(20) NOT NULL COMMENT '车辆ID',
  `task_id` bigint(20) DEFAULT NULL COMMENT '任务ID(任务时段)',
  `start_time` datetime NOT NULL COMMENT '开始时间',
  `end_time` datetime NOT NULL COMMENT '结束时间',
  `mileage` decimal(10,2) DEFAULT '0.00' COMMENT '里程(公里)',
  `is_task_period` char(1) DEFAULT '0' COMMENT '是否任务时段(0否 1是)',
  `gps_count` int(11) DEFAULT '0' COMMENT 'GPS点数',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  PRIMARY KEY (`detail_id`),
  KEY `idx_stats_id` (`stats_id`),
  KEY `idx_vehicle_id` (`vehicle_id`),
  KEY `idx_task_id` (`task_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='车辆里程统计明细表';