admin
2025-08-08 035edfa382d349ba66240fbfef68c14c7cfc95d1
功能完善
11个文件已添加
12个文件已修改
1 文件已重命名
1968 ■■■■■ 已修改文件
src/main/java/com/taoke/autopay/controller/admin/js2/AdminClientInfoJS2Controller.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/controller/admin/js2/AdminOrderTaskController.java 270 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/controller/client/js2/OrderTaskController.java 323 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/dao/OrderTaskExecutionDetailMapper.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/entity/ClientInfo.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/entity/js2/OrderTask.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/factory/js2/OrderTaskExecutionDetailFactory.java 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/factory/js2/OrderTaskFactory.java 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/service/impl/ClientInfoServiceImpl.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/service/impl/js2/OrderTaskExecutionDetailServiceImpl.java 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/service/impl/js2/OrderTaskServiceImpl.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/service/js2/OrderTaskExecutionDetailService.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/service/js2/OrderTaskService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/task/OrderTaskDistributeTask.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/utils/encrypt/AESUtil.java 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/vo/OrderTaskExecutionDetailVO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/vo/admin/js2/OrderTaskVO.java 109 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/mapper/OrderTaskExecutionDetailMapper.xml 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/static/admin/index.html 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/static/admin/order-client-list.html 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/static/admin/order-client-update.html 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/static/admin/order-task-assigned-clients.html 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/static/admin/order-task-list.html 427 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/resources/static/admin/order-task-update.html 164 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main/java/com/taoke/autopay/controller/admin/js2/AdminClientInfoJS2Controller.java
File was renamed from src/main/java/com/taoke/autopay/controller/admin/AdminClientInfoJS2Controller.java
@@ -1,4 +1,4 @@
package com.taoke.autopay.controller.admin;
package com.taoke.autopay.controller.admin.js2;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
@@ -12,6 +12,7 @@
import com.taoke.autopay.service.ClientInfoService;
import com.taoke.autopay.utils.Constant;
import com.taoke.autopay.utils.TimeUtil;
import com.taoke.autopay.utils.encrypt.AESUtil;
import com.taoke.autopay.vo.admin.AdminOrderClientInfoVO;
import net.sf.json.JSONObject;
import org.springframework.stereotype.Controller;
@@ -19,7 +20,6 @@
import org.springframework.web.bind.annotation.ResponseBody;
import org.yeshi.utils.JsonUtil;
import org.yeshi.utils.StringUtil;
import org.yeshi.utils.encrypt.AESUtil;
import javax.annotation.Resource;
import java.io.IOException;
src/main/java/com/taoke/autopay/controller/admin/js2/AdminOrderTaskController.java
New file
@@ -0,0 +1,270 @@
package com.taoke.autopay.controller.admin.js2;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.taoke.autopay.dao.OrderTaskExecutionDetailMapper;
import com.taoke.autopay.dao.OrderTaskMapper;
import com.taoke.autopay.entity.js2.OrderTask;
import com.taoke.autopay.entity.js2.OrderTaskExecutionDetail;
import com.taoke.autopay.exception.OrderTaskException;
import com.taoke.autopay.factory.js2.OrderTaskFactory;
import com.taoke.autopay.service.js2.OrderTaskService;
import com.taoke.autopay.utils.TimeUtil;
import com.taoke.autopay.vo.admin.js2.OrderTaskVO;
import net.sf.json.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.yeshi.utils.JsonUtil;
import org.yeshi.utils.StringUtil;
import javax.annotation.Resource;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Calendar;
import java.util.ArrayList;
/**
 * 下单任务管理
 */
@Controller
@RequestMapping("/admin/api/ordertask/js2")
public class AdminOrderTaskController {
    @Resource
    private OrderTaskService orderTaskService;
    @Resource
    private OrderTaskExecutionDetailMapper orderTaskExecutionDetailMapper;
    private Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, new TypeAdapter<Date>() {
        @Override
        public void write(JsonWriter out, Date value) throws IOException {
            String desc = "";
            if (value != null) {
                // 判断是否是同一天
                desc = TimeUtil.getGernalTime(value.getTime(), "yyyy-MM-dd HH:mm:ss");
                out.value(desc);
            } else {
                out.value("");
            }
        }
        @Override
        public Date read(JsonReader in) throws IOException {
            return new Date();
        }
    }).create();
    /**
     * 任务列表查询(按照关键字,日期查询)
     */
    @ResponseBody
    @RequestMapping("list")
    public String list(String keyword, String startDate, String endDate, Integer status, int page, int limit) {
        try {
            OrderTaskMapper.DaoQuery query = new OrderTaskMapper.DaoQuery();
            if (!StringUtil.isNullOrEmpty(keyword)) {
                query.keywordContent = keyword;
            }
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            if (!StringUtil.isNullOrEmpty(startDate)) {
                query.minCreateTime = sdf.parse(startDate);
            }
            if (!StringUtil.isNullOrEmpty(endDate)) {
                Date end = sdf.parse(endDate);
                // 设置为当天的最后时刻
                Calendar cal = Calendar.getInstance();
                cal.setTime(end);
                cal.set(Calendar.HOUR_OF_DAY, 23);
                cal.set(Calendar.MINUTE, 59);
                cal.set(Calendar.SECOND, 59);
                query.maxCreateTime = cal.getTime();
            }
            if (status != null) {
                query.status = status;
            }
            query.sortList = Arrays.asList(new String[]{"create_time desc"});
            query.start = (page - 1) * limit;
            query.count = limit;
            List<OrderTask> list = orderTaskService.listOrderTasks(query, page, limit);
            long count = orderTaskService.countOrderTasks(query);
            // 转换为VO对象
            List<OrderTaskVO> voList = new ArrayList<>();
            for (OrderTask orderTask : list) {
                voList.add(OrderTaskFactory.createOrderTaskVO(orderTask));
            }
            JSONObject data = new JSONObject();
            data.put("list", gson.toJson(voList));
            data.put("count", count);
            return JsonUtil.loadTrueResult(data);
        } catch (ParseException e) {
            return JsonUtil.loadFalseResult("日期格式错误");
        } catch (Exception e) {
            return JsonUtil.loadFalseResult(e.getMessage());
        }
    }
    /**
     * 获取单个任务信息
     */
    @ResponseBody
    @RequestMapping("get")
    public String get(Long id) {
        try {
            if (id == null) {
                return JsonUtil.loadFalseResult("任务ID不能为空");
            }
            OrderTask orderTask = orderTaskService.getOrderTaskById(id);
            if (orderTask == null) {
                return JsonUtil.loadFalseResult("任务不存在");
            }
            OrderTaskVO orderTaskVO = OrderTaskFactory.createOrderTaskVO(orderTask);
            return JsonUtil.loadTrueResult(gson.toJson(orderTaskVO));
        } catch (Exception e) {
            return JsonUtil.loadFalseResult("系统异常:" + e.getMessage());
        }
    }
    /**
     * 新增任务
     */
    @ResponseBody
    @RequestMapping("add")
    public String add(OrderTaskVO orderTaskVO) {
        try {
            if (orderTaskVO == null) {
                return JsonUtil.loadFalseResult("任务信息不能为空");
            }
            // 将VO转换为实体
            OrderTask orderTask = OrderTaskFactory.createOrderTask(orderTaskVO);
            OrderTask result = orderTaskService.createOrderTask(orderTask);
            OrderTaskVO resultVO = OrderTaskFactory.createOrderTaskVO(result);
            return JsonUtil.loadTrueResult(gson.toJson(resultVO));
        } catch (ParseException e) {
            return JsonUtil.loadFalseResult("日期格式错误:" + e.getMessage());
        } catch (OrderTaskException e) {
            return JsonUtil.loadFalseResult(e.getMessage());
        } catch (Exception e) {
            return JsonUtil.loadFalseResult("系统异常:" + e.getMessage());
        }
    }
    /**
     * 删除任务
     */
    @ResponseBody
    @RequestMapping("delete")
    public String delete(Long id) {
        try {
            if (id == null) {
                return JsonUtil.loadFalseResult("任务ID不能为空");
            }
            orderTaskService.deleteOrderTask(id);
            return JsonUtil.loadTrueResult("");
        } catch (OrderTaskException e) {
            return JsonUtil.loadFalseResult(e.getMessage());
        } catch (Exception e) {
            return JsonUtil.loadFalseResult("系统异常:" + e.getMessage());
        }
    }
    /**
     * 修改任务
     */
    @ResponseBody
    @RequestMapping("update")
    public String update(OrderTaskVO orderTaskVO) {
        try {
            if (orderTaskVO == null || orderTaskVO.getId() == null) {
                return JsonUtil.loadFalseResult("任务信息不完整");
            }
            // 先从数据库获取原始任务信息
            OrderTask orderTask = orderTaskService.getOrderTaskByIdForUpdate(orderTaskVO.getId());
            if (orderTask == null) {
                return JsonUtil.loadFalseResult("任务不存在");
            }
            OrderTask newOrderTask = OrderTaskFactory.createOrderTask(orderTaskVO);
            newOrderTask.setUpdateTime(new Date());
            orderTaskService.updateOrderTask(newOrderTask);
            return JsonUtil.loadTrueResult("");
        } catch (ParseException e) {
            return JsonUtil.loadFalseResult("日期格式错误:" + e.getMessage());
        } catch (OrderTaskException e) {
            return JsonUtil.loadFalseResult(e.getMessage());
        } catch (Exception e) {
            return JsonUtil.loadFalseResult("系统异常:" + e.getMessage());
        }
    }
    /**
     * 任务客户端分配
     */
    @ResponseBody
    @RequestMapping("assign")
    public String assign(Long taskId) {
        try {
            if (taskId == null) {
                return JsonUtil.loadFalseResult("任务ID不能为空");
            }
            int result = orderTaskService.assignTask(taskId);
            JSONObject data = new JSONObject();
            data.put("result", result);
            return JsonUtil.loadTrueResult(data);
        } catch (OrderTaskException e) {
            return JsonUtil.loadFalseResult(e.getMessage());
        } catch (Exception e) {
            return JsonUtil.loadFalseResult("系统异常:" + e.getMessage());
        }
    }
    /**
     * 查询任务已分配的客户端列表
     */
    @ResponseBody
    @RequestMapping("assignedClients")
    public String assignedClients(Long taskId, int page, int limit) {
        try {
            if (taskId == null) {
                return JsonUtil.loadFalseResult("任务ID不能为空");
            }
            OrderTaskExecutionDetailMapper.DaoQuery query = new OrderTaskExecutionDetailMapper.DaoQuery();
            query.taskId = taskId;
            query.start = (page - 1) * limit;
            query.count = limit;
            query.sortList = Arrays.asList("create_time desc");
            List<OrderTaskExecutionDetail> list = orderTaskExecutionDetailMapper.list(query);
            long count = orderTaskExecutionDetailMapper.count(query);
            JSONObject data = new JSONObject();
            data.put("list", gson.toJson(list));
            data.put("count", count);
            return JsonUtil.loadTrueResult(data);
        } catch (Exception e) {
            return JsonUtil.loadFalseResult("系统异常:" + e.getMessage());
        }
    }
}
src/main/java/com/taoke/autopay/controller/client/js2/OrderTaskController.java
New file
@@ -0,0 +1,323 @@
package com.taoke.autopay.controller.client.js2;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonWriter;
import com.taoke.autopay.dao.OrderTaskExecutionDetailMapper;
import com.taoke.autopay.entity.ClientAdditionalInfo;
import com.taoke.autopay.entity.js2.OrderTask;
import com.taoke.autopay.entity.js2.OrderTaskExecutionDetail;
import com.taoke.autopay.factory.js2.OrderTaskExecutionDetailFactory;
import com.taoke.autopay.service.ClientAdditionalInfoService;
import com.taoke.autopay.service.js2.OrderTaskExecutionDetailService;
import com.taoke.autopay.service.js2.OrderTaskService;
import com.taoke.autopay.utils.TimeUtil;
import com.taoke.autopay.vo.OrderTaskExecutionDetailVO;
import net.sf.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.yeshi.utils.JsonUtil;
import org.yeshi.utils.StringUtil;
import javax.annotation.Resource;
import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
/**
 * 手机客户端下单任务控制器
 */
