# Conflicts:
#	src/main/resources/views/platform/theRapyRecuperation/signUp/index.html
This commit is contained in:
Paidax
2025-06-19 02:54:00 +08:00
5 changed files with 1534 additions and 14 deletions
@@ -0,0 +1,30 @@
package io.v.nutz.zhgh.therapyRecuperation.controller.mine;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.nutz.dao.Dao;
import org.nutz.ioc.loader.annotation.Inject;
import org.nutz.ioc.loader.annotation.IocBean;
import org.nutz.mvc.annotation.At;
import org.nutz.mvc.annotation.Ok;
/**
* @author zhf
* @date 2025/6/18 22:46
* @description 我的疗休养
*/
@IocBean
@Ok("json:full")
@At("/platform/theRapyRecuperation/mineEnroll")
public class MineTheRapyRecuperationEnrollController {
@Inject
private Dao dao;
@At("")
@Ok("beetl:/platform/theRapyRecuperation/mine/index.html")
@RequiresPermissions("theRapyRecuperation.mineEnroll")
public void index() {
}
}
@@ -0,0 +1,220 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<div class="search">
<div class="search-item">
<div class="search-item-label">年度:</div>
<div class="search-item-option">
<el-date-picker
v-model="pageForm.year"
type="year"
:clearable="false"
value-format="yyyy" style="width: 100%"
@change="doSearch"
placeholder="选择年">
</el-date-picker>
</div>
</div>
<div class="search-item">
<div class="search-item-label">线路名称:</div>
<div class="search-item-option">
<el-radio-group v-model="pageForm.theRapyRecuperationType" @change="doSearch">
<el-radio-button :label="item.value" v-for="item in chooseButton">{{item.label}}
</el-radio-button>
</el-radio-group>
</div>
</div>
</div>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="线路列表">
<template #func>
<el-radio-group v-model="pageForm.signUpStateId" @change="doSearch">
<el-radio-button label="1">已报名</el-radio-button>
<el-radio-button label="2">已参加</el-radio-button>
<el-radio-button label="3">未参加</el-radio-button>
</el-radio-group>
</template>
</table-tool>
<el-table :data="tableData" :size="tableSize" @sort-change="pageOrder"
align="center"
header-align="center">
<el-table-column :index="indexMethod" align="center" header-align="center" label="序号" type="index"
width="80px"></el-table-column>
<el-table-column align="center" header-align="center" label="年度" type="signingUptime">
<template scope="{row}">
{{moment(row.signingUptime).format('YYYY')}}
</template>
</el-table-column>
<el-table-column align="center" header-align="center"
:label="pageForm.theRapyRecuperationType===3?'名称':'线路名称'">
<template scope="{row}">
{{pageForm.theRapyRecuperationType===3?row.baseName:row.lineName}}
</template>
</el-table-column>
<template v-if="pageForm.theRapyRecuperationType===3">
<el-table-column align="center" header-align="center"
label="联系人" prop="baseContactPerson">
</el-table-column>
<el-table-column align="center" header-align="center"
label="联系方式" prop="baseContactNumber">
</el-table-column>
<el-table-column align="center" header-align="center"
label="已报人数" prop="signUpUserNum">
</el-table-column>
</template>
<template v-if="pageForm.theRapyRecuperationType!==3">
<el-table-column align="center" header-align="center"
label="时间标段" prop="lotName">
</el-table-column>
<el-table-column align="center" header-align="center"
label="组织形式" prop="signUpMode">
<template scope="{row}">
<span>{{row.signUpMode === 1 ? '校工会组织' : '分工会组织'}}</span>
</template>
</el-table-column>
<el-table-column align="center" header-align="center"
label="旅行社" prop="travelAgencyName">
</el-table-column>
<el-table-column align="center" header-align="center"
label="联系方式" prop="contactMobileNumber">
</el-table-column>
<el-table-column align="center" header-align="center"
label="随行家属" prop="companionCount">
</el-table-column>
</template>
<el-table-column label="操作" width="400">
<template scope="{row}">
<el-button @click="openLine(row)" size="mini" type="primary">查看</el-button>
<el-button @click="edit(row)" size="mini" type="primary"
v-if="pageForm.theRapyRecuperationType != '2' && moment().unix() < moment(row.changeEndTime).unix()
&& ![2715,2725,2735].includes(row.stateId)"
:disabled="row.isTakePartIn == true || getDisabled(row)">修改报名信息
</el-button>
<el-button @click="editOther(row)" size="mini" type="primary"
v-if="pageForm.theRapyRecuperationType != '2' && moment().unix() < moment(row.changeEndTime).unix()
&& ![2715,2725,2735].includes(row.stateId)"
:disabled="row.isTakePartIn == true || getDisabled(row)">修改成其他线路
</el-button>
<el-button @click="cancel(row)" size="mini" type="danger" :disabled="getDisabled(row)">
&ensp;
</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
<el-dialog
title="详情"
:visible.sync="lineDialogVisible"
width="50%">
<info ref="lineRef" :index="pageForm.theRapyRecuperationType"></info>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="lineDialogVisible = false">关 闭</el-button>
</span>
</el-dialog>
</div>
<script>
<!--#include('../signUp/info.js'){}#-->
const vue = new Vue({
el: '#app',
mixins: [initTableMixins],
data() {
return {
pageForm: {
theRapyRecuperationType: 0,
signUpStateId: 1,
year: new Date().getFullYear() + "",
},
chooseButton: [],
configData: {},
lineDialogVisible: false
}
},
components: {
'info': INFO
},
methods: {
edit(o) {
},
editOther(o) {
},
openLine(row) {
this.lineDialogVisible = true
this.$nextTick(() => {
this.$refs.lineRef.openInit(row.takePartInLineId, row.takePartInBaseManagementId)
})
},
getDisabled(o) {
if (o.regionalNature === '省内') {
return this.configData.isSnLine && o.stateId === 2750
}
if (o.regionalNature === '省外') {
return this.configData.isSwLine && o.stateId === 2750
}
return true
},
async cancel(o) {
if (this.pageForm.theRapyRecuperationType != '2') {
const re = await $.post('/platform/theRapyRecuperation/line/enroll/canDelete/' + o.id)
if (re.code !== 0) {
this.$message.error(re.msg)
return
}
this.$confirm('您确定要撤销此报名吗?', '温馨提醒', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
const res = await $.post('/platform/theRapyRecuperation/line/enroll/doDeleteMyEnrollInfoById/' + o.id)
if (res.code === 0) {
this.$message.success(res.msg)
this.pageData()
} else {
this.$message.error(res.msg)
}
})
}
},
pageData() {
sublime.showLoadingbar();
this.tableLoading = true
$.post('/platform/theRapyRecuperation/line/enroll/mySignUpPageData', this.pageForm, (data) => {
sublime.closeLoadingbar();
this.tableLoading = false
if (data.code === 0) {
this.tableData = data.data.list;
this.pageForm.totalCount = data.data.totalCount;
} else {
this.$message.error(data.msg);
}
}, "json");
},
async getConfigData() {
const resp = await $.get('/platform/theRapyRecuperation/TheRapyConfig/findOne')
this.configData = resp.data
},
},
async created() {
this.chooseButton = await getEnumOptions('TheRapyRecuperationType')
this.chooseButton = this.chooseButton.filter(o => o.value !== 2)
await this.getConfigData()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,469 @@
<!--#
layout("/layouts/platform.html"){
#-->
<style>
.therapy-page {
background: #ffffff;
}
.therapy-content {
position: relative;
padding: 10px 20px;
}
.category-nav {
background: white;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
overflow: hidden;
}
.category-nav .category-list {
display: flex;
border-bottom: 1px solid #ebeef5;
}
.category-nav .category-item {
flex: 1;
text-align: center;
padding: 20px 0;
font-size: 16px;
color: #606266;
cursor: pointer;
transition: all 0.3s;
position: relative;
}
.category-nav .category-item:hover {
color: #1890ff;
background-color: #f5f7fa;
}
.category-nav .category-item.active {
color: #1890ff;
font-weight: 600;
background-color: #ecf5ff;
}
.category-nav .category-item.active::after {
content: '';
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 3px;
background: #1890ff;
}
.route-cards {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.route-card {
border-radius: 8px;
overflow: hidden;
transition: all 0.3s;
background: white;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
position: relative;
display: flex;
flex-direction: column;
height: 100%;
}
.route-card:hover {
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
}
/* 卡片图片区域 */
.route-image {
height: 160px;
background-size: cover;
background-position: center;
position: relative;
}
.route-image::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.1) 0%, rgba(0, 0, 0, 0.4) 100%);
}
.route-image-title {
position: absolute;
bottom: 15px;
left: 15px;
right: 15px;
color: white;
font-size: 18px;
font-weight: 600;
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
z-index: 2;
}
.route-header {
padding: 15px 15px 10px;
border-bottom: 1px solid #f0f0f0;
}
.route-title {
font-size: 16px;
font-weight: 600;
color: #303133;
margin: 0 0 8px;
line-height: 1.4;
}
.route-date {
font-size: 13px;
color: #909399;
display: flex;
align-items: center;
}
.route-date i {
margin-right: 5px;
}
.route-body {
padding: 15px;
flex: 1;
}
.route-info-item {
display: flex;
justify-content: space-between;
margin-bottom: 12px;
font-size: 14px;
}
.route-info-label {
color: #909399;
}
.route-info-value {
color: #606266;
font-weight: 500;
}
.route-footer {
padding: 15px;
background: #f9f9f9;
display: flex;
justify-content: space-between;
align-items: center;
}
.route-status {
font-size: 13px;
}
.route-signup-btn {
border-radius: 20px;
border: none;
color: white;
font-size: 13px;
}
.hotel-phone {
color: #1890ff;
display: flex;
align-items: center;
}
.hotel-phone i {
margin-right: 5px;
}
.badge {
display: inline-block;
padding: 3px 8px;
border-radius: 12px;
font-size: 12px;
line-height: 1;
}
.badge-primary {
background: rgba(24, 144, 255, 0.1);
color: #1890ff;
}
.empty-data {
padding: 60px 0;
text-align: center;
}
.empty-data i {
font-size: 60px;
color: #e0e0e0;
margin-bottom: 20px;
}
.empty-data p {
color: #909399;
font-size: 14px;
}
</style>
<div id="app" v-cloak>
<guava>
<div class="therapy-page">
<div class="therapy-content">
<!-- 分类导航 -->
<div class="category-nav">
<div class="category-list">
<div
v-for="item in categoryOptions"
:key="item.value"
class="category-item"
:class="{active: pageForm.theRapyRecuperationType === item.value}"
@click="switchCategory(item.value)">
<i :class="getCategoryIcon(item.value)" style="margin-right: 8px;"></i>
{{ item.label }}
</div>
</div>
</div>
<!-- 加载提示 -->
<div v-if="loading" style="text-align: center; padding: 40px 0;">
<i class="el-icon-loading" style="font-size: 30px; color: #1890ff;"></i>
<p style="margin-top: 10px; color: #909399;">正在加载数据...</p>
</div>
<!-- 无数据提示 -->
<div v-else-if="tableData.length === 0" class="empty-data">
<i class="el-icon-warning-outline"></i>
<p>暂无可报名的疗休养线路</p>
</div>
<!-- 卡片网格布局 - 省内/省外线路 -->
<div v-else-if="pageForm.theRapyRecuperationType !== '3'" class="route-cards">
<div class="route-card" v-for="(item, index) in tableData" :key="index">
<!-- 卡片图片区域 -->
<div
class="route-image"
:style="item.fileId ? 'background-image: url(/file_server/fileStreamPreview?id=' + item.fileId + ')' : ''">
<div class="route-image-title">{{ item.lineName }}</div>
</div>
<div class="route-header">
<div class="route-date">
<i class="el-icon-date"></i> {{ item.lotName }}
</div>
</div>
<div class="route-body">
<div class="route-info-item">
<span class="route-info-label">组织机构</span>
<span class="route-info-value">
<span class="badge badge-primary">
{{ item.signUpMode === 2 ? '校工会' : item.usUnionName }}
</span>
</span>
</div>
</div>
<div class="route-footer">
<el-button
type="text"
size="small"
@click="viewDetails(item)">
<i class="el-icon-view"></i> 查看详情
</el-button>
<el-button
class="route-signup-btn"
type="primary"
size="mini"
@click="handleSignUp(item)">
立即报名
</el-button>
</div>
</div>
</div>
<!-- 卡片网格布局 - 定点疗休养 -->
<div v-else class="route-cards">
<div class="route-card" v-for="(item, index) in tableData" :key="index">
<!-- 卡片图片区域 -->
<div
class="route-image"
:style="item.fileId ? 'background-image: url(/file_server/fileStreamPreview?id=' + item.fileId + ')' : ''">
<div class="route-image-title">{{ item.baseName }}</div>
</div>
<!-- <div class="route-header">-->
<!-- <div class="route-date">-->
<!-- <i class="el-icon-location"></i> {{ item.baseAddress || '暂无地址信息' }}-->
<!-- </div>-->
<!-- </div>-->
<div class="route-body">
<div class="route-info-item">
<span class="route-info-label">联系人</span>
<span class="route-info-value">{{ item.baseContactPerson }}</span>
</div>
<div class="route-info-item">
<span class="route-info-label">联系方式</span>
<span class="route-info-value hotel-phone">
<i class="el-icon-phone"></i>
{{ item.baseContactNumber }}
</span>
</div>
<div class="route-info-item">
<span class="route-info-label">已报名人数</span>
<span class="route-info-value">
<span class="badge badge-primary">{{ item.signUpUserNum }}</span>
</span>
</div>
<div class="route-info-item" v-if="item.baseRemark">
<span class="route-info-label">酒店说明</span>
<span class="route-info-value">{{ item.baseRemark }}</span>
</div>
</div>
<div class="route-footer">
<el-button
type="text"
size="small"
@click="viewDetails(item)">
<i class="el-icon-view"></i> 查看详情
</el-button>
<el-button
class="route-signup-btn"
type="primary"
size="mini"
@click="handleSignUp(item)">
立即报名
</el-button>
</div>
</div>
</div>
<!--#include("/layouts/pagination.html"){}#-->
</div>
</div>
</guava>
<line-sign-up ref="lineSignUpRef"></line-sign-up>
</div>
<script>
<!--#include('lineSignUp.js'){}#-->
new Vue({
el: '#app',
mixins: [initTableMixins],
components: {
'line-sign-up': LineSignUp
},
data() {
return {
config: {},
categoryOptions: [],
tableData: [],
loading: false,
pageForm: {
theRapyRecuperationType: '0', // 默认使用字符串类型
lineUnionType: 3,
unionId: "${@shiro.getPrincipalProperty('union').getId()}",
pageNumber: 1,
pageSize: 20,
totalCount: 0
},
}
},
methods: {
// 获取数据
pageData() {
this.loading = true;
const params = {...this.pageForm};
params.theRapyRecuperationType = parseInt(params.theRapyRecuperationType);
$.post('/platform/theRapyRecuperation/line/enroll/pageData', params).then(res => {
this.loading = false;
if (res.code === 0) {
this.tableData = res.data.list;
this.pageForm.totalCount = res.data.totalCount;
} else {
this.$message.error(res.msg || '获取数据失败');
}
})
},
// 获取配置
async getConfig() {
const res = await $.post('/platform/theRapyRecuperation/TheRapyConfig/findOne')
if (res.code === 0) {
this.config = res.data
}
},
// 获取分类 (线路、旅行社)
getCategory() {
getEnumOptions('TheRapyRecuperationType').then(res => {
// 过滤并转换为字符串类型
this.categoryOptions = res.filter(item => item.value !== 2)
.map(item => ({
...item,
value: item.value.toString()
}));
console.log('分类选项:', this.categoryOptions);
})
},
// 获取分类图标
getCategoryIcon(value) {
const icons = {
'0': 'el-icon-map-location', // 省内线路
'1': 'el-icon-location-information', // 省外线路
'3': 'el-icon-house' // 定点疗休养
};
return icons[value] || 'el-icon-location-outline';
},
// 切换分类
switchCategory(value) {
this.pageForm.theRapyRecuperationType = value;
this.pageForm.pageNumber = 1;
this.pageData();
},
// 查看详情
viewDetails(item) {
},
// 处理报名
async handleSignUp(row) {
const {code,data,msg} = await $.post('/platform/theRapyRecuperation/line/enroll/validSignUpInfo',{
enroll: JSON.stringify({
isNormal: true,
takePartInLineId: row.usId,
takePartInUnionId: row.takePartInUnionId,
}),
})
if (code !== 0) {
this.$message.error(msg);
return;
}
if (["0", "1"].includes(this.pageForm.theRapyRecuperationType)) {
// 线路
this.$refs.lineSignUpRef.onOpen(row, this.pageForm, this.config);
} else if (this.pageForm.theRapyRecuperationType === 3) {
// 定点疗休养
}
}
},
created() {
this.pageData();
this.getConfig();
this.getCategory();
}
});
</script>
<!--#
}
#-->
@@ -5,7 +5,8 @@ const INFO = {
<template v-if="[0, 1].includes(index)">
<el-descriptions :column="2" border class="descriptions-form">
<el-descriptions-item label="线路名称" :span="2">
<el-tag>{{lineData.lotName}}</el-tag>{{lineData.lineName}}
<el-tag>{{lineData.lotName}}</el-tag>
{{lineData.lineName}}
</el-descriptions-item>
<el-descriptions-item label="区 域">{{ lineData.regionalNature }}</el-descriptions-item>
@@ -13,10 +14,12 @@ const INFO = {
<el-descriptions-item label="当前报名">
<template v-if="lineData.regionalNature == '省内'">
<template v-if="configData.familyInfo == 2">
{{lineData.signUpUserNum + lineData.signUpUserFamilyNum + '(家属' + lineData.signUpUserFamilyNum + '人)'}}
{{lineData.signUpUserNum + lineData.signUpUserFamilyNum + '(家属' +
lineData.signUpUserFamilyNum + '人)'}}
</template>
<template v-else>
{{lineData.signUpUserNum + lineData.familyNumber + '(家属' + lineData.familyNumber + '人)'}}
{{lineData.signUpUserNum + lineData.familyNumber + '(家属' + lineData.familyNumber +
'人)'}}
</template>
</template>
<template v-else>
@@ -36,35 +39,104 @@ const INFO = {
{{moment(lineData.playStartTime).format('YYYY-MM-DD HH:mm') + ' ~ ' +
moment(lineData.playEndTime).format('YYYY-MM-DD HH:mm')}}
</el-descriptions-item>
<el-descriptions-item label="旅&ensp;行&ensp;社">
<el-descriptions-item label="旅社">
{{lineData.travelAgencyName}}
</el-descriptions-item>
<el-descriptions-item label="联系方式">
<el-descriptions-item label="联系方式" :span="2">
{{lineData.contactMobileNumber}}
</el-descriptions-item>
<el-descriptions-item label="线路详情" :span="2">
<div v-html="lineData.content" @click="contentClick($event)"></div>
</el-descriptions-item>
</el-descriptions>
<el-divider></el-divider>
<div></div>
</template>
<!-- 定点 -->
<template v-if="index == 3">
<el-descriptions :column="2" border class="descriptions-form">
<el-descriptions-item label="酒店名称" :span="2">
<el-tag>{{baseData.lotName}}</el-tag>
{{baseData.baseName}}
</el-descriptions-item>
<el-descriptions-item label="区 域">{{ baseData.regionalNature }}</el-descriptions-item>
<el-descriptions-item label="预计费用">{{ baseData.estimatedCost }}元</el-descriptions-item>
<el-descriptions-item label="报名时间">
{{moment(baseData.signUpStartTime).format('YYYY-MM-DD HH:mm') + ' ~ ' +
moment(baseData.signUpEndTime).format('YYYY-MM-DD HH:mm')}}
</el-descriptions-item>
<el-descriptions-item label="出行时间">
{{moment(baseData.playStartTime).format('YYYY-MM-DD HH:mm') + ' ~ ' +
moment(baseData.playEndTime).format('YYYY-MM-DD HH:mm')}}
</el-descriptions-item>
<el-descriptions-item label="酒店介绍" :span="2">
<div v-html="baseData.content" @click="contentClick($event)"></div>
</el-descriptions-item>
</el-descriptions>
</template>
</div>
`,
props: {
id: { type: String, required: true },
index: { type: Number, required: true },
edit_other: { type: String, required: false, default: '' },
index: {type: Number, required: true},
edit_other: {type: String, required: false, default: ''},
},
data(){
return{
data() {
return {
lineData: {},
baseData: {},
configData: {},
}
},
methods: {
contentClick(e) {
document.addEventListener('click', function (event) {
// 检查点击的元素是否是<a>标签
if (event.target.tagName === 'A') {
// 阻止默认行为,即不进行链接跳转或下载
event.preventDefault();
}
});
const params = new URLSearchParams(new URL(e.target.href).search);
const id = params.get('id');
if (id) {
let pdfStreamUrl = encodeURIComponent('/file_server/fileStreamPreview?id=' + id) //将路径转码
window.open('/assets/platform/plugins/pdfJs/web/viewer.html?file=' + pdfStreamUrl, e.target.innerText)
}
},
async getConfigData() {
const resp = await $.get('/platform/theRapyRecuperation/TheRapyConfig/findOne')
this.configData = resp.data
},
async getLineData(takePartInLineId) {
if (takePartInLineId == null){
this.lineData={}
}
const resp = await $.get("/platform/theRapyRecuperation/line/enroll/selectLineAllInfo", {
usId: takePartInLineId
})
if (resp.code === 0) {
this.lineData = resp.data
}
},
async getBaseData(takePartInBaseManagementId) {
if (takePartInBaseManagementId == null){
this.baseData={}
}
const res = await $.get('/platform/theRapyRecuperation/baseManagement/selectBaseAllInfo', {
id: takePartInBaseManagementId
})
if (res.code === 0) {
this.baseData = res.data
}
},
async openInit(takePartInLineId,takePartInBaseManagementId) {
await this.getConfigData()
await this.getLineData(takePartInLineId)
await this.getBaseData(takePartInBaseManagementId)
}
}
}
@@ -0,0 +1,729 @@
const LineSignUp = {
template: /*language=HTML*/ `
<el-dialog title="报名" :visible.sync="visible" width="70%" :close-on-click-modal="false"
top="50px"
class="line-sign-up-dialog">
<div class="play-times-container">
<div v-if="playTimes.length > 0">
<div class="section-title">请选择出行时间</div>
<el-table :data="playTimes" highlight-current-row @current-change="handleCurrentChange"
class="time-table" :row-class-name="tableRowClassName">
<el-table-column
label="序号"
width="70"
align="center">
<template slot-scope="scope">
<div class="sequence-number">{{ scope.$index + 1 }}</div>
</template>
</el-table-column>
<el-table-column
label="出行时间"
prop="playStartTime"
min-width="120">
<template slot-scope="scope">
{{ moment(scope.row.playStartTime).format('YYYY-MM-DD') }}
</template>
</el-table-column>
<el-table-column
label="报名截止时间"
prop="signUpEndTime"
min-width="150">
<template slot-scope="scope">
{{ moment(scope.row.signUpEndTime).format('YYYY-MM-DD HH:mm') }}
</template>
</el-table-column>
<el-table-column
label="报名限制人数"
prop="maxApplyNum"
width="120"
align="center">
</el-table-column>
<el-table-column
label="已报人数"
min-width="150">
<template slot-scope="scope">
<template v-if="config.familyInfo == 2">
{{ (scope.row.signUpUserNum + scope.row.signUpUserFamilyNum) }}人
(家属{{ scope.row.signUpUserFamilyNum }}人)
</template>
<template v-else>
{{ (scope.row.signUpUserNum + scope.row.signUpUserFamilyNum) }}人
(家属{{ scope.row.familyNumber }}人)
</template>
</template>
</el-table-column>
<el-table-column
label="剩余名额"
width="100"
align="center">
<template slot-scope="scope">
{{ Math.max(0, scope.row.maxApplyNum - (scope.row.signUpUserNum +
scope.row.signUpUserFamilyNum)) }}人
</template>
</el-table-column>
<el-table-column
label="状态"
width="100"
align="center">
<template slot-scope="scope">
<el-tag size="small" type="danger"
v-if="(scope.row.signUpUserNum + scope.row.signUpUserFamilyNum) >= scope.row.maxApplyNum">
已满
</el-tag>
<el-tag size="small" type="warning"
v-else-if="new Date(scope.row.signUpEndTime) < new Date()">
已截止
</el-tag>
<el-tag size="small" type="success" v-else>
可报名
</el-tag>
</template>
</el-table-column>
</el-table>
</div>
<div v-else class="no-times">
<i class="el-icon-warning-outline"></i>
<p>暂无可选出行时间</p>
</div>
</div>
<!-- 报名表单 -->
<div v-if="selectedTime" class="signup-form">
<div class="section-title">填写报名信息</div>
<el-form ref="signupFormRef" :model="signupForm" label-width="100px" :rules="rules">
<!-- 人员基础信息 -->
<div class="form-section">
<div class="sub-section-title">人员基础信息</div>
<el-form-item label="姓名" prop="mainName">
<el-input v-model="signupForm.userName" placeholder="请输入姓名" readonly></el-input>
</el-form-item>
<el-form-item label="工号" prop="loginName">
<el-input v-model="signupForm.loginName" placeholder="请输入工号" readonly></el-input>
</el-form-item>
<el-form-item label="身份证号" prop="idCard">
<el-input v-model="signupForm.idCard" placeholder="请输入身份证号"></el-input>
</el-form-item>
<el-form-item label="手机号" prop="mobile">
<el-input v-model="signupForm.mobile" placeholder="请输入手机号"></el-input>
</el-form-item>
<el-form-item label="所属工会" prop="unionName">
<el-input v-model="signupForm.unionName" readonly></el-input>
</el-form-item>
</div>
<!-- 随行人信息 -->
<div class="form-section">
<div class="sub-section-title">
随行人信息
<el-button type="text" size="small" @click="addAccompanyingPerson" icon="el-icon-plus"
v-if="config.familyInfo === 2">添加随行人
</el-button>
</div>
<div v-if="config.familyInfo === 2" class="accompanying-person-container">
<div v-for="(person, index) in signupForm.companionList" :key="index"
class="accompanying-person">
<div class="person-header">
<span>随行人 {{index + 1}}</span>
<el-button type="text" size="small" @click="removeAccompanyingPerson(index)"
icon="el-icon-delete" class="remove-btn">移除
</el-button>
</div>
<el-form-item :label="'姓名'" :prop="'companionList.' + index + '.userName'"
:rules="rules.userName">
<el-input v-model="person.userName" placeholder="请输入姓名"></el-input>
</el-form-item>
<el-form-item :label="'身份证号'" :prop="'companionList.' + index + '.idCard'"
:rules="rules.idCard">
<el-input v-model="person.idCard" placeholder="请输入身份证号"
@blur="parseIdCard(index)"></el-input>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item :label="'性别'" :prop="'companionList.' + index + '.sex'">
<el-input v-model="person.sex" placeholder="根据身份证号自动读取"
disabled></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item :label="'年龄'" :prop="'companionList.' + index + '.age'">
<el-input v-model.number="person.age" placeholder="根据身份证号自动读取"
disabled></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="身高" :prop="'companionList.' + index + '.height'"
v-if="person.age && person.age <= 12">
<el-input v-model.number="person.height" placeholder="请输入身高">
<template slot="append">cm</template>
</el-input>
</el-form-item>
<el-form-item :label="'手机号'" :prop="'companionList.' + index + '.mobile'"
:rules="rules.mobile">
<el-input v-model="person.mobile" placeholder="请输入手机号"></el-input>
</el-form-item>
<el-divider v-if="index < signupForm.companionList.length - 1"></el-divider>
</div>
<div v-if="signupForm.companionList.length === 0" class="empty-accompanying">
<p>暂无随行人信息,请点击上方"添加随行人"按钮添加</p>
<p>限填写1岁以上70岁以下随行人</p>
</div>
</div>
<template v-else>
<el-form-item label="家属人数" prop="familyNumber"
:rules="[{ required: true, message: '请输入家属人数', trigger: 'blur' }]">
<el-input v-model.number="signupForm.familyNumber"
placeholder="请输入家属人数"></el-input>
</el-form-item>
</template>
</div>
<!-- 房间信息 -->
<div class="form-section" v-if="config.bedInfo">
<div class="sub-section-title">房间信息</div>
<el-form-item label="房间数量" prop="roomCount"
:rules="[{ required: true, message: '请选择房间数量', trigger: 'change'}]">
<el-select v-model="signupForm.roomCount" placeholder="请选择房间数量">
<el-option v-for="room in roomColumns" :key="room.value" :label="room.label"
:value="room.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="用房备注" prop="roomRemark" v-if="signupForm.roomCount"
:rules="[{ required: true, message: '请选择用房备注', trigger: 'change'}]">
<el-select v-model="signupForm.roomRemark" placeholder="请选择用房备注">
<el-option v-for="room in roomRemarkColumns" :key="room.value" :label="room.label"
:value="room.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="拼房人" prop="roommate" v-if="signupForm.roomRemark === 2">
<el-input v-model="signupForm.roommate" placeholder="请输入拼房人"></el-input>
</el-form-item>
<el-form-item label="其它备注" prop="otherRemark">
<el-input type="textarea" v-model="signupForm.otherRemark"
placeholder="如有需要请填写其它备注信息"></el-input>
</el-form-item>
</div>
<!-- 其他信息 -->
<div class="form-section">
<div class="sub-section-title">其他信息</div>
<el-form-item label="第二意向线路" prop="intentionLineId"
:rules="[{ required: true, message: '请选择第二意向线路', trigger: 'change'}]">
<el-select v-model="signupForm.intentionLineId" placeholder="请选择第二意向线路">
<el-option v-for="item in intentionLineColumns" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
<el-form-item label="家属人数" prop="carryFamilyNum"
:rules="[{ required: true, message: '请选择是否携带家属', trigger: 'change' }]">
<el-select v-model="signupForm.carryFamilyNum" placeholder="请选择是否携带家属">
<el-option label="0" :value="0"></el-option>
<el-option label="1" :value="1"></el-option>
<el-option label="2" :value="2"></el-option>
<el-option label="3" :value="3"></el-option>
</el-select>
:
</el-form-item>
</div>
<!-- 安全告知书 -->
<div style="display: flex">
<el-checkbox size="medium" style="width: 70%;color: #F56C6C;white-space: nowrap"
@change="readChangeSafe" v-model="hasReadSafe">
我已查看
<span @click="openReadSafe" style="color: #0e5996; text-decoration: underline">
《安全告知书》
</span>
<el-tag @click="openReadSafe" size="mini" effect="dark">查看</el-tag>
</el-checkbox>
</div>
</el-form>
</div>
<template #footer>
<el-button @click="visible = false">取消</el-button>
<el-button type="primary" @click="submitSignup" :disabled="!selectedTime">确定</el-button>
</template>
<el-dialog title="安全告知书" :visible.sync="readVisibleSafe" :close-on-click-modal="false"
:close-on-press-escape="false" append-to-body>
<iframe
:src="readSafeUrl + '#toolbar=0'"
width="100%"
height="550px"
frameborder="0"
></iframe>
<el-row justify="end" type="flex" class="mt10">
<el-button @click="onReadSafe" type="primary">我已阅读确认</el-button>
</el-row>
</el-dialog>
</el-dialog>
`,
data() {
return {
visible: false,
line: {},
pageForm: {},
config: {},
playTimes: [],
selectedTime: null,
signupForm: {
userName: "${@shiro.getPrincipalProperty('username')}",
loginName: "${@shiro.getPrincipalProperty('loginname')}",
unionName: "${@shiro.getPrincipalProperty('union').getUnionname()}",
idCard: "${@shiro.getPrincipalProperty('idcard')}",
mobile: "${@shiro.getPrincipalProperty('mobile')}",
lineName: '',
takePartInLineId: '',
takePartInUnionId: '',
companionList: [],
bedInfo: {},
roomCount: null,
},
rules: {
mainName: [
{required: true, message: '请输入姓名', trigger: 'blur'},
{min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur'}
],
mainIdCard: [
{required: true, message: '请输入身份证号', trigger: 'blur'},
{
pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/,
message: '请输入正确的身份证号',
trigger: 'blur'
}
],
mainGender: [
{required: true, message: '请选择性别', trigger: 'change'}
],
mainPhone: [
{required: true, message: '请输入手机号', trigger: 'blur'},
{pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur'}
],
accompaningName: [
{required: true, message: '请输入姓名', trigger: 'blur'},
{min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur'}
],
accompanyingIdCard: [
{required: true, message: '请输入身份证号', trigger: 'blur'},
{
pattern: /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/,
message: '请输入正确的身份证号',
trigger: 'blur'
}
],
accompanyingPhone: [
{required: true, message: '请输入手机号', trigger: 'blur'},
{pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号', trigger: 'blur'}
],
accompaningGender: [
{required: true, message: '请选择性别', trigger: 'change'}
]
},
basicRoomColumns: [
{label: '0.5间(一张床)', value: 0.5},
{label: '1间(两张床)', value: 1},
{label: '1.5间(三张床)', value: 1.5},
{label: '2间(四张床)', value: 2},
],
roomColumns: [],
basicRoomRemarkColumns: [
{label: '携带家属,1间房', value: 1},
{label: '已与其他老师自由组合共住1间房', value: 2},
{label: '1间房,额外支付单房差', value: 3},
{label: '正常报名', value: 4},
{label: '随机拼房', value: 5},
{label: '2间房,额外支付单房差', value: 6},
],
roomRemarkColumns: [],
// 第二意向线路
intentionLineColumns: [],
carryFamilyColumns: [0, 1, 2, 3],
// 安全告知书相关
readVisibleSafe: false,
clickReadSafe: false,
hasReadSafe: false,
readSafeUrl: '',
// 报名id
enrollId: null
}
},
watch: {
'signupForm.companionList': {
handler(newVal) {
const {companionList = []} = this.signupForm;
const companionCount = companionList.length;
const bedCount = 0.5 + (companionCount * 0.5);
const bedCountStrategies = {
0.5: () => this.basicRoomColumns.filter(o => o.value <= 1),
1: () => this.basicRoomColumns.filter(o => o.value === 1),
1.5: () => this.basicRoomColumns.filter(o => [1, 2].includes(o.value)),
2: () => this.basicRoomColumns.filter(o => o.value === 2)
};
this.roomColumns = bedCountStrategies[bedCount]?.() || [];
this.signupForm.carryFamilyNum = companionCount
},
deep: true,
immediate: true
},
'signupForm.roomCount'(oldVal, newVal) {
if (newVal && oldVal !== newVal) {
this.$set(this.signupForm, 'roomRemark', '')
}
this.updateRoomRemarkColumns()
}
},
methods: {
async onOpen(row, pageForm, config, enrollId = null) {
this.visible = true;
this.line = row;
this.pageForm = pageForm;
this.config = config;
this.selectedTime = null;
this.enrollId = enrollId;
this.loadSecondChoiceOptions();
await this.getPlayTimes()
if (this.enrollId) {
await this.getEnrollInfo()
}
},
// 获取报名信息
async getEnrollInfo() {
$.post('/platform/theRapyRecuperation/line/enroll/findSignUpInfoById', {id: this.enrollId}).then(res => {
if (res.code === 0) {
this.signupForm = res.data
} else {
this.$message.error(res.msg)
}
})
},
// 获取出行时间
async getPlayTimes() {
const {code, data, msg} = await $.post('/platform/theRapyRecuperation/line/enroll/getSelectLineById',
{
lineId: this.line.lineId,
unionId: this.pageForm.unionId,
theRapyRecuperationType: this.pageForm.theRapyRecuperationType,
lineUnionType: this.pageForm.lineUnionType,
}
)
if (code === 0) {
this.playTimes = data
}
},
// 选择出行时间
handleCurrentChange(row) {
if (!row) return;
if ((row.signUpUserNum + row.signUpUserFamilyNum) >= row.maxApplyNum) {
this.$message.warning('报名人数已满')
return
}
if (new Date(row.signUpEndTime) < new Date()) {
this.$message.warning('报名已截止')
return
}
this.selectedTime = row;
},
// 表格行样式
tableRowClassName({row, rowIndex}) {
if (this.selectedTime && row.usId === this.selectedTime.usId) {
return 'selected-row';
}
// 人数已满的行显示灰色
if ((row.signUpUserNum + row.signUpUserFamilyNum) >= row.maxApplyNum) {
return 'full-row';
}
// 已截止的行显示灰色
if (new Date(row.signUpEndTime) < new Date()) {
return 'expired-row';
}
return '';
},
// 添加随行人
addAccompanyingPerson() {
if (this.signupForm.companionList.length === 3) {
this.$message.warning('最多添加3个随行人')
return
}
this.signupForm.companionList.push({});
this.$set(this.signupForm, 'roomCount', '')
this.$set(this.signupForm, 'roomRemark', '')
},
// 移除随行人
removeAccompanyingPerson(index) {
this.signupForm.companionList.splice(index, 1);
this.$set(this.signupForm, 'roomCount', '')
this.$set(this.signupForm, 'roomRemark', '')
},
// 根据身份证解析年龄和性别
parseIdCard(index) {
let idCard = this.signupForm.companionList[index].idCard;
if (!idCard || idCard.length < 15) return;
// 解析性别 (第17位,奇数为男,偶数为女)
if (idCard.length >= 17) {
const genderBit = parseInt(idCard.substr(16, 1));
this.signupForm.companionList[index].gender = genderBit % 2 === 1 ? 1 : 2; // 1-男, 2-女
}
// 解析年龄
const birthYear = idCard.length === 18 ? idCard.substr(6, 4) : '19' + idCard.substr(6, 2);
const birthMonth = idCard.length === 18 ? idCard.substr(10, 2) : idCard.substr(8, 2);
const birthDay = idCard.length === 18 ? idCard.substr(12, 2) : idCard.substr(10, 2);
const birthDate = new Date(birthYear + '-' + birthMonth + '-' + birthDay)
const today = new Date();
let age = today.getFullYear() - birthDate.getFullYear();
if (today.getMonth() < birthDate.getMonth() ||
(today.getMonth() === birthDate.getMonth() && today.getDate() < birthDate.getDate())) {
age--;
}
this.signupForm.companionList[index].age = age;
},
// 根据房间数量更新用房备注
updateRoomRemarkColumns() {
const {roomCount, companionList = []} = this.signupForm;
const companionCount = companionList.length;
const roomCountStrategies = {
0.5: () => [2, 5],
1: () => {
if (companionCount === 0) return [3];
if (companionCount === 1) return [4];
return [1];
},
1.5: () => [1, 3],
2: () => companionCount < 3 ? [6] : [4]
};
const matchedValues = roomCountStrategies[roomCount]?.();
this.roomRemarkColumns = matchedValues !== undefined
? this.basicRoomRemarkColumns.filter(o => matchedValues.includes(o.value))
: [...this.basicRoomRemarkColumns]; // 默认处理:恢复基础列
// 如果最后的roomRemarkColumns的length只有1,直接默认第一个元素显示
if (this.roomRemarkColumns.length === 1) {
this.$set(this.signupForm, 'roomRemark', this.roomRemarkColumns[0].value)
}
},
// 加载第二意向线路选项
loadSecondChoiceOptions() {
$.post('/platform/mobile/theRapyRecuperation/getOpenLineList', {id: this.line?.usId}).then(res => {
if (res.code === 0) {
this.intentionLineColumns = res.data
}
})
},
// 展示安全告知书
async openReadSafe() {
let pdfStreamUrl = encodeURIComponent(CREATE_PREVIEW_URL(this.config.files[0].id)) //将路径转码
this.readSafeUrl = '/assets/platform/plugins/pdfJs/web/viewer.html?file=' + pdfStreamUrl
this.readVisibleSafe = true
},
// 确认阅读安全告知书
onReadSafe() {
this.clickReadSafe = true
this.hasReadSafe = true
this.readVisibleSafe = false
},
// 阅读安全告知书时,点击取消
readChangeSafe() {
if (this.clickReadSafe === false) {
// this.$message.error('请先阅读《安全告知书》')
this.hasReadSafe = false
} else {
this.hasReadSafe = true
}
},
// 提交报名
async submitSignup() {
this.$refs.signupFormRef.validate(valid => {
if (valid) {
this.$confirm("您确定要提交吗?", "提示", {type: "warning"})
.then(() => {
$.post('/platform/theRapyRecuperation/line/enroll/doSignUpForLine', {enroll: JSON.stringify(this.signupForm)}).then(res => {
if (res.code === 0) {
this.$message.success(res.msg)
} else {
this.$message.error(res.msg)
}
})
})
}
})
},
},
style: /*language=CSS*/ `
.line-sign-up-dialog .el-dialog__body {
padding: 0 20px 20px 20px;
}
.play-times-container {
margin-bottom: 30px;
}
.section-title {
font-size: 18px;
font-weight: 600;
color: #303133;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #EBEEF5;
}
.sub-section-title {
font-size: 16px;
font-weight: 500;
color: #303133;
margin-bottom: 15px;
display: flex;
justify-content: space-between;
align-items: center;
}
.form-section {
margin-bottom: 30px;
padding: 20px;
background-color: #f9f9f9;
border-radius: 5px;
}
.time-table {
width: 100%;
margin-bottom: 20px;
}
.time-table .selected-row {
background-color: #ecf5ff;
}
.time-table .full-row td {
color: #909399;
background-color: #f8f8f8;
}
.time-table .expired-row td {
color: #909399;
background-color: #f8f8f8;
}
.sequence-number {
width: 24px;
height: 24px;
line-height: 24px;
border-radius: 50%;
background-color: #409EFF;
color: white;
font-weight: bold;
margin: 0 auto;
}
.full-row .sequence-number,
.expired-row .sequence-number {
background-color: #909399;
}
.no-times {
text-align: center;
color: #909399;
padding: 40px 0;
background: #f8f8f8;
border-radius: 8px;
}
.no-times i {
font-size: 40px;
margin-bottom: 15px;
color: #DCDFE6;
}
.signup-form {
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #EBEEF5;
}
.selected-time {
font-weight: 500;
color: #409EFF;
}
.accompanying-person-container {
/*display: flex;*/
/*column-gap: 20px;*/
}
.accompanying-person {
position: relative;
margin-bottom: 15px;
}
.person-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
font-weight: 500;
}
.remove-btn {
color: #F56C6C;
}
.empty-accompanying {
text-align: center;
padding: 20px;
color: #909399;
background-color: #f4f4f5;
border-radius: 4px;
}
`
};