疗休养优化1.2
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
-- Add signup ledger age field for mobile confirmation snapshot.
|
||||
ALTER TABLE `tour_ledger`
|
||||
ADD COLUMN `age` int DEFAULT NULL COMMENT '年龄' AFTER `gender`;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- 疗休养配置增加PC首页报名浮动入口控制字段。
|
||||
ALTER TABLE `tour_setting`
|
||||
ADD COLUMN `homeSignupEntryEnabled` tinyint(1) DEFAULT 0 COMMENT '是否展示PC首页报名入口' AFTER `enabled`,
|
||||
ADD COLUMN `homeSignupEntryImage` varchar(500) DEFAULT NULL COMMENT 'PC首页报名入口图片' AFTER `homeSignupEntryEnabled`;
|
||||
@@ -23,6 +23,8 @@ CREATE TABLE IF NOT EXISTS `tour_setting` (
|
||||
`allowFamily` tinyint(1) DEFAULT 0 COMMENT '是否允许携带家属',
|
||||
`fillBedInfo` tinyint(1) DEFAULT 1 COMMENT '是否填报床位信息',
|
||||
`enabled` tinyint(1) DEFAULT 1 COMMENT '是否启用',
|
||||
`homeSignupEntryEnabled` tinyint(1) DEFAULT 0 COMMENT '是否展示PC首页报名入口',
|
||||
`homeSignupEntryImage` varchar(500) DEFAULT NULL COMMENT 'PC首页报名入口图片',
|
||||
`serviceNotice` text COMMENT '服务须知',
|
||||
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
|
||||
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
|
||||
@@ -87,6 +89,8 @@ CREATE TABLE IF NOT EXISTS `tour_user_assignment` (
|
||||
`loginName` varchar(50) DEFAULT NULL COMMENT '工号',
|
||||
`userName` varchar(100) DEFAULT NULL COMMENT '姓名',
|
||||
`gender` varchar(20) DEFAULT NULL COMMENT '性别',
|
||||
`age` int DEFAULT NULL COMMENT '年龄',
|
||||
`idCard` varchar(30) DEFAULT NULL COMMENT '身份证号',
|
||||
`mobile` varchar(30) DEFAULT NULL COMMENT '手机号',
|
||||
`unitId` varchar(32) DEFAULT NULL COMMENT '所在单位ID',
|
||||
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add mobile H5 signup confirmation fields to tour user assignment.
|
||||
ALTER TABLE `tour_user_assignment`
|
||||
ADD COLUMN `age` int DEFAULT NULL COMMENT '年龄' AFTER `gender`,
|
||||
ADD COLUMN `idCard` varchar(30) DEFAULT NULL COMMENT '身份证号' AFTER `age`;
|
||||
@@ -13,6 +13,8 @@ CREATE TABLE IF NOT EXISTS `tour_user_assignment` (
|
||||
`loginName` varchar(50) DEFAULT NULL COMMENT '工号',
|
||||
`userName` varchar(100) DEFAULT NULL COMMENT '姓名',
|
||||
`gender` varchar(20) DEFAULT NULL COMMENT '性别',
|
||||
`age` int DEFAULT NULL COMMENT '年龄',
|
||||
`idCard` varchar(30) DEFAULT NULL COMMENT '身份证号',
|
||||
`mobile` varchar(30) DEFAULT NULL COMMENT '手机号',
|
||||
`unitId` varchar(32) DEFAULT NULL COMMENT '所在单位ID',
|
||||
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 22 KiB |
@@ -93,6 +93,41 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.tour-home-float {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 99999;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 8px 24px rgba(15, 23, 42, 0.22);
|
||||
cursor: pointer;
|
||||
background: #ffffff;
|
||||
will-change: transform;
|
||||
}
|
||||
|
||||
.tour-home-float img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.tour-home-float__title {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 8px 10px;
|
||||
color: #ffffff;
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
background: linear-gradient(180deg, rgba(15, 23, 42, 0), rgba(15, 23, 42, 0.75));
|
||||
text-shadow: 0 1px 3px rgba(15, 23, 42, 0.45);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<div class="v4-container" id="v4-home-app">
|
||||
@@ -133,6 +168,16 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
<jcdt :list="websiteNews"></jcdt>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-for="item in tourHomeFloatEntries"
|
||||
:key="item.id"
|
||||
class="tour-home-float"
|
||||
:style="tourHomeFloatStyle(item)"
|
||||
@click="openTourSignup(item)">
|
||||
<img :src="item.imageUrl" :alt="item.title || ''">
|
||||
<div class="tour-home-float__title">{{ item.title }}</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
@@ -149,6 +194,9 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
data(){
|
||||
return{
|
||||
websiteNews: [],
|
||||
tourHomeFloatEntries: [],
|
||||
tourHomeFloatFrame: null,
|
||||
tourHomeFloatLastTime: 0,
|
||||
homeBannerList: "${config.AppHomeImg!}".split(",").map(item => item.trim()).filter(item => item)
|
||||
}
|
||||
},
|
||||
@@ -162,6 +210,10 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
},
|
||||
mounted() {
|
||||
this.getWebSiteNews()
|
||||
this.loadTourHomeSignupEntry()
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.stopTourHomeFloat()
|
||||
},
|
||||
methods: {
|
||||
getWebSiteNews(){
|
||||
@@ -170,6 +222,109 @@ layout("/layouts/v4/baseLayout.html"){
|
||||
this.websiteNews = res.data
|
||||
}
|
||||
})
|
||||
},
|
||||
loadTourHomeSignupEntry() {
|
||||
this.$axios.post('/platform/home/listTourHomeSignupEntry').then(res => {
|
||||
if (res.code === 0) {
|
||||
this.initTourHomeFloatEntries(res.data || [])
|
||||
}
|
||||
})
|
||||
},
|
||||
initTourHomeFloatEntries(rows) {
|
||||
this.stopTourHomeFloat()
|
||||
// 疗休养首页浮动入口从左上角开始飘动,多条配置时使用轻微偏移避免完全重叠。
|
||||
const width = 285
|
||||
const height = 177
|
||||
const bounds = this.getTourHomeFloatBounds(width, height)
|
||||
this.tourHomeFloatEntries = (rows || []).map((item, index) => {
|
||||
const offset = index * 28
|
||||
return Object.assign({}, item, {
|
||||
width: width,
|
||||
height: height,
|
||||
x: Math.min(bounds.minX + offset, bounds.maxX),
|
||||
y: Math.min(bounds.minY + offset, bounds.maxY),
|
||||
vx: (index % 2 === 0 ? 1 : -1) * (0.028 + index * 0.004),
|
||||
vy: (index % 3 === 0 ? 1 : -1) * (0.022 + index * 0.003)
|
||||
})
|
||||
})
|
||||
if (this.tourHomeFloatEntries.length > 0) {
|
||||
this.startTourHomeFloat()
|
||||
}
|
||||
},
|
||||
startTourHomeFloat() {
|
||||
this.tourHomeFloatLastTime = 0
|
||||
const step = (timestamp) => {
|
||||
if (!this.tourHomeFloatEntries.length) {
|
||||
this.tourHomeFloatFrame = null
|
||||
return
|
||||
}
|
||||
if (!this.tourHomeFloatLastTime) {
|
||||
this.tourHomeFloatLastTime = timestamp
|
||||
}
|
||||
const delta = Math.min(timestamp - this.tourHomeFloatLastTime, 40)
|
||||
this.tourHomeFloatLastTime = timestamp
|
||||
this.moveTourHomeFloat(delta)
|
||||
this.tourHomeFloatFrame = window.requestAnimationFrame(step)
|
||||
}
|
||||
this.tourHomeFloatFrame = window.requestAnimationFrame(step)
|
||||
},
|
||||
stopTourHomeFloat() {
|
||||
if (this.tourHomeFloatFrame) {
|
||||
window.cancelAnimationFrame(this.tourHomeFloatFrame)
|
||||
this.tourHomeFloatFrame = null
|
||||
}
|
||||
this.tourHomeFloatLastTime = 0
|
||||
},
|
||||
moveTourHomeFloat(delta) {
|
||||
this.tourHomeFloatEntries.forEach((item) => {
|
||||
const bounds = this.getTourHomeFloatBounds(item.width, item.height)
|
||||
let nextX = item.x + item.vx * delta
|
||||
let nextY = item.y + item.vy * delta
|
||||
let nextVx = item.vx
|
||||
let nextVy = item.vy
|
||||
if (nextX <= bounds.minX || nextX >= bounds.maxX) {
|
||||
nextVx = -nextVx
|
||||
nextX = Math.min(Math.max(nextX, bounds.minX), bounds.maxX)
|
||||
}
|
||||
if (nextY <= bounds.minY || nextY >= bounds.maxY) {
|
||||
nextVy = -nextVy
|
||||
nextY = Math.min(Math.max(nextY, bounds.minY), bounds.maxY)
|
||||
}
|
||||
this.$set(item, "x", nextX)
|
||||
this.$set(item, "y", nextY)
|
||||
this.$set(item, "vx", nextVx)
|
||||
this.$set(item, "vy", nextVy)
|
||||
})
|
||||
},
|
||||
getTourHomeFloatBounds(width, height) {
|
||||
// 根据固定导航和首页内容容器计算浮动范围,确保图片只在首页内容可视区域内飘动。
|
||||
const viewportWidth = Math.max(document.documentElement.clientWidth || 0, window.innerWidth || 0)
|
||||
const viewportHeight = Math.max(document.documentElement.clientHeight || 0, window.innerHeight || 0)
|
||||
const header = document.querySelector(".v4-header")
|
||||
const container = document.querySelector("#container")
|
||||
const headerRect = header ? header.getBoundingClientRect() : { bottom: 0 }
|
||||
const containerRect = container ? container.getBoundingClientRect() : { left: 0 }
|
||||
const padding = 4
|
||||
const minX = Math.max(containerRect.left + padding, padding)
|
||||
const minY = Math.max(headerRect.bottom + padding, padding)
|
||||
return {
|
||||
minX: minX,
|
||||
minY: minY,
|
||||
maxX: Math.max(viewportWidth - width - padding, minX),
|
||||
maxY: Math.max(viewportHeight - height - padding, minY)
|
||||
}
|
||||
},
|
||||
tourHomeFloatStyle(item) {
|
||||
return {
|
||||
width: item.width + "px",
|
||||
height: item.height + "px",
|
||||
transform: "translate(" + item.x + "px, " + item.y + "px)"
|
||||
}
|
||||
},
|
||||
openTourSignup(item) {
|
||||
// 首页浮动入口点击后打开新页面,避免打断当前首页浏览位置。
|
||||
const url = item && item.href ? item.href : "/platform/tour/signup"
|
||||
window.open(url, "_blank")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
+80
-7
@@ -153,13 +153,21 @@ layout("/layouts/platform.html"){
|
||||
</div>
|
||||
|
||||
<div class="candidate-toolbar">
|
||||
<el-input
|
||||
v-model="candidateForm.keyword"
|
||||
<el-select
|
||||
v-model="selectedCandidateIds"
|
||||
multiple
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
placeholder="姓名/工号"
|
||||
style="width: 240px"
|
||||
@keyup.enter.native="candidateSearch">
|
||||
</el-input>
|
||||
collapse-tags
|
||||
reserve-keyword
|
||||
:remote-method="remoteCandidateSearch"
|
||||
:loading="candidateSelectLoading"
|
||||
placeholder="请选择姓名/工号"
|
||||
style="width: 360px"
|
||||
@change="candidateUserSelectChange">
|
||||
<el-option v-for="item in candidateUserOptions" :key="item.userId" :label="candidateOptionLabel(item)" :value="item.userId"></el-option>
|
||||
</el-select>
|
||||
<el-button type="primary" icon="el-icon-search" @click="candidateSearch">查询</el-button>
|
||||
<el-button @click="resetCandidateSearch">重置</el-button>
|
||||
<span class="candidate-selected">已选 {{ selectedCandidates.length }} 人</span>
|
||||
@@ -312,8 +320,11 @@ layout("/layouts/platform.html"){
|
||||
},
|
||||
assignDialogVisible: false,
|
||||
candidateLoading: false,
|
||||
candidateSelectLoading: false,
|
||||
candidateData: [],
|
||||
candidateUserOptions: [],
|
||||
selectedCandidates: [],
|
||||
selectedCandidateIds: [],
|
||||
assignSubmitting: false,
|
||||
selectMatterDialogVisible: false,
|
||||
selectMatterSubmitting: false,
|
||||
@@ -447,6 +458,8 @@ layout("/layouts/platform.html"){
|
||||
this.quotaInfo = this.defaultQuotaInfo()
|
||||
this.candidateData = []
|
||||
this.selectedCandidates = []
|
||||
this.selectedCandidateIds = []
|
||||
this.candidateUserOptions = []
|
||||
this.assignDialogVisible = true
|
||||
this.loadAssignSettingOptions()
|
||||
},
|
||||
@@ -457,6 +470,8 @@ layout("/layouts/platform.html"){
|
||||
this.quotaInfo = this.defaultQuotaInfo()
|
||||
this.candidateData = []
|
||||
this.selectedCandidates = []
|
||||
this.selectedCandidateIds = []
|
||||
this.candidateUserOptions = []
|
||||
this.assignSubmitting = false
|
||||
},
|
||||
assignYearChange() {
|
||||
@@ -464,12 +479,18 @@ layout("/layouts/platform.html"){
|
||||
this.assignForm.matterId = ""
|
||||
this.assignMatterOptions = []
|
||||
this.quotaInfo = this.defaultQuotaInfo()
|
||||
this.selectedCandidateIds = []
|
||||
this.candidateUserOptions = []
|
||||
this.candidateForm.keyword = ""
|
||||
this.clearCandidateSelection()
|
||||
this.loadAssignSettingOptions()
|
||||
},
|
||||
assignSettingChange() {
|
||||
this.assignForm.matterId = ""
|
||||
this.assignMatterOptions = []
|
||||
this.selectedCandidateIds = []
|
||||
this.candidateUserOptions = []
|
||||
this.candidateForm.keyword = ""
|
||||
this.clearCandidateSelection()
|
||||
this.loadAssignMatterOptions()
|
||||
this.loadQuotaInfo()
|
||||
@@ -536,12 +557,14 @@ layout("/layouts/platform.html"){
|
||||
pageNumber: this.candidateForm.pageNumber,
|
||||
pageSize: this.candidateForm.pageSize,
|
||||
settingId: this.assignForm.settingId,
|
||||
keyword: this.candidateForm.keyword
|
||||
keyword: (this.selectedCandidateIds || []).length > 0 ? "" : this.candidateForm.keyword,
|
||||
userIds: JSON.stringify(this.selectedCandidateIds || [])
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const data = res.data || {}
|
||||
this.candidateData = data.list || []
|
||||
this.candidateForm.totalCount = data.totalCount || 0
|
||||
this.mergeCandidateOptions(this.candidateData)
|
||||
} else {
|
||||
this.$message.warning(res.msg || "候选人员查询失败")
|
||||
}
|
||||
@@ -551,11 +574,16 @@ layout("/layouts/platform.html"){
|
||||
},
|
||||
candidateSearch() {
|
||||
this.candidateForm.pageNumber = 1
|
||||
this.clearCandidateSelection()
|
||||
this.loadCandidatePageData()
|
||||
},
|
||||
resetCandidateSearch() {
|
||||
this.candidateForm.keyword = ""
|
||||
this.candidateForm.pageNumber = 1
|
||||
this.selectedCandidateIds = []
|
||||
this.selectedCandidates = []
|
||||
this.candidateUserOptions = []
|
||||
this.clearCandidateSelection()
|
||||
this.loadCandidatePageData()
|
||||
},
|
||||
candidateSizeChange(size) {
|
||||
@@ -569,6 +597,51 @@ layout("/layouts/platform.html"){
|
||||
},
|
||||
candidateSelectionChange(rows) {
|
||||
this.selectedCandidates = rows || []
|
||||
this.mergeCandidateOptions(this.selectedCandidates)
|
||||
},
|
||||
remoteCandidateSearch(keyword) {
|
||||
if (!this.assignForm.settingId) {
|
||||
this.candidateUserOptions = []
|
||||
return
|
||||
}
|
||||
// 人员选择器复用候选人员接口,后端会限定为当前登录人所在分工会会员。
|
||||
this.candidateForm.keyword = keyword || ""
|
||||
this.candidateSelectLoading = true
|
||||
this.$axios.post(loc() + "/candidatePageData", {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
settingId: this.assignForm.settingId,
|
||||
keyword: keyword || ""
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const data = res.data || {}
|
||||
this.candidateUserOptions = this.mergeOptionLists(this.selectedCandidates, data.list || [])
|
||||
}
|
||||
}).finally(() => {
|
||||
this.candidateSelectLoading = false
|
||||
})
|
||||
},
|
||||
candidateUserSelectChange(userIds) {
|
||||
// 人员选择器仅作为查询条件;最终分配人员仍由下方列表勾选决定。
|
||||
this.selectedCandidateIds = userIds || []
|
||||
},
|
||||
candidateOptionLabel(item) {
|
||||
if (!item) {
|
||||
return ""
|
||||
}
|
||||
return (item.userName || "") + (item.loginName ? "(" + item.loginName + ")" : "")
|
||||
},
|
||||
mergeCandidateOptions(list) {
|
||||
this.candidateUserOptions = this.mergeOptionLists(this.candidateUserOptions, list || [])
|
||||
},
|
||||
mergeOptionLists(first, second) {
|
||||
const map = {}
|
||||
;(first || []).concat(second || []).forEach(item => {
|
||||
if (item && item.userId) {
|
||||
map[item.userId] = Object.assign({}, map[item.userId] || {}, item)
|
||||
}
|
||||
})
|
||||
return Object.keys(map).map(key => map[key])
|
||||
},
|
||||
clearCandidateSelection() {
|
||||
this.selectedCandidates = []
|
||||
|
||||
@@ -189,7 +189,7 @@ layout("/layouts/platform.html"){
|
||||
</el-col>
|
||||
<el-col :span="8">
|
||||
<el-form-item label="联系方式" prop="contactPhone">
|
||||
<el-input v-model="batchForm.contactPhone" maxlength="11" placeholder="请输入联系方式"></el-input>
|
||||
<el-input v-model="batchForm.contactPhone" maxlength="30" placeholder="请输入联系方式"></el-input>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -239,12 +239,10 @@ layout("/layouts/platform.html"){
|
||||
callback(new Error("必填"))
|
||||
}
|
||||
}
|
||||
const validateMobile = (rule, value, callback) => {
|
||||
const mobileReg = /^1[3-9]\d{9}$/
|
||||
const validateContactPhone = (rule, value, callback) => {
|
||||
// 线路联系人电话允许填写座机、分机或其他联系说明,此处只校验必填。
|
||||
if (!value) {
|
||||
callback(new Error("必填"))
|
||||
} else if (!mobileReg.test(value)) {
|
||||
callback(new Error("手机号格式不正确"))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
@@ -328,7 +326,7 @@ layout("/layouts/platform.html"){
|
||||
travelStartTime: [{ validator: validateBatchTime, trigger: ["blur", "change"] }],
|
||||
travelEndTime: [{ validator: validateBatchTime, trigger: ["blur", "change"] }],
|
||||
contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
|
||||
contactPhone: [{ validator: validateMobile, trigger: ["blur", "change"] }],
|
||||
contactPhone: [{ validator: validateContactPhone, trigger: ["blur", "change"] }],
|
||||
minGroupPeople: [{ validator: validatePeople, trigger: ["blur", "change"] }],
|
||||
maxGroupPeople: [{ validator: validatePeople, trigger: ["blur", "change"] }],
|
||||
estimatedCost: [{ validator: validateMoney, trigger: ["blur", "change"] }]
|
||||
|
||||
+81
-7
@@ -157,13 +157,21 @@ layout("/layouts/platform.html"){
|
||||
<el-select v-model="candidateForm.unionId" clearable filterable placeholder="所属分工会" style="width: 220px" @change="candidateSearch">
|
||||
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
|
||||
</el-select>
|
||||
<el-input
|
||||
v-model="candidateForm.keyword"
|
||||
<el-select
|
||||
v-model="selectedCandidateIds"
|
||||
multiple
|
||||
filterable
|
||||
remote
|
||||
clearable
|
||||
placeholder="姓名/工号"
|
||||
style="width: 220px"
|
||||
@keyup.enter.native="candidateSearch">
|
||||
</el-input>
|
||||
collapse-tags
|
||||
reserve-keyword
|
||||
:remote-method="remoteCandidateSearch"
|
||||
:loading="candidateSelectLoading"
|
||||
placeholder="请选择姓名/工号"
|
||||
style="width: 360px"
|
||||
@change="candidateUserSelectChange">
|
||||
<el-option v-for="item in candidateUserOptions" :key="item.userId" :label="candidateOptionLabel(item)" :value="item.userId"></el-option>
|
||||
</el-select>
|
||||
<el-button type="primary" icon="el-icon-search" @click="candidateSearch">查询</el-button>
|
||||
<el-button @click="resetCandidateSearch">重置</el-button>
|
||||
<span class="candidate-selected">已选 {{ selectedCandidates.length }} 人</span>
|
||||
@@ -302,8 +310,11 @@ layout("/layouts/platform.html"){
|
||||
unionOptions: [],
|
||||
assignDialogVisible: false,
|
||||
candidateLoading: false,
|
||||
candidateSelectLoading: false,
|
||||
candidateData: [],
|
||||
candidateUserOptions: [],
|
||||
selectedCandidates: [],
|
||||
selectedCandidateIds: [],
|
||||
assignSubmitting: false,
|
||||
selectMatterDialogVisible: false,
|
||||
selectMatterSubmitting: false,
|
||||
@@ -437,6 +448,8 @@ layout("/layouts/platform.html"){
|
||||
this.candidateForm = this.defaultCandidateForm()
|
||||
this.candidateData = []
|
||||
this.selectedCandidates = []
|
||||
this.selectedCandidateIds = []
|
||||
this.candidateUserOptions = []
|
||||
this.assignDialogVisible = true
|
||||
this.loadAssignSettingOptions()
|
||||
},
|
||||
@@ -446,12 +459,17 @@ layout("/layouts/platform.html"){
|
||||
this.assignMatterOptions = []
|
||||
this.candidateData = []
|
||||
this.selectedCandidates = []
|
||||
this.selectedCandidateIds = []
|
||||
this.candidateUserOptions = []
|
||||
this.assignSubmitting = false
|
||||
},
|
||||
assignYearChange() {
|
||||
this.assignForm.settingId = ""
|
||||
this.assignForm.matterId = ""
|
||||
this.assignMatterOptions = []
|
||||
this.selectedCandidateIds = []
|
||||
this.candidateUserOptions = []
|
||||
this.candidateForm.keyword = ""
|
||||
this.clearCandidateSelection()
|
||||
this.loadAssignSettingOptions()
|
||||
this.loadCandidatePageData()
|
||||
@@ -459,6 +477,9 @@ layout("/layouts/platform.html"){
|
||||
assignSettingChange() {
|
||||
this.assignForm.matterId = ""
|
||||
this.assignMatterOptions = []
|
||||
this.selectedCandidateIds = []
|
||||
this.candidateUserOptions = []
|
||||
this.candidateForm.keyword = ""
|
||||
this.clearCandidateSelection()
|
||||
this.loadAssignMatterOptions()
|
||||
this.loadCandidatePageData()
|
||||
@@ -506,7 +527,8 @@ layout("/layouts/platform.html"){
|
||||
pageSize: this.candidateForm.pageSize,
|
||||
settingId: this.assignForm.settingId,
|
||||
unionId: this.candidateForm.unionId,
|
||||
keyword: this.candidateForm.keyword
|
||||
keyword: (this.selectedCandidateIds || []).length > 0 ? "" : this.candidateForm.keyword,
|
||||
userIds: JSON.stringify(this.selectedCandidateIds || [])
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const data = res.data || {}
|
||||
@@ -514,6 +536,7 @@ layout("/layouts/platform.html"){
|
||||
assignmentMatterId: ""
|
||||
}))
|
||||
this.candidateForm.totalCount = data.totalCount || 0
|
||||
this.mergeCandidateOptions(this.candidateData)
|
||||
} else {
|
||||
this.$message.warning(res.msg || "候选人员查询失败")
|
||||
}
|
||||
@@ -528,12 +551,17 @@ layout("/layouts/platform.html"){
|
||||
},
|
||||
candidateSearch() {
|
||||
this.candidateForm.pageNumber = 1
|
||||
this.clearCandidateSelection()
|
||||
this.loadCandidatePageData()
|
||||
},
|
||||
resetCandidateSearch() {
|
||||
this.candidateForm.unionId = ""
|
||||
this.candidateForm.keyword = ""
|
||||
this.candidateForm.pageNumber = 1
|
||||
this.selectedCandidateIds = []
|
||||
this.selectedCandidates = []
|
||||
this.candidateUserOptions = []
|
||||
this.clearCandidateSelection()
|
||||
this.loadCandidatePageData()
|
||||
},
|
||||
candidateSizeChange(size) {
|
||||
@@ -555,10 +583,56 @@ layout("/layouts/platform.html"){
|
||||
}
|
||||
})
|
||||
this.selectedCandidates = rows || []
|
||||
this.mergeCandidateOptions(this.selectedCandidates)
|
||||
if (this.selectedCandidates.length <= 0) {
|
||||
this.assignForm.matterId = ""
|
||||
}
|
||||
},
|
||||
remoteCandidateSearch(keyword) {
|
||||
if (!this.assignForm.settingId) {
|
||||
this.candidateUserOptions = []
|
||||
return
|
||||
}
|
||||
// 人员选择器复用候选人员接口,校工会可按分工会过滤,也可不选分工会查询全部会员。
|
||||
this.candidateForm.keyword = keyword || ""
|
||||
this.candidateSelectLoading = true
|
||||
this.$axios.post(loc() + "/candidatePageData", {
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
settingId: this.assignForm.settingId,
|
||||
unionId: this.candidateForm.unionId,
|
||||
keyword: keyword || ""
|
||||
}).then((res) => {
|
||||
if (res.code === 0) {
|
||||
const data = res.data || {}
|
||||
this.candidateUserOptions = this.mergeOptionLists(this.selectedCandidates, data.list || [])
|
||||
}
|
||||
}).finally(() => {
|
||||
this.candidateSelectLoading = false
|
||||
})
|
||||
},
|
||||
candidateUserSelectChange(userIds) {
|
||||
// 人员选择器仅作为查询条件;最终分配人员仍由下方列表勾选决定。
|
||||
this.selectedCandidateIds = userIds || []
|
||||
},
|
||||
candidateOptionLabel(item) {
|
||||
if (!item) {
|
||||
return ""
|
||||
}
|
||||
return (item.userName || "") + (item.loginName ? "(" + item.loginName + ")" : "")
|
||||
},
|
||||
mergeCandidateOptions(list) {
|
||||
this.candidateUserOptions = this.mergeOptionLists(this.candidateUserOptions, list || [])
|
||||
},
|
||||
mergeOptionLists(first, second) {
|
||||
const map = {}
|
||||
;(first || []).concat(second || []).forEach(item => {
|
||||
if (item && item.userId) {
|
||||
map[item.userId] = Object.assign({}, map[item.userId] || {}, item)
|
||||
}
|
||||
})
|
||||
return Object.keys(map).map(key => map[key])
|
||||
},
|
||||
isCandidateSelected(row) {
|
||||
return !!row && this.selectedCandidates.some(item => item.userId === row.userId)
|
||||
},
|
||||
|
||||
@@ -241,6 +241,26 @@ layout("/layouts/platform.html"){
|
||||
<el-switch v-model="formData.enabled" active-text="启用" inactive-text="停用"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div class="tour-setting-basic-divider"></div>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="首页报名入口" prop="homeSignupEntryEnabled">
|
||||
<el-switch v-model="formData.homeSignupEntryEnabled" active-text="展示" inactive-text="不展示"></el-switch>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col v-if="formData.homeSignupEntryEnabled" :span="12">
|
||||
<el-form-item label="入口图片" prop="homeSignupEntryImage">
|
||||
<file-upload
|
||||
style="--upload-width: 220px;--upload-height:120px"
|
||||
:value.sync="formData.homeSignupEntryImage"
|
||||
:upload_number="1"
|
||||
upload_mode="image"
|
||||
upload_result_category="interval"
|
||||
upload_result_type="url">
|
||||
</file-upload>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
<div class="tour-setting-basic-divider"></div>
|
||||
<div class="tour-boarding-panel">
|
||||
@@ -621,6 +641,8 @@ layout("/layouts/platform.html"){
|
||||
allowFamily: false,
|
||||
fillBedInfo: true,
|
||||
enabled: true,
|
||||
homeSignupEntryEnabled: false,
|
||||
homeSignupEntryImage: "",
|
||||
lots: [],
|
||||
unionQuotas: [],
|
||||
serviceNotice: ""
|
||||
@@ -693,6 +715,9 @@ layout("/layouts/platform.html"){
|
||||
if (this.formData.fillBedInfo === null || this.formData.fillBedInfo === undefined) {
|
||||
this.$set(this.formData, "fillBedInfo", true)
|
||||
}
|
||||
// 上一年配置只延用业务规则,首页浮动报名入口需要当年重新确认。
|
||||
this.$set(this.formData, "homeSignupEntryEnabled", false)
|
||||
this.$set(this.formData, "homeSignupEntryImage", "")
|
||||
this.lotDeleteList = []
|
||||
this.$message.success("已延用上一年配置信息")
|
||||
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
|
||||
@@ -720,6 +745,8 @@ layout("/layouts/platform.html"){
|
||||
allowFamily: false,
|
||||
fillBedInfo: true,
|
||||
enabled: true,
|
||||
homeSignupEntryEnabled: false,
|
||||
homeSignupEntryImage: "",
|
||||
lots: [],
|
||||
unionQuotas: [],
|
||||
serviceNotice: ""
|
||||
@@ -738,6 +765,14 @@ layout("/layouts/platform.html"){
|
||||
if (this.formData.fillBedInfo === null || this.formData.fillBedInfo === undefined) {
|
||||
this.$set(this.formData, "fillBedInfo", true)
|
||||
}
|
||||
if (this.formData.homeSignupEntryEnabled === null || this.formData.homeSignupEntryEnabled === undefined) {
|
||||
this.$set(this.formData, "homeSignupEntryEnabled", false)
|
||||
}
|
||||
if (!this.formData.homeSignupEntryImage) {
|
||||
this.$set(this.formData, "homeSignupEntryImage", "")
|
||||
} else {
|
||||
this.$set(this.formData, "homeSignupEntryImage", this.normalizeHomeSignupEntryImage(this.formData.homeSignupEntryImage))
|
||||
}
|
||||
this.formData.cycleStartYear = this.formData.cycleStartYear ? String(this.formData.cycleStartYear) : ""
|
||||
this.formData.cycleEndYear = this.formData.cycleEndYear ? String(this.formData.cycleEndYear) : ""
|
||||
this.formData.lots = (this.formData.lots || []).map(item => Object.assign({}, item, {
|
||||
@@ -765,6 +800,12 @@ layout("/layouts/platform.html"){
|
||||
if (submitData.outProvinceRatioType !== "固定人数") {
|
||||
submitData.outProvinceFixedPeople = 0
|
||||
}
|
||||
if (!submitData.homeSignupEntryEnabled) {
|
||||
submitData.homeSignupEntryImage = ""
|
||||
} else {
|
||||
// 入口图片只允许保存一张,兼容历史多路径数据时只取第一张。
|
||||
submitData.homeSignupEntryImage = this.normalizeHomeSignupEntryImage(submitData.homeSignupEntryImage)
|
||||
}
|
||||
submitData.lots = JSON.stringify(this.formData.lots || [])
|
||||
submitData.unionQuotas = JSON.stringify(this.formData.unionQuotas || [])
|
||||
submitData.lotDeleteList = JSON.stringify(this.lotDeleteList)
|
||||
@@ -782,6 +823,21 @@ layout("/layouts/platform.html"){
|
||||
})
|
||||
})
|
||||
},
|
||||
normalizeHomeSignupEntryImage(value) {
|
||||
// file-upload 组件单图场景保存为逗号分隔字符串,这里统一裁剪为第一张图片。
|
||||
if (!value) {
|
||||
return ""
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
return value.length > 0 ? value[0] : ""
|
||||
}
|
||||
const imageList = String(value).split(",").map(function (item) {
|
||||
return item.trim()
|
||||
}).filter(function (item) {
|
||||
return !!item
|
||||
})
|
||||
return imageList.length > 0 ? imageList[0] : ""
|
||||
},
|
||||
addLot() {
|
||||
if (!this.formData.lots) {
|
||||
this.$set(this.formData, "lots", [])
|
||||
|
||||
+3
-4
@@ -116,11 +116,10 @@ const select = {
|
||||
</template>
|
||||
<el-form-item :prop="'times.' + $index + '.contactPhone'"
|
||||
:rules="[
|
||||
{ required: true, message: '手机号码不能为空', trigger: 'blur' },
|
||||
{ pattern: /^1[34578]\\d{9}$/, message: '手机号码格式不正确', trigger: 'blur' }
|
||||
]"
|
||||
{ required: true, message: '联系方式不能为空', trigger: 'blur' }
|
||||
]"
|
||||
label-width="0">
|
||||
<el-input placeholder="请输入联系方式" clearable maxlength="11" v-model="row.contactPhone"></el-input>
|
||||
<el-input placeholder="请输入联系方式" clearable maxlength="30" v-model="row.contactPhone"></el-input>
|
||||
</el-form-item>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="最少参与教工">
|
||||
|
||||
@@ -534,7 +534,7 @@ layout("/layouts/platform_h5.html"){
|
||||
</div>
|
||||
</div>
|
||||
<div class="tour-bottom-bar">
|
||||
<van-button v-if="signupForm.id" type="danger" plain block round :loading="cancelLoading" @click="cancelSignup">取消报名</van-button>
|
||||
<van-button v-if="showCancel(signupForm)" type="danger" plain block round :loading="cancelLoading" @click="cancelSignup">取消报名</van-button>
|
||||
<van-button type="info" block round :loading="signupLoading" @click="submitSignup">提交</van-button>
|
||||
</div>
|
||||
</van-popup>
|
||||
@@ -733,7 +733,7 @@ layout("/layouts/platform_h5.html"){
|
||||
return this.isApprovalRow(row) && this.toBoolean(row.canRevoke)
|
||||
},
|
||||
showCancel(row) {
|
||||
return !this.isApprovalRow(row) || this.canModify(row)
|
||||
return this.toBoolean(row && row.canCancelSignup) && (!this.isApprovalRow(row) || this.canModify(row))
|
||||
},
|
||||
// 退出疗休养报名依赖人员分配记录,已退出的数据不再展示入口。
|
||||
showLeaveTour(row) {
|
||||
@@ -832,8 +832,10 @@ layout("/layouts/platform_h5.html"){
|
||||
travelAgencyName: matter.travelAgencyName || "",
|
||||
boardingPlace: matter.defaultBoardingPlace || "",
|
||||
travelPeriod: matter.travelPeriod || "",
|
||||
signupEndTime: matter.signupEndTime || "",
|
||||
travelStartTime: matter.travelStartTime || "",
|
||||
travelEndTime: matter.travelEndTime || "",
|
||||
canCancelSignup: this.toBoolean(matter.canCancelSignup),
|
||||
allowOverReimbursement: matter.allowOverReimbursement,
|
||||
overCostReimbursed: false,
|
||||
hasFamily: false,
|
||||
@@ -851,8 +853,10 @@ layout("/layouts/platform_h5.html"){
|
||||
travelAgencyName: matter.travelAgencyName || ledger.travelAgencyName || "",
|
||||
boardingPlace: ledger.boardingPlace || matter.defaultBoardingPlace || "",
|
||||
travelPeriod: matter.travelPeriod || "",
|
||||
signupEndTime: matter.signupEndTime || "",
|
||||
travelStartTime: matter.travelStartTime || "",
|
||||
travelEndTime: matter.travelEndTime || "",
|
||||
canCancelSignup: this.toBoolean(matter.canCancelSignup),
|
||||
allowOverReimbursement: matter.allowOverReimbursement,
|
||||
overCostReimbursed: ledger.overCostReimbursed === true || ledger.overCostReimbursed === 1 || ledger.overCostReimbursed === "1",
|
||||
hasFamily: ledger.hasFamily === true || ledger.hasFamily === 1 || ledger.hasFamily === "1"
|
||||
|
||||
@@ -383,6 +383,7 @@ layout("/layouts/platform_h5.html"){
|
||||
const data = res.data || {}
|
||||
const matter = data.matter || {}
|
||||
const staff = data.staff || {}
|
||||
const assignment = data.assignment || {}
|
||||
const ledger = data.ledger || {}
|
||||
const directRelative = data.directRelative || {}
|
||||
this.boardingPlaceOptions = this.parseBoardingPlaceOptions(matter.boardingPlaceOptions || "")
|
||||
@@ -409,7 +410,7 @@ layout("/layouts/platform_h5.html"){
|
||||
hotelName: "",
|
||||
travelAgencyId: matter.travelAgencyId || "",
|
||||
travelAgencyName: matter.travelAgencyName || "",
|
||||
boardingPlace: matter.defaultBoardingPlace || "",
|
||||
boardingPlace: "",
|
||||
travelPeriod: matter.travelPeriod || "",
|
||||
travelStartTime: matter.travelStartTime || "",
|
||||
travelEndTime: matter.travelEndTime || "",
|
||||
@@ -419,7 +420,7 @@ layout("/layouts/platform_h5.html"){
|
||||
intendedRoommate: "",
|
||||
bedType: "",
|
||||
bedInfo: ""
|
||||
}, staff, ledger, {
|
||||
}, staff, assignment, ledger, {
|
||||
year: matter.year,
|
||||
matterId: matter.matterId || ledger.matterId || "",
|
||||
lineId: matter.lineId || ledger.lineId || "",
|
||||
@@ -428,7 +429,7 @@ layout("/layouts/platform_h5.html"){
|
||||
directFamilyUnitLine: matter.directFamilyUnitLine,
|
||||
travelAgencyId: matter.travelAgencyId || ledger.travelAgencyId || "",
|
||||
travelAgencyName: matter.travelAgencyName || ledger.travelAgencyName || "",
|
||||
boardingPlace: ledger.boardingPlace || matter.defaultBoardingPlace || "",
|
||||
boardingPlace: ledger.boardingPlace || assignment.boardingPlace || "",
|
||||
travelPeriod: matter.travelPeriod || "",
|
||||
travelStartTime: matter.travelStartTime || "",
|
||||
travelEndTime: matter.travelEndTime || "",
|
||||
@@ -450,7 +451,6 @@ layout("/layouts/platform_h5.html"){
|
||||
this.familyData = []
|
||||
this.signupForm.hasFamily = false
|
||||
}
|
||||
this.ensureBoardingPlace()
|
||||
})
|
||||
},
|
||||
parseBoardingPlaceOptions(value) {
|
||||
@@ -466,12 +466,6 @@ layout("/layouts/platform_h5.html"){
|
||||
return String(value).split(",").map((item) => item.trim()).filter((item) => item)
|
||||
}
|
||||
},
|
||||
ensureBoardingPlace() {
|
||||
if (!this.signupForm) return
|
||||
if (!this.signupForm.boardingPlace && this.boardingPlaceOptions.length === 1) {
|
||||
this.$set(this.signupForm, "boardingPlace", this.boardingPlaceOptions[0])
|
||||
}
|
||||
},
|
||||
emptyFamily() {
|
||||
return {
|
||||
familyName: "",
|
||||
@@ -597,6 +591,26 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
return true
|
||||
},
|
||||
validateStaffRequired() {
|
||||
// 报名人身份证和手机号为移动端提交必填项,提交前统一清理空格并拦截空值。
|
||||
const idCard = this.signupForm && this.signupForm.idCard ? String(this.signupForm.idCard).trim().toUpperCase() : ""
|
||||
const mobile = this.signupForm && this.signupForm.mobile ? String(this.signupForm.mobile).trim() : ""
|
||||
this.$set(this.signupForm, "idCard", idCard)
|
||||
this.$set(this.signupForm, "mobile", mobile)
|
||||
if (!idCard) {
|
||||
vant.Toast("请填写身份证号码")
|
||||
return false
|
||||
}
|
||||
if (!this.isValidIdCard(idCard)) {
|
||||
vant.Toast("请输入正确的身份证号码")
|
||||
return false
|
||||
}
|
||||
if (!mobile) {
|
||||
vant.Toast("请填写手机号")
|
||||
return false
|
||||
}
|
||||
return true
|
||||
},
|
||||
validateDirectRelative() {
|
||||
if (!this.isDirectFamilyLine(this.signupForm)) return true
|
||||
if (!this.directRelativeForm.relativeName) {
|
||||
@@ -644,6 +658,7 @@ layout("/layouts/platform_h5.html"){
|
||||
},
|
||||
submitSignup() {
|
||||
if (this.pageLoading || this.submitLoading) return
|
||||
if (!this.validateStaffRequired()) return
|
||||
if (this.boardingPlaceOptions.length > 0 && !this.signupForm.boardingPlace) {
|
||||
vant.Toast("请选择乘车地点")
|
||||
return
|
||||
@@ -665,7 +680,7 @@ layout("/layouts/platform_h5.html"){
|
||||
message: res.msg || "恭喜您已报名成功",
|
||||
confirmButtonColor: "#1867b0"
|
||||
}).then(() => {
|
||||
window.location.replace("/platform/tour/signup/h5/signup")
|
||||
window.location.replace("/platform/tour/signup/h5/notice")
|
||||
})
|
||||
} else {
|
||||
this.showSubmitError(res)
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style scoped>
|
||||
.tour-confirm-page {
|
||||
min-height: 100vh;
|
||||
padding: 12px 12px 84px;
|
||||
background: #f4f6f8;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-confirm-card {
|
||||
overflow: hidden;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.06);
|
||||
}
|
||||
|
||||
.tour-confirm-tip {
|
||||
margin: 10px 2px 0;
|
||||
color: #ee8b00;
|
||||
font-size: 12px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.tour-confirm-footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 20;
|
||||
padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
|
||||
border-top: 1px solid #edf0f4;
|
||||
background: #ffffff;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app">
|
||||
<van-nav-bar title="信息确认" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
|
||||
<div class="tour-confirm-page">
|
||||
<van-loading v-if="loading" size="24px" vertical>加载中...</van-loading>
|
||||
<template v-else>
|
||||
<div class="tour-confirm-card">
|
||||
<van-field label="姓名" required v-model="form.userName" maxlength="100" placeholder="请填写姓名"></van-field>
|
||||
<van-field label="性别" required readonly clickable is-link v-model="form.gender" placeholder="请选择性别" @click="genderPickerVisible=true"></van-field>
|
||||
<van-field label="年龄" required readonly v-model="form.age" placeholder="根据身份证号自动计算"></van-field>
|
||||
<van-field label="身份证号" required v-model="form.idCard" maxlength="18" placeholder="请填写身份证号" @blur="normalizeIdCard"></van-field>
|
||||
<van-field label="手机号" required v-model="form.mobile" type="tel" maxlength="30" placeholder="请填写手机号"></van-field>
|
||||
<van-field
|
||||
label="乘车地点"
|
||||
required
|
||||
:readonly="boardingPlaceOptions.length > 0"
|
||||
:clickable="boardingPlaceOptions.length > 0"
|
||||
:is-link="boardingPlaceOptions.length > 0"
|
||||
v-model="form.boardingPlace"
|
||||
placeholder="请选择或填写乘车地点"
|
||||
@click="openBoardingPlacePicker">
|
||||
</van-field>
|
||||
</div>
|
||||
<div class="tour-confirm-tip">
|
||||
请确认本人报名基础信息无误后,即可选择路线报名。
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<div class="tour-confirm-footer">
|
||||
<van-button type="info" block round :loading="submitLoading" @click="submitConfirm">我已确认</van-button>
|
||||
</div>
|
||||
|
||||
<van-popup v-model="genderPickerVisible" position="bottom">
|
||||
<van-picker show-toolbar :columns="genderColumns" @confirm="confirmGender" @cancel="genderPickerVisible=false"></van-picker>
|
||||
</van-popup>
|
||||
|
||||
<van-popup v-model="boardingPlacePickerVisible" position="bottom">
|
||||
<van-picker show-toolbar :columns="boardingPlaceOptions" @confirm="confirmBoardingPlace" @cancel="boardingPlacePickerVisible=false"></van-picker>
|
||||
</van-popup>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
submitLoading: false,
|
||||
existsAssignment: false,
|
||||
form: {
|
||||
userName: "",
|
||||
gender: "",
|
||||
age: "",
|
||||
idCard: "",
|
||||
mobile: "",
|
||||
boardingPlace: ""
|
||||
},
|
||||
genderColumns: ["男", "女"],
|
||||
boardingPlaceOptions: [],
|
||||
genderPickerVisible: false,
|
||||
boardingPlacePickerVisible: false
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
parseBoardingPlaceOptions(value) {
|
||||
if (!value) return []
|
||||
try {
|
||||
const list = JSON.parse(value)
|
||||
if (!Array.isArray(list)) return []
|
||||
return list.map(function (item) {
|
||||
if (typeof item === "string") return item
|
||||
return item && item.name ? item.name : ""
|
||||
}).filter(function (item) {
|
||||
return !!item
|
||||
})
|
||||
} catch (e) {
|
||||
return String(value).split(",").map(function (item) {
|
||||
return item.trim()
|
||||
}).filter(function (item) {
|
||||
return !!item
|
||||
})
|
||||
}
|
||||
},
|
||||
normalizeIdCard() {
|
||||
this.form.idCard = this.form.idCard ? String(this.form.idCard).trim().toUpperCase() : ""
|
||||
this.form.age = this.calcAgeByIdCard(this.form.idCard)
|
||||
},
|
||||
isValidIdCard(value) {
|
||||
if (!value) return false
|
||||
const idCard = String(value).trim().toUpperCase()
|
||||
return /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dX]$/.test(idCard)
|
||||
},
|
||||
calcAgeByIdCard(value) {
|
||||
if (!this.isValidIdCard(value)) {
|
||||
return ""
|
||||
}
|
||||
const idCard = String(value).trim().toUpperCase()
|
||||
const year = Number(idCard.substring(6, 10))
|
||||
const month = Number(idCard.substring(10, 12))
|
||||
const day = Number(idCard.substring(12, 14))
|
||||
const today = new Date()
|
||||
let age = today.getFullYear() - year
|
||||
const currentMonth = today.getMonth() + 1
|
||||
const currentDay = today.getDate()
|
||||
if (currentMonth < month || (currentMonth === month && currentDay < day)) {
|
||||
age--
|
||||
}
|
||||
return age >= 0 ? String(age) : ""
|
||||
},
|
||||
loadInfo() {
|
||||
this.loading = true
|
||||
this.$axios.post("/platform/tour/signup/h5/confirmInfo").then((res) => {
|
||||
this.loading = false
|
||||
if (res.code !== 0) {
|
||||
vant.Toast(res.msg || "信息加载失败")
|
||||
return
|
||||
}
|
||||
const data = res.data || {}
|
||||
const form = data.form || {}
|
||||
this.existsAssignment = data.existsAssignment === true || data.existsAssignment === 1 || data.existsAssignment === "1"
|
||||
if (!this.existsAssignment) {
|
||||
window.location.replace("/platform/tour/signup/h5/signup")
|
||||
return
|
||||
}
|
||||
this.boardingPlaceOptions = this.parseBoardingPlaceOptions(data.boardingPlaceOptions || "")
|
||||
this.form = Object.assign({}, this.form, form, {
|
||||
age: form.idCard ? this.calcAgeByIdCard(form.idCard) : ""
|
||||
})
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
openBoardingPlacePicker() {
|
||||
if (this.boardingPlaceOptions.length === 0) return
|
||||
this.boardingPlacePickerVisible = true
|
||||
},
|
||||
confirmGender(value) {
|
||||
this.form.gender = value
|
||||
this.genderPickerVisible = false
|
||||
},
|
||||
confirmBoardingPlace(value) {
|
||||
this.form.boardingPlace = value
|
||||
this.boardingPlacePickerVisible = false
|
||||
},
|
||||
validateForm() {
|
||||
this.normalizeIdCard()
|
||||
const requiredFields = [
|
||||
{ key: "userName", message: "请填写姓名" },
|
||||
{ key: "gender", message: "请选择性别" },
|
||||
{ key: "idCard", message: "请填写身份证号" },
|
||||
{ key: "age", message: "请填写正确的身份证号" },
|
||||
{ key: "mobile", message: "请填写手机号" },
|
||||
{ key: "boardingPlace", message: "请填写乘车地点" }
|
||||
]
|
||||
for (let i = 0; i < requiredFields.length; i++) {
|
||||
const item = requiredFields[i]
|
||||
if (this.form[item.key] === null || this.form[item.key] === undefined || String(this.form[item.key]).trim() === "") {
|
||||
vant.Toast(item.message)
|
||||
return false
|
||||
}
|
||||
}
|
||||
if (!this.isValidIdCard(this.form.idCard)) {
|
||||
vant.Toast("请输入正确的身份证号")
|
||||
return false
|
||||
}
|
||||
this.form.age = this.calcAgeByIdCard(this.form.idCard)
|
||||
return true
|
||||
},
|
||||
submitConfirm() {
|
||||
if (this.loading || this.submitLoading) return
|
||||
if (!this.validateForm()) return
|
||||
this.submitLoading = true
|
||||
this.$axios.post("/platform/tour/signup/h5/saveConfirmInfo", this.form).then((res) => {
|
||||
this.submitLoading = false
|
||||
if (res.code === 0) {
|
||||
window.location.replace("/platform/tour/signup/h5/signup")
|
||||
} else {
|
||||
vant.Toast(res.msg || "信息确认失败")
|
||||
}
|
||||
}).catch(() => {
|
||||
this.submitLoading = false
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadInfo()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -0,0 +1,42 @@
|
||||
<!--#
|
||||
layout("/layouts/platform_h5.html"){
|
||||
#-->
|
||||
|
||||
<style scoped>
|
||||
.tour-entry-page {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #f4f6f8;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="app" class="tour-entry-page">
|
||||
<van-loading size="24px" vertical>加载中...</van-loading>
|
||||
</div>
|
||||
|
||||
<script nonce="${cspNonce!}">
|
||||
const vue = new Vue({
|
||||
el: "#app",
|
||||
store,
|
||||
methods: {
|
||||
routeByAssignment() {
|
||||
this.$axios.post("/platform/tour/signup/h5/confirmInfo").then((res) => {
|
||||
const data = res.code === 0 ? (res.data || {}) : {}
|
||||
const existsAssignment = data.existsAssignment === true || data.existsAssignment === 1 || data.existsAssignment === "1"
|
||||
window.location.replace(existsAssignment ? "/platform/tour/signup/h5/confirm" : "/platform/tour/signup/h5/noAssignment")
|
||||
}).catch(() => {
|
||||
window.location.replace("/platform/tour/signup/h5/noAssignment")
|
||||
})
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.routeByAssignment()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<!--#
|
||||
}
|
||||
#-->
|
||||
@@ -6,83 +6,32 @@ layout("/layouts/platform_h5.html"){
|
||||
.tour-signup-h5 {
|
||||
min-height: 100vh;
|
||||
background: #f5f7fb;
|
||||
padding-bottom: 80px;
|
||||
padding: 10px 10px 80px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-signup-banner {
|
||||
position: relative;
|
||||
height: 193px;
|
||||
overflow: hidden;
|
||||
background: #0f74bc;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.tour-signup-swipe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tour-signup-swipe img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 193px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.tour-signup-banner::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: linear-gradient(90deg, rgba(5, 83, 143, 0.72) 0%, rgba(5, 83, 143, 0.32) 48%, rgba(5, 83, 143, 0.08) 100%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.tour-signup-banner__text {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
right: 16px;
|
||||
bottom: 20px;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.tour-signup-title {
|
||||
font-size: 22px;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.tour-signup-subtitle {
|
||||
margin-top: 8px;
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.tour-signup-card {
|
||||
margin: 10px 12px 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: calc(100vh - 160px);
|
||||
max-height: calc(100vh - 160px);
|
||||
margin: 0;
|
||||
padding: 14px 14px 16px;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.08);
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.tour-signup-card__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #eef2f7;
|
||||
}
|
||||
|
||||
.tour-signup-card__title {
|
||||
color: #111827;
|
||||
font-size: 17px;
|
||||
font-weight: 700;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.tour-signup-card__year {
|
||||
flex-shrink: 0;
|
||||
color: #0f74bc;
|
||||
@@ -91,13 +40,19 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
|
||||
.tour-signup-notice {
|
||||
margin-top: 14px;
|
||||
color: #334155;
|
||||
font-size: 15px;
|
||||
line-height: 1.8;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.tour-signup-card__body {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
margin-top: 14px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.tour-signup-notice /deep/ img,
|
||||
.tour-signup-notice /deep/ video {
|
||||
max-width: 100%;
|
||||
@@ -140,7 +95,10 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
|
||||
.tour-signup-empty {
|
||||
padding: 34px 0 26px;
|
||||
min-height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.tour-signup-footer {
|
||||
@@ -156,48 +114,38 @@ layout("/layouts/platform_h5.html"){
|
||||
</style>
|
||||
|
||||
<div id="app" class="tour-signup-h5">
|
||||
<van-nav-bar title="疗休养报名" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
|
||||
<div class="tour-signup-banner">
|
||||
<van-swipe class="tour-signup-swipe" :autoplay="3500" indicator-color="white">
|
||||
<van-swipe-item v-for="item in bannerList" :key="item">
|
||||
<img :src="item" alt="疗休养报名">
|
||||
</van-swipe-item>
|
||||
</van-swipe>
|
||||
<div class="tour-signup-banner__text">
|
||||
<div class="tour-signup-title">疗休养报名</div>
|
||||
</div>
|
||||
</div>
|
||||
<van-nav-bar title="须知提醒" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
|
||||
|
||||
<div class="tour-signup-card">
|
||||
<div class="tour-signup-card__header">
|
||||
<div class="tour-signup-card__title">服务须知</div>
|
||||
<div class="tour-signup-card__year">{{ setting.year || currentYear }}年度</div>
|
||||
</div>
|
||||
|
||||
<van-skeleton title :row="8" :loading="loading">
|
||||
<div v-if="setting.serviceNotice" class="tour-signup-notice">
|
||||
<div v-if="serviceNoticePdfHref" class="tour-pdf-preview">
|
||||
<div v-if="pdfPageCount" class="tour-pdf-toolbar">
|
||||
已加载 {{ pdfRenderedPages }} / {{ pdfPageCount }} 页
|
||||
<div class="tour-signup-card__body">
|
||||
<van-skeleton title :row="8" :loading="loading">
|
||||
<div v-if="setting.serviceNotice" class="tour-signup-notice">
|
||||
<div v-if="serviceNoticePdfHref" class="tour-pdf-preview">
|
||||
<div v-if="pdfPageCount" class="tour-pdf-toolbar">
|
||||
已加载 {{ pdfRenderedPages }} / {{ pdfPageCount }} 页
|
||||
</div>
|
||||
<div ref="pdfPreviewContainer" class="tour-pdf-pages"></div>
|
||||
<div v-if="pdfLoading && pdfRenderedPages === 0" class="tour-pdf-status">
|
||||
<van-loading size="22px" vertical>正在加载第一页...</van-loading>
|
||||
</div>
|
||||
<div v-else-if="pdfLoading" class="tour-pdf-status">
|
||||
正在继续加载后续页面...
|
||||
</div>
|
||||
<div v-if="pdfError" class="tour-pdf-status">{{ pdfError }}</div>
|
||||
</div>
|
||||
<div ref="pdfPreviewContainer" class="tour-pdf-pages"></div>
|
||||
<div v-if="pdfLoading && pdfRenderedPages === 0" class="tour-pdf-status">
|
||||
<van-loading size="22px" vertical>正在加载第一页...</van-loading>
|
||||
</div>
|
||||
<div v-else-if="pdfLoading" class="tour-pdf-status">
|
||||
正在继续加载后续页面...
|
||||
</div>
|
||||
<div v-if="pdfError" class="tour-pdf-status">{{ pdfError }}</div>
|
||||
<div v-else v-html="setting.serviceNotice"></div>
|
||||
</div>
|
||||
<div v-else v-html="setting.serviceNotice"></div>
|
||||
</div>
|
||||
<van-empty v-else class="tour-signup-empty" description="暂无服务须知"></van-empty>
|
||||
</van-skeleton>
|
||||
<van-empty v-else class="tour-signup-empty" description="暂无须知"></van-empty>
|
||||
</van-skeleton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tour-signup-footer">
|
||||
<van-button block type="info" color="#0f74bc" round @click="confirmRead">我已阅读</van-button>
|
||||
<van-button block type="info" color="#0f74bc" round @click="confirmRead">我已知晓</van-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -217,10 +165,6 @@ layout("/layouts/platform_h5.html"){
|
||||
pdfPageCount: 0,
|
||||
pdfRenderedPages: 0,
|
||||
pdfRenderToken: 0,
|
||||
bannerList: [
|
||||
"/assets/platform/images/tour/tour-h5-banner-1.jpg",
|
||||
"/assets/platform/images/tour/tour-h5-banner-2.jpg"
|
||||
],
|
||||
setting: {
|
||||
year: "",
|
||||
configName: "",
|
||||
@@ -379,7 +323,7 @@ layout("/layouts/platform_h5.html"){
|
||||
this.renderServiceNoticePdf()
|
||||
})
|
||||
} else {
|
||||
vant.Toast(res.msg || "服务须知加载失败")
|
||||
vant.Toast(res.msg || "须知加载失败")
|
||||
}
|
||||
}).catch(() => {
|
||||
this.loading = false
|
||||
|
||||
@@ -200,7 +200,7 @@ layout("/layouts/platform_h5.html"){
|
||||
</div>
|
||||
<div class="tour-detail-meta-row">
|
||||
<span class="tour-detail-meta-label"><span>联系方式</span><span>:</span></span>
|
||||
<span class="tour-detail-meta-value">{{ lineDetail.contactPhone || signupDetail.contactPhone || '暂无' }}</span>
|
||||
<span class="tour-detail-meta-value">{{ signupDetail.contactPhone || lineDetail.contactPhone || '暂无' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -306,7 +306,7 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
return this.canModifySignup() ? "修改报名" : "审核中"
|
||||
}
|
||||
return "我要核对信息"
|
||||
return "我要报名"
|
||||
},
|
||||
detailActionType() {
|
||||
return this.canRevokeSignup() ? "danger" : "info"
|
||||
|
||||
@@ -462,7 +462,7 @@ layout("/layouts/platform_h5.html"){
|
||||
</style>
|
||||
|
||||
<div id="app" class="tour-line-page">
|
||||
<van-nav-bar title="疗休养线路" left-text="返回" left-arrow @click-left="historyBack('/platform/tour/signup/h5')" fixed placeholder></van-nav-bar>
|
||||
<van-nav-bar title="疗休养线路" left-text="返回" left-arrow @click-left="goHome" fixed placeholder></van-nav-bar>
|
||||
|
||||
<div class="tour-line-toolbar">
|
||||
<div class="tour-line-search-row">
|
||||
@@ -668,6 +668,9 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
goHome() {
|
||||
window.location.replace("/platform/h5/home")
|
||||
},
|
||||
thumbUrl(row) {
|
||||
const thumb = this.resolveThumbPath(row && (row.lineMobileThumb || row.agencyMobileThumb))
|
||||
if (!thumb) {
|
||||
@@ -817,7 +820,7 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
vant.Dialog.confirm({
|
||||
title: "提示",
|
||||
message: "是否确认退出本次疗休养,退出后将取消报名资格!",
|
||||
message: "是否确认退出本次疗休,退出后将取消报名资格!",
|
||||
confirmButtonColor: "#ee2f2f"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/tour/signup/h5/doLeaveTour", { assignmentId: row.assignmentId }).then((res) => {
|
||||
@@ -837,7 +840,7 @@ layout("/layouts/platform_h5.html"){
|
||||
}
|
||||
vant.Dialog.confirm({
|
||||
title: "提示",
|
||||
message: "是否确认取消本线路?",
|
||||
message: "是否确认取消本线路报名?",
|
||||
confirmButtonColor: "#ee2f2f"
|
||||
}).then(() => {
|
||||
this.$axios.post("/platform/tour/signup/h5/doCancelLine", { ledgerId: row.ledgerId }).then((res) => {
|
||||
|
||||
@@ -73,7 +73,7 @@ const home = {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="policy-panel">
|
||||
<div class="policy-panel" @click="enterPolicy">
|
||||
<div class="policy-panel__title">政策文件</div>
|
||||
<div class="policy-panel__action">
|
||||
<span>更多</span>
|
||||
@@ -120,6 +120,7 @@ const home = {
|
||||
return {
|
||||
activityOptions: [],
|
||||
quickEntries: [],
|
||||
disabledHomeModules: ["劳模先进", "普惠信息", "我的课堂", "政策文件"],
|
||||
classroomCourses: [],
|
||||
classroomCourseIndex: 0,
|
||||
classroomCourseTimer: null,
|
||||
@@ -374,16 +375,40 @@ const home = {
|
||||
}, 3000)
|
||||
},
|
||||
menuClick(item) {
|
||||
if (this.isHomeModuleDisabled(item && item.name)) {
|
||||
this.showDisabledHomeModuleNotice()
|
||||
return
|
||||
}
|
||||
this.$pjaxReplace(item.href)
|
||||
},
|
||||
featureClick(item) {
|
||||
if (this.isHomeModuleDisabled(item && item.name)) {
|
||||
this.showDisabledHomeModuleNotice()
|
||||
return
|
||||
}
|
||||
if (item.href) {
|
||||
this.$pjaxReplace(item.href)
|
||||
}
|
||||
},
|
||||
enterClassroom() {
|
||||
if (this.isHomeModuleDisabled("我的课堂")) {
|
||||
this.showDisabledHomeModuleNotice()
|
||||
return
|
||||
}
|
||||
this.$pjaxReplace("/platform/learning/course/h5")
|
||||
},
|
||||
enterPolicy() {
|
||||
if (this.isHomeModuleDisabled("政策文件")) {
|
||||
this.showDisabledHomeModuleNotice()
|
||||
}
|
||||
},
|
||||
isHomeModuleDisabled(moduleName) {
|
||||
// 首页临时停用模块统一在点击入口拦截,保留原模块展示和后台数据配置。
|
||||
return moduleName && this.disabledHomeModules.indexOf(moduleName) !== -1
|
||||
},
|
||||
showDisabledHomeModuleNotice() {
|
||||
this.$toast("暂未启用")
|
||||
},
|
||||
bannerChange(index) {
|
||||
this.activeBannerIndex = index
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user