疗休养优化
This commit is contained in:
@@ -5,7 +5,6 @@ import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.sys.models.Sys_home_activity;
|
||||
@@ -287,76 +286,67 @@ public class SysHomeController {
|
||||
// @CacheDefaults(cacheName = RedisConstant.PLATFORM_REDIS_WKCACHE_PREFIX + "web_news", isHash = true)
|
||||
// @CacheResult(cacheKey = "news", ignoreNull = true, cacheLiveTime = 60 * 60 * 24)
|
||||
public Result getNews() {
|
||||
// 定义要爬取的URL
|
||||
String domain = "https://cdgh.ncu.edu.cn/";
|
||||
try {
|
||||
// 使用Jsoup连接到URL并获取页面内容
|
||||
Document doc = Jsoup.connect(domain).get();
|
||||
|
||||
JSONObject root = new JSONObject();
|
||||
|
||||
// 1. 新闻快讯(来自 .list1)
|
||||
List<JSONObject> newsArray = new ArrayList<>();
|
||||
Elements newsItems = doc.select(".group1 .list1 li");
|
||||
for (Element item : newsItems) {
|
||||
String date = item.select(".date").text().trim();
|
||||
Element link = item.select("a.text").first();
|
||||
String title = link.select(".title").text().trim();
|
||||
String url = link.attr("href").trim();
|
||||
|
||||
JSONObject node = new JSONObject();
|
||||
node.set("title", title);
|
||||
node.set("date", date);
|
||||
node.set("url", domain + url);
|
||||
newsArray.add(node);
|
||||
}
|
||||
root.set("news", newsArray);
|
||||
|
||||
// 2. 通知公告(来自 .list2)
|
||||
List<JSONObject> noticesArray = new ArrayList<>();
|
||||
Elements noticeItems = doc.select(".group2 .list2 li");
|
||||
for (Element item : noticeItems) {
|
||||
String date = item.select(".date").text().trim();
|
||||
Element link = item.select("a.title").first();
|
||||
String title = link.text().trim();
|
||||
String url = link.attr("href").trim();
|
||||
JSONObject node = new JSONObject();
|
||||
node.set("title", title);
|
||||
node.set("date", date);
|
||||
node.set("url", domain + url);
|
||||
noticesArray.add(node);
|
||||
}
|
||||
root.set("notices", noticesArray);
|
||||
|
||||
// 3. 基层风采(来自 .pic-list1)
|
||||
List<JSONObject> grassrootsArray = new ArrayList<>();
|
||||
Elements grassrootsItems = doc.select(".group3 .pic-list1 li");
|
||||
for (Element item : grassrootsItems) {
|
||||
Element link = item.select("a.img-scale").first();
|
||||
String date = link.select(".date").text().trim();
|
||||
String title = link.select(".title").text().trim();
|
||||
String url = link.attr("href").trim();
|
||||
String summary = link.select(".info").text().trim();
|
||||
|
||||
String imageUrl = "";
|
||||
Element img = link.select("img").first();
|
||||
if (img != null) {
|
||||
imageUrl = img.attr("src").trim();
|
||||
}
|
||||
|
||||
JSONObject node = new JSONObject();
|
||||
node.set("title", title);
|
||||
node.set("date", date);
|
||||
node.set("url", domain + url);
|
||||
node.set("summary", summary);
|
||||
node.set("image", domain + imageUrl);
|
||||
grassrootsArray.add(node);
|
||||
}
|
||||
root.set("grassroots", grassrootsArray);
|
||||
return Result.success(root);
|
||||
List<NutMap> news = new ArrayList<>();
|
||||
news.add(NutMap.NEW().setv("label", "新闻快讯")
|
||||
.setv("value", parseHomeNewsItems(domain, doc.select(".group1 .list1 li"), "a.text", ".title")));
|
||||
news.add(NutMap.NEW().setv("label", "通知公告")
|
||||
.setv("value", parseHomeNewsItems(domain, doc.select(".group2 .list2 li"), "a.title", "")));
|
||||
news.add(NutMap.NEW().setv("label", "基层风采")
|
||||
.setv("value", parseHomeNewsItems(domain, doc.select(".group3 .pic-list1 li"), "a.img-scale", ".title")));
|
||||
return Result.success(news);
|
||||
} catch (Exception e) {
|
||||
log.error(e);
|
||||
return Result.success();
|
||||
return Result.success(Collections.emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the NCU union website list nodes to the PC v4 home carousel structure.
|
||||
* The migrated jcdt component renders label/value and item href/time/text fields.
|
||||
*
|
||||
* @param domain base website domain used to complete relative links
|
||||
* @param items news list nodes parsed from the source page
|
||||
* @param linkSelector selector for the clickable element inside each list node
|
||||
* @param titleSelector selector for the title inside the clickable element, blank means using link text
|
||||
* @return normalized news items for the PC v4 home page
|
||||
*/
|
||||
private List<NutMap> parseHomeNewsItems(String domain, Elements items, String linkSelector, String titleSelector) {
|
||||
List<NutMap> rows = new ArrayList<>();
|
||||
for (Element item : items) {
|
||||
Element link = item.select(linkSelector).first();
|
||||
if (link == null) {
|
||||
continue;
|
||||
}
|
||||
String title = StrUtil.isBlank(titleSelector) ? link.text().trim() : link.select(titleSelector).text().trim();
|
||||
String href = link.attr("href").trim();
|
||||
String time = item.select(".date").text().trim();
|
||||
rows.add(NutMap.NEW()
|
||||
.setv("href", completeNewsUrl(domain, href))
|
||||
.setv("time", time)
|
||||
.setv("text", title));
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
/**
|
||||
* Completes relative news links while preserving absolute website links.
|
||||
*
|
||||
* @param domain base website domain
|
||||
* @param href original href from the source page
|
||||
* @return absolute link for opening the news detail page
|
||||
*/
|
||||
private String completeNewsUrl(String domain, String href) {
|
||||
if (StrUtil.isBlank(href) || StrUtil.startWithIgnoreCase(href, "http://")
|
||||
|| StrUtil.startWithIgnoreCase(href, "https://")) {
|
||||
return href;
|
||||
}
|
||||
if (href.startsWith("/")) {
|
||||
return domain + href.substring(1);
|
||||
}
|
||||
return domain + href;
|
||||
}
|
||||
}
|
||||
|
||||
+5
-34
@@ -193,14 +193,15 @@ public class TourGroupController {
|
||||
t.id,
|
||||
t.jobNo,
|
||||
t.userName,
|
||||
'' AS mobile,
|
||||
t.idCard,
|
||||
COALESCE(NULLIF(t.mobile, ''), NULLIF(vu.mobile, ''), '') AS mobile,
|
||||
COALESCE(NULLIF(t.idCard, ''), NULLIF(vu.idCard, ''), '') AS idCard,
|
||||
t.unionId,
|
||||
t.unionName,
|
||||
t.signupTime
|
||||
FROM tour_ledger t
|
||||
INNER JOIN tour_matter m ON m.id = t.matterId
|
||||
INNER JOIN tour_line l ON l.id = m.lineId
|
||||
LEFT JOIN vw_user vu ON vu.loginname = t.jobNo
|
||||
$condition
|
||||
ORDER BY $orderColumn $orderBy, t.signupTime DESC, t.createdAt DESC
|
||||
""");
|
||||
@@ -212,7 +213,6 @@ public class TourGroupController {
|
||||
tourLedgerService.dao().execute(listSql);
|
||||
|
||||
List<NutMap> list = listSql.getList(NutMap.class);
|
||||
fillSignupMobile(list);
|
||||
fillSignupFamilies(list);
|
||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
|
||||
return Result.success(pagination);
|
||||
@@ -300,8 +300,8 @@ public class TourGroupController {
|
||||
t.id AS ledgerId,
|
||||
t.jobNo,
|
||||
t.userName,
|
||||
t.idCard,
|
||||
vu.mobile,
|
||||
COALESCE(NULLIF(t.idCard, ''), NULLIF(vu.idCard, ''), '') AS idCard,
|
||||
COALESCE(NULLIF(t.mobile, ''), NULLIF(vu.mobile, ''), '') AS mobile,
|
||||
t.unionName,
|
||||
l.lineName,
|
||||
CASE
|
||||
@@ -526,35 +526,6 @@ public class TourGroupController {
|
||||
}
|
||||
}
|
||||
|
||||
private void fillSignupMobile(List<NutMap> list) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> jobNos = new ArrayList<>();
|
||||
for (NutMap item : list) {
|
||||
String jobNo = item.getString("jobNo", "");
|
||||
if (StrUtil.isNotBlank(jobNo) && !jobNos.contains(jobNo)) {
|
||||
jobNos.add(jobNo);
|
||||
}
|
||||
}
|
||||
if (jobNos.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Sql sql = Sqls.create("SELECT loginname, mobile FROM vw_user WHERE loginname IN (@jobNos)");
|
||||
sql.setParam("jobNos", jobNos.toArray(new String[0]));
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
|
||||
Map<String, String> mobileMap = new HashMap<>();
|
||||
for (NutMap user : sql.getList(NutMap.class)) {
|
||||
mobileMap.put(user.getString("loginname", ""), user.getString("mobile", ""));
|
||||
}
|
||||
for (NutMap item : list) {
|
||||
item.put("mobile", mobileMap.getOrDefault(item.getString("jobNo", ""), ""));
|
||||
}
|
||||
}
|
||||
|
||||
private Cnd buildQueryCnd(Integer year, String lineName, String lineType, String unionId) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("m.delFlag", "=", false);
|
||||
|
||||
+25
-6
@@ -133,8 +133,8 @@ public class TourLedgerController {
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName) AS currentLineName,
|
||||
COALESCE(NULLIF(vu.sex, ''), t.gender, '') AS currentGender,
|
||||
vu.age AS age,
|
||||
COALESCE(NULLIF(vu.idCard, ''), t.idCard, '') AS currentIdCard,
|
||||
COALESCE(NULLIF(vu.mobile, ''), '') AS mobile,
|
||||
COALESCE(NULLIF(t.idCard, ''), NULLIF(vu.idCard, ''), '') AS currentIdCard,
|
||||
COALESCE(NULLIF(t.mobile, ''), NULLIF(vu.mobile, ''), '') AS currentMobile,
|
||||
IFNULL(f.familyCount, 0) AS familyCount,
|
||||
CASE
|
||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||
@@ -188,6 +188,7 @@ public class TourLedgerController {
|
||||
item.put("lineName", item.getString("currentLineName"));
|
||||
item.put("gender", item.getString("currentGender"));
|
||||
item.put("idCard", item.getString("currentIdCard"));
|
||||
item.put("mobile", item.getString("currentMobile"));
|
||||
});
|
||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
|
||||
return Result.success(pagination);
|
||||
@@ -206,6 +207,7 @@ public class TourLedgerController {
|
||||
if (!canAccessLedgerUnion(ledger.getUnionId())) {
|
||||
return Result.error("无权查看该台账记录");
|
||||
}
|
||||
fillLedgerContactFallback(ledger);
|
||||
ledger.setLineName(getCurrentLineName(ledger));
|
||||
Cnd familyCnd = Cnd.NEW();
|
||||
familyCnd.and(TourLedgerFamily::getLedgerId, "=", id);
|
||||
@@ -392,8 +394,8 @@ public class TourLedgerController {
|
||||
t.userName,
|
||||
COALESCE(NULLIF(vu.sex, ''), t.gender, '') AS gender,
|
||||
vu.age AS age,
|
||||
COALESCE(NULLIF(vu.idCard, ''), t.idCard, '') AS idCard,
|
||||
COALESCE(NULLIF(vu.mobile, ''), '') AS mobile,
|
||||
COALESCE(NULLIF(t.idCard, ''), NULLIF(vu.idCard, ''), '') AS idCard,
|
||||
COALESCE(NULLIF(t.mobile, ''), NULLIF(vu.mobile, ''), '') AS mobile,
|
||||
t.unitName,
|
||||
t.boardingPlace,
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName, '') AS lineName,
|
||||
@@ -491,11 +493,11 @@ public class TourLedgerController {
|
||||
t.id,
|
||||
t.jobNo,
|
||||
t.userName,
|
||||
t.idCard,
|
||||
COALESCE(NULLIF(t.idCard, ''), NULLIF(vu.idCard, ''), '') AS idCard,
|
||||
t.unionId,
|
||||
COALESCE(NULLIF(t.unionName, ''), su.name, vu.unionName, '') AS unionName,
|
||||
COALESCE(NULLIF(su.unionCode, ''), vu.unionCode, '') AS unionCode,
|
||||
COALESCE(NULLIF(vu.mobile, ''), '') AS mobile,
|
||||
COALESCE(NULLIF(t.mobile, ''), NULLIF(vu.mobile, ''), '') AS mobile,
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName, '') AS lineName,
|
||||
CASE
|
||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||
@@ -948,6 +950,23 @@ public class TourLedgerController {
|
||||
return StrUtil.isNotBlank(currentUnionId) && StrUtil.equals(currentUnionId, unionId);
|
||||
}
|
||||
|
||||
private void fillLedgerContactFallback(TourLedger ledger) {
|
||||
if (ledger == null || (StrUtil.isNotBlank(ledger.getIdCard()) && StrUtil.isNotBlank(ledger.getMobile()))) {
|
||||
return;
|
||||
}
|
||||
// Detail views prefer ledger contact data; blank ledger fields fall back to the user profile snapshot for display only.
|
||||
View_user user = tourLedgerService.dao().fetch(View_user.class, Cnd.where(View_user::getLoginname, "=", ledger.getJobNo()));
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isBlank(ledger.getIdCard())) {
|
||||
ledger.setIdCard(user.getIdCard());
|
||||
}
|
||||
if (StrUtil.isBlank(ledger.getMobile())) {
|
||||
ledger.setMobile(user.getMobile());
|
||||
}
|
||||
}
|
||||
|
||||
private void appendApprovedWorkflowFilter(Cnd cnd) {
|
||||
SqlExpressionGroup group = new SqlExpressionGroup();
|
||||
group.or("ins.id", "IS", null);
|
||||
|
||||
+111
-2
@@ -27,6 +27,7 @@ import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerFamily;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLine;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourUserAssignment;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerDirectRelativeService;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerFamilyService;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
|
||||
@@ -147,8 +148,13 @@ public class TourMySignupController {
|
||||
(SELECT MAX(id) FROM wf_process_task WHERE processInstanceId = ins.id AND taskName = 'startTask' AND taskState IN (10, 20)) AS startTaskId,
|
||||
IF(IFNULL(l.directFamilyUnitLine, 0) = 1 OR dr.id IS NOT NULL, 1, 0) AS directFamilyUnitLine,
|
||||
dr.id AS directRelativeId,
|
||||
COALESCE(NULLIF(t.idCard, ''), NULLIF(vu.idCard, ''), '') AS currentIdCard,
|
||||
COALESCE(NULLIF(t.mobile, ''), NULLIF(vu.mobile, ''), '') AS currentMobile,
|
||||
IFNULL(GROUP_CONCAT(DISTINCT task.displayName), IF(ins.id IS NULL, '', '结束')) AS curTaskName,
|
||||
IFNULL(f.familyCount, 0) AS familyCount,
|
||||
a.id AS assignmentId,
|
||||
IFNULL(a.cancelled, 0) AS assignmentCancelled,
|
||||
m.travelStartTime AS actionTravelStartTime,
|
||||
COALESCE(m.travelEndTime, tp.travelEndTime) AS travelEndTime,
|
||||
CASE
|
||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||
@@ -160,6 +166,7 @@ public class TourMySignupController {
|
||||
LEFT JOIN wf_process_task task ON task.processInstanceId = ins.id AND task.taskState = 10
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
LEFT JOIN tour_ledger_direct_relative dr ON dr.ledgerId = t.id AND dr.delFlag = 0
|
||||
LEFT JOIN vw_user vu ON vu.loginname = t.jobNo
|
||||
LEFT JOIN (
|
||||
SELECT ledgerId, COUNT(1) AS familyCount
|
||||
FROM tour_ledger_family
|
||||
@@ -167,6 +174,10 @@ public class TourMySignupController {
|
||||
GROUP BY ledgerId
|
||||
) f ON f.ledgerId = t.id
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_user_assignment a ON a.matterId = t.matterId
|
||||
AND a.userId = @currentUserId
|
||||
AND a.loginName = t.jobNo
|
||||
AND a.delFlag = 0
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
`year`,
|
||||
@@ -192,13 +203,20 @@ public class TourMySignupController {
|
||||
ORDER BY $orderColumn $orderBy, t.`year` DESC, t.signupTime DESC, t.createdAt DESC
|
||||
""");
|
||||
listSql.setCondition(cnd);
|
||||
listSql.setParam("currentUserId", SecurityUtil.getUserId());
|
||||
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
|
||||
listSql.setVar("orderBy", "descending".equals(pageForm.getPageOrderBy()) ? "desc" : "asc");
|
||||
listSql.setPager(tourLedgerService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||
listSql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(listSql);
|
||||
|
||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, listSql.getList(NutMap.class));
|
||||
List<NutMap> list = listSql.getList(NutMap.class);
|
||||
list.forEach(item -> {
|
||||
item.put("idCard", item.getString("currentIdCard"));
|
||||
item.put("mobile", item.getString("currentMobile"));
|
||||
});
|
||||
appendMySignupActionFlags(list);
|
||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@@ -209,6 +227,7 @@ public class TourMySignupController {
|
||||
if (ledger == null) {
|
||||
return Result.error("报名记录不存在");
|
||||
}
|
||||
fillLedgerContactFallback(ledger);
|
||||
Cnd familyCnd = Cnd.where(TourLedgerFamily::getDelFlag, "=", false)
|
||||
.and(TourLedgerFamily::getLedgerId, "=", ledger.getId());
|
||||
familyCnd.asc(TourLedgerFamily::getCreatedAt);
|
||||
@@ -279,6 +298,7 @@ public class TourMySignupController {
|
||||
if (ledger == null) {
|
||||
return Result.error("报名记录不存在");
|
||||
}
|
||||
fillLedgerContactFallback(ledger);
|
||||
if (StrUtil.isBlank(ledger.getMatterId())) {
|
||||
return Result.error("历史报名记录缺少事项信息,不能修改");
|
||||
}
|
||||
@@ -536,6 +556,29 @@ public class TourMySignupController {
|
||||
return Result.success().addMsg("取消报名成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动端我的疗休养退出报名入口。
|
||||
* 仅允许当前登录人操作自己的人员分配记录,具体退出截止天数和台账清理逻辑复用 service 层。
|
||||
*
|
||||
* @param assignmentId 人员分配记录ID
|
||||
* @return 退出结果
|
||||
*/
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission(value = {"tour.mysignup", "h5.tour.mysignup"}, mode = SaMode.OR)
|
||||
public Result doLeaveTour(String assignmentId) {
|
||||
TourUserAssignment assignment = fetchOwnAssignment(assignmentId);
|
||||
if (assignment == null) {
|
||||
return Result.error("人员分配记录不存在或无权操作");
|
||||
}
|
||||
try {
|
||||
tourUserAssignmentService.cancelAssignment(assignment.getId());
|
||||
return Result.success().addMsg("退出疗休养报名成功");
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission(value = {"tour.mysignup", "h5.tour.mysignup"}, mode = SaMode.OR)
|
||||
@@ -605,6 +648,55 @@ public class TourMySignupController {
|
||||
.and(TourLedger::getJobNo, "=", currentJobNo()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询当前登录人的人员分配记录,避免移动端传入他人分配ID进行退出。
|
||||
*/
|
||||
private TourUserAssignment fetchOwnAssignment(String assignmentId) {
|
||||
if (StrUtil.isBlank(assignmentId)) {
|
||||
return null;
|
||||
}
|
||||
return tourUserAssignmentService.fetch(Cnd.where(TourUserAssignment::getId, "=", assignmentId)
|
||||
.and(TourUserAssignment::getUserId, "=", SecurityUtil.getUserId())
|
||||
.and(TourUserAssignment::getLoginName, "=", currentJobNo())
|
||||
.and(TourUserAssignment::getDelFlag, "=", false));
|
||||
}
|
||||
|
||||
/**
|
||||
* 给移动端我的疗休养列表追加操作按钮显示标识,退出截止时间与 service 层最终校验保持一致。
|
||||
*/
|
||||
private void appendMySignupActionFlags(List<NutMap> list) {
|
||||
if (Lang.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
int cancelDeadlineDays = tourUserAssignmentService.getCancelDeadlineDays();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
for (NutMap item : list) {
|
||||
boolean assignmentCancelled = item.getBoolean("assignmentCancelled", false);
|
||||
String assignmentId = item.getString("assignmentId", "");
|
||||
item.put("cancelDeadlineDays", cancelDeadlineDays);
|
||||
item.put("canLeaveTour", StrUtil.isNotBlank(assignmentId)
|
||||
&& !assignmentCancelled
|
||||
&& isWithinLeaveTimeRange(item.getString("actionTravelStartTime", ""), cancelDeadlineDays, now));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前时间是否处于“出行开始前N天”到“出行开始前”的可退出范围内。
|
||||
*/
|
||||
private boolean isWithinLeaveTimeRange(String travelStartTime, int cancelDeadlineDays, LocalDateTime now) {
|
||||
try {
|
||||
String normalized = normalizeDateTime(travelStartTime, false);
|
||||
if (StrUtil.isBlank(normalized)) {
|
||||
return false;
|
||||
}
|
||||
LocalDateTime startTime = LocalDateTime.parse(normalized, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
LocalDateTime deadline = startTime.minusDays(cancelDeadlineDays);
|
||||
return !now.isBefore(deadline) && now.isBefore(startTime);
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Result checkSignup(TourLedger ledger) {
|
||||
if (ledger == null) {
|
||||
return Result.error("参数错误");
|
||||
@@ -1037,13 +1129,30 @@ public class TourMySignupController {
|
||||
ledger.setJobNo(user == null ? SecurityUtil.getUserLoginname() : defaultIfBlank(user.getLoginname(), SecurityUtil.getUserLoginname()));
|
||||
ledger.setUserName(user == null ? SecurityUtil.getUserUsername() : defaultIfBlank(user.getUsername(), SecurityUtil.getUserUsername()));
|
||||
ledger.setGender(user == null ? ledger.getGender() : defaultIfBlank(user.getSex(), ledger.getGender()));
|
||||
ledger.setIdCard(user == null ? ledger.getIdCard() : defaultIfBlank(user.getIdCard(), ledger.getIdCard()));
|
||||
ledger.setIdCard(user == null ? ledger.getIdCard() : defaultIfBlank(ledger.getIdCard(), user.getIdCard()));
|
||||
ledger.setUnitId(user == null ? SecurityUtil.getUnitId() : defaultIfBlank(user.getUnitId(), SecurityUtil.getUnitId()));
|
||||
ledger.setUnitName(user == null ? ledger.getUnitName() : defaultIfBlank(user.getUnitName(), ledger.getUnitName()));
|
||||
ledger.setUnionId(user == null ? SecurityUtil.getUnionId() : defaultIfBlank(user.getUnionId(), ledger.getUnionId()));
|
||||
ledger.setUnionName(user == null ? ledger.getUnionName() : defaultIfBlank(user.getUnionName(), ledger.getUnionName()));
|
||||
}
|
||||
|
||||
private void fillLedgerContactFallback(TourLedger ledger) {
|
||||
if (ledger == null || (StrUtil.isNotBlank(ledger.getIdCard()) && StrUtil.isNotBlank(ledger.getMobile()))) {
|
||||
return;
|
||||
}
|
||||
// My signup detail keeps ledger contact data first; blank contact fields use the user profile snapshot for display only.
|
||||
View_user user = tourLedgerService.dao().fetch(View_user.class, Cnd.where(View_user::getLoginname, "=", ledger.getJobNo()));
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isBlank(ledger.getIdCard())) {
|
||||
ledger.setIdCard(user.getIdCard());
|
||||
}
|
||||
if (StrUtil.isBlank(ledger.getMobile())) {
|
||||
ledger.setMobile(user.getMobile());
|
||||
}
|
||||
}
|
||||
|
||||
private void clearBedInfo(TourLedger ledger, List<TourLedgerFamily> familyList) {
|
||||
if (ledger != null) {
|
||||
ledger.setBedType("");
|
||||
|
||||
+19
@@ -7,6 +7,7 @@ import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.sys.views.View_user;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerDirectRelative;
|
||||
@@ -171,6 +172,7 @@ public class TourSchoolUnionApprovalController {
|
||||
if (ledger == null) {
|
||||
return Result.error("报名记录不存在或无权查看");
|
||||
}
|
||||
fillLedgerContactFallback(ledger);
|
||||
ledger.setLineName(getCurrentLineName(ledger));
|
||||
Cnd familyCnd = Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId())
|
||||
.and(TourLedgerFamily::getDelFlag, "=", false);
|
||||
@@ -465,6 +467,23 @@ public class TourSchoolUnionApprovalController {
|
||||
return sql.getInt() > 0 ? tourLedgerService.fetch(id) : null;
|
||||
}
|
||||
|
||||
private void fillLedgerContactFallback(TourLedger ledger) {
|
||||
if (ledger == null || (StrUtil.isNotBlank(ledger.getIdCard()) && StrUtil.isNotBlank(ledger.getMobile()))) {
|
||||
return;
|
||||
}
|
||||
// Approval detail keeps ledger contact data first; blank contact fields use the user profile snapshot for display only.
|
||||
View_user user = tourLedgerService.dao().fetch(View_user.class, Cnd.where(View_user::getLoginname, "=", ledger.getJobNo()));
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isBlank(ledger.getIdCard())) {
|
||||
ledger.setIdCard(user.getIdCard());
|
||||
}
|
||||
if (StrUtil.isBlank(ledger.getMobile())) {
|
||||
ledger.setMobile(user.getMobile());
|
||||
}
|
||||
}
|
||||
|
||||
private String getCurrentLineName(TourLedger ledger) {
|
||||
if (ledger == null) {
|
||||
return "";
|
||||
|
||||
+145
-17
@@ -26,6 +26,7 @@ import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerFamily;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLine;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourUserAssignment;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerDirectRelativeService;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerFamilyService;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
|
||||
@@ -167,6 +168,8 @@ public class TourSignupController {
|
||||
m.`year`,
|
||||
m.unionId,
|
||||
COALESCE(u.name, '校工会') AS unionName,
|
||||
m.signupStartTime,
|
||||
m.signupEndTime,
|
||||
m.lineId,
|
||||
l.lineName,
|
||||
l.lineType,
|
||||
@@ -185,6 +188,8 @@ public class TourSignupController {
|
||||
my.instanceState,
|
||||
my.taskKey,
|
||||
my.startTaskId,
|
||||
my.assignmentId,
|
||||
IFNULL(my.assignmentCancelled, 0) AS assignmentCancelled,
|
||||
IFNULL(my.canRevoke, 0) AS canRevoke,
|
||||
CASE WHEN my.ledgerId IS NULL THEN 0 ELSE 1 END AS signed
|
||||
FROM tour_matter m
|
||||
@@ -217,9 +222,12 @@ public class TourSignupController {
|
||||
MAX(ins.id) AS instanceId,
|
||||
MAX(ins.state) AS instanceState,
|
||||
MAX(task.taskName) AS taskKey,
|
||||
MAX(ua.id) AS assignmentId,
|
||||
MAX(IFNULL(ua.cancelled, 0)) AS assignmentCancelled,
|
||||
MAX((SELECT MAX(st.id) FROM wf_process_task st WHERE st.processInstanceId = ins.id AND st.taskName = 'startTask' AND st.taskState IN (10, 20))) AS startTaskId,
|
||||
MAX(IF((SELECT taskName FROM wf_process_task tt WHERE tt.id = task.taskParentId) = 'startTask', 1, 0)) AS canRevoke
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN tour_user_assignment ua ON ua.matterId = t.matterId AND ua.loginName = t.jobNo AND ua.delFlag = 0
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id AND ins.state <> @abandonState
|
||||
LEFT JOIN wf_process_task task ON task.processInstanceId = ins.id AND task.taskState = 10
|
||||
WHERE t.delFlag = 0
|
||||
@@ -240,17 +248,73 @@ public class TourSignupController {
|
||||
listSql.setCallback(Sqls.callback.maps());
|
||||
tourMatterService.dao().execute(listSql);
|
||||
|
||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, listSql.getList(NutMap.class));
|
||||
List<NutMap> list = listSql.getList(NutMap.class);
|
||||
appendH5LineActionFlags(list);
|
||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动端退出疗休养入口,复用人员分配退出逻辑并限定只能操作当前登录人的分配记录。
|
||||
*
|
||||
* @param assignmentId 人员分配记录ID
|
||||
* @return 退出结果
|
||||
*/
|
||||
@At("/h5/doLeaveTour")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("h5.tour.signup")
|
||||
public Result h5DoLeaveTour(String assignmentId) {
|
||||
TourUserAssignment assignment = fetchCurrentUserAssignment(assignmentId);
|
||||
if (assignment == null) {
|
||||
return Result.error("人员分配记录不存在或无权操作");
|
||||
}
|
||||
try {
|
||||
tourUserAssignmentService.cancelAssignment(assignment.getId());
|
||||
return Result.success();
|
||||
} catch (IllegalArgumentException e) {
|
||||
return Result.error(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动端取消当前线路,删除本人的报名台账并清空人员分配中的线路快照。
|
||||
*
|
||||
* @param ledgerId 报名台账ID
|
||||
* @return 取消结果
|
||||
*/
|
||||
@At("/h5/doCancelLine")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("h5.tour.signup")
|
||||
public Result h5DoCancelLine(String ledgerId) {
|
||||
if (StrUtil.isBlank(ledgerId)) {
|
||||
return Result.error("报名台账参数错误");
|
||||
}
|
||||
TourLedger ledger = tourLedgerService.fetch(Cnd.where(TourLedger::getId, "=", ledgerId)
|
||||
.and(TourLedger::getJobNo, "=", currentJobNo())
|
||||
.and(TourLedger::getDelFlag, "=", false));
|
||||
if (ledger == null) {
|
||||
return Result.error("报名台账不存在或无权操作");
|
||||
}
|
||||
TourMatter matter = tourMatterService.fetch(ledger.getMatterId());
|
||||
Result signupTimeResult = checkSignupTime(matter);
|
||||
if (signupTimeResult != null) {
|
||||
return signupTimeResult;
|
||||
}
|
||||
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId()));
|
||||
tourLedgerDirectRelativeService.clear(Cnd.where(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId()));
|
||||
tourLedgerService.delete(ledger.getId());
|
||||
tourUserAssignmentService.clearExistingAssignmentMatterAfterLedgerDelete(ledger.getMatterId(), ledger.getJobNo());
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission(value = {"tour.signup", "h5.tour.signup"}, mode = SaMode.OR)
|
||||
public Result pageData(PageForm pageForm, Integer year, String lineName, String lineType, String unionId) {
|
||||
public Result pageData(PageForm pageForm, Integer year, String lineName, String lineType, String unionId, String travelPeriod) {
|
||||
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
|
||||
pageForm.defaultSortAsc("lineName");
|
||||
}
|
||||
Cnd cnd = buildQueryCnd(year, lineName, lineType, unionId);
|
||||
appendTravelPeriodCnd(cnd, travelPeriod);
|
||||
String currentJobNo = currentJobNo();
|
||||
|
||||
Sql countSql = Sqls.create("""
|
||||
@@ -375,9 +439,10 @@ public class TourSignupController {
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询移动端选择路线页的出行时间筛选项,按 yyyyMMdd-yyyyMMdd 格式拼接事项出行起止时间并去重。
|
||||
* Queries distinct travel periods for PC signup and H5 line selection.
|
||||
* The value uses yyyyMMdd-yyyyMMdd so the frontend can pass it back as a stable filter key.
|
||||
*/
|
||||
@At("/h5/travelPeriodOptions")
|
||||
@At({"/h5/travelPeriodOptions", "/travelPeriodOptions"})
|
||||
@SaCheckPermission(value = {"tour.signup", "h5.tour.signup"}, mode = SaMode.OR)
|
||||
public Result h5TravelPeriodOptions(Integer year, String lineType, Boolean directFamilyUnitLine) {
|
||||
Cnd cnd = buildQueryCnd(year, null, null, null);
|
||||
@@ -490,20 +555,11 @@ public class TourSignupController {
|
||||
.and(TourLedger::getJobNo, "=", staff.getString("jobNo"));
|
||||
ledgerCnd.desc(TourLedger::getCreatedAt);
|
||||
TourLedger ledger = tourLedgerService.fetch(ledgerCnd);
|
||||
TourMatter matterEntity = tourMatterService.fetch(matter.getString("matterId"));
|
||||
TourLedger ruleLedger = new TourLedger();
|
||||
ruleLedger.setId(ledger == null ? null : ledger.getId());
|
||||
ruleLedger.setYear(matterEntity == null ? matter.getInt("year") : matterEntity.getYear());
|
||||
ruleLedger.setMatterId(matter.getString("matterId"));
|
||||
ruleLedger.setLineId(matter.getString("lineId"));
|
||||
ruleLedger.setLineName(matter.getString("lineName"));
|
||||
Result ruleResult = checkSignupRule(ruleLedger, matterEntity, ledger);
|
||||
if (ruleResult != null) {
|
||||
return ruleResult;
|
||||
}
|
||||
// Viewing a line should only load detail data; signup rules are enforced when the user starts or submits signup.
|
||||
List<TourLedgerFamily> families = Collections.emptyList();
|
||||
TourLedgerDirectRelative directRelative = null;
|
||||
if (ledger != null) {
|
||||
fillLedgerContactFallback(ledger, staff);
|
||||
Cnd familyCnd = Cnd.where(TourLedgerFamily::getDelFlag, "=", false)
|
||||
.and(TourLedgerFamily::getLedgerId, "=", ledger.getId());
|
||||
familyCnd.asc(TourLedgerFamily::getCreatedAt);
|
||||
@@ -793,7 +849,7 @@ public class TourSignupController {
|
||||
return workflowResult;
|
||||
}
|
||||
}
|
||||
return Result.success().addMsg(approvalRequired ? (update ? "修改已提交,等待审核" : "申请已提交,等待审核") : (update ? "修改成功" : "报名成功"));
|
||||
return Result.success().addMsg(approvalRequired ? (update ? "修改已提交,等待审核" : "申请已提交,等待审核") : (update ? "修改成功" : "恭喜您已报名成功"));
|
||||
}
|
||||
|
||||
@At
|
||||
@@ -1199,6 +1255,65 @@ public class TourSignupController {
|
||||
return setting != null && "ASSIGNED_USER".equals(setting.getSignupEligibilityMode());
|
||||
}
|
||||
|
||||
private void appendH5LineActionFlags(List<NutMap> list) {
|
||||
if (Lang.isEmpty(list)) {
|
||||
return;
|
||||
}
|
||||
int cancelDeadlineDays = tourUserAssignmentService.getCancelDeadlineDays();
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
for (NutMap item : list) {
|
||||
boolean signed = item.getBoolean("signed", false);
|
||||
boolean assignmentCancelled = item.getBoolean("assignmentCancelled", false);
|
||||
String assignmentId = item.getString("assignmentId", "");
|
||||
String ledgerId = item.getString("ledgerId", "");
|
||||
// Button visibility is calculated on the server so H5 follows the same time rules as backend submit actions.
|
||||
item.put("cancelDeadlineDays", cancelDeadlineDays);
|
||||
item.put("canLeaveTour", signed && StrUtil.isNotBlank(assignmentId) && !assignmentCancelled
|
||||
&& isWithinLeaveTimeRange(item.getString("travelStartTime", ""), cancelDeadlineDays, now));
|
||||
item.put("canCancelLine", signed && StrUtil.isNotBlank(ledgerId)
|
||||
&& isWithinSignupRange(item.getString("signupStartTime", ""), item.getString("signupEndTime", ""), now));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isWithinLeaveTimeRange(String travelStartTime, int cancelDeadlineDays, LocalDateTime now) {
|
||||
LocalDateTime startTime = parseDateTimeValue(travelStartTime, false);
|
||||
if (startTime == null) {
|
||||
return false;
|
||||
}
|
||||
LocalDateTime deadline = startTime.minusDays(cancelDeadlineDays);
|
||||
return !now.isBefore(deadline) && now.isBefore(startTime);
|
||||
}
|
||||
|
||||
private boolean isWithinSignupRange(String signupStartTime, String signupEndTime, LocalDateTime now) {
|
||||
LocalDateTime startTime = parseDateTimeValue(signupStartTime, false);
|
||||
LocalDateTime endTime = parseDateTimeValue(signupEndTime, true);
|
||||
if (startTime == null || endTime == null) {
|
||||
return false;
|
||||
}
|
||||
return !now.isBefore(startTime) && !now.isAfter(endTime);
|
||||
}
|
||||
|
||||
private LocalDateTime parseDateTimeValue(String value, boolean endOfDay) {
|
||||
String normalized = normalizeDateTime(value, endOfDay);
|
||||
if (StrUtil.isBlank(normalized)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return LocalDateTime.parse(normalized.substring(0, 19), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private TourUserAssignment fetchCurrentUserAssignment(String assignmentId) {
|
||||
if (StrUtil.isBlank(assignmentId)) {
|
||||
return null;
|
||||
}
|
||||
return tourLedgerService.dao().fetch(TourUserAssignment.class, Cnd.where(TourUserAssignment::getId, "=", assignmentId)
|
||||
.and(TourUserAssignment::getUserId, "=", SecurityUtil.getUserId())
|
||||
.and(TourUserAssignment::getDelFlag, "=", false));
|
||||
}
|
||||
|
||||
/**
|
||||
* 统一校验疗休养事项报名时间,确保资格名单校验在时间校验通过后再执行。
|
||||
*/
|
||||
@@ -1497,13 +1612,26 @@ public class TourSignupController {
|
||||
ledger.setJobNo(user == null ? SecurityUtil.getUserLoginname() : defaultIfBlank(user.getLoginname(), SecurityUtil.getUserLoginname()));
|
||||
ledger.setUserName(user == null ? SecurityUtil.getUserUsername() : defaultIfBlank(user.getUsername(), SecurityUtil.getUserUsername()));
|
||||
ledger.setGender(user == null ? ledger.getGender() : defaultIfBlank(user.getSex(), ledger.getGender()));
|
||||
ledger.setIdCard(user == null ? ledger.getIdCard() : defaultIfBlank(user.getIdCard(), ledger.getIdCard()));
|
||||
ledger.setIdCard(user == null ? ledger.getIdCard() : defaultIfBlank(ledger.getIdCard(), user.getIdCard()));
|
||||
ledger.setUnitId(user == null ? SecurityUtil.getUnitId() : defaultIfBlank(user.getUnitId(), SecurityUtil.getUnitId()));
|
||||
ledger.setUnitName(user == null ? ledger.getUnitName() : defaultIfBlank(user.getUnitName(), ledger.getUnitName()));
|
||||
ledger.setUnionId(user == null ? SecurityUtil.getUnionId() : defaultIfBlank(user.getUnionId(), SecurityUtil.getUnionId()));
|
||||
ledger.setUnionName(user == null ? ledger.getUnionName() : defaultIfBlank(user.getUnionName(), ledger.getUnionName()));
|
||||
}
|
||||
|
||||
private void fillLedgerContactFallback(TourLedger ledger, NutMap staff) {
|
||||
if (ledger == null || staff == null) {
|
||||
return;
|
||||
}
|
||||
// Signup detail keeps saved ledger contact data first; blank contact fields use the user profile snapshot for display only.
|
||||
if (StrUtil.isBlank(ledger.getIdCard())) {
|
||||
ledger.setIdCard(staff.getString("idCard", ""));
|
||||
}
|
||||
if (StrUtil.isBlank(ledger.getMobile())) {
|
||||
ledger.setMobile(staff.getString("mobile", ""));
|
||||
}
|
||||
}
|
||||
|
||||
private List<TourLedgerFamily> parseFamilies(String families) {
|
||||
if (StrUtil.isBlank(families)) {
|
||||
return Collections.emptyList();
|
||||
|
||||
+19
@@ -7,6 +7,7 @@ import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.flow.enums.ProcessTaskStateEnum;
|
||||
import com.budwk.app.sys.views.View_user;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerDirectRelative;
|
||||
@@ -171,6 +172,7 @@ public class TourUnionApprovalController {
|
||||
if (ledger == null) {
|
||||
return Result.error("报名记录不存在或无权查看");
|
||||
}
|
||||
fillLedgerContactFallback(ledger);
|
||||
ledger.setLineName(getCurrentLineName(ledger));
|
||||
Cnd familyCnd = Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId())
|
||||
.and(TourLedgerFamily::getDelFlag, "=", false);
|
||||
@@ -465,6 +467,23 @@ public class TourUnionApprovalController {
|
||||
return sql.getInt() > 0 ? tourLedgerService.fetch(id) : null;
|
||||
}
|
||||
|
||||
private void fillLedgerContactFallback(TourLedger ledger) {
|
||||
if (ledger == null || (StrUtil.isNotBlank(ledger.getIdCard()) && StrUtil.isNotBlank(ledger.getMobile()))) {
|
||||
return;
|
||||
}
|
||||
// Approval detail keeps ledger contact data first; blank contact fields use the user profile snapshot for display only.
|
||||
View_user user = tourLedgerService.dao().fetch(View_user.class, Cnd.where(View_user::getLoginname, "=", ledger.getJobNo()));
|
||||
if (user == null) {
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isBlank(ledger.getIdCard())) {
|
||||
ledger.setIdCard(user.getIdCard());
|
||||
}
|
||||
if (StrUtil.isBlank(ledger.getMobile())) {
|
||||
ledger.setMobile(user.getMobile());
|
||||
}
|
||||
}
|
||||
|
||||
private String getCurrentLineName(TourLedger ledger) {
|
||||
if (ledger == null) {
|
||||
return "";
|
||||
|
||||
+2
-2
@@ -416,9 +416,9 @@ public class TourUnionLedgerController {
|
||||
SELECT
|
||||
t.id,
|
||||
t.userName,
|
||||
t.idCard,
|
||||
COALESCE(NULLIF(t.idCard, ''), NULLIF(vu.idCard, ''), '') AS idCard,
|
||||
t.unionName,
|
||||
vu.mobile,
|
||||
COALESCE(NULLIF(t.mobile, ''), NULLIF(vu.mobile, ''), '') AS mobile,
|
||||
m.travelStartTime,
|
||||
m.travelEndTime,
|
||||
m.estimatedCost,
|
||||
|
||||
@@ -51,6 +51,11 @@ public class TourLedger extends BaseModel implements Serializable {
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String idCard;
|
||||
|
||||
@Column
|
||||
@Comment("手机号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String mobile;
|
||||
|
||||
@Column
|
||||
@Comment("所在单位ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
|
||||
+7
@@ -272,6 +272,13 @@ public interface TourUserAssignmentService extends BaseService<TourUserAssignmen
|
||||
*/
|
||||
void cancelAssignment(String id);
|
||||
|
||||
/**
|
||||
* 查询疗休养退出取消的出行开始前限制天数,供移动端按钮展示和实际退出校验保持一致。
|
||||
*
|
||||
* @return 出行开始前允许退出的天数
|
||||
*/
|
||||
int getCancelDeadlineDays();
|
||||
|
||||
/**
|
||||
* 将人员分配记录从已退出恢复为未退出。
|
||||
* 只恢复人员分配表状态,不自动恢复已删除的报名台账。
|
||||
|
||||
+6
-3
@@ -886,12 +886,15 @@ public class TourUserAssignmentServiceImpl extends BaseServiceImpl<TourUserAssig
|
||||
LocalDateTime travelStartTime = parseTravelStartTime(matter.getTravelStartTime());
|
||||
int days = getCancelDeadlineDays();
|
||||
LocalDateTime deadline = travelStartTime.minusDays(days);
|
||||
if (LocalDateTime.now().isAfter(deadline)) {
|
||||
throw new IllegalArgumentException("当前路线已超过取消时间,需在出行开始前" + days + "天取消");
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
// 退出报名仅允许在“出行开始前N天”到“出行开始前”之间办理,出行开始时刻不能再退出。
|
||||
if (now.isBefore(deadline) || !now.isBefore(travelStartTime)) {
|
||||
throw new IllegalArgumentException("当前不在退出时间范围内,需在出行开始前" + days + "天至出行开始前办理");
|
||||
}
|
||||
}
|
||||
|
||||
private int getCancelDeadlineDays() {
|
||||
@Override
|
||||
public int getCancelDeadlineDays() {
|
||||
try {
|
||||
Sys_config config = sysConfigService == null ? null : sysConfigService.getValueByKey(TOUR_CANCEL_DEADLINE_DAYS_KEY);
|
||||
String value = config == null ? "" : config.getConfigValue();
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Add signup ledger mobile field; signup mobile is stored with the ledger and does not update user profile data.
|
||||
ALTER TABLE `tour_ledger`
|
||||
ADD COLUMN `mobile` varchar(30) DEFAULT NULL COMMENT '手机号' AFTER `idCard`;
|
||||
@@ -128,7 +128,7 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<work-template v-if="$auth.hasRoleOr(['SYSADMIN','SCHOOL_UNION_ADMIN'])"></work-template>
|
||||
<!-- <work-template v-if="$auth.hasRoleOr(['SYSADMIN','SCHOOL_UNION_ADMIN'])"></work-template>-->
|
||||
|
||||
<jcdt :list="websiteNews"></jcdt>
|
||||
</div>
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ layout("/layouts/platform.html"){
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="分配时间" prop="assignedAt" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="操作" width="320" align="center" header-align="center" fixed="right">
|
||||
<el-table-column label="操作" width="380" align="center" header-align="center" fixed="right">
|
||||
<template slot-scope="{row}">
|
||||
<el-button v-if="!row.matterId && row.personType !== 'BACKUP'" size="mini" type="primary" @click="openSelectMatter(row)">选择路线</el-button>
|
||||
<el-button size="mini" type="primary" :loading="personTypeSwitching === row.id" @click="switchPersonType(row, row.personType === 'BACKUP' ? 'FORMAL' : 'BACKUP')">{{ row.personType === 'BACKUP' ? '转为正式' : '转为替补' }}</el-button>
|
||||
|
||||
@@ -42,7 +42,23 @@ layout("/layouts/platform.html"){
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="线路列表"></table-tool>
|
||||
<div class="tour-list-header">
|
||||
<table-tool :app="this" label="线路列表"></table-tool>
|
||||
<div class="tour-period-area" v-if="travelPeriodOptions.length > 0">
|
||||
<div class="tour-period-filter">
|
||||
<button
|
||||
v-for="item in travelPeriodOptions"
|
||||
:key="item.value"
|
||||
type="button"
|
||||
class="tour-period-chip"
|
||||
:class="{'tour-period-chip--active': pageForm.travelPeriod === item.value}"
|
||||
@click="selectTravelPeriod(item)">
|
||||
{{ item.value }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="tour-period-tip">可选择指定出行时间,快速查看对应路线</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table
|
||||
v-loading="tableLoading"
|
||||
@@ -470,6 +486,62 @@ layout("/layouts/platform.html"){
|
||||
.over-cost-form-item .el-radio__input.is-checked + .el-radio__label {
|
||||
color: #f56c6c;
|
||||
}
|
||||
|
||||
.tour-list-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.tour-list-header /deep/ .wk-table-tool,
|
||||
.tour-list-header .wk-table-tool {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.tour-period-area {
|
||||
flex: 1 1 0;
|
||||
min-width: 240px;
|
||||
}
|
||||
|
||||
.tour-period-filter {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tour-period-tip {
|
||||
margin-top: 6px;
|
||||
color: #f56c6c;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.tour-period-chip {
|
||||
min-width: 118px;
|
||||
height: 34px;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
padding: 0 14px;
|
||||
background: #ffffff;
|
||||
color: #606266;
|
||||
font-size: 14px;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
box-sizing: border-box;
|
||||
transition: all 0.18s ease;
|
||||
}
|
||||
|
||||
.tour-period-chip:hover,
|
||||
.tour-period-chip--active {
|
||||
border-color: #0076c8;
|
||||
background: #0076c8;
|
||||
color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
@@ -481,6 +553,7 @@ layout("/layouts/platform.html"){
|
||||
return {
|
||||
unionOptions: [],
|
||||
lineTypeOptions: [],
|
||||
travelPeriodOptions: [],
|
||||
signupVisible: false,
|
||||
signupLoading: false,
|
||||
cancelLoading: false,
|
||||
@@ -505,17 +578,36 @@ layout("/layouts/platform.html"){
|
||||
year: currentYear,
|
||||
lineName: "",
|
||||
lineType: "",
|
||||
unionId: ""
|
||||
unionId: "",
|
||||
travelPeriod: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetSearch() {
|
||||
this.pageForm.year = moment().format("YYYY")
|
||||
const currentYear = moment().format("YYYY")
|
||||
const yearChanged = this.pageForm.year !== currentYear
|
||||
this.pageForm.year = currentYear
|
||||
this.pageForm.lineName = ""
|
||||
this.pageForm.lineType = ""
|
||||
this.pageForm.unionId = ""
|
||||
this.pageForm.travelPeriod = ""
|
||||
if (yearChanged) {
|
||||
return
|
||||
}
|
||||
this.loadLineTypeOptions()
|
||||
this.loadTravelPeriodOptions(true).then(() => {
|
||||
this.doSearch()
|
||||
})
|
||||
},
|
||||
selectTravelPeriod(item) {
|
||||
const travelPeriod = item && item.value ? item.value : ""
|
||||
if (this.pageForm.travelPeriod === travelPeriod) {
|
||||
this.pageForm.travelPeriod = ""
|
||||
this.doSearch()
|
||||
return
|
||||
}
|
||||
this.pageForm.travelPeriod = travelPeriod
|
||||
this.doSearch()
|
||||
},
|
||||
signupActionText(row) {
|
||||
@@ -1026,6 +1118,31 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
})
|
||||
},
|
||||
loadTravelPeriodOptions(defaultFirst) {
|
||||
return this.$axios.post(loc() + "/travelPeriodOptions", {
|
||||
year: this.pageForm.year
|
||||
}).then((res) => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
this.travelPeriodOptions = (res.data || []).map((item) => {
|
||||
const value = item.travelPeriod || ""
|
||||
return {
|
||||
value: value,
|
||||
name: value || "未设置出行时间"
|
||||
}
|
||||
}).filter((item) => item.value).sort((a, b) => {
|
||||
return String(a.value).localeCompare(String(b.value))
|
||||
})
|
||||
if (defaultFirst) {
|
||||
this.pageForm.travelPeriod = this.travelPeriodOptions.length > 0 ? this.travelPeriodOptions[0].value : ""
|
||||
}
|
||||
}).catch(() => {
|
||||
if (defaultFirst) {
|
||||
this.pageForm.travelPeriod = ""
|
||||
}
|
||||
})
|
||||
},
|
||||
loadDirectRelativeOptions() {
|
||||
this.$axios.post(loc() + "/directRelativeOptions").then((res) => {
|
||||
if (res.code === 0) {
|
||||
@@ -1054,12 +1171,18 @@ layout("/layouts/platform.html"){
|
||||
this.loadDirectRelativeOptions()
|
||||
this.loadBedTypeOptions()
|
||||
this.loadFamilyRelationshipOptions()
|
||||
this.pageData()
|
||||
this.loadTravelPeriodOptions(true).then(() => {
|
||||
this.pageData()
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
"pageForm.year"() {
|
||||
this.pageForm.lineType = ""
|
||||
this.pageForm.travelPeriod = ""
|
||||
this.loadLineTypeOptions()
|
||||
this.loadTravelPeriodOptions(true).then(() => {
|
||||
this.doSearch()
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -378,6 +378,7 @@ layout("/layouts/platform_h5.html"){
|
||||
<van-button size="small" type="info" plain @click="openView(row)">查看</van-button>
|
||||
<van-button v-if="canModify(row) || isTravelEnded(row)" size="small" type="info" :disabled="isTravelEnded(row)" @click="openSignup(row)">修改</van-button>
|
||||
<van-button v-if="canRevoke(row)" size="small" type="danger" plain @click="revokeSignup(row)">撤回</van-button>
|
||||
<van-button v-if="showLeaveTour(row)" size="small" type="danger" plain :loading="leaveLoading" @click="leaveTour(row)">退出疗休养报名</van-button>
|
||||
<van-button v-if="showCancel(row)" size="small" type="danger" plain :disabled="isTravelEnded(row)" @click="cancelByRow(row)">取消报名</van-button>
|
||||
<van-button v-if="showExport(row)" size="small" type="info" plain :disabled="!canExport(row)" @click="downloadExport(row, 'cost')">报销申请</van-button>
|
||||
<van-button v-if="showDirectRelativeExport(row)" size="small" type="info" plain :disabled="!canDirectRelativeExport(row)" @click="downloadExport(row, 'direct')">亲属线路申请</van-button>
|
||||
@@ -584,6 +585,7 @@ layout("/layouts/platform_h5.html"){
|
||||
signupVisible: false,
|
||||
signupLoading: false,
|
||||
cancelLoading: false,
|
||||
leaveLoading: false,
|
||||
allowFamily: false,
|
||||
fillBedInfo: true,
|
||||
signupForm: {},
|
||||
@@ -733,6 +735,10 @@ layout("/layouts/platform_h5.html"){
|
||||
showCancel(row) {
|
||||
return !this.isApprovalRow(row) || this.canModify(row)
|
||||
},
|
||||
// 退出疗休养报名依赖人员分配记录,已退出的数据不再展示入口。
|
||||
showLeaveTour(row) {
|
||||
return this.toBoolean(row && row.canLeaveTour)
|
||||
},
|
||||
showExport(row) {
|
||||
return this.toBoolean(row && row.overCostReimbursed)
|
||||
},
|
||||
@@ -1075,6 +1081,32 @@ layout("/layouts/platform_h5.html"){
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
leaveTour(row) {
|
||||
if (!row || !row.assignmentId) {
|
||||
vant.Toast("暂无可退出的疗休养报名")
|
||||
return
|
||||
}
|
||||
vant.Dialog.confirm({
|
||||
title: "温馨提醒",
|
||||
message: "是否确认退出本次疗休养报名,退出后将取消报名资格!",
|
||||
confirmButtonColor: "#ee2f2f"
|
||||
}).then(() => {
|
||||
this.leaveLoading = true
|
||||
this.$axios.post("/platform/tour/mysignup/doLeaveTour", {
|
||||
assignmentId: row.assignmentId
|
||||
}).then((res) => {
|
||||
this.leaveLoading = false
|
||||
if (res.code === 0) {
|
||||
vant.Toast.success(res.msg || "退出疗休养报名成功")
|
||||
this.doSearch()
|
||||
} else {
|
||||
vant.Dialog.alert({ title: "提示", message: res.msg || "退出失败", confirmButtonColor: "#1867b0" })
|
||||
}
|
||||
}).catch(() => {
|
||||
this.leaveLoading = false
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
revokeSignup(row) {
|
||||
vant.Dialog.confirm({
|
||||
title: "温馨提醒",
|
||||
@@ -1106,6 +1138,7 @@ layout("/layouts/platform_h5.html"){
|
||||
this.fillBedInfo = true
|
||||
this.signupLoading = false
|
||||
this.cancelLoading = false
|
||||
this.leaveLoading = false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
@@ -128,10 +128,10 @@ layout("/layouts/platform_h5.html"){
|
||||
<div class="tour-apply-title">教职工信息</div>
|
||||
<van-field label="姓名" readonly v-model="signupForm.userName"></van-field>
|
||||
<van-field label="工号" readonly v-model="signupForm.jobNo"></van-field>
|
||||
<van-field label="身份证号码" v-model="signupForm.idCard" maxlength="30" placeholder="请填写身份证号码"></van-field>
|
||||
<van-field label="身份证号码" required v-model="signupForm.idCard" maxlength="30" placeholder="请填写身份证号码"></van-field>
|
||||
<van-field label="性别" readonly v-model="signupForm.gender"></van-field>
|
||||
<van-field label="年龄" readonly :value="staffAge(signupForm)"></van-field>
|
||||
<van-field label="手机号" v-model="signupForm.mobile" type="tel" maxlength="30" placeholder="请填写手机号"></van-field>
|
||||
<van-field label="手机号" required v-model="signupForm.mobile" type="tel" maxlength="30" placeholder="请填写手机号"></van-field>
|
||||
<van-field label="所属工会" readonly v-model="signupForm.unionName"></van-field>
|
||||
</div>
|
||||
|
||||
@@ -142,6 +142,7 @@ layout("/layouts/platform_h5.html"){
|
||||
<van-field v-if="signupForm.travelPeriod" label="出行时间" readonly v-model="signupForm.travelPeriod"></van-field>
|
||||
<van-field
|
||||
label="乘车地点"
|
||||
required
|
||||
readonly
|
||||
clickable
|
||||
is-link
|
||||
@@ -620,6 +621,27 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
return true
|
||||
},
|
||||
showSubmitError(error) {
|
||||
// Keep signup submit errors local so the global axios interceptor does not show a duplicate toast.
|
||||
vant.Toast.clear()
|
||||
const responseData = error && error.response && error.response.data ? error.response.data : null
|
||||
const message = responseData && (responseData.msg || responseData.message)
|
||||
? (responseData.msg || responseData.message)
|
||||
: (error && (error.msg || error.message) ? (error.msg || error.message) : "报名提交失败")
|
||||
vant.Toast({
|
||||
message: message,
|
||||
duration: 3000
|
||||
})
|
||||
},
|
||||
submitSignupRequest(form) {
|
||||
// Use raw axios for this submit only; the page controls backend error text and toast duration locally.
|
||||
return axios.post("/platform/tour/signup/doSignup", $.param(form), {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
|
||||
"x-requested-with": "XMLHttpRequest"
|
||||
}
|
||||
})
|
||||
},
|
||||
submitSignup() {
|
||||
if (this.pageLoading || this.submitLoading) return
|
||||
if (this.boardingPlaceOptions.length > 0 && !this.signupForm.boardingPlace) {
|
||||
@@ -634,26 +656,23 @@ layout("/layouts/platform_h5.html"){
|
||||
directRelative: this.isDirectFamilyLine(this.signupForm) ? JSON.stringify(this.directRelativeForm) : ""
|
||||
})
|
||||
this.submitLoading = true
|
||||
this.$axios.post("/platform/tour/signup/doSignup", form).then((res) => {
|
||||
this.submitSignupRequest(form).then((resp) => {
|
||||
this.submitLoading = false
|
||||
const res = resp.data || {}
|
||||
if (res.code === 0) {
|
||||
vant.Dialog.alert({
|
||||
title: "提示",
|
||||
message: res.msg || "报名成功",
|
||||
message: res.msg || "恭喜您已报名成功",
|
||||
confirmButtonColor: "#1867b0"
|
||||
}).then(() => {
|
||||
window.location.replace("/platform/tour/signup/h5/signup")
|
||||
})
|
||||
} else {
|
||||
vant.Dialog.alert({
|
||||
title: "提示",
|
||||
message: res.msg || "报名失败",
|
||||
confirmButtonColor: "#1867b0"
|
||||
})
|
||||
this.showSubmitError(res)
|
||||
}
|
||||
}).catch(() => {
|
||||
}).catch((error) => {
|
||||
this.submitLoading = false
|
||||
vant.Toast("报名提交失败")
|
||||
this.showSubmitError(error)
|
||||
})
|
||||
}
|
||||
},
|
||||
|
||||
@@ -229,7 +229,7 @@ layout("/layouts/platform_h5.html"){
|
||||
</div>
|
||||
|
||||
<div class="tour-detail-footer">
|
||||
<van-button :type="detailActionType()" block round :disabled="detailActionDisabled()" @click="handleDetailAction">
|
||||
<van-button :type="detailActionType()" block round @click="handleDetailAction">
|
||||
{{ detailActionText() }}
|
||||
</van-button>
|
||||
</div>
|
||||
@@ -311,13 +311,7 @@ layout("/layouts/platform_h5.html"){
|
||||
detailActionType() {
|
||||
return this.canRevokeSignup() ? "danger" : "info"
|
||||
},
|
||||
detailActionDisabled() {
|
||||
return this.isSigned() && this.isApprovalSignup() && (this.isFinishedSignup() || this.isRejectedSignup() || (!this.canRevokeSignup() && !this.canModifySignup()))
|
||||
},
|
||||
handleDetailAction() {
|
||||
if (this.detailActionDisabled()) {
|
||||
return
|
||||
}
|
||||
if (this.canRevokeSignup()) {
|
||||
this.revokeSignup()
|
||||
return
|
||||
@@ -522,39 +516,8 @@ layout("/layouts/platform_h5.html"){
|
||||
vant.Toast("报名事项信息不完整")
|
||||
return
|
||||
}
|
||||
const goApply = () => {
|
||||
window.location.href = "/platform/tour/signup/h5/apply?matterId=" + encodeURIComponent(this.matterId)
|
||||
}
|
||||
this.$axios.post("/platform/tour/signup/signupEligibilityNotice", { matterId: this.matterId }).then((res) => {
|
||||
if (res.code !== 0) {
|
||||
vant.Dialog.alert({
|
||||
title: "温馨提醒",
|
||||
message: res.msg || "当前不能报名",
|
||||
confirmButtonColor: "#1867b0"
|
||||
})
|
||||
return
|
||||
}
|
||||
const data = res.data || {}
|
||||
if (!data.noticeRequired) {
|
||||
goApply()
|
||||
return
|
||||
}
|
||||
if (data.canApply) {
|
||||
vant.Dialog.confirm({
|
||||
title: "温馨提醒",
|
||||
message: data.message || "当前可报名,是否继续报名?",
|
||||
confirmButtonText: "继续报名",
|
||||
cancelButtonText: "稍后报名",
|
||||
confirmButtonColor: "#1867b0"
|
||||
}).then(goApply).catch(() => {})
|
||||
} else {
|
||||
vant.Dialog.alert({
|
||||
title: "温馨提醒",
|
||||
message: data.message || "当前不能报名",
|
||||
confirmButtonColor: "#1867b0"
|
||||
})
|
||||
}
|
||||
})
|
||||
// 线路详情底部按钮不做报名资格前置校验,所有业务规则统一放到最终提交报名时处理。
|
||||
window.location.href = "/platform/tour/signup/h5/apply?matterId=" + encodeURIComponent(this.matterId)
|
||||
},
|
||||
revokeSignup() {
|
||||
vant.Dialog.confirm({
|
||||
|
||||
@@ -326,13 +326,12 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
|
||||
.tour-line-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
position: relative;
|
||||
padding-right: 76px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tour-line-title {
|
||||
.tour-line-title-left {
|
||||
min-width: 0;
|
||||
color: #0f172a;
|
||||
font-size: 16px;
|
||||
@@ -341,9 +340,15 @@ layout("/layouts/platform_h5.html"){
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.tour-line-title {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.tour-line-type-badge {
|
||||
height: 18px;
|
||||
flex-shrink: 0;
|
||||
display: inline-block;
|
||||
margin-left: 5px;
|
||||
vertical-align: 2px;
|
||||
padding: 0 5px;
|
||||
border: 1px solid #ffd6a8;
|
||||
border-radius: 4px;
|
||||
@@ -355,6 +360,21 @@ layout("/layouts/platform_h5.html"){
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-line-signup-count {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
height: 22px;
|
||||
padding: 0 7px;
|
||||
border-radius: 11px;
|
||||
background: #d9fde8;
|
||||
color: #2fcf6b;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 22px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tour-line-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
@@ -389,7 +409,9 @@ layout("/layouts/platform_h5.html"){
|
||||
|
||||
.tour-line-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
@@ -410,6 +432,12 @@ layout("/layouts/platform_h5.html"){
|
||||
background: #ee2f2f;
|
||||
}
|
||||
|
||||
.tour-line-action--plain {
|
||||
border-color: #1e88e5;
|
||||
background: #ffffff;
|
||||
color: #1e88e5;
|
||||
}
|
||||
|
||||
.tour-line-action--disabled {
|
||||
border-color: #d1d5db;
|
||||
background: #e5e7eb;
|
||||
@@ -509,6 +537,7 @@ layout("/layouts/platform_h5.html"){
|
||||
<van-list
|
||||
v-model="loading"
|
||||
:finished="finished"
|
||||
:immediate-check="false"
|
||||
finished-text="没有更多了"
|
||||
@load="loadData">
|
||||
<div class="tour-line-list">
|
||||
@@ -526,11 +555,14 @@ layout("/layouts/platform_h5.html"){
|
||||
</div>
|
||||
<div class="tour-line-main">
|
||||
<div class="tour-line-title-row">
|
||||
<div class="tour-line-title">{{ row.lineName || '未命名线路' }}</div>
|
||||
<span v-if="lineTypeBadge(row.lineType)" class="tour-line-type-badge">{{ lineTypeBadge(row.lineType) }}</span>
|
||||
<span v-if="isSigned(row)" class="tour-line-signed-icon">
|
||||
<van-icon name="good-job"></van-icon>
|
||||
</span>
|
||||
<div class="tour-line-title-left">
|
||||
<div class="tour-line-title">{{ row.lineName || '未命名线路' }}</div>
|
||||
<span v-if="lineTypeBadge(row.lineType)" class="tour-line-type-badge">{{ lineTypeBadge(row.lineType) }}</span>
|
||||
<span v-if="isSigned(row)" class="tour-line-signed-icon">
|
||||
<van-icon name="good-job"></van-icon>
|
||||
</span>
|
||||
</div>
|
||||
<span v-if="hasSignupCount(row)" class="tour-line-signup-count">已报名 {{ row.signupCount }}人</span>
|
||||
</div>
|
||||
<div class="tour-line-tags">
|
||||
<span v-if="row.lotName" class="tour-line-tag">{{ row.lotName }}</span>
|
||||
@@ -556,6 +588,20 @@ layout("/layouts/platform_h5.html"){
|
||||
@click.stop="handleSignupAction(row)">
|
||||
{{ signupActionText(row) }}
|
||||
</button>
|
||||
<button
|
||||
v-if="showLeaveTourAction(row)"
|
||||
type="button"
|
||||
class="tour-line-action tour-line-action--danger"
|
||||
@click.stop="leaveTour(row)">
|
||||
退出疗休养
|
||||
</button>
|
||||
<button
|
||||
v-if="showCancelLineAction(row)"
|
||||
type="button"
|
||||
class="tour-line-action tour-line-action--plain"
|
||||
@click.stop="cancelLine(row)">
|
||||
取消本线路
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -596,7 +642,6 @@ layout("/layouts/platform_h5.html"){
|
||||
list: [],
|
||||
loading: false,
|
||||
finished: false,
|
||||
lineChecking: false,
|
||||
showUnionPopup: false,
|
||||
unionList: [
|
||||
{id: "", name: "全部分工会"}
|
||||
@@ -680,6 +725,9 @@ layout("/layouts/platform_h5.html"){
|
||||
isSigned(row) {
|
||||
return !!(row && (row.signed === true || row.signed === 1 || row.signed === "1" || row.ledgerId))
|
||||
},
|
||||
hasSignupCount(row) {
|
||||
return row && row.signupCount !== undefined && row.signupCount !== null && row.signupCount !== ""
|
||||
},
|
||||
isApprovalSignup(row) {
|
||||
return !!(row && this.isSigned(row) && row.instanceId)
|
||||
},
|
||||
@@ -714,7 +762,13 @@ layout("/layouts/platform_h5.html"){
|
||||
return "报名"
|
||||
},
|
||||
showSignupAction(row) {
|
||||
return this.isSigned(row) && this.canModifySignup(row)
|
||||
return false
|
||||
},
|
||||
showLeaveTourAction(row) {
|
||||
return this.isSigned(row) && this.toBoolean(row && row.canLeaveTour) && !!(row && row.assignmentId)
|
||||
},
|
||||
showCancelLineAction(row) {
|
||||
return this.isSigned(row) && this.toBoolean(row && row.canCancelLine) && !!(row && row.ledgerId)
|
||||
},
|
||||
signupActionDisabled(row) {
|
||||
return !!(this.isSigned(row) && this.isApprovalSignup(row) && (this.isRejectedSignup(row) || (!this.canRevokeSignup(row) && !this.canModifySignup(row))))
|
||||
@@ -737,39 +791,8 @@ layout("/layouts/platform_h5.html"){
|
||||
vant.Toast("报名事项信息不完整")
|
||||
return
|
||||
}
|
||||
const go = () => {
|
||||
window.location.href = "/platform/tour/signup/h5/apply?matterId=" + encodeURIComponent(row.matterId)
|
||||
}
|
||||
this.$axios.post("/platform/tour/signup/signupEligibilityNotice", { matterId: row.matterId }).then((res) => {
|
||||
if (res.code !== 0) {
|
||||
vant.Dialog.alert({
|
||||
title: "温馨提醒",
|
||||
message: res.msg || "当前不能报名",
|
||||
confirmButtonColor: "#1867b0"
|
||||
})
|
||||
return
|
||||
}
|
||||
const data = res.data || {}
|
||||
if (!data.noticeRequired) {
|
||||
go()
|
||||
return
|
||||
}
|
||||
if (data.canApply) {
|
||||
vant.Dialog.confirm({
|
||||
title: "温馨提醒",
|
||||
message: data.message || "当前可报名,是否继续报名?",
|
||||
confirmButtonText: "继续报名",
|
||||
cancelButtonText: "稍后报名",
|
||||
confirmButtonColor: "#1867b0"
|
||||
}).then(go).catch(() => {})
|
||||
} else {
|
||||
vant.Dialog.alert({
|
||||
title: "温馨提醒",
|
||||
message: data.message || "当前不能报名",
|
||||
confirmButtonColor: "#1867b0"
|
||||
})
|
||||
}
|
||||
})
|
||||
// 进入报名页不再提前做资格校验,所有业务校验统一放到最终提交报名时处理。
|
||||
window.location.href = "/platform/tour/signup/h5/apply?matterId=" + encodeURIComponent(row.matterId)
|
||||
},
|
||||
revokeSignup(row) {
|
||||
vant.Dialog.confirm({
|
||||
@@ -787,6 +810,46 @@ layout("/layouts/platform_h5.html"){
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
leaveTour(row) {
|
||||
if (!row || !row.assignmentId) {
|
||||
vant.Toast("人员分配信息不完整")
|
||||
return
|
||||
}
|
||||
vant.Dialog.confirm({
|
||||
title: "提示",
|
||||
message: "是否确认退出本次疗休养,退出后将取消报名资格!",
|
||||
confirmButtonColor: "#ee2f2f"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/tour/signup/h5/doLeaveTour", { assignmentId: row.assignmentId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
vant.Toast.success(res.msg || "退出成功")
|
||||
this.doSearch()
|
||||
} else {
|
||||
vant.Dialog.alert({ title: "提示", message: res.msg || "退出失败", confirmButtonColor: "#1867b0" })
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
cancelLine(row) {
|
||||
if (!row || !row.ledgerId) {
|
||||
vant.Toast("报名台账信息不完整")
|
||||
return
|
||||
}
|
||||
vant.Dialog.confirm({
|
||||
title: "提示",
|
||||
message: "是否确认取消本线路?",
|
||||
confirmButtonColor: "#ee2f2f"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/tour/signup/h5/doCancelLine", { ledgerId: row.ledgerId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
vant.Toast.success(res.msg || "取消成功")
|
||||
this.doSearch()
|
||||
} else {
|
||||
vant.Dialog.alert({ title: "提示", message: res.msg || "取消失败", confirmButtonColor: "#1867b0" })
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
doSearch() {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageForm.totalCount = 0
|
||||
@@ -799,16 +862,18 @@ layout("/layouts/platform_h5.html"){
|
||||
this.pageForm.lineType = active ? "" : lineType
|
||||
this.pageForm.directFamilyUnitLine = null
|
||||
this.pageForm.travelPeriod = ""
|
||||
this.loadTravelPeriodOptions()
|
||||
this.doSearch()
|
||||
this.loadTravelPeriodOptions(true).then(() => {
|
||||
this.doSearch()
|
||||
})
|
||||
},
|
||||
toggleDirectFamilyLine() {
|
||||
const active = this.pageForm.directFamilyUnitLine === true
|
||||
this.pageForm.directFamilyUnitLine = active ? null : true
|
||||
this.pageForm.lineType = ""
|
||||
this.pageForm.travelPeriod = ""
|
||||
this.loadTravelPeriodOptions()
|
||||
this.doSearch()
|
||||
this.loadTravelPeriodOptions(true).then(() => {
|
||||
this.doSearch()
|
||||
})
|
||||
},
|
||||
openUnionPopup() {
|
||||
this.showUnionPopup = true
|
||||
@@ -849,8 +914,8 @@ layout("/layouts/platform_h5.html"){
|
||||
}))
|
||||
})
|
||||
},
|
||||
loadTravelPeriodOptions() {
|
||||
this.$axios.post("/platform/tour/signup/h5/travelPeriodOptions", {
|
||||
loadTravelPeriodOptions(defaultFirst) {
|
||||
return this.$axios.post("/platform/tour/signup/h5/travelPeriodOptions", {
|
||||
year: this.pageForm.year,
|
||||
lineType: this.pageForm.lineType,
|
||||
directFamilyUnitLine: this.pageForm.directFamilyUnitLine
|
||||
@@ -865,8 +930,15 @@ layout("/layouts/platform_h5.html"){
|
||||
name: item.travelPeriod || "未设置出行时间"
|
||||
}
|
||||
}).filter((item) => item.value).sort((a, b) => {
|
||||
return String(b.value).localeCompare(String(a.value))
|
||||
return String(a.value).localeCompare(String(b.value))
|
||||
})
|
||||
if (defaultFirst) {
|
||||
this.pageForm.travelPeriod = this.travelPeriodList.length > 0 ? this.travelPeriodList[0].value : ""
|
||||
}
|
||||
}).catch(() => {
|
||||
if (defaultFirst) {
|
||||
this.pageForm.travelPeriod = ""
|
||||
}
|
||||
})
|
||||
},
|
||||
loadData() {
|
||||
@@ -901,44 +973,18 @@ layout("/layouts/platform_h5.html"){
|
||||
vant.Toast("线路信息不完整")
|
||||
return
|
||||
}
|
||||
if (this.lineChecking) {
|
||||
return
|
||||
}
|
||||
this.lineChecking = true
|
||||
axios.post("/platform/tour/signup/signupDetail", $.param({ matterId: row.matterId }), {
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
|
||||
"x-requested-with": "XMLHttpRequest"
|
||||
}
|
||||
}).then((resp) => {
|
||||
this.lineChecking = false
|
||||
const res = resp.data || {}
|
||||
if (res.code !== 0) {
|
||||
vant.Dialog.alert({
|
||||
title: "温馨提醒",
|
||||
message: res.msg || "当前线路暂不能报名",
|
||||
confirmButtonColor: "#1867b0"
|
||||
})
|
||||
return
|
||||
}
|
||||
window.location.href = "/platform/tour/signup/h5/lineInfo?matterId="
|
||||
+ encodeURIComponent(row.matterId)
|
||||
+ "&lineId="
|
||||
+ encodeURIComponent(row.lineId)
|
||||
}).catch((err) => {
|
||||
this.lineChecking = false
|
||||
vant.Dialog.alert({
|
||||
title: "温馨提醒",
|
||||
message: err && err.msg ? err.msg : "当前线路暂不能报名",
|
||||
confirmButtonColor: "#1867b0"
|
||||
})
|
||||
})
|
||||
// 点击线路只进入详情页,报名资格和业务规则统一在提交报名按钮处校验。
|
||||
window.location.href = "/platform/tour/signup/h5/lineInfo?matterId="
|
||||
+ encodeURIComponent(row.matterId)
|
||||
+ "&lineId="
|
||||
+ encodeURIComponent(row.lineId)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadUnionOptions()
|
||||
this.loadTravelPeriodOptions()
|
||||
this.loadData()
|
||||
this.loadTravelPeriodOptions(true).then(() => {
|
||||
this.loadData()
|
||||
})
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -575,11 +575,7 @@ const home = {
|
||||
|
||||
.quick-scroll {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
overflow-y: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
-ms-overflow-style: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.quick-scroll::-webkit-scrollbar {
|
||||
@@ -588,10 +584,9 @@ const home = {
|
||||
|
||||
.quick-scroll-grid {
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
grid-template-rows: repeat(2, 82px);
|
||||
grid-auto-columns: 25%;
|
||||
min-width: 100%;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
grid-auto-rows: 82px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.quick-entry-item {
|
||||
|
||||
Reference in New Issue
Block a user