202508091201

This commit is contained in:
张潘 2025-08-10 20:28:56 +08:00
parent 75b6f9ff71
commit 74fc92b222
20 changed files with 1433 additions and 503 deletions

View File

@ -1,4 +1,5 @@
{
"java.compile.nullAnalysis.mode": "automatic",
"java.configuration.updateBuildConfiguration": "interactive"
"java.configuration.updateBuildConfiguration": "interactive",
"java.debug.settings.onBuildFailureProceed": true
}

View File

@ -307,4 +307,4 @@
</pluginRepository>
</pluginRepositories>
</project>
</project>

View File

@ -17,6 +17,12 @@
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- spring-boot-devtools -->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -16,7 +16,7 @@ import java.util.Map;
/**
* 微信支付API控制器无需认证
*
*
* @author Mr. Zhang Pan
* @version 1.0
* @date 2025-01-17
@ -24,61 +24,61 @@ import java.util.Map;
@RestController
@RequestMapping("/api/wechat/pay/v3")
public class ApiWechatPayController extends BaseController {
//
// private static final Logger log = LoggerFactory.getLogger(ApiWechatPayController.class);
//
// @Autowired
// private WechatPayV3Util wechatPayV3Util;
//
// @Autowired
// private WechatCertificateService wechatCertificateService;
//
// /**
// * 快速提现简化参数简化参数
// */
// @PostMapping("/quick-withdraw")
// @Log(title = "微信支付V3快速提现", businessType = BusinessType.OTHER)
// public AjaxResult quickWithdraw(@RequestParam String openid,
// @RequestParam BigDecimal amount,
// @RequestParam(required = false) String desc) {
// try {
// log.info("⚡ API快速提现请求 - 用户户: {}, 金额: {}元", openid.substring(0, 6) + "****", amount);
//
// Map<String, Object> result = wechatPayV3Util.quickWithdraw(openid, amount, desc);
//
// if ((Boolean) result.get("success")) {
// return AjaxResult.success("快速提现成功", result.get("data"));
// } else {
// return AjaxResult.error(result.get("message").toString());
// }
// } catch (Exception e) {
// log.error("❌ API快速提现异常: {}", e.getMessage(), e);
// return AjaxResult.error("快速提现失败: " + e.getMessage());
// }
// }
private static final Logger log = LoggerFactory.getLogger(ApiWechatPayController.class);
@Autowired
private WechatPayV3Util wechatPayV3Util;
@Autowired
private WechatCertificateService wechatCertificateService;
/**
* 快速提现简化参数
*/
@PostMapping("/quick-withdraw")
@Log(title = "微信支付V3快速提现", businessType = BusinessType.OTHER)
public AjaxResult quickWithdraw(@RequestParam String openid,
@RequestParam BigDecimal amount,
@RequestParam(required = false) String desc) {
try {
log.info("⚡ API快速提现请求 - 用户: {}, 金额: {}元", openid.substring(0, 6) + "****", amount);
Map<String, Object> result = wechatPayV3Util.quickWithdraw(openid, amount, desc);
if ((Boolean) result.get("success")) {
return AjaxResult.success("快速提现成功", result.get("data"));
} else {
return AjaxResult.error(result.get("message").toString());
}
} catch (Exception e) {
log.error("❌ API快速提现异常: {}", e.getMessage(), e);
return AjaxResult.error("快速提现失败: " + e.getMessage());
}
}
/**
* 测试私钥加载
*/
@GetMapping("/test-key")
@Log(title = "微信支付V3测试私钥", businessType = BusinessType.OTHER)
public AjaxResult testKey() {
try {
log.info("🔐 API测试私钥加载");
// 通过反射调用私有方法测试私钥加载
java.lang.reflect.Method method = WechatPayV3Util.class.getDeclaredMethod("getPrivateKey");
method.setAccessible(true);
Object privateKey = method.invoke(wechatPayV3Util);
if (privateKey != null) {
return AjaxResult.success("私钥加载成功", "私钥算法: " + privateKey.getClass().getSimpleName());
} else {
return AjaxResult.error("私钥加载失败");
}
} catch (Exception e) {
log.error("❌ API测试私钥异常: {}", e.getMessage(), e);
return AjaxResult.error("测试私钥失败: " + e.getMessage());
}
}
}
// /**
// * 测试私钥加载
// */
// @GetMapping("/test-key")
// @Log(title = "微信支付V3测试私钥", businessType = BusinessType.OTHER)
// public AjaxResult testKey() {
// try {
// log.info("🔐 API测试私钥加载");
//
// // 通过反射调用私有方法测试私钥加载
// java.lang.reflect.Method method = WechatPayV3Util.class.getDeclaredMethod("getPrivateKey");
// method.setAccessible(true);
// Object privateKey = method.invoke(wechatPayV3Util);
//
// if (privateKey != null) {
// return AjaxResult.success("私钥加载成功", "私钥算法: " + privateKey.getClass().getSimpleName());
// } else {
// return AjaxResult.error("私钥加载失败");
// }
// } catch (Exception e) {
// log.error("❌ API测试私钥异常: {}", e.getMessage(), e);
// return AjaxResult.error("测试私钥失败: " + e.getMessage());
// }
// }
}

View File

@ -24,7 +24,7 @@ import java.util.Map;
/**
* 会员管理控制器
*
*
* 提供会员相关的API接口包括
* 1. 会员充值功能
* 2. 充值记录管理
@ -45,46 +45,46 @@ import java.util.Map;
public class AppleMemberController extends BaseController {
// ==================== 依赖注入 ====================
@Autowired
private IUsersService usersService;
@Autowired
private IUserMemberRechargeLogService userMemberRechargeLogService;
@Autowired
private IUserMemberRechargeProgramService userMemberRechargeProgramService;
@Autowired
private ISiteConfigService siteConfigService;
@Autowired
private IUserMemnerConsumptionLogService userMemnerConsumptionLogService;
@Autowired
private IOrderService orderService;
@Autowired
private IOrderCommentService orderCommentService;
@Autowired
private IOrderLogService orderLogService;
@Autowired
private IServiceCateService serviceCateService;
@Autowired
private IUserSecondaryCardService userSecondaryCardService;
@Autowired
private IServiceGoodsService serviceGoodsService;
@Autowired
private IUserDemandQuotationService userDemandQuotationService;
@Autowired
private IUserGroupBuyingService userGroupBuyingService;
@Autowired
private IUserBenefitPointsService userBenefitPointsService;
@ -93,19 +93,19 @@ public class AppleMemberController extends BaseController {
@Autowired
private WechatPayUtil wechatPayUtil;
private com.ruoyi.system.ControllerUtil.WechatPayUtil wechatPayUtil;
// ==================== 会员充值相关接口 ====================
/**
* 会员充值支付接口
*
*
* 支持两种充值方式
* 1. 通过充值套餐ID充值优先级更高
* 2. 通过自定义金额充值
*
*
* 业务逻辑
* - 如果id和money都有值优先使用id充值套餐
* - 如果只有money有值使用自定义金额充值
@ -121,34 +121,34 @@ public class AppleMemberController extends BaseController {
// 1. 验证用户登录状态
//Map<String, Object> params = new HashMap<>();
//params.put("money", 300); // 测试用固定金额实际应从请求参数获取
String token = request.getHeader("token");
Map<String, Object> userValidation = AppletLoginUtil.validateUserToken(token, usersService);
if (!(Boolean) userValidation.get("valid")) {
return AppletControllerUtil.appletWarning("用户未登录或token无效");
}
// 2. 获取用户信息
Users user = (Users) userValidation.get("user");
if (user == null) {
return AppletControllerUtil.appletWarning("用户信息获取失败");
}
// 3. 参数验证逻辑id和money必须有一个有值如果都有值则优先使用id
Object idObj = params.get("id");
Object moneyObj = params.get("money");
boolean idEmpty = (idObj == null || idObj.toString().trim().isEmpty());
boolean moneyEmpty = (moneyObj == null || moneyObj.toString().trim().isEmpty());
if (idEmpty && moneyEmpty) {
return AppletControllerUtil.appletWarning("参数不能为空,类目和金额必须有一个有值");
}
// 如果id和money都有值优先走id逻辑money置空
if (!idEmpty && !moneyEmpty) {
moneyObj = null;
}
// 4. 创建充值记录
String money = "";
UserMemberRechargeLog userMemberRechargeLog = new UserMemberRechargeLog();
@ -161,16 +161,16 @@ public class AppleMemberController extends BaseController {
// 5a. 通过充值套餐ID充值
UserMemberRechargeProgram userMemberRechargeProgram = userMemberRechargeProgramService
.selectUserMemberRechargeProgramById(Integer.valueOf(idObj.toString()));
if (userMemberRechargeProgram != null) {
userMemberRechargeLog.setInmoney(userMemberRechargeProgram.getMoney()); // 应付金额
userMemberRechargeLog.setComemoney(userMemberRechargeProgram.getDiscount()); // 实际到账金额
userMemberRechargeLog.setReamk("购买" + userMemberRechargeProgram.getRechargename()
+ "应付" + userMemberRechargeProgram.getMoney() + "元,应到"
userMemberRechargeLog.setReamk("购买" + userMemberRechargeProgram.getRechargename()
+ "应付" + userMemberRechargeProgram.getMoney() + "元,应到"
+ userMemberRechargeProgram.getDiscount() + "");
userMemberRechargeLog.setProid(userMemberRechargeProgram.getId());
money = userMemberRechargeProgram.getMoney().toString();
// type大于0表示会员包年充值需要特殊处理
if (userMemberRechargeProgram.getType() > 0) {
userMemberRechargeLog.setIsmember(1); // 会员充值
@ -213,7 +213,7 @@ public class AppleMemberController extends BaseController {
// 更新备注信息显示优惠详情
if (actualAmount.compareTo(rechargeAmount) > 0) {
BigDecimal bonusAmount = actualAmount.subtract(rechargeAmount);
userMemberRechargeLog.setReamk("会员现金充值" + money + "元,实际到账" + actualAmount
userMemberRechargeLog.setReamk("会员现金充值" + money + "元,实际到账" + actualAmount
+ "元(含优惠" + bonusAmount + "元)");
} else {
userMemberRechargeLog.setReamk("会员现金充值" + money + "");
@ -229,7 +229,7 @@ public class AppleMemberController extends BaseController {
new BigDecimal("0.01"), // 测试金额
1,
wechatPayUtil.PAY_FH + "api/recharge/pay/notify");
if (payResult != null && Boolean.TRUE.equals(payResult.get("success"))) {
// 构建支付响应数据
Map<String, Object> responseData = new HashMap<>();
@ -244,9 +244,9 @@ public class AppleMemberController extends BaseController {
return AppletControllerUtil.appletWarning("支付下单失败:" + errorMsg);
}
}
return AppletControllerUtil.appletWarning("支付失败");
} catch (Exception e) {
System.err.println("会员充值支付异常:" + e.getMessage());
return AppletControllerUtil.appletError("充值支付失败:" + e.getMessage());
@ -255,7 +255,7 @@ public class AppleMemberController extends BaseController {
/**
* 获取充值类目列表
*
*
* 查询状态为0启用且类型为0普通充值的充值套餐
* 用于用户选择充值项目
*
@ -282,7 +282,7 @@ public class AppleMemberController extends BaseController {
UserMemberRechargeProgram query = new UserMemberRechargeProgram();
query.setStatus(0); // 0=启用状态
query.setType(0); // 0=普通充值类型
List<UserMemberRechargeProgram> list = userMemberRechargeProgramService
.selectUserMemberRechargeProgramList(query);
params.put("balance", user.getBalance());
@ -338,7 +338,7 @@ public class AppleMemberController extends BaseController {
/**
* 获取包年充值项目列表
*
*
* 根据类型ID查询对应的包年充值项目
* 用于会员包年充值功能
*
@ -351,10 +351,10 @@ public class AppleMemberController extends BaseController {
UserMemberRechargeProgram query = new UserMemberRechargeProgram();
query.setStatus(0); // 0=启用状态
query.setType(id); // 指定类型
List<UserMemberRechargeProgram> list = userMemberRechargeProgramService
.selectUserMemberRechargeProgramList(query);
if (!list.isEmpty()) {
return AppletControllerUtil.appletSuccess(list);
} else {
@ -409,7 +409,7 @@ public class AppleMemberController extends BaseController {
/**
* 获取用户充值记录列表
*
*
* 查询当前用户的所有充值记录
* 按时间倒序排列
*
@ -425,20 +425,20 @@ public class AppleMemberController extends BaseController {
if (!(Boolean) userValidation.get("valid")) {
return AppletControllerUtil.appletWarning("用户未登录或token无效");
}
// 2. 获取用户信息
Users user = (Users) userValidation.get("user");
if (user == null) {
return AppletControllerUtil.appletWarning("用户信息获取失败");
}
// 3. 查询充值记录
UserMemberRechargeLog query = new UserMemberRechargeLog();
query.setUid(Math.toIntExact(user.getId()));
List<UserMemberRechargeLog> list = userMemberRechargeLogService
.selectUserMemberRechargeLogList(query);
if (!list.isEmpty()) {
return AppletControllerUtil.appletSuccess(list);
} else {
@ -452,7 +452,7 @@ public class AppleMemberController extends BaseController {
/**
* 获取用户消费记录
*
*
* 查询当前用户的消费记录
* 注意这里只返回第一条记录可能是设计问题建议返回完整列表
*
@ -468,20 +468,20 @@ public class AppleMemberController extends BaseController {
if (!(Boolean) userValidation.get("valid")) {
return AppletControllerUtil.appletWarning("用户未登录或token无效");
}
// 2. 获取用户信息
Users user = (Users) userValidation.get("user");
if (user == null) {
return AppletControllerUtil.appletWarning("用户信息获取失败");
}
// 3. 查询消费记录
UserMemnerConsumptionLog query = new UserMemnerConsumptionLog();
query.setUid(Math.toIntExact(user.getId()));
List<UserMemnerConsumptionLog> list = userMemnerConsumptionLogService
.selectUserMemnerConsumptionLogList(query);
if (!list.isEmpty()) {
// 注意这里只返回第一条记录可能需要根据业务需求调整
return AppletControllerUtil.appletSuccess(list.getFirst());
@ -550,10 +550,10 @@ public class AppleMemberController extends BaseController {
/**
* 服务订单评价接口
*
*
* 用户对已完成的服务订单进行评价
* 包括评分评价内容图片标签等
*
*
* 业务逻辑
* 1. 验证用户登录状态
* 2. 检查订单是否存在且属于当前用户
@ -575,7 +575,7 @@ public class AppleMemberController extends BaseController {
if (!(Boolean) userValidation.get("valid")) {
return AppletControllerUtil.appletdengluWarning("用户信息获取失败");
}
// 2. 获取用户信息
Users user = (Users) userValidation.get("user");
if (user == null) {
@ -739,7 +739,7 @@ public class AppleMemberController extends BaseController {
}
return AjaxResult.success("评价提交成功");
} catch (Exception e) {
System.err.println("服务订单评价异常:" + e.getMessage());
return AjaxResult.error("评价提交失败:" + e.getMessage());
@ -750,7 +750,7 @@ public class AppleMemberController extends BaseController {
/**
* 检查用户是否使用默认头像和昵称
*
*
* 验证用户是否还在使用系统默认的头像和昵称
* 如果是默认的提示用户修改
*
@ -782,7 +782,7 @@ public class AppleMemberController extends BaseController {
}
return AppletControllerUtil.appletSuccess("校验通过");
} catch (Exception e) {
System.err.println("验证用户图像和昵称异常:" + e.getMessage());
return AppletControllerUtil.appletError("验证失败:" + e.getMessage());
@ -793,7 +793,7 @@ public class AppleMemberController extends BaseController {
/**
* 获取二级分类列表
*
*
* 查询所有的服务分类信息
* 用于前端分类展示
*
@ -817,10 +817,10 @@ public class AppleMemberController extends BaseController {
/**
* 师傅报价接口
*
*
* 师傅对用户发布的需求订单进行报价
* 支持新增报价和更新已有报价
*
*
* 业务逻辑
* 1. 验证师傅登录状态
* 2. 验证订单是否存在
@ -894,7 +894,7 @@ public class AppleMemberController extends BaseController {
userDemandQuotationService.insertUserDemandQuotation(quoteRecord);
// isFirstQuote = true;
}
// // 8. 如果是第一次报价更新订单状态为待选择12
// if (isFirstQuote) {
// order.setStatus(12L);
@ -917,10 +917,10 @@ public class AppleMemberController extends BaseController {
/**
* 查询用户服务金/消费金日志支持分页
*
*
* 查询用户的服务金或消费金变动日志
* 支持分页查询按时间倒序排列
*
*
* @param type 日志类型1=服务金2=消费金
* @param limit 每页条数默认10
* @param page 页码默认1
@ -967,7 +967,7 @@ public class AppleMemberController extends BaseController {
Map<String, Object> pageData = PageUtil.buildPageResponse(tableDataInfo, page, limit);
return AppletControllerUtil.appletSuccess(pageData);
} catch (Exception e) {
System.err.println("查询用户服务金/消费金日志异常:" + e.getMessage());
return AppletControllerUtil.appletError("查询服务金/消费金日志失败:" + e.getMessage());

View File

@ -58,7 +58,7 @@ public class DiyCityController extends BaseController
@GetMapping("/datalist")
public AjaxResult listdata(DiyCity diyCity)
{
List<DiyCity> list = diyCityService.selectDiyCityList(diyCity);
List<DiyCity> list = diyCityService.selectDiyCityList(diyCity);
return success(list);
}
@ -155,6 +155,30 @@ public class DiyCityController extends BaseController
{
return toAjax(diyCityService.deleteDiyCityByIds(ids));
}
/**
* 手动触发师傅暂停状态自动恢复
* 用于测试和紧急情况下的手动处理
*/
@PreAuthorize("@ss.hasPermi('system:users:edit')")
@Log(title = "师傅暂停状态恢复", businessType = BusinessType.UPDATE)
@PostMapping("/manualResumeWorkerStatus")
public AjaxResult manualResumeWorkerStatus()
{
try {
// 获取定时任务工具类
com.ruoyi.system.controllerUtil.ScheduledTaskUtil scheduledTaskUtil =
com.ruoyi.common.utils.spring.SpringUtils.getBean(com.ruoyi.system.controllerUtil.ScheduledTaskUtil.class);
// 执行手动恢复
String result = scheduledTaskUtil.manualWorkerStatusResume();
return success(result);
} catch (Exception e) {
logger.error("手动触发师傅暂停状态恢复失败", e);
return error("手动触发师傅暂停状态恢复失败: " + e.getMessage());
}
}
}