@Controller
@RequestMapping("api/client/js2/task")
public class OrderTaskController {
    @Resource
    private ClientAdditionalInfoService clientAdditionalInfoService;
    private Logger loggerTask = LoggerFactory.getLogger("taskLogger");
    private Gson gson = new GsonBuilder().registerTypeAdapter(Date.class, new TypeAdapter<Date>() {
        @Override
        public void write(JsonWriter out, Date value) throws IOException {
            String desc = "";
            if (value != null) {
                desc = TimeUtil.getGernalTime(value.getTime(), "yyyy-MM-dd HH:mm:ss");
                out.value(desc);
            } else {
                out.value("");
            }
        }
        @Override
        public Date read(JsonReader in) throws IOException {
            return new Date();
        }
    }).create();
    @Resource
    private OrderTaskExecutionDetailService orderTaskExecutionDetailService;
    @Resource
    private OrderTaskService orderTaskService;
    /**
     * 获取设备可执行的任务列表
     *
     * @param uid 客户端ID
     * @param page 页码
     * @param pageSize 每页数量
     * @return 任务列表
     */
    @ResponseBody
    @RequestMapping("list")
    public String list(Long uid, int page, int pageSize) {
        try {
            if (uid == null) {
                return JsonUtil.loadFalseResult("客户端ID不能为空");
            }
            List<OrderTaskExecutionDetailVO> voList= new ArrayList<>();
            List<OrderTaskExecutionDetail> list = orderTaskExecutionDetailService.listCanExcuteTaskDetail(uid, (page-1)*pageSize,pageSize);
            if(!list.isEmpty()){
                List<Long> taskIds=new ArrayList<>();
                for (OrderTaskExecutionDetail detail : list) {
                    if(taskIds.contains(detail.getTaskId())){
                        continue;
                    }
                    taskIds.add(detail.getTaskId());
                }
                // 获取手机号与支付宝账号信息
                ClientAdditionalInfo clientAdditionalInfo = clientAdditionalInfoService.getClientAdditionalInfoByClientId(uid);
                List<OrderTask> orderTaskList =  orderTaskService.getOrderTaskByIds(taskIds);
                Map<Long, OrderTask> orderTaskMap=new HashMap<>();
                for(OrderTask task:orderTaskList){
                    orderTaskMap.put(task.getId(),task);
                }
                for(OrderTaskExecutionDetail detail:list){
                    OrderTaskExecutionDetailVO vo = OrderTaskExecutionDetailFactory.createOrderTaskExecutionDetailVO(orderTaskMap.get(detail.getTaskId()),detail,clientAdditionalInfo);
                    voList.add(vo);
                }
            }
            long count = orderTaskExecutionDetailService.countCanExcuteTaskDetail(uid);
            JSONObject data = new JSONObject();
            data.put("list", gson.toJson(voList));
            data.put("count", count);
            return JsonUtil.loadTrueResult(data);
        } catch (Exception e) {
            loggerTask.error("获取任务列表失败: {}", e.getMessage(), e);
            return JsonUtil.loadFalseResult("系统异常:" + e.getMessage());
        }
    }
    /**
     * 下单成功
     *
     * @param uid 客户端ID
     * @param id 任务执行详情ID
     * @param orderNo 订单号
     * @param productTitle 商品标题
     * @param shopName 店铺名称
     * @param orderTimeStr 下单时间字符串
     * @return 操作结果
     */
    @ResponseBody
    @RequestMapping("orderSuccess")
    public String orderSuccess(Long uid, String id, String orderNo, String productTitle, String shopName, String orderTimeStr) {
        loggerTask.info("orderSuccess[{}]: {}-{}-{}-{}-{}", uid, id, orderNo, productTitle, shopName, orderTimeStr);
        try {
            if (uid == null) {
                return JsonUtil.loadFalseResult("客户端ID不能为空");
            }
            if (StringUtil.isNullOrEmpty(id)) {
                return JsonUtil.loadFalseResult("任务ID不能为空");
            }
            if (StringUtil.isNullOrEmpty(orderNo)) {
                return JsonUtil.loadFalseResult("订单号不能为空");
            }
            Date orderTime = null;
            if (!StringUtil.isNullOrEmpty(orderTimeStr)) {
                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                orderTime = sdf.parse(orderTimeStr);
            } else {
                orderTime = new Date();
            }
            orderTaskExecutionDetailService.orderSuccess(id, orderNo, productTitle, shopName, orderTime);
            return JsonUtil.loadTrueResult("");
        } catch (ParseException e) {
            loggerTask.error("下单成功处理失败,时间格式错误: {}", e.getMessage(), e);
            return JsonUtil.loadFalseResult("时间格式错误");
        } catch (Exception e) {
            loggerTask.error("下单成功处理失败: {}", e.getMessage(), e);
            return JsonUtil.loadFalseResult("系统异常:" + e.getMessage());
        }
    }
    /**
     * 下单失败,上传失败原因
     *
     * @param uid 客户端ID
     * @param id 任务执行详情ID
     * @param failureReason 失败原因
     * @return 操作结果
     */
    @ResponseBody
    @RequestMapping("orderFailure")
    public String orderFailure(Long uid, String id, String failureReason) {
        loggerTask.info("orderFailure[{}]: {}-{}", uid, id, failureReason);
        try {
            if (uid == null) {
                return JsonUtil.loadFalseResult("客户端ID不能为空");
            }
            if (StringUtil.isNullOrEmpty(id)) {
                return JsonUtil.loadFalseResult("任务ID不能为空");
            }
            if (StringUtil.isNullOrEmpty(failureReason)) {
                return JsonUtil.loadFalseResult("失败原因不能为空");
            }
            orderTaskExecutionDetailService.orderFailure(id, failureReason);
            return JsonUtil.loadTrueResult("");
        } catch (Exception e) {
            loggerTask.error("下单失败处理失败: {}", e.getMessage(), e);
            return JsonUtil.loadFalseResult("系统异常:" + e.getMessage());
        }
    }
    /**
     * 确认收货成功,上传券码
     *
     * @param uid 客户端ID
     * @param id 任务执行详情ID
     * @param couponCode 券码
     * @return 操作结果
     */
    @ResponseBody
    @RequestMapping("confirmReceiptSuccess")
    public String confirmReceiptSuccess(Long uid, String id, String couponCode) {
        loggerTask.info("confirmReceiptSuccess[{}]: {}-{}", uid, id, couponCode);
        try {
            if (uid == null) {
                return JsonUtil.loadFalseResult("客户端ID不能为空");
            }
            if (StringUtil.isNullOrEmpty(id)) {
                return JsonUtil.loadFalseResult("任务ID不能为空");
            }
            if (StringUtil.isNullOrEmpty(couponCode)) {
                return JsonUtil.loadFalseResult("券码不能为空");
            }
            orderTaskExecutionDetailService.confirmReceiptSuccess(id, couponCode);
            return JsonUtil.loadTrueResult("");
        } catch (Exception e) {
            loggerTask.error("确认收货成功处理失败: {}", e.getMessage(), e);
            return JsonUtil.loadFalseResult("系统异常:" + e.getMessage());
        }
    }
    /**
     * 确认收货失败,上传失败原因
     *
     * @param uid 客户端ID
     * @param id 任务执行详情ID
     * @param reason 失败原因
     * @return 操作结果
     */
    @ResponseBody
    @RequestMapping("confirmReceiptFailure")
    public String confirmReceiptFailure(Long uid, String id, String reason) {
        loggerTask.info("confirmReceiptFailure[{}]: {}-{}", uid, id, reason);
        try {
            if (uid == null) {
                return JsonUtil.loadFalseResult("客户端ID不能为空");
            }
            if (StringUtil.isNullOrEmpty(id)) {
                return JsonUtil.loadFalseResult("任务ID不能为空");
            }
            if (StringUtil.isNullOrEmpty(reason)) {
                return JsonUtil.loadFalseResult("失败原因不能为空");
            }
            orderTaskExecutionDetailService.confirmReceiptFailure(id, reason);
            return JsonUtil.loadTrueResult("");
        } catch (Exception e) {
            loggerTask.error("确认收货失败处理失败: {}", e.getMessage(), e);
            return JsonUtil.loadFalseResult("系统异常:" + e.getMessage());
        }
    }
    /**
     * 评价成功
     *
     * @param uid 客户端ID
     * @param id 任务执行详情ID
     * @return 操作结果
     */
    @ResponseBody
    @RequestMapping("reviewSuccess")
    public String reviewSuccess(Long uid, String id) {
        loggerTask.info("reviewSuccess[{}]: {}", uid, id);
        try {
            if (uid == null) {
                return JsonUtil.loadFalseResult("客户端ID不能为空");
            }
            if (StringUtil.isNullOrEmpty(id)) {
                return JsonUtil.loadFalseResult("任务ID不能为空");
            }
            orderTaskExecutionDetailService.reviewSuccess(id);
            return JsonUtil.loadTrueResult("");
        } catch (Exception e) {
            loggerTask.error("评价成功处理失败: {}", e.getMessage(), e);
            return JsonUtil.loadFalseResult("系统异常:" + e.getMessage());
        }
    }
    /**
     * 评价失败,上传失败原因
     *
     * @param uid 客户端ID
     * @param id 任务执行详情ID
     * @param reason 失败原因
     * @return 操作结果
     */
    @ResponseBody
    @RequestMapping("reviewFailure")
    public String reviewFailure(Long uid, String id, String reason) {
        loggerTask.info("reviewFailure[{}]: {}-{}", uid, id, reason);
        try {
            if (uid == null) {
                return JsonUtil.loadFalseResult("客户端ID不能为空");
            }
            if (StringUtil.isNullOrEmpty(id)) {
                return JsonUtil.loadFalseResult("任务ID不能为空");
            }
            if (StringUtil.isNullOrEmpty(reason)) {
                return JsonUtil.loadFalseResult("失败原因不能为空");
            }
            orderTaskExecutionDetailService.reviewFailure(id, reason);
            return JsonUtil.loadTrueResult("");
        } catch (Exception e) {
            loggerTask.error("评价失败处理失败: {}", e.getMessage(), e);
            return JsonUtil.loadFalseResult("系统异常:" + e.getMessage());
        }
    }
}
src/main/java/com/taoke/autopay/dao/OrderTaskExecutionDetailMapper.java
@@ -24,6 +24,16 @@
    long count(@Param("query") DaoQuery query);
    
    /**
     * 可执行的任务详情列表
     * @param clientId
     * @param minCreateTime
     * @return
     */
    List<OrderTaskExecutionDetail> listCanExcuteTaskDetail(@Param("clientId") Long clientId,@Param("minCreateTime") Date minCreateTime,@Param("start") long start,@Param("count") int count);
    long countCanExcuteTaskDetail(@Param("clientId") Long clientId,@Param("minCreateTime") Date minCreateTime);
    /**
     * 根据执行状态分组统计clientId的数量
     * @return 包含执行状态和对应clientId数量的映射列表
     */
