Merge remote-tracking branch 'origin/feature_疗休养模块UI优化' into release_20260831

This commit is contained in:
2026-08-31 18:05:55 +08:00
30 changed files with 3122 additions and 370 deletions
@@ -1,5 +1,7 @@
package com.budwk.app.zhgh.dayofficework.tour.controller;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.constant.RoleConstant;
@@ -12,7 +14,7 @@ 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.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
@@ -193,7 +195,7 @@ public class TourGroupController {
t.id,
t.jobNo,
t.userName,
'' AS mobile,
COALESCE(NULLIF(t.mobile, ''), NULLIF(vu.mobile, ''), '') AS mobile,
t.idCard,
t.unionId,
t.unionName,
@@ -201,6 +203,7 @@ public class TourGroupController {
FROM tour_ledger t
INNER JOIN tour_matter m ON m.id = t.matterId
INNER JOIN tour_line l ON l.id = m.lineId
LEFT JOIN vw_user vu ON vu.loginname = t.jobNo
$condition
ORDER BY $orderColumn $orderBy, t.signupTime DESC, t.createdAt DESC
""");
@@ -212,7 +215,6 @@ public class TourGroupController {
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);
}
@@ -231,7 +233,7 @@ public class TourGroupController {
List<NutMap> list = queryExportParticipants(matterId);
Workbook workbook = buildParticipantsWorkbook(matter, list);
String lineName = StrUtil.blankToDefault(matter.getString("lineName", ""), "线路");
CommonDownloadUtil.download(lineName + "参加人员名单.xls", workbook, response);
CommonDownloadUtil.download(lineName + "参加人员名单.xlsx", workbook, response);
}
private Cnd buildSignupQueryCnd(String matterId, String keyword, String unionId) {
@@ -300,7 +302,7 @@ public class TourGroupController {
t.jobNo,
t.userName,
t.idCard,
vu.mobile,
COALESCE(NULLIF(t.mobile, ''), NULLIF(vu.mobile, ''), '') AS mobile,
t.unionName,
l.lineName,
CASE
@@ -343,7 +345,8 @@ public class TourGroupController {
ledgerId,
familyName,
relationship,
idCard
idCard,
mobile
FROM tour_ledger_family
WHERE delFlag = 0
AND ledgerId IN (@ledgerIds)
@@ -363,7 +366,9 @@ public class TourGroupController {
}
private Workbook buildParticipantsWorkbook(NutMap matter, List<NutMap> list) {
Workbook workbook = new HSSFWorkbook();
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("参加人员名单");
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 10));
sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, 10));
@@ -414,7 +419,7 @@ public class TourGroupController {
Row familyRow = sheet.createRow(rowIndex++);
familyRow.setHeightInPoints(24);
setParticipantRow(familyRow, seq++, "", family.getString("familyName", ""),
family.getString("idCard", ""), "", "",
family.getString("idCard", ""), family.getString("mobile", ""), "",
item.getString("lineName", matter.getString("lineName", "")),
item.getString("travelPeriod", matter.getString("travelPeriod", "")),
family.getString("relationship", ""), item.getString("userName", ""), bodyStyle);
@@ -481,35 +486,6 @@ public class TourGroupController {
}
}
private void fillSignupMobile(List<NutMap> list) {
if (list == null || list.isEmpty()) {
return;
}
List<String> jobNos = new ArrayList<>();
for (NutMap item : list) {
String jobNo = item.getString("jobNo", "");
if (StrUtil.isNotBlank(jobNo) && !jobNos.contains(jobNo)) {
jobNos.add(jobNo);
}
}
if (jobNos.isEmpty()) {
return;
}
Sql sql = Sqls.create("SELECT loginname, mobile FROM vw_user WHERE loginname IN (@jobNos)");
sql.setParam("jobNos", jobNos.toArray(new String[0]));
sql.setCallback(Sqls.callback.maps());
tourLedgerService.dao().execute(sql);
Map<String, String> mobileMap = new HashMap<>();
for (NutMap user : sql.getList(NutMap.class)) {
mobileMap.put(user.getString("loginname", ""), user.getString("mobile", ""));
}
for (NutMap item : list) {
item.put("mobile", mobileMap.getOrDefault(item.getString("jobNo", ""), ""));
}
}
private Cnd buildQueryCnd(Integer year, String lineName, String lineType, String unionId) {
Cnd cnd = Cnd.NEW();
cnd.and("m.delFlag", "=", false);
@@ -27,7 +27,7 @@ import com.budwk.app.zhgh.dayofficework.tour.service.TourLedgerDirectRelativeSer
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.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
@@ -183,7 +183,7 @@ public class TourLedgerController {
if (StrUtil.isBlank(id)) {
return Result.error("参数错误");
}
TourLedger ledger = tourLedgerService.fetch(id);
TourLedger ledger = tourLedgerService.fillMobileFallback(tourLedgerService.fetch(id));
if (ledger == null) {
return Result.error("台账记录不存在");
}
@@ -328,7 +328,7 @@ public class TourLedgerController {
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"));
zipOutputStream.putNextEntry(new ZipEntry("无数据报名汇总表.xlsx"));
try (Workbook workbook = buildUnionSignupWorkbook("无数据", Collections.emptyList())) {
workbook.write(zipOutputStream);
}
@@ -336,7 +336,7 @@ public class TourLedgerController {
} 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");
String fileName = safeFileName(unionName + "报名汇总表.xlsx");
zipOutputStream.putNextEntry(new ZipEntry(fileName));
try (Workbook workbook = buildUnionSignupWorkbook(unionName, entry.getValue())) {
workbook.write(zipOutputStream);
@@ -360,7 +360,7 @@ public class TourLedgerController {
t.unionId,
COALESCE(NULLIF(t.unionName, ''), su.name, vu.unionName, '') AS unionName,
COALESCE(NULLIF(su.unionCode, ''), vu.unionCode, '') AS unionCode,
COALESCE(NULLIF(vu.mobile, ''), '') AS mobile,
COALESCE(NULLIF(t.mobile, ''), NULLIF(vu.mobile, ''), '') AS mobile,
COALESCE(NULLIF(l.lineName, ''), t.lineName, '') AS lineName,
CASE
WHEN IFNULL(m.travelStartTime, '') <> '' AND IFNULL(m.travelEndTime, '') <> ''
@@ -415,7 +415,9 @@ public class TourLedgerController {
}
private Workbook buildUnionSignupWorkbook(String unionName, List<NutMap> rows) {
HSSFWorkbook workbook = new HSSFWorkbook();
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
XSSFWorkbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("报名汇总表");
sheet.setColumnWidth(0, 8 * 256);
sheet.setColumnWidth(1, 14 * 256);
@@ -407,6 +407,14 @@ public class TourMySignupController {
return Result.success(sql.getList(Sys_dict.class));
}
/**
* 修改本人疗休养报名。
*
* @param ledger 报名台账,包含报名人身份证号码和手机号
* @param families 亲属信息JSON数组,包含姓名、年龄、性别、关系及联系方式
* @param directRelative 直系亲属线路信息JSON,非直系亲属线路可为空
* @return Result,成功时返回修改结果,校验失败时返回具体业务提示
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.mysignup")
@@ -571,9 +579,9 @@ public class TourMySignupController {
if (StrUtil.isBlank(id)) {
return null;
}
return tourLedgerService.fetch(Cnd.where(TourLedger::getDelFlag, "=", false)
return tourLedgerService.fillMobileFallback(tourLedgerService.fetch(Cnd.where(TourLedger::getDelFlag, "=", false)
.and(TourLedger::getId, "=", id)
.and(TourLedger::getJobNo, "=", currentJobNo()));
.and(TourLedger::getJobNo, "=", currentJobNo())));
}
private Result checkSignup(TourLedger ledger) {
@@ -592,6 +600,12 @@ public class TourMySignupController {
if (StrUtil.isBlank(ledger.getLineName())) {
return Result.error("报名线路不能为空");
}
if (StrUtil.isBlank(ledger.getIdCard())) {
return Result.error("身份证号码不能为空");
}
if (StrUtil.isBlank(ledger.getMobile())) {
return Result.error("手机号不能为空");
}
return null;
}
@@ -1005,15 +1019,20 @@ public class TourMySignupController {
if (family == null) {
continue;
}
if (StrUtil.isBlank(family.getFamilyName()) || StrUtil.isBlank(family.getGender())
|| StrUtil.isBlank(family.getIdCard()) || StrUtil.isBlank(family.getRelationship())) {
return Result.error("请完善亲属姓名、性别、身份证号码和关系");
if (StrUtil.isBlank(family.getFamilyName()) || family.getAge() == null
|| StrUtil.isBlank(family.getGender()) || StrUtil.isBlank(family.getRelationship())) {
return Result.error("请完善亲属姓名、年龄、性别和与本人关系");
}
String idCard = family.getIdCard().trim().toUpperCase();
if (!isValidIdCard(idCard)) {
// 年满16周岁的亲属必须提供身份证号码和手机号;未满16周岁时两项允许为空。
if (family.getAge() >= 16 && (StrUtil.isBlank(family.getIdCard()) || StrUtil.isBlank(family.getMobile()))) {
return Result.error("年满16周岁的亲属必须填写身份证号码和手机号");
}
if (StrUtil.isNotBlank(family.getIdCard()) && !isValidIdCard(family.getIdCard().trim().toUpperCase())) {
return Result.error("请输入正确的身份证号码");
}
family.setIdCard(idCard);
if (StrUtil.isNotBlank(family.getIdCard())) {
family.setIdCard(family.getIdCard().trim().toUpperCase());
}
}
return null;
}
@@ -461,7 +461,7 @@ public class TourSchoolUnionApprovalController {
sql.setParam("actorId", SecurityUtil.getUserId());
sql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(sql);
return sql.getInt() > 0 ? tourLedgerService.fetch(id) : null;
return sql.getInt() > 0 ? tourLedgerService.fillMobileFallback(tourLedgerService.fetch(id)) : null;
}
private String getCurrentLineName(TourLedger ledger) {
@@ -472,7 +472,7 @@ public class TourSignupController {
.and(TourLedger::getMatterId, "=", matter.getString("matterId"))
.and(TourLedger::getJobNo, "=", staff.getString("jobNo"));
ledgerCnd.desc(TourLedger::getCreatedAt);
TourLedger ledger = tourLedgerService.fetch(ledgerCnd);
TourLedger ledger = tourLedgerService.fillMobileFallback(tourLedgerService.fetch(ledgerCnd));
TourMatter matterEntity = tourMatterService.fetch(matter.getString("matterId"));
TourLedger ruleLedger = new TourLedger();
ruleLedger.setId(ledger == null ? null : ledger.getId());
@@ -661,10 +661,19 @@ public class TourSignupController {
return Result.success(sql.getList(Sys_dict.class));
}
/**
* 保存疗休养报名。
*
* @param ledger 报名台账基础信息,包含线路、事项、报名人身份证号码和手机号等内容
* @param families 亲属信息JSON数组;每项包含姓名、性别、身份证号码、手机号和关系
* @param directRelative 直系亲属线路信息JSON;非直系亲属线路可传空字符串
* @return Result,成功时返回报名及流程结果,校验失败时返回具体缺失字段提示
*/
@At
@Aop(TransAop.READ_COMMITTED)
@SaCheckPermission("tour.signup")
public Result doSignup(TourLedger ledger, @Param("families") String families, @Param("directRelative") String directRelative) {
public Result doSignup(TourLedger ledger, @Param("families") String families,
@Param("directRelative") String directRelative) {
Result checkResult = checkSignup(ledger);
if (checkResult != null) {
return checkResult;
@@ -826,6 +835,12 @@ public class TourSignupController {
if (StrUtil.isBlank(ledger.getLineName())) {
return Result.error("报名线路不能为空");
}
if (StrUtil.isBlank(ledger.getIdCard())) {
return Result.error("身份证号码不能为空");
}
if (StrUtil.isBlank(ledger.getMobile())) {
return Result.error("手机号不能为空");
}
return null;
}
@@ -874,15 +889,20 @@ public class TourSignupController {
if (family == null) {
continue;
}
if (StrUtil.isBlank(family.getFamilyName()) || StrUtil.isBlank(family.getGender())
|| StrUtil.isBlank(family.getIdCard()) || StrUtil.isBlank(family.getRelationship())) {
return Result.error("请完善亲属姓名、性别、身份证号码和关系");
if (StrUtil.isBlank(family.getFamilyName()) || family.getAge() == null
|| StrUtil.isBlank(family.getGender()) || StrUtil.isBlank(family.getRelationship())) {
return Result.error("请完善亲属姓名、年龄、性别和与本人关系");
}
String idCard = family.getIdCard().trim().toUpperCase();
if (!isValidIdCard(idCard)) {
// 年满16周岁的亲属必须提供身份证号码和手机号;未满16周岁时两项允许为空。
if (family.getAge() >= 16 && (StrUtil.isBlank(family.getIdCard()) || StrUtil.isBlank(family.getMobile()))) {
return Result.error("年满16周岁的亲属必须填写身份证号码和手机号");
}
if (StrUtil.isNotBlank(family.getIdCard()) && !isValidIdCard(family.getIdCard().trim().toUpperCase())) {
return Result.error("请输入正确的身份证号码");
}
family.setIdCard(idCard);
if (StrUtil.isNotBlank(family.getIdCard())) {
family.setIdCard(family.getIdCard().trim().toUpperCase());
}
}
return null;
}
@@ -461,7 +461,7 @@ public class TourUnionApprovalController {
sql.setParam("actorId", SecurityUtil.getUserId());
sql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(sql);
return sql.getInt() > 0 ? tourLedgerService.fetch(id) : null;
return sql.getInt() > 0 ? tourLedgerService.fillMobileFallback(tourLedgerService.fetch(id)) : null;
}
private String getCurrentLineName(TourLedger ledger) {
@@ -1,5 +1,7 @@
package com.budwk.app.zhgh.dayofficework.tour.controller;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.enmus.ExcelType;
import cn.dev33.satoken.annotation.SaCheckPermission;
import cn.hutool.core.util.StrUtil;
import com.budwk.app.base.annotation.SLog;
@@ -15,7 +17,7 @@ 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.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.ss.usermodel.BorderStyle;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
@@ -171,7 +173,7 @@ public class TourUnionLedgerController {
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);
CommonDownloadUtil.download("5天外超出部分疗休养费用由单位承担申请人员汇总表.xlsx", workbook, response);
}
@At
@@ -411,7 +413,7 @@ public class TourUnionLedgerController {
t.userName,
t.idCard,
t.unionName,
vu.mobile,
COALESCE(NULLIF(t.mobile, ''), NULLIF(vu.mobile, ''), '') AS mobile,
m.travelStartTime,
m.travelEndTime,
m.estimatedCost,
@@ -474,7 +476,9 @@ public class TourUnionLedgerController {
}
private Workbook buildOverCostSummaryWorkbook(String unionName, List<NutMap> list) {
Workbook workbook = new HSSFWorkbook();
ExportParams exportParams = new ExportParams();
exportParams.setType(ExcelType.XSSF);
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("汇总表");
sheet.addMergedRegion(new CellRangeAddress(0, 0, 0, 9));
sheet.addMergedRegion(new CellRangeAddress(1, 1, 0, 9));
@@ -675,7 +679,7 @@ public class TourUnionLedgerController {
sql.setParam("unionId", currentUnionId());
sql.setCallback(Sqls.callback.integer());
tourLedgerService.dao().execute(sql);
return sql.getInt() > 0 ? tourLedgerService.fetch(id) : null;
return sql.getInt() > 0 ? tourLedgerService.fillMobileFallback(tourLedgerService.fetch(id)) : null;
}
private String getTravelPeriod(TourLedger ledger) {
@@ -51,6 +51,11 @@ public class TourLedger extends BaseModel implements Serializable {
@ColDefine(type = ColType.VARCHAR, width = 30)
private String idCard;
@Column
@Comment("手机号")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String mobile;
@Column
@Comment("所在单位ID")
@ColDefine(type = ColType.VARCHAR, width = 32)
@@ -56,6 +56,11 @@ public class TourLedgerFamily extends BaseModel implements Serializable {
@ColDefine(type = ColType.VARCHAR, width = 30)
private String idCard;
@Column
@Comment("手机号")
@ColDefine(type = ColType.VARCHAR, width = 30)
private String mobile;
@Column
@Comment("年龄")
@ColDefine(type = ColType.INT, width = 3)
@@ -8,6 +8,14 @@ import com.budwk.app.zhgh.dayofficework.tour.models.TourLedger;
*/
public interface TourLedgerService extends BaseService<TourLedger> {
/**
* 补充报名台账手机号。台账已保存手机号时保持原值,仅在台账手机号为空时按工号读取用户视图手机号。
*
* @param ledger 待补充手机号的报名台账,允许为空
* @return 原报名台账对象;参数为空时返回空
*/
TourLedger fillMobileFallback(TourLedger ledger);
/**
* 校验当前人员在省内间隔周期内是否已经实际参加过省内线路。
*
@@ -26,6 +26,18 @@ public class TourLedgerServiceImpl extends BaseServiceImpl<TourLedger> implement
super(dao);
}
@Override
public TourLedger fillMobileFallback(TourLedger ledger) {
if (ledger == null || StrUtil.isNotBlank(ledger.getMobile()) || StrUtil.isBlank(ledger.getJobNo())) {
return ledger;
}
View_user user = dao().fetch(View_user.class, Cnd.where(View_user::getLoginname, "=", ledger.getJobNo()));
if (user != null) {
ledger.setMobile(user.getMobile());
}
return ledger;
}
@Override
public String checkInProvinceParticipation(String jobNo,
Integer inProvinceYears,
@@ -0,0 +1,6 @@
-- 疗休养报名手机号仅由业务代码控制必填,数据库字段保持可空以兼容历史数据。
ALTER TABLE `tour_ledger`
ADD COLUMN `mobile` varchar(30) NULL COMMENT '手机号' AFTER `idCard`;
ALTER TABLE `tour_ledger_family`
ADD COLUMN `mobile` varchar(30) NULL COMMENT '手机号' AFTER `idCard`;
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

@@ -256,6 +256,7 @@
})
$(document).on("pjax:send", function () {
window.h5HistoryLayerManager.reset()
showH5PageSkeleton()
NProgress.start()
window.customStyleList = document.querySelectorAll('style[id^="style-"]')
})
@@ -270,6 +271,7 @@
window.customStyleList = null
}
NProgress.done()
hideH5PageSkeleton()
toggleShowBar()
})
@@ -281,6 +283,13 @@
})()
function pjaxReplace(url, data = null) {
showH5PageSkeleton(url)
// 疗休养报名页面使用独立 H5 布局,其中包含图片懒加载等专用初始化逻辑。
// 禁止通过公共 PJAX 只替换页面片段,否则专用布局脚本不会执行,首次进入会出现图片懒加载指令未注册。
if (url && url.indexOf("/platform/tour/signup/h5") === 0) {
window.location.href = url
return
}
return $.pjax({
url: url,
container: "#container",
@@ -293,6 +302,24 @@
})
}
// 仅在疗休养 H5 页面切换期间显示骨架,避免影响共用布局下的其他业务模块。
const showH5PageSkeleton = (targetUrl) => {
const pageUrl = targetUrl || window.location.pathname
if (pageUrl.indexOf("/platform/tour/") !== 0) return
const skeleton = document.getElementById("tour-page-transition-skeleton")
if (skeleton) skeleton.classList.add("is-visible")
}
const hideH5PageSkeleton = () => {
const skeleton = document.getElementById("tour-page-transition-skeleton")
if (skeleton) skeleton.classList.remove("is-visible")
}
// 从疗休养完整页面返回公共 H5 页面时可能命中 BFCache,需要清除离开前保留的骨架状态。
window.addEventListener("pageshow", () => {
hideH5PageSkeleton()
})
let h5GlobalLoading = null
let h5PendingRequestCount = 0
@@ -362,6 +389,7 @@
}
function historyBack(func) {
showH5PageSkeleton()
if (window.history.length > 1) {
window.history.back()
} else {
@@ -607,6 +635,8 @@
Vue.prototype.$axios = commonUtil.axiosService()
Vue.prototype.$downLoad = commonUtil.downLoadService.bind(commonUtil)
Vue.prototype.$pjaxReplace = pjaxReplace
Vue.prototype.$showH5PageSkeleton = showH5PageSkeleton
Vue.prototype.$hideH5PageSkeleton = hideH5PageSkeleton
</script>
<script nonce="${cspNonce!}">
@@ -652,9 +682,41 @@
.h5-voice-menu .voice-menu-text {
display: none;
}
.tour-page-transition-skeleton {
position: fixed;
inset: 0;
z-index: 30000;
display: none;
padding: 58px 12px 20px;
background: #f3f7fd;
box-sizing: border-box;
}
.tour-page-transition-skeleton.is-visible { display: block; }
.tour-page-transition-skeleton__nav,
.tour-page-transition-skeleton__hero,
.tour-page-transition-skeleton__line,
.tour-page-transition-skeleton__card {
background: linear-gradient(90deg, #edf2f8 25%, #f8fbff 37%, #edf2f8 63%);
background-size: 400% 100%;
animation: tour-page-skeleton-loading 1.35s ease infinite;
}
.tour-page-transition-skeleton__nav { position: absolute; top: 0; left: 0; right: 0; height: 46px; background-color: #fff; }
.tour-page-transition-skeleton__hero { height: 154px; border-radius: 14px; }
.tour-page-transition-skeleton__line { width: 42%; height: 18px; margin: 18px 2px 12px; border-radius: 9px; }
.tour-page-transition-skeleton__card { height: 126px; margin-top: 12px; border-radius: 14px; }
@keyframes tour-page-skeleton-loading { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
</style>
</head>
<body>
<div id="tour-page-transition-skeleton" class="tour-page-transition-skeleton" aria-hidden="true">
<div class="tour-page-transition-skeleton__nav"></div>
<div class="tour-page-transition-skeleton__hero"></div>
<div class="tour-page-transition-skeleton__line"></div>
<div class="tour-page-transition-skeleton__card"></div>
<div class="tour-page-transition-skeleton__card"></div>
<div class="tour-page-transition-skeleton__card"></div>
</div>
<div class="main-page page-showtabbar">
<div class="page-wrapper">
<div id="container">${layoutContent}</div>
@@ -11,11 +11,38 @@
<link rel="stylesheet" href="${base!}/assets/platform/css/common.css?v=20260524"/>
<link rel="stylesheet" href="${base!}/assets/platform/css/h5.css?v=20260524"/>
<style>
.tour-page-transition-skeleton {
position: fixed;
inset: 0;
z-index: 30000;
display: none;
padding: 58px 12px 20px;
background: #f3f7fd;
box-sizing: border-box;
}
.tour-page-transition-skeleton.is-visible { display: block; }
.tour-page-transition-skeleton__nav,
.tour-page-transition-skeleton__hero,
.tour-page-transition-skeleton__line,
.tour-page-transition-skeleton__card {
background: linear-gradient(90deg, #edf2f8 25%, #f8fbff 37%, #edf2f8 63%);
background-size: 400% 100%;
animation: tour-page-skeleton-loading 1.35s ease infinite;
}
.tour-page-transition-skeleton__nav { position: absolute; top: 0; left: 0; right: 0; height: 46px; background-color: #fff; }
.tour-page-transition-skeleton__hero { height: 154px; border-radius: 14px; }
.tour-page-transition-skeleton__line { width: 42%; height: 18px; margin: 18px 2px 12px; border-radius: 9px; }
.tour-page-transition-skeleton__card { height: 126px; margin-top: 12px; border-radius: 14px; }
@keyframes tour-page-skeleton-loading { 0% { background-position: 100% 50%; } 100% { background-position: 0 50%; } }
</style>
<script src="${base!}/assets/platform/plugins/vue/vue.js?v=20260524"></script>
<script src="${base!}/assets/platform/plugins/vant/index.js?v=20260524"></script>
<script src="${base!}/assets/platform/plugins/axios/axios.js?v=20260524"></script>
<script src="${base!}/assets/platform/plugins/jquery/jquery.js?v=20260524"></script>
<script src="${base!}/assets/platform/js/util/commonUtil.js?v=20260524"></script>
<script src="${base!}/assets/platform/js/util/h5PopupHistory.js?v=20260830"></script>
<script nonce="${cspNonce!}">
Vue.config.devtools = true
@@ -123,6 +150,28 @@
}
}
// 疗休养页面跳转前统一展示结构化骨架,避免旧页面或未完成样式短暂闪现。
const showH5PageSkeleton = () => {
const skeleton = document.getElementById("tour-page-transition-skeleton")
if (skeleton) skeleton.classList.add("is-visible")
}
const hideH5PageSkeleton = () => {
const skeleton = document.getElementById("tour-page-transition-skeleton")
if (skeleton) skeleton.classList.remove("is-visible")
}
// 浏览器返回或前进时可能直接恢复 BFCache 页面,需要主动关闭离开页面前显示的骨架层。
// 此处只恢复页面展示,不刷新数据,从而保留线路列表的筛选条件、分页和滚动位置。
window.addEventListener("pageshow", () => {
hideH5PageSkeleton()
})
const pjaxReplace = (url) => {
showH5PageSkeleton()
window.location.href = url
}
function historyBack(fallbackUrl, func) {
if (typeof fallbackUrl === "function") {
func = fallbackUrl
@@ -130,6 +179,7 @@
} else if (typeof fallbackUrl !== "string" || !fallbackUrl) {
fallbackUrl = "/platform/h5/home"
}
showH5PageSkeleton()
if (window.history.length > 1) {
window.history.back()
} else {
@@ -158,9 +208,20 @@
Vue.prototype.$auth = commonUtil.authService()
Vue.prototype.$axios = commonUtil.axiosService()
Vue.prototype.$downLoad = commonUtil.downLoadService.bind(commonUtil)
Vue.prototype.$pjaxReplace = pjaxReplace
Vue.prototype.$showH5PageSkeleton = showH5PageSkeleton
Vue.prototype.$hideH5PageSkeleton = hideH5PageSkeleton
</script>
</head>
<body>
<div id="tour-page-transition-skeleton" class="tour-page-transition-skeleton" aria-hidden="true">
<div class="tour-page-transition-skeleton__nav"></div>
<div class="tour-page-transition-skeleton__hero"></div>
<div class="tour-page-transition-skeleton__line"></div>
<div class="tour-page-transition-skeleton__card"></div>
<div class="tour-page-transition-skeleton__card"></div>
<div class="tour-page-transition-skeleton__card"></div>
</div>
<div class="main-page">
<div class="page-wrapper">
<div id="container">${layoutContent}</div>
@@ -159,6 +159,7 @@ layout("/layouts/platform.html"){
<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.mobile || '' }}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{ detail.unitName || '' }}</el-descriptions-item>
<el-descriptions-item label="所在工会">{{ detail.unionName || '' }}</el-descriptions-item>
</el-descriptions>
@@ -205,6 +206,7 @@ layout("/layouts/platform.html"){
<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="mobile" min-width="140" 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>
@@ -99,6 +99,7 @@ layout("/layouts/platform.html"){
<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.mobile || '' }}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{ detail.unitName || '' }}</el-descriptions-item>
<el-descriptions-item label="所在工会">{{ detail.unionName || '' }}</el-descriptions-item>
</el-descriptions>
@@ -145,6 +146,7 @@ layout("/layouts/platform.html"){
<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="mobile" min-width="140" 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="detailFillBedInfo" label="床型" prop="bedType" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
@@ -229,6 +231,13 @@ layout("/layouts/platform.html"){
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="手机号" required>
<el-input v-model="signupForm.mobile" maxlength="30" placeholder="请输入手机号"></el-input>
</el-form-item>
</el-col>
</el-row>
<div class="tour-title tour-section">报名信息</div>
<el-row :gutter="20">
@@ -387,7 +396,12 @@ layout("/layouts/platform.html"){
</el-table-column>
<el-table-column label="身份证号码" min-width="180" align="center" header-align="center">
<template slot-scope="{row}">
<el-input v-model="row.idCard" maxlength="18" placeholder="请输入" @blur="normalizeFamilyIdCard(row)"></el-input>
<el-input v-model="row.idCard" maxlength="18" :placeholder="familyContactRequired(row) ? '16岁及以上必填' : '选填'" @blur="normalizeFamilyIdCard(row)"></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.mobile" maxlength="30" :placeholder="familyContactRequired(row) ? '16岁及以上必填' : '选填'"></el-input>
</template>
</el-table-column>
<el-table-column label="年龄" width="110" align="center" header-align="center">
@@ -1181,6 +1195,7 @@ layout("/layouts/platform.html"){
familyName: "",
gender: "",
idCard: "",
mobile: "",
age: null,
bedType: "",
bedInfo: "",
@@ -1222,18 +1237,27 @@ layout("/layouts/platform.html"){
this.$message.warning("请添加亲属信息")
return false
}
const invalid = this.familyData.some(item => !item.familyName || !item.gender || !item.idCard || !item.relationship)
if (invalid) {
this.$message.warning("请完善亲属姓名、性别、身份证号码和关系")
const invalidBase = this.familyData.some(item => !item.familyName || item.age === null || item.age === undefined
|| !item.gender || !item.relationship)
if (invalidBase) {
this.$message.warning("请完善亲属姓名、年龄、性别和与本人关系")
return false
}
const invalidIdCard = this.familyData.some(item => !this.isValidIdCard(item.idCard))
const invalidContact = this.familyData.some(item => this.familyContactRequired(item) && (!item.idCard || !item.mobile))
if (invalidContact) {
this.$message.warning("年满16周岁的亲属必须填写身份证号码和手机号")
return false
}
const invalidIdCard = this.familyData.some(item => item.idCard && !this.isValidIdCard(item.idCard))
if (invalidIdCard) {
this.$message.warning("请输入正确的身份证号码")
return false
}
return true
},
familyContactRequired(family) {
return family && family.age !== null && family.age !== undefined && Number(family.age) >= 16
},
normalizeFamilyIdCard(row) {
if (!row || !row.idCard) return
row.idCard = String(row.idCard).trim().toUpperCase()
@@ -1272,6 +1296,10 @@ layout("/layouts/platform.html"){
return true
},
submitSignup() {
if (!this.signupForm.idCard || !this.signupForm.mobile) {
this.$message.warning("请完善报名人身份证号码和手机号")
return
}
if (!this.validateFamilies()) return
if (!this.validateDirectRelative()) return
const families = this.allowFamily && this.signupForm.hasFamily ? this.familyData : []
@@ -1281,7 +1309,6 @@ layout("/layouts/platform.html"){
})
this.signupLoading = true
this.$axios.post(loc() + "/doSignup", form).then((res) => {
this.signupLoading = false
if (res.code === 0) {
this.$message.success(res.msg || "修改成功")
this.signupVisible = false
@@ -1291,6 +1318,7 @@ layout("/layouts/platform.html"){
this.$message.warning(res.msg || "修改失败")
}
}).catch(() => {
}).finally(() => {
this.signupLoading = false
})
},
@@ -184,6 +184,7 @@ layout("/layouts/platform.html"){
<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.mobile || '' }}</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>
@@ -220,6 +221,7 @@ layout("/layouts/platform.html"){
<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="mobile" min-width="140" 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>
@@ -128,6 +128,13 @@ layout("/layouts/platform.html"){
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="手机号" required>
<el-input v-model="signupForm.mobile" maxlength="30" placeholder="请输入手机号"></el-input>
</el-form-item>
</el-col>
</el-row>
<div class="tour-signup-title tour-signup-section">报名信息</div>
<el-row :gutter="20">
@@ -286,7 +293,12 @@ layout("/layouts/platform.html"){
</el-table-column>
<el-table-column label="身份证号码" min-width="180" align="center" header-align="center">
<template slot-scope="{row}">
<el-input v-model="row.idCard" maxlength="18" placeholder="请输入" @blur="normalizeFamilyIdCard(row)"></el-input>
<el-input v-model="row.idCard" maxlength="18" :placeholder="familyContactRequired(row) ? '16岁及以上必填' : '选填'" @blur="normalizeFamilyIdCard(row)"></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.mobile" maxlength="30" :placeholder="familyContactRequired(row) ? '16岁及以上必填' : '选填'"></el-input>
</template>
</el-table-column>
<el-table-column label="年龄" width="110" align="center" header-align="center">
@@ -799,6 +811,7 @@ layout("/layouts/platform.html"){
familyName: "",
gender: "",
idCard: "",
mobile: "",
age: null,
bedType: "",
bedInfo: "",
@@ -840,18 +853,27 @@ layout("/layouts/platform.html"){
this.$message.warning("请添加亲属信息")
return false
}
const invalid = this.familyData.some(item => !item.familyName || !item.gender || !item.idCard || !item.relationship)
if (invalid) {
this.$message.warning("请完善亲属姓名、性别、身份证号码和关系")
const invalidBase = this.familyData.some(item => !item.familyName || item.age === null || item.age === undefined
|| !item.gender || !item.relationship)
if (invalidBase) {
this.$message.warning("请完善亲属姓名、年龄、性别和与本人关系")
return false
}
const invalidIdCard = this.familyData.some(item => !this.isValidIdCard(item.idCard))
const invalidContact = this.familyData.some(item => this.familyContactRequired(item) && (!item.idCard || !item.mobile))
if (invalidContact) {
this.$message.warning("年满16周岁的亲属必须填写身份证号码和手机号")
return false
}
const invalidIdCard = this.familyData.some(item => item.idCard && !this.isValidIdCard(item.idCard))
if (invalidIdCard) {
this.$message.warning("请输入正确的身份证号码")
return false
}
return true
},
familyContactRequired(family) {
return family && family.age !== null && family.age !== undefined && Number(family.age) >= 16
},
normalizeFamilyIdCard(row) {
if (!row || !row.idCard) return
row.idCard = String(row.idCard).trim().toUpperCase()
@@ -890,6 +912,10 @@ layout("/layouts/platform.html"){
return true
},
submitSignup() {
if (!this.signupForm.idCard || !this.signupForm.mobile) {
this.$message.warning("请完善报名人身份证号码和手机号")
return
}
if (!this.validateFamilies()) return
if (!this.validateDirectRelative()) return
const families = this.allowFamily && this.signupForm.hasFamily ? this.familyData : []
@@ -899,7 +925,6 @@ layout("/layouts/platform.html"){
})
this.signupLoading = true
this.$axios.post(loc() + "/doSignup", form).then((res) => {
this.signupLoading = false
if (res.code === 0) {
this.$message.success(res.msg || "报名成功")
this.signupVisible = false
@@ -908,6 +933,7 @@ layout("/layouts/platform.html"){
this.$message.warning(res.msg || "报名失败")
}
}).catch(() => {
}).finally(() => {
this.signupLoading = false
})
},
@@ -184,6 +184,7 @@ layout("/layouts/platform.html"){
<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.mobile || '' }}</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>
@@ -220,6 +221,7 @@ layout("/layouts/platform.html"){
<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="mobile" min-width="140" 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>
@@ -149,6 +149,7 @@ layout("/layouts/platform.html"){
<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.mobile || '' }}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{ detail.unitName || '' }}</el-descriptions-item>
<el-descriptions-item label="所在工会">{{ detail.unionName || '' }}</el-descriptions-item>
</el-descriptions>
@@ -195,6 +196,7 @@ layout("/layouts/platform.html"){
<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="mobile" min-width="140" 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>
@@ -0,0 +1,283 @@
/* 疗休养移动端统一视觉主题:仅使用 tour-h5-theme 下的业务类,避免影响其他 H5 模块。 */
body {
background-color: #f3f7fd;
}
[v-cloak] {
display: none;
}
.tour-h5-theme {
min-height: 100vh;
color: #263548;
background-color: #f3f7fd;
box-sizing: border-box;
}
/* 页面接口首次加载时使用结构化骨架,避免先显示空字段或旧列表。 */
.tour-content-skeleton {
padding: 12px;
background: #f3f7fd;
box-sizing: border-box;
}
.tour-content-skeleton__cover,
.tour-content-skeleton__title,
.tour-content-skeleton__row,
.tour-content-skeleton__card {
background: linear-gradient(90deg, #edf2f8 25%, #f8fbff 37%, #edf2f8 63%);
background-size: 400% 100%;
animation: tour-content-skeleton-loading 1.35s ease infinite;
}
.tour-content-skeleton__cover { height: 168px; border-radius: 14px; }
.tour-content-skeleton__title { width: 46%; height: 18px; margin: 16px 2px 10px; border-radius: 9px; }
.tour-content-skeleton__row { width: 76%; height: 13px; margin: 10px 2px; border-radius: 7px; }
.tour-content-skeleton__card { height: 126px; margin-top: 12px; border-radius: 14px; }
@keyframes tour-content-skeleton-loading {
0% { background-position: 100% 50%; }
100% { background-position: 0 50%; }
}
.tour-h5-theme .van-nav-bar {
background-color: #fff;
}
.tour-h5-theme .van-nav-bar__title {
color: #172033;
font-size: 16px;
font-weight: 600;
}
.tour-h5-theme .van-nav-bar .van-icon,
.tour-h5-theme .van-nav-bar__text {
color: #1989fa;
}
.tour-h5-theme .van-nav-bar::after,
.tour-h5-theme .van-hairline--bottom::after {
border-bottom-color: #edf2f8;
}
.tour-h5-card {
overflow: hidden;
border-radius: 12px;
background-color: #fff;
box-shadow: 0 7px 20px rgba(55, 85, 126, .08);
}
.tour-h5-section-title {
position: relative;
display: flex;
min-height: 46px;
padding: 0 14px 0 20px;
align-items: center;
justify-content: space-between;
color: #202938;
font-size: 16px;
line-height: 22px;
font-weight: 600;
box-sizing: border-box;
}
.tour-h5-section-title::before {
position: absolute;
top: 15px;
bottom: 15px;
left: 10px;
width: 3px;
border-radius: 2px;
background-color: #1989fa;
content: "";
}
.tour-h5-tag {
display: inline-flex;
min-height: 22px;
padding: 2px 8px;
align-items: center;
justify-content: center;
border-radius: 11px;
color: #1989fa;
background-color: #eaf4ff;
font-size: 11px;
line-height: 18px;
box-sizing: border-box;
white-space: nowrap;
}
.tour-h5-tag.is-success {
color: #20a765;
background-color: #e9f8f0;
}
.tour-h5-tag.is-warning {
color: #e98018;
background-color: #fff3e4;
}
.tour-h5-tag.is-muted {
color: #7c899b;
background-color: #f0f2f5;
}
.tour-h5-search {
margin: 0 0 8px;
padding: 0;
overflow: hidden;
border-radius: 12px;
background-color: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
}
.tour-h5-search .van-search__content {
height: 42px;
padding-left: 12px;
align-items: center;
border-radius: 12px;
background-color: #fff;
}
.tour-h5-search .van-field__control {
color: #263548;
font-size: 14px;
}
.tour-h5-search .van-field__control::placeholder {
color: #a7b0bf;
}
.tour-h5-filter {
overflow: hidden;
border-radius: 10px;
background-color: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
}
.tour-h5-filter .van-dropdown-menu__bar,
.tour-h5-filter .van-dropdown-menu__item,
.tour-h5-filter .van-dropdown-menu__title {
height: 44px;
}
.tour-h5-filter .van-dropdown-menu__bar {
box-shadow: none;
}
.tour-h5-filter .van-dropdown-menu__title {
color: #66758a;
font-size: 14px;
}
.tour-h5-filter .van-dropdown-menu__title--active,
.tour-h5-filter .van-dropdown-item__option--active,
.tour-h5-filter .van-dropdown-item__option--active .van-dropdown-item__icon {
color: #1989fa;
}
.tour-h5-filter .van-dropdown-item__content {
width: calc(100% - 24px);
margin: 0 12px;
overflow: hidden;
border-radius: 0 0 12px 12px;
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
}
.tour-h5-empty {
display: flex;
min-height: 290px;
padding: 40px 18px 32px;
align-items: center;
justify-content: center;
flex-direction: column;
box-sizing: border-box;
text-align: center;
}
.tour-h5-empty img {
display: block;
width: 60%;
max-width: 190px;
height: auto;
object-fit: contain;
}
.tour-h5-empty__title {
margin-top: 8px;
color: #50627a;
font-size: 15px;
line-height: 22px;
font-weight: 600;
}
.tour-h5-empty__hint {
margin-top: 5px;
color: #9aa7b8;
font-size: 12px;
line-height: 18px;
}
.tour-h5-fixed-bar {
position: fixed;
right: 0;
bottom: 0;
left: 0;
z-index: 30;
display: flex;
gap: 10px;
padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
border-top: 1px solid #edf1f6;
background-color: rgba(255, 255, 255, .98);
box-shadow: 0 -6px 18px rgba(38, 66, 103, .08);
box-sizing: border-box;
}
.tour-h5-fixed-bar .van-button {
height: 42px;
border-radius: 8px;
font-size: 15px;
font-weight: 600;
}
.tour-h5-theme .van-cell {
min-height: 48px;
padding: 13px 14px;
color: #35445a;
font-size: 14px;
}
.tour-h5-theme .van-cell__title {
color: #65748a;
}
.tour-h5-theme .van-cell__value {
color: #263548;
}
.tour-h5-theme .van-field__label {
color: #65748a;
}
.tour-h5-theme .van-field__control {
color: #263548;
}
.tour-h5-theme .van-button--info,
.tour-h5-theme .van-button--primary {
border-color: #0f7bf3;
background-color: #0f7bf3;
}
.tour-h5-theme .van-button--plain.van-button--info {
color: #0f7bf3;
background-color: #fff;
}
@media (max-width: 350px) {
.tour-h5-fixed-bar {
gap: 7px;
padding-right: 10px;
padding-left: 10px;
}
}
File diff suppressed because it is too large Load Diff
@@ -2,34 +2,36 @@
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
<style>
.tour-approval-page {
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
background: #f4f6f8;
background: #f4f8ff;
box-sizing: border-box;
}
.tour-approval-sticky {
flex-shrink: 0;
background: #f4f6f8;
padding: 8px 12px 10px;
background: #f4f8ff;
}
.tour-approval-list-scroll {
min-height: 0;
flex: 1;
overflow-y: auto;
padding-bottom: 18px;
padding: 2px 12px 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;
border-radius: 18px;
background: #ffffff;
box-shadow: 0 4px 16px rgba(34, 94, 170, 0.06);
}
.tour-card-tags {
@@ -39,12 +41,36 @@ layout("/layouts/platform_h5.html"){
margin-bottom: 8px;
}
.tour-card-heading {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 10px;
}
.tour-card-person {
overflow: hidden;
color: #17233d;
font-size: 16px;
font-weight: 700;
text-overflow: ellipsis;
white-space: nowrap;
}
.tour-card-job-no {
margin-left: 7px;
color: #64748b;
font-size: 12px;
font-weight: 400;
}
.tour-tag {
display: inline-flex;
align-items: center;
min-height: 22px;
padding: 0 8px;
border-radius: 4px;
border-radius: 999px;
background: #eef6ff;
color: #0b75bd;
font-size: 12px;
@@ -60,17 +86,24 @@ layout("/layouts/platform_h5.html"){
color: #d97706;
}
.tour-approval-detail {
height: 100%;
background: #f4f8ff;
}
.tour-approval-sheet {
max-height: 86vh;
height: calc(100% - 46px);
overflow-y: auto;
padding-bottom: env(safe-area-inset-bottom);
background: #f4f6f8;
padding: 2px 0 calc(86px + env(safe-area-inset-bottom));
background: #f4f8ff;
box-sizing: border-box;
}
.tour-section {
margin: 10px 12px;
overflow: hidden;
border-radius: 8px;
border-radius: 14px;
box-shadow: 0 4px 16px rgba(34, 94, 170, 0.06);
background: #ffffff;
}
@@ -78,7 +111,7 @@ layout("/layouts/platform_h5.html"){
position: relative;
padding: 14px 16px;
border-bottom: 1px solid #edf0f4;
color: #0b75bd;
color: #17233d;
font-size: 16px;
font-weight: 700;
line-height: 1.2;
@@ -92,7 +125,7 @@ layout("/layouts/platform_h5.html"){
width: 30px;
height: 4px;
border-radius: 0 0 3px 3px;
background: #0b75bd;
background: #1677ff;
}
.tour-approval-actions {
@@ -106,9 +139,303 @@ layout("/layouts/platform_h5.html"){
white-space: pre-wrap;
word-break: break-word;
}
.tour-approval-sticky /deep/ .van-dropdown-menu__bar,
.tour-approval-sticky .van-dropdown-menu__bar {
height: 44px;
border-radius: 12px;
box-shadow: 0 4px 16px rgba(34, 94, 170, 0.06);
}
.tour-approval-list-scroll /deep/ .table-list-card,
.tour-approval-list-scroll /deep/ .list-card {
border-radius: 14px;
box-shadow: 0 4px 16px rgba(34, 94, 170, 0.06);
}
.tour-approval-list-scroll /deep/ .action-btn {
color: #1677ff;
}
<!--#include("../common/theme.css"){}#-->
/* 审核列表与提案校工会预审核采用同一信息层级和卡片规范。 */
body {
background-color: #f3f7fd;
}
.tour-approval-page {
background-color: #f3f7fd;
}
.tour-approval-page .van-nav-bar {
background-color: #fff;
}
.tour-approval-page .van-nav-bar__title {
color: #172033;
font-size: 16px;
font-weight: 600;
}
.tour-approval-sticky {
padding: 22px 12px 1px;
background-color: #f3f7fd;
}
.tour-approval-search {
margin: 0 0 8px;
padding: 0;
overflow: hidden;
border-radius: 12px;
background-color: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
}
.tour-approval-search .van-search__content {
height: 42px;
padding-left: 12px;
align-items: center;
border-radius: 12px;
background-color: #fff;
}
.tour-approval-search .van-field__control {
color: #263548;
font-size: 14px;
}
.tour-approval-filter {
margin-bottom: 10px;
overflow: hidden;
border-radius: 10px;
background-color: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
transition: border-radius .18s ease;
}
.tour-approval-filter.is-open {
border-radius: 10px 10px 0 0;
}
.tour-approval-filter .van-dropdown-menu__bar,
.tour-approval-filter .van-dropdown-menu__item,
.tour-approval-filter .van-dropdown-menu__title {
height: 44px;
}
.tour-approval-filter .van-dropdown-menu__bar {
border-radius: 0;
box-shadow: none;
}
.tour-approval-filter .van-dropdown-menu__title {
display: inline-flex;
align-items: center;
color: #66758a;
font-size: 14px;
line-height: 20px;
}
.tour-approval-filter .van-dropdown-menu__title--active,
.tour-approval-filter .van-dropdown-item__option--active,
.tour-approval-filter .van-dropdown-item__option--active .van-dropdown-item__icon {
color: #1989fa;
}
.tour-approval-filter .van-dropdown-item__content {
width: calc(100% - 24px);
margin: 0 12px;
overflow: hidden;
box-sizing: border-box;
border-radius: 0 0 12px 12px;
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
}
.tour-approval-filter .van-dropdown-item__option {
display: flex;
min-height: 48px;
padding: 0 16px;
align-items: center;
color: #46566c;
font-size: 14px;
}
.tour-approval-list-scroll {
padding: 2px 12px 24px;
background-color: #f3f7fd;
}
.tour-approval-list-scroll .empty-state {
display: none;
}
.tour-approval-empty {
display: flex;
min-height: 280px;
padding: 46px 18px 36px;
align-items: center;
justify-content: center;
flex-direction: column;
box-sizing: border-box;
text-align: center;
}
.tour-approval-empty img {
display: block;
width: 60%;
max-width: 190px;
height: auto;
object-fit: contain;
}
.tour-approval-empty__title {
margin-top: 10px;
color: #50627a;
font-size: 15px;
line-height: 22px;
font-weight: 600;
}
.tour-approval-empty__hint {
margin-top: 5px;
color: #9aa7b8;
font-size: 12px;
line-height: 18px;
}
.tour-card-heading {
margin: 0 0 7px;
align-items: flex-start;
}
.tour-card-heading-main {
min-width: 0;
padding-right: 10px;
flex: 1;
}
.tour-card-person {
color: #253247;
font-size: 15px;
line-height: 22px;
font-weight: 600;
}
.tour-card-job-no {
display: block;
margin: 1px 0 0;
color: #9ba6b6;
font-size: 12px;
line-height: 18px;
}
.tour-card-status {
flex: none;
}
.tour-card-tags {
margin-bottom: 7px;
}
.tour-approval-list-scroll .action-btn {
color: #1989fa;
}
.tour-approval-list-scroll .action-btn .van-icon {
margin-right: 6px;
font-size: 15px;
}
.tour-approval-detail,
.tour-approval-sheet {
background-color: #f3f7fd;
}
.tour-approval-sheet {
padding: 18px 12px calc(92px + env(safe-area-inset-bottom));
}
.tour-section {
margin: 0 0 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background-color: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
.tour-section-title {
position: relative;
padding: 12px 14px 8px 21px;
border-bottom: 0;
color: #354255;
font-size: 15px;
line-height: 22px;
font-weight: 600;
}
.tour-section-title::before {
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
height: auto;
border-radius: 2px;
background-color: #1989fa;
}
.tour-section .van-cell {
min-height: 44px;
padding: 10px 14px;
background: transparent;
}
.tour-section .van-cell__title {
flex: 0 0 92px;
width: 92px;
color: #596779;
font-size: 14px;
}
.tour-section .van-cell__value {
color: #4f5d70;
font-size: 14px;
text-align: left;
overflow-wrap: anywhere;
}
.tour-section .tour-approval-opinion {
width: calc(100% - 28px);
margin: 0 14px 14px;
padding: 12px;
border: 1px solid #c9d5e5;
border-radius: 10px;
background-color: #f7f9fd;
box-sizing: border-box;
}
.tour-approval-actions {
gap: 10px;
padding: 0 14px 14px;
}
.tour-approval-actions .van-button {
height: 40px;
border-radius: 10px;
font-size: 14px;
}
@media (max-width: 350px) {
.tour-approval-sticky,
.tour-approval-list-scroll {
padding-right: 10px;
padding-left: 10px;
}
}
</style>
<div id="app" class="tour-approval-page" v-cloak>
<div id="app" class="tour-approval-page tour-h5-theme" 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">
@@ -116,27 +443,39 @@ layout("/layouts/platform_h5.html"){
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 class="tour-approval-filter" :class="{'is-open': filterOpened}" active-color="#1989fa" :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item :options="auditOptions" @change="doSearch" @open="onFilterOpen" @close="onFilterClose" v-model="pageForm.audit"></van-dropdown-item>
<van-dropdown-item :options="unionOptions" @change="doSearch" @open="onFilterOpen" @close="onFilterClose" v-model="pageForm.unionId"></van-dropdown-item>
<van-dropdown-item :options="lineTypeOptions" @change="doSearch" @open="onFilterOpen" @close="onFilterClose" 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">
<div v-if="pageSkeletonLoading" class="tour-content-skeleton">
<div class="tour-content-skeleton__card" v-for="index in 3" :key="'school-approval-skeleton-' + index"></div>
</div>
<div v-if="!pageSkeletonLoading && pageForm.totalCount === 0" class="tour-approval-empty">
<img src="/assets/platform/images/tour/tour-h5-empty-v3.png?v=20260831" alt="暂无审核记录插画">
<div class="tour-approval-empty__title">暂无审核记录</div>
<div class="tour-approval-empty__hint">当前筛选条件下暂时没有疗休养报名</div>
</div>
<table-list v-show="!pageSkeletonLoading" api="/platform/tour/schoolUnionApproval/pageData" :page_form.sync="pageForm" ref="tableListRef" title="userName" :show_loading_overlay="false" @ready="onReady" @loading-change="onTableLoading">
<template #header="{index,row}">
<div class="tour-card-heading">
<div class="tour-card-heading-main">
<div class="tour-card-person">{{ row.userName || '--' }}</div>
<div class="tour-card-job-no">工号:{{ row.jobNo || '--' }}</div>
</div>
<span class="tour-h5-tag tour-card-status" :class="row.taskState === 10 ? 'is-warning' : 'is-success'">{{ row.curTaskName || (row.taskState === 10 ? '待审核' : '已处理') }}</span>
</div>
</template>
<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>
@@ -156,11 +495,11 @@ layout("/layouts/platform_h5.html"){
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<van-icon name="eye-o"></van-icon>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
<i class="fa fa-check"></i>
<van-icon name="edit"></van-icon>
<span>审核</span>
</div>
</template>
@@ -172,6 +511,7 @@ layout("/layouts/platform_h5.html"){
<div class="tour-section-title">{{ formData.taskName || '审核' }}</div>
<van-form ref="formRef">
<van-field
class="tour-approval-opinion"
v-model="formData.tf_opinion"
name="tf_opinion"
label="审批意见"
@@ -197,8 +537,10 @@ layout("/layouts/platform_h5.html"){
<script nonce="${cspNonce!}">
const TOUR_INFO = {
template: /*language=HTML*/ `
<van-action-sheet v-model="visible" title="报名详情">
<div class="tour-approval-sheet">
<van-popup v-model="visible" position="right" :style="{ width: '100%', height: '100%' }" :close-on-click-overlay="false">
<div class="tour-approval-detail">
<van-nav-bar title="报名详情" left-text="返回" left-arrow @click-left="onClose"></van-nav-bar>
<div class="tour-approval-sheet">
<div class="tour-section">
<div class="tour-section-title">教职工信息</div>
<van-cell title="工号" :value="detail.jobNo || ''"></van-cell>
@@ -261,9 +603,10 @@ layout("/layouts/platform_h5.html"){
</template>
</div>
<slot></slot>
<slot></slot>
</div>
</div>
</van-action-sheet>
</van-popup>
`,
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
@@ -276,13 +619,15 @@ layout("/layouts/platform_h5.html"){
allowFamily: false,
fillBedInfo: true,
directFamilyUnitLine: false,
doneTasks: []
doneTasks: [],
popupHistoryToken: ""
}
},
methods: {
onOpen(row) {
this.row = row || {}
this.visible = true
this.$set(this, "visible", true)
window.h5PopupHistory.open(this.popupHistoryToken, "detail")
this.detail = {}
this.familyData = []
this.directRelative = {}
@@ -294,7 +639,9 @@ layout("/layouts/platform_h5.html"){
this.getDoneTasks()
},
onClose() {
this.visible = false
if (!window.h5PopupHistory.close(this.popupHistoryToken, "detail")) {
this.$set(this, "visible", false)
}
},
info() {
this.$axios.post("/platform/tour/schoolUnionApproval/detail", { id: this.row.id }).then((res) => {
@@ -337,6 +684,15 @@ layout("/layouts/platform_h5.html"){
}
return this.hasFamily() ? "是" : "否"
}
},
created() {
this.$set(this, "popupHistoryToken", window.h5PopupHistory.register(() => {
this.$set(this, "visible", false)
this.$emit("closed")
}))
},
beforeDestroy() {
window.h5PopupHistory.unregister(this.popupHistoryToken)
}
}
@@ -370,7 +726,9 @@ layout("/layouts/platform_h5.html"){
{ text: "全部线路类型", value: "" }
],
formData: {},
showApprovalForm: false
showApprovalForm: false,
pageSkeletonLoading: true,
filterOpened: false
}
},
methods: {
@@ -378,12 +736,24 @@ layout("/layouts/platform_h5.html"){
onReady() {
this.doSearch()
},
onTableLoading(state) {
if (state && state.initial) {
this.$set(this, "pageSkeletonLoading", !!state.loading)
}
},
onFilterOpen() {
this.$set(this, "filterOpened", true)
},
onFilterClose() {
this.$set(this, "filterOpened", false)
},
doSearch() {
this.$nextTick(async () => {
await Promise.all([this.loadUnionOptions(), this.loadLineTypeOptions()])
this.$nextTick(() => {
Promise.all([this.loadUnionOptions(), this.loadLineTypeOptions()]).then(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
})
},
loadUnionOptions() {
@@ -435,16 +805,12 @@ layout("/layouts/platform_h5.html"){
}
this.$refs.infoRef.onOpen(row)
},
async handleTaskAction(val) {
try {
await this.$refs.formRef.validate()
} catch (e) {
return
}
this.$dialog.confirm({
handleTaskAction(val) {
this.$refs.formRef.validate().then(() => {
this.$dialog.confirm({
title: "提示",
message: "您确定要提交吗?"
}).then(() => {
}).then(() => {
const loading = this.$toast.loading({
message: "加载中...",
forbidClick: true,
@@ -467,6 +833,7 @@ layout("/layouts/platform_h5.html"){
}).finally(() => {
loading.close()
})
}).catch(() => {})
}).catch(() => {})
}
}
@@ -40,28 +40,132 @@ layout("/layouts/platform_tour_signup_h5.html"){
}
.tour-card-title-row {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
box-sizing: border-box;
}
.tour-card-actions {
margin-left: auto;
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;
.tour-family-tabs {
padding-top: 8px;
}
.tour-family-tabs-shell {
position: relative;
}
.tour-family-tab-arrow {
position: absolute;
top: 14px;
z-index: 2;
width: 28px;
height: 28px;
padding: 0;
border: 1px solid #d7e7fb;
border-radius: 50%;
color: #1989fa;
background-color: rgba(255, 255, 255, .94);
font-size: 15px;
line-height: 26px;
text-align: center;
box-shadow: 0 2px 8px rgba(43, 73, 112, .1);
box-sizing: border-box;
}
.tour-family-tab-arrow.is-left {
left: 7px;
}
.tour-family-tab-arrow.is-right {
right: 7px;
}
.tour-family-tab-arrow:disabled {
border-color: #edf1f6;
color: #c5ceda;
background-color: #f8fafc;
box-shadow: none;
}
.tour-family-tabs.is-single {
padding-top: 0;
}
/* 只有一位亲属时直接展示表单,不显示没有切换意义的页签标题栏。 */
.tour-family-tabs.is-single .van-tabs__wrap {
display: none;
}
.tour-family-tabs .van-tabs__wrap {
height: 40px;
padding: 0 12px;
}
.tour-family-tabs .van-tabs__nav {
background-color: #f6f9fd;
border-radius: 8px;
}
/* 页签标题栏使用原生惯性滚动,复杂亲属表单不再参与手势拖动。 */
.tour-family-tabs .van-tabs__wrap--scrollable .van-tabs__nav {
overflow-x: auto;
overflow-y: hidden;
width: 100%;
justify-content: flex-start;
scroll-behavior: smooth;
overscroll-behavior-x: contain;
touch-action: pan-x;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
}
.tour-family-tabs .van-tabs__wrap--scrollable .van-tab {
-webkit-box-flex: 0 !important;
-webkit-flex: 0 0 auto !important;
flex: 0 0 auto !important;
min-width: 88px;
padding: 0 14px;
color: #718198;
font-size: 13px;
transition: color .2s ease, background-color .2s ease;
}
.tour-family-tabs .van-tab--active {
color: #1989fa;
font-weight: 600;
}
.tour-family-tabs .van-tabs__line {
width: 26px;
height: 3px;
border-radius: 2px;
background-color: #1989fa;
transition-timing-function: cubic-bezier(.22, .61, .36, 1);
}
.tour-family-tabs .van-tabs__track {
transition-timing-function: cubic-bezier(.22, .61, .36, 1) !important;
}
.tour-family-tabs .van-tab__text {
display: block;
max-width: 112px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tour-family-form {
padding-top: 6px;
}
.tour-field-tip {
@@ -96,21 +200,77 @@ layout("/layouts/platform_tour_signup_h5.html"){
font-size: 13px;
text-align: center;
}
<!--#include("../common/theme.css"){}#-->
.tour-apply-page {
padding: 12px 12px 82px;
background-color: #f3f7fd;
}
.tour-apply-card {
margin-bottom: 10px;
border-radius: 12px;
box-shadow: 0 7px 20px rgba(55, 85, 126, .08);
}
.tour-apply-title {
min-height: 46px;
padding: 0 14px 0 20px;
display: flex;
align-items: center;
border-bottom-color: #edf1f6;
color: #202938;
font-size: 16px;
line-height: 22px;
}
.tour-apply-title::before {
top: 15px;
bottom: 15px;
left: 10px;
width: 3px;
height: auto;
border-radius: 2px;
background-color: #1989fa;
}
.tour-card-actions .van-button {
height: 29px;
border-radius: 6px;
}
.tour-submit-bar {
padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
border-top-color: #edf1f6;
box-shadow: 0 -6px 18px rgba(38, 66, 103, .08);
}
.tour-submit-bar .van-button {
height: 42px;
border-radius: 8px;
font-size: 15px;
font-weight: 600;
}
</style>
<div id="app">
<div id="app" class="tour-h5-theme" v-cloak>
<van-nav-bar title="疗休养报名" left-text="返回" left-arrow @click-left="historyBack('/platform/tour/signup/h5/signup')" fixed placeholder></van-nav-bar>
<div class="tour-apply-page">
<van-loading v-if="pageLoading" size="24px" vertical>加载中...</van-loading>
<div v-if="pageLoading" class="tour-content-skeleton">
<div class="tour-content-skeleton__card"></div>
<div class="tour-content-skeleton__card"></div>
<div class="tour-content-skeleton__card"></div>
</div>
<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 required label="身份证号码" v-model="signupForm.idCard" maxlength="30" placeholder="请填写身份证号码"></van-field>
<van-field required label="手机号" v-model="signupForm.mobile" type="tel" maxlength="30" placeholder="请填写手机号"></van-field>
<van-field label="所属工会" readonly v-model="signupForm.unionName"></van-field>
</div>
@@ -177,41 +337,81 @@ layout("/layouts/platform_tour_signup_h5.html"){
当前线路不支持填写亲属
</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>
<div class="tour-family-tabs-shell" :class="{ 'has-arrows': familyData.length > 4 }">
<button
v-if="familyData.length > 4"
type="button"
class="tour-family-tab-arrow is-left"
:disabled="familyActive <= 0"
aria-label="上一位亲属"
@click.stop="moveFamilyTab(-1)">
<van-icon name="arrow-left"></van-icon>
</button>
<van-tabs
ref="familyTabs"
class="tour-family-tabs"
:class="{ 'is-single': familyData.length === 1 }"
v-model="familyActive"
animated
:duration="0.28"
:ellipsis="false"
:swipe-threshold="3"
color="#1989fa"
title-active-color="#1989fa"
@change="handleFamilyTabChange">
<van-tab
v-for="(family, familyIndex) in familyData"
:key="family.id || 'family-' + familyIndex"
:title="familyTabTitle(family, familyIndex)">
<div class="tour-family-form">
<van-field required label="姓名" v-model="family.familyName" maxlength="100" placeholder="请填写姓名"></van-field>
<van-field required label="年龄" v-model.number="family.age" type="digit" placeholder="请填写年龄"></van-field>
<van-field required label="性别">
<template #input>
<van-radio-group v-model="family.gender" direction="horizontal">
<van-radio name="男"></van-radio>
<van-radio name="女"></van-radio>
</van-radio-group>
</template>
</van-field>
<van-field :required="familyContactRequired(family)" label="身份证号码" v-model="family.idCard" maxlength="18" placeholder="请填写身份证号码" @blur="normalizeFamilyIdCard(family)"></van-field>
<van-field :required="familyContactRequired(family)" label="手机号" v-model="family.mobile" type="tel" maxlength="30" placeholder="请填写手机号"></van-field>
<van-field
required
label="与本人关系"
readonly
clickable
is-link
placeholder="请选择关系"
v-model="family.relationship"
@click="openRelationshipPicker">
</van-field>
<van-field
v-if="fillBedInfo"
label="床型"
readonly
clickable
is-link
placeholder="请选择房型"
v-model="family.bedType"
@click="openBedPicker('family')">
</van-field>
<van-field v-if="fillBedInfo" label="床位" v-model="family.bedInfo" maxlength="100" placeholder="请输入床位数量"></van-field>
<van-field v-if="fillBedInfo" label="意向拼床人" v-model="family.intendedRoommate" maxlength="100" placeholder="请输入意向拼床人"></van-field>
<div v-if="fillBedInfo" class="tour-field-tip">提醒:如果跟报名人员同一房间,无须选择!</div>
</div>
</van-tab>
</van-tabs>
<button
v-if="familyData.length > 4"
type="button"
class="tour-family-tab-arrow is-right"
:disabled="familyActive >= familyData.length - 1"
aria-label="下一位亲属"
@click.stop="moveFamilyTab(1)">
<van-icon name="arrow"></van-icon>
</button>
</div>
</template>
<div v-else class="tour-apply-empty">暂无亲属,请按需添加</div>
</div>
@@ -222,16 +422,16 @@ layout("/layouts/platform_tour_signup_h5.html"){
<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 v-model="bedPickerVisible" position="bottom" :close-on-click-overlay="false">
<van-picker show-toolbar :columns="bedTypeColumns" @confirm="confirmBedType" @cancel="closePicker('bed-picker')"></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 v-model="relationshipPickerVisible" position="bottom" :close-on-click-overlay="false">
<van-picker show-toolbar :columns="relationshipColumns" @confirm="confirmRelationship" @cancel="closePicker('relationship-picker')"></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 v-model="directRelativePickerVisible" position="bottom" :close-on-click-overlay="false">
<van-picker show-toolbar :columns="directRelativeColumns" @confirm="confirmDirectRelative" @cancel="closePicker('direct-relative-picker')"></van-picker>
</van-popup>
</div>
@@ -255,6 +455,7 @@ layout("/layouts/platform_tour_signup_h5.html"){
bedPickerVisible: false,
relationshipPickerVisible: false,
directRelativePickerVisible: false,
popupHistoryToken: "",
bedPickerTarget: "self",
matterId: GetQueryString("matterId") || ""
}
@@ -274,6 +475,52 @@ layout("/layouts/platform_tour_signup_h5.html"){
}
},
methods: {
// 页签优先展示亲属姓名;姓名尚未填写时使用亲属序号,便于区分每份亲属表单。
familyTabTitle(family, familyIndex) {
const familyName = family && family.familyName ? String(family.familyName).trim() : ""
return familyName || "亲属" + (familyIndex + 1)
},
// 等待页签渲染完成后再滚动定位,确保新增亲属始终展示在可视区域内。
focusFamilyTab(familyIndex) {
const lastIndex = Math.max(this.familyData.length - 1, 0)
const nextIndex = Math.min(Math.max(Number(familyIndex) || 0, 0), lastIndex)
this.$set(this, "familyActive", nextIndex)
this.$nextTick(() => {
const familyTabs = this.$refs.familyTabs
if (familyTabs && typeof familyTabs.resize === "function") {
familyTabs.resize()
}
this.scrollFamilyTabIntoView(nextIndex)
})
},
// 将当前亲属页签平滑定位到导航可视区域中间,兼容箭头切换和直接点击页签。
scrollFamilyTabIntoView(familyIndex) {
const familyTabs = this.$refs.familyTabs
const tabRoot = familyTabs && familyTabs.$el
if (!tabRoot) return
const tabNav = tabRoot.querySelector(".van-tabs__nav")
const tabItems = tabRoot.querySelectorAll(".van-tab")
const currentTab = tabItems[familyIndex]
if (!tabNav || !currentTab) return
const maxScrollLeft = Math.max(tabNav.scrollWidth - tabNav.clientWidth, 0)
const targetScrollLeft = currentTab.offsetLeft - (tabNav.clientWidth - currentTab.offsetWidth) / 2
tabNav.scrollTo({
left: Math.min(Math.max(targetScrollLeft, 0), maxScrollLeft),
behavior: "smooth"
})
},
// 直接点击页签时,等待 Vant 更新激活状态后再执行横向定位。
handleFamilyTabChange(familyIndex) {
this.$nextTick(() => {
this.scrollFamilyTabIntoView(Number(familyIndex) || 0)
})
},
// 左右箭头按相邻顺序切换亲属,并复用页签组件的平滑定位能力。
moveFamilyTab(step) {
const nextIndex = Number(this.familyActive) + Number(step)
if (nextIndex < 0 || nextIndex >= this.familyData.length) return
this.focusFamilyTab(nextIndex)
},
formatDateTime(date) {
const pad = (value) => String(value).padStart(2, "0")
return date.getFullYear()
@@ -426,13 +673,15 @@ layout("/layouts/platform_tour_signup_h5.html"){
addFamily() {
if (!this.allowFamily || this.isDirectFamilyLine(this.signupForm)) return
this.familyData.push(this.emptyFamily())
this.familyActive = this.familyData.length - 1
this.focusFamilyTab(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)
const removedIndex = this.familyActive
this.familyData.splice(removedIndex, 1)
// 删除当前页签后优先停留在同一位置;删除末项时回到新的最后一项。
this.focusFamilyTab(this.familyData.length === 0 ? 0 : Math.min(removedIndex, this.familyData.length - 1))
this.signupForm.hasFamily = this.familyData.length > 0
},
openBedPicker(target) {
@@ -442,6 +691,7 @@ layout("/layouts/platform_tour_signup_h5.html"){
}
this.bedPickerTarget = target
this.bedPickerVisible = true
window.h5PopupHistory.open(this.popupHistoryToken, "bed-picker")
},
confirmBedType(value) {
if (this.bedPickerTarget === "family") {
@@ -449,7 +699,7 @@ layout("/layouts/platform_tour_signup_h5.html"){
} else {
this.signupForm.bedType = value
}
this.bedPickerVisible = false
this.closePicker("bed-picker")
},
openRelationshipPicker() {
if (this.relationshipColumns.length === 0) {
@@ -457,10 +707,11 @@ layout("/layouts/platform_tour_signup_h5.html"){
return
}
this.relationshipPickerVisible = true
window.h5PopupHistory.open(this.popupHistoryToken, "relationship-picker")
},
confirmRelationship(value) {
this.currentFamily.relationship = value
this.relationshipPickerVisible = false
this.closePicker("relationship-picker")
},
openDirectRelativePicker() {
if (this.directRelativeColumns.length === 0) {
@@ -468,12 +719,27 @@ layout("/layouts/platform_tour_signup_h5.html"){
return
}
this.directRelativePickerVisible = true
window.h5PopupHistory.open(this.popupHistoryToken, "direct-relative-picker")
},
confirmDirectRelative(value) {
const item = value && value.item ? value.item : {}
this.directRelativeForm.relationshipCode = item.code || ""
this.directRelativeForm.relationshipName = item.name || value.text || ""
this.directRelativePickerVisible = false
this.closePicker("direct-relative-picker")
},
closePicker(popupKey) {
if (!window.h5PopupHistory.close(this.popupHistoryToken, popupKey)) {
this.syncPickerVisible(popupKey, false)
}
},
syncPickerVisible(popupKey, visible) {
if (popupKey === "bed-picker") {
this.bedPickerVisible = visible
} else if (popupKey === "relationship-picker") {
this.relationshipPickerVisible = visible
} else if (popupKey === "direct-relative-picker") {
this.directRelativePickerVisible = visible
}
},
normalizeFamilyIdCard(row) {
if (!row || !row.idCard) return
@@ -484,18 +750,46 @@ layout("/layouts/platform_tour_signup_h5.html"){
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)
},
isBlankValue(value) {
return value === undefined || value === null || !String(value).trim()
},
// 年满16周岁的亲属才强制填写身份证号码和手机号。
familyContactRequired(family) {
return family && !this.isBlankValue(family.age) && Number(family.age) >= 16
},
// 报名人身份证号码和手机号为提交报名的基础必填信息。
validateSignupContact() {
if (this.isBlankValue(this.signupForm.idCard)) {
vant.Toast("请填写身份证号码")
return false
}
if (this.isBlankValue(this.signupForm.mobile)) {
vant.Toast("请填写手机号")
return false
}
return true
},
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("请完善亲属姓名、性别、身份证号码和关系")
const invalidBase = this.familyData.some(item => this.isBlankValue(item.familyName)
|| this.isBlankValue(item.age)
|| this.isBlankValue(item.gender)
|| this.isBlankValue(item.relationship))
if (invalidBase) {
vant.Toast("请完善亲属姓名、年龄、性别和与本人关系")
return false
}
const invalidIdCard = this.familyData.some(item => !this.isValidIdCard(item.idCard))
const invalidContact = this.familyData.some(item => this.familyContactRequired(item)
&& (this.isBlankValue(item.idCard) || this.isBlankValue(item.mobile)))
if (invalidContact) {
vant.Toast("年满16周岁的亲属必须填写身份证号码和手机号")
return false
}
const invalidIdCard = this.familyData.some(item => !this.isBlankValue(item.idCard) && !this.isValidIdCard(item.idCard))
if (invalidIdCard) {
vant.Toast("请输入正确的亲属身份证号码")
return false
@@ -528,6 +822,7 @@ layout("/layouts/platform_tour_signup_h5.html"){
},
submitSignup() {
if (this.pageLoading || this.submitLoading) return
if (!this.validateSignupContact()) return
if (!this.validateFamilies()) return
if (!this.validateDirectRelative()) return
const families = this.allowFamily && this.signupForm.hasFamily ? this.familyData : []
@@ -537,13 +832,13 @@ layout("/layouts/platform_tour_signup_h5.html"){
})
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(() => {
this.$showH5PageSkeleton()
window.location.replace("/platform/tour/signup/h5/signup")
})
} else {
@@ -554,13 +849,20 @@ layout("/layouts/platform_tour_signup_h5.html"){
})
}
}).catch(() => {
this.submitLoading = false
// 接口失败提示由全局 Axios 拦截器统一展示,避免与名额已满等业务提示重复。
}).finally(() => {
this.submitLoading = false
})
}
},
created() {
this.popupHistoryToken = window.h5PopupHistory.register((popupKey) => {
this.syncPickerVisible(popupKey, false)
})
this.init()
},
beforeDestroy() {
window.h5PopupHistory.unregister(this.popupHistoryToken)
}
})
</script>
@@ -163,9 +163,122 @@ layout("/layouts/platform_tour_signup_h5.html"){
box-sizing: border-box;
z-index: 20;
}
<!--#include("../common/theme.css"){}#-->
#app.tour-signup-h5 {
padding-bottom: 0;
background-color: #f3f7fd;
}
.tour-signup-banner {
height: 208px;
margin: 12px 12px 0;
border-radius: 14px;
background-color: #1779d8;
box-shadow: 0 8px 22px rgba(30, 112, 207, .2);
}
.tour-signup-swipe img {
height: 208px;
}
.tour-signup-banner::after {
background: linear-gradient(90deg, rgba(8, 72, 145, .74) 0%, rgba(8, 72, 145, .36) 51%, rgba(8, 72, 145, .04) 100%);
}
.tour-signup-banner__text {
left: 18px;
bottom: 25px;
}
.tour-signup-title {
width: 180px;
font-size: 25px;
line-height: 1.35;
letter-spacing: .5px;
}
.tour-signup-subtitle {
margin-top: 10px;
font-size: 13px;
}
.tour-signup-card {
margin: 12px 12px 0;
padding: 14px 14px 16px;
height: calc(100vh - 352px);
height: calc(100vh - 352px - env(safe-area-inset-bottom));
min-height: 240px;
display: flex;
flex-direction: column;
overflow: hidden;
border-radius: 14px;
box-shadow: 0 7px 20px rgba(55, 85, 126, .09);
box-sizing: border-box;
}
.tour-signup-card__header {
flex: 0 0 auto;
}
/* 标题栏保持固定,富文本、PDF、空状态及加载骨架只在卡片剩余区域内滚动。 */
.tour-signup-card > .van-skeleton,
.tour-signup-card > .tour-signup-notice,
.tour-signup-card > .tour-signup-empty {
min-height: 0;
flex: 1;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.tour-signup-card__title {
display: flex;
align-items: center;
color: #202938;
font-size: 16px;
}
.tour-signup-card__title .van-icon {
margin-right: 7px;
color: #1989fa;
font-size: 19px;
}
.tour-signup-card__year {
min-height: 24px;
padding: 2px 9px;
border-radius: 12px;
background-color: #eaf4ff;
line-height: 20px;
box-sizing: border-box;
}
.tour-signup-notice {
font-size: 14px;
line-height: 1.75;
}
.tour-signup-footer {
padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
border-top: 1px solid #edf1f6;
box-shadow: 0 -6px 18px rgba(38, 66, 103, .08);
}
.tour-signup-footer-spacer {
height: 62px;
height: calc(62px + env(safe-area-inset-bottom));
}
.tour-signup-footer .van-button {
height: 42px;
border-radius: 8px;
font-size: 15px;
font-weight: 600;
}
</style>
<div id="app" class="tour-signup-h5">
<div id="app" class="tour-signup-h5 tour-h5-theme" v-cloak>
<van-nav-bar title="疗休养报名" left-text="返回" left-arrow @click-left="$pjaxReplace('/platform/h5/home')" fixed placeholder></van-nav-bar>
<div class="tour-signup-banner">
@@ -175,13 +288,14 @@ layout("/layouts/platform_tour_signup_h5.html"){
</van-swipe-item>
</van-swipe>
<div class="tour-signup-banner__text">
<div class="tour-signup-title">疗休养报名</div>
<div class="tour-signup-title">工会疗休养<br>乐享健康时光</div>
<div class="tour-signup-subtitle">放松身心 · 收获健康 · 美好同行</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__title"><van-icon name="notes-o"></van-icon>报名须知</div>
<div class="tour-signup-card__year">{{ setting.year || currentYear }}年度</div>
</div>
@@ -227,7 +341,7 @@ layout("/layouts/platform_tour_signup_h5.html"){
store,
data() {
return {
loading: false,
loading: true,
currentYear: new Date().getFullYear(),
serviceNoticePdfHref: "",
pdfDoc: null,
@@ -240,8 +354,7 @@ layout("/layouts/platform_tour_signup_h5.html"){
readCountdown: 10,
readCountdownTimer: null,
bannerList: [
"/assets/platform/images/tour/tour-h5-banner-1.jpg",
"/assets/platform/images/tour/tour-h5-banner-2.jpg"
"/assets/platform/images/tour/tour-h5-hero-v3.png?v=20260830"
],
setting: {
year: "",
@@ -419,7 +532,6 @@ layout("/layouts/platform_tour_signup_h5.html"){
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.serviceNoticePdfHref = this.parseContentHref(this.setting.serviceNotice)
@@ -430,6 +542,7 @@ layout("/layouts/platform_tour_signup_h5.html"){
vant.Toast(res.msg || "服务须知加载失败")
}
}).catch(() => {
}).finally(() => {
this.loading = false
})
},
@@ -170,14 +170,111 @@ layout("/layouts/platform_tour_signup_h5.html"){
background: #ffffff;
box-sizing: border-box;
}
<!--#include("../common/theme.css"){}#-->
#app {
background-color: #f3f7fd;
}
.tour-detail-page {
padding: 12px 12px 76px;
background-color: #f3f7fd;
}
.tour-detail-cover {
width: 100%;
height: 178px;
flex: none;
overflow: hidden;
border-radius: 12px;
background-color: #eaf2fb;
box-shadow: 0 7px 20px rgba(55, 85, 126, .1);
}
.tour-detail-cover .van-image__img {
width: 100%;
height: 100%;
object-fit: cover;
}
.tour-detail-header {
margin-top: 10px;
padding: 14px;
border-radius: 12px;
box-shadow: 0 7px 20px rgba(55, 85, 126, .08);
}
.tour-detail-title {
color: #202938;
font-size: 18px;
line-height: 25px;
}
.tour-line-tag {
min-height: 22px;
height: auto;
padding: 2px 8px;
border-radius: 11px;
color: #1989fa;
background-color: #eaf4ff;
font-size: 11px;
line-height: 18px;
}
.tour-detail-meta {
margin-top: 12px;
padding-top: 10px;
border-top: 1px solid #edf1f6;
color: #35445a;
font-size: 13px;
line-height: 1.8;
}
.tour-detail-section {
margin-top: 10px;
padding: 14px;
border-radius: 12px;
box-shadow: 0 7px 20px rgba(55, 85, 126, .08);
}
.tour-detail-section-title {
margin-bottom: 10px;
border-left-color: #1989fa;
color: #202938;
font-size: 16px;
}
.tour-detail-footer {
padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
border-top-color: #edf1f6;
box-shadow: 0 -6px 18px rgba(38, 66, 103, .08);
}
.tour-detail-footer .van-button {
height: 42px;
border-radius: 8px;
font-size: 15px;
font-weight: 600;
}
</style>
<div id="app">
<div id="app" class="tour-h5-theme" v-cloak>
<van-nav-bar title="线路详情" left-text="返回" left-arrow @click-left="historyBack('/platform/tour/signup/h5/signup')" fixed placeholder></van-nav-bar>
<div class="tour-detail-page">
<van-loading v-if="detailLoading" size="24px" vertical>加载中...</van-loading>
<div v-if="detailLoading" class="tour-content-skeleton">
<div class="tour-content-skeleton__cover"></div>
<div class="tour-content-skeleton__title"></div>
<div class="tour-content-skeleton__row"></div>
<div class="tour-content-skeleton__row"></div>
<div class="tour-content-skeleton__card"></div>
</div>
<template v-else>
<van-image class="tour-detail-cover" :src="detailCover()" fit="cover">
<template #loading><van-loading type="spinner" size="22"></van-loading></template>
<template #error><van-icon name="photo-o" size="36" color="#9ba9bc"></van-icon></template>
</van-image>
<div class="tour-detail-header">
<div class="tour-detail-title">{{ lineDetail.lineName || signupDetail.lineName || '线路详情' }}</div>
<div class="tour-detail-tags">
@@ -257,6 +354,9 @@ layout("/layouts/platform_tour_signup_h5.html"){
}
},
methods: {
detailCover() {
return this.lineDetail.mobileThumb || "/assets/platform/images/tour/tour-h5-hero-v3.png?v=20260830"
},
travelPeriod(row) {
if (!row) {
return ""
@@ -493,7 +593,6 @@ layout("/layouts/platform_tour_signup_h5.html"){
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 {
@@ -513,8 +612,9 @@ layout("/layouts/platform_tour_signup_h5.html"){
}, process)
}
}).catch(() => {
this.detailLoading = false
vant.Toast("线路详情加载失败")
}).finally(() => {
this.detailLoading = false
})
},
onApply() {
@@ -523,7 +623,7 @@ layout("/layouts/platform_tour_signup_h5.html"){
return
}
const goApply = () => {
window.location.href = "/platform/tour/signup/h5/apply?matterId=" + encodeURIComponent(this.matterId)
this.$pjaxReplace("/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) {
@@ -21,6 +21,10 @@ layout("/layouts/platform_tour_signup_h5.html"){
box-sizing: border-box;
}
.tour-line-refresh {
min-height: 100%;
}
.tour-line-toolbar {
padding: 10px 0 8px 16px;
background: #f4f6f8;
@@ -60,52 +64,51 @@ layout("/layouts/platform_tour_signup_h5.html"){
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;
gap: 8px;
box-sizing: border-box;
}
.tour-line-union-entry {
width: 48px;
min-height: 42px;
flex-shrink: 0;
width: 100%;
height: 44px;
margin-top: 8px;
border: 0;
padding: 0;
background: transparent;
color: #64748b;
padding: 0 14px;
border-radius: 10px;
background: #ffffff;
color: #66758a;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
font-size: 11px;
line-height: 1.2;
justify-content: space-between;
font-size: 14px;
line-height: 20px;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
box-sizing: border-box;
}
.tour-line-union-entry .van-icon {
color: #64748b;
font-size: 24px;
font-size: 16px;
line-height: 1;
}
.tour-line-union-entry__label {
display: inline-flex;
min-width: 0;
align-items: center;
gap: 6px;
}
.tour-line-union-entry__text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tour-line-union-entry--active,
.tour-line-union-entry--active .van-icon {
color: #0b75bd;
@@ -376,25 +379,252 @@ layout("/layouts/platform_tour_signup_h5.html"){
font-size: 15px;
}
}
.tour-line-thumb-loading {
width: 100%;
height: 100%;
background: linear-gradient(90deg, #edf2f8 25%, #f8fbff 37%, #edf2f8 63%);
background-size: 400% 100%;
animation: tour-content-skeleton-loading 1.35s ease infinite;
}
<!--#include("../common/theme.css"){}#-->
.tour-line-page {
background-color: #f3f7fd;
}
.tour-line-toolbar {
padding: 12px 12px 10px;
background-color: #f3f7fd;
}
.tour-line-toolbar .van-search {
display: flex;
width: 0;
align-items: center;
margin: 0;
padding: 0;
overflow: hidden;
border-radius: 12px;
background-color: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
}
.tour-line-toolbar .van-search__content {
height: 42px;
padding-left: 12px;
align-items: center;
border-radius: 12px;
background-color: #fff;
}
.tour-line-toolbar .van-search__field {
display: flex;
align-items: center;
font-size: 14px;
}
.tour-line-toolbar .van-field__control {
color: #263548;
font-size: 14px;
line-height: 20px;
}
.tour-line-union-entry {
color: #66758a;
}
.tour-line-union-entry .van-icon {
color: #8fa0b5;
}
.tour-line-search-row .tour-line-union-entry {
width: 52px;
height: 42px;
min-height: 42px;
margin-top: 0;
padding: 0;
flex-shrink: 0;
flex-direction: column;
justify-content: center;
gap: 2px;
border-radius: 0;
color: #66758a;
background: transparent;
font-size: 11px;
line-height: 1.2;
box-shadow: none;
}
.tour-line-search-row .tour-line-union-entry .van-icon {
color: #8fa0b5;
font-size: 21px;
}
.tour-line-search-row .tour-line-union-entry--active,
.tour-line-search-row .tour-line-union-entry--active .van-icon {
color: #1989fa;
}
.tour-line-shortcuts {
gap: 8px;
margin-top: 10px;
}
.tour-line-shortcut {
min-height: 86px;
padding: 11px 5px 9px;
border-radius: 11px;
box-shadow: 0 5px 16px rgba(43, 73, 112, .07);
}
.tour-line-shortcut__icon {
width: 38px;
height: 38px;
}
.tour-line-shortcut__text {
font-size: 13px;
}
.tour-line-shortcut__hint {
color: #9aa7b8;
font-size: 10px;
}
.tour-line-scroll {
padding-bottom: 18px;
}
.tour-line-list {
padding: 0 12px;
}
.tour-line-card {
display: block;
min-height: 0;
margin: 0 0 10px;
padding: 0;
overflow: hidden;
border-radius: 10px;
box-shadow: 0 7px 20px rgba(55, 85, 126, .08);
}
.tour-line-thumb {
width: 100%;
height: 150px;
border-radius: 0;
}
.tour-line-main {
position: relative;
padding: 12px 38px 14px 14px;
cursor: pointer;
}
.tour-line-title {
overflow: hidden;
color: #303846;
font-size: 14px;
line-height: 21px;
text-overflow: ellipsis;
white-space: nowrap;
}
.tour-line-type-badge,
.tour-line-tag {
height: auto;
padding: 3px 8px;
border: 0;
border-radius: 4px;
color: #397de0;
background-color: #edf5ff;
font-size: 11px;
line-height: 16px;
font-weight: 400;
}
.tour-line-info {
margin-top: 4px;
padding-top: 0;
border-top: 0;
color: #98a2b2;
font-size: 12px;
line-height: 18px;
}
.tour-line-info span {
color: #98a2b2;
}
.tour-line-tags {
margin-top: 10px;
padding-top: 9px;
gap: 6px;
border-top: 1px solid #edf1f6;
}
.tour-line-card__arrow {
position: absolute;
top: 16px;
right: 14px;
color: #b4bdca;
font-size: 16px;
}
.tour-line-actions {
margin-top: 10px;
margin-right: -24px;
}
.tour-line-action {
display: inline-flex;
min-width: 64px;
height: 30px;
padding: 0 10px;
border: 1px solid #1989fa;
align-items: center;
justify-content: center;
border-radius: 6px;
color: #1989fa;
background: transparent;
font-size: 13px;
line-height: 20px;
box-sizing: border-box;
}
.tour-line-action .van-icon {
margin-right: 5px;
font-size: 15px;
}
.tour-line-action--danger {
border-color: #ee4d5a;
color: #ee4d5a;
background: transparent;
}
.tour-line-action--disabled {
border-color: #d8dee8;
color: #b8c0cc;
background: transparent;
cursor: not-allowed;
}
</style>
<div id="app" class="tour-line-page">
<div id="app" class="tour-line-page tour-h5-theme" v-cloak>
<van-nav-bar title="疗休养线路" left-text="返回" left-arrow @click-left="historyBack('/platform/tour/signup/h5')" 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="请输入线路名称搜索"
shape="round"
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"
@@ -413,6 +643,7 @@ layout("/layouts/platform_tour_signup_h5.html"){
<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 class="tour-line-shortcut__hint">发现身边美景</div>
</div>
</div>
<div
@@ -422,6 +653,7 @@ layout("/layouts/platform_tour_signup_h5.html"){
<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 class="tour-line-shortcut__hint">畅游大好河山</div>
</div>
</div>
<div
@@ -431,23 +663,29 @@ layout("/layouts/platform_tour_signup_h5.html"){
<img class="tour-line-shortcut__icon" src="/assets/platform/images/tour/tour-direct-family-icon.png?v=20260524" alt="直系亲属">
<div class="tour-line-shortcut__text">
<div>直系亲属</div>
<div class="tour-line-shortcut__hint">家属同行共享</div>
</div>
</div>
</div>
</div>
<div class="tour-line-scroll">
<van-pull-refresh class="tour-line-refresh" v-model="refreshing" @refresh="onRefresh">
<div v-if="initialLoading" class="tour-content-skeleton">
<div class="tour-content-skeleton__card" v-for="index in 3" :key="'line-skeleton-' + index"></div>
</div>
<van-list
v-else
v-model="loading"
:finished="finished"
finished-text="没有更多了"
:finished-text="list.length > 0 ? '没有更多了' : ''"
@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" lazy-load>
<template #loading>
<div class="tour-line-thumb-empty">图片加载中</div>
<div class="tour-line-thumb-loading" aria-label="图片加载中"></div>
</template>
<template #error>
<div class="tour-line-thumb-empty">暂无图片</div>
@@ -463,10 +701,6 @@ layout("/layouts/platform_tour_signup_h5.html"){
<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>
@@ -474,6 +708,11 @@ layout("/layouts/platform_tour_signup_h5.html"){
<div><span>服务单位:</span>{{ row.travelAgencyName || '暂无' }}</div>
-->
</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>
<van-icon class="tour-line-card__arrow" name="arrow"></van-icon>
<div class="tour-line-actions">
<button
v-if="showSignupAction(row)"
@@ -485,7 +724,8 @@ layout("/layouts/platform_tour_signup_h5.html"){
}"
:disabled="signupActionDisabled(row)"
@click.stop="handleSignupAction(row)">
{{ signupActionText(row) }}
<van-icon :name="canRevokeSignup(row) ? 'revoke' : 'edit'"></van-icon>
<span>{{ signupActionText(row) }}</span>
</button>
</div>
</div>
@@ -493,10 +733,15 @@ layout("/layouts/platform_tour_signup_h5.html"){
</div>
</van-list>
<van-empty v-if="!loading && list.length === 0" description="暂无线路"></van-empty>
<div v-if="!loading && list.length === 0" class="tour-h5-empty">
<img src="/assets/platform/images/tour/tour-h5-empty-v3.png?v=20260831" alt="暂无线路插画">
<div class="tour-h5-empty__title">暂无线路</div>
<div class="tour-h5-empty__hint">当前筛选条件下暂未发布疗休养线路</div>
</div>
</van-pull-refresh>
</div>
<van-popup v-model="showUnionPopup" position="bottom" round>
<van-popup v-model="showUnionPopup" position="bottom" round :close-on-click-overlay="false" @click-overlay="closeUnionPopup">
<div class="tour-line-union-popup">
<div class="tour-line-union-popup__title">选择分工会</div>
<div class="tour-line-union-popup__list">
@@ -525,9 +770,12 @@ layout("/layouts/platform_tour_signup_h5.html"){
return {
list: [],
loading: false,
refreshing: false,
initialLoading: true,
finished: false,
lineChecking: false,
showUnionPopup: false,
popupHistoryToken: "",
unionList: [
{id: "", name: "全部分工会"}
],
@@ -619,7 +867,7 @@ layout("/layouts/platform_tour_signup_h5.html"){
return
}
const go = () => {
window.location.href = "/platform/tour/signup/h5/apply?matterId=" + encodeURIComponent(row.matterId)
this.$pjaxReplace("/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) {
@@ -672,6 +920,7 @@ layout("/layouts/platform_tour_signup_h5.html"){
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.list = []
this.$set(this, "initialLoading", true)
this.finished = false
this.loadData()
},
@@ -689,17 +938,26 @@ layout("/layouts/platform_tour_signup_h5.html"){
},
openUnionPopup() {
this.showUnionPopup = true
if (this.popupHistoryToken) {
window.h5PopupHistory.open(this.popupHistoryToken, "union-picker")
}
if (this.unionList.length <= 1) {
this.loadUnionOptions()
}
},
closeUnionPopup() {
if (!this.popupHistoryToken || !window.h5PopupHistory.close(this.popupHistoryToken, "union-picker")) {
this.showUnionPopup = false
}
},
selectUnion(item) {
const unionId = item && item.id ? item.id : ""
this.showUnionPopup = false
if (this.pageForm.unionId === unionId) {
this.closeUnionPopup()
return
}
this.pageForm.unionId = unionId
this.closeUnionPopup()
this.doSearch()
},
loadUnionOptions() {
@@ -723,7 +981,6 @@ layout("/layouts/platform_tour_signup_h5.html"){
}
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
@@ -739,10 +996,27 @@ layout("/layouts/platform_tour_signup_h5.html"){
this.pageForm.pageNumber += 1
}
}).catch(() => {
this.loading = false
this.finished = true
}).finally(() => {
this.loading = false
this.$set(this, "refreshing", false)
this.$set(this, "initialLoading", false)
})
},
/**
* 下拉刷新当前筛选结果:保留线路名称、线路类型和分工会条件,只重置分页数据。
*/
onRefresh() {
if (this.loading) {
this.$set(this, "refreshing", false)
return
}
this.$set(this.pageForm, "pageNumber", 1)
this.$set(this.pageForm, "totalCount", 0)
this.$set(this, "list", [])
this.$set(this, "finished", false)
this.loadData()
},
onLine(row) {
if (!row || !row.lineId || !row.matterId) {
vant.Toast("线路信息不完整")
@@ -758,7 +1032,6 @@ layout("/layouts/platform_tour_signup_h5.html"){
"x-requested-with": "XMLHttpRequest"
}
}).then((resp) => {
this.lineChecking = false
const res = resp.data || {}
if (res.code !== 0) {
vant.Dialog.alert({
@@ -768,23 +1041,32 @@ layout("/layouts/platform_tour_signup_h5.html"){
})
return
}
window.location.href = "/platform/tour/signup/h5/lineInfo?matterId="
this.$pjaxReplace("/platform/tour/signup/h5/lineInfo?matterId="
+ encodeURIComponent(row.matterId)
+ "&lineId="
+ encodeURIComponent(row.lineId)
+ encodeURIComponent(row.lineId))
}).catch((err) => {
this.lineChecking = false
vant.Dialog.alert({
title: "温馨提醒",
message: err && err.msg ? err.msg : "当前线路暂不能报名",
confirmButtonColor: "#1867b0"
})
}).finally(() => {
this.lineChecking = false
})
}
},
created() {
this.popupHistoryToken = window.h5PopupHistory.register((popupKey) => {
if (popupKey === "union-picker") {
this.showUnionPopup = false
}
})
this.loadUnionOptions()
this.loadData()
},
beforeDestroy() {
window.h5PopupHistory.unregister(this.popupHistoryToken)
}
})
</script>
@@ -2,34 +2,36 @@
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
<style>
.tour-approval-page {
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
background: #f4f6f8;
background: #f4f8ff;
box-sizing: border-box;
}
.tour-approval-sticky {
flex-shrink: 0;
background: #f4f6f8;
padding: 8px 12px 10px;
background: #f4f8ff;
}
.tour-approval-list-scroll {
min-height: 0;
flex: 1;
overflow-y: auto;
padding-bottom: 18px;
padding: 2px 12px 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;
border-radius: 18px;
background: #ffffff;
box-shadow: 0 4px 16px rgba(34, 94, 170, 0.06);
}
.tour-card-tags {
@@ -39,12 +41,36 @@ layout("/layouts/platform_h5.html"){
margin-bottom: 8px;
}
.tour-card-heading {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 10px;
}
.tour-card-person {
overflow: hidden;
color: #17233d;
font-size: 16px;
font-weight: 700;
text-overflow: ellipsis;
white-space: nowrap;
}
.tour-card-job-no {
margin-left: 7px;
color: #64748b;
font-size: 12px;
font-weight: 400;
}
.tour-tag {
display: inline-flex;
align-items: center;
min-height: 22px;
padding: 0 8px;
border-radius: 4px;
border-radius: 999px;
background: #eef6ff;
color: #0b75bd;
font-size: 12px;
@@ -60,17 +86,24 @@ layout("/layouts/platform_h5.html"){
color: #d97706;
}
.tour-approval-detail {
height: 100%;
background: #f4f8ff;
}
.tour-approval-sheet {
max-height: 86vh;
height: calc(100% - 46px);
overflow-y: auto;
padding-bottom: env(safe-area-inset-bottom);
background: #f4f6f8;
padding: 2px 0 calc(86px + env(safe-area-inset-bottom));
background: #f4f8ff;
box-sizing: border-box;
}
.tour-section {
margin: 10px 12px;
overflow: hidden;
border-radius: 8px;
border-radius: 14px;
box-shadow: 0 4px 16px rgba(34, 94, 170, 0.06);
background: #ffffff;
}
@@ -78,7 +111,7 @@ layout("/layouts/platform_h5.html"){
position: relative;
padding: 14px 16px;
border-bottom: 1px solid #edf0f4;
color: #0b75bd;
color: #17233d;
font-size: 16px;
font-weight: 700;
line-height: 1.2;
@@ -92,7 +125,7 @@ layout("/layouts/platform_h5.html"){
width: 30px;
height: 4px;
border-radius: 0 0 3px 3px;
background: #0b75bd;
background: #1677ff;
}
.tour-approval-actions {
@@ -106,9 +139,303 @@ layout("/layouts/platform_h5.html"){
white-space: pre-wrap;
word-break: break-word;
}
.tour-approval-sticky /deep/ .van-dropdown-menu__bar,
.tour-approval-sticky .van-dropdown-menu__bar {
height: 44px;
border-radius: 12px;
box-shadow: 0 4px 16px rgba(34, 94, 170, 0.06);
}
.tour-approval-list-scroll /deep/ .table-list-card,
.tour-approval-list-scroll /deep/ .list-card {
border-radius: 14px;
box-shadow: 0 4px 16px rgba(34, 94, 170, 0.06);
}
.tour-approval-list-scroll /deep/ .action-btn {
color: #1677ff;
}
<!--#include("../common/theme.css"){}#-->
/* 审核列表与提案校工会预审核采用同一信息层级和卡片规范。 */
body {
background-color: #f3f7fd;
}
.tour-approval-page {
background-color: #f3f7fd;
}
.tour-approval-page .van-nav-bar {
background-color: #fff;
}
.tour-approval-page .van-nav-bar__title {
color: #172033;
font-size: 16px;
font-weight: 600;
}
.tour-approval-sticky {
padding: 22px 12px 1px;
background-color: #f3f7fd;
}
.tour-approval-search {
margin: 0 0 8px;
padding: 0;
overflow: hidden;
border-radius: 12px;
background-color: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .1);
}
.tour-approval-search .van-search__content {
height: 42px;
padding-left: 12px;
align-items: center;
border-radius: 12px;
background-color: #fff;
}
.tour-approval-search .van-field__control {
color: #263548;
font-size: 14px;
}
.tour-approval-filter {
margin-bottom: 10px;
overflow: hidden;
border-radius: 10px;
background-color: #fff;
box-shadow: 0 5px 16px rgba(43, 73, 112, .08);
transition: border-radius .18s ease;
}
.tour-approval-filter.is-open {
border-radius: 10px 10px 0 0;
}
.tour-approval-filter .van-dropdown-menu__bar,
.tour-approval-filter .van-dropdown-menu__item,
.tour-approval-filter .van-dropdown-menu__title {
height: 44px;
}
.tour-approval-filter .van-dropdown-menu__bar {
border-radius: 0;
box-shadow: none;
}
.tour-approval-filter .van-dropdown-menu__title {
display: inline-flex;
align-items: center;
color: #66758a;
font-size: 14px;
line-height: 20px;
}
.tour-approval-filter .van-dropdown-menu__title--active,
.tour-approval-filter .van-dropdown-item__option--active,
.tour-approval-filter .van-dropdown-item__option--active .van-dropdown-item__icon {
color: #1989fa;
}
.tour-approval-filter .van-dropdown-item__content {
width: calc(100% - 24px);
margin: 0 12px;
overflow: hidden;
box-sizing: border-box;
border-radius: 0 0 12px 12px;
box-shadow: 0 10px 24px rgba(31, 65, 108, .14);
}
.tour-approval-filter .van-dropdown-item__option {
display: flex;
min-height: 48px;
padding: 0 16px;
align-items: center;
color: #46566c;
font-size: 14px;
}
.tour-approval-list-scroll {
padding: 2px 12px 24px;
background-color: #f3f7fd;
}
.tour-approval-list-scroll .empty-state {
display: none;
}
.tour-approval-empty {
display: flex;
min-height: 280px;
padding: 46px 18px 36px;
align-items: center;
justify-content: center;
flex-direction: column;
box-sizing: border-box;
text-align: center;
}
.tour-approval-empty img {
display: block;
width: 60%;
max-width: 190px;
height: auto;
object-fit: contain;
}
.tour-approval-empty__title {
margin-top: 10px;
color: #50627a;
font-size: 15px;
line-height: 22px;
font-weight: 600;
}
.tour-approval-empty__hint {
margin-top: 5px;
color: #9aa7b8;
font-size: 12px;
line-height: 18px;
}
.tour-card-heading {
margin: 0 0 7px;
align-items: flex-start;
}
.tour-card-heading-main {
min-width: 0;
padding-right: 10px;
flex: 1;
}
.tour-card-person {
color: #253247;
font-size: 15px;
line-height: 22px;
font-weight: 600;
}
.tour-card-job-no {
display: block;
margin: 1px 0 0;
color: #9ba6b6;
font-size: 12px;
line-height: 18px;
}
.tour-card-status {
flex: none;
}
.tour-card-tags {
margin-bottom: 7px;
}
.tour-approval-list-scroll .action-btn {
color: #1989fa;
}
.tour-approval-list-scroll .action-btn .van-icon {
margin-right: 6px;
font-size: 15px;
}
.tour-approval-detail,
.tour-approval-sheet {
background-color: #f3f7fd;
}
.tour-approval-sheet {
padding: 18px 12px calc(92px + env(safe-area-inset-bottom));
}
.tour-section {
margin: 0 0 11px;
overflow: hidden;
border: 1px solid #e9eff7;
border-radius: 10px;
background-color: #fff;
box-shadow: 0 3px 11px rgba(51, 87, 126, .08);
}
.tour-section-title {
position: relative;
padding: 12px 14px 8px 21px;
border-bottom: 0;
color: #354255;
font-size: 15px;
line-height: 22px;
font-weight: 600;
}
.tour-section-title::before {
top: 15px;
bottom: 11px;
left: 12px;
width: 2px;
height: auto;
border-radius: 2px;
background-color: #1989fa;
}
.tour-section .van-cell {
min-height: 44px;
padding: 10px 14px;
background: transparent;
}
.tour-section .van-cell__title {
flex: 0 0 92px;
width: 92px;
color: #596779;
font-size: 14px;
}
.tour-section .van-cell__value {
color: #4f5d70;
font-size: 14px;
text-align: left;
overflow-wrap: anywhere;
}
.tour-section .tour-approval-opinion {
width: calc(100% - 28px);
margin: 0 14px 14px;
padding: 12px;
border: 1px solid #c9d5e5;
border-radius: 10px;
background-color: #f7f9fd;
box-sizing: border-box;
}
.tour-approval-actions {
gap: 10px;
padding: 0 14px 14px;
}
.tour-approval-actions .van-button {
height: 40px;
border-radius: 10px;
font-size: 14px;
}
@media (max-width: 350px) {
.tour-approval-sticky,
.tour-approval-list-scroll {
padding-right: 10px;
padding-left: 10px;
}
}
</style>
<div id="app" class="tour-approval-page" v-cloak>
<div id="app" class="tour-approval-page tour-h5-theme" 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">
@@ -116,26 +443,38 @@ layout("/layouts/platform_h5.html"){
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="lineTypeOptions" @change="doSearch" v-model="pageForm.lineType"></van-dropdown-item>
<van-dropdown-menu class="tour-approval-filter" :class="{'is-open': filterOpened}" active-color="#1989fa" :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item :options="auditOptions" @change="doSearch" @open="onFilterOpen" @close="onFilterClose" v-model="pageForm.audit"></van-dropdown-item>
<van-dropdown-item :options="lineTypeOptions" @change="doSearch" @open="onFilterOpen" @close="onFilterClose" v-model="pageForm.lineType"></van-dropdown-item>
</van-dropdown-menu>
</van-sticky>
<div class="tour-approval-list-scroll">
<table-list api="/platform/tour/unionApproval/pageData" :page_form.sync="pageForm" ref="tableListRef" title="userName" @ready="onReady">
<div v-if="pageSkeletonLoading" class="tour-content-skeleton">
<div class="tour-content-skeleton__card" v-for="index in 3" :key="'union-approval-skeleton-' + index"></div>
</div>
<div v-if="!pageSkeletonLoading && pageForm.totalCount === 0" class="tour-approval-empty">
<img src="/assets/platform/images/tour/tour-h5-empty-v3.png?v=20260831" alt="暂无审核记录插画">
<div class="tour-approval-empty__title">暂无审核记录</div>
<div class="tour-approval-empty__hint">当前筛选条件下暂时没有疗休养报名</div>
</div>
<table-list v-show="!pageSkeletonLoading" api="/platform/tour/unionApproval/pageData" :page_form.sync="pageForm" ref="tableListRef" title="userName" :show_loading_overlay="false" @ready="onReady" @loading-change="onTableLoading">
<template #header="{index,row}">
<div class="tour-card-heading">
<div class="tour-card-heading-main">
<div class="tour-card-person">{{ row.userName || '--' }}</div>
<div class="tour-card-job-no">工号:{{ row.jobNo || '--' }}</div>
</div>
<span class="tour-h5-tag tour-card-status" :class="row.taskState === 10 ? 'is-warning' : 'is-success'">{{ row.curTaskName || (row.taskState === 10 ? '待审核' : '已处理') }}</span>
</div>
</template>
<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>
@@ -155,11 +494,11 @@ layout("/layouts/platform_h5.html"){
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<van-icon name="eye-o"></van-icon>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
<i class="fa fa-check"></i>
<van-icon name="edit"></van-icon>
<span>审核</span>
</div>
</template>
@@ -171,6 +510,7 @@ layout("/layouts/platform_h5.html"){
<div class="tour-section-title">{{ formData.taskName || '审核' }}</div>
<van-form ref="formRef">
<van-field
class="tour-approval-opinion"
v-model="formData.tf_opinion"
name="tf_opinion"
label="审批意见"
@@ -195,8 +535,10 @@ layout("/layouts/platform_h5.html"){
<script nonce="${cspNonce!}">
const TOUR_INFO = {
template: /*language=HTML*/ `
<van-action-sheet v-model="visible" title="报名详情">
<div class="tour-approval-sheet">
<van-popup v-model="visible" position="right" :style="{ width: '100%', height: '100%' }" :close-on-click-overlay="false">
<div class="tour-approval-detail">
<van-nav-bar title="报名详情" left-text="返回" left-arrow @click-left="onClose"></van-nav-bar>
<div class="tour-approval-sheet">
<div class="tour-section">
<div class="tour-section-title">教职工信息</div>
<van-cell title="工号" :value="detail.jobNo || ''"></van-cell>
@@ -259,9 +601,10 @@ layout("/layouts/platform_h5.html"){
</template>
</div>
<slot></slot>
<slot></slot>
</div>
</div>
</van-action-sheet>
</van-popup>
`,
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
@@ -274,13 +617,15 @@ layout("/layouts/platform_h5.html"){
allowFamily: false,
fillBedInfo: true,
directFamilyUnitLine: false,
doneTasks: []
doneTasks: [],
popupHistoryToken: ""
}
},
methods: {
onOpen(row) {
this.row = row || {}
this.visible = true
this.$set(this, "visible", true)
window.h5PopupHistory.open(this.popupHistoryToken, "detail")
this.detail = {}
this.familyData = []
this.directRelative = {}
@@ -292,7 +637,9 @@ layout("/layouts/platform_h5.html"){
this.getDoneTasks()
},
onClose() {
this.visible = false
if (!window.h5PopupHistory.close(this.popupHistoryToken, "detail")) {
this.$set(this, "visible", false)
}
},
info() {
this.$axios.post("/platform/tour/unionApproval/detail", { id: this.row.id }).then((res) => {
@@ -335,6 +682,15 @@ layout("/layouts/platform_h5.html"){
}
return this.hasFamily() ? "是" : "否"
}
},
created() {
this.$set(this, "popupHistoryToken", window.h5PopupHistory.register(() => {
this.$set(this, "visible", false)
this.$emit("closed")
}))
},
beforeDestroy() {
window.h5PopupHistory.unregister(this.popupHistoryToken)
}
}
@@ -364,7 +720,9 @@ layout("/layouts/platform_h5.html"){
{ text: "全部线路类型", value: "" }
],
formData: {},
showApprovalForm: false
showApprovalForm: false,
pageSkeletonLoading: true,
filterOpened: false
}
},
methods: {
@@ -372,12 +730,24 @@ layout("/layouts/platform_h5.html"){
onReady() {
this.doSearch()
},
onTableLoading(state) {
if (state && state.initial) {
this.$set(this, "pageSkeletonLoading", !!state.loading)
}
},
onFilterOpen() {
this.$set(this, "filterOpened", true)
},
onFilterClose() {
this.$set(this, "filterOpened", false)
},
doSearch() {
this.$nextTick(async () => {
await this.loadLineTypeOptions()
this.$nextTick(() => {
this.loadLineTypeOptions().then(() => {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
})
},
loadLineTypeOptions() {
@@ -410,16 +780,12 @@ layout("/layouts/platform_h5.html"){
}
this.$refs.infoRef.onOpen(row)
},
async handleTaskAction(val) {
try {
await this.$refs.formRef.validate()
} catch (e) {
return
}
this.$dialog.confirm({
handleTaskAction(val) {
this.$refs.formRef.validate().then(() => {
this.$dialog.confirm({
title: "提示",
message: "您确定要提交吗?"
}).then(() => {
}).then(() => {
const loading = this.$toast.loading({
message: "加载中...",
forbidClick: true,
@@ -442,6 +808,7 @@ layout("/layouts/platform_h5.html"){
}).finally(() => {
loading.close()
})
}).catch(() => {})
}).catch(() => {})
}
}