View File

@ -51,7 +51,7 @@ public class ServiceGoodsController extends BaseController {
if (serviceCate != null) {
serviceGoodsdata.setCateName(serviceCate.getTitle());
}
serviceGoodsdata.setIcon("https://img.huafurenjia.cn/" + serviceGoodsdata.getIcon());
serviceGoodsdata.setIcon(com.ruoyi.system.ControllerUtil.AppletControllerUtil.buildImageUrl(serviceGoodsdata.getIcon()));
}
return getDataTable(list);
@ -131,7 +131,7 @@ public class ServiceGoodsController extends BaseController {
public AjaxResult add(@RequestBody ServiceGoods serviceGoods) {
// 验证和处理基检现象数据格式
validateAndProcessBasicField(serviceGoods);
// 处理一级和二级分类ID
processCategoryIds(serviceGoods);
@ -147,7 +147,7 @@ public class ServiceGoodsController extends BaseController {
public AjaxResult edit(@RequestBody ServiceGoods serviceGoods) {
// 验证和处理基检现象数据格式
validateAndProcessBasicField(serviceGoods);
// 处理一级和二级分类ID
processCategoryIds(serviceGoods);

View File

@ -22,7 +22,7 @@ import com.ruoyi.system.service.IUserMemnerConsumptionLogService;
/**
* 请填写功能名称Controller
*
*
* @author ruoyi
* @date 2025-05-14
*/
@ -47,6 +47,10 @@ public class UsersController extends BaseController
{
startPage();
List<Users> list = usersService.selectUsersList(users);
for(Users u:list){
u.setAvatar(com.ruoyi.system.ControllerUtil.AppletControllerUtil.buildImageUrl(u.getAvatar()));
}
return getDataTable(list);
}
@ -149,6 +153,54 @@ public class UsersController extends BaseController
newUsers.setStatus(users.getStatus());
return toAjax(usersService.updateUsers(newUsers));
}
/**
* 暂停接单
*/
@PreAuthorize("@ss.hasPermi('system:users:edit')")
@Log(title = "暂停接单", businessType = BusinessType.UPDATE)
@PutMapping("/pauseOrder")
public AjaxResult pauseOrder(@RequestBody Users users)
{
// 参数验证
if (users.getId() == null) {
return error("用户ID不能为空");
}
if (users.getProhibitTimeNum() == null || users.getProhibitTimeNum() <= 0) {
return error("暂停时长必须大于0小时");
}
if (users.getProhibitTimeNum() > 168) {
return error("暂停时长不能超过168小时7天");
}
// 获取当前用户信息
Users existingUser = usersService.selectUsersById(users.getId());
if (existingUser == null) {
return error("用户不存在");
}
// 记录操作日志
System.out.println("暂停师傅接单:" + existingUser.getName() + ",暂停时长:" + users.getProhibitTimeNum() + "小时");
// 计算禁止接单结束时间
java.util.Date currentTime = new java.util.Date();
java.util.Calendar calendar = java.util.Calendar.getInstance();
calendar.setTime(currentTime);
calendar.add(java.util.Calendar.HOUR_OF_DAY, users.getProhibitTimeNum().intValue());
// 设置暂停接单相关字段
existingUser.setProhibitTimeNum(users.getProhibitTimeNum());
existingUser.setProhibitTime(calendar.getTime());
existingUser.setIsStop(1);
int result = usersService.updateUsers(existingUser);
if (result > 0) {
System.out.println("师傅暂停接单设置成功,到期时间:" + calendar.getTime());
return success("暂停接单设置成功");
} else {
return error("暂停接单设置失败");
}
}
/**
* 删除请填写功能名称
*/
@ -183,7 +235,7 @@ public class UsersController extends BaseController
{
startPage();
UserMemnerConsumptionLog query = new UserMemnerConsumptionLog();
query.setUid(Math.toIntExact(userId)); //
query.setUid(Math.toIntExact(userId)); //
return getDataTable(userMemnerConsumptionLogService.selectUserMemnerConsumptionLogList(query));
}
}

View File

@ -10,7 +10,7 @@ import java.util.Map;
import java.math.BigDecimal;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.system.ControllerUtil.ScheduledTaskUtil;
import com.ruoyi.system.controllerUtil.ScheduledTaskUtil;
import com.ruoyi.system.domain.Users;
import com.ruoyi.system.service.IUsersService;
import org.springframework.security.access.prepost.PreAuthorize;

View File

@ -2,21 +2,22 @@ package com.ruoyi.system.ControllerUtil;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.system.controllerUtil.ScheduledTaskUtil;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
/**
* 定时任务工具类使用示例
*
*
* 此类展示了如何在Controller中使用ScheduledTaskUtil
* 包括手动触发任务获取统计信息等功能
*
*
* 使用说明
* 1. 在需要的Controller中注入ScheduledTaskUtil
* 2. 调用相应的方法进行操作
* 3. 查看任务执行状态和统计信息
*
*
* @author RuoYi
* @date 2024-01-01
*/
@ -26,11 +27,11 @@ public class ScheduledTaskExample {
/**
* 手动触发派单超时处理
*
*
* 使用场景
* - 系统维护后需要立即检查超时订单
* - 紧急情况下需要手动处理超时订单
*
*
* 请求示例GET /system/scheduled-task/dispatch-timeout
*/
@GetMapping("/dispatch-timeout")
@ -46,11 +47,11 @@ public class ScheduledTaskExample {
/**
* 手动触发订单状态检查
*
*
* 使用场景
* - 发现异常订单时进行状态检查
* - 定期人工检查订单状态
*
*
* 请求示例GET /system/scheduled-task/status-check
*/
@GetMapping("/status-check")
@ -66,11 +67,11 @@ public class ScheduledTaskExample {
/**
* 手动触发系统数据清理
*
*
* 使用场景
* - 磁盘空间不足时清理数据
* - 系统维护时清理垃圾数据
*
*
* 请求示例GET /system/scheduled-task/data-cleanup
*/
@GetMapping("/data-cleanup")
@ -86,11 +87,11 @@ public class ScheduledTaskExample {
/**
* 手动触发健康检查
*
*
* 使用场景
* - 系统启动后验证各组件是否正常
* - 定期检查系统健康状态
*
*
* 请求示例GET /system/scheduled-task/health-check
*/
@GetMapping("/health-check")
@ -106,12 +107,12 @@ public class ScheduledTaskExample {
/**
* 获取任务执行统计信息
*
*
* 使用场景
* - 监控任务执行情况
* - 分析系统性能
* - 故障排查
*
*
* 请求示例GET /system/scheduled-task/statistics
*/
@GetMapping("/statistics")
@ -127,12 +128,12 @@ public class ScheduledTaskExample {
/**
* 获取格式化的任务统计报告
*
*
* 使用场景
* - 生成系统运行报告
* - 展示给运维人员查看
* - 导出系统状态信息
*
*
* 请求示例GET /system/scheduled-task/report
*/
@GetMapping("/report")
@ -148,12 +149,12 @@ public class ScheduledTaskExample {
/**
* 获取线程池状态信息
*
*
* 使用场景
* - 监控线程池使用情况
* - 性能调优参考
* - 系统负载分析
*
*
* 请求示例GET /system/scheduled-task/thread-pool
*/
@GetMapping("/thread-pool")
@ -169,12 +170,12 @@ public class ScheduledTaskExample {
/**
* 重启任务线程池
*
*
* 使用场景
* - 线程池出现异常时重启
* - 系统维护后重启服务
* - 配置更改后重新初始化
*
*
* 请求示例POST /system/scheduled-task/restart-pool
*/
@PostMapping("/restart-pool")
@ -190,13 +191,13 @@ public class ScheduledTaskExample {
/**
* 停止所有任务
*
*
* 使用场景
* - 系统维护时暂停任务
* - 紧急情况下停止所有任务
*
*
* 注意此操作只停止线程池@Scheduled注解的任务仍会执行
*
*
* 请求示例POST /system/scheduled-task/stop-all
*/
@PostMapping("/stop-all")
@ -213,20 +214,20 @@ public class ScheduledTaskExample {
/**
* ==================== 使用说明 ====================
*
*
* 1. 自动执行的定时任务无需手动调用
* - 订单派单超时处理每5分钟执行一次
* - 订单状态超时检查每10分钟执行一次
* - 系统数据清理每天凌晨2点执行
* - 系统健康检查每30分钟执行一次
*
*
* 2. 手动触发任务的方式
* - 通过HTTP接口调用如上面的Controller方法
* - 在其他Service中直接调用工具类方法
* - 在系统管理后台添加按钮触发
*
*
* 3. 在其他地方使用的示例代码
*
*
* // 在Service中使用
* @Service
* public class OrderService {
@ -236,35 +237,35 @@ public class ScheduledTaskExample {
* // 处理结果...
* }
* }
*
*
* // 在Controller中使用
* @RestController
* public class OrderController {
* @Autowired
* private ScheduledTaskUtil scheduledTaskUtil;
*
*
* @GetMapping("/check-timeout")
* public AjaxResult checkTimeout() {
* String result = scheduledTaskUtil.manualDispatchTimeoutCheck();
* return AjaxResult.success(result);
* }
* }
*
*
* 4. 监控和统计
* - 可通过 /system/scheduled-task/statistics 接口获取详细统计
* - 可通过 /system/scheduled-task/report 接口获取格式化报告
* - 统计信息包括执行次数成功率平均耗时等
*
*
* 5. 配置说明
* - 派单超时时间20分钟可在getDispatchTimeoutOrders方法中修改
* - 待支付超时时间30分钟可在checkPaymentTimeoutOrders方法中修改
* - 服务超时时间4小时可在checkServiceTimeoutOrders方法中修改
* - 日志保留时间30天可在cleanupExpiredOrderLogs方法中修改
*
*
* 6. 注意事项
* - 确保Spring容器中有IOrderService的实现
* - 如果需要数据库操作需要在相应方法中添加具体实现
* - 可根据实际业务需求调整各种超时时间和处理逻辑
* - 建议在生产环境中根据服务器性能调整线程池参数
*
*/
*
*/

View File

@ -1,10 +1,12 @@
package com.ruoyi.system.ControllerUtil;
package com.ruoyi.system.controllerUtil;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.system.domain.Order;
import com.ruoyi.system.domain.Users;
import com.ruoyi.system.service.IOrderService;
import com.ruoyi.system.service.IUsersService;
import com.ruoyi.system.service.IWorkerMoneyLogService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -22,20 +24,21 @@ import java.util.concurrent.*;
/**
* 定时任务工具类
*
*
* 主要功能
* 1. 订单派单超时处理20分钟自动处理
* 2. 订单状态超时检查
* 3. 系统数据清理
* 4. 任务调度管理
* 5. 异步任务执行
*
* 6. 师傅暂停状态自动恢复处理
*
* 使用方式
* - 自动定时执行通过@Scheduled注解配置的定时任务会自动执行
* - 手动调用可直接调用相应的方法进行手动处理
* - 异步执行支持异步执行耗时任务不阻塞主线程
* - 项目启动时自动执行实现CommandLineRunner接口项目启动时自动调用run方法
*
*
* @author RuoYi
* @date 2024-01-01
*/
@ -47,13 +50,15 @@ public class ScheduledTaskUtil implements CommandLineRunner {
// 订单服务通过Spring工具类获取
private static IOrderService orderService;
// 师傅用户服务通过Spring工具类获取
private static IUsersService usersService;
// 订单服务通过Spring工具类获取
private static IWorkerMoneyLogService workerMoneyLogService;
// 线程池用于异步执行任务
private ThreadPoolExecutor executorService;
// 任务执行统计
private final Map<String, TaskStatistics> taskStats = new ConcurrentHashMap<>();
@ -68,10 +73,24 @@ public class ScheduledTaskUtil implements CommandLineRunner {
} catch (Exception e) {
log.warn("获取订单服务失败,部分功能可能不可用: {}", e.getMessage());
}
// 获取师傅用户服务
try {
usersService = SpringUtils.getBean(IUsersService.class);
} catch (Exception e) {
log.warn("获取师傅用户服务失败,部分功能可能不可用: {}", e.getMessage());
}
// 获取订单服务
try {
workerMoneyLogService = SpringUtils.getBean(IWorkerMoneyLogService.class);
} catch (Exception e) {
log.warn("获取订单服务失败,部分功能可能不可用: {}", e.getMessage());
}
// 初始化线程池
initThreadPool();
log.info("定时任务工具类初始化完成");
}
@ -106,6 +125,7 @@ public class ScheduledTaskUtil implements CommandLineRunner {
cleanupSystemData();
healthCheck();
updateWorkerMoneyLook();
autoResumeWorkerOrderStatus(); // 添加师傅暂停状态自动恢复任务
log.info("定时任务自动执行完成");
}
@ -138,7 +158,7 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 订单派单超时处理任务
* 每5分钟执行一次检查派单超过20分钟的订单
*
*
* 使用说明
* - 自动执行无需手动调用
* - 处理逻辑将超时未接单的订单重新派发或标记为超时
@ -170,7 +190,7 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 订单状态超时检查任务
* 每10分钟执行一次检查各种状态的订单是否超时
*
*
* 使用说明
* - 检查服务中订单是否超过预期时间
* - 检查待支付订单是否超时
@ -180,21 +200,21 @@ public class ScheduledTaskUtil implements CommandLineRunner {
public void checkOrderStatusTimeout() {
String taskName = "订单状态超时检查";
long startTime = System.currentTimeMillis();
try {
log.info("开始执行{}任务", taskName);
// 检查服务中超时订单
checkServiceTimeoutOrders();
// 检查待支付超时订单
checkPaymentTimeoutOrders();
// 检查其他异常状态订单
checkAbnormalStatusOrders();
updateTaskStatistics(taskName, true, System.currentTimeMillis() - startTime);
} catch (Exception e) {
log.error("{}任务执行失败", taskName, e);
updateTaskStatistics(taskName, false, System.currentTimeMillis() - startTime);
@ -204,7 +224,7 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 系统数据清理任务
* 每天凌晨2点执行清理过期数据
*
*
* 使用说明
* - 清理30天前的日志数据
* - 清理临时文件
@ -214,22 +234,22 @@ public class ScheduledTaskUtil implements CommandLineRunner {
public void cleanupSystemData() {
String taskName = "系统数据清理";
long startTime = System.currentTimeMillis();
try {
log.info("开始执行{}任务", taskName);
// 清理过期订单日志
cleanupExpiredOrderLogs();
// 清理临时文件
cleanupTempFiles();
// 清理任务统计数据
cleanupTaskStatistics();
updateTaskStatistics(taskName, true, System.currentTimeMillis() - startTime);
log.info("{}任务执行完成", taskName);
} catch (Exception e) {
log.error("{}任务执行失败", taskName, e);
updateTaskStatistics(taskName, false, System.currentTimeMillis() - startTime);
@ -245,21 +265,21 @@ public class ScheduledTaskUtil implements CommandLineRunner {
String taskName = "系统健康检查";
log.info("开始执行{}任务", taskName);
long startTime = System.currentTimeMillis();
try {
log.info("开始执行{}任务", taskName);
// 检查数据库连接
checkDatabaseConnection();
// 检查线程池状态
checkThreadPoolStatus();
// 检查系统资源
checkSystemResources();
updateTaskStatistics(taskName, true, System.currentTimeMillis() - startTime);
} catch (Exception e) {
log.error("{}任务执行失败", taskName, e);
updateTaskStatistics(taskName, false, System.currentTimeMillis() - startTime);
@ -269,7 +289,7 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 测试定时任务
* 每3秒执行一次打印测试信息
*
*
* 使用说明
* - 用于测试定时任务的执行情况
*/
@ -278,11 +298,87 @@ public class ScheduledTaskUtil implements CommandLineRunner {
log.info("测试定时任务执行中... 当前时间: {}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
/**
* 师傅暂停状态自动恢复处理任务
* 每1小时执行一次检查暂停时间已过期的师傅并自动恢复接单状态
*
* 处理逻辑
* 1. 查询type=2且is_stop=1的师傅数据
* 2. 判断prohibit_time是否小于当前时间即暂停时间已过期
* 3. 将过期的师傅is_stop设置为0恢复接单状态
*
* 使用说明
* - 自动执行无需手动调用
* - 执行频率每1小时
* - 确保师傅暂停时间到期后能自动恢复接单
*/
@Scheduled(fixedRate = 60 * 60 * 1000) // 每1小时执行一次
public void autoResumeWorkerOrderStatus() {
String taskName = "师傅暂停状态自动恢复";
long startTime = System.currentTimeMillis();
log.info("开始执行{}任务", taskName);
try {
// 确保usersService已初始化
if (usersService == null) {
usersService = SpringUtils.getBean(IUsersService.class);
}
if (usersService == null) {
log.warn("{}任务跳过执行,师傅用户服务未初始化", taskName);
return;
}
// 查询所有暂停状态的师傅type=2 is_stop=1
List<Users> pausedWorkers = usersService.selectPausedWorkers();
if (pausedWorkers.isEmpty()) {
log.info("{}任务执行完成,无暂停状态的师傅", taskName);
updateTaskStatistics(taskName, true, System.currentTimeMillis() - startTime);
return;
}
log.info("发现{}个暂停状态的师傅,开始检查恢复条件", pausedWorkers.size());
// 当前时间
Date currentTime = new Date();
int resumedCount = 0;
// 遍历所有暂停的师傅
for (Users worker : pausedWorkers) {
try {
// 检查是否应该恢复接单状态
if (shouldResumeWorker(worker, currentTime)) {
// 恢复师傅接单状态
resumeWorkerOrderStatus(worker);
resumedCount++;
log.info("师傅{}(ID:{})暂停时间已过期,已自动恢复接单状态",
worker.getName(), worker.getId());
}
} catch (Exception e) {
log.error("处理师傅{}(ID:{})暂停状态恢复失败",
worker.getName(), worker.getId(), e);
}
}
log.info("{}任务执行完成,共处理{}个师傅,成功恢复{}个师傅的接单状态",
taskName, pausedWorkers.size(), resumedCount);
updateTaskStatistics(taskName, true, System.currentTimeMillis() - startTime);
} catch (Exception e) {
log.error("{}任务执行失败", taskName, e);
updateTaskStatistics(taskName, false, System.currentTimeMillis() - startTime);
}
log.info("{}任务执行完成,耗时{}毫秒", taskName, System.currentTimeMillis() - startTime);
}
// ========================= 业务处理方法 =========================
/**
* 获取派单超过20分钟的订单
*
*
* @return 超时订单列表
*/
private List<Order> getDispatchTimeoutOrders() {
@ -290,21 +386,21 @@ public class ScheduledTaskUtil implements CommandLineRunner {
// 计算20分钟前的时间
LocalDateTime timeoutThreshold = LocalDateTime.now().minusMinutes(20);
Date thresholdDate = Date.from(timeoutThreshold.atZone(ZoneId.systemDefault()).toInstant());
// 构建查询条件
Order queryOrder = new Order();
queryOrder.setJsonStatus(1); // 假设1表示已派单状态
// 如果orderService可用,执行查询
if (orderService != null) {
// 这里需要根据实际的Service方法进行调用
// 示例return orderService.selectTimeoutDispatchOrders(thresholdDate);
log.debug("查询派单超时订单,截止时间:{}", thresholdDate);
}
// 临时返回空列表实际使用时需要实现相应的查询方法
return new ArrayList<>();
} catch (Exception e) {
log.error("获取派单超时订单失败", e);
return new ArrayList<>();
@ -313,7 +409,7 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 异步处理超时订单
*
*
* @param timeoutOrders 超时订单列表
*/
@Async
@ -331,12 +427,12 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 处理单个超时订单
*
*
* @param order 超时订单
*/
private void processTimeoutOrder(Order order) {
log.info("处理派单超时订单:{}", order.getOrderId());
try {
// 业务处理逻辑
if (canRedispatch(order)) {
@ -348,10 +444,10 @@ public class ScheduledTaskUtil implements CommandLineRunner {
markOrderAsTimeout(order);
log.info("订单{}标记为派单超时", order.getOrderId());
}
// 记录处理日志
recordOrderProcessLog(order, "派单超时处理");
} catch (Exception e) {
log.error("处理超时订单{}失败", order.getOrderId(), e);
}
@ -362,14 +458,14 @@ public class ScheduledTaskUtil implements CommandLineRunner {
*/
private void checkServiceTimeoutOrders() {
log.info("检查服务中超时订单");
try {
// 获取服务中超过预期时间的订单
LocalDateTime timeoutThreshold = LocalDateTime.now().minusHours(4); // 假设服务超过4小时为超时
// 处理服务超时订单
processServiceTimeoutOrders(timeoutThreshold);
} catch (Exception e) {
log.error("检查服务中超时订单失败", e);
}
@ -380,14 +476,14 @@ public class ScheduledTaskUtil implements CommandLineRunner {
*/
private void checkPaymentTimeoutOrders() {
log.info("检查待支付超时订单");
try {
// 获取待支付超过30分钟的订单
LocalDateTime timeoutThreshold = LocalDateTime.now().minusMinutes(30);
// 处理超时订单
processPaymentTimeoutOrders(timeoutThreshold);
} catch (Exception e) {
log.error("检查待支付超时订单失败", e);
}
@ -398,14 +494,14 @@ public class ScheduledTaskUtil implements CommandLineRunner {
*/
private void checkAbnormalStatusOrders() {
log.info("检查异常状态订单");
try {
// 检查状态异常的订单
// 例如长时间处于某个中间状态的订单
LocalDateTime abnormalThreshold = LocalDateTime.now().minusHours(24);
processAbnormalStatusOrders(abnormalThreshold);
} catch (Exception e) {
log.error("检查异常状态订单失败", e);
}
@ -415,14 +511,14 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 判断订单是否可以重新派单
*
*
* @param order 订单对象
* @return true-可以重新派单false-不能重新派单
*/
private boolean canRedispatch(Order order) {
// 业务判断逻辑
// 例如检查重派次数订单类型订单创建时间等
// 简单示例检查是否已经重派过多次
// 实际项目中需要根据订单表中的重派次数字段来判断
return true; // 简化实现实际使用时需要完善判断逻辑
@ -430,7 +526,7 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 重新派单
*
*
* @param order 订单对象
*/
private void redispatchOrder(Order order) {
@ -438,16 +534,16 @@ public class ScheduledTaskUtil implements CommandLineRunner {
// 重新派单逻辑
order.setJsonStatus(1); // 重新设为派单状态
order.setUpdateTime(new Date());
// 如果有重派次数字段需要增加重派次数
// order.setRedispatchCount(order.getRedispatchCount() + 1);
// 更新订单
if (orderService != null) {
// orderService.updateOrder(order);
log.info("订单{}重新派单操作完成", order.getOrderId());
}
} catch (Exception e) {
log.error("重新派单失败,订单号:{}", order.getOrderId(), e);
}
@ -455,21 +551,21 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 标记订单为超时
*
* @param order 订单对象
*
* @param order 订单对象
*/
private void markOrderAsTimeout(Order order) {
try {
// 标记为超时状态
order.setJsonStatus(99); // 假设99表示超时状态
order.setUpdateTime(new Date());
// 更新订单
if (orderService != null) {
// orderService.updateOrder(order);
log.info("订单{}标记为超时状态", order.getOrderId());
}
} catch (Exception e) {
log.error("标记订单超时失败,订单号:{}", order.getOrderId(), e);
}
@ -477,17 +573,20 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 记录订单处理日志
*
*
* @param order 订单对象
* @param operation 操作描述
*/
private void recordOrderProcessLog(Order order, String operation) {
try {
// 使用OrderUtil记录日志
OrderUtil orderUtil = SpringUtils.getBean(OrderUtil.class);
orderUtil.SaveOrderLog(order);
// 注意这里需要根据实际的OrderUtil类路径进行调整
// 如果OrderUtil不存在可以注释掉这部分代码或者实现其他日志记录方式
log.info("订单{}{}处理日志记录", order.getOrderId(), operation);
log.info("订单{}{}处理日志记录成功", order.getOrderId(), operation);
// 临时注释掉OrderUtil调用避免编译错误
// com.ruoyi.system.controllerUtil.OrderUtil orderUtil = SpringUtils.getBean(com.ruoyi.system.controllerUtil.OrderUtil.class);
// orderUtil.SaveOrderLog(order);
} catch (Exception e) {
log.error("记录订单处理日志失败,订单号:{}", order.getOrderId(), e);
@ -496,23 +595,23 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 处理服务中超时订单
*
*
* @param timeoutThreshold 超时时间阈值
*/
private void processServiceTimeoutOrders(LocalDateTime timeoutThreshold) {
try {
log.debug("处理服务中超时订单,超时阈值:{}", timeoutThreshold);
// 查询服务中超时的订单
// 这里需要根据实际的数据库结构和Service方法来实现
// 示例处理逻辑
// 1. 查询服务状态且创建时间早于阈值的订单
// 2. 发送提醒通知
// 3. 或者自动标记为异常状态
log.debug("服务中超时订单处理完成");
} catch (Exception e) {
log.error("处理服务中超时订单失败", e);
}
@ -520,21 +619,21 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 处理待支付超时订单
*
*
* @param timeoutThreshold 超时时间阈值
*/
private void processPaymentTimeoutOrders(LocalDateTime timeoutThreshold) {
try {
log.debug("处理待支付超时订单,超时阈值:{}", timeoutThreshold);
// 查询待支付超时的订单
// 示例处理逻辑
// 1. 查询待支付状态且创建时间早于阈值的订单
// 2. 自动取消订单
// 3. 释放相关资源
log.debug("待支付超时订单处理完成");
} catch (Exception e) {
log.error("处理待支付超时订单失败", e);
}
@ -542,21 +641,21 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 处理异常状态订单
*
*
* @param abnormalThreshold 异常时间阈值
*/
private void processAbnormalStatusOrders(LocalDateTime abnormalThreshold) {
try {
log.debug("处理异常状态订单,异常阈值:{}", abnormalThreshold);
// 查询异常状态的订单
// 示例处理逻辑
// 1. 查询长时间处于中间状态的订单
// 2. 发送告警通知
// 3. 人工介入处理
log.debug("异常状态订单处理完成");
} catch (Exception e) {
log.error("处理异常状态订单失败", e);
}
@ -568,18 +667,18 @@ public class ScheduledTaskUtil implements CommandLineRunner {
private void cleanupExpiredOrderLogs() {
try {
log.info("开始清理过期订单日志");
// 删除30天前的日志
LocalDateTime expireTime = LocalDateTime.now().minusDays(30);
Date expireDate = Date.from(expireTime.atZone(ZoneId.systemDefault()).toInstant());
// 执行清理
// 这里需要调用相应的日志服务方法
// int deletedCount = orderLogService.deleteExpiredLogs(expireDate);
// log.info("清理过期订单日志完成,删除{}条记录", deletedCount);
log.info("清理过期订单日志完成");
} catch (Exception e) {
log.error("清理过期订单日志失败", e);
}
@ -591,16 +690,16 @@ public class ScheduledTaskUtil implements CommandLineRunner {
private void cleanupTempFiles() {
try {
log.info("开始清理临时文件");
// 清理临时目录中的过期文件
// 具体实现根据项目需求
String tempDir = System.getProperty("java.io.tmpdir");
log.debug("临时文件目录:{}", tempDir);
// 这里可以添加具体的文件清理逻辑
log.info("清理临时文件完成");
} catch (Exception e) {
log.error("清理临时文件失败", e);
}
@ -613,14 +712,14 @@ public class ScheduledTaskUtil implements CommandLineRunner {
try {
// 只保留最近7天的统计数据
LocalDateTime expireTime = LocalDateTime.now().minusDays(7);
taskStats.entrySet().removeIf(entry -> {
TaskStatistics stats = entry.getValue();
return stats.getLastExecuteTime() != null && stats.getLastExecuteTime().isBefore(expireTime);
});
log.info("清理任务统计数据完成");
} catch (Exception e) {
log.error("清理任务统计数据失败", e);
}
@ -639,7 +738,7 @@ public class ScheduledTaskUtil implements CommandLineRunner {
} else {
log.warn("订单服务未初始化,跳过数据库连接检查");
}
} catch (Exception e) {
log.error("数据库连接检查失败", e);
}
@ -654,15 +753,15 @@ public class ScheduledTaskUtil implements CommandLineRunner {
int queueSize = executorService.getQueue().size();
int poolSize = executorService.getPoolSize();
long completedTaskCount = executorService.getCompletedTaskCount();
log.debug("线程池状态 - 活跃线程数: {}, 池大小: {}, 队列大小: {}, 已完成任务: {}",
log.debug("线程池状态 - 活跃线程数: {}, 池大小: {}, 队列大小: {}, 已完成任务: {}",
activeCount, poolSize, queueSize, completedTaskCount);
// 如果队列积压过多记录警告
if (queueSize > 80) {
log.warn("线程池队列积压过多,当前队列大小: {}", queueSize);
}
// 如果活跃线程数过多记录警告
if (activeCount > 8) {
log.warn("线程池活跃线程数过多,当前活跃线程数: {}", activeCount);
@ -681,22 +780,22 @@ public class ScheduledTaskUtil implements CommandLineRunner {
long freeMemory = runtime.freeMemory();
long usedMemory = totalMemory - freeMemory;
long maxMemory = runtime.maxMemory();
double memoryUsagePercent = (double) usedMemory / totalMemory * 100;
double maxMemoryUsagePercent = (double) usedMemory / maxMemory * 100;
log.debug("内存使用情况 - 总内存: {}MB, 已用: {}MB, 最大内存: {}MB, 当前使用率: {:.2f}%, 最大使用率: {:.2f}%",
totalMemory / 1024 / 1024, usedMemory / 1024 / 1024, maxMemory / 1024 / 1024,
log.debug("内存使用情况 - 总内存: {}MB, 已用: {}MB, 最大内存: {}MB, 当前使用率: {:.2f}%, 最大使用率: {:.2f}%",
totalMemory / 1024 / 1024, usedMemory / 1024 / 1024, maxMemory / 1024 / 1024,
memoryUsagePercent, maxMemoryUsagePercent);
// 内存使用率过高时记录警告
if (maxMemoryUsagePercent > 80) {
log.warn("内存使用率过高: {:.2f}%", maxMemoryUsagePercent);
}
// 检查垃圾回收情况
System.gc(); // 建议进行垃圾回收实际生产环境中谨慎使用
} catch (Exception e) {
log.error("检查系统资源失败", e);
}
@ -704,7 +803,7 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 更新任务统计信息
*
*
* @param taskName 任务名称
* @param success 是否成功
* @param duration 执行时长毫秒
@ -723,11 +822,11 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 手动触发派单超时处理
*
*
* 使用说明
* - 可在特殊情况下手动调用此方法
* - 例如系统维护后需要立即检查超时订单
*
*
* @return 处理结果描述
*/
public String manualDispatchTimeoutCheck() {
@ -743,7 +842,7 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 手动触发订单状态检查
*
*
* @return 处理结果描述
*/
public String manualOrderStatusCheck() {
@ -759,7 +858,7 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 手动触发系统数据清理
*
*
* @return 处理结果描述
*/
public String manualDataCleanup() {
@ -775,7 +874,7 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 手动触发健康检查
*
*
* @return 处理结果描述
*/
public String manualHealthCheck() {
@ -789,13 +888,33 @@ public class ScheduledTaskUtil implements CommandLineRunner {
}
}
/**
* 手动触发师傅暂停状态自动恢复
*
* 使用说明
* - 可在特殊情况下手动调用此方法
* - 例如系统维护后需要立即检查并恢复过期的师傅暂停状态
*
* @return 处理结果描述
*/
public String manualWorkerStatusResume() {
try {
log.info("手动触发师傅暂停状态自动恢复");
autoResumeWorkerOrderStatus();
return "师傅暂停状态自动恢复执行成功";
} catch (Exception e) {
log.error("手动师傅暂停状态自动恢复失败", e);
return "师傅暂停状态自动恢复执行失败: " + e.getMessage();
}
}
/**
* 获取任务执行统计信息
*
*
* 使用说明
* - 可用于监控各个定时任务的执行情况
* - 包含执行次数成功率平均执行时间等信息
*
*
* @return 任务统计信息
*/
public Map<String, TaskStatistics> getTaskStatistics() {
@ -804,18 +923,18 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 获取格式化的任务统计报告
*
*
* @return 格式化的统计报告
*/
public String getTaskStatisticsReport() {
StringBuilder report = new StringBuilder();
report.append("=== 定时任务执行统计报告 ===\n");
report.append(String.format("报告生成时间: %s\n",
report.append(String.format("报告生成时间: %s\n",
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))));
report.append(String.format("线程池状态: 活跃线程=%d, 队列大小=%d\n",
report.append(String.format("线程池状态: 活跃线程=%d, 队列大小=%d\n",
executorService.getActiveCount(), executorService.getQueue().size()));
report.append("\n");
if (taskStats.isEmpty()) {
report.append("暂无任务执行记录\n");
} else {
@ -823,13 +942,13 @@ public class ScheduledTaskUtil implements CommandLineRunner {
report.append(stats.toString()).append("\n");
}
}
return report.toString();
}
/**
* 获取线程池状态信息
*
*
* @return 线程池状态信息
*/
public Map<String, Object> getThreadPoolStatus() {
@ -847,7 +966,7 @@ public class ScheduledTaskUtil implements CommandLineRunner {
/**
* 停止所有定时任务
*
*
* 使用说明
* - 用于系统维护时临时停止定时任务
* - 注意这只是停止线程池@Scheduled注解的任务仍会执行
@ -880,6 +999,75 @@ public class ScheduledTaskUtil implements CommandLineRunner {
log.info("定时任务线程池已重启");
}
/**
* 判断师傅是否应该恢复接单状态
*
* @param worker 师傅用户对象
* @param currentTime 当前时间
* @return true-应该恢复false-不应该恢复
*/
private boolean shouldResumeWorker(Users worker, Date currentTime) {
try {
// 检查必要字段
if (worker.getProhibitTime() == null) {
log.debug("师傅{}(ID:{})的prohibit_time为空跳过处理",
worker.getName(), worker.getId());
return false;
}
// 判断暂停时间是否已过期prohibit_time < 当前时间
boolean isExpired = worker.getProhibitTime().before(currentTime);
if (isExpired) {
log.debug("师傅{}(ID:{})的暂停时间已过期prohibit_time: {}, 当前时间: {}",
worker.getName(), worker.getId(),
worker.getProhibitTime(), currentTime);
} else {
log.debug("师傅{}(ID:{})的暂停时间未过期prohibit_time: {}, 当前时间: {}",
worker.getName(), worker.getId(),
worker.getProhibitTime(), currentTime);
}
return isExpired;
} catch (Exception e) {
log.error("判断师傅{}(ID:{})是否应该恢复接单状态时发生异常",
worker.getName(), worker.getId(), e);
return false;
}
}
/**
* 恢复师傅接单状态
*
* @param worker 师傅用户对象
*/
private void resumeWorkerOrderStatus(Users worker) {
try {
// 创建更新对象只更新必要字段
Users updateUser = new Users();
updateUser.setId(worker.getId());
updateUser.setIsStop(0); // 恢复接单状态
updateUser.setProhibitTimeNum(0); // 清空暂停时长
// 注意这里不清空prohibit_time保留历史记录
// 执行更新
int updateResult = usersService.updateUsers(updateUser);
if (updateResult > 0) {
log.info("师傅{}(ID:{})接单状态恢复成功", worker.getName(), worker.getId());
} else {
log.warn("师傅{}(ID:{})接单状态恢复失败,更新结果: {}",
worker.getName(), worker.getId(), updateResult);
}
} catch (Exception e) {
log.error("恢复师傅{}(ID:{})接单状态时发生异常",
worker.getName(), worker.getId(), e);
throw e; // 重新抛出异常让上层处理
}
}
// ========================= 内部类 =========================
/**
@ -963,4 +1151,4 @@ public class ScheduledTaskUtil implements CommandLineRunner {
);
}
}
}
}

View File

@ -115,4 +115,11 @@ public interface UsersMapper
* @return 符合条件的师傅列表限制10条
*/
public List<Users> selectTestDispatchWorkers();
/**
* 查询暂停状态的师傅列表
* 用于定时任务自动恢复过期的师傅暂停状态
* @return 暂停状态的师傅列表type=2且is_stop=1
*/
public List<Users> selectPausedWorkers();
}

View File

@ -115,4 +115,11 @@ public interface IUsersService
* @return 符合条件的师傅列表限制10条
*/
public List<Users> selectTestDispatchWorkers();
/**
* 查询暂停状态的师傅列表
* 用于定时任务自动恢复过期的师傅暂停状态
* @return 暂停状态的师傅列表type=2且is_stop=1
*/
public List<Users> selectPausedWorkers();
}

View File

@ -164,4 +164,14 @@ public class UsersServiceImpl implements IUsersService
public List<Users> selectTestDispatchWorkers() {
return usersMapper.selectTestDispatchWorkers();
}
/**
* 查询暂停状态的师傅列表
* 用于定时任务自动恢复过期的师傅暂停状态
* @return 暂停状态的师傅列表type=2且is_stop=1
*/
@Override
public List<Users> selectPausedWorkers() {
return usersMapper.selectPausedWorkers();
}
}

View File

@ -692,4 +692,16 @@
AND DATE(worker_time) = CURDATE()
</where>
</select>
<!-- 查询暂停状态的师傅列表 - 用于定时任务自动恢复 -->
<select id="selectPausedWorkers" resultMap="UsersResult">
<include refid="selectUsersVo"/>
<where>
<!-- 查询条件:师傅类型且暂停状态 -->
type = '2'
AND is_stop = 1
AND prohibit_time IS NOT NULL
</where>
ORDER BY prohibit_time ASC
</select>
</mapper>

View File

@ -79,6 +79,7 @@
"sass-loader": "10.1.1",
"script-ext-html-webpack-plugin": "2.1.5",
"svg-sprite-loader": "5.1.1",
"vue-cli-service": "^5.0.10",
"vue-template-compiler": "2.6.12"
},
"engines": {

View File

@ -17,9 +17,6 @@ export function datalist(query) {
})
}
// 查询自定义地区详细
export function getDiyCity(id) {
return request({
@ -35,8 +32,6 @@ export function getTreeDataList() {
})
}
// 新增自定义地区
export function addDiyCity(data) {
return request({
@ -56,9 +51,17 @@ export function updateDiyCity(data) {
}
// 删除自定义地区
export function delDiyCity(id) {
export function delDiyCity(ids) {
return request({
url: '/system/DiyCity/' + id,
url: '/system/DiyCity/' + ids,
method: 'delete'
})
}
// 手动触发师傅暂停状态自动恢复
export function manualResumeWorkerStatus() {
return request({
url: '/system/DiyCity/manualResumeWorkerStatus',
method: 'post'
})
}

View File

@ -48,6 +48,15 @@ export function updateUsers(data) {
})
}
// 暂停接单
export function pauseOrder(data) {
return request({
url: '/system/users/pauseOrder',
method: 'put',
data: data
})
}
// 删除用户列表
export function delUsers(id) {
return request({

View File

@ -43,11 +43,10 @@
<el-form-item label="服务城市" prop="serviceCityPid">
<el-select
v-model="selectedCities"
v-model="selectedCity"
placeholder="请选择服务城市"
filterable
multiple
collapse-tags
clearable
style="width: 100%"
@change="handleCityChange">
<el-option
@ -57,17 +56,6 @@
:value="item.id">
</el-option>
</el-select>
<!-- 显示已选择的城市标签 -->
<div class="selected-tags" v-if="selectedCities.length > 0">
<el-tag
v-for="cityId in selectedCities"
:key="cityId"
closable
@close="removeCity(cityId)"
style="margin: 2px;">
{{ getCityName(cityId) }}
</el-tag>
</div>
</el-form-item>
<el-form-item label="服务地区" prop="serviceCityIds">
@ -76,7 +64,6 @@
placeholder="请选择服务地区"
filterable
multiple
collapse-tags
style="width: 100%"
@change="handleAreaChange">
<el-option
@ -86,7 +73,7 @@
:value="item.id">
</el-option>
</el-select>
<!-- 显示已选择的地区标签 -->
<!-- 显示已选择的地区标签
<div class="selected-tags" v-if="selectedAreas.length > 0">
<el-tag
v-for="areaId in selectedAreas"
@ -96,7 +83,7 @@
style="margin: 2px;">
{{ getAreaName(areaId) }}
</el-tag>
</div>
</div> -->
</el-form-item>
<el-form-item label="技能" prop="skillIds">
@ -116,7 +103,7 @@
:value="item.id"
/>
</el-select>
<!-- 显示已选择的技能标签 -->
<!-- 显示已选择的技能标签
<div class="selected-tags" v-if="selectedSkills.length > 0">
<el-tag
v-for="skillId in selectedSkills"
@ -128,13 +115,11 @@
>
{{ getSkillName(skillId) }}
</el-tag>
</div>
</div> -->
</div>
</el-form-item>
<el-form-item label="当前佣金" prop="commission">
<el-input v-model="form.commission" disabled />
</el-form-item>
<el-form-item label="状态">
<el-switch
@ -146,9 +131,7 @@
</el-switch>
</el-form-item>
<el-form-item label="创建时间" prop="createdAt">
<el-input v-model="form.createdAt" disabled />
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="resetForm">重置</el-button>
@ -212,7 +195,7 @@ export default {
},
//
cityList: [],
selectedCities: [],
selectedCity: undefined,
cityNameCache: {},
//
@ -223,7 +206,10 @@ export default {
//
skillList: [],
selectedSkills: [],
skillNameCache: {}
skillNameCache: {},
//
isInitializing: false
}
},
watch: {
@ -243,15 +229,6 @@ export default {
this.form.prohibitTimeNum = parseInt(this.form.prohibitTimeNum) || 0
}
// ID
this.processSelectedCities();
// ID
this.processSelectedAreas();
// ID
this.processSelectedSkills();
//
this.$nextTick(() => {
if (this.$refs["form"]) {
@ -265,14 +242,46 @@ export default {
visible(val) {
if (val) {
console.log('UserEditDialog - 弹窗打开,开始加载数据');
this.loadCityList();
this.loadSkillList();
this.isInitializing = true; //
//
Promise.all([
this.loadCityList(),
this.loadSkillList()
]).then(() => {
console.log('UserEditDialog - 基础数据加载完成,开始处理已选择的数据');
// ID
this.processSelectedCity();
// ID
this.processSelectedSkills();
//
if (this.selectedCity) {
this.loadAreaList().then(() => {
// ID
this.processSelectedAreas();
this.isInitializing = false; //
});
} else {
this.isInitializing = false; //
}
}).catch(() => {
this.isInitializing = false; // 使
});
} else {
//
this.resetDialogData();
}
},
selectedCities(val) {
this.form.serviceCityPid = val.join(',');
selectedCity(val) {
this.form.serviceCityPid = val;
this.updateCityNameCache();
this.loadAreaList();
//
if (!this.isInitializing && this.visible && this.cityList.length > 0) {
//
this.selectedAreas = [];
this.areaList = [];
this.loadAreaList();
}
},
selectedAreas(val) {
this.form.serviceCityIds = val.join(',');
@ -288,12 +297,13 @@ export default {
loadCityList() {
console.log('UserEditDialog - 开始加载城市列表');
const queryParams = {
parentId: 0 //
parentId: 0 //
}
datalist(queryParams).then(response => {
return datalist(queryParams).then(response => {
console.log('UserEditDialog - 获取城市列表成功:', response);
if (response.code === 200) {
this.cityList = response.rows || [];
// AjaxResult.success()data
this.cityList = response.data || [];
console.log('UserEditDialog - 城市列表:', this.cityList);
this.updateCityNameCache();
} else {
@ -305,6 +315,7 @@ export default {
{ id: 52, title: '安徽省' }
];
}
return response;
}).catch((error) => {
console.error('UserEditDialog - 获取城市列表异常:', error);
this.cityList = [
@ -313,82 +324,72 @@ export default {
{ id: 44, title: '湖南省' },
{ id: 52, title: '安徽省' }
];
return Promise.reject(error);
});
},
//
loadAreaList() {
console.log('UserEditDialog - 开始加载地区列表,选中的城市:', this.selectedCities);
console.log('UserEditDialog - 开始加载地区列表,选中的城市:', this.selectedCity);
this.areaList = [];
this.areaNameCache = {};
if (this.selectedCities.length === 0) {
return;
if (!this.selectedCity) {
return Promise.resolve(); //
}
//
this.selectedCities.forEach(cityId => {
const queryParams = {
parentId: cityId
//
const queryParams = {
parentId: this.selectedCity // ID
}
return datalist(queryParams).then(response => {
console.log(`UserEditDialog - 获取城市${this.selectedCity}的地区数据成功:`, response);
if (response.code === 200) {
// AjaxResult.success()data
this.areaList = response.data || [];
}
datalist(queryParams).then(response => {
console.log(`UserEditDialog - 获取城市${cityId}的地区数据成功:`, response);
if (response.code === 200) {
const newAreas = response.rows || [];
//
newAreas.forEach(area => {
const exists = this.areaList.find(item => item.id === area.id);
if (!exists) {
this.areaList.push(area);
}
});
console.log('UserEditDialog - 合并后的地区列表:', this.areaList);
this.updateAreaNameCache();
}
}).catch((error) => {
console.error(`UserEditDialog - 获取城市${cityId}的地区数据失败:`, error);
// 使
let defaultAreas = [];
if (cityId === 1) {
defaultAreas = [
{ id: 2, title: '新城区' },
{ id: 5, title: '碑林区' },
{ id: 7, title: '莲湖区' },
{ id: 10, title: '灞桥区' },
{ id: 11, title: '未央区' },
{ id: 12, title: '雁塔区' },
{ id: 13, title: '阎良区' },
{ id: 14, title: '临潼区' },
{ id: 15, title: '长安区' },
{ id: 16, title: '高陵区' },
{ id: 17, title: '鄠邑区' }
];
} else if (cityId === 52) {
defaultAreas = [
{ id: 53, title: '瑶海区' },
{ id: 54, title: '庐阳区' },
{ id: 55, title: '蜀山区' },
{ id: 56, title: '包河区' },
{ id: 57, title: '经开区' },
{ id: 58, title: '高新区' }
];
}
defaultAreas.forEach(area => {
const exists = this.areaList.find(item => item.id === area.id);
if (!exists) {
this.areaList.push(area);
}
});
this.updateAreaNameCache();
});
console.log('UserEditDialog - 地区数据加载完成,地区列表:', this.areaList);
this.updateAreaNameCache();
return response;
}).catch((error) => {
console.error(`UserEditDialog - 获取城市${this.selectedCity}的地区数据失败:`, error);
// 使
let defaultAreas = [];
if (this.selectedCity === 1) {
defaultAreas = [
{ id: 2, title: '新城区' },
{ id: 5, title: '碑林区' },
{ id: 7, title: '莲湖区' },
{ id: 10, title: '灞桥区' },
{ id: 11, title: '未央区' },
{ id: 12, title: '雁塔区' },
{ id: 13, title: '阎良区' },
{ id: 14, title: '临潼区' },
{ id: 15, title: '长安区' },
{ id: 16, title: '高陵区' },
{ id: 17, title: '鄠邑区' }
];
} else if (this.selectedCity === 52) {
defaultAreas = [
{ id: 53, title: '瑶海区' },
{ id: 54, title: '庐阳区' },
{ id: 55, title: '蜀山区' },
{ id: 56, title: '包河区' },
{ id: 57, title: '经开区' },
{ id: 58, title: '高新区' }
];
}
this.areaList = defaultAreas;
this.updateAreaNameCache();
return Promise.resolve(); // 使Promise
});
},
//
loadSkillList() {
console.log('UserEditDialog - 开始加载技能列表');
getSiteSkillList().then(response => {
return getSiteSkillList().then(response => {
console.log('UserEditDialog - 获取技能列表成功:', response);
if (response.code === 200) {
this.skillList = response.data || [];
@ -403,6 +404,7 @@ export default {
{ id: 4, title: '工程施工' }
];
}
return response;
}).catch((error) => {
console.error('UserEditDialog - 获取技能列表异常:', error);
this.skillList = [
@ -411,99 +413,134 @@ export default {
{ id: 3, title: '改造维修' },
{ id: 4, title: '工程施工' }
];
return Promise.reject(error);
});
},
//
processSelectedCities() {
processSelectedCity() {
console.log('开始处理服务城市数据:', this.form.serviceCityPid, '类型:', typeof this.form.serviceCityPid);
this.selectedCity = undefined; //
if (this.form.serviceCityPid) {
console.log('处理服务城市数据:', this.form.serviceCityPid, '类型:', typeof this.form.serviceCityPid);
try {
let cityId = this.form.serviceCityPid;
if (typeof this.form.serviceCityPid === 'string') {
if (this.form.serviceCityPid.startsWith('[') && this.form.serviceCityPid.endsWith(']')) {
this.selectedCities = JSON.parse(this.form.serviceCityPid).map(Number).filter(n => !isNaN(n));
const parsed = JSON.parse(this.form.serviceCityPid);
cityId = Array.isArray(parsed) ? parsed[0] : parsed;
} else if (this.form.serviceCityPid.includes(',')) {
//
cityId = this.form.serviceCityPid.split(',')[0].trim();
} else {
this.selectedCities = this.form.serviceCityPid.split(',').map(Number).filter(n => !isNaN(n));
cityId = this.form.serviceCityPid.trim();
}
} else if (Array.isArray(this.form.serviceCityPid)) {
this.selectedCities = this.form.serviceCityPid.map(Number).filter(n => !isNaN(n));
} else {
this.selectedCities = [];
cityId = this.form.serviceCityPid[0];
} else if (typeof this.form.serviceCityPid === 'number') {
cityId = this.form.serviceCityPid;
}
//
const parsedId = parseInt(cityId);
if (!isNaN(parsedId) && parsedId > 0) {
this.selectedCity = parsedId;
console.log('解析后的服务城市:', this.selectedCity);
//
this.$nextTick(() => {
this.updateCityNameCache();
});
}
console.log('解析后的服务城市:', this.selectedCities);
} catch (error) {
console.error('解析服务城市数据失败:', error);
this.selectedCities = [];
this.selectedCity = undefined;
}
} else {
this.selectedCities = [];
}
},
//
processSelectedAreas() {
console.log('开始处理服务地区数据:', this.form.serviceCityIds, '类型:', typeof this.form.serviceCityIds);
this.selectedAreas = []; //
if (this.form.serviceCityIds) {
console.log('处理服务地区数据:', this.form.serviceCityIds, '类型:', typeof this.form.serviceCityIds);
try {
let areaIds = [];
if (typeof this.form.serviceCityIds === 'string') {
if (this.form.serviceCityIds.startsWith('[') && this.form.serviceCityIds.endsWith(']')) {
this.selectedAreas = JSON.parse(this.form.serviceCityIds).map(Number).filter(n => !isNaN(n));
areaIds = JSON.parse(this.form.serviceCityIds);
} else {
this.selectedAreas = this.form.serviceCityIds.split(',').map(Number).filter(n => !isNaN(n));
areaIds = this.form.serviceCityIds.split(',').map(id => id.trim()).filter(id => id);
}
} else if (Array.isArray(this.form.serviceCityIds)) {
this.selectedAreas = this.form.serviceCityIds.map(Number).filter(n => !isNaN(n));
} else {
this.selectedAreas = [];
areaIds = this.form.serviceCityIds;
} else if (typeof this.form.serviceCityIds === 'number') {
areaIds = [this.form.serviceCityIds];
}
//
this.selectedAreas = areaIds.map(id => parseInt(id)).filter(id => !isNaN(id) && id > 0);
console.log('解析后的服务地区:', this.selectedAreas);
//
this.$nextTick(() => {
this.updateAreaNameCache();
});
} catch (error) {
console.error('解析服务地区数据失败:', error);
this.selectedAreas = [];
}
} else {
this.selectedAreas = [];
}
},
//
processSelectedSkills() {
console.log('开始处理技能数据:', this.form.skillIds, '类型:', typeof this.form.skillIds);
this.selectedSkills = []; //
if (this.form.skillIds) {
console.log('处理技能数据:', this.form.skillIds, '类型:', typeof this.form.skillIds);
try {
let skillIds = [];
if (typeof this.form.skillIds === 'string') {
if (this.form.skillIds.startsWith('[') && this.form.skillIds.endsWith(']')) {
this.selectedSkills = JSON.parse(this.form.skillIds).map(Number).filter(n => !isNaN(n));
skillIds = JSON.parse(this.form.skillIds);
} else {
this.selectedSkills = this.form.skillIds.split(',').map(Number).filter(n => !isNaN(n));
skillIds = this.form.skillIds.split(',').map(id => id.trim()).filter(id => id);
}
} else if (Array.isArray(this.form.skillIds)) {
this.selectedSkills = this.form.skillIds.map(Number).filter(n => !isNaN(n));
} else {
this.selectedSkills = [];
skillIds = this.form.skillIds;
} else if (typeof this.form.skillIds === 'number') {
skillIds = [this.form.skillIds];
}
//
this.selectedSkills = skillIds.map(id => parseInt(id)).filter(id => !isNaN(id) && id > 0);
console.log('解析后的技能:', this.selectedSkills);
//
this.$nextTick(() => {
this.updateSkillNameCache();
});
} catch (error) {
console.error('解析技能数据失败:', error);
this.selectedSkills = [];
}
} else {
this.selectedSkills = [];
}
},
//
updateCityNameCache() {
console.log('更新城市名称缓存,已选择城市:', this.selectedCities, '城市列表:', this.cityList);
console.log('更新城市名称缓存,已选择城市:', this.selectedCity, '城市列表:', this.cityList);
this.cityNameCache = {};
this.selectedCities.forEach(cityId => {
const city = this.cityList.find(item => item.id === cityId);
if (this.selectedCity) {
const city = this.cityList.find(item => item.id === this.selectedCity);
if (city) {
this.cityNameCache[cityId] = city.title;
this.cityNameCache[this.selectedCity] = city.title;
} else {
console.warn('未找到城市ID:', cityId, '对应的名称');
console.warn('未找到城市ID:', this.selectedCity, '对应的名称');
}
});
}
console.log('城市名称缓存:', this.cityNameCache);
},
@ -554,10 +591,7 @@ export default {
//
removeCity(cityId) {
const index = this.selectedCities.indexOf(cityId);
if (index > -1) {
this.selectedCities.splice(index, 1);
}
this.selectedCity = undefined;
},
//
@ -578,10 +612,17 @@ export default {
//
handleCityChange() {
console.log('城市选择变化:', this.selectedCities);
console.log('城市选择变化:', this.selectedCity);
this.updateCityNameCache();
//
this.selectedAreas = [];
this.areaList = [];
//
this.loadAreaList();
if (this.selectedCity) {
this.loadAreaList();
}
},
//
@ -626,18 +667,27 @@ export default {
})
},
//
resetDialogData() {
this.isInitializing = false;
this.cityList = [];
this.selectedCity = undefined;
this.cityNameCache = {};
this.areaList = [];
this.selectedAreas = [];
this.areaNameCache = {};
this.skillList = [];
this.selectedSkills = [];
this.skillNameCache = {};
},
//
resetForm() {
// id
const id = this.form.id
const createdAt = this.form.createdAt
this.$refs["form"].resetFields()
this.selectedCities = []
this.selectedAreas = []
this.selectedSkills = []
this.cityNameCache = {}
this.areaNameCache = {}
this.skillNameCache = {}
this.resetDialogData()
this.form = {
id: id,
name: undefined,

View File

@ -160,6 +160,16 @@
v-hasPermi="['system:users:remove']"
>删除</el-button>
</el-col>
<el-col :span="1.5">
<el-button
type="warning"
plain
icon="el-icon-refresh"
size="mini"
@click="handleManualResumeWorkerStatus"
v-hasPermi="['system:users:edit']"
>恢复过期暂停</el-button>
</el-col>
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@ -197,17 +207,31 @@
<span>{{ parseTime(scope.row.workerTime, '{y}-{m}-{d}') }}</span>
</template>
</el-table-column>
<el-table-column label="等级" align="center" prop="level">
<el-table-column label="等级" align="center" prop="level" width="100">
<template slot-scope="scope">
<el-select v-model="scope.row.level" @change="handleRowClick(scope.row)">
<el-option
v-for="item in levelList"
:key="item.id"
:label="item.title"
:value="item.id"
></el-option>
</el-select>
<!-- <el-link type="primary" @click="showWorkerLevelDialog(scope.row)">{{ scope.row.level }}</el-link>-->
<div class="level-container">
<el-select
v-model="scope.row.level"
@change="handleRowClick(scope.row)"
size="small"
class="level-select-custom"
:class="'level-' + scope.row.level"
placeholder="选择等级"
>
<el-option
v-for="item in levelList"
:key="item.id"
:label="item.title"
:value="item.id"
class="level-option-item"
>
<div class="level-option-wrapper">
<span class="level-text">{{ item.title }}</span>
<span class="level-number">{{ item.id }}</span>
</div>
</el-option>
</el-select>
</div>
</template>
</el-table-column>
<el-table-column label="当前佣金" align="center" prop="commission" />
@ -230,6 +254,27 @@
</template>
</el-table-column>
<el-table-column label="累计提现" align="center" prop="propose" />
<el-table-column label="接单状态" align="center" prop="isStop" width="100">
<template slot-scope="scope">
<el-tag v-if="scope.row.isStop === 1 && !scope.row.isExpired" type="danger">已暂停</el-tag>
<el-tag v-else-if="scope.row.isStop === 1 && scope.row.isExpired" type="warning">已过期</el-tag>
<el-tag v-else type="success">正常</el-tag>
</template>
</el-table-column>
<el-table-column label="暂停时长" align="center" prop="prohibitTimeNum" width="80">
<template slot-scope="scope">
<span v-if="scope.row.isStop === 1 && scope.row.prohibitTimeNum">{{ scope.row.prohibitTimeNum }}小时</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="暂停到期时间" align="center" prop="prohibitTime" width="150">
<template slot-scope="scope">
<span v-if="scope.row.isStop === 1 && scope.row.prohibitTime" :class="{'expired-time': scope.row.isExpired}">
{{ parseTime(scope.row.prohibitTime, '{y}-{m}-{d} {h}:{i}') }}
</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column label="服务区域" align="center" prop="serviceCityIds" width="150">
<template slot-scope="scope">
<span v-if="scope.row.serviceCityIds">{{ formatServiceAreas(scope.row.serviceCityIds) }}</span>
@ -251,6 +296,33 @@
@click="handleUpdate(scope.row)"
v-hasPermi="['system:users:edit']"
>修改</el-button>
<el-button
v-if="scope.row.isStop !== 1"
size="mini"
type="text"
icon="el-icon-video-pause"
@click="handlePauseOrder(scope.row)"
v-hasPermi="['system:users:edit']"
style="color: #E6A23C;"
>暂停接单</el-button>
<el-button
v-else-if="scope.row.isExpired"
size="mini"
type="text"
icon="el-icon-refresh"
@click="handleResumeOrder(scope.row)"
v-hasPermi="['system:users:edit']"
style="color: #E6A23C;"
>恢复过期</el-button>
<el-button
v-else
size="mini"
type="text"
icon="el-icon-video-play"
@click="handleResumeOrder(scope.row)"
v-hasPermi="['system:users:edit']"
style="color: #67C23A;"
>恢复接单</el-button>
<el-button
size="mini"
type="text"
@ -304,13 +376,53 @@
:user-name="workerLevelUserName"
@level-selected="handleWorkerLevelSelected"
/>
<!-- 暂停接单弹窗 -->
<el-dialog title="暂停接单" :visible.sync="pauseOrderDialogVisible" width="500px" append-to-body>
<el-form ref="pauseOrderForm" :model="pauseOrderForm" :rules="pauseOrderRules" label-width="100px">
<el-form-item label="师傅姓名">
<el-input v-model="pauseOrderForm.name" disabled />
</el-form-item>
<el-form-item label="暂停时长" prop="prohibitTimeNum">
<div class="time-input">
<el-button icon="el-icon-minus" @click="decreasePauseTime" size="small"></el-button>
<el-input-number
v-model="pauseOrderForm.prohibitTimeNum"
:min="1"
:max="168"
:controls="false"
placeholder="1"
style="width: 100px; text-align: center;" />
<el-button icon="el-icon-plus" @click="increasePauseTime" size="small"></el-button>
<span style="margin-left: 10px;">小时</span>
</div>
<div style="margin-top: 5px; color: #909399; font-size: 12px;">
暂停时长范围1-168小时7
</div>
</el-form-item>
<el-form-item label="暂停原因" prop="reason">
<el-input
v-model="pauseOrderForm.reason"
type="textarea"
:rows="3"
placeholder="请输入暂停接单原因(可选)"
maxlength="200"
show-word-limit
/>
</el-form-item>
</el-form>
<div slot="footer" class="dialog-footer">
<el-button @click="pauseOrderDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmPauseOrder" :loading="pauseOrderLoading">确认暂停</el-button>
</div>
</el-dialog>
</div>
</template>
<script>
import { listUsers, getUsers, delUsers, addUsers, updateUsers,getUserDataList,changetypeStatus } from "@/api/system/users"
import { listUsers, getUsers, delUsers, addUsers, updateUsers,getUserDataList,changetypeStatus,pauseOrder } from "@/api/system/users"
import { listWorkerLevel } from '@/api/system/WorkerLevel'
import { selectAreaList } from "@/api/system/WorkerApply"
import { datalist, getDiyCity, manualResumeWorkerStatus } from "@/api/system/DiyCity"
import { getSiteSkillList } from "@/api/system/SiteSkill"
import UserEditDialog from './UserEditDialog.vue'
import WorkerMoneyLogTable from '@/views/system/workerMoneyLog/WorkerMoneyLogTable.vue'
@ -388,7 +500,20 @@ export default {
//
areaDataCache: {},
//
skillDataCache: {}
skillDataCache: {},
//
pauseOrderDialogVisible: false,
pauseOrderForm: {
name: '',
prohibitTimeNum: 1,
reason: ''
},
pauseOrderRules: {
prohibitTimeNum: [
{ required: true, message: '请选择暂停时长', trigger: 'change' }
]
},
pauseOrderLoading: false
}
},
created() {
@ -396,15 +521,23 @@ export default {
this.getlevelList();
this.initAreaDataCache();
this.initSkillDataCache();
//
console.log('UsersWorker - 组件创建完成');
},
mounted() {
// 便
this.$nextTick(() => {
//
//
console.log('UsersWorker - 组件挂载,立即初始化缓存');
this.initAreaDataCache();
this.initSkillDataCache();
//
setTimeout(() => {
console.log('UsersWorker - 缓存初始化完成,重新获取列表数据');
console.log('UsersWorker - 缓存初始化时间结束,重新获取列表数据');
this.getList();
}, 1000);
}, 2000);
});
},
methods: {
@ -415,6 +548,15 @@ export default {
this.usersList = response.rows
this.total = response.total
this.loading = false
//
this.checkExpiredPauseStatus();
//
console.log('UsersWorker - 获取到的用户列表:', this.usersList);
if (this.usersList.length > 0) {
console.log('第一个用户的服务地区数据:', this.usersList[0].serviceCityIds, '类型:', typeof this.usersList[0].serviceCityIds);
}
})
},
//
@ -482,10 +624,13 @@ export default {
this.multiple = !selection.length
},
handleRowClick(row) {
updateUsers(row).then(() => {
this.$message.success('修改成功')
this.$message.success('等级修改成功')
}).catch(error => {
console.error('等级修改失败:', error);
this.$message.error('等级修改失败,请重试');
//
this.getList()
})
},
@ -624,15 +769,20 @@ export default {
try {
areaIds = JSON.parse(serviceCityIds);
} catch (e) {
areaIds = serviceCityIds.split(',').map(id => id.trim()).filter(id => id);
console.warn('JSON解析失败使用逗号分隔:', e);
areaIds = serviceCityIds.replace(/[\[\]]/g, '').split(',').map(id => id.trim()).filter(id => id);
}
} else {
areaIds = serviceCityIds.split(',').map(id => id.trim()).filter(id => id);
}
} else if (Array.isArray(serviceCityIds)) {
areaIds = serviceCityIds;
} else {
areaIds = [serviceCityIds];
}
//
areaIds = areaIds.map(id => parseInt(id)).filter(id => !isNaN(id) && id > 0);
console.log('解析后的地区ID数组:', areaIds);
console.log('地区数据缓存:', this.areaDataCache);
@ -642,7 +792,13 @@ export default {
const areaNames = areaIds.map(id => {
const cached = this.areaDataCache[id];
console.log(`地区ID ${id} 对应的缓存数据:`, cached);
return cached ? cached.title : id;
if (cached) {
return cached.title;
} else {
//
this.loadAreaById(id);
return `区域${id}`; //
}
});
const result = areaNames.join(', ');
@ -699,83 +855,87 @@ export default {
//
initAreaDataCache() {
console.log('开始初始化地区数据缓存');
// IDAPI
const commonAreas = [
// 西
{ id: 2, title: "新城区" },
{ id: 5, title: "碑林区" },
{ id: 7, title: "莲湖区" },
{ id: 10, title: "灞桥区" },
{ id: 11, title: "未央区" },
{ id: 12, title: "雁塔区" },
{ id: 13, title: "阎良区" },
{ id: 14, title: "临潼区" },
{ id: 15, title: "长安区" },
{ id: 16, title: "高陵区" },
{ id: 17, title: "鄠邑区" },
//
{ id: 53, title: "瑶海区" },
{ id: 54, title: "庐阳区" },
{ id: 55, title: "蜀山区" },
{ id: 56, title: "包河区" },
{ id: 57, title: "经开区" },
{ id: 58, title: "高新区" }
];
commonAreas.forEach(area => {
this.areaDataCache[area.id] = area;
console.log(`添加常见地区到缓存: ${area.id} -> ${area.title}`);
});
//
selectAreaList("100000").then(response => {
const queryParams = {
parentId: 0 //
}
datalist(queryParams).then(response => {
console.log('获取省份数据成功:', response);
if (response.data) {
if (response.code === 200 && response.data) {
//
response.data.forEach(province => {
this.areaDataCache[province.id] = province;
console.log(`添加省份到缓存: ${province.id} -> ${province.title}`);
//
selectAreaList(province.id).then(cityResponse => {
});
//
const loadPromises = response.data.map(province => {
const cityParams = {
parentId: province.id
}
return datalist(cityParams).then(cityResponse => {
console.log(`获取城市数据成功 (${province.id}):`, cityResponse);
if (cityResponse.data) {
if (cityResponse.code === 200 && cityResponse.data) {
cityResponse.data.forEach(city => {
this.areaDataCache[city.id] = city;
console.log(`添加城市到缓存: ${city.id} -> ${city.title}`);
});
}
return cityResponse;
}).catch((error) => {
console.error(`获取城市数据失败 (${province.id}):`, error);
// 使
if (province.id === "610100") {
const defaultCities = [
{ id: "610102", title: "新城区" },
{ id: "610103", title: "碑林区" },
{ id: "610104", title: "莲湖区" },
{ id: "610111", title: "灞桥区" },
{ id: "610112", title: "未央区" },
{ id: "610113", title: "雁塔区" },
{ id: "610114", title: "阎良区" },
{ id: "610115", title: "临潼区" },
{ id: "610116", title: "长安区" },
{ id: "610117", title: "高陵区" }
];
defaultCities.forEach(city => {
this.areaDataCache[city.id] = city;
console.log(`添加默认城市到缓存: ${city.id} -> ${city.title}`);
});
}
return Promise.resolve();
});
});
//
Promise.all(loadPromises).then(() => {
console.log('所有地区数据加载完成,最终缓存:', this.areaDataCache);
//
this.$forceUpdate();
});
}
}).catch((error) => {
console.error('获取省份数据失败:', error);
// 使
const defaultProvinces = [
{ id: "610100", title: "西安市" },
{ id: "610200", title: "铜川市" },
{ id: "610300", title: "宝鸡市" }
{ id: 1, title: "陕西省" },
{ id: 27, title: "上海市" },
{ id: 44, title: "湖南省" },
{ id: 52, title: "安徽省" }
];
defaultProvinces.forEach(province => {
this.areaDataCache[province.id] = province;
console.log(`添加默认省份到缓存: ${province.id} -> ${province.title}`);
});
// ID
const commonAreas = [
{ id: "1", title: "北京市" },
{ id: "2", title: "上海市" },
{ id: "3", title: "广州市" },
{ id: "4", title: "深圳市" },
{ id: "5", title: "杭州市" },
{ id: "6", title: "南京市" },
{ id: "7", title: "武汉市" },
{ id: "8", title: "成都市" },
{ id: "9", title: "西安市" },
{ id: "10", title: "重庆市" },
{ id: "11", title: "天津市" },
{ id: "12", title: "苏州市" },
{ id: "13", title: "无锡市" },
{ id: "14", title: "宁波市" },
{ id: "15", title: "青岛市" },
{ id: "16", title: "大连市" },
{ id: "17", title: "厦门市" }
];
commonAreas.forEach(area => {
this.areaDataCache[area.id] = area;
console.log(`添加常见地区到缓存: ${area.id} -> ${area.title}`);
});
});
},
//
@ -803,7 +963,406 @@ export default {
console.log(`添加默认技能到缓存: ${skill.id} -> ${skill.title}`);
});
});
},
// ID
loadAreaById(id) {
if (this.areaDataCache[id]) {
return; //
}
// 使getDiyCity APIID
getDiyCity(id).then(response => {
if (response.code === 200 && response.data) {
this.areaDataCache[id] = response.data;
console.log(`从API加载地区到缓存: ${id} -> ${response.data.title}`);
//
this.$forceUpdate();
} else {
console.warn(`API加载地区失败ID: ${id}, 响应:`, response);
}
}).catch((error) => {
console.error(`API加载地区失败ID: ${id}:`, error);
});
},
//
handlePauseOrder(row) {
console.log('点击暂停接单,师傅信息:', row);
if (!row || !row.id) {
this.$message.error('师傅信息不完整,无法暂停接单');
return;
}
//
this.pauseOrderForm = {
id: row.id,
name: row.name,
prohibitTimeNum: 1,
reason: ''
};
//
this.$nextTick(() => {
if (this.$refs.pauseOrderForm) {
this.$refs.pauseOrderForm.clearValidate();
}
});
this.pauseOrderDialogVisible = true;
},
//
decreasePauseTime() {
if (this.pauseOrderForm.prohibitTimeNum > 1) {
this.pauseOrderForm.prohibitTimeNum--;
}
},
//
increasePauseTime() {
if (this.pauseOrderForm.prohibitTimeNum < 168) {
this.pauseOrderForm.prohibitTimeNum++;
}
},
//
confirmPauseOrder() {
this.$refs.pauseOrderForm.validate(valid => {
if (valid) {
this.pauseOrderLoading = true;
const pauseData = {
id: this.pauseOrderForm.id,
prohibitTimeNum: this.pauseOrderForm.prohibitTimeNum
};
pauseOrder(pauseData).then(response => {
this.$modal.msgSuccess(`已成功暂停师傅"${this.pauseOrderForm.name}"接单${this.pauseOrderForm.prohibitTimeNum}小时`);
this.pauseOrderDialogVisible = false;
this.getList(); //
}).catch(error => {
console.error('暂停接单失败:', error);
this.$modal.msgError('暂停接单失败,请重试');
}).finally(() => {
this.pauseOrderLoading = false;
});
}
});
},
//
handleResumeOrder(row) {
console.log('点击恢复接单,师傅信息:', row);
if (!row || !row.id) {
this.$message.error('师傅信息不完整,无法恢复接单');
return;
}
this.$modal.confirm(`确认要恢复师傅"${row.name}"的接单状态吗?`).then(() => {
this.resumeOrder(row.id, row.name);
}).catch(() => {
console.log('用户取消恢复接单操作');
});
},
//
resumeOrder(userId, userName) {
const resumeData = {
id: userId,
prohibitTimeNum: 0,
isStop: 0
};
updateUsers(resumeData).then(response => {
this.$modal.msgSuccess(`已成功恢复师傅"${userName}"的接单状态`);
this.getList(); //
}).catch(error => {
console.error('恢复接单失败:', error);
this.$modal.msgError('恢复接单失败,请重试');
});
},
//
checkExpiredPauseStatus() {
const now = new Date();
let hasExpired = false;
this.usersList.forEach(user => {
if (user.isStop === 1 && user.prohibitTime) {
const prohibitTime = new Date(user.prohibitTime);
if (prohibitTime < now) {
//
user.isExpired = true;
hasExpired = true;
console.log(`师傅"${user.name}"的暂停时间已过期`);
}
}
});
//
if (hasExpired) {
this.$message.info('检测到部分师傅的暂停时间已过期,请手动恢复接单状态。');
}
},
//
updateUserStatus(id, status) {
changetypeStatus(id, status).then(() => {
this.$message.success(`用户状态更新成功`);
this.getList(); //
}).catch(error => {
console.error('更新用户状态失败:', error);
this.$message.error('更新用户状态失败,请重试');
});
},
//
handleManualResumeWorkerStatus() {
this.$modal.confirm('确认要执行师傅暂停状态自动恢复任务吗?此操作将检查所有暂停的师傅,并自动恢复已过期的师傅接单状态。').then(() => {
this.executeManualResumeWorkerStatus();
}).catch(() => {
console.log('用户取消手动恢复过期暂停操作');
});
},
//
executeManualResumeWorkerStatus() {
//
const loading = this.$loading({
lock: true,
text: '正在执行师傅暂停状态恢复任务...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
manualResumeWorkerStatus().then(response => {
loading.close();
if (response.code === 200) {
this.$message.success(response.msg || '师傅暂停状态自动恢复任务执行成功');
//
this.getList();
} else {
this.$message.error(response.msg || '师傅暂停状态自动恢复任务执行失败');
}
}).catch(error => {
loading.close();
console.error('执行师傅暂停状态自动恢复失败:', error);
this.$message.error('执行师傅暂停状态自动恢复失败,请重试');
});
},
//
resumeAllExpiredWorkers() {
const expiredUsers = this.usersList.filter(user => user.isStop === 1 && user.isExpired);
if (expiredUsers.length === 0) {
this.$message.info('没有过期暂停的师傅。');
return;
}
const userIds = expiredUsers.map(user => user.id);
const userNames = expiredUsers.map(user => user.name);
this.$modal.confirm(`确认要恢复以下${userIds.length}个师傅的接单状态吗?\n${userNames.join('、')}`).then(() => {
const resumePromises = userIds.map(id => {
const resumeData = {
id: id,
prohibitTimeNum: 0,
isStop: 0
};
return updateUsers(resumeData);
});
Promise.all(resumePromises).then(() => {
this.$message.success(`已成功恢复${userIds.length}个师傅的接单状态。`);
this.getList(); //
}).catch(error => {
console.error('批量恢复过期暂停失败:', error);
this.$message.error('批量恢复过期暂停失败,请重试');
});
}).catch(() => {
console.log('用户取消批量恢复过期暂停操作');
});
}
}
}
</script>
<style scoped>
.time-input {
display: flex;
align-items: center;
}
.selected-tags {
margin-top: 8px;
max-width: 100%;
overflow: hidden;
}
.selected-tags .el-tag {
margin: 2px;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.time-input .el-button {
border-radius: 4px;
margin: 0 5px;
}
.time-input .el-input-number {
margin: 0 5px;
}
.expired-time {
color: #E6A23C;
font-weight: bold;
text-decoration: line-through;
}
/* 等级选择器样式 */
.level-container {
display: flex;
justify-content: center;
align-items: center;
}
.level-select-custom {
width: 80px !important;
font-weight: 600;
border-radius: 20px;
transition: all 0.3s ease;
border-width: 2px;
}
.level-select-custom:hover {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.level-select-custom:focus-within {
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
/* 不同等级的颜色样式 */
.level-1 {
background-color: #f8f9fa;
border-color: #6c757d;
color: #495057;
}
.level-1:hover {
border-color: #5a6268;
background-color: #f1f3f4;
}
.level-2 {
background-color: #e8f5e8;
border-color: #28a745;
color: #155724;
}
.level-2:hover {
border-color: #1e7e34;
background-color: #d1ecf1;
}
.level-3 {
background-color: #e3f2fd;
border-color: #2196f3;
color: #0d47a1;
}
.level-3:hover {
border-color: #1976d2;
background-color: #bbdefb;
}
.level-4 {
background-color: #fff3e0;
border-color: #ff9800;
color: #e65100;
}
.level-4:hover {
border-color: #f57c00;
background-color: #ffe0b2;
}
.level-5 {
background-color: #ffebee;
border-color: #f44336;
color: #b71c1c;
}
.level-5:hover {
border-color: #d32f2f;
background-color: #ffcdd2;
}
/* 下拉选项样式 */
.level-option-item {
padding: 8px 16px !important;
border-radius: 6px;
margin: 2px 4px;
transition: all 0.2s ease;
}
.level-option-item:hover {
background-color: #f8f9fa !important;
transform: translateX(4px);
}
.level-option-wrapper {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
}
.level-text {
font-weight: 500;
color: #333;
}
.level-number {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 50%;
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: bold;
margin-left: 8px;
}
/* 选择器下拉箭头样式优化 */
.level-select-custom .el-input__suffix {
right: 8px;
}
.level-select-custom .el-input__suffix .el-input__suffix-inner .el-select__caret {
color: inherit;
font-size: 14px;
transition: transform 0.3s ease;
}
.level-select-custom.is-focus .el-select__caret {
transform: rotateZ(180deg);
}
/* 响应式设计 */
@media (max-width: 768px) {
.level-select-custom {
width: 70px !important;
font-size: 12px;
}
.level-number {
width: 16px;
height: 16px;
font-size: 10px;
}
}
</style>