@@ -35,7 +45,6 @@
        public Long clientId;
        public Integer executionStatus;
        public String statusDescription;
        public Date executionTime;
        public String orderNo;
        public String productName;
        public String shopName;
src/main/java/com/taoke/autopay/entity/ClientInfo.java
@@ -52,4 +52,23 @@
    private Integer rule;
    @Column(name = "client_type")
    private Integer clientType;
    public static enum ClientType{
        AGENT_PAYMENT(CLIENT_TYPE_AGENT_PAYMENT, "c"),
        ORDER(CLIENT_TYPE_ORDER, "s");
        private int value;
        private String accountPrefix;
        ClientType(int value, String accountPrefix){
            this.value = value;
            this.accountPrefix = accountPrefix;
        }
        public int getValue(){
            return value;
        }
        public String getAccountPrefix(){
            return accountPrefix;
        }
    }
}
src/main/java/com/taoke/autopay/entity/js2/OrderTask.java
@@ -2,6 +2,7 @@
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Tolerate;
import org.springframework.data.annotation.Id;
import org.yeshi.utils.generater.mybatis.Column;
import org.yeshi.utils.generater.mybatis.Table;
@@ -28,6 +29,11 @@
    /** 已分配 */
    public static final int STATUS_ASSIGNED = 2;
    @Tolerate
    public OrderTask(){
    }
    /**
     * 主键ID(自增)
     */
src/main/java/com/taoke/autopay/factory/js2/OrderTaskExecutionDetailFactory.java
New file
@@ -0,0 +1,77 @@
package com.taoke.autopay.factory.js2;
import com.taoke.autopay.entity.ClientAdditionalInfo;
import com.taoke.autopay.entity.js2.OrderTask;
import com.taoke.autopay.entity.js2.OrderTaskExecutionDetail;
import com.taoke.autopay.utils.TimeUtil;
import com.taoke.autopay.vo.OrderTaskExecutionDetailVO;
import org.yeshi.utils.StringUtil;
/**
 * @author
 * @title: OrderTaskExecutionDetailFactory
 * @description: 下单任务执行详情工厂类
 * @date 2025/8/8
 */
public class OrderTaskExecutionDetailFactory {
    /**
     * 根据OrderTask、OrderTaskExecutionDetail和ClientAdditionalInfo创建OrderTaskExecutionDetailVO
     *
     * @param orderTask 下单任务实体
     * @param orderTaskExecutionDetail 下单任务执行详情实体
     * @param clientAdditionalInfo 客户端附加信息
     * @return OrderTaskExecutionDetailVO对象
     */
    public static OrderTaskExecutionDetailVO createOrderTaskExecutionDetailVO(
            OrderTask orderTask,
            OrderTaskExecutionDetail orderTaskExecutionDetail,
            ClientAdditionalInfo clientAdditionalInfo) {
        if (orderTask == null || orderTaskExecutionDetail == null) {
            return null;
        }
        OrderTaskExecutionDetailVO.OrderTaskExecutionDetailVOBuilder builder = OrderTaskExecutionDetailVO.builder();
        // 设置基本字段
        builder.id(orderTaskExecutionDetail.getId())
                .taskId(orderTaskExecutionDetail.getTaskId())
                .clientId(orderTaskExecutionDetail.getClientId())
                .orderNo(orderTaskExecutionDetail.getOrderNo())
                .productName(orderTaskExecutionDetail.getProductName())
                .shopName(orderTaskExecutionDetail.getShopName());
        // 设置客户端附加信息
        if (clientAdditionalInfo != null) {
            builder.mobile(clientAdditionalInfo.getMobile())
                    .alipayAccount(clientAdditionalInfo.getAlipayAccount())
                    .alipayPassword(clientAdditionalInfo.getAlipayPassword());
        }
        // 根据执行状态设置执行类型
        Integer executionStatus = orderTaskExecutionDetail.getExecutionStatus();
        if (executionStatus != null) {
            if (executionStatus == OrderTaskExecutionDetail.STATUS_NOT_ORDERED) {
                builder.executionType(OrderTaskExecutionDetailVO.EXECUTION_TYPE_ORDER);
            } else if (executionStatus == OrderTaskExecutionDetail.STATUS_ORDERED) {
                builder.executionType(OrderTaskExecutionDetailVO.EXECUTION_TYPE_RECEIVE);
            } else if (executionStatus == OrderTaskExecutionDetail.STATUS_RECEIVE_SUCCESS) {
                builder.executionType(OrderTaskExecutionDetailVO.EXECUTION_TYPE_REVIEW);
            }
        }
        // 设置口令内容作为key
        if (!StringUtil.isNullOrEmpty(orderTask.getKeywordContent())) {
            builder.key(orderTask.getKeywordContent());
        }
        // 处理时间字段
        if (orderTaskExecutionDetail.getCreateTime() != null) {
            builder.createTime(TimeUtil.getGernalTime(orderTaskExecutionDetail.getCreateTime().getTime(), "yyyy-MM-dd HH:mm:ss"));
        }
        return builder.build();
    }
}
src/main/java/com/taoke/autopay/factory/js2/OrderTaskFactory.java
New file
@@ -0,0 +1,121 @@
package com.taoke.autopay.factory.js2;
import com.taoke.autopay.entity.js2.OrderTask;
import com.taoke.autopay.vo.admin.js2.OrderTaskVO;
import com.taoke.autopay.utils.TimeUtil;
import org.yeshi.utils.StringUtil;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
 * @author
 * @title: OrderTaskFactory
 * @description: 下单任务工厂类
 * @date 2025/7/31
 */
