Changes
This commit is contained in:
@@ -213,6 +213,7 @@ public class FlowTodoCenterController {
|
||||
public Result statistics() {
|
||||
// 查询我发起的流程
|
||||
int startedCount = dao.count(ProcessInstance.class, Cnd.where(ProcessInstance::getOperator, "=", SecurityUtil.getUserId()));
|
||||
int completedCount = dao.count(ProcessInstance.class, Cnd.where(ProcessInstance::getOperator, "=", SecurityUtil.getUserId()).and(ProcessInstance::getState, "=", 20));
|
||||
// 查询我的待办任务
|
||||
Sql todoSql = Sqls.create("""
|
||||
SELECT
|
||||
@@ -262,7 +263,7 @@ public class FlowTodoCenterController {
|
||||
dao.execute(msgSql);
|
||||
int msgCount = msgSql.getInt();
|
||||
|
||||
return Result.success(Map.of("startedCount", startedCount, "todoCount", todoCount, "doneCount", doneCount, "notifications", msgCount));
|
||||
return Result.success(Map.of("startedCount", startedCount, "completedCount", completedCount, "todoCount", todoCount, "doneCount", doneCount, "notifications", msgCount));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+625
@@ -0,0 +1,625 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
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.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.sys.models.Sys_union;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService;
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
import org.apache.poi.ss.usermodel.BorderStyle;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.usermodel.Font;
|
||||
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.VerticalAlignment;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/platform/tour/group")
|
||||
public class TourGroupController {
|
||||
|
||||
@Inject
|
||||
private TourMatterService tourMatterService;
|
||||
|
||||
@Inject
|
||||
private TourLedgerService tourLedgerService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/Tour/group/index.html")
|
||||
@SaCheckPermission("tour.group")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.group")
|
||||
public Result pageData(PageForm pageForm, Integer year, String lineName, String lineType, String unionId) {
|
||||
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
|
||||
pageForm.defaultSortAsc("lineName");
|
||||
}
|
||||
Cnd cnd = buildQueryCnd(year, lineName, lineType, unionId);
|
||||
|
||||
Sql countSql = Sqls.create("""
|
||||
SELECT COUNT(1)
|
||||
FROM tour_matter m
|
||||
INNER JOIN tour_line l ON l.id = m.lineId
|
||||
LEFT JOIN sys_union u ON u.id = m.unionId
|
||||
$condition
|
||||
""");
|
||||
countSql.setCondition(cnd);
|
||||
countSql.setCallback(Sqls.callback.integer());
|
||||
tourMatterService.dao().execute(countSql);
|
||||
int count = countSql.getInt();
|
||||
|
||||
Sql listSql = Sqls.create("""
|
||||
SELECT
|
||||
m.id AS matterId,
|
||||
m.`year`,
|
||||
m.matterName,
|
||||
m.unionId,
|
||||
COALESCE(u.name, '校工会') AS unionName,
|
||||
m.lineId,
|
||||
l.lineName,
|
||||
l.lineType,
|
||||
l.directFamilyUnitLine,
|
||||
m.travelStartTime,
|
||||
m.travelEndTime,
|
||||
CASE
|
||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||
ELSE ''
|
||||
END AS travelPeriod,
|
||||
m.minGroupPeople,
|
||||
m.maxGroupPeople,
|
||||
IFNULL(sc.signupCount, 0) AS signupCount,
|
||||
CASE
|
||||
WHEN m.maxGroupPeople IS NOT NULL AND m.maxGroupPeople > 0 AND IFNULL(sc.signupCount, 0) > m.maxGroupPeople THEN 'over'
|
||||
WHEN m.minGroupPeople IS NOT NULL AND m.minGroupPeople > 0 AND IFNULL(sc.signupCount, 0) >= m.minGroupPeople THEN 'formed'
|
||||
ELSE 'unformed'
|
||||
END AS groupStatus,
|
||||
CASE
|
||||
WHEN m.maxGroupPeople IS NOT NULL AND m.maxGroupPeople > 0 AND IFNULL(sc.signupCount, 0) > m.maxGroupPeople THEN '超员'
|
||||
WHEN m.minGroupPeople IS NOT NULL AND m.minGroupPeople > 0 AND IFNULL(sc.signupCount, 0) >= m.minGroupPeople THEN '已成团'
|
||||
ELSE '未成团'
|
||||
END AS groupStatusName
|
||||
FROM tour_matter m
|
||||
INNER JOIN tour_line l ON l.id = m.lineId
|
||||
LEFT JOIN sys_union u ON u.id = m.unionId
|
||||
LEFT JOIN (
|
||||
SELECT t.matterId, SUM(1 + IFNULL(f.familyCount, 0)) AS signupCount
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN (
|
||||
SELECT ledgerId, COUNT(1) AS familyCount
|
||||
FROM tour_ledger_family
|
||||
WHERE delFlag = 0
|
||||
GROUP BY ledgerId
|
||||
) f ON f.ledgerId = t.id
|
||||
WHERE t.delFlag = 0
|
||||
AND t.matterId IS NOT NULL
|
||||
AND t.matterId <> ''
|
||||
GROUP BY t.matterId
|
||||
) sc ON sc.matterId = m.id
|
||||
$condition
|
||||
ORDER BY $orderColumn $orderBy, m.`year` DESC, m.travelStartTime ASC, l.lineName ASC, m.createdAt DESC
|
||||
""");
|
||||
listSql.setCondition(cnd);
|
||||
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
|
||||
listSql.setVar("orderBy", "descending".equals(pageForm.getPageOrderBy()) ? "desc" : "asc");
|
||||
listSql.setPager(tourMatterService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||
listSql.setCallback(Sqls.callback.maps());
|
||||
tourMatterService.dao().execute(listSql);
|
||||
|
||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, listSql.getList(NutMap.class));
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.group")
|
||||
public Result unionOptions() {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.asc("unionCode");
|
||||
cnd.asc("name");
|
||||
return Result.success(tourMatterService.dao().query(Sys_union.class, cnd));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.group")
|
||||
public Result lineTypeOptions(Integer year) {
|
||||
Cnd cnd = buildQueryCnd(year, null, null, null);
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT l.lineType
|
||||
FROM tour_matter m
|
||||
INNER JOIN tour_line l ON l.id = m.lineId
|
||||
LEFT JOIN sys_union u ON u.id = m.unionId
|
||||
$condition
|
||||
ORDER BY l.lineType ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourMatterService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.group")
|
||||
public Result signupPageData(PageForm pageForm, String matterId, String keyword, String unionId) {
|
||||
if (StrUtil.isBlank(matterId)) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
|
||||
pageForm.defaultSortDesc("signupTime");
|
||||
}
|
||||
Cnd cnd = buildSignupQueryCnd(matterId, keyword, unionId);
|
||||
Sql countSql = Sqls.create("""
|
||||
SELECT COUNT(1)
|
||||
FROM tour_ledger t
|
||||
INNER JOIN tour_matter m ON m.id = t.matterId
|
||||
INNER JOIN tour_line l ON l.id = m.lineId
|
||||
$condition
|
||||
""");
|
||||
countSql.setCondition(cnd);
|
||||
countSql.setCallback(Sqls.callback.integer());
|
||||
tourLedgerService.dao().execute(countSql);
|
||||
int count = countSql.getInt();
|
||||
|
||||
Sql listSql = Sqls.create("""
|
||||
SELECT
|
||||
t.id,
|
||||
t.jobNo,
|
||||
t.userName,
|
||||
'' AS mobile,
|
||||
t.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
|
||||
$condition
|
||||
ORDER BY $orderColumn $orderBy, t.signupTime DESC, t.createdAt DESC
|
||||
""");
|
||||
listSql.setCondition(cnd);
|
||||
listSql.setVar("orderColumn", getSignupOrderColumn(pageForm.getPageOrderName()));
|
||||
listSql.setVar("orderBy", "ascending".equals(pageForm.getPageOrderBy()) ? "asc" : "desc");
|
||||
listSql.setPager(tourLedgerService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||
listSql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(listSql);
|
||||
|
||||
List<NutMap> list = listSql.getList(NutMap.class);
|
||||
fillSignupMobile(list);
|
||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("tour.group")
|
||||
public void exportParticipants(String matterId, HttpServletResponse response) {
|
||||
if (StrUtil.isBlank(matterId)) {
|
||||
return;
|
||||
}
|
||||
NutMap matter = fetchExportMatter(matterId);
|
||||
if (matter == null || matter.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<NutMap> list = queryExportParticipants(matterId);
|
||||
Workbook workbook = buildParticipantsWorkbook(matter, list);
|
||||
String lineName = StrUtil.blankToDefault(matter.getString("lineName", ""), "线路");
|
||||
CommonDownloadUtil.download(lineName + "参加人员名单.xls", workbook, response);
|
||||
}
|
||||
|
||||
private Cnd buildSignupQueryCnd(String matterId, String keyword, String unionId) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("t.delFlag", "=", false);
|
||||
cnd.and("t.matterId", "=", matterId);
|
||||
cnd.and("m.delFlag", "=", false);
|
||||
cnd.and("m.enabled", "=", true);
|
||||
cnd.and("m.lineId", "IS NOT", null);
|
||||
cnd.and("m.lineId", "<>", "");
|
||||
cnd.and("l.delFlag", "=", false);
|
||||
cnd.and("l.enabled", "=", true);
|
||||
if (StrUtil.isNotBlank(keyword)) {
|
||||
SqlExpressionGroup keywordGroup = new SqlExpressionGroup();
|
||||
keywordGroup.orLike("t.userName", keyword.trim());
|
||||
keywordGroup.orLike("t.jobNo", keyword.trim());
|
||||
cnd.and(keywordGroup);
|
||||
}
|
||||
cnd.andEX("t.unionId", "=", unionId);
|
||||
applyMatterDataScope(cnd);
|
||||
return cnd;
|
||||
}
|
||||
|
||||
private NutMap fetchExportMatter(String matterId) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("m.delFlag", "=", false);
|
||||
cnd.and("m.enabled", "=", true);
|
||||
cnd.and("m.id", "=", matterId);
|
||||
cnd.and("m.lineId", "IS NOT", null);
|
||||
cnd.and("m.lineId", "<>", "");
|
||||
cnd.and("l.delFlag", "=", false);
|
||||
cnd.and("l.enabled", "=", true);
|
||||
applyMatterDataScope(cnd);
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
m.id AS matterId,
|
||||
m.`year`,
|
||||
m.unionId,
|
||||
COALESCE(u.name, '校工会') AS unionName,
|
||||
l.lineName,
|
||||
l.lineType,
|
||||
m.travelStartTime,
|
||||
m.travelEndTime,
|
||||
CASE
|
||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||
ELSE ''
|
||||
END AS travelPeriod
|
||||
FROM tour_matter m
|
||||
INNER JOIN tour_line l ON l.id = m.lineId
|
||||
LEFT JOIN sys_union u ON u.id = m.unionId
|
||||
$condition
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.map());
|
||||
tourMatterService.dao().execute(sql);
|
||||
return sql.getObject(NutMap.class);
|
||||
}
|
||||
|
||||
private List<NutMap> queryExportParticipants(String matterId) {
|
||||
Cnd cnd = buildSignupQueryCnd(matterId, null, null);
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
t.id AS ledgerId,
|
||||
t.jobNo,
|
||||
t.userName,
|
||||
t.idCard,
|
||||
vu.mobile,
|
||||
t.unionName,
|
||||
l.lineName,
|
||||
CASE
|
||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||
ELSE ''
|
||||
END AS travelPeriod,
|
||||
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 t.signupTime DESC, t.createdAt DESC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
List<NutMap> list = sql.getList(NutMap.class);
|
||||
fillExportFamilies(list);
|
||||
return list;
|
||||
}
|
||||
|
||||
private void fillExportFamilies(List<NutMap> list) {
|
||||
if (list == null || list.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> ledgerIds = new ArrayList<>();
|
||||
for (NutMap item : list) {
|
||||
String ledgerId = item.getString("ledgerId", "");
|
||||
if (StrUtil.isNotBlank(ledgerId)) {
|
||||
ledgerIds.add(ledgerId);
|
||||
}
|
||||
}
|
||||
if (ledgerIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
ledgerId,
|
||||
familyName,
|
||||
relationship,
|
||||
idCard
|
||||
FROM tour_ledger_family
|
||||
WHERE delFlag = 0
|
||||
AND ledgerId IN (@ledgerIds)
|
||||
ORDER BY createdAt ASC
|
||||
""");
|
||||
sql.setParam("ledgerIds", ledgerIds.toArray(new String[0]));
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
|
||||
Map<String, List<NutMap>> familyMap = new HashMap<>();
|
||||
for (NutMap family : sql.getList(NutMap.class)) {
|
||||
familyMap.computeIfAbsent(family.getString("ledgerId", ""), key -> new ArrayList<>()).add(family);
|
||||
}
|
||||
for (NutMap item : list) {
|
||||
item.put("families", familyMap.getOrDefault(item.getString("ledgerId", ""), List.of()));
|
||||
}
|
||||
}
|
||||
|
||||
private Workbook buildParticipantsWorkbook(NutMap matter, List<NutMap> list) {
|
||||
Workbook workbook = new HSSFWorkbook();
|
||||
Sheet sheet = workbook.createSheet("参加人员名单");
|
||||
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 10));
|
||||
sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, 10));
|
||||
|
||||
double[] widths = {6, 14, 12, 22, 16, 24, 34, 26, 12, 12, 16};
|
||||
for (int i = 0; i < widths.length; i++) {
|
||||
sheet.setColumnWidth(i, (int) (widths[i] * 256));
|
||||
}
|
||||
|
||||
CellStyle titleStyle = createStyle(workbook, "黑体", (short) 16, true, HorizontalAlignment.CENTER, false, false);
|
||||
CellStyle unionStyle = createStyle(workbook, "宋体", (short) 12, true, HorizontalAlignment.LEFT, false, false);
|
||||
CellStyle headerStyle = createStyle(workbook, "宋体", (short) 11, true, HorizontalAlignment.CENTER, true, true);
|
||||
CellStyle bodyStyle = createStyle(workbook, "宋体", (short) 11, false, HorizontalAlignment.CENTER, true, true);
|
||||
|
||||
Row titleRow = sheet.createRow(0);
|
||||
titleRow.setHeightInPoints(30);
|
||||
setCell(titleRow, 0, StrUtil.blankToDefault(matter.getString("lineName", ""), ""), titleStyle);
|
||||
fillMergedCells(titleRow, 1, 10, titleStyle);
|
||||
|
||||
Row unionRow = sheet.createRow(1);
|
||||
unionRow.setHeightInPoints(26);
|
||||
setCell(unionRow, 0, "分工会:" + StrUtil.blankToDefault(matter.getString("unionName", ""), ""), unionStyle);
|
||||
fillMergedCells(unionRow, 1, 10, unionStyle);
|
||||
|
||||
String[] headers = {"序号", "工号", "姓名", "身份证号码", "电话号码", "所属分工会", "所选线路名称", "疗休养时间", "亲属关系", "姓名", "备注"};
|
||||
Row headerRow = sheet.createRow(2);
|
||||
headerRow.setHeightInPoints(35);
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
setCell(headerRow, i, headers[i], headerStyle);
|
||||
}
|
||||
|
||||
int rowIndex = 3;
|
||||
int seq = 1;
|
||||
for (NutMap item : list == null ? List.<NutMap>of() : list) {
|
||||
Row staffRow = sheet.createRow(rowIndex++);
|
||||
staffRow.setHeightInPoints(24);
|
||||
setParticipantRow(staffRow, seq++, item.getString("jobNo", ""), item.getString("userName", ""),
|
||||
item.getString("idCard", ""), item.getString("mobile", ""), item.getString("unionName", ""),
|
||||
item.getString("lineName", matter.getString("lineName", "")), item.getString("travelPeriod", matter.getString("travelPeriod", "")),
|
||||
"", item.getString("userName", ""), bodyStyle);
|
||||
|
||||
Object familiesObj = item.get("families");
|
||||
if (familiesObj instanceof List<?> families) {
|
||||
for (Object obj : families) {
|
||||
if (!(obj instanceof NutMap family)) {
|
||||
continue;
|
||||
}
|
||||
Row familyRow = sheet.createRow(rowIndex++);
|
||||
familyRow.setHeightInPoints(24);
|
||||
setParticipantRow(familyRow, seq++, "", family.getString("familyName", ""),
|
||||
family.getString("idCard", ""), "", "",
|
||||
item.getString("lineName", matter.getString("lineName", "")),
|
||||
item.getString("travelPeriod", matter.getString("travelPeriod", "")),
|
||||
family.getString("relationship", ""), item.getString("userName", ""), bodyStyle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int minRows = Math.max(rowIndex, 23);
|
||||
while (rowIndex < minRows) {
|
||||
Row row = sheet.createRow(rowIndex++);
|
||||
row.setHeightInPoints(24);
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
setCell(row, i, "", bodyStyle);
|
||||
}
|
||||
}
|
||||
return workbook;
|
||||
}
|
||||
|
||||
private void setParticipantRow(Row row, int seq, String jobNo, String userName, String idCard, String mobile,
|
||||
String unionName, String lineName, String travelPeriod, String relationship,
|
||||
String participantName, CellStyle style) {
|
||||
setCell(row, 0, String.valueOf(seq), style);
|
||||
setCell(row, 1, jobNo, style);
|
||||
setCell(row, 2, userName, style);
|
||||
setCell(row, 3, idCard, style);
|
||||
setCell(row, 4, mobile, style);
|
||||
setCell(row, 5, unionName, style);
|
||||
setCell(row, 6, lineName, style);
|
||||
setCell(row, 7, travelPeriod, style);
|
||||
setCell(row, 8, relationship, style);
|
||||
setCell(row, 9, participantName, style);
|
||||
setCell(row, 10, "", style);
|
||||
}
|
||||
|
||||
private CellStyle createStyle(Workbook workbook, String fontName, short fontSize, boolean bold, HorizontalAlignment alignment, boolean wrap, boolean border) {
|
||||
Font font = workbook.createFont();
|
||||
font.setFontName(fontName);
|
||||
font.setFontHeightInPoints(fontSize);
|
||||
font.setBold(bold);
|
||||
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
style.setFont(font);
|
||||
style.setAlignment(alignment);
|
||||
style.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
style.setWrapText(wrap);
|
||||
if (border) {
|
||||
style.setBorderLeft(BorderStyle.THIN);
|
||||
style.setBorderTop(BorderStyle.THIN);
|
||||
style.setBorderRight(BorderStyle.THIN);
|
||||
style.setBorderBottom(BorderStyle.THIN);
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
private void setCell(Row row, int col, String value, CellStyle style) {
|
||||
Cell cell = row.createCell(col);
|
||||
cell.setCellValue(StrUtil.blankToDefault(value, ""));
|
||||
cell.setCellStyle(style);
|
||||
}
|
||||
|
||||
private void fillMergedCells(Row row, int startCol, int endCol, CellStyle style) {
|
||||
for (int i = startCol; i <= endCol; i++) {
|
||||
setCell(row, i, "", style);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
cnd.and("m.enabled", "=", true);
|
||||
cnd.and("m.lineId", "IS NOT", null);
|
||||
cnd.and("m.lineId", "<>", "");
|
||||
cnd.and("l.delFlag", "=", false);
|
||||
cnd.and("l.enabled", "=", true);
|
||||
cnd.andEX("m.`year`", "=", year == null ? LocalDate.now().getYear() : year);
|
||||
cnd.and(Cnd.likeEX("l.lineName", lineName));
|
||||
cnd.andEX("l.lineType", "=", lineType);
|
||||
cnd.andEX("m.unionId", "=", unionId);
|
||||
applyMatterDataScope(cnd);
|
||||
return cnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* 线路成团按事项归属控制数据范围:校工会查看全部;分工会查看本工会事项;其它角色仅查看自己创建的事项。
|
||||
*/
|
||||
private void applyMatterDataScope(Cnd cnd) {
|
||||
if (hasSchoolUnionScope()) {
|
||||
return;
|
||||
}
|
||||
if (hasBranchUnionScope()) {
|
||||
cnd.and("m.unionId", "=", SecurityUtil.getUnionId());
|
||||
return;
|
||||
}
|
||||
SqlExpressionGroup creatorGroup = new SqlExpressionGroup();
|
||||
creatorGroup.or("m.creatorUserId", "=", SecurityUtil.getUserId());
|
||||
creatorGroup.or("m.createdBy", "=", SecurityUtil.getUserId());
|
||||
cnd.and(creatorGroup);
|
||||
}
|
||||
|
||||
private boolean hasSchoolUnionScope() {
|
||||
return AuthUtil.hasRole(RoleConstant.SYSADMIN.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_ADMIN.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_CHAIRMAN.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_VICE_CHAIRMAN.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_WELFARE_ADMIN.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_MEMBER_ADMIN.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_TC_ADMIN.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_WC_ADMIN.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_DC_ADMIN.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_PROPOSAL_ADMIN.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_CLUB_ADMIN.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.SCHOOL_UNION_ACTIVITY_ADMIN.name());
|
||||
}
|
||||
|
||||
private boolean hasBranchUnionScope() {
|
||||
return AuthUtil.hasRole(RoleConstant.BRANCH_UNION_ADMIN.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_VICE_CHAIRMAN.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_OPERATOR.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_GROUP_LEADER.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_ARTICLE_WRITER.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_WY.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_ZUZHI_WY.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_XUANCHUAN_WY.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_NVGONG_WY.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_QINGNIAN_WY.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_WENTI_WY.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_SHENGGHUO_WY.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_TIAOJIE_WY.name())
|
||||
|| AuthUtil.hasRole(RoleConstant.BRANCH_UNION_WENTI_SPORTS.name());
|
||||
}
|
||||
|
||||
private String getOrderColumn(String orderName) {
|
||||
if ("lineName".equals(orderName)) {
|
||||
return "l.lineName";
|
||||
}
|
||||
if ("travelPeriod".equals(orderName)) {
|
||||
return "m.travelStartTime";
|
||||
}
|
||||
if ("matterName".equals(orderName)) {
|
||||
return "m.matterName";
|
||||
}
|
||||
if ("unionName".equals(orderName)) {
|
||||
return "u.name";
|
||||
}
|
||||
if ("lineType".equals(orderName)) {
|
||||
return "l.lineType";
|
||||
}
|
||||
if ("signupCount".equals(orderName)) {
|
||||
return "signupCount";
|
||||
}
|
||||
if ("minGroupPeople".equals(orderName)) {
|
||||
return "m.minGroupPeople";
|
||||
}
|
||||
if ("maxGroupPeople".equals(orderName)) {
|
||||
return "m.maxGroupPeople";
|
||||
}
|
||||
if ("groupStatusName".equals(orderName)) {
|
||||
return "groupStatus";
|
||||
}
|
||||
return "l.lineName";
|
||||
}
|
||||
|
||||
private String getSignupOrderColumn(String orderName) {
|
||||
if ("jobNo".equals(orderName)) {
|
||||
return "t.jobNo";
|
||||
}
|
||||
if ("userName".equals(orderName)) {
|
||||
return "t.userName";
|
||||
}
|
||||
if ("idCard".equals(orderName)) {
|
||||
return "t.idCard";
|
||||
}
|
||||
if ("unionName".equals(orderName)) {
|
||||
return "t.unionName";
|
||||
}
|
||||
return "t.signupTime";
|
||||
}
|
||||
}
|
||||
+959
@@ -0,0 +1,959 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
||||
|
||||
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||
import cn.afterturn.easypoi.excel.ExcelImportUtil;
|
||||
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.ImportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
|
||||
import cn.afterturn.easypoi.excel.entity.params.ExcelExportEntity;
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.base.model.ExcelImportRes;
|
||||
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.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.flow.enums.ProcessInstanceStateEnum;
|
||||
import com.budwk.app.sys.models.Sys_union;
|
||||
import com.budwk.app.sys.views.View_user;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.mode.TourLedgerImportExcelMode;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerDirectRelative;
|
||||
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.TourTravelAgency;
|
||||
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;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
import org.apache.poi.ss.usermodel.BorderStyle;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.usermodel.FillPatternType;
|
||||
import org.apache.poi.ss.usermodel.Font;
|
||||
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||
import org.apache.poi.ss.usermodel.IndexedColors;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.VerticalAlignment;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.AdaptBy;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
import org.nutz.mvc.upload.TempFile;
|
||||
import org.nutz.mvc.upload.UploadAdaptor;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipOutputStream;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/platform/tour/ledger")
|
||||
public class TourLedgerController {
|
||||
|
||||
@Inject
|
||||
private TourLedgerService tourLedgerService;
|
||||
|
||||
@Inject
|
||||
private TourLedgerFamilyService tourLedgerFamilyService;
|
||||
|
||||
@Inject
|
||||
private TourLedgerDirectRelativeService tourLedgerDirectRelativeService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/Tour/ledger/index.html")
|
||||
@SaCheckPermission("tour.ledger")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.ledger")
|
||||
public Result pageData(PageForm pageForm, Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType, Boolean directFamilyOnly, Boolean overCostOnly) {
|
||||
Cnd cnd = buildQueryCnd(startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType, directFamilyOnly, overCostOnly);
|
||||
|
||||
Sql countSql = Sqls.create("""
|
||||
SELECT COUNT(DISTINCT t.id)
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
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
|
||||
$condition
|
||||
""");
|
||||
countSql.setCondition(cnd);
|
||||
countSql.setCallback(Sqls.callback.integer());
|
||||
tourLedgerService.dao().execute(countSql);
|
||||
int count = countSql.getInt();
|
||||
|
||||
Sql listSql = Sqls.create("""
|
||||
SELECT
|
||||
t.*,
|
||||
ins.id AS instanceId,
|
||||
ins.state AS instanceState,
|
||||
ins.processDefineId AS instanceProcessDefineId,
|
||||
IF(IFNULL(l.directFamilyUnitLine, 0) = 1 OR dr.id IS NOT NULL, 1, 0) AS directFamilyUnitLine,
|
||||
dr.id AS directRelativeId,
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName) AS currentLineName,
|
||||
IFNULL(f.familyCount, 0) AS familyCount,
|
||||
CASE
|
||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||
ELSE tp.travelPeriod
|
||||
END AS travelPeriod
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id
|
||||
LEFT JOIN (
|
||||
SELECT ledgerId, COUNT(1) AS familyCount
|
||||
FROM tour_ledger_family
|
||||
WHERE delFlag = 0
|
||||
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_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 (
|
||||
SELECT
|
||||
`year`,
|
||||
lineId,
|
||||
GROUP_CONCAT(
|
||||
DISTINCT CASE
|
||||
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
|
||||
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
|
||||
ELSE NULL
|
||||
END
|
||||
ORDER BY travelStartTime ASC
|
||||
SEPARATOR ';'
|
||||
) AS travelPeriod
|
||||
FROM tour_matter
|
||||
WHERE delFlag = 0
|
||||
AND lineId IS NOT NULL
|
||||
AND lineId <> ''
|
||||
GROUP BY `year`, lineId
|
||||
) tp ON (t.matterId IS NULL OR t.matterId = '') AND tp.`year` = t.`year` AND tp.lineId = t.lineId
|
||||
$condition
|
||||
GROUP BY t.id
|
||||
ORDER BY $orderColumn $orderBy, t.`year` DESC, t.signupTime DESC, t.createdAt DESC
|
||||
""");
|
||||
listSql.setCondition(cnd);
|
||||
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);
|
||||
|
||||
var list = listSql.getList(NutMap.class);
|
||||
list.forEach(item -> item.put("lineName", item.getString("currentLineName")));
|
||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.ledger")
|
||||
public Result detail(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
TourLedger ledger = tourLedgerService.fetch(id);
|
||||
if (ledger == null) {
|
||||
return Result.error("台账记录不存在");
|
||||
}
|
||||
ledger.setLineName(getCurrentLineName(ledger));
|
||||
Cnd familyCnd = Cnd.NEW();
|
||||
familyCnd.and(TourLedgerFamily::getLedgerId, "=", id);
|
||||
familyCnd.and(TourLedgerFamily::getDelFlag, "=", false);
|
||||
familyCnd.asc(TourLedgerFamily::getCreatedAt);
|
||||
TourLedgerDirectRelative directRelative = tourLedgerDirectRelativeService.fetch(
|
||||
Cnd.where(TourLedgerDirectRelative::getDelFlag, "=", false)
|
||||
.and(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId()));
|
||||
return Result.success(NutMap.NEW()
|
||||
.addv("ledger", ledger)
|
||||
.addv("travelPeriod", getTravelPeriod(ledger))
|
||||
.addv("families", tourLedgerFamilyService.query(familyCnd))
|
||||
.addv("directRelative", directRelative)
|
||||
.addv("fillBedInfo", isFillBedInfo(ledger))
|
||||
.addv("directFamilyUnitLine", isDirectFamilyUnitLine(ledger)));
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tour.ledger")
|
||||
@SLog(type = "tour", tag = "疗休养台账", msg = "删除疗休养台账")
|
||||
public Result doDelete(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
TourLedger ledger = tourLedgerService.fetch(id);
|
||||
if (ledger == null) {
|
||||
return Result.error("台账记录不存在");
|
||||
}
|
||||
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", id));
|
||||
tourLedgerService.delete(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tour.ledger")
|
||||
@SLog(type = "tour", tag = "疗休养台账", msg = "设置参加人员")
|
||||
public Result setParticipants(@Param("ids") String ids) {
|
||||
List<String> idList = StrUtil.isBlank(ids) ? Collections.emptyList() : Json.fromJsonAsList(String.class, ids);
|
||||
if (idList.isEmpty()) {
|
||||
return Result.error("请选择要设置的参加人员");
|
||||
}
|
||||
tourLedgerService.dao().update(TourLedger.class, Chain.make("joined", true), Cnd.where("id", "in", idList));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tour.ledger")
|
||||
@SLog(type = "tour", tag = "疗休养台账", msg = "参加人员导入")
|
||||
@AdaptBy(type = UploadAdaptor.class, args = {"ioc:fileUpload"})
|
||||
public Result importParticipants(@Param("file") TempFile file) {
|
||||
if (file == null) {
|
||||
return Result.error("请选择导入文件");
|
||||
}
|
||||
List<TourLedgerImportExcelMode> importRows = ExcelImportUtil.importExcel(file.getFile(), TourLedgerImportExcelMode.class, new ImportParams());
|
||||
ExcelImportRes<TourLedgerImportExcelMode> excelImportRes = new ExcelImportRes<>();
|
||||
excelImportRes.setTotalRecords(importRows.size());
|
||||
|
||||
for (int i = 0; i < importRows.size(); i++) {
|
||||
TourLedgerImportExcelMode row = importRows.get(i);
|
||||
if (!validateImportRow(row, i)) {
|
||||
continue;
|
||||
}
|
||||
Boolean joined = parseJoined(row.getJoined());
|
||||
if (joined == null) {
|
||||
row.setErrInfo("是否参加只能填写1、0、是、否、true、false、已参加、未参加", i);
|
||||
continue;
|
||||
}
|
||||
TourLine line = fetchImportLine(row);
|
||||
if (line == null) {
|
||||
row.setErrInfo("未找到唯一匹配的线路", i);
|
||||
continue;
|
||||
}
|
||||
List<TourLedger> ledgers = queryImportLedgerCandidates(row);
|
||||
if (ledgers.isEmpty()) {
|
||||
Result createResult = insertImportLedger(row, line, joined);
|
||||
if (createResult.isSuccess()) {
|
||||
continue;
|
||||
}
|
||||
row.setErrInfo(createResult.getMsg(), i);
|
||||
continue;
|
||||
}
|
||||
TourLedger ledger = resolveImportLedger(row, ledgers, line);
|
||||
if (ledger == null) {
|
||||
row.setErrInfo("匹配到多条台账记录,报名时间或线路未能唯一匹配,请核对年度、工号、报名时间、线路名称", i);
|
||||
continue;
|
||||
}
|
||||
Chain chain = Chain.make("joined", joined)
|
||||
.add("lineId", line.getId())
|
||||
.add("lineName", line.getLineName())
|
||||
.add("lineType", line.getLineType());
|
||||
String signupTime = normalizeSignupTime(row.getSignupTime());
|
||||
if (StrUtil.isNotBlank(signupTime)) {
|
||||
chain.add("signupTime", signupTime);
|
||||
}
|
||||
tourLedgerService.dao().update(TourLedger.class, chain, Cnd.where("id", "=", ledger.getId()));
|
||||
}
|
||||
|
||||
excelImportRes.setErrorDetails(importRows.stream().filter(s -> StrUtil.isNotBlank(s.getErrMsg())).collect(Collectors.toList()));
|
||||
excelImportRes.setFailedCount(excelImportRes.getErrorDetails().size());
|
||||
excelImportRes.setSuccessCount(Math.max(importRows.size() - excelImportRes.getFailedCount(), 0));
|
||||
return Result.success(excelImportRes);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.ledger")
|
||||
@Ok("void")
|
||||
public void downloadTemplate(HttpServletResponse response) {
|
||||
List<ExcelExportEntity> entities = new ArrayList<>();
|
||||
entities.add(new ExcelExportEntity("年度", "year", 12));
|
||||
entities.add(new ExcelExportEntity("工号", "jobNo", 18));
|
||||
entities.add(new ExcelExportEntity("姓名", "userName", 18));
|
||||
entities.add(new ExcelExportEntity("报名时间", "signupTime", 24));
|
||||
entities.add(new ExcelExportEntity("线路名称", "lineName", 30));
|
||||
entities.add(new ExcelExportEntity("线路类型", "lineType", 18));
|
||||
entities.add(new ExcelExportEntity("是否参加", "joined", 14));
|
||||
|
||||
ExportParams exportParams = new ExportParams();
|
||||
exportParams.setSheetName("tour_ledger");
|
||||
exportParams.setType(ExcelType.XSSF);
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, entities, Collections.emptyList());
|
||||
CommonDownloadUtil.download("参加人员导入模板.xlsx", workbook, response);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.ledger")
|
||||
@Ok("void")
|
||||
public void exportUnionSignupZip(Integer startYear, Integer endYear, String keyword, String unionId, String lineId,
|
||||
String travelPeriod, String lineType, Boolean directFamilyOnly, Boolean overCostOnly,
|
||||
HttpServletResponse response) throws Exception {
|
||||
List<NutMap> rows = queryUnionSignupRows(startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType,
|
||||
directFamilyOnly, overCostOnly);
|
||||
Map<String, List<NutMap>> unionRows = groupRowsByUnion(rows);
|
||||
|
||||
String zipName = URLEncoder.encode("疗休养分工会报名汇总表.zip", StandardCharsets.UTF_8.toString());
|
||||
response.setContentType("application/zip");
|
||||
response.setHeader("Content-Disposition", "attachment;filename=" + zipName + ";filename*=UTF-8''" + zipName);
|
||||
try (ZipOutputStream zipOutputStream = new ZipOutputStream(new BufferedOutputStream(response.getOutputStream()))) {
|
||||
if (unionRows.isEmpty()) {
|
||||
zipOutputStream.putNextEntry(new ZipEntry("无数据报名汇总表.xls"));
|
||||
try (Workbook workbook = buildUnionSignupWorkbook("无数据", Collections.emptyList())) {
|
||||
workbook.write(zipOutputStream);
|
||||
}
|
||||
zipOutputStream.closeEntry();
|
||||
} else {
|
||||
for (Map.Entry<String, List<NutMap>> entry : unionRows.entrySet()) {
|
||||
String unionName = entry.getValue().isEmpty() ? entry.getKey() : entry.getValue().get(0).getString("unionName", entry.getKey());
|
||||
String fileName = safeFileName(unionName + "报名汇总表.xls");
|
||||
zipOutputStream.putNextEntry(new ZipEntry(fileName));
|
||||
try (Workbook workbook = buildUnionSignupWorkbook(unionName, entry.getValue())) {
|
||||
workbook.write(zipOutputStream);
|
||||
}
|
||||
zipOutputStream.closeEntry();
|
||||
}
|
||||
}
|
||||
zipOutputStream.flush();
|
||||
}
|
||||
}
|
||||
|
||||
private List<NutMap> queryUnionSignupRows(Integer startYear, Integer endYear, String keyword, String unionId, String lineId,
|
||||
String travelPeriod, String lineType, Boolean directFamilyOnly, Boolean overCostOnly) {
|
||||
Cnd cnd = buildQueryCnd(startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType, directFamilyOnly, overCostOnly);
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
t.id,
|
||||
t.jobNo,
|
||||
t.userName,
|
||||
t.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(l.lineName, ''), t.lineName, '') AS lineName,
|
||||
CASE
|
||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||
ELSE tp.travelPeriod
|
||||
END AS travelPeriod
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
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 sys_union su ON su.id = t.unionId
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
`year`,
|
||||
lineId,
|
||||
GROUP_CONCAT(
|
||||
DISTINCT CASE
|
||||
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
|
||||
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
|
||||
ELSE NULL
|
||||
END
|
||||
ORDER BY travelStartTime ASC
|
||||
SEPARATOR ';'
|
||||
) AS travelPeriod
|
||||
FROM tour_matter
|
||||
WHERE delFlag = 0
|
||||
AND lineId IS NOT NULL
|
||||
AND lineId <> ''
|
||||
GROUP BY `year`, lineId
|
||||
) tp ON (t.matterId IS NULL OR t.matterId = '') AND tp.`year` = t.`year` AND tp.lineId = t.lineId
|
||||
$condition
|
||||
GROUP BY t.id
|
||||
ORDER BY unionCode ASC, unionName ASC, t.signupTime DESC, t.createdAt DESC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return sql.getList(NutMap.class);
|
||||
}
|
||||
|
||||
private Map<String, List<NutMap>> groupRowsByUnion(List<NutMap> rows) {
|
||||
Map<String, List<NutMap>> unionRows = new LinkedHashMap<>();
|
||||
for (NutMap row : rows) {
|
||||
String unionName = StrUtil.blankToDefault(row.getString("unionName"), "未分工会");
|
||||
String unionCode = StrUtil.blankToDefault(row.getString("unionCode"), "");
|
||||
String key = StrUtil.isBlank(unionCode) ? unionName : unionCode + "_" + unionName;
|
||||
unionRows.computeIfAbsent(key, item -> new ArrayList<>()).add(row);
|
||||
}
|
||||
return unionRows;
|
||||
}
|
||||
|
||||
private Workbook buildUnionSignupWorkbook(String unionName, List<NutMap> rows) {
|
||||
HSSFWorkbook workbook = new HSSFWorkbook();
|
||||
Sheet sheet = workbook.createSheet("报名汇总表");
|
||||
sheet.setColumnWidth(0, 8 * 256);
|
||||
sheet.setColumnWidth(1, 14 * 256);
|
||||
sheet.setColumnWidth(2, 14 * 256);
|
||||
sheet.setColumnWidth(3, 24 * 256);
|
||||
sheet.setColumnWidth(4, 16 * 256);
|
||||
sheet.setColumnWidth(5, 24 * 256);
|
||||
sheet.setColumnWidth(6, 36 * 256);
|
||||
sheet.setColumnWidth(7, 26 * 256);
|
||||
sheet.setColumnWidth(8, 18 * 256);
|
||||
|
||||
CellStyle titleStyle = createUnionSignupTitleStyle(workbook);
|
||||
CellStyle headerStyle = createUnionSignupHeaderStyle(workbook);
|
||||
CellStyle bodyStyle = createUnionSignupBodyStyle(workbook);
|
||||
|
||||
Row titleRow = sheet.createRow(0);
|
||||
titleRow.setHeightInPoints(28);
|
||||
Cell titleCell = titleRow.createCell(0);
|
||||
titleCell.setCellValue(StrUtil.blankToDefault(unionName, "未分工会") + "报名汇总表");
|
||||
titleCell.setCellStyle(titleStyle);
|
||||
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 8));
|
||||
for (int i = 1; i <= 8; i++) {
|
||||
titleRow.createCell(i).setCellStyle(titleStyle);
|
||||
}
|
||||
|
||||
Row headerRow = sheet.createRow(1);
|
||||
headerRow.setHeightInPoints(24);
|
||||
String[] headers = {"序号", "工号", "姓名", "身份证号码", "电话号码", "所属分工会", "所选线路名称", "疗休养时间", "备注"};
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
setCell(headerRow, i, headers[i], headerStyle);
|
||||
}
|
||||
|
||||
int rowIndex = 2;
|
||||
int seq = 1;
|
||||
for (NutMap row : rows) {
|
||||
Row dataRow = sheet.createRow(rowIndex++);
|
||||
dataRow.setHeightInPoints(22);
|
||||
setCell(dataRow, 0, String.valueOf(seq++), bodyStyle);
|
||||
setCell(dataRow, 1, row.getString("jobNo", ""), bodyStyle);
|
||||
setCell(dataRow, 2, row.getString("userName", ""), bodyStyle);
|
||||
setCell(dataRow, 3, row.getString("idCard", ""), bodyStyle);
|
||||
setCell(dataRow, 4, row.getString("mobile", ""), bodyStyle);
|
||||
setCell(dataRow, 5, row.getString("unionName", ""), bodyStyle);
|
||||
setCell(dataRow, 6, row.getString("lineName", ""), bodyStyle);
|
||||
setCell(dataRow, 7, row.getString("travelPeriod", ""), bodyStyle);
|
||||
setCell(dataRow, 8, "", bodyStyle);
|
||||
}
|
||||
return workbook;
|
||||
}
|
||||
|
||||
private CellStyle createUnionSignupTitleStyle(Workbook workbook) {
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
style.setAlignment(HorizontalAlignment.CENTER);
|
||||
style.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
style.setBorderTop(BorderStyle.THIN);
|
||||
style.setBorderRight(BorderStyle.THIN);
|
||||
style.setBorderBottom(BorderStyle.THIN);
|
||||
style.setBorderLeft(BorderStyle.THIN);
|
||||
Font font = workbook.createFont();
|
||||
font.setBold(true);
|
||||
font.setFontHeightInPoints((short) 16);
|
||||
style.setFont(font);
|
||||
return style;
|
||||
}
|
||||
|
||||
private CellStyle createUnionSignupHeaderStyle(Workbook workbook) {
|
||||
CellStyle style = createUnionSignupBodyStyle(workbook);
|
||||
style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
|
||||
style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
|
||||
Font font = workbook.createFont();
|
||||
font.setBold(true);
|
||||
style.setFont(font);
|
||||
return style;
|
||||
}
|
||||
|
||||
private CellStyle createUnionSignupBodyStyle(Workbook workbook) {
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
style.setAlignment(HorizontalAlignment.CENTER);
|
||||
style.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
style.setWrapText(true);
|
||||
style.setBorderTop(BorderStyle.THIN);
|
||||
style.setBorderRight(BorderStyle.THIN);
|
||||
style.setBorderBottom(BorderStyle.THIN);
|
||||
style.setBorderLeft(BorderStyle.THIN);
|
||||
return style;
|
||||
}
|
||||
|
||||
private void setCell(Row row, int index, String value, CellStyle style) {
|
||||
Cell cell = row.createCell(index);
|
||||
cell.setCellValue(StrUtil.blankToDefault(value, ""));
|
||||
cell.setCellStyle(style);
|
||||
}
|
||||
|
||||
private String safeFileName(String fileName) {
|
||||
String safeName = StrUtil.blankToDefault(fileName, "报名汇总表.xls")
|
||||
.replaceAll("[\\\\/:*?\"<>|]", "、");
|
||||
return safeName.length() > 120 ? safeName.substring(0, 120) : safeName;
|
||||
}
|
||||
|
||||
private boolean validateImportRow(TourLedgerImportExcelMode row, int index) {
|
||||
if (row.getYear() == null) {
|
||||
row.setErrInfo("年度为空", index);
|
||||
return false;
|
||||
}
|
||||
if (StrUtil.isBlank(row.getJobNo())) {
|
||||
row.setErrInfo("工号为空", index);
|
||||
return false;
|
||||
}
|
||||
if (StrUtil.isBlank(row.getLineName())) {
|
||||
row.setErrInfo("线路名称为空", index);
|
||||
return false;
|
||||
}
|
||||
if (StrUtil.isBlank(row.getLineType())) {
|
||||
row.setErrInfo("线路类型为空", index);
|
||||
return false;
|
||||
}
|
||||
if (StrUtil.isBlank(row.getJoined())) {
|
||||
row.setErrInfo("是否参加为空", index);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private TourLine fetchImportLine(TourLedgerImportExcelMode row) {
|
||||
Cnd cnd = Cnd.where(TourLine::getDelFlag, "=", false)
|
||||
.and(TourLine::getYear, "=", row.getYear())
|
||||
.and(TourLine::getLineName, "=", row.getLineName().trim())
|
||||
.and(TourLine::getLineType, "=", row.getLineType().trim());
|
||||
List<TourLine> lines = tourLedgerService.dao().query(TourLine.class, cnd);
|
||||
return lines.size() == 1 ? lines.get(0) : null;
|
||||
}
|
||||
|
||||
private List<TourLedger> queryImportLedgerCandidates(TourLedgerImportExcelMode row) {
|
||||
Cnd cnd = Cnd.where(TourLedger::getDelFlag, "=", false)
|
||||
.and(TourLedger::getYear, "=", row.getYear())
|
||||
.and(TourLedger::getJobNo, "=", row.getJobNo().trim());
|
||||
return tourLedgerService.query(cnd);
|
||||
}
|
||||
|
||||
private TourLedger resolveImportLedger(TourLedgerImportExcelMode row, List<TourLedger> ledgers, TourLine line) {
|
||||
if (ledgers.size() == 1) {
|
||||
return ledgers.get(0);
|
||||
}
|
||||
List<TourLedger> matched = ledgers;
|
||||
if (row.getSignupTime() != null) {
|
||||
String importSignupTime = normalizeSignupTime(row.getSignupTime());
|
||||
matched = matched.stream()
|
||||
.filter(item -> signupTimeEquals(importSignupTime, item.getSignupTime()))
|
||||
.collect(Collectors.toList());
|
||||
if (matched.size() == 1) {
|
||||
return matched.get(0);
|
||||
}
|
||||
}
|
||||
matched = ledgers.stream()
|
||||
.filter(item -> ledgerLineEquals(item, line))
|
||||
.collect(Collectors.toList());
|
||||
return matched.size() == 1 ? matched.get(0) : null;
|
||||
}
|
||||
|
||||
private boolean ledgerLineEquals(TourLedger ledger, TourLine line) {
|
||||
if (ledger == null || line == null) {
|
||||
return false;
|
||||
}
|
||||
if (StrUtil.isNotBlank(ledger.getLineId()) && ledger.getLineId().equals(line.getId())) {
|
||||
return true;
|
||||
}
|
||||
return StrUtil.equals(ledger.getLineName(), line.getLineName())
|
||||
&& StrUtil.equals(ledger.getLineType(), line.getLineType());
|
||||
}
|
||||
|
||||
private Result insertImportLedger(TourLedgerImportExcelMode row, TourLine line, Boolean joined) {
|
||||
View_user user = fetchImportUser(row.getJobNo());
|
||||
if (user == null) {
|
||||
return Result.error("系统用户中未找到该工号");
|
||||
}
|
||||
TourLedger ledger = new TourLedger();
|
||||
ledger.setYear(row.getYear());
|
||||
ledger.setJobNo(StrUtil.blankToDefault(user.getLoginname(), row.getJobNo().trim()));
|
||||
ledger.setUserName(StrUtil.blankToDefault(user.getUsername(), row.getUserName()));
|
||||
ledger.setGender(user.getSex());
|
||||
ledger.setIdCard(user.getIdCard());
|
||||
ledger.setUnitId(user.getUnitId());
|
||||
ledger.setUnitName(user.getUnitName());
|
||||
ledger.setUnionId(user.getUnionId());
|
||||
ledger.setUnionName(user.getUnionName());
|
||||
ledger.setSignupTime(normalizeSignupTime(row.getSignupTime()));
|
||||
ledger.setLineId(line.getId());
|
||||
ledger.setLineName(line.getLineName());
|
||||
ledger.setLineType(line.getLineType());
|
||||
ledger.setTravelAgencyId(line.getTravelAgencyId());
|
||||
ledger.setTravelAgencyName(getTravelAgencyName(line.getTravelAgencyId()));
|
||||
ledger.setHasFamily(false);
|
||||
ledger.setJoined(joined);
|
||||
ledger.setReimbursed(false);
|
||||
ledger.setOverCostReimbursed(false);
|
||||
tourLedgerService.insert(ledger);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
private View_user fetchImportUser(String jobNo) {
|
||||
if (StrUtil.isBlank(jobNo)) {
|
||||
return null;
|
||||
}
|
||||
return tourLedgerService.dao().fetch(View_user.class, Cnd.where(View_user::getLoginname, "=", jobNo.trim()));
|
||||
}
|
||||
|
||||
private String getTravelAgencyName(String travelAgencyId) {
|
||||
if (StrUtil.isBlank(travelAgencyId)) {
|
||||
return "";
|
||||
}
|
||||
TourTravelAgency agency = tourLedgerService.dao().fetch(TourTravelAgency.class, travelAgencyId);
|
||||
return agency == null ? "" : StrUtil.blankToDefault(agency.getAgencyName(), "");
|
||||
}
|
||||
|
||||
private boolean signupTimeEquals(String importSignupTime, String ledgerSignupTime) {
|
||||
if (StrUtil.isBlank(importSignupTime) || StrUtil.isBlank(ledgerSignupTime)) {
|
||||
return false;
|
||||
}
|
||||
String ledgerTime = normalizeSignupTime(ledgerSignupTime);
|
||||
if (importSignupTime.equals(ledgerTime)) {
|
||||
return true;
|
||||
}
|
||||
return importSignupTime.length() == 16 && ledgerTime.startsWith(importSignupTime)
|
||||
|| ledgerTime.length() == 16 && importSignupTime.startsWith(ledgerTime);
|
||||
}
|
||||
|
||||
private String normalizeSignupTime(Object value) {
|
||||
if (value instanceof Date date) {
|
||||
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
|
||||
}
|
||||
String text = StrUtil.trimToEmpty(value == null ? "" : String.valueOf(value)).replace('T', ' ');
|
||||
if (StrUtil.isBlank(text)) {
|
||||
return "";
|
||||
}
|
||||
text = text.replaceAll("\\.0$", "");
|
||||
String[] dateTimePatterns = {
|
||||
"yyyy-MM-dd HH:mm:ss",
|
||||
"yyyy-MM-dd HH:mm",
|
||||
"yyyy/M/d H:mm:ss",
|
||||
"yyyy/M/d H:mm",
|
||||
"M/d/yy H:mm:ss",
|
||||
"M/d/yy H:mm",
|
||||
"M/d/yyyy H:mm:ss",
|
||||
"M/d/yyyy H:mm"
|
||||
};
|
||||
for (String pattern : dateTimePatterns) {
|
||||
try {
|
||||
LocalDateTime dateTime = LocalDateTime.parse(text, DateTimeFormatter.ofPattern(pattern));
|
||||
return dateTime.format(DateTimeFormatter.ofPattern(pattern.contains(":ss") ? "yyyy-MM-dd HH:mm:ss" : "yyyy-MM-dd HH:mm"));
|
||||
} catch (DateTimeParseException ignored) {
|
||||
}
|
||||
}
|
||||
String[] datePatterns = {"yyyy-MM-dd", "yyyy/M/d", "M/d/yy", "M/d/yyyy"};
|
||||
for (String pattern : datePatterns) {
|
||||
try {
|
||||
LocalDate date = LocalDate.parse(text, DateTimeFormatter.ofPattern(pattern));
|
||||
return date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
|
||||
} catch (DateTimeParseException ignored) {
|
||||
}
|
||||
}
|
||||
return text;
|
||||
}
|
||||
|
||||
private Boolean parseJoined(String value) {
|
||||
String joined = StrUtil.trimToEmpty(value).toLowerCase();
|
||||
if ("1".equals(joined) || "1.0".equals(joined) || "是".equals(joined) || "true".equals(joined) || "已参加".equals(joined)) {
|
||||
return true;
|
||||
}
|
||||
if ("0".equals(joined) || "0.0".equals(joined) || "否".equals(joined) || "false".equals(joined) || "未参加".equals(joined)) {
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.ledger")
|
||||
public Result unionOptions() {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.asc("unionCode");
|
||||
cnd.asc("name");
|
||||
return Result.success(tourLedgerService.dao().query(Sys_union.class, cnd));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.ledger")
|
||||
public Result lineOptions(Integer startYear, Integer endYear, String travelPeriod, String lineType, String unionId, String keyword, Boolean directFamilyOnly, Boolean overCostOnly) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("t.delFlag", "=", false);
|
||||
cnd.andEX("t.`year`", ">=", startYear);
|
||||
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
|
||||
cnd.and("t.lineName", "IS NOT", null);
|
||||
cnd.and("t.lineName", "<>", "");
|
||||
cnd.andEX("t.unionId", "=", unionId);
|
||||
cnd.andEX("t.lineType", "=", lineType);
|
||||
appendTravelPeriodFilter(cnd, travelPeriod);
|
||||
appendKeywordFilter(cnd, keyword);
|
||||
appendScopeFilter(cnd, directFamilyOnly, overCostOnly);
|
||||
appendApprovedWorkflowFilter(cnd);
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT
|
||||
CASE
|
||||
WHEN IFNULL(t.lineId, '') <> '' THEN t.lineId
|
||||
ELSE CONCAT('legacy:', t.lineName)
|
||||
END AS lineId,
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName) AS lineName
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
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
|
||||
$condition
|
||||
ORDER BY lineName ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.ledger")
|
||||
public Result travelPeriodOptions(Integer startYear, Integer endYear, String lineId, String lineType, String unionId, String keyword, Boolean directFamilyOnly, Boolean overCostOnly) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("t.delFlag", "=", false);
|
||||
cnd.andEX("t.`year`", ">=", startYear);
|
||||
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
|
||||
cnd.and("m.travelStartTime", "IS NOT", null);
|
||||
cnd.and("m.travelStartTime", "<>", "");
|
||||
cnd.and("m.travelEndTime", "IS NOT", null);
|
||||
cnd.and("m.travelEndTime", "<>", "");
|
||||
cnd.andEX("t.unionId", "=", unionId);
|
||||
cnd.andEX("t.lineType", "=", lineType);
|
||||
appendLineFilter(cnd, lineId);
|
||||
appendKeywordFilter(cnd, keyword);
|
||||
appendScopeFilter(cnd, directFamilyOnly, overCostOnly);
|
||||
appendApprovedWorkflowFilter(cnd);
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime) AS travelPeriod
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
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
|
||||
$condition
|
||||
ORDER BY travelPeriod ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
private Cnd buildQueryCnd(Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType, Boolean directFamilyOnly, Boolean overCostOnly) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("t.delFlag", "=", false);
|
||||
cnd.andEX("t.`year`", ">=", startYear);
|
||||
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
|
||||
cnd.andEX("t.unionId", "=", unionId);
|
||||
cnd.andEX("t.lineType", "=", lineType);
|
||||
appendLineFilter(cnd, lineId);
|
||||
appendTravelPeriodFilter(cnd, travelPeriod);
|
||||
appendKeywordFilter(cnd, keyword);
|
||||
appendScopeFilter(cnd, directFamilyOnly, overCostOnly);
|
||||
appendApprovedWorkflowFilter(cnd);
|
||||
return cnd;
|
||||
}
|
||||
|
||||
private void appendApprovedWorkflowFilter(Cnd cnd) {
|
||||
SqlExpressionGroup group = new SqlExpressionGroup();
|
||||
group.or("ins.id", "IS", null);
|
||||
group.or("ins.state", "=", ProcessInstanceStateEnum.FINISHED.getCode());
|
||||
cnd.and(group);
|
||||
}
|
||||
|
||||
private void appendScopeFilter(Cnd cnd, Boolean directFamilyOnly, Boolean overCostOnly) {
|
||||
if (Boolean.TRUE.equals(directFamilyOnly)) {
|
||||
cnd.and("IF(IFNULL(l.directFamilyUnitLine, 0) = 1 OR dr.id IS NOT NULL, 1, 0)", "=", 1);
|
||||
}
|
||||
if (Boolean.TRUE.equals(overCostOnly)) {
|
||||
cnd.and("t.overCostReimbursed", "=", true);
|
||||
}
|
||||
}
|
||||
|
||||
private void appendKeywordFilter(Cnd cnd, String keyword) {
|
||||
if (StrUtil.isNotBlank(keyword)) {
|
||||
SqlExpressionGroup group = new SqlExpressionGroup();
|
||||
group.orLike("t.userName", keyword.trim());
|
||||
group.orLike("t.jobNo", keyword.trim());
|
||||
cnd.and(group);
|
||||
}
|
||||
}
|
||||
|
||||
private String getOrderColumn(String orderName) {
|
||||
if ("year".equals(orderName)) {
|
||||
return "t.`year`";
|
||||
}
|
||||
if ("jobNo".equals(orderName)) {
|
||||
return "t.jobNo";
|
||||
}
|
||||
if ("userName".equals(orderName)) {
|
||||
return "t.userName";
|
||||
}
|
||||
if ("lineName".equals(orderName)) {
|
||||
return "COALESCE(NULLIF(l.lineName, ''), t.lineName)";
|
||||
}
|
||||
if ("travelPeriod".equals(orderName)) {
|
||||
return "COALESCE(m.travelStartTime, tp.travelPeriod)";
|
||||
}
|
||||
if ("lineType".equals(orderName)) {
|
||||
return "t.lineType";
|
||||
}
|
||||
if ("signupTime".equals(orderName)) {
|
||||
return "t.signupTime";
|
||||
}
|
||||
if ("familyCount".equals(orderName)) {
|
||||
return "familyCount";
|
||||
}
|
||||
if ("joined".equals(orderName)) {
|
||||
return "t.joined";
|
||||
}
|
||||
return "t.`year`";
|
||||
}
|
||||
|
||||
private String getTravelPeriod(TourLedger ledger) {
|
||||
if (ledger == null || ledger.getYear() == null || StrUtil.isBlank(ledger.getLineId())) {
|
||||
return "";
|
||||
}
|
||||
if (StrUtil.isNotBlank(ledger.getMatterId())) {
|
||||
Sql exactSql = Sqls.create("""
|
||||
SELECT CASE
|
||||
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
|
||||
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
|
||||
ELSE ''
|
||||
END AS travelPeriod
|
||||
FROM tour_matter
|
||||
WHERE delFlag = 0
|
||||
AND id = @matterId
|
||||
""");
|
||||
exactSql.setParam("matterId", ledger.getMatterId());
|
||||
exactSql.setCallback(Sqls.callback.str());
|
||||
tourLedgerService.dao().execute(exactSql);
|
||||
return exactSql.getString();
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT GROUP_CONCAT(
|
||||
DISTINCT CASE
|
||||
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
|
||||
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
|
||||
ELSE NULL
|
||||
END
|
||||
ORDER BY travelStartTime ASC
|
||||
SEPARATOR ';'
|
||||
) AS travelPeriod
|
||||
FROM tour_matter
|
||||
WHERE delFlag = 0
|
||||
AND `year` = @year
|
||||
AND lineId = @lineId
|
||||
""");
|
||||
sql.setParam("year", ledger.getYear());
|
||||
sql.setParam("lineId", ledger.getLineId());
|
||||
sql.setCallback(Sqls.callback.str());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return sql.getString();
|
||||
}
|
||||
|
||||
private boolean isDirectFamilyUnitLine(TourLedger ledger) {
|
||||
if (ledger == null || StrUtil.isBlank(ledger.getLineId())) {
|
||||
return false;
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT IFNULL(directFamilyUnitLine, 0)
|
||||
FROM tour_line
|
||||
WHERE delFlag = 0
|
||||
AND id = @lineId
|
||||
""");
|
||||
sql.setParam("lineId", ledger.getLineId());
|
||||
sql.setCallback(Sqls.callback.integer());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return sql.getInt() > 0;
|
||||
}
|
||||
|
||||
private boolean isFillBedInfo(TourLedger ledger) {
|
||||
if (ledger == null || StrUtil.isBlank(ledger.getMatterId())) {
|
||||
return true;
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT IFNULL(MAX(s.fillBedInfo), 1)
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_setting s ON s.id = m.settingId AND s.delFlag = 0
|
||||
WHERE t.id = @ledgerId
|
||||
""");
|
||||
sql.setParam("ledgerId", ledger.getId());
|
||||
sql.setCallback(Sqls.callback.integer());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return sql.getInt() > 0;
|
||||
}
|
||||
|
||||
private void appendTravelPeriodFilter(Cnd cnd, String travelPeriod) {
|
||||
if (StrUtil.isBlank(travelPeriod)) {
|
||||
return;
|
||||
}
|
||||
cnd.and("CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)", "=", travelPeriod);
|
||||
}
|
||||
|
||||
private void appendLineFilter(Cnd cnd, String lineId) {
|
||||
if (StrUtil.isBlank(lineId)) {
|
||||
return;
|
||||
}
|
||||
if (lineId.startsWith("legacy:")) {
|
||||
SqlExpressionGroup legacyGroup = new SqlExpressionGroup();
|
||||
legacyGroup.or("t.lineId", "IS", null);
|
||||
legacyGroup.or("t.lineId", "=", "");
|
||||
cnd.and(legacyGroup);
|
||||
cnd.and("t.lineName", "=", lineId.substring("legacy:".length()));
|
||||
return;
|
||||
}
|
||||
cnd.and("t.lineId", "=", lineId);
|
||||
}
|
||||
|
||||
private String getCurrentLineName(TourLedger ledger) {
|
||||
if (ledger == null) {
|
||||
return "";
|
||||
}
|
||||
if (StrUtil.isBlank(ledger.getLineId())) {
|
||||
return ledger.getLineName();
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT lineName
|
||||
FROM tour_line
|
||||
WHERE delFlag = 0
|
||||
AND id = @lineId
|
||||
""");
|
||||
sql.setParam("lineId", ledger.getLineId());
|
||||
sql.setCallback(Sqls.callback.str());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return StrUtil.blankToDefault(sql.getString(), ledger.getLineName());
|
||||
}
|
||||
}
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
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.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.sys.views.View_user;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLine;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourTravelAgency;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLineService;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/platform/tour/route")
|
||||
public class TourLineController {
|
||||
|
||||
@Inject
|
||||
private TourLineService tourLineService;
|
||||
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/Tour/route/index.html")
|
||||
@SaCheckPermission("tour.route")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.route")
|
||||
public Result pageData(PageForm pageForm, Integer year, String lineName, String lotId) {
|
||||
if (StrUtil.isBlank(pageForm.getPageOrderName())) {
|
||||
pageForm.defaultSortDesc("enabled");
|
||||
}
|
||||
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("l.delFlag", "=", false);
|
||||
cnd.andEX("l.`year`", "=", year);
|
||||
cnd.and(Cnd.likeEX("l.lineName", lineName));
|
||||
cnd.andEX("l.lotId", "=", lotId);
|
||||
boolean sysAdmin = AuthUtil.hasRole(RoleConstant.SYSADMIN.name());
|
||||
boolean branchUnionScope = !sysAdmin && AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name());
|
||||
applyDataScope(cnd, sysAdmin, branchUnionScope);
|
||||
String creatorJoin = branchUnionScope ? """
|
||||
LEFT JOIN sys_unit lineUnit ON lineUnit.id = l.unitId
|
||||
LEFT JOIN sys_user creatorUser ON creatorUser.id = COALESCE(NULLIF(l.creatorUserId, ''), l.createdBy)
|
||||
LEFT JOIN sys_unit creatorUnit ON creatorUnit.id = creatorUser.unitId
|
||||
""" : "";
|
||||
|
||||
Sql countSql = Sqls.create("""
|
||||
SELECT COUNT(1)
|
||||
FROM tour_line l
|
||||
$creatorJoin
|
||||
$condition
|
||||
""");
|
||||
countSql.setVar("creatorJoin", creatorJoin);
|
||||
countSql.setCondition(cnd);
|
||||
countSql.setCallback(Sqls.callback.integer());
|
||||
tourLineService.dao().execute(countSql);
|
||||
int count = countSql.getInt();
|
||||
|
||||
Sql listSql = Sqls.create("""
|
||||
SELECT
|
||||
l.*,
|
||||
a.agencyName AS travelAgencyName,
|
||||
lot.lotName AS lotName
|
||||
FROM tour_line l
|
||||
LEFT JOIN tour_travel_agency a ON a.id = l.travelAgencyId
|
||||
LEFT JOIN tour_setting_lot lot ON lot.id = l.lotId
|
||||
$creatorJoin
|
||||
$condition
|
||||
ORDER BY $orderColumn $orderBy, l.`year` DESC, l.lineCode ASC, l.createdAt DESC
|
||||
""");
|
||||
listSql.setVar("creatorJoin", creatorJoin);
|
||||
listSql.setCondition(cnd);
|
||||
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
|
||||
listSql.setVar("orderBy", "descending".equals(pageForm.getPageOrderBy()) ? "desc" : "asc");
|
||||
listSql.setPager(tourLineService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||
listSql.setCallback(Sqls.callback.maps());
|
||||
tourLineService.dao().execute(listSql);
|
||||
|
||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, listSql.getList(NutMap.class));
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.route")
|
||||
public Result detail(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
TourLine line = tourLineService.fetch(id);
|
||||
return line == null ? Result.error("线路不存在") : Result.success(line);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.route")
|
||||
public Result travelAgencyOptions(Integer year) {
|
||||
Cnd cnd = Cnd.where(TourTravelAgency::getEnabled, "=", true);
|
||||
cnd.andEX(TourTravelAgency::getYear, "=", year);
|
||||
cnd.asc(TourTravelAgency::getAgencyCode).asc(TourTravelAgency::getAgencyName);
|
||||
return Result.success(tourLineService.dao().query(TourTravelAgency.class, cnd));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.route")
|
||||
public Result lotOptions(Integer year) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX("s.`year`", "=", year);
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT lot.id, lot.lotName, lot.lotValue, lot.activityCost
|
||||
FROM tour_setting_lot lot
|
||||
INNER JOIN tour_setting s ON s.id = lot.settingId
|
||||
$condition
|
||||
ORDER BY lot.lotValue DESC, lot.lotName ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLineService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.route")
|
||||
public Result lineTypeOptions() {
|
||||
List<Sys_dict> list = sysDictService.getSubListByCode("lineType");
|
||||
if (list == null) {
|
||||
return Result.success(Collections.emptyList());
|
||||
}
|
||||
return Result.success(list.stream().filter(item -> !item.isDisabled()).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tour.route")
|
||||
@SLog(type = "tour", tag = "线路管理", msg = "保存线路信息")
|
||||
public Result doSubmit(TourLine line) {
|
||||
Result checkResult = check(line);
|
||||
if (checkResult != null) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
Cnd sameCodeCnd = Cnd.where(TourLine::getYear, "=", line.getYear())
|
||||
.and(TourLine::getLineCode, "=", line.getLineCode());
|
||||
if (StrUtil.isNotBlank(line.getId())) {
|
||||
sameCodeCnd.and(TourLine::getId, "<>", line.getId());
|
||||
}
|
||||
if (tourLineService.count(sameCodeCnd) > 0) {
|
||||
return Result.error("同年度下线路编号已存在");
|
||||
}
|
||||
if (line.getEnabled() == null) {
|
||||
line.setEnabled(true);
|
||||
}
|
||||
if (line.getOpenFlag() == null) {
|
||||
line.setOpenFlag(true);
|
||||
}
|
||||
if (line.getDirectFamilyUnitLine() == null) {
|
||||
line.setDirectFamilyUnitLine(false);
|
||||
}
|
||||
if (StrUtil.isBlank(line.getId())) {
|
||||
fillCreatorInfo(line, null);
|
||||
tourLineService.insert(line);
|
||||
} else {
|
||||
fillCreatorInfo(line, tourLineService.fetch(line.getId()));
|
||||
tourLineService.updateIgnoreNull(line);
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tour.route")
|
||||
@SLog(type = "tour", tag = "线路管理", msg = "删除线路信息")
|
||||
public Result doDelete(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
tourLineService.delete(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
private Result check(TourLine line) {
|
||||
if (line == null) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
if (line.getYear() == null) {
|
||||
return Result.error("创建年度不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(line.getTravelAgencyId())) {
|
||||
return Result.error("旅行社名称不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(line.getLineName())) {
|
||||
return Result.error("线路名称不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(line.getLineCode())) {
|
||||
return Result.error("线路编号不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(line.getLineType())) {
|
||||
return Result.error("线路类型不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(line.getLotId())) {
|
||||
return Result.error("时间标段不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(line.getMobileThumb())) {
|
||||
return Result.error("移动端缩略图不能为空");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建人和所在单位由当前登录人生成,编辑时保留原创建信息。
|
||||
*/
|
||||
private void fillCreatorInfo(TourLine line, TourLine oldLine) {
|
||||
if (oldLine != null) {
|
||||
line.setCreatorUserId(defaultIfBlank(oldLine.getCreatorUserId(), SecurityUtil.getUserId()));
|
||||
line.setCreatorName(defaultIfBlank(oldLine.getCreatorName(), SecurityUtil.getUserUsername()));
|
||||
line.setUnitId(defaultIfBlank(oldLine.getUnitId(), SecurityUtil.getUnitId()));
|
||||
line.setUnitName(defaultIfBlank(oldLine.getUnitName(), getCurrentUnitName()));
|
||||
return;
|
||||
}
|
||||
line.setCreatorUserId(SecurityUtil.getUserId());
|
||||
line.setCreatorName(SecurityUtil.getUserUsername());
|
||||
line.setUnitId(SecurityUtil.getUnitId());
|
||||
line.setUnitName(getCurrentUnitName());
|
||||
}
|
||||
|
||||
private String getCurrentUnitName() {
|
||||
View_user user = tourLineService.dao().fetch(View_user.class, Cnd.where("id", "=", SecurityUtil.getUserId()));
|
||||
return user == null ? "" : defaultIfBlank(user.getUnitName(), "");
|
||||
}
|
||||
|
||||
private String defaultIfBlank(String value, String defaultValue) {
|
||||
return StrUtil.isBlank(value) ? defaultValue : value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 线路数据范围:
|
||||
* SYSADMIN 可查看全部;分工会主席查看本工会创建的线路;其它角色保持原逻辑,仅查看自己创建的线路。
|
||||
*/
|
||||
private void applyDataScope(Cnd cnd, boolean sysAdmin, boolean branchUnionScope) {
|
||||
if (sysAdmin) {
|
||||
return;
|
||||
}
|
||||
if (branchUnionScope) {
|
||||
SqlExpressionGroup unionGroup = new SqlExpressionGroup();
|
||||
unionGroup.or("lineUnit.unionId", "=", SecurityUtil.getUnionId());
|
||||
unionGroup.or("creatorUnit.unionId", "=", SecurityUtil.getUnionId());
|
||||
cnd.and(unionGroup);
|
||||
return;
|
||||
}
|
||||
SqlExpressionGroup creatorGroup = new SqlExpressionGroup();
|
||||
creatorGroup.or("l.creatorUserId", "=", SecurityUtil.getUserId());
|
||||
creatorGroup.or("l.createdBy", "=", SecurityUtil.getUserId());
|
||||
cnd.and(creatorGroup);
|
||||
}
|
||||
|
||||
private String getOrderColumn(String orderName) {
|
||||
if ("year".equals(orderName)) {
|
||||
return "l.`year`";
|
||||
}
|
||||
if ("lineCode".equals(orderName)) {
|
||||
return "l.lineCode";
|
||||
}
|
||||
if ("lotName".equals(orderName)) {
|
||||
return "lot.lotName";
|
||||
}
|
||||
if ("lineType".equals(orderName)) {
|
||||
return "l.lineType";
|
||||
}
|
||||
if ("enabled".equals(orderName)) {
|
||||
return "l.enabled";
|
||||
}
|
||||
return "l.`year`";
|
||||
}
|
||||
}
|
||||
+441
@@ -0,0 +1,441 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
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.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.models.Sys_union;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
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.TourMatter;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/platform/tour/matter")
|
||||
public class TourMatterController {
|
||||
|
||||
private static final String MOBILE_PATTERN = "^1[3-9]\\d{9}$";
|
||||
|
||||
@Inject
|
||||
private TourMatterService tourMatterService;
|
||||
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/Tour/matter/index.html")
|
||||
@SaCheckPermission("tour.matter")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.matter")
|
||||
public Result pageData(PageForm pageForm, Integer year, String matterName, String unionId, String organizationType) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("m.delFlag", "=", false);
|
||||
cnd.andEX("m.`year`", "=", year);
|
||||
cnd.and(Cnd.likeEX("m.matterName", matterName));
|
||||
cnd.andEX("m.unionId", "=", unionId);
|
||||
cnd.andEX("m.organizationType", "=", organizationType);
|
||||
applyMatterDataScope(cnd);
|
||||
|
||||
Sql countSql = Sqls.create("""
|
||||
SELECT COUNT(1)
|
||||
FROM tour_matter m
|
||||
$condition
|
||||
""");
|
||||
countSql.setCondition(cnd);
|
||||
countSql.setCallback(Sqls.callback.integer());
|
||||
tourMatterService.dao().execute(countSql);
|
||||
int count = countSql.getInt();
|
||||
|
||||
Sql listSql = Sqls.create("""
|
||||
SELECT
|
||||
m.*,
|
||||
s.configName AS settingName,
|
||||
u.name AS unionName,
|
||||
d.name AS organizationTypeName,
|
||||
l.lineName AS lineName,
|
||||
l.lineType AS lineType,
|
||||
l.directFamilyUnitLine AS directFamilyUnitLine
|
||||
FROM tour_matter m
|
||||
LEFT JOIN tour_setting s ON s.id = m.settingId
|
||||
LEFT JOIN sys_union u ON u.id = m.unionId
|
||||
LEFT JOIN tour_line l ON l.id = m.lineId
|
||||
LEFT JOIN sys_dict d ON d.`code` = m.organizationType
|
||||
AND d.parentId = (SELECT id FROM sys_dict WHERE `code` = 'organizationType' LIMIT 1)
|
||||
$condition
|
||||
ORDER BY $orderColumn $orderBy, m.`year` DESC, m.createdAt DESC
|
||||
""");
|
||||
listSql.setCondition(cnd);
|
||||
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
|
||||
listSql.setVar("orderBy", "descending".equals(pageForm.getPageOrderBy()) ? "desc" : "asc");
|
||||
listSql.setPager(tourMatterService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||
listSql.setCallback(Sqls.callback.maps());
|
||||
tourMatterService.dao().execute(listSql);
|
||||
|
||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, listSql.getList(NutMap.class));
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.matter")
|
||||
public Result detail(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
TourMatter matter = tourMatterService.fetch(id);
|
||||
return matter == null ? Result.error("事项不存在") : Result.success(matter);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.matter")
|
||||
public Result settingOptions(Integer year) {
|
||||
Cnd cnd = Cnd.where(TourSetting::getEnabled, "=", true);
|
||||
cnd.andEX(TourSetting::getYear, "=", year);
|
||||
cnd.desc(TourSetting::getCreatedAt);
|
||||
cnd.desc(TourSetting::getUpdatedAt);
|
||||
cnd.asc(TourSetting::getConfigName);
|
||||
return Result.success(tourMatterService.dao().query(TourSetting.class, cnd));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.matter")
|
||||
public Result unionOptions() {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.asc("unionCode");
|
||||
cnd.asc("name");
|
||||
return Result.success(tourMatterService.dao().query(Sys_union.class, cnd));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.matter")
|
||||
public Result lineOptions(Integer year) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("l.delFlag", "=", false);
|
||||
cnd.and("l.enabled", "=", true);
|
||||
cnd.and("l.openFlag", "=", true);
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
l.*,
|
||||
lot.lotName AS lotName,
|
||||
lot.lotValue AS lotValue,
|
||||
lot.lotValue AS lotDays,
|
||||
lot.activityCost AS activityCost
|
||||
FROM tour_line l
|
||||
LEFT JOIN tour_setting_lot lot ON lot.id = l.lotId
|
||||
$condition
|
||||
ORDER BY l.lineCode ASC, l.lineName ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourMatterService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.matter")
|
||||
public Result organizationTypeOptions() {
|
||||
List<Sys_dict> list = sysDictService.getSubListByCode("organizationType");
|
||||
if (list == null) {
|
||||
return Result.success(Collections.emptyList());
|
||||
}
|
||||
return Result.success(list.stream().filter(item -> !item.isDisabled()).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tour.matter")
|
||||
@SLog(type = "tour", tag = "疗休养事项", msg = "保存疗休养事项")
|
||||
public Result doSubmit(TourMatter matter) {
|
||||
Result checkResult = check(matter);
|
||||
if (checkResult != null) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
Cnd sameNameCnd = Cnd.where(TourMatter::getYear, "=", matter.getYear())
|
||||
.and(TourMatter::getMatterName, "=", matter.getMatterName());
|
||||
if (StrUtil.isNotBlank(matter.getId())) {
|
||||
sameNameCnd.and(TourMatter::getId, "<>", matter.getId());
|
||||
}
|
||||
if (tourMatterService.count(sameNameCnd) > 0) {
|
||||
return Result.error("同年度下事项名称已存在");
|
||||
}
|
||||
if (matter.getEnabled() == null) {
|
||||
matter.setEnabled(true);
|
||||
}
|
||||
fillCreatorAndUnion(matter);
|
||||
if (StrUtil.isBlank(matter.getId())) {
|
||||
tourMatterService.insert(matter);
|
||||
} else {
|
||||
tourMatterService.updateIgnoreNull(matter);
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tour.matter")
|
||||
@SLog(type = "tour", tag = "疗休养事项", msg = "删除疗休养事项")
|
||||
public Result doDelete(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
TourMatter matter = tourMatterService.fetch(id);
|
||||
if (matter == null) {
|
||||
return Result.error("事项不存在");
|
||||
}
|
||||
long signupCount = countSignup(id);
|
||||
if (signupCount > 0) {
|
||||
return Result.error("当前事项对应线路已有人员报名,不能删除");
|
||||
}
|
||||
tourMatterService.delete(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.matter")
|
||||
public Result deleteInfo(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
TourMatter matter = tourMatterService.fetch(id);
|
||||
if (matter == null) {
|
||||
return Result.error("事项不存在");
|
||||
}
|
||||
long signupCount = countSignup(id);
|
||||
return Result.success(NutMap.NEW()
|
||||
.addv("signupCount", signupCount)
|
||||
.addv("canDelete", signupCount <= 0)
|
||||
.addv("message", signupCount > 0 ? "当前事项已有人员报名,不能删除" : "当前事项暂无人员报名,可删除"));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.matter")
|
||||
public Result lineConfig(String matterId) {
|
||||
if (StrUtil.isBlank(matterId)) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
TourMatter matter = tourMatterService.fetch(matterId);
|
||||
return matter == null ? Result.error("事项不存在") : Result.success(matter);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.matter")
|
||||
public Result settingPeople(String matterId) {
|
||||
if (StrUtil.isBlank(matterId)) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
TourMatter matter = tourMatterService.fetch(matterId);
|
||||
if (matter == null || StrUtil.isBlank(matter.getSettingId())) {
|
||||
return Result.success(NutMap.NEW());
|
||||
}
|
||||
TourSetting setting = tourMatterService.dao().fetch(TourSetting.class, matter.getSettingId());
|
||||
if (setting == null) {
|
||||
return Result.success(NutMap.NEW());
|
||||
}
|
||||
return Result.success(NutMap.NEW()
|
||||
.addv("minGroupPeople", setting.getMinGroupPeople())
|
||||
.addv("maxGroupPeople", setting.getMaxGroupPeople()));
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tour.matter")
|
||||
@SLog(type = "tour", tag = "疗休养事项", msg = "保存事项线路配置")
|
||||
public Result lineConfigDoSubmit(TourMatter matter) {
|
||||
Result checkResult = checkLineConfig(matter);
|
||||
if (checkResult != null) {
|
||||
return checkResult;
|
||||
}
|
||||
TourMatter oldMatter = tourMatterService.fetch(matter.getId());
|
||||
if (oldMatter == null) {
|
||||
return Result.error("事项不存在");
|
||||
}
|
||||
oldMatter.setLineId(matter.getLineId());
|
||||
oldMatter.setSignupStartTime(matter.getSignupStartTime());
|
||||
oldMatter.setSignupEndTime(matter.getSignupEndTime());
|
||||
oldMatter.setTravelStartTime(matter.getTravelStartTime());
|
||||
oldMatter.setTravelEndTime(matter.getTravelEndTime());
|
||||
oldMatter.setContactName(matter.getContactName());
|
||||
oldMatter.setContactPhone(matter.getContactPhone());
|
||||
oldMatter.setMinGroupPeople(matter.getMinGroupPeople());
|
||||
oldMatter.setMaxGroupPeople(matter.getMaxGroupPeople());
|
||||
oldMatter.setEstimatedCost(matter.getEstimatedCost());
|
||||
tourMatterService.update(oldMatter);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
private Result check(TourMatter matter) {
|
||||
if (matter == null) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
if (matter.getYear() == null) {
|
||||
return Result.error("年度不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(matter.getMatterName())) {
|
||||
return Result.error("事项名称不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(matter.getSettingId())) {
|
||||
return Result.error("疗休养配置不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(matter.getOrganizationType())) {
|
||||
return Result.error("组织形式不能为空");
|
||||
}
|
||||
if (!"schoolUnion".equals(matter.getOrganizationType()) && StrUtil.isBlank(matter.getUnionId())) {
|
||||
return Result.error("所属工会不能为空");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Result checkLineConfig(TourMatter matter) {
|
||||
if (matter == null) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
if (StrUtil.isBlank(matter.getId())) {
|
||||
return Result.error("事项不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(matter.getLineId())) {
|
||||
return Result.error("线路不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(matter.getSignupStartTime()) || StrUtil.isBlank(matter.getSignupEndTime())
|
||||
|| StrUtil.isBlank(matter.getTravelStartTime()) || StrUtil.isBlank(matter.getTravelEndTime())) {
|
||||
return Result.error("时间不能为空");
|
||||
}
|
||||
if (matter.getSignupStartTime().compareTo(matter.getSignupEndTime()) >= 0) {
|
||||
return Result.error("报名开始时间必须小于报名结束时间");
|
||||
}
|
||||
if (matter.getTravelStartTime().compareTo(matter.getTravelEndTime()) > 0) {
|
||||
return Result.error("出行开始时间不能晚于出行结束时间");
|
||||
}
|
||||
if (matter.getSignupEndTime().compareTo(matter.getTravelStartTime()) >= 0) {
|
||||
return Result.error("报名结束时间必须小于出行开始时间");
|
||||
}
|
||||
if (StrUtil.isBlank(matter.getContactName())) {
|
||||
return Result.error("联系人不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(matter.getContactPhone()) || !matter.getContactPhone().matches(MOBILE_PATTERN)) {
|
||||
return Result.error("联系方式格式不正确");
|
||||
}
|
||||
if (matter.getMinGroupPeople() == null || matter.getMinGroupPeople() <= 0) {
|
||||
return Result.error("最少成团人数必须大于0");
|
||||
}
|
||||
if (matter.getMaxGroupPeople() == null || matter.getMaxGroupPeople() <= 0) {
|
||||
return Result.error("最多成团人数必须大于0");
|
||||
}
|
||||
if (matter.getMinGroupPeople() > matter.getMaxGroupPeople()) {
|
||||
return Result.error("最少成团人数不能大于最多成团人数");
|
||||
}
|
||||
if (matter.getEstimatedCost() == null || matter.getEstimatedCost().signum() < 0) {
|
||||
return Result.error("预计费用不能小于0");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void fillCreatorAndUnion(TourMatter matter) {
|
||||
if (StrUtil.isBlank(matter.getId())) {
|
||||
matter.setCreatorUserId(SecurityUtil.getUserId());
|
||||
matter.setCreatorName(SecurityUtil.getUserUsername());
|
||||
} else {
|
||||
TourMatter oldMatter = tourMatterService.fetch(matter.getId());
|
||||
if (oldMatter != null) {
|
||||
matter.setCreatorUserId(oldMatter.getCreatorUserId());
|
||||
matter.setCreatorName(oldMatter.getCreatorName());
|
||||
}
|
||||
}
|
||||
if ("schoolUnion".equals(matter.getOrganizationType())) {
|
||||
matter.setUnionId("");
|
||||
}
|
||||
}
|
||||
|
||||
private long countSignup(String matterId) {
|
||||
return tourMatterService.dao().count(TourLedger.class, Cnd.where(TourLedger::getDelFlag, "=", false)
|
||||
.and(TourLedger::getMatterId, "=", matterId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 事项数据范围:SYSADMIN 查看全部;分工会主席查看本工会事项;其它角色仅查看自己创建的事项。
|
||||
*/
|
||||
private void applyMatterDataScope(Cnd cnd) {
|
||||
if (AuthUtil.hasRole(RoleConstant.SYSADMIN.name())) {
|
||||
return;
|
||||
}
|
||||
if (AuthUtil.hasRole(RoleConstant.BRANCH_UNION_CHAIRMAN.name())) {
|
||||
cnd.and("m.unionId", "=", SecurityUtil.getUnionId());
|
||||
return;
|
||||
}
|
||||
SqlExpressionGroup creatorGroup = new SqlExpressionGroup();
|
||||
creatorGroup.or("m.creatorUserId", "=", SecurityUtil.getUserId());
|
||||
creatorGroup.or("m.createdBy", "=", SecurityUtil.getUserId());
|
||||
cnd.and(creatorGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* 事项批次选择线路时沿用线路管理的数据范围,避免在事项页面选到权限外线路。
|
||||
*/
|
||||
private void applyLineDataScope(Cnd cnd, boolean sysAdmin, boolean branchUnionScope) {
|
||||
if (sysAdmin) {
|
||||
return;
|
||||
}
|
||||
if (branchUnionScope) {
|
||||
SqlExpressionGroup unionGroup = new SqlExpressionGroup();
|
||||
unionGroup.or("lineUnit.unionId", "=", SecurityUtil.getUnionId());
|
||||
unionGroup.or("creatorUnit.unionId", "=", SecurityUtil.getUnionId());
|
||||
cnd.and(unionGroup);
|
||||
return;
|
||||
}
|
||||
SqlExpressionGroup creatorGroup = new SqlExpressionGroup();
|
||||
creatorGroup.or("l.creatorUserId", "=", SecurityUtil.getUserId());
|
||||
creatorGroup.or("l.createdBy", "=", SecurityUtil.getUserId());
|
||||
cnd.and(creatorGroup);
|
||||
}
|
||||
|
||||
private String getOrderColumn(String orderName) {
|
||||
if ("year".equals(orderName)) {
|
||||
return "m.`year`";
|
||||
}
|
||||
if ("matterName".equals(orderName)) {
|
||||
return "m.matterName";
|
||||
}
|
||||
if ("unionName".equals(orderName)) {
|
||||
return "u.name";
|
||||
}
|
||||
if ("lineName".equals(orderName)) {
|
||||
return "l.lineName";
|
||||
}
|
||||
if ("settingName".equals(orderName)) {
|
||||
return "s.configName";
|
||||
}
|
||||
if ("organizationTypeName".equals(orderName)) {
|
||||
return "d.location";
|
||||
}
|
||||
if ("enabled".equals(orderName)) {
|
||||
return "m.enabled";
|
||||
}
|
||||
return "m.`year`";
|
||||
}
|
||||
}
|
||||
+1845
File diff suppressed because it is too large
Load Diff
+13
@@ -0,0 +1,13 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
||||
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
|
||||
/**
|
||||
* 分阶段建设的菜单占位入口。
|
||||
*/
|
||||
@IocBean
|
||||
@At("/platform/tour")
|
||||
public class TourPlaceholderController {
|
||||
|
||||
}
|
||||
+580
@@ -0,0 +1,580 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
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.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerDirectRelative;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerFamily;
|
||||
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;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/platform/tour/schoolUnionApproval")
|
||||
public class TourSchoolUnionApprovalController {
|
||||
|
||||
private static final String WORKFLOW_KEY = "LXYBZXQSXL";
|
||||
private static final String TASK_DISPLAY_NAME = "校工会审核";
|
||||
|
||||
@Inject
|
||||
private TourLedgerService tourLedgerService;
|
||||
|
||||
@Inject
|
||||
private TourLedgerFamilyService tourLedgerFamilyService;
|
||||
|
||||
@Inject
|
||||
private TourLedgerDirectRelativeService tourLedgerDirectRelativeService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/Tour/schoolUnionApproval/index.html")
|
||||
@SaCheckPermission("tour.schoolUnionApproval")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/h5")
|
||||
@Ok("beetl:/platform/zhghh5/dayofficework/tour/schoolUnionApproval/index.html")
|
||||
@SaCheckPermission("tour.schoolUnionApproval")
|
||||
public void h5() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.schoolUnionApproval")
|
||||
public Result pageData(PageForm pageForm, Boolean audit, Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType) {
|
||||
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType);
|
||||
|
||||
Sql countSql = Sqls.create("""
|
||||
SELECT COUNT(DISTINCT task.id)
|
||||
FROM wf_process_task task
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
|
||||
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
|
||||
INNER JOIN tour_ledger t ON t.id = ins.businessNo
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
$condition
|
||||
""");
|
||||
countSql.setCondition(cnd);
|
||||
countSql.setCallback(Sqls.callback.integer());
|
||||
tourLedgerService.dao().execute(countSql);
|
||||
int count = countSql.getInt();
|
||||
|
||||
Sql listSql = Sqls.create("""
|
||||
SELECT
|
||||
t.*,
|
||||
ins.id AS instanceId,
|
||||
ins.businessNo,
|
||||
ins.state AS instanceState,
|
||||
ins.variable AS instanceVariable,
|
||||
ins.processDefineId AS instanceProcessDefineId,
|
||||
task.id AS taskId,
|
||||
task.taskName AS taskKey,
|
||||
task.displayName AS taskName,
|
||||
task.taskType,
|
||||
task.performType AS taskPerformType,
|
||||
task.taskState,
|
||||
task.finishTime,
|
||||
task.taskParentId,
|
||||
task.variable AS taskVariable,
|
||||
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName), '结束') AS curTaskName,
|
||||
CASE
|
||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
||||
ELSE CONCAT(
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
||||
'(',
|
||||
CASE
|
||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
||||
ELSE IFNULL(mu.name, '')
|
||||
END,
|
||||
')'
|
||||
)
|
||||
END AS currentLineName,
|
||||
CASE
|
||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||
ELSE tp.travelPeriod
|
||||
END AS travelPeriod,
|
||||
IFNULL(f.familyCount, 0) AS familyCount
|
||||
FROM wf_process_task task
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
|
||||
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
|
||||
INNER JOIN tour_ledger t ON t.id = ins.businessNo
|
||||
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
|
||||
LEFT JOIN (
|
||||
SELECT ledgerId, COUNT(1) AS familyCount
|
||||
FROM tour_ledger_family
|
||||
WHERE delFlag = 0
|
||||
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_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
LEFT JOIN sys_union mu ON mu.id = m.unionId
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
`year`,
|
||||
lineId,
|
||||
GROUP_CONCAT(
|
||||
DISTINCT CASE
|
||||
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
|
||||
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
|
||||
ELSE NULL
|
||||
END
|
||||
ORDER BY travelStartTime ASC
|
||||
SEPARATOR ';'
|
||||
) AS travelPeriod
|
||||
FROM tour_matter
|
||||
WHERE delFlag = 0
|
||||
AND lineId IS NOT NULL
|
||||
AND lineId <> ''
|
||||
GROUP BY `year`, lineId
|
||||
) tp ON (t.matterId IS NULL OR t.matterId = '') AND tp.`year` = t.`year` AND tp.lineId = t.lineId
|
||||
$condition
|
||||
GROUP BY task.id
|
||||
ORDER BY $orderColumn $orderBy, task.createdAt DESC, t.signupTime DESC, t.createdAt DESC
|
||||
""");
|
||||
listSql.setCondition(cnd);
|
||||
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
|
||||
listSql.setVar("orderBy", "ascending".equals(pageForm.getPageOrderBy()) ? "asc" : "desc");
|
||||
listSql.setPager(tourLedgerService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||
listSql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(listSql);
|
||||
|
||||
List<NutMap> list = listSql.getList(NutMap.class);
|
||||
list.forEach(item -> item.put("lineName", item.getString("currentLineName")));
|
||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.schoolUnionApproval")
|
||||
public Result detail(String id) {
|
||||
TourLedger ledger = fetchAuditLedger(id);
|
||||
if (ledger == null) {
|
||||
return Result.error("报名记录不存在或无权查看");
|
||||
}
|
||||
ledger.setLineName(getCurrentLineName(ledger));
|
||||
Cnd familyCnd = Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId())
|
||||
.and(TourLedgerFamily::getDelFlag, "=", false);
|
||||
familyCnd.asc(TourLedgerFamily::getCreatedAt);
|
||||
TourLedgerDirectRelative directRelative = tourLedgerDirectRelativeService.fetch(
|
||||
Cnd.where(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId())
|
||||
.and(TourLedgerDirectRelative::getDelFlag, "=", false));
|
||||
NutMap signupConfig = getSignupConfig(ledger);
|
||||
return Result.success(NutMap.NEW()
|
||||
.addv("ledger", ledger)
|
||||
.addv("travelPeriod", getTravelPeriod(ledger))
|
||||
.addv("families", tourLedgerFamilyService.query(familyCnd))
|
||||
.addv("directRelative", directRelative)
|
||||
.addv("allowFamily", signupConfig.getBoolean("allowFamily", false))
|
||||
.addv("fillBedInfo", signupConfig.getBoolean("fillBedInfo", true))
|
||||
.addv("directFamilyUnitLine", signupConfig.getBoolean("directFamilyUnitLine", false)));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.schoolUnionApproval")
|
||||
public Result unionOptions(Boolean audit, Integer startYear, Integer endYear, String keyword, String lineId, String travelPeriod, String lineType) {
|
||||
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, null, lineId, travelPeriod, lineType);
|
||||
cnd.and("t.unionId", "IS NOT", null);
|
||||
cnd.and("t.unionId", "<>", "");
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT
|
||||
t.unionId AS id,
|
||||
COALESCE(NULLIF(u.name, ''), t.unionName) AS name,
|
||||
u.unionCode
|
||||
FROM wf_process_task task
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
|
||||
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
|
||||
INNER JOIN tour_ledger t ON t.id = ins.businessNo
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
LEFT JOIN sys_union u ON u.id = t.unionId
|
||||
$condition
|
||||
ORDER BY u.unionCode ASC, name ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.schoolUnionApproval")
|
||||
public Result lineOptions(Boolean audit, Integer startYear, Integer endYear, String travelPeriod, String lineType, String unionId, String keyword) {
|
||||
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, unionId, null, travelPeriod, lineType);
|
||||
cnd.and("t.lineName", "IS NOT", null);
|
||||
cnd.and("t.lineName", "<>", "");
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT
|
||||
CASE
|
||||
WHEN IFNULL(t.lineId, '') <> '' THEN CONCAT(t.lineId, '|', IFNULL(m.unionId, ''))
|
||||
ELSE CONCAT('legacy:', t.lineName, '|', IFNULL(m.unionId, ''))
|
||||
END AS lineId,
|
||||
CASE
|
||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
||||
ELSE CONCAT(
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
||||
'(',
|
||||
CASE
|
||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
||||
ELSE IFNULL(mu.name, '')
|
||||
END,
|
||||
')'
|
||||
)
|
||||
END AS lineName
|
||||
FROM wf_process_task task
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
|
||||
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
|
||||
INNER JOIN tour_ledger t ON t.id = ins.businessNo
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
LEFT JOIN sys_union mu ON mu.id = m.unionId
|
||||
$condition
|
||||
ORDER BY lineName ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.schoolUnionApproval")
|
||||
public Result travelPeriodOptions(Boolean audit, Integer startYear, Integer endYear, String lineId, String lineType, String unionId, String keyword) {
|
||||
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, unionId, lineId, null, lineType);
|
||||
cnd.and("m.travelStartTime", "IS NOT", null);
|
||||
cnd.and("m.travelStartTime", "<>", "");
|
||||
cnd.and("m.travelEndTime", "IS NOT", null);
|
||||
cnd.and("m.travelEndTime", "<>", "");
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime) AS travelPeriod
|
||||
FROM wf_process_task task
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
|
||||
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
|
||||
INNER JOIN tour_ledger t ON t.id = ins.businessNo
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
$condition
|
||||
ORDER BY travelPeriod ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.schoolUnionApproval")
|
||||
public Result lineTypeOptions(Boolean audit, Integer startYear, Integer endYear, String lineId, String travelPeriod, String unionId, String keyword) {
|
||||
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, unionId, lineId, travelPeriod, null);
|
||||
cnd.and("t.lineType", "IS NOT", null);
|
||||
cnd.and("t.lineType", "<>", "");
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT t.lineType
|
||||
FROM wf_process_task task
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
|
||||
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
|
||||
INNER JOIN tour_ledger t ON t.id = ins.businessNo
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
$condition
|
||||
ORDER BY t.lineType ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
private Cnd buildAuditCnd(Boolean audit, Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("t.delFlag", "=", false);
|
||||
cnd.and("def.name", "=", WORKFLOW_KEY);
|
||||
cnd.and("task.displayName", "=", TASK_DISPLAY_NAME);
|
||||
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
|
||||
appendAuditStateFilter(cnd, audit);
|
||||
cnd.andEX("t.`year`", ">=", startYear);
|
||||
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
|
||||
cnd.andEX("t.unionId", "=", unionId);
|
||||
cnd.andEX("t.lineType", "=", lineType);
|
||||
appendLineFilter(cnd, lineId);
|
||||
appendTravelPeriodFilter(cnd, travelPeriod);
|
||||
appendKeywordFilter(cnd, keyword);
|
||||
return cnd;
|
||||
}
|
||||
|
||||
private void appendAuditStateFilter(Cnd cnd, Boolean audit) {
|
||||
if (Boolean.TRUE.equals(audit)) {
|
||||
cnd.and("task.taskState", "in", List.of(
|
||||
ProcessTaskStateEnum.FINISHED.getCode(),
|
||||
ProcessTaskStateEnum.WITHDRAW.getCode(),
|
||||
ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||
return;
|
||||
}
|
||||
cnd.and("task.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
|
||||
}
|
||||
|
||||
private void appendKeywordFilter(Cnd cnd, String keyword) {
|
||||
if (StrUtil.isBlank(keyword)) {
|
||||
return;
|
||||
}
|
||||
SqlExpressionGroup group = new SqlExpressionGroup();
|
||||
group.orLike("t.userName", keyword.trim());
|
||||
group.orLike("t.jobNo", keyword.trim());
|
||||
group.orLike("t.lineName", keyword.trim());
|
||||
group.orLike("l.lineName", keyword.trim());
|
||||
cnd.and(group);
|
||||
}
|
||||
|
||||
private void appendTravelPeriodFilter(Cnd cnd, String travelPeriod) {
|
||||
if (StrUtil.isBlank(travelPeriod)) {
|
||||
return;
|
||||
}
|
||||
cnd.and("CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)", "=", travelPeriod);
|
||||
}
|
||||
|
||||
private void appendLineFilter(Cnd cnd, String lineId) {
|
||||
if (StrUtil.isBlank(lineId)) {
|
||||
return;
|
||||
}
|
||||
LineFilter lineFilter = parseLineFilter(lineId);
|
||||
if (lineFilter.getLineId().startsWith("legacy:")) {
|
||||
SqlExpressionGroup legacyGroup = new SqlExpressionGroup();
|
||||
legacyGroup.or("t.lineId", "IS", null);
|
||||
legacyGroup.or("t.lineId", "=", "");
|
||||
cnd.and(legacyGroup);
|
||||
cnd.and("t.lineName", "=", lineFilter.getLineId().substring("legacy:".length()));
|
||||
} else {
|
||||
cnd.and("t.lineId", "=", lineFilter.getLineId());
|
||||
}
|
||||
appendMatterUnionFilter(cnd, lineFilter.getUnionId());
|
||||
}
|
||||
|
||||
private LineFilter parseLineFilter(String value) {
|
||||
int delimiterIndex = value.indexOf("|");
|
||||
if (delimiterIndex < 0) {
|
||||
return new LineFilter(value, null);
|
||||
}
|
||||
return new LineFilter(value.substring(0, delimiterIndex), value.substring(delimiterIndex + 1));
|
||||
}
|
||||
|
||||
private void appendMatterUnionFilter(Cnd cnd, String unionId) {
|
||||
if (unionId == null) {
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isBlank(unionId)) {
|
||||
SqlExpressionGroup group = new SqlExpressionGroup();
|
||||
group.or("m.unionId", "IS", null);
|
||||
group.or("m.unionId", "=", "");
|
||||
cnd.and(group);
|
||||
return;
|
||||
}
|
||||
cnd.and("m.unionId", "=", unionId);
|
||||
}
|
||||
|
||||
private String getOrderColumn(String orderName) {
|
||||
if ("jobNo".equals(orderName)) {
|
||||
return "t.jobNo";
|
||||
}
|
||||
if ("userName".equals(orderName)) {
|
||||
return "t.userName";
|
||||
}
|
||||
if ("lineName".equals(orderName)) {
|
||||
return """
|
||||
CASE
|
||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
||||
ELSE CONCAT(
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
||||
'(',
|
||||
CASE
|
||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
||||
ELSE IFNULL(mu.name, '')
|
||||
END,
|
||||
')'
|
||||
)
|
||||
END
|
||||
""";
|
||||
}
|
||||
if ("travelPeriod".equals(orderName)) {
|
||||
return "COALESCE(m.travelStartTime, tp.travelPeriod)";
|
||||
}
|
||||
if ("lineType".equals(orderName)) {
|
||||
return "t.lineType";
|
||||
}
|
||||
if ("signupTime".equals(orderName)) {
|
||||
return "t.signupTime";
|
||||
}
|
||||
if ("familyCount".equals(orderName)) {
|
||||
return "familyCount";
|
||||
}
|
||||
if ("instanceState".equals(orderName)) {
|
||||
return "ins.state";
|
||||
}
|
||||
return "task.createdAt";
|
||||
}
|
||||
|
||||
private TourLedger fetchAuditLedger(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return null;
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT COUNT(1)
|
||||
FROM wf_process_task task
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
|
||||
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
|
||||
INNER JOIN tour_ledger t ON t.id = ins.businessNo
|
||||
WHERE t.delFlag = 0
|
||||
AND t.id = @id
|
||||
AND def.name = @workflowKey
|
||||
AND task.displayName = @taskDisplayName
|
||||
AND ta.actorId = @actorId
|
||||
""");
|
||||
sql.setParam("id", id);
|
||||
sql.setParam("workflowKey", WORKFLOW_KEY);
|
||||
sql.setParam("taskDisplayName", TASK_DISPLAY_NAME);
|
||||
sql.setParam("actorId", SecurityUtil.getUserId());
|
||||
sql.setCallback(Sqls.callback.integer());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return sql.getInt() > 0 ? tourLedgerService.fetch(id) : null;
|
||||
}
|
||||
|
||||
private String getCurrentLineName(TourLedger ledger) {
|
||||
if (ledger == null) {
|
||||
return "";
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT CASE
|
||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
||||
ELSE CONCAT(
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
||||
'(',
|
||||
CASE
|
||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
||||
ELSE IFNULL(mu.name, '')
|
||||
END,
|
||||
')'
|
||||
)
|
||||
END AS lineName
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
LEFT JOIN sys_union mu ON mu.id = m.unionId
|
||||
WHERE t.id = @ledgerId
|
||||
""");
|
||||
sql.setParam("ledgerId", ledger.getId());
|
||||
sql.setCallback(Sqls.callback.str());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return StrUtil.blankToDefault(sql.getString(), ledger.getLineName());
|
||||
}
|
||||
|
||||
private String getTravelPeriod(TourLedger ledger) {
|
||||
if (ledger == null || ledger.getYear() == null || StrUtil.isBlank(ledger.getLineId())) {
|
||||
return "";
|
||||
}
|
||||
if (StrUtil.isNotBlank(ledger.getMatterId())) {
|
||||
Sql exactSql = Sqls.create("""
|
||||
SELECT CASE
|
||||
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
|
||||
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
|
||||
ELSE ''
|
||||
END AS travelPeriod
|
||||
FROM tour_matter
|
||||
WHERE delFlag = 0
|
||||
AND id = @matterId
|
||||
""");
|
||||
exactSql.setParam("matterId", ledger.getMatterId());
|
||||
exactSql.setCallback(Sqls.callback.str());
|
||||
tourLedgerService.dao().execute(exactSql);
|
||||
return exactSql.getString();
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT GROUP_CONCAT(
|
||||
DISTINCT CASE
|
||||
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
|
||||
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
|
||||
ELSE NULL
|
||||
END
|
||||
ORDER BY travelStartTime ASC
|
||||
SEPARATOR ';'
|
||||
) AS travelPeriod
|
||||
FROM tour_matter
|
||||
WHERE delFlag = 0
|
||||
AND `year` = @year
|
||||
AND lineId = @lineId
|
||||
""");
|
||||
sql.setParam("year", ledger.getYear());
|
||||
sql.setParam("lineId", ledger.getLineId());
|
||||
sql.setCallback(Sqls.callback.str());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return sql.getString();
|
||||
}
|
||||
|
||||
private NutMap getSignupConfig(TourLedger ledger) {
|
||||
if (ledger == null || StrUtil.isBlank(ledger.getLineId())) {
|
||||
return NutMap.NEW().addv("allowFamily", false).addv("fillBedInfo", true).addv("directFamilyUnitLine", false);
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
IFNULL(s.allowFamily, 0) AS allowFamily,
|
||||
IFNULL(s.fillBedInfo, 1) AS fillBedInfo,
|
||||
IFNULL(l.directFamilyUnitLine, 0) AS directFamilyUnitLine
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_setting s ON s.id = m.settingId AND s.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
WHERE t.id = @ledgerId
|
||||
""");
|
||||
sql.setParam("ledgerId", ledger.getId());
|
||||
sql.setCallback(Sqls.callback.map());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
NutMap map = sql.getObject(NutMap.class);
|
||||
if (map == null || map.isEmpty()) {
|
||||
return NutMap.NEW().addv("allowFamily", false).addv("fillBedInfo", true).addv("directFamilyUnitLine", false);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static class LineFilter {
|
||||
private final String lineId;
|
||||
private final String unionId;
|
||||
|
||||
private LineFilter(String lineId, String unionId) {
|
||||
this.lineId = lineId;
|
||||
this.unionId = unionId;
|
||||
}
|
||||
|
||||
private String getLineId() {
|
||||
return lineId;
|
||||
}
|
||||
|
||||
private String getUnionId() {
|
||||
return unionId;
|
||||
}
|
||||
}
|
||||
}
|
||||
+246
@@ -0,0 +1,246 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
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.zhgh.dayofficework.tour.models.TourSetting;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourSettingLot;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourSettingService;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.json.Json;
|
||||
import org.nutz.lang.Lang;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/platform/tour/setting")
|
||||
public class TourSettingController {
|
||||
|
||||
@Inject
|
||||
private TourSettingService tourSettingService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/Tour/setting/index.html")
|
||||
@SaCheckPermission("tour.setting")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.setting")
|
||||
public Result pageData(PageForm pageForm, Integer year, String configName) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX(TourSetting::getYear, "=", year);
|
||||
cnd.and(Cnd.likeEX(TourSetting::getConfigName, configName));
|
||||
|
||||
cnd.desc(TourSetting::getYear).asc(TourSetting::getSortNo).desc(TourSetting::getCreatedAt);
|
||||
Pagination<TourSetting> pagination = tourSettingService.listPage(
|
||||
pageForm.getPageNumber(),
|
||||
pageForm.getPageSize(),
|
||||
TourSetting.class,
|
||||
cnd
|
||||
);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.setting")
|
||||
public Result detail(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
TourSetting tourSetting = tourSettingService.fetch(id);
|
||||
if (tourSetting == null) {
|
||||
return Result.error("配置不存在");
|
||||
}
|
||||
// 编辑页面需要一起带出标段,按标段值倒序保持与老疗休养配置一致。
|
||||
Cnd lotCnd = Cnd.NEW();
|
||||
lotCnd.desc(TourSettingLot::getLotValue);
|
||||
tourSettingService.dao().fetchLinks(tourSetting, "lots", lotCnd);
|
||||
return Result.success(tourSetting);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.setting")
|
||||
public Result previousYearInfo(Integer year) {
|
||||
if (year == null) {
|
||||
return Result.error("请先选择年度");
|
||||
}
|
||||
List<TourSetting> settings = tourSettingService.query(Cnd.where(TourSetting::getYear, "=", year - 1)
|
||||
.and(TourSetting::getDelFlag, "=", false)
|
||||
.desc(TourSetting::getUpdatedAt)
|
||||
.desc(TourSetting::getCreatedAt));
|
||||
if (Lang.isEmpty(settings)) {
|
||||
return Result.error("未找到上一年度配置");
|
||||
}
|
||||
TourSetting tourSetting = settings.get(0);
|
||||
Cnd lotCnd = Cnd.NEW();
|
||||
lotCnd.desc(TourSettingLot::getLotValue);
|
||||
tourSettingService.dao().fetchLinks(tourSetting, "lots", lotCnd);
|
||||
return Result.success(tourSetting);
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tour.setting")
|
||||
@SLog(type = "tour", tag = "疗休养设置", msg = "保存疗休养配置")
|
||||
public Result doSubmit(TourSetting tourSetting,
|
||||
@Param(value = "lots") String lots,
|
||||
@Param(value = "lotDeleteList") String[] lotDeleteList) {
|
||||
Result checkResult = check(tourSetting);
|
||||
if (checkResult != null) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
Cnd sameNameCnd = Cnd.where(TourSetting::getYear, "=", tourSetting.getYear())
|
||||
.and(TourSetting::getConfigName, "=", tourSetting.getConfigName());
|
||||
if (StrUtil.isNotBlank(tourSetting.getId())) {
|
||||
sameNameCnd.and(TourSetting::getId, "<>", tourSetting.getId());
|
||||
}
|
||||
if (tourSettingService.count(sameNameCnd) > 0) {
|
||||
return Result.error("同年度下配置名称已存在");
|
||||
}
|
||||
|
||||
// 布尔值给默认值,避免前端未传时出现空状态。
|
||||
if (tourSetting.getEnabled() == null) {
|
||||
tourSetting.setEnabled(true);
|
||||
}
|
||||
if (tourSetting.getAllowFamily() == null) {
|
||||
tourSetting.setAllowFamily(false);
|
||||
}
|
||||
if (tourSetting.getFillBedInfo() == null) {
|
||||
tourSetting.setFillBedInfo(true);
|
||||
}
|
||||
if (StrUtil.isBlank(tourSetting.getOutProvinceRatioType())) {
|
||||
tourSetting.setOutProvinceRatioType("当年报名人数");
|
||||
}
|
||||
if (!"固定人数".equals(tourSetting.getOutProvinceRatioType())) {
|
||||
tourSetting.setOutProvinceFixedPeople(0);
|
||||
}
|
||||
// 前端按项目既有约定把子表数组序列化提交,这里显式解析,避免自动绑定漏掉标段。
|
||||
if (StrUtil.isNotBlank(lots)) {
|
||||
try {
|
||||
tourSetting.setLots(Json.fromJsonAsList(TourSettingLot.class, lots));
|
||||
} catch (Exception e) {
|
||||
return Result.error("标段值和标段费用必须为整数");
|
||||
}
|
||||
}
|
||||
tourSetting.setLots(normalizeLots(tourSetting));
|
||||
Result lotCheckResult = checkLots(tourSetting.getLots());
|
||||
if (lotCheckResult != null) {
|
||||
return lotCheckResult;
|
||||
}
|
||||
if (StrUtil.isBlank(tourSetting.getId())) {
|
||||
if (Lang.isEmpty(tourSetting.getLots())) {
|
||||
tourSettingService.insert(tourSetting);
|
||||
} else {
|
||||
tourSettingService.insertWith(tourSetting, "lots");
|
||||
}
|
||||
} else {
|
||||
// 编辑时先处理页面删除的标段,再保存配置和当前标段行。
|
||||
if (Lang.isNotEmpty(lotDeleteList)) {
|
||||
tourSettingService.dao().clear(TourSettingLot.class, Cnd.where(TourSettingLot::getId, "in", lotDeleteList));
|
||||
}
|
||||
tourSettingService.updateIgnoreNull(tourSetting);
|
||||
saveLots(tourSetting);
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tour.setting")
|
||||
@SLog(type = "tour", tag = "疗休养设置", msg = "删除疗休养配置")
|
||||
public Result doDelete(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
tourSettingService.dao().clear(TourSettingLot.class, Cnd.where(TourSettingLot::getSettingId, "=", id));
|
||||
tourSettingService.delete(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
private List<TourSettingLot> normalizeLots(TourSetting tourSetting) {
|
||||
if (tourSetting == null || Lang.isEmpty(tourSetting.getLots())) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 过滤前端空行,避免误写入只有ID或空字段的标段记录。
|
||||
return tourSetting.getLots().stream()
|
||||
.filter(item -> item != null
|
||||
&& (StrUtil.isNotBlank(item.getLotName())
|
||||
|| StrUtil.isNotBlank(item.getLotValue())
|
||||
|| item.getActivityCost() != null))
|
||||
.peek(item -> item.setSettingId(tourSetting.getId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void saveLots(TourSetting tourSetting) {
|
||||
List<TourSettingLot> lots = tourSetting.getLots();
|
||||
if (Lang.isEmpty(lots)) {
|
||||
return;
|
||||
}
|
||||
lots.forEach(item -> item.setSettingId(tourSetting.getId()));
|
||||
tourSettingService.dao().insertOrUpdate(lots);
|
||||
}
|
||||
|
||||
private Result check(TourSetting tourSetting) {
|
||||
if (tourSetting == null) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
if (tourSetting.getYear() == null) {
|
||||
return Result.error("年度不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(tourSetting.getConfigName())) {
|
||||
return Result.error("配置名称不能为空");
|
||||
}
|
||||
if (tourSetting.getMinGroupPeople() != null && tourSetting.getMaxGroupPeople() != null
|
||||
&& tourSetting.getMinGroupPeople() > tourSetting.getMaxGroupPeople()) {
|
||||
return Result.error("最少成团人数不能大于最多成团人数");
|
||||
}
|
||||
if (tourSetting.getCycleStartYear() != null && tourSetting.getCycleEndYear() != null
|
||||
&& tourSetting.getCycleStartYear() > tourSetting.getCycleEndYear()) {
|
||||
return Result.error("周期开始年度不能大于周期结束年度");
|
||||
}
|
||||
if (StrUtil.isNotBlank(tourSetting.getOutProvinceRatioType())
|
||||
&& !"当年参加人数".equals(tourSetting.getOutProvinceRatioType())
|
||||
&& !"当年报名人数".equals(tourSetting.getOutProvinceRatioType())
|
||||
&& !"可参加教职工人数".equals(tourSetting.getOutProvinceRatioType())
|
||||
&& !"固定人数".equals(tourSetting.getOutProvinceRatioType())) {
|
||||
return Result.error("省外人数占比类型不正确");
|
||||
}
|
||||
if ("固定人数".equals(tourSetting.getOutProvinceRatioType())
|
||||
&& (tourSetting.getOutProvinceFixedPeople() == null || tourSetting.getOutProvinceFixedPeople() < 0)) {
|
||||
return Result.error("固定人数必须大于等于0");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Result checkLots(List<TourSettingLot> lots) {
|
||||
if (Lang.isEmpty(lots)) {
|
||||
return null;
|
||||
}
|
||||
for (int i = 0; i < lots.size(); i++) {
|
||||
TourSettingLot lot = lots.get(i);
|
||||
String rowNo = "第" + (i + 1) + "行";
|
||||
if (StrUtil.isBlank(lot.getLotValue()) || !lot.getLotValue().matches("^\\d+$")) {
|
||||
return Result.error(rowNo + "标段值必须为整数");
|
||||
}
|
||||
if (lot.getActivityCost() == null || lot.getActivityCost() < 0) {
|
||||
return Result.error(rowNo + "标段费用必须为整数");
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+1486
File diff suppressed because it is too large
Load Diff
+160
@@ -0,0 +1,160 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
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.zhgh.dayofficework.tour.models.TourTravelAgency;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourTravelAgencyService;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/platform/tour/travelAgency")
|
||||
public class TourTravelAgencyController {
|
||||
|
||||
private static final String MOBILE_PATTERN = "^1[3-9]\\d{9}$";
|
||||
private static final String EMAIL_PATTERN = "^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$";
|
||||
|
||||
@Inject
|
||||
private TourTravelAgencyService travelAgencyService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/Tour/travelAgency/index.html")
|
||||
@SaCheckPermission("tour.travelAgency")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.travelAgency")
|
||||
public Result pageData(PageForm pageForm, Integer year, String agencyName, String contactName, String contactPhone) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX(TourTravelAgency::getYear, "=", year);
|
||||
cnd.and(Cnd.likeEX(TourTravelAgency::getAgencyName, agencyName));
|
||||
cnd.and(Cnd.likeEX(TourTravelAgency::getContactName, contactName));
|
||||
cnd.and(Cnd.likeEX(TourTravelAgency::getContactPhone, contactPhone));
|
||||
applyOrder(cnd, pageForm);
|
||||
Pagination<TourTravelAgency> pagination = travelAgencyService.listPage(
|
||||
pageForm.getPageNumber(),
|
||||
pageForm.getPageSize(),
|
||||
TourTravelAgency.class,
|
||||
cnd
|
||||
);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.travelAgency")
|
||||
public Result detail(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
TourTravelAgency agency = travelAgencyService.fetch(id);
|
||||
return agency == null ? Result.error("旅行社不存在") : Result.success(agency);
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tour.travelAgency")
|
||||
@SLog(type = "tour", tag = "旅行社管理", msg = "保存旅行社信息")
|
||||
public Result doSubmit(TourTravelAgency agency) {
|
||||
Result checkResult = check(agency);
|
||||
if (checkResult != null) {
|
||||
return checkResult;
|
||||
}
|
||||
|
||||
Cnd sameCodeCnd = Cnd.where(TourTravelAgency::getYear, "=", agency.getYear())
|
||||
.and(TourTravelAgency::getAgencyCode, "=", agency.getAgencyCode());
|
||||
if (StrUtil.isNotBlank(agency.getId())) {
|
||||
sameCodeCnd.and(TourTravelAgency::getId, "<>", agency.getId());
|
||||
}
|
||||
if (travelAgencyService.count(sameCodeCnd) > 0) {
|
||||
return Result.error("同年度下旅行社编号已存在");
|
||||
}
|
||||
if (agency.getEnabled() == null) {
|
||||
agency.setEnabled(true);
|
||||
}
|
||||
if (StrUtil.isBlank(agency.getId())) {
|
||||
travelAgencyService.insert(agency);
|
||||
} else {
|
||||
travelAgencyService.updateIgnoreNull(agency);
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tour.travelAgency")
|
||||
@SLog(type = "tour", tag = "旅行社管理", msg = "删除旅行社信息")
|
||||
public Result doDelete(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
travelAgencyService.delete(id);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
private Result check(TourTravelAgency agency) {
|
||||
if (agency == null) {
|
||||
return Result.error("参数错误");
|
||||
}
|
||||
if (agency.getYear() == null) {
|
||||
return Result.error("年度不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(agency.getAgencyName())) {
|
||||
return Result.error("旅行社名称不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(agency.getAgencyCode())) {
|
||||
return Result.error("旅行社编号不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(agency.getContactName())) {
|
||||
return Result.error("联系人不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(agency.getContactPhone())) {
|
||||
return Result.error("联系人手机不能为空");
|
||||
}
|
||||
if (!agency.getContactPhone().matches(MOBILE_PATTERN)) {
|
||||
return Result.error("联系人手机格式不正确");
|
||||
}
|
||||
if (StrUtil.isBlank(agency.getEmail())) {
|
||||
return Result.error("邮箱不能为空");
|
||||
}
|
||||
if (!agency.getEmail().matches(EMAIL_PATTERN)) {
|
||||
return Result.error("邮箱格式不正确");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void applyOrder(Cnd cnd, PageForm pageForm) {
|
||||
String orderName = pageForm.getPageOrderName();
|
||||
String orderBy = pageForm.getPageOrderBy();
|
||||
if (StrUtil.isBlank(orderName)) {
|
||||
cnd.asc(TourTravelAgency::getYear).asc(TourTravelAgency::getAgencyCode).desc(TourTravelAgency::getCreatedAt);
|
||||
return;
|
||||
}
|
||||
boolean descending = "descending".equals(orderBy);
|
||||
if ("year".equals(orderName)) {
|
||||
if (descending) {
|
||||
cnd.desc(TourTravelAgency::getYear);
|
||||
} else {
|
||||
cnd.asc(TourTravelAgency::getYear);
|
||||
}
|
||||
} else if ("agencyCode".equals(orderName)) {
|
||||
if (descending) {
|
||||
cnd.desc(TourTravelAgency::getAgencyCode);
|
||||
} else {
|
||||
cnd.asc(TourTravelAgency::getAgencyCode);
|
||||
}
|
||||
} else {
|
||||
cnd.asc(TourTravelAgency::getYear).asc(TourTravelAgency::getAgencyCode).desc(TourTravelAgency::getCreatedAt);
|
||||
}
|
||||
}
|
||||
}
|
||||
+580
@@ -0,0 +1,580 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
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.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerDirectRelative;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerFamily;
|
||||
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;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/platform/tour/unionApproval")
|
||||
public class TourUnionApprovalController {
|
||||
|
||||
private static final String WORKFLOW_KEY = "LXYBZXQSXL";
|
||||
private static final String TASK_DISPLAY_NAME = "分工会审核";
|
||||
|
||||
@Inject
|
||||
private TourLedgerService tourLedgerService;
|
||||
|
||||
@Inject
|
||||
private TourLedgerFamilyService tourLedgerFamilyService;
|
||||
|
||||
@Inject
|
||||
private TourLedgerDirectRelativeService tourLedgerDirectRelativeService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/Tour/unionApproval/index.html")
|
||||
@SaCheckPermission("tour.unionApproval")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/h5")
|
||||
@Ok("beetl:/platform/zhghh5/dayofficework/tour/unionApproval/index.html")
|
||||
@SaCheckPermission("tour.unionApproval")
|
||||
public void h5() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.unionApproval")
|
||||
public Result pageData(PageForm pageForm, Boolean audit, Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType) {
|
||||
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType);
|
||||
|
||||
Sql countSql = Sqls.create("""
|
||||
SELECT COUNT(DISTINCT task.id)
|
||||
FROM wf_process_task task
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
|
||||
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
|
||||
INNER JOIN tour_ledger t ON t.id = ins.businessNo
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
$condition
|
||||
""");
|
||||
countSql.setCondition(cnd);
|
||||
countSql.setCallback(Sqls.callback.integer());
|
||||
tourLedgerService.dao().execute(countSql);
|
||||
int count = countSql.getInt();
|
||||
|
||||
Sql listSql = Sqls.create("""
|
||||
SELECT
|
||||
t.*,
|
||||
ins.id AS instanceId,
|
||||
ins.businessNo,
|
||||
ins.state AS instanceState,
|
||||
ins.variable AS instanceVariable,
|
||||
ins.processDefineId AS instanceProcessDefineId,
|
||||
task.id AS taskId,
|
||||
task.taskName AS taskKey,
|
||||
task.displayName AS taskName,
|
||||
task.taskType,
|
||||
task.performType AS taskPerformType,
|
||||
task.taskState,
|
||||
task.finishTime,
|
||||
task.taskParentId,
|
||||
task.variable AS taskVariable,
|
||||
IFNULL(GROUP_CONCAT(DISTINCT nt.displayName), '结束') AS curTaskName,
|
||||
CASE
|
||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
||||
ELSE CONCAT(
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
||||
'(',
|
||||
CASE
|
||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
||||
ELSE IFNULL(mu.name, '')
|
||||
END,
|
||||
')'
|
||||
)
|
||||
END AS currentLineName,
|
||||
CASE
|
||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||
ELSE tp.travelPeriod
|
||||
END AS travelPeriod,
|
||||
IFNULL(f.familyCount, 0) AS familyCount
|
||||
FROM wf_process_task task
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
|
||||
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
|
||||
INNER JOIN tour_ledger t ON t.id = ins.businessNo
|
||||
LEFT JOIN wf_process_task nt ON nt.processInstanceId = ins.id AND nt.taskState = 10
|
||||
LEFT JOIN (
|
||||
SELECT ledgerId, COUNT(1) AS familyCount
|
||||
FROM tour_ledger_family
|
||||
WHERE delFlag = 0
|
||||
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_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
LEFT JOIN sys_union mu ON mu.id = m.unionId
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
`year`,
|
||||
lineId,
|
||||
GROUP_CONCAT(
|
||||
DISTINCT CASE
|
||||
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
|
||||
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
|
||||
ELSE NULL
|
||||
END
|
||||
ORDER BY travelStartTime ASC
|
||||
SEPARATOR ';'
|
||||
) AS travelPeriod
|
||||
FROM tour_matter
|
||||
WHERE delFlag = 0
|
||||
AND lineId IS NOT NULL
|
||||
AND lineId <> ''
|
||||
GROUP BY `year`, lineId
|
||||
) tp ON (t.matterId IS NULL OR t.matterId = '') AND tp.`year` = t.`year` AND tp.lineId = t.lineId
|
||||
$condition
|
||||
GROUP BY task.id
|
||||
ORDER BY $orderColumn $orderBy, task.createdAt DESC, t.signupTime DESC, t.createdAt DESC
|
||||
""");
|
||||
listSql.setCondition(cnd);
|
||||
listSql.setVar("orderColumn", getOrderColumn(pageForm.getPageOrderName()));
|
||||
listSql.setVar("orderBy", "ascending".equals(pageForm.getPageOrderBy()) ? "asc" : "desc");
|
||||
listSql.setPager(tourLedgerService.dao().createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||
listSql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(listSql);
|
||||
|
||||
List<NutMap> list = listSql.getList(NutMap.class);
|
||||
list.forEach(item -> item.put("lineName", item.getString("currentLineName")));
|
||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.unionApproval")
|
||||
public Result detail(String id) {
|
||||
TourLedger ledger = fetchAuditLedger(id);
|
||||
if (ledger == null) {
|
||||
return Result.error("报名记录不存在或无权查看");
|
||||
}
|
||||
ledger.setLineName(getCurrentLineName(ledger));
|
||||
Cnd familyCnd = Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId())
|
||||
.and(TourLedgerFamily::getDelFlag, "=", false);
|
||||
familyCnd.asc(TourLedgerFamily::getCreatedAt);
|
||||
TourLedgerDirectRelative directRelative = tourLedgerDirectRelativeService.fetch(
|
||||
Cnd.where(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId())
|
||||
.and(TourLedgerDirectRelative::getDelFlag, "=", false));
|
||||
NutMap signupConfig = getSignupConfig(ledger);
|
||||
return Result.success(NutMap.NEW()
|
||||
.addv("ledger", ledger)
|
||||
.addv("travelPeriod", getTravelPeriod(ledger))
|
||||
.addv("families", tourLedgerFamilyService.query(familyCnd))
|
||||
.addv("directRelative", directRelative)
|
||||
.addv("allowFamily", signupConfig.getBoolean("allowFamily", false))
|
||||
.addv("fillBedInfo", signupConfig.getBoolean("fillBedInfo", true))
|
||||
.addv("directFamilyUnitLine", signupConfig.getBoolean("directFamilyUnitLine", false)));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.unionApproval")
|
||||
public Result unionOptions(Boolean audit, Integer startYear, Integer endYear, String keyword, String lineId, String travelPeriod, String lineType) {
|
||||
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, null, lineId, travelPeriod, lineType);
|
||||
cnd.and("t.unionId", "IS NOT", null);
|
||||
cnd.and("t.unionId", "<>", "");
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT
|
||||
t.unionId AS id,
|
||||
COALESCE(NULLIF(u.name, ''), t.unionName) AS name,
|
||||
u.unionCode
|
||||
FROM wf_process_task task
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
|
||||
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
|
||||
INNER JOIN tour_ledger t ON t.id = ins.businessNo
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
LEFT JOIN sys_union u ON u.id = t.unionId
|
||||
$condition
|
||||
ORDER BY u.unionCode ASC, name ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.unionApproval")
|
||||
public Result lineOptions(Boolean audit, Integer startYear, Integer endYear, String travelPeriod, String lineType, String unionId, String keyword) {
|
||||
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, unionId, null, travelPeriod, lineType);
|
||||
cnd.and("t.lineName", "IS NOT", null);
|
||||
cnd.and("t.lineName", "<>", "");
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT
|
||||
CASE
|
||||
WHEN IFNULL(t.lineId, '') <> '' THEN CONCAT(t.lineId, '|', IFNULL(m.unionId, ''))
|
||||
ELSE CONCAT('legacy:', t.lineName, '|', IFNULL(m.unionId, ''))
|
||||
END AS lineId,
|
||||
CASE
|
||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
||||
ELSE CONCAT(
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
||||
'(',
|
||||
CASE
|
||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
||||
ELSE IFNULL(mu.name, '')
|
||||
END,
|
||||
')'
|
||||
)
|
||||
END AS lineName
|
||||
FROM wf_process_task task
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
|
||||
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
|
||||
INNER JOIN tour_ledger t ON t.id = ins.businessNo
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
LEFT JOIN sys_union mu ON mu.id = m.unionId
|
||||
$condition
|
||||
ORDER BY lineName ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.unionApproval")
|
||||
public Result travelPeriodOptions(Boolean audit, Integer startYear, Integer endYear, String lineId, String lineType, String unionId, String keyword) {
|
||||
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, unionId, lineId, null, lineType);
|
||||
cnd.and("m.travelStartTime", "IS NOT", null);
|
||||
cnd.and("m.travelStartTime", "<>", "");
|
||||
cnd.and("m.travelEndTime", "IS NOT", null);
|
||||
cnd.and("m.travelEndTime", "<>", "");
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime) AS travelPeriod
|
||||
FROM wf_process_task task
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
|
||||
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
|
||||
INNER JOIN tour_ledger t ON t.id = ins.businessNo
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
$condition
|
||||
ORDER BY travelPeriod ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.unionApproval")
|
||||
public Result lineTypeOptions(Boolean audit, Integer startYear, Integer endYear, String lineId, String travelPeriod, String unionId, String keyword) {
|
||||
Cnd cnd = buildAuditCnd(audit, startYear, endYear, keyword, unionId, lineId, travelPeriod, null);
|
||||
cnd.and("t.lineType", "IS NOT", null);
|
||||
cnd.and("t.lineType", "<>", "");
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT t.lineType
|
||||
FROM wf_process_task task
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
|
||||
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
|
||||
INNER JOIN tour_ledger t ON t.id = ins.businessNo
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
$condition
|
||||
ORDER BY t.lineType ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
private Cnd buildAuditCnd(Boolean audit, Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("t.delFlag", "=", false);
|
||||
cnd.and("def.name", "=", WORKFLOW_KEY);
|
||||
cnd.and("task.displayName", "=", TASK_DISPLAY_NAME);
|
||||
cnd.and("ta.actorId", "in", List.of(SecurityUtil.getUserId()));
|
||||
appendAuditStateFilter(cnd, audit);
|
||||
cnd.andEX("t.`year`", ">=", startYear);
|
||||
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
|
||||
cnd.andEX("t.unionId", "=", unionId);
|
||||
cnd.andEX("t.lineType", "=", lineType);
|
||||
appendLineFilter(cnd, lineId);
|
||||
appendTravelPeriodFilter(cnd, travelPeriod);
|
||||
appendKeywordFilter(cnd, keyword);
|
||||
return cnd;
|
||||
}
|
||||
|
||||
private void appendAuditStateFilter(Cnd cnd, Boolean audit) {
|
||||
if (Boolean.TRUE.equals(audit)) {
|
||||
cnd.and("task.taskState", "in", List.of(
|
||||
ProcessTaskStateEnum.FINISHED.getCode(),
|
||||
ProcessTaskStateEnum.WITHDRAW.getCode(),
|
||||
ProcessTaskStateEnum.INTERRUPT.getCode()));
|
||||
return;
|
||||
}
|
||||
cnd.and("task.taskState", "=", ProcessTaskStateEnum.DOING.getCode());
|
||||
}
|
||||
|
||||
private void appendKeywordFilter(Cnd cnd, String keyword) {
|
||||
if (StrUtil.isBlank(keyword)) {
|
||||
return;
|
||||
}
|
||||
SqlExpressionGroup group = new SqlExpressionGroup();
|
||||
group.orLike("t.userName", keyword.trim());
|
||||
group.orLike("t.jobNo", keyword.trim());
|
||||
group.orLike("t.lineName", keyword.trim());
|
||||
group.orLike("l.lineName", keyword.trim());
|
||||
cnd.and(group);
|
||||
}
|
||||
|
||||
private void appendTravelPeriodFilter(Cnd cnd, String travelPeriod) {
|
||||
if (StrUtil.isBlank(travelPeriod)) {
|
||||
return;
|
||||
}
|
||||
cnd.and("CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)", "=", travelPeriod);
|
||||
}
|
||||
|
||||
private void appendLineFilter(Cnd cnd, String lineId) {
|
||||
if (StrUtil.isBlank(lineId)) {
|
||||
return;
|
||||
}
|
||||
LineFilter lineFilter = parseLineFilter(lineId);
|
||||
if (lineFilter.getLineId().startsWith("legacy:")) {
|
||||
SqlExpressionGroup legacyGroup = new SqlExpressionGroup();
|
||||
legacyGroup.or("t.lineId", "IS", null);
|
||||
legacyGroup.or("t.lineId", "=", "");
|
||||
cnd.and(legacyGroup);
|
||||
cnd.and("t.lineName", "=", lineFilter.getLineId().substring("legacy:".length()));
|
||||
} else {
|
||||
cnd.and("t.lineId", "=", lineFilter.getLineId());
|
||||
}
|
||||
appendMatterUnionFilter(cnd, lineFilter.getUnionId());
|
||||
}
|
||||
|
||||
private LineFilter parseLineFilter(String value) {
|
||||
int delimiterIndex = value.indexOf("|");
|
||||
if (delimiterIndex < 0) {
|
||||
return new LineFilter(value, null);
|
||||
}
|
||||
return new LineFilter(value.substring(0, delimiterIndex), value.substring(delimiterIndex + 1));
|
||||
}
|
||||
|
||||
private void appendMatterUnionFilter(Cnd cnd, String unionId) {
|
||||
if (unionId == null) {
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isBlank(unionId)) {
|
||||
SqlExpressionGroup group = new SqlExpressionGroup();
|
||||
group.or("m.unionId", "IS", null);
|
||||
group.or("m.unionId", "=", "");
|
||||
cnd.and(group);
|
||||
return;
|
||||
}
|
||||
cnd.and("m.unionId", "=", unionId);
|
||||
}
|
||||
|
||||
private String getOrderColumn(String orderName) {
|
||||
if ("jobNo".equals(orderName)) {
|
||||
return "t.jobNo";
|
||||
}
|
||||
if ("userName".equals(orderName)) {
|
||||
return "t.userName";
|
||||
}
|
||||
if ("lineName".equals(orderName)) {
|
||||
return """
|
||||
CASE
|
||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
||||
ELSE CONCAT(
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
||||
'(',
|
||||
CASE
|
||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
||||
ELSE IFNULL(mu.name, '')
|
||||
END,
|
||||
')'
|
||||
)
|
||||
END
|
||||
""";
|
||||
}
|
||||
if ("travelPeriod".equals(orderName)) {
|
||||
return "COALESCE(m.travelStartTime, tp.travelPeriod)";
|
||||
}
|
||||
if ("lineType".equals(orderName)) {
|
||||
return "t.lineType";
|
||||
}
|
||||
if ("signupTime".equals(orderName)) {
|
||||
return "t.signupTime";
|
||||
}
|
||||
if ("familyCount".equals(orderName)) {
|
||||
return "familyCount";
|
||||
}
|
||||
if ("instanceState".equals(orderName)) {
|
||||
return "ins.state";
|
||||
}
|
||||
return "task.createdAt";
|
||||
}
|
||||
|
||||
private TourLedger fetchAuditLedger(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return null;
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT COUNT(1)
|
||||
FROM wf_process_task task
|
||||
LEFT JOIN wf_process_instance ins ON ins.id = task.processInstanceId
|
||||
LEFT JOIN wf_process_define def ON def.id = ins.processDefineId
|
||||
LEFT JOIN wf_process_task_actor ta ON ta.processTaskId = task.id
|
||||
INNER JOIN tour_ledger t ON t.id = ins.businessNo
|
||||
WHERE t.delFlag = 0
|
||||
AND t.id = @id
|
||||
AND def.name = @workflowKey
|
||||
AND task.displayName = @taskDisplayName
|
||||
AND ta.actorId = @actorId
|
||||
""");
|
||||
sql.setParam("id", id);
|
||||
sql.setParam("workflowKey", WORKFLOW_KEY);
|
||||
sql.setParam("taskDisplayName", TASK_DISPLAY_NAME);
|
||||
sql.setParam("actorId", SecurityUtil.getUserId());
|
||||
sql.setCallback(Sqls.callback.integer());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return sql.getInt() > 0 ? tourLedgerService.fetch(id) : null;
|
||||
}
|
||||
|
||||
private String getCurrentLineName(TourLedger ledger) {
|
||||
if (ledger == null) {
|
||||
return "";
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT CASE
|
||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
||||
ELSE CONCAT(
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
||||
'(',
|
||||
CASE
|
||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
||||
ELSE IFNULL(mu.name, '')
|
||||
END,
|
||||
')'
|
||||
)
|
||||
END AS lineName
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
LEFT JOIN sys_union mu ON mu.id = m.unionId
|
||||
WHERE t.id = @ledgerId
|
||||
""");
|
||||
sql.setParam("ledgerId", ledger.getId());
|
||||
sql.setCallback(Sqls.callback.str());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return StrUtil.blankToDefault(sql.getString(), ledger.getLineName());
|
||||
}
|
||||
|
||||
private String getTravelPeriod(TourLedger ledger) {
|
||||
if (ledger == null || ledger.getYear() == null || StrUtil.isBlank(ledger.getLineId())) {
|
||||
return "";
|
||||
}
|
||||
if (StrUtil.isNotBlank(ledger.getMatterId())) {
|
||||
Sql exactSql = Sqls.create("""
|
||||
SELECT CASE
|
||||
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
|
||||
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
|
||||
ELSE ''
|
||||
END AS travelPeriod
|
||||
FROM tour_matter
|
||||
WHERE delFlag = 0
|
||||
AND id = @matterId
|
||||
""");
|
||||
exactSql.setParam("matterId", ledger.getMatterId());
|
||||
exactSql.setCallback(Sqls.callback.str());
|
||||
tourLedgerService.dao().execute(exactSql);
|
||||
return exactSql.getString();
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT GROUP_CONCAT(
|
||||
DISTINCT CASE
|
||||
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
|
||||
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
|
||||
ELSE NULL
|
||||
END
|
||||
ORDER BY travelStartTime ASC
|
||||
SEPARATOR ';'
|
||||
) AS travelPeriod
|
||||
FROM tour_matter
|
||||
WHERE delFlag = 0
|
||||
AND `year` = @year
|
||||
AND lineId = @lineId
|
||||
""");
|
||||
sql.setParam("year", ledger.getYear());
|
||||
sql.setParam("lineId", ledger.getLineId());
|
||||
sql.setCallback(Sqls.callback.str());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return sql.getString();
|
||||
}
|
||||
|
||||
private NutMap getSignupConfig(TourLedger ledger) {
|
||||
if (ledger == null || StrUtil.isBlank(ledger.getLineId())) {
|
||||
return NutMap.NEW().addv("allowFamily", false).addv("fillBedInfo", true).addv("directFamilyUnitLine", false);
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
IFNULL(s.allowFamily, 0) AS allowFamily,
|
||||
IFNULL(s.fillBedInfo, 1) AS fillBedInfo,
|
||||
IFNULL(l.directFamilyUnitLine, 0) AS directFamilyUnitLine
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_setting s ON s.id = m.settingId AND s.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
WHERE t.id = @ledgerId
|
||||
""");
|
||||
sql.setParam("ledgerId", ledger.getId());
|
||||
sql.setCallback(Sqls.callback.map());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
NutMap map = sql.getObject(NutMap.class);
|
||||
if (map == null || map.isEmpty()) {
|
||||
return NutMap.NEW().addv("allowFamily", false).addv("fillBedInfo", true).addv("directFamilyUnitLine", false);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private static class LineFilter {
|
||||
private final String lineId;
|
||||
private final String unionId;
|
||||
|
||||
private LineFilter(String lineId, String unionId) {
|
||||
this.lineId = lineId;
|
||||
this.unionId = unionId;
|
||||
}
|
||||
|
||||
private String getLineId() {
|
||||
return lineId;
|
||||
}
|
||||
|
||||
private String getUnionId() {
|
||||
return unionId;
|
||||
}
|
||||
}
|
||||
}
|
||||
+905
@@ -0,0 +1,905 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
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.base.utils.CommonDownloadUtil;
|
||||
import com.budwk.app.sys.models.Sys_union;
|
||||
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;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerFamily;
|
||||
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;
|
||||
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
|
||||
import org.apache.poi.ss.usermodel.BorderStyle;
|
||||
import org.apache.poi.ss.usermodel.Cell;
|
||||
import org.apache.poi.ss.usermodel.CellStyle;
|
||||
import org.apache.poi.ss.usermodel.Font;
|
||||
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||
import org.apache.poi.ss.usermodel.Row;
|
||||
import org.apache.poi.ss.usermodel.Sheet;
|
||||
import org.apache.poi.ss.usermodel.VerticalAlignment;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
import org.apache.poi.ss.util.CellRangeAddress;
|
||||
import org.nutz.aop.interceptor.ioc.TransAop;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.List;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@At("/platform/tour/unionledger")
|
||||
public class TourUnionLedgerController {
|
||||
|
||||
@Inject
|
||||
private TourLedgerService tourLedgerService;
|
||||
|
||||
@Inject
|
||||
private TourLedgerFamilyService tourLedgerFamilyService;
|
||||
|
||||
@Inject
|
||||
private TourLedgerDirectRelativeService tourLedgerDirectRelativeService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/dayofficework/Tour/unionledger/index.html")
|
||||
@SaCheckPermission("tour.unionledger")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.unionledger")
|
||||
public Result pageData(PageForm pageForm, Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType, String scopeType, Boolean overCostOnly) {
|
||||
String currentUnionId = currentUnionId();
|
||||
if (StrUtil.isBlank(currentUnionId)) {
|
||||
return Result.error("当前登录人所属工会不能为空");
|
||||
}
|
||||
Cnd cnd = buildQueryCnd(currentUnionId, startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType, scopeType, overCostOnly);
|
||||
|
||||
Sql countSql = Sqls.create("""
|
||||
SELECT COUNT(DISTINCT t.id)
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id
|
||||
LEFT JOIN wf_process_task task ON task.processInstanceId = ins.id AND task.taskState = 10
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
$condition
|
||||
""");
|
||||
countSql.setCondition(cnd);
|
||||
countSql.setCallback(Sqls.callback.integer());
|
||||
tourLedgerService.dao().execute(countSql);
|
||||
int count = countSql.getInt();
|
||||
|
||||
Sql listSql = Sqls.create("""
|
||||
SELECT
|
||||
t.*,
|
||||
ins.id AS instanceId,
|
||||
ins.state AS instanceState,
|
||||
ins.processDefineId AS instanceProcessDefineId,
|
||||
IFNULL(l.directFamilyUnitLine, 0) AS directFamilyUnitLine,
|
||||
IFNULL(GROUP_CONCAT(DISTINCT task.displayName), IF(ins.id IS NULL, '', '结束')) AS curTaskName,
|
||||
CASE
|
||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
||||
ELSE CONCAT(
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
||||
'(',
|
||||
CASE
|
||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
||||
ELSE IFNULL(mu.name, '')
|
||||
END,
|
||||
')'
|
||||
)
|
||||
END AS currentLineName,
|
||||
IFNULL(f.familyCount, 0) AS familyCount,
|
||||
CASE
|
||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||
ELSE tp.travelPeriod
|
||||
END AS travelPeriod
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN wf_process_instance ins ON ins.businessNo = t.id
|
||||
LEFT JOIN wf_process_task task ON task.processInstanceId = ins.id AND task.taskState = 10
|
||||
LEFT JOIN (
|
||||
SELECT ledgerId, COUNT(1) AS familyCount
|
||||
FROM tour_ledger_family
|
||||
WHERE delFlag = 0
|
||||
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_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
LEFT JOIN sys_union mu ON mu.id = m.unionId
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
`year`,
|
||||
lineId,
|
||||
GROUP_CONCAT(
|
||||
DISTINCT CASE
|
||||
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
|
||||
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
|
||||
ELSE NULL
|
||||
END
|
||||
ORDER BY travelStartTime ASC
|
||||
SEPARATOR ';'
|
||||
) AS travelPeriod
|
||||
FROM tour_matter
|
||||
WHERE delFlag = 0
|
||||
AND lineId IS NOT NULL
|
||||
AND lineId <> ''
|
||||
GROUP BY `year`, lineId
|
||||
) tp ON (t.matterId IS NULL OR t.matterId = '') AND tp.`year` = t.`year` AND tp.lineId = t.lineId
|
||||
$condition
|
||||
GROUP BY t.id
|
||||
ORDER BY $orderColumn $orderBy, t.`year` DESC, t.signupTime DESC, t.createdAt DESC
|
||||
""");
|
||||
listSql.setCondition(cnd);
|
||||
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);
|
||||
|
||||
var list = listSql.getList(NutMap.class);
|
||||
list.forEach(item -> item.put("lineName", item.getString("currentLineName")));
|
||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, list);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@SaCheckPermission("tour.unionledger")
|
||||
public void exportOverCostSummary(PageForm pageForm, Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType, String scopeType, HttpServletResponse response) {
|
||||
String currentUnionId = currentUnionId();
|
||||
if (StrUtil.isBlank(currentUnionId)) {
|
||||
return;
|
||||
}
|
||||
Cnd cnd = buildQueryCnd(currentUnionId, startYear, endYear, keyword, unionId, lineId, travelPeriod, lineType, scopeType, true);
|
||||
List<NutMap> list = queryOverCostSummaryList(cnd, pageForm);
|
||||
Workbook workbook = buildOverCostSummaryWorkbook(currentUnionName(), list);
|
||||
CommonDownloadUtil.download("5天外超出部分疗休养费用由单位承担申请人员汇总表.xls", workbook, response);
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.unionledger")
|
||||
public Result detail(String id) {
|
||||
TourLedger ledger = fetchScopedLedger(id);
|
||||
if (ledger == null) {
|
||||
return Result.error("台账记录不存在或无权查看");
|
||||
}
|
||||
ledger.setLineName(getCurrentLineName(ledger));
|
||||
Cnd familyCnd = Cnd.where(TourLedgerFamily::getLedgerId, "=", id)
|
||||
.and(TourLedgerFamily::getDelFlag, "=", false);
|
||||
familyCnd.asc(TourLedgerFamily::getCreatedAt);
|
||||
TourLedgerDirectRelative directRelative = tourLedgerDirectRelativeService.fetch(
|
||||
Cnd.where(TourLedgerDirectRelative::getDelFlag, "=", false)
|
||||
.and(TourLedgerDirectRelative::getLedgerId, "=", ledger.getId()));
|
||||
return Result.success(NutMap.NEW()
|
||||
.addv("ledger", ledger)
|
||||
.addv("travelPeriod", getTravelPeriod(ledger))
|
||||
.addv("families", tourLedgerFamilyService.query(familyCnd))
|
||||
.addv("directRelative", directRelative)
|
||||
.addv("fillBedInfo", isFillBedInfo(ledger))
|
||||
.addv("directFamilyUnitLine", isDirectFamilyUnitLine(ledger)));
|
||||
}
|
||||
|
||||
@At
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
@SaCheckPermission("tour.unionledger")
|
||||
@SLog(type = "tour", tag = "分工会疗休养台账", msg = "删除分工会疗休养台账")
|
||||
public Result doDelete(String id) {
|
||||
TourLedger ledger = fetchScopedLedger(id);
|
||||
if (ledger == null) {
|
||||
return Result.error("台账记录不存在或无权删除");
|
||||
}
|
||||
tourLedgerFamilyService.clear(Cnd.where(TourLedgerFamily::getLedgerId, "=", ledger.getId()));
|
||||
tourLedgerService.clear(Cnd.where(TourLedger::getId, "=", ledger.getId()));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.unionledger")
|
||||
public Result unionOptions(Integer startYear, Integer endYear) {
|
||||
String currentUnionId = currentUnionId();
|
||||
if (StrUtil.isBlank(currentUnionId)) {
|
||||
return Result.error("当前登录人所属工会不能为空");
|
||||
}
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("t.delFlag", "=", false);
|
||||
cnd.andEX("t.`year`", ">=", startYear);
|
||||
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
|
||||
cnd.and("t.unionId", "IS NOT", null);
|
||||
cnd.and("t.unionId", "<>", "");
|
||||
applyUnionLedgerScope(cnd, currentUnionId);
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT
|
||||
t.unionId AS id,
|
||||
COALESCE(NULLIF(u.name, ''), t.unionName) AS name,
|
||||
u.unionCode
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN sys_union u ON u.id = t.unionId
|
||||
$condition
|
||||
ORDER BY
|
||||
CASE WHEN t.unionId = @currentUnionId THEN 0 ELSE 1 END,
|
||||
u.unionCode ASC,
|
||||
name ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setParam("currentUnionId", currentUnionId);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.unionledger")
|
||||
public Result lineOptions(Integer startYear, Integer endYear, String travelPeriod, String lineType, String unionId, String keyword, String scopeType) {
|
||||
String currentUnionId = currentUnionId();
|
||||
if (StrUtil.isBlank(currentUnionId)) {
|
||||
return Result.error("当前登录人所属工会不能为空");
|
||||
}
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("t.delFlag", "=", false);
|
||||
cnd.andEX("t.`year`", ">=", startYear);
|
||||
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
|
||||
cnd.and("t.lineName", "IS NOT", null);
|
||||
cnd.and("t.lineName", "<>", "");
|
||||
cnd.andEX("t.unionId", "=", unionId);
|
||||
cnd.andEX("t.lineType", "=", lineType);
|
||||
appendTravelPeriodFilter(cnd, travelPeriod);
|
||||
appendScopeFilter(cnd, currentUnionId, scopeType);
|
||||
appendKeywordFilter(cnd, keyword);
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT
|
||||
CASE
|
||||
WHEN IFNULL(t.lineId, '') <> '' THEN CONCAT(t.lineId, '|', IFNULL(m.unionId, ''))
|
||||
ELSE CONCAT('legacy:', t.lineName, '|', IFNULL(m.unionId, ''))
|
||||
END AS lineId,
|
||||
CASE
|
||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
||||
ELSE CONCAT(
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
||||
'(',
|
||||
CASE
|
||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
||||
ELSE IFNULL(mu.name, '')
|
||||
END,
|
||||
')'
|
||||
)
|
||||
END AS lineName
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
LEFT JOIN sys_union mu ON mu.id = m.unionId
|
||||
$condition
|
||||
ORDER BY lineName ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.unionledger")
|
||||
public Result travelPeriodOptions(Integer startYear, Integer endYear, String lineId, String lineType, String unionId, String keyword, String scopeType) {
|
||||
String currentUnionId = currentUnionId();
|
||||
if (StrUtil.isBlank(currentUnionId)) {
|
||||
return Result.error("当前登录人所属工会不能为空");
|
||||
}
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("t.delFlag", "=", false);
|
||||
cnd.andEX("t.`year`", ">=", startYear);
|
||||
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
|
||||
cnd.and("m.travelStartTime", "IS NOT", null);
|
||||
cnd.and("m.travelStartTime", "<>", "");
|
||||
cnd.and("m.travelEndTime", "IS NOT", null);
|
||||
cnd.and("m.travelEndTime", "<>", "");
|
||||
cnd.andEX("t.unionId", "=", unionId);
|
||||
cnd.andEX("t.lineType", "=", lineType);
|
||||
appendLineFilter(cnd, lineId);
|
||||
appendScopeFilter(cnd, currentUnionId, scopeType);
|
||||
appendKeywordFilter(cnd, keyword);
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime) AS travelPeriod
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
$condition
|
||||
ORDER BY travelPeriod ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.unionledger")
|
||||
public Result lineTypeOptions(Integer startYear, Integer endYear, String lineId, String travelPeriod, String unionId, String keyword, String scopeType) {
|
||||
String currentUnionId = currentUnionId();
|
||||
if (StrUtil.isBlank(currentUnionId)) {
|
||||
return Result.error("当前登录人所属工会不能为空");
|
||||
}
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("t.delFlag", "=", false);
|
||||
cnd.andEX("t.`year`", ">=", startYear);
|
||||
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
|
||||
cnd.and("t.lineType", "IS NOT", null);
|
||||
cnd.and("t.lineType", "<>", "");
|
||||
cnd.andEX("t.unionId", "=", unionId);
|
||||
appendLineFilter(cnd, lineId);
|
||||
appendTravelPeriodFilter(cnd, travelPeriod);
|
||||
appendScopeFilter(cnd, currentUnionId, scopeType);
|
||||
appendKeywordFilter(cnd, keyword);
|
||||
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT DISTINCT t.lineType
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
$condition
|
||||
ORDER BY t.lineType ASC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return Result.success(sql.getList(NutMap.class));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tour.unionledger")
|
||||
public Result summaryStats(Integer startYear, Integer endYear, String unionId, String lineId, String travelPeriod, String lineType) {
|
||||
String currentUnionId = currentUnionId();
|
||||
if (StrUtil.isBlank(currentUnionId)) {
|
||||
return Result.error("当前登录人所属工会不能为空");
|
||||
}
|
||||
Cnd ownCnd = buildSummaryCnd(currentUnionId, startYear, endYear, unionId, lineId, travelPeriod, lineType, "ownUnionJoined");
|
||||
Cnd organizedCnd = buildSummaryCnd(currentUnionId, startYear, endYear, unionId, lineId, travelPeriod, lineType, "organizedLineJoined");
|
||||
|
||||
Sql ownSql = Sqls.create("""
|
||||
SELECT COUNT(1)
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
$condition
|
||||
""");
|
||||
ownSql.setCondition(ownCnd);
|
||||
ownSql.setCallback(Sqls.callback.integer());
|
||||
tourLedgerService.dao().execute(ownSql);
|
||||
|
||||
Sql organizedSql = Sqls.create("""
|
||||
SELECT CAST(IFNULL(SUM(1 + IFNULL(f.familyCount, 0)), 0) AS SIGNED)
|
||||
FROM tour_ledger t
|
||||
INNER JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN (
|
||||
SELECT ledgerId, COUNT(1) AS familyCount
|
||||
FROM tour_ledger_family
|
||||
WHERE delFlag = 0
|
||||
GROUP BY ledgerId
|
||||
) f ON f.ledgerId = t.id
|
||||
$condition
|
||||
""");
|
||||
organizedSql.setCondition(organizedCnd);
|
||||
organizedSql.setCallback(Sqls.callback.integer());
|
||||
tourLedgerService.dao().execute(organizedSql);
|
||||
|
||||
return Result.success(NutMap.NEW()
|
||||
.addv("ownUnionJoinedStaffCount", ownSql.getInt())
|
||||
.addv("organizedLineJoinedTotalCount", organizedSql.getInt()));
|
||||
}
|
||||
|
||||
private List<NutMap> queryOverCostSummaryList(Cnd cnd, PageForm pageForm) {
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT
|
||||
t.id,
|
||||
t.userName,
|
||||
t.idCard,
|
||||
t.unionName,
|
||||
vu.mobile,
|
||||
m.travelStartTime,
|
||||
m.travelEndTime,
|
||||
m.estimatedCost,
|
||||
CASE
|
||||
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
|
||||
THEN CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)
|
||||
ELSE tp.travelPeriod
|
||||
END AS travelPeriod,
|
||||
CASE
|
||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
||||
ELSE CONCAT(
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
||||
'(',
|
||||
CASE
|
||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
||||
ELSE IFNULL(mu.name, '')
|
||||
END,
|
||||
')'
|
||||
)
|
||||
END AS currentLineName
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
LEFT JOIN sys_union mu ON mu.id = m.unionId
|
||||
LEFT JOIN vw_user vu ON vu.loginname = t.jobNo
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
`year`,
|
||||
lineId,
|
||||
GROUP_CONCAT(
|
||||
DISTINCT CASE
|
||||
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
|
||||
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
|
||||
ELSE NULL
|
||||
END
|
||||
ORDER BY travelStartTime ASC
|
||||
SEPARATOR ';'
|
||||
) AS travelPeriod
|
||||
FROM tour_matter
|
||||
WHERE delFlag = 0
|
||||
AND lineId IS NOT NULL
|
||||
AND lineId <> ''
|
||||
GROUP BY `year`, lineId
|
||||
) tp ON (t.matterId IS NULL OR t.matterId = '') AND tp.`year` = t.`year` AND tp.lineId = t.lineId
|
||||
$condition
|
||||
ORDER BY $orderColumn $orderBy, t.`year` DESC, t.signupTime DESC, t.createdAt DESC
|
||||
""");
|
||||
sql.setCondition(cnd);
|
||||
sql.setVar("orderColumn", getOrderColumn(pageForm == null ? null : pageForm.getPageOrderName()));
|
||||
sql.setVar("orderBy", pageForm != null && "ascending".equals(pageForm.getPageOrderBy()) ? "asc" : "desc");
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
List<NutMap> list = sql.getList(NutMap.class);
|
||||
for (NutMap item : list) {
|
||||
item.put("lineName", item.getString("currentLineName"));
|
||||
item.put("totalDays", calcDays(item.getString("travelStartTime", ""), item.getString("travelEndTime", "")));
|
||||
item.put("estimatedCostText", formatAmount(item.get("estimatedCost")));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
private Workbook buildOverCostSummaryWorkbook(String unionName, List<NutMap> list) {
|
||||
Workbook workbook = new HSSFWorkbook();
|
||||
Sheet sheet = workbook.createSheet("汇总表");
|
||||
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 9));
|
||||
sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, 9));
|
||||
sheet.addMergedRegion(new CellRangeAddress(2, 2, 0, 9));
|
||||
|
||||
double[] widths = {5.14, 14.14, 21.29, 15.71, 27, 40, 50.43, 13.71, 22.57, 15.43};
|
||||
for (int i = 0; i < widths.length; i++) {
|
||||
sheet.setColumnWidth(i, (int) (widths[i] * 256));
|
||||
}
|
||||
|
||||
CellStyle attachStyle = createStyle(workbook, "宋体", (short) 12, false, HorizontalAlignment.LEFT, false, false);
|
||||
CellStyle titleStyle = createStyle(workbook, "黑体", (short) 18, true, HorizontalAlignment.CENTER, false, false);
|
||||
CellStyle unionStyle = createStyle(workbook, "仿宋_GB2312", (short) 14, true, HorizontalAlignment.LEFT, false, false);
|
||||
unionStyle.setBorderBottom(BorderStyle.THIN);
|
||||
CellStyle headerStyle = createStyle(workbook, "宋体", (short) 12, true, HorizontalAlignment.CENTER, true, true);
|
||||
CellStyle bodyStyle = createStyle(workbook, "宋体", (short) 12, false, HorizontalAlignment.CENTER, false, true);
|
||||
|
||||
Row row1 = sheet.createRow(0);
|
||||
row1.setHeightInPoints(32);
|
||||
setCell(row1, 0, "附件12", attachStyle);
|
||||
fillMergedCells(row1, 1, 9, attachStyle);
|
||||
|
||||
Row row2 = sheet.createRow(1);
|
||||
row2.setHeightInPoints(32);
|
||||
setCell(row2, 0, "5天外超出部分疗休养费用由单位承担申请人员汇总表", titleStyle);
|
||||
fillMergedCells(row2, 1, 9, titleStyle);
|
||||
|
||||
Row row3 = sheet.createRow(2);
|
||||
row3.setHeightInPoints(32);
|
||||
setCell(row3, 0, "分工会:" + StrUtil.blankToDefault(unionName, ""), unionStyle);
|
||||
fillMergedCells(row3, 1, 9, unionStyle);
|
||||
|
||||
String[] headers = {"序号", "姓名", "身份证号码", "电话号码", "所属分工会", "所选线路名称", "疗休养时间", "疗休养时长", "疗休养费用总额(元)", "备注"};
|
||||
Row header = sheet.createRow(3);
|
||||
header.setHeightInPoints(43);
|
||||
for (int i = 0; i < headers.length; i++) {
|
||||
setCell(header, i, headers[i], headerStyle);
|
||||
}
|
||||
|
||||
int rowCount = Math.max(list == null ? 0 : list.size(), 20);
|
||||
for (int i = 0; i < rowCount; i++) {
|
||||
Row row = sheet.createRow(i + 4);
|
||||
row.setHeightInPoints(25);
|
||||
NutMap item = list != null && i < list.size() ? list.get(i) : null;
|
||||
setCell(row, 0, String.valueOf(i + 1), bodyStyle);
|
||||
setCell(row, 1, item == null ? "" : item.getString("userName", ""), bodyStyle);
|
||||
setCell(row, 2, item == null ? "" : item.getString("idCard", ""), bodyStyle);
|
||||
setCell(row, 3, item == null ? "" : item.getString("mobile", ""), bodyStyle);
|
||||
setCell(row, 4, item == null ? "" : item.getString("unionName", ""), bodyStyle);
|
||||
setCell(row, 5, item == null ? "" : item.getString("lineName", ""), bodyStyle);
|
||||
setCell(row, 6, item == null ? "" : item.getString("travelPeriod", ""), bodyStyle);
|
||||
setCell(row, 7, item == null ? "" : item.getString("totalDays", ""), bodyStyle);
|
||||
setCell(row, 8, item == null ? "" : item.getString("estimatedCostText", ""), bodyStyle);
|
||||
setCell(row, 9, "", bodyStyle);
|
||||
}
|
||||
return workbook;
|
||||
}
|
||||
|
||||
private CellStyle createStyle(Workbook workbook, String fontName, short fontSize, boolean bold, HorizontalAlignment alignment, boolean wrap, boolean border) {
|
||||
Font font = workbook.createFont();
|
||||
font.setFontName(fontName);
|
||||
font.setFontHeightInPoints(fontSize);
|
||||
font.setBold(bold);
|
||||
|
||||
CellStyle style = workbook.createCellStyle();
|
||||
style.setFont(font);
|
||||
style.setAlignment(alignment);
|
||||
style.setVerticalAlignment(VerticalAlignment.CENTER);
|
||||
style.setWrapText(wrap);
|
||||
if (border) {
|
||||
style.setBorderLeft(BorderStyle.THIN);
|
||||
style.setBorderTop(BorderStyle.THIN);
|
||||
style.setBorderRight(BorderStyle.THIN);
|
||||
style.setBorderBottom(BorderStyle.THIN);
|
||||
}
|
||||
return style;
|
||||
}
|
||||
|
||||
private void setCell(Row row, int col, String value, CellStyle style) {
|
||||
Cell cell = row.createCell(col);
|
||||
cell.setCellValue(StrUtil.blankToDefault(value, ""));
|
||||
cell.setCellStyle(style);
|
||||
}
|
||||
|
||||
private void fillMergedCells(Row row, int startCol, int endCol, CellStyle style) {
|
||||
for (int i = startCol; i <= endCol; i++) {
|
||||
setCell(row, i, "", style);
|
||||
}
|
||||
}
|
||||
|
||||
private Integer calcDays(String start, String end) {
|
||||
if (StrUtil.isBlank(start) || StrUtil.isBlank(end)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
LocalDate startDate = LocalDate.parse(start.substring(0, 10));
|
||||
LocalDate endDate = LocalDate.parse(end.substring(0, 10));
|
||||
return Math.toIntExact(ChronoUnit.DAYS.between(startDate, endDate) + 1);
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private String formatAmount(Object value) {
|
||||
if (value == null) {
|
||||
return "";
|
||||
}
|
||||
try {
|
||||
BigDecimal amount = new BigDecimal(String.valueOf(value));
|
||||
return amount.stripTrailingZeros().toPlainString();
|
||||
} catch (Exception e) {
|
||||
return String.valueOf(value);
|
||||
}
|
||||
}
|
||||
|
||||
private Cnd buildQueryCnd(String currentUnionId, Integer startYear, Integer endYear, String keyword, String unionId, String lineId, String travelPeriod, String lineType, String scopeType, Boolean overCostOnly) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("t.delFlag", "=", false);
|
||||
cnd.andEX("t.`year`", ">=", startYear);
|
||||
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
|
||||
cnd.andEX("t.unionId", "=", unionId);
|
||||
cnd.andEX("t.lineType", "=", lineType);
|
||||
if (Boolean.TRUE.equals(overCostOnly)) {
|
||||
cnd.and("t.overCostReimbursed", "=", true);
|
||||
}
|
||||
appendLineFilter(cnd, lineId);
|
||||
appendTravelPeriodFilter(cnd, travelPeriod);
|
||||
appendScopeFilter(cnd, currentUnionId, scopeType);
|
||||
appendKeywordFilter(cnd, keyword);
|
||||
return cnd;
|
||||
}
|
||||
|
||||
private Cnd buildSummaryCnd(String currentUnionId, Integer startYear, Integer endYear, String unionId, String lineId, String travelPeriod, String lineType, String scopeType) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and("t.delFlag", "=", false);
|
||||
cnd.andEX("t.`year`", ">=", startYear);
|
||||
cnd.andEX("t.`year`", "<=", endYear == null ? LocalDate.now().getYear() : endYear);
|
||||
cnd.andEX("t.unionId", "=", unionId);
|
||||
cnd.andEX("t.lineType", "=", lineType);
|
||||
appendLineFilter(cnd, lineId);
|
||||
appendTravelPeriodFilter(cnd, travelPeriod);
|
||||
appendScopeFilter(cnd, currentUnionId, scopeType);
|
||||
return cnd;
|
||||
}
|
||||
|
||||
private void appendKeywordFilter(Cnd cnd, String keyword) {
|
||||
if (StrUtil.isNotBlank(keyword)) {
|
||||
SqlExpressionGroup group = new SqlExpressionGroup();
|
||||
group.orLike("t.userName", keyword.trim());
|
||||
group.orLike("t.jobNo", keyword.trim());
|
||||
cnd.and(group);
|
||||
}
|
||||
}
|
||||
|
||||
private void applyUnionLedgerScope(Cnd cnd, String currentUnionId) {
|
||||
SqlExpressionGroup group = new SqlExpressionGroup();
|
||||
group.or("t.unionId", "=", currentUnionId);
|
||||
group.or("m.unionId", "=", currentUnionId);
|
||||
cnd.and(group);
|
||||
}
|
||||
|
||||
private void appendScopeFilter(Cnd cnd, String currentUnionId, String scopeType) {
|
||||
if ("ownUnionJoined".equals(scopeType)) {
|
||||
cnd.and("t.unionId", "=", currentUnionId);
|
||||
return;
|
||||
}
|
||||
if ("organizedLineJoined".equals(scopeType)) {
|
||||
cnd.and("m.unionId", "=", currentUnionId);
|
||||
return;
|
||||
}
|
||||
applyUnionLedgerScope(cnd, currentUnionId);
|
||||
}
|
||||
|
||||
private String buildYearCondition(Integer startYear, Integer endYear) {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (startYear != null) {
|
||||
builder.append(" AND t.`year` >= ").append(startYear);
|
||||
}
|
||||
if (endYear != null) {
|
||||
builder.append(" AND t.`year` <= ").append(endYear);
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
private TourLedger fetchScopedLedger(String id) {
|
||||
if (StrUtil.isBlank(id) || StrUtil.isBlank(currentUnionId())) {
|
||||
return null;
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT COUNT(1)
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
WHERE t.delFlag = 0
|
||||
AND t.id = @id
|
||||
AND (t.unionId = @unionId OR m.unionId = @unionId)
|
||||
""");
|
||||
sql.setParam("id", id);
|
||||
sql.setParam("unionId", currentUnionId());
|
||||
sql.setCallback(Sqls.callback.integer());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return sql.getInt() > 0 ? tourLedgerService.fetch(id) : null;
|
||||
}
|
||||
|
||||
private String getTravelPeriod(TourLedger ledger) {
|
||||
if (ledger == null || ledger.getYear() == null || StrUtil.isBlank(ledger.getLineId())) {
|
||||
return "";
|
||||
}
|
||||
if (StrUtil.isNotBlank(ledger.getMatterId())) {
|
||||
Sql exactSql = Sqls.create("""
|
||||
SELECT CASE
|
||||
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
|
||||
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
|
||||
ELSE ''
|
||||
END AS travelPeriod
|
||||
FROM tour_matter
|
||||
WHERE delFlag = 0
|
||||
AND id = @matterId
|
||||
""");
|
||||
exactSql.setParam("matterId", ledger.getMatterId());
|
||||
exactSql.setCallback(Sqls.callback.str());
|
||||
tourLedgerService.dao().execute(exactSql);
|
||||
return exactSql.getString();
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT GROUP_CONCAT(
|
||||
DISTINCT CASE
|
||||
WHEN IFNULL(travelStartTime, '') <> '' AND IFNULL(travelEndTime, '') <> ''
|
||||
THEN CONCAT(travelStartTime, ' 至 ', travelEndTime)
|
||||
ELSE NULL
|
||||
END
|
||||
ORDER BY travelStartTime ASC
|
||||
SEPARATOR ';'
|
||||
) AS travelPeriod
|
||||
FROM tour_matter
|
||||
WHERE delFlag = 0
|
||||
AND `year` = @year
|
||||
AND lineId = @lineId
|
||||
""");
|
||||
sql.setParam("year", ledger.getYear());
|
||||
sql.setParam("lineId", ledger.getLineId());
|
||||
sql.setCallback(Sqls.callback.str());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return sql.getString();
|
||||
}
|
||||
|
||||
private boolean isDirectFamilyUnitLine(TourLedger ledger) {
|
||||
if (ledger == null || StrUtil.isBlank(ledger.getLineId())) {
|
||||
return false;
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT IFNULL(directFamilyUnitLine, 0)
|
||||
FROM tour_line
|
||||
WHERE delFlag = 0
|
||||
AND id = @lineId
|
||||
""");
|
||||
sql.setParam("lineId", ledger.getLineId());
|
||||
sql.setCallback(Sqls.callback.integer());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return sql.getInt() > 0;
|
||||
}
|
||||
|
||||
private boolean isFillBedInfo(TourLedger ledger) {
|
||||
if (ledger == null || StrUtil.isBlank(ledger.getMatterId())) {
|
||||
return true;
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT IFNULL(MAX(s.fillBedInfo), 1)
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_setting s ON s.id = m.settingId AND s.delFlag = 0
|
||||
WHERE t.id = @ledgerId
|
||||
""");
|
||||
sql.setParam("ledgerId", ledger.getId());
|
||||
sql.setCallback(Sqls.callback.integer());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return sql.getInt() > 0;
|
||||
}
|
||||
|
||||
private void appendTravelPeriodFilter(Cnd cnd, String travelPeriod) {
|
||||
if (StrUtil.isBlank(travelPeriod)) {
|
||||
return;
|
||||
}
|
||||
cnd.and("CONCAT(m.travelStartTime, ' 至 ', m.travelEndTime)", "=", travelPeriod);
|
||||
}
|
||||
|
||||
private void appendLineFilter(Cnd cnd, String lineId) {
|
||||
if (StrUtil.isBlank(lineId)) {
|
||||
return;
|
||||
}
|
||||
LineFilter lineFilter = parseLineFilter(lineId);
|
||||
if (lineFilter.getLineId().startsWith("legacy:")) {
|
||||
SqlExpressionGroup legacyGroup = new SqlExpressionGroup();
|
||||
legacyGroup.or("t.lineId", "IS", null);
|
||||
legacyGroup.or("t.lineId", "=", "");
|
||||
cnd.and(legacyGroup);
|
||||
cnd.and("t.lineName", "=", lineFilter.getLineId().substring("legacy:".length()));
|
||||
} else {
|
||||
cnd.and("t.lineId", "=", lineFilter.getLineId());
|
||||
}
|
||||
appendMatterUnionFilter(cnd, lineFilter.getUnionId());
|
||||
}
|
||||
|
||||
private LineFilter parseLineFilter(String value) {
|
||||
int delimiterIndex = value.indexOf("|");
|
||||
if (delimiterIndex < 0) {
|
||||
return new LineFilter(value, null);
|
||||
}
|
||||
return new LineFilter(value.substring(0, delimiterIndex), value.substring(delimiterIndex + 1));
|
||||
}
|
||||
|
||||
private void appendMatterUnionFilter(Cnd cnd, String unionId) {
|
||||
if (unionId == null) {
|
||||
return;
|
||||
}
|
||||
if (StrUtil.isBlank(unionId)) {
|
||||
SqlExpressionGroup group = new SqlExpressionGroup();
|
||||
group.or("m.unionId", "IS", null);
|
||||
group.or("m.unionId", "=", "");
|
||||
cnd.and(group);
|
||||
return;
|
||||
}
|
||||
cnd.and("m.unionId", "=", unionId);
|
||||
}
|
||||
|
||||
private String getCurrentLineName(TourLedger ledger) {
|
||||
if (ledger == null) {
|
||||
return "";
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT CASE
|
||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
||||
ELSE CONCAT(
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
||||
'(',
|
||||
CASE
|
||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
||||
ELSE IFNULL(mu.name, '')
|
||||
END,
|
||||
')'
|
||||
)
|
||||
END AS lineName
|
||||
FROM tour_ledger t
|
||||
LEFT JOIN tour_matter m ON m.id = t.matterId AND m.delFlag = 0
|
||||
LEFT JOIN tour_line l ON l.id = t.lineId AND l.delFlag = 0
|
||||
LEFT JOIN sys_union mu ON mu.id = m.unionId
|
||||
WHERE t.id = @ledgerId
|
||||
""");
|
||||
sql.setParam("ledgerId", ledger.getId());
|
||||
sql.setCallback(Sqls.callback.str());
|
||||
tourLedgerService.dao().execute(sql);
|
||||
return StrUtil.blankToDefault(sql.getString(), ledger.getLineName());
|
||||
}
|
||||
|
||||
private static class LineFilter {
|
||||
private final String lineId;
|
||||
private final String unionId;
|
||||
|
||||
private LineFilter(String lineId, String unionId) {
|
||||
this.lineId = lineId;
|
||||
this.unionId = unionId;
|
||||
}
|
||||
|
||||
private String getLineId() {
|
||||
return lineId;
|
||||
}
|
||||
|
||||
private String getUnionId() {
|
||||
return unionId;
|
||||
}
|
||||
}
|
||||
|
||||
private String currentUnionId() {
|
||||
return SecurityUtil.getUnionId();
|
||||
}
|
||||
|
||||
private String currentUnionName() {
|
||||
String unionId = currentUnionId();
|
||||
if (StrUtil.isBlank(unionId)) {
|
||||
return "";
|
||||
}
|
||||
Sys_union union = tourLedgerService.dao().fetch(Sys_union.class, unionId);
|
||||
return union == null ? "" : StrUtil.blankToDefault(union.getName(), "");
|
||||
}
|
||||
|
||||
private String getOrderColumn(String orderName) {
|
||||
if ("year".equals(orderName)) {
|
||||
return "t.`year`";
|
||||
}
|
||||
if ("jobNo".equals(orderName)) {
|
||||
return "t.jobNo";
|
||||
}
|
||||
if ("userName".equals(orderName)) {
|
||||
return "t.userName";
|
||||
}
|
||||
if ("lineName".equals(orderName)) {
|
||||
return """
|
||||
CASE
|
||||
WHEN m.id IS NULL THEN COALESCE(NULLIF(l.lineName, ''), t.lineName)
|
||||
ELSE CONCAT(
|
||||
COALESCE(NULLIF(l.lineName, ''), t.lineName),
|
||||
'(',
|
||||
CASE
|
||||
WHEN IFNULL(m.unionId, '') = '' THEN '校工会'
|
||||
ELSE IFNULL(mu.name, '')
|
||||
END,
|
||||
')'
|
||||
)
|
||||
END
|
||||
""";
|
||||
}
|
||||
if ("travelPeriod".equals(orderName)) {
|
||||
return "COALESCE(m.travelStartTime, tp.travelPeriod)";
|
||||
}
|
||||
if ("lineType".equals(orderName)) {
|
||||
return "t.lineType";
|
||||
}
|
||||
if ("signupTime".equals(orderName)) {
|
||||
return "t.signupTime";
|
||||
}
|
||||
if ("familyCount".equals(orderName)) {
|
||||
return "familyCount";
|
||||
}
|
||||
if ("joined".equals(orderName)) {
|
||||
return "t.joined";
|
||||
}
|
||||
return "t.`year`";
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.mode;
|
||||
|
||||
import cn.afterturn.easypoi.excel.annotation.Excel;
|
||||
import com.budwk.app.base.model.ExcelImportError;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class TourLedgerImportExcelMode extends ExcelImportError {
|
||||
|
||||
@Excel(name = "年度")
|
||||
private Integer year;
|
||||
|
||||
@Excel(name = "工号")
|
||||
private String jobNo;
|
||||
|
||||
@Excel(name = "姓名")
|
||||
private String userName;
|
||||
|
||||
@Excel(name = "报名时间", format = "yyyy-MM-dd HH:mm:ss")
|
||||
private Date signupTime;
|
||||
|
||||
@Excel(name = "线路名称")
|
||||
private String lineName;
|
||||
|
||||
@Excel(name = "线路类型")
|
||||
private String lineType;
|
||||
|
||||
@Excel(name = "是否参加")
|
||||
private String joined;
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 疗休养教职工报名台账。
|
||||
* 后续报名模块完成后,将已报名或已参加的教职工写入本表,台账页负责跨年度查询和详情查看。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Table("tour_ledger")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("普惠疗休养台账")
|
||||
public class TourLedger extends BaseModel implements Serializable {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("年度")
|
||||
@ColDefine(type = ColType.INT, width = 4)
|
||||
private Integer year;
|
||||
|
||||
@Column
|
||||
@Comment("工号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String jobNo;
|
||||
|
||||
@Column
|
||||
@Comment("姓名")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String userName;
|
||||
|
||||
@Column
|
||||
@Comment("性别")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String gender;
|
||||
|
||||
@Column
|
||||
@Comment("身份证号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String idCard;
|
||||
|
||||
@Column
|
||||
@Comment("所在单位ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String unitId;
|
||||
|
||||
@Column
|
||||
@Comment("所在单位")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String unitName;
|
||||
|
||||
@Column
|
||||
@Comment("所在工会ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String unionId;
|
||||
|
||||
@Column
|
||||
@Comment("所在工会")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String unionName;
|
||||
|
||||
@Column
|
||||
@Comment("报名时间")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String signupTime;
|
||||
|
||||
@Column
|
||||
@Comment("报名事项ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String matterId;
|
||||
|
||||
@Column
|
||||
@Comment("报名线路ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String lineId;
|
||||
|
||||
@Column
|
||||
@Comment("报名线路")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String lineName;
|
||||
|
||||
@Column
|
||||
@Comment("线路类型")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String lineType;
|
||||
|
||||
@Column
|
||||
@Comment("报名酒店")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String hotelName;
|
||||
|
||||
@Column
|
||||
@Comment("旅行社ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String travelAgencyId;
|
||||
|
||||
@Column
|
||||
@Comment("旅行社")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String travelAgencyName;
|
||||
|
||||
@Column
|
||||
@Comment("是否携带家属")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
@Default("0")
|
||||
private Boolean hasFamily;
|
||||
|
||||
@Column
|
||||
@Comment("意向拼床人")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String intendedRoommate;
|
||||
|
||||
@Column
|
||||
@Comment("床型")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String bedType;
|
||||
|
||||
@Column
|
||||
@Comment("床位信息")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String bedInfo;
|
||||
|
||||
@Column
|
||||
@Comment("是否参加")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
@Default("0")
|
||||
private Boolean joined;
|
||||
|
||||
@Column
|
||||
@Comment("是否报销")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
@Default("0")
|
||||
private Boolean reimbursed;
|
||||
|
||||
@Column
|
||||
@Comment("报销超出费用")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
@Default("0")
|
||||
private Boolean overCostReimbursed;
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 直系亲属线路报名信息。
|
||||
* 与普通携带亲属信息分表存放,用于直系亲属线路的专属申请信息。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Table("tour_ledger_direct_relative")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("普惠疗休养直系亲属线路报名信息")
|
||||
public class TourLedgerDirectRelative extends BaseModel implements Serializable {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("台账ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String ledgerId;
|
||||
|
||||
@Column
|
||||
@Comment("亲属姓名")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String relativeName;
|
||||
|
||||
@Column
|
||||
@Comment("所在单位")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String unitName;
|
||||
|
||||
@Column
|
||||
@Comment("亲属关系编码")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String relationshipCode;
|
||||
|
||||
@Column
|
||||
@Comment("亲属关系")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String relationshipName;
|
||||
|
||||
@Column
|
||||
@Comment("线路ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String lineId;
|
||||
|
||||
@Column
|
||||
@Comment("线路名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String lineName;
|
||||
|
||||
@Column
|
||||
@Comment("出行开始日期")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String travelStartTime;
|
||||
|
||||
@Column
|
||||
@Comment("出行结束日期")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String travelEndTime;
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 疗休养台账家属信息。
|
||||
* 与教职工台账通过 ledgerId 关联,用于查看教职工携带家属的历史记录。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Table("tour_ledger_family")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("普惠疗休养台账家属信息")
|
||||
public class TourLedgerFamily extends BaseModel implements Serializable {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("台账ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String ledgerId;
|
||||
|
||||
@Column
|
||||
@Comment("教职工工号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String staffJobNo;
|
||||
|
||||
@Column
|
||||
@Comment("教职工姓名")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String staffName;
|
||||
|
||||
@Column
|
||||
@Comment("家属姓名")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String familyName;
|
||||
|
||||
@Column
|
||||
@Comment("性别")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String gender;
|
||||
|
||||
@Column
|
||||
@Comment("身份证号码")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String idCard;
|
||||
|
||||
@Column
|
||||
@Comment("年龄")
|
||||
@ColDefine(type = ColType.INT, width = 3)
|
||||
private Integer age;
|
||||
|
||||
@Column
|
||||
@Comment("床型")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String bedType;
|
||||
|
||||
@Column
|
||||
@Comment("床位")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String bedInfo;
|
||||
|
||||
@Column
|
||||
@Comment("意向拼床人")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String intendedRoommate;
|
||||
|
||||
@Column
|
||||
@Comment("关系")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String relationship;
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 疗休养线路管理。
|
||||
* 当前阶段先维护线路基础信息,后续报名、台账等模块可通过线路ID继续关联。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Table("tour_line")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("普惠疗休养线路")
|
||||
public class TourLine extends BaseModel implements Serializable {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("创建年度")
|
||||
@ColDefine(type = ColType.INT, width = 4)
|
||||
private Integer year;
|
||||
|
||||
@Column
|
||||
@Comment("线路编号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String lineCode;
|
||||
|
||||
@Column
|
||||
@Comment("线路名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String lineName;
|
||||
|
||||
@Column
|
||||
@Comment("旅行社ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String travelAgencyId;
|
||||
|
||||
@Column
|
||||
@Comment("创建人ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String creatorUserId;
|
||||
|
||||
@Column
|
||||
@Comment("创建人")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String creatorName;
|
||||
|
||||
@Column
|
||||
@Comment("所在单位ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String unitId;
|
||||
|
||||
@Column
|
||||
@Comment("所在单位")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String unitName;
|
||||
|
||||
@Column
|
||||
@Comment("线路类型")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String lineType;
|
||||
|
||||
@Column
|
||||
@Comment("时间标段ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String lotId;
|
||||
|
||||
@Column
|
||||
@Comment("线路内容")
|
||||
@ColDefine(type = ColType.TEXT)
|
||||
private String lineContent;
|
||||
|
||||
@Column
|
||||
@Comment("移动端缩略图")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 500)
|
||||
private String mobileThumb;
|
||||
|
||||
@Column
|
||||
@Comment("是否对外开放")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
@Default("1")
|
||||
private Boolean openFlag;
|
||||
|
||||
@Column
|
||||
@Comment("是否直系亲属线路")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
@Default("0")
|
||||
private Boolean directFamilyUnitLine;
|
||||
|
||||
@Column
|
||||
@Comment("激活状态")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
@Default("1")
|
||||
private Boolean enabled;
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
/**
|
||||
* 疗休养事项。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Table("tour_matter")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("普惠疗休养事项")
|
||||
public class TourMatter extends BaseModel implements Serializable {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("年度")
|
||||
@ColDefine(type = ColType.INT, width = 4)
|
||||
private Integer year;
|
||||
|
||||
@Column
|
||||
@Comment("事项名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String matterName;
|
||||
|
||||
@Column
|
||||
@Comment("疗休养配置ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String settingId;
|
||||
|
||||
@Column
|
||||
@Comment("创建人ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String creatorUserId;
|
||||
|
||||
@Column
|
||||
@Comment("创建人")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String creatorName;
|
||||
|
||||
@Column
|
||||
@Comment("所属工会ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String unionId;
|
||||
|
||||
@Column
|
||||
@Comment("组织形式")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String organizationType;
|
||||
|
||||
@Column
|
||||
@Comment("移动端缩略图")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 500)
|
||||
private String mobileThumb;
|
||||
|
||||
@Column
|
||||
@Comment("线路ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String lineId;
|
||||
|
||||
@Column
|
||||
@Comment("报名开始时间")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String signupStartTime;
|
||||
|
||||
@Column
|
||||
@Comment("报名结束时间")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String signupEndTime;
|
||||
|
||||
@Column
|
||||
@Comment("出行开始时间")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String travelStartTime;
|
||||
|
||||
@Column
|
||||
@Comment("出行结束时间")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String travelEndTime;
|
||||
|
||||
@Column
|
||||
@Comment("联系人")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String contactName;
|
||||
|
||||
@Column
|
||||
@Comment("联系方式")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String contactPhone;
|
||||
|
||||
@Column
|
||||
@Comment("最少成团人数")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer minGroupPeople;
|
||||
|
||||
@Column
|
||||
@Comment("最多成团人数")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer maxGroupPeople;
|
||||
|
||||
@Column
|
||||
@Comment("预计费用")
|
||||
@ColDefine(customType = "decimal(10,2)")
|
||||
private BigDecimal estimatedCost;
|
||||
|
||||
@Column
|
||||
@Comment("事项状态")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
@Default("1")
|
||||
private Boolean enabled;
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 疗休养基础配置。
|
||||
* 这里先沉淀创建事项会复用的基础字段,后续线路、报名等阶段可以继续通过配置ID关联扩展。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Table("tour_setting")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("普惠疗休养配置")
|
||||
public class TourSetting extends BaseModel implements Serializable {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("年度")
|
||||
@ColDefine(type = ColType.INT, width = 4)
|
||||
private Integer year;
|
||||
|
||||
@Column
|
||||
@Comment("疗休养配置名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String configName;
|
||||
|
||||
@Column
|
||||
@Comment("疗休养类型")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String tourType;
|
||||
|
||||
@Column
|
||||
@Comment("可参加人员范围ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String activityGroupId;
|
||||
|
||||
@Column
|
||||
@Comment("排序编号")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer sortNo;
|
||||
|
||||
@Column
|
||||
@Comment("最少成团人数")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer minGroupPeople;
|
||||
|
||||
@Column
|
||||
@Comment("最多成团人数")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer maxGroupPeople;
|
||||
|
||||
@Column
|
||||
@Comment("省外几年去一次")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer outProvinceYears;
|
||||
|
||||
@Column
|
||||
@Comment("省外人数占比")
|
||||
@ColDefine(customType = "decimal(10,2)")
|
||||
private BigDecimal outProvinceRatio;
|
||||
|
||||
@Column
|
||||
@Comment("省外人数占比类型")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String outProvinceRatioType;
|
||||
|
||||
@Column
|
||||
@Comment("省外固定人数")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer outProvinceFixedPeople;
|
||||
|
||||
@Column
|
||||
@Comment("周期开始年度")
|
||||
@ColDefine(type = ColType.INT, width = 4)
|
||||
private Integer cycleStartYear;
|
||||
|
||||
@Column
|
||||
@Comment("周期结束年度")
|
||||
@ColDefine(type = ColType.INT, width = 4)
|
||||
private Integer cycleEndYear;
|
||||
|
||||
@Column
|
||||
@Comment("周期内总费用")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer cycleTotalCost;
|
||||
|
||||
@Column
|
||||
@Comment("周期允许次数")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer cycleAllowedTimes;
|
||||
|
||||
@Column
|
||||
@Comment("是否允许携带家属")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
@Default("0")
|
||||
private Boolean allowFamily;
|
||||
|
||||
@Column
|
||||
@Comment("是否填报床位信息")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
@Default("1")
|
||||
private Boolean fillBedInfo;
|
||||
|
||||
@Column
|
||||
@Comment("是否启用")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
@Default("1")
|
||||
private Boolean enabled;
|
||||
|
||||
/**
|
||||
* 标段管理沿用老疗休养配置的子表设计,后续线路、旅行社等模块可通过标段ID继续关联。
|
||||
*/
|
||||
@Many(field = "settingId")
|
||||
private List<TourSettingLot> lots;
|
||||
|
||||
@Column
|
||||
@Comment("服务须知")
|
||||
@ColDefine(type = ColType.TEXT)
|
||||
private String serviceNotice;
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 疗休养配置标段。
|
||||
* 标段从基础配置中拆成子表,便于后续线路、目的地、报名等模块复用同一标段口径。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Table("tour_setting_lot")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("普惠疗休养配置标段")
|
||||
public class TourSettingLot extends BaseModel implements Serializable {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("所属配置ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String settingId;
|
||||
|
||||
@Column
|
||||
@Comment("标段名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String lotName;
|
||||
|
||||
@Column
|
||||
@Comment("标段值")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String lotValue;
|
||||
|
||||
@Column
|
||||
@Comment("标段费用")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer activityCost;
|
||||
|
||||
@Column
|
||||
@Comment("允许超出报销")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
private Boolean allowOverReimbursement;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.*;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* 旅行社管理。
|
||||
* 先维护疗休养线路创建会复用的旅行社基础信息,后续线路模块可通过旅行社ID关联。
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Table("tour_travel_agency")
|
||||
@TableMeta("{'mysql-charset':'utf8mb4'}")
|
||||
@Comment("普惠疗休养旅行社")
|
||||
public class TourTravelAgency extends BaseModel implements Serializable {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("年度")
|
||||
@ColDefine(type = ColType.INT, width = 4)
|
||||
private Integer year;
|
||||
|
||||
@Column
|
||||
@Comment("旅行社编号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String agencyCode;
|
||||
|
||||
@Column
|
||||
@Comment("旅行社名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String agencyName;
|
||||
|
||||
@Column
|
||||
@Comment("联系人")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String contactName;
|
||||
|
||||
@Column
|
||||
@Comment("联系人手机")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 30)
|
||||
private String contactPhone;
|
||||
|
||||
@Column
|
||||
@Comment("邮箱")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String email;
|
||||
|
||||
@Column
|
||||
@Comment("备注")
|
||||
@ColDefine(type = ColType.TEXT)
|
||||
private String remark;
|
||||
|
||||
@Column
|
||||
@Comment("移动端缩略图")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 500)
|
||||
private String mobileThumb;
|
||||
|
||||
@Column
|
||||
@Comment("激活状态")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
@Default("1")
|
||||
private Boolean enabled;
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerDirectRelative;
|
||||
|
||||
public interface TourLedgerDirectRelativeService extends BaseService<TourLedgerDirectRelative> {
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerFamily;
|
||||
|
||||
public interface TourLedgerFamilyService extends BaseService<TourLedgerFamily> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
|
||||
|
||||
public interface TourLedgerService extends BaseService<TourLedger> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLine;
|
||||
|
||||
public interface TourLineService extends BaseService<TourLine> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
|
||||
|
||||
public interface TourMatterService extends BaseService<TourMatter> {
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
|
||||
|
||||
public interface TourSettingService extends BaseService<TourSetting> {
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.service;
|
||||
|
||||
import com.budwk.app.base.service.BaseService;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourTravelAgency;
|
||||
|
||||
public interface TourTravelAgencyService extends BaseService<TourTravelAgency> {
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
|
||||
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerDirectRelative;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerDirectRelativeService;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class TourLedgerDirectRelativeServiceImpl extends BaseServiceImpl<TourLedgerDirectRelative> implements TourLedgerDirectRelativeService {
|
||||
|
||||
public TourLedgerDirectRelativeServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
|
||||
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedgerFamily;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerFamilyService;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class TourLedgerFamilyServiceImpl extends BaseServiceImpl<TourLedgerFamily> implements TourLedgerFamilyService {
|
||||
|
||||
public TourLedgerFamilyServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
|
||||
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerService;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class TourLedgerServiceImpl extends BaseServiceImpl<TourLedger> implements TourLedgerService {
|
||||
|
||||
public TourLedgerServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
|
||||
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourLine;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourLineService;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class TourLineServiceImpl extends BaseServiceImpl<TourLine> implements TourLineService {
|
||||
|
||||
public TourLineServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
|
||||
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourMatter;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourMatterService;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class TourMatterServiceImpl extends BaseServiceImpl<TourMatter> implements TourMatterService {
|
||||
|
||||
public TourMatterServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
|
||||
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourSetting;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourSettingService;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class TourSettingServiceImpl extends BaseServiceImpl<TourSetting> implements TourSettingService {
|
||||
|
||||
public TourSettingServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package com.budwk.app.zhgh.dayofficework.tour.service.impl;
|
||||
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.models.TourTravelAgency;
|
||||
import com.budwk.app.zhgh.dayofficework.tour.service.TourTravelAgencyService;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
|
||||
@IocBean(args = {"refer:dao"})
|
||||
public class TourTravelAgencyServiceImpl extends BaseServiceImpl<TourTravelAgency> implements TourTravelAgencyService {
|
||||
|
||||
public TourTravelAgencyServiceImpl(Dao dao) {
|
||||
super(dao);
|
||||
}
|
||||
}
|
||||
+45
-5
@@ -25,6 +25,7 @@ import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.aop.Aop;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
@@ -68,11 +69,15 @@ public class TeacherCongressInstitutionController {
|
||||
Teacher_congress_institution rootInstitution = new Teacher_congress_institution();
|
||||
rootInstitution.setId("0");
|
||||
rootInstitution.setParentId("");
|
||||
rootInstitution.setName("教代会机构");
|
||||
rootInstitution.setName("两代会组织机构");
|
||||
rootInstitution.setLocation(0);
|
||||
institutionList.add(rootInstitution);
|
||||
|
||||
List<TreeNode<String>> treeNodes = institutionList.stream().map(institution -> new TreeNode<>(institution.getId(), institution.getParentId(), institution.getName(), institution.getLocation())).toList();
|
||||
List<TreeNode<String>> treeNodes = institutionList.stream().map(institution -> {
|
||||
TreeNode<String> treeNode = new TreeNode<>(institution.getId(), institution.getParentId(), institution.getName(), institution.getLocation());
|
||||
treeNode.setExtra(Map.of("code", Strings.sNull(institution.getCode())));
|
||||
return treeNode;
|
||||
}).toList();
|
||||
List<Tree<String>> treeList = TreeUtil.build(treeNodes, "");
|
||||
return Result.success(treeList);
|
||||
}
|
||||
@@ -97,6 +102,16 @@ public class TeacherCongressInstitutionController {
|
||||
return Result.success(Map.of("treeFlat", children, "treeList", treeList));
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tc.institution")
|
||||
public Result specialCommitteeRoleOptions() {
|
||||
Sys_dict parent = sysDictService.fetch(Cnd.where(Sys_dict::getCode, "=", "SPECIAL_COMMITTEE_ROLES"));
|
||||
if (ObjectUtil.isEmpty(parent)) {
|
||||
return Result.success(List.of());
|
||||
}
|
||||
List<Sys_dict> children = sysDictService.query(Cnd.where(Sys_dict::getPath, "like", parent.getPath() + "%").and(Sys_dict::getId, "!=", parent.getId()).asc(Sys_dict::getLocation));
|
||||
return Result.success(children);
|
||||
}
|
||||
|
||||
/**
|
||||
* 机构分页查询
|
||||
@@ -113,6 +128,7 @@ public class TeacherCongressInstitutionController {
|
||||
cnd.and("parentId", "=", parentId);
|
||||
cnd.and("sessionId", "=", sessionId);
|
||||
cnd.asc("location");
|
||||
cnd.asc("code");
|
||||
Pagination pagination = baseService.listPage(pageForm.getPageNumber(), pageForm.getPageSize(), "teacher_congress_institution", cnd);
|
||||
return Result.success(pagination);
|
||||
}
|
||||
@@ -129,7 +145,7 @@ public class TeacherCongressInstitutionController {
|
||||
if (dao.count(Teacher_congress_institution.class, Cnd.where(Teacher_congress_institution::getCode, "=", institution.getCode()).and(Teacher_congress_institution::getSessionId, "=", institution.getSessionId())) > 0) {
|
||||
return Result.error("机构已存在");
|
||||
}
|
||||
if (StrUtil.isNotBlank(institution.getParentId()) && !institution.getParentId().equals("0")) {
|
||||
if (StrUtil.isNotBlank(institution.getParentId()) && !institution.getParentId().equals("0") && dao.count(Teacher_congress_institution.class, Cnd.where(Teacher_congress_institution::getId, "=", institution.getParentId()).and(Teacher_congress_institution::getSessionId, "=", institution.getSessionId())) == 0) {
|
||||
Sys_dict sysDict = sysDictService.fetch(Cnd.where(Sys_dict::getId, "=", institution.getParentId()));
|
||||
Teacher_congress_institution parentInstitution = dao.fetch(Teacher_congress_institution.class,
|
||||
Cnd.where(Teacher_congress_institution::getCode, "=", sysDict.getCode())
|
||||
@@ -151,10 +167,34 @@ public class TeacherCongressInstitutionController {
|
||||
* @param id
|
||||
* @return
|
||||
*/
|
||||
@At
|
||||
@SaCheckPermission("tc.institution")
|
||||
public Result update(Teacher_congress_institution institution) {
|
||||
Teacher_congress_institution dbInstitution = dao.fetch(Teacher_congress_institution.class, institution.getId());
|
||||
if (ObjectUtil.isEmpty(dbInstitution)) {
|
||||
return Result.error("机构不存在");
|
||||
}
|
||||
dbInstitution.setName(institution.getName());
|
||||
dbInstitution.setIntroduce(institution.getIntroduce());
|
||||
dbInstitution.setLocation(institution.getLocation());
|
||||
dao.update(dbInstitution);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@SaCheckPermission("tc.institution")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result delete(@Valid String id) {
|
||||
Teacher_congress_institution institution = dao.fetch(Teacher_congress_institution.class, id);
|
||||
if (ObjectUtil.isEmpty(institution)) {
|
||||
return Result.error("机构不存在");
|
||||
}
|
||||
List<Teacher_congress_institution> siblingList = dao.query(Teacher_congress_institution.class, Cnd.where(Teacher_congress_institution::getParentId, "=", institution.getParentId()).and(Teacher_congress_institution::getSessionId, "=", institution.getSessionId()).asc(Teacher_congress_institution::getLocation).asc(Teacher_congress_institution::getCode));
|
||||
for (int i = 0; i < siblingList.size() && i < 6; i++) {
|
||||
if (id.equals(siblingList.get(i).getId())) {
|
||||
return Result.error("你选择的组织机构是两代会基本机构,不允许删除。");
|
||||
}
|
||||
}
|
||||
dao.delete(Teacher_congress_institution.class, id);
|
||||
dao.clear(Teacher_congress_institution_user.class, Cnd.where("institutionId", "=", id));
|
||||
return Result.success();
|
||||
@@ -214,8 +254,8 @@ public class TeacherCongressInstitutionController {
|
||||
@SaCheckPermission("tc.institution")
|
||||
@SLog(tag = "教代会-机构设置", msg = "添加人员")
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public Result userInsert(@Valid String userId, @Valid String institutionId, @Valid String sessionId, @Valid String identity) {
|
||||
teacherCongressInstitutionUserService.userInsert(userId, institutionId, sessionId, identity);
|
||||
public Result userInsert(@Valid String userId, @Valid String institutionId, @Valid String sessionId, @Valid String identity, String roleCode) {
|
||||
teacherCongressInstitutionUserService.userInsert(userId, institutionId, sessionId, identity, roleCode);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ public interface TeacherCongressInstitutionUserService extends BaseService<Teach
|
||||
* @param sessionId 届次id
|
||||
* @param identity 身份 主任|副主任|成员
|
||||
*/
|
||||
void userInsert(String userId, String institutionId, String sessionId, String identity);
|
||||
void userInsert(String userId, String institutionId, String sessionId, String identity, String roleCode);
|
||||
|
||||
/**
|
||||
* 删除机构成员
|
||||
|
||||
+24
-1
@@ -1,5 +1,6 @@
|
||||
package com.budwk.app.zhgh.democratic.teachercongress.institution.service.impl;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.exception.BaseException;
|
||||
import com.budwk.app.base.service.impl.BaseServiceImpl;
|
||||
@@ -30,7 +31,7 @@ public class TeacherCongressInstitutionUserServiceImpl extends BaseServiceImpl<T
|
||||
|
||||
@Override
|
||||
@Aop(TransAop.READ_COMMITTED)
|
||||
public void userInsert(String userId, String institutionId, String sessionId, String identity) {
|
||||
public void userInsert(String userId, String institutionId, String sessionId, String identity, String roleCode) {
|
||||
int count = dao().count(Teacher_congress_institution_user.class,
|
||||
Cnd.where(Teacher_congress_institution_user::getInstitutionId, "=", institutionId)
|
||||
.and(Teacher_congress_institution_user::getUserId, "=", userId)
|
||||
@@ -49,6 +50,28 @@ public class TeacherCongressInstitutionUserServiceImpl extends BaseServiceImpl<T
|
||||
dao().insert(institutionUser);
|
||||
|
||||
Teacher_congress_institution institution = dao().fetch(Teacher_congress_institution.class, institutionId);
|
||||
if ("ZWH001".equals(institution.getCode()) || "ZXWYH".equals(institution.getCode()) || "DBZGSCXZ".equals(institution.getCode())) {
|
||||
if (StrUtil.isBlank(roleCode)) {
|
||||
throw new BaseException("请选择角色");
|
||||
}
|
||||
Sys_role sysRole = sysRoleService.fetch(Cnd.where(Sys_role::getCode, "=", roleCode));
|
||||
if (sysRole == null) {
|
||||
throw new BaseException("无法找到" + roleCode + "对应编码的角色");
|
||||
}
|
||||
int existsRole = dao().count(Sys_user_role.class, Cnd.where(Sys_user_role::getUserId, "=", userId)
|
||||
.and(Sys_user_role::getTcSessionId, "=", sessionId)
|
||||
.and(Sys_user_role::getRoleId, "=", sysRole.getId())
|
||||
);
|
||||
if (existsRole == 0) {
|
||||
Sys_user_role insertSysUserRole = new Sys_user_role();
|
||||
insertSysUserRole.setRoleId(sysRole.getId());
|
||||
insertSysUserRole.setUserId(userId);
|
||||
insertSysUserRole.setTcSessionId(sessionId);
|
||||
dao().insert(insertSysUserRole);
|
||||
sysRoleService.clearCache();
|
||||
sysUserService.clearCache();
|
||||
}
|
||||
}
|
||||
if (institution.getCode().contains("TEACHER_CONGRESS_INSTITUTION_PROPOSAL_COMMITTEE") && identity.equals("主任")) {
|
||||
Sys_role sysRole = sysRoleService.getByCode(RoleConstant.PROPOSAL_COMMITTEE_DIRECTOR);
|
||||
int existsRole = dao().count(Sys_user_role.class, Cnd.where(Sys_user_role::getUserId, "=", userId)
|
||||
|
||||
+383
@@ -0,0 +1,383 @@
|
||||
package com.budwk.app.zhgh.learning.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
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.zhgh.learning.models.LearningCourse;
|
||||
import com.budwk.app.zhgh.learning.models.LearningCourseOutline;
|
||||
import com.budwk.app.zhgh.learning.models.LearningCourseType;
|
||||
import com.budwk.app.zhgh.learning.models.LearningOutlineResource;
|
||||
import com.budwk.app.zhgh.learning.models.LearningStudyRule;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.pager.Pager;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@Api("学习教育平台-章节内容管理")
|
||||
@At("/platform/learning/chapter/content")
|
||||
public class LearningChapterContentController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/Learning/chapterContent/index.html")
|
||||
@SaCheckPermission("learning.chapter.content")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("课程列表")
|
||||
@SaCheckPermission("learning.chapter.content")
|
||||
public Result courses(String courseId, String courseName, String courseTypeId, String lecturerName, String status) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX("id", "=", courseId);
|
||||
cnd.and(Cnd.likeEX("courseName", courseName));
|
||||
cnd.andEX("courseTypeId", "=", courseTypeId);
|
||||
cnd.and(Cnd.likeEX("lecturerName", lecturerName));
|
||||
cnd.andEX("status", "=", status);
|
||||
cnd.desc("createdAt");
|
||||
return Result.success(dao.query(LearningCourse.class, cnd, dao.createPager(1, 50)));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("课程类型")
|
||||
@SaCheckPermission("learning.chapter.content")
|
||||
public Result courseTypes() {
|
||||
return Result.success(dao.query(LearningCourseType.class, Cnd.where("enabled", "=", true).asc("sortNum").asc("createdAt")));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("课程大纲树")
|
||||
@SaCheckPermission("learning.chapter.content")
|
||||
public Result tree(String courseId) {
|
||||
if (StrUtil.isBlank(courseId)) {
|
||||
return Result.success(List.of());
|
||||
}
|
||||
List<LearningCourseOutline> outlines = dao.query(LearningCourseOutline.class, Cnd.where("courseId", "=", courseId).asc("sortOrder").asc("createdAt"));
|
||||
Map<String, List<NutMap>> childrenMap = new HashMap<>();
|
||||
List<NutMap> roots = new ArrayList<>();
|
||||
for (LearningCourseOutline outline : outlines) {
|
||||
NutMap map = NutMap.NEW()
|
||||
.addv("id", outline.getId())
|
||||
.addv("courseId", outline.getCourseId())
|
||||
.addv("courseName", outline.getCourseName())
|
||||
.addv("parentId", outline.getParentId())
|
||||
.addv("nodeType", outline.getNodeType())
|
||||
.addv("title", outline.getTitle())
|
||||
.addv("subtitle", outline.getSubtitle())
|
||||
.addv("description", outline.getDescription())
|
||||
.addv("sortOrder", outline.getSortOrder())
|
||||
.addv("required", outline.getRequired())
|
||||
.addv("status", outline.getStatus())
|
||||
.addv("children", new ArrayList<>());
|
||||
String parentId = StrUtil.blankToDefault(outline.getParentId(), "");
|
||||
childrenMap.computeIfAbsent(parentId, k -> new ArrayList<>()).add(map);
|
||||
}
|
||||
roots.addAll(childrenMap.getOrDefault("", new ArrayList<>()));
|
||||
appendChildren(roots, childrenMap);
|
||||
return Result.success(roots);
|
||||
}
|
||||
|
||||
private void appendChildren(List<NutMap> nodes, Map<String, List<NutMap>> childrenMap) {
|
||||
for (NutMap node : nodes) {
|
||||
List<NutMap> children = childrenMap.getOrDefault(node.getString("id"), new ArrayList<>());
|
||||
node.put("children", children);
|
||||
appendChildren(children, childrenMap);
|
||||
}
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("新增/编辑大纲节点")
|
||||
@SaCheckPermission("learning.chapter.content")
|
||||
@SLog(tag = "保存大纲节点", msg = "节点标题:${args[0].title}")
|
||||
public Result saveNode(LearningCourseOutline outline) {
|
||||
Result checkResult = checkNode(outline);
|
||||
if (checkResult != null) {
|
||||
return checkResult;
|
||||
}
|
||||
LearningCourse course = dao.fetch(LearningCourse.class, outline.getCourseId());
|
||||
outline.setCourseName(course == null ? outline.getCourseName() : course.getCourseName());
|
||||
if ("chapter".equals(outline.getNodeType())) {
|
||||
outline.setParentId("");
|
||||
}
|
||||
if (outline.getRequired() == null) {
|
||||
outline.setRequired(false);
|
||||
}
|
||||
if (StrUtil.isBlank(outline.getStatus())) {
|
||||
outline.setStatus("enabled");
|
||||
}
|
||||
if (outline.getSortOrder() == null) {
|
||||
outline.setSortOrder(nextNodeSort(outline.getCourseId(), outline.getParentId()));
|
||||
}
|
||||
if (StrUtil.isBlank(outline.getId())) {
|
||||
dao.insert(outline);
|
||||
} else {
|
||||
dao.updateIgnoreNull(outline);
|
||||
}
|
||||
ensureRule(outline.getCourseId(), "outline", outline.getId(), outline.getRequired(), "enabled");
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("删除大纲节点")
|
||||
@SaCheckPermission("learning.chapter.content")
|
||||
@SLog(tag = "删除大纲节点", msg = "节点ID:${args[0]}")
|
||||
public Result deleteNode(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("请选择要删除的数据");
|
||||
}
|
||||
int childCount = dao.count(LearningCourseOutline.class, Cnd.where("parentId", "=", id));
|
||||
int resourceCount = dao.count(LearningOutlineResource.class, Cnd.where("outlineId", "=", id));
|
||||
if (childCount > 0 || resourceCount > 0) {
|
||||
return Result.error("该节点下存在子节点或学习资料,请先删除后再操作");
|
||||
}
|
||||
dao.clear(LearningStudyRule.class, Cnd.where("targetId", "=", id).and("targetType", "=", "outline"));
|
||||
dao.clear(LearningCourseOutline.class, Cnd.where("id", "=", id));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("启用/禁用大纲节点")
|
||||
@SaCheckPermission("learning.chapter.content")
|
||||
public Result toggleNode(String id, String status) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("请选择要操作的数据");
|
||||
}
|
||||
dao.update(LearningCourseOutline.class, Chain.make("status", status), Cnd.where("id", "=", id));
|
||||
dao.update(LearningStudyRule.class, Chain.make("status", status), Cnd.where("targetId", "=", id).and("targetType", "=", "outline"));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("移动大纲节点")
|
||||
@SaCheckPermission("learning.chapter.content")
|
||||
public Result moveNode(String id, String direction) {
|
||||
LearningCourseOutline current = dao.fetch(LearningCourseOutline.class, id);
|
||||
if (current == null) {
|
||||
return Result.error("节点不存在");
|
||||
}
|
||||
Cnd cnd = Cnd.where("courseId", "=", current.getCourseId()).and("parentId", "=", StrUtil.blankToDefault(current.getParentId(), ""));
|
||||
if ("up".equals(direction)) {
|
||||
cnd.and("sortOrder", "<", current.getSortOrder()).desc("sortOrder");
|
||||
} else {
|
||||
cnd.and("sortOrder", ">", current.getSortOrder()).asc("sortOrder");
|
||||
}
|
||||
LearningCourseOutline target = dao.fetch(LearningCourseOutline.class, cnd);
|
||||
if (target == null) {
|
||||
return Result.success();
|
||||
}
|
||||
Integer currentSort = current.getSortOrder();
|
||||
dao.update(LearningCourseOutline.class, Chain.make("sortOrder", target.getSortOrder()), Cnd.where("id", "=", current.getId()));
|
||||
dao.update(LearningCourseOutline.class, Chain.make("sortOrder", currentSort), Cnd.where("id", "=", target.getId()));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("学习资料分页")
|
||||
@SaCheckPermission("learning.chapter.content")
|
||||
public Result resourcePage(PageForm pageForm, String outlineId) {
|
||||
if (StrUtil.isBlank(outlineId)) {
|
||||
return Result.success(new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), 0, List.of()));
|
||||
}
|
||||
Cnd cnd = Cnd.where("outlineId", "=", outlineId);
|
||||
String orderColumn = getResourceOrderColumn(pageForm.getPageOrderName());
|
||||
if (StrUtil.isBlank(orderColumn)) {
|
||||
cnd.asc("sortOrder").asc("createdAt");
|
||||
} else if ("descending".equals(pageForm.getPageOrderBy())) {
|
||||
cnd.desc(orderColumn);
|
||||
} else {
|
||||
cnd.asc(orderColumn);
|
||||
}
|
||||
int count = dao.count(LearningOutlineResource.class, Cnd.where("outlineId", "=", outlineId));
|
||||
Pager pager = dao.createPager(pageForm.getPageNumber(), pageForm.getPageSize());
|
||||
return Result.success(new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, dao.query(LearningOutlineResource.class, cnd, pager)));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("新增/编辑学习资料")
|
||||
@SaCheckPermission("learning.chapter.content")
|
||||
@SLog(tag = "保存学习资料", msg = "资料标题:${args[0].resourceTitle}")
|
||||
public Result saveResource(LearningOutlineResource resource) {
|
||||
Result checkResult = checkResource(resource);
|
||||
if (checkResult != null) {
|
||||
return checkResult;
|
||||
}
|
||||
if (resource.getRequired() == null) {
|
||||
resource.setRequired(false);
|
||||
}
|
||||
if (resource.getAllowPreview() == null) {
|
||||
resource.setAllowPreview(true);
|
||||
}
|
||||
if (resource.getAllowDownload() == null) {
|
||||
resource.setAllowDownload(false);
|
||||
}
|
||||
if (StrUtil.isBlank(resource.getStatus())) {
|
||||
resource.setStatus("enabled");
|
||||
}
|
||||
if (resource.getSortOrder() == null) {
|
||||
resource.setSortOrder(nextResourceSort(resource.getOutlineId()));
|
||||
}
|
||||
if (StrUtil.isBlank(resource.getId())) {
|
||||
dao.insert(resource);
|
||||
} else {
|
||||
dao.updateIgnoreNull(resource);
|
||||
}
|
||||
ensureRule(resource.getCourseId(), "resource", resource.getId(), resource.getRequired(), resource.getStatus());
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("删除学习资料")
|
||||
@SaCheckPermission("learning.chapter.content")
|
||||
@SLog(tag = "删除学习资料", msg = "资料ID:${args[0]}")
|
||||
public Result deleteResource(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("请选择要删除的数据");
|
||||
}
|
||||
dao.clear(LearningStudyRule.class, Cnd.where("targetId", "=", id).and("targetType", "=", "resource"));
|
||||
dao.clear(LearningOutlineResource.class, Cnd.where("id", "=", id));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("查询学习规则")
|
||||
@SaCheckPermission("learning.chapter.content")
|
||||
public Result getRule(String courseId, String targetType, String targetId) {
|
||||
LearningStudyRule rule = dao.fetch(LearningStudyRule.class, Cnd.where("courseId", "=", courseId).and("targetType", "=", targetType).and("targetId", "=", targetId));
|
||||
if (rule == null) {
|
||||
rule = defaultRule(courseId, targetType, targetId, false, "enabled");
|
||||
}
|
||||
return Result.success(rule);
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("保存学习规则")
|
||||
@SaCheckPermission("learning.chapter.content")
|
||||
public Result saveRule(LearningStudyRule rule) {
|
||||
if (StrUtil.isBlank(rule.getCourseId()) || StrUtil.isBlank(rule.getTargetType()) || StrUtil.isBlank(rule.getTargetId())) {
|
||||
return Result.error("规则对象不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(rule.getStatus())) {
|
||||
rule.setStatus("enabled");
|
||||
}
|
||||
LearningStudyRule old = dao.fetch(LearningStudyRule.class, Cnd.where("courseId", "=", rule.getCourseId()).and("targetType", "=", rule.getTargetType()).and("targetId", "=", rule.getTargetId()));
|
||||
if (old == null) {
|
||||
dao.insert(rule);
|
||||
} else {
|
||||
rule.setId(old.getId());
|
||||
dao.updateIgnoreNull(rule);
|
||||
}
|
||||
if ("outline".equals(rule.getTargetType())) {
|
||||
dao.update(LearningCourseOutline.class, Chain.make("required", rule.getRequired()).add("status", rule.getStatus()), Cnd.where("id", "=", rule.getTargetId()));
|
||||
}
|
||||
if ("resource".equals(rule.getTargetType())) {
|
||||
dao.update(LearningOutlineResource.class, Chain.make("required", rule.getRequired()).add("status", rule.getStatus()), Cnd.where("id", "=", rule.getTargetId()));
|
||||
}
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
private Result checkNode(LearningCourseOutline outline) {
|
||||
if (outline == null || StrUtil.isBlank(outline.getCourseId())) {
|
||||
return Result.error("请选择课程");
|
||||
}
|
||||
if (StrUtil.isBlank(outline.getNodeType())) {
|
||||
return Result.error("请选择节点类型");
|
||||
}
|
||||
if (StrUtil.isBlank(outline.getTitle())) {
|
||||
return Result.error("标题不能为空");
|
||||
}
|
||||
if ("section".equals(outline.getNodeType()) && StrUtil.isBlank(outline.getParentId())) {
|
||||
return Result.error("新增节需要选择所属章");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Result checkResource(LearningOutlineResource resource) {
|
||||
if (resource == null || StrUtil.isBlank(resource.getCourseId()) || StrUtil.isBlank(resource.getOutlineId())) {
|
||||
return Result.error("请选择章/节节点");
|
||||
}
|
||||
if (StrUtil.isBlank(resource.getResourceTitle())) {
|
||||
return Result.error("资料标题不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(resource.getResourceType())) {
|
||||
return Result.error("请选择资料类型");
|
||||
}
|
||||
if (StrUtil.isBlank(resource.getFileData())) {
|
||||
return Result.error("请上传附件");
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private Integer nextNodeSort(String courseId, String parentId) {
|
||||
LearningCourseOutline outline = dao.fetch(LearningCourseOutline.class, Cnd.where("courseId", "=", courseId).and("parentId", "=", StrUtil.blankToDefault(parentId, "")).desc("sortOrder"));
|
||||
return outline == null || outline.getSortOrder() == null ? 1 : outline.getSortOrder() + 1;
|
||||
}
|
||||
|
||||
private Integer nextResourceSort(String outlineId) {
|
||||
LearningOutlineResource resource = dao.fetch(LearningOutlineResource.class, Cnd.where("outlineId", "=", outlineId).desc("sortOrder"));
|
||||
return resource == null || resource.getSortOrder() == null ? 1 : resource.getSortOrder() + 1;
|
||||
}
|
||||
|
||||
private void ensureRule(String courseId, String targetType, String targetId, Boolean required, String status) {
|
||||
LearningStudyRule rule = dao.fetch(LearningStudyRule.class, Cnd.where("courseId", "=", courseId).and("targetType", "=", targetType).and("targetId", "=", targetId));
|
||||
if (rule == null) {
|
||||
dao.insert(defaultRule(courseId, targetType, targetId, required, status));
|
||||
} else {
|
||||
dao.update(LearningStudyRule.class,
|
||||
Chain.make("required", required != null && required).add("status", StrUtil.blankToDefault(status, "enabled")),
|
||||
Cnd.where("id", "=", rule.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
private LearningStudyRule defaultRule(String courseId, String targetType, String targetId, Boolean required, String status) {
|
||||
LearningStudyRule rule = new LearningStudyRule();
|
||||
rule.setCourseId(courseId);
|
||||
rule.setTargetType(targetType);
|
||||
rule.setTargetId(targetId);
|
||||
rule.setRequired(required != null && required);
|
||||
rule.setStudyMode("mixed");
|
||||
rule.setCompletionRule("all_required_resource");
|
||||
rule.setCompletePercent(90);
|
||||
rule.setMinStudySeconds(0);
|
||||
rule.setUnlockRule("free");
|
||||
rule.setAllowSkip(false);
|
||||
rule.setAllowDrag(true);
|
||||
rule.setPauseCountTime(false);
|
||||
rule.setHiddenCountTime(false);
|
||||
rule.setInactiveCountTime(false);
|
||||
rule.setStatus(StrUtil.blankToDefault(status, "enabled"));
|
||||
return rule;
|
||||
}
|
||||
|
||||
private String getResourceOrderColumn(String prop) {
|
||||
Map<String, String> columns = new HashMap<>();
|
||||
columns.put("resourceTitle", "resourceTitle");
|
||||
columns.put("resourceType", "resourceType");
|
||||
columns.put("fileExt", "fileExt");
|
||||
columns.put("durationSeconds", "durationSeconds");
|
||||
columns.put("sortOrder", "sortOrder");
|
||||
columns.put("required", "required");
|
||||
columns.put("allowPreview", "allowPreview");
|
||||
columns.put("allowDownload", "allowDownload");
|
||||
columns.put("status", "status");
|
||||
return columns.get(prop);
|
||||
}
|
||||
}
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
package com.budwk.app.zhgh.learning.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.io.FileUtil;
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.CharsetUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
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.base.utils.OfficePlusUtil;
|
||||
import com.budwk.app.sys.enums.SysFileEngineTypeEnum;
|
||||
import com.budwk.app.sys.models.Sys_file;
|
||||
import com.budwk.app.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.sys.utils.SysFileMinIoUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.learning.models.LearningCourseOutline;
|
||||
import com.budwk.app.zhgh.learning.models.LearningCourseType;
|
||||
import com.budwk.app.zhgh.learning.models.LearningOutlineResource;
|
||||
import com.budwk.app.zhgh.learning.models.LearningStudyRecord;
|
||||
import com.google.common.net.HttpHeaders;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.util.List;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@Api("学习教育平台-课程展示")
|
||||
@At("/platform/learning/course/display")
|
||||
public class LearningCourseDisplayController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/Learning/courseDisplay/index.html")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At("/detail")
|
||||
@Ok("beetl:/platform/zhgh/Learning/courseDisplay/detail.html")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public void detail() {
|
||||
}
|
||||
|
||||
@At("/study")
|
||||
@Ok("beetl:/platform/zhgh/Learning/courseDisplay/study.html")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public void study() {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("课程展示列表")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public Result pageData(PageForm pageForm, String keyword, String courseTypeId, String recommendFlag) {
|
||||
Cnd cnd = Cnd.where("c.status", "=", "published");
|
||||
if (StrUtil.isNotBlank(keyword)) {
|
||||
cnd.and(Cnd.exps("c.courseName", "like", "%" + keyword + "%")
|
||||
.or("c.courseIntro", "like", "%" + keyword + "%"));
|
||||
}
|
||||
cnd.andEX("c.courseTypeId", "=", courseTypeId);
|
||||
if (StrUtil.isNotBlank(recommendFlag)) {
|
||||
cnd.and("c.recommendFlags", "like", "%" + recommendFlag + "%");
|
||||
}
|
||||
|
||||
Sql countSql = Sqls.create("""
|
||||
SELECT COUNT(1)
|
||||
FROM learning_course c
|
||||
LEFT JOIN learning_course_type t ON t.id = c.courseTypeId
|
||||
$condition
|
||||
""");
|
||||
countSql.setCondition(cnd);
|
||||
countSql.setCallback(Sqls.callback.integer());
|
||||
dao.execute(countSql);
|
||||
int count = countSql.getInt();
|
||||
|
||||
Sql listSql = Sqls.create("""
|
||||
SELECT c.id, c.courseName, c.courseIntro, c.startTime, c.endTime, c.openType, c.cover,
|
||||
c.recommendFlags, c.courseTypeId, c.lecturerName, c.sortNum, t.typeName AS courseTypeName
|
||||
FROM learning_course c
|
||||
LEFT JOIN learning_course_type t ON t.id = c.courseTypeId
|
||||
$condition
|
||||
ORDER BY c.sortNum ASC, c.createdAt DESC
|
||||
""");
|
||||
listSql.setCondition(cnd);
|
||||
listSql.setPager(dao.createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||
listSql.setCallback(Sqls.callback.maps());
|
||||
dao.execute(listSql);
|
||||
List<NutMap> rows = listSql.getList(NutMap.class);
|
||||
rows.forEach(row -> {
|
||||
Object startTime = row.get("startTime");
|
||||
Object endTime = row.get("endTime");
|
||||
row.put("startTimeText", startTime == null ? "" : DateUtil.formatDateTime(DateUtil.date((java.util.Date) startTime)));
|
||||
row.put("endTimeText", endTime == null ? "" : DateUtil.formatDateTime(DateUtil.date((java.util.Date) endTime)));
|
||||
});
|
||||
return Result.success(new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, rows));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("课程学习基础信息")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public Result courseInfo(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("请选择课程");
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT c.id, c.courseName, c.courseIntro, c.startTime, c.endTime, c.openType, c.cover,
|
||||
c.recommendFlags, c.lecturerName, t.typeName AS courseTypeName
|
||||
FROM learning_course c
|
||||
LEFT JOIN learning_course_type t ON t.id = c.courseTypeId
|
||||
WHERE c.id = @id
|
||||
""");
|
||||
sql.params().set("id", id);
|
||||
sql.setCallback(Sqls.callback.map());
|
||||
dao.execute(sql);
|
||||
NutMap course = sql.getObject(NutMap.class);
|
||||
if (course == null) {
|
||||
return Result.error("课程不存在");
|
||||
}
|
||||
Object startTime = course.get("startTime");
|
||||
Object endTime = course.get("endTime");
|
||||
course.put("startTimeText", startTime == null ? "" : DateUtil.formatDateTime(DateUtil.date((java.util.Date) startTime)));
|
||||
course.put("endTimeText", endTime == null ? "" : DateUtil.formatDateTime(DateUtil.date((java.util.Date) endTime)));
|
||||
return Result.success(course);
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("课程学习安排")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public Result studyTree(String courseId) {
|
||||
if (StrUtil.isBlank(courseId)) {
|
||||
return Result.success(List.of());
|
||||
}
|
||||
List<LearningCourseOutline> outlines = dao.query(LearningCourseOutline.class, Cnd.where("courseId", "=", courseId).and("status", "=", "enabled").asc("sortOrder").asc("createdAt"));
|
||||
List<LearningOutlineResource> resources = dao.query(LearningOutlineResource.class, Cnd.where("courseId", "=", courseId).and("status", "=", "enabled").asc("sortOrder").asc("createdAt"));
|
||||
List<LearningStudyRecord> records = dao.query(LearningStudyRecord.class, Cnd.where("courseId", "=", courseId).and("userId", "=", SecurityUtil.getUserId()));
|
||||
Map<String, List<NutMap>> childrenMap = new HashMap<>();
|
||||
List<NutMap> roots = new ArrayList<>();
|
||||
Map<String, LearningStudyRecord> recordMap = new HashMap<>();
|
||||
for (LearningStudyRecord record : records) {
|
||||
recordMap.put(record.getOutlineId(), record);
|
||||
}
|
||||
|
||||
Map<String, NutMap> outlineNodeMap = new HashMap<>();
|
||||
for (LearningCourseOutline outline : outlines) {
|
||||
List<NutMap> children = new ArrayList<>();
|
||||
NutMap node = NutMap.NEW()
|
||||
.addv("id", outline.getId())
|
||||
.addv("type", "outline")
|
||||
.addv("nodeType", outline.getNodeType())
|
||||
.addv("title", outline.getTitle())
|
||||
.addv("required", outline.getRequired())
|
||||
.addv("progressPercent", recordMap.containsKey(outline.getId()) ? recordMap.get(outline.getId()).getProgressPercent() : 0)
|
||||
.addv("studySeconds", recordMap.containsKey(outline.getId()) ? recordMap.get(outline.getId()).getStudySeconds() : 0)
|
||||
.addv("lastPositionSeconds", recordMap.containsKey(outline.getId()) ? value(recordMap.get(outline.getId()).getLastPositionSeconds()) : 0)
|
||||
.addv("completeStatus", recordMap.containsKey(outline.getId()) ? recordMap.get(outline.getId()).getCompleteStatus() : "not_started")
|
||||
.addv("children", children);
|
||||
outlineNodeMap.put(outline.getId(), node);
|
||||
childrenMap.put(outline.getId(), children);
|
||||
}
|
||||
for (LearningCourseOutline outline : outlines) {
|
||||
NutMap node = outlineNodeMap.get(outline.getId());
|
||||
if (StrUtil.isBlank(outline.getParentId())) {
|
||||
roots.add(node);
|
||||
} else {
|
||||
childrenMap.computeIfAbsent(outline.getParentId(), key -> new ArrayList<>()).add(node);
|
||||
}
|
||||
}
|
||||
for (LearningOutlineResource resource : resources) {
|
||||
NutMap node = NutMap.NEW()
|
||||
.addv("id", resource.getId())
|
||||
.addv("type", "resource")
|
||||
.addv("outlineId", resource.getOutlineId())
|
||||
.addv("title", resource.getResourceTitle())
|
||||
.addv("resourceType", resource.getResourceType())
|
||||
.addv("fileExt", resource.getFileExt())
|
||||
.addv("fileData", resource.getFileData())
|
||||
.addv("durationSeconds", resource.getDurationSeconds())
|
||||
.addv("required", resource.getRequired())
|
||||
.addv("progressPercent", recordMap.containsKey(resource.getOutlineId()) ? recordMap.get(resource.getOutlineId()).getProgressPercent() : 0)
|
||||
.addv("studySeconds", recordMap.containsKey(resource.getOutlineId()) ? recordMap.get(resource.getOutlineId()).getStudySeconds() : 0)
|
||||
.addv("lastPositionSeconds", recordMap.containsKey(resource.getOutlineId()) ? value(recordMap.get(resource.getOutlineId()).getLastPositionSeconds()) : 0)
|
||||
.addv("completeStatus", recordMap.containsKey(resource.getOutlineId()) ? recordMap.get(resource.getOutlineId()).getCompleteStatus() : "not_started");
|
||||
childrenMap.computeIfAbsent(resource.getOutlineId(), key -> new ArrayList<>()).add(node);
|
||||
}
|
||||
return Result.success(roots);
|
||||
}
|
||||
|
||||
@At
|
||||
@Ok("void")
|
||||
@ApiOperation("PDF内联预览")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public void pdfPreview(String id, HttpServletResponse response) throws IOException {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
response.sendError(HttpServletResponse.SC_BAD_REQUEST, "文件ID不能为空");
|
||||
return;
|
||||
}
|
||||
Sys_file file = dao.fetch(Sys_file.class, id);
|
||||
if (file == null) {
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在");
|
||||
return;
|
||||
}
|
||||
byte[] bytes;
|
||||
if (SysFileEngineTypeEnum.MINIO.getValue().equals(file.getEngine())) {
|
||||
bytes = SysFileMinIoUtil.getFileBytes(file.getBucket(), file.getStoragePath());
|
||||
} else {
|
||||
File localFile = FileUtil.file(file.getStoragePath());
|
||||
if (!FileUtil.exist(localFile)) {
|
||||
response.sendError(HttpServletResponse.SC_NOT_FOUND, "文件不存在");
|
||||
return;
|
||||
}
|
||||
bytes = IoUtil.readBytes(FileUtil.getInputStream(localFile));
|
||||
}
|
||||
if (!"pdf".equalsIgnoreCase(file.getSuffix())) {
|
||||
File sourceFile = File.createTempFile("learning_preview_origin", "." + file.getSuffix());
|
||||
File pdfFile = File.createTempFile("learning_preview", ".pdf");
|
||||
try {
|
||||
Files.write(sourceFile.toPath(), bytes, StandardOpenOption.WRITE);
|
||||
OfficePlusUtil.convert(sourceFile.getPath(), pdfFile.getPath());
|
||||
if (!FileUtil.exist(pdfFile) || pdfFile.length() == 0) {
|
||||
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "文件转换PDF失败");
|
||||
return;
|
||||
}
|
||||
bytes = IoUtil.readBytes(FileUtil.getInputStream(pdfFile));
|
||||
} finally {
|
||||
FileUtil.del(sourceFile);
|
||||
FileUtil.del(pdfFile);
|
||||
}
|
||||
}
|
||||
String fileName = StrUtil.blankToDefault(FileUtil.mainName(file.getName()), "preview") + ".pdf";
|
||||
String encodedName = URLEncoder.encode(fileName, CharsetUtil.UTF_8).replace("+", "%20");
|
||||
response.setContentType("application/pdf");
|
||||
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, "inline; filename=\"preview.pdf\"; filename*=UTF-8''" + encodedName);
|
||||
response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(bytes.length));
|
||||
try (OutputStream out = response.getOutputStream()) {
|
||||
out.write(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("启用课程类型")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public Result courseTypes() {
|
||||
return Result.success(dao.query(LearningCourseType.class, Cnd.where("enabled", "=", true).asc("sortNum").asc("createdAt")));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("推荐标识")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public Result recommendOptions() {
|
||||
Sys_dict root = sysDictService.fetch(Cnd.where("code", "=", "学习教育").or("name", "=", "学习教育"));
|
||||
if (root == null) {
|
||||
return Result.success(List.of());
|
||||
}
|
||||
List<Sys_dict> firstLevel = sysDictService.query(Cnd.where("parentId", "=", Strings.sNull(root.getId())).and("disabled", "=", false).asc("location"));
|
||||
Sys_dict group = firstLevel.stream()
|
||||
.filter(dict -> "推荐标识".equals(dict.getName()) || "推荐标识".equals(dict.getCode()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (group == null) {
|
||||
return Result.success(firstLevel);
|
||||
}
|
||||
List<Sys_dict> children = sysDictService.query(Cnd.where("parentId", "=", Strings.sNull(group.getId())).and("disabled", "=", false).asc("location"));
|
||||
return Result.success(children.isEmpty() ? firstLevel : children);
|
||||
}
|
||||
|
||||
private int value(Integer value) {
|
||||
return value == null ? 0 : value;
|
||||
}
|
||||
}
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
package com.budwk.app.zhgh.learning.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
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.sys.models.Sys_dict;
|
||||
import com.budwk.app.sys.services.SysDictService;
|
||||
import com.budwk.app.zhgh.learning.models.LearningCourse;
|
||||
import com.budwk.app.zhgh.learning.models.LearningCourseOutline;
|
||||
import com.budwk.app.zhgh.learning.models.LearningCourseType;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.Strings;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@Api("学习教育平台-课程管理")
|
||||
@At("/platform/learning/course/manage")
|
||||
public class LearningCourseManageController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
@Inject
|
||||
private SysDictService sysDictService;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/Learning/courseManage/index.html")
|
||||
@SaCheckPermission("learning.course.manage")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("课程列表")
|
||||
@SaCheckPermission("learning.course.manage")
|
||||
public Result pageData(PageForm pageForm,
|
||||
String courseName,
|
||||
String courseTypeId,
|
||||
String startTime,
|
||||
String endTime,
|
||||
String lecturerName,
|
||||
String status,
|
||||
String recommendFlag) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and(Cnd.likeEX("c.courseName", courseName));
|
||||
cnd.andEX("c.courseTypeId", "=", courseTypeId);
|
||||
cnd.and(Cnd.likeEX("c.lecturerName", lecturerName));
|
||||
cnd.andEX("c.status", "=", status);
|
||||
if (StrUtil.isNotBlank(recommendFlag)) {
|
||||
cnd.and("c.recommendFlags", "like", "%" + recommendFlag + "%");
|
||||
}
|
||||
if (StrUtil.isNotBlank(startTime)) {
|
||||
cnd.and("c.startTime", ">=", DateUtil.parse(startTime));
|
||||
}
|
||||
if (StrUtil.isNotBlank(endTime)) {
|
||||
cnd.and("c.endTime", "<=", DateUtil.parse(endTime));
|
||||
}
|
||||
|
||||
String orderColumn = getOrderColumn(pageForm.getPageOrderName());
|
||||
String orderBy = "descending".equals(pageForm.getPageOrderBy()) ? "desc" : "asc";
|
||||
if (StrUtil.isBlank(orderColumn)) {
|
||||
orderColumn = "c.sortNum";
|
||||
orderBy = "asc";
|
||||
}
|
||||
|
||||
Sql countSql = Sqls.create("""
|
||||
SELECT COUNT(1)
|
||||
FROM learning_course c
|
||||
LEFT JOIN learning_course_type t ON t.id = c.courseTypeId
|
||||
$condition
|
||||
""");
|
||||
countSql.setCondition(cnd);
|
||||
countSql.setCallback(Sqls.callback.integer());
|
||||
dao.execute(countSql);
|
||||
int count = countSql.getInt();
|
||||
|
||||
Sql listSql = Sqls.create("""
|
||||
SELECT c.*, t.typeName AS courseTypeName
|
||||
FROM learning_course c
|
||||
LEFT JOIN learning_course_type t ON t.id = c.courseTypeId
|
||||
$condition
|
||||
ORDER BY $orderColumn $orderBy
|
||||
""");
|
||||
listSql.setCondition(cnd);
|
||||
listSql.setVar("orderColumn", orderColumn);
|
||||
listSql.setVar("orderBy", orderBy);
|
||||
listSql.setPager(dao.createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||
listSql.setCallback(Sqls.callback.maps());
|
||||
dao.execute(listSql);
|
||||
|
||||
Pagination<NutMap> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, listSql.getList(NutMap.class));
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("新增课程")
|
||||
@SaCheckPermission("learning.course.manage")
|
||||
@SLog(tag = "新增课程", msg = "课程名称:${args[0].courseName}")
|
||||
public Result doAdd(LearningCourse course) {
|
||||
Result checkResult = checkCourse(course, null);
|
||||
if (checkResult != null) {
|
||||
return checkResult;
|
||||
}
|
||||
if (StrUtil.isBlank(course.getStatus())) {
|
||||
course.setStatus("draft");
|
||||
}
|
||||
dao.insert(course);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("编辑课程")
|
||||
@SaCheckPermission("learning.course.manage")
|
||||
@SLog(tag = "编辑课程", msg = "课程ID:${args[0].id}")
|
||||
public Result doEdit(LearningCourse course) {
|
||||
if (StrUtil.isBlank(course.getId())) {
|
||||
return Result.error("请选择要编辑的数据");
|
||||
}
|
||||
Result checkResult = checkCourse(course, course.getId());
|
||||
if (checkResult != null) {
|
||||
return checkResult;
|
||||
}
|
||||
dao.updateIgnoreNull(course);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("删除课程")
|
||||
@SaCheckPermission("learning.course.manage")
|
||||
@SLog(tag = "删除课程", msg = "课程ID:${args[0]}")
|
||||
public Result doDelete(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("请选择要删除的数据");
|
||||
}
|
||||
int outlineCount = dao.count(LearningCourseOutline.class, Cnd.where("courseId", "=", id));
|
||||
if (outlineCount > 0) {
|
||||
return Result.error("该课程存在章节内容,请先删除章节内容后再删除课程");
|
||||
}
|
||||
dao.clear(LearningCourse.class, Cnd.where("id", "=", id));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("启用课程类型")
|
||||
@SaCheckPermission("learning.course.manage")
|
||||
public Result courseTypes() {
|
||||
return Result.success(dao.query(LearningCourseType.class, Cnd.where("enabled", "=", true).asc("sortNum").asc("createdAt")));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("学习教育数据字典")
|
||||
@SaCheckPermission("learning.course.manage")
|
||||
public Result learningDictOptions(String name) {
|
||||
Sys_dict root = sysDictService.fetch(Cnd.where("code", "=", "学习教育").or("name", "=", "学习教育"));
|
||||
if (root == null) {
|
||||
return Result.success(List.of());
|
||||
}
|
||||
List<Sys_dict> firstLevel = sysDictService.query(Cnd.where("parentId", "=", Strings.sNull(root.getId())).and("disabled", "=", false).asc("location"));
|
||||
if (StrUtil.isBlank(name)) {
|
||||
return Result.success(firstLevel);
|
||||
}
|
||||
Sys_dict group = firstLevel.stream()
|
||||
.filter(dict -> name.equals(dict.getName()) || name.equals(dict.getCode()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (group == null) {
|
||||
return Result.success(firstLevel);
|
||||
}
|
||||
List<Sys_dict> children = sysDictService.query(Cnd.where("parentId", "=", Strings.sNull(group.getId())).and("disabled", "=", false).asc("location"));
|
||||
return Result.success(children.isEmpty() ? firstLevel : children);
|
||||
}
|
||||
|
||||
private Result checkCourse(LearningCourse course, String excludeId) {
|
||||
if (course == null || StrUtil.isBlank(course.getCourseName())) {
|
||||
return Result.error("课程名称不能为空");
|
||||
}
|
||||
if (StrUtil.isBlank(course.getCourseTypeId())) {
|
||||
return Result.error("请选择课程类型");
|
||||
}
|
||||
if (StrUtil.isBlank(course.getLecturerName())) {
|
||||
return Result.error("授课讲师不能为空");
|
||||
}
|
||||
if (course.getSortNum() == null) {
|
||||
return Result.error("排序编码不能为空");
|
||||
}
|
||||
if (!"long_term".equals(course.getOpenType()) && (course.getStartTime() == null || course.getEndTime() == null)) {
|
||||
return Result.error("请选择开课时间");
|
||||
}
|
||||
Cnd cnd = Cnd.where("courseName", "=", course.getCourseName().trim());
|
||||
if (StrUtil.isNotBlank(excludeId)) {
|
||||
cnd.and("id", "!=", excludeId);
|
||||
}
|
||||
if (dao.count(LearningCourse.class, cnd) > 0) {
|
||||
return Result.error("课程名称已存在");
|
||||
}
|
||||
course.setCourseName(course.getCourseName().trim());
|
||||
if ("long_term".equals(course.getOpenType())) {
|
||||
course.setStartTime(null);
|
||||
course.setEndTime(null);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String getOrderColumn(String prop) {
|
||||
Map<String, String> columns = new HashMap<>();
|
||||
columns.put("courseName", "c.courseName");
|
||||
columns.put("courseTypeName", "t.typeName");
|
||||
columns.put("lecturerName", "c.lecturerName");
|
||||
columns.put("startTime", "c.startTime");
|
||||
columns.put("status", "c.status");
|
||||
columns.put("recommendFlags", "c.recommendFlags");
|
||||
columns.put("sortNum", "c.sortNum");
|
||||
return columns.get(prop);
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
package com.budwk.app.zhgh.learning.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.annotation.SLog;
|
||||
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.zhgh.learning.models.LearningCourseType;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.pager.Pager;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@Api("学习教育平台-课程类型设置")
|
||||
@At("/platform/learning/course/type")
|
||||
public class LearningCourseTypeController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/Learning/courseType/index.html")
|
||||
@SaCheckPermission("learning.course.type")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("课程类型列表")
|
||||
@SaCheckPermission("learning.course.type")
|
||||
public Result pageData(PageForm pageForm, @Param("typeName") String typeName) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and(Cnd.likeEX("typeName", typeName));
|
||||
cnd.asc("sortNum").asc("createdAt");
|
||||
int count = dao.count(LearningCourseType.class, cnd);
|
||||
Pager pager = dao.createPager(pageForm.getPageNumber(), pageForm.getPageSize());
|
||||
Pagination<LearningCourseType> pagination = new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), count, dao.query(LearningCourseType.class, cnd, pager));
|
||||
return Result.success(pagination);
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("新增课程类型")
|
||||
@SaCheckPermission("learning.course.type")
|
||||
@SLog(tag = "新增课程类型", msg = "课程类型名称:${args[0].typeName}")
|
||||
public Result doAdd(LearningCourseType courseType) {
|
||||
Result checkResult = checkCourseType(courseType, null);
|
||||
if (checkResult != null) {
|
||||
return checkResult;
|
||||
}
|
||||
if (courseType.getEnabled() == null) {
|
||||
courseType.setEnabled(true);
|
||||
}
|
||||
dao.insert(courseType);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("编辑课程类型")
|
||||
@SaCheckPermission("learning.course.type")
|
||||
@SLog(tag = "编辑课程类型", msg = "课程类型ID:${args[0].id}")
|
||||
public Result doEdit(LearningCourseType courseType) {
|
||||
if (StrUtil.isBlank(courseType.getId())) {
|
||||
return Result.error("请选择要编辑的数据");
|
||||
}
|
||||
Result checkResult = checkCourseType(courseType, courseType.getId());
|
||||
if (checkResult != null) {
|
||||
return checkResult;
|
||||
}
|
||||
dao.updateIgnoreNull(courseType);
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("删除课程类型")
|
||||
@SaCheckPermission("learning.course.type")
|
||||
@SLog(tag = "删除课程类型", msg = "课程类型ID:${args[0]}")
|
||||
public Result doDelete(String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("请选择要删除的数据");
|
||||
}
|
||||
dao.clear(LearningCourseType.class, Cnd.where("id", "=", id));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
private Result checkCourseType(LearningCourseType courseType, String excludeId) {
|
||||
if (courseType == null || StrUtil.isBlank(courseType.getTypeName())) {
|
||||
return Result.error("课程类型名称不能为空");
|
||||
}
|
||||
if (courseType.getSortNum() == null) {
|
||||
return Result.error("排序编号不能为空");
|
||||
}
|
||||
Cnd cnd = Cnd.where("typeName", "=", courseType.getTypeName().trim());
|
||||
if (StrUtil.isNotBlank(excludeId)) {
|
||||
cnd.and("id", "!=", excludeId);
|
||||
}
|
||||
if (dao.count(LearningCourseType.class, cnd) > 0) {
|
||||
return Result.error("课程类型名称已存在");
|
||||
}
|
||||
courseType.setTypeName(courseType.getTypeName().trim());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package com.budwk.app.zhgh.learning.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
@IocBean
|
||||
@At("/platform/learning")
|
||||
public class LearningPlatformController {
|
||||
|
||||
@At("/activity/manage")
|
||||
@Ok("beetl:/platform/zhgh/Learning/activityManage/index.html")
|
||||
@SaCheckPermission("learning.activity.manage")
|
||||
public void activityManage() {
|
||||
}
|
||||
|
||||
@At("/my/record")
|
||||
@Ok("beetl:/platform/zhgh/Learning/myRecord/index.html")
|
||||
@SaCheckPermission("learning.my.record")
|
||||
public void myRecord() {
|
||||
}
|
||||
|
||||
@At("/my/record/h5")
|
||||
@Ok("beetl:/platform/zhghh5/learning/myRecord/index.html")
|
||||
@SaCheckPermission("learning.my.record")
|
||||
public void myRecordH5() {
|
||||
}
|
||||
|
||||
@At("/statistics")
|
||||
@Ok("beetl:/platform/zhgh/Learning/statistics/index.html")
|
||||
@SaCheckPermission("learning.statistics")
|
||||
public void statistics() {
|
||||
}
|
||||
|
||||
@At("/course/h5")
|
||||
@Ok("beetl:/platform/zhghh5/learning/course/index.html")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public void courseH5() {
|
||||
}
|
||||
|
||||
@At("/course/h5/study")
|
||||
@Ok("beetl:/platform/zhghh5/learning/course/study.html")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public void courseH5Study() {
|
||||
}
|
||||
}
|
||||
+330
@@ -0,0 +1,330 @@
|
||||
package com.budwk.app.zhgh.learning.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
import com.budwk.app.base.page.Pagination;
|
||||
import com.budwk.app.base.result.Result;
|
||||
import com.budwk.app.base.utils.PageUtil;
|
||||
import com.budwk.app.sys.models.Sys_union;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.learning.models.LearningCourse;
|
||||
import com.budwk.app.zhgh.learning.param.LearningStatisticsPageForm;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.Static;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@Api("学习教育平台-学习统计")
|
||||
@At("/platform/learning/statistics")
|
||||
public class LearningStatisticsController {
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
@At("")
|
||||
@Ok("beetl:/platform/zhgh/Learning/statistics/index.html")
|
||||
@SaCheckPermission("learning.statistics")
|
||||
public void index() {
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("课程统计")
|
||||
@SaCheckPermission("learning.statistics")
|
||||
public Result coursePageData(LearningStatisticsPageForm pageForm) {
|
||||
Cnd cnd = courseCondition(pageForm);
|
||||
Sql countSql = Sqls.create("""
|
||||
SELECT COUNT(1)
|
||||
FROM learning_course c
|
||||
$condition
|
||||
""");
|
||||
countSql.setCondition(cnd);
|
||||
countSql.setCallback(Sqls.callback.integer());
|
||||
dao.execute(countSql);
|
||||
|
||||
Sql listSql = Sqls.create("""
|
||||
SELECT
|
||||
c.id AS courseId,
|
||||
c.courseName,
|
||||
IFNULL(COUNT(uc.userId), 0) AS learnerCount,
|
||||
IFNULL(SUM(CASE WHEN target.targetCount > 0 AND uc.completedOutlineCount >= target.targetCount THEN 1 ELSE 0 END), 0) AS completedCount,
|
||||
IFNULL(ROUND(AVG(uc.studySeconds)), 0) AS avgStudySeconds
|
||||
FROM learning_course c
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
courseId,
|
||||
CASE
|
||||
WHEN SUM(CASE WHEN required = 1 THEN 1 ELSE 0 END) > 0
|
||||
THEN SUM(CASE WHEN required = 1 THEN 1 ELSE 0 END)
|
||||
ELSE COUNT(1)
|
||||
END AS targetCount,
|
||||
SUM(CASE WHEN required = 1 THEN 1 ELSE 0 END) AS requiredCount
|
||||
FROM learning_course_outline
|
||||
WHERE status = 'enabled'
|
||||
GROUP BY courseId
|
||||
) target ON target.courseId = c.id
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
r.courseId,
|
||||
r.userId,
|
||||
SUM(IFNULL(r.studySeconds, 0)) AS studySeconds,
|
||||
COUNT(DISTINCT CASE
|
||||
WHEN r.completeStatus = 'completed'
|
||||
AND ((target.requiredCount > 0 AND o.required = 1) OR target.requiredCount = 0)
|
||||
THEN r.outlineId
|
||||
ELSE NULL
|
||||
END) AS completedOutlineCount
|
||||
FROM learning_study_record r
|
||||
JOIN learning_course_outline o ON o.id = r.outlineId AND o.courseId = r.courseId AND o.status = 'enabled'
|
||||
JOIN (
|
||||
SELECT
|
||||
courseId,
|
||||
CASE
|
||||
WHEN SUM(CASE WHEN required = 1 THEN 1 ELSE 0 END) > 0
|
||||
THEN SUM(CASE WHEN required = 1 THEN 1 ELSE 0 END)
|
||||
ELSE COUNT(1)
|
||||
END AS targetCount,
|
||||
SUM(CASE WHEN required = 1 THEN 1 ELSE 0 END) AS requiredCount
|
||||
FROM learning_course_outline
|
||||
WHERE status = 'enabled'
|
||||
GROUP BY courseId
|
||||
) target ON target.courseId = r.courseId
|
||||
$recordCondition
|
||||
GROUP BY r.courseId, r.userId
|
||||
) uc ON uc.courseId = c.id
|
||||
$condition
|
||||
GROUP BY c.id, c.courseName
|
||||
ORDER BY $orderColumn $orderBy
|
||||
""");
|
||||
listSql.setCondition(cnd);
|
||||
listSql.setVar("recordCondition", new Static(recordWhere(pageForm)));
|
||||
setOrder(listSql, pageForm, "course");
|
||||
listSql.setPager(dao.createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||
listSql.setCallback(Sqls.callback.maps());
|
||||
dao.execute(listSql);
|
||||
List<NutMap> rows = listSql.getList(NutMap.class);
|
||||
rows.forEach(row -> row.put("avgStudyTimeText", formatStudySeconds(row.getInt("avgStudySeconds", 0))));
|
||||
return Result.success(new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), countSql.getInt(), rows));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("分工会统计")
|
||||
@SaCheckPermission("learning.statistics")
|
||||
public Result unionPageData(LearningStatisticsPageForm pageForm) {
|
||||
Cnd cnd = unionCondition(pageForm);
|
||||
Sql countSql = Sqls.create("""
|
||||
SELECT COUNT(1)
|
||||
FROM learning_course c
|
||||
JOIN (
|
||||
SELECT r.courseId, r.unionId
|
||||
FROM learning_study_record r
|
||||
$recordCondition
|
||||
GROUP BY r.courseId, r.unionId
|
||||
) uc ON uc.courseId = c.id
|
||||
LEFT JOIN sys_union un ON un.id = uc.unionId
|
||||
$condition
|
||||
""");
|
||||
countSql.setCondition(cnd);
|
||||
countSql.setVar("recordCondition", new Static(recordWhere(pageForm)));
|
||||
countSql.setCallback(Sqls.callback.integer());
|
||||
dao.execute(countSql);
|
||||
|
||||
Sql listSql = Sqls.create("""
|
||||
SELECT
|
||||
c.id AS courseId,
|
||||
c.courseName,
|
||||
un.id AS unionId,
|
||||
IFNULL(un.name, uc.unionName) AS unionName,
|
||||
COUNT(uc.userId) AS learnerCount,
|
||||
IFNULL(SUM(uc.studySeconds), 0) AS studySeconds,
|
||||
IFNULL(SUM(CASE WHEN target.targetCount > 0 AND uc.completedOutlineCount >= target.targetCount THEN 1 ELSE 0 END), 0) AS completedCount
|
||||
FROM learning_course c
|
||||
JOIN (
|
||||
SELECT
|
||||
r.courseId,
|
||||
r.userId,
|
||||
r.unionId,
|
||||
MAX(r.unionName) AS unionName,
|
||||
SUM(IFNULL(r.studySeconds, 0)) AS studySeconds,
|
||||
COUNT(DISTINCT CASE
|
||||
WHEN r.completeStatus = 'completed'
|
||||
AND ((target.requiredCount > 0 AND o.required = 1) OR target.requiredCount = 0)
|
||||
THEN r.outlineId
|
||||
ELSE NULL
|
||||
END) AS completedOutlineCount
|
||||
FROM learning_study_record r
|
||||
JOIN learning_course_outline o ON o.id = r.outlineId AND o.courseId = r.courseId AND o.status = 'enabled'
|
||||
JOIN (
|
||||
SELECT
|
||||
courseId,
|
||||
CASE
|
||||
WHEN SUM(CASE WHEN required = 1 THEN 1 ELSE 0 END) > 0
|
||||
THEN SUM(CASE WHEN required = 1 THEN 1 ELSE 0 END)
|
||||
ELSE COUNT(1)
|
||||
END AS targetCount,
|
||||
SUM(CASE WHEN required = 1 THEN 1 ELSE 0 END) AS requiredCount
|
||||
FROM learning_course_outline
|
||||
WHERE status = 'enabled'
|
||||
GROUP BY courseId
|
||||
) target ON target.courseId = r.courseId
|
||||
$recordCondition
|
||||
GROUP BY r.courseId, r.userId, r.unionId
|
||||
) uc ON uc.courseId = c.id
|
||||
LEFT JOIN sys_union un ON un.id = uc.unionId
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
courseId,
|
||||
CASE
|
||||
WHEN SUM(CASE WHEN required = 1 THEN 1 ELSE 0 END) > 0
|
||||
THEN SUM(CASE WHEN required = 1 THEN 1 ELSE 0 END)
|
||||
ELSE COUNT(1)
|
||||
END AS targetCount,
|
||||
SUM(CASE WHEN required = 1 THEN 1 ELSE 0 END) AS requiredCount
|
||||
FROM learning_course_outline
|
||||
WHERE status = 'enabled'
|
||||
GROUP BY courseId
|
||||
) target ON target.courseId = c.id
|
||||
$condition
|
||||
GROUP BY c.id, c.courseName, uc.unionId, un.id, un.name
|
||||
ORDER BY $orderColumn $orderBy
|
||||
""");
|
||||
listSql.setCondition(cnd);
|
||||
listSql.setVar("recordCondition", new Static(recordWhere(pageForm)));
|
||||
setOrder(listSql, pageForm, "union");
|
||||
listSql.setPager(dao.createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||
listSql.setCallback(Sqls.callback.maps());
|
||||
dao.execute(listSql);
|
||||
List<NutMap> rows = listSql.getList(NutMap.class);
|
||||
rows.forEach(row -> {
|
||||
int learnerCount = row.getInt("learnerCount", 0);
|
||||
int completedCount = row.getInt("completedCount", 0);
|
||||
row.put("studyTimeText", formatStudySeconds(row.getInt("studySeconds", 0)));
|
||||
row.put("completeRate", learnerCount == 0 ? "0%" : BigDecimal.valueOf(completedCount * 100.0 / learnerCount).setScale(2, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString() + "%");
|
||||
});
|
||||
return Result.success(new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), countSql.getInt(), rows));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("可选课程")
|
||||
@SaCheckPermission("learning.statistics")
|
||||
public Result courseOptions(String courseName) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.and(Cnd.likeEX("courseName", courseName));
|
||||
cnd.desc("createdAt").asc("sortNum");
|
||||
return Result.success(dao.query(LearningCourse.class, cnd, dao.createPager(1, 50)));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("可选分工会")
|
||||
@SaCheckPermission("learning.statistics")
|
||||
public Result unionOptions() {
|
||||
if (hasSchoolScope()) {
|
||||
return Result.success(dao.query(Sys_union.class, Cnd.NEW().asc("unionCode").asc("name")));
|
||||
}
|
||||
if (hasBranchScope()) {
|
||||
return Result.success(dao.query(Sys_union.class, Cnd.where("id", "=", SecurityUtil.getUnionId())));
|
||||
}
|
||||
return Result.success(List.of());
|
||||
}
|
||||
|
||||
private Cnd courseCondition(LearningStatisticsPageForm pageForm) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
cnd.andEX("c.id", "=", pageForm.getCourseId());
|
||||
cnd.and(Cnd.likeEX("c.courseName", pageForm.getCourseName()));
|
||||
return cnd;
|
||||
}
|
||||
|
||||
private Cnd unionCondition(LearningStatisticsPageForm pageForm) {
|
||||
Cnd cnd = courseCondition(pageForm);
|
||||
cnd.andEX("uc.unionId", "=", pageForm.getUnionId());
|
||||
return cnd;
|
||||
}
|
||||
|
||||
private String recordWhere(LearningStatisticsPageForm pageForm) {
|
||||
StringBuilder where = new StringBuilder("WHERE 1 = 1");
|
||||
if (hasBranchScope()) {
|
||||
where.append(" AND r.unionId = '").append(escapeSql(SecurityUtil.getUnionId())).append("'");
|
||||
} else if (!hasSchoolScope()) {
|
||||
where.append(" AND r.userId = '").append(escapeSql(SecurityUtil.getUserId())).append("'");
|
||||
}
|
||||
if (StrUtil.isNotBlank(pageForm.getUnionId())) {
|
||||
where.append(" AND r.unionId = '").append(escapeSql(pageForm.getUnionId())).append("'");
|
||||
}
|
||||
return where.toString();
|
||||
}
|
||||
|
||||
private void setOrder(Sql sql, LearningStatisticsPageForm pageForm, String type) {
|
||||
String orderColumn = "union".equals(type) ? getUnionOrderColumn(pageForm.getPageOrderName()) : getCourseOrderColumn(pageForm.getPageOrderName());
|
||||
String orderBy = PageUtil.getOrder(pageForm.getPageOrderBy());
|
||||
if (StrUtil.isBlank(orderColumn)) {
|
||||
orderColumn = "union".equals(type) ? "c.courseName ASC, un.unionCode" : "c.sortNum ASC, c.createdAt";
|
||||
orderBy = "DESC";
|
||||
} else if (StrUtil.isBlank(orderBy)) {
|
||||
orderBy = "ASC";
|
||||
}
|
||||
sql.setVar("orderColumn", new Static(orderColumn));
|
||||
sql.setVar("orderBy", new Static(orderBy));
|
||||
}
|
||||
|
||||
private String getCourseOrderColumn(String prop) {
|
||||
Map<String, String> columns = new HashMap<>();
|
||||
columns.put("courseName", "c.courseName");
|
||||
columns.put("learnerCount", "learnerCount");
|
||||
columns.put("completedCount", "completedCount");
|
||||
columns.put("avgStudySeconds", "avgStudySeconds");
|
||||
return columns.get(prop);
|
||||
}
|
||||
|
||||
private String getUnionOrderColumn(String prop) {
|
||||
Map<String, String> columns = new HashMap<>();
|
||||
columns.put("courseName", "c.courseName");
|
||||
columns.put("unionName", "unionName");
|
||||
columns.put("learnerCount", "learnerCount");
|
||||
columns.put("studySeconds", "studySeconds");
|
||||
columns.put("completeRate", "completedCount");
|
||||
return columns.get(prop);
|
||||
}
|
||||
|
||||
private boolean hasSchoolScope() {
|
||||
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
|
||||
}
|
||||
|
||||
private boolean hasBranchScope() {
|
||||
return AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_ADMIN.name());
|
||||
}
|
||||
|
||||
private String escapeSql(String value) {
|
||||
return value == null ? "" : value.replace("'", "''");
|
||||
}
|
||||
|
||||
private String formatStudySeconds(Integer seconds) {
|
||||
int value = seconds == null ? 0 : seconds;
|
||||
int hour = value / 3600;
|
||||
int minute = value % 3600 / 60;
|
||||
int second = value % 60;
|
||||
if (hour > 0) {
|
||||
return hour + "小时" + minute + "分" + second + "秒";
|
||||
}
|
||||
if (minute > 0) {
|
||||
return minute + "分" + second + "秒";
|
||||
}
|
||||
return second + "秒";
|
||||
}
|
||||
}
|
||||
+493
@@ -0,0 +1,493 @@
|
||||
package com.budwk.app.zhgh.learning.controller;
|
||||
|
||||
import cn.dev33.satoken.annotation.SaCheckPermission;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.budwk.app.base.constant.RoleConstant;
|
||||
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.base.utils.PageUtil;
|
||||
import com.budwk.app.sys.models.Sys_union;
|
||||
import com.budwk.app.web.commons.auth.utils.AuthUtil;
|
||||
import com.budwk.app.web.commons.auth.utils.SecurityUtil;
|
||||
import com.budwk.app.zhgh.learning.models.LearningCourse;
|
||||
import com.budwk.app.zhgh.learning.models.LearningCourseOutline;
|
||||
import com.budwk.app.zhgh.learning.models.LearningOutlineResource;
|
||||
import com.budwk.app.zhgh.learning.models.LearningStudyRecord;
|
||||
import com.budwk.app.zhgh.learning.models.LearningStudyRule;
|
||||
import com.budwk.app.zhgh.learning.models.LearningStudySegment;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.nutz.dao.Chain;
|
||||
import org.nutz.dao.Cnd;
|
||||
import org.nutz.dao.Dao;
|
||||
import org.nutz.dao.Sqls;
|
||||
import org.nutz.dao.sql.Sql;
|
||||
import org.nutz.dao.util.cri.SqlExpressionGroup;
|
||||
import org.nutz.ioc.loader.annotation.Inject;
|
||||
import org.nutz.ioc.loader.annotation.IocBean;
|
||||
import org.nutz.lang.util.NutMap;
|
||||
import org.nutz.mvc.annotation.At;
|
||||
import org.nutz.mvc.annotation.Ok;
|
||||
import org.nutz.mvc.annotation.Param;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@IocBean
|
||||
@Ok("json:full")
|
||||
@Api("学习教育平台-学习记录")
|
||||
@At("/platform/learning/study/record")
|
||||
public class LearningStudyRecordController {
|
||||
|
||||
private static final int MAX_HEARTBEAT_SECONDS = 30;
|
||||
|
||||
@Inject
|
||||
private Dao dao;
|
||||
|
||||
@At
|
||||
@ApiOperation("学习记录分页")
|
||||
@SaCheckPermission("learning.my.record")
|
||||
public Result pageData(PageForm pageForm,
|
||||
@Param("keyword") String keyword,
|
||||
@Param("unionId") String unionId,
|
||||
@Param("courseId") String courseId,
|
||||
@Param("completeStatus") String completeStatus) {
|
||||
Cnd cnd = Cnd.NEW();
|
||||
if (StrUtil.isNotBlank(keyword)) {
|
||||
SqlExpressionGroup seg = new SqlExpressionGroup();
|
||||
seg.or("r.loginName", "like", "%" + keyword + "%");
|
||||
seg.or("r.userName", "like", "%" + keyword + "%");
|
||||
cnd.and(seg);
|
||||
}
|
||||
cnd.andEX("r.unionId", "=", unionId);
|
||||
cnd.andEX("r.courseId", "=", courseId);
|
||||
cnd.andEX("r.completeStatus", "=", completeStatus);
|
||||
appendScope(cnd);
|
||||
|
||||
String orderColumn = getOrderColumn(pageForm.getPageOrderName());
|
||||
if (StrUtil.isNotBlank(orderColumn) && StrUtil.isNotBlank(pageForm.getPageOrderBy())) {
|
||||
cnd.orderBy(orderColumn, PageUtil.getOrder(pageForm.getPageOrderBy()));
|
||||
} else {
|
||||
cnd.desc("r.latestStudyTime");
|
||||
}
|
||||
|
||||
Sql countSql = Sqls.create("SELECT COUNT(1) FROM learning_study_record r $condition");
|
||||
countSql.setCondition(cnd);
|
||||
countSql.setCallback(Sqls.callback.integer());
|
||||
dao.execute(countSql);
|
||||
|
||||
Sql listSql = Sqls.create("""
|
||||
SELECT r.*
|
||||
FROM learning_study_record r
|
||||
$condition
|
||||
""");
|
||||
listSql.setCondition(cnd);
|
||||
listSql.setPager(dao.createPager(pageForm.getPageNumber(), pageForm.getPageSize()));
|
||||
listSql.setCallback(Sqls.callback.maps());
|
||||
dao.execute(listSql);
|
||||
List<NutMap> rows = listSql.getList(NutMap.class);
|
||||
rows.forEach(row -> row.put("studyTimeText", formatStudySeconds(row.getInt("studySeconds", 0))));
|
||||
return Result.success(new Pagination<>(pageForm.getPageNumber(), pageForm.getPageSize(), countSql.getInt(), rows));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("移动端我的学习汇总")
|
||||
@SaCheckPermission("learning.my.record")
|
||||
public Result h5Summary() {
|
||||
String userId = SecurityUtil.getUserId();
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT IFNULL(SUM(IFNULL(studySeconds, 0)), 0) AS studySeconds,
|
||||
COUNT(DISTINCT courseId) AS courseCount,
|
||||
COUNT(DISTINCT CASE WHEN completeStatus = 'completed' THEN courseId END) AS completedCourseCount,
|
||||
COUNT(DISTINCT outlineId) AS outlineCount,
|
||||
COUNT(DISTINCT CASE WHEN completeStatus = 'completed' THEN outlineId END) AS completedOutlineCount
|
||||
FROM learning_study_record
|
||||
WHERE userId = @userId
|
||||
""");
|
||||
sql.params().set("userId", userId);
|
||||
sql.setCallback(Sqls.callback.map());
|
||||
dao.execute(sql);
|
||||
NutMap data = sql.getObject(NutMap.class);
|
||||
if (data == null) {
|
||||
data = NutMap.NEW();
|
||||
}
|
||||
int studySeconds = data.getInt("studySeconds", 0);
|
||||
data.put("studyHour", studySeconds / 3600);
|
||||
data.put("studyMinute", studySeconds % 3600 / 60);
|
||||
return Result.success(data);
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("移动端我的学习课程")
|
||||
@SaCheckPermission("learning.my.record")
|
||||
public Result h5Courses() {
|
||||
String userId = SecurityUtil.getUserId();
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT r.courseId,
|
||||
MAX(r.courseName) AS courseName,
|
||||
MAX(c.cover) AS cover,
|
||||
COUNT(1) AS outlineCount,
|
||||
SUM(CASE WHEN r.completeStatus = 'completed' THEN 1 ELSE 0 END) AS completedOutlineCount,
|
||||
IFNULL(SUM(IFNULL(r.studySeconds, 0)), 0) AS studySeconds,
|
||||
IFNULL(ROUND(AVG(IFNULL(r.progressPercent, 0))), 0) AS progressPercent,
|
||||
MAX(r.latestStudyTime) AS latestStudyTime
|
||||
FROM learning_study_record r
|
||||
LEFT JOIN learning_course c ON c.id = r.courseId
|
||||
WHERE r.userId = @userId
|
||||
GROUP BY r.courseId
|
||||
ORDER BY latestStudyTime DESC
|
||||
""");
|
||||
sql.params().set("userId", userId);
|
||||
sql.setCallback(Sqls.callback.maps());
|
||||
dao.execute(sql);
|
||||
List<NutMap> rows = sql.getList(NutMap.class);
|
||||
rows.forEach(row -> row.put("studyTimeText", formatStudySeconds(row.getInt("studySeconds", 0))));
|
||||
return Result.success(rows);
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("开始学习")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public Result start(@Param("courseId") String courseId,
|
||||
@Param("resourceId") String resourceId,
|
||||
@Param("positionSeconds") Integer positionSeconds) {
|
||||
if (StrUtil.hasBlank(courseId, resourceId)) {
|
||||
return Result.error("请选择课程资料后开始学习");
|
||||
}
|
||||
LearningCourse course = dao.fetch(LearningCourse.class, courseId);
|
||||
LearningOutlineResource resource = dao.fetch(LearningOutlineResource.class, resourceId);
|
||||
if (course == null || resource == null || !courseId.equals(resource.getCourseId())) {
|
||||
return Result.error("课程资料不存在");
|
||||
}
|
||||
LearningCourseOutline outline = dao.fetch(LearningCourseOutline.class, resource.getOutlineId());
|
||||
if (outline == null) {
|
||||
return Result.error("章节不存在");
|
||||
}
|
||||
|
||||
String userId = SecurityUtil.getUserId();
|
||||
Date now = new Date();
|
||||
closeUnfinishedSegments(userId, "interrupted");
|
||||
|
||||
LearningStudyRecord record = dao.fetch(LearningStudyRecord.class, Cnd.where("userId", "=", userId)
|
||||
.and("courseId", "=", courseId)
|
||||
.and("outlineId", "=", outline.getId()));
|
||||
if (record == null) {
|
||||
record = buildRecord(userId, course, outline, now);
|
||||
record.setLastPositionSeconds(sanitizePosition(positionSeconds, 0));
|
||||
dao.insert(record);
|
||||
} else {
|
||||
int requiredSeconds = requiredSeconds(outline.getId());
|
||||
dao.update(LearningStudyRecord.class, Chain.make("latestStudyTime", now)
|
||||
.add("courseName", course.getCourseName())
|
||||
.add("outlineName", outline.getTitle())
|
||||
.add("lastPositionSeconds", sanitizePosition(positionSeconds, record.getLastPositionSeconds()))
|
||||
.add("requiredSeconds", requiredSeconds)
|
||||
.add("progressPercent", progress(record.getStudySeconds(), requiredSeconds))
|
||||
.add("completeStatus", "completed".equals(record.getCompleteStatus()) ? "completed" : "studying"),
|
||||
Cnd.where("id", "=", record.getId()));
|
||||
record = dao.fetch(LearningStudyRecord.class, record.getId());
|
||||
}
|
||||
|
||||
LearningStudySegment segment = new LearningStudySegment();
|
||||
segment.setRecordId(record.getId());
|
||||
segment.setUserId(userId);
|
||||
segment.setCourseId(courseId);
|
||||
segment.setOutlineId(outline.getId());
|
||||
segment.setResourceId(resourceId);
|
||||
segment.setResourceName(resource.getResourceTitle());
|
||||
segment.setStartTime(now);
|
||||
segment.setLastHeartbeatTime(now);
|
||||
segment.setActiveSeconds(0);
|
||||
segment.setState("studying");
|
||||
dao.insert(segment);
|
||||
|
||||
return Result.success(NutMap.NEW()
|
||||
.addv("recordId", record.getId())
|
||||
.addv("segmentId", segment.getId())
|
||||
.addv("studySeconds", record.getStudySeconds())
|
||||
.addv("lastPositionSeconds", record.getLastPositionSeconds())
|
||||
.addv("requiredSeconds", record.getRequiredSeconds())
|
||||
.addv("progressPercent", record.getProgressPercent())
|
||||
.addv("completeStatus", record.getCompleteStatus()));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("学习心跳")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public Result heartbeat(@Param("segmentId") String segmentId,
|
||||
@Param("activeSeconds") Integer activeSeconds,
|
||||
@Param("positionSeconds") Integer positionSeconds) {
|
||||
LearningStudySegment segment = fetchOwnStudyingSegment(segmentId);
|
||||
if (segment == null) {
|
||||
return Result.error("学习时段已结束,请重新开始学习");
|
||||
}
|
||||
int seconds = Math.max(0, Math.min(activeSeconds == null ? 0 : activeSeconds, MAX_HEARTBEAT_SECONDS));
|
||||
return Result.success(addActiveSeconds(segment, seconds, false, positionSeconds));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("结束学习")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public Result finish(@Param("segmentId") String segmentId,
|
||||
@Param("activeSeconds") Integer activeSeconds,
|
||||
@Param("positionSeconds") Integer positionSeconds) {
|
||||
LearningStudySegment segment = fetchOwnStudyingSegment(segmentId);
|
||||
if (segment == null) {
|
||||
return Result.success();
|
||||
}
|
||||
int seconds = Math.max(0, Math.min(activeSeconds == null ? 0 : activeSeconds, MAX_HEARTBEAT_SECONDS));
|
||||
NutMap result = addActiveSeconds(segment, seconds, true, positionSeconds);
|
||||
dao.update(LearningStudySegment.class, Chain.make("endTime", new Date()).add("state", "finished"), Cnd.where("id", "=", segmentId));
|
||||
return Result.success(result);
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("保存播放位置")
|
||||
@SaCheckPermission("learning.course.display")
|
||||
public Result position(@Param("courseId") String courseId,
|
||||
@Param("resourceId") String resourceId,
|
||||
@Param("positionSeconds") Integer positionSeconds) {
|
||||
if (StrUtil.hasBlank(courseId, resourceId) || positionSeconds == null || positionSeconds < 0) {
|
||||
return Result.success();
|
||||
}
|
||||
LearningOutlineResource resource = dao.fetch(LearningOutlineResource.class, resourceId);
|
||||
if (resource == null || !courseId.equals(resource.getCourseId())) {
|
||||
return Result.success();
|
||||
}
|
||||
dao.update(LearningStudyRecord.class,
|
||||
Chain.make("lastPositionSeconds", positionSeconds).add("latestStudyTime", new Date()),
|
||||
Cnd.where("userId", "=", SecurityUtil.getUserId())
|
||||
.and("courseId", "=", courseId)
|
||||
.and("outlineId", "=", resource.getOutlineId()));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("可选课程")
|
||||
@SaCheckPermission("learning.my.record")
|
||||
public Result courseOptions() {
|
||||
return Result.success(dao.query(LearningCourse.class, Cnd.NEW().asc("sortNum").desc("createdAt")));
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("可选分工会")
|
||||
@SaCheckPermission("learning.my.record")
|
||||
public Result unionOptions() {
|
||||
if (hasSchoolScope()) {
|
||||
return Result.success(dao.query(Sys_union.class, Cnd.NEW().asc("unionCode").asc("name")));
|
||||
}
|
||||
if (hasBranchScope()) {
|
||||
return Result.success(dao.query(Sys_union.class, Cnd.where("id", "=", SecurityUtil.getUnionId())));
|
||||
}
|
||||
return Result.success(List.of());
|
||||
}
|
||||
|
||||
@At
|
||||
@ApiOperation("删除学习记录")
|
||||
@SaCheckPermission("learning.my.record")
|
||||
public Result delete(@Param("id") String id) {
|
||||
if (StrUtil.isBlank(id)) {
|
||||
return Result.error("请选择要删除的学习记录");
|
||||
}
|
||||
LearningStudyRecord record = dao.fetch(LearningStudyRecord.class, id);
|
||||
if (record == null) {
|
||||
return Result.error("学习记录不存在");
|
||||
}
|
||||
if (!canOperate(record)) {
|
||||
return Result.error("无权删除该学习记录");
|
||||
}
|
||||
dao.clear(LearningStudySegment.class, Cnd.where("recordId", "=", id));
|
||||
dao.clear(LearningStudyRecord.class, Cnd.where("id", "=", id));
|
||||
return Result.success();
|
||||
}
|
||||
|
||||
private LearningStudyRecord buildRecord(String userId, LearningCourse course, LearningCourseOutline outline, Date now) {
|
||||
NutMap user = currentUserInfo(userId);
|
||||
int requiredSeconds = requiredSeconds(outline.getId());
|
||||
LearningStudyRecord record = new LearningStudyRecord();
|
||||
record.setUserId(userId);
|
||||
record.setLoginName(user.getString("loginname", SecurityUtil.getUserLoginname()));
|
||||
record.setUserName(user.getString("username", SecurityUtil.getUserUsername()));
|
||||
record.setUnionId(user.getString("unionId", SecurityUtil.getUnionId()));
|
||||
record.setUnionName(user.getString("unionName", ""));
|
||||
record.setCourseId(course.getId());
|
||||
record.setCourseName(course.getCourseName());
|
||||
record.setOutlineId(outline.getId());
|
||||
record.setOutlineName(outline.getTitle());
|
||||
record.setFirstStudyTime(now);
|
||||
record.setLatestStudyTime(now);
|
||||
record.setStudySeconds(0);
|
||||
record.setLastPositionSeconds(0);
|
||||
record.setRequiredSeconds(requiredSeconds);
|
||||
record.setProgressPercent(0);
|
||||
record.setCompleteStatus("studying");
|
||||
return record;
|
||||
}
|
||||
|
||||
private NutMap addActiveSeconds(LearningStudySegment segment, int activeSeconds, boolean finish, Integer positionSeconds) {
|
||||
Date now = new Date();
|
||||
int segmentSeconds = value(segment.getActiveSeconds()) + activeSeconds;
|
||||
dao.update(LearningStudySegment.class, Chain.make("activeSeconds", segmentSeconds).add("lastHeartbeatTime", now), Cnd.where("id", "=", segment.getId()));
|
||||
|
||||
LearningStudyRecord record = dao.fetch(LearningStudyRecord.class, segment.getRecordId());
|
||||
int studySeconds = value(record.getStudySeconds()) + activeSeconds;
|
||||
int requiredSeconds = requiredSeconds(record.getOutlineId());
|
||||
int progressPercent = progress(studySeconds, requiredSeconds);
|
||||
String completeStatus = completeStatus(studySeconds, requiredSeconds, finish);
|
||||
if ("completed".equals(completeStatus)) {
|
||||
progressPercent = 100;
|
||||
}
|
||||
Chain chain = Chain.make("latestStudyTime", now)
|
||||
.add("studySeconds", studySeconds)
|
||||
.add("requiredSeconds", requiredSeconds)
|
||||
.add("progressPercent", progressPercent)
|
||||
.add("completeStatus", completeStatus);
|
||||
int lastPositionSeconds = value(record.getLastPositionSeconds());
|
||||
if (positionSeconds != null && positionSeconds >= 0) {
|
||||
lastPositionSeconds = positionSeconds;
|
||||
chain.add("lastPositionSeconds", positionSeconds);
|
||||
}
|
||||
if ("completed".equals(completeStatus) && record.getCompletedAt() == null) {
|
||||
chain.add("completedAt", now);
|
||||
}
|
||||
dao.update(LearningStudyRecord.class, chain, Cnd.where("id", "=", record.getId()));
|
||||
return NutMap.NEW()
|
||||
.addv("studySeconds", studySeconds)
|
||||
.addv("studyTimeText", formatStudySeconds(studySeconds))
|
||||
.addv("lastPositionSeconds", lastPositionSeconds)
|
||||
.addv("requiredSeconds", requiredSeconds)
|
||||
.addv("progressPercent", progressPercent)
|
||||
.addv("completeStatus", completeStatus);
|
||||
}
|
||||
|
||||
private LearningStudySegment fetchOwnStudyingSegment(String segmentId) {
|
||||
if (StrUtil.isBlank(segmentId)) {
|
||||
return null;
|
||||
}
|
||||
return dao.fetch(LearningStudySegment.class, Cnd.where("id", "=", segmentId)
|
||||
.and("userId", "=", SecurityUtil.getUserId())
|
||||
.and("state", "=", "studying"));
|
||||
}
|
||||
|
||||
private void closeUnfinishedSegments(String userId, String state) {
|
||||
dao.update(LearningStudySegment.class,
|
||||
Chain.make("endTime", new Date()).add("state", state),
|
||||
Cnd.where("userId", "=", userId).and("state", "=", "studying"));
|
||||
}
|
||||
|
||||
private int requiredSeconds(String outlineId) {
|
||||
LearningStudyRule rule = dao.fetch(LearningStudyRule.class, Cnd.where("targetType", "=", "outline")
|
||||
.and("targetId", "=", outlineId)
|
||||
.and("status", "=", "enabled"));
|
||||
if (rule != null && value(rule.getMinStudySeconds()) > 0) {
|
||||
return rule.getMinStudySeconds();
|
||||
}
|
||||
Sql sql = Sqls.create("""
|
||||
SELECT IFNULL(SUM(IFNULL(durationSeconds, 0)), 0)
|
||||
FROM learning_outline_resource
|
||||
WHERE outlineId = @outlineId AND status = 'enabled'
|
||||
""");
|
||||
sql.params().set("outlineId", outlineId);
|
||||
sql.setCallback(Sqls.callback.integer());
|
||||
dao.execute(sql);
|
||||
return Math.max(0, sql.getInt());
|
||||
}
|
||||
|
||||
private int progress(Integer studySeconds, int requiredSeconds) {
|
||||
if (requiredSeconds <= 0) {
|
||||
return 0;
|
||||
}
|
||||
return Math.min(100, (int) Math.floor(value(studySeconds) * 100.0 / requiredSeconds));
|
||||
}
|
||||
|
||||
private String completeStatus(Integer studySeconds, int requiredSeconds, boolean finish) {
|
||||
if (requiredSeconds <= 0) {
|
||||
return finish ? "completed" : "studying";
|
||||
}
|
||||
if (value(studySeconds) >= requiredSeconds) {
|
||||
return "completed";
|
||||
}
|
||||
return value(studySeconds) > 0 || finish ? "studying" : "not_started";
|
||||
}
|
||||
|
||||
private NutMap currentUserInfo(String userId) {
|
||||
Sql sql = Sqls.create("SELECT id, loginname, username, unionId AS unionId, unionName AS unionName FROM vw_user WHERE id = @id");
|
||||
sql.params().set("id", userId);
|
||||
sql.setCallback(Sqls.callback.map());
|
||||
dao.execute(sql);
|
||||
NutMap user = sql.getObject(NutMap.class);
|
||||
return user == null ? NutMap.NEW() : user;
|
||||
}
|
||||
|
||||
private void appendScope(Cnd cnd) {
|
||||
if (hasSchoolScope()) {
|
||||
return;
|
||||
}
|
||||
if (hasBranchScope()) {
|
||||
cnd.and("r.unionId", "=", SecurityUtil.getUnionId());
|
||||
return;
|
||||
}
|
||||
cnd.and("r.userId", "=", SecurityUtil.getUserId());
|
||||
}
|
||||
|
||||
private boolean hasSchoolScope() {
|
||||
return AuthUtil.hasRoleOr(RoleConstant.SYSADMIN.name(), RoleConstant.SCHOOL_UNION_ADMIN.name());
|
||||
}
|
||||
|
||||
private boolean hasBranchScope() {
|
||||
return AuthUtil.hasRoleOr(RoleConstant.BRANCH_UNION_ADMIN.name());
|
||||
}
|
||||
|
||||
private boolean canOperate(LearningStudyRecord record) {
|
||||
if (hasSchoolScope()) {
|
||||
return true;
|
||||
}
|
||||
if (hasBranchScope()) {
|
||||
return StrUtil.equals(record.getUnionId(), SecurityUtil.getUnionId());
|
||||
}
|
||||
return StrUtil.equals(record.getUserId(), SecurityUtil.getUserId());
|
||||
}
|
||||
|
||||
private String getOrderColumn(String prop) {
|
||||
Map<String, String> columns = new HashMap<>();
|
||||
columns.put("loginName", "r.loginName");
|
||||
columns.put("userName", "r.userName");
|
||||
columns.put("unionName", "r.unionName");
|
||||
columns.put("courseName", "r.courseName");
|
||||
columns.put("outlineName", "r.outlineName");
|
||||
columns.put("firstStudyTime", "r.firstStudyTime");
|
||||
columns.put("latestStudyTime", "r.latestStudyTime");
|
||||
columns.put("studySeconds", "r.studySeconds");
|
||||
columns.put("progressPercent", "r.progressPercent");
|
||||
columns.put("completeStatus", "r.completeStatus");
|
||||
return columns.get(prop);
|
||||
}
|
||||
|
||||
private String formatStudySeconds(Integer seconds) {
|
||||
int value = value(seconds);
|
||||
int hour = value / 3600;
|
||||
int minute = value % 3600 / 60;
|
||||
int second = value % 60;
|
||||
if (hour > 0) {
|
||||
return hour + "小时" + minute + "分" + second + "秒";
|
||||
}
|
||||
if (minute > 0) {
|
||||
return minute + "分" + second + "秒";
|
||||
}
|
||||
return second + "秒";
|
||||
}
|
||||
|
||||
private int value(Integer value) {
|
||||
return value == null ? 0 : value;
|
||||
}
|
||||
|
||||
private int sanitizePosition(Integer positionSeconds, Integer defaultValue) {
|
||||
if (positionSeconds == null || positionSeconds < 0) {
|
||||
return value(defaultValue);
|
||||
}
|
||||
return positionSeconds;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
package com.budwk.app.zhgh.learning.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.ColDefine;
|
||||
import org.nutz.dao.entity.annotation.ColType;
|
||||
import org.nutz.dao.entity.annotation.Column;
|
||||
import org.nutz.dao.entity.annotation.Comment;
|
||||
import org.nutz.dao.entity.annotation.Name;
|
||||
import org.nutz.dao.entity.annotation.Table;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@Table
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class LearningCourse extends BaseModel {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("课程名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String courseName;
|
||||
|
||||
@Column
|
||||
@Comment("课程类型")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String courseTypeId;
|
||||
|
||||
@Column
|
||||
@Comment("授课讲师")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String lecturerName;
|
||||
|
||||
@Column
|
||||
@Comment("讲师基本信息")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 500)
|
||||
private String lecturerInfo;
|
||||
|
||||
@Column
|
||||
@Comment("课程简介")
|
||||
@ColDefine(customType = "longtext")
|
||||
private String courseIntro;
|
||||
|
||||
@Column
|
||||
@Comment("适合人群")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 500)
|
||||
private String suitablePeople;
|
||||
|
||||
@Column
|
||||
@Comment("学习目标")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 500)
|
||||
private String learningGoal;
|
||||
|
||||
@Column
|
||||
@Comment("课程周期")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String coursePeriod;
|
||||
|
||||
@Column
|
||||
@Comment("开课类型")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String openType;
|
||||
|
||||
@Column
|
||||
@Comment("开始时间")
|
||||
@ColDefine(type = ColType.DATETIME)
|
||||
private Date startTime;
|
||||
|
||||
@Column
|
||||
@Comment("结束时间")
|
||||
@ColDefine(type = ColType.DATETIME)
|
||||
private Date endTime;
|
||||
|
||||
@Column
|
||||
@Comment("课程状态")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String status;
|
||||
|
||||
@Column
|
||||
@Comment("推荐标识")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 500)
|
||||
private String recommendFlags;
|
||||
|
||||
@Column
|
||||
@Comment("课程标签")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 500)
|
||||
private String courseTags;
|
||||
|
||||
@Column
|
||||
@Comment("学习对象")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String targetType;
|
||||
|
||||
@Column
|
||||
@Comment("指定组织")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 500)
|
||||
private String targetOrgText;
|
||||
|
||||
@Column
|
||||
@Comment("排序编码")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer sortNum;
|
||||
|
||||
@Column
|
||||
@Comment("课程封面")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 500)
|
||||
private String cover;
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.budwk.app.zhgh.learning.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.ColDefine;
|
||||
import org.nutz.dao.entity.annotation.ColType;
|
||||
import org.nutz.dao.entity.annotation.Column;
|
||||
import org.nutz.dao.entity.annotation.Comment;
|
||||
import org.nutz.dao.entity.annotation.Name;
|
||||
import org.nutz.dao.entity.annotation.Table;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
@Data
|
||||
@Table
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class LearningCourseOutline extends BaseModel {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("所属课程ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String courseId;
|
||||
|
||||
@Column
|
||||
@Comment("所属课程名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String courseName;
|
||||
|
||||
@Column
|
||||
@Comment("父节点ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String parentId;
|
||||
|
||||
@Column
|
||||
@Comment("节点类型")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String nodeType;
|
||||
|
||||
@Column
|
||||
@Comment("章/节标题")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String title;
|
||||
|
||||
@Column
|
||||
@Comment("副标题")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 200)
|
||||
private String subtitle;
|
||||
|
||||
@Column
|
||||
@Comment("简介")
|
||||
@ColDefine(customType = "longtext")
|
||||
private String description;
|
||||
|
||||
@Column
|
||||
@Comment("排序值")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer sortOrder;
|
||||
|
||||
@Column
|
||||
@Comment("是否必学")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
private Boolean required;
|
||||
|
||||
@Column
|
||||
@Comment("状态")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.budwk.app.zhgh.learning.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.ColDefine;
|
||||
import org.nutz.dao.entity.annotation.ColType;
|
||||
import org.nutz.dao.entity.annotation.Column;
|
||||
import org.nutz.dao.entity.annotation.Comment;
|
||||
import org.nutz.dao.entity.annotation.Name;
|
||||
import org.nutz.dao.entity.annotation.Table;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
@Data
|
||||
@Table
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class LearningCourseType extends BaseModel {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("课程类型名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String typeName;
|
||||
|
||||
@Column
|
||||
@Comment("排序编号")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer sortNum;
|
||||
|
||||
@Column
|
||||
@Comment("是否启用")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
private Boolean enabled;
|
||||
|
||||
@Column
|
||||
@Comment("备注")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 500)
|
||||
private String remark;
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
package com.budwk.app.zhgh.learning.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.ColDefine;
|
||||
import org.nutz.dao.entity.annotation.ColType;
|
||||
import org.nutz.dao.entity.annotation.Column;
|
||||
import org.nutz.dao.entity.annotation.Comment;
|
||||
import org.nutz.dao.entity.annotation.Name;
|
||||
import org.nutz.dao.entity.annotation.Table;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
@Data
|
||||
@Table
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class LearningOutlineResource extends BaseModel {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("所属课程ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String courseId;
|
||||
|
||||
@Column
|
||||
@Comment("所属章/节ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String outlineId;
|
||||
|
||||
@Column
|
||||
@Comment("资料标题")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String resourceTitle;
|
||||
|
||||
@Column
|
||||
@Comment("资料类型")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String resourceType;
|
||||
|
||||
@Column
|
||||
@Comment("文件扩展名")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String fileExt;
|
||||
|
||||
@Column
|
||||
@Comment("附件")
|
||||
@ColDefine(customType = "longtext")
|
||||
private String fileData;
|
||||
|
||||
@Column
|
||||
@Comment("视频/音频时长")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer durationSeconds;
|
||||
|
||||
@Column
|
||||
@Comment("排序值")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer sortOrder;
|
||||
|
||||
@Column
|
||||
@Comment("是否必学")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
private Boolean required;
|
||||
|
||||
@Column
|
||||
@Comment("是否允许预览")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
private Boolean allowPreview;
|
||||
|
||||
@Column
|
||||
@Comment("是否允许下载")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
private Boolean allowDownload;
|
||||
|
||||
@Column
|
||||
@Comment("状态")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package com.budwk.app.zhgh.learning.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.ColDefine;
|
||||
import org.nutz.dao.entity.annotation.ColType;
|
||||
import org.nutz.dao.entity.annotation.Column;
|
||||
import org.nutz.dao.entity.annotation.Comment;
|
||||
import org.nutz.dao.entity.annotation.Index;
|
||||
import org.nutz.dao.entity.annotation.Name;
|
||||
import org.nutz.dao.entity.annotation.Table;
|
||||
import org.nutz.dao.entity.annotation.TableIndexes;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@Table("learning_study_record")
|
||||
@TableIndexes({
|
||||
@Index(name = "idx_learning_record_user_course_outline", fields = {"userId", "courseId", "outlineId"}, unique = true),
|
||||
@Index(name = "idx_learning_record_union", fields = {"unionId"}, unique = false),
|
||||
@Index(name = "idx_learning_record_course", fields = {"courseId"}, unique = false)
|
||||
})
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class LearningStudyRecord extends BaseModel {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("学习人ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String userId;
|
||||
|
||||
@Column
|
||||
@Comment("工号")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 120)
|
||||
private String loginName;
|
||||
|
||||
@Column
|
||||
@Comment("姓名")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String userName;
|
||||
|
||||
@Column
|
||||
@Comment("所属分工会ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String unionId;
|
||||
|
||||
@Column
|
||||
@Comment("所属分工会")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String unionName;
|
||||
|
||||
@Column
|
||||
@Comment("课程ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String courseId;
|
||||
|
||||
@Column
|
||||
@Comment("课程名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String courseName;
|
||||
|
||||
@Column
|
||||
@Comment("章节ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String outlineId;
|
||||
|
||||
@Column
|
||||
@Comment("章节名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String outlineName;
|
||||
|
||||
@Column
|
||||
@Comment("第一次进入时间")
|
||||
@ColDefine(type = ColType.DATETIME)
|
||||
private Date firstStudyTime;
|
||||
|
||||
@Column
|
||||
@Comment("最近学习时间")
|
||||
@ColDefine(type = ColType.DATETIME)
|
||||
private Date latestStudyTime;
|
||||
|
||||
@Column
|
||||
@Comment("累计有效学习时长(秒)")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer studySeconds;
|
||||
|
||||
@Column
|
||||
@Comment("最近播放位置(秒)")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer lastPositionSeconds;
|
||||
|
||||
@Column
|
||||
@Comment("要求学习时长(秒)")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer requiredSeconds;
|
||||
|
||||
@Column
|
||||
@Comment("学习进度")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer progressPercent;
|
||||
|
||||
@Column
|
||||
@Comment("完成状态")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String completeStatus;
|
||||
|
||||
@Column
|
||||
@Comment("完成时间")
|
||||
@ColDefine(type = ColType.DATETIME)
|
||||
private Date completedAt;
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package com.budwk.app.zhgh.learning.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.ColDefine;
|
||||
import org.nutz.dao.entity.annotation.ColType;
|
||||
import org.nutz.dao.entity.annotation.Column;
|
||||
import org.nutz.dao.entity.annotation.Comment;
|
||||
import org.nutz.dao.entity.annotation.Name;
|
||||
import org.nutz.dao.entity.annotation.Table;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
@Data
|
||||
@Table
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class LearningStudyRule extends BaseModel {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("所属课程ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String courseId;
|
||||
|
||||
@Column
|
||||
@Comment("规则对象类型")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String targetType;
|
||||
|
||||
@Column
|
||||
@Comment("规则对象ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String targetId;
|
||||
|
||||
@Column
|
||||
@Comment("是否必学")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
private Boolean required;
|
||||
|
||||
@Column
|
||||
@Comment("学习方式")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String studyMode;
|
||||
|
||||
@Column
|
||||
@Comment("完成规则")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 50)
|
||||
private String completionRule;
|
||||
|
||||
@Column
|
||||
@Comment("完成比例")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer completePercent;
|
||||
|
||||
@Column
|
||||
@Comment("最少学习时长")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer minStudySeconds;
|
||||
|
||||
@Column
|
||||
@Comment("解锁规则")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String unlockRule;
|
||||
|
||||
@Column
|
||||
@Comment("是否允许跳过")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
private Boolean allowSkip;
|
||||
|
||||
@Column
|
||||
@Comment("是否允许拖动")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
private Boolean allowDrag;
|
||||
|
||||
@Column
|
||||
@Comment("暂停是否计时")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
private Boolean pauseCountTime;
|
||||
|
||||
@Column
|
||||
@Comment("页面隐藏是否计时")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
private Boolean hiddenCountTime;
|
||||
|
||||
@Column
|
||||
@Comment("长时间无操作是否计时")
|
||||
@ColDefine(type = ColType.BOOLEAN)
|
||||
private Boolean inactiveCountTime;
|
||||
|
||||
@Column
|
||||
@Comment("状态")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String status;
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package com.budwk.app.zhgh.learning.models;
|
||||
|
||||
import com.budwk.app.base.model.BaseModel;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.nutz.dao.entity.annotation.ColDefine;
|
||||
import org.nutz.dao.entity.annotation.ColType;
|
||||
import org.nutz.dao.entity.annotation.Column;
|
||||
import org.nutz.dao.entity.annotation.Comment;
|
||||
import org.nutz.dao.entity.annotation.Index;
|
||||
import org.nutz.dao.entity.annotation.Name;
|
||||
import org.nutz.dao.entity.annotation.Table;
|
||||
import org.nutz.dao.entity.annotation.TableIndexes;
|
||||
import org.nutz.dao.interceptor.annotation.PrevInsert;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@Data
|
||||
@Table("learning_study_segment")
|
||||
@TableIndexes({
|
||||
@Index(name = "idx_learning_segment_record", fields = {"recordId"}, unique = false),
|
||||
@Index(name = "idx_learning_segment_user_state", fields = {"userId", "state"}, unique = false)
|
||||
})
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class LearningStudySegment extends BaseModel {
|
||||
|
||||
@Column
|
||||
@Name
|
||||
@Comment("ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
@PrevInsert(uu32 = true)
|
||||
private String id;
|
||||
|
||||
@Column
|
||||
@Comment("学习记录ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String recordId;
|
||||
|
||||
@Column
|
||||
@Comment("学习人ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String userId;
|
||||
|
||||
@Column
|
||||
@Comment("课程ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String courseId;
|
||||
|
||||
@Column
|
||||
@Comment("章节ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String outlineId;
|
||||
|
||||
@Column
|
||||
@Comment("资源ID")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 32)
|
||||
private String resourceId;
|
||||
|
||||
@Column
|
||||
@Comment("资源名称")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 100)
|
||||
private String resourceName;
|
||||
|
||||
@Column
|
||||
@Comment("开始时间")
|
||||
@ColDefine(type = ColType.DATETIME)
|
||||
private Date startTime;
|
||||
|
||||
@Column
|
||||
@Comment("结束时间")
|
||||
@ColDefine(type = ColType.DATETIME)
|
||||
private Date endTime;
|
||||
|
||||
@Column
|
||||
@Comment("最近心跳时间")
|
||||
@ColDefine(type = ColType.DATETIME)
|
||||
private Date lastHeartbeatTime;
|
||||
|
||||
@Column
|
||||
@Comment("本时段有效学习时长(秒)")
|
||||
@ColDefine(type = ColType.INT)
|
||||
private Integer activeSeconds;
|
||||
|
||||
@Column
|
||||
@Comment("状态")
|
||||
@ColDefine(type = ColType.VARCHAR, width = 20)
|
||||
private String state;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.budwk.app.zhgh.learning.param;
|
||||
|
||||
import com.budwk.app.base.param.PageForm;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
@Data
|
||||
public class LearningStatisticsPageForm extends PageForm {
|
||||
|
||||
private String courseId;
|
||||
|
||||
private String courseName;
|
||||
|
||||
private String unionId;
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 92 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 67 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 56 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.0 KiB |
@@ -0,0 +1,439 @@
|
||||
;(function (window, $) {
|
||||
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition
|
||||
const commandWords = [
|
||||
"打开",
|
||||
"进入",
|
||||
"跳转",
|
||||
"跳到",
|
||||
"去",
|
||||
"访问",
|
||||
"帮我",
|
||||
"请",
|
||||
"页面",
|
||||
"菜单",
|
||||
"一下"
|
||||
]
|
||||
|
||||
const state = {
|
||||
recognition: null,
|
||||
listening: false,
|
||||
menus: null,
|
||||
parentMap: {},
|
||||
lastText: "",
|
||||
pendingMatches: [],
|
||||
awaitingChoice: false,
|
||||
afterRecognitionEnd: null
|
||||
}
|
||||
|
||||
function normalizePlain(text) {
|
||||
return String(text || "")
|
||||
.toLowerCase()
|
||||
.replace(/[,。!?、,.!?;;::\s]/g, "")
|
||||
}
|
||||
|
||||
function normalizeCommand(text) {
|
||||
return normalizePlain(text)
|
||||
.replace(new RegExp(commandWords.join("|"), "g"), "")
|
||||
}
|
||||
|
||||
function notify(message, type) {
|
||||
if (window.ELEMENT && ELEMENT.Message) {
|
||||
ELEMENT.Message({message, type: type || "info"})
|
||||
return
|
||||
}
|
||||
window.alert(message)
|
||||
}
|
||||
|
||||
function speak(message, onEnd) {
|
||||
if (!window.speechSynthesis || !window.SpeechSynthesisUtterance) {
|
||||
if (typeof onEnd === "function") {
|
||||
onEnd()
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
window.speechSynthesis.cancel()
|
||||
const utterance = new SpeechSynthesisUtterance(message)
|
||||
let ended = false
|
||||
function finish() {
|
||||
if (ended) {
|
||||
return
|
||||
}
|
||||
ended = true
|
||||
if (typeof onEnd === "function") {
|
||||
onEnd()
|
||||
}
|
||||
}
|
||||
|
||||
utterance.lang = "zh-CN"
|
||||
utterance.rate = 1
|
||||
utterance.volume = 1
|
||||
utterance.onend = function () {
|
||||
finish()
|
||||
}
|
||||
utterance.onerror = function () {
|
||||
finish()
|
||||
}
|
||||
window.speechSynthesis.speak(utterance)
|
||||
setTimeout(finish, Math.max(2500, message.length * 220))
|
||||
}
|
||||
|
||||
function runAfterRecognitionEnd(callback) {
|
||||
state.afterRecognitionEnd = callback
|
||||
if (!state.listening && typeof state.afterRecognitionEnd === "function") {
|
||||
const next = state.afterRecognitionEnd
|
||||
state.afterRecognitionEnd = null
|
||||
setTimeout(next, 150)
|
||||
}
|
||||
}
|
||||
|
||||
function escapeHtml(text) {
|
||||
return String(text || "")
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """)
|
||||
.replace(/'/g, "'")
|
||||
}
|
||||
|
||||
function flattenMenus(menus, parentId, result) {
|
||||
result = result || []
|
||||
;(menus || []).forEach(function (menu) {
|
||||
const id = menu.id
|
||||
const realParentId = menu.parentId || parentId || ""
|
||||
if (id) {
|
||||
state.parentMap[id] = realParentId
|
||||
}
|
||||
result.push(menu)
|
||||
if (menu.children && menu.children.length) {
|
||||
flattenMenus(menu.children, id, result)
|
||||
}
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
function getStoreMenus() {
|
||||
try {
|
||||
return window.store && window.store.state && window.store.state.user && window.store.state.user.menus
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function getSessionMenus() {
|
||||
try {
|
||||
return JSON.parse(window.sessionStorage.getItem("zhgh_sub_app_menus") || "[]")
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
function getMenus() {
|
||||
const cached = state.menus
|
||||
if (cached && cached.length) {
|
||||
return $.Deferred().resolve(cached).promise()
|
||||
}
|
||||
|
||||
const storeMenus = getStoreMenus()
|
||||
if (storeMenus && storeMenus.length) {
|
||||
state.menus = flattenMenus(storeMenus, "", []).filter(function (menu) {
|
||||
return menu.href
|
||||
})
|
||||
return $.Deferred().resolve(state.menus).promise()
|
||||
}
|
||||
|
||||
return $.get("/platform/sys/user/getLogonUser").then(function (res) {
|
||||
if (res && res.code === 0 && res.data && res.data.menus) {
|
||||
state.parentMap = {}
|
||||
state.menus = flattenMenus(res.data.menus, "", []).filter(function (menu) {
|
||||
return menu.href
|
||||
})
|
||||
return state.menus
|
||||
}
|
||||
const sessionMenus = getSessionMenus()
|
||||
if (sessionMenus && sessionMenus.length) {
|
||||
state.menus = flattenMenus(sessionMenus, "", []).filter(function (menu) {
|
||||
return menu.href
|
||||
})
|
||||
return state.menus
|
||||
}
|
||||
return []
|
||||
}, function () {
|
||||
const sessionMenus = getSessionMenus()
|
||||
if (sessionMenus && sessionMenus.length) {
|
||||
state.menus = flattenMenus(sessionMenus, "", []).filter(function (menu) {
|
||||
return menu.href
|
||||
})
|
||||
return state.menus
|
||||
}
|
||||
return []
|
||||
})
|
||||
}
|
||||
|
||||
function scoreMenu(menu, rawText) {
|
||||
const query = normalizeCommand(rawText)
|
||||
const rawQuery = normalizePlain(rawText)
|
||||
const name = normalizePlain(menu.name)
|
||||
const aliasName = normalizePlain(menu.aliasName)
|
||||
const href = normalizePlain(menu.href)
|
||||
const permission = normalizePlain(menu.permission)
|
||||
|
||||
if (!query || !name) {
|
||||
return 0
|
||||
}
|
||||
if (query === name || rawQuery === name || query === aliasName || rawQuery === aliasName) {
|
||||
return 100
|
||||
}
|
||||
if (name.indexOf(query) > -1 || name.indexOf(rawQuery) > -1 || aliasName.indexOf(query) > -1 || aliasName.indexOf(rawQuery) > -1) {
|
||||
return 80
|
||||
}
|
||||
if (query.indexOf(name) > -1 || rawQuery.indexOf(name) > -1 || (aliasName && (query.indexOf(aliasName) > -1 || rawQuery.indexOf(aliasName) > -1))) {
|
||||
return 70
|
||||
}
|
||||
if (href.indexOf(query) > -1 || permission.indexOf(query) > -1) {
|
||||
return 45
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
function matchMenus(text, menus) {
|
||||
return (menus || [])
|
||||
.map(function (menu) {
|
||||
return {
|
||||
menu,
|
||||
score: scoreMenu(menu, text)
|
||||
}
|
||||
})
|
||||
.filter(function (item) {
|
||||
return item.score > 0
|
||||
})
|
||||
.sort(function (a, b) {
|
||||
if (b.score !== a.score) {
|
||||
return b.score - a.score
|
||||
}
|
||||
return String(a.menu.name || "").length - String(b.menu.name || "").length
|
||||
})
|
||||
.slice(0, 5)
|
||||
}
|
||||
|
||||
function getRootMenuId(menu) {
|
||||
let id = menu.id
|
||||
let parentId = state.parentMap[id] || menu.parentId
|
||||
while (parentId) {
|
||||
id = parentId
|
||||
parentId = state.parentMap[id]
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
function getCurrentSubAppId() {
|
||||
try {
|
||||
const app = JSON.parse(window.sessionStorage.getItem("zhgh_sub_app") || "{}")
|
||||
return app.id
|
||||
} catch (e) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function openMenu(menu) {
|
||||
state.pendingMatches = []
|
||||
state.awaitingChoice = false
|
||||
if (!menu || !menu.href) {
|
||||
notify("该菜单没有配置可打开的地址", "warning")
|
||||
return
|
||||
}
|
||||
|
||||
const targetRootId = getRootMenuId(menu)
|
||||
const currentSubAppId = getCurrentSubAppId()
|
||||
const hasSubAppContainer = $("#sub-app-container-main-content-body").length > 0
|
||||
|
||||
if (hasSubAppContainer && currentSubAppId && currentSubAppId === targetRootId && typeof commonUtil !== "undefined" && commonUtil.pjaxPush) {
|
||||
commonUtil.pjaxPush(menu.href)
|
||||
return
|
||||
}
|
||||
window.location.href = menu.href
|
||||
}
|
||||
|
||||
function showCandidateMessage(matches) {
|
||||
const items = matches
|
||||
.map(function (item, index) {
|
||||
const href = item.menu.href ? " <span style=\"color:#909399\">" + escapeHtml(item.menu.href) + "</span>" : ""
|
||||
return "<p style=\"margin:6px 0\">" + (index + 1) + ". " + escapeHtml(item.menu.name) + href + "</p>"
|
||||
})
|
||||
.join("")
|
||||
|
||||
if (window.ELEMENT && ELEMENT.MessageBox) {
|
||||
ELEMENT.MessageBox.alert(items, "找到多个菜单,请说“打开第几个”", {
|
||||
dangerouslyUseHTMLString: true,
|
||||
confirmButtonText: "知道了",
|
||||
type: "info",
|
||||
callback: function () {}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function getChoiceIndex(text) {
|
||||
const normalized = normalizePlain(text)
|
||||
const numberMap = {
|
||||
"1": 0,
|
||||
"一": 0,
|
||||
"壹": 0,
|
||||
"幺": 0,
|
||||
"2": 1,
|
||||
"二": 1,
|
||||
"两": 1,
|
||||
"贰": 1,
|
||||
"3": 2,
|
||||
"三": 2,
|
||||
"叁": 2,
|
||||
"4": 3,
|
||||
"四": 3,
|
||||
"肆": 3,
|
||||
"5": 4,
|
||||
"五": 4,
|
||||
"伍": 4
|
||||
}
|
||||
|
||||
const digitMatch = normalized.match(/第?([1-5])个?/)
|
||||
if (digitMatch) {
|
||||
return numberMap[digitMatch[1]]
|
||||
}
|
||||
|
||||
const chineseMatch = normalized.match(/第?([一二两三四五壹贰叁肆伍幺])个?/)
|
||||
if (chineseMatch) {
|
||||
return numberMap[chineseMatch[1]]
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
function handleChoice(text) {
|
||||
const index = getChoiceIndex(text)
|
||||
const matches = state.pendingMatches || []
|
||||
if (index >= 0 && index < matches.length) {
|
||||
if (window.ELEMENT && ELEMENT.MessageBox && typeof ELEMENT.MessageBox.close === "function") {
|
||||
ELEMENT.MessageBox.close()
|
||||
}
|
||||
openMenu(matches[index].menu)
|
||||
return
|
||||
}
|
||||
|
||||
notify("没有识别到有效序号,请说打开第几个", "warning")
|
||||
speak("没有识别到有效序号,请说打开第几个", function () {
|
||||
listenForChoice()
|
||||
})
|
||||
}
|
||||
|
||||
function listenForChoice() {
|
||||
state.awaitingChoice = true
|
||||
setTimeout(function () {
|
||||
start(true)
|
||||
}, 300)
|
||||
}
|
||||
|
||||
function chooseMenu(matches) {
|
||||
if (!matches.length) {
|
||||
notify("未找到可访问的菜单,请换个名称再试", "warning")
|
||||
return
|
||||
}
|
||||
if (matches.length === 1 || matches[0].score > matches[1].score) {
|
||||
openMenu(matches[0].menu)
|
||||
return
|
||||
}
|
||||
|
||||
state.pendingMatches = matches
|
||||
state.awaitingChoice = true
|
||||
showCandidateMessage(matches)
|
||||
|
||||
const prompt = matches
|
||||
.map(function (item, index) {
|
||||
return "第" + (index + 1) + "个," + item.menu.name
|
||||
})
|
||||
.join("。")
|
||||
speak("找到多个菜单,您需要打开第几个。" + prompt, function () {
|
||||
listenForChoice()
|
||||
})
|
||||
}
|
||||
|
||||
function updateButton(listening) {
|
||||
const $button = $("#voice-menu-btn")
|
||||
$button.toggleClass("is-listening", listening)
|
||||
$button.attr("title", listening ? "正在听,请说出菜单名称" : "语音打开菜单")
|
||||
$button.find(".voice-menu-text").text(listening ? "聆听中" : "语音")
|
||||
}
|
||||
|
||||
function start(choiceMode) {
|
||||
choiceMode = choiceMode || state.awaitingChoice
|
||||
if (!SpeechRecognition) {
|
||||
notify("当前浏览器不支持语音识别,请使用 Chrome 或 Edge", "warning")
|
||||
return
|
||||
}
|
||||
if (state.listening) {
|
||||
state.recognition.stop()
|
||||
return
|
||||
}
|
||||
|
||||
const recognition = new SpeechRecognition()
|
||||
recognition.lang = "zh-CN"
|
||||
recognition.interimResults = false
|
||||
recognition.continuous = false
|
||||
recognition.maxAlternatives = 1
|
||||
|
||||
recognition.onstart = function () {
|
||||
state.listening = true
|
||||
updateButton(true)
|
||||
notify(choiceMode ? "请说打开第几个" : "请说出要打开的菜单名称", "info")
|
||||
}
|
||||
recognition.onend = function () {
|
||||
state.listening = false
|
||||
updateButton(false)
|
||||
if (typeof state.afterRecognitionEnd === "function") {
|
||||
const next = state.afterRecognitionEnd
|
||||
state.afterRecognitionEnd = null
|
||||
setTimeout(next, 150)
|
||||
}
|
||||
}
|
||||
recognition.onerror = function (event) {
|
||||
const message = event.error === "not-allowed" ? "麦克风授权失败,请确认 HTTPS 或 localhost 环境并允许浏览器使用麦克风" : "语音识别失败,请再试一次"
|
||||
notify(message, "warning")
|
||||
}
|
||||
recognition.onresult = function (event) {
|
||||
const text = event.results && event.results[0] && event.results[0][0] && event.results[0][0].transcript
|
||||
state.lastText = text || ""
|
||||
if (!state.lastText) {
|
||||
notify("没有识别到语音内容", "warning")
|
||||
return
|
||||
}
|
||||
if (choiceMode || state.awaitingChoice) {
|
||||
runAfterRecognitionEnd(function () {
|
||||
handleChoice(state.lastText)
|
||||
})
|
||||
return
|
||||
}
|
||||
getMenus().then(function (menus) {
|
||||
const matches = matchMenus(state.lastText, menus)
|
||||
runAfterRecognitionEnd(function () {
|
||||
chooseMenu(matches)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
state.recognition = recognition
|
||||
recognition.start()
|
||||
}
|
||||
|
||||
function init() {
|
||||
$(document).on("click", "#voice-menu-btn", function () {
|
||||
start()
|
||||
})
|
||||
}
|
||||
|
||||
window.voiceMenuNavigator = {
|
||||
start,
|
||||
refreshMenus: function () {
|
||||
state.menus = null
|
||||
state.parentMap = {}
|
||||
}
|
||||
}
|
||||
|
||||
$(init)
|
||||
})(window, jQuery)
|
||||
Binary file not shown.
Binary file not shown.
@@ -85,6 +85,7 @@
|
||||
<script src="${base!}/components/plugins/sysDict/DictData.js"></script>
|
||||
<script src="${base!}/assets/platform/js/util/commonUtil.js"></script>
|
||||
<script src="${base!}/assets/platform/js/util/coordinateUtil.js"></script>
|
||||
<script src="${base!}/assets/platform/js/util/voiceMenuNavigator.js"></script>
|
||||
<script src="${base!}/assets/platform/js/main.js"></script>
|
||||
<script src="${base!}/assets/platform/js/mixin/initTableMixins.js"></script>
|
||||
<script src="${base!}/assets/platform/js/mixin/styleMixin.js"></script>
|
||||
@@ -541,6 +542,33 @@
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.v4-voice-menu {
|
||||
color: #ffffff;
|
||||
cursor: pointer;
|
||||
padding: 0 14px;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
transition: all 0.2s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
white-space: nowrap;
|
||||
font: inherit;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.v4-voice-menu:hover,
|
||||
.v4-voice-menu.is-listening {
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.v4-voice-menu.is-listening i {
|
||||
color: #ffdf6b;
|
||||
}
|
||||
|
||||
.v4-avatar {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
@@ -606,6 +634,10 @@
|
||||
.v4-nav {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.voice-menu-text {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 页脚样式 */
|
||||
@@ -677,6 +709,10 @@
|
||||
</div>
|
||||
|
||||
<div class="v4-user-section">
|
||||
<button type="button" class="v4-voice-menu" id="voice-menu-btn" title="语音打开菜单">
|
||||
<i class="fa fa-microphone"></i>
|
||||
<span class="voice-menu-text">语音</span>
|
||||
</button>
|
||||
<div class="v4-user-info">
|
||||
<span class="v4-user-name">${@auth.getPrincipalProperty('username')} (${@auth.getPrincipalProperty('loginname')})</span>
|
||||
<!-- <i class="fa fa-angle-down"></i> -->
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
const entry = {
|
||||
template: /*language=HTML*/ `
|
||||
<div class="entry-wrapper">
|
||||
<!-- 标签页导航 -->
|
||||
<div class="tabs">
|
||||
<div class="tab-item" :class="{ active: activeCategory === 'serv' }"
|
||||
@click="setActiveCategory('serv')">
|
||||
<i class="fa fa-star"></i> 推荐服务
|
||||
<div class="entry-section" v-for="section in entrySections" :key="section.key">
|
||||
<div class="entry-section-header">
|
||||
<div class="entry-section-title">
|
||||
<i :class="section.icon"></i>
|
||||
<span>{{ section.title }}</span>
|
||||
<em>/Applications</em>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-item" :class="{ active: activeCategory === 'app' }"
|
||||
@click="setActiveCategory('app')">
|
||||
<i class="fa fa-fire"></i> 推荐应用
|
||||
</div>
|
||||
<div class="tab-item" :class="{ active: activeCategory === 'fav' }"
|
||||
@click="setActiveCategory('fav')">
|
||||
<i class="fa fa-heart"></i> 我的收藏
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 应用块区域 -->
|
||||
<div class="apps-container">
|
||||
<div class="app-grid">
|
||||
<div class="app-item" v-for="(item, index) in currentServices" :key="index"
|
||||
@click="openService(item)">
|
||||
<div class="app-icon">
|
||||
<img :src="item.picIcon" alt="" />
|
||||
<div class="apps-container">
|
||||
<div class="app-grid">
|
||||
<div class="app-item"
|
||||
v-for="(item, index) in section.list"
|
||||
:key="item.id || index"
|
||||
@click="openService(item, section.key)">
|
||||
<div class="app-icon">
|
||||
<img :src="item.picIcon" alt="" />
|
||||
</div>
|
||||
<div class="app-name">{{ item.name }}</div>
|
||||
</div>
|
||||
<div class="app-name">{{ item.name }}</div>
|
||||
<el-empty class="app-empty"
|
||||
v-if="!section.list || section.list.length === 0"
|
||||
description="暂无数据"
|
||||
:image-size="72">
|
||||
</el-empty>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -33,7 +33,6 @@ const entry = {
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
activeCategory: 'serv',
|
||||
hasMore: true,
|
||||
entries: {
|
||||
serv: [],
|
||||
@@ -43,8 +42,12 @@ const entry = {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentServices() {
|
||||
return this.entries[this.activeCategory] || [];
|
||||
entrySections() {
|
||||
return [
|
||||
{ key: 'serv', title: '推荐服务', icon: 'fa fa-star', list: this.entries.serv },
|
||||
{ key: 'app', title: '推荐应用', icon: 'fa fa-fire', list: this.entries.app },
|
||||
{ key: 'fav', title: '我的收藏', icon: 'fa fa-heart', list: this.entries.fav }
|
||||
];
|
||||
}
|
||||
},
|
||||
created() {
|
||||
@@ -53,10 +56,6 @@ const entry = {
|
||||
this.getFavorite()
|
||||
},
|
||||
methods: {
|
||||
setActiveCategory(category) {
|
||||
this.activeCategory = category;
|
||||
},
|
||||
|
||||
// 查询推荐服务
|
||||
async getRecommendService() {
|
||||
this.$axios.post('/platform/home/listRecommendService', {platform: "PC"}).then(res => {
|
||||
@@ -84,35 +83,37 @@ const entry = {
|
||||
})
|
||||
},
|
||||
|
||||
// 点击服务
|
||||
openService(service) {
|
||||
if(this.activeCategory === 'serv'){
|
||||
if(!service.href) this.$message.error('无效的链接地址')
|
||||
// 点击服务或应用
|
||||
openService(service, category) {
|
||||
if (category === 'serv') {
|
||||
if (!service.href) {
|
||||
this.$message.error('无效的链接地址')
|
||||
return
|
||||
}
|
||||
window.open(service.href)
|
||||
return
|
||||
}
|
||||
|
||||
if(this.activeCategory === 'app'){
|
||||
// 储存到缓存
|
||||
if (category === 'app') {
|
||||
window.sessionStorage.setItem("zhgh_sub_app", JSON.stringify(service))
|
||||
// 新标签页打开
|
||||
window.open("/platform/v4/subApp?appId=" + service.id, "_blank")
|
||||
return
|
||||
}
|
||||
|
||||
if(this.activeCategory === 'fav'){
|
||||
if(!service.href) this.$message.error('无效的链接地址')
|
||||
window.open(service.href)
|
||||
return
|
||||
|
||||
if(!service.parentId){
|
||||
// 储存到缓存
|
||||
window.sessionStorage.setItem("zhgh_sub_app", JSON.stringify(app))
|
||||
// 新标签页打开
|
||||
window.open("/platform/v4/subApp?appId=" + app.id, "_blank")
|
||||
if (category === 'fav') {
|
||||
if (service.href) {
|
||||
window.open(service.href)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (!service.parentId) {
|
||||
window.sessionStorage.setItem("zhgh_sub_app", JSON.stringify(service))
|
||||
window.open("/platform/v4/subApp?appId=" + service.id, "_blank")
|
||||
return
|
||||
}
|
||||
|
||||
this.$message.error('无效的链接地址')
|
||||
}
|
||||
}
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
@@ -121,37 +122,36 @@ const entry = {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tabs {
|
||||
margin-top: 5px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
border-bottom: 2px solid #e8e8e8;
|
||||
padding-bottom: 10px;
|
||||
.entry-section + .entry-section {
|
||||
margin-top: 22px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
.entry-section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.entry-section-title {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
gap: 6px;
|
||||
color: #19324d;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.entry-section-title i {
|
||||
color: #409eff;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.entry-section-title em {
|
||||
color: #b5c0d6;
|
||||
font-size: 14px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
padding-bottom: 8px;
|
||||
transition: color 0.2s ease;
|
||||
}
|
||||
|
||||
.tab-item:hover {
|
||||
color: #1890ff;
|
||||
}
|
||||
|
||||
.tab-item.active {
|
||||
color: #1890ff;
|
||||
font-weight: bold;
|
||||
border-bottom: 2px solid #1890ff;
|
||||
}
|
||||
|
||||
.tab-item i {
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.apps-container {
|
||||
@@ -162,19 +162,17 @@ const entry = {
|
||||
|
||||
.app-grid {
|
||||
display: grid;
|
||||
/* 改为自适应列数:每格最小 80px,自动换行 */
|
||||
grid-template-columns: repeat(auto-fit, minmax(80px, 1fr));
|
||||
|
||||
justify-items: center; /* 子项内容居中(图标和文字) */
|
||||
justify-content: start; /* 整体网格靠左对齐,避免居中 */
|
||||
|
||||
gap: 4px;
|
||||
padding: 12px 4px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(96px, 1fr));
|
||||
justify-items: center;
|
||||
justify-content: start;
|
||||
gap: 12px 18px;
|
||||
min-height: 116px;
|
||||
padding: 18px 20px;
|
||||
background: #fff;
|
||||
border: 1px solid #edf1f7;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.06);
|
||||
overflow-x: hidden;
|
||||
|
||||
max-height: calc(350px - 74px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.app-item {
|
||||
@@ -187,6 +185,7 @@ const entry = {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
padding: 6px;
|
||||
cursor: pointer;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
||||
}
|
||||
|
||||
@@ -208,6 +207,7 @@ const entry = {
|
||||
}
|
||||
|
||||
.app-name {
|
||||
min-height: 34px;
|
||||
font-size: 12px;
|
||||
color: #333;
|
||||
line-height: 1.4;
|
||||
@@ -215,31 +215,26 @@ const entry = {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
.app-empty {
|
||||
grid-column: 1 / -1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.tabs {
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tab-item {
|
||||
padding: 8px 0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.app-grid {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 15px;
|
||||
grid-template-columns: repeat(3, minmax(72px, 1fr));
|
||||
gap: 12px;
|
||||
padding: 14px 10px;
|
||||
}
|
||||
|
||||
.app-item {
|
||||
padding: 12px;
|
||||
padding: 8px 4px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.app-grid {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-columns: repeat(2, minmax(72px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
|
||||
.section-banner .banner-img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.section-banner .banner-img img {
|
||||
@@ -21,6 +21,11 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* 临时屏蔽首页背景图,保留原图片节点便于恢复 */
|
||||
.section-banner .banner-img > img {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.section-wrapper {
|
||||
width: 100%;
|
||||
}
|
||||
@@ -29,7 +34,7 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
width: 80%;
|
||||
margin: 20px auto;
|
||||
display: grid;
|
||||
grid-template-columns: 2.2fr 0.8fr;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 24px;
|
||||
align-items: stretch;
|
||||
row-gap: 24px;
|
||||
@@ -39,16 +44,16 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
display: flex;
|
||||
/*align-items: center;*/
|
||||
min-width: 320px;
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.08);
|
||||
padding: 16px;
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
box-shadow: none;
|
||||
padding: 0;
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.entry-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 16px rgba(0,0,0,0.12);
|
||||
transform: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.act-card {
|
||||
@@ -77,10 +82,6 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
<user></user>
|
||||
|
||||
<div class="home-grid">
|
||||
<div class="act-card">
|
||||
<act></act>
|
||||
</div>
|
||||
|
||||
<div class="entry-card">
|
||||
<entry></entry>
|
||||
</div>
|
||||
|
||||
@@ -12,7 +12,7 @@ const stats = {
|
||||
@mouseenter="onMouseEnter"
|
||||
@mouseleave="onMouseLeave"
|
||||
>
|
||||
<div class="stat-icon">
|
||||
<div class="stat-icon stat-icon-hidden">
|
||||
<i class="fa fa-clock-o"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
@@ -28,7 +28,7 @@ const stats = {
|
||||
@mouseenter="onMouseEnter"
|
||||
@mouseleave="onMouseLeave"
|
||||
>
|
||||
<div class="stat-icon">
|
||||
<div class="stat-icon stat-icon-hidden">
|
||||
<i class="fa fa-check-circle"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
@@ -44,7 +44,7 @@ const stats = {
|
||||
@mouseenter="onMouseEnter"
|
||||
@mouseleave="onMouseLeave"
|
||||
>
|
||||
<div class="stat-icon">
|
||||
<div class="stat-icon stat-icon-hidden">
|
||||
<i class="fa fa-bell"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
@@ -60,7 +60,7 @@ const stats = {
|
||||
@mouseenter="onMouseEnter"
|
||||
@mouseleave="onMouseLeave"
|
||||
>
|
||||
<div class="stat-icon">
|
||||
<div class="stat-icon stat-icon-hidden">
|
||||
<i class="fa fa-file-text"></i>
|
||||
</div>
|
||||
<div class="stat-content">
|
||||
@@ -122,7 +122,7 @@ const stats = {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: -130px auto 0;
|
||||
margin: 20px auto 0;
|
||||
/*border: 1px solid #e8e8e8;*/
|
||||
}
|
||||
|
||||
@@ -131,7 +131,8 @@ const stats = {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 16px;
|
||||
width: 32%;
|
||||
margin-left: 20px;
|
||||
min-width: 360px;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.stat-item {
|
||||
@@ -162,6 +163,11 @@ const stats = {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* 临时屏蔽待办、已办、消息、发起四个统计图标 */
|
||||
.stat-icon-hidden {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.stat-item.pending .stat-icon,
|
||||
.stat-item.completed .stat-icon,
|
||||
.stat-item.messages .stat-icon,
|
||||
@@ -185,7 +191,7 @@ const stats = {
|
||||
.stat-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 10px;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
@@ -209,5 +215,10 @@ const stats = {
|
||||
height: 190px !important;
|
||||
border-radius: 30px;
|
||||
}
|
||||
|
||||
/* 临时屏蔽“欢迎来到职工之家”图片,保留原节点便于恢复 */
|
||||
.stats-family {
|
||||
display: none;
|
||||
}
|
||||
`
|
||||
};
|
||||
|
||||
@@ -68,10 +68,10 @@ const user = {
|
||||
<div class="header">
|
||||
<h3>待办中心</h3>
|
||||
<el-tabs v-model="activeTab" type="card" @tab-click="handleTabClick">
|
||||
<el-tab-pane label="待办事宜" name="todo"></el-tab-pane>
|
||||
<el-tab-pane label="已办事宜" name="done"></el-tab-pane>
|
||||
<el-tab-pane label="我的发起" name="started"></el-tab-pane>
|
||||
<el-tab-pane label="办结事务" name="completed"></el-tab-pane>
|
||||
<el-tab-pane :label="tabLabel('待办事宜', taskStats.todoCount)" name="todo"></el-tab-pane>
|
||||
<el-tab-pane :label="tabLabel('已办事宜', taskStats.doneCount)" name="done"></el-tab-pane>
|
||||
<el-tab-pane :label="tabLabel('我的发起', taskStats.startedCount)" name="started"></el-tab-pane>
|
||||
<el-tab-pane :label="tabLabel('办结事务', taskStats.completedCount)" name="completed"></el-tab-pane>
|
||||
</el-tabs>
|
||||
</div>
|
||||
|
||||
@@ -124,6 +124,12 @@ const user = {
|
||||
pageSize: 5,
|
||||
totalCount: 0
|
||||
},
|
||||
taskStats: {
|
||||
todoCount: 0,
|
||||
doneCount: 0,
|
||||
startedCount: 0,
|
||||
completedCount: 0
|
||||
},
|
||||
// 🔆 天气相关数据
|
||||
weatherData: null,
|
||||
weatherError: false,
|
||||
@@ -228,6 +234,19 @@ const user = {
|
||||
}
|
||||
},
|
||||
// 获取任务列表
|
||||
tabLabel(title, count) {
|
||||
return title + "(" + (count || 0) + ")"
|
||||
},
|
||||
async getStatistics() {
|
||||
try {
|
||||
const res = await $.post("/flow/todoCenter/statistics")
|
||||
if (res.code === 0) {
|
||||
this.taskStats = Object.assign({}, this.taskStats, res.data)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("获取待办统计失败:", error)
|
||||
}
|
||||
},
|
||||
async getTasks() {
|
||||
this.loading = true
|
||||
try {
|
||||
@@ -245,6 +264,7 @@ const user = {
|
||||
},
|
||||
pageData() {
|
||||
this.getTasks()
|
||||
this.getStatistics()
|
||||
},
|
||||
handleTabClick(tab) {
|
||||
this.pageData()
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<el-card shadow="never">学习活动管理模块待完善</el-card>
|
||||
</div>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,883 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<style>
|
||||
.learning-outline-layout {
|
||||
height: calc(100vh - 235px);
|
||||
}
|
||||
.learning-outline-tree {
|
||||
height: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
.outline-context-menu {
|
||||
position: fixed;
|
||||
z-index: 3000;
|
||||
padding: 5px 0;
|
||||
margin: 0;
|
||||
list-style: none;
|
||||
background: #fff;
|
||||
border: 1px solid #dcdfe6;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, .1);
|
||||
}
|
||||
.outline-context-menu li {
|
||||
min-width: 120px;
|
||||
padding: 7px 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.outline-context-menu li:hover {
|
||||
background: #f2f6fc;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="课程名称">
|
||||
<el-select
|
||||
v-model="currentCourseId"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
:remote-method="courseNameRemoteSearch"
|
||||
placeholder="请选择或搜索课程名称"
|
||||
style="width: 100%"
|
||||
@change="courseChange"
|
||||
@clear="courseClear">
|
||||
<el-option v-for="item in courseOptions" :key="item.id" :label="item.courseName" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="课程类型">
|
||||
<el-select v-model="courseQuery.courseTypeId" clearable filterable placeholder="请选择课程类型" style="width: 100%">
|
||||
<el-option v-for="item in courseTypeOptions" :key="item.id" :label="item.typeName" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="授课讲师">
|
||||
<el-input v-model="courseQuery.lecturerName" clearable placeholder="请输入授课讲师" style="width: 100%" @keyup.enter.native="loadCourses"></el-input>
|
||||
</search-item>
|
||||
<search-item label="课程状态">
|
||||
<el-select v-model="courseQuery.status" clearable placeholder="请选择课程状态" style="width: 100%">
|
||||
<el-option v-for="item in statusOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<div class="search-query">
|
||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="loadCourses">搜索</el-button>
|
||||
<el-button size="medium" @click="resetCourseQuery">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-row :gutter="10" class="mt10 learning-outline-layout">
|
||||
<el-col :span="6" style="height: 100%">
|
||||
<el-card shadow="never" style="height: 100%">
|
||||
<table-tool label="课程大纲">
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openNodeForm('chapter')">新增章</el-button>
|
||||
</table-tool>
|
||||
<div class="learning-outline-tree">
|
||||
<el-tree
|
||||
ref="outlineTree"
|
||||
:data="outlineTreeData"
|
||||
node-key="id"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
:expand-on-click-node="false"
|
||||
:props="{children:'children', label:'title'}"
|
||||
@node-click="nodeClick"
|
||||
@node-contextmenu="openContextMenu">
|
||||
<span slot-scope="{ node, data }">
|
||||
<i :class="data.nodeType === 'course' ? 'el-icon-collection' : (data.nodeType === 'chapter' ? 'el-icon-folder' : 'el-icon-document')"></i>
|
||||
<span>{{data.title}}</span>
|
||||
<el-tag v-if="data.status === 'disabled'" size="mini" type="info" style="margin-left: 6px">禁用</el-tag>
|
||||
<el-tag v-if="data.required" size="mini" type="warning" style="margin-left: 6px">必学</el-tag>
|
||||
</span>
|
||||
</el-tree>
|
||||
<el-empty v-if="treeData.length === 0" description="暂无课程大纲"></el-empty>
|
||||
</div>
|
||||
</el-card>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="18" style="height: 100%">
|
||||
<el-card shadow="never" style="height: 100%; overflow: auto">
|
||||
<el-empty v-if="!selectedNode.id" description="请选择左侧章/节节点"></el-empty>
|
||||
<el-tabs v-else v-model="activeTab">
|
||||
<el-tab-pane label="基础信息" name="basic">
|
||||
<el-form :model="nodeForm" ref="nodeForm" label-width="110px" :rules="nodeRules">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="节点类型">
|
||||
<el-tag :type="nodeForm.nodeType === 'chapter' ? 'primary' : 'success'">{{nodeForm.nodeType === 'chapter' ? '章' : '节'}}</el-tag>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排序值" prop="sortOrder">
|
||||
<el-input-number v-model="nodeForm.sortOrder" :controls="false" :min="1" :precision="0" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="nodeForm.title" maxlength="100" placeholder="请输入章/节标题"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="副标题" prop="subtitle">
|
||||
<el-input v-model="nodeForm.subtitle" maxlength="200" placeholder="请输入副标题"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="简介" prop="description">
|
||||
<el-input v-model="nodeForm.description" type="textarea" :rows="5" maxlength="2000" show-word-limit placeholder="请输入章/节简介"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="saveNodeBasic">保存基础信息</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="学习资料" name="resource">
|
||||
<table-tool label="学习资料">
|
||||
<el-button size="mini" type="primary" icon="el-icon-plus" @click="openResourceForm()">新增资料</el-button>
|
||||
</table-tool>
|
||||
<el-table :data="resourceData" border @sort-change="resourcePageOrder">
|
||||
<el-table-column label="序号" type="index" width="70" :index="resourceIndex"></el-table-column>
|
||||
<el-table-column label="资料标题" prop="resourceTitle" sortable="custom" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="资料类型" prop="resourceType" sortable="custom" width="110">
|
||||
<template slot-scope="{row}">{{getResourceTypeName(row.resourceType)}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="文件格式" prop="fileExt" sortable="custom" width="110"></el-table-column>
|
||||
<el-table-column label="学习时长(分钟)" prop="durationSeconds" sortable="custom" width="140">
|
||||
<template slot-scope="{row}">{{formatDurationMinutes(row.durationSeconds)}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="排序" prop="sortOrder" sortable="custom" width="90"></el-table-column>
|
||||
<el-table-column label="必学" prop="required" sortable="custom" width="90">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag :type="row.required ? 'warning' : 'info'" size="mini">{{row.required ? '是' : '否'}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="预览" prop="allowPreview" sortable="custom" width="90">
|
||||
<template slot-scope="{row}">{{row.allowPreview ? '允许' : '不允许'}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="下载" prop="allowDownload" sortable="custom" width="90">
|
||||
<template slot-scope="{row}">{{row.allowDownload ? '允许' : '不允许'}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="状态" prop="status" sortable="custom" width="90">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag :type="row.status === 'enabled' ? 'success' : 'info'" size="mini">{{row.status === 'enabled' ? '启用' : '禁用'}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="openResourceForm(row)">编辑</el-button>
|
||||
<el-button size="mini" type="danger" @click="deleteResource(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="mt20"
|
||||
@size-change="resourceSizeChange"
|
||||
@current-change="resourceNumberChange"
|
||||
:current-page="resourcePageForm.pageNumber"
|
||||
:page-sizes="[5,10,20,30,50]"
|
||||
:page-size="resourcePageForm.pageSize"
|
||||
layout="total, sizes, prev, pager, next"
|
||||
:total="resourcePageForm.totalCount">
|
||||
</el-pagination>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="完成规则" name="rule">
|
||||
<el-form :model="ruleForm" label-width="150px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否必学">
|
||||
<el-switch v-model="ruleForm.required"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学习方式">
|
||||
<el-select v-model="ruleForm.studyMode" style="width: 100%">
|
||||
<el-option v-for="item in studyModeOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="完成规则">
|
||||
<el-select v-model="ruleForm.completionRule" style="width: 100%">
|
||||
<el-option v-for="item in completionRuleOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="完成比例">
|
||||
<el-input-number v-model="ruleForm.completePercent" :min="0" :max="100" :precision="0" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="最少学习时长(分钟)">
|
||||
<el-input-number v-model="ruleForm.minStudyMinutes" :min="0" :precision="0" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="解锁规则">
|
||||
<el-select v-model="ruleForm.unlockRule" style="width: 100%">
|
||||
<el-option v-for="item in unlockRuleOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8"><el-form-item label="允许跳过"><el-switch v-model="ruleForm.allowSkip"></el-switch></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="允许拖动"><el-switch v-model="ruleForm.allowDrag"></el-switch></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="暂停计时"><el-switch v-model="ruleForm.pauseCountTime"></el-switch></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8"><el-form-item label="隐藏计时"><el-switch v-model="ruleForm.hiddenCountTime"></el-switch></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="无操作计时"><el-switch v-model="ruleForm.inactiveCountTime"></el-switch></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="saveRule">保存完成规则</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="状态设置" name="status">
|
||||
<el-form :model="nodeForm" label-width="110px">
|
||||
<el-form-item label="节点状态">
|
||||
<el-radio-group v-model="nodeForm.status">
|
||||
<el-radio label="enabled">启用</el-radio>
|
||||
<el-radio label="disabled">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否必学">
|
||||
<el-switch v-model="nodeForm.required"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="saveNodeBasic">保存状态</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<ul v-show="contextVisible" class="outline-context-menu" :style="{left: contextLeft + 'px', top: contextTop + 'px'}">
|
||||
<li @click="openNodeForm('chapter')">新增章</li>
|
||||
<li v-if="contextNode.nodeType === 'chapter'" @click="openNodeForm('section', contextNode)">新增节</li>
|
||||
<li v-if="contextNode.nodeType !== 'course'" @click="openNodeForm(contextNode.nodeType, contextNode)">编辑</li>
|
||||
<li v-if="contextNode.nodeType !== 'course'" @click="deleteNode(contextNode)">删除</li>
|
||||
<li v-if="contextNode.nodeType !== 'course'" @click="toggleNode(contextNode)">{{contextNode.status === 'enabled' ? '禁用' : '启用'}}</li>
|
||||
<li v-if="contextNode.nodeType !== 'course'" @click="moveNode(contextNode, 'up')">上移</li>
|
||||
<li v-if="contextNode.nodeType !== 'course'" @click="moveNode(contextNode, 'down')">下移</li>
|
||||
<li v-if="contextNode.nodeType !== 'course'" @click="openResourceForm(null, contextNode)">上传资料</li>
|
||||
<li v-if="contextNode.nodeType !== 'course'" @click="setRequired(contextNode)">设置必学</li>
|
||||
</ul>
|
||||
|
||||
<el-dialog :title="nodeDialogTitle" :visible.sync="nodeDialogVisible" :close-on-click-modal="false" width="45%">
|
||||
<el-form :model="nodeDialogForm" ref="nodeDialogForm" label-width="100px" :rules="nodeRules">
|
||||
<el-form-item label="节点类型">
|
||||
<el-tag>{{nodeDialogForm.nodeType === 'chapter' ? '章' : '节'}}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="上级节点">
|
||||
<el-input v-model="nodeDialogForm.parentName" disabled placeholder="上级节点"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="标题" prop="title">
|
||||
<el-input v-model="nodeDialogForm.title" maxlength="100" placeholder="请输入标题"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="副标题">
|
||||
<el-input v-model="nodeDialogForm.subtitle" maxlength="200" placeholder="请输入副标题"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="简介">
|
||||
<el-input v-model="nodeDialogForm.description" type="textarea" :rows="4" maxlength="2000" show-word-limit></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序值">
|
||||
<el-input-number v-model="nodeDialogForm.sortOrder" :min="1" :precision="0" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否必学">
|
||||
<el-switch v-model="nodeDialogForm.required"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="nodeDialogForm.status">
|
||||
<el-radio label="enabled">启用</el-radio>
|
||||
<el-radio label="disabled">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="nodeDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitNodeDialog">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog :title="resourceForm.id ? '编辑学习资料' : '新增学习资料'" :visible.sync="resourceDialogVisible" :close-on-click-modal="false" width="55%">
|
||||
<el-form :model="resourceForm" ref="resourceForm" label-width="120px" :rules="resourceRules">
|
||||
<el-form-item label="所属章节">
|
||||
<el-input v-model="resourceForm.outlineName" disabled placeholder="所属章节"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="资料标题" prop="resourceTitle">
|
||||
<el-input v-model="resourceForm.resourceTitle" maxlength="100" placeholder="请输入资料标题"></el-input>
|
||||
</el-form-item>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="资料类型" prop="resourceType">
|
||||
<el-select v-model="resourceForm.resourceType" placeholder="请选择资料类型" style="width: 100%" @change="resourceTypeChange">
|
||||
<el-option v-for="item in resourceTypeOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排序值" prop="sortOrder">
|
||||
<el-input-number v-model="resourceForm.sortOrder" :min="1" :precision="0" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学习时长(分钟)">
|
||||
<el-input-number v-model="resourceForm.durationMinutes" :min="0" :precision="0" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="状态">
|
||||
<el-radio-group v-model="resourceForm.status">
|
||||
<el-radio label="enabled">启用</el-radio>
|
||||
<el-radio label="disabled">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8"><el-form-item label="是否必学"><el-switch v-model="resourceForm.required"></el-switch></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="允许预览"><el-switch v-model="resourceForm.allowPreview"></el-switch></el-form-item></el-col>
|
||||
<el-col :span="8"><el-form-item label="允许下载"><el-switch v-model="resourceForm.allowDownload"></el-switch></el-form-item></el-col>
|
||||
</el-row>
|
||||
<el-form-item label="附件" prop="fileData">
|
||||
<file-upload
|
||||
:value.sync="resourceForm.fileData"
|
||||
:upload_number="1"
|
||||
upload_mode="drag"
|
||||
upload_result_category="array"
|
||||
complete_result
|
||||
:accept="fileAccept">
|
||||
</file-upload>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="resourceDialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="submitResource">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
courseQuery: {},
|
||||
courseOptions: [],
|
||||
courseTypeOptions: [],
|
||||
currentCourseId: "",
|
||||
treeData: [],
|
||||
selectedNode: {},
|
||||
activeTab: "basic",
|
||||
nodeForm: {},
|
||||
nodeDialogVisible: false,
|
||||
nodeDialogTitle: "",
|
||||
nodeDialogForm: {},
|
||||
nodeRules: {
|
||||
title: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
sortOrder: [{ required: true, message: "必填", trigger: ["blur", "change"] }]
|
||||
},
|
||||
contextVisible: false,
|
||||
contextLeft: 0,
|
||||
contextTop: 0,
|
||||
contextNode: {},
|
||||
resourceData: [],
|
||||
resourcePageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "",
|
||||
pageOrderBy: ""
|
||||
},
|
||||
resourceDialogVisible: false,
|
||||
resourceForm: {},
|
||||
resourceRules: {
|
||||
resourceTitle: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
resourceType: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
sortOrder: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
fileData: [{ required: true, message: "请上传附件", trigger: ["blur", "change"] }]
|
||||
},
|
||||
fileAccept: "",
|
||||
ruleForm: {},
|
||||
statusOptions: [
|
||||
{ name: "草稿", code: "draft" },
|
||||
{ name: "待发布", code: "pending" },
|
||||
{ name: "已发布", code: "published" },
|
||||
{ name: "已下架", code: "offline" }
|
||||
],
|
||||
resourceTypeOptions: [
|
||||
{ name: "视频", code: "video", accept: ".mp4,.mov" },
|
||||
{ name: "音频", code: "audio", accept: ".mp3,.wav,.aac" },
|
||||
{ name: "PDF", code: "pdf", accept: ".pdf" },
|
||||
{ name: "PPT课件", code: "ppt", accept: ".ppt,.pptx" },
|
||||
{ name: "Word文档", code: "word", accept: ".doc,.docx" },
|
||||
{ name: "图片", code: "image", accept: ".jpg,.jpeg,.png" },
|
||||
{ name: "其他", code: "other", accept: "" }
|
||||
],
|
||||
studyModeOptions: [
|
||||
{ name: "视频", code: "video" },
|
||||
{ name: "音频", code: "audio" },
|
||||
{ name: "文档", code: "document" },
|
||||
{ name: "图片", code: "image" },
|
||||
{ name: "混合资料", code: "mixed" }
|
||||
],
|
||||
completionRuleOptions: [
|
||||
{ name: "完成所有必学资料", code: "all_required_resource" },
|
||||
{ name: "按资料学习进度", code: "resource_progress" },
|
||||
{ name: "达到最少学习时长", code: "min_study_time" },
|
||||
{ name: "管理员手动确认", code: "manual" }
|
||||
],
|
||||
unlockRuleOptions: [
|
||||
{ name: "自由学习", code: "free" },
|
||||
{ name: "顺序解锁", code: "sequential" }
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentCourse() {
|
||||
return this.courseOptions.find(v => v.id === this.currentCourseId)
|
||||
},
|
||||
outlineTreeData() {
|
||||
if (!this.currentCourseId || !this.currentCourse) {
|
||||
return []
|
||||
}
|
||||
return [{
|
||||
id: "course-" + this.currentCourseId,
|
||||
courseId: this.currentCourseId,
|
||||
title: this.currentCourse.courseName,
|
||||
nodeType: "course",
|
||||
children: this.treeData
|
||||
}]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetCourseQuery() {
|
||||
this.courseQuery = {}
|
||||
this.currentCourseId = ""
|
||||
this.loadCourses()
|
||||
},
|
||||
getQueryParam(name) {
|
||||
return new URLSearchParams(window.location.search).get(name) || ""
|
||||
},
|
||||
async loadCourseTypes() {
|
||||
const resp = await this.$axios.post(loc() + "/courseTypes")
|
||||
if (resp.code === 0) {
|
||||
this.courseTypeOptions = resp.data
|
||||
}
|
||||
},
|
||||
async loadCourses(keepCurrent = false) {
|
||||
const resp = await this.$axios.post(loc() + "/courses", this.courseQuery)
|
||||
if (resp.code === 0) {
|
||||
this.courseOptions = resp.data
|
||||
if (!keepCurrent && (!this.currentCourseId || !this.courseOptions.some(v => v.id === this.currentCourseId))) {
|
||||
this.currentCourseId = this.courseOptions.length ? this.courseOptions[0].id : ""
|
||||
}
|
||||
await this.loadTree()
|
||||
}
|
||||
},
|
||||
courseNameRemoteSearch(query) {
|
||||
this.courseQuery.courseName = query
|
||||
this.loadCourses(true)
|
||||
},
|
||||
courseChange() {
|
||||
this.selectedNode = {}
|
||||
this.loadTree()
|
||||
},
|
||||
courseClear() {
|
||||
this.currentCourseId = ""
|
||||
this.selectedNode = {}
|
||||
this.treeData = []
|
||||
},
|
||||
async loadTree() {
|
||||
if (!this.currentCourseId) {
|
||||
this.treeData = []
|
||||
this.selectedNode = {}
|
||||
return
|
||||
}
|
||||
const resp = await this.$axios.post(loc() + "/tree", { courseId: this.currentCourseId })
|
||||
if (resp.code === 0) {
|
||||
this.treeData = resp.data
|
||||
let nodeToSelect = null
|
||||
if (this.selectedNode.id) {
|
||||
nodeToSelect = this.findNode(this.treeData, this.selectedNode.id)
|
||||
}
|
||||
if (!nodeToSelect) {
|
||||
nodeToSelect = this.findFirstOutlineNode(this.treeData)
|
||||
}
|
||||
if (nodeToSelect) {
|
||||
this.nodeClick(nodeToSelect)
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.outlineTree) {
|
||||
this.$refs.outlineTree.setCurrentKey(nodeToSelect.id)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this.selectedNode = {}
|
||||
this.nodeForm = {}
|
||||
this.resourceData = []
|
||||
}
|
||||
}
|
||||
},
|
||||
findFirstOutlineNode(nodes) {
|
||||
if (!nodes || !nodes.length) return null
|
||||
for (const node of nodes) {
|
||||
if (node.nodeType !== "course") return node
|
||||
const child = this.findFirstOutlineNode(node.children || [])
|
||||
if (child) return child
|
||||
}
|
||||
return null
|
||||
},
|
||||
findNode(nodes, id) {
|
||||
for (const node of nodes) {
|
||||
if (node.id === id) return node
|
||||
const child = this.findNode(node.children || [], id)
|
||||
if (child) return child
|
||||
}
|
||||
return null
|
||||
},
|
||||
getParentName(type, node, course, isEdit) {
|
||||
if (type === "chapter") {
|
||||
return course ? course.courseName : ""
|
||||
}
|
||||
if (!isEdit) {
|
||||
return node ? node.title : ""
|
||||
}
|
||||
const parent = node && node.parentId ? this.findNode(this.treeData, node.parentId) : null
|
||||
return parent ? parent.title : (course ? course.courseName : "")
|
||||
},
|
||||
nodeClick(data) {
|
||||
if (data.nodeType === "course") {
|
||||
this.selectedNode = {}
|
||||
this.nodeForm = {}
|
||||
this.resourceData = []
|
||||
return
|
||||
}
|
||||
this.selectedNode = data
|
||||
this.nodeForm = Object.assign({}, data)
|
||||
this.activeTab = "basic"
|
||||
this.loadResources()
|
||||
this.loadRule()
|
||||
},
|
||||
openContextMenu(event, data) {
|
||||
event.preventDefault()
|
||||
this.contextNode = data
|
||||
this.contextVisible = true
|
||||
this.contextLeft = event.clientX
|
||||
this.contextTop = event.clientY
|
||||
},
|
||||
openNodeForm(type, node = null) {
|
||||
if (!this.currentCourseId) {
|
||||
this.$message.warning("请先选择课程")
|
||||
return
|
||||
}
|
||||
const course = this.courseOptions.find(v => v.id === this.currentCourseId)
|
||||
if (node && node.id && node.nodeType === type) {
|
||||
this.nodeDialogTitle = "编辑" + (type === "chapter" ? "章" : "节")
|
||||
this.nodeDialogForm = Object.assign({}, node, {
|
||||
parentName: this.getParentName(type, node, course, true)
|
||||
})
|
||||
} else {
|
||||
this.nodeDialogTitle = "新增" + (type === "chapter" ? "章" : "节")
|
||||
this.nodeDialogForm = {
|
||||
courseId: this.currentCourseId,
|
||||
courseName: course ? course.courseName : "",
|
||||
parentId: type === "section" && node ? node.id : "",
|
||||
parentName: this.getParentName(type, node, course, false),
|
||||
nodeType: type,
|
||||
title: "",
|
||||
subtitle: "",
|
||||
description: "",
|
||||
sortOrder: 1,
|
||||
required: false,
|
||||
status: "enabled"
|
||||
}
|
||||
}
|
||||
this.nodeDialogVisible = true
|
||||
this.$nextTick(() => this.$refs.nodeDialogForm && this.$refs.nodeDialogForm.clearValidate())
|
||||
},
|
||||
submitNodeDialog() {
|
||||
this.$refs.nodeDialogForm.validate(async valid => {
|
||||
if (!valid) return
|
||||
const resp = await this.$axios.post(loc() + "/saveNode", this.nodeDialogForm)
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.nodeDialogVisible = false
|
||||
await this.loadTree()
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
saveNodeBasic() {
|
||||
this.$refs.nodeForm.validate(async valid => {
|
||||
if (!valid) return
|
||||
const resp = await this.$axios.post(loc() + "/saveNode", this.nodeForm)
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.selectedNode = Object.assign({}, this.nodeForm)
|
||||
await this.loadTree()
|
||||
await this.loadRule()
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
deleteNode(node) {
|
||||
this.$confirm("确定要删除该节点吗?若下级有内容将不允许删除。", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(async () => {
|
||||
const resp = await this.$axios.post(loc() + "/deleteNode", { id: node.id })
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.selectedNode = {}
|
||||
await this.loadTree()
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
async toggleNode(node) {
|
||||
const status = node.status === "enabled" ? "disabled" : "enabled"
|
||||
const resp = await this.$axios.post(loc() + "/toggleNode", { id: node.id, status })
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
await this.loadTree()
|
||||
}
|
||||
},
|
||||
async moveNode(node, direction) {
|
||||
const resp = await this.$axios.post(loc() + "/moveNode", { id: node.id, direction })
|
||||
if (resp.code === 0) {
|
||||
await this.loadTree()
|
||||
}
|
||||
},
|
||||
async setRequired(node) {
|
||||
const next = !node.required
|
||||
const data = Object.assign({}, node, { required: next })
|
||||
const resp = await this.$axios.post(loc() + "/saveNode", data)
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
await this.loadTree()
|
||||
if (this.selectedNode.id === node.id) {
|
||||
this.nodeClick(Object.assign({}, node, { required: next }))
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadResources() {
|
||||
if (!this.selectedNode.id) return
|
||||
const resp = await this.$axios.post(loc() + "/resourcePage", Object.assign({}, this.resourcePageForm, { outlineId: this.selectedNode.id }))
|
||||
if (resp.code === 0) {
|
||||
this.resourceData = resp.data.list
|
||||
this.resourcePageForm.totalCount = resp.data.totalCount
|
||||
}
|
||||
},
|
||||
resourcePageOrder(column) {
|
||||
this.resourcePageForm.pageOrderName = column.prop
|
||||
this.resourcePageForm.pageOrderBy = column.order
|
||||
this.loadResources()
|
||||
},
|
||||
resourceSizeChange(val) {
|
||||
this.resourcePageForm.pageSize = val
|
||||
this.loadResources()
|
||||
},
|
||||
resourceNumberChange(val) {
|
||||
this.resourcePageForm.pageNumber = val
|
||||
this.loadResources()
|
||||
},
|
||||
resourceIndex(index) {
|
||||
return index + (this.resourcePageForm.pageNumber - 1) * this.resourcePageForm.pageSize + 1
|
||||
},
|
||||
openResourceForm(row = null, node = null) {
|
||||
const target = node || this.selectedNode
|
||||
if (!target.id) {
|
||||
this.$message.warning("请先选择章/节节点")
|
||||
return
|
||||
}
|
||||
if (row) {
|
||||
this.resourceForm = Object.assign({}, row, {
|
||||
outlineName: target.title,
|
||||
fileData: this.parseFiles(row.fileData),
|
||||
durationMinutes: this.formatDurationMinutes(row.durationSeconds)
|
||||
})
|
||||
} else {
|
||||
this.resourceForm = {
|
||||
courseId: target.courseId,
|
||||
outlineId: target.id,
|
||||
outlineName: target.title,
|
||||
resourceTitle: "",
|
||||
resourceType: "video",
|
||||
fileExt: "",
|
||||
fileData: [],
|
||||
durationSeconds: 0,
|
||||
durationMinutes: 0,
|
||||
sortOrder: 1,
|
||||
required: false,
|
||||
allowPreview: true,
|
||||
allowDownload: false,
|
||||
status: "enabled"
|
||||
}
|
||||
}
|
||||
this.resourceTypeChange(this.resourceForm.resourceType)
|
||||
this.resourceDialogVisible = true
|
||||
this.$nextTick(() => this.$refs.resourceForm && this.$refs.resourceForm.clearValidate())
|
||||
},
|
||||
resourceTypeChange(val) {
|
||||
const type = this.resourceTypeOptions.find(v => v.code === val)
|
||||
this.fileAccept = type ? type.accept : ""
|
||||
this.autoFillMediaDuration()
|
||||
},
|
||||
submitResource() {
|
||||
this.$refs.resourceForm.validate(async valid => {
|
||||
if (!valid) return
|
||||
const data = Object.assign({}, this.resourceForm)
|
||||
data.durationSeconds = Math.round((data.durationMinutes || 0) * 60)
|
||||
data.fileExt = this.getFileExt(data.fileData)
|
||||
if (!this.validResourceExt(data.resourceType, data.fileExt)) {
|
||||
this.$message.warning("当前资料类型不支持上传 ." + data.fileExt + " 格式文件")
|
||||
return
|
||||
}
|
||||
delete data.outlineName
|
||||
delete data.durationMinutes
|
||||
data.fileData = JSON.stringify(data.fileData || [])
|
||||
const resp = await this.$axios.post(loc() + "/saveResource", data)
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.resourceDialogVisible = false
|
||||
this.loadResources()
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
deleteResource(row) {
|
||||
this.$confirm("确定要删除该学习资料吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(async () => {
|
||||
const resp = await this.$axios.post(loc() + "/deleteResource", { id: row.id })
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.loadResources()
|
||||
}
|
||||
})
|
||||
},
|
||||
async loadRule() {
|
||||
if (!this.selectedNode.id) return
|
||||
const resp = await this.$axios.post(loc() + "/getRule", {
|
||||
courseId: this.selectedNode.courseId,
|
||||
targetType: "outline",
|
||||
targetId: this.selectedNode.id
|
||||
})
|
||||
if (resp.code === 0) {
|
||||
this.ruleForm = Object.assign({}, resp.data, {
|
||||
minStudyMinutes: this.formatDurationMinutes(resp.data.minStudySeconds)
|
||||
})
|
||||
}
|
||||
},
|
||||
async saveRule() {
|
||||
const data = Object.assign({}, this.ruleForm, {
|
||||
minStudySeconds: Math.round((this.ruleForm.minStudyMinutes || 0) * 60)
|
||||
})
|
||||
delete data.minStudyMinutes
|
||||
const resp = await this.$axios.post(loc() + "/saveRule", data)
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
await this.loadTree()
|
||||
await this.loadRule()
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
},
|
||||
parseFiles(value) {
|
||||
if (!value) return []
|
||||
if (Array.isArray(value)) return value
|
||||
try {
|
||||
return JSON.parse(value)
|
||||
} catch (e) {
|
||||
return []
|
||||
}
|
||||
},
|
||||
getFileUrl(files) {
|
||||
const file = Array.isArray(files) && files.length ? files[0] : null
|
||||
if (!file) return ""
|
||||
return file.url || file.response?.data || file.data || ""
|
||||
},
|
||||
getFileExt(files) {
|
||||
const file = Array.isArray(files) && files.length ? files[0] : null
|
||||
if (!file) return ""
|
||||
const name = file.name || file.url || file.response?.data || ""
|
||||
const index = name.lastIndexOf(".")
|
||||
return index > -1 ? name.substring(index + 1).toLowerCase() : ""
|
||||
},
|
||||
formatDurationMinutes(durationSeconds) {
|
||||
const seconds = Number(durationSeconds || 0)
|
||||
return seconds > 0 ? Math.ceil(seconds / 60) : 0
|
||||
},
|
||||
autoFillMediaDuration() {
|
||||
if (!["video", "audio"].includes(this.resourceForm.resourceType)) return
|
||||
const url = this.getFileUrl(this.resourceForm.fileData)
|
||||
if (!url) return
|
||||
const media = document.createElement(this.resourceForm.resourceType === "video" ? "video" : "audio")
|
||||
media.preload = "metadata"
|
||||
media.onloadedmetadata = () => {
|
||||
window.URL.revokeObjectURL(media.src)
|
||||
if (isFinite(media.duration) && media.duration > 0) {
|
||||
this.$set(this.resourceForm, "durationMinutes", Math.ceil(media.duration / 60))
|
||||
}
|
||||
}
|
||||
media.onerror = () => {
|
||||
window.URL.revokeObjectURL(media.src)
|
||||
}
|
||||
media.src = url
|
||||
},
|
||||
validResourceExt(resourceType, fileExt) {
|
||||
const item = this.resourceTypeOptions.find(v => v.code === resourceType)
|
||||
if (!item || !item.accept) return true
|
||||
return item.accept.split(",").map(v => v.replace(".", "").toLowerCase()).includes(fileExt)
|
||||
},
|
||||
getResourceTypeName(code) {
|
||||
const item = this.resourceTypeOptions.find(v => v.code === code)
|
||||
return item ? item.name : code
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
"resourceForm.fileData": {
|
||||
handler() {
|
||||
this.autoFillMediaDuration()
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
document.addEventListener("click", () => this.contextVisible = false)
|
||||
},
|
||||
async created() {
|
||||
await this.loadCourseTypes()
|
||||
const courseId = this.getQueryParam("courseId")
|
||||
if (courseId) {
|
||||
this.currentCourseId = courseId
|
||||
this.courseQuery.courseId = courseId
|
||||
await this.loadCourses(true)
|
||||
this.$delete(this.courseQuery, "courseId")
|
||||
} else {
|
||||
await this.loadCourses()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,12 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<el-card shadow="never">
|
||||
<div slot="header">课程介绍</div>
|
||||
<el-empty description="课程介绍页面后续设计"></el-empty>
|
||||
</el-card>
|
||||
</div>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,350 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<style>
|
||||
.course-display {
|
||||
padding: 8px 10px 22px;
|
||||
background: #f4f5f7;
|
||||
min-height: calc(100vh - 88px);
|
||||
}
|
||||
.course-filter {
|
||||
background: #fff;
|
||||
border: 1px solid #e5e8ef;
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
.filter-row {
|
||||
display: flex;
|
||||
min-height: 70px;
|
||||
border-bottom: 1px solid #e8ebf0;
|
||||
}
|
||||
.filter-row:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
.filter-label {
|
||||
width: 132px;
|
||||
padding: 20px 16px;
|
||||
background: #eef5ff;
|
||||
color: #1f2d3d;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
.filter-options {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px 18px;
|
||||
padding: 16px 18px;
|
||||
}
|
||||
.filter-chip {
|
||||
min-width: 76px;
|
||||
height: 32px;
|
||||
padding: 0 18px;
|
||||
border: 0;
|
||||
border-radius: 9px;
|
||||
background: transparent;
|
||||
color: #526070;
|
||||
cursor: pointer;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
}
|
||||
.filter-chip.active {
|
||||
background: #1e63e9;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
}
|
||||
.course-result-bar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.course-count {
|
||||
color: #1f2d3d;
|
||||
}
|
||||
.course-count span {
|
||||
color: #2468f2;
|
||||
padding: 0 4px;
|
||||
}
|
||||
.course-search {
|
||||
width: 310px;
|
||||
}
|
||||
.course-list {
|
||||
background: #fff;
|
||||
border-top: 1px solid #dfe4ec;
|
||||
}
|
||||
.course-item {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
padding: 18px 0;
|
||||
border-bottom: 1px solid #dfe4ec;
|
||||
}
|
||||
.course-cover {
|
||||
position: relative;
|
||||
width: 270px;
|
||||
height: 150px;
|
||||
flex: 0 0 270px;
|
||||
background: #e9eef5;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.course-cover img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
.course-cover-empty {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #8a97a8;
|
||||
background: linear-gradient(135deg, #eaf0f8, #d8e3f2);
|
||||
font-weight: 600;
|
||||
}
|
||||
.course-cover-tag {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 12px;
|
||||
min-width: 70px;
|
||||
height: 24px;
|
||||
padding: 0 10px;
|
||||
background: #f5a400;
|
||||
color: #fff;
|
||||
font-size: 12px;
|
||||
line-height: 24px;
|
||||
text-align: center;
|
||||
border-radius: 12px 0 0 12px;
|
||||
}
|
||||
.course-info {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding-right: 12px;
|
||||
}
|
||||
.course-title {
|
||||
display: inline-block;
|
||||
margin: 4px 0 8px;
|
||||
color: #111827;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
cursor: pointer;
|
||||
}
|
||||
.course-title:hover {
|
||||
color: #006fc9;
|
||||
}
|
||||
.course-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px 18px;
|
||||
color: #8b96a6;
|
||||
font-size: 13px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.course-intro {
|
||||
color: #4f5f73;
|
||||
line-height: 1.8;
|
||||
margin-bottom: 12px;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
.course-time {
|
||||
color: #8b96a6;
|
||||
font-size: 13px;
|
||||
}
|
||||
.recommend-tags {
|
||||
display: inline-flex;
|
||||
gap: 6px;
|
||||
margin-left: 8px;
|
||||
vertical-align: 2px;
|
||||
}
|
||||
.course-empty {
|
||||
background: #fff;
|
||||
padding: 80px 0;
|
||||
text-align: center;
|
||||
color: #8b96a6;
|
||||
border-top: 1px solid #dfe4ec;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" class="course-display" v-cloak>
|
||||
<div class="course-filter">
|
||||
<div class="filter-row">
|
||||
<div class="filter-label">推荐标识</div>
|
||||
<div class="filter-options">
|
||||
<button class="filter-chip" :class="{active: !query.recommendFlag}" @click="selectRecommend('')">全部</button>
|
||||
<button
|
||||
v-for="item in recommendOptions"
|
||||
:key="item.code"
|
||||
class="filter-chip"
|
||||
:class="{active: query.recommendFlag === item.code}"
|
||||
@click="selectRecommend(item.code)">
|
||||
{{item.name}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-row">
|
||||
<div class="filter-label">课程类型</div>
|
||||
<div class="filter-options">
|
||||
<button class="filter-chip" :class="{active: !query.courseTypeId}" @click="selectCourseType('')">全部</button>
|
||||
<button
|
||||
v-for="item in courseTypeOptions"
|
||||
:key="item.id"
|
||||
class="filter-chip"
|
||||
:class="{active: query.courseTypeId === item.id}"
|
||||
@click="selectCourseType(item.id)">
|
||||
{{item.typeName}}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="course-result-bar">
|
||||
<div class="course-count">为您找到相关课程<span>{{pageForm.totalCount || 0}}</span>门</div>
|
||||
<el-input
|
||||
class="course-search"
|
||||
v-model="query.keyword"
|
||||
clearable
|
||||
placeholder="搜索关键字"
|
||||
prefix-icon="el-icon-search"
|
||||
@keyup.enter.native="doSearch"
|
||||
@clear="doSearch">
|
||||
<el-button slot="append" icon="el-icon-search" @click="doSearch"></el-button>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<div v-if="pageForm.list && pageForm.list.length" class="course-list">
|
||||
<div v-for="course in pageForm.list" :key="course.id" class="course-item">
|
||||
<div class="course-cover">
|
||||
<img v-if="getCoverUrl(course.cover)" :src="getCoverUrl(course.cover)" :alt="course.courseName">
|
||||
<div v-else class="course-cover-empty">{{course.courseTypeName || '课程'}}</div>
|
||||
<div v-if="firstRecommendName(course.recommendFlags)" class="course-cover-tag">{{firstRecommendName(course.recommendFlags)}}</div>
|
||||
</div>
|
||||
<div class="course-info">
|
||||
<div>
|
||||
<span class="course-title" @click="openDetail(course)">{{course.courseName}}</span>
|
||||
<span class="recommend-tags">
|
||||
<el-tag v-for="code in splitFlags(course.recommendFlags)" :key="code" size="mini" type="warning">{{getRecommendName(code)}}</el-tag>
|
||||
</span>
|
||||
</div>
|
||||
<div class="course-meta">
|
||||
<span><i class="el-icon-office-building"></i> {{course.courseTypeName || '未设置课程类型'}}</span>
|
||||
<span><i class="el-icon-user"></i> {{course.lecturerName || '未设置讲师'}}</span>
|
||||
</div>
|
||||
<div class="course-intro">{{course.courseIntro || '暂无课程介绍'}}</div>
|
||||
<div class="course-time">
|
||||
<i class="el-icon-time"></i>
|
||||
<span v-if="course.openType === 'long_term'">长期开放</span>
|
||||
<span v-else>{{course.startTimeText || '-'}} 至 {{course.endTimeText || '-'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="course-empty">暂无相关课程</div>
|
||||
|
||||
<el-pagination
|
||||
class="mt20"
|
||||
background
|
||||
layout="total, sizes, prev, pager, next"
|
||||
:current-page.sync="pageForm.pageNumber"
|
||||
:page-size.sync="pageForm.pageSize"
|
||||
:total="pageForm.totalCount"
|
||||
@size-change="pageData"
|
||||
@current-change="pageData">
|
||||
</el-pagination>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
query: {
|
||||
keyword: "",
|
||||
courseTypeId: "",
|
||||
recommendFlag: ""
|
||||
},
|
||||
courseTypeOptions: [],
|
||||
recommendOptions: [],
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
list: []
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async pageData() {
|
||||
const resp = await this.$axios.post(loc() + "/pageData", Object.assign({}, this.query, {
|
||||
pageNumber: this.pageForm.pageNumber,
|
||||
pageSize: this.pageForm.pageSize
|
||||
}))
|
||||
if (resp.code === 0) {
|
||||
this.pageForm = resp.data
|
||||
}
|
||||
},
|
||||
doSearch() {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageData()
|
||||
},
|
||||
selectRecommend(code) {
|
||||
this.query.recommendFlag = code
|
||||
this.doSearch()
|
||||
},
|
||||
selectCourseType(id) {
|
||||
this.query.courseTypeId = id
|
||||
this.doSearch()
|
||||
},
|
||||
async loadOptions() {
|
||||
const typeResp = await this.$axios.post(loc() + "/courseTypes")
|
||||
if (typeResp.code === 0) {
|
||||
this.courseTypeOptions = typeResp.data
|
||||
}
|
||||
const recommendResp = await this.$axios.post(loc() + "/recommendOptions")
|
||||
if (recommendResp.code === 0) {
|
||||
this.recommendOptions = recommendResp.data
|
||||
}
|
||||
},
|
||||
getCoverUrl(cover) {
|
||||
if (!cover) return ""
|
||||
if (Array.isArray(cover)) {
|
||||
return cover.length ? (cover[0].url || cover[0].response?.data || cover[0].data || "") : ""
|
||||
}
|
||||
if (typeof cover === "string" && cover.trim().startsWith("[")) {
|
||||
try {
|
||||
const files = JSON.parse(cover)
|
||||
return files.length ? (files[0].url || files[0].response?.data || files[0].data || "") : ""
|
||||
} catch (e) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return cover
|
||||
},
|
||||
splitFlags(flags) {
|
||||
return flags ? flags.split(",").filter(Boolean) : []
|
||||
},
|
||||
getRecommendName(code) {
|
||||
const item = this.recommendOptions.find(v => v.code === code)
|
||||
return item ? item.name : code
|
||||
},
|
||||
firstRecommendName(flags) {
|
||||
const codes = this.splitFlags(flags)
|
||||
return codes.length ? this.getRecommendName(codes[0]) : ""
|
||||
},
|
||||
openDetail(course) {
|
||||
window.location.href = loc() + "/study?id=" + course.id
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
await this.loadOptions()
|
||||
await this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,755 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<style>
|
||||
#sidebar-menu,
|
||||
#menu-toggle-btn,
|
||||
#menu-overlay {
|
||||
display: none !important;
|
||||
}
|
||||
#sub-app-container-main-content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
#sub-app-container-main-content-body {
|
||||
height: 100%;
|
||||
}
|
||||
.study-page {
|
||||
min-height: calc(100vh - 50px);
|
||||
background: #eef3f8;
|
||||
}
|
||||
.study-header {
|
||||
height: 150px;
|
||||
margin: 12px 14px 0;
|
||||
padding: 20px 24px;
|
||||
color: #1f2937;
|
||||
background: linear-gradient(120deg, #ffffff 0%, #f4f8ff 58%, #eaf2ff 100%);
|
||||
border: 1px solid #dbe7f5;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 6px 18px rgba(31, 64, 116, .08);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 22px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.study-back {
|
||||
align-self: flex-start;
|
||||
margin-left: auto;
|
||||
order: 5;
|
||||
color: #1e63c8;
|
||||
border-color: #b8d0f2;
|
||||
background: #fff;
|
||||
}
|
||||
.study-cover {
|
||||
width: 190px;
|
||||
height: 102px;
|
||||
object-fit: cover;
|
||||
background: #eef4fb;
|
||||
border: 1px solid #d7e3f1;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 12px rgba(31, 64, 116, .12);
|
||||
}
|
||||
.study-cover-empty {
|
||||
width: 190px;
|
||||
height: 102px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #eef4fb;
|
||||
color: #6b7d90;
|
||||
border: 1px solid #d7e3f1;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 12px rgba(31, 64, 116, .12);
|
||||
font-weight: 600;
|
||||
}
|
||||
.study-course-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
.study-title {
|
||||
font-size: 28px;
|
||||
line-height: 1.2;
|
||||
font-weight: 700;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.study-title .el-tag {
|
||||
margin-left: 8px;
|
||||
vertical-align: 5px;
|
||||
}
|
||||
.study-meta {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px 26px;
|
||||
color: #526376;
|
||||
font-size: 14px;
|
||||
}
|
||||
.study-body {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
height: calc(100vh - 212px);
|
||||
padding: 14px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.study-tree-panel {
|
||||
width: 340px;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.study-tree-title {
|
||||
height: 54px;
|
||||
padding: 0 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: #1768e5;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
border-bottom: 1px solid #edf1f7;
|
||||
}
|
||||
.study-tree {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
}
|
||||
.study-node {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
max-width: 100%;
|
||||
gap: 6px;
|
||||
}
|
||||
.study-node-title {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.study-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
background: #fff;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.resource-head {
|
||||
min-height: 54px;
|
||||
padding: 0 18px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #edf1f7;
|
||||
}
|
||||
.resource-title {
|
||||
color: #1f2937;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.resource-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
.study-progress {
|
||||
width: 180px;
|
||||
}
|
||||
.resource-view {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 20px;
|
||||
background: #f8fafc;
|
||||
}
|
||||
.media-player {
|
||||
width: 100%;
|
||||
max-height: calc(100vh - 300px);
|
||||
background: #000;
|
||||
}
|
||||
.audio-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 320px;
|
||||
background: linear-gradient(135deg, #132544, #0a1224);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.audio-wrap audio {
|
||||
width: 80%;
|
||||
}
|
||||
.image-preview {
|
||||
max-width: 100%;
|
||||
display: block;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
}
|
||||
.doc-frame {
|
||||
width: 100%;
|
||||
height: calc(100vh - 275px);
|
||||
border: 0;
|
||||
background: #fff;
|
||||
}
|
||||
.file-fallback {
|
||||
padding: 60px 20px;
|
||||
text-align: center;
|
||||
color: #667085;
|
||||
background: #fff;
|
||||
border-radius: 6px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" class="study-page" v-cloak>
|
||||
<div class="study-header">
|
||||
<el-button class="study-back" icon="el-icon-back" size="small" @click="goBack">返回</el-button>
|
||||
<img v-if="coverUrl" class="study-cover" :src="coverUrl" :alt="course.courseName">
|
||||
<div v-else class="study-cover-empty">课程图片</div>
|
||||
<div class="study-course-main">
|
||||
<div class="study-title">
|
||||
{{course.courseName || '课程学习'}}
|
||||
<el-tag v-for="code in splitFlags(course.recommendFlags)" :key="code" size="small" type="warning">{{getRecommendName(code)}}</el-tag>
|
||||
</div>
|
||||
<div class="study-meta">
|
||||
<span><i class="el-icon-user"></i> 授课讲师:{{course.lecturerName || '未设置'}}</span>
|
||||
<span><i class="el-icon-date"></i> {{course.openType === 'long_term' ? '长期开放' : ((course.startTimeText || '-') + ' 至 ' + (course.endTimeText || '-'))}}</span>
|
||||
<span><i class="el-icon-collection-tag"></i> 课程类型:{{course.courseTypeName || '未设置'}}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="study-body">
|
||||
<div class="study-tree-panel">
|
||||
<div class="study-tree-title">
|
||||
<span>课程安排</span>
|
||||
<el-button type="text" size="mini" @click="collapseAll">收起</el-button>
|
||||
</div>
|
||||
<div class="study-tree">
|
||||
<el-tree
|
||||
ref="studyTree"
|
||||
:data="treeData"
|
||||
node-key="id"
|
||||
default-expand-all
|
||||
highlight-current
|
||||
:expand-on-click-node="false"
|
||||
:props="{children:'children', label:'title'}"
|
||||
@node-click="nodeClick">
|
||||
<span slot-scope="{ data }" class="study-node">
|
||||
<i :class="getNodeIcon(data)"></i>
|
||||
<span class="study-node-title">{{data.title}}</span>
|
||||
<el-tag v-if="data.required" size="mini" type="warning">必学</el-tag>
|
||||
</span>
|
||||
</el-tree>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="study-content">
|
||||
<div class="resource-head">
|
||||
<div class="resource-title">{{selectedResource.title || '请选择课程资料'}}</div>
|
||||
<div class="resource-actions">
|
||||
<el-progress
|
||||
v-if="selectedResource.id"
|
||||
class="study-progress"
|
||||
:percentage="recordProgress"
|
||||
:stroke-width="8">
|
||||
</el-progress>
|
||||
<el-tag v-if="studying" size="mini" type="success">学习中 {{recordStudyTime}}</el-tag>
|
||||
<el-button v-if="selectedResource.id && !studying" size="mini" type="primary" icon="el-icon-video-play" @click="startStudy">开始学习</el-button>
|
||||
<el-button v-if="studying" size="mini" type="danger" icon="el-icon-video-pause" @click="finishStudy(false)">结束学习</el-button>
|
||||
<el-button v-if="fileUrl" size="mini" type="text" icon="el-icon-download" @click="openFile">打开原文件</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="resource-view">
|
||||
<template v-if="selectedResource.id">
|
||||
<video
|
||||
v-if="selectedResource.resourceType === 'video'"
|
||||
ref="mediaPlayer"
|
||||
class="media-player"
|
||||
:src="fileUrl"
|
||||
controls
|
||||
controlslist="nodownload"
|
||||
@play="mediaPlay"
|
||||
@ended="mediaEnded"
|
||||
@timeupdate="saveProgress"
|
||||
@loadedmetadata="mediaReady">
|
||||
</video>
|
||||
<div v-else-if="selectedResource.resourceType === 'audio'" class="audio-wrap">
|
||||
<audio
|
||||
ref="mediaPlayer"
|
||||
:src="fileUrl"
|
||||
controls
|
||||
controlslist="nodownload"
|
||||
@play="mediaPlay"
|
||||
@ended="mediaEnded"
|
||||
@timeupdate="saveProgress"
|
||||
@loadedmetadata="mediaReady">
|
||||
</audio>
|
||||
</div>
|
||||
<img v-else-if="selectedResource.resourceType === 'image'" class="image-preview" :src="fileUrl" :alt="selectedResource.title">
|
||||
<iframe v-else-if="canInlinePreview(selectedResource)" class="doc-frame" :src="inlinePreviewUrl"></iframe>
|
||||
<div v-else class="file-fallback">
|
||||
<file-preview :files="selectedResource.fileData" complete_result></file-preview>
|
||||
<el-button class="mt20" type="primary" icon="el-icon-view" @click="openFile">打开资料</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<el-empty v-else description="请选择左侧课程资料"></el-empty>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data() {
|
||||
return {
|
||||
courseId: "",
|
||||
apiBase: "/platform/learning/course/display",
|
||||
recordApi: "/platform/learning/study/record",
|
||||
course: {},
|
||||
treeData: [],
|
||||
selectedResource: {},
|
||||
recommendOptions: [],
|
||||
studying: false,
|
||||
currentSegmentId: "",
|
||||
recordProgress: 0,
|
||||
recordStudyTime: "0秒",
|
||||
pendingSeconds: 0,
|
||||
heartbeatTimer: null,
|
||||
startingStudy: false,
|
||||
autoResumeKey: "",
|
||||
lastPositionSyncAt: 0,
|
||||
lastActiveAt: Date.now(),
|
||||
inactiveLimit: 60000
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
coverUrl() {
|
||||
return this.getFileUrl(this.course.cover)
|
||||
},
|
||||
fileUrl() {
|
||||
return this.getFileUrl(this.selectedResource.fileData)
|
||||
},
|
||||
inlinePreviewUrl() {
|
||||
const id = this.getFileId(this.selectedResource.fileData)
|
||||
return id ? this.apiBase + "/pdfPreview?id=" + encodeURIComponent(id) : this.fileUrl
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getQuery(name) {
|
||||
return new URLSearchParams(window.location.search).get(name) || ""
|
||||
},
|
||||
async loadCourse() {
|
||||
const resp = await this.$axios.post(this.apiBase + "/courseInfo", { id: this.courseId })
|
||||
if (resp.code === 0) {
|
||||
this.course = resp.data
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
},
|
||||
async loadTree() {
|
||||
const resp = await this.$axios.post(this.apiBase + "/studyTree", { courseId: this.courseId })
|
||||
if (resp.code === 0) {
|
||||
this.treeData = resp.data || []
|
||||
const first = this.findFirstResource(this.treeData)
|
||||
if (first) {
|
||||
this.nodeClick(first)
|
||||
this.$nextTick(() => this.$refs.studyTree && this.$refs.studyTree.setCurrentKey(first.id))
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadRecommendOptions() {
|
||||
const resp = await this.$axios.post(this.apiBase + "/recommendOptions")
|
||||
if (resp.code === 0) {
|
||||
this.recommendOptions = resp.data
|
||||
}
|
||||
},
|
||||
findFirstResource(nodes) {
|
||||
for (const node of nodes || []) {
|
||||
if (node.type === "resource") return node
|
||||
const child = this.findFirstResource(node.children || [])
|
||||
if (child) return child
|
||||
}
|
||||
return null
|
||||
},
|
||||
nodeClick(data) {
|
||||
if (data.type !== "resource") return
|
||||
if (this.studying && this.selectedResource.id !== data.id) {
|
||||
this.$message.warning("请先结束当前章节资料的学习")
|
||||
return
|
||||
}
|
||||
this.selectedResource = data
|
||||
this.recordProgress = data.progressPercent || 0
|
||||
this.recordStudyTime = this.formatSeconds(Number(data.studySeconds || 0))
|
||||
},
|
||||
getNodeIcon(data) {
|
||||
if (data.type === "resource") {
|
||||
const map = {
|
||||
video: "el-icon-video-camera",
|
||||
audio: "el-icon-headset",
|
||||
image: "el-icon-picture-outline",
|
||||
pdf: "el-icon-document",
|
||||
word: "el-icon-document",
|
||||
ppt: "el-icon-document"
|
||||
}
|
||||
return map[data.resourceType] || "el-icon-paperclip"
|
||||
}
|
||||
return data.nodeType === "chapter" ? "el-icon-folder" : "el-icon-notebook-2"
|
||||
},
|
||||
getFileUrl(value) {
|
||||
if (!value) return ""
|
||||
if (Array.isArray(value)) {
|
||||
return value.length ? (value[0].url || value[0].response?.data || value[0].data || "") : ""
|
||||
}
|
||||
if (typeof value === "string" && value.trim().startsWith("[")) {
|
||||
try {
|
||||
const files = JSON.parse(value)
|
||||
return files.length ? (files[0].url || files[0].response?.data || files[0].data || "") : ""
|
||||
} catch (e) {
|
||||
return ""
|
||||
}
|
||||
}
|
||||
return value
|
||||
},
|
||||
getFileId(value) {
|
||||
const url = this.getFileUrl(value)
|
||||
if (!url) return ""
|
||||
const matched = url.match(/[?&]id=([^&]+)/)
|
||||
if (matched) return decodeURIComponent(matched[1])
|
||||
if (!url.includes("/") && !url.includes(".")) return url
|
||||
return ""
|
||||
},
|
||||
canInlinePreview(resource) {
|
||||
const type = resource.resourceType
|
||||
const ext = (resource.fileExt || "").toLowerCase()
|
||||
return ["pdf", "ppt", "word"].includes(type) || ["pdf", "ppt", "pptx", "doc", "docx"].includes(ext)
|
||||
},
|
||||
progressKey() {
|
||||
return "learning-progress-" + this.courseId + "-" + this.selectedResource.id
|
||||
},
|
||||
mediaReady() {
|
||||
this.askResume()
|
||||
},
|
||||
askResume() {
|
||||
if (!["video", "audio"].includes(this.selectedResource.resourceType)) return
|
||||
if (this.studying || this.startingStudy) return
|
||||
const currentKey = this.progressKey()
|
||||
if (this.autoResumeKey === currentKey) return
|
||||
this.autoResumeKey = currentKey
|
||||
const player = this.$refs.mediaPlayer
|
||||
const localSaved = Number(localStorage.getItem(this.progressKey()) || 0)
|
||||
const serverSaved = Number(this.selectedResource.lastPositionSeconds || 0)
|
||||
const saved = Math.max(localSaved, serverSaved)
|
||||
if (!player || !saved || saved < 5 || saved >= player.duration - 5) return
|
||||
this.$confirm("检测到上次学习到 " + this.formatSeconds(saved) + ",请选择继续学习或从头开始。", "继续学习", {
|
||||
confirmButtonText: "继续学习",
|
||||
cancelButtonText: "从头开始",
|
||||
type: "info"
|
||||
}).then(() => {
|
||||
this.startStudy({ autoPlay: true, seekTo: saved })
|
||||
}).catch(() => {
|
||||
localStorage.removeItem(this.progressKey())
|
||||
this.saveServerPosition(0)
|
||||
this.startStudy({ autoPlay: true, seekTo: 0 })
|
||||
})
|
||||
},
|
||||
async mediaPlay() {
|
||||
if (this.studying || this.startingStudy) return
|
||||
await this.startStudy({ fromMedia: true })
|
||||
},
|
||||
mediaEnded() {
|
||||
this.finishStudy(false, { silent: true })
|
||||
localStorage.removeItem(this.progressKey())
|
||||
},
|
||||
async playSelectedMedia(seekTo) {
|
||||
if (!["video", "audio"].includes(this.selectedResource.resourceType)) return
|
||||
await this.$nextTick()
|
||||
const player = this.$refs.mediaPlayer
|
||||
if (!player) return
|
||||
try {
|
||||
if (typeof seekTo === "number" && !Number.isNaN(seekTo)) {
|
||||
await this.seekBeforePlay(player, seekTo)
|
||||
}
|
||||
const playPromise = player.play && player.play()
|
||||
if (playPromise && playPromise.catch) {
|
||||
await playPromise.catch(() => {
|
||||
this.$message.warning("浏览器阻止了自动播放,请点击播放器播放按钮继续")
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
this.$message.warning("播放器定位失败,请手动点击播放后继续")
|
||||
}
|
||||
},
|
||||
async seekBeforePlay(player, seekTo) {
|
||||
await this.waitForMediaReady(player)
|
||||
const target = this.normalizeSeekTarget(player, seekTo)
|
||||
if (target === null) return
|
||||
if (Math.abs((player.currentTime || 0) - target) <= 1) return
|
||||
player.pause()
|
||||
player.currentTime = target
|
||||
await this.waitForSeek(player, target, 1800)
|
||||
if (Math.abs((player.currentTime || 0) - target) > 1) {
|
||||
player.currentTime = target
|
||||
await this.waitForSeek(player, target, 1200)
|
||||
}
|
||||
},
|
||||
waitForMediaReady(player) {
|
||||
if (player.readyState >= 1 && !Number.isNaN(player.duration)) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
const done = () => {
|
||||
player.removeEventListener("loadedmetadata", done)
|
||||
player.removeEventListener("durationchange", done)
|
||||
resolve()
|
||||
}
|
||||
player.addEventListener("loadedmetadata", done, { once: true })
|
||||
player.addEventListener("durationchange", done, { once: true })
|
||||
setTimeout(done, 2000)
|
||||
})
|
||||
},
|
||||
waitForSeek(player, target, timeout) {
|
||||
return new Promise(resolve => {
|
||||
const done = () => {
|
||||
player.removeEventListener("seeked", done)
|
||||
player.removeEventListener("timeupdate", done)
|
||||
resolve()
|
||||
}
|
||||
player.addEventListener("seeked", done, { once: true })
|
||||
player.addEventListener("timeupdate", done, { once: true })
|
||||
setTimeout(done, timeout)
|
||||
})
|
||||
},
|
||||
normalizeSeekTarget(player, seekTo) {
|
||||
const raw = Math.max(0, Number(seekTo || 0))
|
||||
if (Number.isNaN(raw)) return null
|
||||
if (!Number.isNaN(player.duration) && player.duration > 0) {
|
||||
return Math.min(raw, Math.max(0, player.duration - 1))
|
||||
}
|
||||
return raw
|
||||
},
|
||||
saveProgress() {
|
||||
if (!["video", "audio"].includes(this.selectedResource.resourceType)) return
|
||||
const player = this.$refs.mediaPlayer
|
||||
if (player && player.currentTime > 0) {
|
||||
const position = Math.floor(player.currentTime)
|
||||
localStorage.setItem(this.progressKey(), String(position))
|
||||
this.$set(this.selectedResource, "lastPositionSeconds", position)
|
||||
if (Date.now() - this.lastPositionSyncAt > 5000) {
|
||||
this.saveServerPosition(position)
|
||||
}
|
||||
}
|
||||
},
|
||||
saveServerPosition(position, useBeacon) {
|
||||
if (!this.selectedResource.id || !["video", "audio"].includes(this.selectedResource.resourceType)) return
|
||||
const seconds = Math.max(0, Math.floor(Number(position || 0)))
|
||||
this.lastPositionSyncAt = Date.now()
|
||||
if (useBeacon && navigator.sendBeacon) {
|
||||
const body = new URLSearchParams()
|
||||
body.append("courseId", this.courseId)
|
||||
body.append("resourceId", this.selectedResource.id)
|
||||
body.append("positionSeconds", String(seconds))
|
||||
const blob = new Blob([body.toString()], { type: "application/x-www-form-urlencoded;charset=UTF-8" })
|
||||
navigator.sendBeacon(this.recordApi + "/position", blob)
|
||||
return
|
||||
}
|
||||
this.$axios.post(this.recordApi + "/position", {
|
||||
courseId: this.courseId,
|
||||
resourceId: this.selectedResource.id,
|
||||
positionSeconds: seconds
|
||||
})
|
||||
},
|
||||
async startStudy(options) {
|
||||
const config = options && !options.target ? options : {}
|
||||
if (!this.selectedResource.id) {
|
||||
this.$message.warning("请选择左侧课程资料")
|
||||
return
|
||||
}
|
||||
if (this.studying) {
|
||||
if (config.autoPlay) {
|
||||
this.playSelectedMedia(config.seekTo)
|
||||
}
|
||||
return
|
||||
}
|
||||
this.startingStudy = true
|
||||
let resp
|
||||
try {
|
||||
resp = await this.$axios.post(this.recordApi + "/start", {
|
||||
courseId: this.courseId,
|
||||
resourceId: this.selectedResource.id,
|
||||
positionSeconds: typeof config.seekTo === "number" ? Math.floor(config.seekTo) : this.getMediaPosition()
|
||||
})
|
||||
} finally {
|
||||
this.startingStudy = false
|
||||
}
|
||||
if (resp.code !== 0) {
|
||||
this.$message.warning(resp.msg)
|
||||
return
|
||||
}
|
||||
this.currentSegmentId = resp.data.segmentId
|
||||
this.studying = true
|
||||
this.pendingSeconds = 0
|
||||
this.lastActiveAt = Date.now()
|
||||
this.applyRecordState(resp.data)
|
||||
this.startHeartbeatTimer()
|
||||
if (config.autoPlay !== false && !config.fromMedia) {
|
||||
this.playSelectedMedia(config.seekTo)
|
||||
}
|
||||
this.$message.success("已开始学习")
|
||||
},
|
||||
async heartbeat() {
|
||||
if (!this.studying || !this.currentSegmentId || this.pendingSeconds <= 0) return
|
||||
const seconds = this.pendingSeconds
|
||||
this.pendingSeconds = 0
|
||||
const resp = await this.$axios.post(this.recordApi + "/heartbeat", {
|
||||
segmentId: this.currentSegmentId,
|
||||
activeSeconds: seconds,
|
||||
positionSeconds: this.getMediaPosition()
|
||||
})
|
||||
if (resp.code === 0) {
|
||||
this.applyRecordState(resp.data)
|
||||
} else {
|
||||
this.stopHeartbeatTimer()
|
||||
this.studying = false
|
||||
this.currentSegmentId = ""
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
},
|
||||
async finishStudy(force, options) {
|
||||
const config = options || {}
|
||||
if (!this.currentSegmentId) return
|
||||
const segmentId = this.currentSegmentId
|
||||
const seconds = this.pendingSeconds
|
||||
this.pendingSeconds = 0
|
||||
this.stopHeartbeatTimer()
|
||||
this.studying = false
|
||||
this.currentSegmentId = ""
|
||||
if (!force) {
|
||||
this.pauseSelectedMedia()
|
||||
}
|
||||
if (force && navigator.sendBeacon) {
|
||||
this.saveServerPosition(this.getMediaPosition(), true)
|
||||
const formData = new FormData()
|
||||
formData.append("segmentId", segmentId)
|
||||
formData.append("activeSeconds", String(seconds))
|
||||
formData.append("positionSeconds", String(this.getMediaPosition()))
|
||||
navigator.sendBeacon(this.recordApi + "/finish", formData)
|
||||
return
|
||||
}
|
||||
const resp = await this.$axios.post(this.recordApi + "/finish", {
|
||||
segmentId: segmentId,
|
||||
activeSeconds: seconds,
|
||||
positionSeconds: this.getMediaPosition()
|
||||
})
|
||||
if (resp.code === 0 && resp.data) {
|
||||
this.applyRecordState(resp.data)
|
||||
if (!config.silent) {
|
||||
this.$message.success("学习已结束")
|
||||
}
|
||||
}
|
||||
},
|
||||
pauseSelectedMedia() {
|
||||
if (!["video", "audio"].includes(this.selectedResource.resourceType)) return
|
||||
const player = this.$refs.mediaPlayer
|
||||
if (player && !player.paused) {
|
||||
player.pause()
|
||||
}
|
||||
},
|
||||
startHeartbeatTimer() {
|
||||
this.stopHeartbeatTimer()
|
||||
this.heartbeatTimer = setInterval(() => {
|
||||
if (!this.studying) return
|
||||
if (this.isEffectiveLearning()) {
|
||||
this.pendingSeconds += 1
|
||||
}
|
||||
if (this.pendingSeconds >= 15) {
|
||||
this.heartbeat()
|
||||
}
|
||||
}, 1000)
|
||||
},
|
||||
stopHeartbeatTimer() {
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer)
|
||||
this.heartbeatTimer = null
|
||||
}
|
||||
},
|
||||
isEffectiveLearning() {
|
||||
if (document.hidden) return false
|
||||
if (Date.now() - this.lastActiveAt > this.inactiveLimit) return false
|
||||
if (["video", "audio"].includes(this.selectedResource.resourceType)) {
|
||||
const player = this.$refs.mediaPlayer
|
||||
return !!player && !player.paused && !player.ended
|
||||
}
|
||||
return true
|
||||
},
|
||||
markActive() {
|
||||
this.lastActiveAt = Date.now()
|
||||
},
|
||||
applyRecordState(data) {
|
||||
this.recordProgress = Number(data.progressPercent || 0)
|
||||
this.recordStudyTime = data.studyTimeText || this.formatSeconds(Number(data.studySeconds || 0))
|
||||
if (this.selectedResource.id && data.lastPositionSeconds !== undefined) {
|
||||
this.$set(this.selectedResource, "lastPositionSeconds", Number(data.lastPositionSeconds || 0))
|
||||
}
|
||||
},
|
||||
getMediaPosition() {
|
||||
if (!["video", "audio"].includes(this.selectedResource.resourceType)) return 0
|
||||
const player = this.$refs.mediaPlayer
|
||||
return player ? Math.floor(player.currentTime || 0) : Number(this.selectedResource.lastPositionSeconds || 0)
|
||||
},
|
||||
formatSeconds(seconds) {
|
||||
const hour = Math.floor(seconds / 3600)
|
||||
const minute = Math.floor(seconds % 3600 / 60)
|
||||
const second = Math.floor(seconds % 60)
|
||||
if (hour > 0) return hour + "小时" + minute + "分" + second + "秒"
|
||||
if (minute > 0) return minute + "分" + second + "秒"
|
||||
return second + "秒"
|
||||
},
|
||||
splitFlags(flags) {
|
||||
return flags ? flags.split(",").filter(Boolean) : []
|
||||
},
|
||||
getRecommendName(code) {
|
||||
const item = this.recommendOptions.find(v => v.code === code)
|
||||
return item ? item.name : code
|
||||
},
|
||||
openFile() {
|
||||
if (this.fileUrl) window.open(this.fileUrl)
|
||||
},
|
||||
collapseAll() {
|
||||
const nodesMap = this.$refs.studyTree && this.$refs.studyTree.store.nodesMap
|
||||
Object.keys(nodesMap || {}).forEach(key => {
|
||||
nodesMap[key].expanded = false
|
||||
})
|
||||
},
|
||||
async goBack() {
|
||||
this.saveServerPosition(this.getMediaPosition())
|
||||
await this.finishStudy(false, { silent: true })
|
||||
window.location.href = this.apiBase
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.courseId = this.getQuery("id")
|
||||
window.addEventListener("mousemove", this.markActive)
|
||||
window.addEventListener("keydown", this.markActive)
|
||||
window.addEventListener("click", this.markActive)
|
||||
window.addEventListener("scroll", this.markActive, true)
|
||||
window.addEventListener("beforeunload", () => this.finishStudy(true))
|
||||
document.addEventListener("visibilitychange", () => {
|
||||
if (document.hidden) {
|
||||
this.saveServerPosition(this.getMediaPosition(), true)
|
||||
}
|
||||
})
|
||||
await this.loadRecommendOptions()
|
||||
await this.loadCourse()
|
||||
await this.loadTree()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.finishStudy(true)
|
||||
this.stopHeartbeatTimer()
|
||||
window.removeEventListener("mousemove", this.markActive)
|
||||
window.removeEventListener("keydown", this.markActive)
|
||||
window.removeEventListener("click", this.markActive)
|
||||
window.removeEventListener("scroll", this.markActive, true)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,396 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="课程名称">
|
||||
<el-input v-model="pageForm.courseName" clearable placeholder="请输入课程名称" style="width: 100%" @keyup.enter.native="doSearch"></el-input>
|
||||
</search-item>
|
||||
<search-item label="课程类型">
|
||||
<el-select v-model="pageForm.courseTypeId" clearable filterable placeholder="请选择课程类型" style="width: 100%">
|
||||
<el-option v-for="item in courseTypeOptions" :key="item.id" :label="item.typeName" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="开课时间">
|
||||
<el-date-picker
|
||||
v-model="pageForm.courseTime"
|
||||
type="daterange"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
range-separator="至"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="授课讲师">
|
||||
<el-input v-model="pageForm.lecturerName" clearable placeholder="请输入授课讲师" style="width: 100%" @keyup.enter.native="doSearch"></el-input>
|
||||
</search-item>
|
||||
<search-item label="课程状态">
|
||||
<el-select v-model="pageForm.status" clearable placeholder="请选择课程状态" style="width: 100%">
|
||||
<el-option v-for="item in statusOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="推荐标识">
|
||||
<el-select v-model="pageForm.recommendFlag" clearable placeholder="请选择推荐标识" style="width: 100%">
|
||||
<el-option v-for="item in recommendOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<div class="search-query">
|
||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="课程列表">
|
||||
<el-button @click="openAdd" size="medium" type="primary">
|
||||
<i class="el-icon-plus"></i>
|
||||
新增课程
|
||||
</el-button>
|
||||
</table-tool>
|
||||
|
||||
<el-table
|
||||
v-loading="tableLoading"
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
border
|
||||
@sort-change="pageOrder">
|
||||
<el-table-column :index="indexMethod" align="center" header-align="center" label="序号" type="index" width="80"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="课程名称" prop="courseName" sortable="custom" show-overflow-tooltip min-width="180"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="课程类型" prop="courseTypeName" sortable="custom" show-overflow-tooltip width="140"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="授课讲师" prop="lecturerName" sortable="custom" show-overflow-tooltip width="140"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="开课时间" prop="startTime" sortable="custom" width="230">
|
||||
<template slot-scope="{row}">
|
||||
<span v-if="row.openType === 'long_term'">长期开放</span>
|
||||
<span v-else>{{formatTime(row.startTime)}} 至 {{formatTime(row.endTime)}}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="课程状态" prop="status" sortable="custom" width="120">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag :type="getStatusType(row.status)" size="mini">{{getStatusName(row.status)}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="推荐标识" prop="recommendFlags" sortable="custom" show-overflow-tooltip min-width="140">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag
|
||||
v-for="code in splitValue(row.recommendFlags)"
|
||||
:key="code"
|
||||
:type="getRecommendType(code)"
|
||||
size="mini"
|
||||
style="margin: 2px 3px">
|
||||
{{getDictName(code, recommendOptions)}}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="排序编码" prop="sortNum" sortable="custom" width="120"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="操作" width="260">
|
||||
<template slot-scope="{row}">
|
||||
<el-button @click="openEdit(row)" size="mini" type="primary">编辑</el-button>
|
||||
<el-button @click="goChapterContent(row)" size="mini" type="success">章节内容</el-button>
|
||||
<el-button @click="doDelete(row)" size="mini" type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog :title="title" :visible.sync="dialogVisible" :close-on-click-modal="false" width="72%">
|
||||
<el-form :model="formData" ref="courseForm" :rules="formRules" label-width="110px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="课程名称" prop="courseName">
|
||||
<el-input v-model="formData.courseName" maxlength="100" placeholder="请输入课程名称"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="课程类型" prop="courseTypeId">
|
||||
<el-select v-model="formData.courseTypeId" filterable placeholder="请选择课程类型" style="width: 100%">
|
||||
<el-option v-for="item in courseTypeOptions" :key="item.id" :label="item.typeName" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="授课讲师" prop="lecturerName">
|
||||
<el-input v-model="formData.lecturerName" maxlength="100" placeholder="请输入教师姓名"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="课程状态" prop="status">
|
||||
<el-select v-model="formData.status" placeholder="请选择课程状态" style="width: 100%">
|
||||
<el-option v-for="item in statusOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开课方式" prop="openType">
|
||||
<el-radio-group v-model="formData.openType">
|
||||
<el-radio label="fixed">固定开课时间</el-radio>
|
||||
<el-radio label="long_term">长期开放</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="开课时间" prop="openTime" v-if="formData.openType !== 'long_term'">
|
||||
<el-date-picker
|
||||
v-model="formData.openTime"
|
||||
type="datetimerange"
|
||||
start-placeholder="开始时间"
|
||||
end-placeholder="结束时间"
|
||||
range-separator="至"
|
||||
value-format="yyyy-MM-dd HH:mm:ss"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="推荐标识" prop="recommendFlagList">
|
||||
<el-select v-model="formData.recommendFlagList" multiple clearable placeholder="请选择推荐标识" style="width: 100%">
|
||||
<el-option v-for="item in recommendOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="排序编码" prop="sortNum">
|
||||
<el-input-number v-model="formData.sortNum" :controls="false" :min="0" :precision="0" style="width: 100%" placeholder="请输入排序编码"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="课程介绍" prop="courseIntro">
|
||||
<el-input v-model="formData.courseIntro" type="textarea" maxlength="2000" :rows="4" show-word-limit placeholder="请输入课程简介"></el-input>
|
||||
</el-form-item>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="适合人群" prop="suitablePeople">
|
||||
<el-input v-model="formData.suitablePeople" type="textarea" maxlength="500" :rows="3" show-word-limit placeholder="请输入适合人群"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="学习目标" prop="learningGoal">
|
||||
<el-input v-model="formData.learningGoal" type="textarea" maxlength="500" :rows="3" show-word-limit placeholder="请输入学习目标"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="课程封面" prop="cover">
|
||||
<file-upload
|
||||
:upload_number="1"
|
||||
:upload_size="5242880"
|
||||
:value.sync="formData.cover"
|
||||
accept=".jpg,.jpeg,.png"
|
||||
complete_result
|
||||
upload_mode="image"
|
||||
upload_result_category="interval">
|
||||
</file-upload>
|
||||
<div class="el-upload__tip">只能上传1个文件;只能上传.jpg,.jpeg,.png文件;单个文件大小不能超过5M。</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :disabled="subDis" @click="operation">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
title: "",
|
||||
dialogVisible: false,
|
||||
subDis: false,
|
||||
courseTypeOptions: [],
|
||||
recommendOptions: [],
|
||||
statusOptions: [
|
||||
{ name: "草稿", code: "draft" },
|
||||
{ name: "待发布", code: "pending" },
|
||||
{ name: "已发布", code: "published" },
|
||||
{ name: "已下架", code: "offline" }
|
||||
],
|
||||
formData: {},
|
||||
formRules: {
|
||||
courseName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
courseTypeId: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
lecturerName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
status: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
sortNum: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
cover: [{ required: true, message: "请上传课程封面", trigger: ["blur", "change"] }]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
buildPageParams() {
|
||||
const params = Object.assign({}, this.pageForm)
|
||||
params.startTime = params.courseTime && params.courseTime.length ? params.courseTime[0] : ""
|
||||
params.endTime = params.courseTime && params.courseTime.length ? params.courseTime[1] : ""
|
||||
return params
|
||||
},
|
||||
pageData(data = null) {
|
||||
const address = this.pageDataUrl ? this.pageDataUrl : loc() + "/pageData"
|
||||
this.tableLoading = true
|
||||
this.$axios.post(address, data ? data : this.buildPageParams()).then((res) => {
|
||||
this.tableLoading = false
|
||||
if (res.code === 0) {
|
||||
this.tableData = res.data.list
|
||||
this.pageForm.totalCount = res.data.totalCount
|
||||
}
|
||||
})
|
||||
},
|
||||
resetSearch() {
|
||||
this.pageForm.courseName = ""
|
||||
this.pageForm.courseTypeId = ""
|
||||
this.pageForm.courseTime = []
|
||||
this.pageForm.lecturerName = ""
|
||||
this.pageForm.status = ""
|
||||
this.pageForm.recommendFlag = ""
|
||||
this.doSearch()
|
||||
},
|
||||
openAdd() {
|
||||
this.title = "新增课程"
|
||||
this.formData = {
|
||||
courseName: "",
|
||||
courseTypeId: "",
|
||||
lecturerName: "",
|
||||
courseIntro: "",
|
||||
suitablePeople: "",
|
||||
learningGoal: "",
|
||||
openType: "fixed",
|
||||
openTime: [],
|
||||
status: "draft",
|
||||
recommendFlagList: [],
|
||||
sortNum: 0,
|
||||
cover: ""
|
||||
}
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.courseForm && this.$refs.courseForm.clearValidate()
|
||||
})
|
||||
},
|
||||
openEdit(row) {
|
||||
this.title = "编辑课程"
|
||||
this.formData = Object.assign({}, row, {
|
||||
openTime: row.startTime && row.endTime ? [this.formatTime(row.startTime), this.formatTime(row.endTime)] : [],
|
||||
recommendFlagList: this.splitValue(row.recommendFlags)
|
||||
})
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.courseForm && this.$refs.courseForm.clearValidate()
|
||||
})
|
||||
},
|
||||
goChapterContent(row) {
|
||||
const url = "/platform/learning/chapter/content?courseId=" + encodeURIComponent(row.id || "")
|
||||
if (window.commonUtil && commonUtil.pjaxPush) {
|
||||
commonUtil.pjaxPush(url)
|
||||
} else {
|
||||
window.location.href = url
|
||||
}
|
||||
},
|
||||
operation() {
|
||||
this.$refs.courseForm.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
if (this.formData.openType !== "long_term" && (!this.formData.openTime || this.formData.openTime.length !== 2)) {
|
||||
this.$message.warning("请选择开课时间")
|
||||
return
|
||||
}
|
||||
const data = Object.assign({}, this.formData)
|
||||
data.startTime = data.openType === "long_term" ? "" : data.openTime[0]
|
||||
data.endTime = data.openType === "long_term" ? "" : data.openTime[1]
|
||||
data.recommendFlags = (data.recommendFlagList || []).join(",")
|
||||
delete data.openTime
|
||||
delete data.recommendFlagList
|
||||
const method = data.id ? "/doEdit" : "/doAdd"
|
||||
this.subDis = true
|
||||
const resp = await this.$axios.post(loc() + method, data)
|
||||
this.subDis = false
|
||||
if (resp.code === 0) {
|
||||
this.dialogVisible = false
|
||||
this.$message.success(resp.msg)
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
doDelete(row) {
|
||||
this.$confirm("确定要删除该课程吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(async () => {
|
||||
const resp = await this.$axios.post(loc() + "/doDelete", { id: row.id })
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.error(resp.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
async getCourseTypes() {
|
||||
const resp = await this.$axios.post(loc() + "/courseTypes")
|
||||
if (resp.code === 0) {
|
||||
this.courseTypeOptions = resp.data
|
||||
}
|
||||
},
|
||||
async getLearningDict(name) {
|
||||
const resp = await this.$axios.post(loc() + "/learningDictOptions", { name })
|
||||
return resp.code === 0 ? resp.data : []
|
||||
},
|
||||
splitValue(value) {
|
||||
return value ? value.split(",").filter(Boolean) : []
|
||||
},
|
||||
getStatusName(code) {
|
||||
const item = this.statusOptions.find(v => v.code === code)
|
||||
return item ? item.name : ""
|
||||
},
|
||||
getStatusType(code) {
|
||||
const map = {
|
||||
draft: "info",
|
||||
pending: "warning",
|
||||
published: "success",
|
||||
offline: "danger"
|
||||
}
|
||||
return map[code] || ""
|
||||
},
|
||||
getDictName(code, options) {
|
||||
const item = options.find(v => v.code === code)
|
||||
return item ? item.name : code
|
||||
},
|
||||
getDictNames(value, options) {
|
||||
const values = this.splitValue(value)
|
||||
return values.map(code => {
|
||||
const item = options.find(v => v.code === code)
|
||||
return item ? item.name : code
|
||||
}).join("、")
|
||||
},
|
||||
getRecommendType(code) {
|
||||
const name = this.getDictName(code, this.recommendOptions)
|
||||
if (name.includes("热门") || name.includes("推荐")) return "warning"
|
||||
if (name.includes("新")) return "success"
|
||||
return ""
|
||||
},
|
||||
formatTime(time) {
|
||||
return time ? this.$moment(time).format("YYYY-MM-DD HH:mm:ss") : ""
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.pageForm.courseTime = []
|
||||
await this.getCourseTypes()
|
||||
this.recommendOptions = await this.getLearningDict("推荐标识")
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,189 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="课程类型名称">
|
||||
<el-input
|
||||
v-model="pageForm.typeName"
|
||||
clearable
|
||||
placeholder="请输入课程类型名称"
|
||||
style="width: 100%"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<div class="search-query">
|
||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="课程类型列表">
|
||||
<el-button @click="openAdd" size="medium" type="primary">
|
||||
<i class="el-icon-plus"></i>
|
||||
新建
|
||||
</el-button>
|
||||
</table-tool>
|
||||
|
||||
<el-table
|
||||
v-loading="tableLoading"
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
border>
|
||||
<el-table-column :index="indexMethod" align="center" header-align="center" label="序号" type="index" width="80"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="课程类型名称" prop="typeName" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="备注" prop="remark" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="排序编号" prop="sortNum" width="140"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="是否启用" prop="enabled" width="140">
|
||||
<template slot-scope="{row}">
|
||||
<el-switch
|
||||
v-model="row.enabled"
|
||||
active-color="#13ce66"
|
||||
inactive-color="#ff4949"
|
||||
@change="switchChange(row)">
|
||||
</el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="操作" width="180">
|
||||
<template slot-scope="{row}">
|
||||
<el-button @click="openEdit(row)" size="mini" type="primary">编辑</el-button>
|
||||
<el-button @click="doDelete(row)" size="mini" type="danger">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog :title="title" :visible.sync="dialogVisible" :close-on-click-modal="false" width="40%">
|
||||
<el-form :model="formData" ref="courseTypeForm" :rules="formRules" label-width="120px">
|
||||
<el-form-item label="课程类型名称" prop="typeName">
|
||||
<el-input v-model="formData.typeName" maxlength="100" placeholder="请输入课程类型名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序编号" prop="sortNum">
|
||||
<el-input-number
|
||||
v-model="formData.sortNum"
|
||||
:controls="false"
|
||||
:min="0"
|
||||
:precision="0"
|
||||
placeholder="请输入排序编号"
|
||||
style="width: 100%">
|
||||
</el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否启用" prop="enabled">
|
||||
<el-switch v-model="formData.enabled" active-color="#13ce66" inactive-color="#ff4949"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="formData.remark"
|
||||
type="textarea"
|
||||
maxlength="500"
|
||||
show-word-limit
|
||||
:rows="4"
|
||||
placeholder="请输入备注">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :disabled="subDis" @click="operation">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
title: "",
|
||||
dialogVisible: false,
|
||||
subDis: false,
|
||||
formData: {},
|
||||
formRules: {
|
||||
typeName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
sortNum: [{ required: true, message: "必填", trigger: ["blur", "change"] }]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetSearch() {
|
||||
this.pageForm.typeName = ""
|
||||
this.doSearch()
|
||||
},
|
||||
openAdd() {
|
||||
this.title = "新建课程类型"
|
||||
this.formData = {
|
||||
typeName: "",
|
||||
sortNum: 0,
|
||||
enabled: true,
|
||||
remark: ""
|
||||
}
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.courseTypeForm && this.$refs.courseTypeForm.clearValidate()
|
||||
})
|
||||
},
|
||||
openEdit(row) {
|
||||
this.title = "编辑课程类型"
|
||||
this.formData = Object.assign({}, row)
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.$refs.courseTypeForm && this.$refs.courseTypeForm.clearValidate()
|
||||
})
|
||||
},
|
||||
operation() {
|
||||
const method = this.formData.id ? "/doEdit" : "/doAdd"
|
||||
this.$refs.courseTypeForm.validate(async (valid) => {
|
||||
if (!valid) return
|
||||
this.subDis = true
|
||||
const resp = await this.$axios.post(loc() + method, this.formData)
|
||||
this.subDis = false
|
||||
if (resp.code === 0) {
|
||||
this.dialogVisible = false
|
||||
this.$message.success(resp.msg)
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
})
|
||||
},
|
||||
async switchChange(row) {
|
||||
const resp = await this.$axios.post(loc() + "/doEdit", row)
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.error(resp.msg)
|
||||
row.enabled = !row.enabled
|
||||
}
|
||||
},
|
||||
doDelete(row) {
|
||||
this.$confirm("确定要删除该课程类型吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(async () => {
|
||||
const resp = await this.$axios.post(loc() + "/doDelete", { id: row.id })
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.error(resp.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,180 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="工号/姓名">
|
||||
<el-input
|
||||
v-model="pageForm.keyword"
|
||||
clearable
|
||||
placeholder="请输入工号或姓名"
|
||||
style="width: 100%"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="所属分工会" v-if="unionOptions.length">
|
||||
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属分工会" style="width: 100%">
|
||||
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="课程名称">
|
||||
<el-select v-model="pageForm.courseId" clearable filterable placeholder="请选择课程" style="width: 100%">
|
||||
<el-option v-for="item in courseOptions" :key="item.id" :label="item.courseName" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="是否完成">
|
||||
<el-select v-model="pageForm.completeStatus" clearable placeholder="请选择完成状态" style="width: 100%">
|
||||
<el-option v-for="item in statusOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<div class="search-query">
|
||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="学习列表"></table-tool>
|
||||
<el-table
|
||||
v-loading="tableLoading"
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
border
|
||||
@sort-change="pageOrder">
|
||||
<el-table-column :index="indexMethod" align="center" header-align="center" label="序号" type="index" width="70"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="工号" prop="loginName" sortable="custom" show-overflow-tooltip width="120"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="姓名" prop="userName" sortable="custom" show-overflow-tooltip width="110"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="所属分工会" prop="unionName" sortable="custom" show-overflow-tooltip min-width="150"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="课程名称" prop="courseName" sortable="custom" show-overflow-tooltip min-width="180"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="章节名称" prop="outlineName" sortable="custom" show-overflow-tooltip min-width="160"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="学习开始时间" prop="firstStudyTime" sortable="custom" width="170">
|
||||
<template slot-scope="{row}">{{formatTime(row.firstStudyTime)}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="最近学习时间" prop="latestStudyTime" sortable="custom" width="170">
|
||||
<template slot-scope="{row}">{{formatTime(row.latestStudyTime)}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="学习时长" prop="studySeconds" sortable="custom" width="130">
|
||||
<template slot-scope="{row}">{{row.studyTimeText}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="学习进度" prop="progressPercent" sortable="custom" width="180">
|
||||
<template slot-scope="{row}">
|
||||
<el-progress :percentage="Number(row.progressPercent || 0)" :stroke-width="8"></el-progress>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="是否完成" prop="completeStatus" sortable="custom" width="110">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag :type="statusType(row.completeStatus)" size="mini">{{statusName(row.completeStatus)}}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="操作" width="100">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="danger" icon="el-icon-delete" @click="deleteRecord(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
apiBase: "/platform/learning/study/record",
|
||||
courseOptions: [],
|
||||
unionOptions: [],
|
||||
statusOptions: [
|
||||
{ name: "未开始", code: "not_started", type: "info" },
|
||||
{ name: "学习中", code: "studying", type: "warning" },
|
||||
{ name: "已完成", code: "completed", type: "success" }
|
||||
]
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
pageData() {
|
||||
this.tableLoading = true
|
||||
this.$axios.post(this.apiBase + "/pageData", this.pageForm).then(resp => {
|
||||
this.tableLoading = false
|
||||
if (resp.code === 0) {
|
||||
this.tableData = resp.data.list || []
|
||||
this.pageForm.totalCount = resp.data.totalCount
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
}).catch(() => {
|
||||
this.tableLoading = false
|
||||
})
|
||||
},
|
||||
pageOrder(column) {
|
||||
this.pageForm.pageOrderName = column.prop
|
||||
this.pageForm.pageOrderBy = column.order
|
||||
this.pageData()
|
||||
},
|
||||
doSearch() {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageData()
|
||||
},
|
||||
resetSearch() {
|
||||
this.pageForm.keyword = ""
|
||||
this.pageForm.unionId = ""
|
||||
this.pageForm.courseId = ""
|
||||
this.pageForm.completeStatus = ""
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageData()
|
||||
},
|
||||
async loadOptions() {
|
||||
const courseResp = await this.$axios.post(this.apiBase + "/courseOptions")
|
||||
if (courseResp.code === 0) {
|
||||
this.courseOptions = courseResp.data || []
|
||||
}
|
||||
const unionResp = await this.$axios.post(this.apiBase + "/unionOptions")
|
||||
if (unionResp.code === 0) {
|
||||
this.unionOptions = unionResp.data || []
|
||||
}
|
||||
},
|
||||
statusName(code) {
|
||||
const item = this.statusOptions.find(v => v.code === code)
|
||||
return item ? item.name : "未开始"
|
||||
},
|
||||
statusType(code) {
|
||||
const item = this.statusOptions.find(v => v.code === code)
|
||||
return item ? item.type : "info"
|
||||
},
|
||||
formatTime(value) {
|
||||
return value ? this.$moment(value).format("YYYY-MM-DD HH:mm:ss") : ""
|
||||
},
|
||||
deleteRecord(row) {
|
||||
this.$confirm("确定要删除该学习记录吗?删除后该章节的学习时段也会一并清理。", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(async () => {
|
||||
const resp = await this.$axios.post(this.apiBase + "/delete", { id: row.id })
|
||||
if (resp.code === 0) {
|
||||
this.$message.success(resp.msg)
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.$set(this.pageForm, "keyword", "")
|
||||
this.$set(this.pageForm, "unionId", "")
|
||||
this.$set(this.pageForm, "courseId", "")
|
||||
this.$set(this.pageForm, "completeStatus", "")
|
||||
await this.loadOptions()
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,167 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="课程名称">
|
||||
<el-select
|
||||
v-model="pageForm.courseId"
|
||||
clearable
|
||||
filterable
|
||||
remote
|
||||
:remote-method="queryCourseOptions"
|
||||
placeholder="请选择课程"
|
||||
style="width: 100%">
|
||||
<el-option v-for="item in courseOptions" :key="item.id" :label="item.courseName" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="所属分工会" v-if="unionOptions.length">
|
||||
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属分工会" style="width: 100%">
|
||||
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<div class="search-query">
|
||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<el-tabs v-model="activeTab" @tab-click="tabChange">
|
||||
<el-tab-pane label="课程统计" name="course"></el-tab-pane>
|
||||
<el-tab-pane label="分工会统计" name="union"></el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<table-tool :app="this" :label="activeTab === 'course' ? '课程统计' : '分工会统计'"></table-tool>
|
||||
<el-table
|
||||
v-if="activeTab === 'course'"
|
||||
v-loading="tableLoading"
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
border
|
||||
@sort-change="pageOrder">
|
||||
<el-table-column :index="indexMethod" align="center" header-align="center" label="序号" type="index" width="70"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="课程名称" prop="courseName" sortable="custom" show-overflow-tooltip min-width="220" width="360"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="学习人数" prop="learnerCount" sortable="custom" width="130"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="完成人数" prop="completedCount" sortable="custom" width="130"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="平均学习时长" prop="avgStudySeconds" sortable="custom" width="170">
|
||||
<template slot-scope="{row}">{{row.avgStudyTimeText}}</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-table
|
||||
v-else
|
||||
v-loading="tableLoading"
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
border
|
||||
@sort-change="pageOrder">
|
||||
<el-table-column :index="indexMethod" align="center" header-align="center" label="序号" type="index" width="70"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="课程名称" prop="courseName" sortable="custom" show-overflow-tooltip min-width="220"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="分工会名称" prop="unionName" sortable="custom" show-overflow-tooltip min-width="170"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="学习人数" prop="learnerCount" sortable="custom" width="130"></el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="学习时长" prop="studySeconds" sortable="custom" width="160">
|
||||
<template slot-scope="{row}">{{row.studyTimeText}}</template>
|
||||
</el-table-column>
|
||||
<el-table-column align="center" header-align="center" label="完成率" prop="completeRate" sortable="custom" width="130"></el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
return {
|
||||
apiBase: "/platform/learning/statistics",
|
||||
activeTab: "course",
|
||||
courseOptions: [],
|
||||
unionOptions: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
pageData() {
|
||||
this.tableLoading = true
|
||||
const url = this.activeTab === "course" ? "/coursePageData" : "/unionPageData"
|
||||
this.$axios.post(this.apiBase + url, this.pageForm).then(resp => {
|
||||
this.tableLoading = false
|
||||
if (resp.code === 0) {
|
||||
this.tableData = resp.data.list || []
|
||||
this.pageForm.totalCount = resp.data.totalCount
|
||||
} else {
|
||||
this.$message.warning(resp.msg)
|
||||
}
|
||||
}).catch(() => {
|
||||
this.tableLoading = false
|
||||
})
|
||||
},
|
||||
pageOrder(column) {
|
||||
this.pageForm.pageOrderName = column.prop
|
||||
this.pageForm.pageOrderBy = column.order
|
||||
this.pageData()
|
||||
},
|
||||
async doSearch() {
|
||||
this.pageForm.pageNumber = 1
|
||||
await this.ensureUnionDefaultCourse()
|
||||
this.pageData()
|
||||
},
|
||||
async resetSearch() {
|
||||
this.pageForm.courseId = ""
|
||||
this.pageForm.unionId = ""
|
||||
this.pageForm.pageOrderName = ""
|
||||
this.pageForm.pageOrderBy = ""
|
||||
this.pageForm.pageNumber = 1
|
||||
await this.queryCourseOptions("")
|
||||
await this.ensureUnionDefaultCourse()
|
||||
this.pageData()
|
||||
},
|
||||
async tabChange() {
|
||||
this.tableData = []
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageForm.pageOrderName = ""
|
||||
this.pageForm.pageOrderBy = ""
|
||||
await this.ensureUnionDefaultCourse()
|
||||
this.pageData()
|
||||
},
|
||||
async queryCourseOptions(query) {
|
||||
const resp = await this.$axios.post(this.apiBase + "/courseOptions", { courseName: query || "" })
|
||||
if (resp.code === 0) {
|
||||
this.courseOptions = resp.data || []
|
||||
}
|
||||
},
|
||||
async ensureUnionDefaultCourse() {
|
||||
if (this.activeTab !== "union" || this.pageForm.courseId) {
|
||||
return
|
||||
}
|
||||
if (!this.courseOptions.length) {
|
||||
await this.queryCourseOptions("")
|
||||
}
|
||||
if (this.courseOptions.length) {
|
||||
this.pageForm.courseId = this.courseOptions[0].id
|
||||
}
|
||||
},
|
||||
async loadUnionOptions() {
|
||||
const resp = await this.$axios.post(this.apiBase + "/unionOptions")
|
||||
if (resp.code === 0) {
|
||||
this.unionOptions = resp.data || []
|
||||
}
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
this.$set(this.pageForm, "courseId", "")
|
||||
this.$set(this.pageForm, "unionId", "")
|
||||
await this.queryCourseOptions("")
|
||||
await this.loadUnionOptions()
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,353 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="年度">
|
||||
<el-date-picker
|
||||
v-model="pageForm.year"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择年度"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="线路名称">
|
||||
<el-input
|
||||
v-model="pageForm.lineName"
|
||||
clearable
|
||||
placeholder="请输入线路名称"
|
||||
style="width: 100%"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="线路类型">
|
||||
<el-select v-model="pageForm.lineType" clearable filterable placeholder="请选择线路类型" style="width: 100%">
|
||||
<el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="所属工会">
|
||||
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
|
||||
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<div class="search-query" style="margin-left: auto;">
|
||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="线路列表"></table-tool>
|
||||
|
||||
<el-table
|
||||
v-loading="tableLoading"
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
border
|
||||
:default-sort="{prop: 'lineName', order: 'ascending'}"
|
||||
:row-class-name="tableRowClassName"
|
||||
@sort-change="pageOrder">
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="线路名称" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="出行时段" prop="travelPeriod" width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="事项名称" prop="matterName" min-width="200" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="线路类型" prop="lineType" width="140" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="最少成团人数" prop="minGroupPeople" width="140" sortable="custom" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
{{ row.minGroupPeople || 0 }}人
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="最多成团人数" prop="maxGroupPeople" width="140" sortable="custom" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
{{ row.maxGroupPeople || 0 }}人
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="报名人数" prop="signupCount" width="120" sortable="custom" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" :type="Number(row.signupCount || 0) > 0 ? 'success' : 'info'">{{ row.signupCount || 0 }}人</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="成团状态" prop="groupStatusName" width="120" sortable="custom" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" :type="groupStatusType(row.groupStatus)">{{ row.groupStatusName || '未成团' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
|
||||
<el-button size="mini" type="primary" @click="exportLine(row)">导出</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
|
||||
<el-drawer
|
||||
:title="viewTitle"
|
||||
:visible.sync="viewVisible"
|
||||
direction="rtl"
|
||||
size="72%"
|
||||
custom-class="tour-group-view-drawer"
|
||||
:close-on-click-modal="false">
|
||||
<div class="tour-group-view">
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="姓名/工号">
|
||||
<el-input
|
||||
v-model="viewForm.keyword"
|
||||
clearable
|
||||
placeholder="请输入姓名或工号"
|
||||
style="width: 100%"
|
||||
@keyup.enter.native="viewSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="所属工会">
|
||||
<el-select v-model="viewForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
|
||||
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<div class="search-query" style="margin-left: auto;">
|
||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="viewSearch">搜索</el-button>
|
||||
<el-button size="medium" @click="viewReset">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<div class="tour-group-view-title">报名人员列表</div>
|
||||
<el-table
|
||||
v-loading="viewLoading"
|
||||
:data="viewTableData"
|
||||
:size="tableSize"
|
||||
border
|
||||
@sort-change="viewPageOrder">
|
||||
<el-table-column label="序号" type="index" :index="viewIndexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="手机号码" prop="mobile" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="身份证号" prop="idCard" min-width="190" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
</el-table>
|
||||
<el-row class="el-pagination-container tour-group-view-pagination">
|
||||
<el-pagination
|
||||
background
|
||||
:current-page="viewForm.pageNumber"
|
||||
:page-size="viewForm.pageSize"
|
||||
:page-sizes="[10, 20, 50, 100]"
|
||||
:total="viewForm.totalCount"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
@size-change="viewSizeChange"
|
||||
@current-change="viewCurrentChange">
|
||||
</el-pagination>
|
||||
</el-row>
|
||||
</el-card>
|
||||
</div>
|
||||
</el-drawer>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
const currentYear = moment().format("YYYY")
|
||||
return {
|
||||
unionOptions: [],
|
||||
lineTypeOptions: [],
|
||||
viewVisible: false,
|
||||
viewLoading: false,
|
||||
viewRequestSeq: 0,
|
||||
viewTitle: "",
|
||||
viewRow: {},
|
||||
viewTableData: [],
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "lineName",
|
||||
pageOrderBy: "ascending",
|
||||
year: currentYear,
|
||||
lineName: "",
|
||||
lineType: "",
|
||||
unionId: ""
|
||||
},
|
||||
viewForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "signupTime",
|
||||
pageOrderBy: "descending",
|
||||
keyword: "",
|
||||
unionId: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetSearch() {
|
||||
this.pageForm.year = moment().format("YYYY")
|
||||
this.pageForm.lineName = ""
|
||||
this.pageForm.lineType = ""
|
||||
this.pageForm.unionId = ""
|
||||
this.loadLineTypeOptions()
|
||||
this.doSearch()
|
||||
},
|
||||
groupStatusType(status) {
|
||||
if (status === "formed") return "success"
|
||||
if (status === "over") return "danger"
|
||||
return "info"
|
||||
},
|
||||
tableRowClassName({ row }) {
|
||||
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
|
||||
},
|
||||
isDirectFamilyLine(row) {
|
||||
if (!row) return false
|
||||
return row.directFamilyUnitLine === true
|
||||
|| row.directFamilyUnitLine === 1
|
||||
|| row.directFamilyUnitLine === "1"
|
||||
},
|
||||
openView(row) {
|
||||
this.viewRow = row || {}
|
||||
this.viewTitle = this.viewRow.lineName || "报名人员"
|
||||
this.viewVisible = true
|
||||
this.viewForm.pageNumber = 1
|
||||
this.viewForm.pageSize = 10
|
||||
this.viewForm.totalCount = 0
|
||||
this.viewForm.pageOrderName = "signupTime"
|
||||
this.viewForm.pageOrderBy = "descending"
|
||||
this.viewForm.keyword = ""
|
||||
this.viewForm.unionId = ""
|
||||
this.viewTableData = []
|
||||
this.loadViewData()
|
||||
},
|
||||
viewSearch() {
|
||||
this.viewForm.pageNumber = 1
|
||||
this.loadViewData()
|
||||
},
|
||||
viewReset() {
|
||||
this.viewForm.keyword = ""
|
||||
this.viewForm.unionId = ""
|
||||
this.viewForm.pageNumber = 1
|
||||
this.loadViewData()
|
||||
},
|
||||
viewIndexMethod(index) {
|
||||
return (this.viewForm.pageNumber - 1) * this.viewForm.pageSize + index + 1
|
||||
},
|
||||
viewSizeChange(size) {
|
||||
this.viewForm.pageSize = size
|
||||
this.viewForm.pageNumber = 1
|
||||
this.loadViewData()
|
||||
},
|
||||
viewCurrentChange(page) {
|
||||
this.viewForm.pageNumber = page
|
||||
this.loadViewData()
|
||||
},
|
||||
viewPageOrder({ prop, order }) {
|
||||
this.viewForm.pageOrderName = prop || "signupTime"
|
||||
this.viewForm.pageOrderBy = order || "descending"
|
||||
this.viewForm.pageNumber = 1
|
||||
this.loadViewData()
|
||||
},
|
||||
loadViewData() {
|
||||
if (!this.viewRow || !this.viewRow.matterId) {
|
||||
this.viewTableData = []
|
||||
this.viewForm.totalCount = 0
|
||||
return
|
||||
}
|
||||
this.viewLoading = true
|
||||
const requestSeq = ++this.viewRequestSeq
|
||||
this.$axios.post(loc() + "/signupPageData", {
|
||||
matterId: this.viewRow.matterId,
|
||||
keyword: this.viewForm.keyword,
|
||||
unionId: this.viewForm.unionId,
|
||||
pageNumber: this.viewForm.pageNumber,
|
||||
pageSize: this.viewForm.pageSize,
|
||||
pageOrderName: this.viewForm.pageOrderName,
|
||||
pageOrderBy: this.viewForm.pageOrderBy
|
||||
}).then((res) => {
|
||||
if (requestSeq !== this.viewRequestSeq) return
|
||||
if (res.code === 0) {
|
||||
const data = res.data || {}
|
||||
this.viewTableData = data.list || []
|
||||
this.viewForm.totalCount = data.totalCount || 0
|
||||
} else {
|
||||
this.$message.warning(res.msg || "查询失败")
|
||||
}
|
||||
}).finally(() => {
|
||||
if (requestSeq === this.viewRequestSeq) {
|
||||
this.viewLoading = false
|
||||
}
|
||||
})
|
||||
},
|
||||
exportLine(row) {
|
||||
if (!row || !row.matterId) {
|
||||
this.$message.warning("线路信息不完整")
|
||||
return
|
||||
}
|
||||
window.location.href = loc() + "/exportParticipants?matterId=" + encodeURIComponent(row.matterId)
|
||||
},
|
||||
loadUnionOptions() {
|
||||
this.$axios.post(loc() + "/unionOptions").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.unionOptions = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
loadLineTypeOptions() {
|
||||
this.$axios.post(loc() + "/lineTypeOptions", {
|
||||
year: this.pageForm.year
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.lineTypeOptions = (res.data || []).filter(item => item.lineType)
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadUnionOptions()
|
||||
this.loadLineTypeOptions()
|
||||
this.pageData()
|
||||
},
|
||||
watch: {
|
||||
"pageForm.year"() {
|
||||
this.pageForm.lineType = ""
|
||||
this.loadLineTypeOptions()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.tour-group-view-drawer .el-drawer__body {
|
||||
background: #f5f7fa;
|
||||
padding: 12px;
|
||||
overflow: auto;
|
||||
}
|
||||
.tour-group-view-pagination {
|
||||
margin-top: 12px;
|
||||
text-align: right;
|
||||
}
|
||||
.tour-group-view-title {
|
||||
border-left: 4px solid #0079c2;
|
||||
color: #0079c2;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
margin-bottom: 12px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.el-table .direct-family-row > td,
|
||||
.el-table .direct-family-row > td .cell {
|
||||
color: #f56c6c;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,653 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="开始年度">
|
||||
<el-date-picker
|
||||
v-model="pageForm.startYear"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择开始年度"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="结束年度">
|
||||
<el-date-picker
|
||||
v-model="pageForm.endYear"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择结束年度"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="姓名/工号">
|
||||
<el-input
|
||||
v-model="pageForm.keyword"
|
||||
clearable
|
||||
placeholder="请输入姓名或工号"
|
||||
style="width: 100%"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="所属工会">
|
||||
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
|
||||
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="线路">
|
||||
<el-select v-model="pageForm.lineId" clearable filterable placeholder="请选择线路" style="width: 100%">
|
||||
<el-option v-for="item in lineOptions" :key="item.lineId" :label="item.lineName" :value="item.lineId"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="出行时段">
|
||||
<el-select v-model="pageForm.travelPeriod" clearable filterable placeholder="请选择出行时段" style="width: 100%">
|
||||
<el-option v-for="item in travelPeriodOptions" :key="item.travelPeriod" :label="item.travelPeriod" :value="item.travelPeriod"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="线路类型">
|
||||
<dict-select
|
||||
v-model="pageForm.lineType"
|
||||
code="lineType"
|
||||
option_value="name"
|
||||
placeholder="请选择线路类型">
|
||||
</dict-select>
|
||||
</search-item>
|
||||
<div class="search-query" style="margin-left: auto;">
|
||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<div class="tour-ledger-toolbar">
|
||||
<div class="tour-ledger-toolbar-title">
|
||||
<table-tool :app="this" label="台账列表"></table-tool>
|
||||
</div>
|
||||
<div class="tour-ledger-actions">
|
||||
<el-button size="medium" type="primary" icon="el-icon-upload2" @click="showImportDialog = true">参加人员导入</el-button>
|
||||
<el-button size="medium" type="primary" icon="el-icon-check" @click="setParticipants">设置参加人员</el-button>
|
||||
<el-button size="medium" type="primary" icon="el-icon-download" @click="exportUnionSignupZip">导出分工会报名压缩包</el-button>
|
||||
</div>
|
||||
<div class="tour-ledger-scope">
|
||||
<el-button
|
||||
class="tour-scope-btn"
|
||||
size="medium"
|
||||
:class="{'is-active': pageForm.directFamilyOnly}"
|
||||
@click="setDirectFamilyOnly">
|
||||
参加直系亲属单位线路人员
|
||||
</el-button>
|
||||
<el-button
|
||||
class="tour-scope-btn"
|
||||
size="medium"
|
||||
:class="{'is-active': pageForm.overCostOnly}"
|
||||
@click="setOverCostOnly">
|
||||
申请超出费用由单位承担人员
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="tableLoading"
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
border
|
||||
row-key="id"
|
||||
:row-class-name="tableRowClassName"
|
||||
@selection-change="handleSelectionChange"
|
||||
@sort-change="pageOrder">
|
||||
<el-table-column type="selection" width="55" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="所在单位" prop="unitName" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="报名线路" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="出行时段" prop="travelPeriod" min-width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="线路类型" prop="lineType" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="报名时间" prop="signupTime" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="携带家属" prop="familyCount" width="120" sortable="custom" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" :type="Number(row.familyCount || 0) > 0 ? 'success' : 'info'">{{ row.familyCount || 0 }}人</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否参加" prop="joined" width="120" sortable="custom" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" :type="row.joined ? 'success' : 'info'">{{ row.joined ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="报销超出费用" prop="overCostReimbursed" width="140" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" :type="row.overCostReimbursed ? 'success' : 'info'">{{ row.overCostReimbursed ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
|
||||
<el-button size="mini" type="danger" @click="doDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
<div class="tour-ledger-tip">温馨提醒:走审核流程的报名,审核通过后才在台账中显示</div>
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<excel-import
|
||||
ref="excelImportRef"
|
||||
url="/platform/tour/ledger/importParticipants"
|
||||
template_url="/platform/tour/ledger/downloadTemplate"
|
||||
:visible.sync="showImportDialog"
|
||||
title="参加人员导入"
|
||||
width="700px"
|
||||
@import-success="afterImport"
|
||||
:extra_params="{}">
|
||||
</excel-import>
|
||||
|
||||
<el-dialog
|
||||
title="台账详情"
|
||||
:visible.sync="detailVisible"
|
||||
:close-on-click-modal="false"
|
||||
width="72%">
|
||||
<div class="tour-ledger-section">
|
||||
<div class="tour-ledger-title">教职工信息</div>
|
||||
<el-descriptions :column="3" border size="medium">
|
||||
<el-descriptions-item label="工号">{{ detail.jobNo || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ detail.userName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ detail.gender || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="身份证号">{{ detail.idCard || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在单位">{{ detail.unitName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在工会">{{ detail.unionName || '' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<div class="tour-ledger-section mt10">
|
||||
<div class="tour-ledger-title">
|
||||
报名信息
|
||||
<el-link v-if="detailHasWorkflow()" type="primary" @click="openChart">点击查看流程图</el-link>
|
||||
</div>
|
||||
<el-descriptions :column="3" border size="medium">
|
||||
<el-descriptions-item label="报名时间">{{ detail.signupTime || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名线路">{{ detail.lineName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="线路类型">{{ detail.lineType || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名酒店">{{ detail.hotelName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名旅行社">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出行时间">{{ detail.travelPeriod || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="fillBedInfo" label="床型">{{ detail.bedType || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="fillBedInfo" label="床位信息">{{ detail.bedInfo || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="fillBedInfo" label="意向拼床人">{{ detail.intendedRoommate || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否携带家属">{{ familyText() }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否参加">{{ detail.joined ? '是' : '否' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否报销">{{ detail.reimbursed ? '是' : '否' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报销超出费用">{{ detail.overCostReimbursed ? '是' : '否' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<div v-if="isDirectFamilyLine()" class="tour-ledger-section mt10">
|
||||
<div class="tour-ledger-title">直系亲属线路</div>
|
||||
<el-descriptions :column="3" border size="medium">
|
||||
<el-descriptions-item label="亲属姓名">{{ directRelative.relativeName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在单位">{{ directRelative.unitName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="亲属关系">{{ directRelative.relationshipName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="线路名称">{{ directRelative.lineName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出行开始日期">{{ directRelative.travelStartTime || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出行结束日期">{{ directRelative.travelEndTime || '' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<div v-if="showFamilySection()" class="tour-ledger-section mt10">
|
||||
<div class="tour-ledger-title">家属信息</div>
|
||||
<el-table :data="familyData" border :size="tableSize" empty-text="暂无家属信息">
|
||||
<el-table-column label="序号" type="index" width="70" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="家属姓名" prop="familyName" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="性别" prop="gender" width="90" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="身份证号码" prop="idCard" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="年龄" prop="age" width="90" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="关系" prop="relationship" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column v-if="fillBedInfo" label="床型" prop="bedType" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column v-if="fillBedInfo" label="床位" prop="bedInfo" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column v-if="fillBedInfo" label="意向拼床人" prop="intendedRoommate" min-width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<template v-if="detailHasWorkflow()" v-for="task in doneTasks">
|
||||
<div class="tour-ledger-section mt10" :key="task.id">
|
||||
<div class="tour-ledger-title">{{ task.displayName }}</div>
|
||||
<el-descriptions border :column="3" v-if="task.ext && task.ext.isFirstTaskNode">
|
||||
<el-descriptions-item label="申请用户">
|
||||
{{ task.ext.initiatorName }}({{ task.ext.initiatorAccount }})
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="办理结果">
|
||||
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-descriptions border :column="3" v-else>
|
||||
<el-descriptions-item label="办理用户">
|
||||
{{ (task.taskFormData && task.taskFormData.userName) || '' }}({{ (task.taskFormData && task.taskFormData.loginName) || '' }})
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="办理结果">
|
||||
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext && task.ext.submitType"></dict-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="办理意见" :span="3">
|
||||
{{ (task.taskFormData && (task.taskFormData.opinion || task.taskFormData.tf_opinion)) || '' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<snaker-chart ref="snakerChartRef"></snaker-chart>
|
||||
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="detailVisible = false">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.tour-ledger-section {
|
||||
padding: 0 2px;
|
||||
}
|
||||
.tour-ledger-title {
|
||||
border-left: 4px solid #0079c2;
|
||||
color: #0079c2;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
margin-bottom: 14px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.tour-ledger-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
gap: 24px;
|
||||
margin-bottom: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tour-ledger-toolbar-title {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.tour-ledger-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
gap: 10px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tour-ledger-actions .el-button {
|
||||
margin: 0;
|
||||
}
|
||||
.tour-ledger-scope {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
flex-wrap: nowrap;
|
||||
gap: 12px;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tour-ledger-scope .el-button {
|
||||
margin: 0;
|
||||
}
|
||||
.tour-ledger-scope .el-button + .el-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
.tour-ledger-scope .tour-scope-btn {
|
||||
background: #ecf5ff;
|
||||
border-color: #b3d8ff;
|
||||
border-radius: 4px;
|
||||
color: #0079c2;
|
||||
font-weight: 600;
|
||||
height: 34px;
|
||||
line-height: 1;
|
||||
padding: 8px 18px;
|
||||
}
|
||||
.tour-ledger-scope .tour-scope-btn:hover,
|
||||
.tour-ledger-scope .tour-scope-btn:focus {
|
||||
background: #d9ecff;
|
||||
border-color: #66b1ff;
|
||||
color: #006bb0;
|
||||
}
|
||||
.tour-ledger-scope .tour-scope-btn.is-active {
|
||||
background: #0079c2;
|
||||
border-color: #0079c2;
|
||||
box-shadow: 0 2px 6px rgba(0, 121, 194, 0.24);
|
||||
color: #fff;
|
||||
}
|
||||
.tour-ledger-scope .tour-scope-btn.is-active:hover,
|
||||
.tour-ledger-scope .tour-scope-btn.is-active:focus {
|
||||
background: #006bb0;
|
||||
border-color: #006bb0;
|
||||
color: #fff;
|
||||
}
|
||||
.tour-ledger-tip {
|
||||
color: #f56c6c;
|
||||
font-family: SimSun, "宋体", serif;
|
||||
font-size: 9pt;
|
||||
line-height: 1.6;
|
||||
margin-top: -4px;
|
||||
text-align: center;
|
||||
}
|
||||
.el-table .direct-family-row > td,
|
||||
.el-table .direct-family-row > td .cell {
|
||||
color: #f56c6c;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
|
||||
data() {
|
||||
const currentYear = moment().format("YYYY")
|
||||
return {
|
||||
detailVisible: false,
|
||||
unionOptions: [],
|
||||
lineOptions: [],
|
||||
travelPeriodOptions: [],
|
||||
detail: {},
|
||||
familyData: [],
|
||||
directRelative: {},
|
||||
directFamilyUnitLine: false,
|
||||
fillBedInfo: true,
|
||||
detailRow: {},
|
||||
doneTasks: [],
|
||||
showImportDialog: false,
|
||||
multipleSelection: [],
|
||||
filterOptionsTimer: null,
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "signupTime",
|
||||
pageOrderBy: "descending",
|
||||
startYear: currentYear,
|
||||
endYear: currentYear,
|
||||
keyword: "",
|
||||
unionId: "",
|
||||
lineId: "",
|
||||
travelPeriod: "",
|
||||
lineType: "",
|
||||
directFamilyOnly: false,
|
||||
overCostOnly: false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toBoolean(value) {
|
||||
return value === true || value === 1 || value === "1" || value === "true" || value === "TRUE"
|
||||
},
|
||||
tableRowClassName({ row }) {
|
||||
return this.isDirectFamilyRow(row) ? "direct-family-row" : ""
|
||||
},
|
||||
isDirectFamilyRow(row) {
|
||||
return this.toBoolean(row && row.directFamilyUnitLine) || !!(row && row.directRelativeId)
|
||||
},
|
||||
handleSelectionChange(val) {
|
||||
this.multipleSelection = val || []
|
||||
},
|
||||
clearTableSelection() {
|
||||
this.multipleSelection = []
|
||||
if (this.$refs.tableRef) {
|
||||
this.$refs.tableRef.clearSelection()
|
||||
}
|
||||
},
|
||||
setParticipants() {
|
||||
if (!this.multipleSelection.length) {
|
||||
this.$message.warning("请选择要设置的参加人员")
|
||||
return
|
||||
}
|
||||
this.$confirm("确定将选中的" + this.multipleSelection.length + "条台账设置为已参加吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post(loc() + "/setParticipants", {
|
||||
ids: JSON.stringify(this.multipleSelection.map(item => item.id))
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("设置成功")
|
||||
this.clearTableSelection()
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(res.msg || "设置失败")
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
exportUnionSignupZip() {
|
||||
const params = new URLSearchParams()
|
||||
;["startYear", "endYear", "keyword", "unionId", "lineId", "travelPeriod", "lineType"].forEach(key => {
|
||||
const value = this.pageForm[key]
|
||||
if (value !== undefined && value !== null && value !== "") {
|
||||
params.append(key, value)
|
||||
}
|
||||
})
|
||||
if (this.pageForm.directFamilyOnly) {
|
||||
params.append("directFamilyOnly", "true")
|
||||
}
|
||||
if (this.pageForm.overCostOnly) {
|
||||
params.append("overCostOnly", "true")
|
||||
}
|
||||
window.location.href = loc() + "/exportUnionSignupZip?" + params.toString()
|
||||
},
|
||||
afterImport() {
|
||||
this.showImportDialog = false
|
||||
this.clearTableSelection()
|
||||
this.pageData()
|
||||
this.loadFilterOptions()
|
||||
},
|
||||
isDirectFamilyLine() {
|
||||
return this.directFamilyUnitLine
|
||||
|| this.toBoolean(this.detail && this.detail.directFamilyUnitLine)
|
||||
|| !!(this.directRelative && this.directRelative.id)
|
||||
},
|
||||
detailHasWorkflow() {
|
||||
return !!(this.detailRow && this.detailRow.instanceId)
|
||||
},
|
||||
hasFamily() {
|
||||
return this.toBoolean(this.detail && this.detail.hasFamily) || this.familyData.length > 0
|
||||
},
|
||||
familyText() {
|
||||
if (this.isDirectFamilyLine()) {
|
||||
return "否"
|
||||
}
|
||||
if (!this.hasFamily()) {
|
||||
return "否"
|
||||
}
|
||||
const count = Number(this.familyData.length || 0)
|
||||
return count > 0 ? count + "人" : "是"
|
||||
},
|
||||
showFamilySection() {
|
||||
return this.hasFamily() && !this.isDirectFamilyLine()
|
||||
},
|
||||
loadDoneTasks() {
|
||||
this.$axios.post("/flow/common/doneTasks", { bizId: this.detailRow.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.doneTasks = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
openChart() {
|
||||
if (!this.detailRow.instanceProcessDefineId || !this.detailRow.instanceId) {
|
||||
this.$message.warning("暂无流程图信息")
|
||||
return
|
||||
}
|
||||
this.$refs.snakerChartRef.onOpenFull(this.detailRow.instanceProcessDefineId, this.detailRow.instanceId)
|
||||
},
|
||||
resetSearch() {
|
||||
this.pageForm.startYear = moment().format("YYYY")
|
||||
this.pageForm.endYear = moment().format("YYYY")
|
||||
this.pageForm.keyword = ""
|
||||
this.pageForm.unionId = ""
|
||||
this.pageForm.lineId = ""
|
||||
this.pageForm.travelPeriod = ""
|
||||
this.pageForm.lineType = ""
|
||||
this.pageForm.directFamilyOnly = false
|
||||
this.pageForm.overCostOnly = false
|
||||
this.doSearch()
|
||||
},
|
||||
openView(row) {
|
||||
this.detailRow = row || {}
|
||||
this.detail = {}
|
||||
this.familyData = []
|
||||
this.directRelative = {}
|
||||
this.directFamilyUnitLine = false
|
||||
this.fillBedInfo = true
|
||||
this.doneTasks = []
|
||||
this.$axios.post(loc() + "/detail", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const data = res.data || {}
|
||||
this.detail = data.ledger || {}
|
||||
this.detail.travelPeriod = data.travelPeriod || ""
|
||||
this.familyData = data.families || []
|
||||
this.directRelative = data.directRelative || {}
|
||||
this.directFamilyUnitLine = this.toBoolean(data.directFamilyUnitLine)
|
||||
this.fillBedInfo = data.fillBedInfo === undefined || data.fillBedInfo === null || this.toBoolean(data.fillBedInfo)
|
||||
this.detailVisible = true
|
||||
if (this.detailHasWorkflow()) {
|
||||
this.loadDoneTasks()
|
||||
}
|
||||
} else {
|
||||
this.$message.warning(res.msg || "查询失败")
|
||||
}
|
||||
})
|
||||
},
|
||||
doDelete(row) {
|
||||
this.$confirm("确定删除【" + row.userName + "】的报名台账吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post(loc() + "/doDelete", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("删除成功")
|
||||
this.pageData()
|
||||
this.loadFilterOptions()
|
||||
} else {
|
||||
this.$message.warning(res.msg || "删除失败")
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
loadUnionOptions() {
|
||||
this.$axios.post(loc() + "/unionOptions").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.unionOptions = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
loadFilterOptions() {
|
||||
this.loadLineOptions()
|
||||
this.loadTravelPeriodOptions()
|
||||
},
|
||||
scheduleFilterOptions(delay) {
|
||||
if (this.filterOptionsTimer) {
|
||||
clearTimeout(this.filterOptionsTimer)
|
||||
}
|
||||
this.filterOptionsTimer = setTimeout(() => {
|
||||
this.loadFilterOptions()
|
||||
this.filterOptionsTimer = null
|
||||
}, delay === undefined ? 80 : delay)
|
||||
},
|
||||
loadLineOptions() {
|
||||
this.$axios.post(loc() + "/lineOptions", {
|
||||
startYear: this.pageForm.startYear,
|
||||
endYear: this.pageForm.endYear,
|
||||
travelPeriod: this.pageForm.travelPeriod,
|
||||
lineType: this.pageForm.lineType,
|
||||
unionId: this.pageForm.unionId,
|
||||
keyword: this.pageForm.keyword,
|
||||
directFamilyOnly: this.pageForm.directFamilyOnly,
|
||||
overCostOnly: this.pageForm.overCostOnly
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.lineOptions = res.data || []
|
||||
if (this.pageForm.lineId && !this.lineOptions.some(item => item.lineId === this.pageForm.lineId)) {
|
||||
this.pageForm.lineId = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
loadTravelPeriodOptions() {
|
||||
this.$axios.post(loc() + "/travelPeriodOptions", {
|
||||
startYear: this.pageForm.startYear,
|
||||
endYear: this.pageForm.endYear,
|
||||
lineId: this.pageForm.lineId,
|
||||
lineType: this.pageForm.lineType,
|
||||
unionId: this.pageForm.unionId,
|
||||
keyword: this.pageForm.keyword,
|
||||
directFamilyOnly: this.pageForm.directFamilyOnly,
|
||||
overCostOnly: this.pageForm.overCostOnly
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.travelPeriodOptions = res.data || []
|
||||
if (this.pageForm.travelPeriod && !this.travelPeriodOptions.some(item => item.travelPeriod === this.pageForm.travelPeriod)) {
|
||||
this.pageForm.travelPeriod = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
doSearch() {
|
||||
this.tableKey = new Date().getTime()
|
||||
this.pageForm.pageNumber = 1
|
||||
this.clearTableSelection()
|
||||
this.pageData()
|
||||
this.scheduleFilterOptions(0)
|
||||
},
|
||||
setDirectFamilyOnly() {
|
||||
this.pageForm.directFamilyOnly = !this.pageForm.directFamilyOnly
|
||||
this.pageForm.pageNumber = 1
|
||||
this.clearTableSelection()
|
||||
this.pageData()
|
||||
this.scheduleFilterOptions(0)
|
||||
},
|
||||
setOverCostOnly() {
|
||||
this.pageForm.overCostOnly = !this.pageForm.overCostOnly
|
||||
this.pageForm.pageNumber = 1
|
||||
this.clearTableSelection()
|
||||
this.pageData()
|
||||
this.scheduleFilterOptions(0)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadUnionOptions()
|
||||
this.pageData()
|
||||
this.scheduleFilterOptions(150)
|
||||
},
|
||||
watch: {
|
||||
"pageForm.startYear"() {
|
||||
this.pageForm.lineId = ""
|
||||
this.pageForm.travelPeriod = ""
|
||||
this.scheduleFilterOptions()
|
||||
},
|
||||
"pageForm.endYear"() {
|
||||
this.pageForm.lineId = ""
|
||||
this.pageForm.travelPeriod = ""
|
||||
this.scheduleFilterOptions()
|
||||
},
|
||||
"pageForm.lineId"() {
|
||||
this.scheduleFilterOptions()
|
||||
},
|
||||
"pageForm.travelPeriod"() {
|
||||
this.scheduleFilterOptions()
|
||||
},
|
||||
"pageForm.lineType"() {
|
||||
this.scheduleFilterOptions()
|
||||
},
|
||||
"pageForm.unionId"() {
|
||||
this.scheduleFilterOptions()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,712 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="年度">
|
||||
<el-date-picker
|
||||
v-model="pageForm.year"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择年度"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="所属工会">
|
||||
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
|
||||
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="事项名称">
|
||||
<el-input
|
||||
v-model="pageForm.matterName"
|
||||
clearable
|
||||
placeholder="请输入事项名称"
|
||||
style="width: 100%"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="组织形式">
|
||||
<el-select v-model="pageForm.organizationType" clearable filterable placeholder="请选择组织形式" style="width: 100%">
|
||||
<el-option v-for="item in organizationTypeOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<div class="search-query" style="margin-left: auto;">
|
||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="事项列表">
|
||||
<el-button @click="openAdd" size="medium" type="primary">
|
||||
<i class="el-icon-plus"></i>
|
||||
新增
|
||||
</el-button>
|
||||
</table-tool>
|
||||
|
||||
<el-table
|
||||
v-loading="tableLoading"
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
border
|
||||
:row-class-name="tableRowClassName"
|
||||
@sort-change="pageOrder">
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="年度" prop="year" width="110" sortable="custom" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="事项名称" prop="matterName" min-width="200" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="线路名称" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="组织形式" prop="organizationTypeName" width="150" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="联系人" prop="contactName" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="联系方式" prop="contactPhone" width="150" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="事项状态" prop="enabled" width="160" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-switch
|
||||
v-model="row.enabled"
|
||||
active-text="启用"
|
||||
inactive-text="禁用"
|
||||
@change="toggleEnabled(row)">
|
||||
</el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="260" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="openBatchForm(row)">选择线路</el-button>
|
||||
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button size="mini" type="danger" :loading="row.deleteLoading" @click="doDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog
|
||||
:title="title"
|
||||
:visible.sync="dialogVisible"
|
||||
:close-on-click-modal="false"
|
||||
width="54%"
|
||||
@closed="destroyForm">
|
||||
<el-form :model="formData" :rules="formRules" label-width="110px" ref="form" :disabled="viewMode">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="年度" prop="year">
|
||||
<el-date-picker
|
||||
v-model="formData.year"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择年度"
|
||||
style="width: 100%"
|
||||
@change="yearChange">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="事项状态" prop="enabled">
|
||||
<el-radio-group v-model="formData.enabled">
|
||||
<el-radio :label="true">启用</el-radio>
|
||||
<el-radio :label="false">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="事项名称" prop="matterName">
|
||||
<el-input v-model="formData.matterName" maxlength="30" show-word-limit placeholder="请输入事项名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建人">
|
||||
<el-input v-model="formData.creatorName" readonly placeholder="当前登录人"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="疗休养配置" prop="settingId">
|
||||
<el-select v-model="formData.settingId" clearable filterable placeholder="请选择配置" style="width: 100%">
|
||||
<el-option v-for="item in settingOptions" :key="item.id" :label="item.configName" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="组织形式" prop="organizationType">
|
||||
<el-select v-model="formData.organizationType" clearable filterable placeholder="请选择组织形式" style="width: 100%" @change="organizationTypeChange">
|
||||
<el-option v-for="item in organizationTypeOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="所属工会" prop="unionId">
|
||||
<el-select v-model="formData.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%" :disabled="isUnionDisabled()">
|
||||
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button v-if="!viewMode" type="primary" :loading="subDis" @click="doSubmit">保存</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
|
||||
<el-dialog
|
||||
:title="batchTitle"
|
||||
:visible.sync="batchFormVisible"
|
||||
:close-on-click-modal="false"
|
||||
width="74%">
|
||||
<el-form :model="batchForm" :rules="batchRules" label-width="110px" ref="batchFormRef">
|
||||
<el-form-item label="线路" prop="lineId">
|
||||
<el-select v-model="batchForm.lineId" clearable filterable placeholder="请选择线路" style="width: 100%" @change="lineChange">
|
||||
<el-option v-for="item in lineOptions" :key="item.id" :label="lineOptionLabel(item)" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="报名开始时间" prop="signupStartTime">
|
||||
<el-date-picker v-model="batchForm.signupStartTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择时间" style="width: 100%"></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="报名结束时间" prop="signupEndTime">
|
||||
<el-date-picker v-model="batchForm.signupEndTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择时间" style="width: 100%"></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="出行开始时间" prop="travelStartTime">
|
||||
<el-date-picker v-model="batchForm.travelStartTime" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width: 100%" @change="travelStartChange"></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="出行结束时间" prop="travelEndTime">
|
||||
<el-date-picker v-model="batchForm.travelEndTime" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width: 100%" @change="travelEndChange"></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="联系人" prop="contactName">
|
||||
<el-input v-model="batchForm.contactName" maxlength="10" placeholder="请输入联系人"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="联系方式" prop="contactPhone">
|
||||
<el-input v-model="batchForm.contactPhone" maxlength="11" placeholder="请输入联系方式"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="8">
|
||||
<el-form-item label="预计费用" prop="estimatedCost">
|
||||
<el-input v-model="batchForm.estimatedCost" maxlength="10" placeholder="请输入预计费用"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="最少成团人数" prop="minGroupPeople">
|
||||
<el-input-number v-model="batchForm.minGroupPeople" :min="1" :precision="0" controls-position="right" style="width: 100%" @change="peopleChange"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="最多成团人数" prop="maxGroupPeople">
|
||||
<el-input-number v-model="batchForm.maxGroupPeople" :min="1" :precision="0" controls-position="right" style="width: 100%" @change="peopleChange"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="batchFormVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="batchSubDis" @click="submitBatch">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.el-table .direct-family-row > td,
|
||||
.el-table .direct-family-row > td .cell {
|
||||
color: #f56c6c;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
const currentYear = moment().format("YYYY")
|
||||
const validateUnion = (rule, value, callback) => {
|
||||
if (this.formData.organizationType === "schoolUnion" || value) {
|
||||
callback()
|
||||
} else {
|
||||
callback(new Error("必填"))
|
||||
}
|
||||
}
|
||||
const validateMobile = (rule, value, callback) => {
|
||||
const mobileReg = /^1[3-9]\d{9}$/
|
||||
if (!value) {
|
||||
callback(new Error("必填"))
|
||||
} else if (!mobileReg.test(value)) {
|
||||
callback(new Error("手机号格式不正确"))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const validateMoney = (rule, value, callback) => {
|
||||
const moneyReg = /^(0|[1-9]\d*)(\.\d{1,2})?$/
|
||||
if (value === "" || value === null || value === undefined) {
|
||||
callback(new Error("必填"))
|
||||
} else if (!moneyReg.test(String(value))) {
|
||||
callback(new Error("请输入非负金额,最多两位小数"))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const validatePeople = (rule, value, callback) => {
|
||||
if (!value || value <= 0) {
|
||||
callback(new Error("人数必须大于0"))
|
||||
} else if (this.batchForm.minGroupPeople && this.batchForm.maxGroupPeople
|
||||
&& this.batchForm.minGroupPeople > this.batchForm.maxGroupPeople) {
|
||||
callback(new Error("最少人数不能大于最多人数"))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const validateBatchTime = (rule, value, callback) => {
|
||||
if (!value) {
|
||||
callback(new Error("必填"))
|
||||
return
|
||||
}
|
||||
const form = this.batchForm
|
||||
if (form.signupStartTime && form.signupEndTime && form.signupStartTime >= form.signupEndTime) {
|
||||
callback(new Error("报名开始时间必须小于报名结束时间"))
|
||||
return
|
||||
}
|
||||
if (form.travelStartTime && form.travelEndTime && form.travelStartTime > form.travelEndTime) {
|
||||
callback(new Error("出行开始时间不能晚于出行结束时间"))
|
||||
return
|
||||
}
|
||||
if (form.signupEndTime && form.travelStartTime && form.signupEndTime >= form.travelStartTime) {
|
||||
callback(new Error("报名结束时间必须小于出行开始时间"))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
return {
|
||||
title: "",
|
||||
dialogVisible: false,
|
||||
viewMode: false,
|
||||
subDis: false,
|
||||
batchFormVisible: false,
|
||||
batchSubDis: false,
|
||||
batchTitle: "",
|
||||
currentMatter: {},
|
||||
batchForm: {},
|
||||
lineOptions: [],
|
||||
settingOptions: [],
|
||||
unionOptions: [],
|
||||
organizationTypeOptions: [],
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
year: currentYear,
|
||||
matterName: "",
|
||||
unionId: "",
|
||||
organizationType: ""
|
||||
},
|
||||
formData: {},
|
||||
formRules: {
|
||||
year: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
matterName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
settingId: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
unionId: [{ validator: validateUnion, trigger: ["blur", "change"] }],
|
||||
organizationType: [{ required: true, message: "必填", trigger: ["blur", "change"] }]
|
||||
},
|
||||
batchRules: {
|
||||
lineId: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
signupStartTime: [{ validator: validateBatchTime, trigger: ["blur", "change"] }],
|
||||
signupEndTime: [{ validator: validateBatchTime, trigger: ["blur", "change"] }],
|
||||
travelStartTime: [{ validator: validateBatchTime, trigger: ["blur", "change"] }],
|
||||
travelEndTime: [{ validator: validateBatchTime, trigger: ["blur", "change"] }],
|
||||
contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
contactPhone: [{ validator: validateMobile, trigger: ["blur", "change"] }],
|
||||
minGroupPeople: [{ validator: validatePeople, trigger: ["blur", "change"] }],
|
||||
maxGroupPeople: [{ validator: validatePeople, trigger: ["blur", "change"] }],
|
||||
estimatedCost: [{ validator: validateMoney, trigger: ["blur", "change"] }]
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
"pageForm.year"(year) {
|
||||
this.loadSettings(year)
|
||||
},
|
||||
"batchForm.lineId"() {
|
||||
if (this.batchFormVisible) {
|
||||
this.applySelectedLineDefaults(true)
|
||||
}
|
||||
},
|
||||
"batchForm.travelStartTime"() {
|
||||
if (this.batchFormVisible) {
|
||||
this.fillTravelEndByLot()
|
||||
}
|
||||
},
|
||||
"batchForm.travelEndTime"() {
|
||||
if (this.batchFormVisible) {
|
||||
this.validateTravelFields()
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetSearch() {
|
||||
this.pageForm.year = moment().format("YYYY")
|
||||
this.pageForm.matterName = ""
|
||||
this.pageForm.unionId = ""
|
||||
this.pageForm.organizationType = ""
|
||||
this.loadSettings(this.pageForm.year)
|
||||
this.doSearch()
|
||||
},
|
||||
tableRowClassName({ row }) {
|
||||
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
|
||||
},
|
||||
isDirectFamilyLine(row) {
|
||||
if (!row) return false
|
||||
return row.directFamilyUnitLine === true
|
||||
|| row.directFamilyUnitLine === 1
|
||||
|| row.directFamilyUnitLine === "1"
|
||||
},
|
||||
emptyForm() {
|
||||
const user = this.currentUser()
|
||||
return {
|
||||
year: moment().format("YYYY"),
|
||||
matterName: "",
|
||||
settingId: "",
|
||||
creatorUserId: user.id || "",
|
||||
creatorName: user.username || "",
|
||||
unionId: "",
|
||||
organizationType: "",
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
openAdd() {
|
||||
this.title = "新增事项信息"
|
||||
this.viewMode = false
|
||||
this.formData = this.emptyForm()
|
||||
this.loadSettings(this.formData.year, true)
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
|
||||
},
|
||||
openEdit(row) {
|
||||
this.title = "编辑事项信息"
|
||||
this.viewMode = false
|
||||
this.loadDetail(row.id)
|
||||
},
|
||||
openView(row) {
|
||||
this.title = "查看事项信息"
|
||||
this.viewMode = true
|
||||
this.loadDetail(row.id)
|
||||
},
|
||||
loadDetail(id) {
|
||||
this.$axios.post(loc() + "/detail", { id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.formData = Object.assign(this.emptyForm(), res.data || {})
|
||||
this.formData.year = this.formData.year ? String(this.formData.year) : ""
|
||||
this.ensureSelectedUnionOption()
|
||||
this.loadSettings(this.formData.year)
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
|
||||
} else {
|
||||
this.$message.warning(res.msg || "查询失败")
|
||||
}
|
||||
})
|
||||
},
|
||||
currentUser() {
|
||||
return (this.$store && this.$store.state && this.$store.state.user) || {}
|
||||
},
|
||||
currentUnion() {
|
||||
return this.currentUser().union || {}
|
||||
},
|
||||
organizationTypeChange() {
|
||||
this.applyOrganizationTypeRule()
|
||||
this.$nextTick(() => this.$refs.form && this.$refs.form.validateField("unionId"))
|
||||
},
|
||||
applyOrganizationTypeRule() {
|
||||
if (this.formData.organizationType === "schoolUnion") {
|
||||
this.formData.unionId = ""
|
||||
return
|
||||
}
|
||||
if (this.formData.organizationType === "branchUnion" || this.formData.organizationType === "personal") {
|
||||
const union = this.currentUnion()
|
||||
this.formData.unionId = union.id || ""
|
||||
this.ensureCurrentUnionOption(union)
|
||||
}
|
||||
},
|
||||
ensureCurrentUnionOption(union) {
|
||||
if (!union || !union.id) return
|
||||
const exists = this.unionOptions.some(item => item.id === union.id)
|
||||
if (!exists) {
|
||||
this.unionOptions.push({ id: union.id, name: union.name || "" })
|
||||
}
|
||||
},
|
||||
ensureSelectedUnionOption() {
|
||||
if (!this.formData.unionId) return
|
||||
const exists = this.unionOptions.some(item => item.id === this.formData.unionId)
|
||||
if (!exists) {
|
||||
this.unionOptions.push({ id: this.formData.unionId, name: this.formData.unionName || "" })
|
||||
}
|
||||
},
|
||||
isUnionDisabled() {
|
||||
return ["schoolUnion", "branchUnion", "personal"].includes(this.formData.organizationType)
|
||||
},
|
||||
yearChange(year) {
|
||||
this.formData.settingId = ""
|
||||
this.loadSettings(year, true)
|
||||
},
|
||||
loadSettings(year, autoSelectLatest) {
|
||||
this.$axios.post(loc() + "/settingOptions", { year }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.settingOptions = res.data || []
|
||||
if (autoSelectLatest && !this.formData.settingId && this.settingOptions.length > 0) {
|
||||
this.formData.settingId = this.settingOptions[0].id
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
loadUnions() {
|
||||
this.$axios.post(loc() + "/unionOptions").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.unionOptions = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
loadOrganizationTypes() {
|
||||
this.$axios.post(loc() + "/organizationTypeOptions").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.organizationTypeOptions = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
emptyBatchForm() {
|
||||
return {
|
||||
id: this.currentMatter.id || "",
|
||||
lineId: "",
|
||||
signupStartTime: "",
|
||||
signupEndTime: "",
|
||||
travelStartTime: "",
|
||||
travelEndTime: "",
|
||||
contactName: "",
|
||||
contactPhone: "",
|
||||
minGroupPeople: null,
|
||||
maxGroupPeople: null,
|
||||
estimatedCost: ""
|
||||
}
|
||||
},
|
||||
openBatchForm(row) {
|
||||
this.currentMatter = row
|
||||
this.loadLines(row.year)
|
||||
this.batchTitle = "选择线路"
|
||||
this.batchForm = this.emptyBatchForm()
|
||||
this.$axios.post(loc() + "/lineConfig", { matterId: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.batchForm = Object.assign(this.emptyBatchForm(), res.data || {})
|
||||
this.batchForm.travelStartTime = this.dateOnly(this.batchForm.travelStartTime)
|
||||
this.batchForm.travelEndTime = this.dateOnly(this.batchForm.travelEndTime)
|
||||
this.applySelectedLineDefaults(false)
|
||||
if (!this.batchForm.minGroupPeople || !this.batchForm.maxGroupPeople) {
|
||||
this.loadSettingPeople((data) => {
|
||||
this.batchForm.minGroupPeople = data.minGroupPeople || null
|
||||
this.batchForm.maxGroupPeople = data.maxGroupPeople || null
|
||||
})
|
||||
}
|
||||
this.batchFormVisible = true
|
||||
this.$nextTick(() => {
|
||||
this.applySelectedLineDefaults(true)
|
||||
this.$refs.batchFormRef && this.$refs.batchFormRef.clearValidate()
|
||||
})
|
||||
} else {
|
||||
this.$message.warning(res.msg || "查询失败")
|
||||
}
|
||||
})
|
||||
},
|
||||
loadLines(year) {
|
||||
this.$axios.post(loc() + "/lineOptions", { year }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.lineOptions = res.data || []
|
||||
this.applySelectedLineDefaults(true)
|
||||
}
|
||||
})
|
||||
},
|
||||
lineOptionLabel(item) {
|
||||
if (!item) return ""
|
||||
const lineName = this.lineField(item, "lineName") || ""
|
||||
const lineType = this.lineField(item, "lineType") || ""
|
||||
return lineType ? lineName + "(" + lineType + ")" : lineName
|
||||
},
|
||||
lineChange() {
|
||||
this.applySelectedLineDefaults(true)
|
||||
},
|
||||
travelStartChange() {
|
||||
this.fillTravelEndByLot()
|
||||
},
|
||||
travelEndChange() {
|
||||
this.validateTravelFields()
|
||||
},
|
||||
selectedLine() {
|
||||
return (this.lineOptions || []).find((item) => this.lineField(item, "id") === this.batchForm.lineId) || null
|
||||
},
|
||||
applySelectedLineDefaults(recalculateEnd) {
|
||||
const line = this.selectedLine()
|
||||
if (!line) return
|
||||
const activityCost = this.lineField(line, "activityCost")
|
||||
if (activityCost !== null && activityCost !== undefined && activityCost !== "") {
|
||||
this.batchForm.estimatedCost = activityCost
|
||||
}
|
||||
if (recalculateEnd) {
|
||||
this.fillTravelEndByLot()
|
||||
}
|
||||
},
|
||||
fillTravelEndByLot() {
|
||||
const line = this.selectedLine()
|
||||
const days = this.lineLotDays(line)
|
||||
if (!this.batchForm.travelStartTime || !days) return
|
||||
this.$set(this.batchForm, "travelEndTime", moment(this.batchForm.travelStartTime).add(days - 1, "days").format("YYYY-MM-DD"))
|
||||
this.validateTravelFields()
|
||||
},
|
||||
lineLotDays(line) {
|
||||
if (!line) return null
|
||||
const lotDays = this.lineField(line, "lotDays") || this.lineField(line, "lotValue")
|
||||
if (!/^\d+$/.test(String(lotDays || ""))) return null
|
||||
const days = parseInt(lotDays, 10)
|
||||
return days > 0 ? days : null
|
||||
},
|
||||
lineField(line, field) {
|
||||
if (!line) return null
|
||||
if (line[field] !== undefined) return line[field]
|
||||
const lowerField = field.toLowerCase()
|
||||
const matchedKey = Object.keys(line).find((key) => key.toLowerCase() === lowerField)
|
||||
return matchedKey ? line[matchedKey] : null
|
||||
},
|
||||
validateTravelFields() {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.batchFormRef) {
|
||||
this.$refs.batchFormRef.validateField(["travelStartTime", "travelEndTime"])
|
||||
}
|
||||
})
|
||||
},
|
||||
dateOnly(value) {
|
||||
if (!value) return ""
|
||||
return String(value).substring(0, 10)
|
||||
},
|
||||
loadSettingPeople(callback) {
|
||||
this.$axios.post(loc() + "/settingPeople", { matterId: this.currentMatter.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const data = res.data || {}
|
||||
callback && callback(data)
|
||||
}
|
||||
})
|
||||
},
|
||||
peopleChange() {
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.batchFormRef) {
|
||||
this.$refs.batchFormRef.validateField(["minGroupPeople", "maxGroupPeople"])
|
||||
}
|
||||
})
|
||||
},
|
||||
submitBatch() {
|
||||
this.$refs.batchFormRef.validate((valid) => {
|
||||
if (!valid) return
|
||||
this.batchSubDis = true
|
||||
this.$axios.post(loc() + "/lineConfigDoSubmit", this.batchForm).then((res) => {
|
||||
this.batchSubDis = false
|
||||
if (res.code === 0) {
|
||||
this.$message.success("保存成功")
|
||||
this.batchFormVisible = false
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(res.msg || "保存失败")
|
||||
}
|
||||
}).catch(() => {
|
||||
this.batchSubDis = false
|
||||
})
|
||||
})
|
||||
},
|
||||
doSubmit() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (!valid) return
|
||||
this.subDis = true
|
||||
this.$axios.post(loc() + "/doSubmit", this.formData).then((res) => {
|
||||
this.subDis = false
|
||||
if (res.code === 0) {
|
||||
this.$message.success("保存成功")
|
||||
this.dialogVisible = false
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(res.msg || "保存失败")
|
||||
}
|
||||
}).catch(() => {
|
||||
this.subDis = false
|
||||
})
|
||||
})
|
||||
},
|
||||
toggleEnabled(row) {
|
||||
this.$axios.post(loc() + "/doSubmit", row).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("状态已更新")
|
||||
} else {
|
||||
row.enabled = !row.enabled
|
||||
this.$message.warning(res.msg || "状态更新失败")
|
||||
}
|
||||
}).catch(() => {
|
||||
row.enabled = !row.enabled
|
||||
})
|
||||
},
|
||||
doDelete(row) {
|
||||
this.$set(row, "deleteLoading", true)
|
||||
this.$axios.post(loc() + "/deleteInfo", { id: row.id }).then((res) => {
|
||||
this.$set(row, "deleteLoading", false)
|
||||
if (res.code !== 0) {
|
||||
this.$message.warning(res.msg || "删除检查失败")
|
||||
return
|
||||
}
|
||||
const data = res.data || {}
|
||||
const signupCount = Number(data.signupCount || 0)
|
||||
const matterName = row.matterName || ""
|
||||
if (!data.canDelete) {
|
||||
this.$alert("事项【" + matterName + "】已有 " + signupCount + " 人报名,不能删除。", "删除提醒", {
|
||||
confirmButtonText: "知道了",
|
||||
type: "warning"
|
||||
})
|
||||
return
|
||||
}
|
||||
this.$confirm("事项【" + matterName + "】暂无人员报名,确认删除吗?", "删除提醒", {
|
||||
confirmButtonText: "确认删除",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning",
|
||||
confirmButtonClass: "el-button--danger"
|
||||
}).then(() => {
|
||||
this.$set(row, "deleteLoading", true)
|
||||
this.$axios.post(loc() + "/doDelete", { id: row.id }).then((deleteRes) => {
|
||||
this.$set(row, "deleteLoading", false)
|
||||
if (deleteRes.code === 0) {
|
||||
this.$message.success("删除成功")
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(deleteRes.msg || "删除失败")
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$set(row, "deleteLoading", false)
|
||||
})
|
||||
}).catch(() => {})
|
||||
}).catch(() => {
|
||||
this.$set(row, "deleteLoading", false)
|
||||
})
|
||||
},
|
||||
destroyForm() {
|
||||
this.formData = {}
|
||||
this.viewMode = false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadSettings(this.pageForm.year)
|
||||
this.loadUnions()
|
||||
this.loadOrganizationTypes()
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<el-card shadow="never">
|
||||
<el-empty description="${title!'功能建设中'}">
|
||||
<template slot="description">
|
||||
<span>${title!'功能建设中'}正在分阶段建设中</span>
|
||||
</template>
|
||||
</el-empty>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app"
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,432 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="创建年度">
|
||||
<el-date-picker
|
||||
v-model="pageForm.year"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择创建年度"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="线路名称">
|
||||
<el-input
|
||||
v-model="pageForm.lineName"
|
||||
clearable
|
||||
placeholder="请输入线路名称"
|
||||
style="width: 100%"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="时间标段">
|
||||
<el-select v-model="pageForm.lotId" clearable filterable placeholder="请选择时间标段" style="width: 100%">
|
||||
<el-option v-for="item in lotOptions" :key="item.id" :label="item.lotName" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<div class="search-query" style="margin-left: auto;">
|
||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="线路列表">
|
||||
<el-button @click="openAdd" size="medium" type="primary">
|
||||
<i class="el-icon-plus"></i>
|
||||
新增
|
||||
</el-button>
|
||||
</table-tool>
|
||||
|
||||
<el-table
|
||||
v-loading="tableLoading"
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
border
|
||||
:default-sort="{prop: 'enabled', order: 'descending'}"
|
||||
:row-class-name="tableRowClassName"
|
||||
@sort-change="pageOrder">
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="创建年度" prop="year" width="120" sortable="custom" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="线路编号" prop="lineCode" width="140" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="线路名称" prop="lineName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="时间标段" prop="lotName" width="140" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="线路类型" prop="lineType" width="140" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="创建人" prop="creatorName" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="是否对外开放" prop="openFlag" width="160" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-switch
|
||||
v-model="row.openFlag"
|
||||
active-text="是"
|
||||
inactive-text="否"
|
||||
@change="toggleOpenFlag(row)">
|
||||
</el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="激活状态" prop="enabled" width="160" sortable="custom" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-switch
|
||||
v-model="row.enabled"
|
||||
active-text="启用"
|
||||
inactive-text="禁用"
|
||||
@change="toggleEnabled(row)">
|
||||
</el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="260" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
|
||||
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button size="mini" type="danger" @click="doDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog
|
||||
:title="title"
|
||||
:visible.sync="dialogVisible"
|
||||
:close-on-click-modal="false"
|
||||
width="72%"
|
||||
@closed="destroyEditor">
|
||||
<el-form :model="formData" :rules="formRules" label-width="110px" ref="form" :disabled="viewMode">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="创建年度" prop="year">
|
||||
<el-date-picker
|
||||
v-model="formData.year"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择创建年度"
|
||||
style="width: 100%"
|
||||
@change="yearChange">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="旅行社名称" prop="travelAgencyId">
|
||||
<el-select v-model="formData.travelAgencyId" clearable filterable placeholder="请选择旅行社" style="width: 100%">
|
||||
<el-option v-for="item in travelAgencyOptions" :key="item.id" :label="item.agencyName" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="线路名称" prop="lineName">
|
||||
<el-input v-model="formData.lineName" maxlength="30" show-word-limit placeholder="请输入线路名称"></el-input>
|
||||
</el-form-item>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="创建人">
|
||||
<el-input v-model="formData.creatorName" readonly placeholder="当前登录人"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="所在单位">
|
||||
<el-input v-model="formData.unitName" readonly placeholder="当前登录人所在单位"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="线路编号" prop="lineCode">
|
||||
<el-input v-model="formData.lineCode" maxlength="50" placeholder="建议年度加序号,如202601"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="激活状态" prop="enabled">
|
||||
<el-radio-group v-model="formData.enabled">
|
||||
<el-radio :label="true">启用</el-radio>
|
||||
<el-radio :label="false">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否对外开放" prop="openFlag">
|
||||
<el-radio-group v-model="formData.openFlag">
|
||||
<el-radio :label="true">是</el-radio>
|
||||
<el-radio :label="false">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="直系亲属线路" prop="directFamilyUnitLine">
|
||||
<el-radio-group v-model="formData.directFamilyUnitLine">
|
||||
<el-radio :label="true">是</el-radio>
|
||||
<el-radio :label="false">否</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="线路类型" prop="lineType">
|
||||
<el-select v-model="formData.lineType" clearable placeholder="请选择线路类型" style="width: 100%">
|
||||
<el-option v-for="item in lineTypeOptions" :key="item.code" :label="item.name" :value="item.name"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="时间标段" prop="lotId">
|
||||
<el-select v-model="formData.lotId" clearable filterable placeholder="请选择时间标段" style="width: 100%">
|
||||
<el-option v-for="item in lotOptions" :key="item.id" :label="item.lotName" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="线路内容" prop="lineContent">
|
||||
<text-editor v-model="formData.lineContent"></text-editor>
|
||||
</el-form-item>
|
||||
<el-form-item label="移动端缩略图" prop="mobileThumb">
|
||||
<!-- 与旅行社管理保持一致:移动端缩略图使用单图上传并保存URL。 -->
|
||||
<file-upload
|
||||
style="--upload-width: 200px;--upload-height:108px"
|
||||
:upload_number="1"
|
||||
:upload_size="20971520"
|
||||
:value.sync="formData.mobileThumb"
|
||||
accept=".jpg,.jpeg,.png"
|
||||
complete_result
|
||||
upload_mode="image"
|
||||
upload_result_category="interval">
|
||||
</file-upload>
|
||||
<div class="el-upload__tip">支持jpg、jpeg、png格式,大小不超过20MB。</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button v-if="!viewMode" type="primary" :loading="submitLoading" @click="doSubmit">保存</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.el-table .direct-family-row > td,
|
||||
.el-table .direct-family-row > td .cell {
|
||||
color: #f56c6c;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
const currentYear = moment().format("YYYY")
|
||||
return {
|
||||
title: "",
|
||||
dialogVisible: false,
|
||||
viewMode: false,
|
||||
submitLoading: false,
|
||||
travelAgencyOptions: [],
|
||||
lotOptions: [],
|
||||
lineTypeOptions: [],
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "enabled",
|
||||
pageOrderBy: "descending",
|
||||
year: currentYear,
|
||||
lineName: "",
|
||||
lotId: ""
|
||||
},
|
||||
formData: {},
|
||||
formRules: {
|
||||
year: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
travelAgencyId: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
lineName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
lineCode: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
lineType: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
lotId: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
mobileThumb: [{ required: true, message: "请上传移动端缩略图", trigger: ["blur", "change"] }]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetSearch() {
|
||||
this.pageForm.year = moment().format("YYYY")
|
||||
this.pageForm.lineName = ""
|
||||
this.pageForm.lotId = ""
|
||||
this.loadOptions(this.pageForm.year)
|
||||
this.doSearch()
|
||||
},
|
||||
tableRowClassName({ row }) {
|
||||
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
|
||||
},
|
||||
isDirectFamilyLine(row) {
|
||||
if (!row) return false
|
||||
return row.directFamilyUnitLine === true
|
||||
|| row.directFamilyUnitLine === 1
|
||||
|| row.directFamilyUnitLine === "1"
|
||||
},
|
||||
emptyForm() {
|
||||
const user = (this.$store && this.$store.state && this.$store.state.user) || {}
|
||||
return {
|
||||
year: moment().format("YYYY"),
|
||||
travelAgencyId: "",
|
||||
creatorUserId: user.id || "",
|
||||
creatorName: user.username || "",
|
||||
unitId: user.unit?.id || "",
|
||||
unitName: user.unit?.name || "",
|
||||
lineName: "",
|
||||
lineCode: "",
|
||||
enabled: true,
|
||||
openFlag: true,
|
||||
directFamilyUnitLine: false,
|
||||
lineType: "",
|
||||
lotId: "",
|
||||
lineContent: "",
|
||||
mobileThumb: ""
|
||||
}
|
||||
},
|
||||
openAdd() {
|
||||
this.title = "新增线路信息"
|
||||
this.viewMode = false
|
||||
this.formData = this.emptyForm()
|
||||
this.loadOptions(this.formData.year)
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
|
||||
},
|
||||
openEdit(row) {
|
||||
this.title = "编辑线路信息"
|
||||
this.viewMode = false
|
||||
this.loadDetail(row.id)
|
||||
},
|
||||
openView(row) {
|
||||
this.title = "查看线路信息"
|
||||
this.viewMode = true
|
||||
this.loadDetail(row.id)
|
||||
},
|
||||
loadDetail(id) {
|
||||
this.$axios.post(loc() + "/detail", { id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.formData = Object.assign(this.emptyForm(), res.data || {})
|
||||
this.formData.year = this.formData.year ? String(this.formData.year) : ""
|
||||
this.loadOptions(this.formData.year)
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
|
||||
} else {
|
||||
this.$message.warning(res.msg || "查询失败")
|
||||
}
|
||||
})
|
||||
},
|
||||
yearChange(year) {
|
||||
this.formData.travelAgencyId = ""
|
||||
this.formData.lotId = ""
|
||||
this.loadOptions(year)
|
||||
},
|
||||
loadOptions(year) {
|
||||
this.loadTravelAgencies(year)
|
||||
this.loadLots(year)
|
||||
this.loadLineTypes()
|
||||
},
|
||||
loadTravelAgencies(year) {
|
||||
this.$axios.post(loc() + "/travelAgencyOptions", { year }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.travelAgencyOptions = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
loadLots(year) {
|
||||
this.$axios.post(loc() + "/lotOptions", { year }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.lotOptions = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
loadLineTypes() {
|
||||
this.$axios.post(loc() + "/lineTypeOptions").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.lineTypeOptions = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
doSubmit() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (!valid) return
|
||||
this.submitLoading = true
|
||||
this.$axios.post(loc() + "/doSubmit", this.formData).then((res) => {
|
||||
this.submitLoading = false
|
||||
if (res.code === 0) {
|
||||
this.$message.success("保存成功")
|
||||
this.dialogVisible = false
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(res.msg || "保存失败")
|
||||
}
|
||||
}).catch(() => {
|
||||
this.submitLoading = false
|
||||
})
|
||||
})
|
||||
},
|
||||
toggleEnabled(row) {
|
||||
this.$axios.post(loc() + "/doSubmit", row).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("状态已更新")
|
||||
} else {
|
||||
row.enabled = !row.enabled
|
||||
this.$message.warning(res.msg || "状态更新失败")
|
||||
}
|
||||
}).catch(() => {
|
||||
row.enabled = !row.enabled
|
||||
})
|
||||
},
|
||||
toggleOpenFlag(row) {
|
||||
this.$axios.post(loc() + "/doSubmit", row).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("对外开放状态已更新")
|
||||
} else {
|
||||
row.openFlag = !row.openFlag
|
||||
this.$message.warning(res.msg || "对外开放状态更新失败")
|
||||
}
|
||||
}).catch(() => {
|
||||
row.openFlag = !row.openFlag
|
||||
})
|
||||
},
|
||||
doDelete(row) {
|
||||
this.$confirm("确定删除【" + row.lineName + "】吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post(loc() + "/doDelete", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("删除成功")
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(res.msg || "删除失败")
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
destroyEditor() {
|
||||
this.formData = {}
|
||||
this.viewMode = false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadOptions(this.pageForm.year)
|
||||
this.pageData()
|
||||
},
|
||||
watch: {
|
||||
"pageForm.year"(year) {
|
||||
this.pageForm.lotId = ""
|
||||
this.loadOptions(year)
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
+547
@@ -0,0 +1,547 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="开始年度">
|
||||
<el-date-picker
|
||||
v-model="pageForm.startYear"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择开始年度"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="结束年度">
|
||||
<el-date-picker
|
||||
v-model="pageForm.endYear"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择结束年度"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="姓名/工号">
|
||||
<el-input
|
||||
v-model="pageForm.keyword"
|
||||
clearable
|
||||
placeholder="请输入姓名或工号"
|
||||
style="width: 100%"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="所属工会">
|
||||
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
|
||||
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="线路">
|
||||
<el-select v-model="pageForm.lineId" clearable filterable placeholder="请选择线路" style="width: 100%">
|
||||
<el-option v-for="item in lineOptions" :key="item.lineId" :label="item.lineName" :value="item.lineId"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="出行时段">
|
||||
<el-select v-model="pageForm.travelPeriod" clearable filterable placeholder="请选择出行时段" style="width: 100%">
|
||||
<el-option v-for="item in travelPeriodOptions" :key="item.travelPeriod" :label="item.travelPeriod" :value="item.travelPeriod"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="线路类型">
|
||||
<el-select v-model="pageForm.lineType" clearable filterable placeholder="请选择线路类型" style="width: 100%">
|
||||
<el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<div class="search-query" style="margin-left: auto;">
|
||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="校工会审核">
|
||||
<el-radio-group class="mr5" size="small" v-model="pageForm.audit" @change="changeAudit">
|
||||
<el-radio-button :label="true">已审核</el-radio-button>
|
||||
<el-radio-button :label="false">未审核</el-radio-button>
|
||||
</el-radio-group>
|
||||
</table-tool>
|
||||
<el-table
|
||||
v-loading="tableLoading"
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
border
|
||||
@sort-change="pageOrder">
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="所在单位" prop="unitName" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="报名线路" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="出行时段" prop="travelPeriod" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="线路类型" prop="lineType" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="报名时间" prop="signupTime" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="携带家属" prop="familyCount" width="120" sortable="custom" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" :type="Number(row.familyCount || 0) > 0 ? 'success' : 'info'">{{ row.familyCount || 0 }}人</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="报销超出费用" prop="overCostReimbursed" width="140" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" :type="row.overCostReimbursed ? 'success' : 'info'">{{ row.overCostReimbursed ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前节点" prop="curTaskName" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="流程状态" prop="instanceState" width="120" sortable="custom" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
|
||||
<el-button v-if="row.taskState === 10" size="mini" type="primary" @click="openApproval(row)">审核</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
|
||||
<template #edit>
|
||||
<tour-approval-info ref="infoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">
|
||||
{{ formData.taskName }}
|
||||
</div>
|
||||
<el-form
|
||||
:model="formData"
|
||||
ref="formRef"
|
||||
label-width="0"
|
||||
label-suffix=":"
|
||||
class="flow-task-form">
|
||||
<el-form-item
|
||||
label="审批意见"
|
||||
prop="tf_opinion"
|
||||
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
|
||||
<user-opinion-textarea v-model="formData.tf_opinion"></user-opinion-textarea>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row type="flex" justify="end">
|
||||
<el-button @click="$refs.guava.index()" size="small">取消</el-button>
|
||||
<el-button @click="handleTaskAction(6)" size="small" type="info">退回到发起人</el-button>
|
||||
<el-button @click="handleTaskAction(2)" size="small" type="danger">拒绝申请</el-button>
|
||||
<el-button @click="handleTaskAction(1)" size="small" type="primary">同意申请</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</tour-approval-info>
|
||||
</template>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#app .search .search-item {
|
||||
width: calc((100% - 150px) / 4);
|
||||
}
|
||||
#app .search .search-query {
|
||||
margin-left: auto;
|
||||
}
|
||||
@media screen and (max-width: 1350px) {
|
||||
#app .search .search-item {
|
||||
width: calc((100% - 100px) / 3);
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 1200px) {
|
||||
#app .search .search-item {
|
||||
width: calc((100% - 50px) / 2);
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 992px) {
|
||||
#app .search .search-item {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.tour-approval-section {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.tour-approval-table {
|
||||
width: 100%;
|
||||
}
|
||||
.tour-approval-empty {
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
const TOUR_APPROVAL_INFO = {
|
||||
template: /*language=HTML*/ `
|
||||
<div>
|
||||
<div class="process-title">
|
||||
报名信息
|
||||
<el-link type="primary" @click="openChart">点击查看流程图</el-link>
|
||||
</div>
|
||||
<el-descriptions :column="3" border class="tour-approval-section">
|
||||
<el-descriptions-item label="工号">{{ detail.jobNo || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ detail.userName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ detail.gender || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="身份证号">{{ detail.idCard || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在单位">{{ detail.unitName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在工会">{{ detail.unionName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名时间">{{ detail.signupTime || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名线路">{{ detail.lineName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出行时段">{{ detail.travelPeriod || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="线路类型">{{ detail.lineType || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名酒店">{{ detail.hotelName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="旅行社">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否携带家属">
|
||||
{{ familyText() }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="报销超出费用">{{ detail.overCostReimbursed ? '是' : '否' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="fillBedInfo" label="意向拼床人">{{ detail.intendedRoommate || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="fillBedInfo" label="床型">{{ detail.bedType || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="fillBedInfo" label="床位信息">{{ detail.bedInfo || '' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div v-if="isDirectFamilyLine()" class="tour-approval-section">
|
||||
<div class="process-title">直系亲属线路</div>
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item label="亲属姓名">{{ directRelative.relativeName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在单位">{{ directRelative.unitName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="亲属关系">{{ directRelative.relationshipName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="线路名称">{{ directRelative.lineName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出行开始日期">{{ directRelative.travelStartTime || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出行结束日期">{{ directRelative.travelEndTime || '' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<div v-if="showFamilySection()" class="tour-approval-section">
|
||||
<div class="process-title">亲属信息</div>
|
||||
<el-table :data="familyData" border size="mini" empty-text="暂无亲属信息" class="tour-approval-table">
|
||||
<el-table-column label="序号" type="index" width="70" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="家属姓名" prop="familyName" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="性别" prop="gender" width="90" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="身份证号码" prop="idCard" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="年龄" prop="age" width="90" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="关系" prop="relationship" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column v-if="fillBedInfo" label="床型" prop="bedType" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column v-if="fillBedInfo" label="床位" prop="bedInfo" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column v-if="fillBedInfo" label="意向拼床人" prop="intendedRoommate" min-width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<template v-for="task in doneTasks">
|
||||
<div class="mt10">
|
||||
<div class="process-title">{{ task.displayName }}</div>
|
||||
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-if="task.ext.isFirstTaskNode">
|
||||
<el-descriptions-item label="申请用户">
|
||||
{{ task.ext.initiatorName }}({{ task.ext.initiatorAccount }})
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="办理结果">
|
||||
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-else>
|
||||
<el-descriptions-item label="办理用户">
|
||||
{{ task.taskFormData.userName }}({{ task.taskFormData.loginName }})
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="办理结果">
|
||||
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="办理意见" :span="3">
|
||||
{{ task.taskFormData.opinion || task.taskFormData.tf_opinion || '' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<snaker-chart ref="snakerChartRef"></snaker-chart>
|
||||
</div>
|
||||
`,
|
||||
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
|
||||
data() {
|
||||
return {
|
||||
row: {},
|
||||
detail: {},
|
||||
familyData: [],
|
||||
directRelative: {},
|
||||
allowFamily: false,
|
||||
fillBedInfo: true,
|
||||
directFamilyUnitLine: false,
|
||||
doneTasks: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen(row) {
|
||||
this.row = row || {}
|
||||
this.detail = {}
|
||||
this.familyData = []
|
||||
this.directRelative = {}
|
||||
this.allowFamily = false
|
||||
this.fillBedInfo = true
|
||||
this.directFamilyUnitLine = false
|
||||
this.doneTasks = []
|
||||
this.info()
|
||||
this.getDoneTasks()
|
||||
},
|
||||
info() {
|
||||
this.$axios.post(loc() + "/detail", { id: this.row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const data = res.data || {}
|
||||
this.detail = data.ledger || {}
|
||||
this.detail.travelPeriod = data.travelPeriod || this.row.travelPeriod || ""
|
||||
this.familyData = data.families || []
|
||||
this.directRelative = data.directRelative || {}
|
||||
this.allowFamily = this.toBoolean(data.allowFamily)
|
||||
this.fillBedInfo = data.fillBedInfo === undefined || data.fillBedInfo === null || this.toBoolean(data.fillBedInfo)
|
||||
this.directFamilyUnitLine = this.toBoolean(data.directFamilyUnitLine)
|
||||
} else {
|
||||
this.$message.warning(res.msg || "查询失败")
|
||||
}
|
||||
})
|
||||
},
|
||||
toBoolean(value) {
|
||||
return value === true || value === 1 || value === "1"
|
||||
},
|
||||
isDirectFamilyLine() {
|
||||
return this.directFamilyUnitLine
|
||||
|| this.toBoolean(this.detail.directFamilyUnitLine)
|
||||
|| (this.directRelative && this.directRelative.id)
|
||||
},
|
||||
hasFamily() {
|
||||
return this.toBoolean(this.detail.hasFamily) || this.familyData.length > 0
|
||||
},
|
||||
showFamilySection() {
|
||||
return this.allowFamily && this.hasFamily() && !this.isDirectFamilyLine()
|
||||
},
|
||||
familyText() {
|
||||
if (this.isDirectFamilyLine()) {
|
||||
return "否"
|
||||
}
|
||||
if (!this.allowFamily) {
|
||||
return "否"
|
||||
}
|
||||
return this.hasFamily() ? "是" : "否"
|
||||
},
|
||||
getDoneTasks() {
|
||||
this.$axios.post("/flow/common/doneTasks", { bizId: this.row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.doneTasks = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
openChart() {
|
||||
if (!this.row.instanceProcessDefineId || !this.row.instanceId) {
|
||||
this.$message.warning("暂无流程图信息")
|
||||
return
|
||||
}
|
||||
this.$refs.snakerChartRef.onOpenFull(this.row.instanceProcessDefineId, this.row.instanceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
const currentYear = moment().format("YYYY")
|
||||
return {
|
||||
unionOptions: [],
|
||||
lineOptions: [],
|
||||
travelPeriodOptions: [],
|
||||
lineTypeOptions: [],
|
||||
showApprovalForm: false,
|
||||
formData: {
|
||||
tf_opinion: ""
|
||||
},
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "signupTime",
|
||||
pageOrderBy: "descending",
|
||||
audit: false,
|
||||
startYear: currentYear,
|
||||
endYear: currentYear,
|
||||
keyword: "",
|
||||
unionId: "",
|
||||
lineId: "",
|
||||
travelPeriod: "",
|
||||
lineType: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetSearch() {
|
||||
this.pageForm.startYear = moment().format("YYYY")
|
||||
this.pageForm.endYear = moment().format("YYYY")
|
||||
this.pageForm.keyword = ""
|
||||
this.pageForm.unionId = ""
|
||||
this.pageForm.lineId = ""
|
||||
this.pageForm.travelPeriod = ""
|
||||
this.pageForm.lineType = ""
|
||||
this.loadOptions()
|
||||
this.doSearch()
|
||||
},
|
||||
changeAudit() {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.clearCascadeFilters()
|
||||
this.loadOptions()
|
||||
this.pageData()
|
||||
},
|
||||
clearCascadeFilters() {
|
||||
this.pageForm.unionId = ""
|
||||
this.pageForm.lineId = ""
|
||||
this.pageForm.travelPeriod = ""
|
||||
this.pageForm.lineType = ""
|
||||
},
|
||||
openView(row) {
|
||||
this.$refs.guava.edit(() => {
|
||||
this.showApprovalForm = false
|
||||
this.formData = {
|
||||
tf_opinion: ""
|
||||
}
|
||||
this.$refs.infoRef.onOpen(row)
|
||||
})
|
||||
},
|
||||
openApproval(row) {
|
||||
this.$refs.guava.edit(() => {
|
||||
this.showApprovalForm = true
|
||||
this.formData = {
|
||||
processTaskId: row.taskId,
|
||||
taskName: row.curTaskName || row.taskName,
|
||||
tf_opinion: ""
|
||||
}
|
||||
this.$refs.infoRef.onOpen(row)
|
||||
})
|
||||
},
|
||||
handleTaskAction(val) {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (!valid) return
|
||||
this.$confirm("您确定要提交吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
const loading = createLoading()
|
||||
this.$axios.post("/flow/common/executeTask", {
|
||||
data: JSON.stringify({
|
||||
...this.formData,
|
||||
submitType: val
|
||||
})
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$refs.guava.index()
|
||||
this.$message.success(res.msg || "操作成功")
|
||||
this.doSearch()
|
||||
} else {
|
||||
this.$message.warning(res.msg || "操作失败")
|
||||
}
|
||||
}).finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
}).catch(() => {})
|
||||
})
|
||||
},
|
||||
loadOptions() {
|
||||
this.loadUnionOptions()
|
||||
this.loadLineOptions()
|
||||
this.loadTravelPeriodOptions()
|
||||
this.loadLineTypeOptions()
|
||||
},
|
||||
baseOptionParams() {
|
||||
return {
|
||||
audit: this.pageForm.audit,
|
||||
startYear: this.pageForm.startYear,
|
||||
endYear: this.pageForm.endYear,
|
||||
keyword: this.pageForm.keyword,
|
||||
unionId: this.pageForm.unionId,
|
||||
lineId: this.pageForm.lineId,
|
||||
travelPeriod: this.pageForm.travelPeriod,
|
||||
lineType: this.pageForm.lineType
|
||||
}
|
||||
},
|
||||
loadUnionOptions() {
|
||||
this.$axios.post(loc() + "/unionOptions", this.baseOptionParams()).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.unionOptions = res.data || []
|
||||
if (this.pageForm.unionId && !this.unionOptions.some(item => item.id === this.pageForm.unionId)) {
|
||||
this.pageForm.unionId = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
loadLineOptions() {
|
||||
this.$axios.post(loc() + "/lineOptions", this.baseOptionParams()).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.lineOptions = res.data || []
|
||||
if (this.pageForm.lineId && !this.lineOptions.some(item => item.lineId === this.pageForm.lineId)) {
|
||||
this.pageForm.lineId = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
loadTravelPeriodOptions() {
|
||||
this.$axios.post(loc() + "/travelPeriodOptions", this.baseOptionParams()).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.travelPeriodOptions = res.data || []
|
||||
if (this.pageForm.travelPeriod && !this.travelPeriodOptions.some(item => item.travelPeriod === this.pageForm.travelPeriod)) {
|
||||
this.pageForm.travelPeriod = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
loadLineTypeOptions() {
|
||||
this.$axios.post(loc() + "/lineTypeOptions", this.baseOptionParams()).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.lineTypeOptions = res.data || []
|
||||
if (this.pageForm.lineType && !this.lineTypeOptions.some(item => item.lineType === this.pageForm.lineType)) {
|
||||
this.pageForm.lineType = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadOptions()
|
||||
this.pageData()
|
||||
},
|
||||
components: {
|
||||
"tour-approval-info": TOUR_APPROVAL_INFO
|
||||
},
|
||||
watch: {
|
||||
"pageForm.startYear"() {
|
||||
this.clearCascadeFilters()
|
||||
this.loadOptions()
|
||||
},
|
||||
"pageForm.endYear"() {
|
||||
this.clearCascadeFilters()
|
||||
this.loadOptions()
|
||||
},
|
||||
"pageForm.lineId"() {
|
||||
this.loadTravelPeriodOptions()
|
||||
this.loadLineTypeOptions()
|
||||
},
|
||||
"pageForm.travelPeriod"() {
|
||||
this.loadLineOptions()
|
||||
this.loadLineTypeOptions()
|
||||
},
|
||||
"pageForm.lineType"() {
|
||||
this.loadLineOptions()
|
||||
this.loadTravelPeriodOptions()
|
||||
},
|
||||
"pageForm.unionId"() {
|
||||
this.loadLineOptions()
|
||||
this.loadTravelPeriodOptions()
|
||||
this.loadLineTypeOptions()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,634 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="年度">
|
||||
<el-date-picker
|
||||
v-model="pageForm.year"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择年度"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="配置名称">
|
||||
<el-input
|
||||
v-model="pageForm.configName"
|
||||
clearable
|
||||
placeholder="请输入疗休养配置名称"
|
||||
style="width: 100%"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<div class="search-query">
|
||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="疗休养配置">
|
||||
<el-button type="primary" size="medium" @click="openAdd">
|
||||
<i class="el-icon-plus"></i>
|
||||
新建
|
||||
</el-button>
|
||||
</table-tool>
|
||||
|
||||
<el-table
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
border
|
||||
class="vi-table"
|
||||
ref="table"
|
||||
row-key="id"
|
||||
v-loading="tableLoading"
|
||||
@sort-change="pageOrder"
|
||||
>
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="70" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="年度" prop="year" width="100" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="疗休养配置名称" prop="configName" min-width="260" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="周期开始年度" prop="cycleStartYear" width="130" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="最少成团人数" prop="minGroupPeople" width="120" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="最多成团人数" prop="maxGroupPeople" width="120" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="是否携带家属" prop="allowFamily" width="120" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" type="success" v-if="row.allowFamily">允许</el-tag>
|
||||
<el-tag size="mini" type="info" v-else>不允许</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否启用" prop="enabled" width="100" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" type="success" v-if="row.enabled">启用</el-tag>
|
||||
<el-tag size="mini" type="info" v-else>停用</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="150" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button size="mini" type="danger" @click="doDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog
|
||||
:title="title"
|
||||
:visible.sync="dialogVisible"
|
||||
:close-on-click-modal="false"
|
||||
width="64.8%"
|
||||
@closed="destroyEditor">
|
||||
<el-form :model="formData" :rules="formRules" label-width="120px" ref="form">
|
||||
<div v-if="title === '新建疗休养配置'" class="tour-setting-inherit">
|
||||
<el-button type="primary" plain size="medium" icon="el-icon-copy-document" :loading="inheritLoading" @click="inheritPreviousYearInfo">延用上一年信息</el-button>
|
||||
</div>
|
||||
<el-tabs v-model="activeTab" type="card">
|
||||
<el-tab-pane label="基本信息" name="basic">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="年度" prop="year">
|
||||
<el-date-picker
|
||||
v-model="formData.year"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择年度"
|
||||
style="width: 100%"
|
||||
></el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="配置名称" prop="configName">
|
||||
<el-input v-model="formData.configName" maxlength="100" placeholder="请输入配置名称"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="疗休养类型" prop="tourType">
|
||||
<el-select v-model="formData.tourType" clearable allow-create filterable placeholder="请选择或输入类型" style="width: 100%">
|
||||
<el-option label="省内疗休养" value="省内疗休养"></el-option>
|
||||
<el-option label="省外疗休养" value="省外疗休养"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="可参加人员" prop="activityGroupId">
|
||||
<div class="tour-user-scope">
|
||||
<el-select
|
||||
v-model="formData.activityGroupId"
|
||||
placeholder="请选择可参加人员"
|
||||
filterable
|
||||
clearable
|
||||
style="width: 100%">
|
||||
<el-option
|
||||
v-for="item in activityGroupList"
|
||||
:key="item.groupId"
|
||||
:label="item.groupName"
|
||||
:value="item.groupId">
|
||||
</el-option>
|
||||
</el-select>
|
||||
<el-button type="primary" @click="openUserScope">设置</el-button>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="最少成团人数" prop="minGroupPeople">
|
||||
<el-input-number v-model="formData.minGroupPeople" :controls="false" :min="0" :precision="0" placeholder="请输入最少成团人数" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="最多成团人数" prop="maxGroupPeople">
|
||||
<el-input-number v-model="formData.maxGroupPeople" :controls="false" :min="0" :precision="0" placeholder="请输入最多成团人数" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div class="tour-setting-basic-divider"></div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="省外几年去一次" prop="outProvinceYears">
|
||||
<el-input-number v-model="formData.outProvinceYears" :controls="false" :min="0" :precision="0" placeholder="请输入省外间隔年限" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="省外人数占比" prop="outProvinceRatio">
|
||||
<el-input-number v-model="formData.outProvinceRatio" :controls="false" :min="0" :max="100" :precision="2" placeholder="请输入省外人数占比" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="省外占比类型" prop="outProvinceRatioType">
|
||||
<el-select v-model="formData.outProvinceRatioType" placeholder="请选择省外占比类型" style="width: 100%" @change="outProvinceRatioTypeChange">
|
||||
<el-option label="当年报名人数" value="当年报名人数"></el-option>
|
||||
<el-option label="可参加教职工人数" value="可参加教职工人数"></el-option>
|
||||
<el-option label="固定人数" value="固定人数"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-if="formData.outProvinceRatioType === '固定人数'" :span="12">
|
||||
<el-form-item label="固定人数" prop="outProvinceFixedPeople">
|
||||
<el-input-number v-model="formData.outProvinceFixedPeople" :controls="false" :min="0" :precision="0" placeholder="请输入固定人数" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div class="tour-setting-basic-divider"></div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="周期开始年度" prop="cycleStartYear">
|
||||
<el-date-picker
|
||||
v-model="formData.cycleStartYear"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
clearable
|
||||
placeholder="请选择周期开始年度"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="周期结束年度" prop="cycleEndYear">
|
||||
<el-date-picker
|
||||
v-model="formData.cycleEndYear"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
clearable
|
||||
placeholder="请选择周期结束年度"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="周期内总费用" prop="cycleTotalCost">
|
||||
<el-input-number v-model="formData.cycleTotalCost" :controls="false" :min="0" :precision="0" placeholder="请输入周期内总费用" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="周期允许次数" prop="cycleAllowedTimes">
|
||||
<el-input-number v-model="formData.cycleAllowedTimes" :controls="false" :min="0" :precision="0" placeholder="请输入周期允许次数" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div class="tour-setting-basic-divider"></div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="允许携带家属" prop="allowFamily">
|
||||
<el-switch v-model="formData.allowFamily" active-text="允许" inactive-text="不允许"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="填报床位信息" prop="fillBedInfo">
|
||||
<el-switch v-model="formData.fillBedInfo" active-text="是" inactive-text="否"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否启用" prop="enabled">
|
||||
<el-switch v-model="formData.enabled" active-text="启用" inactive-text="停用"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="标段管理" name="lots">
|
||||
<div style="text-align: right; margin-bottom: 10px;">
|
||||
<el-button type="primary" size="medium" icon="el-icon-plus" @click="addLot">增加一条</el-button>
|
||||
</div>
|
||||
<el-table
|
||||
:data="formData.lots"
|
||||
border
|
||||
class="vi-table"
|
||||
empty-text="暂无标段"
|
||||
style="width: 100%">
|
||||
<el-table-column label="序号" type="index" width="70" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="标段名称" min-width="180" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-input v-model="row.lotName" maxlength="50" placeholder="请输入标段名称"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="标段值" min-width="160" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-input v-model="row.lotValue" maxlength="10" placeholder="请输入整数" @input="integerInput(row, 'lotValue', $event)"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="标段费用" min-width="150" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-input v-model="row.activityCost" maxlength="10" placeholder="请输入整数" @input="integerInput(row, 'activityCost', $event)"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="允许超出报销" width="140" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-checkbox v-model="row.allowOverReimbursement"></el-checkbox>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="90" align="center" header-align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="danger" size="mini" icon="el-icon-delete" @click="deleteLot(scope.$index, scope.row)"></el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="服务须知" name="notice">
|
||||
<el-form-item prop="serviceNotice" label-width="0">
|
||||
<text-editor v-model="formData.serviceNotice"></text-editor>
|
||||
</el-form-item>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" :loading="submitLoading" @click="doSubmit">确认</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
<drawer-user-scope
|
||||
ref="drawerUserScope"
|
||||
:group_id.sync="formData.activityGroupId"
|
||||
@group_change="handleActivityGroupChange">
|
||||
</drawer-user-scope>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
// 前端先做成团人数区间校验,后端也会再兜底校验一次。
|
||||
const checkGroupPeople = (rule, value, callback) => {
|
||||
if (this.formData.minGroupPeople !== null && this.formData.maxGroupPeople !== null
|
||||
&& this.formData.minGroupPeople > this.formData.maxGroupPeople) {
|
||||
callback(new Error("最少成团人数不能大于最多成团人数"))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const checkCycleYear = (rule, value, callback) => {
|
||||
const startYear = parseInt(this.formData.cycleStartYear, 10)
|
||||
const endYear = parseInt(this.formData.cycleEndYear, 10)
|
||||
if (!isNaN(startYear) && !isNaN(endYear) && startYear > endYear) {
|
||||
callback(new Error("周期开始年度不能大于周期结束年度"))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const checkOutProvinceFixedPeople = (rule, value, callback) => {
|
||||
if (this.formData.outProvinceRatioType === "固定人数"
|
||||
&& (value === null || value === undefined || value === "" || value < 0)) {
|
||||
callback(new Error("固定人数必须大于等于0"))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
title: "",
|
||||
dialogVisible: false,
|
||||
activeTab: "basic",
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
year: "",
|
||||
configName: ""
|
||||
},
|
||||
activityGroupList: [],
|
||||
formData: {},
|
||||
lotDeleteList: [],
|
||||
inheritLoading: false,
|
||||
formRules: {
|
||||
year: [{required: true, message: "必填", trigger: ["blur", "change"]}],
|
||||
configName: [{required: true, message: "必填", trigger: ["blur", "change"]}],
|
||||
minGroupPeople: [{validator: checkGroupPeople, trigger: ["blur", "change"]}],
|
||||
maxGroupPeople: [{validator: checkGroupPeople, trigger: ["blur", "change"]}],
|
||||
outProvinceRatioType: [{required: true, message: "必填", trigger: ["blur", "change"]}],
|
||||
outProvinceFixedPeople: [{validator: checkOutProvinceFixedPeople, trigger: ["blur", "change"]}],
|
||||
cycleStartYear: [{validator: checkCycleYear, trigger: ["blur", "change"]}],
|
||||
cycleEndYear: [{validator: checkCycleYear, trigger: ["blur", "change"]}]
|
||||
}
|
||||
}
|
||||
},
|
||||
components: {
|
||||
"drawer-user-scope": httpVueLoader("/components/module/activity/DrawerUserScope.vue")
|
||||
},
|
||||
methods: {
|
||||
async getActivityGroup() {
|
||||
const {data} = await this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup")
|
||||
this.activityGroupList = (data || []).map((item) => {
|
||||
return Object.assign({}, item, {
|
||||
groupId: item.groupId === null || item.groupId === undefined ? "" : String(item.groupId)
|
||||
})
|
||||
})
|
||||
},
|
||||
openUserScope() {
|
||||
if (this.$refs.drawerUserScope) {
|
||||
this.$refs.drawerUserScope.groupId = this.formData.activityGroupId || ""
|
||||
this.$refs.drawerUserScope.userScopeDialog = true
|
||||
}
|
||||
},
|
||||
async handleActivityGroupChange() {
|
||||
if (this.formData.activityGroupId !== null && this.formData.activityGroupId !== undefined) {
|
||||
this.formData.activityGroupId = String(this.formData.activityGroupId)
|
||||
}
|
||||
await this.getActivityGroup()
|
||||
},
|
||||
resetSearch() {
|
||||
this.pageForm.year = ""
|
||||
this.pageForm.configName = ""
|
||||
this.doSearch()
|
||||
},
|
||||
defaultFormData() {
|
||||
return {
|
||||
year: moment().format("YYYY"),
|
||||
configName: "",
|
||||
tourType: "",
|
||||
activityGroupId: "",
|
||||
sortNo: 0,
|
||||
minGroupPeople: 0,
|
||||
maxGroupPeople: 0,
|
||||
outProvinceYears: 0,
|
||||
outProvinceRatio: 0,
|
||||
outProvinceRatioType: "当年报名人数",
|
||||
outProvinceFixedPeople: 0,
|
||||
cycleStartYear: "",
|
||||
cycleEndYear: "",
|
||||
cycleTotalCost: null,
|
||||
cycleAllowedTimes: null,
|
||||
allowFamily: false,
|
||||
fillBedInfo: true,
|
||||
enabled: true,
|
||||
lots: [],
|
||||
serviceNotice: ""
|
||||
}
|
||||
},
|
||||
openAdd() {
|
||||
this.title = "新建疗休养配置"
|
||||
this.lotDeleteList = []
|
||||
this.activeTab = "basic"
|
||||
// 新建时给出默认年度和开关值,减少校工会管理员录入成本。
|
||||
this.formData = this.defaultFormData()
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
|
||||
},
|
||||
inheritPreviousYearInfo() {
|
||||
const currentYear = parseInt(this.formData.year, 10)
|
||||
if (isNaN(currentYear)) {
|
||||
this.$message.warning("请先选择年度")
|
||||
return
|
||||
}
|
||||
this.inheritLoading = true
|
||||
this.$axios.post(loc() + "/previousYearInfo", {year: currentYear}).then((res) => {
|
||||
this.inheritLoading = false
|
||||
if (res.code !== 0) {
|
||||
this.$message.warning(res.msg || "未找到上一年度配置")
|
||||
return
|
||||
}
|
||||
const previous = res.data || {}
|
||||
const inheritedLots = (previous.lots || []).map(item => {
|
||||
return {
|
||||
lotName: item.lotName || "",
|
||||
lotValue: item.lotValue || "",
|
||||
activityCost: item.activityCost,
|
||||
allowOverReimbursement: !!item.allowOverReimbursement
|
||||
}
|
||||
})
|
||||
this.formData = Object.assign(this.defaultFormData(), previous, {
|
||||
year: String(currentYear),
|
||||
cycleStartYear: previous.cycleStartYear ? String(previous.cycleStartYear) : "",
|
||||
cycleEndYear: previous.cycleEndYear ? String(previous.cycleEndYear) : "",
|
||||
lots: inheritedLots
|
||||
})
|
||||
delete this.formData.id
|
||||
delete this.formData.createdAt
|
||||
delete this.formData.createdBy
|
||||
delete this.formData.updatedAt
|
||||
delete this.formData.updatedBy
|
||||
delete this.formData.delFlag
|
||||
if (!this.formData.outProvinceRatioType) {
|
||||
this.$set(this.formData, "outProvinceRatioType", "当年报名人数")
|
||||
}
|
||||
if (this.formData.outProvinceFixedPeople === null || this.formData.outProvinceFixedPeople === undefined) {
|
||||
this.$set(this.formData, "outProvinceFixedPeople", 0)
|
||||
}
|
||||
if (this.formData.fillBedInfo === null || this.formData.fillBedInfo === undefined) {
|
||||
this.$set(this.formData, "fillBedInfo", true)
|
||||
}
|
||||
this.lotDeleteList = []
|
||||
this.$message.success("已延用上一年配置信息")
|
||||
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
|
||||
}).catch(() => {
|
||||
this.inheritLoading = false
|
||||
})
|
||||
},
|
||||
openEdit(row) {
|
||||
this.title = "编辑疗休养配置"
|
||||
this.lotDeleteList = []
|
||||
this.activeTab = "basic"
|
||||
this.$axios.post(loc() + "/detail", {id: row.id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.formData = Object.assign({
|
||||
activityGroupId: "",
|
||||
outProvinceRatioType: "当年报名人数",
|
||||
outProvinceFixedPeople: 0,
|
||||
cycleStartYear: "",
|
||||
cycleEndYear: "",
|
||||
cycleTotalCost: null,
|
||||
cycleAllowedTimes: null,
|
||||
allowFamily: false,
|
||||
fillBedInfo: true,
|
||||
enabled: true,
|
||||
lots: [],
|
||||
serviceNotice: ""
|
||||
}, res.data || {})
|
||||
this.formData.year = this.formData.year ? String(this.formData.year) : ""
|
||||
if (!this.formData.outProvinceRatioType) {
|
||||
this.$set(this.formData, "outProvinceRatioType", "当年报名人数")
|
||||
}
|
||||
if (this.formData.outProvinceFixedPeople === null || this.formData.outProvinceFixedPeople === undefined) {
|
||||
this.$set(this.formData, "outProvinceFixedPeople", 0)
|
||||
}
|
||||
if (this.formData.fillBedInfo === null || this.formData.fillBedInfo === undefined) {
|
||||
this.$set(this.formData, "fillBedInfo", true)
|
||||
}
|
||||
this.formData.cycleStartYear = this.formData.cycleStartYear ? String(this.formData.cycleStartYear) : ""
|
||||
this.formData.cycleEndYear = this.formData.cycleEndYear ? String(this.formData.cycleEndYear) : ""
|
||||
this.formData.lots = (this.formData.lots || []).map(item => Object.assign({}, item, {
|
||||
allowOverReimbursement: !!item.allowOverReimbursement
|
||||
}))
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
|
||||
} else {
|
||||
this.$message.warning(res.msg || "查询失败")
|
||||
}
|
||||
})
|
||||
},
|
||||
doSubmit() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (!valid) return
|
||||
if (!this.validateLots()) return
|
||||
this.submitLoading = true
|
||||
// Nutz 对子表集合按字符串化 JSON 绑定更稳定,和体检项目维护的提交方式保持一致。
|
||||
const submitData = JSON.parse(JSON.stringify(this.formData))
|
||||
if (submitData.outProvinceRatioType !== "固定人数") {
|
||||
submitData.outProvinceFixedPeople = 0
|
||||
}
|
||||
submitData.lots = JSON.stringify(this.formData.lots || [])
|
||||
submitData.lotDeleteList = JSON.stringify(this.lotDeleteList)
|
||||
this.$axios.post(loc() + "/doSubmit", submitData).then((res) => {
|
||||
this.submitLoading = false
|
||||
if (res.code === 0) {
|
||||
this.$message.success("保存成功")
|
||||
this.dialogVisible = false
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(res.msg || "保存失败")
|
||||
}
|
||||
}).catch(() => {
|
||||
this.submitLoading = false
|
||||
})
|
||||
})
|
||||
},
|
||||
addLot() {
|
||||
if (!this.formData.lots) {
|
||||
this.$set(this.formData, "lots", [])
|
||||
}
|
||||
this.formData.lots.push({
|
||||
lotName: "",
|
||||
lotValue: "",
|
||||
activityCost: null,
|
||||
allowOverReimbursement: false
|
||||
})
|
||||
},
|
||||
integerInput(row, field, value) {
|
||||
const nextValue = String(value || "").replace(/[^\d]/g, "")
|
||||
this.$set(row, field, nextValue)
|
||||
},
|
||||
outProvinceRatioTypeChange(value) {
|
||||
if (value !== "固定人数") {
|
||||
this.$set(this.formData, "outProvinceFixedPeople", 0)
|
||||
}
|
||||
this.$nextTick(() => this.$refs.form && this.$refs.form.validateField("outProvinceFixedPeople"))
|
||||
},
|
||||
validateLots() {
|
||||
const lots = this.formData.lots || []
|
||||
for (let i = 0; i < lots.length; i++) {
|
||||
const row = lots[i]
|
||||
const hasContent = row && (row.lotName || row.lotValue || row.activityCost !== null && row.activityCost !== undefined && row.activityCost !== "")
|
||||
if (!hasContent) continue
|
||||
if (!/^\d+$/.test(String(row.lotValue || ""))) {
|
||||
this.$message.warning("第" + (i + 1) + "行标段值必须为整数")
|
||||
return false
|
||||
}
|
||||
if (!/^\d+$/.test(String(row.activityCost === null || row.activityCost === undefined ? "" : row.activityCost))) {
|
||||
this.$message.warning("第" + (i + 1) + "行标段费用必须为整数")
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
deleteLot(index, row) {
|
||||
this.$confirm("确定删除该标段吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
if (row && row.id) {
|
||||
this.lotDeleteList.push(row.id)
|
||||
}
|
||||
this.formData.lots.splice(index, 1)
|
||||
})
|
||||
},
|
||||
doDelete(row) {
|
||||
this.$confirm("确定删除【" + row.configName + "】吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post(loc() + "/doDelete", {id: row.id}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("删除成功")
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(res.msg || "删除失败")
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
destroyEditor() {
|
||||
this.formData = {}
|
||||
this.lotDeleteList = []
|
||||
this.inheritLoading = false
|
||||
this.activeTab = "basic"
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.getActivityGroup()
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.tour-user-scope {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tour-user-scope .el-select {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tour-setting-inherit {
|
||||
margin-bottom: -40px;
|
||||
padding-right: 96px;
|
||||
position: relative;
|
||||
text-align: right;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tour-setting-basic-divider {
|
||||
border-top: 1px dashed #dcdfe6;
|
||||
margin: 2px 0 18px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,338 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="年度">
|
||||
<el-date-picker
|
||||
v-model="pageForm.year"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择年度"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="旅行社名称">
|
||||
<el-input
|
||||
v-model="pageForm.agencyName"
|
||||
clearable
|
||||
placeholder="请输入旅行社名称"
|
||||
style="width: 100%"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="联系人">
|
||||
<el-input
|
||||
v-model="pageForm.contactName"
|
||||
clearable
|
||||
placeholder="请输入联系人"
|
||||
style="width: 100%"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="联系电话">
|
||||
<el-input
|
||||
v-model="pageForm.contactPhone"
|
||||
clearable
|
||||
placeholder="请输入联系电话"
|
||||
style="width: 100%"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<div class="search-query" style="margin-left: auto;">
|
||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="旅行社列表">
|
||||
<el-button @click="openAdd" size="medium" type="primary">
|
||||
<i class="el-icon-plus"></i>
|
||||
新增
|
||||
</el-button>
|
||||
</table-tool>
|
||||
|
||||
<el-table
|
||||
v-loading="tableLoading"
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
border
|
||||
@sort-change="pageOrder">
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="年度" prop="year" width="110" sortable="custom" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="旅行社编号" prop="agencyCode" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="旅行社名称" prop="agencyName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="联系人" prop="contactName" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="联系电话" prop="contactPhone" width="150" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="激活状态" prop="enabled" width="160" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-switch
|
||||
v-model="row.enabled"
|
||||
active-text="启用"
|
||||
inactive-text="禁用"
|
||||
@change="toggleEnabled(row)">
|
||||
</el-switch>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="260" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
|
||||
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
|
||||
<el-button size="mini" type="danger" @click="doDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog
|
||||
:title="title"
|
||||
:visible.sync="dialogVisible"
|
||||
:close-on-click-modal="false"
|
||||
width="72%"
|
||||
@closed="destroyForm">
|
||||
<el-form :model="formData" :rules="formRules" label-width="110px" ref="form" :disabled="viewMode">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="年度" prop="year">
|
||||
<el-date-picker
|
||||
v-model="formData.year"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择年度"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="旅行社名称" prop="agencyName">
|
||||
<el-input v-model="formData.agencyName" maxlength="30" show-word-limit placeholder="请输入旅行社名称"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="旅行社编号" prop="agencyCode">
|
||||
<el-input v-model="formData.agencyCode" maxlength="50" placeholder="请输入旅行社编号"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="激活状态" prop="enabled">
|
||||
<el-radio-group v-model="formData.enabled">
|
||||
<el-radio :label="true">启用</el-radio>
|
||||
<el-radio :label="false">禁用</el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系人" prop="contactName">
|
||||
<el-input v-model="formData.contactName" maxlength="10" show-word-limit placeholder="请输入联系人"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="联系人手机" prop="contactPhone">
|
||||
<el-input v-model="formData.contactPhone" maxlength="30" placeholder="请输入联系人手机"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="邮箱" prop="email">
|
||||
<el-input v-model="formData.email" maxlength="100" placeholder="请输入邮箱"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input v-model="formData.remark" type="textarea" maxlength="100" :rows="3" show-word-limit placeholder="请输入备注"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="移动端缩略图" prop="mobileThumb">
|
||||
<!-- 复用课程管理的封面上传逻辑:单图上传,保存URL,供移动端列表展示使用。 -->
|
||||
<file-upload
|
||||
style="--upload-width: 200px;--upload-height:108px"
|
||||
:upload_number="1"
|
||||
:upload_size="20971520"
|
||||
:value.sync="formData.mobileThumb"
|
||||
accept=".jpg,.jpeg,.png"
|
||||
complete_result
|
||||
upload_mode="image"
|
||||
upload_result_category="interval">
|
||||
</file-upload>
|
||||
<div class="el-upload__tip">支持jpg、jpeg、png格式,大小不超过20MB。</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button v-if="!viewMode" type="primary" :loading="subDis" @click="doSubmit">保存</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
const currentYear = moment().format("YYYY")
|
||||
const validateMobile = (rule, value, callback) => {
|
||||
const mobileReg = /^1[3-9]\d{9}$/
|
||||
if (!value) {
|
||||
callback(new Error("必填"))
|
||||
} else if (!mobileReg.test(value)) {
|
||||
callback(new Error("手机号格式不正确"))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
const validateEmail = (rule, value, callback) => {
|
||||
const emailReg = /^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$/
|
||||
if (!value) {
|
||||
callback(new Error("必填"))
|
||||
} else if (!emailReg.test(value)) {
|
||||
callback(new Error("邮箱格式不正确"))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
return {
|
||||
title: "",
|
||||
dialogVisible: false,
|
||||
viewMode: false,
|
||||
subDis: false,
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
year: currentYear,
|
||||
agencyName: "",
|
||||
contactName: "",
|
||||
contactPhone: ""
|
||||
},
|
||||
formData: {},
|
||||
formRules: {
|
||||
year: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
agencyName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
agencyCode: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
contactPhone: [{ validator: validateMobile, trigger: ["blur", "change"] }],
|
||||
email: [{ validator: validateEmail, trigger: ["blur", "change"] }]
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetSearch() {
|
||||
this.pageForm.year = moment().format("YYYY")
|
||||
this.pageForm.agencyName = ""
|
||||
this.pageForm.contactName = ""
|
||||
this.pageForm.contactPhone = ""
|
||||
this.doSearch()
|
||||
},
|
||||
emptyForm() {
|
||||
return {
|
||||
year: moment().format("YYYY"),
|
||||
agencyName: "",
|
||||
agencyCode: "",
|
||||
contactName: "",
|
||||
contactPhone: "",
|
||||
email: "",
|
||||
remark: "",
|
||||
mobileThumb: "",
|
||||
enabled: true
|
||||
}
|
||||
},
|
||||
openAdd() {
|
||||
this.title = "新增旅行社信息"
|
||||
this.viewMode = false
|
||||
this.formData = this.emptyForm()
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
|
||||
},
|
||||
openEdit(row) {
|
||||
this.title = "编辑旅行社信息"
|
||||
this.viewMode = false
|
||||
this.loadDetail(row.id)
|
||||
},
|
||||
openView(row) {
|
||||
this.title = "查看旅行社信息"
|
||||
this.viewMode = true
|
||||
this.loadDetail(row.id)
|
||||
},
|
||||
loadDetail(id) {
|
||||
this.$axios.post(loc() + "/detail", { id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.formData = Object.assign(this.emptyForm(), res.data || {})
|
||||
this.formData.year = this.formData.year ? String(this.formData.year) : ""
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
|
||||
} else {
|
||||
this.$message.warning(res.msg || "查询失败")
|
||||
}
|
||||
})
|
||||
},
|
||||
doSubmit() {
|
||||
this.$refs.form.validate((valid) => {
|
||||
if (!valid) return
|
||||
this.subDis = true
|
||||
this.$axios.post(loc() + "/doSubmit", this.formData).then((res) => {
|
||||
this.subDis = false
|
||||
if (res.code === 0) {
|
||||
this.$message.success("保存成功")
|
||||
this.dialogVisible = false
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(res.msg || "保存失败")
|
||||
}
|
||||
}).catch(() => {
|
||||
this.subDis = false
|
||||
})
|
||||
})
|
||||
},
|
||||
toggleEnabled(row) {
|
||||
this.$axios.post(loc() + "/doSubmit", row).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("状态已更新")
|
||||
} else {
|
||||
row.enabled = !row.enabled
|
||||
this.$message.warning(res.msg || "状态更新失败")
|
||||
}
|
||||
}).catch(() => {
|
||||
row.enabled = !row.enabled
|
||||
})
|
||||
},
|
||||
doDelete(row) {
|
||||
this.$confirm("确定删除【" + row.agencyName + "】吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post(loc() + "/doDelete", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("删除成功")
|
||||
this.pageData()
|
||||
} else {
|
||||
this.$message.warning(res.msg || "删除失败")
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
destroyForm() {
|
||||
this.formData = {}
|
||||
this.viewMode = false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.pageData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,547 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="开始年度">
|
||||
<el-date-picker
|
||||
v-model="pageForm.startYear"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择开始年度"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="结束年度">
|
||||
<el-date-picker
|
||||
v-model="pageForm.endYear"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择结束年度"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="姓名/工号">
|
||||
<el-input
|
||||
v-model="pageForm.keyword"
|
||||
clearable
|
||||
placeholder="请输入姓名或工号"
|
||||
style="width: 100%"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="所属工会">
|
||||
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
|
||||
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="线路">
|
||||
<el-select v-model="pageForm.lineId" clearable filterable placeholder="请选择线路" style="width: 100%">
|
||||
<el-option v-for="item in lineOptions" :key="item.lineId" :label="item.lineName" :value="item.lineId"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="出行时段">
|
||||
<el-select v-model="pageForm.travelPeriod" clearable filterable placeholder="请选择出行时段" style="width: 100%">
|
||||
<el-option v-for="item in travelPeriodOptions" :key="item.travelPeriod" :label="item.travelPeriod" :value="item.travelPeriod"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="线路类型">
|
||||
<el-select v-model="pageForm.lineType" clearable filterable placeholder="请选择线路类型" style="width: 100%">
|
||||
<el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<div class="search-query" style="margin-left: auto;">
|
||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<table-tool :app="this" label="分工会审核">
|
||||
<el-radio-group class="mr5" size="small" v-model="pageForm.audit" @change="changeAudit">
|
||||
<el-radio-button :label="true">已审核</el-radio-button>
|
||||
<el-radio-button :label="false">未审核</el-radio-button>
|
||||
</el-radio-group>
|
||||
</table-tool>
|
||||
<el-table
|
||||
v-loading="tableLoading"
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
border
|
||||
@sort-change="pageOrder">
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="所在单位" prop="unitName" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="报名线路" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="出行时段" prop="travelPeriod" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="线路类型" prop="lineType" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="报名时间" prop="signupTime" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="携带家属" prop="familyCount" width="120" sortable="custom" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" :type="Number(row.familyCount || 0) > 0 ? 'success' : 'info'">{{ row.familyCount || 0 }}人</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="报销超出费用" prop="overCostReimbursed" width="140" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" :type="row.overCostReimbursed ? 'success' : 'info'">{{ row.overCostReimbursed ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="当前节点" prop="curTaskName" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="流程状态" prop="instanceState" width="120" sortable="custom" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
|
||||
<el-button v-if="row.taskState === 10" size="mini" type="primary" @click="openApproval(row)">审核</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
|
||||
<template #edit>
|
||||
<tour-approval-info ref="infoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
<div class="process-title">
|
||||
{{ formData.taskName }}
|
||||
</div>
|
||||
<el-form
|
||||
:model="formData"
|
||||
ref="formRef"
|
||||
label-width="0"
|
||||
label-suffix=":"
|
||||
class="flow-task-form">
|
||||
<el-form-item
|
||||
label="审批意见"
|
||||
prop="tf_opinion"
|
||||
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
|
||||
<user-opinion-textarea v-model="formData.tf_opinion"></user-opinion-textarea>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-row type="flex" justify="end">
|
||||
<el-button @click="$refs.guava.index()" size="small">取消</el-button>
|
||||
<el-button @click="handleTaskAction(6)" size="small" type="info">退回到发起人</el-button>
|
||||
<el-button @click="handleTaskAction(2)" size="small" type="danger">拒绝申请</el-button>
|
||||
<el-button @click="handleTaskAction(1)" size="small" type="primary">同意申请</el-button>
|
||||
</el-row>
|
||||
</div>
|
||||
</tour-approval-info>
|
||||
</template>
|
||||
</guava>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#app .search .search-item {
|
||||
width: calc((100% - 150px) / 4);
|
||||
}
|
||||
#app .search .search-query {
|
||||
margin-left: auto;
|
||||
}
|
||||
@media screen and (max-width: 1350px) {
|
||||
#app .search .search-item {
|
||||
width: calc((100% - 100px) / 3);
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 1200px) {
|
||||
#app .search .search-item {
|
||||
width: calc((100% - 50px) / 2);
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 992px) {
|
||||
#app .search .search-item {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
.tour-approval-section {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
.tour-approval-table {
|
||||
width: 100%;
|
||||
}
|
||||
.tour-approval-empty {
|
||||
color: #909399;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
const TOUR_APPROVAL_INFO = {
|
||||
template: /*language=HTML*/ `
|
||||
<div>
|
||||
<div class="process-title">
|
||||
报名信息
|
||||
<el-link type="primary" @click="openChart">点击查看流程图</el-link>
|
||||
</div>
|
||||
<el-descriptions :column="3" border class="tour-approval-section">
|
||||
<el-descriptions-item label="工号">{{ detail.jobNo || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ detail.userName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ detail.gender || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="身份证号">{{ detail.idCard || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在单位">{{ detail.unitName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在工会">{{ detail.unionName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名时间">{{ detail.signupTime || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名线路">{{ detail.lineName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出行时段">{{ detail.travelPeriod || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="线路类型">{{ detail.lineType || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名酒店">{{ detail.hotelName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="旅行社">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否携带家属">
|
||||
{{ familyText() }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="报销超出费用">{{ detail.overCostReimbursed ? '是' : '否' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="fillBedInfo" label="意向拼床人">{{ detail.intendedRoommate || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="fillBedInfo" label="床型">{{ detail.bedType || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="fillBedInfo" label="床位信息">{{ detail.bedInfo || '' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<div v-if="isDirectFamilyLine()" class="tour-approval-section">
|
||||
<div class="process-title">直系亲属线路</div>
|
||||
<el-descriptions :column="3" border>
|
||||
<el-descriptions-item label="亲属姓名">{{ directRelative.relativeName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在单位">{{ directRelative.unitName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="亲属关系">{{ directRelative.relationshipName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="线路名称">{{ directRelative.lineName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出行开始日期">{{ directRelative.travelStartTime || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出行结束日期">{{ directRelative.travelEndTime || '' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<div v-if="showFamilySection()" class="tour-approval-section">
|
||||
<div class="process-title">亲属信息</div>
|
||||
<el-table :data="familyData" border size="mini" empty-text="暂无亲属信息" class="tour-approval-table">
|
||||
<el-table-column label="序号" type="index" width="70" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="家属姓名" prop="familyName" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="性别" prop="gender" width="90" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="身份证号码" prop="idCard" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="年龄" prop="age" width="90" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="关系" prop="relationship" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column v-if="fillBedInfo" label="床型" prop="bedType" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column v-if="fillBedInfo" label="床位" prop="bedInfo" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column v-if="fillBedInfo" label="意向拼床人" prop="intendedRoommate" min-width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<template v-for="task in doneTasks">
|
||||
<div class="mt10">
|
||||
<div class="process-title">{{ task.displayName }}</div>
|
||||
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-if="task.ext.isFirstTaskNode">
|
||||
<el-descriptions-item label="申请用户">
|
||||
{{ task.ext.initiatorName }}({{ task.ext.initiatorAccount }})
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="办理结果">
|
||||
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
|
||||
<el-descriptions border class="flow-task-form" :column="3" :key="task.id" v-else>
|
||||
<el-descriptions-item label="办理用户">
|
||||
{{ task.taskFormData.userName }}({{ task.taskFormData.loginName }})
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="办理结果">
|
||||
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="办理意见" :span="3">
|
||||
{{ task.taskFormData.opinion || task.taskFormData.tf_opinion || '' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<slot></slot>
|
||||
|
||||
<snaker-chart ref="snakerChartRef"></snaker-chart>
|
||||
</div>
|
||||
`,
|
||||
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
|
||||
data() {
|
||||
return {
|
||||
row: {},
|
||||
detail: {},
|
||||
familyData: [],
|
||||
directRelative: {},
|
||||
allowFamily: false,
|
||||
fillBedInfo: true,
|
||||
directFamilyUnitLine: false,
|
||||
doneTasks: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen(row) {
|
||||
this.row = row || {}
|
||||
this.detail = {}
|
||||
this.familyData = []
|
||||
this.directRelative = {}
|
||||
this.allowFamily = false
|
||||
this.fillBedInfo = true
|
||||
this.directFamilyUnitLine = false
|
||||
this.doneTasks = []
|
||||
this.info()
|
||||
this.getDoneTasks()
|
||||
},
|
||||
info() {
|
||||
this.$axios.post(loc() + "/detail", { id: this.row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const data = res.data || {}
|
||||
this.detail = data.ledger || {}
|
||||
this.detail.travelPeriod = data.travelPeriod || this.row.travelPeriod || ""
|
||||
this.familyData = data.families || []
|
||||
this.directRelative = data.directRelative || {}
|
||||
this.allowFamily = this.toBoolean(data.allowFamily)
|
||||
this.fillBedInfo = data.fillBedInfo === undefined || data.fillBedInfo === null || this.toBoolean(data.fillBedInfo)
|
||||
this.directFamilyUnitLine = this.toBoolean(data.directFamilyUnitLine)
|
||||
} else {
|
||||
this.$message.warning(res.msg || "查询失败")
|
||||
}
|
||||
})
|
||||
},
|
||||
toBoolean(value) {
|
||||
return value === true || value === 1 || value === "1"
|
||||
},
|
||||
isDirectFamilyLine() {
|
||||
return this.directFamilyUnitLine
|
||||
|| this.toBoolean(this.detail.directFamilyUnitLine)
|
||||
|| (this.directRelative && this.directRelative.id)
|
||||
},
|
||||
hasFamily() {
|
||||
return this.toBoolean(this.detail.hasFamily) || this.familyData.length > 0
|
||||
},
|
||||
showFamilySection() {
|
||||
return this.allowFamily && this.hasFamily() && !this.isDirectFamilyLine()
|
||||
},
|
||||
familyText() {
|
||||
if (this.isDirectFamilyLine()) {
|
||||
return "否"
|
||||
}
|
||||
if (!this.allowFamily) {
|
||||
return "否"
|
||||
}
|
||||
return this.hasFamily() ? "是" : "否"
|
||||
},
|
||||
getDoneTasks() {
|
||||
this.$axios.post("/flow/common/doneTasks", { bizId: this.row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.doneTasks = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
openChart() {
|
||||
if (!this.row.instanceProcessDefineId || !this.row.instanceId) {
|
||||
this.$message.warning("暂无流程图信息")
|
||||
return
|
||||
}
|
||||
this.$refs.snakerChartRef.onOpenFull(this.row.instanceProcessDefineId, this.row.instanceId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
data() {
|
||||
const currentYear = moment().format("YYYY")
|
||||
return {
|
||||
unionOptions: [],
|
||||
lineOptions: [],
|
||||
travelPeriodOptions: [],
|
||||
lineTypeOptions: [],
|
||||
showApprovalForm: false,
|
||||
formData: {
|
||||
tf_opinion: ""
|
||||
},
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "signupTime",
|
||||
pageOrderBy: "descending",
|
||||
audit: false,
|
||||
startYear: currentYear,
|
||||
endYear: currentYear,
|
||||
keyword: "",
|
||||
unionId: "",
|
||||
lineId: "",
|
||||
travelPeriod: "",
|
||||
lineType: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
resetSearch() {
|
||||
this.pageForm.startYear = moment().format("YYYY")
|
||||
this.pageForm.endYear = moment().format("YYYY")
|
||||
this.pageForm.keyword = ""
|
||||
this.pageForm.unionId = ""
|
||||
this.pageForm.lineId = ""
|
||||
this.pageForm.travelPeriod = ""
|
||||
this.pageForm.lineType = ""
|
||||
this.loadOptions()
|
||||
this.doSearch()
|
||||
},
|
||||
changeAudit() {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.clearCascadeFilters()
|
||||
this.loadOptions()
|
||||
this.pageData()
|
||||
},
|
||||
clearCascadeFilters() {
|
||||
this.pageForm.unionId = ""
|
||||
this.pageForm.lineId = ""
|
||||
this.pageForm.travelPeriod = ""
|
||||
this.pageForm.lineType = ""
|
||||
},
|
||||
openView(row) {
|
||||
this.$refs.guava.edit(() => {
|
||||
this.showApprovalForm = false
|
||||
this.formData = {
|
||||
tf_opinion: ""
|
||||
}
|
||||
this.$refs.infoRef.onOpen(row)
|
||||
})
|
||||
},
|
||||
openApproval(row) {
|
||||
this.$refs.guava.edit(() => {
|
||||
this.showApprovalForm = true
|
||||
this.formData = {
|
||||
processTaskId: row.taskId,
|
||||
taskName: row.curTaskName || row.taskName,
|
||||
tf_opinion: ""
|
||||
}
|
||||
this.$refs.infoRef.onOpen(row)
|
||||
})
|
||||
},
|
||||
handleTaskAction(val) {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
if (!valid) return
|
||||
this.$confirm("您确定要提交吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
const loading = createLoading()
|
||||
this.$axios.post("/flow/common/executeTask", {
|
||||
data: JSON.stringify({
|
||||
...this.formData,
|
||||
submitType: val
|
||||
})
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$refs.guava.index()
|
||||
this.$message.success(res.msg || "操作成功")
|
||||
this.doSearch()
|
||||
} else {
|
||||
this.$message.warning(res.msg || "操作失败")
|
||||
}
|
||||
}).finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
}).catch(() => {})
|
||||
})
|
||||
},
|
||||
loadOptions() {
|
||||
this.loadUnionOptions()
|
||||
this.loadLineOptions()
|
||||
this.loadTravelPeriodOptions()
|
||||
this.loadLineTypeOptions()
|
||||
},
|
||||
baseOptionParams() {
|
||||
return {
|
||||
audit: this.pageForm.audit,
|
||||
startYear: this.pageForm.startYear,
|
||||
endYear: this.pageForm.endYear,
|
||||
keyword: this.pageForm.keyword,
|
||||
unionId: this.pageForm.unionId,
|
||||
lineId: this.pageForm.lineId,
|
||||
travelPeriod: this.pageForm.travelPeriod,
|
||||
lineType: this.pageForm.lineType
|
||||
}
|
||||
},
|
||||
loadUnionOptions() {
|
||||
this.$axios.post(loc() + "/unionOptions", this.baseOptionParams()).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.unionOptions = res.data || []
|
||||
if (this.pageForm.unionId && !this.unionOptions.some(item => item.id === this.pageForm.unionId)) {
|
||||
this.pageForm.unionId = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
loadLineOptions() {
|
||||
this.$axios.post(loc() + "/lineOptions", this.baseOptionParams()).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.lineOptions = res.data || []
|
||||
if (this.pageForm.lineId && !this.lineOptions.some(item => item.lineId === this.pageForm.lineId)) {
|
||||
this.pageForm.lineId = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
loadTravelPeriodOptions() {
|
||||
this.$axios.post(loc() + "/travelPeriodOptions", this.baseOptionParams()).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.travelPeriodOptions = res.data || []
|
||||
if (this.pageForm.travelPeriod && !this.travelPeriodOptions.some(item => item.travelPeriod === this.pageForm.travelPeriod)) {
|
||||
this.pageForm.travelPeriod = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
loadLineTypeOptions() {
|
||||
this.$axios.post(loc() + "/lineTypeOptions", this.baseOptionParams()).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.lineTypeOptions = res.data || []
|
||||
if (this.pageForm.lineType && !this.lineTypeOptions.some(item => item.lineType === this.pageForm.lineType)) {
|
||||
this.pageForm.lineType = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadOptions()
|
||||
this.pageData()
|
||||
},
|
||||
components: {
|
||||
"tour-approval-info": TOUR_APPROVAL_INFO
|
||||
},
|
||||
watch: {
|
||||
"pageForm.startYear"() {
|
||||
this.clearCascadeFilters()
|
||||
this.loadOptions()
|
||||
},
|
||||
"pageForm.endYear"() {
|
||||
this.clearCascadeFilters()
|
||||
this.loadOptions()
|
||||
},
|
||||
"pageForm.lineId"() {
|
||||
this.loadTravelPeriodOptions()
|
||||
this.loadLineTypeOptions()
|
||||
},
|
||||
"pageForm.travelPeriod"() {
|
||||
this.loadLineOptions()
|
||||
this.loadLineTypeOptions()
|
||||
},
|
||||
"pageForm.lineType"() {
|
||||
this.loadLineOptions()
|
||||
this.loadTravelPeriodOptions()
|
||||
},
|
||||
"pageForm.unionId"() {
|
||||
this.loadLineOptions()
|
||||
this.loadTravelPeriodOptions()
|
||||
this.loadLineTypeOptions()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,609 @@
|
||||
<!--#
|
||||
layout("/layouts/platform.html"){
|
||||
#-->
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<guava ref="guava">
|
||||
<el-card shadow="never">
|
||||
<search :is-search-button="false">
|
||||
<search-item label="开始年度">
|
||||
<el-date-picker
|
||||
v-model="pageForm.startYear"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择开始年度"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="结束年度">
|
||||
<el-date-picker
|
||||
v-model="pageForm.endYear"
|
||||
type="year"
|
||||
value-format="yyyy"
|
||||
placeholder="请选择结束年度"
|
||||
style="width: 100%">
|
||||
</el-date-picker>
|
||||
</search-item>
|
||||
<search-item label="姓名/工号">
|
||||
<el-input
|
||||
v-model="pageForm.keyword"
|
||||
clearable
|
||||
placeholder="请输入姓名或工号"
|
||||
style="width: 100%"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</search-item>
|
||||
<search-item label="所属工会">
|
||||
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
|
||||
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="线路">
|
||||
<el-select v-model="pageForm.lineId" clearable filterable placeholder="请选择线路" style="width: 100%">
|
||||
<el-option v-for="item in lineOptions" :key="item.lineId" :label="item.lineName" :value="item.lineId"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="出行时段">
|
||||
<el-select v-model="pageForm.travelPeriod" clearable filterable placeholder="请选择出行时段" style="width: 100%">
|
||||
<el-option v-for="item in travelPeriodOptions" :key="item.travelPeriod" :label="item.travelPeriod" :value="item.travelPeriod"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<search-item label="线路类型">
|
||||
<el-select v-model="pageForm.lineType" clearable filterable placeholder="请选择线路类型" style="width: 100%">
|
||||
<el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option>
|
||||
</el-select>
|
||||
</search-item>
|
||||
<div class="search-query" style="margin-left: auto;">
|
||||
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
<el-button size="medium" @click="resetSearch">重置</el-button>
|
||||
</div>
|
||||
</search>
|
||||
</el-card>
|
||||
|
||||
<el-card class="mt10" shadow="never">
|
||||
<div class="tour-ledger-toolbar">
|
||||
<div class="tour-ledger-toolbar-title">
|
||||
<table-tool :app="this" label="分工会台账"></table-tool>
|
||||
</div>
|
||||
<div class="tour-ledger-scope">
|
||||
<el-button
|
||||
class="tour-scope-btn"
|
||||
size="medium"
|
||||
:class="{'is-active': pageForm.scopeType === 'ownUnionJoined'}"
|
||||
@click="setScopeType('ownUnionJoined')">
|
||||
本工会参加疗休养教职工({{ summaryStats.ownUnionJoinedStaffCount || 0 }})
|
||||
</el-button>
|
||||
<el-button
|
||||
class="tour-scope-btn"
|
||||
size="medium"
|
||||
:class="{'is-active': pageForm.scopeType === 'organizedLineJoined'}"
|
||||
@click="setScopeType('organizedLineJoined')">
|
||||
参加本工会组织线路人员(含其他工会及家属)({{ summaryStats.organizedLineJoinedTotalCount || 0 }})
|
||||
</el-button>
|
||||
<el-button
|
||||
class="tour-scope-btn"
|
||||
size="medium"
|
||||
:class="{'is-active': pageForm.overCostOnly}"
|
||||
@click="setOverCostOnly">
|
||||
超出费用由单位承担
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="pageForm.overCostOnly"
|
||||
type="primary"
|
||||
size="medium"
|
||||
icon="el-icon-download"
|
||||
@click="downloadOverCostSummary">
|
||||
导出
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
v-loading="tableLoading"
|
||||
:data="tableData"
|
||||
:size="tableSize"
|
||||
border
|
||||
@sort-change="pageOrder">
|
||||
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="所在单位" prop="unitName" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="报名线路" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="出行时段" prop="travelPeriod" min-width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="线路类型" prop="lineType" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="报名时间" prop="signupTime" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="携带家属" prop="familyCount" width="120" sortable="custom" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" :type="Number(row.familyCount || 0) > 0 ? 'success' : 'info'">{{ row.familyCount || 0 }}人</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否参加" prop="joined" width="120" sortable="custom" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" :type="row.joined ? 'success' : 'info'">{{ row.joined ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="报销超出费用" prop="overCostReimbursed" width="140" align="center" header-align="center">
|
||||
<template slot-scope="{row}">
|
||||
<el-tag size="mini" :type="row.overCostReimbursed ? 'success' : 'info'">{{ row.overCostReimbursed ? '是' : '否' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
|
||||
<template slot-scope="{row}">
|
||||
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
|
||||
<el-button size="mini" type="danger" @click="doDelete(row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
</guava>
|
||||
|
||||
<el-dialog
|
||||
title="台账详情"
|
||||
:visible.sync="detailVisible"
|
||||
:close-on-click-modal="false"
|
||||
width="72%">
|
||||
<div class="tour-ledger-section">
|
||||
<div class="tour-ledger-title">教职工信息</div>
|
||||
<el-descriptions :column="3" border size="medium">
|
||||
<el-descriptions-item label="工号">{{ detail.jobNo || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="姓名">{{ detail.userName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="性别">{{ detail.gender || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="身份证号">{{ detail.idCard || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在单位">{{ detail.unitName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在工会">{{ detail.unionName || '' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<div class="tour-ledger-section mt10">
|
||||
<div class="tour-ledger-title">
|
||||
报名信息
|
||||
<el-link v-if="detailHasWorkflow()" type="primary" @click="openChart">点击查看流程图</el-link>
|
||||
</div>
|
||||
<el-descriptions :column="3" border size="medium">
|
||||
<el-descriptions-item label="报名时间">{{ detail.signupTime || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名线路">{{ detail.lineName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="线路类型">{{ detail.lineType || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名酒店">{{ detail.hotelName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报名旅行社">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出行时间">{{ detail.travelPeriod || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="fillBedInfo" label="床型">{{ detail.bedType || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="fillBedInfo" label="床位信息">{{ detail.bedInfo || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="fillBedInfo" label="意向拼床人">{{ detail.intendedRoommate || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否携带家属">{{ familyText() }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否参加">{{ detail.joined ? '是' : '否' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否报销">{{ detail.reimbursed ? '是' : '否' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="报销超出费用">{{ detail.overCostReimbursed ? '是' : '否' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<div v-if="isDirectFamilyLine()" class="tour-ledger-section mt10">
|
||||
<div class="tour-ledger-title">直系亲属线路</div>
|
||||
<el-descriptions :column="3" border size="medium">
|
||||
<el-descriptions-item label="亲属姓名">{{ directRelative.relativeName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="所在单位">{{ directRelative.unitName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="亲属关系">{{ directRelative.relationshipName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="线路名称">{{ directRelative.lineName || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出行开始日期">{{ directRelative.travelStartTime || '' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="出行结束日期">{{ directRelative.travelEndTime || '' }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<div v-if="showFamilySection()" class="tour-ledger-section mt10">
|
||||
<div class="tour-ledger-title">家属信息</div>
|
||||
<el-table :data="familyData" border :size="tableSize" empty-text="暂无家属信息">
|
||||
<el-table-column label="序号" type="index" width="70" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="家属姓名" prop="familyName" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="性别" prop="gender" width="90" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="身份证号码" prop="idCard" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="年龄" prop="age" width="90" align="center" header-align="center"></el-table-column>
|
||||
<el-table-column label="关系" prop="relationship" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column v-if="fillBedInfo" label="床型" prop="bedType" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column v-if="fillBedInfo" label="床位" prop="bedInfo" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column v-if="fillBedInfo" label="意向拼床人" prop="intendedRoommate" min-width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<template v-if="detailHasWorkflow()" v-for="task in doneTasks">
|
||||
<div class="tour-ledger-section mt10" :key="task.id">
|
||||
<div class="tour-ledger-title">{{ task.displayName }}</div>
|
||||
<el-descriptions border :column="3" v-if="task.ext && task.ext.isFirstTaskNode">
|
||||
<el-descriptions-item label="申请用户">
|
||||
{{ task.ext.initiatorName }}({{ task.ext.initiatorAccount }})
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="办理结果">
|
||||
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<el-descriptions border :column="3" v-else>
|
||||
<el-descriptions-item label="办理用户">
|
||||
{{ (task.taskFormData && task.taskFormData.userName) || '' }}({{ (task.taskFormData && task.taskFormData.loginName) || '' }})
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="办理结果">
|
||||
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext && task.ext.submitType"></dict-tag>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="办理意见" :span="3">
|
||||
{{ (task.taskFormData && (task.taskFormData.opinion || task.taskFormData.tf_opinion)) || '' }}
|
||||
</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<snaker-chart ref="snakerChartRef"></snaker-chart>
|
||||
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="detailVisible = false">确定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.tour-ledger-section {
|
||||
padding: 0 2px;
|
||||
}
|
||||
.tour-ledger-title {
|
||||
border-left: 4px solid #0079c2;
|
||||
color: #0079c2;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
line-height: 16px;
|
||||
margin-bottom: 14px;
|
||||
padding-left: 10px;
|
||||
}
|
||||
.tour-ledger-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
gap: 24px;
|
||||
margin-bottom: 12px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tour-ledger-toolbar-title {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
.tour-ledger-scope {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex: 0 0 auto;
|
||||
flex-wrap: nowrap;
|
||||
gap: 12px;
|
||||
margin: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.tour-ledger-scope .el-button {
|
||||
margin: 0;
|
||||
}
|
||||
.tour-ledger-scope .el-button + .el-button {
|
||||
margin-left: 0;
|
||||
}
|
||||
.tour-ledger-scope .tour-scope-btn {
|
||||
background: #ecf5ff;
|
||||
border-color: #b3d8ff;
|
||||
border-radius: 4px;
|
||||
color: #0079c2;
|
||||
font-weight: 600;
|
||||
height: 34px;
|
||||
line-height: 1;
|
||||
padding: 8px 18px;
|
||||
}
|
||||
.tour-ledger-scope .tour-scope-btn:hover,
|
||||
.tour-ledger-scope .tour-scope-btn:focus {
|
||||
background: #d9ecff;
|
||||
border-color: #66b1ff;
|
||||
color: #006bb0;
|
||||
}
|
||||
.tour-ledger-scope .tour-scope-btn.is-active {
|
||||
background: #0079c2;
|
||||
border-color: #0079c2;
|
||||
box-shadow: 0 2px 6px rgba(0, 121, 194, 0.24);
|
||||
color: #fff;
|
||||
}
|
||||
.tour-ledger-scope .tour-scope-btn.is-active:hover,
|
||||
.tour-ledger-scope .tour-scope-btn.is-active:focus {
|
||||
background: #006bb0;
|
||||
border-color: #006bb0;
|
||||
color: #fff;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
new Vue({
|
||||
el: "#app",
|
||||
mixins: [initTableMixins],
|
||||
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
|
||||
data() {
|
||||
const currentYear = moment().format("YYYY")
|
||||
return {
|
||||
detailVisible: false,
|
||||
unionOptions: [],
|
||||
lineOptions: [],
|
||||
travelPeriodOptions: [],
|
||||
lineTypeOptions: [],
|
||||
summaryStats: {},
|
||||
detail: {},
|
||||
familyData: [],
|
||||
directRelative: {},
|
||||
directFamilyUnitLine: false,
|
||||
fillBedInfo: true,
|
||||
detailRow: {},
|
||||
doneTasks: [],
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "signupTime",
|
||||
pageOrderBy: "descending",
|
||||
startYear: currentYear,
|
||||
endYear: currentYear,
|
||||
keyword: "",
|
||||
unionId: "",
|
||||
lineId: "",
|
||||
travelPeriod: "",
|
||||
lineType: "",
|
||||
scopeType: "",
|
||||
overCostOnly: false
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
toBoolean(value) {
|
||||
return value === true || value === 1 || value === "1" || value === "true"
|
||||
},
|
||||
isDirectFamilyLine() {
|
||||
return this.directFamilyUnitLine
|
||||
|| this.toBoolean(this.detail && this.detail.directFamilyUnitLine)
|
||||
|| !!(this.directRelative && this.directRelative.id)
|
||||
},
|
||||
detailHasWorkflow() {
|
||||
return !!(this.detailRow && this.detailRow.instanceId)
|
||||
},
|
||||
hasFamily() {
|
||||
return this.toBoolean(this.detail && this.detail.hasFamily) || this.familyData.length > 0
|
||||
},
|
||||
familyText() {
|
||||
if (this.isDirectFamilyLine()) {
|
||||
return "否"
|
||||
}
|
||||
if (!this.hasFamily()) {
|
||||
return "否"
|
||||
}
|
||||
const count = Number(this.familyData.length || 0)
|
||||
return count > 0 ? count + "人" : "是"
|
||||
},
|
||||
showFamilySection() {
|
||||
return this.hasFamily() && !this.isDirectFamilyLine()
|
||||
},
|
||||
loadDoneTasks() {
|
||||
this.$axios.post("/flow/common/doneTasks", { bizId: this.detailRow.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.doneTasks = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
openChart() {
|
||||
if (!this.detailRow.instanceProcessDefineId || !this.detailRow.instanceId) {
|
||||
this.$message.warning("暂无流程图信息")
|
||||
return
|
||||
}
|
||||
this.$refs.snakerChartRef.onOpenFull(this.detailRow.instanceProcessDefineId, this.detailRow.instanceId)
|
||||
},
|
||||
resetSearch() {
|
||||
this.pageForm.startYear = moment().format("YYYY")
|
||||
this.pageForm.endYear = moment().format("YYYY")
|
||||
this.pageForm.keyword = ""
|
||||
this.pageForm.unionId = ""
|
||||
this.pageForm.lineId = ""
|
||||
this.pageForm.travelPeriod = ""
|
||||
this.pageForm.lineType = ""
|
||||
this.pageForm.scopeType = ""
|
||||
this.pageForm.overCostOnly = false
|
||||
this.loadUnionOptions()
|
||||
this.loadLineOptions()
|
||||
this.loadTravelPeriodOptions()
|
||||
this.loadLineTypeOptions()
|
||||
this.loadSummaryStats()
|
||||
this.doSearch()
|
||||
},
|
||||
openView(row) {
|
||||
this.detailRow = row || {}
|
||||
this.detail = {}
|
||||
this.familyData = []
|
||||
this.directRelative = {}
|
||||
this.directFamilyUnitLine = false
|
||||
this.fillBedInfo = true
|
||||
this.doneTasks = []
|
||||
this.$axios.post(loc() + "/detail", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const data = res.data || {}
|
||||
this.detail = data.ledger || {}
|
||||
this.detail.travelPeriod = data.travelPeriod || ""
|
||||
this.familyData = data.families || []
|
||||
this.directRelative = data.directRelative || {}
|
||||
this.directFamilyUnitLine = this.toBoolean(data.directFamilyUnitLine)
|
||||
this.fillBedInfo = data.fillBedInfo === undefined || data.fillBedInfo === null || this.toBoolean(data.fillBedInfo)
|
||||
this.detailVisible = true
|
||||
if (this.detailHasWorkflow()) {
|
||||
this.loadDoneTasks()
|
||||
}
|
||||
} else {
|
||||
this.$message.warning(res.msg || "查询失败")
|
||||
}
|
||||
})
|
||||
},
|
||||
doDelete(row) {
|
||||
this.$confirm("确定删除【" + row.userName + "】的报名台账吗?", "提示", {
|
||||
confirmButtonText: "确定",
|
||||
cancelButtonText: "取消",
|
||||
type: "warning"
|
||||
}).then(() => {
|
||||
this.$axios.post(loc() + "/doDelete", { id: row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$message.success("删除成功")
|
||||
this.pageData()
|
||||
this.loadSummaryStats()
|
||||
this.loadLineOptions()
|
||||
this.loadTravelPeriodOptions()
|
||||
this.loadLineTypeOptions()
|
||||
} else {
|
||||
this.$message.warning(res.msg || "删除失败")
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
loadUnionOptions() {
|
||||
this.$axios.post(loc() + "/unionOptions", {
|
||||
startYear: this.pageForm.startYear,
|
||||
endYear: this.pageForm.endYear
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.unionOptions = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
loadLineOptions() {
|
||||
this.$axios.post(loc() + "/lineOptions", {
|
||||
startYear: this.pageForm.startYear,
|
||||
endYear: this.pageForm.endYear,
|
||||
travelPeriod: this.pageForm.travelPeriod,
|
||||
lineType: this.pageForm.lineType,
|
||||
unionId: this.pageForm.unionId,
|
||||
keyword: this.pageForm.keyword,
|
||||
scopeType: this.pageForm.scopeType
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.lineOptions = res.data || []
|
||||
if (this.pageForm.lineId && !this.lineOptions.some(item => item.lineId === this.pageForm.lineId)) {
|
||||
this.pageForm.lineId = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
loadTravelPeriodOptions() {
|
||||
this.$axios.post(loc() + "/travelPeriodOptions", {
|
||||
startYear: this.pageForm.startYear,
|
||||
endYear: this.pageForm.endYear,
|
||||
lineId: this.pageForm.lineId,
|
||||
lineType: this.pageForm.lineType,
|
||||
unionId: this.pageForm.unionId,
|
||||
keyword: this.pageForm.keyword,
|
||||
scopeType: this.pageForm.scopeType
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.travelPeriodOptions = res.data || []
|
||||
if (this.pageForm.travelPeriod && !this.travelPeriodOptions.some(item => item.travelPeriod === this.pageForm.travelPeriod)) {
|
||||
this.pageForm.travelPeriod = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
loadLineTypeOptions() {
|
||||
this.$axios.post(loc() + "/lineTypeOptions", {
|
||||
startYear: this.pageForm.startYear,
|
||||
endYear: this.pageForm.endYear,
|
||||
lineId: this.pageForm.lineId,
|
||||
travelPeriod: this.pageForm.travelPeriod,
|
||||
unionId: this.pageForm.unionId,
|
||||
keyword: this.pageForm.keyword,
|
||||
scopeType: this.pageForm.scopeType
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.lineTypeOptions = res.data || []
|
||||
if (this.pageForm.lineType && !this.lineTypeOptions.some(item => item.lineType === this.pageForm.lineType)) {
|
||||
this.pageForm.lineType = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
loadSummaryStats() {
|
||||
this.$axios.post(loc() + "/summaryStats", {
|
||||
startYear: this.pageForm.startYear,
|
||||
endYear: this.pageForm.endYear,
|
||||
unionId: this.pageForm.unionId,
|
||||
lineId: this.pageForm.lineId,
|
||||
travelPeriod: this.pageForm.travelPeriod,
|
||||
lineType: this.pageForm.lineType
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.summaryStats = res.data || {}
|
||||
}
|
||||
})
|
||||
},
|
||||
setScopeType(scopeType) {
|
||||
this.pageForm.scopeType = this.pageForm.scopeType === scopeType ? "" : scopeType
|
||||
this.pageForm.pageNumber = 1
|
||||
this.loadLineOptions()
|
||||
this.loadTravelPeriodOptions()
|
||||
this.loadLineTypeOptions()
|
||||
this.pageData()
|
||||
},
|
||||
setOverCostOnly() {
|
||||
this.pageForm.overCostOnly = !this.pageForm.overCostOnly
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageData()
|
||||
},
|
||||
downloadOverCostSummary() {
|
||||
this.$downLoad(loc() + "/exportOverCostSummary", this.pageForm)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.loadUnionOptions()
|
||||
this.loadLineOptions()
|
||||
this.loadTravelPeriodOptions()
|
||||
this.loadLineTypeOptions()
|
||||
this.loadSummaryStats()
|
||||
this.pageData()
|
||||
},
|
||||
watch: {
|
||||
"pageForm.startYear"() {
|
||||
this.pageForm.lineId = ""
|
||||
this.pageForm.travelPeriod = ""
|
||||
this.pageForm.lineType = ""
|
||||
this.pageForm.unionId = ""
|
||||
this.pageForm.scopeType = ""
|
||||
this.loadUnionOptions()
|
||||
this.loadLineOptions()
|
||||
this.loadTravelPeriodOptions()
|
||||
this.loadLineTypeOptions()
|
||||
this.loadSummaryStats()
|
||||
},
|
||||
"pageForm.endYear"() {
|
||||
this.pageForm.lineId = ""
|
||||
this.pageForm.travelPeriod = ""
|
||||
this.pageForm.lineType = ""
|
||||
this.pageForm.unionId = ""
|
||||
this.pageForm.scopeType = ""
|
||||
this.loadUnionOptions()
|
||||
this.loadLineOptions()
|
||||
this.loadTravelPeriodOptions()
|
||||
this.loadLineTypeOptions()
|
||||
this.loadSummaryStats()
|
||||
},
|
||||
"pageForm.lineId"() {
|
||||
this.loadTravelPeriodOptions()
|
||||
this.loadLineTypeOptions()
|
||||
this.loadSummaryStats()
|
||||
},
|
||||
"pageForm.travelPeriod"() {
|
||||
this.loadLineOptions()
|
||||
this.loadLineTypeOptions()
|
||||
this.loadSummaryStats()
|
||||
},
|
||||
"pageForm.lineType"() {
|
||||
this.loadLineOptions()
|
||||
this.loadTravelPeriodOptions()
|
||||
this.loadSummaryStats()
|
||||
},
|
||||
"pageForm.unionId"() {
|
||||
this.loadLineOptions()
|
||||
this.loadTravelPeriodOptions()
|
||||
this.loadLineTypeOptions()
|
||||
this.loadSummaryStats()
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -50,7 +50,7 @@ const basicForm = {
|
||||
onSave() {
|
||||
this.$refs.formRef.validate(valid => {
|
||||
if (valid) {
|
||||
this.$axios.post('/platform/edu/courses/' + (this.formData.id ? 'update' : 'insert'), this.formData).then(res => {
|
||||
this.$axios.post('/' + (this.formData.id ? 'update' : 'insert'), this.formData).then(res => {
|
||||
if (res.code === 0) {
|
||||
this.visible = false
|
||||
this.$message.success(res.msg)
|
||||
|
||||
+95
-69
@@ -2,47 +2,57 @@ const BASIC_TABLE_COMPONENT = {
|
||||
template: `
|
||||
<div>
|
||||
<el-row type="flex">
|
||||
<el-col></el-col>
|
||||
</el-row>
|
||||
<el-col></el-col>
|
||||
</el-row>
|
||||
<table-tool>
|
||||
<el-button type="primary" size="small" icon="el-icon-plus" @click="openAdd()">新建机构</el-button>
|
||||
<el-button type="primary" size="small" icon="el-icon-plus" @click="openAdd()">新建机构</el-button>
|
||||
</table-tool>
|
||||
<el-table key="1" :data="tableData" ref="tableRef">
|
||||
<el-table-column label="序号" width="100px" type="index" :index="indexMethod"></el-table-column>
|
||||
<el-table-column prop="name" label="机构名称"></el-table-column>
|
||||
<el-table-column prop="introduce" label="描述"></el-table-column>
|
||||
<el-table-column label="操作" width="100px">
|
||||
<el-table-column prop="code" label="机构代码"></el-table-column>
|
||||
<el-table-column prop="introduce" label="描述" min-width="220px">
|
||||
<template slot-scope="{row}">
|
||||
<el-link size="mini" type="danger" @click="del(row.id)">删除</el-link>
|
||||
<div class="institution-description">{{row.introduce}}</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="140px">
|
||||
<template slot-scope="scope">
|
||||
<template>
|
||||
<el-link size="mini" type="primary" @click="openEdit(scope.row)">修改</el-link>
|
||||
</template>
|
||||
<el-divider direction="vertical"></el-divider>
|
||||
<el-tooltip v-if="isBasicInstitution(scope.$index)"
|
||||
content="你选择的组织机构是两代会基本机构,不允许删除。"
|
||||
placement="top">
|
||||
<span class="disabled-delete-link">删除</span>
|
||||
</el-tooltip>
|
||||
<el-link v-else size="mini" type="danger" @click="del(scope.row.id)">删除</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-dialog :visible.sync="dialogFormVisible" :title="formData.id?'编辑':'新增'" width="40%">
|
||||
<el-form :model="formData" ref="formRef" :rules="rules" label-width="100px">
|
||||
<el-form-item label="机构名称" prop="ids">
|
||||
<el-cascader
|
||||
v-model="ids"
|
||||
:options="treeList"
|
||||
:props="props"
|
||||
@change="parentChange"
|
||||
placeholder="请选择机构"
|
||||
style="width: 100%"
|
||||
></el-cascader>
|
||||
</el-form-item>
|
||||
<el-form-item label="机构代码" prop="code">
|
||||
<el-input placeholder="请输入机构代码" v-model="formData.code" disabled></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="introduce">
|
||||
<el-input v-model="formData.introduce" placeholder="请输入描述"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="notes">
|
||||
<el-input v-model="formData.notes" placeholder="请输入备注"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-dialog :visible.sync="dialogFormVisible" :title="isEdit ? '编辑' : '新增'" width="40%">
|
||||
<el-form :model="formData" ref="formRef" :rules="rules" label-width="100px">
|
||||
<el-form-item label="上级机构名称">
|
||||
<el-input :value="parentName || '-'" disabled></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="机构名称" prop="name">
|
||||
<el-input placeholder="请输入机构名称" v-model="formData.name"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="机构代码" prop="code">
|
||||
<el-input placeholder="请输入机构代码" v-model="formData.code" :disabled="isEdit"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序编码" prop="location">
|
||||
<el-input-number v-model="formData.location" :min="0" :step="1" style="width: 100%"></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="描述" prop="introduce">
|
||||
<el-input v-model="formData.introduce" placeholder="请输入描述"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogFormVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="doSubmit">确 定</el-button>
|
||||
<el-button @click="dialogFormVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="doSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -56,6 +66,10 @@ const BASIC_TABLE_COMPONENT = {
|
||||
parentId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
parentName: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -63,20 +77,13 @@ const BASIC_TABLE_COMPONENT = {
|
||||
rules: {
|
||||
name: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
code: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
parentId: [{ required: true, message: "必填", trigger: ["change", "blur"] }]
|
||||
parentId: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
location: [{ required: true, message: "必填", trigger: ["change", "blur"] }]
|
||||
},
|
||||
parentData: {},
|
||||
parentIds: [],
|
||||
ids: [],
|
||||
treeList: [],
|
||||
treeFlat: [],
|
||||
props: {
|
||||
checkStrictly: true,
|
||||
multiple: false,
|
||||
label: "name",
|
||||
value: "id"
|
||||
},
|
||||
dialogFormVisible: false
|
||||
dialogFormVisible: false,
|
||||
isEdit: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -95,38 +102,44 @@ const BASIC_TABLE_COMPONENT = {
|
||||
})
|
||||
},
|
||||
openAdd() {
|
||||
this.isEdit = false
|
||||
this.dialogFormVisible = true
|
||||
this.formData = {}
|
||||
this.ids = []
|
||||
$.post("/platform/teacherCongress/institution/formTree", { sessionId: this.sessionId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.treeList = res.data.treeList
|
||||
this.treeFlat = res.data.treeFlat
|
||||
}
|
||||
})
|
||||
this.formData = {
|
||||
parentId: this.parentId,
|
||||
location: 0
|
||||
}
|
||||
},
|
||||
// 上级机构选择变化
|
||||
parentChange(val) {
|
||||
const id = val[val.length - 1]
|
||||
const tree = this.treeFlat.find((tree) => tree.id === id)
|
||||
this.$set(this.formData, "code", tree?.code)
|
||||
this.$set(this.formData, "name", tree?.name)
|
||||
openEdit(row) {
|
||||
this.isEdit = true
|
||||
this.dialogFormVisible = true
|
||||
this.formData = {
|
||||
...row,
|
||||
location: row.location || 0
|
||||
}
|
||||
},
|
||||
isBasicInstitution(index) {
|
||||
const pageNumber = this.pageForm.pageNumber || 1
|
||||
const pageSize = this.pageForm.pageSize || 10
|
||||
return (pageNumber - 1) * pageSize + index < 6
|
||||
},
|
||||
doSubmit() {
|
||||
this.$refs.formRef.validate((valid) => {
|
||||
this.formData.id = this.ids[this.ids.length - 1]
|
||||
this.formData.parentId = this.ids[this.ids.length - 2] || this.parentId
|
||||
this.formData.sessionId = this.sessionId
|
||||
if (valid) {
|
||||
this.$axios.post(loc() + "/insert", this.formData).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.dialogFormVisible = false
|
||||
this.$message.success(res.msg)
|
||||
this.doSearch()
|
||||
this.$emit("refresh", null)
|
||||
}
|
||||
})
|
||||
if (!valid) {
|
||||
return
|
||||
}
|
||||
this.formData.sessionId = this.sessionId
|
||||
if (!this.isEdit) {
|
||||
this.formData.parentId = this.parentId
|
||||
}
|
||||
const url = this.isEdit ? loc() + "/update" : loc() + "/insert"
|
||||
this.$axios.post(url, this.formData).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.dialogFormVisible = false
|
||||
this.$message.success(res.msg)
|
||||
this.doSearch()
|
||||
this.$emit("refresh", null)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
@@ -145,5 +158,18 @@ const BASIC_TABLE_COMPONENT = {
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
style: /*language=CSS*/ `
|
||||
.institution-description {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.disabled-delete-link {
|
||||
color: #c0c4cc;
|
||||
cursor: not-allowed;
|
||||
font-size: 12px;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
+40
-20
@@ -9,21 +9,27 @@ layout("/layouts/platform.html"){
|
||||
</el-col>
|
||||
<el-col :span="19">
|
||||
<el-card shadow="never" style="height: 100%">
|
||||
<template>
|
||||
<basic-table
|
||||
:session-id="sessionId"
|
||||
:parent-id="currentTreeData.id"
|
||||
v-if="showInstitutionTable || (currentTreeData && currentTreeData.id==='0')"
|
||||
ref="basicTableRef"
|
||||
@refresh="$refs.treeRef.listTree()"
|
||||
></basic-table>
|
||||
</template>
|
||||
<user-table
|
||||
:session-id="sessionId"
|
||||
:institution-id="currentTreeData.id"
|
||||
ref="userTableRef"
|
||||
v-if="!showInstitutionTable && sessionId"
|
||||
></user-table>
|
||||
<el-tabs v-model="activeTab" @tab-click="handleTabClick">
|
||||
<el-tab-pane v-if="hasChildInstitution" label="机构列表" name="institution">
|
||||
<basic-table
|
||||
:session-id="sessionId"
|
||||
:parent-id="currentTreeData && currentTreeData.id"
|
||||
:parent-name="currentTreeData && currentTreeData.name"
|
||||
v-if="activeTab === 'institution' && sessionId && currentTreeData"
|
||||
ref="basicTableRef"
|
||||
@refresh="refreshTree"
|
||||
></basic-table>
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="人员列表" name="user">
|
||||
<user-table
|
||||
:session-id="sessionId"
|
||||
:institution-id="currentTreeData && currentTreeData.id"
|
||||
:institution-code="currentTreeData && currentTreeData.code"
|
||||
ref="userTableRef"
|
||||
v-if="activeTab === 'user' && sessionId && currentTreeData"
|
||||
></user-table>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
</el-card>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -43,16 +49,16 @@ layout("/layouts/platform.html"){
|
||||
"basic-table": BASIC_TABLE_COMPONENT
|
||||
},
|
||||
computed: {
|
||||
showInstitutionTable() {
|
||||
const hasChildren = this.currentTreeData?.children?.length > 0
|
||||
return hasChildren
|
||||
hasChildInstitution() {
|
||||
return this.currentTreeData && this.currentTreeData.children && this.currentTreeData.children.length > 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentTreeNode: null,
|
||||
currentTreeData: null,
|
||||
sessionId: null
|
||||
sessionId: null,
|
||||
activeTab: "institution"
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -60,14 +66,28 @@ layout("/layouts/platform.html"){
|
||||
this.currentTreeData = data
|
||||
this.currentTreeNode = node
|
||||
this.sessionId = sessionId
|
||||
if (this.hasChildInstitution) {
|
||||
this.activeTab = "institution"
|
||||
} else if (this.activeTab === "institution") {
|
||||
this.activeTab = "user"
|
||||
}
|
||||
this.refreshActiveTab()
|
||||
},
|
||||
handleTabClick() {
|
||||
this.refreshActiveTab()
|
||||
},
|
||||
refreshActiveTab() {
|
||||
this.$nextTick(() => {
|
||||
if (this.showInstitutionTable) {
|
||||
if (this.activeTab === "institution") {
|
||||
this.$refs.basicTableRef && this.$refs.basicTableRef.doSearch()
|
||||
} else {
|
||||
this.$refs.userTableRef && this.$refs.userTableRef.doSearch()
|
||||
}
|
||||
})
|
||||
},
|
||||
refreshTree() {
|
||||
this.$refs.treeRef.listTree(this.currentTreeData && this.currentTreeData.id)
|
||||
},
|
||||
openEdit(row) {}
|
||||
},
|
||||
created() {}
|
||||
|
||||
+21
-2
@@ -46,11 +46,30 @@ const TREE_COMPONENT = {
|
||||
this.$emit("node-click", data, node, this.sessionId)
|
||||
},
|
||||
filterNode() {},
|
||||
listTree() {
|
||||
findTreeNode(list, id) {
|
||||
if (!id || !list) {
|
||||
return null
|
||||
}
|
||||
for (const item of list) {
|
||||
if (item.id === id) {
|
||||
return item
|
||||
}
|
||||
const child = this.findTreeNode(item.children, id)
|
||||
if (child) {
|
||||
return child
|
||||
}
|
||||
}
|
||||
return null
|
||||
},
|
||||
listTree(selectedId) {
|
||||
this.$axios.post("/platform/teacherCongress/institution/leftTree", { sessionId: this.sessionId }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.treeData = res.data
|
||||
this.$emit("node-click", this.treeData[0], null, this.sessionId)
|
||||
if (this.treeData && this.treeData.length > 0) {
|
||||
this.treeData[0].name = "两代会组织机构"
|
||||
}
|
||||
const selectedNode = this.findTreeNode(this.treeData, selectedId) || this.treeData[0]
|
||||
this.$emit("node-click", selectedNode, null, this.sessionId)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
+74
-35
@@ -3,58 +3,73 @@ const USER_TABLE_COMPONENT = {
|
||||
<div>
|
||||
<el-card shadow="never" style="height: 100%">
|
||||
<el-row type="flex" :gutter="20">
|
||||
<el-col :span="4">
|
||||
<el-input v-model="pageForm.searchKeyword" clearable placeholder="请输入姓名或者工号">" @keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="search-query">
|
||||
<el-button type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<el-input v-model="pageForm.searchKeyword"
|
||||
clearable
|
||||
placeholder="请输入姓名或者工号"
|
||||
@keyup.enter.native="doSearch">
|
||||
</el-input>
|
||||
</el-col>
|
||||
<el-col :span="4">
|
||||
<div class="search-query">
|
||||
<el-button type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-divider class="mb10 mt10"></el-divider>
|
||||
<table-tool>
|
||||
<el-button type="primary" size="small" icon="el-icon-plus" @click="openAdd()">设置人员</el-button>
|
||||
</table-tool>
|
||||
<el-button type="primary" size="small" icon="el-icon-plus" @click="openAdd()">设置人员</el-button>
|
||||
</table-tool>
|
||||
<el-table key="1" :data="tableData" ref="tableRef">
|
||||
<el-table-column label="序号" width="100px" type="index" :index="indexMethod"></el-table-column>
|
||||
<el-table-column prop="username" label="姓名"></el-table-column>
|
||||
<el-table-column prop="loginname" label="工号"></el-table-column>
|
||||
<el-table-column prop="sex" label="性别"></el-table-column>
|
||||
<el-table-column prop="mobile" label="手机号码"></el-table-column>
|
||||
<el-table-column prop="unitName" label="单位" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="identity" label="身份" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="操作" width="100px">
|
||||
<template slot-scope="{row}">
|
||||
<el-link size="mini" type="danger" @click="del(row.id)">删除</el-link>
|
||||
</template>
|
||||
<el-table-column label="序号" width="100px" type="index" :index="indexMethod"></el-table-column>
|
||||
<el-table-column prop="username" label="姓名"></el-table-column>
|
||||
<el-table-column prop="loginname" label="工号"></el-table-column>
|
||||
<el-table-column prop="sex" label="性别"></el-table-column>
|
||||
<el-table-column prop="mobile" label="手机号码"></el-table-column>
|
||||
<el-table-column prop="unitName" label="单位" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column prop="identity" label="身份" show-overflow-tooltip></el-table-column>
|
||||
<el-table-column label="操作" width="100px">
|
||||
<template slot-scope="{row}">
|
||||
<el-link size="mini" type="danger" @click="del(row.id)">删除</el-link>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</el-table>
|
||||
<!--#include("/layouts/pagination.html"){}#-->
|
||||
</el-card>
|
||||
<el-dialog title="设置人员" width="50%" :visible.sync="dialogVisible">
|
||||
<el-form :model="formData" ref="formRef" label-width="120px" :rules="formRules">
|
||||
<el-form :model="formData" ref="formRef" label-width="120px" :rules="formRules">
|
||||
<el-form-item label="届次" prop="sessionId">
|
||||
<el-select v-model="formData.sessionId" disabled>
|
||||
<el-option v-for="i in sessionOptions" :label="i.fullName" :value="i.id" :key="i.id"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="工号或姓名" prop="userId">
|
||||
<el-form-item v-if="needRole" label="角色" prop="roleCode">
|
||||
<el-select v-model="formData.roleCode" placeholder="请选择角色" style="width: 100%">
|
||||
<el-option v-for="item in roleOptions"
|
||||
:key="item.code"
|
||||
:label="item.name"
|
||||
:value="item.code">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="工号或者姓名" prop="userId">
|
||||
<user-select
|
||||
v-model="formData.userId"
|
||||
style="width: 100%"
|
||||
api_input_key_name="query"
|
||||
:option_label_func="(item)=>{return item.username + item.loginname}"
|
||||
v-model="formData.userId"
|
||||
style="width: 100%"
|
||||
api_input_key_name="query"
|
||||
:option_label_func="(item)=>{return item.username + item.loginname}"
|
||||
></user-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="身份" prop="identity">
|
||||
<dict-select v-model="formData.identity" code="TEACHER_CONGRESS_INSTITUTION_USER_ROLE"></dict-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="doSubmit">确 定</el-button>
|
||||
</el-form>
|
||||
<div style="color: #e6a23c; line-height: 22px; margin: 0 0 12px 120px;">
|
||||
注意:如果需要跟角色绑定,请在数据字典双代会组织机构中添加对应的角色标识。
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取消</el-button>
|
||||
<el-button type="primary" @click="doSubmit">确定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
@@ -68,6 +83,10 @@ const USER_TABLE_COMPONENT = {
|
||||
institutionId: {
|
||||
required: true,
|
||||
type: String
|
||||
},
|
||||
institutionCode: {
|
||||
type: String,
|
||||
default: ""
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -75,10 +94,17 @@ const USER_TABLE_COMPONENT = {
|
||||
formRules: {
|
||||
sessionId: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
userId: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
identity: [{ required: true, message: "必填", trigger: ["change", "blur"] }]
|
||||
identity: [{ required: true, message: "必填", trigger: ["change", "blur"] }],
|
||||
roleCode: [{ required: true, message: "必填", trigger: ["change", "blur"] }]
|
||||
},
|
||||
dialogVisible: false,
|
||||
sessionOptions: []
|
||||
sessionOptions: [],
|
||||
roleOptions: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
needRole() {
|
||||
return ["ZWH001", "ZXWYH", "DBZGSCXZ"].includes(this.institutionCode)
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
@@ -136,6 +162,18 @@ const USER_TABLE_COMPONENT = {
|
||||
})
|
||||
},
|
||||
|
||||
listRoleOptions() {
|
||||
if (!this.needRole) {
|
||||
this.roleOptions = []
|
||||
return
|
||||
}
|
||||
this.$axios.post("/platform/teacherCongress/institution/specialCommitteeRoleOptions").then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.roleOptions = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
openAdd() {
|
||||
this.dialogVisible = true
|
||||
this.formData = {
|
||||
@@ -143,6 +181,7 @@ const USER_TABLE_COMPONENT = {
|
||||
institutionId: this.institutionId
|
||||
}
|
||||
this.listSession()
|
||||
this.listRoleOptions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+94
-31
@@ -3,6 +3,55 @@ layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style scoped>
|
||||
.site-cug-audit-page {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f4f6f8;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.site-cug-audit-sticky {
|
||||
flex-shrink: 0;
|
||||
background: #f4f6f8;
|
||||
}
|
||||
.site-cug-audit-list-scroll {
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 18px;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.site-cug-audit-search /deep/ .van-search__content,
|
||||
.site-cug-audit-search .van-search__content {
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
}
|
||||
.site-cug-card-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.site-cug-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 22px;
|
||||
padding: 0 8px;
|
||||
border-radius: 4px;
|
||||
background: #eef6ff;
|
||||
color: #0b75bd;
|
||||
font-size: 12px;
|
||||
}
|
||||
.site-cug-tag.gray {
|
||||
background: #f1f5f9;
|
||||
color: #64748b;
|
||||
}
|
||||
.site-cug-tag.warn {
|
||||
background: #fff3e5;
|
||||
color: #d97706;
|
||||
}
|
||||
.action-btn.disabled {
|
||||
opacity: 0.45;
|
||||
pointer-events: none;
|
||||
@@ -15,18 +64,23 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" v-cloak>
|
||||
<div id="app" class="site-cug-audit-page" v-cloak>
|
||||
<van-nav-bar title="校工会审核" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
|
||||
<van-sticky offset-top="46px">
|
||||
<van-sticky offset-top="46px" class="site-cug-audit-sticky">
|
||||
<van-search
|
||||
class="site-cug-audit-search"
|
||||
v-model="pageForm.searchKeyword"
|
||||
:show-action="false"
|
||||
:reverse-color="false"
|
||||
show-action
|
||||
clearable
|
||||
input-align="left"
|
||||
placeholder="请输入姓名/工号/场地搜索"
|
||||
@search="doSearch"
|
||||
></van-search>
|
||||
@clear="doSearch">
|
||||
<template #action>
|
||||
<div @click="doSearch">搜索</div>
|
||||
</template>
|
||||
</van-search>
|
||||
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
|
||||
<van-dropdown-item v-model="pageForm.siteType" :options="typeOptions" :multiple="false" @change="doSearch"></van-dropdown-item>
|
||||
<van-dropdown-item v-model="pageForm.siteId" :options="siteOptions" :multiple="false" @change="doSearch"></van-dropdown-item>
|
||||
@@ -37,32 +91,38 @@ layout("/layouts/platform_h5.html"){
|
||||
</van-tabs>
|
||||
</van-sticky>
|
||||
|
||||
<table-list api="/platform/siteCug/schoolUnionAudit/pageData" :page_form.sync="pageForm" ref="tableListRef" title="applyUserName" @ready="onReady">
|
||||
<template v-slot="{index,row}">
|
||||
<table-column label="场地名称">{{row.siteName}}</table-column>
|
||||
<table-column label="预约类型">{{row.reserveType === 'club' ? '协会预约' : '分工会预约'}}</table-column>
|
||||
<table-column label="预约主体">{{row.reserveType === 'club' ? (row.clubName || '-') : (row.applyUnionName || '-')}}</table-column>
|
||||
<table-column label="所属单位">{{row.applyUnitName}}</table-column>
|
||||
<table-column label="开始时间">{{row.reserveStartTime}}</table-column>
|
||||
<table-column label="结束时间">{{row.reserveEndTime}}</table-column>
|
||||
<table-column label="当前节点">{{row.curTaskName || '-'}}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
</div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onAudit(row)">
|
||||
<i class="fa fa-edit"></i>
|
||||
<span>审核</span>
|
||||
</div>
|
||||
<div class="action-btn delete" v-if="canRevoke(row)" :class="{ loading: revokeLoading }" @click="onRevoke(row)">
|
||||
<van-loading v-if="revokeLoading" size="14px" color="#fff"></van-loading>
|
||||
<i v-else class="fa fa-reply"></i>
|
||||
<span>撤回</span>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
<div class="site-cug-audit-list-scroll">
|
||||
<table-list api="/platform/siteCug/schoolUnionAudit/pageData" :page_form.sync="pageForm" ref="tableListRef" title="applyUserName" @ready="onReady">
|
||||
<template v-slot="{index,row}">
|
||||
<div class="site-cug-card-tags">
|
||||
<span v-if="row.applyLoginName" class="site-cug-tag">{{ row.applyLoginName }}</span>
|
||||
<span class="site-cug-tag gray">{{ reserveTypeText(row) }}</span>
|
||||
<span v-if="row.yearlyBatch" class="site-cug-tag warn">预约本年</span>
|
||||
</div>
|
||||
<table-column label="场地名称">{{row.siteName}}</table-column>
|
||||
<table-column label="预约主体">{{row.reserveType === 'club' ? (row.clubName || '-') : (row.applyUnionName || '-')}}</table-column>
|
||||
<table-column label="所属单位">{{row.applyUnitName}}</table-column>
|
||||
<table-column label="开始时间">{{row.reserveStartTime}}</table-column>
|
||||
<table-column label="结束时间">{{row.reserveEndTime}}</table-column>
|
||||
<table-column label="当前节点">{{row.curTaskName || '-'}}</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
</div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onAudit(row)">
|
||||
<i class="fa fa-edit"></i>
|
||||
<span>审核</span>
|
||||
</div>
|
||||
<div class="action-btn delete" v-if="canRevoke(row)" :class="{ loading: revokeLoading }" @click="onRevoke(row)">
|
||||
<van-loading v-if="revokeLoading" size="14px" color="#fff"></van-loading>
|
||||
<i v-else class="fa fa-reply"></i>
|
||||
<span>撤回</span>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
</div>
|
||||
|
||||
<info ref="infoRef">
|
||||
<div v-if="showApprovalForm">
|
||||
@@ -140,6 +200,9 @@ layout("/layouts/platform_h5.html"){
|
||||
canRevoke(row) {
|
||||
return Number(row.instanceState) === 20
|
||||
},
|
||||
reserveTypeText(row) {
|
||||
return row && row.reserveType === 'club' ? '协会预约' : '分工会预约'
|
||||
},
|
||||
onView(row) {
|
||||
this.showApprovalForm = false
|
||||
this.$refs.infoRef.onOpen(row)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+478
@@ -0,0 +1,478 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style scoped>
|
||||
.tour-approval-page {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f4f6f8;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-approval-sticky {
|
||||
flex-shrink: 0;
|
||||
background: #f4f6f8;
|
||||
}
|
||||
|
||||
.tour-approval-list-scroll {
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 18px;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-approval-search /deep/ .van-search__content,
|
||||
.tour-approval-search .van-search__content {
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.tour-card-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.tour-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 22px;
|
||||
padding: 0 8px;
|
||||
border-radius: 4px;
|
||||
background: #eef6ff;
|
||||
color: #0b75bd;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tour-tag.gray {
|
||||
background: #f1f5f9;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.tour-tag.warn {
|
||||
background: #fff3e5;
|
||||
color: #d97706;
|
||||
}
|
||||
|
||||
.tour-approval-sheet {
|
||||
max-height: 86vh;
|
||||
overflow-y: auto;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
background: #f4f6f8;
|
||||
}
|
||||
|
||||
.tour-section {
|
||||
margin: 10px 12px;
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.tour-section-title {
|
||||
position: relative;
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid #edf0f4;
|
||||
color: #0b75bd;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.tour-section-title::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 0;
|
||||
width: 30px;
|
||||
height: 4px;
|
||||
border-radius: 0 0 3px 3px;
|
||||
background: #0b75bd;
|
||||
}
|
||||
|
||||
.tour-approval-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
padding: 10px 12px 14px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.tour-process-opinion {
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" class="tour-approval-page" v-cloak>
|
||||
<van-nav-bar title="校工会审核" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
|
||||
<van-sticky offset-top="46px" class="tour-approval-sticky">
|
||||
<van-search
|
||||
class="tour-approval-search"
|
||||
v-model="pageForm.keyword"
|
||||
placeholder="请输入工号/姓名/线路搜索"
|
||||
show-action
|
||||
clearable
|
||||
input-align="left"
|
||||
@search="doSearch"
|
||||
@clear="doSearch">
|
||||
<template #action>
|
||||
<div @click="doSearch">搜索</div>
|
||||
</template>
|
||||
</van-search>
|
||||
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
|
||||
<van-dropdown-item :options="auditOptions" @change="doSearch" v-model="pageForm.audit"></van-dropdown-item>
|
||||
<van-dropdown-item :options="unionOptions" @change="doSearch" v-model="pageForm.unionId"></van-dropdown-item>
|
||||
<van-dropdown-item :options="lineTypeOptions" @change="doSearch" v-model="pageForm.lineType"></van-dropdown-item>
|
||||
</van-dropdown-menu>
|
||||
</van-sticky>
|
||||
|
||||
<div class="tour-approval-list-scroll">
|
||||
<table-list api="/platform/tour/schoolUnionApproval/pageData" :page_form.sync="pageForm" ref="tableListRef" title="userName" @ready="onReady">
|
||||
<template v-slot="{index,row}">
|
||||
<div class="tour-card-tags">
|
||||
<span v-if="row.jobNo" class="tour-tag">{{ row.jobNo }}</span>
|
||||
<span v-if="row.lineType" class="tour-tag gray">{{ row.lineType }}</span>
|
||||
<span v-if="row.overCostReimbursed" class="tour-tag warn">报销超出费用</span>
|
||||
</div>
|
||||
<table-column label="线路名称">{{ row.lineName || '' }}</table-column>
|
||||
<table-column label="出行时段">{{ row.travelPeriod || '' }}</table-column>
|
||||
<table-column label="报销超出费用">{{ row.overCostReimbursed ? '是' : '否' }}</table-column>
|
||||
<table-column label="当前节点">{{ row.curTaskName || '' }}</table-column>
|
||||
<table-column label="流程状态">
|
||||
<enum-tag
|
||||
v-if="row.instanceState !== null && row.instanceState !== undefined && row.instanceState !== ''"
|
||||
:value="row.instanceState"
|
||||
name="ProcessInstanceStateEnum"
|
||||
label_key="message"
|
||||
size="small">
|
||||
</enum-tag>
|
||||
</table-column>
|
||||
</template>
|
||||
<template #actions="{index,row}">
|
||||
<div class="action-btn" @click="onView(row)">
|
||||
<i class="fa fa-eye"></i>
|
||||
<span>查看</span>
|
||||
</div>
|
||||
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
|
||||
<i class="fa fa-check"></i>
|
||||
<span>审核</span>
|
||||
</div>
|
||||
</template>
|
||||
</table-list>
|
||||
</div>
|
||||
|
||||
<tour-info ref="infoRef">
|
||||
<div v-if="showApprovalForm" class="tour-section">
|
||||
<div class="tour-section-title">{{ formData.taskName || '审核' }}</div>
|
||||
<van-form ref="formRef">
|
||||
<van-field
|
||||
v-model="formData.tf_opinion"
|
||||
name="tf_opinion"
|
||||
label="审批意见"
|
||||
placeholder="请输入审批意见"
|
||||
:rules="[{ required: true, message: '请填写审批意见' }]"
|
||||
required
|
||||
type="textarea"
|
||||
rows="3"
|
||||
maxlength="200"
|
||||
show-word-limit>
|
||||
</van-field>
|
||||
</van-form>
|
||||
<div class="tour-approval-actions">
|
||||
<van-button type="danger" block @click="handleTaskAction(6)">退回</van-button>
|
||||
<van-button type="danger" block @click="handleTaskAction(2)">拒绝</van-button>
|
||||
<van-button type="primary" block @click="handleTaskAction(1)">同意</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</tour-info>
|
||||
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
const TOUR_INFO = {
|
||||
template: /*language=HTML*/ `
|
||||
<van-action-sheet v-model="visible" title="报名详情">
|
||||
<div class="tour-approval-sheet">
|
||||
<div class="tour-section">
|
||||
<div class="tour-section-title">教职工信息</div>
|
||||
<van-cell title="工号" :value="detail.jobNo || ''"></van-cell>
|
||||
<van-cell title="姓名" :value="detail.userName || ''"></van-cell>
|
||||
<van-cell title="性别" :value="detail.gender || ''"></van-cell>
|
||||
<van-cell title="身份证号" :value="detail.idCard || ''"></van-cell>
|
||||
<van-cell title="所在单位" :value="detail.unitName || ''"></van-cell>
|
||||
<van-cell title="所在工会" :value="detail.unionName || ''"></van-cell>
|
||||
</div>
|
||||
|
||||
<div class="tour-section">
|
||||
<div class="tour-section-title">报名信息</div>
|
||||
<van-cell title="报名线路" :value="detail.lineName || ''"></van-cell>
|
||||
<van-cell title="线路类型" :value="detail.lineType || ''"></van-cell>
|
||||
<van-cell title="出行时段" :value="detail.travelPeriod || ''"></van-cell>
|
||||
<van-cell title="报名时间" :value="detail.signupTime || ''"></van-cell>
|
||||
<van-cell title="报名酒店" :value="detail.hotelName || ''"></van-cell>
|
||||
<van-cell title="旅行社" :value="detail.travelAgencyName || ''"></van-cell>
|
||||
<van-cell v-if="fillBedInfo" title="床型" :value="detail.bedType || ''"></van-cell>
|
||||
<van-cell v-if="fillBedInfo" title="床位信息" :value="detail.bedInfo || ''"></van-cell>
|
||||
<van-cell v-if="fillBedInfo" title="意向拼床人" :value="detail.intendedRoommate || ''"></van-cell>
|
||||
<van-cell title="携带家属" :value="familyText()"></van-cell>
|
||||
<van-cell title="报销超出费用" :value="detail.overCostReimbursed ? '是' : '否'"></van-cell>
|
||||
</div>
|
||||
|
||||
<div v-if="isDirectFamilyLine()" class="tour-section">
|
||||
<div class="tour-section-title">直系亲属线路</div>
|
||||
<van-cell title="亲属姓名" :value="directRelative.relativeName || ''"></van-cell>
|
||||
<van-cell title="所在单位" :value="directRelative.unitName || ''"></van-cell>
|
||||
<van-cell title="亲属关系" :value="directRelative.relationshipName || ''"></van-cell>
|
||||
<van-cell title="线路名称" :value="directRelative.lineName || ''"></van-cell>
|
||||
<van-cell title="出行开始" :value="directRelative.travelStartTime || ''"></van-cell>
|
||||
<van-cell title="出行结束" :value="directRelative.travelEndTime || ''"></van-cell>
|
||||
</div>
|
||||
|
||||
<div v-if="showFamilySection()" class="tour-section">
|
||||
<div class="tour-section-title">家属信息</div>
|
||||
<template v-for="(item,index) in familyData">
|
||||
<van-cell :key="'title_' + index" :title="'家属' + (index + 1)"></van-cell>
|
||||
<van-cell :key="'name_' + index" title="姓名" :value="item.familyName || ''"></van-cell>
|
||||
<van-cell :key="'gender_' + index" title="性别" :value="item.gender || ''"></van-cell>
|
||||
<van-cell :key="'id_' + index" title="身份证号" :value="item.idCard || ''"></van-cell>
|
||||
<van-cell :key="'rel_' + index" title="关系" :value="item.relationship || ''"></van-cell>
|
||||
<van-cell v-if="fillBedInfo" :key="'bed_' + index" title="床型" :value="item.bedType || ''"></van-cell>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div v-if="doneTasks.length > 0" class="tour-section">
|
||||
<div class="tour-section-title">流程记录</div>
|
||||
<template v-for="task in doneTasks">
|
||||
<van-cell-group :key="task.id" :title="task.displayName || task.taskName">
|
||||
<van-cell title="办理时间" :value="task.finishTime || ''"></van-cell>
|
||||
<van-cell title="办理结果">
|
||||
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext && task.ext.submitType"></dict-tag>
|
||||
</van-cell>
|
||||
<van-cell title="办理意见" v-if="task.taskFormData" class="direction-column-cell">
|
||||
<div class="tour-process-opinion">{{ task.taskFormData.opinion || task.taskFormData.tf_opinion || '' }}</div>
|
||||
</van-cell>
|
||||
</van-cell-group>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<slot></slot>
|
||||
</div>
|
||||
</van-action-sheet>
|
||||
`,
|
||||
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
row: {},
|
||||
detail: {},
|
||||
familyData: [],
|
||||
directRelative: {},
|
||||
allowFamily: false,
|
||||
fillBedInfo: true,
|
||||
directFamilyUnitLine: false,
|
||||
doneTasks: []
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onOpen(row) {
|
||||
this.row = row || {}
|
||||
this.visible = true
|
||||
this.detail = {}
|
||||
this.familyData = []
|
||||
this.directRelative = {}
|
||||
this.allowFamily = false
|
||||
this.fillBedInfo = true
|
||||
this.directFamilyUnitLine = false
|
||||
this.doneTasks = []
|
||||
this.info()
|
||||
this.getDoneTasks()
|
||||
},
|
||||
onClose() {
|
||||
this.visible = false
|
||||
},
|
||||
info() {
|
||||
this.$axios.post("/platform/tour/schoolUnionApproval/detail", { id: this.row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const data = res.data || {}
|
||||
this.detail = data.ledger || {}
|
||||
this.detail.travelPeriod = data.travelPeriod || this.row.travelPeriod || ""
|
||||
this.familyData = data.families || []
|
||||
this.directRelative = data.directRelative || {}
|
||||
this.allowFamily = this.toBoolean(data.allowFamily)
|
||||
this.fillBedInfo = data.fillBedInfo === undefined || data.fillBedInfo === null || this.toBoolean(data.fillBedInfo)
|
||||
this.directFamilyUnitLine = this.toBoolean(data.directFamilyUnitLine)
|
||||
} else {
|
||||
vant.Toast(res.msg || "查询失败")
|
||||
}
|
||||
})
|
||||
},
|
||||
getDoneTasks() {
|
||||
this.$axios.post("/flow/common/doneTasks", { bizId: this.row.id }).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.doneTasks = res.data || []
|
||||
}
|
||||
})
|
||||
},
|
||||
toBoolean(value) {
|
||||
return value === true || value === 1 || value === "1" || value === "true" || value === "TRUE"
|
||||
},
|
||||
isDirectFamilyLine() {
|
||||
return this.directFamilyUnitLine || this.toBoolean(this.detail.directFamilyUnitLine) || !!(this.directRelative && this.directRelative.id)
|
||||
},
|
||||
hasFamily() {
|
||||
return this.toBoolean(this.detail.hasFamily) || this.familyData.length > 0
|
||||
},
|
||||
showFamilySection() {
|
||||
return this.allowFamily && this.hasFamily() && !this.isDirectFamilyLine()
|
||||
},
|
||||
familyText() {
|
||||
if (this.isDirectFamilyLine() || !this.allowFamily) {
|
||||
return "否"
|
||||
}
|
||||
return this.hasFamily() ? "是" : "否"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
components: {
|
||||
"tour-info": TOUR_INFO
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
pageOrderName: "signupTime",
|
||||
pageOrderBy: "descending",
|
||||
audit: false,
|
||||
keyword: "",
|
||||
unionId: "",
|
||||
lineType: ""
|
||||
},
|
||||
auditOptions: [
|
||||
{ text: "未审核", value: false },
|
||||
{ text: "已审核", value: true }
|
||||
],
|
||||
unionOptions: [
|
||||
{ text: "全部工会", value: "" }
|
||||
],
|
||||
lineTypeOptions: [
|
||||
{ text: "全部线路类型", value: "" }
|
||||
],
|
||||
formData: {},
|
||||
showApprovalForm: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
historyBack,
|
||||
onReady() {
|
||||
this.doSearch()
|
||||
},
|
||||
doSearch() {
|
||||
this.$nextTick(async () => {
|
||||
await Promise.all([this.loadUnionOptions(), this.loadLineTypeOptions()])
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageForm.totalCount = 0
|
||||
this.$refs.tableListRef.doSearch()
|
||||
})
|
||||
},
|
||||
loadUnionOptions() {
|
||||
return this.$axios.post("/platform/tour/schoolUnionApproval/unionOptions", {
|
||||
audit: this.pageForm.audit,
|
||||
keyword: this.pageForm.keyword,
|
||||
lineType: this.pageForm.lineType
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const options = (res.data || []).map(item => ({
|
||||
text: item.name,
|
||||
value: item.id
|
||||
})).filter(item => item.value)
|
||||
this.unionOptions = [{ text: "全部工会", value: "" }].concat(options)
|
||||
if (this.pageForm.unionId && !this.unionOptions.some(item => item.value === this.pageForm.unionId)) {
|
||||
this.pageForm.unionId = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
loadLineTypeOptions() {
|
||||
return this.$axios.post("/platform/tour/schoolUnionApproval/lineTypeOptions", {
|
||||
audit: this.pageForm.audit,
|
||||
keyword: this.pageForm.keyword,
|
||||
unionId: this.pageForm.unionId
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const options = (res.data || []).map(item => ({
|
||||
text: item.lineType,
|
||||
value: item.lineType
|
||||
})).filter(item => item.value)
|
||||
this.lineTypeOptions = [{ text: "全部线路类型", value: "" }].concat(options)
|
||||
if (this.pageForm.lineType && !this.lineTypeOptions.some(item => item.value === this.pageForm.lineType)) {
|
||||
this.pageForm.lineType = ""
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
onView(row) {
|
||||
this.showApprovalForm = false
|
||||
this.$refs.infoRef.onOpen(row)
|
||||
},
|
||||
onApproval(row) {
|
||||
this.showApprovalForm = true
|
||||
this.formData = {
|
||||
processTaskId: row.taskId,
|
||||
taskName: row.curTaskName || row.taskName,
|
||||
tf_opinion: ""
|
||||
}
|
||||
this.$refs.infoRef.onOpen(row)
|
||||
},
|
||||
async handleTaskAction(val) {
|
||||
try {
|
||||
await this.$refs.formRef.validate()
|
||||
} catch (e) {
|
||||
return
|
||||
}
|
||||
this.$dialog.confirm({
|
||||
title: "提示",
|
||||
message: "您确定要提交吗?"
|
||||
}).then(() => {
|
||||
const loading = this.$toast.loading({
|
||||
message: "加载中...",
|
||||
forbidClick: true,
|
||||
overlay: true,
|
||||
duration: 0
|
||||
})
|
||||
this.$axios.post("/flow/common/executeTask", {
|
||||
data: JSON.stringify({
|
||||
...this.formData,
|
||||
submitType: val
|
||||
})
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
this.$refs.infoRef.onClose()
|
||||
this.$toast.success(res.msg || "操作成功")
|
||||
this.doSearch()
|
||||
} else {
|
||||
this.$toast(res.msg || "操作失败")
|
||||
}
|
||||
}).finally(() => {
|
||||
loading.close()
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,556 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style scoped>
|
||||
.tour-apply-page {
|
||||
min-height: 100vh;
|
||||
padding: 10px 14px 78px;
|
||||
background: #f4f6f8;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-apply-card {
|
||||
margin-bottom: 14px;
|
||||
overflow: hidden;
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
.tour-apply-title {
|
||||
position: relative;
|
||||
padding: 14px 16px;
|
||||
border-bottom: 1px solid #edf0f4;
|
||||
color: #0b75bd;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.tour-apply-title::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 0;
|
||||
width: 30px;
|
||||
height: 4px;
|
||||
border-radius: 0 0 3px 3px;
|
||||
background: #0b75bd;
|
||||
}
|
||||
|
||||
.tour-card-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.tour-card-actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tour-family-tab {
|
||||
margin: 10px 12px 0;
|
||||
height: 30px;
|
||||
border-radius: 5px;
|
||||
background: #1684d2;
|
||||
color: #ffffff;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
line-height: 30px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tour-field-tip {
|
||||
padding: 0 16px 12px;
|
||||
color: #ee2f2f;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.tour-over-cost /deep/ .van-field__label,
|
||||
.tour-over-cost /deep/ .van-field__label span,
|
||||
.tour-over-cost /deep/ .van-radio__label {
|
||||
color: #ee2f2f;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tour-submit-bar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 20;
|
||||
padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
|
||||
border-top: 1px solid #edf0f4;
|
||||
background: #ffffff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-apply-empty {
|
||||
padding: 24px 0;
|
||||
color: #94a3b8;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app">
|
||||
<van-nav-bar title="疗休养报名" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
|
||||
<div class="tour-apply-page">
|
||||
<van-loading v-if="pageLoading" size="24px" vertical>加载中...</van-loading>
|
||||
|
||||
<template v-else>
|
||||
<div class="tour-apply-card">
|
||||
<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="手机号" v-model="signupForm.mobile" type="tel" maxlength="30" placeholder="请填写手机号"></van-field>
|
||||
<van-field label="所属工会" readonly v-model="signupForm.unionName"></van-field>
|
||||
</div>
|
||||
|
||||
<div class="tour-apply-card">
|
||||
<div class="tour-apply-title">报名信息</div>
|
||||
<van-field label="报名线路" readonly v-model="signupForm.lineName"></van-field>
|
||||
<van-field label="线路类型" readonly v-model="signupForm.lineType"></van-field>
|
||||
<van-field v-if="signupForm.travelPeriod" label="出行时间" readonly v-model="signupForm.travelPeriod"></van-field>
|
||||
<van-field label="报名酒店" v-model="signupForm.hotelName" maxlength="100" placeholder="请输入报名酒店"></van-field>
|
||||
<van-field
|
||||
v-if="fillBedInfo"
|
||||
label="床型"
|
||||
readonly
|
||||
clickable
|
||||
is-link
|
||||
placeholder="请选择房型"
|
||||
v-model="signupForm.bedType"
|
||||
@click="openBedPicker('self')">
|
||||
</van-field>
|
||||
<van-field v-if="fillBedInfo" label="床位信息" v-model="signupForm.bedInfo" maxlength="100" placeholder="请输入床位数量"></van-field>
|
||||
<van-field v-if="fillBedInfo" label="意向拼床人" v-model="signupForm.intendedRoommate" maxlength="100" placeholder="请输入意向拼床人"></van-field>
|
||||
<van-field v-if="canApplyOverReimbursement(signupForm)" class="tour-over-cost" label="报销超出费用">
|
||||
<template #input>
|
||||
<van-radio-group v-model="signupForm.overCostReimbursed" direction="horizontal">
|
||||
<van-radio :name="true">是</van-radio>
|
||||
<van-radio :name="false">否</van-radio>
|
||||
</van-radio-group>
|
||||
</template>
|
||||
</van-field>
|
||||
<div v-if="canApplyOverReimbursement(signupForm) && signupForm.overCostReimbursed" class="tour-field-tip">
|
||||
提醒:您选择了报销超出费用,会影响到下一年度的疗休养,请认真思考。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="isDirectFamilyLine(signupForm)" class="tour-apply-card">
|
||||
<div class="tour-apply-title">直系亲属线路</div>
|
||||
<van-field label="亲属姓名" v-model="directRelativeForm.relativeName" maxlength="100" placeholder="请输入亲属姓名"></van-field>
|
||||
<van-field label="所在单位" v-model="directRelativeForm.unitName" maxlength="100" placeholder="请输入所在单位"></van-field>
|
||||
<van-field
|
||||
label="亲属关系"
|
||||
readonly
|
||||
clickable
|
||||
is-link
|
||||
placeholder="请选择亲属关系"
|
||||
v-model="directRelativeForm.relationshipName"
|
||||
@click="openDirectRelativePicker">
|
||||
</van-field>
|
||||
<van-field label="线路名称" v-model="directRelativeForm.lineName" maxlength="100" placeholder="请输入线路名称"></van-field>
|
||||
<van-field label="出行开始" v-model="directRelativeForm.travelStartTime" type="date" placeholder="请选择出行开始日期"></van-field>
|
||||
<van-field label="出行结束" v-model="directRelativeForm.travelEndTime" type="date" placeholder="请选择出行结束日期"></van-field>
|
||||
</div>
|
||||
|
||||
<div class="tour-apply-card">
|
||||
<div class="tour-apply-title">
|
||||
<div class="tour-card-title-row">
|
||||
<span>亲属信息</span>
|
||||
<div class="tour-card-actions">
|
||||
<van-button size="small" type="default" :disabled="familyData.length === 0" @click="removeFamily">删除亲属</van-button>
|
||||
<van-button size="small" type="info" :disabled="!allowFamily || isDirectFamilyLine(signupForm)" @click="addFamily">添加亲属</van-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!allowFamily || isDirectFamilyLine(signupForm)" class="tour-apply-empty">
|
||||
当前线路不支持填写亲属
|
||||
</div>
|
||||
<template v-else-if="familyData.length > 0">
|
||||
<div class="tour-family-tab">亲属{{ familyActive + 1 }}</div>
|
||||
<van-field label="姓名" v-model="currentFamily.familyName" maxlength="100" placeholder="请填写姓名"></van-field>
|
||||
<van-field label="年龄" v-model.number="currentFamily.age" type="digit" placeholder="请填写年龄"></van-field>
|
||||
<van-field label="性别">
|
||||
<template #input>
|
||||
<van-radio-group v-model="currentFamily.gender" direction="horizontal">
|
||||
<van-radio name="男">男</van-radio>
|
||||
<van-radio name="女">女</van-radio>
|
||||
</van-radio-group>
|
||||
</template>
|
||||
</van-field>
|
||||
<van-field label="身份证号码" v-model="currentFamily.idCard" maxlength="18" placeholder="请填写身份证号码" @blur="normalizeFamilyIdCard(currentFamily)"></van-field>
|
||||
<van-field label="手机号" v-model="currentFamily.mobile" maxlength="30" placeholder="请填写手机号"></van-field>
|
||||
<van-field
|
||||
label="与本人关系"
|
||||
readonly
|
||||
clickable
|
||||
is-link
|
||||
placeholder="请选择关系"
|
||||
v-model="currentFamily.relationship"
|
||||
@click="openRelationshipPicker">
|
||||
</van-field>
|
||||
<van-field
|
||||
v-if="fillBedInfo"
|
||||
label="床型"
|
||||
readonly
|
||||
clickable
|
||||
is-link
|
||||
placeholder="请选择房型"
|
||||
v-model="currentFamily.bedType"
|
||||
@click="openBedPicker('family')">
|
||||
</van-field>
|
||||
<van-field v-if="fillBedInfo" label="床位" v-model="currentFamily.bedInfo" maxlength="100" placeholder="请输入床位数量"></van-field>
|
||||
<van-field v-if="fillBedInfo" label="意向拼床人" v-model="currentFamily.intendedRoommate" maxlength="100" placeholder="请输入意向拼床人"></van-field>
|
||||
<div v-if="fillBedInfo" class="tour-field-tip">提醒:如果跟报名人员同一房间,无须选择!</div>
|
||||
</template>
|
||||
<div v-else class="tour-apply-empty">暂无亲属,请按需添加</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="tour-submit-bar">
|
||||
<van-button type="info" block round :loading="submitLoading" @click="submitSignup">提交报名</van-button>
|
||||
</div>
|
||||
|
||||
<van-popup v-model="bedPickerVisible" position="bottom">
|
||||
<van-picker show-toolbar :columns="bedTypeColumns" @confirm="confirmBedType" @cancel="bedPickerVisible=false"></van-picker>
|
||||
</van-popup>
|
||||
|
||||
<van-popup v-model="relationshipPickerVisible" position="bottom">
|
||||
<van-picker show-toolbar :columns="relationshipColumns" @confirm="confirmRelationship" @cancel="relationshipPickerVisible=false"></van-picker>
|
||||
</van-popup>
|
||||
|
||||
<van-popup v-model="directRelativePickerVisible" position="bottom">
|
||||
<van-picker show-toolbar :columns="directRelativeColumns" @confirm="confirmDirectRelative" @cancel="directRelativePickerVisible=false"></van-picker>
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
data() {
|
||||
return {
|
||||
pageLoading: true,
|
||||
submitLoading: false,
|
||||
allowFamily: false,
|
||||
fillBedInfo: true,
|
||||
signupForm: {},
|
||||
directRelativeForm: {},
|
||||
familyData: [],
|
||||
familyActive: 0,
|
||||
bedTypeOptions: [],
|
||||
familyRelationshipOptions: [],
|
||||
directRelativeOptions: [],
|
||||
bedPickerVisible: false,
|
||||
relationshipPickerVisible: false,
|
||||
directRelativePickerVisible: false,
|
||||
bedPickerTarget: "self",
|
||||
matterId: GetQueryString("matterId") || ""
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
currentFamily() {
|
||||
return this.familyData[this.familyActive] || {}
|
||||
},
|
||||
bedTypeColumns() {
|
||||
return this.bedTypeOptions.map(item => item.name || item.code).filter(Boolean)
|
||||
},
|
||||
relationshipColumns() {
|
||||
return this.familyRelationshipOptions.map(item => item.name || item.code).filter(Boolean)
|
||||
},
|
||||
directRelativeColumns() {
|
||||
return this.directRelativeOptions.map(item => ({ text: item.name || item.code, item }))
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
if (!this.matterId) {
|
||||
vant.Dialog.alert({ title: "提示", message: "报名事项参数缺失", confirmButtonColor: "#1867b0" })
|
||||
this.pageLoading = false
|
||||
return
|
||||
}
|
||||
Promise.all([this.loadOptions(), this.loadSignupDetail()]).finally(() => {
|
||||
this.pageLoading = false
|
||||
})
|
||||
},
|
||||
loadOptions() {
|
||||
return Promise.all([
|
||||
this.$axios.post("/platform/tour/signup/bedTypeOptions").then(res => {
|
||||
if (res.code === 0) this.bedTypeOptions = res.data || []
|
||||
}),
|
||||
this.$axios.post("/platform/tour/signup/familyRelationshipOptions").then(res => {
|
||||
if (res.code === 0) this.familyRelationshipOptions = res.data || []
|
||||
}),
|
||||
this.$axios.post("/platform/tour/signup/directRelativeOptions").then(res => {
|
||||
if (res.code === 0) this.directRelativeOptions = res.data || []
|
||||
})
|
||||
])
|
||||
},
|
||||
loadSignupDetail() {
|
||||
return this.$axios.post("/platform/tour/signup/signupDetail", { matterId: this.matterId }).then((res) => {
|
||||
if (res.code !== 0) {
|
||||
vant.Dialog.alert({
|
||||
title: "提示",
|
||||
message: res.msg || "报名信息加载失败",
|
||||
confirmButtonColor: "#1867b0"
|
||||
})
|
||||
return
|
||||
}
|
||||
const data = res.data || {}
|
||||
const matter = data.matter || {}
|
||||
const staff = data.staff || {}
|
||||
const ledger = data.ledger || {}
|
||||
const directRelative = data.directRelative || {}
|
||||
this.allowFamily = matter.allowFamily === true || matter.allowFamily === 1 || matter.allowFamily === "1"
|
||||
this.fillBedInfo = matter.fillBedInfo === undefined || matter.fillBedInfo === null || matter.fillBedInfo === true || matter.fillBedInfo === 1 || matter.fillBedInfo === "1"
|
||||
this.signupForm = Object.assign({
|
||||
id: "",
|
||||
year: matter.year,
|
||||
jobNo: "",
|
||||
userName: "",
|
||||
gender: "",
|
||||
idCard: "",
|
||||
mobile: "",
|
||||
unitId: "",
|
||||
unitName: "",
|
||||
unionId: "",
|
||||
unionName: "",
|
||||
signupTime: this.$moment ? this.$moment().format("YYYY-MM-DD HH:mm:ss") : "",
|
||||
matterId: matter.matterId || "",
|
||||
lineId: matter.lineId || "",
|
||||
lineName: matter.lineName || "",
|
||||
lineType: matter.lineType || "",
|
||||
directFamilyUnitLine: matter.directFamilyUnitLine,
|
||||
hotelName: "",
|
||||
travelAgencyId: matter.travelAgencyId || "",
|
||||
travelAgencyName: matter.travelAgencyName || "",
|
||||
travelPeriod: matter.travelPeriod || "",
|
||||
travelStartTime: matter.travelStartTime || "",
|
||||
travelEndTime: matter.travelEndTime || "",
|
||||
allowOverReimbursement: matter.allowOverReimbursement,
|
||||
overCostReimbursed: false,
|
||||
hasFamily: false,
|
||||
intendedRoommate: "",
|
||||
bedType: "",
|
||||
bedInfo: ""
|
||||
}, staff, ledger, {
|
||||
year: matter.year,
|
||||
matterId: matter.matterId || ledger.matterId || "",
|
||||
lineId: matter.lineId || ledger.lineId || "",
|
||||
lineName: matter.lineName || ledger.lineName || "",
|
||||
lineType: matter.lineType || ledger.lineType || "",
|
||||
directFamilyUnitLine: matter.directFamilyUnitLine,
|
||||
travelAgencyId: matter.travelAgencyId || ledger.travelAgencyId || "",
|
||||
travelAgencyName: matter.travelAgencyName || ledger.travelAgencyName || "",
|
||||
travelPeriod: matter.travelPeriod || "",
|
||||
travelStartTime: matter.travelStartTime || "",
|
||||
travelEndTime: matter.travelEndTime || "",
|
||||
allowOverReimbursement: matter.allowOverReimbursement,
|
||||
overCostReimbursed: ledger.overCostReimbursed === true || ledger.overCostReimbursed === 1 || ledger.overCostReimbursed === "1",
|
||||
hasFamily: ledger.hasFamily === true || ledger.hasFamily === 1 || ledger.hasFamily === "1"
|
||||
})
|
||||
this.directRelativeForm = Object.assign(this.emptyDirectRelative(), {
|
||||
lineId: matter.lineId || "",
|
||||
lineName: matter.lineName || "",
|
||||
travelStartTime: matter.travelStartTime || "",
|
||||
travelEndTime: matter.travelEndTime || ""
|
||||
}, directRelative)
|
||||
this.familyData = (data.families || []).map(item => Object.assign(this.emptyFamily(), item))
|
||||
if (this.familyData.length > 0) {
|
||||
this.signupForm.hasFamily = true
|
||||
}
|
||||
if (!this.allowFamily || this.isDirectFamilyLine(this.signupForm)) {
|
||||
this.familyData = []
|
||||
this.signupForm.hasFamily = false
|
||||
}
|
||||
})
|
||||
},
|
||||
emptyFamily() {
|
||||
return {
|
||||
familyName: "",
|
||||
gender: "",
|
||||
idCard: "",
|
||||
mobile: "",
|
||||
age: null,
|
||||
bedType: "",
|
||||
bedInfo: "",
|
||||
intendedRoommate: "",
|
||||
staffJobNo: this.signupForm.jobNo || "",
|
||||
staffName: this.signupForm.userName || "",
|
||||
relationship: ""
|
||||
}
|
||||
},
|
||||
emptyDirectRelative() {
|
||||
return {
|
||||
relativeName: "",
|
||||
unitName: "",
|
||||
relationshipCode: "",
|
||||
relationshipName: "",
|
||||
lineId: this.signupForm.lineId || "",
|
||||
lineName: this.signupForm.lineName || "",
|
||||
travelStartTime: this.signupForm.travelStartTime || "",
|
||||
travelEndTime: this.signupForm.travelEndTime || ""
|
||||
}
|
||||
},
|
||||
isDirectFamilyLine(row) {
|
||||
return row && (row.directFamilyUnitLine === true || row.directFamilyUnitLine === 1 || row.directFamilyUnitLine === "1")
|
||||
},
|
||||
canApplyOverReimbursement(row) {
|
||||
return row && (row.allowOverReimbursement === true || row.allowOverReimbursement === 1 || row.allowOverReimbursement === "1")
|
||||
},
|
||||
addFamily() {
|
||||
if (!this.allowFamily || this.isDirectFamilyLine(this.signupForm)) return
|
||||
this.familyData.push(this.emptyFamily())
|
||||
this.familyActive = this.familyData.length - 1
|
||||
this.signupForm.hasFamily = true
|
||||
},
|
||||
removeFamily() {
|
||||
if (this.familyData.length === 0) return
|
||||
this.familyData.splice(this.familyActive, 1)
|
||||
this.familyActive = Math.max(0, this.familyActive - 1)
|
||||
this.signupForm.hasFamily = this.familyData.length > 0
|
||||
},
|
||||
openBedPicker(target) {
|
||||
if (this.bedTypeColumns.length === 0) {
|
||||
vant.Toast("暂无床型选项")
|
||||
return
|
||||
}
|
||||
this.bedPickerTarget = target
|
||||
this.bedPickerVisible = true
|
||||
},
|
||||
confirmBedType(value) {
|
||||
if (this.bedPickerTarget === "family") {
|
||||
this.currentFamily.bedType = value
|
||||
} else {
|
||||
this.signupForm.bedType = value
|
||||
}
|
||||
this.bedPickerVisible = false
|
||||
},
|
||||
openRelationshipPicker() {
|
||||
if (this.relationshipColumns.length === 0) {
|
||||
vant.Toast("暂无关系选项")
|
||||
return
|
||||
}
|
||||
this.relationshipPickerVisible = true
|
||||
},
|
||||
confirmRelationship(value) {
|
||||
this.currentFamily.relationship = value
|
||||
this.relationshipPickerVisible = false
|
||||
},
|
||||
openDirectRelativePicker() {
|
||||
if (this.directRelativeColumns.length === 0) {
|
||||
vant.Toast("暂无亲属关系选项")
|
||||
return
|
||||
}
|
||||
this.directRelativePickerVisible = true
|
||||
},
|
||||
confirmDirectRelative(value) {
|
||||
const item = value && value.item ? value.item : {}
|
||||
this.directRelativeForm.relationshipCode = item.code || ""
|
||||
this.directRelativeForm.relationshipName = item.name || value.text || ""
|
||||
this.directRelativePickerVisible = false
|
||||
},
|
||||
normalizeFamilyIdCard(row) {
|
||||
if (!row || !row.idCard) return
|
||||
row.idCard = String(row.idCard).trim().toUpperCase()
|
||||
},
|
||||
isValidIdCard(value) {
|
||||
if (!value) return false
|
||||
const idCard = String(value).trim().toUpperCase()
|
||||
return /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dX]$/.test(idCard)
|
||||
},
|
||||
validateFamilies() {
|
||||
if (!this.allowFamily || !this.signupForm.hasFamily) return true
|
||||
if (this.familyData.length === 0) {
|
||||
vant.Toast("请添加亲属信息")
|
||||
return false
|
||||
}
|
||||
const invalid = this.familyData.some(item => !item.familyName || !item.gender || !item.idCard || !item.relationship)
|
||||
if (invalid) {
|
||||
vant.Toast("请完善亲属姓名、性别、身份证号码和关系")
|
||||
return false
|
||||
}
|
||||
const invalidIdCard = this.familyData.some(item => !this.isValidIdCard(item.idCard))
|
||||
if (invalidIdCard) {
|
||||
vant.Toast("请输入正确的亲属身份证号码")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
validateDirectRelative() {
|
||||
if (!this.isDirectFamilyLine(this.signupForm)) return true
|
||||
if (!this.directRelativeForm.relativeName) {
|
||||
vant.Toast("请输入亲属姓名")
|
||||
return false
|
||||
}
|
||||
if (!this.directRelativeForm.unitName) {
|
||||
vant.Toast("请输入亲属所在单位")
|
||||
return false
|
||||
}
|
||||
if (!this.directRelativeForm.relationshipCode) {
|
||||
vant.Toast("请选择亲属关系")
|
||||
return false
|
||||
}
|
||||
if (!this.directRelativeForm.lineName) {
|
||||
vant.Toast("请输入直系亲属线路名称")
|
||||
return false
|
||||
}
|
||||
if (!this.directRelativeForm.travelStartTime || !this.directRelativeForm.travelEndTime) {
|
||||
vant.Toast("请选择直系亲属出行时间")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
submitSignup() {
|
||||
if (this.pageLoading || this.submitLoading) return
|
||||
if (!this.validateFamilies()) return
|
||||
if (!this.validateDirectRelative()) return
|
||||
const families = this.allowFamily && this.signupForm.hasFamily ? this.familyData : []
|
||||
const form = Object.assign({}, this.signupForm, {
|
||||
families: JSON.stringify(families),
|
||||
directRelative: this.isDirectFamilyLine(this.signupForm) ? JSON.stringify(this.directRelativeForm) : ""
|
||||
})
|
||||
this.submitLoading = true
|
||||
this.$axios.post("/platform/tour/signup/doSignup", form).then((res) => {
|
||||
this.submitLoading = false
|
||||
if (res.code === 0) {
|
||||
vant.Dialog.alert({
|
||||
title: "提示",
|
||||
message: res.msg || "报名成功",
|
||||
confirmButtonColor: "#1867b0"
|
||||
}).then(() => {
|
||||
window.location.href = "/platform/tour/signup/h5/signup"
|
||||
})
|
||||
} else {
|
||||
vant.Dialog.alert({
|
||||
title: "提示",
|
||||
message: res.msg || "报名失败",
|
||||
confirmButtonColor: "#1867b0"
|
||||
})
|
||||
}
|
||||
}).catch(() => {
|
||||
this.submitLoading = false
|
||||
vant.Toast("报名提交失败")
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,210 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style scoped>
|
||||
.tour-signup-h5 {
|
||||
min-height: 100vh;
|
||||
background: #f5f7fb;
|
||||
padding-bottom: 80px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-signup-banner {
|
||||
position: relative;
|
||||
height: 193px;
|
||||
overflow: hidden;
|
||||
background: #0f74bc;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tour-signup-swipe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tour-signup-swipe img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 193px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.tour-signup-banner::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(90deg, rgba(5, 83, 143, 0.72) 0%, rgba(5, 83, 143, 0.32) 48%, rgba(5, 83, 143, 0.08) 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tour-signup-banner__text {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
bottom: 20px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tour-signup-title {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.tour-signup-subtitle {
|
||||
margin-top: 8px;
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.tour-signup-card {
|
||||
margin: 10px 12px 0;
|
||||
padding: 14px 14px 16px;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.tour-signup-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #eef2f7;
|
||||
}
|
||||
|
||||
.tour-signup-card__title {
|
||||
color: #111827;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.tour-signup-card__year {
|
||||
flex-shrink: 0;
|
||||
color: #0f74bc;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.tour-signup-notice {
|
||||
margin-top: 14px;
|
||||
color: #334155;
|
||||
font-size: 15px;
|
||||
line-height: 1.8;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.tour-signup-notice /deep/ img,
|
||||
.tour-signup-notice /deep/ video {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.tour-signup-notice /deep/ #pdf-container {
|
||||
min-height: 560px;
|
||||
}
|
||||
|
||||
.tour-signup-empty {
|
||||
padding: 34px 0 26px;
|
||||
}
|
||||
|
||||
.tour-signup-footer {
|
||||
position: fixed;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
|
||||
background: rgba(255, 255, 255, 0.96);
|
||||
box-shadow: 0 -6px 18px rgba(15, 23, 42, 0.08);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" class="tour-signup-h5">
|
||||
<van-nav-bar title="疗休养报名" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
|
||||
<div class="tour-signup-banner">
|
||||
<van-swipe class="tour-signup-swipe" :autoplay="3500" indicator-color="white">
|
||||
<van-swipe-item v-for="item in bannerList" :key="item">
|
||||
<img :src="item" alt="疗休养报名">
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
<div class="tour-signup-banner__text">
|
||||
<div class="tour-signup-title">疗休养报名</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tour-signup-card">
|
||||
<div class="tour-signup-card__header">
|
||||
<div class="tour-signup-card__title">服务须知</div>
|
||||
<div class="tour-signup-card__year">{{ setting.year || currentYear }}年度</div>
|
||||
</div>
|
||||
|
||||
<van-skeleton title :row="8" :loading="loading">
|
||||
<div v-if="setting.serviceNotice" class="tour-signup-notice">
|
||||
<pdf-preview :key="serviceNoticeKey" :content="setting.serviceNotice"></pdf-preview>
|
||||
</div>
|
||||
<van-empty v-else class="tour-signup-empty" description="暂无服务须知"></van-empty>
|
||||
</van-skeleton>
|
||||
</div>
|
||||
|
||||
<div class="tour-signup-footer">
|
||||
<van-button block type="info" color="#0f74bc" round @click="confirmRead">我已阅读</van-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
components: {
|
||||
"pdf-preview": httpVueLoader("/components/plugins/sysFilePreview/PdfIndex.vue?v=" + new Date().getTime())
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
currentYear: new Date().getFullYear(),
|
||||
serviceNoticeKey: "",
|
||||
bannerList: [
|
||||
"/assets/platform/images/tour/tour-h5-banner-1.jpg",
|
||||
"/assets/platform/images/tour/tour-h5-banner-2.jpg"
|
||||
],
|
||||
setting: {
|
||||
year: "",
|
||||
configName: "",
|
||||
serviceNotice: ""
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
loadServiceNotice() {
|
||||
this.loading = true
|
||||
this.$axios.post("/platform/tour/signup/h5/serviceNotice").then((res) => {
|
||||
this.loading = false
|
||||
if (res.code === 0) {
|
||||
this.setting = Object.assign({}, this.setting, res.data || {})
|
||||
this.serviceNoticeKey = (this.setting.year || this.currentYear) + "_" + new Date().getTime()
|
||||
} else {
|
||||
vant.Toast(res.msg || "服务须知加载失败")
|
||||
}
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
confirmRead() {
|
||||
window.location.href = "/platform/tour/signup/h5/signup"
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadServiceNotice()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,559 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style scoped>
|
||||
#app {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tour-detail-page {
|
||||
height: calc(100vh - 46px);
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 10px 70px;
|
||||
background: #f4f6f8;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-detail-header {
|
||||
padding: 14px 12px;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05);
|
||||
}
|
||||
|
||||
.tour-detail-title {
|
||||
color: #0f172a;
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.tour-detail-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.tour-line-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 20px;
|
||||
padding: 0 7px;
|
||||
border-radius: 4px;
|
||||
background: #0b75bd;
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.tour-detail-meta {
|
||||
margin-top: 10px;
|
||||
color: #334155;
|
||||
font-size: 13px;
|
||||
line-height: 1.7;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.tour-detail-meta-row {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tour-detail-meta-label {
|
||||
width: 72px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
color: #64748b;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tour-detail-meta-value {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tour-detail-section {
|
||||
margin-top: 10px;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 12px;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-detail-section-title {
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 8px;
|
||||
padding-left: 8px;
|
||||
border-left: 3px solid #0b75bd;
|
||||
color: #0f172a;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.tour-detail-content /deep/ img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.tour-detail-section-scroll {
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.tour-pdf-preview {
|
||||
min-height: 220px;
|
||||
}
|
||||
|
||||
.tour-pdf-status {
|
||||
padding: 16px 0;
|
||||
color: #64748b;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tour-pdf-toolbar {
|
||||
margin-bottom: 8px;
|
||||
color: #64748b;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.tour-pdf-pages {
|
||||
min-height: 180px;
|
||||
}
|
||||
|
||||
.tour-pdf-page {
|
||||
margin-bottom: 10px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
background: #f8fafc;
|
||||
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.08);
|
||||
}
|
||||
|
||||
.tour-pdf-page canvas {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tour-detail-empty {
|
||||
padding: 18px 0;
|
||||
color: #94a3b8;
|
||||
font-size: 13px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tour-detail-footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
padding: 9px 12px calc(9px + env(safe-area-inset-bottom));
|
||||
border-top: 1px solid #edf0f4;
|
||||
background: #ffffff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app">
|
||||
<van-nav-bar title="线路详情" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
|
||||
<div class="tour-detail-page">
|
||||
<van-loading v-if="detailLoading" size="24px" vertical>加载中...</van-loading>
|
||||
<template v-else>
|
||||
<div class="tour-detail-header">
|
||||
<div class="tour-detail-title">{{ lineDetail.lineName || signupDetail.lineName || '线路详情' }}</div>
|
||||
<div class="tour-detail-tags">
|
||||
<span v-if="lineDetail.lotName || signupDetail.lotName" class="tour-line-tag">{{ lineDetail.lotName || signupDetail.lotName }}</span>
|
||||
<span v-if="lineDetail.lineType || signupDetail.lineType" class="tour-line-tag">{{ lineDetail.lineType || signupDetail.lineType }}</span>
|
||||
<span v-if="signupDetail.unionName" class="tour-line-tag">{{ signupDetail.unionName }}</span>
|
||||
</div>
|
||||
<div class="tour-detail-meta">
|
||||
<div v-if="travelPeriod(signupDetail)" class="tour-detail-meta-row">
|
||||
<span class="tour-detail-meta-label"><span>出行时间</span><span>:</span></span>
|
||||
<span class="tour-detail-meta-value">{{ travelPeriod(signupDetail) }}</span>
|
||||
</div>
|
||||
<div class="tour-detail-meta-row">
|
||||
<span class="tour-detail-meta-label"><span>已报人数</span><span>:</span></span>
|
||||
<span class="tour-detail-meta-value">{{ signupDetail.signupCount || 0 }}(家属{{ signupDetail.familyCount || 0 }}人)</span>
|
||||
</div>
|
||||
<div class="tour-detail-meta-row">
|
||||
<span class="tour-detail-meta-label"><span>联系人</span><span>:</span></span>
|
||||
<span class="tour-detail-meta-value">{{ signupDetail.contactName || '暂无' }}</span>
|
||||
</div>
|
||||
<div class="tour-detail-meta-row">
|
||||
<span class="tour-detail-meta-label"><span>联系方式</span><span>:</span></span>
|
||||
<span class="tour-detail-meta-value">{{ lineDetail.contactPhone || signupDetail.contactPhone || '暂无' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tour-detail-section">
|
||||
<div class="tour-detail-section-title">线路介绍</div>
|
||||
<div class="tour-detail-section-scroll">
|
||||
<div v-if="lineDetail.lineContent && lineContentHref" class="tour-pdf-preview">
|
||||
<div v-if="pdfPageCount" class="tour-pdf-toolbar">
|
||||
已加载 {{ pdfRenderedPages }} / {{ pdfPageCount }} 页
|
||||
</div>
|
||||
<div ref="pdfPreviewContainer" class="tour-pdf-pages"></div>
|
||||
<div v-if="pdfLoading && pdfRenderedPages === 0" class="tour-pdf-status">
|
||||
<van-loading size="22px" vertical>正在加载第一页...</van-loading>
|
||||
</div>
|
||||
<div v-else-if="pdfLoading" class="tour-pdf-status">
|
||||
正在继续加载后续页面...
|
||||
</div>
|
||||
<div v-if="pdfError" class="tour-pdf-status">{{ pdfError }}</div>
|
||||
</div>
|
||||
<div v-else-if="lineDetail.lineContent" class="tour-detail-content" v-html="lineDetail.lineContent">
|
||||
</div>
|
||||
<div v-else class="tour-detail-empty">暂无线路介绍</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="tour-detail-footer">
|
||||
<van-button :type="detailActionType()" block round :disabled="detailActionDisabled()" @click="handleDetailAction">
|
||||
{{ detailActionText() }}
|
||||
</van-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
data() {
|
||||
return {
|
||||
detailLoading: true,
|
||||
lineDetail: {},
|
||||
signupDetail: {},
|
||||
lineContentHref: "",
|
||||
pdfDoc: null,
|
||||
pdfLoadingTask: null,
|
||||
pdfLoading: false,
|
||||
pdfError: "",
|
||||
pdfPageCount: 0,
|
||||
pdfRenderedPages: 0,
|
||||
pdfRenderToken: 0,
|
||||
matterId: GetQueryString("matterId") || "",
|
||||
lineId: GetQueryString("lineId") || ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
travelPeriod(row) {
|
||||
if (!row) {
|
||||
return ""
|
||||
}
|
||||
if (row.travelStartTime && row.travelEndTime) {
|
||||
return row.travelStartTime + " 至 " + row.travelEndTime
|
||||
}
|
||||
return row.travelPeriod || row.travelStartTime || row.travelEndTime || ""
|
||||
},
|
||||
toBoolean(value) {
|
||||
return value === true || value === 1 || value === "1" || value === "true" || value === "TRUE"
|
||||
},
|
||||
isSigned() {
|
||||
return !!(this.signupDetail && this.signupDetail.signed)
|
||||
},
|
||||
isApprovalSignup() {
|
||||
return this.isSigned() && !!this.signupDetail.instanceId
|
||||
},
|
||||
canRevokeSignup() {
|
||||
return this.isApprovalSignup() && this.toBoolean(this.signupDetail.canRevoke) && !!this.signupDetail.startTaskId
|
||||
},
|
||||
isFinishedSignup() {
|
||||
return this.isApprovalSignup() && Number(this.signupDetail.instanceState) === 20
|
||||
},
|
||||
isRejectedSignup() {
|
||||
return this.isApprovalSignup() && Number(this.signupDetail.instanceState) === 45
|
||||
},
|
||||
canModifySignup() {
|
||||
if (!this.isSigned()) {
|
||||
return true
|
||||
}
|
||||
if (!this.isApprovalSignup()) {
|
||||
return true
|
||||
}
|
||||
return !this.isFinishedSignup() && !this.isRejectedSignup() && this.signupDetail.taskKey === "startTask"
|
||||
},
|
||||
detailActionText() {
|
||||
if (this.isSigned()) {
|
||||
if (this.canRevokeSignup()) {
|
||||
return "撤回"
|
||||
}
|
||||
if (this.isFinishedSignup()) {
|
||||
return "已完成"
|
||||
}
|
||||
if (this.isRejectedSignup()) {
|
||||
return "已拒绝"
|
||||
}
|
||||
return this.canModifySignup() ? "修改报名" : "审核中"
|
||||
}
|
||||
return "我要报名"
|
||||
},
|
||||
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
|
||||
}
|
||||
this.onApply()
|
||||
},
|
||||
parseLineContentHref(content) {
|
||||
if (!content) {
|
||||
return ""
|
||||
}
|
||||
try {
|
||||
const parser = new DOMParser()
|
||||
const doc = parser.parseFromString(content, "text/html")
|
||||
const link = doc.querySelector("a[href]")
|
||||
return link ? link.getAttribute("href") : ""
|
||||
} catch (e) {
|
||||
return ""
|
||||
}
|
||||
},
|
||||
getPdfLib() {
|
||||
return window.pdfjsLib || window["pdfjs-dist/build/pdf"]
|
||||
},
|
||||
clearPdfPreview() {
|
||||
this.pdfRenderToken += 1
|
||||
this.pdfDoc = null
|
||||
this.pdfLoading = false
|
||||
this.pdfError = ""
|
||||
this.pdfPageCount = 0
|
||||
this.pdfRenderedPages = 0
|
||||
if (this.pdfLoadingTask && this.pdfLoadingTask.destroy) {
|
||||
try {
|
||||
this.pdfLoadingTask.destroy()
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
this.pdfLoadingTask = null
|
||||
if (this.$refs.pdfPreviewContainer) {
|
||||
this.$refs.pdfPreviewContainer.innerHTML = ""
|
||||
}
|
||||
},
|
||||
renderLinePdf() {
|
||||
this.clearPdfPreview()
|
||||
if (!this.lineContentHref) {
|
||||
return
|
||||
}
|
||||
const pdfLib = this.getPdfLib()
|
||||
if (!pdfLib || !pdfLib.getDocument) {
|
||||
this.pdfError = "PDF预览组件加载失败"
|
||||
return
|
||||
}
|
||||
if (pdfLib.GlobalWorkerOptions) {
|
||||
pdfLib.GlobalWorkerOptions.workerSrc = "/assets/platform/plugins/pdfJs/h5/pdf.worker.js"
|
||||
}
|
||||
const token = this.pdfRenderToken
|
||||
this.pdfLoading = true
|
||||
this.pdfLoadingTask = pdfLib.getDocument({
|
||||
url: this.lineContentHref,
|
||||
disableAutoFetch: false,
|
||||
disableStream: false,
|
||||
disableRange: false
|
||||
})
|
||||
this.pdfLoadingTask.promise.then((pdf) => {
|
||||
if (token !== this.pdfRenderToken) {
|
||||
return
|
||||
}
|
||||
this.pdfDoc = pdf
|
||||
this.pdfPageCount = pdf.numPages || 0
|
||||
return this.renderPdfPage(1, token)
|
||||
}).then(() => {
|
||||
if (token !== this.pdfRenderToken) {
|
||||
return
|
||||
}
|
||||
this.renderRestPdfPages(2, token)
|
||||
}).catch(() => {
|
||||
if (token !== this.pdfRenderToken) {
|
||||
return
|
||||
}
|
||||
this.pdfLoading = false
|
||||
this.pdfError = "PDF加载失败,请稍后重试"
|
||||
})
|
||||
},
|
||||
renderPdfPage(pageNumber, token) {
|
||||
if (!this.pdfDoc || token !== this.pdfRenderToken || pageNumber > this.pdfPageCount) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
return this.pdfDoc.getPage(pageNumber).then((page) => {
|
||||
if (token !== this.pdfRenderToken || !this.$refs.pdfPreviewContainer) {
|
||||
return
|
||||
}
|
||||
const container = this.$refs.pdfPreviewContainer
|
||||
const baseViewport = page.getViewport({scale: 1})
|
||||
const containerWidth = Math.max(container.clientWidth || 0, window.innerWidth - 44)
|
||||
const cssScale = containerWidth / baseViewport.width
|
||||
const outputScale = Math.min(window.devicePixelRatio || 1, 2)
|
||||
const viewport = page.getViewport({scale: cssScale})
|
||||
const pageEl = document.createElement("div")
|
||||
pageEl.className = "tour-pdf-page"
|
||||
const canvas = document.createElement("canvas")
|
||||
const context = canvas.getContext("2d")
|
||||
canvas.width = Math.floor(viewport.width * outputScale)
|
||||
canvas.height = Math.floor(viewport.height * outputScale)
|
||||
canvas.style.width = Math.floor(viewport.width) + "px"
|
||||
canvas.style.height = Math.floor(viewport.height) + "px"
|
||||
pageEl.appendChild(canvas)
|
||||
container.appendChild(pageEl)
|
||||
const renderContext = {
|
||||
canvasContext: context,
|
||||
viewport: viewport,
|
||||
transform: outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null
|
||||
}
|
||||
return page.render(renderContext).promise.then(() => {
|
||||
if (token === this.pdfRenderToken) {
|
||||
this.pdfRenderedPages = Math.max(this.pdfRenderedPages, pageNumber)
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
renderRestPdfPages(pageNumber, token) {
|
||||
if (token !== this.pdfRenderToken || !this.pdfDoc || pageNumber > this.pdfPageCount) {
|
||||
this.pdfLoading = false
|
||||
return
|
||||
}
|
||||
setTimeout(() => {
|
||||
this.renderPdfPage(pageNumber, token).then(() => {
|
||||
this.renderRestPdfPages(pageNumber + 1, token)
|
||||
}).catch(() => {
|
||||
if (token === this.pdfRenderToken) {
|
||||
this.pdfLoading = false
|
||||
this.pdfError = "部分页面加载失败"
|
||||
}
|
||||
})
|
||||
}, 120)
|
||||
},
|
||||
loadDetail() {
|
||||
if (!this.matterId || !this.lineId) {
|
||||
this.detailLoading = false
|
||||
vant.Dialog.alert({
|
||||
title: "提示",
|
||||
message: "线路参数不完整",
|
||||
confirmButtonColor: "#1867b0"
|
||||
})
|
||||
return
|
||||
}
|
||||
Promise.all([
|
||||
this.$axios.post("/platform/tour/signup/lineDetail", { lineId: this.lineId }),
|
||||
this.$axios.post("/platform/tour/signup/signupDetail", { matterId: this.matterId })
|
||||
]).then(([lineRes, signupRes]) => {
|
||||
this.detailLoading = false
|
||||
if (lineRes.code !== 0) {
|
||||
vant.Toast(lineRes.msg || "线路详情加载失败")
|
||||
} else {
|
||||
this.lineDetail = lineRes.data || {}
|
||||
this.lineContentHref = this.parseLineContentHref(this.lineDetail.lineContent)
|
||||
this.$nextTick(() => {
|
||||
this.renderLinePdf()
|
||||
})
|
||||
}
|
||||
if (signupRes.code !== 0) {
|
||||
vant.Toast(signupRes.msg || "报名信息加载失败")
|
||||
} else {
|
||||
const data = signupRes.data || {}
|
||||
const process = data.process || {}
|
||||
this.signupDetail = Object.assign({}, data.matter || {}, {
|
||||
signed: !!(data.ledger && data.ledger.id)
|
||||
}, process)
|
||||
}
|
||||
}).catch(() => {
|
||||
this.detailLoading = false
|
||||
vant.Toast("线路详情加载失败")
|
||||
})
|
||||
},
|
||||
onApply() {
|
||||
if (!this.matterId) {
|
||||
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"
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
revokeSignup() {
|
||||
vant.Dialog.confirm({
|
||||
title: "提示",
|
||||
message: "您确定要撤回吗?",
|
||||
confirmButtonColor: "#ee2f2f"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/common/revokeTask", { taskId: this.signupDetail.startTaskId }).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
vant.Toast.success(resp.msg || "撤回成功")
|
||||
this.loadDetail()
|
||||
} else {
|
||||
vant.Dialog.alert({ title: "提示", message: resp.msg || "撤回失败", confirmButtonColor: "#1867b0" })
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadDetail()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.clearPdfPreview()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,787 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style scoped>
|
||||
.tour-line-page {
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f4f6f8;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-line-scroll {
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding-bottom: 18px;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-line-toolbar {
|
||||
padding: 10px 0 8px 16px;
|
||||
background: #f4f6f8;
|
||||
}
|
||||
|
||||
.tour-line-toolbar /deep/ .van-search {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.tour-line-toolbar .van-search {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.tour-line-toolbar /deep/ .van-search__content {
|
||||
height: 38px;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.tour-line-toolbar .van-search__content {
|
||||
height: 38px;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.tour-line-toolbar /deep/ .van-search__action {
|
||||
padding: 0 0 0 6px;
|
||||
}
|
||||
|
||||
.tour-line-toolbar .van-search__action {
|
||||
padding: 0 0 0 6px;
|
||||
}
|
||||
|
||||
.tour-line-search-button {
|
||||
width: 42px;
|
||||
height: 38px;
|
||||
border: 0;
|
||||
border-radius: 8px;
|
||||
background: #1e88e5;
|
||||
color: #ffffff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 20px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.tour-line-search-row {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-line-union-entry {
|
||||
width: 48px;
|
||||
min-height: 42px;
|
||||
flex-shrink: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
color: #64748b;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 2px;
|
||||
font-size: 11px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.tour-line-union-entry .van-icon {
|
||||
color: #64748b;
|
||||
font-size: 24px;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.tour-line-union-entry--active,
|
||||
.tour-line-union-entry--active .van-icon {
|
||||
color: #0b75bd;
|
||||
}
|
||||
|
||||
.tour-line-union-popup {
|
||||
max-height: 68vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tour-line-union-popup__title {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 1;
|
||||
padding: 14px 16px 10px;
|
||||
background: #ffffff;
|
||||
color: #0f172a;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-line-union-popup__list {
|
||||
overflow-y: auto;
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
|
||||
.tour-line-union-popup__item {
|
||||
min-height: 48px;
|
||||
transition: background-color 0.18s ease, color 0.18s ease;
|
||||
}
|
||||
|
||||
.tour-line-union-popup__item:hover,
|
||||
.tour-line-union-popup__item:active {
|
||||
background: rgba(30, 136, 229, 0.1);
|
||||
}
|
||||
|
||||
.tour-line-union-popup__item:hover /deep/ .van-cell__title,
|
||||
.tour-line-union-popup__item:active /deep/ .van-cell__title {
|
||||
color: #0b75bd;
|
||||
}
|
||||
|
||||
.tour-line-union-popup__item--active /deep/ .van-cell__title {
|
||||
color: #0b75bd;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.tour-line-shortcuts {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.tour-line-shortcut {
|
||||
min-height: 76px;
|
||||
padding: 12px 8px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-line-shortcut--active {
|
||||
border-color: #0b75bd;
|
||||
background: rgba(11, 117, 189, 0.08);
|
||||
}
|
||||
|
||||
.tour-line-shortcut__icon {
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
flex-shrink: 0;
|
||||
display: block;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.tour-line-shortcut__text {
|
||||
min-width: 0;
|
||||
color: #0f172a;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
line-height: 1.3;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tour-line-shortcut__hint {
|
||||
margin-top: 2px;
|
||||
color: #64748b;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.tour-line-list {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.tour-line-card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
min-height: 112px;
|
||||
margin: 8px 0;
|
||||
padding: 10px;
|
||||
border-radius: 6px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-line-thumb {
|
||||
width: 145px;
|
||||
height: 98px;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 6px;
|
||||
background: #e5edf6;
|
||||
}
|
||||
|
||||
.tour-line-thumb img,
|
||||
.tour-line-thumb .van-image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.tour-line-thumb-empty {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #8a98a8;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.tour-line-main {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.tour-line-signed-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
border-radius: 50%;
|
||||
background: #a78bfa;
|
||||
color: #ffffff;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
box-shadow: 0 2px 5px rgba(167, 139, 250, 0.32);
|
||||
}
|
||||
|
||||
.tour-line-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.tour-line-title {
|
||||
min-width: 0;
|
||||
color: #0f172a;
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.tour-line-type-badge {
|
||||
height: 18px;
|
||||
flex-shrink: 0;
|
||||
padding: 0 5px;
|
||||
border: 1px solid #ffd6a8;
|
||||
border-radius: 4px;
|
||||
background: #fff3e5;
|
||||
color: #d97706;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
line-height: 17px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-line-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.tour-line-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 20px;
|
||||
padding: 0 7px;
|
||||
border-radius: 4px;
|
||||
background: #39a9ed;
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.tour-line-info {
|
||||
margin-top: 5px;
|
||||
color: #111827;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.tour-line-info span {
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.tour-line-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.tour-line-action {
|
||||
min-width: 70px;
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid #1e88e5;
|
||||
border-radius: 14px;
|
||||
background: #1e88e5;
|
||||
color: #ffffff;
|
||||
font-size: 12px;
|
||||
line-height: 26px;
|
||||
}
|
||||
|
||||
.tour-line-action--danger {
|
||||
border-color: #ee2f2f;
|
||||
background: #ee2f2f;
|
||||
}
|
||||
|
||||
.tour-line-action--disabled {
|
||||
border-color: #d1d5db;
|
||||
background: #e5e7eb;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
@media (max-width: 360px) {
|
||||
.tour-line-card {
|
||||
gap: 9px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.tour-line-thumb {
|
||||
width: 126px;
|
||||
height: 92px;
|
||||
}
|
||||
|
||||
.tour-line-title {
|
||||
font-size: 15px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" class="tour-line-page">
|
||||
<van-nav-bar title="疗休养线路" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
|
||||
<div class="tour-line-toolbar">
|
||||
<div class="tour-line-search-row">
|
||||
<van-search
|
||||
v-model="pageForm.lineName"
|
||||
show-action
|
||||
clearable
|
||||
placeholder="请输入线路名称搜索"
|
||||
@search="doSearch"
|
||||
@clear="doSearch">
|
||||
<template #action>
|
||||
<button type="button" class="tour-line-search-button" @click="doSearch">
|
||||
<van-icon name="search"></van-icon>
|
||||
</button>
|
||||
</template>
|
||||
</van-search>
|
||||
<button
|
||||
type="button"
|
||||
class="tour-line-union-entry"
|
||||
:class="{'tour-line-union-entry--active': !!pageForm.unionId}"
|
||||
@click="openUnionPopup">
|
||||
<van-icon name="friends-o"></van-icon>
|
||||
<span>分工会</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="tour-line-shortcuts">
|
||||
<div
|
||||
class="tour-line-shortcut"
|
||||
:class="{'tour-line-shortcut--active': pageForm.lineType === '省内线路' && pageForm.directFamilyUnitLine === null}"
|
||||
@click="toggleLineType('省内线路')">
|
||||
<img class="tour-line-shortcut__icon" src="/assets/platform/images/tour/tour-in-province.png" alt="省内线路">
|
||||
<div class="tour-line-shortcut__text">
|
||||
<div>省内线路</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tour-line-shortcut"
|
||||
:class="{'tour-line-shortcut--active': pageForm.lineType === '省外线路' && pageForm.directFamilyUnitLine === null}"
|
||||
@click="toggleLineType('省外线路')">
|
||||
<img class="tour-line-shortcut__icon" src="/assets/platform/images/tour/tour-out-province.png" alt="省外线路">
|
||||
<div class="tour-line-shortcut__text">
|
||||
<div>省外线路</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="tour-line-shortcut"
|
||||
:class="{'tour-line-shortcut--active': pageForm.directFamilyUnitLine === true}"
|
||||
@click="toggleDirectFamilyLine">
|
||||
<img class="tour-line-shortcut__icon" src="/assets/platform/images/tour/tour-direct-family.jpg" alt="直系亲属">
|
||||
<div class="tour-line-shortcut__text">
|
||||
<div>直系亲属</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tour-line-scroll">
|
||||
<van-list
|
||||
v-model="loading"
|
||||
:finished="finished"
|
||||
finished-text="没有更多了"
|
||||
@load="loadData">
|
||||
<div class="tour-line-list">
|
||||
<div class="tour-line-card" v-for="row in list" :key="row.matterId" @click="onLine(row)">
|
||||
<div class="tour-line-thumb">
|
||||
<van-image v-if="thumbUrl(row)" :src="thumbUrl(row)" fit="cover"></van-image>
|
||||
<div v-else class="tour-line-thumb-empty">暂无图片</div>
|
||||
</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>
|
||||
<div class="tour-line-tags">
|
||||
<span v-if="row.lotName" class="tour-line-tag">{{ row.lotName }}</span>
|
||||
<span v-if="row.unionName" class="tour-line-tag">{{ row.unionName }}</span>
|
||||
</div>
|
||||
<div class="tour-line-info">
|
||||
<div><span>出行开始时间:</span>{{ row.travelStartTime || '暂无' }}</div>
|
||||
<div><span>出行结束时间:</span>{{ row.travelEndTime || '暂无' }}</div>
|
||||
<!-- 临时屏蔽旅行社信息,保留字段和接口便于后续恢复。
|
||||
<div><span>旅行社:</span>{{ row.travelAgencyName || '暂无' }}</div>
|
||||
-->
|
||||
</div>
|
||||
<div class="tour-line-actions">
|
||||
<button
|
||||
v-if="showSignupAction(row)"
|
||||
type="button"
|
||||
class="tour-line-action"
|
||||
:class="{
|
||||
'tour-line-action--danger': canRevokeSignup(row),
|
||||
'tour-line-action--disabled': signupActionDisabled(row)
|
||||
}"
|
||||
:disabled="signupActionDisabled(row)"
|
||||
@click.stop="handleSignupAction(row)">
|
||||
{{ signupActionText(row) }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</van-list>
|
||||
|
||||
<van-empty v-if="!loading && list.length === 0" description="暂无线路"></van-empty>
|
||||
</div>
|
||||
|
||||
<van-popup v-model="showUnionPopup" position="bottom" round>
|
||||
<div class="tour-line-union-popup">
|
||||
<div class="tour-line-union-popup__title">选择分工会</div>
|
||||
<div class="tour-line-union-popup__list">
|
||||
<van-cell
|
||||
v-for="item in unionList"
|
||||
:key="item.id || 'all'"
|
||||
clickable
|
||||
class="tour-line-union-popup__item"
|
||||
:class="{'tour-line-union-popup__item--active': pageForm.unionId === item.id}"
|
||||
:title="item.name"
|
||||
@click="selectUnion(item)">
|
||||
<template #right-icon>
|
||||
<van-icon v-if="pageForm.unionId === item.id" name="success" color="#0b75bd"></van-icon>
|
||||
</template>
|
||||
</van-cell>
|
||||
</div>
|
||||
</div>
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
data() {
|
||||
return {
|
||||
list: [],
|
||||
loading: false,
|
||||
finished: false,
|
||||
lineChecking: false,
|
||||
showUnionPopup: false,
|
||||
unionList: [
|
||||
{id: "", name: "全部分工会"}
|
||||
],
|
||||
pageForm: {
|
||||
pageNumber: 1,
|
||||
pageSize: 10,
|
||||
totalCount: 0,
|
||||
year: new Date().getFullYear(),
|
||||
lineName: "",
|
||||
lineType: "",
|
||||
unionId: "",
|
||||
directFamilyUnitLine: null,
|
||||
pageOrderName: "lineName",
|
||||
pageOrderBy: "ascending"
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
thumbUrl(row) {
|
||||
return row.lineMobileThumb || row.agencyMobileThumb || ""
|
||||
},
|
||||
lineTypeBadge(lineType) {
|
||||
if (lineType === "省内线路" || lineType === "省内") {
|
||||
return "省内"
|
||||
}
|
||||
if (lineType === "省外线路" || lineType === "省外") {
|
||||
return "省外"
|
||||
}
|
||||
return ""
|
||||
},
|
||||
isSigned(row) {
|
||||
return !!(row && (row.signed === true || row.signed === 1 || row.signed === "1" || row.ledgerId))
|
||||
},
|
||||
isApprovalSignup(row) {
|
||||
return !!(row && this.isSigned(row) && row.instanceId)
|
||||
},
|
||||
canRevokeSignup(row) {
|
||||
return this.isApprovalSignup(row) && this.toBoolean(row.canRevoke) && !!row.startTaskId
|
||||
},
|
||||
isFinishedSignup(row) {
|
||||
return this.isApprovalSignup(row) && Number(row.instanceState) === 20
|
||||
},
|
||||
isRejectedSignup(row) {
|
||||
return this.isApprovalSignup(row) && Number(row.instanceState) === 45
|
||||
},
|
||||
canModifySignup(row) {
|
||||
if (!row || !this.isSigned(row)) {
|
||||
return true
|
||||
}
|
||||
if (!this.isApprovalSignup(row)) {
|
||||
return true
|
||||
}
|
||||
return !this.isFinishedSignup(row) && !this.isRejectedSignup(row) && row.taskKey === "startTask"
|
||||
},
|
||||
signupActionText(row) {
|
||||
if (this.isSigned(row)) {
|
||||
if (this.canRevokeSignup(row)) {
|
||||
return "撤回"
|
||||
}
|
||||
if (this.isRejectedSignup(row)) {
|
||||
return "已拒绝"
|
||||
}
|
||||
return this.canModifySignup(row) ? "修改" : "审核中"
|
||||
}
|
||||
return "报名"
|
||||
},
|
||||
showSignupAction(row) {
|
||||
return this.isSigned(row) && this.canModifySignup(row)
|
||||
},
|
||||
signupActionDisabled(row) {
|
||||
return !!(this.isSigned(row) && this.isApprovalSignup(row) && (this.isRejectedSignup(row) || (!this.canRevokeSignup(row) && !this.canModifySignup(row))))
|
||||
},
|
||||
toBoolean(value) {
|
||||
return value === true || value === 1 || value === "1" || value === "true" || value === "TRUE"
|
||||
},
|
||||
handleSignupAction(row) {
|
||||
if (this.signupActionDisabled(row)) {
|
||||
return
|
||||
}
|
||||
if (this.canRevokeSignup(row)) {
|
||||
this.revokeSignup(row)
|
||||
return
|
||||
}
|
||||
this.goApply(row)
|
||||
},
|
||||
goApply(row) {
|
||||
if (!row || !row.matterId) {
|
||||
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"
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
revokeSignup(row) {
|
||||
vant.Dialog.confirm({
|
||||
title: "提示",
|
||||
message: "您确定要撤回吗?",
|
||||
confirmButtonColor: "#ee2f2f"
|
||||
}).then(() => {
|
||||
this.$axios.post("/flow/common/revokeTask", { taskId: row.startTaskId }).then((resp) => {
|
||||
if (resp.code === 0) {
|
||||
vant.Toast.success(resp.msg || "撤回成功")
|
||||
this.doSearch()
|
||||
} else {
|
||||
vant.Dialog.alert({ title: "提示", message: resp.msg || "撤回失败", confirmButtonColor: "#1867b0" })
|
||||
}
|
||||
})
|
||||
}).catch(() => {})
|
||||
},
|
||||
doSearch() {
|
||||
this.pageForm.pageNumber = 1
|
||||
this.pageForm.totalCount = 0
|
||||
this.list = []
|
||||
this.finished = false
|
||||
this.loadData()
|
||||
},
|
||||
toggleLineType(lineType) {
|
||||
const active = this.pageForm.lineType === lineType && this.pageForm.directFamilyUnitLine === null
|
||||
this.pageForm.lineType = active ? "" : lineType
|
||||
this.pageForm.directFamilyUnitLine = null
|
||||
this.doSearch()
|
||||
},
|
||||
toggleDirectFamilyLine() {
|
||||
const active = this.pageForm.directFamilyUnitLine === true
|
||||
this.pageForm.directFamilyUnitLine = active ? null : true
|
||||
this.pageForm.lineType = ""
|
||||
this.doSearch()
|
||||
},
|
||||
openUnionPopup() {
|
||||
this.showUnionPopup = true
|
||||
if (this.unionList.length <= 1) {
|
||||
this.loadUnionOptions()
|
||||
}
|
||||
},
|
||||
selectUnion(item) {
|
||||
const unionId = item && item.id ? item.id : ""
|
||||
this.showUnionPopup = false
|
||||
if (this.pageForm.unionId === unionId) {
|
||||
return
|
||||
}
|
||||
this.pageForm.unionId = unionId
|
||||
this.doSearch()
|
||||
},
|
||||
loadUnionOptions() {
|
||||
this.$axios.post("/platform/tour/signup/unionOptions").then((res) => {
|
||||
if (res.code !== 0) {
|
||||
return
|
||||
}
|
||||
const rows = res.data || []
|
||||
this.unionList = [{id: "", name: "全部分工会"}].concat(rows.map((item) => {
|
||||
return {
|
||||
id: item.id || "",
|
||||
name: item.name || "未命名分工会"
|
||||
}
|
||||
}))
|
||||
})
|
||||
},
|
||||
loadData() {
|
||||
if (this.finished) {
|
||||
this.loading = false
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
this.$axios.post("/platform/tour/signup/h5/pageData", this.pageForm).then((res) => {
|
||||
this.loading = false
|
||||
if (res.code !== 0) {
|
||||
vant.Toast(res.msg || "线路加载失败")
|
||||
this.finished = true
|
||||
return
|
||||
}
|
||||
const data = res.data || {}
|
||||
const rows = data.list || []
|
||||
this.pageForm.totalCount = data.totalCount || 0
|
||||
this.list = this.pageForm.pageNumber === 1 ? rows : this.list.concat(rows)
|
||||
if (this.list.length >= this.pageForm.totalCount || rows.length === 0) {
|
||||
this.finished = true
|
||||
} else {
|
||||
this.pageForm.pageNumber += 1
|
||||
}
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
this.finished = true
|
||||
})
|
||||
},
|
||||
onLine(row) {
|
||||
if (!row || !row.lineId || !row.matterId) {
|
||||
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"
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadUnionOptions()
|
||||
this.loadData()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user