Merge branch 'main' of http://49.232.154.205:10100/liujingchao/fad_oa into main
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
package com.ruoyi.fadapp.controller;
|
||||||
|
|
||||||
|
import com.ruoyi.common.core.domain.R;
|
||||||
|
import com.ruoyi.fadapp.service.IFadAppLocationService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FAD APP 定位控制器
|
||||||
|
*/
|
||||||
|
@Validated
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/fadapp/location")
|
||||||
|
public class FadAppLocationController {
|
||||||
|
|
||||||
|
private final IFadAppLocationService locationService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据经纬度获取城市
|
||||||
|
*
|
||||||
|
* @param latitude 纬度
|
||||||
|
* @param longitude 经度
|
||||||
|
* @return 城市名称
|
||||||
|
*/
|
||||||
|
@GetMapping("/city")
|
||||||
|
public R<String> getCity(@RequestParam Double latitude, @RequestParam Double longitude) {
|
||||||
|
return R.ok(locationService.getCityByLocation(latitude, longitude));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,9 +11,18 @@ import org.springframework.validation.annotation.Validated;
|
|||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PathVariable;
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import javax.validation.constraints.NotNull;
|
import javax.validation.constraints.NotNull;
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FAD APP用户管理Controller
|
* FAD APP用户管理Controller
|
||||||
@@ -26,6 +35,7 @@ import javax.validation.constraints.NotNull;
|
|||||||
@RequestMapping("/fadapp/user")
|
@RequestMapping("/fadapp/user")
|
||||||
public class FadAppUserController extends BaseController {
|
public class FadAppUserController extends BaseController {
|
||||||
|
|
||||||
|
private static final String IPSTACK_ACCESS_KEY = "";
|
||||||
private final IFadAppUserService userService;
|
private final IFadAppUserService userService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -63,4 +73,56 @@ public class FadAppUserController extends BaseController {
|
|||||||
public R<Object> getUserStats() {
|
public R<Object> getUserStats() {
|
||||||
return R.ok(userService.getUserStats());
|
return R.ok(userService.getUserStats());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据经纬度获取城市
|
||||||
|
*/
|
||||||
|
@GetMapping("/location/city")
|
||||||
|
public R<Object> getCityByLocation(@RequestParam Double latitude,
|
||||||
|
@RequestParam Double longitude) {
|
||||||
|
return R.ok(resolveCity(latitude, longitude));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveCity(Double latitude, Double longitude) {
|
||||||
|
if (latitude == null || longitude == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
String urlStr = "https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat="
|
||||||
|
+ URLEncoder.encode(String.valueOf(latitude), StandardCharsets.UTF_8.name())
|
||||||
|
+ "&lon=" + URLEncoder.encode(String.valueOf(longitude), StandardCharsets.UTF_8.name())
|
||||||
|
+ "&zoom=10&addressdetails=1";
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) new URL(urlStr).openConnection();
|
||||||
|
connection.setRequestMethod("GET");
|
||||||
|
connection.setConnectTimeout(5000);
|
||||||
|
connection.setReadTimeout(5000);
|
||||||
|
connection.setRequestProperty("User-Agent", "FAD-App/1.0");
|
||||||
|
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
|
||||||
|
StringBuilder result = new StringBuilder();
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
result.append(line);
|
||||||
|
}
|
||||||
|
return extractCity(result.toString());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractCity(String json) {
|
||||||
|
if (json == null || json.isEmpty()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
String[] keys = new String[]{"city", "town", "village", "county", "state"};
|
||||||
|
for (String key : keys) {
|
||||||
|
Pattern pattern = Pattern.compile("\\\"" + key + "\\\"\\s*:\\s*\\\"([^\\\"]+)\\\"");
|
||||||
|
Matcher matcher = pattern.matcher(json);
|
||||||
|
if (matcher.find()) {
|
||||||
|
return matcher.group(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.ruoyi.fadapp.service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FAD APP 定位服务接口
|
||||||
|
*/
|
||||||
|
public interface IFadAppLocationService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据经纬度获取城市
|
||||||
|
*
|
||||||
|
* @param latitude 纬度
|
||||||
|
* @param longitude 经度
|
||||||
|
* @return 城市名称
|
||||||
|
*/
|
||||||
|
String getCityByLocation(Double latitude, Double longitude);
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package com.ruoyi.fadapp.service.impl;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
|
import com.ruoyi.fadapp.service.IFadAppLocationService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FAD APP 定位服务实现
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
public class FadAppLocationServiceImpl implements IFadAppLocationService {
|
||||||
|
|
||||||
|
private static final String AMAP_REVERSE_URL = "https://restapi.amap.com/v3/geocode/regeo";
|
||||||
|
|
||||||
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
|
@Value("${fad.amap.key:}")
|
||||||
|
private String amapKey;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCityByLocation(Double latitude, Double longitude) {
|
||||||
|
if (latitude == null || longitude == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!org.springframework.util.StringUtils.hasText(amapKey)) {
|
||||||
|
log.warn("高德地图 key 未配置");
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Map<String, Object> params = new LinkedHashMap<>();
|
||||||
|
params.put("key", amapKey);
|
||||||
|
params.put("location", longitude + "," + latitude);
|
||||||
|
params.put("extensions", "base");
|
||||||
|
params.put("batch", "false");
|
||||||
|
params.put("roadlevel", 0);
|
||||||
|
params.put("output", "JSON");
|
||||||
|
|
||||||
|
StringBuilder url = new StringBuilder(AMAP_REVERSE_URL).append("?");
|
||||||
|
params.forEach((key, value) -> url.append(key).append("=").append(value).append("&"));
|
||||||
|
url.setLength(url.length() - 1);
|
||||||
|
|
||||||
|
JSONObject response = restTemplate.getForObject(url.toString(), JSONObject.class);
|
||||||
|
if (response == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!"1".equals(response.getString("status"))) {
|
||||||
|
log.warn("高德逆地理编码失败: {}", response);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject regeocode = response.getJSONObject("regeocode");
|
||||||
|
if (regeocode == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
JSONObject addressComponent = regeocode.getJSONObject("addressComponent");
|
||||||
|
if (addressComponent == null) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
String city = addressComponent.getString("city");
|
||||||
|
if (org.springframework.util.StringUtils.hasText(city)) {
|
||||||
|
return city;
|
||||||
|
}
|
||||||
|
|
||||||
|
String province = addressComponent.getString("province");
|
||||||
|
if (org.springframework.util.StringUtils.hasText(province)) {
|
||||||
|
return province;
|
||||||
|
}
|
||||||
|
|
||||||
|
return "";
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("根据经纬度获取城市失败, latitude={}, longitude={}, err={}", latitude, longitude, e.getMessage());
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
3
fad-app/src/main/resources/application.yml
Normal file
3
fad-app/src/main/resources/application.yml
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fad:
|
||||||
|
amap:
|
||||||
|
key: 978ae5bc551f57d172d3e397af5a6f67
|
||||||
@@ -6,6 +6,7 @@ import com.ruoyi.common.core.domain.PageQuery;
|
|||||||
import com.ruoyi.common.core.domain.R;
|
import com.ruoyi.common.core.domain.R;
|
||||||
import com.ruoyi.common.core.page.TableDataInfo;
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
import com.ruoyi.common.enums.BusinessType;
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
|
import com.ruoyi.common.helper.LoginHelper;
|
||||||
import com.ruoyi.hrm.domain.bo.HrmFlowTaskBo;
|
import com.ruoyi.hrm.domain.bo.HrmFlowTaskBo;
|
||||||
import com.ruoyi.hrm.domain.bo.HrmFlowTaskApproveBo;
|
import com.ruoyi.hrm.domain.bo.HrmFlowTaskApproveBo;
|
||||||
import com.ruoyi.hrm.domain.bo.HrmSealStampBo;
|
import com.ruoyi.hrm.domain.bo.HrmSealStampBo;
|
||||||
@@ -112,4 +113,14 @@ public class HrmFlowTaskController extends BaseController {
|
|||||||
@RequestParam(required = false) String remark) {
|
@RequestParam(required = false) String remark) {
|
||||||
return toAjax(service.transfer(taskId, newAssigneeUserId, actionUserId, remark));
|
return toAjax(service.transfer(taskId, newAssigneeUserId, actionUserId, remark));
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 审批历史列表:查询当前用户的审批历史,包含分页
|
||||||
|
*/
|
||||||
|
@GetMapping("/historyList")
|
||||||
|
public TableDataInfo<HrmFlowTaskVo> historyList(PageQuery pageQuery) {
|
||||||
|
// 使用若依自带的 LoginHelper 获取当前登录用户的 ID (类型是 Long)
|
||||||
|
Long userId = LoginHelper.getUserId();
|
||||||
|
// 调用 Service
|
||||||
|
return service.selectHistoryTaskList(userId, pageQuery);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.ruoyi.hrm.service;
|
|||||||
|
|
||||||
import com.ruoyi.common.core.domain.PageQuery;
|
import com.ruoyi.common.core.domain.PageQuery;
|
||||||
import com.ruoyi.common.core.page.TableDataInfo;
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
|
import com.ruoyi.hrm.domain.HrmFlowTask;
|
||||||
import com.ruoyi.hrm.domain.bo.HrmFlowTaskBo;
|
import com.ruoyi.hrm.domain.bo.HrmFlowTaskBo;
|
||||||
import com.ruoyi.hrm.domain.vo.HrmFlowTaskVo;
|
import com.ruoyi.hrm.domain.vo.HrmFlowTaskVo;
|
||||||
|
|
||||||
@@ -42,6 +43,10 @@ public interface IHrmFlowTaskService {
|
|||||||
*/
|
*/
|
||||||
Boolean transfer(Long taskId, Long newAssigneeUserId, Long actionUserId, String remark);
|
Boolean transfer(Long taskId, Long newAssigneeUserId, Long actionUserId, String remark);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当前用户的审批历史,并回填业务数据
|
||||||
|
*/
|
||||||
|
TableDataInfo<HrmFlowTaskVo> selectHistoryTaskList(Long userId, PageQuery pageQuery);
|
||||||
/**
|
/**
|
||||||
* 根据业务类型 + 业务ID 查询当前待办任务(pending),用于详情页自动带出 currentTaskId
|
* 根据业务类型 + 业务ID 查询当前待办任务(pending),用于详情页自动带出 currentTaskId
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
|||||||
private final FlowAssigneeHelper assigneeHelper;
|
private final FlowAssigneeHelper assigneeHelper;
|
||||||
private final BizStatusSyncHelper bizStatusSyncHelper;
|
private final BizStatusSyncHelper bizStatusSyncHelper;
|
||||||
|
|
||||||
|
private final HrmFlowTaskMapper hrmFlowTaskMapper;
|
||||||
// 注入五个业务Mapper
|
// 注入五个业务Mapper
|
||||||
private final HrmLeaveReqMapper leaveReqMapper;
|
private final HrmLeaveReqMapper leaveReqMapper;
|
||||||
private final HrmTravelReqMapper travelReqMapper;
|
private final HrmTravelReqMapper travelReqMapper;
|
||||||
@@ -397,4 +398,27 @@ public class HrmFlowTaskServiceImpl implements IHrmFlowTaskService {
|
|||||||
lqw.orderByDesc(HrmFlowTask::getCreateTime);
|
lqw.orderByDesc(HrmFlowTask::getCreateTime);
|
||||||
return lqw;
|
return lqw;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* 查询当前用户的审批历史,回填业务数据以便前端复用页面
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public TableDataInfo<HrmFlowTaskVo> selectHistoryTaskList(Long userId, PageQuery pageQuery) {
|
||||||
|
|
||||||
|
LambdaQueryWrapper<HrmFlowTask> lqw = Wrappers.lambdaQuery();
|
||||||
|
|
||||||
|
lqw.eq(HrmFlowTask::getAssigneeUserId, userId);
|
||||||
|
|
||||||
|
lqw.ne(HrmFlowTask::getStatus, "pending");
|
||||||
|
|
||||||
|
lqw.orderByDesc(HrmFlowTask::getCreateTime);
|
||||||
|
|
||||||
|
Page<HrmFlowTaskVo> result = baseMapper.selectVoPage(pageQuery.build(), lqw);
|
||||||
|
|
||||||
|
if (result.getRecords() != null && !result.getRecords().isEmpty()) {
|
||||||
|
fillBizData(result.getRecords());
|
||||||
|
}
|
||||||
|
|
||||||
|
return TableDataInfo.build(result);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -317,3 +317,8 @@ flowable:
|
|||||||
check-process-definitions: false
|
check-process-definitions: false
|
||||||
# 关闭历史任务定时任务job
|
# 关闭历史任务定时任务job
|
||||||
async-history-executor-activate: false
|
async-history-executor-activate: false
|
||||||
|
|
||||||
|
|
||||||
|
fad:
|
||||||
|
amap:
|
||||||
|
key: 978ae5bc551f57d172d3e397af5a6f67
|
||||||
@@ -68,6 +68,7 @@
|
|||||||
<version>1.0.0</version>
|
<version>1.0.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
||||||
<groupId>com.xuxueli</groupId>
|
<groupId>com.xuxueli</groupId>
|
||||||
<artifactId>xxl-job-core</artifactId>
|
<artifactId>xxl-job-core</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.alibaba.fastjson2.JSONArray;
|
|||||||
import com.alibaba.fastjson2.JSONObject;
|
import com.alibaba.fastjson2.JSONObject;
|
||||||
import com.ruoyi.oa.domain.OaExpress;
|
import com.ruoyi.oa.domain.OaExpress;
|
||||||
import com.ruoyi.oa.domain.vo.OaExpressVo;
|
import com.ruoyi.oa.domain.vo.OaExpressVo;
|
||||||
|
import java.util.Base64;
|
||||||
import com.ruoyi.common.utils.DateUtils;
|
import com.ruoyi.common.utils.DateUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.ruoyi.workflow.service;
|
package com.ruoyi.workflow.service;
|
||||||
|
|
||||||
|
|
||||||
import com.ruoyi.workflow.domain.FlowRecord;
|
import com.ruoyi.workflow.domain.FlowRecord;
|
||||||
import com.ruoyi.workflow.domain.bo.WfTaskBo;
|
import com.ruoyi.workflow.domain.bo.WfTaskBo;
|
||||||
import org.flowable.bpmn.model.FlowElement;
|
import org.flowable.bpmn.model.FlowElement;
|
||||||
import org.flowable.engine.runtime.ProcessInstance;
|
import org.flowable.engine.runtime.ProcessInstance;
|
||||||
|
import org.flowable.task.api.history.HistoricTaskInstance;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -124,4 +126,5 @@ public interface IWfTaskService {
|
|||||||
* @return Map包含isStartNode和isEndNode信息
|
* @return Map包含isStartNode和isEndNode信息
|
||||||
*/
|
*/
|
||||||
Map<String, Boolean> checkTaskNodeType(String taskId);
|
Map<String, Boolean> checkTaskNodeType(String taskId);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import cn.hutool.core.collection.CollUtil;
|
|||||||
import cn.hutool.core.convert.Convert;
|
import cn.hutool.core.convert.Convert;
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
import cn.hutool.core.util.ObjectUtil;
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.AbstractWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.ruoyi.common.core.service.UserService;
|
import com.ruoyi.common.core.service.UserService;
|
||||||
import com.ruoyi.common.exception.ServiceException;
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
import com.ruoyi.common.helper.LoginHelper;
|
import com.ruoyi.common.helper.LoginHelper;
|
||||||
@@ -63,6 +65,7 @@ public class WfTaskServiceImpl extends FlowServiceFactory implements IWfTaskServ
|
|||||||
|
|
||||||
private final IWfCopyService copyService;
|
private final IWfCopyService copyService;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 完成任务
|
* 完成任务
|
||||||
*
|
*
|
||||||
@@ -788,4 +791,7 @@ public class WfTaskServiceImpl extends FlowServiceFactory implements IWfTaskServ
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,6 +238,13 @@ export function delFlowInstance (instId) {
|
|||||||
method: 'delete'
|
method: 'delete'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
export function listHistoryFlowTask(query) {
|
||||||
|
return request({
|
||||||
|
url: '/hrm/flow/task/historyList',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询实例对应的所有审批任务
|
* 查询实例对应的所有审批任务
|
||||||
@@ -247,4 +254,9 @@ export function listAssignTask (instId) {
|
|||||||
url: `/hrm/flow/instance/tasks/${instId}`,
|
url: `/hrm/flow/instance/tasks/${instId}`,
|
||||||
method: 'get'
|
method: 'get'
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询当前用户的审批历史
|
||||||
|
*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
import router from './router'
|
import router from './router'
|
||||||
import store from './store'
|
import store from './store'
|
||||||
import {Message} from 'element-ui'
|
import { Message } from 'element-ui'
|
||||||
import NProgress from 'nprogress'
|
import NProgress from 'nprogress'
|
||||||
import 'nprogress/nprogress.css'
|
import 'nprogress/nprogress.css'
|
||||||
import {getToken} from '@/utils/auth'
|
import { getToken } from '@/utils/auth'
|
||||||
import {isRelogin} from '@/utils/request'
|
import { isRelogin } from '@/utils/request'
|
||||||
|
|
||||||
NProgress.configure({showSpinner: false})
|
NProgress.configure({ showSpinner: false })
|
||||||
|
|
||||||
const whiteList = ['/login', '/register']
|
const whiteList = ['/login', '/register']
|
||||||
|
|
||||||
@@ -14,25 +14,32 @@ router.beforeEach((to, from, next) => {
|
|||||||
NProgress.start()
|
NProgress.start()
|
||||||
if (getToken()) {
|
if (getToken()) {
|
||||||
to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
|
to.meta.title && store.dispatch('settings/setTitle', to.meta.title)
|
||||||
/* has token*/
|
|
||||||
if (to.path === '/login') {
|
if (to.path === '/login') {
|
||||||
next({path: '/'})
|
next({ path: '/' })
|
||||||
NProgress.done()
|
NProgress.done()
|
||||||
} else {
|
} else {
|
||||||
if (store.getters.roles.length === 0) {
|
if (store.getters.roles.length === 0) {
|
||||||
isRelogin.show = true
|
isRelogin.show = true
|
||||||
// 判断当前用户是否已拉取完user_info信息
|
|
||||||
store.dispatch('GetInfo').then(res => {
|
store.dispatch('GetInfo').then(res => {
|
||||||
isRelogin.show = false
|
isRelogin.show = false
|
||||||
store.dispatch('GenerateRoutes').then(accessRoutes => {
|
store.dispatch('GenerateRoutes').then(accessRoutes => {
|
||||||
// 根据roles权限生成可访问的路由表
|
|
||||||
router.addRoutes(accessRoutes) // 动态添加可访问路由表
|
const officeMenu = accessRoutes.find(m => m.path === '/oa')
|
||||||
next({...to, replace: true}) // hack方法 确保addRoutes已完成
|
if (officeMenu) {
|
||||||
|
const staticOa = router.options.routes.find(r => r.path === '/oa')
|
||||||
|
const historyMenu = staticOa?.children?.find(c => c.name === 'ApprovalHistory')
|
||||||
|
if (historyMenu) {
|
||||||
|
officeMenu.children.push(historyMenu)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
router.addRoutes(accessRoutes)
|
||||||
|
next({ ...to, replace: true })
|
||||||
})
|
})
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
store.dispatch('LogOut').then(() => {
|
store.dispatch('LogOut').then(() => {
|
||||||
Message.error(err)
|
Message.error(err)
|
||||||
next({path: '/'})
|
next({ path: '/' })
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
@@ -40,12 +47,10 @@ router.beforeEach((to, from, next) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 没有token
|
|
||||||
if (whiteList.indexOf(to.path) !== -1) {
|
if (whiteList.indexOf(to.path) !== -1) {
|
||||||
// 在免登录白名单,直接进入
|
|
||||||
next()
|
next()
|
||||||
} else {
|
} else {
|
||||||
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
|
next(`/login?redirect=${to.fullPath}`)
|
||||||
NProgress.done()
|
NProgress.done()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -53,4 +58,4 @@ router.beforeEach((to, from, next) => {
|
|||||||
|
|
||||||
router.afterEach(() => {
|
router.afterEach(() => {
|
||||||
NProgress.done()
|
NProgress.done()
|
||||||
})
|
})
|
||||||
@@ -7,28 +7,6 @@ Vue.use(Router);
|
|||||||
/* Layout */
|
/* Layout */
|
||||||
import Layout from "@/layout";
|
import Layout from "@/layout";
|
||||||
|
|
||||||
/**
|
|
||||||
* Note: 路由配置项
|
|
||||||
*
|
|
||||||
* hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1
|
|
||||||
* alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面
|
|
||||||
* // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面
|
|
||||||
* // 若你想不管路由下面的 children 声明的个数都显示你的根路由
|
|
||||||
* // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由
|
|
||||||
* redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击
|
|
||||||
* name:'router-name' // 设定路由的名字,一定要填写不然使用<keep-alive>时会出现各种问题
|
|
||||||
* query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数
|
|
||||||
* roles: ['admin', 'common'] // 访问路由的角色权限
|
|
||||||
* permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限
|
|
||||||
* meta : {
|
|
||||||
noCache: true // 如果设置为true,则不会被 <keep-alive> 缓存(默认 false)
|
|
||||||
title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字
|
|
||||||
icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg
|
|
||||||
breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示
|
|
||||||
activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 公共路由
|
// 公共路由
|
||||||
export const constantRoutes = [
|
export const constantRoutes = [
|
||||||
{
|
{
|
||||||
@@ -73,12 +51,10 @@ export const constantRoutes = [
|
|||||||
name: "Index",
|
name: "Index",
|
||||||
meta: { title: "工作台", icon: "dashboard", affix: true },
|
meta: { title: "工作台", icon: "dashboard", affix: true },
|
||||||
beforeEnter: (to, from, next) => {
|
beforeEnter: (to, from, next) => {
|
||||||
// 从本地存储获取角色信息
|
|
||||||
currentRole().then((res) => {
|
currentRole().then((res) => {
|
||||||
console.log(res)
|
|
||||||
const role = res.data[0].roleKey;
|
const role = res.data[0].roleKey;
|
||||||
if (role === "temp") {
|
if (role === "temp") {
|
||||||
next("/temp"); // 重定向到临时页面
|
next("/temp");
|
||||||
} else {
|
} else {
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
@@ -91,16 +67,15 @@ export const constantRoutes = [
|
|||||||
path: "/temp",
|
path: "/temp",
|
||||||
component: () => import("@/views/temp"),
|
component: () => import("@/views/temp"),
|
||||||
name: "Temp",
|
name: "Temp",
|
||||||
hidden: true, // 隐藏路由
|
hidden: true,
|
||||||
meta: { title: "临时页面", icon: "dashboard" },
|
meta: { title: "临时页面", icon: "dashboard" },
|
||||||
beforeEnter: (to, from, next) => {
|
beforeEnter: (to, from, next) => {
|
||||||
// 从本地存储获取角色信息
|
|
||||||
currentRole().then((res) => {
|
currentRole().then((res) => {
|
||||||
const role = res.data[0].roleKey;
|
const role = res.data[0].roleKey;
|
||||||
if (role === "temp") {
|
if (role === "temp") {
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
next("/index"); // 重定向到工作台
|
next("/index");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -144,14 +119,12 @@ export const constantRoutes = [
|
|||||||
name: "updateOnboarding",
|
name: "updateOnboarding",
|
||||||
meta: { title: "更新入职数据", activeMenu: "/people/onboarding" },
|
meta: { title: "更新入职数据", activeMenu: "/people/onboarding" },
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: "addOffboarding",
|
path: "addOffboarding",
|
||||||
component: () => import("@/views/oa/offboarding/add"),
|
component: () => import("@/views/oa/offboarding/add"),
|
||||||
name: "addOffboarding",
|
name: "addOffboarding",
|
||||||
meta: { title: "新增离职申请", activeMenu: "/people/offboarding" },
|
meta: { title: "新增离职申请", activeMenu: "/people/offboarding" },
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: "updateOffboarding/:offboardingId(\\d+)",
|
path: "updateOffboarding/:offboardingId(\\d+)",
|
||||||
component: () => import("@/views/oa/offboarding/update"),
|
component: () => import("@/views/oa/offboarding/update"),
|
||||||
@@ -174,7 +147,6 @@ export const constantRoutes = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: "/money",
|
path: "/money",
|
||||||
component: Layout,
|
component: Layout,
|
||||||
@@ -188,7 +160,6 @@ export const constantRoutes = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: "/claim",
|
path: "/claim",
|
||||||
component: Layout,
|
component: Layout,
|
||||||
@@ -210,7 +181,6 @@ export const constantRoutes = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 动态路由,基于用户权限动态去加载
|
|
||||||
export const dynamicRoutes = [
|
export const dynamicRoutes = [
|
||||||
{
|
{
|
||||||
path: "/system/user-auth",
|
path: "/system/user-auth",
|
||||||
@@ -240,7 +210,6 @@ export const dynamicRoutes = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: "/system/dict-data",
|
path: "/system/dict-data",
|
||||||
component: Layout,
|
component: Layout,
|
||||||
@@ -255,7 +224,6 @@ export const dynamicRoutes = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
path: "/oa/warehouse-data",
|
path: "/oa/warehouse-data",
|
||||||
component: Layout,
|
component: Layout,
|
||||||
@@ -348,23 +316,20 @@ export const dynamicRoutes = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// 防止连续点击多次路由报错
|
|
||||||
let routerPush = Router.prototype.push;
|
let routerPush = Router.prototype.push;
|
||||||
let routerReplace = Router.prototype.replace;
|
let routerReplace = Router.prototype.replace;
|
||||||
// push
|
Router.prototype.push = function push(location) {
|
||||||
Router.prototype.push = function push (location) {
|
|
||||||
return routerPush.call(this, location).catch((err) => err);
|
return routerPush.call(this, location).catch((err) => err);
|
||||||
};
|
};
|
||||||
// replace
|
Router.prototype.replace = function replace(location) {
|
||||||
Router.prototype.replace = function push (location) {
|
|
||||||
return routerReplace.call(this, location).catch((err) => err);
|
return routerReplace.call(this, location).catch((err) => err);
|
||||||
};
|
};
|
||||||
|
|
||||||
const router = new Router({
|
const router = new Router({
|
||||||
base: process.env.VUE_APP_CONTEXT_PATH,
|
base: process.env.VUE_APP_CONTEXT_PATH,
|
||||||
mode: "history", // 去掉url中的#
|
mode: "history",
|
||||||
scrollBehavior: () => ({ y: 0 }),
|
scrollBehavior: () => ({ y: 0 }),
|
||||||
routes: constantRoutes,
|
routes: constantRoutes,
|
||||||
});
|
});
|
||||||
|
|
||||||
export default router;
|
export default router;
|
||||||
@@ -29,11 +29,36 @@ const permission = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
// 生成路由
|
GenerateRoutes({ commit }) {
|
||||||
GenerateRoutes({ commit }) {
|
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
// 向后端请求路由数据
|
|
||||||
getRouters().then(res => {
|
getRouters().then(res => {
|
||||||
|
|
||||||
|
|
||||||
|
const oaMenu = res.data.find(item => item.path === '/oa' || (item.meta && item.meta.title === '办公中心'));
|
||||||
|
|
||||||
|
if (oaMenu) {
|
||||||
|
if (!oaMenu.children) oaMenu.children = [];
|
||||||
|
|
||||||
|
|
||||||
|
const hasHistory = oaMenu.children.some(child => child.path === 'flowHistory');
|
||||||
|
|
||||||
|
if (!hasHistory) {
|
||||||
|
oaMenu.children.push({
|
||||||
|
name: 'HrmFlowHistory',
|
||||||
|
path: 'flowHistory',
|
||||||
|
hidden: false,
|
||||||
|
component: 'hrm/flow/taskHistory',
|
||||||
|
meta: {
|
||||||
|
title: '审批历史',
|
||||||
|
icon: 'date-range',
|
||||||
|
noCache: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ================= 新增拦截代码:将页面注入到办公中心结束 =================
|
||||||
|
|
||||||
const sdata = JSON.parse(JSON.stringify(res.data))
|
const sdata = JSON.parse(JSON.stringify(res.data))
|
||||||
const rdata = JSON.parse(JSON.stringify(res.data))
|
const rdata = JSON.parse(JSON.stringify(res.data))
|
||||||
const sidebarRoutes = filterAsyncRouter(sdata)
|
const sidebarRoutes = filterAsyncRouter(sdata)
|
||||||
@@ -130,4 +155,4 @@ export const loadView = (view) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default permission
|
export default permission
|
||||||
@@ -4,14 +4,13 @@
|
|||||||
<div slot="header" class="card-header">
|
<div slot="header" class="card-header">
|
||||||
<span>{{ bizTitle }}</span>
|
<span>{{ bizTitle }}</span>
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<el-button size="mini" icon="el-icon-arrow-left" @click="$router.back()">返回</el-button>
|
<el-button v-if="!preview" size="mini" icon="el-icon-arrow-left" @click="$router.back()">返回</el-button>
|
||||||
<el-button size="mini" icon="el-icon-refresh" @click="loadDetail">刷新</el-button>
|
<el-button size="mini" icon="el-icon-refresh" @click="loadDetail">刷新</el-button>
|
||||||
|
|
||||||
<!-- 审批操作按钮 -->
|
<el-button v-if="!preview && canApprove" type="success" size="mini" :loading="actionLoading" @click="handleApprove">
|
||||||
<el-button v-if="canApprove" type="success" size="mini" :loading="actionLoading" @click="handleApprove">
|
|
||||||
通过
|
通过
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button v-if="canApprove" type="danger" size="mini" :loading="actionLoading" @click="handleReject">
|
<el-button v-if="!preview && canApprove" type="danger" size="mini" :loading="actionLoading" @click="handleReject">
|
||||||
驳回
|
驳回
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -54,20 +53,21 @@
|
|||||||
<ProjectInfo :info="detail" />
|
<ProjectInfo :info="detail" />
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<div class="block-title">审批操作</div>
|
<div v-if="!preview">
|
||||||
<el-card class="inner-card" shadow="never">
|
<div class="block-title">审批操作</div>
|
||||||
<div class="hint-text">系统将自动识别你在该单据上的“当前待办任务”。若你不是当前办理人,将不会显示办理按钮。</div>
|
<el-card class="inner-card" shadow="never">
|
||||||
<div v-if="currentTask" class="btn-row">
|
<div v-if="currentTask" class="btn-row">
|
||||||
<el-input v-model="actionRemark" type="textarea" :rows="3" placeholder="填写审批意见(可选)" />
|
<el-input v-model="actionRemark" type="textarea" :rows="3" placeholder="填写审批意见(可选)" />
|
||||||
<div class="btn-row mt10">
|
<div class="btn-row mt10">
|
||||||
<el-button type="success" v-if="canApprove" :loading="actionSubmitting"
|
<el-button type="success" v-if="canApprove" :loading="actionSubmitting"
|
||||||
@click="submitTaskAction('approve')">通过</el-button>
|
@click="submitTaskAction('approve')">通过</el-button>
|
||||||
<el-button type="danger" v-if="canApprove" :loading="actionSubmitting"
|
<el-button type="danger" v-if="canApprove" :loading="actionSubmitting"
|
||||||
@click="submitTaskAction('reject')">驳回</el-button>
|
@click="submitTaskAction('reject')">驳回</el-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div v-else class="empty">当前无待办任务(可能已处理完成,或你不是当前审批人)</div>
|
||||||
<div v-else class="empty">当前无待办任务(可能已处理完成,或你不是当前审批人)</div>
|
</el-card>
|
||||||
</el-card>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<el-card class="report-card" shadow="never">
|
<el-card class="report-card" shadow="never">
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
<span>操作汇报</span>
|
<span>操作汇报</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="comment-form">
|
<div v-if="!preview" class="comment-form">
|
||||||
<editor v-model="commentForm.commentContent" placeholder="填写操作汇报(可选)" />
|
<editor v-model="commentForm.commentContent" placeholder="填写操作汇报(可选)" />
|
||||||
<file-upload v-model="commentForm.attachments" />
|
<file-upload v-model="commentForm.attachments" />
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
<div class="comment-meta">
|
<div class="comment-meta">
|
||||||
<span class="comment-operator">{{ item.createByName }}</span>
|
<span class="comment-operator">{{ item.createByName }}</span>
|
||||||
<span class="comment-time">{{ item.createTime }}</span>
|
<span class="comment-time">{{ item.createTime }}</span>
|
||||||
<el-button v-if="isSelf(item)" type="danger" size="mini" @click="handleDeleteComment(item.commentId)"
|
<el-button v-if="!preview && isSelf(item)" type="danger" size="mini" @click="handleDeleteComment(item.commentId)"
|
||||||
:loading="buttonLoading">删除</el-button>
|
:loading="buttonLoading">删除</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -132,7 +132,8 @@ export default {
|
|||||||
name: 'BizDetailContainer',
|
name: 'BizDetailContainer',
|
||||||
props: {
|
props: {
|
||||||
bizId: { type: String, required: true },
|
bizId: { type: String, required: true },
|
||||||
bizType: { type: String, required: true }
|
bizType: { type: String, required: true },
|
||||||
|
preview: { type: Boolean, default: false }
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
FilePreview,
|
FilePreview,
|
||||||
@@ -414,7 +415,7 @@ export default {
|
|||||||
async exportComment () {
|
async exportComment () {
|
||||||
try {
|
try {
|
||||||
this.buttonLoading = true
|
this.buttonLoading = true
|
||||||
const res = await addFlowComment({
|
await addFlowComment({
|
||||||
instId: this.flowInstance.instId,
|
instId: this.flowInstance.instId,
|
||||||
commentContent: this.commentForm.commentContent,
|
commentContent: this.commentForm.commentContent,
|
||||||
attachments: this.commentForm.attachments
|
attachments: this.commentForm.attachments
|
||||||
|
|||||||
209
ruoyi-ui/src/views/hrm/flow/taskHistory.vue
Normal file
209
ruoyi-ui/src/views/hrm/flow/taskHistory.vue
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
<template>
|
||||||
|
<div class="hrm-page">
|
||||||
|
<div class="flow-task-layout">
|
||||||
|
<el-card class="metal-panel left" shadow="never">
|
||||||
|
<div slot="header" class="panel-header">
|
||||||
|
<div class="actions-inline">
|
||||||
|
<el-button size="mini" plain icon="el-icon-refresh" @click="fetchList">刷新</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-table
|
||||||
|
:data="list"
|
||||||
|
v-loading="loading"
|
||||||
|
height="680"
|
||||||
|
stripe
|
||||||
|
highlight-current-row
|
||||||
|
@row-click="openDetail"
|
||||||
|
>
|
||||||
|
<el-table-column label="业务" min-width="140">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<div class="biz-cell">
|
||||||
|
<span class="biz-type">{{ bizTypeText(scope.row.bizType) }}</span>
|
||||||
|
<span class="biz-title">{{ scope.row.title || scope.row.remark }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="审批状态" min-width="100">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<el-tag :type="statusType(scope.row.status)" size="mini">{{ statusText(scope.row.status) }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="审批时间" min-width="160">
|
||||||
|
<template slot-scope="scope">
|
||||||
|
<span>{{ formatDate(scope.row.updateTime || scope.row.createTime) || '-' }}</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="备注" prop="remark" show-overflow-tooltip />
|
||||||
|
</el-table>
|
||||||
|
</el-card>
|
||||||
|
|
||||||
|
<el-card class="metal-panel right" shadow="never">
|
||||||
|
<div slot="header" class="panel-header"></div>
|
||||||
|
|
||||||
|
<div v-if="!detailTask" class="placeholder">
|
||||||
|
<div class="p-title">请在左侧选择一条审批记录</div>
|
||||||
|
<div class="p-sub">右侧将展示表单内容和流转历史</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="detail-wrap">
|
||||||
|
<div class="detail-summary">
|
||||||
|
<div class="ds-left">
|
||||||
|
<div class="ds-title">{{ bizTypeText(detailTask.bizType) }} · {{ detailTask.title || '审批详情' }}</div>
|
||||||
|
<div class="ds-sub">
|
||||||
|
<el-tag size="mini" :type="statusType(detailTask.status)">{{ statusText(detailTask.status) }}</el-tag>
|
||||||
|
<span class="muted">审批时间 {{ formatDate(detailTask.updateTime || detailTask.createTime) || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<el-tabs value="form">
|
||||||
|
<el-tab-pane label="表单数据" name="form">
|
||||||
|
<LeaveDetail v-if="detailTask && detailTask.bizType === 'leave'" :biz-id="detailTask.bizId" :embedded="true" :preview="true" />
|
||||||
|
<TravelDetail v-else-if="detailTask && detailTask.bizType === 'travel'" :biz-id="detailTask.bizId" :embedded="true" :preview="true" />
|
||||||
|
<SealDetail v-else-if="detailTask && detailTask.bizType === 'seal'" :biz-id="detailTask.bizId" :embedded="true" :preview="true" />
|
||||||
|
<ReimburseDetail v-else-if="detailTask && detailTask.bizType === 'reimburse'" :biz-id="detailTask.bizId" :embedded="true" :preview="true" />
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<el-table :data="formData" v-loading="formLoading" size="mini" height="260">
|
||||||
|
<el-table-column label="字段" prop="fieldName" min-width="140" />
|
||||||
|
<el-table-column label="展示" prop="fieldLabel" min-width="160" show-overflow-tooltip />
|
||||||
|
<el-table-column label="值" prop="fieldValue" min-width="220" show-overflow-tooltip />
|
||||||
|
</el-table>
|
||||||
|
</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
|
||||||
|
<el-tab-pane label="流转历史" name="history">
|
||||||
|
<el-timeline v-loading="actionLoading" v-if="actionList.length">
|
||||||
|
<el-timeline-item
|
||||||
|
v-for="(a, idx) in actionList"
|
||||||
|
:key="idx"
|
||||||
|
:timestamp="formatDate(a.createTime)"
|
||||||
|
:type="actionTagType(a.action)"
|
||||||
|
>
|
||||||
|
<div class="timeline-row">
|
||||||
|
<div class="t-main">
|
||||||
|
<span class="t-action">{{ actionText(a.action) }}</span>
|
||||||
|
<span class="t-user">· 办理人:{{ formatUser(a.createBy, 'createBy') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="t-remark" v-if="a.remark">{{ a.remark }}</div>
|
||||||
|
</div>
|
||||||
|
</el-timeline-item>
|
||||||
|
</el-timeline>
|
||||||
|
<div v-else class="empty">暂无流转记录</div>
|
||||||
|
</el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { listHistoryFlowTask, listFlowAction, listFlowFormData } from '@/api/hrm/flow'
|
||||||
|
import { listUser } from '@/api/system/user'
|
||||||
|
import LeaveDetail from '@/views/hrm/requests/leaveDetail.vue'
|
||||||
|
import ReimburseDetail from '@/views/hrm/requests/reimburseDetail.vue'
|
||||||
|
import SealDetail from '@/views/hrm/requests/sealDetail.vue'
|
||||||
|
import TravelDetail from '@/views/hrm/requests/travelDetail.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'HrmFlowHistory',
|
||||||
|
dicts: ['hrm_stamp_image'],
|
||||||
|
components: { LeaveDetail, TravelDetail, SealDetail, ReimburseDetail },
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
query: { status: undefined, pageNum: 1, pageSize: 50 },
|
||||||
|
list: [],
|
||||||
|
total: 0,
|
||||||
|
loading: false,
|
||||||
|
detailTask: null,
|
||||||
|
actionList: [],
|
||||||
|
actionLoading: false,
|
||||||
|
formData: [],
|
||||||
|
formLoading: false,
|
||||||
|
allUsers: []
|
||||||
|
}
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.loadAllUsers()
|
||||||
|
this.fetchList()
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
bizTypeText(val) { const map = { leave: '请假', travel: '出差', seal: '用印', payroll: '薪酬', reimburse: '报销' }; return map[val] || val || '-' },
|
||||||
|
statusText(status) { const map = { pending: '待办', done: '已通过', approved: '已通过', rejected: '已驳回', withdrawn: '已撤回' }; return map[status] || status || '-' },
|
||||||
|
statusType(status) { const map = { pending: 'warning', done: 'success', approved: 'success', rejected: 'danger', withdrawn: 'info' }; return map[status] || 'info' },
|
||||||
|
formatDate(val) { if (!val) return ''; const d = new Date(val); const p = n => (n < 10 ? `0${n}` : n); return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}` },
|
||||||
|
actionText(action) { const map = { submit: '提交', approve: '通过', reject: '驳回', withdraw: '撤回', cancel: '撤销', stamp: '盖章', transfer: '转发' }; return map[action] || action || '-' },
|
||||||
|
actionTagType(action) { const map = { submit: 'primary', approve: 'success', reject: 'danger', withdraw: 'info', cancel: 'info', stamp: 'primary', transfer: 'warning' }; return map[action] || 'info' },
|
||||||
|
async loadAllUsers() { try { const res = await listUser({ pageNum: 1, pageSize: 1000 }); this.allUsers = res.rows || [] } catch (e) { this.allUsers = [] } },
|
||||||
|
formatUser(userId, fieldName) { if (!userId) return '-'; const user = this.allUsers.find(u => u[fieldName] === userId); return user ? `${user.nickName || user.userName}` : '-' },
|
||||||
|
|
||||||
|
fetchList() {
|
||||||
|
this.loading = true
|
||||||
|
listHistoryFlowTask(this.query).then(res => {
|
||||||
|
this.list = res.rows || []
|
||||||
|
this.total = res.total || 0
|
||||||
|
if (!this.detailTask && this.list.length) this.openDetail(this.list[0])
|
||||||
|
}).finally(() => { this.loading = false })
|
||||||
|
},
|
||||||
|
|
||||||
|
openDetail(row) {
|
||||||
|
if (!row) return
|
||||||
|
this.detailTask = row
|
||||||
|
this.loadActions(row)
|
||||||
|
this.loadFormData(row)
|
||||||
|
},
|
||||||
|
|
||||||
|
loadActions(row) {
|
||||||
|
if (!row || !row.instId) return
|
||||||
|
this.actionLoading = true
|
||||||
|
listFlowAction({ instId: row.instId }).then(res => { this.actionList = res.rows || [] }).finally(() => { this.actionLoading = false })
|
||||||
|
},
|
||||||
|
|
||||||
|
loadFormData(row) {
|
||||||
|
if (!row || !row.instId) return
|
||||||
|
this.formLoading = true
|
||||||
|
listFlowFormData({ instId: row.instId }).then(res => { this.formData = res.rows || [] }).finally(() => { this.formLoading = false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.hrm-page {
|
||||||
|
padding: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.flow-task-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 520px 1fr;
|
||||||
|
gap: 12px;
|
||||||
|
align-items: start;
|
||||||
|
}
|
||||||
|
.metal-panel {
|
||||||
|
border: 1px solid rgba(215, 217, 223, 0.55);
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
.panel-header { display: flex; justify-content: space-between; align-items: center; font-weight: 700; color: #2b2f36; padding: 0; min-height: 0; }
|
||||||
|
.header-title { display: flex; flex-direction: column; }
|
||||||
|
.header-title .sub { font-size: 12px; color: #8a8f99; margin-top: 2px; font-weight: 500; }
|
||||||
|
.actions-inline { display: flex; gap: 8px; align-items: center; }
|
||||||
|
.biz-cell { display: flex; flex-direction: column; gap: 2px; }
|
||||||
|
.biz-type { font-size: 12px; color: #8a8f99; }
|
||||||
|
.biz-title { font-weight: 500; color: #2b2f36; line-height: 1.2; }
|
||||||
|
.muted { color: #8a8f99; font-size: 12px; }
|
||||||
|
.placeholder { padding: 18px 14px; border: 1px dashed #eef0f3; border-radius: 8px; background: #fff; }
|
||||||
|
.p-title { font-weight: 700; color: #2b2f36; }
|
||||||
|
.p-sub { margin-top: 6px; color: #8a8f99; font-size: 13px; }
|
||||||
|
.detail-wrap { padding-right: 2px; }
|
||||||
|
.detail-summary { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; padding: 12px; border: 1px solid rgba(230, 232, 237, 0.65); border-radius: 8px; background: #fff; }
|
||||||
|
.ds-title { font-weight: 700; color: #2b2f36; }
|
||||||
|
.ds-sub { margin-top: 6px; display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
|
||||||
|
/* removed extra right-side summary metadata */
|
||||||
|
.empty { padding: 12px; color: #8a8f99; }
|
||||||
|
.timeline-row .t-main { font-weight: 600; color: #2b2f36; }
|
||||||
|
.timeline-row .t-remark { margin-top: 4px; color: #606266; font-size: 13px; }
|
||||||
|
</style>
|
||||||
@@ -17,6 +17,9 @@
|
|||||||
<router-link to="/hrm/payroll">
|
<router-link to="/hrm/payroll">
|
||||||
<el-button type="success" plain icon="el-icon-coin">薪酬与指标</el-button>
|
<el-button type="success" plain icon="el-icon-coin">薪酬与指标</el-button>
|
||||||
</router-link>
|
</router-link>
|
||||||
|
<router-link to="/oa/taskHistory">
|
||||||
|
<el-button type="default" plain icon="el-icon-tickets">审批历史</el-button>
|
||||||
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="hero-stat">
|
<div class="hero-stat">
|
||||||
|
|||||||
36
ruoyi-ui/src/views/hrm/js/History
Normal file
36
ruoyi-ui/src/views/hrm/js/History
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
// 获取审批历史(已完成)
|
||||||
|
export function listHistoryFlowTask(query) {
|
||||||
|
return request({
|
||||||
|
url: '/workflow/task/historyList',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 任务详情
|
||||||
|
export function getTodoTaskByBiz(taskId) {
|
||||||
|
return request({
|
||||||
|
url: '/workflow/task/getByTaskId/' + taskId,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 审批记录
|
||||||
|
export function listFlowAction(query) {
|
||||||
|
return request({
|
||||||
|
url: '/workflow/instance/action/list',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
export function listFlowFormData(query) {
|
||||||
|
return request({
|
||||||
|
url: '/workflow/instance/form/data',
|
||||||
|
method: 'get',
|
||||||
|
params: query
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<BizDetailContainer :bizId="currentBizId" bizType="appropriation">
|
<BizDetailContainer :bizId="currentBizId" bizType="appropriation" :preview="preview">
|
||||||
<template slot-scope="{ detail }">
|
<template slot-scope="{ detail }">
|
||||||
<div>
|
<div>
|
||||||
<!-- 拨款金额信息 -->
|
<!-- 拨款金额信息 -->
|
||||||
@@ -59,7 +59,8 @@ export default {
|
|||||||
name: 'AppropriationDetail',
|
name: 'AppropriationDetail',
|
||||||
props: {
|
props: {
|
||||||
bizId: { type: [String, Number], default: null },
|
bizId: { type: [String, Number], default: null },
|
||||||
embedded: { type: Boolean, default: false }
|
embedded: { type: Boolean, default: false },
|
||||||
|
preview: { type: Boolean, default: false }
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
FilePreview,
|
FilePreview,
|
||||||
@@ -114,8 +115,7 @@ export default {
|
|||||||
return this.detail.status === 'pending' && (this.currentTask?.assigneeUserName === this.$store.getters.name || this.currentTask?.assigneeUserId === this.$store.getters.id)
|
return this.detail.status === 'pending' && (this.currentTask?.assigneeUserName === this.$store.getters.name || this.currentTask?.assigneeUserId === this.$store.getters.id)
|
||||||
},
|
},
|
||||||
canWithdraw () {
|
canWithdraw () {
|
||||||
return false;
|
return false
|
||||||
return this.detail.status === 'pending' && this.detail.createBy === this.$store.getters.name
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
created () {
|
created () {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<BizDetailContainer :bizId="currentBizId" bizType="leave">
|
<BizDetailContainer :bizId="currentBizId" bizType="leave" :preview="preview">
|
||||||
<template slot-scope="{ detail }">
|
<template slot-scope="{ detail }">
|
||||||
<div>
|
<div>
|
||||||
<!-- 请假日期信息 -->
|
<!-- 请假日期信息 -->
|
||||||
@@ -50,7 +50,8 @@ export default {
|
|||||||
name: 'LeaveDetail',
|
name: 'LeaveDetail',
|
||||||
props: {
|
props: {
|
||||||
bizId: { type: [String, Number], default: null },
|
bizId: { type: [String, Number], default: null },
|
||||||
embedded: { type: Boolean, default: false }
|
embedded: { type: Boolean, default: false },
|
||||||
|
preview: { type: Boolean, default: false }
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
FilePreview,
|
FilePreview,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<BizDetailContainer :bizId="currentBizId" bizType="reimburse">
|
<BizDetailContainer :bizId="currentBizId" bizType="reimburse" :preview="preview">
|
||||||
<template slot-scope="{ detail }">
|
<template slot-scope="{ detail }">
|
||||||
<div>
|
<div>
|
||||||
<!-- 报销金额信息 -->
|
<!-- 报销金额信息 -->
|
||||||
@@ -47,7 +47,8 @@ export default {
|
|||||||
name: 'ReimburseDetail',
|
name: 'ReimburseDetail',
|
||||||
props: {
|
props: {
|
||||||
bizId: { type: [String, Number], default: null },
|
bizId: { type: [String, Number], default: null },
|
||||||
embedded: { type: Boolean, default: false }
|
embedded: { type: Boolean, default: false },
|
||||||
|
preview: { type: Boolean, default: false }
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
BizDetailContainer,
|
BizDetailContainer,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<BizDetailContainer :bizId="currentBizId" bizType="travel">
|
<BizDetailContainer :bizId="currentBizId" bizType="travel" :preview="preview">
|
||||||
<template slot-scope="{ detail }">
|
<template slot-scope="{ detail }">
|
||||||
<div>
|
<div>
|
||||||
<!-- 出差时间与行程 -->
|
<!-- 出差时间与行程 -->
|
||||||
@@ -46,7 +46,8 @@ export default {
|
|||||||
name: 'TravelDetail',
|
name: 'TravelDetail',
|
||||||
props: {
|
props: {
|
||||||
bizId: { type: [String, Number], default: '' },
|
bizId: { type: [String, Number], default: '' },
|
||||||
embedded: { type: Boolean, default: false }
|
embedded: { type: Boolean, default: false },
|
||||||
|
preview: { type: Boolean, default: false }
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
BizDetailContainer,
|
BizDetailContainer,
|
||||||
|
|||||||
Reference in New Issue
Block a user