public class OrderTaskFactory {
    /**
     * 将OrderTask转换为OrderTaskVO
     * @param orderTask OrderTask实体
     * @return OrderTaskVO对象
     */
    public static OrderTaskVO createOrderTaskVO(OrderTask orderTask) {
        if (orderTask == null) {
            return null;
        }
        OrderTaskVO.OrderTaskVOBuilder builder = OrderTaskVO.builder();
        builder.id(orderTask.getId())
                .keywordContent(orderTask.getKeywordContent())
                .keywordMd5(orderTask.getKeywordMd5())
                .productSource(orderTask.getProductSource())
                .productName(orderTask.getProductName())
                .shopName(orderTask.getShopName())
                .requiredOrderCount(orderTask.getRequiredOrderCount())
                .completedOrderCount(orderTask.getCompletedOrderCount())
                .receivedOrderCount(orderTask.getReceivedOrderCount())
                .reviewedOrderCount(orderTask.getReviewedOrderCount())
                .receiveCycleHours(orderTask.getReceiveCycleHours())
                .status(orderTask.getStatus())
                .statusDescription(orderTask.getStatusDescription());
        // 处理日期字段转换为字符串
        if (orderTask.getOrderStartTime() != null) {
            builder.orderStartTime(TimeUtil.getGernalTime(orderTask.getOrderStartTime().getTime(), "yyyy-MM-dd HH:mm:ss"));
        }
        if (orderTask.getOrderEndTime() != null) {
            builder.orderEndTime(TimeUtil.getGernalTime(orderTask.getOrderEndTime().getTime(), "yyyy-MM-dd HH:mm:ss"));
        }
        if (orderTask.getCreateTime() != null) {
            builder.createTime(TimeUtil.getGernalTime(orderTask.getCreateTime().getTime(), "yyyy-MM-dd HH:mm:ss"));
        }
        if (orderTask.getUpdateTime() != null) {
            builder.updateTime(TimeUtil.getGernalTime(orderTask.getUpdateTime().getTime(), "yyyy-MM-dd HH:mm:ss"));
        }
        if (orderTask.getEstimatedReceiveTime() != null) {
            builder.estimatedReceiveTime(TimeUtil.getGernalTime(orderTask.getEstimatedReceiveTime().getTime(), "yyyy-MM-dd HH:mm:ss"));
        }
        return builder.build();
    }
    /**
     * 将OrderTaskVO转换为OrderTask
     * @param orderTaskVO OrderTaskVO对象
     * @return OrderTask实体
     */
    public static OrderTask createOrderTask(OrderTaskVO orderTaskVO) throws ParseException {
        if (orderTaskVO == null) {
            return null;
        }
        OrderTask.OrderTaskBuilder builder = OrderTask.builder();
        builder.id(orderTaskVO.getId())
                .keywordContent(orderTaskVO.getKeywordContent())
                .keywordMd5(orderTaskVO.getKeywordMd5())
                .productSource(orderTaskVO.getProductSource())
                .productName(orderTaskVO.getProductName())
                .shopName(orderTaskVO.getShopName())
                .requiredOrderCount(orderTaskVO.getRequiredOrderCount())
                .completedOrderCount(orderTaskVO.getCompletedOrderCount())
                .receivedOrderCount(orderTaskVO.getReceivedOrderCount())
                .reviewedOrderCount(orderTaskVO.getReviewedOrderCount())
                .receiveCycleHours(orderTaskVO.getReceiveCycleHours())
                .status(orderTaskVO.getStatus())
                .statusDescription(orderTaskVO.getStatusDescription());
        // 处理字符串字段转换为日期
        SimpleDateFormat dateTimeFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        if (!StringUtil.isNullOrEmpty(orderTaskVO.getOrderStartTime())) {
            builder.orderStartTime(dateTimeFormat.parse(orderTaskVO.getOrderStartTime()));
        }
        if (!StringUtil.isNullOrEmpty(orderTaskVO.getOrderEndTime())) {
            builder.orderEndTime(dateTimeFormat.parse(orderTaskVO.getOrderEndTime()));
        }
        if (!StringUtil.isNullOrEmpty(orderTaskVO.getCreateTime())) {
            builder.createTime(dateTimeFormat.parse(orderTaskVO.getCreateTime()));
        }
        if (!StringUtil.isNullOrEmpty(orderTaskVO.getUpdateTime())) {
            builder.updateTime(dateTimeFormat.parse(orderTaskVO.getUpdateTime()));
        }
        if (!StringUtil.isNullOrEmpty(orderTaskVO.getEstimatedReceiveTime())) {
            builder.estimatedReceiveTime(dateTimeFormat.parse(orderTaskVO.getEstimatedReceiveTime()));
        }
        return builder.build();
    }
}
src/main/java/com/taoke/autopay/service/impl/ClientInfoServiceImpl.java
@@ -101,13 +101,13 @@
            query.count = 1;
            List<ClientInfo> list = list(query);
            long maxId = 0;
            if (list.size() > 0) {
                maxId = list.get(0).getId();
            if (!list.isEmpty()) {
                maxId =Long.parseLong(StringUtil.getNumberFromString(list.get(0).getAccount()).split(",")[0]) + 1;
            }
            if(info.getClientType()==ClientInfo.CLIENT_TYPE_AGENT_PAYMENT) {
                info.setAccount("c" + maxId);
                info.setAccount(ClientInfo.ClientType.AGENT_PAYMENT.getAccountPrefix() + maxId);
            }else if(info.getClientType()==ClientInfo.CLIENT_TYPE_ORDER){
                info.setAccount("s" + maxId);
                info.setAccount(ClientInfo.ClientType.ORDER.getAccountPrefix() + maxId);
            }
        }
        clientInfoMapper.insertSelective(info);
src/main/java/com/taoke/autopay/service/impl/js2/OrderTaskExecutionDetailServiceImpl.java
@@ -349,6 +349,17 @@
        logger.info("任务执行详情ID:{} 评价失败,原因:{}", id, reason);
    }
    @Override
    public List<OrderTaskExecutionDetail> listCanExcuteTaskDetail(Long clientId, int page, int pageSize) {
        // 获取30分钟以内的订单任务执行详情
        return orderTaskExecutionDetailMapper.listCanExcuteTaskDetail(clientId, new Date(System.currentTimeMillis() - 1000*60*30L));
    }
    @Override
    public long countCanExcuteTaskDetail(Long clientId) {
        return orderTaskExecutionDetailMapper.countCanExcuteTaskDetail(clientId, new Date(System.currentTimeMillis() - 1000*60*30L));
    }
    /**
     * 验证订单任务执行详情的必填字段
     *
src/main/java/com/taoke/autopay/service/impl/js2/OrderTaskServiceImpl.java
@@ -96,6 +96,14 @@
        return orderTaskMapper.selectByPrimaryKey(id);
    }
    
    @Override
    public List<OrderTask> getOrderTaskByIds(List<Long> ids) {
        if(ids==null||ids.isEmpty()){
            return new ArrayList<>();
        }
       return  orderTaskMapper.listByIds(ids);
    }
    @Transactional(rollbackFor = Exception.class)
    @Override
    public OrderTask getOrderTaskByIdForUpdate(Long id) {
@@ -203,13 +211,18 @@
            }
        }
        // 统计所有设备正在执行任务的数量
        List<ClientCountDTO> clientCountList =   orderTaskExecutionDetailMapper.statisticClientIdsCountByStatus(Arrays.asList( new Integer[]{OrderTaskExecutionDetail.STATUS_NOT_ORDERED}));
        Map<Long,  Integer> clientCountMap = new HashMap<>();
        for(ClientCountDTO dto:clientCountList){
            clientCountMap.put(dto.getClientId(), dto.getCount());
        }
        // 剔除已经存在2个任务以上的设备
        for(int i=0;i<clientInfoList.size();i++){
            if(clientCountMap.containsKey(clientInfoList.get(i).getId())&&clientCountMap.get(clientInfoList.get(i).getId())>=2){
                clientInfoList.remove(i);
                i--;
            }
        }
        clientInfoList.sort(new Comparator<ClientInfo>() {
            @Override
src/main/java/com/taoke/autopay/service/js2/OrderTaskExecutionDetailService.java
@@ -3,6 +3,7 @@
import com.taoke.autopay.dao.OrderTaskExecutionDetailMapper;
import com.taoke.autopay.entity.js2.OrderTaskExecutionDetail;
import com.taoke.autopay.exception.OrderTaskExecutionDetailException;
import org.apache.ibatis.annotations.Param;
import java.util.Date;
import java.util.List;
@@ -129,4 +130,19 @@
     * @throws OrderTaskExecutionDetailException 任务执行详情更新异常
     */
    void reviewFailure(String id, String reason) throws OrderTaskExecutionDetailException;
    /**
     * 获取可执行的任务详情列表
     * @param clientId
     * @return
     */
    List<OrderTaskExecutionDetail> listCanExcuteTaskDetail(Long clientId, int page, int pageSize);
    /**
     * 统计可执行任务详情数量
     * @param clientId
     * @return
     */
    long countCanExcuteTaskDetail(@Param("clientId") Long clientId);
}
src/main/java/com/taoke/autopay/service/js2/OrderTaskService.java
@@ -30,6 +30,14 @@
     * @return 下单任务实体
     */
    OrderTask getOrderTaskById(Long id);
    /**
     * 根据ID批量查询下单任务
     * @param ids
     * @return
     */
    List<OrderTask> getOrderTaskByIds(List<Long> ids);
    
    /**
     * 根据ID更新下单任务(带锁)
src/main/java/com/taoke/autopay/task/OrderTaskDistributeTask.java
New file
@@ -0,0 +1,51 @@
package com.taoke.autopay.task;
import com.taoke.autopay.dao.OrderTaskMapper;
import com.taoke.autopay.entity.js2.OrderTask;
import com.taoke.autopay.service.js2.OrderTaskService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import javax.annotation.Resource;
import java.util.Date;
import java.util.List;
@Configuration
@EnableScheduling
public class OrderTaskDistributeTask {
    @Value("${task-enable}")
    private boolean taskEnable;
    @Resource
    private OrderTaskService orderTaskService;
    /**
     * 为下单任务分配客户端设备
     */
    @Scheduled(cron = "0/5 * * * * ? ")
    private void assignOrderTask() {
        if(!taskEnable){
            return;
        }
        // 对处于分配中的任务进行分配
        try {
            OrderTaskMapper.DaoQuery query=new OrderTaskMapper.DaoQuery();
            query.status = OrderTask.STATUS_ASSIGNING;
            // 对1小时内创建的任务进行分配
            query.minCreateTime=new Date(System.currentTimeMillis() - 1000 * 60 * 60L);
            List<OrderTask> orderTaskList =  orderTaskService.listOrderTasks(query, 1, 20);
            if (orderTaskList != null) {
                for (OrderTask orderTask : orderTaskList) {
                    orderTaskService.assignTask(orderTask.getId());
                }
            }
        } catch (Exception e) {
        }
    }
}
src/main/java/com/taoke/autopay/utils/encrypt/AESUtil.java
New file
@@ -0,0 +1,129 @@
package com.taoke.autopay.utils.encrypt;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.security.SecureRandom;
import java.util.Base64;
/**
 * AES加密解密工具类
 */
