This commit is contained in:
2026-06-08 08:56:48 +08:00
parent 67a46212e2
commit 5cf398f2b1
53 changed files with 5613 additions and 94 deletions
@@ -0,0 +1,13 @@
-- 疗休养乘车地点字段升级脚本。
-- 配置表保存乘车地点列表 JSON;事项、人员分配、台账保存最终/默认乘车地点。
ALTER TABLE `tour_setting`
MODIFY COLUMN `boardingPlace` text COMMENT '乘车地点列表JSON';
ALTER TABLE `tour_matter`
ADD COLUMN `defaultBoardingPlace` varchar(100) DEFAULT NULL COMMENT '默认乘车地点' AFTER `lineId`;
ALTER TABLE `tour_user_assignment`
ADD COLUMN `boardingPlace` varchar(100) DEFAULT NULL COMMENT '乘车地点' AFTER `lineName`;
ALTER TABLE `tour_ledger`
ADD COLUMN `boardingPlace` varchar(100) DEFAULT NULL COMMENT '乘车地点' AFTER `travelAgencyName`;
@@ -0,0 +1,29 @@
-- 疗休养退出申请表。
CREATE TABLE IF NOT EXISTS `tour_leave_apply` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`matterId` varchar(32) DEFAULT NULL COMMENT '疗休养事项ID',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`jobNo` varchar(50) DEFAULT NULL COMMENT '工号',
`userName` varchar(100) DEFAULT NULL COMMENT '姓名',
`userId` varchar(32) DEFAULT NULL COMMENT '用户ID',
`unionId` varchar(32) DEFAULT NULL COMMENT '所属工会ID',
`unionName` varchar(100) DEFAULT NULL COMMENT '所属工会',
`reason` text COMMENT '不能参加原因',
`status` varchar(30) DEFAULT NULL COMMENT '状态',
`auditBy` varchar(32) DEFAULT NULL COMMENT '审核人ID',
`auditName` varchar(100) DEFAULT NULL COMMENT '审核人姓名',
`auditTime` varchar(30) DEFAULT NULL COMMENT '审核时间',
`auditRemark` text COMMENT '审核备注',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_tour_leave_apply_user` (`userId`),
KEY `idx_tour_leave_apply_job_no` (`jobNo`),
KEY `idx_tour_leave_apply_union` (`unionId`),
KEY `idx_tour_leave_apply_status` (`status`),
KEY `idx_tour_leave_apply_matter` (`matterId`),
KEY `idx_tour_leave_apply_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疗休养退出申请';
+13 -1
View File
@@ -9,6 +9,17 @@ CREATE TABLE IF NOT EXISTS `tour_matter` (
`unionId` varchar(32) DEFAULT NULL COMMENT '所属工会ID',
`organizationType` varchar(100) DEFAULT NULL COMMENT '组织形式',
`mobileThumb` varchar(500) DEFAULT NULL COMMENT '移动端缩略图',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`defaultBoardingPlace` varchar(100) DEFAULT NULL COMMENT '默认乘车地点',
`signupStartTime` varchar(20) DEFAULT NULL COMMENT '报名开始时间',
`signupEndTime` varchar(20) DEFAULT NULL COMMENT '报名结束时间',
`travelStartTime` varchar(20) DEFAULT NULL COMMENT '出行开始时间',
`travelEndTime` varchar(20) DEFAULT NULL COMMENT '出行结束时间',
`contactName` varchar(30) DEFAULT NULL COMMENT '联系人',
`contactPhone` varchar(30) DEFAULT NULL COMMENT '联系方式',
`minGroupPeople` int DEFAULT NULL COMMENT '最少成团人数',
`maxGroupPeople` int DEFAULT NULL COMMENT '最多成团人数',
`estimatedCost` decimal(10,2) DEFAULT NULL COMMENT '预计费用',
`enabled` tinyint(1) DEFAULT 1 COMMENT '事项状态',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
@@ -21,5 +32,6 @@ CREATE TABLE IF NOT EXISTS `tour_matter` (
KEY `idx_tour_matter_creator` (`creatorUserId`),
KEY `idx_tour_matter_setting` (`settingId`),
KEY `idx_tour_matter_union` (`unionId`),
KEY `idx_tour_matter_org_type` (`organizationType`)
KEY `idx_tour_matter_org_type` (`organizationType`),
KEY `idx_tour_matter_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项';
@@ -0,0 +1,4 @@
-- 疗休养配置新增乘车地点、出行人数指标。
ALTER TABLE `tour_setting`
ADD COLUMN `boardingPlace` text COMMENT '乘车地点列表JSON' AFTER `tourType`,
ADD COLUMN `travelPeopleQuota` int DEFAULT 0 COMMENT '出行人数指标' AFTER `boardingPlace`;
@@ -0,0 +1,2 @@
ALTER TABLE `tour_setting`
ADD COLUMN `signupEligibilityMode` varchar(30) DEFAULT 'ASSIGNED_USER' COMMENT '报名资格校验方式' AFTER `activityGroupId`;
+75 -1
View File
@@ -5,7 +5,10 @@ CREATE TABLE IF NOT EXISTS `tour_setting` (
`year` int DEFAULT NULL COMMENT '年度',
`configName` varchar(100) DEFAULT NULL COMMENT '疗休养配置名称',
`tourType` varchar(50) DEFAULT NULL COMMENT '疗休养类型',
`boardingPlace` text COMMENT '乘车地点列表JSON',
`travelPeopleQuota` int DEFAULT 0 COMMENT '出行人数指标',
`activityGroupId` varchar(32) DEFAULT NULL COMMENT '可参加人员范围ID',
`signupEligibilityMode` varchar(30) DEFAULT 'ASSIGNED_USER' COMMENT '报名资格校验方式',
`sortNo` int DEFAULT NULL COMMENT '排序编号',
`minGroupPeople` int DEFAULT NULL COMMENT '最少成团人数',
`maxGroupPeople` int DEFAULT NULL COMMENT '最多成团人数',
@@ -51,6 +54,65 @@ CREATE TABLE IF NOT EXISTS `tour_setting_lot` (
KEY `idx_tour_setting_lot_value` (`lotValue`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养配置标段';
-- 疗休养配置分工会名额分配表。
CREATE TABLE IF NOT EXISTS `tour_setting_union_quota` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`settingId` varchar(32) DEFAULT NULL COMMENT '所属疗休养配置ID',
`unionId` varchar(32) DEFAULT NULL COMMENT '分工会ID',
`unionName` varchar(100) DEFAULT NULL COMMENT '分工会名称',
`formalQuota` int DEFAULT 0 COMMENT '正式人员名额',
`backupQuota` int DEFAULT 0 COMMENT '替补人员名额',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_tour_setting_union_quota_setting` (`settingId`),
KEY `idx_tour_setting_union_quota_union` (`unionId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疗休养配置分工会名额分配';
-- 疗休养人员分配表。
CREATE TABLE IF NOT EXISTS `tour_user_assignment` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`settingId` varchar(32) DEFAULT NULL COMMENT '疗休养配置ID',
`matterId` varchar(32) DEFAULT NULL COMMENT '疗休养事项ID',
`matterName` varchar(100) DEFAULT NULL COMMENT '疗休养事项名称',
`travelAgencyId` varchar(32) DEFAULT NULL COMMENT '旅行社ID',
`travelAgencyName` varchar(100) DEFAULT NULL COMMENT '旅行社名称',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`lineName` varchar(100) DEFAULT NULL COMMENT '线路名称',
`boardingPlace` varchar(100) DEFAULT NULL COMMENT '乘车地点',
`userId` varchar(32) DEFAULT NULL COMMENT '人员ID',
`loginName` varchar(50) DEFAULT NULL COMMENT '工号',
`userName` varchar(100) DEFAULT NULL COMMENT '姓名',
`gender` varchar(20) DEFAULT NULL COMMENT '性别',
`mobile` varchar(30) DEFAULT NULL COMMENT '手机号',
`unitId` varchar(32) DEFAULT NULL COMMENT '所在单位ID',
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
`unionId` varchar(32) DEFAULT NULL COMMENT '所在分工会ID',
`unionName` varchar(100) DEFAULT NULL COMMENT '所在分工会',
`assignSource` varchar(30) DEFAULT NULL COMMENT '分配来源',
`personType` varchar(30) DEFAULT NULL COMMENT '人员类型',
`cancelled` tinyint(1) DEFAULT 0 COMMENT '是否已退出',
`assignedBy` varchar(32) DEFAULT NULL COMMENT '分配人ID',
`assignedName` varchar(100) DEFAULT NULL COMMENT '分配人姓名',
`assignedAt` varchar(30) DEFAULT NULL COMMENT '分配时间',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tour_user_assignment_setting_user` (`settingId`, `userId`),
KEY `idx_tour_user_assignment_setting` (`settingId`),
KEY `idx_tour_user_assignment_user` (`userId`),
KEY `idx_tour_user_assignment_union` (`unionId`),
KEY `idx_tour_user_assignment_source` (`assignSource`),
KEY `idx_tour_user_assignment_person_type` (`personType`),
KEY `idx_tour_user_assignment_cancelled` (`cancelled`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疗休养人员分配';
-- 旅行社管理表。
CREATE TABLE IF NOT EXISTS `tour_travel_agency` (
`id` varchar(32) NOT NULL COMMENT 'ID',
@@ -118,6 +180,17 @@ CREATE TABLE IF NOT EXISTS `tour_matter` (
`unionId` varchar(32) DEFAULT NULL COMMENT '所属工会ID',
`organizationType` varchar(100) DEFAULT NULL COMMENT '组织形式',
`mobileThumb` varchar(500) DEFAULT NULL COMMENT '移动端缩略图',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`defaultBoardingPlace` varchar(100) DEFAULT NULL COMMENT '默认乘车地点',
`signupStartTime` varchar(20) DEFAULT NULL COMMENT '报名开始时间',
`signupEndTime` varchar(20) DEFAULT NULL COMMENT '报名结束时间',
`travelStartTime` varchar(20) DEFAULT NULL COMMENT '出行开始时间',
`travelEndTime` varchar(20) DEFAULT NULL COMMENT '出行结束时间',
`contactName` varchar(30) DEFAULT NULL COMMENT '联系人',
`contactPhone` varchar(30) DEFAULT NULL COMMENT '联系方式',
`minGroupPeople` int DEFAULT NULL COMMENT '最少成团人数',
`maxGroupPeople` int DEFAULT NULL COMMENT '最多成团人数',
`estimatedCost` decimal(10,2) DEFAULT NULL COMMENT '预计费用',
`enabled` tinyint(1) DEFAULT 1 COMMENT '事项状态',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
@@ -130,7 +203,8 @@ CREATE TABLE IF NOT EXISTS `tour_matter` (
KEY `idx_tour_matter_creator` (`creatorUserId`),
KEY `idx_tour_matter_setting` (`settingId`),
KEY `idx_tour_matter_union` (`unionId`),
KEY `idx_tour_matter_org_type` (`organizationType`)
KEY `idx_tour_matter_org_type` (`organizationType`),
KEY `idx_tour_matter_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项';
-- 疗休养事项批次表。
@@ -0,0 +1,17 @@
-- 疗休养配置分工会名额分配表。
CREATE TABLE IF NOT EXISTS `tour_setting_union_quota` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`settingId` varchar(32) DEFAULT NULL COMMENT '所属疗休养配置ID',
`unionId` varchar(32) DEFAULT NULL COMMENT '分工会ID',
`unionName` varchar(100) DEFAULT NULL COMMENT '分工会名称',
`formalQuota` int DEFAULT 0 COMMENT '正式人员名额',
`backupQuota` int DEFAULT 0 COMMENT '替补人员名额',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_tour_setting_union_quota_setting` (`settingId`),
KEY `idx_tour_setting_union_quota_union` (`unionId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疗休养配置分工会名额分配';
@@ -0,0 +1,4 @@
-- 疗休养人员分配新增退出状态。
ALTER TABLE `tour_user_assignment`
ADD COLUMN `cancelled` tinyint(1) DEFAULT 0 COMMENT '是否已退出' AFTER `personType`,
ADD KEY `idx_tour_user_assignment_cancelled` (`cancelled`);
@@ -0,0 +1,40 @@
-- 疗休养人员分配表。
CREATE TABLE IF NOT EXISTS `tour_user_assignment` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`settingId` varchar(32) DEFAULT NULL COMMENT '疗休养配置ID',
`matterId` varchar(32) DEFAULT NULL COMMENT '疗休养事项ID',
`matterName` varchar(100) DEFAULT NULL COMMENT '疗休养事项名称',
`travelAgencyId` varchar(32) DEFAULT NULL COMMENT '旅行社ID',
`travelAgencyName` varchar(100) DEFAULT NULL COMMENT '旅行社名称',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`lineName` varchar(100) DEFAULT NULL COMMENT '线路名称',
`boardingPlace` varchar(100) DEFAULT NULL COMMENT '乘车地点',
`userId` varchar(32) DEFAULT NULL COMMENT '人员ID',
`loginName` varchar(50) DEFAULT NULL COMMENT '工号',
`userName` varchar(100) DEFAULT NULL COMMENT '姓名',
`gender` varchar(20) DEFAULT NULL COMMENT '性别',
`mobile` varchar(30) DEFAULT NULL COMMENT '手机号',
`unitId` varchar(32) DEFAULT NULL COMMENT '所在单位ID',
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
`unionId` varchar(32) DEFAULT NULL COMMENT '所在分工会ID',
`unionName` varchar(100) DEFAULT NULL COMMENT '所在分工会',
`assignSource` varchar(30) DEFAULT NULL COMMENT '分配来源',
`personType` varchar(30) DEFAULT NULL COMMENT '人员类型',
`cancelled` tinyint(1) DEFAULT 0 COMMENT '是否已退出',
`assignedBy` varchar(32) DEFAULT NULL COMMENT '分配人ID',
`assignedName` varchar(100) DEFAULT NULL COMMENT '分配人姓名',
`assignedAt` varchar(30) DEFAULT NULL COMMENT '分配时间',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tour_user_assignment_setting_user` (`settingId`, `userId`),
KEY `idx_tour_user_assignment_setting` (`settingId`),
KEY `idx_tour_user_assignment_user` (`userId`),
KEY `idx_tour_user_assignment_union` (`unionId`),
KEY `idx_tour_user_assignment_source` (`assignSource`),
KEY `idx_tour_user_assignment_person_type` (`personType`),
KEY `idx_tour_user_assignment_cancelled` (`cancelled`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疗休养人员分配';
@@ -1,7 +1,7 @@
<template>
<div class="search">
<slot></slot>
<div class="search-query">
<div v-if="isSearchButton" class="search-query">
<el-button type="primary" icon="el-icon-search" @click="$emit('search', null)">搜索</el-button>
</div>
</div>
@@ -9,7 +9,14 @@
<script>
module.exports = {
name: "pageFormSearch"
name: "pageFormSearch",
props: {
// 允许业务页面自行放置查询/重置按钮,避免公共组件再追加默认查询按钮。
isSearchButton: {
type: Boolean,
default: true
}
}
}
</script>
@@ -300,7 +300,7 @@ const ACTIVITY_CULTURE_INFO_MANAGE = {
cancelButtonText: "取消",
type: "warning"
}).then(async () => {
const resp = await this.$axios.post(loc() + "/sendNotify", {
const resp = await this.$axios.post("/platform/activity/culture/infoManage/sendNotify", {
activityId: this.notifyActivityId,
content: this.notifyContent
})
@@ -0,0 +1,649 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="年度">
<el-date-picker
v-model="pageForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%"
@change="pageYearChange">
</el-date-picker>
</search-item>
<search-item label="疗休养配置">
<el-select v-model="pageForm.settingId" clearable filterable placeholder="请选择配置" style="width: 100%" @change="pageSettingChange">
<el-option v-for="item in settingOptions" :key="item.id" :label="item.configName" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="分配路线">
<el-select v-model="pageForm.matterId" clearable filterable placeholder="请选择分配路线" style="width: 100%">
<el-option v-for="item in pageMatterOptions" :key="item.matterId" :label="matterOptionLabel(item)" :value="item.matterId"></el-option>
</el-select>
</search-item>
<search-item label="人员类型">
<el-select v-model="pageForm.personType" clearable placeholder="请选择人员类型" style="width: 100%">
<el-option label="正式人员" value="FORMAL"></el-option>
<el-option label="替补人员" value="BACKUP"></el-option>
</el-select>
</search-item>
<search-item label="姓名/工号">
<el-input
v-model="pageForm.keyword"
clearable
placeholder="请输入姓名或工号"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="分工会人员分配列表">
<el-button type="primary" size="medium" @click="openAssign">
<i class="el-icon-user"></i>
人员分配
</el-button>
</table-tool>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
class="vi-table"
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="70" align="center" header-align="center"></el-table-column>
<el-table-column label="姓名" prop="userName" width="110" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="工号" prop="loginName" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="unitName" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路" prop="lineName" min-width="200" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="出行时间" prop="travelPeriod" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="乘车地点" prop="boardingPlace" min-width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="人员类型" prop="personType" width="110" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="row.personType === 'BACKUP' ? 'warning' : 'success'">{{ personTypeText(row.personType) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="是否退出" prop="cancelled" width="110" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="isCancelled(row) ? 'danger' : 'success'">{{ isCancelled(row) ? '已退出' : '未退出' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="分配时间" prop="assignedAt" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="320" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" :loading="personTypeSwitching === row.id" @click="switchPersonType(row, row.personType === 'BACKUP' ? 'FORMAL' : 'BACKUP')">{{ row.personType === 'BACKUP' ? '转为正式' : '转为替补' }}</el-button>
<el-button v-if="isCancelled(row) && $auth.hasPermission('tour.branchUserAssignment.cancelRestore')" size="mini" type="warning" @click="restoreCancel(row)">取消退出</el-button>
<el-button size="mini" type="danger" @click="doDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
<el-dialog
custom-class="tour-user-assignment-dialog"
title="人员分配"
:visible.sync="assignDialogVisible"
:close-on-click-modal="false"
width="76%"
@closed="resetAssignDialog">
<el-form :model="assignForm" :rules="assignRules" ref="assignFormRef" label-width="110px">
<el-row :gutter="16">
<el-col :span="6">
<el-form-item label="年度" prop="year">
<el-date-picker
v-model="assignForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%"
@change="assignYearChange">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="疗休养配置" prop="settingId">
<el-select v-model="assignForm.settingId" clearable filterable placeholder="请选择配置" style="width: 100%" @change="assignSettingChange">
<el-option v-for="item in assignSettingOptions" :key="item.id" :label="item.configName" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="分配路线" prop="matterId">
<el-select v-model="assignForm.matterId" clearable filterable placeholder="可选,选择后代报名" style="width: 100%" :disabled="assignForm.personType === 'BACKUP'">
<el-option v-for="item in assignMatterOptions" :key="item.matterId" :label="matterOptionLabel(item)" :value="item.matterId"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="人员类型" prop="personType">
<el-radio-group v-model="assignForm.personType" @change="assignPersonTypeChange">
<el-radio-button label="FORMAL">正式人员</el-radio-button>
<el-radio-button label="BACKUP">替补人员</el-radio-button>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="quota-bar">
<div class="quota-item">
<span class="quota-label">正式名额</span>
<span>{{ quotaInfo.formalQuota }}</span>
<span class="quota-muted">已分配 {{ quotaInfo.formalUsed }},剩余 {{ quotaInfo.formalRemaining }}</span>
</div>
<div class="quota-item">
<span class="quota-label">替补名额</span>
<span>{{ quotaInfo.backupQuota }}</span>
<span class="quota-muted">已分配 {{ quotaInfo.backupUsed }},剩余 {{ quotaInfo.backupRemaining }}</span>
</div>
</div>
<div class="candidate-toolbar">
<el-input
v-model="candidateForm.keyword"
clearable
placeholder="姓名/工号"
style="width: 240px"
@keyup.enter.native="candidateSearch">
</el-input>
<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>
</div>
<el-table
ref="candidateTable"
v-loading="candidateLoading"
:data="candidateData"
row-key="userId"
border
size="mini"
height="420"
@selection-change="candidateSelectionChange">
<el-table-column type="selection" width="48" :reserve-selection="true" align="center" header-align="center"></el-table-column>
<el-table-column label="姓名" prop="userName" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="工号" prop="loginName" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="gender" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="手机号" prop="mobile" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属分工会" prop="unionName" min-width="160" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="unitName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
</el-table>
<el-row class="el-pagination-container candidate-pagination">
<el-pagination
background
:current-page="candidateForm.pageNumber"
:page-sizes="[10, 20, 50, 100]"
:page-size="candidateForm.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="candidateForm.totalCount"
@size-change="candidateSizeChange"
@current-change="candidatePageChange">
</el-pagination>
</el-row>
<span slot="footer" class="dialog-footer">
<el-button @click="assignDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="assignSubmitting" @click="doAssign">保存分配</el-button>
</span>
</el-dialog>
</div>
<style>
#app .search .search-item {
width: calc((100% - 150px) / 4);
}
.tour-user-assignment-dialog .el-dialog__body {
max-height: 72vh;
overflow-y: auto;
}
.quota-bar {
display: flex;
gap: 12px;
margin-bottom: 12px;
}
.quota-item {
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 10px 12px;
min-width: 220px;
color: #303133;
background: #fafafa;
}
.quota-label {
font-weight: 600;
margin-right: 10px;
}
.quota-muted {
margin-left: 10px;
color: #909399;
}
.candidate-toolbar {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.candidate-selected {
color: #606266;
white-space: nowrap;
}
.candidate-pagination {
margin-top: 12px;
margin-bottom: 0;
text-align: right;
}
@media screen and (max-width: 1350px) {
#app .search .search-item {
width: calc((100% - 100px) / 3);
}
}
@media screen and (max-width: 1200px) {
#app .search .search-item {
width: calc((100% - 50px) / 2);
}
.quota-bar {
flex-direction: column;
}
}
@media screen and (max-width: 992px) {
#app .search .search-item {
width: 100%;
}
.candidate-selected {
margin-left: 0;
}
}
</style>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
const currentYear = moment().format("YYYY")
return {
settingOptions: [],
pageMatterOptions: [],
assignSettingOptions: [],
assignMatterOptions: [],
quotaInfo: {
formalQuota: 0,
backupQuota: 0,
formalUsed: 0,
backupUsed: 0,
formalRemaining: 0,
backupRemaining: 0
},
assignDialogVisible: false,
candidateLoading: false,
candidateData: [],
selectedCandidates: [],
assignSubmitting: false,
personTypeSwitching: "",
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "assignedAt",
pageOrderBy: "descending",
year: currentYear,
settingId: "",
matterId: "",
personType: "",
keyword: ""
},
assignForm: {
year: currentYear,
settingId: "",
matterId: "",
personType: "FORMAL"
},
candidateForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
keyword: ""
},
assignRules: {
year: [{required: true, message: "请选择年度", trigger: ["change", "blur"]}],
settingId: [{required: true, message: "请选择疗休养配置", trigger: ["change", "blur"]}],
personType: [{required: true, message: "请选择人员类型", trigger: ["change", "blur"]}]
}
}
},
methods: {
defaultAssignForm(currentYear) {
return {
year: currentYear || moment().format("YYYY"),
settingId: "",
matterId: "",
personType: "FORMAL"
}
},
defaultCandidateForm() {
return {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
keyword: ""
}
},
defaultQuotaInfo() {
return {
formalQuota: 0,
backupQuota: 0,
formalUsed: 0,
backupUsed: 0,
formalRemaining: 0,
backupRemaining: 0
}
},
resetSearch() {
const currentYear = moment().format("YYYY")
this.pageForm.year = currentYear
this.pageForm.settingId = ""
this.pageForm.matterId = ""
this.pageForm.personType = ""
this.pageForm.keyword = ""
this.loadSettingOptions()
},
pageYearChange() {
this.pageForm.settingId = ""
this.pageForm.matterId = ""
this.pageMatterOptions = []
this.loadSettingOptions()
},
pageSettingChange() {
this.pageForm.matterId = ""
this.loadPageMatterOptions()
},
loadSettingOptions() {
this.$axios.post(loc() + "/settingOptions", {year: this.pageForm.year}).then((res) => {
if (res.code === 0) {
this.settingOptions = res.data || []
// 主页面默认选择当前年度第一条配置,避免进入页面后查询范围为空。
if (!this.pageForm.settingId && this.settingOptions.length > 0) {
this.pageForm.settingId = this.settingOptions[0].id
this.loadPageMatterOptions()
this.doSearch()
} else {
this.pageMatterOptions = []
this.doSearch()
}
}
})
},
loadPageMatterOptions() {
if (!this.pageForm.settingId) {
this.pageMatterOptions = []
return
}
this.$axios.post(loc() + "/matterOptions", {settingId: this.pageForm.settingId}).then((res) => {
if (res.code === 0) {
this.pageMatterOptions = res.data || []
}
})
},
openAssign() {
const year = this.pageForm.year || moment().format("YYYY")
this.assignForm = this.defaultAssignForm(year)
this.candidateForm = this.defaultCandidateForm()
this.quotaInfo = this.defaultQuotaInfo()
this.candidateData = []
this.selectedCandidates = []
this.assignDialogVisible = true
this.loadAssignSettingOptions()
},
resetAssignDialog() {
this.assignForm = this.defaultAssignForm(moment().format("YYYY"))
this.candidateForm = this.defaultCandidateForm()
this.assignMatterOptions = []
this.quotaInfo = this.defaultQuotaInfo()
this.candidateData = []
this.selectedCandidates = []
this.assignSubmitting = false
},
assignYearChange() {
this.assignForm.settingId = ""
this.assignForm.matterId = ""
this.assignMatterOptions = []
this.quotaInfo = this.defaultQuotaInfo()
this.clearCandidateSelection()
this.loadAssignSettingOptions()
},
assignSettingChange() {
this.assignForm.matterId = ""
this.assignMatterOptions = []
this.clearCandidateSelection()
this.loadAssignMatterOptions()
this.loadQuotaInfo()
this.loadCandidatePageData()
},
assignPersonTypeChange() {
if (this.assignForm.personType === "BACKUP") {
this.assignForm.matterId = ""
}
},
loadAssignSettingOptions() {
this.$axios.post(loc() + "/settingOptions", {year: this.assignForm.year}).then((res) => {
if (res.code === 0) {
this.assignSettingOptions = res.data || []
// 人员分配弹窗独立默认取当前年度第一条配置,再加载名额、线路和候选人员。
if (!this.assignForm.settingId && this.assignSettingOptions.length > 0) {
this.assignForm.settingId = this.assignSettingOptions[0].id
this.loadAssignMatterOptions()
this.loadQuotaInfo()
this.loadCandidatePageData()
} else if (this.assignForm.settingId) {
this.loadAssignMatterOptions()
this.loadQuotaInfo()
this.loadCandidatePageData()
} else {
this.assignMatterOptions = []
this.quotaInfo = this.defaultQuotaInfo()
this.candidateData = []
this.candidateForm.totalCount = 0
}
}
})
},
loadAssignMatterOptions() {
if (!this.assignForm.settingId) {
this.assignMatterOptions = []
return
}
this.$axios.post(loc() + "/matterOptions", {settingId: this.assignForm.settingId}).then((res) => {
if (res.code === 0) {
this.assignMatterOptions = res.data || []
}
})
},
loadQuotaInfo() {
if (!this.assignForm.settingId) {
this.quotaInfo = this.defaultQuotaInfo()
return
}
this.$axios.post(loc() + "/quotaInfo", {settingId: this.assignForm.settingId}).then((res) => {
if (res.code === 0) {
this.quotaInfo = Object.assign(this.defaultQuotaInfo(), res.data || {})
}
})
},
loadCandidatePageData() {
if (!this.assignForm.settingId) {
this.candidateData = []
this.candidateForm.totalCount = 0
return
}
this.candidateLoading = true
this.$axios.post(loc() + "/candidatePageData", {
pageNumber: this.candidateForm.pageNumber,
pageSize: this.candidateForm.pageSize,
settingId: this.assignForm.settingId,
keyword: this.candidateForm.keyword
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.candidateData = data.list || []
this.candidateForm.totalCount = data.totalCount || 0
} else {
this.$message.warning(res.msg || "候选人员查询失败")
}
}).finally(() => {
this.candidateLoading = false
})
},
candidateSearch() {
this.candidateForm.pageNumber = 1
this.loadCandidatePageData()
},
resetCandidateSearch() {
this.candidateForm.keyword = ""
this.candidateForm.pageNumber = 1
this.loadCandidatePageData()
},
candidateSizeChange(size) {
this.candidateForm.pageSize = size
this.candidateForm.pageNumber = 1
this.loadCandidatePageData()
},
candidatePageChange(pageNumber) {
this.candidateForm.pageNumber = pageNumber
this.loadCandidatePageData()
},
candidateSelectionChange(rows) {
this.selectedCandidates = rows || []
},
clearCandidateSelection() {
this.selectedCandidates = []
if (this.$refs.candidateTable) {
this.$refs.candidateTable.clearSelection()
}
},
doAssign() {
this.$refs.assignFormRef.validate((valid) => {
if (!valid) return
if (this.selectedCandidates.length <= 0) {
this.$message.warning("请选择需要分配的人员")
return
}
this.$confirm("确定保存当前人员分配吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
const userIds = this.selectedCandidates.map(item => item.userId)
this.assignSubmitting = true
this.$axios.post(loc() + "/doAssign", {
settingId: this.assignForm.settingId,
matterId: this.assignForm.personType === "BACKUP" ? "" : this.assignForm.matterId,
personType: this.assignForm.personType,
userIds: JSON.stringify(userIds)
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.$message.success("分配成功" + (data.ledgerCount > 0 ? ",已代报名" + data.ledgerCount + "人" : "") + (data.skipCount > 0 ? ",已跳过" + data.skipCount + "人" : ""))
this.assignDialogVisible = false
this.doSearch()
} else {
this.$message.warning(res.msg || "分配失败")
}
}).finally(() => {
this.assignSubmitting = false
})
}).catch(() => {})
})
},
switchPersonType(row, targetType) {
this.personTypeSwitching = row.id
this.$axios.post(loc() + "/switchPersonType", {
id: row.id,
personType: targetType
}).then((res) => {
if (res.code === 0) {
this.$message.success("切换成功")
this.doSearch()
} else {
this.$message.warning(res.msg || "切换失败")
}
}).finally(() => {
this.personTypeSwitching = ""
})
},
doDelete(row) {
this.$axios.post(loc() + "/deleteInfo", {id: row.id}).then((infoRes) => {
if (infoRes.code !== 0) {
this.$message.warning(infoRes.msg || "删除校验失败")
return
}
const info = infoRes.data || {}
const hasLedger = !!info.hasLedger
const message = hasLedger ? "该人员已报名,删除分配记录将同时删除台账数据,是否继续?" : "确定删除该人员分配记录吗?"
this.$confirm(message, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doDelete", {
id: row.id,
deleteLedger: hasLedger
}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg || "删除成功")
this.doSearch()
} else {
this.$message.warning(res.msg || "删除失败")
}
})
}).catch(() => {})
})
},
personTypeText(personType) {
return personType === "BACKUP" ? "替补人员" : "正式人员"
},
isCancelled(row) {
return row && (row.cancelled === true || row.cancelled === 1)
},
restoreCancel(row) {
this.$confirm("确定将【" + row.userName + "】恢复为未退出状态吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/restoreCancel", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success("取消退出成功")
this.doSearch()
} else {
this.$message.warning(res.msg || "取消退出失败")
}
})
}).catch(() => {})
},
matterOptionLabel(item) {
const lineName = item.lineName || ""
return lineName || item.matterName || ""
}
},
mounted() {
this.loadSettingOptions()
}
})
</script>
<!--#
}
#-->
@@ -34,7 +34,7 @@ layout("/layouts/platform.html"){
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
@@ -112,7 +112,7 @@ layout("/layouts/platform.html"){
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="viewSearch">搜索</el-button>
<el-button size="medium" @click="viewReset">重置</el-button>
</div>
@@ -127,6 +127,23 @@ layout("/layouts/platform.html"){
:size="tableSize"
border
@sort-change="viewPageOrder">
<el-table-column type="expand" width="50">
<template slot-scope="{row}">
<el-table
:data="row.families || []"
border
size="mini"
class="tour-family-sub-table"
empty-text="暂无家属信息">
<el-table-column label="姓名" prop="familyName" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="年龄" prop="age" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="性别" prop="gender" width="90" align="center" header-align="center"></el-table-column>
<el-table-column label="手机号" prop="mobile" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="身份证号码" prop="idCard" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="关系" prop="relationship" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
</el-table>
</template>
</el-table-column>
<el-table-column label="序号" type="index" :index="viewIndexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
@@ -342,6 +359,10 @@ layout("/layouts/platform.html"){
margin-bottom: 12px;
padding-left: 10px;
}
.tour-family-sub-table {
margin: 8px 24px;
width: calc(100% - 48px);
}
.el-table .direct-family-row > td,
.el-table .direct-family-row > td .cell {
color: #f56c6c;
@@ -0,0 +1,192 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="姓名/工号">
<el-input
v-model="pageForm.keyword"
clearable
placeholder="请输入姓名或工号"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="所属工会">
<el-input
v-model="pageForm.unionName"
clearable
placeholder="请输入所属工会"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="状态">
<el-select v-model="pageForm.status" clearable placeholder="请选择状态" style="width: 100%">
<el-option label="未取消" value="NORMAL"></el-option>
<el-option label="已取消" value="CANCELLED"></el-option>
</el-select>
</search-item>
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="疗休养退出取消列表"></table-tool>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="工号" prop="loginName" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="疗休养配置" prop="settingName" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="路线" prop="lineName" min-width="200" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="出行时间" prop="travelPeriod" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="乘车地点" prop="boardingPlace" min-width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="人员类型" prop="personType" width="110" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="small" :type="row.personType === 'BACKUP' ? 'warning' : 'success'">{{ personTypeText(row.personType) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="状态" prop="cancelStatus" width="110" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="small" :type="isCancelled(row) ? 'danger' : 'success'">{{ isCancelled(row) ? '已取消' : '未取消' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="分配来源" prop="assignSource" width="120" align="center" header-align="center">
<template slot-scope="{row}">
{{ assignSourceText(row.assignSource) }}
</template>
</el-table-column>
<el-table-column label="分配时间" prop="assignedAt" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="170" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button v-if="!isCancelled(row)" size="mini" type="danger" @click="doCancel(row)">退出</el-button>
<el-button v-if="isCancelled(row) && permissionInfo.canRestore" size="mini" type="primary" @click="doRestore(row)">取消退出</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
</div>
<style>
#app .search .search-item {
width: calc((100% - 150px) / 4);
}
@media screen and (max-width: 1200px) {
#app .search .search-item {
width: calc((100% - 50px) / 2);
}
}
@media screen and (max-width: 992px) {
#app .search .search-item {
width: 100%;
}
}
</style>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
return {
permissionInfo: {
canRestore: false
},
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
keyword: "",
unionName: "",
status: "",
pageOrderName: "assignedAt",
pageOrderBy: "descending"
}
}
},
methods: {
resetSearch() {
this.pageForm.keyword = ""
this.pageForm.unionName = ""
this.pageForm.status = ""
this.doSearch()
},
loadPermissionInfo() {
this.$axios.post(loc() + "/permissionInfo").then((res) => {
if (res.code === 0) {
this.permissionInfo = Object.assign({ canRestore: false }, res.data || {})
}
})
},
isCancelled(row) {
return row && (row.cancelStatus === "CANCELLED" || row.cancelled === true || row.cancelled === 1)
},
personTypeText(personType) {
return personType === "BACKUP" ? "替补人员" : "正式人员"
},
assignSourceText(assignSource) {
if (assignSource === "SCHOOL_UNION") {
return "校工会分配"
}
if (assignSource === "BRANCH_UNION") {
return "分工会分配"
}
return assignSource || ""
},
doCancel(row) {
this.$confirm("确定取消【" + row.userName + "】的疗休养资格吗?取消后将同步删除该人员对应台账数据。", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doCancel", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success("取消成功")
this.pageData()
} else {
this.$message.warning(res.msg || "取消失败")
}
})
}).catch(() => {})
},
doRestore(row) {
this.$confirm("确定将【" + row.userName + "】恢复为未取消状态吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doRestore", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success("取消退出成功")
this.pageData()
} else {
this.$message.warning(res.msg || "取消退出失败")
}
})
}).catch(() => {})
}
},
mounted() {
this.loadPermissionInfo()
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -56,7 +56,7 @@ layout("/layouts/platform.html"){
placeholder="请选择线路类型">
</dict-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
@@ -71,9 +71,10 @@ layout("/layouts/platform.html"){
<div class="tour-ledger-actions">
<el-button size="medium" type="primary" icon="el-icon-upload2" @click="showImportDialog = true">参加人员导入</el-button>
<el-button size="medium" type="primary" icon="el-icon-check" @click="setParticipants">设置参加人员</el-button>
<el-button size="medium" type="primary" icon="el-icon-download" @click="exportLedgerData">导出台账数据</el-button>
<el-button size="medium" type="primary" icon="el-icon-download" @click="exportUnionSignupZip">导出分工会报名压缩包</el-button>
</div>
<div class="tour-ledger-scope">
<div v-if="false" class="tour-ledger-scope">
<el-button
class="tour-scope-btn"
size="medium"
@@ -104,7 +105,12 @@ layout("/layouts/platform.html"){
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="gender" width="90" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="年龄" prop="age" width="90" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="身份证号" prop="idCard" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="手机号" prop="mobile" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="unitName" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="乘车地点" prop="boardingPlace" min-width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="报名线路" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="出行时段" prop="travelPeriod" min-width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路类型" prop="lineType" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
@@ -437,6 +443,16 @@ layout("/layouts/platform.html"){
}
window.location.href = loc() + "/exportUnionSignupZip?" + params.toString()
},
exportLedgerData() {
const params = new URLSearchParams()
;["startYear", "endYear", "keyword", "unionId", "lineId", "travelPeriod", "lineType"].forEach(key => {
const value = this.pageForm[key]
if (value !== undefined && value !== null && value !== "") {
params.append(key, value)
}
})
window.location.href = loc() + "/exportLedgerData?" + params.toString()
},
afterImport() {
this.showImportDialog = false
this.clearTableSelection()
@@ -34,7 +34,7 @@ layout("/layouts/platform.html"){
<el-option v-for="item in organizationTypeOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
@@ -154,6 +154,11 @@ layout("/layouts/platform.html"){
<el-option v-for="item in lineOptions" :key="item.id" :label="lineOptionLabel(item)" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="默认乘车地点" prop="defaultBoardingPlace">
<el-select v-model="batchForm.defaultBoardingPlace" clearable filterable placeholder="请选择默认乘车地点" style="width: 100%">
<el-option v-for="item in boardingPlaceOptions" :key="item" :label="item" :value="item"></el-option>
</el-select>
</el-form-item>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="报名开始时间" prop="signupStartTime">
@@ -295,6 +300,7 @@ layout("/layouts/platform.html"){
currentMatter: {},
batchForm: {},
lineOptions: [],
boardingPlaceOptions: [],
settingOptions: [],
unionOptions: [],
organizationTypeOptions: [],
@@ -486,6 +492,7 @@ layout("/layouts/platform.html"){
signupEndTime: "",
travelStartTime: "",
travelEndTime: "",
defaultBoardingPlace: "",
contactName: "",
contactPhone: "",
minGroupPeople: null,
@@ -496,6 +503,7 @@ layout("/layouts/platform.html"){
openBatchForm(row) {
this.currentMatter = row
this.loadLines(row.year)
this.loadBoardingPlaceOptions(row.settingId)
this.batchTitle = "选择线路"
this.batchForm = this.emptyBatchForm()
this.$axios.post(loc() + "/lineConfig", { matterId: row.id }).then((res) => {
@@ -503,6 +511,7 @@ layout("/layouts/platform.html"){
this.batchForm = Object.assign(this.emptyBatchForm(), res.data || {})
this.batchForm.travelStartTime = this.dateOnly(this.batchForm.travelStartTime)
this.batchForm.travelEndTime = this.dateOnly(this.batchForm.travelEndTime)
this.ensureBatchBoardingPlace()
this.applySelectedLineDefaults(false)
if (!this.batchForm.minGroupPeople || !this.batchForm.maxGroupPeople) {
this.loadSettingPeople((data) => {
@@ -528,6 +537,44 @@ layout("/layouts/platform.html"){
}
})
},
loadBoardingPlaceOptions(settingId) {
this.boardingPlaceOptions = []
if (!settingId) {
return
}
this.$axios.post(loc() + "/settingBoardingPlaces", { settingId: settingId }).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.boardingPlaceOptions = this.parseBoardingPlaceOptions(data.boardingPlace || "")
this.ensureBatchBoardingPlace()
}
})
},
parseBoardingPlaceOptions(value) {
if (!value) {
return []
}
try {
const list = JSON.parse(value)
if (!Array.isArray(list)) {
return []
}
return list.map((item) => {
if (typeof item === "string") {
return item
}
return item && item.name ? item.name : ""
}).filter((item) => item)
} catch (e) {
return String(value).split(",").map((item) => item.trim()).filter((item) => item)
}
},
ensureBatchBoardingPlace() {
if (!this.batchForm || !this.boardingPlaceOptions.length || this.batchForm.defaultBoardingPlace) {
return
}
this.$set(this.batchForm, "defaultBoardingPlace", this.boardingPlaceOptions[0])
},
lineOptionLabel(item) {
if (!item) return ""
const lineName = this.lineField(item, "lineName") || ""
@@ -29,7 +29,7 @@ layout("/layouts/platform.html"){
<el-option v-for="item in lineOptions" :key="item.lineName" :label="item.lineName" :value="item.lineName"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
@@ -254,11 +254,20 @@ layout("/layouts/platform.html"){
<el-input v-model="signupForm.hotelName" :disabled="isDirectFamilyLine(signupForm)" maxlength="100" placeholder="请输入报名酒店"></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="乘车地点">
<el-select v-model="signupForm.boardingPlace" clearable filterable placeholder="请选择乘车地点" style="width: 100%">
<el-option v-for="item in boardingPlaceOptions" :key="item" :label="item" :value="item"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="报名旅行社">
<el-input v-model="signupForm.travelAgencyName" :disabled="isDirectFamilyLine(signupForm)" readonly></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="出行时间">
<el-input v-model="signupForm.travelPeriod" :disabled="isDirectFamilyLine(signupForm)" readonly></el-input>
@@ -748,6 +757,7 @@ layout("/layouts/platform.html"){
detailDoneTasks: [],
fillBedInfo: true,
signupForm: {},
boardingPlaceOptions: [],
familyData: [],
bedTypeOptions: [],
familyRelationshipOptions: [],
@@ -1091,6 +1101,7 @@ layout("/layouts/platform.html"){
const matter = data.matter || {}
const ledger = data.ledger || {}
const directRelative = data.directRelative || {}
this.boardingPlaceOptions = this.parseBoardingPlaceOptions(matter.boardingPlaceOptions || "")
this.allowFamily = matter.allowFamily === true || matter.allowFamily === 1 || matter.allowFamily === "1"
this.fillBedInfo = matter.fillBedInfo === undefined || matter.fillBedInfo === null || matter.fillBedInfo === true || matter.fillBedInfo === 1 || matter.fillBedInfo === "1"
this.signupForm = Object.assign({
@@ -1113,6 +1124,7 @@ layout("/layouts/platform.html"){
hotelName: "",
travelAgencyId: matter.travelAgencyId || "",
travelAgencyName: matter.travelAgencyName || "",
boardingPlace: matter.defaultBoardingPlace || "",
travelPeriod: matter.travelPeriod || "",
travelStartTime: matter.travelStartTime || "",
travelEndTime: matter.travelEndTime || "",
@@ -1131,6 +1143,7 @@ layout("/layouts/platform.html"){
directFamilyUnitLine: matter.directFamilyUnitLine,
travelAgencyId: matter.travelAgencyId || ledger.travelAgencyId || "",
travelAgencyName: matter.travelAgencyName || ledger.travelAgencyName || "",
boardingPlace: ledger.boardingPlace || matter.defaultBoardingPlace || "",
travelPeriod: matter.travelPeriod || "",
travelStartTime: matter.travelStartTime || "",
travelEndTime: matter.travelEndTime || "",
@@ -1148,12 +1161,32 @@ layout("/layouts/platform.html"){
if (this.familyData.length > 0) {
this.signupForm.hasFamily = true
}
this.ensureBoardingPlace()
this.signupVisible = true
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
parseBoardingPlaceOptions(value) {
if (!value) return []
try {
const list = JSON.parse(value)
if (!Array.isArray(list)) return []
return list.map((item) => {
if (typeof item === "string") return item
return item && item.name ? item.name : ""
}).filter((item) => item)
} catch (e) {
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])
}
},
isDirectFamilyLine(row) {
if (!row) return false
return row.directFamilyUnitLine === true
@@ -1274,6 +1307,10 @@ layout("/layouts/platform.html"){
return true
},
submitSignup() {
if (this.boardingPlaceOptions.length > 0 && !this.signupForm.boardingPlace) {
this.$message.warning("请选择乘车地点")
return
}
if (!this.validateFamilies()) return
if (!this.validateDirectRelative()) return
const families = this.allowFamily && this.signupForm.hasFamily ? this.familyData : []
@@ -1327,6 +1364,7 @@ layout("/layouts/platform.html"){
},
destroySignup() {
this.signupForm = {}
this.boardingPlaceOptions = []
this.familyData = []
this.directRelativeForm = {}
this.allowFamily = false
@@ -29,7 +29,7 @@ layout("/layouts/platform.html"){
<el-option v-for="item in lotOptions" :key="item.id" :label="item.lotName" :value="item.id"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
@@ -53,7 +53,7 @@ layout("/layouts/platform.html"){
<el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
@@ -142,9 +142,6 @@ layout("/layouts/platform.html"){
#app .search .search-item {
width: calc((100% - 150px) / 4);
}
#app .search .search-query {
margin-left: auto;
}
@media screen and (max-width: 1350px) {
#app .search .search-item {
width: calc((100% - 100px) / 3);
@@ -0,0 +1,703 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="年度">
<el-date-picker
v-model="pageForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%"
@change="pageYearChange">
</el-date-picker>
</search-item>
<search-item label="疗休养配置">
<el-select v-model="pageForm.settingId" clearable filterable placeholder="请选择配置" style="width: 100%" @change="pageSettingChange">
<el-option v-for="item in settingOptions" :key="item.id" :label="item.configName" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="分配线路">
<el-select v-model="pageForm.matterId" clearable filterable placeholder="请选择分配线路" style="width: 100%">
<el-option v-for="item in pageMatterOptions" :key="item.matterId" :label="matterOptionLabel(item)" :value="item.matterId"></el-option>
</el-select>
</search-item>
<search-item label="所属分工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择分工会" style="width: 100%">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="人员类型">
<el-select v-model="pageForm.personType" clearable placeholder="请选择人员类型" style="width: 100%">
<el-option label="正式人员" value="FORMAL"></el-option>
<el-option label="替补人员" value="BACKUP"></el-option>
</el-select>
</search-item>
<search-item label="姓名/工号">
<el-input
v-model="pageForm.keyword"
clearable
placeholder="请输入姓名或工号"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="校工会人员分配列表">
<el-button type="primary" size="medium" @click="openAssign">
<i class="el-icon-user"></i>
人员分配
</el-button>
</table-tool>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
class="vi-table"
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="70" align="center" header-align="center"></el-table-column>
<el-table-column label="姓名" prop="userName" width="110" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="工号" prop="loginName" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属分工会" prop="unionName" min-width="160" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="unitName" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路" prop="lineName" min-width="200" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="出行时间" prop="travelPeriod" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="乘车地点" prop="boardingPlace" min-width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="人员类型" prop="personType" width="110" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="row.personType === 'BACKUP' ? 'warning' : 'success'">{{ personTypeText(row.personType) }}</el-tag>
</template>
</el-table-column>
<el-table-column label="是否退出" prop="cancelled" width="110" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="isCancelled(row) ? 'danger' : 'success'">{{ isCancelled(row) ? '已退出' : '未退出' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="分配时间" prop="assignedAt" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" width="320" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button v-if="!row.matterId && row.personType !== 'BACKUP'" size="mini" type="primary" @click="openSelectMatter(row)">选择线路</el-button>
<el-button v-if="isCancelled(row) && $auth.hasPermission('tour.schoolUserAssignment.cancelRestore')" size="mini" type="warning" @click="restoreCancel(row)">取消退出</el-button>
<el-button size="mini" type="danger" @click="doDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
<el-dialog
custom-class="tour-user-assignment-dialog"
title="人员分配"
:visible.sync="assignDialogVisible"
:close-on-click-modal="false"
width="78%"
@closed="resetAssignDialog">
<el-form :model="assignForm" :rules="assignRules" ref="assignFormRef" label-width="110px">
<el-row :gutter="16">
<el-col :span="6">
<el-form-item label="年度" prop="year">
<el-date-picker
v-model="assignForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%"
@change="assignYearChange">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="疗休养配置" prop="settingId">
<el-select v-model="assignForm.settingId" clearable filterable placeholder="请选择配置" style="width: 100%" @change="assignSettingChange">
<el-option v-for="item in assignSettingOptions" :key="item.id" :label="item.configName" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="6">
<el-form-item label="分配线路" prop="matterId">
<el-select
v-model="assignForm.matterId"
clearable
filterable
:disabled="selectedCandidates.length <= 0"
placeholder="请先勾选人员"
style="width: 100%"
@change="assignMatterChange">
<el-option v-for="item in assignMatterOptions" :key="item.matterId" :label="matterOptionLabel(item)" :value="item.matterId"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
</el-form>
<div class="candidate-toolbar">
<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"
clearable
placeholder="姓名/工号"
style="width: 220px"
@keyup.enter.native="candidateSearch">
</el-input>
<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>
</div>
<el-table
ref="candidateTable"
v-loading="candidateLoading"
:data="candidateData"
row-key="userId"
border
size="mini"
height="420"
@selection-change="candidateSelectionChange">
<el-table-column type="selection" width="48" :reserve-selection="true" align="center" header-align="center"></el-table-column>
<el-table-column label="姓名" prop="userName" width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="工号" prop="loginName" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="gender" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="手机号" prop="mobile" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属分工会" prop="unionName" min-width="160" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="unitName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="分配线路" min-width="260" align="center" header-align="center">
<template slot-scope="{row}">
<el-select
v-model="row.assignmentMatterId"
clearable
filterable
:disabled="!isCandidateSelected(row)"
placeholder="请先勾选人员"
style="width: 100%">
<el-option v-for="item in assignMatterOptions" :key="item.matterId" :label="matterOptionLabel(item)" :value="item.matterId"></el-option>
</el-select>
</template>
</el-table-column>
</el-table>
<el-row class="el-pagination-container candidate-pagination">
<el-pagination
background
:current-page="candidateForm.pageNumber"
:page-sizes="[10, 20, 50, 100]"
:page-size="candidateForm.pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="candidateForm.totalCount"
@size-change="candidateSizeChange"
@current-change="candidatePageChange">
</el-pagination>
</el-row>
<span slot="footer" class="dialog-footer">
<el-button @click="assignDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="assignSubmitting" @click="doAssign">保存分配</el-button>
</span>
</el-dialog>
<el-dialog
title="选择分配线路"
:visible.sync="selectMatterDialogVisible"
:close-on-click-modal="false"
width="520px"
@closed="resetSelectMatterDialog">
<el-form :model="selectMatterForm" :rules="selectMatterRules" ref="selectMatterFormRef" label-width="110px">
<el-form-item label="分配人员">
<el-input v-model="selectMatterForm.userName" disabled></el-input>
</el-form-item>
<el-form-item label="分配线路" prop="matterId">
<el-select v-model="selectMatterForm.matterId" clearable filterable placeholder="请选择分配线路" style="width: 100%">
<el-option v-for="item in selectMatterOptions" :key="item.matterId" :label="matterOptionLabel(item)" :value="item.matterId"></el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="selectMatterDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="selectMatterSubmitting" @click="doSelectMatter">保存</el-button>
</span>
</el-dialog>
</div>
<style>
#app .search .search-item {
width: calc((100% - 150px) / 4);
}
.tour-user-assignment-dialog .el-dialog__body {
max-height: 72vh;
overflow-y: auto;
}
.candidate-toolbar {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 12px;
flex-wrap: wrap;
}
.candidate-selected {
color: #606266;
white-space: nowrap;
}
.candidate-pagination {
margin-top: 12px;
margin-bottom: 0;
text-align: right;
}
@media screen and (max-width: 1350px) {
#app .search .search-item {
width: calc((100% - 100px) / 3);
}
}
@media screen and (max-width: 1200px) {
#app .search .search-item {
width: calc((100% - 50px) / 2);
}
}
@media screen and (max-width: 992px) {
#app .search .search-item {
width: 100%;
}
.candidate-selected {
margin-left: 0;
}
}
</style>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
const currentYear = moment().format("YYYY")
return {
settingOptions: [],
pageMatterOptions: [],
assignSettingOptions: [],
assignMatterOptions: [],
unionOptions: [],
assignDialogVisible: false,
candidateLoading: false,
candidateData: [],
selectedCandidates: [],
assignSubmitting: false,
selectMatterDialogVisible: false,
selectMatterSubmitting: false,
selectMatterOptions: [],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "assignedAt",
pageOrderBy: "descending",
year: currentYear,
settingId: "",
matterId: "",
unionId: "",
personType: "",
keyword: ""
},
assignForm: {
year: currentYear,
settingId: "",
matterId: ""
},
candidateForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
unionId: "",
keyword: ""
},
selectMatterForm: {
id: "",
settingId: "",
matterId: "",
userName: ""
},
assignRules: {
year: [{required: true, message: "请选择年度", trigger: ["change", "blur"]}],
settingId: [{required: true, message: "请选择疗休养配置", trigger: ["change", "blur"]}]
},
selectMatterRules: {
matterId: [{required: true, message: "请选择分配线路", trigger: ["change", "blur"]}]
}
}
},
methods: {
defaultAssignForm(currentYear) {
return {
year: currentYear || moment().format("YYYY"),
settingId: "",
matterId: ""
}
},
defaultCandidateForm() {
return {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
unionId: "",
keyword: ""
}
},
defaultSelectMatterForm() {
return {
id: "",
settingId: "",
matterId: "",
userName: ""
}
},
resetSearch() {
const currentYear = moment().format("YYYY")
this.pageForm.year = currentYear
this.pageForm.settingId = ""
this.pageForm.matterId = ""
this.pageForm.unionId = ""
this.pageForm.personType = ""
this.pageForm.keyword = ""
this.loadSettingOptions()
},
pageYearChange() {
this.pageForm.settingId = ""
this.pageForm.matterId = ""
this.pageMatterOptions = []
this.loadSettingOptions()
},
pageSettingChange() {
this.pageForm.matterId = ""
this.loadPageMatterOptions()
},
loadSettingOptions() {
this.$axios.post(loc() + "/settingOptions", {year: this.pageForm.year}).then((res) => {
if (res.code === 0) {
this.settingOptions = res.data || []
// 主页面查询条件默认选择当前年度第一条配置,并以该配置刷新列表。
if (!this.pageForm.settingId && this.settingOptions.length > 0) {
this.pageForm.settingId = this.settingOptions[0].id
this.loadPageMatterOptions()
this.doSearch()
} else {
this.pageMatterOptions = []
this.doSearch()
}
}
})
},
loadPageMatterOptions() {
if (!this.pageForm.settingId) {
this.pageMatterOptions = []
return
}
this.$axios.post(loc() + "/matterOptions", {settingId: this.pageForm.settingId}).then((res) => {
if (res.code === 0) {
this.pageMatterOptions = res.data || []
}
})
},
loadUnionOptions() {
this.$axios.post(loc() + "/unionOptions").then((res) => {
if (res.code === 0) {
this.unionOptions = res.data || []
}
})
},
openAssign() {
const year = this.pageForm.year || moment().format("YYYY")
this.assignForm = this.defaultAssignForm(year)
this.assignForm.matterId = ""
this.candidateForm = this.defaultCandidateForm()
this.candidateData = []
this.selectedCandidates = []
this.assignDialogVisible = true
this.loadAssignSettingOptions()
},
resetAssignDialog() {
this.assignForm = this.defaultAssignForm(moment().format("YYYY"))
this.candidateForm = this.defaultCandidateForm()
this.assignMatterOptions = []
this.candidateData = []
this.selectedCandidates = []
this.assignSubmitting = false
},
assignYearChange() {
this.assignForm.settingId = ""
this.assignForm.matterId = ""
this.assignMatterOptions = []
this.clearCandidateSelection()
this.loadAssignSettingOptions()
this.loadCandidatePageData()
},
assignSettingChange() {
this.assignForm.matterId = ""
this.assignMatterOptions = []
this.clearCandidateSelection()
this.loadAssignMatterOptions()
this.loadCandidatePageData()
},
loadAssignSettingOptions() {
this.$axios.post(loc() + "/settingOptions", {year: this.assignForm.year}).then((res) => {
if (res.code === 0) {
this.assignSettingOptions = res.data || []
// 人员分配弹窗独立于主列表筛选,默认取当前年度第一条可用配置。
if (!this.assignForm.settingId && this.assignSettingOptions.length > 0) {
this.assignForm.settingId = this.assignSettingOptions[0].id
this.loadAssignMatterOptions()
this.loadCandidatePageData()
} else if (this.assignForm.settingId) {
this.loadAssignMatterOptions()
this.loadCandidatePageData()
} else {
this.assignMatterOptions = []
this.candidateData = []
this.candidateForm.totalCount = 0
}
}
})
},
loadAssignMatterOptions() {
if (!this.assignForm.settingId) {
this.assignMatterOptions = []
return
}
this.$axios.post(loc() + "/matterOptions", {settingId: this.assignForm.settingId}).then((res) => {
if (res.code === 0) {
this.assignMatterOptions = res.data || []
}
})
},
loadCandidatePageData() {
if (!this.assignForm.settingId) {
this.candidateData = []
this.candidateForm.totalCount = 0
return
}
this.candidateLoading = true
this.$axios.post(loc() + "/candidatePageData", {
pageNumber: this.candidateForm.pageNumber,
pageSize: this.candidateForm.pageSize,
settingId: this.assignForm.settingId,
unionId: this.candidateForm.unionId,
keyword: this.candidateForm.keyword
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.candidateData = (data.list || []).map(item => Object.assign({}, item, {
assignmentMatterId: ""
}))
this.candidateForm.totalCount = data.totalCount || 0
} else {
this.$message.warning(res.msg || "候选人员查询失败")
}
}).finally(() => {
this.candidateLoading = false
})
},
assignMatterChange(value) {
;(this.selectedCandidates || []).forEach(row => {
this.$set(row, "assignmentMatterId", value || "")
})
},
candidateSearch() {
this.candidateForm.pageNumber = 1
this.loadCandidatePageData()
},
resetCandidateSearch() {
this.candidateForm.unionId = ""
this.candidateForm.keyword = ""
this.candidateForm.pageNumber = 1
this.loadCandidatePageData()
},
candidateSizeChange(size) {
this.candidateForm.pageSize = size
this.candidateForm.pageNumber = 1
this.loadCandidatePageData()
},
candidatePageChange(pageNumber) {
this.candidateForm.pageNumber = pageNumber
this.loadCandidatePageData()
},
candidateSelectionChange(rows) {
const selectedIds = (rows || []).map(item => item.userId)
;(this.candidateData || []).forEach(row => {
if (!selectedIds.includes(row.userId)) {
this.$set(row, "assignmentMatterId", "")
} else if (!row.assignmentMatterId && this.assignForm.matterId) {
this.$set(row, "assignmentMatterId", this.assignForm.matterId)
}
})
this.selectedCandidates = rows || []
if (this.selectedCandidates.length <= 0) {
this.assignForm.matterId = ""
}
},
isCandidateSelected(row) {
return !!row && this.selectedCandidates.some(item => item.userId === row.userId)
},
clearCandidateSelection() {
this.selectedCandidates = []
if (this.$refs.candidateTable) {
this.$refs.candidateTable.clearSelection()
}
},
doAssign() {
this.$refs.assignFormRef.validate((valid) => {
if (!valid) return
if (this.selectedCandidates.length <= 0) {
this.$message.warning("请选择需要分配的人员")
return
}
this.$confirm("确定保存当前人员分配吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
const assignItems = this.selectedCandidates.map(item => {
return {
userId: item.userId,
matterId: item.assignmentMatterId || this.assignForm.matterId || ""
}
})
this.assignSubmitting = true
this.$axios.post(loc() + "/doAssignItems", {
settingId: this.assignForm.settingId,
assignItems: JSON.stringify(assignItems)
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.$message.success("分配成功" + (data.ledgerCount > 0 ? ",已代报名" + data.ledgerCount + "人" : "") + (data.skipCount > 0 ? ",已跳过" + data.skipCount + "人" : ""))
this.assignDialogVisible = false
this.doSearch()
} else {
this.$message.warning(res.msg || "分配失败")
}
}).finally(() => {
this.assignSubmitting = false
})
}).catch(() => {})
})
},
openSelectMatter(row) {
this.selectMatterForm = {
id: row.id,
settingId: row.settingId,
matterId: "",
userName: row.userName || ""
}
this.selectMatterOptions = []
this.selectMatterDialogVisible = true
this.loadSelectMatterOptions()
},
resetSelectMatterDialog() {
this.selectMatterForm = this.defaultSelectMatterForm()
this.selectMatterOptions = []
this.selectMatterSubmitting = false
},
loadSelectMatterOptions() {
if (!this.selectMatterForm.settingId) {
this.selectMatterOptions = []
return
}
this.$axios.post(loc() + "/matterOptions", {settingId: this.selectMatterForm.settingId}).then((res) => {
if (res.code === 0) {
this.selectMatterOptions = res.data || []
}
})
},
doSelectMatter() {
this.$refs.selectMatterFormRef.validate((valid) => {
if (!valid) return
this.selectMatterSubmitting = true
this.$axios.post(loc() + "/selectMatter", {
id: this.selectMatterForm.id,
matterId: this.selectMatterForm.matterId
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.$message.success("分配线路选择成功" + (data.ledgerCount > 0 ? ",已代报名" + data.ledgerCount + "人" : ""))
this.selectMatterDialogVisible = false
this.doSearch()
} else {
this.$message.warning(res.msg || "分配线路选择失败")
}
}).finally(() => {
this.selectMatterSubmitting = false
})
})
},
doDelete(row) {
this.$axios.post(loc() + "/deleteInfo", {id: row.id}).then((infoRes) => {
if (infoRes.code !== 0) {
this.$message.warning(infoRes.msg || "删除校验失败")
return
}
const info = infoRes.data || {}
const hasLedger = !!info.hasLedger
const message = hasLedger ? "该人员已报名,删除分配记录将同时删除台账数据,是否继续?" : "确定删除该人员分配记录吗?"
this.$confirm(message, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doDelete", {
id: row.id,
deleteLedger: hasLedger
}).then((res) => {
if (res.code === 0) {
this.$message.success(res.msg || "删除成功")
this.doSearch()
} else {
this.$message.warning(res.msg || "删除失败")
}
})
}).catch(() => {})
})
},
personTypeText(personType) {
return personType === "BACKUP" ? "替补人员" : "正式人员"
},
isCancelled(row) {
return row && (row.cancelled === true || row.cancelled === 1)
},
restoreCancel(row) {
this.$confirm("确定将【" + row.userName + "】恢复为未退出状态吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/restoreCancel", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success("取消退出成功")
this.doSearch()
} else {
this.$message.warning(res.msg || "取消退出失败")
}
})
}).catch(() => {})
},
matterOptionLabel(item) {
const lineName = item.lineName || ""
return lineName || item.matterName || ""
}
},
mounted() {
this.loadSettingOptions()
this.loadUnionOptions()
}
})
</script>
<!--#
}
#-->
@@ -79,6 +79,7 @@ layout("/layouts/platform.html"){
</guava>
<el-dialog
custom-class="tour-setting-dialog"
:title="title"
:visible.sync="dialogVisible"
:close-on-click-modal="false"
@@ -145,20 +146,23 @@ layout("/layouts/platform.html"){
<el-input-number v-model="formData.maxGroupPeople" :controls="false" :min="0" :precision="0" placeholder="请输入最多成团人数" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="出行人数指标" prop="travelPeopleQuota">
<el-input-number v-model="formData.travelPeopleQuota" :controls="false" :min="0" :precision="0" placeholder="请输入出行人数指标" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="24">
<div class="tour-setting-basic-divider"></div>
</el-col>
<el-col :span="12">
<el-form-item label="省外几年去一次" prop="outProvinceYears">
<el-input-number v-model="formData.outProvinceYears" :controls="false" :min="0" :precision="0" placeholder="请输入省外间隔年限" style="width: 100%"></el-input-number>
<el-form-item label="控制省外比例" prop="signupEligibilityMode">
<el-select v-model="formData.signupEligibilityMode" placeholder="请选择控制省外比例" style="width: 100%" @change="signupEligibilityModeChange">
<el-option label="是" value="SCOPE_GROUP"></el-option>
<el-option label="否" value="ASSIGNED_USER"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="省外人数占比" prop="outProvinceRatio">
<el-input-number v-model="formData.outProvinceRatio" :controls="false" :min="0" :max="100" :precision="2" placeholder="请输入省外人数占比" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="12">
<el-col v-if="showOutProvinceRatioFields()" :span="12">
<el-form-item label="省外占比类型" prop="outProvinceRatioType">
<el-select v-model="formData.outProvinceRatioType" placeholder="请选择省外占比类型" style="width: 100%" @change="outProvinceRatioTypeChange">
<el-option label="当年报名人数" value="当年报名人数"></el-option>
@@ -167,7 +171,17 @@ layout("/layouts/platform.html"){
</el-select>
</el-form-item>
</el-col>
<el-col v-if="formData.outProvinceRatioType === '固定人数'" :span="12">
<el-col v-if="showOutProvinceRatioFields()" :span="12">
<el-form-item label="省外几年去一次" prop="outProvinceYears">
<el-input-number v-model="formData.outProvinceYears" :controls="false" :min="0" :precision="0" placeholder="请输入省外间隔年限" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col v-if="showOutProvinceRatioFields()" :span="12">
<el-form-item label="省外人数占比" prop="outProvinceRatio">
<el-input-number v-model="formData.outProvinceRatio" :controls="false" :min="0" :max="100" :precision="2" placeholder="请输入省外人数占比" style="width: 100%"></el-input-number>
</el-form-item>
</el-col>
<el-col v-if="showOutProvinceRatioFields() && formData.outProvinceRatioType === '固定人数'" :span="12">
<el-form-item label="固定人数" prop="outProvinceFixedPeople">
<el-input-number v-model="formData.outProvinceFixedPeople" :controls="false" :min="0" :precision="0" placeholder="请输入固定人数" style="width: 100%"></el-input-number>
</el-form-item>
@@ -227,45 +241,129 @@ layout("/layouts/platform.html"){
<el-switch v-model="formData.enabled" active-text="启用" inactive-text="停用"></el-switch>
</el-form-item>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="标段管理" name="lots">
<div style="text-align: right; margin-bottom: 10px;">
<el-button type="primary" size="medium" icon="el-icon-plus" @click="addLot">增加一条</el-button>
<el-col :span="24">
<div class="tour-setting-basic-divider"></div>
<div class="tour-boarding-panel">
<div class="tour-boarding-toolbar">
<span class="tour-boarding-title">乘车地点</span>
<el-button type="primary" size="mini" icon="el-icon-plus" @click="addBoardingPlace">新增</el-button>
</div>
<el-table
:data="formData.lots"
:data="boardingPlaceRows"
border
class="vi-table"
empty-text="暂无标段"
empty-text="暂无乘车地点"
style="width: 100%">
<el-table-column label="序号" type="index" width="70" align="center" header-align="center"></el-table-column>
<el-table-column label="标段名称" min-width="180" align="center" header-align="center">
<el-table-column label="乘车地点" min-width="260" align="center" header-align="center">
<template slot-scope="{row}">
<el-input v-model="row.lotName" maxlength="50" placeholder="请输入标段名称"></el-input>
<el-input v-model="row.name" maxlength="100" placeholder="请输入乘车地点"></el-input>
</template>
</el-table-column>
<el-table-column label="标段值" min-width="160" align="center" header-align="center">
<template slot-scope="{row}">
<el-input v-model="row.lotValue" maxlength="10" placeholder="请输入整数" @input="integerInput(row, 'lotValue', $event)"></el-input>
</template>
</el-table-column>
<el-table-column label="标段费用" min-width="150" align="center" header-align="center">
<template slot-scope="{row}">
<el-input v-model="row.activityCost" maxlength="10" placeholder="请输入整数" @input="integerInput(row, 'activityCost', $event)"></el-input>
</template>
</el-table-column>
<el-table-column label="允许超出报销" width="140" align="center" header-align="center">
<template slot-scope="{row}">
<el-checkbox v-model="row.allowOverReimbursement"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center" header-align="center">
<el-table-column label="操作" width="100" align="center" header-align="center">
<template slot-scope="scope">
<el-button type="danger" size="mini" icon="el-icon-delete" @click="deleteLot(scope.$index, scope.row)"></el-button>
<el-button type="danger" size="mini" icon="el-icon-delete" @click="deleteBoardingPlace(scope.$index)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-col>
</el-row>
</el-tab-pane>
<el-tab-pane label="标段管理" name="lots">
<div class="tour-tab-fill">
<div class="tour-tab-toolbar">
<el-button type="primary" size="medium" icon="el-icon-plus" @click="addLot">增加一条</el-button>
</div>
<el-table
:data="formData.lots"
border
class="vi-table tour-tab-table"
empty-text="暂无标段"
height="100%"
style="width: 100%">
<el-table-column label="序号" type="index" width="70" align="center" header-align="center"></el-table-column>
<el-table-column label="标段名称" min-width="180" align="center" header-align="center">
<template slot-scope="{row}">
<el-input v-model="row.lotName" maxlength="50" placeholder="请输入标段名称"></el-input>
</template>
</el-table-column>
<el-table-column label="标段值" min-width="160" align="center" header-align="center">
<template slot-scope="{row}">
<el-input v-model="row.lotValue" maxlength="10" placeholder="请输入整数" @input="integerInput(row, 'lotValue', $event)"></el-input>
</template>
</el-table-column>
<el-table-column label="标段费用" min-width="150" align="center" header-align="center">
<template slot-scope="{row}">
<el-input v-model="row.activityCost" maxlength="10" placeholder="请输入整数" @input="integerInput(row, 'activityCost', $event)"></el-input>
</template>
</el-table-column>
<el-table-column label="允许超出报销" width="140" align="center" header-align="center">
<template slot-scope="{row}">
<el-checkbox v-model="row.allowOverReimbursement"></el-checkbox>
</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center" header-align="center">
<template slot-scope="scope">
<el-button type="danger" size="mini" icon="el-icon-delete" @click="deleteLot(scope.$index, scope.row)"></el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
<el-tab-pane label="工会名额分配" name="unionQuota">
<div class="tour-tab-fill">
<div class="tour-quota-toolbar">
<div class="tour-quota-stat">
<span>出行人数指标</span>
<strong>{{ travelPeopleQuota }}</strong>
</div>
<div class="tour-quota-stat">
<span>校工会正式分配数量</span>
<strong>{{ quotaOverview.schoolFormalAssignedCount }}</strong>
</div>
<div class="tour-quota-stat">
<span>分工会总名额</span>
<strong>{{ branchTotalQuota }}</strong>
</div>
<div class="tour-quota-stat">
<span>分工会总会员数</span>
<strong>{{ branchTotalMemberCount }}</strong>
</div>
<el-input-number
v-model="quotaAllocate.ratio"
:controls="false"
:min="0"
:precision="2"
placeholder="比例"
class="tour-quota-input">
</el-input-number>
<el-button type="primary" size="medium" @click="allocateQuotaByRatio">按比例分配</el-button>
</div>
<el-table
:data="formData.unionQuotas"
border
class="vi-table tour-quota-table tour-tab-table"
empty-text="暂无分工会"
height="100%"
show-summary
:summary-method="unionQuotaSummary"
style="width: 100%">
<el-table-column label="序号" type="index" width="70" align="center" header-align="center"></el-table-column>
<el-table-column label="分工会" prop="unionName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="当前会员数" prop="memberCount" width="120" align="center" header-align="center"></el-table-column>
<el-table-column label="正式人员数量" width="170" align="center" header-align="center">
<template slot-scope="{row}">
<el-input-number v-model="row.formalQuota" :controls="false" :min="0" :precision="0" style="width: 100%"></el-input-number>
</template>
</el-table-column>
<el-table-column label="替补人员数量" width="170" align="center" header-align="center">
<template slot-scope="{row}">
<el-input-number v-model="row.backupQuota" :controls="false" :min="0" :precision="0" style="width: 100%"></el-input-number>
</template>
</el-table-column>
</el-table>
</div>
</el-tab-pane>
<el-tab-pane label="服务须知" name="notice">
<el-form-item prop="serviceNotice" label-width="0">
@@ -329,8 +427,15 @@ layout("/layouts/platform.html"){
configName: ""
},
activityGroupList: [],
boardingPlaceRows: [],
formData: {},
lotDeleteList: [],
quotaOverview: {
schoolFormalAssignedCount: 0
},
quotaAllocate: {
ratio: null
},
inheritLoading: false,
formRules: {
year: [{required: true, message: "必填", trigger: ["blur", "change"]}],
@@ -347,6 +452,24 @@ layout("/layouts/platform.html"){
components: {
"drawer-user-scope": httpVueLoader("/components/module/activity/DrawerUserScope.vue")
},
computed: {
travelPeopleQuota() {
return this.toNonNegativeInteger(this.formData.travelPeopleQuota)
},
branchTotalMemberCount() {
const rows = this.formData.unionQuotas || []
return rows.reduce((sum, row) => sum + this.toNonNegativeInteger(row.memberCount), 0)
},
branchTotalQuota() {
const quota = this.travelPeopleQuota - this.toNonNegativeInteger(this.quotaOverview.schoolFormalAssignedCount)
return quota > 0 ? quota : 0
}
},
watch: {
"formData.travelPeopleQuota": function() {
this.refreshQuotaRatio()
}
},
methods: {
getActivityGroup() {
return this.$axios.post("/platform/activity/basic/scope/getActivityUserScopeGroup").then((res) => {
@@ -358,6 +481,106 @@ layout("/layouts/platform.html"){
})
})
},
loadUnionQuotaRows(settingId) {
this.$axios.post(loc() + "/unionQuotaRows", {settingId: settingId || ""}).then((res) => {
if (res.code === 0) {
this.$set(this.formData, "unionQuotas", this.normalizeUnionQuotaRows(res.data || []))
this.refreshQuotaRatio()
} else {
this.$message.warning(res.msg || "查询分工会名额失败")
}
})
},
loadUnionQuotaOverview(settingId) {
this.$axios.post(loc() + "/unionQuotaOverview", {settingId: settingId || ""}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.quotaOverview.schoolFormalAssignedCount = this.toNonNegativeInteger(data.schoolFormalAssignedCount)
this.refreshQuotaRatio()
} else {
this.$message.warning(res.msg || "查询工会名额统计失败")
}
})
},
normalizeUnionQuotaRows(rows) {
return (rows || []).map(item => {
return Object.assign({}, item, {
formalQuota: this.toNonNegativeInteger(item.formalQuota),
backupQuota: this.toNonNegativeInteger(item.backupQuota),
memberCount: this.toNonNegativeInteger(item.memberCount)
})
})
},
toNonNegativeInteger(value) {
const numberValue = parseInt(value, 10)
if (isNaN(numberValue) || numberValue < 0) {
return 0
}
return numberValue
},
toNumber(value) {
const numberValue = parseFloat(value)
return isNaN(numberValue) ? null : numberValue
},
truncateDecimal(value, precision) {
const times = Math.pow(10, precision)
return Math.floor(value * times) / times
},
refreshQuotaRatio() {
if (this.branchTotalMemberCount <= 0 || this.branchTotalQuota <= 0) {
this.quotaAllocate.ratio = null
return
}
this.quotaAllocate.ratio = this.truncateDecimal(this.branchTotalQuota / this.branchTotalMemberCount, 2)
},
unionQuotaSummary(param) {
const columns = param.columns || []
const rows = this.formData.unionQuotas || []
return columns.map((column, index) => {
if (index === 0) {
return "合计"
}
if (column.property === "memberCount") {
return rows.reduce((sum, row) => sum + this.toNonNegativeInteger(row.memberCount), 0)
}
if (column.label === "正式人员数量") {
return rows.reduce((sum, row) => sum + this.toNonNegativeInteger(row.formalQuota), 0)
}
if (column.label === "替补人员数量") {
return rows.reduce((sum, row) => sum + this.toNonNegativeInteger(row.backupQuota), 0)
}
return ""
})
},
allocateQuotaByRatio() {
const rows = this.formData.unionQuotas || []
const ratio = this.toNumber(this.quotaAllocate.ratio)
if (!rows.length) {
this.$message.warning("暂无分工会数据")
return
}
if (ratio === null || this.quotaAllocate.ratio === "" || this.quotaAllocate.ratio === undefined) {
this.$message.warning("比例为空,未分配")
return
}
if (ratio < 0) {
this.$message.warning("比例不能为负数")
return
}
// 按比例分配:比例 * 分工会会员数,小数直接舍去,不补余数。
const nextFormalQuotas = rows.map(row => {
const memberCount = this.toNonNegativeInteger(row.memberCount)
return Math.floor(ratio * memberCount)
})
const formalTotal = nextFormalQuotas.reduce((sum, value) => sum + value, 0)
if (formalTotal > this.branchTotalQuota) {
this.$message.warning("当前正式人员总数 " + formalTotal + ",分工会总名额 " + this.branchTotalQuota + ",分配后总人数不能超过分工会总名额")
return
}
nextFormalQuotas.forEach((formalQuota, index) => {
this.$set(rows[index], "formalQuota", formalQuota)
})
},
openUserScope() {
if (this.$refs.drawerUserScope) {
this.$refs.drawerUserScope.groupId = this.formData.activityGroupId || ""
@@ -380,6 +603,8 @@ layout("/layouts/platform.html"){
year: moment().format("YYYY"),
configName: "",
tourType: "",
boardingPlace: "[]",
travelPeopleQuota: 0,
activityGroupId: "",
sortNo: 0,
minGroupPeople: 0,
@@ -388,6 +613,7 @@ layout("/layouts/platform.html"){
outProvinceRatio: 0,
outProvinceRatioType: "当年报名人数",
outProvinceFixedPeople: 0,
signupEligibilityMode: "ASSIGNED_USER",
cycleStartYear: "",
cycleEndYear: "",
cycleTotalCost: null,
@@ -396,6 +622,7 @@ layout("/layouts/platform.html"){
fillBedInfo: true,
enabled: true,
lots: [],
unionQuotas: [],
serviceNotice: ""
}
},
@@ -405,7 +632,11 @@ layout("/layouts/platform.html"){
this.activeTab = "basic"
// 新建时给出默认年度和开关值,减少校工会管理员录入成本。
this.formData = this.defaultFormData()
this.boardingPlaceRows = []
this.quotaOverview.schoolFormalAssignedCount = 0
this.dialogVisible = true
this.loadUnionQuotaRows("")
this.loadUnionQuotaOverview("")
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
},
inheritPreviousYearInfo() {
@@ -430,12 +661,20 @@ layout("/layouts/platform.html"){
allowOverReimbursement: !!item.allowOverReimbursement
}
})
const inheritedUnionQuotas = this.normalizeUnionQuotaRows(previous.unionQuotas || []).map(item => {
return Object.assign({}, item, {
id: "",
settingId: ""
})
})
this.formData = Object.assign(this.defaultFormData(), previous, {
year: String(currentYear),
cycleStartYear: previous.cycleStartYear ? String(previous.cycleStartYear) : "",
cycleEndYear: previous.cycleEndYear ? String(previous.cycleEndYear) : "",
lots: inheritedLots
lots: inheritedLots,
unionQuotas: inheritedUnionQuotas
})
this.boardingPlaceRows = this.parseBoardingPlaceRows(this.formData.boardingPlace)
delete this.formData.id
delete this.formData.createdAt
delete this.formData.createdBy
@@ -445,6 +684,9 @@ layout("/layouts/platform.html"){
if (!this.formData.outProvinceRatioType) {
this.$set(this.formData, "outProvinceRatioType", "当年报名人数")
}
if (!this.formData.signupEligibilityMode) {
this.$set(this.formData, "signupEligibilityMode", "ASSIGNED_USER")
}
if (this.formData.outProvinceFixedPeople === null || this.formData.outProvinceFixedPeople === undefined) {
this.$set(this.formData, "outProvinceFixedPeople", 0)
}
@@ -465,9 +707,12 @@ layout("/layouts/platform.html"){
this.$axios.post(loc() + "/detail", {id: row.id}).then((res) => {
if (res.code === 0) {
this.formData = Object.assign({
boardingPlace: "[]",
travelPeopleQuota: 0,
activityGroupId: "",
outProvinceRatioType: "当年报名人数",
outProvinceFixedPeople: 0,
signupEligibilityMode: "ASSIGNED_USER",
cycleStartYear: "",
cycleEndYear: "",
cycleTotalCost: null,
@@ -476,12 +721,17 @@ layout("/layouts/platform.html"){
fillBedInfo: true,
enabled: true,
lots: [],
unionQuotas: [],
serviceNotice: ""
}, res.data || {})
this.boardingPlaceRows = this.parseBoardingPlaceRows(this.formData.boardingPlace)
this.formData.year = this.formData.year ? String(this.formData.year) : ""
if (!this.formData.outProvinceRatioType) {
this.$set(this.formData, "outProvinceRatioType", "当年报名人数")
}
if (!this.formData.signupEligibilityMode) {
this.$set(this.formData, "signupEligibilityMode", "ASSIGNED_USER")
}
if (this.formData.outProvinceFixedPeople === null || this.formData.outProvinceFixedPeople === undefined) {
this.$set(this.formData, "outProvinceFixedPeople", 0)
}
@@ -493,6 +743,8 @@ layout("/layouts/platform.html"){
this.formData.lots = (this.formData.lots || []).map(item => Object.assign({}, item, {
allowOverReimbursement: !!item.allowOverReimbursement
}))
this.formData.unionQuotas = this.normalizeUnionQuotaRows(this.formData.unionQuotas || [])
this.loadUnionQuotaOverview(row.id)
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
} else {
@@ -504,13 +756,17 @@ layout("/layouts/platform.html"){
this.$refs.form.validate((valid) => {
if (!valid) return
if (!this.validateLots()) return
if (!this.validateUnionQuotas()) return
this.submitLoading = true
// Nutz 对子表集合按字符串化 JSON 绑定更稳定,和体检项目维护的提交方式保持一致。
const submitData = JSON.parse(JSON.stringify(this.formData))
// 乘车地点用配置表 JSON 字段保存,避免新增表影响原疗休养配置结构。
submitData.boardingPlace = this.stringifyBoardingPlaceRows()
if (submitData.outProvinceRatioType !== "固定人数") {
submitData.outProvinceFixedPeople = 0
}
submitData.lots = JSON.stringify(this.formData.lots || [])
submitData.unionQuotas = JSON.stringify(this.formData.unionQuotas || [])
submitData.lotDeleteList = JSON.stringify(this.lotDeleteList)
this.$axios.post(loc() + "/doSubmit", submitData).then((res) => {
this.submitLoading = false
@@ -537,6 +793,39 @@ layout("/layouts/platform.html"){
allowOverReimbursement: false
})
},
addBoardingPlace() {
this.boardingPlaceRows.push({ name: "" })
},
deleteBoardingPlace(index) {
this.boardingPlaceRows.splice(index, 1)
},
parseBoardingPlaceRows(value) {
if (!value) {
return []
}
try {
const list = JSON.parse(value)
if (!Array.isArray(list)) {
return []
}
return list.map((item) => {
if (typeof item === "string") {
return { name: item }
}
return { name: item && item.name ? item.name : "" }
}).filter((item) => item.name)
} catch (e) {
return String(value).split(",").map((item) => {
return { name: item.trim() }
}).filter((item) => item.name)
}
},
stringifyBoardingPlaceRows() {
const rows = (this.boardingPlaceRows || []).map((item) => {
return { name: item && item.name ? String(item.name).trim() : "" }
}).filter((item) => item.name)
return JSON.stringify(rows)
},
integerInput(row, field, value) {
const nextValue = String(value || "").replace(/[^\d]/g, "")
this.$set(row, field, nextValue)
@@ -547,6 +836,20 @@ layout("/layouts/platform.html"){
}
this.$nextTick(() => this.$refs.form && this.$refs.form.validateField("outProvinceFixedPeople"))
},
showOutProvinceRatioFields() {
return this.formData.signupEligibilityMode === "SCOPE_GROUP"
},
signupEligibilityModeChange(value) {
// 省内外比例控制关闭时,隐藏字段不再参与当前表单校验。
if (value !== "SCOPE_GROUP") {
this.$set(this.formData, "outProvinceFixedPeople", 0)
this.$nextTick(() => {
if (this.$refs.form) {
this.$refs.form.clearValidate(["outProvinceYears", "outProvinceRatio", "outProvinceRatioType", "outProvinceFixedPeople"])
}
})
}
},
validateLots() {
const lots = this.formData.lots || []
for (let i = 0; i < lots.length; i++) {
@@ -564,6 +867,38 @@ layout("/layouts/platform.html"){
}
return true
},
validateUnionQuotas() {
const rows = this.formData.unionQuotas || []
for (let i = 0; i < rows.length; i++) {
const row = rows[i]
if (row.formalQuota === null || row.formalQuota === undefined || row.formalQuota === "") {
row.formalQuota = 0
}
if (row.backupQuota === null || row.backupQuota === undefined || row.backupQuota === "") {
row.backupQuota = 0
}
const formalQuota = this.toNonNegativeInteger(row.formalQuota)
const backupQuota = this.toNonNegativeInteger(row.backupQuota)
if (!/^\d+$/.test(String(row.formalQuota))) {
this.$message.warning("第" + (i + 1) + "行正式人员数量必须为非负整数")
return false
}
if (!/^\d+$/.test(String(row.backupQuota))) {
this.$message.warning("第" + (i + 1) + "行替补人员数量必须为非负整数")
return false
}
this.$set(row, "formalQuota", formalQuota)
this.$set(row, "backupQuota", backupQuota)
}
const formalTotal = rows.reduce((sum, row) => {
return sum + this.toNonNegativeInteger(row.formalQuota)
}, 0)
if (formalTotal > this.branchTotalQuota) {
this.$message.warning("当前正式人员总数 " + formalTotal + ",分工会总名额 " + this.branchTotalQuota + ",分配后总人数不能超过分工会总名额")
return false
}
return true
},
deleteLot(index, row) {
this.$confirm("确定删除该标段吗?", "提示", {
confirmButtonText: "确定",
@@ -594,7 +929,14 @@ layout("/layouts/platform.html"){
},
destroyEditor() {
this.formData = {}
this.boardingPlaceRows = []
this.lotDeleteList = []
this.quotaAllocate = {
ratio: null
}
this.quotaOverview = {
schoolFormalAssignedCount: 0
}
this.inheritLoading = false
this.activeTab = "basic"
}
@@ -618,6 +960,7 @@ layout("/layouts/platform.html"){
}
.tour-setting-inherit {
flex-shrink: 0;
margin-bottom: -40px;
padding-right: 96px;
position: relative;
@@ -629,6 +972,129 @@ layout("/layouts/platform.html"){
border-top: 1px dashed #dcdfe6;
margin: 2px 0 18px;
}
.tour-boarding-toolbar {
align-items: center;
display: flex;
justify-content: space-between;
margin-bottom: 10px;
}
.tour-boarding-panel {
max-width: 560px;
}
.tour-boarding-title {
color: #303133;
font-weight: 600;
}
.tour-quota-toolbar {
display: flex;
align-items: center;
flex-shrink: 0;
gap: 10px;
margin-bottom: 10px;
flex-wrap: wrap;
}
.tour-quota-input {
width: 160px;
}
.tour-quota-stat {
align-items: center;
border: 1px solid #dcdfe6;
border-radius: 4px;
display: inline-flex;
gap: 8px;
height: 36px;
padding: 0 10px;
}
.tour-quota-stat span {
color: #606266;
}
.tour-quota-stat strong {
color: #303133;
font-weight: 600;
}
.tour-quota-table {
min-height: 0;
}
.tour-tab-fill {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.tour-tab-toolbar {
flex-shrink: 0;
margin-bottom: 10px;
text-align: right;
}
.tour-tab-table {
flex: 1;
min-height: 0;
}
.tour-setting-dialog {
height: 85vh;
max-height: 85vh;
display: flex;
flex-direction: column;
}
.tour-setting-dialog .el-dialog__header {
flex-shrink: 0;
}
.tour-setting-dialog .el-dialog__body {
flex: 1;
min-height: 0;
overflow: hidden;
padding-bottom: 0;
}
.tour-setting-dialog .el-dialog__footer {
flex-shrink: 0;
}
.tour-setting-dialog .el-form {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
}
.tour-setting-dialog .el-tabs {
display: flex;
flex-direction: column;
flex: 1;
height: 100%;
min-height: 0;
}
.tour-setting-dialog .el-tabs__header {
flex-shrink: 0;
}
.tour-setting-dialog .el-tabs__content {
flex: 1;
min-height: 0;
overflow-y: auto;
overflow-x: hidden;
padding: 0 6px 18px 0;
}
.tour-setting-dialog .el-tab-pane {
height: 100%;
}
</style>
<!--#
@@ -34,7 +34,7 @@ layout("/layouts/platform.html"){
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
@@ -153,11 +153,20 @@ layout("/layouts/platform.html"){
<el-input v-model="signupForm.hotelName" :disabled="isDirectFamilyLine(signupForm)" maxlength="100" placeholder="请输入报名酒店"></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="乘车地点">
<el-select v-model="signupForm.boardingPlace" clearable filterable placeholder="请选择乘车地点" style="width: 100%">
<el-option v-for="item in boardingPlaceOptions" :key="item" :label="item" :value="item"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="报名旅行社">
<el-input v-model="signupForm.travelAgencyName" :disabled="isDirectFamilyLine(signupForm)" readonly></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="出行时间">
<el-input v-model="signupForm.travelPeriod" :disabled="isDirectFamilyLine(signupForm)" readonly></el-input>
@@ -477,6 +486,7 @@ layout("/layouts/platform.html"){
fillBedInfo: true,
signupForm: {},
familyData: [],
boardingPlaceOptions: [],
bedTypeOptions: [],
familyRelationshipOptions: [],
directRelativeOptions: [],
@@ -721,6 +731,7 @@ layout("/layouts/platform.html"){
const staff = data.staff || {}
const ledger = data.ledger || {}
const directRelative = data.directRelative || {}
this.boardingPlaceOptions = this.parseBoardingPlaceOptions(matter.boardingPlaceOptions || "")
this.allowFamily = matter.allowFamily === true || matter.allowFamily === 1 || matter.allowFamily === "1"
this.fillBedInfo = matter.fillBedInfo === undefined || matter.fillBedInfo === null || matter.fillBedInfo === true || matter.fillBedInfo === 1 || matter.fillBedInfo === "1"
this.signupForm = Object.assign({
@@ -743,6 +754,7 @@ layout("/layouts/platform.html"){
hotelName: "",
travelAgencyId: matter.travelAgencyId || "",
travelAgencyName: matter.travelAgencyName || "",
boardingPlace: matter.defaultBoardingPlace || "",
travelPeriod: matter.travelPeriod || "",
travelStartTime: matter.travelStartTime || "",
travelEndTime: matter.travelEndTime || "",
@@ -761,6 +773,7 @@ layout("/layouts/platform.html"){
directFamilyUnitLine: matter.directFamilyUnitLine,
travelAgencyId: matter.travelAgencyId || ledger.travelAgencyId || "",
travelAgencyName: matter.travelAgencyName || ledger.travelAgencyName || "",
boardingPlace: ledger.boardingPlace || matter.defaultBoardingPlace || "",
travelPeriod: matter.travelPeriod || "",
travelStartTime: matter.travelStartTime || "",
travelEndTime: matter.travelEndTime || "",
@@ -778,12 +791,32 @@ layout("/layouts/platform.html"){
if (this.familyData.length > 0) {
this.signupForm.hasFamily = true
}
this.ensureBoardingPlace()
this.signupVisible = true
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
parseBoardingPlaceOptions(value) {
if (!value) return []
try {
const list = JSON.parse(value)
if (!Array.isArray(list)) return []
return list.map((item) => {
if (typeof item === "string") return item
return item && item.name ? item.name : ""
}).filter((item) => item)
} catch (e) {
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])
}
},
isOutProvinceLine(lineType) {
return lineType === "省外线路" || lineType === "省外"
},
@@ -890,6 +923,10 @@ layout("/layouts/platform.html"){
return true
},
submitSignup() {
if (this.boardingPlaceOptions.length > 0 && !this.signupForm.boardingPlace) {
this.$message.warning("请选择乘车地点")
return
}
if (!this.validateFamilies()) return
if (!this.validateDirectRelative()) return
const families = this.allowFamily && this.signupForm.hasFamily ? this.familyData : []
@@ -942,6 +979,7 @@ layout("/layouts/platform.html"){
destroySignup() {
this.signupForm = {}
this.familyData = []
this.boardingPlaceOptions = []
this.directRelativeForm = {}
this.allowFamily = false
this.fillBedInfo = true
@@ -42,7 +42,7 @@ layout("/layouts/platform.html"){
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<div class="search-query" style="margin-left: auto;">
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
@@ -221,8 +221,8 @@ layout("/layouts/platform.html"){
agencyName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
agencyCode: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
contactPhone: [{ validator: validateMobile, trigger: ["blur", "change"] }],
email: [{ validator: validateEmail, trigger: ["blur", "change"] }]
contactPhone: [{ validator: validateMobile, required: true, trigger: ["blur", "change"] }],
email: [{ validator: validateEmail, required: true, trigger: ["blur", "change"] }]
}
}
},
@@ -53,7 +53,7 @@ layout("/layouts/platform.html"){
<el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
@@ -142,9 +142,6 @@ layout("/layouts/platform.html"){
#app .search .search-item {
width: calc((100% - 150px) / 4);
}
#app .search .search-query {
margin-left: auto;
}
@media screen and (max-width: 1350px) {
#app .search .search-item {
width: calc((100% - 100px) / 3);
@@ -53,7 +53,7 @@ layout("/layouts/platform.html"){
<el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option>
</el-select>
</search-item>
<div class="search-query" style="margin-left: auto;">
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
@@ -479,6 +479,7 @@ layout("/layouts/platform_h5.html"){
<van-field label="线路类型" readonly v-model="signupForm.lineType"></van-field>
<van-field label="出行时间" readonly v-model="signupForm.travelPeriod"></van-field>
<van-field label="报名酒店" v-model="signupForm.hotelName" maxlength="100" placeholder="请输入报名酒店"></van-field>
<van-field readonly clickable is-link label="乘车地点" v-model="signupForm.boardingPlace" placeholder="请选择乘车地点" @click="openBoardingPlacePicker"></van-field>
<van-field v-if="fillBedInfo" readonly clickable is-link label="床型" v-model="signupForm.bedType" placeholder="请选择床型" @click="openBedPicker('self')"></van-field>
<van-field v-if="fillBedInfo" label="床位信息" v-model="signupForm.bedInfo" maxlength="100" placeholder="请输入床位信息"></van-field>
<van-field v-if="fillBedInfo" label="意向拼床人" v-model="signupForm.intendedRoommate" maxlength="100" placeholder="请输入意向拼床人"></van-field>
@@ -546,6 +547,9 @@ layout("/layouts/platform_h5.html"){
<van-popup v-model="directRelativePickerVisible" position="bottom">
<van-picker show-toolbar :columns="directRelativeColumns" @confirm="confirmDirectRelative" @cancel="directRelativePickerVisible=false"></van-picker>
</van-popup>
<van-popup v-model="boardingPlacePickerVisible" position="bottom">
<van-picker show-toolbar :columns="boardingPlaceColumns" @confirm="confirmBoardingPlace" @cancel="boardingPlacePickerVisible=false"></van-picker>
</van-popup>
</div>
<script nonce="${cspNonce!}">
@@ -583,6 +587,7 @@ layout("/layouts/platform_h5.html"){
allowFamily: false,
fillBedInfo: true,
signupForm: {},
boardingPlaceOptions: [],
familyData: [],
directRelativeForm: {},
bedTypeOptions: [],
@@ -593,7 +598,8 @@ layout("/layouts/platform_h5.html"){
bedPickerFamilyIndex: -1,
relationshipPickerVisible: false,
relationshipFamilyIndex: -1,
directRelativePickerVisible: false
directRelativePickerVisible: false,
boardingPlacePickerVisible: false
}
},
computed: {
@@ -613,6 +619,9 @@ layout("/layouts/platform_h5.html"){
},
directRelativeColumns() {
return this.directRelativeOptions.map(item => ({ text: item.name || item.code, item }))
},
boardingPlaceColumns() {
return this.boardingPlaceOptions
}
},
methods: {
@@ -774,6 +783,7 @@ layout("/layouts/platform_h5.html"){
const matter = data.matter || {}
const ledger = data.ledger || {}
const directRelative = data.directRelative || {}
this.boardingPlaceOptions = this.parseBoardingPlaceOptions(matter.boardingPlaceOptions || "")
this.allowFamily = matter.allowFamily === true || matter.allowFamily === 1 || matter.allowFamily === "1"
this.fillBedInfo = matter.fillBedInfo === undefined || matter.fillBedInfo === null || matter.fillBedInfo === true || matter.fillBedInfo === 1 || matter.fillBedInfo === "1"
this.signupForm = Object.assign({
@@ -796,6 +806,7 @@ layout("/layouts/platform_h5.html"){
hotelName: "",
travelAgencyId: matter.travelAgencyId || "",
travelAgencyName: matter.travelAgencyName || "",
boardingPlace: matter.defaultBoardingPlace || "",
travelPeriod: matter.travelPeriod || "",
travelStartTime: matter.travelStartTime || "",
travelEndTime: matter.travelEndTime || "",
@@ -814,6 +825,7 @@ layout("/layouts/platform_h5.html"){
directFamilyUnitLine: matter.directFamilyUnitLine,
travelAgencyId: matter.travelAgencyId || ledger.travelAgencyId || "",
travelAgencyName: matter.travelAgencyName || ledger.travelAgencyName || "",
boardingPlace: ledger.boardingPlace || matter.defaultBoardingPlace || "",
travelPeriod: matter.travelPeriod || "",
travelStartTime: matter.travelStartTime || "",
travelEndTime: matter.travelEndTime || "",
@@ -831,9 +843,29 @@ layout("/layouts/platform_h5.html"){
if (this.familyData.length > 0) {
this.signupForm.hasFamily = true
}
this.ensureBoardingPlace()
this.signupVisible = true
})
},
parseBoardingPlaceOptions(value) {
if (!value) return []
try {
const list = JSON.parse(value)
if (!Array.isArray(list)) return []
return list.map((item) => {
if (typeof item === "string") return item
return item && item.name ? item.name : ""
}).filter((item) => item)
} catch (e) {
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])
}
},
isDirectFamilyLine(row) {
return row && (row.directFamilyUnitLine === true || row.directFamilyUnitLine === 1 || row.directFamilyUnitLine === "1" || row.directFamilyUnitLine === "true" || row.directFamilyUnitLine === "TRUE" || !!row.directRelativeId)
},
@@ -918,6 +950,17 @@ layout("/layouts/platform_h5.html"){
this.directRelativeForm.relationshipName = item.name || value.text || ""
this.directRelativePickerVisible = false
},
openBoardingPlacePicker() {
if (this.boardingPlaceColumns.length === 0) {
vant.Toast("暂无乘车地点")
return
}
this.boardingPlacePickerVisible = true
},
confirmBoardingPlace(value) {
this.signupForm.boardingPlace = value
this.boardingPlacePickerVisible = false
},
normalizeFamilyIdCard(row) {
if (!row || !row.idCard) return
row.idCard = String(row.idCard).trim().toUpperCase()
@@ -957,6 +1000,10 @@ layout("/layouts/platform_h5.html"){
return true
},
submitSignup() {
if (this.boardingPlaceOptions.length > 0 && !this.signupForm.boardingPlace) {
vant.Toast("请选择乘车地点")
return
}
if (!this.validateFamilies() || !this.validateDirectRelative()) return
const families = this.allowFamily && this.signupForm.hasFamily ? this.familyData : []
const form = Object.assign({}, this.signupForm, {
@@ -1034,6 +1081,7 @@ layout("/layouts/platform_h5.html"){
},
destroySignup() {
this.signupForm = {}
this.boardingPlaceOptions = []
this.familyData = []
this.directRelativeForm = {}
this.allowFamily = false
@@ -1,5 +1,5 @@
<!--#
layout("/layouts/platform_tour_signup_h5.html"){
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
@@ -120,6 +120,15 @@ layout("/layouts/platform_tour_signup_h5.html"){
<van-field label="线路类型" readonly v-model="signupForm.lineType"></van-field>
<van-field v-if="signupForm.travelPeriod" label="出行时间" readonly v-model="signupForm.travelPeriod"></van-field>
<van-field label="报名酒店" v-model="signupForm.hotelName" maxlength="100" placeholder="请输入报名酒店"></van-field>
<van-field
label="乘车地点"
readonly
clickable
is-link
placeholder="请选择乘车地点"
v-model="signupForm.boardingPlace"
@click="openBoardingPlacePicker">
</van-field>
<van-field
v-if="fillBedInfo"
label="床型"
@@ -233,6 +242,10 @@ layout("/layouts/platform_tour_signup_h5.html"){
<van-popup v-model="directRelativePickerVisible" position="bottom">
<van-picker show-toolbar :columns="directRelativeColumns" @confirm="confirmDirectRelative" @cancel="directRelativePickerVisible=false"></van-picker>
</van-popup>
<van-popup v-model="boardingPlacePickerVisible" position="bottom">
<van-picker show-toolbar :columns="boardingPlaceColumns" @confirm="confirmBoardingPlace" @cancel="boardingPlacePickerVisible=false"></van-picker>
</van-popup>
</div>
<script nonce="${cspNonce!}">
@@ -252,9 +265,11 @@ layout("/layouts/platform_tour_signup_h5.html"){
bedTypeOptions: [],
familyRelationshipOptions: [],
directRelativeOptions: [],
boardingPlaceOptions: [],
bedPickerVisible: false,
relationshipPickerVisible: false,
directRelativePickerVisible: false,
boardingPlacePickerVisible: false,
bedPickerTarget: "self",
matterId: GetQueryString("matterId") || ""
}
@@ -271,6 +286,9 @@ layout("/layouts/platform_tour_signup_h5.html"){
},
directRelativeColumns() {
return this.directRelativeOptions.map(item => ({ text: item.name || item.code, item }))
},
boardingPlaceColumns() {
return this.boardingPlaceOptions
}
},
methods: {
@@ -326,6 +344,7 @@ layout("/layouts/platform_tour_signup_h5.html"){
const staff = data.staff || {}
const ledger = data.ledger || {}
const directRelative = data.directRelative || {}
this.boardingPlaceOptions = this.parseBoardingPlaceOptions(matter.boardingPlaceOptions || "")
this.allowFamily = matter.allowFamily === true || matter.allowFamily === 1 || matter.allowFamily === "1"
this.fillBedInfo = matter.fillBedInfo === undefined || matter.fillBedInfo === null || matter.fillBedInfo === true || matter.fillBedInfo === 1 || matter.fillBedInfo === "1"
this.signupForm = Object.assign({
@@ -349,6 +368,7 @@ layout("/layouts/platform_tour_signup_h5.html"){
hotelName: "",
travelAgencyId: matter.travelAgencyId || "",
travelAgencyName: matter.travelAgencyName || "",
boardingPlace: matter.defaultBoardingPlace || "",
travelPeriod: matter.travelPeriod || "",
travelStartTime: matter.travelStartTime || "",
travelEndTime: matter.travelEndTime || "",
@@ -367,6 +387,7 @@ layout("/layouts/platform_tour_signup_h5.html"){
directFamilyUnitLine: matter.directFamilyUnitLine,
travelAgencyId: matter.travelAgencyId || ledger.travelAgencyId || "",
travelAgencyName: matter.travelAgencyName || ledger.travelAgencyName || "",
boardingPlace: ledger.boardingPlace || matter.defaultBoardingPlace || "",
travelPeriod: matter.travelPeriod || "",
travelStartTime: matter.travelStartTime || "",
travelEndTime: matter.travelEndTime || "",
@@ -388,8 +409,28 @@ layout("/layouts/platform_tour_signup_h5.html"){
this.familyData = []
this.signupForm.hasFamily = false
}
this.ensureBoardingPlace()
})
},
parseBoardingPlaceOptions(value) {
if (!value) return []
try {
const list = JSON.parse(value)
if (!Array.isArray(list)) return []
return list.map((item) => {
if (typeof item === "string") return item
return item && item.name ? item.name : ""
}).filter((item) => item)
} catch (e) {
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: "",
@@ -475,6 +516,17 @@ layout("/layouts/platform_tour_signup_h5.html"){
this.directRelativeForm.relationshipName = item.name || value.text || ""
this.directRelativePickerVisible = false
},
openBoardingPlacePicker() {
if (this.boardingPlaceColumns.length === 0) {
vant.Toast("暂无乘车地点")
return
}
this.boardingPlacePickerVisible = true
},
confirmBoardingPlace(value) {
this.signupForm.boardingPlace = value
this.boardingPlacePickerVisible = false
},
normalizeFamilyIdCard(row) {
if (!row || !row.idCard) return
row.idCard = String(row.idCard).trim().toUpperCase()
@@ -528,6 +580,10 @@ layout("/layouts/platform_tour_signup_h5.html"){
},
submitSignup() {
if (this.pageLoading || this.submitLoading) return
if (this.boardingPlaceOptions.length > 0 && !this.signupForm.boardingPlace) {
vant.Toast("请选择乘车地点")
return
}
if (!this.validateFamilies()) return
if (!this.validateDirectRelative()) return
const families = this.allowFamily && this.signupForm.hasFamily ? this.familyData : []
@@ -1,5 +1,5 @@
<!--#
layout("/layouts/platform_tour_signup_h5.html"){
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
@@ -1,5 +1,5 @@
<!--#
layout("/layouts/platform_tour_signup_h5.html"){
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
@@ -1,5 +1,5 @@
<!--#
layout("/layouts/platform_tour_signup_h5.html"){
layout("/layouts/platform_h5.html"){
#-->
<style scoped>