public class AESUtil {
    private static final String ALGORITHM = "AES";
    private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";
    /**
     * AES加密
     * @param content 待加密内容
     * @param key 加密密钥
     * @return 加密后的字符串
     */
    public static String encrypt(String content, String key) {
        try {
            // 创建AES密钥
            SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), ALGORITHM);
            // 创建密码器
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            // 初始化为加密模式
            cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
            // 执行加密操作
            byte[] encrypted = cipher.doFinal(content.getBytes("utf-8"));
            // 使用Base64编码返回
            return Base64.getEncoder().encodeToString(encrypted);
        } catch (Exception e) {
            throw new RuntimeException("AES加密失败", e);
        }
    }
    /**
     * AES解密
     * @param content 待解密内容
     * @param key 解密密钥
     * @return 解密后的字符串
     */
    public static String decrypt(String content, String key) {
        try {
            // 使用Base64解码
            byte[] encrypted = Base64.getDecoder().decode(content);
            // 创建AES密钥
            SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(), ALGORITHM);
            // 创建密码器
            Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);
            // 初始化为解密模式
            cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
            // 执行解密操作
            byte[] decrypted = cipher.doFinal(encrypted);
            // 转换为字符串返回
            return new String(decrypted, "utf-8");
        } catch (Exception e) {
            throw new RuntimeException("AES解密失败", e);
        }
    }
    /**
     * 生成AES密钥
     * @return 生成的密钥字符串
     */
    public static String generateKey() {
        try {
            // 创建KeyGenerator对象
            KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
            // 初始化密钥长度为128位
            keyGenerator.init(128);
            // 生成密钥
            SecretKey secretKey = keyGenerator.generateKey();
            // 获取密钥字节数组
            byte[] keyBytes = secretKey.getEncoded();
            // 使用Base64编码返回
            return Base64.getEncoder().encodeToString(keyBytes);
        } catch (Exception e) {
            throw new RuntimeException("生成AES密钥失败", e);
        }
    }
    /**
     * 根据密码生成固定密钥
     * @param password 密码
     * @return 固定密钥
     */
    public static String generateKeyByPassword(String password) {
        try {
            // 创建随机数生成器
            SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
            secureRandom.setSeed(password.getBytes());
            // 创建KeyGenerator对象
            KeyGenerator keyGenerator = KeyGenerator.getInstance(ALGORITHM);
            // 初始化随机数生成器
            keyGenerator.init(128, secureRandom);
            // 生成密钥
            SecretKey secretKey = keyGenerator.generateKey();
            // 获取密钥字节数组
            byte[] keyBytes = secretKey.getEncoded();
            // 使用Base64编码返回
            return Base64.getEncoder().encodeToString(keyBytes);
        } catch (Exception e) {
            throw new RuntimeException("根据密码生成密钥失败", e);
        }
    }
}
src/main/java/com/taoke/autopay/vo/OrderTaskExecutionDetailVO.java
New file
@@ -0,0 +1,36 @@
package com.taoke.autopay.vo;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Tolerate;
@Data
@Builder
public class OrderTaskExecutionDetailVO {
    // 下单
    public final static int EXECUTION_TYPE_ORDER = 1;
    // 确认收货
    public final static int EXECUTION_TYPE_RECEIVE = 2;
    // 评价
    public final static int EXECUTION_TYPE_REVIEW = 3;
    @Tolerate
    public OrderTaskExecutionDetailVO(){
    }
    private String id;
    private String key;
    private Long taskId;
    private Long clientId;
    private Integer executionType;
    private String orderNo;
    private String productName;
    private String shopName;
    private String mobile;
    private String alipayAccount;
    private String alipayPassword;
    private String createTime;
}
src/main/java/com/taoke/autopay/vo/admin/js2/OrderTaskVO.java
New file
@@ -0,0 +1,109 @@
package com.taoke.autopay.vo.admin.js2;
import lombok.Builder;
import lombok.Data;
import lombok.experimental.Tolerate;
/**
 * @author
 * @title: OrderTaskVO
 * @description: 下单任务VO实体类
 * @date 2025/7/31
 */
@Data
@Builder
public class OrderTaskVO {
    @Tolerate
    public OrderTaskVO() {
    }
    /**
     * 主键ID(自增)
     */
    private Long id;
    /**
     * 口令内容
     */
    private String keywordContent;
    /**
     * 口令md5
     */
    private String keywordMd5;
    /**
     * 商品来源
     */
    private Integer productSource;
    /**
     * 商品名称
     */
    private String productName;
    /**
     * 店铺名称
     */
    private String shopName;
    /**
     * 任务所需下单数量
     */
    private Integer requiredOrderCount;
    /**
     * 已经下单数量
     */
    private Integer completedOrderCount;
    /**
     * 已经确认收货的数量
     */
    private Integer receivedOrderCount;
    /**
     * 已经评价的数量
     */
    private Integer reviewedOrderCount;
    /**
     * 确认收货周期(小时)
     */
    private Integer receiveCycleHours;
    /**
     * 下单开始时间
     */
    private String orderStartTime;
    /**
     * 下单结束时间
     */
    private String orderEndTime;
    /**
     * 创建时间
     */
    private String createTime;
    /**
     * 更新时间
     */
    private String updateTime;
    /**
     * 预估确认收货时间
     */
    private String estimatedReceiveTime;
    /**
     * 状态(int型)
     */
    private Integer status;
    /**
     * 状态简介
     */
    private String statusDescription;
}
src/main/resources/mapper/OrderTaskExecutionDetailMapper.xml
@@ -26,7 +26,7 @@
    </resultMap>
    <sql id="Base_Column_List">id,task_id,client_id,execution_status,status_description,execution_time,order_no,product_name,shop_name,coupon_code,order_time,receive_time,review_time,create_time,update_time</sql>
    <sql id="Base_Column_List">id,task_id,client_id,execution_status,status_description,order_no,product_name,shop_name,coupon_code,order_time,receive_time,review_time,create_time,update_time</sql>
    <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.String">select
        <include refid="Base_Column_List"/>
        from table_order_task_execution_detail where id = #{id,jdbcType=VARCHAR}
@@ -77,6 +77,36 @@
        <include refid="listWhereSQL"/>
    </select>
    
    <select id="listCanExcuteTaskDetail" resultMap="BaseResultMap" >
select
        <include refid="Base_Column_List"/>
        from `table_order_task_execution_detail` d LEFT JOIN `table_order_task` t ON t.`id`=d.`task_id`
        WHERE
        client_id = #{clientId}
        AND
        (
        (d.`execution_status`=0 AND d.`create_time` > #{minCreateTime})
        OR (d.`execution_status`=1 AND  UNIX_TIMESTAMP(d.`order_time`) + t.receive_cycle_hours*60*60 < UNIX_TIMESTAMP(NOW()))
        OR (d.`execution_status`=2)
        ) order by d.create_time limit #{start},#{count}
    </select>
    <select id="countCanExcuteTaskDetail" resultType="java.lang.Long">select count(*) from
        `table_order_task_execution_detail` d LEFT JOIN `table_order_task` t ON t.`id`=d.`task_id`
        WHERE
        client_id = #{clientId}
        AND
        (
        (d.`execution_status`=0 AND d.`create_time` > #{minCreateTime})
        OR (d.`execution_status`=1 AND  UNIX_TIMESTAMP(d.`order_time`) + t.receive_cycle_hours*60*60 < UNIX_TIMESTAMP(NOW()))
        OR (d.`execution_status`=2)
        )
    </select>
    <select id="statisticClientIdsCountByStatus" resultMap="ClientCountResultMap">
        SELECT client_id as clientId,count(*) as count
        FROM table_order_task_execution_detail where
@@ -90,7 +120,7 @@
    <delete id="deleteByPrimaryKey"
            parameterType="java.lang.String">delete from table_order_task_execution_detail where id = #{id,jdbcType=VARCHAR}</delete>
    <insert id="insert" parameterType="com.taoke.autopay.entity.js2.OrderTaskExecutionDetail" useGeneratedKeys="true"
            keyProperty="id">insert into table_order_task_execution_detail (id,task_id,client_id,execution_status,status_description,execution_time,order_no,product_name,shop_name,coupon_code,order_time,receive_time,review_time,create_time,update_time) values (#{id,jdbcType=VARCHAR},#{taskId,jdbcType=BIGINT},#{clientId,jdbcType=BIGINT},#{executionStatus,jdbcType=INTEGER},#{statusDescription,jdbcType=VARCHAR},#{executionTime,jdbcType=TIMESTAMP},#{orderNo,jdbcType=VARCHAR},#{productName,jdbcType=VARCHAR},#{shopName,jdbcType=VARCHAR},#{couponCode,jdbcType=VARCHAR},#{orderTime,jdbcType=TIMESTAMP},#{receiveTime,jdbcType=TIMESTAMP},#{reviewTime,jdbcType=TIMESTAMP},#{createTime,jdbcType=TIMESTAMP},#{updateTime,jdbcType=TIMESTAMP})</insert>
            keyProperty="id">insert into table_order_task_execution_detail (id,task_id,client_id,execution_status,status_description,order_no,product_name,shop_name,coupon_code,order_time,receive_time,review_time,create_time,update_time) values (#{id,jdbcType=VARCHAR},#{taskId,jdbcType=BIGINT},#{clientId,jdbcType=BIGINT},#{executionStatus,jdbcType=INTEGER},#{statusDescription,jdbcType=VARCHAR},#{orderNo,jdbcType=VARCHAR},#{productName,jdbcType=VARCHAR},#{shopName,jdbcType=VARCHAR},#{couponCode,jdbcType=VARCHAR},#{orderTime,jdbcType=TIMESTAMP},#{receiveTime,jdbcType=TIMESTAMP},#{reviewTime,jdbcType=TIMESTAMP},#{createTime,jdbcType=TIMESTAMP},#{updateTime,jdbcType=TIMESTAMP})</insert>
    <insert id="insertSelective" parameterType="com.taoke.autopay.entity.js2.OrderTaskExecutionDetail" useGeneratedKeys="true"
            keyProperty="id">insert into table_order_task_execution_detail
        <trim prefix="(" suffix=")" suffixOverrides=",">
@@ -128,7 +158,7 @@
        </trim>
    </insert>
    <update id="updateByPrimaryKey"
            parameterType="com.taoke.autopay.entity.js2.OrderTaskExecutionDetail">update table_order_task_execution_detail set task_id = #{taskId,jdbcType=BIGINT},client_id = #{clientId,jdbcType=BIGINT},execution_status = #{executionStatus,jdbcType=INTEGER},status_description = #{statusDescription,jdbcType=VARCHAR},execution_time = #{executionTime,jdbcType=TIMESTAMP},order_no = #{orderNo,jdbcType=VARCHAR},product_name = #{productName,jdbcType=VARCHAR},shop_name = #{shopName,jdbcType=VARCHAR},coupon_code = #{couponCode,jdbcType=VARCHAR},order_time = #{orderTime,jdbcType=TIMESTAMP},receive_time = #{receiveTime,jdbcType=TIMESTAMP},review_time = #{reviewTime,jdbcType=TIMESTAMP},create_time = #{createTime,jdbcType=TIMESTAMP},update_time = #{updateTime,jdbcType=TIMESTAMP} where id = #{id,jdbcType=VARCHAR}</update>
            parameterType="com.taoke.autopay.entity.js2.OrderTaskExecutionDetail">update table_order_task_execution_detail set task_id = #{taskId,jdbcType=BIGINT},client_id = #{clientId,jdbcType=BIGINT},execution_status = #{executionStatus,jdbcType=INTEGER},status_description = #{statusDescription,jdbcType=VARCHAR},order_no = #{orderNo,jdbcType=VARCHAR},product_name = #{productName,jdbcType=VARCHAR},shop_name = #{shopName,jdbcType=VARCHAR},coupon_code = #{couponCode,jdbcType=VARCHAR},order_time = #{orderTime,jdbcType=TIMESTAMP},receive_time = #{receiveTime,jdbcType=TIMESTAMP},review_time = #{reviewTime,jdbcType=TIMESTAMP},create_time = #{createTime,jdbcType=TIMESTAMP},update_time = #{updateTime,jdbcType=TIMESTAMP} where id = #{id,jdbcType=VARCHAR}</update>
    <update id="updateByPrimaryKeySelective" parameterType="com.taoke.autopay.entity.js2.OrderTaskExecutionDetail">update table_order_task_execution_detail
        <set>
            <if test="taskId != null">task_id=#{taskId,jdbcType=BIGINT},</if>
src/main/resources/static/admin/index.html
@@ -78,6 +78,7 @@
                    <li class="layui-nav-item">
                        <a href="javascript:;"><i class="iconfont"></i>JS2系统</a>
                        <dl class="layui-nav-child">
                            <dd><a href="javascript:;" data-url="order-task-list.html" data-id='71' data-text="下单任务列表"><span class="l-line"></span>下单任务列表</a></dd>
                            <dd><a href="javascript:;" data-url="order-client-list.html" data-id='7' data-text="下单设备列表"><span class="l-line"></span>下单设备列表</a></dd>
                        </dl>
                    </li>
src/main/resources/static/admin/order-client-list.html
@@ -141,6 +141,32 @@
                });
            }
            function updatePwd(id) {
                // 修改密码
                layer.prompt({
                    formType: 2,
                    value: '',
                    title: '请输入密码',
                    area: ['200px', '50px'] //自定义文本域宽高
                }, function(value, index, elem){
                    if(value.length < 6){
                        layer.msg("密码不能少于6位数");
                        return;
                    }
                    $.post("/admin/api/clientinfo/js2/setpwd", {"id":id,"pwd":value}, function(response) {
                        if (response.code == 0) {
                            layer.close(index);
                            layer.msg("密码修改成功");
                        } else {
                            layer.msg(response.msg);
                        }
                    }, 'json').fail(function(jqXHR, textStatus, errorThrown) {
                        layer.msg("网络请求失败");
                    });
                });
            }
            layui.use(['form', 'jquery', 'layer', 'table'], function() {
                var table = layui.table;
                var form = layui.form;
@@ -221,11 +247,11 @@
                            {
                                field: '',
                                title: '操作',
                                width: 120,
                                width: 200,
                                fixed: 'right',
                                align: 'center',
                                templet: function(d) {
                                    return '<div><a href="javascript:void(0)" onclick="updateClient(' + d["client.id"] + ')" class="layui-table-link">修改</a></div>';
                                    return '<div><a href="javascript:void" onclick="updatePwd(' + d["client.id"] + ')" class="layui-table-link">设置密码</a> &nbsp;&nbsp;  <a href="javascript:void(0)" onclick="updateClient(' + d["client.id"] + ')" class="layui-table-link">修改</a></div>';
                                }
                            }
                        ]
src/main/resources/static/admin/order-client-update.html
@@ -72,7 +72,7 @@
    <div class="layui-form-item">
        <label class="layui-form-label">支付宝密码:</label>
        <div class="layui-input-inline">
            <input type="text" name="alipayPassword" required lay-verify="" placeholder="无更改无需填写" autocomplete="off" class="layui-input">
            <input type="password" name="alipayPassword" required lay-verify="" placeholder="无更改无需填写" autocomplete="off" class="layui-input">
        </div>
    </div>
src/main/resources/static/admin/order-task-assigned-clients.html
New file
@@ -0,0 +1,92 @@
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no"/>
    <title>已分配设备列表</title>
    <link rel="stylesheet" type="text/css" href="/admin/layui/css/layui.css"/>
    <link rel="stylesheet" type="text/css" href="/admin/css/admin.css"/>
    <script src="js/http_api.js"></script>
    <style>
        .layui-table-cell {
            height: 50px;
            white-space: normal;
            max-height: 50px;
        }
        /* 添加横向滚动样式 */
        .scroll-x {
            overflow: hidden !important;
            overflow-x: auto !important;
            white-space: nowrap;
            outline: none;
        }
    </style>
</head>
<body>
<div class="page-content-wrap">
    <div class="scroll-x">
        <table class="layui-table" lay-even lay-skin="nob" id="clientsTable" style="height: 100%"></table>
    </div>
</div>
<script src="/admin/layui/layui.js" type="text/javascript" charset="utf-8"></script>
<script src="/admin/js/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script>
    layui.use(['table', 'layer'], function () {
        var table = layui.table;
        var layer = layui.layer;
        var urlParams = new URLSearchParams(window.location.search);
        var taskId = urlParams.get('taskId');
        // 渲染表格
        var tableIns = table.render({
            elem: '#clientsTable',
            url: '/admin/api/ordertask/js2/assignedClients',
            where: {
                taskId: taskId
            },
            parseData: function(res) {
                return {
                    "code": res.code,
                    "msg": res.msg,
                    "count": res.data.count,
                    "data": res.data.list
                }
            },
            page: true,
            cols: [[
                {field: 'clientId', title: '客户端ID', width: 100},
                {field: 'orderNo', title: '订单号', width: 150},
                {field: 'productName', title: '商品名称', width: 150},
                {field: 'shopName', title: '店铺名称', width: 120},
                {field: 'couponCode', title: '券码', width: 120},
                {
                    field: 'executionStatus', title: '执行状态', width: 120, templet: function (d) {
                        switch (d.executionStatus) {
                            case 0: return '<span style="color: #999">未下单</span>';
                            case 1: return '<span style="color: #009900">已下单</span>';
                            case -1: return '<span style="color: #ff0000">下单失败</span>';
                            case 2: return '<span style="color: #009900">确认收货成功</span>';
                            case -2: return '<span style="color: #ff0000">确认收货失败</span>';
                            case 3: return '<span style="color: #009900">评价成功</span>';
                            case -3: return '<span style="color: #ff0000">评价失败</span>';
                            default: return '未知状态';
                        }
                    }
                },
                {field: 'statusDescription', title: '状态说明', width: 150},
                {field: 'orderTime', title: '下单时间', width: 160},
                {field: 'receiveTime', title: '收货时间', width: 160},
                {field: 'reviewTime', title: '评价时间', width: 160},
                {field: 'createTime', title: '创建时间', width: 160},
                {field: 'updateTime', title: '更新时间', width: 160}
            ]]
        });
    });
</script>
</body>
</html>
src/main/resources/static/admin/order-task-list.html
New file
@@ -0,0 +1,427 @@
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="renderer" content="webkit">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport"
        content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/>
    <title>下单任务列表</title>
    <link rel="stylesheet" type="text/css" href="/admin/layui/css/layui.css"/>
    <link rel="stylesheet" type="text/css" href="/admin/css/admin.css"/>
    <style>
        .layui-table-cell {
            height: 50px;
            white-space: normal;
            max-height: 50px;
        }
        #add_task {
            padding: 10px;
        }
    </style>
</head>
<body>
<div class="page-content-wrap">
    <form class="layui-form" action="" lay-filter='search'>
        <div class="layui-form-item">
            <div class="layui-inline">
                <input type="text" name="keyword" id="keyword" placeholder="按口令内容搜索" autocomplete="off"
                       class="layui-input">
            </div>
            <div class="layui-inline">
                <input type="text" name="startDate" id="startDate" placeholder="开始日期" autocomplete="off"
                       class="layui-input" readonly>
            </div>
            <div class="layui-inline">
                <input type="text" name="endDate" id="endDate" placeholder="结束日期" autocomplete="off"
                       class="layui-input" readonly>
            </div>
            <div class="layui-inline">
                <select name="status" id="status">
                    <option value="">全部状态</option>
                    <option value="0">未分配</option>
                    <option value="1">分配中</option>
                    <option value="2">已分配</option>
                </select>
            </div>
            <div class="layui-inline">
                <button class="layui-btn layui-btn-normal" lay-submit lay-filter="search" id="search"><i
                    class="layui-icon layui-icon-search"></i>搜索
                </button>
                <a href="javascript:void();" class="layui-btn layui-btn-warm" onclick="showCreateTask()"><i
                    class="layui-icon layui-icon-add-circle"></i> 添加任务</a>
            </div>
        </div>
    </form>
    <div>
        <table class="layui-table" lay-even lay-skin="nob" id="table">
        </table>
    </div>
</div>
<div id="add_task" style="display: none; padding: 20px;">
    <form class="layui-form" action="" lay-filter='add-task'>
        <div class="layui-form-item">
            <label class="layui-form-label">口令内容</label>
            <div class="layui-input-block">
                <textarea type="text" name="keywordContent" rows="5" required lay-verify="required" placeholder="请输入口令内容"
                          autocomplete="off" class="layui-textarea"></textarea>
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">商品来源</label>
            <div class="layui-input-block">
                <select name="productSource" required lay-verify="required">
                    <option value="">请选择商品来源</option>
                    <option value="1">抖音</option>
                    <option value="2">快手</option>
                </select>
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">商品名称</label>
            <div class="layui-input-block">
                <textarea type="text" name="productName" rows="3" required lay-verify="required" placeholder="请输入商品名称"
                          autocomplete="off" class="layui-textarea"></textarea>
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">店铺名称</label>
            <div class="layui-input-block">
                <input type="text" name="shopName" placeholder="请输入店铺名称" autocomplete="off" class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">所需下单数量</label>
            <div class="layui-input-block">
                <input type="number" name="requiredOrderCount" required lay-verify="required|number" min="1"
                       placeholder="请输入所需下单数量" autocomplete="off" class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">收货周期(小时)</label>
            <div class="layui-input-block">
                <input type="number" name="receiveCycleHours" required lay-verify="required|number" min="1"
                       placeholder="请输入收货周期(小时)" autocomplete="off" class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">下单开始时间</label>
            <div class="layui-input-block">
                <input type="text" name="orderStartTime" id="orderStartTime" required lay-verify="required"
                       placeholder="请选择下单开始时间" autocomplete="off" class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <label class="layui-form-label">下单结束时间</label>
            <div class="layui-input-block">
                <input type="text" name="orderEndTime" id="orderEndTime" required lay-verify="required"
                       placeholder="请选择下单结束时间" autocomplete="off" class="layui-input">
            </div>
        </div>
        <div class="layui-form-item">
            <div class="layui-input-block">
                <button class="layui-btn" lay-submit lay-filter="add-task">立即创建</button>
                <button type="reset" class="layui-btn layui-btn-primary">重置</button>
            </div>
        </div>
    </form>
</div>
<script src="/admin/layui/layui.js" type="text/javascript" charset="utf-8"></script>
<script src="/admin/js/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script>
    // 修改任务信息
    function updateTask(id) {
        var layerIndex = layer.open({
            title: "修改下单任务信息",
            type: 2,
            area: ['600px', '600px'],
            shade: 0.3,
            shadeClose: false,
            resize: false,
            content: '/admin/order-task-update.html?id=' + id,
            btn: ['确定', '取消'],
            yes: function (index) {
                //submit方法为弹框内容中的方法
                window["layui-layer-iframe" + index].submit(function (res) {
                    let fdata = {
                        id: id
                    };
                    for (let key in res) {
                        if (key.indexOf(".") >= 0) {
                            let k1 = key.split(".")[0];
                            let k2 = key.split(".")[1];
                            if (k1 in fdata) {
                            } else {
                                fdata[k1] = {};
                            }
                            fdata[k1][k2] = res[key];
                        } else {
                            fdata[key] = res[key]
                        }
                    }
                    for (let key in fdata) {
                        if (typeof (fdata[key]) == 'object') {
                            fdata[key] = JSON.stringify(fdata[key]);
                        }
                    }
                    try {
                        var loadIndex = layer.load(1);
                        $.post("/admin/api/ordertask/js2/update", fdata, function (response) {
                            layer.close(loadIndex);
                            if (response.code == 0) {
                                layer.close(layerIndex);
                                layer.msg("更改成功");
                                // 重新加载表格
                                tableIns.reload();
                            } else {
                                layer.msg(response.msg);
                            }
                        }, 'json').fail(function (jqXHR, textStatus, errorThrown) {
                            layer.close(loadIndex);
                            layer.msg("网络请求失败");
                        });
                    } catch (e) {
                        console.log(e);
                    }
                });
            },
            cancel: function () {
            }
        });
    }
    // 删除任务
    function deleteTask(id) {
        layer.confirm('确定要删除这个任务吗?', {
            btn: ['确定', '取消']
        }, function (index) {
            $.post("/admin/api/ordertask/js2/delete", {"id": id}, function (response) {
                if (response.code == 0) {
                    layer.msg("删除成功");
                    // 重新加载表格
                    tableIns.reload();
                } else {
                    layer.msg(response.msg);
                }
            }, 'json').fail(function (jqXHR, textStatus, errorThrown) {
                layer.msg("网络请求失败");
            });
            layer.close(index);
        });
    }
    // 分配任务
    function assignTask(id) {
        layer.confirm('确定要分配这个任务吗?', {
            btn: ['确定', '取消']
        }, function (index) {
            $.post("/admin/api/ordertask/js2/assign", {"taskId": id}, function (response) {
                if (response.code == 0) {
                    layer.msg("分配成功");
                    // 重新加载表格
                    tableIns.reload();
                } else {
                    layer.msg(response.msg);
                }
            }, 'json').fail(function (jqXHR, textStatus, errorThrown) {
                layer.msg("网络请求失败");
            });
            layer.close(index);
        });
    }
    // 查看已分配设备
    function viewAssignedClients(id) {
        var layerIndex = layer.open({
            title: "已分配设备列表",
            type: 2,
            area: ['1200px', '600px'],
            shade: 0.3,
            shadeClose: false,
            resize: false,
            content: '/admin/order-task-assigned-clients.html?taskId=' + id
        });
    }
    function formatDateTime(date, format) {
        var now = date;
        var year = now.getFullYear();
        var month = (now.getMonth() + 1).toString().padStart(2, '0');
        var day = now.getDate().toString().padStart(2, '0');
        var hours = now.getHours().toString().padStart(2, '0');
        var minutes = now.getMinutes().toString().padStart(2, '0');
        var seconds = now.getSeconds().toString().padStart(2, '0');
        // 默认格式
        if (!format) {
            return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds;
        }
        // 自定义格式支持
        return format
            .replace('YYYY', year)
            .replace('MM', month)
            .replace('DD', day)
            .replace('HH', hours)
            .replace('mm', minutes)
            .replace('ss', seconds);
    }
    function showCreateTask() {
        // 开始与结束时间赋当前时间
        // 创建任务
        $("input[name=orderStartTime]").val(formatDateTime(new Date()));
        $("input[name=orderEndTime]").val(formatDateTime(new Date(new Date().getTime()+1000*60*60*24)));
        var index = layer.open({
            type: 1,
            title: "创建下单任务",
            content: $("#add_task"),
            area: ['600px', '700px'],
            end: function (e) {
                $("#add_task").css("display", "none");
            }
        });
    }
    layui.use(['form', 'jquery', 'layer', 'table', 'laydate'], function () {
        var table = layui.table;
        var form = layui.form;
        var $ = layui.jquery;
        var laydate = layui.laydate;
        // 日期控件初始化
        laydate.render({
            elem: '#startDate',
            theme: '#448aff'
        });
        laydate.render({
            elem: '#endDate',
            theme: '#448aff'
        });
        laydate.render({
            elem: '#orderStartTime',
            type: 'datetime',
            theme: '#448aff'
        });
        laydate.render({
            elem: '#orderEndTime',
            type: 'datetime',
            theme: '#448aff'
        });
        let table_option = {
            elem: '#table',
            url: '/admin/api/ordertask/js2/list',
            where: {
                'keyword': $("#keyword").val(),
                'startDate': $("#startDate").val(),
                'endDate': $("#endDate").val(),
                'status': $("#status").val()
            },
            parseData: function(res) { //res 即为原始返回的数据
                return {
                    "code": res.code, //解析接口状态
                    "msg": res.msg, //解析提示文本
                    "count": res.data.count, //解析数据长度
                    "data": res.data.list //解析数据列表
                }
            },
            page: true,
            cols: [[
                {field: 'id', title: '任务ID', width: 80, fixed: 'left'},
                {field: 'keywordContent', title: '口令内容', width: 150},
                {
                    field: 'productSource', title: '商品来源', width: 90, templet: function (d) {
                        if (d.productSource == 1) {
                            return '抖音';
                        } else if (d.productSource == 2) {
                            return '快手';
                        }
                        return '未知';
                    }
                },
                {field: 'productName', title: '商品名称', width: 150},
                {field: 'shopName', title: '店铺名称', width: 120},
                {field: 'requiredOrderCount', title: '需下单数', width: 90},
                {field: 'completedOrderCount', title: '已下单数', width: 90},
                {field: 'receivedOrderCount', title: '已收货数', width: 90},
                {field: 'reviewedOrderCount', title: '已评价数', width: 90},
                {field: 'receiveCycleHours', title: '收货周期(小时)', width: 120},
                {field: 'orderStartTime', title: '下单开始时间', width: 160},
                {field: 'orderEndTime', title: '下单结束时间', width: 160},
                {field: 'createTime', title: '创建时间', width: 160},
                {
                    field: 'status', title: '状态', width: 100, templet: function (d) {
                        if (d.status == 0) {
                            return '<span style="color: #999">未分配</span>';
                        } else if (d.status == 1) {
                            return '<span style="color: #ff9900">分配中</span>';
                        } else if (d.status == 2) {
                            return '<span style="color: #009900">已分配</span>';
                        }
                        return '未知';
                    }
                },
                {
                    field: '', title: '操作', width: 200, align: 'center', fixed:"right", templet: function (d) {
                        let html = '<div>';
                        html += '<a href="javascript:void(0)" onclick="updateTask(' + d.id + ')" class="layui-table-link">修改</a> &nbsp;&nbsp;';
                        html += '<a href="javascript:void(0)" onclick="deleteTask(' + d.id + ')" class="layui-table-link">删除</a> &nbsp;&nbsp;';
                        if (d.status == 0) {
                            html += '<a href="javascript:void(0)" onclick="assignTask(' + d.id + ')" class="layui-table-link">分配</a>';
                        }else{
                            html += '<a href="javascript:void(0)" onclick="viewAssignedClients(' + d.id + ')" class="layui-table-link">已分配设备</a>';
                        }
                        html += '</div>';
                        return html;
                    }
                }
            ]]
        };
        //第一个实例
        let tableIns = table.render(table_option);
        //监听提交
        form.on('submit(search)', function (data) {
            tableIns.reload({
                where: data.field,
                page: {
                    curr: 1 //重新从第 1 页开始
                }
            });
            return false;
        });
        form.on('submit(add-task)', function (data) {
            $.post("/admin/api/ordertask/js2/add", data.field, function (response) {
                if (response.code == 0) {
                    layer.msg("添加成功");
                    // 关闭弹窗
                    layer.closeAll('page');
                    // 重新加载表格
                    tableIns.reload();
                } else {
                    layer.msg(response.msg);
                }
            }, 'json').fail(function (jqXHR, textStatus, errorThrown) {
                layer.msg("网络请求失败");
            });
            return false;
        });
    });
</script>
</body>
</html>
src/main/resources/static/admin/order-task-update.html
New file
@@ -0,0 +1,164 @@
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
        content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"/>
    <title>修改下单任务</title>
    <link rel="stylesheet" type="text/css" href="layui/css/layui.css"/>
    <style>
        body {
            padding: 10px;
        }
        #sure {
            visibility: hidden;
        }
        .layui-form-label {
            width: 150px;
        }
        .layui-input-block {
            margin-left: 180px;
            max-width: 400px;
        }
    </style>
</head>
<body>
<form class="layui-form" lay-filter="edit">
    <input type="hidden" name="id"/>
    <div class="layui-form-item">
        <label class="layui-form-label">口令内容:</label>
        <div class="layui-input-block">
            <textarea  name="keywordContent" rows="5" required lay-verify="required" placeholder="口令内容"
                       autocomplete="off" class="layui-textarea"></textarea>
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">商品来源:</label>
        <div class="layui-input-block">
            <select name="productSource" required lay-verify="required">
                <option value="">请选择商品来源</option>
                <option value="1">抖音</option>
                <option value="2">快手</option>
            </select>
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">商品名称:</label>
        <div class="layui-input-block">
            <textarea name="productName" rows="3" required lay-verify="required" placeholder="商品名称" autocomplete="off"
                      class="layui-textarea"></textarea>
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">店铺名称:</label>
        <div class="layui-input-block">
            <input type="text" name="shopName" placeholder="店铺名称" autocomplete="off" class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">所需下单数量:</label>
        <div class="layui-input-block">
            <input type="number" name="requiredOrderCount" required lay-verify="required|number" min="1"
                   placeholder="所需下单数量" autocomplete="off" class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">收货周期(小时):</label>
        <div class="layui-input-block">
            <input type="number" name="receiveCycleHours" required lay-verify="required|number" min="1"
                   placeholder="收货周期(小时)" autocomplete="off" class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">下单开始时间:</label>
        <div class="layui-input-block">
            <input type="text" name="orderStartTime" id="orderStartTime" required lay-verify="required"
                   placeholder="下单开始时间" autocomplete="off" class="layui-input">
        </div>
    </div>
    <div class="layui-form-item">
        <label class="layui-form-label">下单结束时间:</label>
        <div class="layui-input-block">
            <input type="text" name="orderEndTime" id="orderEndTime" required lay-verify="required"
                   placeholder="下单结束时间" autocomplete="off" class="layui-input">
        </div>
    </div>
    <div class="layui-input-block">
        <button class="layui-btn layui-btn-normal" lay-submit lay-filter="sure" id="sure">确定</button>
    </div>
</form>
<script src="layui/layui.js" type="text/javascript" charset="utf-8"></script>
<script src="js/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script src="js/http_api.js"></script>
<script>
    var listener;
    function submit(callback) {
        //暂存回调方法
        listener = callback;
        //表单提交按钮
        $("#sure").click();
    }
    layui.use(['form', 'layedit', 'laydate'], function () {
        var form = layui.form,
            layer = layui.layer,
            laydate = layui.laydate;
        //自定义验证规则
        form.verify({
            num: [/^\d+$/, "必须为正整数"],
            money: [/^\d+(\.\d{1,2})?$/, "金额最多保留2位小数"]
        });
        // 日期控件初始化
        laydate.render({
            elem: '#orderStartTime',
            type: 'datetime',
            theme: '#448aff'
        });
        laydate.render({
            elem: '#orderEndTime',
            type: 'datetime',
            theme: '#448aff'
        });
        var id = http_util.getQueryString("id");
        // 获取值
        $.post("/admin/api/ordertask/js2/get", {
            "id": id
        }, function (response) {
            if (response.code == 0) {
                form.val("edit", response.data);
            } else {
                layer.msg(response.msg);
            }
        }, 'json').fail(function (jqXHR, textStatus, errorThrown) {
            layer.msg("网络请求失败");
        });
        //监听提交
        form.on('submit(sure)', function (data) {
            listener(data.field);
            return false;
        });
    });
</script>
</body>
</html>