Merge remote-tracking branch 'origin/main'

This commit is contained in:
dd3s_206
2026-06-08 15:08:45 +08:00
114 changed files with 27166 additions and 11 deletions
@@ -0,0 +1,8 @@
ALTER TABLE activity_user_scope
ADD COLUMN groupType INT(2) NULL COMMENT '分组类型 1.结果分组 2.SQL条件分组' AFTER userId;
ALTER TABLE activity_user_scope
ADD COLUMN groupSql LONGTEXT NULL COMMENT 'SQL条件分组保存的查询SQL' AFTER groupType;
ALTER TABLE activity_user_scope
ADD INDEX INDEX_ACTIVITY_USER_SCOPE_GROUPID_USERID (groupId, userId);
@@ -0,0 +1,122 @@
-- 普惠疗休养平台电脑端菜单。
-- 使用 permission 做幂等判断,避免同一环境重复执行时插入重复菜单。
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT
'b7c63f0c73a44a9a8c4f3d3bb1a10001',
'',
LPAD(IFNULL(MAX(CAST(path AS UNSIGNED)), 0) + 1, 4, '0'),
'普惠疗休养',
'Tour',
'menu',
'',
'',
'ti-map-alt',
1,
0,
'tour',
NULL,
991,
1,
'',
UNIX_TIMESTAMP(NOW()) * 1000,
'',
UNIX_TIMESTAMP(NOW()) * 1000,
0,
'PC',
NULL,
NULL,
'p',
0,
0
FROM sys_menu
WHERE (parentId = '' OR parentId IS NULL)
AND CHAR_LENGTH(path) = 4
HAVING NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10002', p.id, CONCAT(p.path, '0001'), '疗休养设置', 'Tour Setting', 'menu', '/platform/tour/setting', 'data-pjax', '', 1, 0, 'tour.setting', NULL, 1, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.setting') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10003', p.id, CONCAT(p.path, '0002'), '旅行社管理', 'Travel Agency', 'menu', '/platform/tour/travelAgency', 'data-pjax', '', 1, 0, 'tour.travelAgency', NULL, 2, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.travelAgency') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10004', p.id, CONCAT(p.path, '0003'), '线路管理', 'Route Manage', 'menu', '/platform/tour/route', 'data-pjax', '', 1, 0, 'tour.route', NULL, 3, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'x', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.route') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10005', p.id, CONCAT(p.path, '0004'), '疗休养事项', 'Tour Matter', 'menu', '/platform/tour/matter', 'data-pjax', '', 1, 0, 'tour.matter', NULL, 4, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.matter') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10006', p.id, CONCAT(p.path, '0005'), '疗休养报名', 'Tour Signup', 'menu', '/platform/tour/signup', 'data-pjax', '', 1, 0, 'tour.signup', NULL, 5, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.signup') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10008', p.id, CONCAT(p.path, '0006'), '我的报名', 'My Signup', 'menu', '/platform/tour/mysignup', 'data-pjax', '', 1, 0, 'tour.mysignup', NULL, 6, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'w', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.mysignup') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10009', p.id, CONCAT(p.path, '0007'), '分工会查询', 'Union Ledger', 'menu', '/platform/tour/unionledger', 'data-pjax', '', 1, 0, 'tour.unionledger', NULL, 7, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'f', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.unionledger') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10011', p.id, CONCAT(p.path, '0010'), '分工会审核', 'Union Approval', 'menu', '/platform/tour/unionApproval', 'data-pjax', '', 1, 0, 'tour.unionApproval', NULL, 8, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'f', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.unionApproval') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10012', p.id, CONCAT(p.path, '0011'), '校工会审核', 'School Union Approval', 'menu', '/platform/tour/schoolUnionApproval', 'data-pjax', '', 1, 0, 'tour.schoolUnionApproval', NULL, 9, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'x', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.schoolUnionApproval') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10007', p.id, CONCAT(p.path, '0008'), '疗休养台账', 'Tour Ledger', 'menu', '/platform/tour/ledger', 'data-pjax', '', 1, 0, 'tour.ledger', NULL, 10, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'l', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.ledger') t);
INSERT INTO sys_menu (id, parentId, path, name, aliasName, type, href, target, icon, showit, disabled, permission, note, location, hasChildren, createdBy, createdAt, updatedBy, updatedAt, delFlag, platform, moduleId, picIcon, initialPinyinName, isRecommendApp, isRecommendService)
SELECT 'b7c63f0c73a44a9a8c4f3d3bb1a10010', p.id, CONCAT(p.path, '0009'), '线路成团', 'Tour Group', 'menu', '/platform/tour/group', 'data-pjax', '', 1, 0, 'tour.group', NULL, 11, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'x', 0, 0
FROM sys_menu p
WHERE p.permission = 'tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'tour.group') t);
-- 给系统管理员默认授权,其他角色可在角色管理中按需分配。
INSERT INTO sys_role_menu (roleId, menuId)
SELECT r.id, m.id
FROM sys_role r
JOIN sys_menu m ON m.permission IN (
'tour',
'tour.setting',
'tour.travelAgency',
'tour.route',
'tour.matter',
'tour.signup',
'tour.group',
'tour.mysignup',
'tour.unionledger',
'tour.unionApproval',
'tour.schoolUnionApproval',
'tour.ledger'
)
LEFT JOIN sys_role_menu rm ON rm.roleId = r.id AND rm.menuId = m.id
WHERE r.code = 'SYSADMIN'
AND rm.roleId IS NULL;
@@ -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`;
+253
View File
@@ -0,0 +1,253 @@
-- 普惠疗休养系统字典初始化。
-- 执行后可在 /platform/sys/dict 页面看到:疗休养(Tour) -> 组织形式/疗休养类型/线路类型/床型。
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict001', '', '9500', '疗休养', '普惠疗休养平台字典', 'Tour', 0, 9500, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'Tour') t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict002', p.id, CONCAT(p.path, '0001'), '组织形式', '疗休养组织形式', 'organizationType', 0, 1, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'Tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'organizationType' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict003', p.id, CONCAT(p.path, '0001'), '校工会组织', '疗休养组织形式', 'schoolUnion', 0, 1, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'organizationType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'schoolUnion' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict004', p.id, CONCAT(p.path, '0002'), '分工会组织', '疗休养组织形式', 'branchUnion', 0, 2, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'organizationType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'branchUnion' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict005', p.id, CONCAT(p.path, '0003'), '个人组织', '疗休养组织形式', 'personal', 0, 3, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'organizationType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'personal' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict006', p.id, CONCAT(p.path, '0002'), '疗休养类型', '疗休养类型', 'tourType', 0, 2, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'Tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'tourType' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict007', p.id, CONCAT(p.path, '0001'), '普惠性疗休养', '疗休养类型', 'inclusiveTour', 0, 1, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'tourType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'inclusiveTour' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict008', p.id, CONCAT(p.path, '0002'), '优秀职工疗休养', '疗休养类型', 'excellentWorkerTour', 0, 2, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'tourType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'excellentWorkerTour' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict009', p.id, CONCAT(p.path, '0003'), '线路类型', '线路类型', 'lineType', 0, 3, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'Tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'lineType' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict010', p.id, CONCAT(p.path, '0001'), '省内线路', '线路类型', 'inProvinceLine', 0, 1, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'lineType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'inProvinceLine' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict011', p.id, CONCAT(p.path, '0002'), '省外线路', '线路类型', 'outProvinceLine', 0, 2, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'lineType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'outProvinceLine' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict020', p.id, CONCAT(p.path, '0004'), '床型', '床型', 'bedType', 0, 4, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'Tour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'bedType' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict021', p.id, CONCAT(p.path, '0001'), '双人床', '床型', 'doubleBed', 0, 1, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'bedType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'doubleBed' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict022', p.id, CONCAT(p.path, '0002'), '单人床', '床型', 'singleBed', 0, 2, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'bedType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'singleBed' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict023', p.id, CONCAT(p.path, '0003'), '大床', '床型', 'kingBed', 0, 3, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'bedType'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'kingBed' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict012', '', '9600', '亲属关系', '亲属关系', 'familyRelationship', 0, 9600, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'familyRelationship') t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict013', p.id, CONCAT(p.path, '0001'), '直系亲属', '亲属关系', 'directRelative', 0, 1, 1,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'familyRelationship'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'directRelative' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict014', p.id, CONCAT(p.path, '0001'), '父亲', '直系亲属', 'father', 0, 1, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'directRelative'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'father' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict015', p.id, CONCAT(p.path, '0002'), '母亲', '直系亲属', 'mother', 0, 2, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'directRelative'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'mother' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict016', p.id, CONCAT(p.path, '0003'), '丈夫', '直系亲属', 'husband', 0, 3, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'directRelative'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'husband' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict017', p.id, CONCAT(p.path, '0004'), '妻子', '直系亲属', 'wife', 0, 4, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'directRelative'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'wife' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict018', p.id, CONCAT(p.path, '0005'), '儿子', '直系亲属', 'son', 0, 5, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'directRelative'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'son' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'b7c63f0c73a44a9a8c4f3d3bdict019', p.id, CONCAT(p.path, '0006'), '女儿', '直系亲属', 'daughter', 0, 6, 0,
'', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0
FROM `sys_dict` p
WHERE p.`code` = 'directRelative'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM `sys_dict` WHERE `code` = 'daughter' AND `parentId` = p.id) t);
UPDATE `sys_dict` SET `hasChildren` = 1 WHERE `code` IN ('Tour', 'organizationType', 'tourType', 'lineType', 'bedType', 'familyRelationship', 'directRelative');
@@ -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='疗休养退出申请';
@@ -0,0 +1,7 @@
-- Add matter reference so signup records are unique per travel period/matter.
ALTER TABLE `tour_ledger`
ADD COLUMN `matterId` varchar(32) DEFAULT NULL COMMENT '报名事项ID' AFTER `signupTime`;
ALTER TABLE `tour_ledger`
ADD KEY `idx_tour_ledger_matter` (`matterId`),
ADD KEY `idx_tour_ledger_matter_job` (`matterId`, `jobNo`);
@@ -0,0 +1,2 @@
ALTER TABLE `tour_ledger`
ADD COLUMN `overCostReimbursed` tinyint(1) DEFAULT 0 COMMENT '报销超出费用' AFTER `reimbursed`;
@@ -0,0 +1,9 @@
-- 疗休养台账查询加速索引。
ALTER TABLE `tour_ledger`
ADD KEY `idx_tour_ledger_year_signup` (`year`, `delFlag`, `signupTime`),
ADD KEY `idx_tour_ledger_year_line` (`year`, `lineId`, `delFlag`),
ADD KEY `idx_tour_ledger_year_union_type` (`year`, `unionId`, `lineType`, `delFlag`),
ADD KEY `idx_tour_ledger_year_job` (`year`, `jobNo`, `delFlag`);
ALTER TABLE `wf_process_instance`
ADD KEY `idx_wf_process_instance_business_state` (`businessNo`, `state`);
@@ -0,0 +1,21 @@
-- 直系亲属线路报名信息表,独立于普通携带亲属信息表。
CREATE TABLE IF NOT EXISTS `tour_ledger_direct_relative` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`ledgerId` varchar(32) DEFAULT NULL COMMENT '台账ID',
`relativeName` varchar(100) DEFAULT NULL COMMENT '亲属姓名',
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
`relationshipCode` varchar(50) DEFAULT NULL COMMENT '亲属关系编码',
`relationshipName` varchar(50) DEFAULT NULL COMMENT '亲属关系',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`lineName` varchar(100) DEFAULT NULL COMMENT '线路名称',
`travelStartTime` varchar(20) DEFAULT NULL COMMENT '出行开始日期',
`travelEndTime` varchar(20) 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`),
KEY `idx_tour_ledger_direct_relative_ledger` (`ledgerId`),
KEY `idx_tour_ledger_direct_relative_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养直系亲属线路报名信息';
@@ -0,0 +1,10 @@
-- 线路管理增加创建人和所在单位业务字段。
ALTER TABLE `tour_line`
ADD COLUMN `creatorUserId` varchar(32) DEFAULT NULL COMMENT '创建人ID' AFTER `travelAgencyId`,
ADD COLUMN `creatorName` varchar(100) DEFAULT NULL COMMENT '创建人' AFTER `creatorUserId`,
ADD COLUMN `unitId` varchar(32) DEFAULT NULL COMMENT '所在单位ID' AFTER `creatorName`,
ADD COLUMN `unitName` varchar(100) DEFAULT NULL COMMENT '所在单位' AFTER `unitId`;
ALTER TABLE `tour_line`
ADD KEY `idx_tour_line_creator` (`creatorUserId`),
ADD KEY `idx_tour_line_unit` (`unitId`);
@@ -0,0 +1,3 @@
-- 线路管理增加是否直系亲属线路字段,默认否。
ALTER TABLE `tour_line`
ADD COLUMN `directFamilyUnitLine` tinyint(1) DEFAULT 0 COMMENT '是否直系亲属线路' AFTER `openFlag`;
@@ -0,0 +1,3 @@
-- 线路管理增加是否对外开放字段,默认是。
ALTER TABLE `tour_line`
ADD COLUMN `openFlag` tinyint(1) DEFAULT 1 COMMENT '是否对外开放' AFTER `mobileThumb`;
@@ -0,0 +1,7 @@
-- 疗休养事项增加创建人字段。
ALTER TABLE `tour_matter`
ADD COLUMN `creatorUserId` varchar(32) DEFAULT NULL COMMENT '创建人ID' AFTER `settingId`,
ADD COLUMN `creatorName` varchar(100) DEFAULT NULL COMMENT '创建人' AFTER `creatorUserId`;
ALTER TABLE `tour_matter`
ADD KEY `idx_tour_matter_creator` (`creatorUserId`);
@@ -0,0 +1,26 @@
-- 疗休养事项批次表。
CREATE TABLE IF NOT EXISTS `tour_matter_batch` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`matterId` varchar(32) DEFAULT NULL COMMENT '事项ID',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`batchName` 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 '出行结束时间',
`changeDeadline` 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 '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_tour_matter_batch_matter` (`matterId`),
KEY `idx_tour_matter_batch_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项批次';
@@ -0,0 +1,37 @@
-- 疗休养事项表。
CREATE TABLE IF NOT EXISTS `tour_matter` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`year` int DEFAULT NULL COMMENT '年度',
`matterName` varchar(100) DEFAULT NULL COMMENT '事项名称',
`settingId` varchar(32) DEFAULT NULL COMMENT '疗休养配置ID',
`creatorUserId` varchar(32) DEFAULT NULL COMMENT '创建人ID',
`creatorName` varchar(100) DEFAULT NULL COMMENT '创建人',
`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 '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tour_matter_year_name` (`year`, `matterName`),
KEY `idx_tour_matter_year` (`year`),
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_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项';
@@ -0,0 +1,6 @@
-- Add activity user scope reference for tour settings.
ALTER TABLE `tour_setting`
ADD COLUMN `activityGroupId` varchar(32) DEFAULT NULL COMMENT '可参加人员范围ID' AFTER `tourType`;
ALTER TABLE `tour_setting`
ADD KEY `idx_tour_setting_activity_group` (`activityGroupId`);
@@ -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,3 @@
-- 疗休养配置增加周期允许次数字段。
ALTER TABLE `tour_setting`
ADD COLUMN `cycleAllowedTimes` int DEFAULT NULL COMMENT '周期允许次数' AFTER `cycleTotalCost`;
@@ -0,0 +1,2 @@
ALTER TABLE `tour_setting`
ADD COLUMN `cycleTotalCost` int DEFAULT NULL COMMENT '周期内总费用' AFTER `cycleEndYear`;
@@ -0,0 +1,4 @@
-- Add optional cycle year range for tour settings.
ALTER TABLE `tour_setting`
ADD COLUMN `cycleStartYear` int DEFAULT NULL COMMENT '周期开始年度' AFTER `outProvinceRatio`,
ADD COLUMN `cycleEndYear` int DEFAULT NULL COMMENT '周期结束年度' AFTER `cycleStartYear`;
@@ -0,0 +1,3 @@
-- 疗休养配置增加是否填报床位信息字段,默认开启以兼容历史配置。
ALTER TABLE `tour_setting`
ADD COLUMN `fillBedInfo` tinyint(1) DEFAULT 1 COMMENT '是否填报床位信息' AFTER `allowFamily`;
@@ -0,0 +1,3 @@
ALTER TABLE `tour_setting`
ADD COLUMN `outProvinceRatioType` varchar(50) DEFAULT '当年报名人数' COMMENT '省外人数占比类型' AFTER `outProvinceRatio`,
ADD COLUMN `outProvinceFixedPeople` int DEFAULT 0 COMMENT '省外固定人数' AFTER `outProvinceRatioType`;
@@ -0,0 +1,2 @@
ALTER TABLE `tour_setting`
ADD COLUMN `signupEligibilityMode` varchar(30) DEFAULT 'ASSIGNED_USER' COMMENT '报名资格校验方式' AFTER `activityGroupId`;
@@ -0,0 +1,257 @@
-- 普惠疗休养基础配置表。
-- 若生产环境未开启 Nutz 自动建表,请先执行本脚本再使用“疗休养设置”菜单。
CREATE TABLE IF NOT EXISTS `tour_setting` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`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 '最多成团人数',
`outProvinceYears` int DEFAULT NULL COMMENT '省外几年去一次',
`outProvinceRatio` decimal(10,2) DEFAULT NULL COMMENT '省外人数占比',
`outProvinceRatioType` varchar(50) DEFAULT '当年报名人数' COMMENT '省外人数占比类型',
`outProvinceFixedPeople` int DEFAULT 0 COMMENT '省外固定人数',
`cycleStartYear` int DEFAULT NULL COMMENT '周期开始年度',
`cycleEndYear` int DEFAULT NULL COMMENT '周期结束年度',
`cycleTotalCost` int DEFAULT NULL COMMENT '周期内总费用',
`cycleAllowedTimes` int DEFAULT NULL COMMENT '周期允许次数',
`allowFamily` tinyint(1) DEFAULT 0 COMMENT '是否允许携带家属',
`fillBedInfo` tinyint(1) DEFAULT 1 COMMENT '是否填报床位信息',
`enabled` tinyint(1) DEFAULT 1 COMMENT '是否启用',
`serviceNotice` 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_setting_year` (`year`),
KEY `idx_tour_setting_activity_group` (`activityGroupId`),
KEY `idx_tour_setting_sort` (`sortNo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养配置';
-- 疗休养配置标段表。
-- 标段先挂在配置上维护,后续线路、旅行社、报名等模块可继续通过 lotId 做业务关联。
CREATE TABLE IF NOT EXISTS `tour_setting_lot` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`settingId` varchar(32) DEFAULT NULL COMMENT '所属配置ID',
`lotName` varchar(50) DEFAULT NULL COMMENT '标段名称',
`lotValue` varchar(50) DEFAULT NULL COMMENT '标段值',
`activityCost` int DEFAULT NULL COMMENT '标段费用',
`allowOverReimbursement` tinyint(1) 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_lot_setting` (`settingId`),
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',
`year` int DEFAULT NULL COMMENT '年度',
`agencyCode` varchar(50) DEFAULT NULL COMMENT '旅行社编号',
`agencyName` varchar(100) DEFAULT NULL COMMENT '旅行社名称',
`contactName` varchar(30) DEFAULT NULL COMMENT '联系人',
`contactPhone` varchar(30) DEFAULT NULL COMMENT '联系人手机',
`email` varchar(100) DEFAULT NULL COMMENT '邮箱',
`remark` text COMMENT '备注',
`mobileThumb` varchar(500) DEFAULT NULL COMMENT '移动端缩略图',
`enabled` tinyint(1) DEFAULT 1 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_travel_agency_year_code` (`year`, `agencyCode`),
KEY `idx_tour_travel_agency_name` (`agencyName`),
KEY `idx_tour_travel_agency_contact` (`contactName`, `contactPhone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养旅行社';
-- 线路管理表。
CREATE TABLE IF NOT EXISTS `tour_line` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`year` int DEFAULT NULL COMMENT '创建年度',
`lineCode` varchar(50) DEFAULT NULL COMMENT '线路编号',
`lineName` varchar(100) DEFAULT NULL COMMENT '线路名称',
`travelAgencyId` varchar(32) DEFAULT NULL COMMENT '旅行社ID',
`creatorUserId` varchar(32) DEFAULT NULL COMMENT '创建人ID',
`creatorName` varchar(100) DEFAULT NULL COMMENT '创建人',
`unitId` varchar(32) DEFAULT NULL COMMENT '所在单位ID',
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
`lineType` varchar(50) DEFAULT NULL COMMENT '线路类型',
`lotId` varchar(32) DEFAULT NULL COMMENT '时间标段ID',
`lineContent` text COMMENT '线路内容',
`mobileThumb` varchar(500) DEFAULT NULL COMMENT '移动端缩略图',
`openFlag` tinyint(1) DEFAULT 1 COMMENT '是否对外开放',
`directFamilyUnitLine` tinyint(1) DEFAULT 0 COMMENT '是否直系亲属线路',
`enabled` tinyint(1) DEFAULT 1 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_line_year_code` (`year`, `lineCode`),
KEY `idx_tour_line_year` (`year`),
KEY `idx_tour_line_name` (`lineName`),
KEY `idx_tour_line_agency` (`travelAgencyId`),
KEY `idx_tour_line_creator` (`creatorUserId`),
KEY `idx_tour_line_unit` (`unitId`),
KEY `idx_tour_line_lot` (`lotId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养线路';
-- 疗休养事项表。
CREATE TABLE IF NOT EXISTS `tour_matter` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`year` int DEFAULT NULL COMMENT '年度',
`matterName` varchar(100) DEFAULT NULL COMMENT '事项名称',
`settingId` varchar(32) DEFAULT NULL COMMENT '疗休养配置ID',
`creatorUserId` varchar(32) DEFAULT NULL COMMENT '创建人ID',
`creatorName` varchar(100) DEFAULT NULL COMMENT '创建人',
`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 '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tour_matter_year_name` (`year`, `matterName`),
KEY `idx_tour_matter_year` (`year`),
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_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项';
-- 疗休养事项批次表。
CREATE TABLE IF NOT EXISTS `tour_matter_batch` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`matterId` varchar(32) DEFAULT NULL COMMENT '事项ID',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`batchName` 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 '出行结束时间',
`changeDeadline` 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 '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_tour_matter_batch_matter` (`matterId`),
KEY `idx_tour_matter_batch_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项批次';
-- 直系亲属线路报名信息表。
CREATE TABLE IF NOT EXISTS `tour_ledger_direct_relative` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`ledgerId` varchar(32) DEFAULT NULL COMMENT '台账ID',
`relativeName` varchar(100) DEFAULT NULL COMMENT '亲属姓名',
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
`relationshipCode` varchar(50) DEFAULT NULL COMMENT '亲属关系编码',
`relationshipName` varchar(50) DEFAULT NULL COMMENT '亲属关系',
`lineId` varchar(32) DEFAULT NULL COMMENT '线路ID',
`lineName` varchar(100) DEFAULT NULL COMMENT '线路名称',
`travelStartTime` varchar(20) DEFAULT NULL COMMENT '出行开始日期',
`travelEndTime` varchar(20) 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`),
KEY `idx_tour_ledger_direct_relative_ledger` (`ledgerId`),
KEY `idx_tour_ledger_direct_relative_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养直系亲属线路报名信息';
@@ -0,0 +1,2 @@
ALTER TABLE `tour_setting_lot`
ADD COLUMN `allowOverReimbursement` tinyint(1) DEFAULT 0 COMMENT '允许超出报销' AFTER `activityCost`;
@@ -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='疗休养人员分配';
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

@@ -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>
<!--#
}
#-->
@@ -0,0 +1,374 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="年度">
<el-date-picker
v-model="pageForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="线路名称">
<el-input
v-model="pageForm.lineName"
clearable
placeholder="请输入线路名称"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="线路类型">
<el-select v-model="pageForm.lineType" clearable filterable placeholder="请选择线路类型" style="width: 100%">
<el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option>
</el-select>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="线路列表"></table-tool>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
:default-sort="{prop: 'lineName', order: 'ascending'}"
:row-class-name="tableRowClassName"
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="线路名称" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="出行时段" prop="travelPeriod" width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="事项名称" prop="matterName" min-width="200" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路类型" prop="lineType" width="140" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="最少成团人数" prop="minGroupPeople" width="140" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
{{ row.minGroupPeople || 0 }}人
</template>
</el-table-column>
<el-table-column label="最多成团人数" prop="maxGroupPeople" width="140" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
{{ row.maxGroupPeople || 0 }}人
</template>
</el-table-column>
<el-table-column label="报名人数" prop="signupCount" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="Number(row.signupCount || 0) > 0 ? 'success' : 'info'">{{ row.signupCount || 0 }}人</el-tag>
</template>
</el-table-column>
<el-table-column label="成团状态" prop="groupStatusName" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="groupStatusType(row.groupStatus)">{{ row.groupStatusName || '未成团' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
<el-button size="mini" type="primary" @click="exportLine(row)">导出</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<el-drawer
:title="viewTitle"
:visible.sync="viewVisible"
direction="rtl"
size="72%"
custom-class="tour-group-view-drawer"
:close-on-click-modal="false">
<div class="tour-group-view">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="姓名/工号">
<el-input
v-model="viewForm.keyword"
clearable
placeholder="请输入姓名或工号"
style="width: 100%"
@keyup.enter.native="viewSearch">
</el-input>
</search-item>
<search-item label="所属工会">
<el-select v-model="viewForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="viewSearch">搜索</el-button>
<el-button size="medium" @click="viewReset">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<div class="tour-group-view-title">报名人员列表</div>
<el-table
v-loading="viewLoading"
:data="viewTableData"
:size="tableSize"
border
@sort-change="viewPageOrder">
<el-table-column 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>
<el-table-column label="手机号码" prop="mobile" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="身份证号" prop="idCard" min-width="190" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
</el-table>
<el-row class="el-pagination-container tour-group-view-pagination">
<el-pagination
background
:current-page="viewForm.pageNumber"
:page-size="viewForm.pageSize"
:page-sizes="[10, 20, 50, 100]"
:total="viewForm.totalCount"
layout="total, sizes, prev, pager, next, jumper"
@size-change="viewSizeChange"
@current-change="viewCurrentChange">
</el-pagination>
</el-row>
</el-card>
</div>
</el-drawer>
</guava>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
const currentYear = moment().format("YYYY")
return {
unionOptions: [],
lineTypeOptions: [],
viewVisible: false,
viewLoading: false,
viewRequestSeq: 0,
viewTitle: "",
viewRow: {},
viewTableData: [],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "lineName",
pageOrderBy: "ascending",
year: currentYear,
lineName: "",
lineType: "",
unionId: ""
},
viewForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "signupTime",
pageOrderBy: "descending",
keyword: "",
unionId: ""
}
}
},
methods: {
resetSearch() {
this.pageForm.year = moment().format("YYYY")
this.pageForm.lineName = ""
this.pageForm.lineType = ""
this.pageForm.unionId = ""
this.loadLineTypeOptions()
this.doSearch()
},
groupStatusType(status) {
if (status === "formed") return "success"
if (status === "over") return "danger"
return "info"
},
tableRowClassName({ row }) {
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
},
isDirectFamilyLine(row) {
if (!row) return false
return row.directFamilyUnitLine === true
|| row.directFamilyUnitLine === 1
|| row.directFamilyUnitLine === "1"
},
openView(row) {
this.viewRow = row || {}
this.viewTitle = this.viewRow.lineName || "报名人员"
this.viewVisible = true
this.viewForm.pageNumber = 1
this.viewForm.pageSize = 10
this.viewForm.totalCount = 0
this.viewForm.pageOrderName = "signupTime"
this.viewForm.pageOrderBy = "descending"
this.viewForm.keyword = ""
this.viewForm.unionId = ""
this.viewTableData = []
this.loadViewData()
},
viewSearch() {
this.viewForm.pageNumber = 1
this.loadViewData()
},
viewReset() {
this.viewForm.keyword = ""
this.viewForm.unionId = ""
this.viewForm.pageNumber = 1
this.loadViewData()
},
viewIndexMethod(index) {
return (this.viewForm.pageNumber - 1) * this.viewForm.pageSize + index + 1
},
viewSizeChange(size) {
this.viewForm.pageSize = size
this.viewForm.pageNumber = 1
this.loadViewData()
},
viewCurrentChange(page) {
this.viewForm.pageNumber = page
this.loadViewData()
},
viewPageOrder({ prop, order }) {
this.viewForm.pageOrderName = prop || "signupTime"
this.viewForm.pageOrderBy = order || "descending"
this.viewForm.pageNumber = 1
this.loadViewData()
},
loadViewData() {
if (!this.viewRow || !this.viewRow.matterId) {
this.viewTableData = []
this.viewForm.totalCount = 0
return
}
this.viewLoading = true
const requestSeq = ++this.viewRequestSeq
this.$axios.post(loc() + "/signupPageData", {
matterId: this.viewRow.matterId,
keyword: this.viewForm.keyword,
unionId: this.viewForm.unionId,
pageNumber: this.viewForm.pageNumber,
pageSize: this.viewForm.pageSize,
pageOrderName: this.viewForm.pageOrderName,
pageOrderBy: this.viewForm.pageOrderBy
}).then((res) => {
if (requestSeq !== this.viewRequestSeq) return
if (res.code === 0) {
const data = res.data || {}
this.viewTableData = data.list || []
this.viewForm.totalCount = data.totalCount || 0
} else {
this.$message.warning(res.msg || "查询失败")
}
}).finally(() => {
if (requestSeq === this.viewRequestSeq) {
this.viewLoading = false
}
})
},
exportLine(row) {
if (!row || !row.matterId) {
this.$message.warning("线路信息不完整")
return
}
window.location.href = loc() + "/exportParticipants?matterId=" + encodeURIComponent(row.matterId)
},
loadUnionOptions() {
this.$axios.post(loc() + "/unionOptions").then((res) => {
if (res.code === 0) {
this.unionOptions = res.data || []
}
})
},
loadLineTypeOptions() {
this.$axios.post(loc() + "/lineTypeOptions", {
year: this.pageForm.year
}).then((res) => {
if (res.code === 0) {
this.lineTypeOptions = (res.data || []).filter(item => item.lineType)
}
})
}
},
mounted() {
this.loadUnionOptions()
this.loadLineTypeOptions()
this.pageData()
},
watch: {
"pageForm.year"() {
this.pageForm.lineType = ""
this.loadLineTypeOptions()
}
}
})
</script>
<style>
.tour-group-view-drawer .el-drawer__body {
background: #f5f7fa;
padding: 12px;
overflow: auto;
}
.tour-group-view-pagination {
margin-top: 12px;
text-align: right;
}
.tour-group-view-title {
border-left: 4px solid #0079c2;
color: #0079c2;
font-size: 14px;
font-weight: 600;
line-height: 16px;
margin-bottom: 12px;
padding-left: 10px;
}
.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;
}
</style>
<!--#
}
#-->
@@ -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>
<!--#
}
#-->
@@ -0,0 +1,669 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="开始年度">
<el-date-picker
v-model="pageForm.startYear"
type="year"
value-format="yyyy"
placeholder="请选择开始年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="结束年度">
<el-date-picker
v-model="pageForm.endYear"
type="year"
value-format="yyyy"
placeholder="请选择结束年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="姓名/工号">
<el-input
v-model="pageForm.keyword"
clearable
placeholder="请输入姓名或工号"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="线路">
<el-select v-model="pageForm.lineId" clearable filterable placeholder="请选择线路" style="width: 100%">
<el-option v-for="item in lineOptions" :key="item.lineId" :label="item.lineName" :value="item.lineId"></el-option>
</el-select>
</search-item>
<search-item label="出行时段">
<el-select v-model="pageForm.travelPeriod" clearable filterable placeholder="请选择出行时段" style="width: 100%">
<el-option v-for="item in travelPeriodOptions" :key="item.travelPeriod" :label="item.travelPeriod" :value="item.travelPeriod"></el-option>
</el-select>
</search-item>
<search-item label="线路类型">
<dict-select
v-model="pageForm.lineType"
code="lineType"
option_value="name"
placeholder="请选择线路类型">
</dict-select>
</search-item>
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<div class="tour-ledger-toolbar">
<div class="tour-ledger-toolbar-title">
<table-tool :app="this" label="台账列表"></table-tool>
</div>
<div class="tour-ledger-actions">
<el-button size="medium" type="primary" icon="el-icon-upload2" @click="showImportDialog = true">参加人员导入</el-button>
<el-button size="medium" type="primary" icon="el-icon-check" @click="setParticipants">设置参加人员</el-button>
<el-button size="medium" type="primary" icon="el-icon-download" @click="exportLedgerData">导出台账数据</el-button>
<el-button size="medium" type="primary" icon="el-icon-download" @click="exportUnionSignupZip">导出分工会报名压缩包</el-button>
</div>
<div v-if="false" class="tour-ledger-scope">
<el-button
class="tour-scope-btn"
size="medium"
:class="{'is-active': pageForm.directFamilyOnly}"
@click="setDirectFamilyOnly">
参加直系亲属单位线路人员
</el-button>
<el-button
class="tour-scope-btn"
size="medium"
:class="{'is-active': pageForm.overCostOnly}"
@click="setOverCostOnly">
申请超出费用由单位承担人员
</el-button>
</div>
</div>
<el-table
ref="tableRef"
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
row-key="id"
:row-class-name="tableRowClassName"
@selection-change="handleSelectionChange"
@sort-change="pageOrder">
<el-table-column type="selection" width="55" align="center" header-align="center"></el-table-column>
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="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>
<el-table-column label="报名时间" prop="signupTime" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="携带家属" prop="familyCount" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="Number(row.familyCount || 0) > 0 ? 'success' : 'info'">{{ row.familyCount || 0 }}人</el-tag>
</template>
</el-table-column>
<el-table-column label="是否参加" prop="joined" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="row.joined ? 'success' : 'info'">{{ row.joined ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="报销超出费用" prop="overCostReimbursed" width="140" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="row.overCostReimbursed ? 'success' : 'info'">{{ row.overCostReimbursed ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
<el-button size="mini" type="danger" @click="doDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
<div class="tour-ledger-tip">温馨提醒:走审核流程的报名,审核通过后才在台账中显示</div>
</el-card>
</guava>
<excel-import
ref="excelImportRef"
url="/platform/tour/ledger/importParticipants"
template_url="/platform/tour/ledger/downloadTemplate"
:visible.sync="showImportDialog"
title="参加人员导入"
width="700px"
@import-success="afterImport"
:extra_params="{}">
</excel-import>
<el-dialog
title="台账详情"
:visible.sync="detailVisible"
:close-on-click-modal="false"
width="72%">
<div class="tour-ledger-section">
<div class="tour-ledger-title">教职工信息</div>
<el-descriptions :column="3" border size="medium">
<el-descriptions-item label="工号">{{ detail.jobNo || '' }}</el-descriptions-item>
<el-descriptions-item label="姓名">{{ detail.userName || '' }}</el-descriptions-item>
<el-descriptions-item label="性别">{{ detail.gender || '' }}</el-descriptions-item>
<el-descriptions-item label="身份证号">{{ detail.idCard || '' }}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{ detail.unitName || '' }}</el-descriptions-item>
<el-descriptions-item label="所在工会">{{ detail.unionName || '' }}</el-descriptions-item>
</el-descriptions>
</div>
<div class="tour-ledger-section mt10">
<div class="tour-ledger-title">
报名信息
<el-link v-if="detailHasWorkflow()" type="primary" @click="openChart">点击查看流程图</el-link>
</div>
<el-descriptions :column="3" border size="medium">
<el-descriptions-item label="报名时间">{{ detail.signupTime || '' }}</el-descriptions-item>
<el-descriptions-item label="报名线路">{{ detail.lineName || '' }}</el-descriptions-item>
<el-descriptions-item label="线路类型">{{ detail.lineType || '' }}</el-descriptions-item>
<el-descriptions-item label="报名酒店">{{ detail.hotelName || '' }}</el-descriptions-item>
<el-descriptions-item label="报名旅行社">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
<el-descriptions-item label="出行时间">{{ detail.travelPeriod || '' }}</el-descriptions-item>
<el-descriptions-item v-if="fillBedInfo" label="床型">{{ detail.bedType || '' }}</el-descriptions-item>
<el-descriptions-item v-if="fillBedInfo" label="床位信息">{{ detail.bedInfo || '' }}</el-descriptions-item>
<el-descriptions-item v-if="fillBedInfo" label="意向拼床人">{{ detail.intendedRoommate || '' }}</el-descriptions-item>
<el-descriptions-item label="是否携带家属">{{ familyText() }}</el-descriptions-item>
<el-descriptions-item label="是否参加">{{ detail.joined ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="是否报销">{{ detail.reimbursed ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="报销超出费用">{{ detail.overCostReimbursed ? '是' : '否' }}</el-descriptions-item>
</el-descriptions>
</div>
<div v-if="isDirectFamilyLine()" class="tour-ledger-section mt10">
<div class="tour-ledger-title">直系亲属线路</div>
<el-descriptions :column="3" border size="medium">
<el-descriptions-item label="亲属姓名">{{ directRelative.relativeName || '' }}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{ directRelative.unitName || '' }}</el-descriptions-item>
<el-descriptions-item label="亲属关系">{{ directRelative.relationshipName || '' }}</el-descriptions-item>
<el-descriptions-item label="线路名称">{{ directRelative.lineName || '' }}</el-descriptions-item>
<el-descriptions-item label="出行开始日期">{{ directRelative.travelStartTime || '' }}</el-descriptions-item>
<el-descriptions-item label="出行结束日期">{{ directRelative.travelEndTime || '' }}</el-descriptions-item>
</el-descriptions>
</div>
<div v-if="showFamilySection()" class="tour-ledger-section mt10">
<div class="tour-ledger-title">家属信息</div>
<el-table :data="familyData" border :size="tableSize" empty-text="暂无家属信息">
<el-table-column label="序号" type="index" width="70" align="center" header-align="center"></el-table-column>
<el-table-column label="家属姓名" prop="familyName" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="gender" width="90" align="center" header-align="center"></el-table-column>
<el-table-column label="身份证号码" prop="idCard" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="年龄" prop="age" width="90" align="center" header-align="center"></el-table-column>
<el-table-column label="关系" prop="relationship" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column v-if="fillBedInfo" label="床型" prop="bedType" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column v-if="fillBedInfo" label="床位" prop="bedInfo" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column v-if="fillBedInfo" label="意向拼床人" prop="intendedRoommate" min-width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
</el-table>
</div>
<template v-if="detailHasWorkflow()" v-for="task in doneTasks">
<div class="tour-ledger-section mt10" :key="task.id">
<div class="tour-ledger-title">{{ task.displayName }}</div>
<el-descriptions border :column="3" v-if="task.ext && task.ext.isFirstTaskNode">
<el-descriptions-item label="申请用户">
{{ task.ext.initiatorName }}({{ task.ext.initiatorAccount }})
</el-descriptions-item>
<el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
</el-descriptions>
<el-descriptions border :column="3" v-else>
<el-descriptions-item label="办理用户">
{{ (task.taskFormData && task.taskFormData.userName) || '' }}({{ (task.taskFormData && task.taskFormData.loginName) || '' }})
</el-descriptions-item>
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext && task.ext.submitType"></dict-tag>
</el-descriptions-item>
<el-descriptions-item label="办理意见" :span="3">
{{ (task.taskFormData && (task.taskFormData.opinion || task.taskFormData.tf_opinion)) || '' }}
</el-descriptions-item>
</el-descriptions>
</div>
</template>
<snaker-chart ref="snakerChartRef"></snaker-chart>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="detailVisible = false">确定</el-button>
</span>
</el-dialog>
</div>
<style>
.tour-ledger-section {
padding: 0 2px;
}
.tour-ledger-title {
border-left: 4px solid #0079c2;
color: #0079c2;
font-size: 14px;
font-weight: 600;
line-height: 16px;
margin-bottom: 14px;
padding-left: 10px;
}
.tour-ledger-toolbar {
display: flex;
align-items: center;
flex-wrap: nowrap;
gap: 24px;
margin-bottom: 12px;
white-space: nowrap;
}
.tour-ledger-toolbar-title {
flex: 0 0 auto;
}
.tour-ledger-actions {
display: flex;
align-items: center;
flex: 0 0 auto;
gap: 10px;
white-space: nowrap;
}
.tour-ledger-actions .el-button {
margin: 0;
}
.tour-ledger-scope {
display: flex;
align-items: center;
flex: 0 0 auto;
flex-wrap: nowrap;
gap: 12px;
margin: 0;
white-space: nowrap;
}
.tour-ledger-scope .el-button {
margin: 0;
}
.tour-ledger-scope .el-button + .el-button {
margin-left: 0;
}
.tour-ledger-scope .tour-scope-btn {
background: #ecf5ff;
border-color: #b3d8ff;
border-radius: 4px;
color: #0079c2;
font-weight: 600;
height: 34px;
line-height: 1;
padding: 8px 18px;
}
.tour-ledger-scope .tour-scope-btn:hover,
.tour-ledger-scope .tour-scope-btn:focus {
background: #d9ecff;
border-color: #66b1ff;
color: #006bb0;
}
.tour-ledger-scope .tour-scope-btn.is-active {
background: #0079c2;
border-color: #0079c2;
box-shadow: 0 2px 6px rgba(0, 121, 194, 0.24);
color: #fff;
}
.tour-ledger-scope .tour-scope-btn.is-active:hover,
.tour-ledger-scope .tour-scope-btn.is-active:focus {
background: #006bb0;
border-color: #006bb0;
color: #fff;
}
.tour-ledger-tip {
color: #f56c6c;
font-family: SimSun, "宋体", serif;
font-size: 9pt;
line-height: 1.6;
margin-top: -4px;
text-align: center;
}
.el-table .direct-family-row > td,
.el-table .direct-family-row > td .cell {
color: #f56c6c;
}
</style>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
const currentYear = moment().format("YYYY")
return {
detailVisible: false,
unionOptions: [],
lineOptions: [],
travelPeriodOptions: [],
detail: {},
familyData: [],
directRelative: {},
directFamilyUnitLine: false,
fillBedInfo: true,
detailRow: {},
doneTasks: [],
showImportDialog: false,
multipleSelection: [],
filterOptionsTimer: null,
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "signupTime",
pageOrderBy: "descending",
startYear: currentYear,
endYear: currentYear,
keyword: "",
unionId: "",
lineId: "",
travelPeriod: "",
lineType: "",
directFamilyOnly: false,
overCostOnly: false
}
}
},
methods: {
toBoolean(value) {
return value === true || value === 1 || value === "1" || value === "true" || value === "TRUE"
},
tableRowClassName({ row }) {
return this.isDirectFamilyRow(row) ? "direct-family-row" : ""
},
isDirectFamilyRow(row) {
return this.toBoolean(row && row.directFamilyUnitLine) || !!(row && row.directRelativeId)
},
handleSelectionChange(val) {
this.multipleSelection = val || []
},
clearTableSelection() {
this.multipleSelection = []
if (this.$refs.tableRef) {
this.$refs.tableRef.clearSelection()
}
},
setParticipants() {
if (!this.multipleSelection.length) {
this.$message.warning("请选择要设置的参加人员")
return
}
this.$confirm("确定将选中的" + this.multipleSelection.length + "条台账设置为已参加吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/setParticipants", {
ids: JSON.stringify(this.multipleSelection.map(item => item.id))
}).then((res) => {
if (res.code === 0) {
this.$message.success("设置成功")
this.clearTableSelection()
this.pageData()
} else {
this.$message.warning(res.msg || "设置失败")
}
})
})
},
exportUnionSignupZip() {
const params = new URLSearchParams()
;["startYear", "endYear", "keyword", "unionId", "lineId", "travelPeriod", "lineType"].forEach(key => {
const value = this.pageForm[key]
if (value !== undefined && value !== null && value !== "") {
params.append(key, value)
}
})
if (this.pageForm.directFamilyOnly) {
params.append("directFamilyOnly", "true")
}
if (this.pageForm.overCostOnly) {
params.append("overCostOnly", "true")
}
window.location.href = loc() + "/exportUnionSignupZip?" + params.toString()
},
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()
this.pageData()
this.loadFilterOptions()
},
isDirectFamilyLine() {
return this.directFamilyUnitLine
|| this.toBoolean(this.detail && this.detail.directFamilyUnitLine)
|| !!(this.directRelative && this.directRelative.id)
},
detailHasWorkflow() {
return !!(this.detailRow && this.detailRow.instanceId)
},
hasFamily() {
return this.toBoolean(this.detail && this.detail.hasFamily) || this.familyData.length > 0
},
familyText() {
if (this.isDirectFamilyLine()) {
return "否"
}
if (!this.hasFamily()) {
return "否"
}
const count = Number(this.familyData.length || 0)
return count > 0 ? count + "人" : "是"
},
showFamilySection() {
return this.hasFamily() && !this.isDirectFamilyLine()
},
loadDoneTasks() {
this.$axios.post("/flow/common/doneTasks", { bizId: this.detailRow.id }).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data || []
}
})
},
openChart() {
if (!this.detailRow.instanceProcessDefineId || !this.detailRow.instanceId) {
this.$message.warning("暂无流程图信息")
return
}
this.$refs.snakerChartRef.onOpenFull(this.detailRow.instanceProcessDefineId, this.detailRow.instanceId)
},
resetSearch() {
this.pageForm.startYear = moment().format("YYYY")
this.pageForm.endYear = moment().format("YYYY")
this.pageForm.keyword = ""
this.pageForm.unionId = ""
this.pageForm.lineId = ""
this.pageForm.travelPeriod = ""
this.pageForm.lineType = ""
this.pageForm.directFamilyOnly = false
this.pageForm.overCostOnly = false
this.doSearch()
},
openView(row) {
this.detailRow = row || {}
this.detail = {}
this.familyData = []
this.directRelative = {}
this.directFamilyUnitLine = false
this.fillBedInfo = true
this.doneTasks = []
this.$axios.post(loc() + "/detail", { id: row.id }).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.detail = data.ledger || {}
this.detail.travelPeriod = data.travelPeriod || ""
this.familyData = data.families || []
this.directRelative = data.directRelative || {}
this.directFamilyUnitLine = this.toBoolean(data.directFamilyUnitLine)
this.fillBedInfo = data.fillBedInfo === undefined || data.fillBedInfo === null || this.toBoolean(data.fillBedInfo)
this.detailVisible = true
if (this.detailHasWorkflow()) {
this.loadDoneTasks()
}
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
doDelete(row) {
this.$confirm("确定删除【" + row.userName + "】的报名台账吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doDelete", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success("删除成功")
this.pageData()
this.loadFilterOptions()
} else {
this.$message.warning(res.msg || "删除失败")
}
})
})
},
loadUnionOptions() {
this.$axios.post(loc() + "/unionOptions").then((res) => {
if (res.code === 0) {
this.unionOptions = res.data || []
}
})
},
loadFilterOptions() {
this.loadLineOptions()
this.loadTravelPeriodOptions()
},
scheduleFilterOptions(delay) {
if (this.filterOptionsTimer) {
clearTimeout(this.filterOptionsTimer)
}
this.filterOptionsTimer = setTimeout(() => {
this.loadFilterOptions()
this.filterOptionsTimer = null
}, delay === undefined ? 80 : delay)
},
loadLineOptions() {
this.$axios.post(loc() + "/lineOptions", {
startYear: this.pageForm.startYear,
endYear: this.pageForm.endYear,
travelPeriod: this.pageForm.travelPeriod,
lineType: this.pageForm.lineType,
unionId: this.pageForm.unionId,
keyword: this.pageForm.keyword,
directFamilyOnly: this.pageForm.directFamilyOnly,
overCostOnly: this.pageForm.overCostOnly
}).then((res) => {
if (res.code === 0) {
this.lineOptions = res.data || []
if (this.pageForm.lineId && !this.lineOptions.some(item => item.lineId === this.pageForm.lineId)) {
this.pageForm.lineId = ""
}
}
})
},
loadTravelPeriodOptions() {
this.$axios.post(loc() + "/travelPeriodOptions", {
startYear: this.pageForm.startYear,
endYear: this.pageForm.endYear,
lineId: this.pageForm.lineId,
lineType: this.pageForm.lineType,
unionId: this.pageForm.unionId,
keyword: this.pageForm.keyword,
directFamilyOnly: this.pageForm.directFamilyOnly,
overCostOnly: this.pageForm.overCostOnly
}).then((res) => {
if (res.code === 0) {
this.travelPeriodOptions = res.data || []
if (this.pageForm.travelPeriod && !this.travelPeriodOptions.some(item => item.travelPeriod === this.pageForm.travelPeriod)) {
this.pageForm.travelPeriod = ""
}
}
})
},
doSearch() {
this.tableKey = new Date().getTime()
this.pageForm.pageNumber = 1
this.clearTableSelection()
this.pageData()
this.scheduleFilterOptions(0)
},
setDirectFamilyOnly() {
this.pageForm.directFamilyOnly = !this.pageForm.directFamilyOnly
this.pageForm.pageNumber = 1
this.clearTableSelection()
this.pageData()
this.scheduleFilterOptions(0)
},
setOverCostOnly() {
this.pageForm.overCostOnly = !this.pageForm.overCostOnly
this.pageForm.pageNumber = 1
this.clearTableSelection()
this.pageData()
this.scheduleFilterOptions(0)
}
},
mounted() {
this.loadUnionOptions()
this.pageData()
this.scheduleFilterOptions(150)
},
watch: {
"pageForm.startYear"() {
this.pageForm.lineId = ""
this.pageForm.travelPeriod = ""
this.scheduleFilterOptions()
},
"pageForm.endYear"() {
this.pageForm.lineId = ""
this.pageForm.travelPeriod = ""
this.scheduleFilterOptions()
},
"pageForm.lineId"() {
this.scheduleFilterOptions()
},
"pageForm.travelPeriod"() {
this.scheduleFilterOptions()
},
"pageForm.lineType"() {
this.scheduleFilterOptions()
},
"pageForm.unionId"() {
this.scheduleFilterOptions()
}
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,759 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="年度">
<el-date-picker
v-model="pageForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="事项名称">
<el-input
v-model="pageForm.matterName"
clearable
placeholder="请输入事项名称"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="组织形式">
<el-select v-model="pageForm.organizationType" clearable filterable placeholder="请选择组织形式" style="width: 100%">
<el-option v-for="item in organizationTypeOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
</el-select>
</search-item>
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="事项列表">
<el-button @click="openAdd" size="medium" type="primary">
<i class="el-icon-plus"></i>
新增
</el-button>
</table-tool>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
:row-class-name="tableRowClassName"
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="年度" prop="year" width="110" sortable="custom" align="center" header-align="center"></el-table-column>
<el-table-column label="事项名称" prop="matterName" min-width="200" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所属工会" prop="unionName" min-width="180" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路名称" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="组织形式" prop="organizationTypeName" width="150" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="联系人" prop="contactName" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="联系方式" prop="contactPhone" width="150" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="事项状态" prop="enabled" width="160" align="center" header-align="center">
<template slot-scope="{row}">
<el-switch
v-model="row.enabled"
active-text="启用"
inactive-text="禁用"
@change="toggleEnabled(row)">
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" width="260" align="center" header-align="center">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openBatchForm(row)">选择线路</el-button>
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
<el-button size="mini" type="danger" :loading="row.deleteLoading" @click="doDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
<el-dialog
:title="title"
:visible.sync="dialogVisible"
:close-on-click-modal="false"
width="54%"
@closed="destroyForm">
<el-form :model="formData" :rules="formRules" label-width="110px" ref="form" :disabled="viewMode">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="年度" prop="year">
<el-date-picker
v-model="formData.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%"
@change="yearChange">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="事项状态" prop="enabled">
<el-radio-group v-model="formData.enabled">
<el-radio :label="true">启用</el-radio>
<el-radio :label="false">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="事项名称" prop="matterName">
<el-input v-model="formData.matterName" maxlength="30" show-word-limit placeholder="请输入事项名称"></el-input>
</el-form-item>
<el-form-item label="创建人">
<el-input v-model="formData.creatorName" readonly placeholder="当前登录人"></el-input>
</el-form-item>
<el-form-item label="疗休养配置" prop="settingId">
<el-select v-model="formData.settingId" clearable filterable placeholder="请选择配置" style="width: 100%">
<el-option v-for="item in settingOptions" :key="item.id" :label="item.configName" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item label="组织形式" prop="organizationType">
<el-select v-model="formData.organizationType" clearable filterable placeholder="请选择组织形式" style="width: 100%" @change="organizationTypeChange">
<el-option v-for="item in organizationTypeOptions" :key="item.code" :label="item.name" :value="item.code"></el-option>
</el-select>
</el-form-item>
<el-form-item label="所属工会" prop="unionId">
<el-select v-model="formData.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%" :disabled="isUnionDisabled()">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button v-if="!viewMode" type="primary" :loading="subDis" @click="doSubmit">保存</el-button>
</span>
</el-dialog>
<el-dialog
:title="batchTitle"
:visible.sync="batchFormVisible"
:close-on-click-modal="false"
width="74%">
<el-form :model="batchForm" :rules="batchRules" label-width="110px" ref="batchFormRef">
<el-form-item label="线路" prop="lineId">
<el-select v-model="batchForm.lineId" clearable filterable placeholder="请选择线路" style="width: 100%" @change="lineChange">
<el-option v-for="item in lineOptions" :key="item.id" :label="lineOptionLabel(item)" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-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">
<el-date-picker v-model="batchForm.signupStartTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择时间" style="width: 100%"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="报名结束时间" prop="signupEndTime">
<el-date-picker v-model="batchForm.signupEndTime" type="datetime" value-format="yyyy-MM-dd HH:mm:ss" placeholder="选择时间" style="width: 100%"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="出行开始时间" prop="travelStartTime">
<el-date-picker v-model="batchForm.travelStartTime" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width: 100%" @change="travelStartChange"></el-date-picker>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="出行结束时间" prop="travelEndTime">
<el-date-picker v-model="batchForm.travelEndTime" type="date" value-format="yyyy-MM-dd" placeholder="选择日期" style="width: 100%" @change="travelEndChange"></el-date-picker>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="联系人" prop="contactName">
<el-input v-model="batchForm.contactName" maxlength="10" placeholder="请输入联系人"></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="联系方式" prop="contactPhone">
<el-input v-model="batchForm.contactPhone" maxlength="11" placeholder="请输入联系方式"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="8">
<el-form-item label="预计费用" prop="estimatedCost">
<el-input v-model="batchForm.estimatedCost" maxlength="10" placeholder="请输入预计费用"></el-input>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="最少成团人数" prop="minGroupPeople">
<el-input-number v-model="batchForm.minGroupPeople" :min="1" :precision="0" controls-position="right" style="width: 100%" @change="peopleChange"></el-input-number>
</el-form-item>
</el-col>
<el-col :span="8">
<el-form-item label="最多成团人数" prop="maxGroupPeople">
<el-input-number v-model="batchForm.maxGroupPeople" :min="1" :precision="0" controls-position="right" style="width: 100%" @change="peopleChange"></el-input-number>
</el-form-item>
</el-col>
</el-row>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="batchFormVisible = false">取消</el-button>
<el-button type="primary" :loading="batchSubDis" @click="submitBatch">确定</el-button>
</span>
</el-dialog>
</div>
<style>
.el-table .direct-family-row > td,
.el-table .direct-family-row > td .cell {
color: #f56c6c;
}
</style>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
mixins: [initTableMixins],
data() {
const currentYear = moment().format("YYYY")
const validateUnion = (rule, value, callback) => {
if (this.formData.organizationType === "schoolUnion" || value) {
callback()
} else {
callback(new Error("必填"))
}
}
const validateMobile = (rule, value, callback) => {
const mobileReg = /^1[3-9]\d{9}$/
if (!value) {
callback(new Error("必填"))
} else if (!mobileReg.test(value)) {
callback(new Error("手机号格式不正确"))
} else {
callback()
}
}
const validateMoney = (rule, value, callback) => {
const moneyReg = /^(0|[1-9]\d*)(\.\d{1,2})?$/
if (value === "" || value === null || value === undefined) {
callback(new Error("必填"))
} else if (!moneyReg.test(String(value))) {
callback(new Error("请输入非负金额,最多两位小数"))
} else {
callback()
}
}
const validatePeople = (rule, value, callback) => {
if (!value || value <= 0) {
callback(new Error("人数必须大于0"))
} else if (this.batchForm.minGroupPeople && this.batchForm.maxGroupPeople
&& this.batchForm.minGroupPeople > this.batchForm.maxGroupPeople) {
callback(new Error("最少人数不能大于最多人数"))
} else {
callback()
}
}
const validateBatchTime = (rule, value, callback) => {
if (!value) {
callback(new Error("必填"))
return
}
const form = this.batchForm
if (form.signupStartTime && form.signupEndTime && form.signupStartTime >= form.signupEndTime) {
callback(new Error("报名开始时间必须小于报名结束时间"))
return
}
if (form.travelStartTime && form.travelEndTime && form.travelStartTime > form.travelEndTime) {
callback(new Error("出行开始时间不能晚于出行结束时间"))
return
}
if (form.signupEndTime && form.travelStartTime && form.signupEndTime >= form.travelStartTime) {
callback(new Error("报名结束时间必须小于出行开始时间"))
return
}
callback()
}
return {
title: "",
dialogVisible: false,
viewMode: false,
subDis: false,
batchFormVisible: false,
batchSubDis: false,
batchTitle: "",
currentMatter: {},
batchForm: {},
lineOptions: [],
boardingPlaceOptions: [],
settingOptions: [],
unionOptions: [],
organizationTypeOptions: [],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
year: currentYear,
matterName: "",
unionId: "",
organizationType: ""
},
formData: {},
formRules: {
year: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
matterName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
settingId: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
unionId: [{ validator: validateUnion, trigger: ["blur", "change"] }],
organizationType: [{ required: true, message: "必填", trigger: ["blur", "change"] }]
},
batchRules: {
lineId: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
signupStartTime: [{ validator: validateBatchTime, trigger: ["blur", "change"] }],
signupEndTime: [{ validator: validateBatchTime, trigger: ["blur", "change"] }],
travelStartTime: [{ validator: validateBatchTime, trigger: ["blur", "change"] }],
travelEndTime: [{ validator: validateBatchTime, trigger: ["blur", "change"] }],
contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
contactPhone: [{ validator: validateMobile, trigger: ["blur", "change"] }],
minGroupPeople: [{ validator: validatePeople, trigger: ["blur", "change"] }],
maxGroupPeople: [{ validator: validatePeople, trigger: ["blur", "change"] }],
estimatedCost: [{ validator: validateMoney, trigger: ["blur", "change"] }]
}
}
},
watch: {
"pageForm.year"(year) {
this.loadSettings(year)
},
"batchForm.lineId"() {
if (this.batchFormVisible) {
this.applySelectedLineDefaults(true)
}
},
"batchForm.travelStartTime"() {
if (this.batchFormVisible) {
this.fillTravelEndByLot()
}
},
"batchForm.travelEndTime"() {
if (this.batchFormVisible) {
this.validateTravelFields()
}
}
},
methods: {
resetSearch() {
this.pageForm.year = moment().format("YYYY")
this.pageForm.matterName = ""
this.pageForm.unionId = ""
this.pageForm.organizationType = ""
this.loadSettings(this.pageForm.year)
this.doSearch()
},
tableRowClassName({ row }) {
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
},
isDirectFamilyLine(row) {
if (!row) return false
return row.directFamilyUnitLine === true
|| row.directFamilyUnitLine === 1
|| row.directFamilyUnitLine === "1"
},
emptyForm() {
const user = this.currentUser()
return {
year: moment().format("YYYY"),
matterName: "",
settingId: "",
creatorUserId: user.id || "",
creatorName: user.username || "",
unionId: "",
organizationType: "",
enabled: true
}
},
openAdd() {
this.title = "新增事项信息"
this.viewMode = false
this.formData = this.emptyForm()
this.loadSettings(this.formData.year, true)
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
},
openEdit(row) {
this.title = "编辑事项信息"
this.viewMode = false
this.loadDetail(row.id)
},
openView(row) {
this.title = "查看事项信息"
this.viewMode = true
this.loadDetail(row.id)
},
loadDetail(id) {
this.$axios.post(loc() + "/detail", { id }).then((res) => {
if (res.code === 0) {
this.formData = Object.assign(this.emptyForm(), res.data || {})
this.formData.year = this.formData.year ? String(this.formData.year) : ""
this.ensureSelectedUnionOption()
this.loadSettings(this.formData.year)
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
currentUser() {
return (this.$store && this.$store.state && this.$store.state.user) || {}
},
currentUnion() {
return this.currentUser().union || {}
},
organizationTypeChange() {
this.applyOrganizationTypeRule()
this.$nextTick(() => this.$refs.form && this.$refs.form.validateField("unionId"))
},
applyOrganizationTypeRule() {
if (this.formData.organizationType === "schoolUnion") {
this.formData.unionId = ""
return
}
if (this.formData.organizationType === "branchUnion" || this.formData.organizationType === "personal") {
const union = this.currentUnion()
this.formData.unionId = union.id || ""
this.ensureCurrentUnionOption(union)
}
},
ensureCurrentUnionOption(union) {
if (!union || !union.id) return
const exists = this.unionOptions.some(item => item.id === union.id)
if (!exists) {
this.unionOptions.push({ id: union.id, name: union.name || "" })
}
},
ensureSelectedUnionOption() {
if (!this.formData.unionId) return
const exists = this.unionOptions.some(item => item.id === this.formData.unionId)
if (!exists) {
this.unionOptions.push({ id: this.formData.unionId, name: this.formData.unionName || "" })
}
},
isUnionDisabled() {
return ["schoolUnion", "branchUnion", "personal"].includes(this.formData.organizationType)
},
yearChange(year) {
this.formData.settingId = ""
this.loadSettings(year, true)
},
loadSettings(year, autoSelectLatest) {
this.$axios.post(loc() + "/settingOptions", { year }).then((res) => {
if (res.code === 0) {
this.settingOptions = res.data || []
if (autoSelectLatest && !this.formData.settingId && this.settingOptions.length > 0) {
this.formData.settingId = this.settingOptions[0].id
}
}
})
},
loadUnions() {
this.$axios.post(loc() + "/unionOptions").then((res) => {
if (res.code === 0) {
this.unionOptions = res.data || []
}
})
},
loadOrganizationTypes() {
this.$axios.post(loc() + "/organizationTypeOptions").then((res) => {
if (res.code === 0) {
this.organizationTypeOptions = res.data || []
}
})
},
emptyBatchForm() {
return {
id: this.currentMatter.id || "",
lineId: "",
signupStartTime: "",
signupEndTime: "",
travelStartTime: "",
travelEndTime: "",
defaultBoardingPlace: "",
contactName: "",
contactPhone: "",
minGroupPeople: null,
maxGroupPeople: null,
estimatedCost: ""
}
},
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) => {
if (res.code === 0) {
this.batchForm = Object.assign(this.emptyBatchForm(), res.data || {})
this.batchForm.travelStartTime = this.dateOnly(this.batchForm.travelStartTime)
this.batchForm.travelEndTime = this.dateOnly(this.batchForm.travelEndTime)
this.ensureBatchBoardingPlace()
this.applySelectedLineDefaults(false)
if (!this.batchForm.minGroupPeople || !this.batchForm.maxGroupPeople) {
this.loadSettingPeople((data) => {
this.batchForm.minGroupPeople = data.minGroupPeople || null
this.batchForm.maxGroupPeople = data.maxGroupPeople || null
})
}
this.batchFormVisible = true
this.$nextTick(() => {
this.applySelectedLineDefaults(true)
this.$refs.batchFormRef && this.$refs.batchFormRef.clearValidate()
})
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
loadLines(year) {
this.$axios.post(loc() + "/lineOptions", { year }).then((res) => {
if (res.code === 0) {
this.lineOptions = res.data || []
this.applySelectedLineDefaults(true)
}
})
},
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") || ""
const lineType = this.lineField(item, "lineType") || ""
return lineType ? lineName + "" + lineType + "" : lineName
},
lineChange() {
this.applySelectedLineDefaults(true)
},
travelStartChange() {
this.fillTravelEndByLot()
},
travelEndChange() {
this.validateTravelFields()
},
selectedLine() {
return (this.lineOptions || []).find((item) => this.lineField(item, "id") === this.batchForm.lineId) || null
},
applySelectedLineDefaults(recalculateEnd) {
const line = this.selectedLine()
if (!line) return
const activityCost = this.lineField(line, "activityCost")
if (activityCost !== null && activityCost !== undefined && activityCost !== "") {
this.batchForm.estimatedCost = activityCost
}
if (recalculateEnd) {
this.fillTravelEndByLot()
}
},
fillTravelEndByLot() {
const line = this.selectedLine()
const days = this.lineLotDays(line)
if (!this.batchForm.travelStartTime || !days) return
this.$set(this.batchForm, "travelEndTime", moment(this.batchForm.travelStartTime).add(days - 1, "days").format("YYYY-MM-DD"))
this.validateTravelFields()
},
lineLotDays(line) {
if (!line) return null
const lotDays = this.lineField(line, "lotDays") || this.lineField(line, "lotValue")
if (!/^\d+$/.test(String(lotDays || ""))) return null
const days = parseInt(lotDays, 10)
return days > 0 ? days : null
},
lineField(line, field) {
if (!line) return null
if (line[field] !== undefined) return line[field]
const lowerField = field.toLowerCase()
const matchedKey = Object.keys(line).find((key) => key.toLowerCase() === lowerField)
return matchedKey ? line[matchedKey] : null
},
validateTravelFields() {
this.$nextTick(() => {
if (this.$refs.batchFormRef) {
this.$refs.batchFormRef.validateField(["travelStartTime", "travelEndTime"])
}
})
},
dateOnly(value) {
if (!value) return ""
return String(value).substring(0, 10)
},
loadSettingPeople(callback) {
this.$axios.post(loc() + "/settingPeople", { matterId: this.currentMatter.id }).then((res) => {
if (res.code === 0) {
const data = res.data || {}
callback && callback(data)
}
})
},
peopleChange() {
this.$nextTick(() => {
if (this.$refs.batchFormRef) {
this.$refs.batchFormRef.validateField(["minGroupPeople", "maxGroupPeople"])
}
})
},
submitBatch() {
this.$refs.batchFormRef.validate((valid) => {
if (!valid) return
this.batchSubDis = true
this.$axios.post(loc() + "/lineConfigDoSubmit", this.batchForm).then((res) => {
this.batchSubDis = false
if (res.code === 0) {
this.$message.success("保存成功")
this.batchFormVisible = false
this.pageData()
} else {
this.$message.warning(res.msg || "保存失败")
}
}).catch(() => {
this.batchSubDis = false
})
})
},
doSubmit() {
this.$refs.form.validate((valid) => {
if (!valid) return
this.subDis = true
this.$axios.post(loc() + "/doSubmit", this.formData).then((res) => {
this.subDis = false
if (res.code === 0) {
this.$message.success("保存成功")
this.dialogVisible = false
this.pageData()
} else {
this.$message.warning(res.msg || "保存失败")
}
}).catch(() => {
this.subDis = false
})
})
},
toggleEnabled(row) {
this.$axios.post(loc() + "/doSubmit", row).then((res) => {
if (res.code === 0) {
this.$message.success("状态已更新")
} else {
row.enabled = !row.enabled
this.$message.warning(res.msg || "状态更新失败")
}
}).catch(() => {
row.enabled = !row.enabled
})
},
doDelete(row) {
this.$set(row, "deleteLoading", true)
this.$axios.post(loc() + "/deleteInfo", { id: row.id }).then((res) => {
this.$set(row, "deleteLoading", false)
if (res.code !== 0) {
this.$message.warning(res.msg || "删除检查失败")
return
}
const data = res.data || {}
const signupCount = Number(data.signupCount || 0)
const matterName = row.matterName || ""
if (!data.canDelete) {
this.$alert("事项【" + matterName + "】已有 " + signupCount + " 人报名,不能删除。", "删除提醒", {
confirmButtonText: "知道了",
type: "warning"
})
return
}
this.$confirm("事项【" + matterName + "】暂无人员报名,确认删除吗?", "删除提醒", {
confirmButtonText: "确认删除",
cancelButtonText: "取消",
type: "warning",
confirmButtonClass: "el-button--danger"
}).then(() => {
this.$set(row, "deleteLoading", true)
this.$axios.post(loc() + "/doDelete", { id: row.id }).then((deleteRes) => {
this.$set(row, "deleteLoading", false)
if (deleteRes.code === 0) {
this.$message.success("删除成功")
this.pageData()
} else {
this.$message.warning(deleteRes.msg || "删除失败")
}
}).catch(() => {
this.$set(row, "deleteLoading", false)
})
}).catch(() => {})
}).catch(() => {
this.$set(row, "deleteLoading", false)
})
},
destroyForm() {
this.formData = {}
this.viewMode = false
}
},
mounted() {
this.loadSettings(this.pageForm.year)
this.loadUnions()
this.loadOrganizationTypes()
this.pageData()
}
})
</script>
<!--#
}
#-->
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,23 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<el-card shadow="never">
<el-empty description="${title!'功能建设中'}">
<template slot="description">
<span>${title!'功能建设中'}正在分阶段建设中</span>
</template>
</el-empty>
</el-card>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app"
})
</script>
<!--#
}
#-->
@@ -0,0 +1,432 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="创建年度">
<el-date-picker
v-model="pageForm.year"
type="year"
value-format="yyyy"
placeholder="请选择创建年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="线路名称">
<el-input
v-model="pageForm.lineName"
clearable
placeholder="请输入线路名称"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="时间标段">
<el-select v-model="pageForm.lotId" clearable filterable placeholder="请选择时间标段" style="width: 100%">
<el-option v-for="item in lotOptions" :key="item.id" :label="item.lotName" :value="item.id"></el-option>
</el-select>
</search-item>
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="线路列表">
<el-button @click="openAdd" size="medium" type="primary">
<i class="el-icon-plus"></i>
新增
</el-button>
</table-tool>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
:default-sort="{prop: 'enabled', order: 'descending'}"
:row-class-name="tableRowClassName"
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="创建年度" prop="year" width="120" sortable="custom" align="center" header-align="center"></el-table-column>
<el-table-column label="线路编号" prop="lineCode" width="140" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路名称" prop="lineName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="时间标段" prop="lotName" width="140" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路类型" prop="lineType" width="140" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="创建人" prop="creatorName" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="是否对外开放" prop="openFlag" width="160" align="center" header-align="center">
<template slot-scope="{row}">
<el-switch
v-model="row.openFlag"
active-text="是"
inactive-text="否"
@change="toggleOpenFlag(row)">
</el-switch>
</template>
</el-table-column>
<el-table-column label="激活状态" prop="enabled" width="160" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-switch
v-model="row.enabled"
active-text="启用"
inactive-text="禁用"
@change="toggleEnabled(row)">
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" width="260" align="center" header-align="center">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
<el-button size="mini" type="danger" @click="doDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
<el-dialog
:title="title"
:visible.sync="dialogVisible"
:close-on-click-modal="false"
width="72%"
@closed="destroyEditor">
<el-form :model="formData" :rules="formRules" label-width="110px" ref="form" :disabled="viewMode">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="创建年度" prop="year">
<el-date-picker
v-model="formData.year"
type="year"
value-format="yyyy"
placeholder="请选择创建年度"
style="width: 100%"
@change="yearChange">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="旅行社名称" prop="travelAgencyId">
<el-select v-model="formData.travelAgencyId" clearable filterable placeholder="请选择旅行社" style="width: 100%">
<el-option v-for="item in travelAgencyOptions" :key="item.id" :label="item.agencyName" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="线路名称" prop="lineName">
<el-input v-model="formData.lineName" maxlength="30" show-word-limit placeholder="请输入线路名称"></el-input>
</el-form-item>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="创建人">
<el-input v-model="formData.creatorName" readonly placeholder="当前登录人"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="所在单位">
<el-input v-model="formData.unitName" readonly placeholder="当前登录人所在单位"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="线路编号" prop="lineCode">
<el-input v-model="formData.lineCode" maxlength="50" placeholder="建议年度加序号,如202601"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="激活状态" prop="enabled">
<el-radio-group v-model="formData.enabled">
<el-radio :label="true">启用</el-radio>
<el-radio :label="false">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="是否对外开放" prop="openFlag">
<el-radio-group v-model="formData.openFlag">
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="直系亲属线路" prop="directFamilyUnitLine">
<el-radio-group v-model="formData.directFamilyUnitLine">
<el-radio :label="true"></el-radio>
<el-radio :label="false"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="线路类型" prop="lineType">
<el-select v-model="formData.lineType" clearable placeholder="请选择线路类型" style="width: 100%">
<el-option v-for="item in lineTypeOptions" :key="item.code" :label="item.name" :value="item.name"></el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="时间标段" prop="lotId">
<el-select v-model="formData.lotId" clearable filterable placeholder="请选择时间标段" style="width: 100%">
<el-option v-for="item in lotOptions" :key="item.id" :label="item.lotName" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="线路内容" prop="lineContent">
<text-editor v-model="formData.lineContent"></text-editor>
</el-form-item>
<el-form-item label="移动端缩略图" prop="mobileThumb">
<!-- 与旅行社管理保持一致:移动端缩略图使用单图上传并保存URL。 -->
<file-upload
style="--upload-width: 200px;--upload-height:108px"
:upload_number="1"
:upload_size="20971520"
:value.sync="formData.mobileThumb"
accept=".jpg,.jpeg,.png"
complete_result
upload_mode="image"
upload_result_category="interval">
</file-upload>
<div class="el-upload__tip">支持jpg、jpeg、png格式,大小不超过20MB。</div>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button v-if="!viewMode" type="primary" :loading="submitLoading" @click="doSubmit">保存</el-button>
</span>
</el-dialog>
</div>
<style>
.el-table .direct-family-row > td,
.el-table .direct-family-row > td .cell {
color: #f56c6c;
}
</style>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
store,
mixins: [initTableMixins],
data() {
const currentYear = moment().format("YYYY")
return {
title: "",
dialogVisible: false,
viewMode: false,
submitLoading: false,
travelAgencyOptions: [],
lotOptions: [],
lineTypeOptions: [],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "enabled",
pageOrderBy: "descending",
year: currentYear,
lineName: "",
lotId: ""
},
formData: {},
formRules: {
year: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
travelAgencyId: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
lineName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
lineCode: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
lineType: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
lotId: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
mobileThumb: [{ required: true, message: "请上传移动端缩略图", trigger: ["blur", "change"] }]
}
}
},
methods: {
resetSearch() {
this.pageForm.year = moment().format("YYYY")
this.pageForm.lineName = ""
this.pageForm.lotId = ""
this.loadOptions(this.pageForm.year)
this.doSearch()
},
tableRowClassName({ row }) {
return this.isDirectFamilyLine(row) ? "direct-family-row" : ""
},
isDirectFamilyLine(row) {
if (!row) return false
return row.directFamilyUnitLine === true
|| row.directFamilyUnitLine === 1
|| row.directFamilyUnitLine === "1"
},
emptyForm() {
const user = (this.$store && this.$store.state && this.$store.state.user) || {}
return {
year: moment().format("YYYY"),
travelAgencyId: "",
creatorUserId: user.id || "",
creatorName: user.username || "",
unitId: user.unit?.id || "",
unitName: user.unit?.name || "",
lineName: "",
lineCode: "",
enabled: true,
openFlag: true,
directFamilyUnitLine: false,
lineType: "",
lotId: "",
lineContent: "",
mobileThumb: ""
}
},
openAdd() {
this.title = "新增线路信息"
this.viewMode = false
this.formData = this.emptyForm()
this.loadOptions(this.formData.year)
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
},
openEdit(row) {
this.title = "编辑线路信息"
this.viewMode = false
this.loadDetail(row.id)
},
openView(row) {
this.title = "查看线路信息"
this.viewMode = true
this.loadDetail(row.id)
},
loadDetail(id) {
this.$axios.post(loc() + "/detail", { id }).then((res) => {
if (res.code === 0) {
this.formData = Object.assign(this.emptyForm(), res.data || {})
this.formData.year = this.formData.year ? String(this.formData.year) : ""
this.loadOptions(this.formData.year)
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
yearChange(year) {
this.formData.travelAgencyId = ""
this.formData.lotId = ""
this.loadOptions(year)
},
loadOptions(year) {
this.loadTravelAgencies(year)
this.loadLots(year)
this.loadLineTypes()
},
loadTravelAgencies(year) {
this.$axios.post(loc() + "/travelAgencyOptions", { year }).then((res) => {
if (res.code === 0) {
this.travelAgencyOptions = res.data || []
}
})
},
loadLots(year) {
this.$axios.post(loc() + "/lotOptions", { year }).then((res) => {
if (res.code === 0) {
this.lotOptions = res.data || []
}
})
},
loadLineTypes() {
this.$axios.post(loc() + "/lineTypeOptions").then((res) => {
if (res.code === 0) {
this.lineTypeOptions = res.data || []
}
})
},
doSubmit() {
this.$refs.form.validate((valid) => {
if (!valid) return
this.submitLoading = true
this.$axios.post(loc() + "/doSubmit", this.formData).then((res) => {
this.submitLoading = false
if (res.code === 0) {
this.$message.success("保存成功")
this.dialogVisible = false
this.pageData()
} else {
this.$message.warning(res.msg || "保存失败")
}
}).catch(() => {
this.submitLoading = false
})
})
},
toggleEnabled(row) {
this.$axios.post(loc() + "/doSubmit", row).then((res) => {
if (res.code === 0) {
this.$message.success("状态已更新")
} else {
row.enabled = !row.enabled
this.$message.warning(res.msg || "状态更新失败")
}
}).catch(() => {
row.enabled = !row.enabled
})
},
toggleOpenFlag(row) {
this.$axios.post(loc() + "/doSubmit", row).then((res) => {
if (res.code === 0) {
this.$message.success("对外开放状态已更新")
} else {
row.openFlag = !row.openFlag
this.$message.warning(res.msg || "对外开放状态更新失败")
}
}).catch(() => {
row.openFlag = !row.openFlag
})
},
doDelete(row) {
this.$confirm("确定删除【" + row.lineName + "】吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doDelete", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success("删除成功")
this.pageData()
} else {
this.$message.warning(res.msg || "删除失败")
}
})
})
},
destroyEditor() {
this.formData = {}
this.viewMode = false
}
},
mounted() {
this.loadOptions(this.pageForm.year)
this.pageData()
},
watch: {
"pageForm.year"(year) {
this.pageForm.lotId = ""
this.loadOptions(year)
}
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,546 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="开始年度">
<el-date-picker
v-model="pageForm.startYear"
type="year"
value-format="yyyy"
placeholder="请选择开始年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="结束年度">
<el-date-picker
v-model="pageForm.endYear"
type="year"
value-format="yyyy"
placeholder="请选择结束年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="姓名/工号">
<el-input
v-model="pageForm.keyword"
clearable
placeholder="请输入姓名或工号"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="线路">
<el-select v-model="pageForm.lineId" clearable filterable placeholder="请选择线路" style="width: 100%">
<el-option v-for="item in lineOptions" :key="item.lineId" :label="item.lineName" :value="item.lineId"></el-option>
</el-select>
</search-item>
<search-item label="出行时段">
<el-select v-model="pageForm.travelPeriod" clearable filterable placeholder="请选择出行时段" style="width: 100%">
<el-option v-for="item in travelPeriodOptions" :key="item.travelPeriod" :label="item.travelPeriod" :value="item.travelPeriod"></el-option>
</el-select>
</search-item>
<search-item label="线路类型">
<el-select v-model="pageForm.lineType" clearable filterable placeholder="请选择线路类型" style="width: 100%">
<el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option>
</el-select>
</search-item>
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="校工会审核">
<el-radio-group class="mr5" size="small" v-model="pageForm.audit" @change="changeAudit">
<el-radio-button :label="true">已审核</el-radio-button>
<el-radio-button :label="false">未审核</el-radio-button>
</el-radio-group>
</table-tool>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="unitName" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="报名线路" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="出行时段" prop="travelPeriod" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路类型" prop="lineType" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="报名时间" prop="signupTime" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="携带家属" prop="familyCount" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="Number(row.familyCount || 0) > 0 ? 'success' : 'info'">{{ row.familyCount || 0 }}人</el-tag>
</template>
</el-table-column>
<el-table-column label="报销超出费用" prop="overCostReimbursed" width="140" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="row.overCostReimbursed ? 'success' : 'info'">{{ row.overCostReimbursed ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="当前节点" prop="curTaskName" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="流程状态" prop="instanceState" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
<el-button v-if="row.taskState === 10" size="mini" type="primary" @click="openApproval(row)">审核</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #edit>
<tour-approval-info ref="infoRef">
<div v-if="showApprovalForm">
<div class="process-title">
{{ formData.taskName }}
</div>
<el-form
:model="formData"
ref="formRef"
label-width="0"
label-suffix=""
class="flow-task-form">
<el-form-item
label="审批意见"
prop="tf_opinion"
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
<user-opinion-textarea v-model="formData.tf_opinion"></user-opinion-textarea>
</el-form-item>
</el-form>
<el-row type="flex" justify="end">
<el-button @click="$refs.guava.index()" size="small">取消</el-button>
<el-button @click="handleTaskAction(6)" size="small" type="info">退回到发起人</el-button>
<el-button @click="handleTaskAction(2)" size="small" type="danger">拒绝申请</el-button>
<el-button @click="handleTaskAction(1)" size="small" type="primary">同意申请</el-button>
</el-row>
</div>
</tour-approval-info>
</template>
</guava>
</div>
<style>
#app .search .search-item {
width: calc((100% - 150px) / 4);
}
@media screen and (max-width: 1350px) {
#app .search .search-item {
width: calc((100% - 100px) / 3);
}
}
@media screen and (max-width: 1200px) {
#app .search .search-item {
width: calc((100% - 50px) / 2);
}
}
@media screen and (max-width: 992px) {
#app .search .search-item {
width: 100%;
}
}
.tour-approval-section {
margin-bottom: 14px;
}
.tour-approval-table {
width: 100%;
}
.tour-approval-empty {
color: #909399;
}
</style>
<script nonce="${cspNonce!}">
const TOUR_APPROVAL_INFO = {
template: [
"\n",
" <div>\n",
" <div class=\"process-title\">\n",
" 报名信息\n",
" <el-link type=\"primary\" @click=\"openChart\">点击查看流程图</el-link>\n",
" </div>\n",
" <el-descriptions :column=\"3\" border class=\"tour-approval-section\">\n",
" <el-descriptions-item label=\"工号\">{{ detail.jobNo || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"姓名\">{{ detail.userName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"性别\">{{ detail.gender || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"身份证号\">{{ detail.idCard || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"所在单位\">{{ detail.unitName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"所在工会\">{{ detail.unionName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"报名时间\">{{ detail.signupTime || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"报名线路\">{{ detail.lineName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"出行时段\">{{ detail.travelPeriod || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"线路类型\">{{ detail.lineType || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"报名酒店\">{{ detail.hotelName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"旅行社\">{{ detail.travelAgencyName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"是否携带家属\">\n",
" {{ familyText() }}\n",
" </el-descriptions-item>\n",
" <el-descriptions-item label=\"报销超出费用\">{{ detail.overCostReimbursed ? '是' : '否' }}</el-descriptions-item>\n",
" <el-descriptions-item v-if=\"fillBedInfo\" label=\"意向拼床人\">{{ detail.intendedRoommate || '' }}</el-descriptions-item>\n",
" <el-descriptions-item v-if=\"fillBedInfo\" label=\"床型\">{{ detail.bedType || '' }}</el-descriptions-item>\n",
" <el-descriptions-item v-if=\"fillBedInfo\" label=\"床位信息\">{{ detail.bedInfo || '' }}</el-descriptions-item>\n",
" </el-descriptions>\n",
"\n",
" <div v-if=\"isDirectFamilyLine()\" class=\"tour-approval-section\">\n",
" <div class=\"process-title\">直系亲属线路</div>\n",
" <el-descriptions :column=\"3\" border>\n",
" <el-descriptions-item label=\"亲属姓名\">{{ directRelative.relativeName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"所在单位\">{{ directRelative.unitName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"亲属关系\">{{ directRelative.relationshipName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"线路名称\">{{ directRelative.lineName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"出行开始日期\">{{ directRelative.travelStartTime || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"出行结束日期\">{{ directRelative.travelEndTime || '' }}</el-descriptions-item>\n",
" </el-descriptions>\n",
" </div>\n",
"\n",
" <div v-if=\"showFamilySection()\" class=\"tour-approval-section\">\n",
" <div class=\"process-title\">亲属信息</div>\n",
" <el-table :data=\"familyData\" border size=\"mini\" empty-text=\"暂无亲属信息\" class=\"tour-approval-table\">\n",
" <el-table-column label=\"序号\" type=\"index\" width=\"70\" align=\"center\" header-align=\"center\"></el-table-column>\n",
" <el-table-column label=\"家属姓名\" prop=\"familyName\" min-width=\"120\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column label=\"性别\" prop=\"gender\" width=\"90\" align=\"center\" header-align=\"center\"></el-table-column>\n",
" <el-table-column label=\"身份证号码\" prop=\"idCard\" min-width=\"180\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column label=\"年龄\" prop=\"age\" width=\"90\" align=\"center\" header-align=\"center\"></el-table-column>\n",
" <el-table-column label=\"关系\" prop=\"relationship\" width=\"110\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column v-if=\"fillBedInfo\" label=\"床型\" prop=\"bedType\" width=\"110\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column v-if=\"fillBedInfo\" label=\"床位\" prop=\"bedInfo\" min-width=\"120\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column v-if=\"fillBedInfo\" label=\"意向拼床人\" prop=\"intendedRoommate\" min-width=\"130\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" </el-table>\n",
" </div>\n",
"\n",
" <template v-for=\"task in doneTasks\">\n",
" <div class=\"mt10\">\n",
" <div class=\"process-title\">{{ task.displayName }}</div>\n",
" <el-descriptions border class=\"flow-task-form\" :column=\"3\" :key=\"task.id\" v-if=\"task.ext.isFirstTaskNode\">\n",
" <el-descriptions-item label=\"申请用户\">\n",
" {{ task.ext.initiatorName }}({{ task.ext.initiatorAccount }})\n",
" </el-descriptions-item>\n",
" <el-descriptions-item label=\"申请时间\">{{ task.finishTime }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"办理结果\">\n",
" <dict-tag :options=\"dict.type.PROCESS_TASK_SUBMIT_TYPE\" :value=\"task.ext.submitType\"></dict-tag>\n",
" </el-descriptions-item>\n",
" </el-descriptions>\n",
"\n",
" <el-descriptions border class=\"flow-task-form\" :column=\"3\" :key=\"task.id\" v-else>\n",
" <el-descriptions-item label=\"办理用户\">\n",
" {{ task.taskFormData.userName }}({{ task.taskFormData.loginName }})\n",
" </el-descriptions-item>\n",
" <el-descriptions-item label=\"办理时间\">{{ task.finishTime }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"办理结果\">\n",
" <dict-tag :options=\"dict.type.PROCESS_TASK_SUBMIT_TYPE\" :value=\"task.ext.submitType\"></dict-tag>\n",
" </el-descriptions-item>\n",
" <el-descriptions-item label=\"办理意见\" :span=\"3\">\n",
" {{ task.taskFormData.opinion || task.taskFormData.tf_opinion || '' }}\n",
" </el-descriptions-item>\n",
" </el-descriptions>\n",
" </div>\n",
" </template>\n",
"\n",
" <slot></slot>\n",
"\n",
" <snaker-chart ref=\"snakerChartRef\"></snaker-chart>\n",
" </div>\n",
" \n"
].join(""),
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
return {
row: {},
detail: {},
familyData: [],
directRelative: {},
allowFamily: false,
fillBedInfo: true,
directFamilyUnitLine: false,
doneTasks: []
}
},
methods: {
onOpen(row) {
this.row = row || {}
this.detail = {}
this.familyData = []
this.directRelative = {}
this.allowFamily = false
this.fillBedInfo = true
this.directFamilyUnitLine = false
this.doneTasks = []
this.info()
this.getDoneTasks()
},
info() {
this.$axios.post(loc() + "/detail", { id: this.row.id }).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.detail = data.ledger || {}
this.detail.travelPeriod = data.travelPeriod || this.row.travelPeriod || ""
this.familyData = data.families || []
this.directRelative = data.directRelative || {}
this.allowFamily = this.toBoolean(data.allowFamily)
this.fillBedInfo = data.fillBedInfo === undefined || data.fillBedInfo === null || this.toBoolean(data.fillBedInfo)
this.directFamilyUnitLine = this.toBoolean(data.directFamilyUnitLine)
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
toBoolean(value) {
return value === true || value === 1 || value === "1"
},
isDirectFamilyLine() {
return this.directFamilyUnitLine
|| this.toBoolean(this.detail.directFamilyUnitLine)
|| (this.directRelative && this.directRelative.id)
},
hasFamily() {
return this.toBoolean(this.detail.hasFamily) || this.familyData.length > 0
},
showFamilySection() {
return this.allowFamily && this.hasFamily() && !this.isDirectFamilyLine()
},
familyText() {
if (this.isDirectFamilyLine()) {
return "否"
}
if (!this.allowFamily) {
return "否"
}
return this.hasFamily() ? "是" : "否"
},
getDoneTasks() {
this.$axios.post("/flow/common/doneTasks", { bizId: this.row.id }).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data || []
}
})
},
openChart() {
if (!this.row.instanceProcessDefineId || !this.row.instanceId) {
this.$message.warning("暂无流程图信息")
return
}
this.$refs.snakerChartRef.onOpenFull(this.row.instanceProcessDefineId, this.row.instanceId)
}
}
}
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
const currentYear = moment().format("YYYY")
return {
unionOptions: [],
lineOptions: [],
travelPeriodOptions: [],
lineTypeOptions: [],
showApprovalForm: false,
formData: {
tf_opinion: ""
},
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "signupTime",
pageOrderBy: "descending",
audit: false,
startYear: currentYear,
endYear: currentYear,
keyword: "",
unionId: "",
lineId: "",
travelPeriod: "",
lineType: ""
}
}
},
methods: {
resetSearch() {
this.pageForm.startYear = moment().format("YYYY")
this.pageForm.endYear = moment().format("YYYY")
this.pageForm.keyword = ""
this.pageForm.unionId = ""
this.pageForm.lineId = ""
this.pageForm.travelPeriod = ""
this.pageForm.lineType = ""
this.loadOptions()
this.doSearch()
},
changeAudit() {
this.pageForm.pageNumber = 1
this.clearCascadeFilters()
this.loadOptions()
this.pageData()
},
clearCascadeFilters() {
this.pageForm.unionId = ""
this.pageForm.lineId = ""
this.pageForm.travelPeriod = ""
this.pageForm.lineType = ""
},
openView(row) {
this.$refs.guava.edit(() => {
this.showApprovalForm = false
this.formData = {
tf_opinion: ""
}
this.$refs.infoRef.onOpen(row)
})
},
openApproval(row) {
this.$refs.guava.edit(() => {
this.showApprovalForm = true
this.formData = {
processTaskId: row.taskId,
taskName: row.curTaskName || row.taskName,
tf_opinion: ""
}
this.$refs.infoRef.onOpen(row)
})
},
handleTaskAction(val) {
this.$refs.formRef.validate((valid) => {
if (!valid) return
this.$confirm("您确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
const loading = createLoading()
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: val
})
}).then((res) => {
if (res.code === 0) {
this.$refs.guava.index()
this.$message.success(res.msg || "操作成功")
this.doSearch()
} else {
this.$message.warning(res.msg || "操作失败")
}
}).finally(() => {
loading.close()
})
}).catch(() => {})
})
},
loadOptions() {
this.loadUnionOptions()
this.loadLineOptions()
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
},
baseOptionParams() {
return {
audit: this.pageForm.audit,
startYear: this.pageForm.startYear,
endYear: this.pageForm.endYear,
keyword: this.pageForm.keyword,
unionId: this.pageForm.unionId,
lineId: this.pageForm.lineId,
travelPeriod: this.pageForm.travelPeriod,
lineType: this.pageForm.lineType
}
},
loadUnionOptions() {
this.$axios.post(loc() + "/unionOptions", this.baseOptionParams()).then((res) => {
if (res.code === 0) {
this.unionOptions = res.data || []
if (this.pageForm.unionId && !this.unionOptions.some(item => item.id === this.pageForm.unionId)) {
this.pageForm.unionId = ""
}
}
})
},
loadLineOptions() {
this.$axios.post(loc() + "/lineOptions", this.baseOptionParams()).then((res) => {
if (res.code === 0) {
this.lineOptions = res.data || []
if (this.pageForm.lineId && !this.lineOptions.some(item => item.lineId === this.pageForm.lineId)) {
this.pageForm.lineId = ""
}
}
})
},
loadTravelPeriodOptions() {
this.$axios.post(loc() + "/travelPeriodOptions", this.baseOptionParams()).then((res) => {
if (res.code === 0) {
this.travelPeriodOptions = res.data || []
if (this.pageForm.travelPeriod && !this.travelPeriodOptions.some(item => item.travelPeriod === this.pageForm.travelPeriod)) {
this.pageForm.travelPeriod = ""
}
}
})
},
loadLineTypeOptions() {
this.$axios.post(loc() + "/lineTypeOptions", this.baseOptionParams()).then((res) => {
if (res.code === 0) {
this.lineTypeOptions = res.data || []
if (this.pageForm.lineType && !this.lineTypeOptions.some(item => item.lineType === this.pageForm.lineType)) {
this.pageForm.lineType = ""
}
}
})
}
},
mounted() {
this.loadOptions()
this.pageData()
},
components: {
"tour-approval-info": TOUR_APPROVAL_INFO
},
watch: {
"pageForm.startYear"() {
this.clearCascadeFilters()
this.loadOptions()
},
"pageForm.endYear"() {
this.clearCascadeFilters()
this.loadOptions()
},
"pageForm.lineId"() {
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
},
"pageForm.travelPeriod"() {
this.loadLineOptions()
this.loadLineTypeOptions()
},
"pageForm.lineType"() {
this.loadLineOptions()
this.loadTravelPeriodOptions()
},
"pageForm.unionId"() {
this.loadLineOptions()
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
}
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,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>
<!--#
}
#-->
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,338 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="年度">
<el-date-picker
v-model="pageForm.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="旅行社名称">
<el-input
v-model="pageForm.agencyName"
clearable
placeholder="请输入旅行社名称"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="联系人">
<el-input
v-model="pageForm.contactName"
clearable
placeholder="请输入联系人"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="联系电话">
<el-input
v-model="pageForm.contactPhone"
clearable
placeholder="请输入联系电话"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="旅行社列表">
<el-button @click="openAdd" size="medium" type="primary">
<i class="el-icon-plus"></i>
新增
</el-button>
</table-tool>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="年度" prop="year" width="110" sortable="custom" align="center" header-align="center"></el-table-column>
<el-table-column label="旅行社编号" prop="agencyCode" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="旅行社名称" prop="agencyName" min-width="220" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="联系人" prop="contactName" width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="联系电话" prop="contactPhone" width="150" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="激活状态" prop="enabled" width="160" align="center" header-align="center">
<template slot-scope="{row}">
<el-switch
v-model="row.enabled"
active-text="启用"
inactive-text="禁用"
@change="toggleEnabled(row)">
</el-switch>
</template>
</el-table-column>
<el-table-column label="操作" width="260" align="center" header-align="center">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
<el-button size="mini" type="primary" @click="openEdit(row)">编辑</el-button>
<el-button size="mini" type="danger" @click="doDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
<el-dialog
:title="title"
:visible.sync="dialogVisible"
:close-on-click-modal="false"
width="72%"
@closed="destroyForm">
<el-form :model="formData" :rules="formRules" label-width="110px" ref="form" :disabled="viewMode">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="年度" prop="year">
<el-date-picker
v-model="formData.year"
type="year"
value-format="yyyy"
placeholder="请选择年度"
style="width: 100%">
</el-date-picker>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="旅行社名称" prop="agencyName">
<el-input v-model="formData.agencyName" maxlength="30" show-word-limit placeholder="请输入旅行社名称"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="旅行社编号" prop="agencyCode">
<el-input v-model="formData.agencyCode" maxlength="50" placeholder="请输入旅行社编号"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="激活状态" prop="enabled">
<el-radio-group v-model="formData.enabled">
<el-radio :label="true">启用</el-radio>
<el-radio :label="false">禁用</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="联系人" prop="contactName">
<el-input v-model="formData.contactName" maxlength="10" show-word-limit placeholder="请输入联系人"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="联系人手机" prop="contactPhone">
<el-input v-model="formData.contactPhone" maxlength="30" placeholder="请输入联系人手机"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="邮箱" prop="email">
<el-input v-model="formData.email" maxlength="100" placeholder="请输入邮箱"></el-input>
</el-form-item>
</el-col>
</el-row>
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" type="textarea" maxlength="100" :rows="3" show-word-limit placeholder="请输入备注"></el-input>
</el-form-item>
<el-form-item label="移动端缩略图" prop="mobileThumb">
<!-- 复用课程管理的封面上传逻辑:单图上传,保存URL,供移动端列表展示使用。 -->
<file-upload
style="--upload-width: 200px;--upload-height:108px"
:upload_number="1"
:upload_size="20971520"
:value.sync="formData.mobileThumb"
accept=".jpg,.jpeg,.png"
complete_result
upload_mode="image"
upload_result_category="interval">
</file-upload>
<div class="el-upload__tip">支持jpg、jpeg、png格式,大小不超过20MB。</div>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button v-if="!viewMode" type="primary" :loading="subDis" @click="doSubmit">保存</el-button>
</span>
</el-dialog>
</div>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
const currentYear = moment().format("YYYY")
const validateMobile = (rule, value, callback) => {
const mobileReg = /^1[3-9]\d{9}$/
if (!value) {
callback(new Error("必填"))
} else if (!mobileReg.test(value)) {
callback(new Error("手机号格式不正确"))
} else {
callback()
}
}
const validateEmail = (rule, value, callback) => {
const emailReg = /^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$/
if (!value) {
callback(new Error("必填"))
} else if (!emailReg.test(value)) {
callback(new Error("邮箱格式不正确"))
} else {
callback()
}
}
return {
title: "",
dialogVisible: false,
viewMode: false,
subDis: false,
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
year: currentYear,
agencyName: "",
contactName: "",
contactPhone: ""
},
formData: {},
formRules: {
year: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
agencyName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
agencyCode: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
contactName: [{ required: true, message: "必填", trigger: ["blur", "change"] }],
contactPhone: [{ validator: validateMobile, required: true, trigger: ["blur", "change"] }],
email: [{ validator: validateEmail, required: true, trigger: ["blur", "change"] }]
}
}
},
methods: {
resetSearch() {
this.pageForm.year = moment().format("YYYY")
this.pageForm.agencyName = ""
this.pageForm.contactName = ""
this.pageForm.contactPhone = ""
this.doSearch()
},
emptyForm() {
return {
year: moment().format("YYYY"),
agencyName: "",
agencyCode: "",
contactName: "",
contactPhone: "",
email: "",
remark: "",
mobileThumb: "",
enabled: true
}
},
openAdd() {
this.title = "新增旅行社信息"
this.viewMode = false
this.formData = this.emptyForm()
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
},
openEdit(row) {
this.title = "编辑旅行社信息"
this.viewMode = false
this.loadDetail(row.id)
},
openView(row) {
this.title = "查看旅行社信息"
this.viewMode = true
this.loadDetail(row.id)
},
loadDetail(id) {
this.$axios.post(loc() + "/detail", { id }).then((res) => {
if (res.code === 0) {
this.formData = Object.assign(this.emptyForm(), res.data || {})
this.formData.year = this.formData.year ? String(this.formData.year) : ""
this.dialogVisible = true
this.$nextTick(() => this.$refs.form && this.$refs.form.clearValidate())
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
doSubmit() {
this.$refs.form.validate((valid) => {
if (!valid) return
this.subDis = true
this.$axios.post(loc() + "/doSubmit", this.formData).then((res) => {
this.subDis = false
if (res.code === 0) {
this.$message.success("保存成功")
this.dialogVisible = false
this.pageData()
} else {
this.$message.warning(res.msg || "保存失败")
}
}).catch(() => {
this.subDis = false
})
})
},
toggleEnabled(row) {
this.$axios.post(loc() + "/doSubmit", row).then((res) => {
if (res.code === 0) {
this.$message.success("状态已更新")
} else {
row.enabled = !row.enabled
this.$message.warning(res.msg || "状态更新失败")
}
}).catch(() => {
row.enabled = !row.enabled
})
},
doDelete(row) {
this.$confirm("确定删除【" + row.agencyName + "】吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doDelete", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success("删除成功")
this.pageData()
} else {
this.$message.warning(res.msg || "删除失败")
}
})
})
},
destroyForm() {
this.formData = {}
this.viewMode = false
}
},
mounted() {
this.pageData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,546 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="开始年度">
<el-date-picker
v-model="pageForm.startYear"
type="year"
value-format="yyyy"
placeholder="请选择开始年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="结束年度">
<el-date-picker
v-model="pageForm.endYear"
type="year"
value-format="yyyy"
placeholder="请选择结束年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="姓名/工号">
<el-input
v-model="pageForm.keyword"
clearable
placeholder="请输入姓名或工号"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="线路">
<el-select v-model="pageForm.lineId" clearable filterable placeholder="请选择线路" style="width: 100%">
<el-option v-for="item in lineOptions" :key="item.lineId" :label="item.lineName" :value="item.lineId"></el-option>
</el-select>
</search-item>
<search-item label="出行时段">
<el-select v-model="pageForm.travelPeriod" clearable filterable placeholder="请选择出行时段" style="width: 100%">
<el-option v-for="item in travelPeriodOptions" :key="item.travelPeriod" :label="item.travelPeriod" :value="item.travelPeriod"></el-option>
</el-select>
</search-item>
<search-item label="线路类型">
<el-select v-model="pageForm.lineType" clearable filterable placeholder="请选择线路类型" style="width: 100%">
<el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option>
</el-select>
</search-item>
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<table-tool :app="this" label="分工会审核">
<el-radio-group class="mr5" size="small" v-model="pageForm.audit" @change="changeAudit">
<el-radio-button :label="true">已审核</el-radio-button>
<el-radio-button :label="false">未审核</el-radio-button>
</el-radio-group>
</table-tool>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="unitName" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="报名线路" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="出行时段" prop="travelPeriod" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路类型" prop="lineType" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="报名时间" prop="signupTime" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="携带家属" prop="familyCount" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="Number(row.familyCount || 0) > 0 ? 'success' : 'info'">{{ row.familyCount || 0 }}人</el-tag>
</template>
</el-table-column>
<el-table-column label="报销超出费用" prop="overCostReimbursed" width="140" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="row.overCostReimbursed ? 'success' : 'info'">{{ row.overCostReimbursed ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="当前节点" prop="curTaskName" width="140" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="流程状态" prop="instanceState" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<enum-tag :value="row.instanceState" name="ProcessInstanceStateEnum" label_key="message" size="small"></enum-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
<el-button v-if="row.taskState === 10" size="mini" type="primary" @click="openApproval(row)">审核</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
<template #edit>
<tour-approval-info ref="infoRef">
<div v-if="showApprovalForm">
<div class="process-title">
{{ formData.taskName }}
</div>
<el-form
:model="formData"
ref="formRef"
label-width="0"
label-suffix=""
class="flow-task-form">
<el-form-item
label="审批意见"
prop="tf_opinion"
:rules="[{required:true,message:'必填',trigger:['change','blur']}]">
<user-opinion-textarea v-model="formData.tf_opinion"></user-opinion-textarea>
</el-form-item>
</el-form>
<el-row type="flex" justify="end">
<el-button @click="$refs.guava.index()" size="small">取消</el-button>
<el-button @click="handleTaskAction(6)" size="small" type="info">退回到发起人</el-button>
<el-button @click="handleTaskAction(2)" size="small" type="danger">拒绝申请</el-button>
<el-button @click="handleTaskAction(1)" size="small" type="primary">同意申请</el-button>
</el-row>
</div>
</tour-approval-info>
</template>
</guava>
</div>
<style>
#app .search .search-item {
width: calc((100% - 150px) / 4);
}
@media screen and (max-width: 1350px) {
#app .search .search-item {
width: calc((100% - 100px) / 3);
}
}
@media screen and (max-width: 1200px) {
#app .search .search-item {
width: calc((100% - 50px) / 2);
}
}
@media screen and (max-width: 992px) {
#app .search .search-item {
width: 100%;
}
}
.tour-approval-section {
margin-bottom: 14px;
}
.tour-approval-table {
width: 100%;
}
.tour-approval-empty {
color: #909399;
}
</style>
<script nonce="${cspNonce!}">
const TOUR_APPROVAL_INFO = {
template: [
"\n",
" <div>\n",
" <div class=\"process-title\">\n",
" 报名信息\n",
" <el-link type=\"primary\" @click=\"openChart\">点击查看流程图</el-link>\n",
" </div>\n",
" <el-descriptions :column=\"3\" border class=\"tour-approval-section\">\n",
" <el-descriptions-item label=\"工号\">{{ detail.jobNo || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"姓名\">{{ detail.userName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"性别\">{{ detail.gender || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"身份证号\">{{ detail.idCard || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"所在单位\">{{ detail.unitName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"所在工会\">{{ detail.unionName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"报名时间\">{{ detail.signupTime || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"报名线路\">{{ detail.lineName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"出行时段\">{{ detail.travelPeriod || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"线路类型\">{{ detail.lineType || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"报名酒店\">{{ detail.hotelName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"旅行社\">{{ detail.travelAgencyName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"是否携带家属\">\n",
" {{ familyText() }}\n",
" </el-descriptions-item>\n",
" <el-descriptions-item label=\"报销超出费用\">{{ detail.overCostReimbursed ? '是' : '否' }}</el-descriptions-item>\n",
" <el-descriptions-item v-if=\"fillBedInfo\" label=\"意向拼床人\">{{ detail.intendedRoommate || '' }}</el-descriptions-item>\n",
" <el-descriptions-item v-if=\"fillBedInfo\" label=\"床型\">{{ detail.bedType || '' }}</el-descriptions-item>\n",
" <el-descriptions-item v-if=\"fillBedInfo\" label=\"床位信息\">{{ detail.bedInfo || '' }}</el-descriptions-item>\n",
" </el-descriptions>\n",
"\n",
" <div v-if=\"isDirectFamilyLine()\" class=\"tour-approval-section\">\n",
" <div class=\"process-title\">直系亲属线路</div>\n",
" <el-descriptions :column=\"3\" border>\n",
" <el-descriptions-item label=\"亲属姓名\">{{ directRelative.relativeName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"所在单位\">{{ directRelative.unitName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"亲属关系\">{{ directRelative.relationshipName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"线路名称\">{{ directRelative.lineName || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"出行开始日期\">{{ directRelative.travelStartTime || '' }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"出行结束日期\">{{ directRelative.travelEndTime || '' }}</el-descriptions-item>\n",
" </el-descriptions>\n",
" </div>\n",
"\n",
" <div v-if=\"showFamilySection()\" class=\"tour-approval-section\">\n",
" <div class=\"process-title\">亲属信息</div>\n",
" <el-table :data=\"familyData\" border size=\"mini\" empty-text=\"暂无亲属信息\" class=\"tour-approval-table\">\n",
" <el-table-column label=\"序号\" type=\"index\" width=\"70\" align=\"center\" header-align=\"center\"></el-table-column>\n",
" <el-table-column label=\"家属姓名\" prop=\"familyName\" min-width=\"120\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column label=\"性别\" prop=\"gender\" width=\"90\" align=\"center\" header-align=\"center\"></el-table-column>\n",
" <el-table-column label=\"身份证号码\" prop=\"idCard\" min-width=\"180\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column label=\"年龄\" prop=\"age\" width=\"90\" align=\"center\" header-align=\"center\"></el-table-column>\n",
" <el-table-column label=\"关系\" prop=\"relationship\" width=\"110\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column v-if=\"fillBedInfo\" label=\"床型\" prop=\"bedType\" width=\"110\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column v-if=\"fillBedInfo\" label=\"床位\" prop=\"bedInfo\" min-width=\"120\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" <el-table-column v-if=\"fillBedInfo\" label=\"意向拼床人\" prop=\"intendedRoommate\" min-width=\"130\" align=\"center\" header-align=\"center\" show-overflow-tooltip></el-table-column>\n",
" </el-table>\n",
" </div>\n",
"\n",
" <template v-for=\"task in doneTasks\">\n",
" <div class=\"mt10\">\n",
" <div class=\"process-title\">{{ task.displayName }}</div>\n",
" <el-descriptions border class=\"flow-task-form\" :column=\"3\" :key=\"task.id\" v-if=\"task.ext.isFirstTaskNode\">\n",
" <el-descriptions-item label=\"申请用户\">\n",
" {{ task.ext.initiatorName }}({{ task.ext.initiatorAccount }})\n",
" </el-descriptions-item>\n",
" <el-descriptions-item label=\"申请时间\">{{ task.finishTime }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"办理结果\">\n",
" <dict-tag :options=\"dict.type.PROCESS_TASK_SUBMIT_TYPE\" :value=\"task.ext.submitType\"></dict-tag>\n",
" </el-descriptions-item>\n",
" </el-descriptions>\n",
"\n",
" <el-descriptions border class=\"flow-task-form\" :column=\"3\" :key=\"task.id\" v-else>\n",
" <el-descriptions-item label=\"办理用户\">\n",
" {{ task.taskFormData.userName }}({{ task.taskFormData.loginName }})\n",
" </el-descriptions-item>\n",
" <el-descriptions-item label=\"办理时间\">{{ task.finishTime }}</el-descriptions-item>\n",
" <el-descriptions-item label=\"办理结果\">\n",
" <dict-tag :options=\"dict.type.PROCESS_TASK_SUBMIT_TYPE\" :value=\"task.ext.submitType\"></dict-tag>\n",
" </el-descriptions-item>\n",
" <el-descriptions-item label=\"办理意见\" :span=\"3\">\n",
" {{ task.taskFormData.opinion || task.taskFormData.tf_opinion || '' }}\n",
" </el-descriptions-item>\n",
" </el-descriptions>\n",
" </div>\n",
" </template>\n",
"\n",
" <slot></slot>\n",
"\n",
" <snaker-chart ref=\"snakerChartRef\"></snaker-chart>\n",
" </div>\n",
" \n"
].join(""),
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
return {
row: {},
detail: {},
familyData: [],
directRelative: {},
allowFamily: false,
fillBedInfo: true,
directFamilyUnitLine: false,
doneTasks: []
}
},
methods: {
onOpen(row) {
this.row = row || {}
this.detail = {}
this.familyData = []
this.directRelative = {}
this.allowFamily = false
this.fillBedInfo = true
this.directFamilyUnitLine = false
this.doneTasks = []
this.info()
this.getDoneTasks()
},
info() {
this.$axios.post(loc() + "/detail", { id: this.row.id }).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.detail = data.ledger || {}
this.detail.travelPeriod = data.travelPeriod || this.row.travelPeriod || ""
this.familyData = data.families || []
this.directRelative = data.directRelative || {}
this.allowFamily = this.toBoolean(data.allowFamily)
this.fillBedInfo = data.fillBedInfo === undefined || data.fillBedInfo === null || this.toBoolean(data.fillBedInfo)
this.directFamilyUnitLine = this.toBoolean(data.directFamilyUnitLine)
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
toBoolean(value) {
return value === true || value === 1 || value === "1"
},
isDirectFamilyLine() {
return this.directFamilyUnitLine
|| this.toBoolean(this.detail.directFamilyUnitLine)
|| (this.directRelative && this.directRelative.id)
},
hasFamily() {
return this.toBoolean(this.detail.hasFamily) || this.familyData.length > 0
},
showFamilySection() {
return this.allowFamily && this.hasFamily() && !this.isDirectFamilyLine()
},
familyText() {
if (this.isDirectFamilyLine()) {
return "否"
}
if (!this.allowFamily) {
return "否"
}
return this.hasFamily() ? "是" : "否"
},
getDoneTasks() {
this.$axios.post("/flow/common/doneTasks", { bizId: this.row.id }).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data || []
}
})
},
openChart() {
if (!this.row.instanceProcessDefineId || !this.row.instanceId) {
this.$message.warning("暂无流程图信息")
return
}
this.$refs.snakerChartRef.onOpenFull(this.row.instanceProcessDefineId, this.row.instanceId)
}
}
}
new Vue({
el: "#app",
mixins: [initTableMixins],
data() {
const currentYear = moment().format("YYYY")
return {
unionOptions: [],
lineOptions: [],
travelPeriodOptions: [],
lineTypeOptions: [],
showApprovalForm: false,
formData: {
tf_opinion: ""
},
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "signupTime",
pageOrderBy: "descending",
audit: false,
startYear: currentYear,
endYear: currentYear,
keyword: "",
unionId: "",
lineId: "",
travelPeriod: "",
lineType: ""
}
}
},
methods: {
resetSearch() {
this.pageForm.startYear = moment().format("YYYY")
this.pageForm.endYear = moment().format("YYYY")
this.pageForm.keyword = ""
this.pageForm.unionId = ""
this.pageForm.lineId = ""
this.pageForm.travelPeriod = ""
this.pageForm.lineType = ""
this.loadOptions()
this.doSearch()
},
changeAudit() {
this.pageForm.pageNumber = 1
this.clearCascadeFilters()
this.loadOptions()
this.pageData()
},
clearCascadeFilters() {
this.pageForm.unionId = ""
this.pageForm.lineId = ""
this.pageForm.travelPeriod = ""
this.pageForm.lineType = ""
},
openView(row) {
this.$refs.guava.edit(() => {
this.showApprovalForm = false
this.formData = {
tf_opinion: ""
}
this.$refs.infoRef.onOpen(row)
})
},
openApproval(row) {
this.$refs.guava.edit(() => {
this.showApprovalForm = true
this.formData = {
processTaskId: row.taskId,
taskName: row.curTaskName || row.taskName,
tf_opinion: ""
}
this.$refs.infoRef.onOpen(row)
})
},
handleTaskAction(val) {
this.$refs.formRef.validate((valid) => {
if (!valid) return
this.$confirm("您确定要提交吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
const loading = createLoading()
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: val
})
}).then((res) => {
if (res.code === 0) {
this.$refs.guava.index()
this.$message.success(res.msg || "操作成功")
this.doSearch()
} else {
this.$message.warning(res.msg || "操作失败")
}
}).finally(() => {
loading.close()
})
}).catch(() => {})
})
},
loadOptions() {
this.loadUnionOptions()
this.loadLineOptions()
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
},
baseOptionParams() {
return {
audit: this.pageForm.audit,
startYear: this.pageForm.startYear,
endYear: this.pageForm.endYear,
keyword: this.pageForm.keyword,
unionId: this.pageForm.unionId,
lineId: this.pageForm.lineId,
travelPeriod: this.pageForm.travelPeriod,
lineType: this.pageForm.lineType
}
},
loadUnionOptions() {
this.$axios.post(loc() + "/unionOptions", this.baseOptionParams()).then((res) => {
if (res.code === 0) {
this.unionOptions = res.data || []
if (this.pageForm.unionId && !this.unionOptions.some(item => item.id === this.pageForm.unionId)) {
this.pageForm.unionId = ""
}
}
})
},
loadLineOptions() {
this.$axios.post(loc() + "/lineOptions", this.baseOptionParams()).then((res) => {
if (res.code === 0) {
this.lineOptions = res.data || []
if (this.pageForm.lineId && !this.lineOptions.some(item => item.lineId === this.pageForm.lineId)) {
this.pageForm.lineId = ""
}
}
})
},
loadTravelPeriodOptions() {
this.$axios.post(loc() + "/travelPeriodOptions", this.baseOptionParams()).then((res) => {
if (res.code === 0) {
this.travelPeriodOptions = res.data || []
if (this.pageForm.travelPeriod && !this.travelPeriodOptions.some(item => item.travelPeriod === this.pageForm.travelPeriod)) {
this.pageForm.travelPeriod = ""
}
}
})
},
loadLineTypeOptions() {
this.$axios.post(loc() + "/lineTypeOptions", this.baseOptionParams()).then((res) => {
if (res.code === 0) {
this.lineTypeOptions = res.data || []
if (this.pageForm.lineType && !this.lineTypeOptions.some(item => item.lineType === this.pageForm.lineType)) {
this.pageForm.lineType = ""
}
}
})
}
},
mounted() {
this.loadOptions()
this.pageData()
},
components: {
"tour-approval-info": TOUR_APPROVAL_INFO
},
watch: {
"pageForm.startYear"() {
this.clearCascadeFilters()
this.loadOptions()
},
"pageForm.endYear"() {
this.clearCascadeFilters()
this.loadOptions()
},
"pageForm.lineId"() {
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
},
"pageForm.travelPeriod"() {
this.loadLineOptions()
this.loadLineTypeOptions()
},
"pageForm.lineType"() {
this.loadLineOptions()
this.loadTravelPeriodOptions()
},
"pageForm.unionId"() {
this.loadLineOptions()
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
}
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,609 @@
<!--#
layout("/layouts/platform.html"){
#-->
<div id="app" v-cloak>
<guava ref="guava">
<el-card shadow="never">
<search :is-search-button="false">
<search-item label="开始年度">
<el-date-picker
v-model="pageForm.startYear"
type="year"
value-format="yyyy"
placeholder="请选择开始年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="结束年度">
<el-date-picker
v-model="pageForm.endYear"
type="year"
value-format="yyyy"
placeholder="请选择结束年度"
style="width: 100%">
</el-date-picker>
</search-item>
<search-item label="姓名/工号">
<el-input
v-model="pageForm.keyword"
clearable
placeholder="请输入姓名或工号"
style="width: 100%"
@keyup.enter.native="doSearch">
</el-input>
</search-item>
<search-item label="所属工会">
<el-select v-model="pageForm.unionId" clearable filterable placeholder="请选择所属工会" style="width: 100%">
<el-option v-for="item in unionOptions" :key="item.id" :label="item.name" :value="item.id"></el-option>
</el-select>
</search-item>
<search-item label="线路">
<el-select v-model="pageForm.lineId" clearable filterable placeholder="请选择线路" style="width: 100%">
<el-option v-for="item in lineOptions" :key="item.lineId" :label="item.lineName" :value="item.lineId"></el-option>
</el-select>
</search-item>
<search-item label="出行时段">
<el-select v-model="pageForm.travelPeriod" clearable filterable placeholder="请选择出行时段" style="width: 100%">
<el-option v-for="item in travelPeriodOptions" :key="item.travelPeriod" :label="item.travelPeriod" :value="item.travelPeriod"></el-option>
</el-select>
</search-item>
<search-item label="线路类型">
<el-select v-model="pageForm.lineType" clearable filterable placeholder="请选择线路类型" style="width: 100%">
<el-option v-for="item in lineTypeOptions" :key="item.lineType" :label="item.lineType" :value="item.lineType"></el-option>
</el-select>
</search-item>
<div class="search-query">
<el-button size="medium" type="primary" icon="el-icon-search" @click="doSearch">搜索</el-button>
<el-button size="medium" @click="resetSearch">重置</el-button>
</div>
</search>
</el-card>
<el-card class="mt10" shadow="never">
<div class="tour-ledger-toolbar">
<div class="tour-ledger-toolbar-title">
<table-tool :app="this" label="分工会台账"></table-tool>
</div>
<div class="tour-ledger-scope">
<el-button
class="tour-scope-btn"
size="medium"
:class="{'is-active': pageForm.scopeType === 'ownUnionJoined'}"
@click="setScopeType('ownUnionJoined')">
本工会参加疗休养教职工({{ summaryStats.ownUnionJoinedStaffCount || 0 }}
</el-button>
<el-button
class="tour-scope-btn"
size="medium"
:class="{'is-active': pageForm.scopeType === 'organizedLineJoined'}"
@click="setScopeType('organizedLineJoined')">
参加本工会组织线路人员(含其他工会及家属)({{ summaryStats.organizedLineJoinedTotalCount || 0 }}
</el-button>
<el-button
class="tour-scope-btn"
size="medium"
:class="{'is-active': pageForm.overCostOnly}"
@click="setOverCostOnly">
超出费用由单位承担
</el-button>
<el-button
v-if="pageForm.overCostOnly"
type="primary"
size="medium"
icon="el-icon-download"
@click="downloadOverCostSummary">
导出
</el-button>
</div>
</div>
<el-table
v-loading="tableLoading"
:data="tableData"
:size="tableSize"
border
@sort-change="pageOrder">
<el-table-column label="序号" type="index" :index="indexMethod" width="80" align="center" header-align="center"></el-table-column>
<el-table-column label="工号" prop="jobNo" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="姓名" prop="userName" width="120" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="所在单位" prop="unitName" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="报名线路" prop="lineName" min-width="220" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="出行时段" prop="travelPeriod" min-width="260" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="线路类型" prop="lineType" width="130" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="报名时间" prop="signupTime" width="170" sortable="custom" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="携带家属" prop="familyCount" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="Number(row.familyCount || 0) > 0 ? 'success' : 'info'">{{ row.familyCount || 0 }}人</el-tag>
</template>
</el-table-column>
<el-table-column label="是否参加" prop="joined" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="row.joined ? 'success' : 'info'">{{ row.joined ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="报销超出费用" prop="overCostReimbursed" width="140" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="row.overCostReimbursed ? 'success' : 'info'">{{ row.overCostReimbursed ? '是' : '否' }}</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="160" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button size="mini" type="primary" @click="openView(row)">查看</el-button>
<el-button size="mini" type="danger" @click="doDelete(row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<!--#include("/layouts/pagination.html"){}#-->
</el-card>
</guava>
<el-dialog
title="台账详情"
:visible.sync="detailVisible"
:close-on-click-modal="false"
width="72%">
<div class="tour-ledger-section">
<div class="tour-ledger-title">教职工信息</div>
<el-descriptions :column="3" border size="medium">
<el-descriptions-item label="工号">{{ detail.jobNo || '' }}</el-descriptions-item>
<el-descriptions-item label="姓名">{{ detail.userName || '' }}</el-descriptions-item>
<el-descriptions-item label="性别">{{ detail.gender || '' }}</el-descriptions-item>
<el-descriptions-item label="身份证号">{{ detail.idCard || '' }}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{ detail.unitName || '' }}</el-descriptions-item>
<el-descriptions-item label="所在工会">{{ detail.unionName || '' }}</el-descriptions-item>
</el-descriptions>
</div>
<div class="tour-ledger-section mt10">
<div class="tour-ledger-title">
报名信息
<el-link v-if="detailHasWorkflow()" type="primary" @click="openChart">点击查看流程图</el-link>
</div>
<el-descriptions :column="3" border size="medium">
<el-descriptions-item label="报名时间">{{ detail.signupTime || '' }}</el-descriptions-item>
<el-descriptions-item label="报名线路">{{ detail.lineName || '' }}</el-descriptions-item>
<el-descriptions-item label="线路类型">{{ detail.lineType || '' }}</el-descriptions-item>
<el-descriptions-item label="报名酒店">{{ detail.hotelName || '' }}</el-descriptions-item>
<el-descriptions-item label="报名旅行社">{{ detail.travelAgencyName || '' }}</el-descriptions-item>
<el-descriptions-item label="出行时间">{{ detail.travelPeriod || '' }}</el-descriptions-item>
<el-descriptions-item v-if="fillBedInfo" label="床型">{{ detail.bedType || '' }}</el-descriptions-item>
<el-descriptions-item v-if="fillBedInfo" label="床位信息">{{ detail.bedInfo || '' }}</el-descriptions-item>
<el-descriptions-item v-if="fillBedInfo" label="意向拼床人">{{ detail.intendedRoommate || '' }}</el-descriptions-item>
<el-descriptions-item label="是否携带家属">{{ familyText() }}</el-descriptions-item>
<el-descriptions-item label="是否参加">{{ detail.joined ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="是否报销">{{ detail.reimbursed ? '是' : '否' }}</el-descriptions-item>
<el-descriptions-item label="报销超出费用">{{ detail.overCostReimbursed ? '是' : '否' }}</el-descriptions-item>
</el-descriptions>
</div>
<div v-if="isDirectFamilyLine()" class="tour-ledger-section mt10">
<div class="tour-ledger-title">直系亲属线路</div>
<el-descriptions :column="3" border size="medium">
<el-descriptions-item label="亲属姓名">{{ directRelative.relativeName || '' }}</el-descriptions-item>
<el-descriptions-item label="所在单位">{{ directRelative.unitName || '' }}</el-descriptions-item>
<el-descriptions-item label="亲属关系">{{ directRelative.relationshipName || '' }}</el-descriptions-item>
<el-descriptions-item label="线路名称">{{ directRelative.lineName || '' }}</el-descriptions-item>
<el-descriptions-item label="出行开始日期">{{ directRelative.travelStartTime || '' }}</el-descriptions-item>
<el-descriptions-item label="出行结束日期">{{ directRelative.travelEndTime || '' }}</el-descriptions-item>
</el-descriptions>
</div>
<div v-if="showFamilySection()" class="tour-ledger-section mt10">
<div class="tour-ledger-title">家属信息</div>
<el-table :data="familyData" border :size="tableSize" empty-text="暂无家属信息">
<el-table-column label="序号" type="index" width="70" align="center" header-align="center"></el-table-column>
<el-table-column label="家属姓名" prop="familyName" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="性别" prop="gender" width="90" align="center" header-align="center"></el-table-column>
<el-table-column label="身份证号码" prop="idCard" min-width="180" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column label="年龄" prop="age" width="90" align="center" header-align="center"></el-table-column>
<el-table-column label="关系" prop="relationship" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column v-if="fillBedInfo" label="床型" prop="bedType" width="110" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column v-if="fillBedInfo" label="床位" prop="bedInfo" min-width="120" align="center" header-align="center" show-overflow-tooltip></el-table-column>
<el-table-column v-if="fillBedInfo" label="意向拼床人" prop="intendedRoommate" min-width="130" align="center" header-align="center" show-overflow-tooltip></el-table-column>
</el-table>
</div>
<template v-if="detailHasWorkflow()" v-for="task in doneTasks">
<div class="tour-ledger-section mt10" :key="task.id">
<div class="tour-ledger-title">{{ task.displayName }}</div>
<el-descriptions border :column="3" v-if="task.ext && task.ext.isFirstTaskNode">
<el-descriptions-item label="申请用户">
{{ task.ext.initiatorName }}({{ task.ext.initiatorAccount }})
</el-descriptions-item>
<el-descriptions-item label="申请时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext.submitType"></dict-tag>
</el-descriptions-item>
</el-descriptions>
<el-descriptions border :column="3" v-else>
<el-descriptions-item label="办理用户">
{{ (task.taskFormData && task.taskFormData.userName) || '' }}({{ (task.taskFormData && task.taskFormData.loginName) || '' }})
</el-descriptions-item>
<el-descriptions-item label="办理时间">{{ task.finishTime }}</el-descriptions-item>
<el-descriptions-item label="办理结果">
<dict-tag :options="dict.type.PROCESS_TASK_SUBMIT_TYPE" :value="task.ext && task.ext.submitType"></dict-tag>
</el-descriptions-item>
<el-descriptions-item label="办理意见" :span="3">
{{ (task.taskFormData && (task.taskFormData.opinion || task.taskFormData.tf_opinion)) || '' }}
</el-descriptions-item>
</el-descriptions>
</div>
</template>
<snaker-chart ref="snakerChartRef"></snaker-chart>
<span slot="footer" class="dialog-footer">
<el-button type="primary" @click="detailVisible = false">确定</el-button>
</span>
</el-dialog>
</div>
<style>
.tour-ledger-section {
padding: 0 2px;
}
.tour-ledger-title {
border-left: 4px solid #0079c2;
color: #0079c2;
font-size: 14px;
font-weight: 600;
line-height: 16px;
margin-bottom: 14px;
padding-left: 10px;
}
.tour-ledger-toolbar {
display: flex;
align-items: center;
flex-wrap: nowrap;
gap: 24px;
margin-bottom: 12px;
white-space: nowrap;
}
.tour-ledger-toolbar-title {
flex: 0 0 auto;
}
.tour-ledger-scope {
display: flex;
align-items: center;
flex: 0 0 auto;
flex-wrap: nowrap;
gap: 12px;
margin: 0;
white-space: nowrap;
}
.tour-ledger-scope .el-button {
margin: 0;
}
.tour-ledger-scope .el-button + .el-button {
margin-left: 0;
}
.tour-ledger-scope .tour-scope-btn {
background: #ecf5ff;
border-color: #b3d8ff;
border-radius: 4px;
color: #0079c2;
font-weight: 600;
height: 34px;
line-height: 1;
padding: 8px 18px;
}
.tour-ledger-scope .tour-scope-btn:hover,
.tour-ledger-scope .tour-scope-btn:focus {
background: #d9ecff;
border-color: #66b1ff;
color: #006bb0;
}
.tour-ledger-scope .tour-scope-btn.is-active {
background: #0079c2;
border-color: #0079c2;
box-shadow: 0 2px 6px rgba(0, 121, 194, 0.24);
color: #fff;
}
.tour-ledger-scope .tour-scope-btn.is-active:hover,
.tour-ledger-scope .tour-scope-btn.is-active:focus {
background: #006bb0;
border-color: #006bb0;
color: #fff;
}
</style>
<script nonce="${cspNonce!}">
new Vue({
el: "#app",
mixins: [initTableMixins],
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
const currentYear = moment().format("YYYY")
return {
detailVisible: false,
unionOptions: [],
lineOptions: [],
travelPeriodOptions: [],
lineTypeOptions: [],
summaryStats: {},
detail: {},
familyData: [],
directRelative: {},
directFamilyUnitLine: false,
fillBedInfo: true,
detailRow: {},
doneTasks: [],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "signupTime",
pageOrderBy: "descending",
startYear: currentYear,
endYear: currentYear,
keyword: "",
unionId: "",
lineId: "",
travelPeriod: "",
lineType: "",
scopeType: "",
overCostOnly: false
}
}
},
methods: {
toBoolean(value) {
return value === true || value === 1 || value === "1" || value === "true"
},
isDirectFamilyLine() {
return this.directFamilyUnitLine
|| this.toBoolean(this.detail && this.detail.directFamilyUnitLine)
|| !!(this.directRelative && this.directRelative.id)
},
detailHasWorkflow() {
return !!(this.detailRow && this.detailRow.instanceId)
},
hasFamily() {
return this.toBoolean(this.detail && this.detail.hasFamily) || this.familyData.length > 0
},
familyText() {
if (this.isDirectFamilyLine()) {
return "否"
}
if (!this.hasFamily()) {
return "否"
}
const count = Number(this.familyData.length || 0)
return count > 0 ? count + "人" : "是"
},
showFamilySection() {
return this.hasFamily() && !this.isDirectFamilyLine()
},
loadDoneTasks() {
this.$axios.post("/flow/common/doneTasks", { bizId: this.detailRow.id }).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data || []
}
})
},
openChart() {
if (!this.detailRow.instanceProcessDefineId || !this.detailRow.instanceId) {
this.$message.warning("暂无流程图信息")
return
}
this.$refs.snakerChartRef.onOpenFull(this.detailRow.instanceProcessDefineId, this.detailRow.instanceId)
},
resetSearch() {
this.pageForm.startYear = moment().format("YYYY")
this.pageForm.endYear = moment().format("YYYY")
this.pageForm.keyword = ""
this.pageForm.unionId = ""
this.pageForm.lineId = ""
this.pageForm.travelPeriod = ""
this.pageForm.lineType = ""
this.pageForm.scopeType = ""
this.pageForm.overCostOnly = false
this.loadUnionOptions()
this.loadLineOptions()
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
this.loadSummaryStats()
this.doSearch()
},
openView(row) {
this.detailRow = row || {}
this.detail = {}
this.familyData = []
this.directRelative = {}
this.directFamilyUnitLine = false
this.fillBedInfo = true
this.doneTasks = []
this.$axios.post(loc() + "/detail", { id: row.id }).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.detail = data.ledger || {}
this.detail.travelPeriod = data.travelPeriod || ""
this.familyData = data.families || []
this.directRelative = data.directRelative || {}
this.directFamilyUnitLine = this.toBoolean(data.directFamilyUnitLine)
this.fillBedInfo = data.fillBedInfo === undefined || data.fillBedInfo === null || this.toBoolean(data.fillBedInfo)
this.detailVisible = true
if (this.detailHasWorkflow()) {
this.loadDoneTasks()
}
} else {
this.$message.warning(res.msg || "查询失败")
}
})
},
doDelete(row) {
this.$confirm("确定删除【" + row.userName + "】的报名台账吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.$axios.post(loc() + "/doDelete", { id: row.id }).then((res) => {
if (res.code === 0) {
this.$message.success("删除成功")
this.pageData()
this.loadSummaryStats()
this.loadLineOptions()
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
} else {
this.$message.warning(res.msg || "删除失败")
}
})
})
},
loadUnionOptions() {
this.$axios.post(loc() + "/unionOptions", {
startYear: this.pageForm.startYear,
endYear: this.pageForm.endYear
}).then((res) => {
if (res.code === 0) {
this.unionOptions = res.data || []
}
})
},
loadLineOptions() {
this.$axios.post(loc() + "/lineOptions", {
startYear: this.pageForm.startYear,
endYear: this.pageForm.endYear,
travelPeriod: this.pageForm.travelPeriod,
lineType: this.pageForm.lineType,
unionId: this.pageForm.unionId,
keyword: this.pageForm.keyword,
scopeType: this.pageForm.scopeType
}).then((res) => {
if (res.code === 0) {
this.lineOptions = res.data || []
if (this.pageForm.lineId && !this.lineOptions.some(item => item.lineId === this.pageForm.lineId)) {
this.pageForm.lineId = ""
}
}
})
},
loadTravelPeriodOptions() {
this.$axios.post(loc() + "/travelPeriodOptions", {
startYear: this.pageForm.startYear,
endYear: this.pageForm.endYear,
lineId: this.pageForm.lineId,
lineType: this.pageForm.lineType,
unionId: this.pageForm.unionId,
keyword: this.pageForm.keyword,
scopeType: this.pageForm.scopeType
}).then((res) => {
if (res.code === 0) {
this.travelPeriodOptions = res.data || []
if (this.pageForm.travelPeriod && !this.travelPeriodOptions.some(item => item.travelPeriod === this.pageForm.travelPeriod)) {
this.pageForm.travelPeriod = ""
}
}
})
},
loadLineTypeOptions() {
this.$axios.post(loc() + "/lineTypeOptions", {
startYear: this.pageForm.startYear,
endYear: this.pageForm.endYear,
lineId: this.pageForm.lineId,
travelPeriod: this.pageForm.travelPeriod,
unionId: this.pageForm.unionId,
keyword: this.pageForm.keyword,
scopeType: this.pageForm.scopeType
}).then((res) => {
if (res.code === 0) {
this.lineTypeOptions = res.data || []
if (this.pageForm.lineType && !this.lineTypeOptions.some(item => item.lineType === this.pageForm.lineType)) {
this.pageForm.lineType = ""
}
}
})
},
loadSummaryStats() {
this.$axios.post(loc() + "/summaryStats", {
startYear: this.pageForm.startYear,
endYear: this.pageForm.endYear,
unionId: this.pageForm.unionId,
lineId: this.pageForm.lineId,
travelPeriod: this.pageForm.travelPeriod,
lineType: this.pageForm.lineType
}).then((res) => {
if (res.code === 0) {
this.summaryStats = res.data || {}
}
})
},
setScopeType(scopeType) {
this.pageForm.scopeType = this.pageForm.scopeType === scopeType ? "" : scopeType
this.pageForm.pageNumber = 1
this.loadLineOptions()
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
this.pageData()
},
setOverCostOnly() {
this.pageForm.overCostOnly = !this.pageForm.overCostOnly
this.pageForm.pageNumber = 1
this.pageData()
},
downloadOverCostSummary() {
this.$downLoad(loc() + "/exportOverCostSummary", this.pageForm)
}
},
mounted() {
this.loadUnionOptions()
this.loadLineOptions()
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
this.loadSummaryStats()
this.pageData()
},
watch: {
"pageForm.startYear"() {
this.pageForm.lineId = ""
this.pageForm.travelPeriod = ""
this.pageForm.lineType = ""
this.pageForm.unionId = ""
this.pageForm.scopeType = ""
this.loadUnionOptions()
this.loadLineOptions()
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
this.loadSummaryStats()
},
"pageForm.endYear"() {
this.pageForm.lineId = ""
this.pageForm.travelPeriod = ""
this.pageForm.lineType = ""
this.pageForm.unionId = ""
this.pageForm.scopeType = ""
this.loadUnionOptions()
this.loadLineOptions()
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
this.loadSummaryStats()
},
"pageForm.lineId"() {
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
this.loadSummaryStats()
},
"pageForm.travelPeriod"() {
this.loadLineOptions()
this.loadLineTypeOptions()
this.loadSummaryStats()
},
"pageForm.lineType"() {
this.loadLineOptions()
this.loadTravelPeriodOptions()
this.loadSummaryStats()
},
"pageForm.unionId"() {
this.loadLineOptions()
this.loadTravelPeriodOptions()
this.loadLineTypeOptions()
this.loadSummaryStats()
}
}
})
</script>
<!--#
}
#-->
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,480 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.tour-approval-page {
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
background: #f4f6f8;
box-sizing: border-box;
}
.tour-approval-sticky {
flex-shrink: 0;
background: #f4f6f8;
}
.tour-approval-list-scroll {
min-height: 0;
flex: 1;
overflow-y: auto;
padding-bottom: 18px;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
}
.tour-approval-search /deep/ .van-search__content,
.tour-approval-search .van-search__content {
border-radius: 8px;
background: #ffffff;
}
.tour-card-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 8px;
}
.tour-tag {
display: inline-flex;
align-items: center;
min-height: 22px;
padding: 0 8px;
border-radius: 4px;
background: #eef6ff;
color: #0b75bd;
font-size: 12px;
}
.tour-tag.gray {
background: #f1f5f9;
color: #64748b;
}
.tour-tag.warn {
background: #fff3e5;
color: #d97706;
}
.tour-approval-sheet {
max-height: 86vh;
overflow-y: auto;
padding-bottom: env(safe-area-inset-bottom);
background: #f4f6f8;
}
.tour-section {
margin: 10px 12px;
overflow: hidden;
border-radius: 8px;
background: #ffffff;
}
.tour-section-title {
position: relative;
padding: 14px 16px;
border-bottom: 1px solid #edf0f4;
color: #0b75bd;
font-size: 16px;
font-weight: 700;
line-height: 1.2;
}
.tour-section-title::before {
content: "";
position: absolute;
left: 16px;
top: 0;
width: 30px;
height: 4px;
border-radius: 0 0 3px 3px;
background: #0b75bd;
}
.tour-approval-actions {
display: flex;
gap: 8px;
padding: 10px 12px 14px;
background: #ffffff;
}
.tour-process-opinion {
white-space: pre-wrap;
word-break: break-word;
}
</style>
<div id="app" class="tour-approval-page" v-cloak>
<van-nav-bar title="校工会审核" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<van-sticky offset-top="46px" class="tour-approval-sticky">
<van-search
class="tour-approval-search"
v-model="pageForm.keyword"
placeholder="请输入工号/姓名/线路搜索"
show-action
clearable
input-align="left"
@search="doSearch"
@clear="doSearch">
<template #action>
<div @click="doSearch">搜索</div>
</template>
</van-search>
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item :options="auditOptions" @change="doSearch" v-model="pageForm.audit"></van-dropdown-item>
<van-dropdown-item :options="unionOptions" @change="doSearch" v-model="pageForm.unionId"></van-dropdown-item>
<van-dropdown-item :options="lineTypeOptions" @change="doSearch" v-model="pageForm.lineType"></van-dropdown-item>
</van-dropdown-menu>
</van-sticky>
<div class="tour-approval-list-scroll">
<table-list api="/platform/tour/schoolUnionApproval/pageData" :page_form.sync="pageForm" ref="tableListRef" title="userName" @ready="onReady">
<template v-slot="{index,row}">
<div class="tour-card-tags">
<span v-if="row.jobNo" class="tour-tag">{{ row.jobNo }}</span>
<span v-if="row.lineType" class="tour-tag gray">{{ row.lineType }}</span>
<span v-if="row.overCostReimbursed" class="tour-tag warn">报销超出费用</span>
</div>
<table-column label="线路名称">{{ row.lineName || '' }}</table-column>
<table-column label="出行时段">{{ row.travelPeriod || '' }}</table-column>
<table-column label="报销超出费用">{{ row.overCostReimbursed ? '是' : '否' }}</table-column>
<table-column label="当前节点">{{ row.curTaskName || '' }}</table-column>
<table-column label="流程状态">
<enum-tag
v-if="row.instanceState !== null && row.instanceState !== undefined && row.instanceState !== ''"
:value="row.instanceState"
name="ProcessInstanceStateEnum"
label_key="message"
size="small">
</enum-tag>
</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
<i class="fa fa-check"></i>
<span>审核</span>
</div>
</template>
</table-list>
</div>
<tour-info ref="infoRef">
<div v-if="showApprovalForm" class="tour-section">
<div class="tour-section-title">{{ formData.taskName || '审核' }}</div>
<van-form ref="formRef">
<van-field
v-model="formData.tf_opinion"
name="tf_opinion"
label="审批意见"
placeholder="请输入审批意见"
:rules="[{ required: true, message: '请填写审批意见' }]"
required
type="textarea"
rows="3"
maxlength="200"
show-word-limit>
</van-field>
</van-form>
<div class="tour-approval-actions">
<van-button type="danger" block @click="handleTaskAction(6)">退回</van-button>
<van-button type="danger" block @click="handleTaskAction(2)">拒绝</van-button>
<van-button type="primary" block @click="handleTaskAction(1)">同意</van-button>
</div>
</div>
</tour-info>
</div>
<script nonce="${cspNonce!}">
const TOUR_INFO = {
template: [
"\n",
" <van-action-sheet v-model=\"visible\" title=\"报名详情\">\n",
" <div class=\"tour-approval-sheet\">\n",
" <div class=\"tour-section\">\n",
" <div class=\"tour-section-title\">教职工信息</div>\n",
" <van-cell title=\"工号\" :value=\"detail.jobNo || ''\"></van-cell>\n",
" <van-cell title=\"姓名\" :value=\"detail.userName || ''\"></van-cell>\n",
" <van-cell title=\"性别\" :value=\"detail.gender || ''\"></van-cell>\n",
" <van-cell title=\"身份证号\" :value=\"detail.idCard || ''\"></van-cell>\n",
" <van-cell title=\"所在单位\" :value=\"detail.unitName || ''\"></van-cell>\n",
" <van-cell title=\"所在工会\" :value=\"detail.unionName || ''\"></van-cell>\n",
" </div>\n",
"\n",
" <div class=\"tour-section\">\n",
" <div class=\"tour-section-title\">报名信息</div>\n",
" <van-cell title=\"报名线路\" :value=\"detail.lineName || ''\"></van-cell>\n",
" <van-cell title=\"线路类型\" :value=\"detail.lineType || ''\"></van-cell>\n",
" <van-cell title=\"出行时段\" :value=\"detail.travelPeriod || ''\"></van-cell>\n",
" <van-cell title=\"报名时间\" :value=\"detail.signupTime || ''\"></van-cell>\n",
" <van-cell title=\"报名酒店\" :value=\"detail.hotelName || ''\"></van-cell>\n",
" <van-cell title=\"旅行社\" :value=\"detail.travelAgencyName || ''\"></van-cell>\n",
" <van-cell v-if=\"fillBedInfo\" title=\"床型\" :value=\"detail.bedType || ''\"></van-cell>\n",
" <van-cell v-if=\"fillBedInfo\" title=\"床位信息\" :value=\"detail.bedInfo || ''\"></van-cell>\n",
" <van-cell v-if=\"fillBedInfo\" title=\"意向拼床人\" :value=\"detail.intendedRoommate || ''\"></van-cell>\n",
" <van-cell title=\"携带家属\" :value=\"familyText()\"></van-cell>\n",
" <van-cell title=\"报销超出费用\" :value=\"detail.overCostReimbursed ? '是' : '否'\"></van-cell>\n",
" </div>\n",
"\n",
" <div v-if=\"isDirectFamilyLine()\" class=\"tour-section\">\n",
" <div class=\"tour-section-title\">直系亲属线路</div>\n",
" <van-cell title=\"亲属姓名\" :value=\"directRelative.relativeName || ''\"></van-cell>\n",
" <van-cell title=\"所在单位\" :value=\"directRelative.unitName || ''\"></van-cell>\n",
" <van-cell title=\"亲属关系\" :value=\"directRelative.relationshipName || ''\"></van-cell>\n",
" <van-cell title=\"线路名称\" :value=\"directRelative.lineName || ''\"></van-cell>\n",
" <van-cell title=\"出行开始\" :value=\"directRelative.travelStartTime || ''\"></van-cell>\n",
" <van-cell title=\"出行结束\" :value=\"directRelative.travelEndTime || ''\"></van-cell>\n",
" </div>\n",
"\n",
" <div v-if=\"showFamilySection()\" class=\"tour-section\">\n",
" <div class=\"tour-section-title\">家属信息</div>\n",
" <template v-for=\"(item,index) in familyData\">\n",
" <van-cell :key=\"'title_' + index\" :title=\"'家属' + (index + 1)\"></van-cell>\n",
" <van-cell :key=\"'name_' + index\" title=\"姓名\" :value=\"item.familyName || ''\"></van-cell>\n",
" <van-cell :key=\"'gender_' + index\" title=\"性别\" :value=\"item.gender || ''\"></van-cell>\n",
" <van-cell :key=\"'id_' + index\" title=\"身份证号\" :value=\"item.idCard || ''\"></van-cell>\n",
" <van-cell :key=\"'rel_' + index\" title=\"关系\" :value=\"item.relationship || ''\"></van-cell>\n",
" <van-cell v-if=\"fillBedInfo\" :key=\"'bed_' + index\" title=\"床型\" :value=\"item.bedType || ''\"></van-cell>\n",
" </template>\n",
" </div>\n",
"\n",
" <div v-if=\"doneTasks.length > 0\" class=\"tour-section\">\n",
" <div class=\"tour-section-title\">流程记录</div>\n",
" <template v-for=\"task in doneTasks\">\n",
" <van-cell-group :key=\"task.id\" :title=\"task.displayName || task.taskName\">\n",
" <van-cell title=\"办理时间\" :value=\"task.finishTime || ''\"></van-cell>\n",
" <van-cell title=\"办理结果\">\n",
" <dict-tag :options=\"dict.type.PROCESS_TASK_SUBMIT_TYPE\" :value=\"task.ext && task.ext.submitType\"></dict-tag>\n",
" </van-cell>\n",
" <van-cell title=\"办理意见\" v-if=\"task.taskFormData\" class=\"direction-column-cell\">\n",
" <div class=\"tour-process-opinion\">{{ task.taskFormData.opinion || task.taskFormData.tf_opinion || '' }}</div>\n",
" </van-cell>\n",
" </van-cell-group>\n",
" </template>\n",
" </div>\n",
"\n",
" <slot></slot>\n",
" </div>\n",
" </van-action-sheet>\n",
" \n"
].join(""),
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
return {
visible: false,
row: {},
detail: {},
familyData: [],
directRelative: {},
allowFamily: false,
fillBedInfo: true,
directFamilyUnitLine: false,
doneTasks: []
}
},
methods: {
onOpen(row) {
this.row = row || {}
this.visible = true
this.detail = {}
this.familyData = []
this.directRelative = {}
this.allowFamily = false
this.fillBedInfo = true
this.directFamilyUnitLine = false
this.doneTasks = []
this.info()
this.getDoneTasks()
},
onClose() {
this.visible = false
},
info() {
this.$axios.post("/platform/tour/schoolUnionApproval/detail", { id: this.row.id }).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.detail = data.ledger || {}
this.detail.travelPeriod = data.travelPeriod || this.row.travelPeriod || ""
this.familyData = data.families || []
this.directRelative = data.directRelative || {}
this.allowFamily = this.toBoolean(data.allowFamily)
this.fillBedInfo = data.fillBedInfo === undefined || data.fillBedInfo === null || this.toBoolean(data.fillBedInfo)
this.directFamilyUnitLine = this.toBoolean(data.directFamilyUnitLine)
} else {
vant.Toast(res.msg || "查询失败")
}
})
},
getDoneTasks() {
this.$axios.post("/flow/common/doneTasks", { bizId: this.row.id }).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data || []
}
})
},
toBoolean(value) {
return value === true || value === 1 || value === "1" || value === "true" || value === "TRUE"
},
isDirectFamilyLine() {
return this.directFamilyUnitLine || this.toBoolean(this.detail.directFamilyUnitLine) || !!(this.directRelative && this.directRelative.id)
},
hasFamily() {
return this.toBoolean(this.detail.hasFamily) || this.familyData.length > 0
},
showFamilySection() {
return this.allowFamily && this.hasFamily() && !this.isDirectFamilyLine()
},
familyText() {
if (this.isDirectFamilyLine() || !this.allowFamily) {
return "否"
}
return this.hasFamily() ? "是" : "否"
}
}
}
new Vue({
el: "#app",
store,
components: {
"tour-info": TOUR_INFO
},
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "signupTime",
pageOrderBy: "descending",
audit: false,
keyword: "",
unionId: "",
lineType: ""
},
auditOptions: [
{ text: "未审核", value: false },
{ text: "已审核", value: true }
],
unionOptions: [
{ text: "全部工会", value: "" }
],
lineTypeOptions: [
{ text: "全部线路类型", value: "" }
],
formData: {},
showApprovalForm: false
}
},
methods: {
historyBack,
onReady() {
this.doSearch()
},
doSearch() {
this.$nextTick(async () => {
await Promise.all([this.loadUnionOptions(), this.loadLineTypeOptions()])
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
},
loadUnionOptions() {
return this.$axios.post("/platform/tour/schoolUnionApproval/unionOptions", {
audit: this.pageForm.audit,
keyword: this.pageForm.keyword,
lineType: this.pageForm.lineType
}).then((res) => {
if (res.code === 0) {
const options = (res.data || []).map(item => ({
text: item.name,
value: item.id
})).filter(item => item.value)
this.unionOptions = [{ text: "全部工会", value: "" }].concat(options)
if (this.pageForm.unionId && !this.unionOptions.some(item => item.value === this.pageForm.unionId)) {
this.pageForm.unionId = ""
}
}
})
},
loadLineTypeOptions() {
return this.$axios.post("/platform/tour/schoolUnionApproval/lineTypeOptions", {
audit: this.pageForm.audit,
keyword: this.pageForm.keyword,
unionId: this.pageForm.unionId
}).then((res) => {
if (res.code === 0) {
const options = (res.data || []).map(item => ({
text: item.lineType,
value: item.lineType
})).filter(item => item.value)
this.lineTypeOptions = [{ text: "全部线路类型", value: "" }].concat(options)
if (this.pageForm.lineType && !this.lineTypeOptions.some(item => item.value === this.pageForm.lineType)) {
this.pageForm.lineType = ""
}
}
})
},
onView(row) {
this.showApprovalForm = false
this.$refs.infoRef.onOpen(row)
},
onApproval(row) {
this.showApprovalForm = true
this.formData = {
processTaskId: row.taskId,
taskName: row.curTaskName || row.taskName,
tf_opinion: ""
}
this.$refs.infoRef.onOpen(row)
},
async handleTaskAction(val) {
try {
await this.$refs.formRef.validate()
} catch (e) {
return
}
this.$dialog.confirm({
title: "提示",
message: "您确定要提交吗?"
}).then(() => {
const loading = this.$toast.loading({
message: "加载中...",
forbidClick: true,
overlay: true,
duration: 0
})
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: val
})
}).then((res) => {
if (res.code === 0) {
this.$refs.infoRef.onClose()
this.$toast.success(res.msg || "操作成功")
this.doSearch()
} else {
this.$toast(res.msg || "操作失败")
}
}).finally(() => {
loading.close()
})
}).catch(() => {})
}
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,626 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.tour-apply-page {
min-height: 100vh;
padding: 10px 14px 78px;
background: #f4f6f8;
box-sizing: border-box;
}
.tour-apply-card {
margin-bottom: 14px;
overflow: hidden;
border-radius: 6px;
background: #ffffff;
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05);
}
.tour-apply-title {
position: relative;
padding: 14px 16px;
border-bottom: 1px solid #edf0f4;
color: #0b75bd;
font-size: 16px;
font-weight: 700;
line-height: 1.2;
}
.tour-apply-title::before {
content: "";
position: absolute;
left: 16px;
top: 0;
width: 30px;
height: 4px;
border-radius: 0 0 3px 3px;
background: #0b75bd;
}
.tour-card-title-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
}
.tour-card-actions {
display: flex;
gap: 8px;
flex-shrink: 0;
}
.tour-family-tab {
margin: 10px 12px 0;
height: 30px;
border-radius: 5px;
background: #1684d2;
color: #ffffff;
font-size: 14px;
font-weight: 700;
line-height: 30px;
text-align: center;
}
.tour-field-tip {
padding: 0 16px 12px;
color: #ee2f2f;
font-size: 12px;
line-height: 1.5;
}
.tour-over-cost /deep/ .van-field__label,
.tour-over-cost /deep/ .van-field__label span,
.tour-over-cost /deep/ .van-radio__label {
color: #ee2f2f;
font-weight: 700;
}
.tour-submit-bar {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
border-top: 1px solid #edf0f4;
background: #ffffff;
box-sizing: border-box;
}
.tour-apply-empty {
padding: 24px 0;
color: #94a3b8;
font-size: 13px;
text-align: center;
}
</style>
<div id="app">
<van-nav-bar title="疗休养报名" left-text="返回" left-arrow @click-left="historyBack('/platform/tour/signup/h5/signup')" fixed placeholder></van-nav-bar>
<div class="tour-apply-page">
<van-loading v-if="pageLoading" size="24px" vertical>加载中...</van-loading>
<template v-else>
<div class="tour-apply-card">
<div class="tour-apply-title">教职工信息</div>
<van-field label="姓名" readonly v-model="signupForm.userName"></van-field>
<van-field label="工号" readonly v-model="signupForm.jobNo"></van-field>
<van-field label="身份证号码" v-model="signupForm.idCard" maxlength="30" placeholder="请填写身份证号码"></van-field>
<van-field label="手机号" v-model="signupForm.mobile" type="tel" maxlength="30" placeholder="请填写手机号"></van-field>
<van-field label="所属工会" readonly v-model="signupForm.unionName"></van-field>
</div>
<div class="tour-apply-card">
<div class="tour-apply-title">报名信息</div>
<van-field label="报名线路" readonly v-model="signupForm.lineName"></van-field>
<van-field label="线路类型" readonly v-model="signupForm.lineType"></van-field>
<van-field v-if="signupForm.travelPeriod" label="出行时间" readonly v-model="signupForm.travelPeriod"></van-field>
<van-field label="报名酒店" v-model="signupForm.hotelName" maxlength="100" placeholder="请输入报名酒店"></van-field>
<van-field
label="乘车地点"
readonly
clickable
is-link
placeholder="请选择乘车地点"
v-model="signupForm.boardingPlace"
@click="openBoardingPlacePicker">
</van-field>
<van-field
v-if="fillBedInfo"
label="床型"
readonly
clickable
is-link
placeholder="请选择房型"
v-model="signupForm.bedType"
@click="openBedPicker('self')">
</van-field>
<van-field v-if="fillBedInfo" label="床位信息" v-model="signupForm.bedInfo" maxlength="100" placeholder="请输入床位数量"></van-field>
<van-field v-if="fillBedInfo" label="意向拼床人" v-model="signupForm.intendedRoommate" maxlength="100" placeholder="请输入意向拼床人"></van-field>
<van-field v-if="canApplyOverReimbursement(signupForm)" class="tour-over-cost" label="报销超出费用">
<template #input>
<van-radio-group v-model="signupForm.overCostReimbursed" direction="horizontal">
<van-radio :name="true"></van-radio>
<van-radio :name="false"></van-radio>
</van-radio-group>
</template>
</van-field>
<div v-if="canApplyOverReimbursement(signupForm) && signupForm.overCostReimbursed" class="tour-field-tip">
提醒:您选择了报销超出费用,会影响到下一年度的疗休养,请认真思考。
</div>
</div>
<div v-if="isDirectFamilyLine(signupForm)" class="tour-apply-card">
<div class="tour-apply-title">直系亲属线路</div>
<van-field label="亲属姓名" v-model="directRelativeForm.relativeName" maxlength="100" placeholder="请输入亲属姓名"></van-field>
<van-field label="所在单位" v-model="directRelativeForm.unitName" maxlength="100" placeholder="请输入所在单位"></van-field>
<van-field
label="亲属关系"
readonly
clickable
is-link
placeholder="请选择亲属关系"
v-model="directRelativeForm.relationshipName"
@click="openDirectRelativePicker">
</van-field>
<van-field label="线路名称" v-model="directRelativeForm.lineName" maxlength="100" placeholder="请输入线路名称"></van-field>
<van-field label="出行开始" v-model="directRelativeForm.travelStartTime" type="date" placeholder="请选择出行开始日期"></van-field>
<van-field label="出行结束" v-model="directRelativeForm.travelEndTime" type="date" placeholder="请选择出行结束日期"></van-field>
</div>
<div class="tour-apply-card">
<div class="tour-apply-title">
<div class="tour-card-title-row">
<span>亲属信息</span>
<div class="tour-card-actions">
<van-button size="small" type="default" :disabled="familyData.length === 0" @click="removeFamily">删除亲属</van-button>
<van-button size="small" type="info" :disabled="!allowFamily || isDirectFamilyLine(signupForm)" @click="addFamily">添加亲属</van-button>
</div>
</div>
</div>
<div v-if="!allowFamily || isDirectFamilyLine(signupForm)" class="tour-apply-empty">
当前线路不支持填写亲属
</div>
<template v-else-if="familyData.length > 0">
<div class="tour-family-tab">亲属{{ familyActive + 1 }}</div>
<van-field label="姓名" v-model="currentFamily.familyName" maxlength="100" placeholder="请填写姓名"></van-field>
<van-field label="年龄" v-model.number="currentFamily.age" type="digit" placeholder="请填写年龄"></van-field>
<van-field label="性别">
<template #input>
<van-radio-group v-model="currentFamily.gender" direction="horizontal">
<van-radio name="男"></van-radio>
<van-radio name="女"></van-radio>
</van-radio-group>
</template>
</van-field>
<van-field label="身份证号码" v-model="currentFamily.idCard" maxlength="18" placeholder="请填写身份证号码" @blur="normalizeFamilyIdCard(currentFamily)"></van-field>
<van-field label="手机号" v-model="currentFamily.mobile" maxlength="30" placeholder="请填写手机号"></van-field>
<van-field
label="与本人关系"
readonly
clickable
is-link
placeholder="请选择关系"
v-model="currentFamily.relationship"
@click="openRelationshipPicker">
</van-field>
<van-field
v-if="fillBedInfo"
label="床型"
readonly
clickable
is-link
placeholder="请选择房型"
v-model="currentFamily.bedType"
@click="openBedPicker('family')">
</van-field>
<van-field v-if="fillBedInfo" label="床位" v-model="currentFamily.bedInfo" maxlength="100" placeholder="请输入床位数量"></van-field>
<van-field v-if="fillBedInfo" label="意向拼床人" v-model="currentFamily.intendedRoommate" maxlength="100" placeholder="请输入意向拼床人"></van-field>
<div v-if="fillBedInfo" class="tour-field-tip">提醒:如果跟报名人员同一房间,无须选择!</div>
</template>
<div v-else class="tour-apply-empty">暂无亲属,请按需添加</div>
</div>
</template>
</div>
<div class="tour-submit-bar">
<van-button type="info" block round :loading="submitLoading" @click="submitSignup">提交报名</van-button>
</div>
<van-popup v-model="bedPickerVisible" position="bottom">
<van-picker show-toolbar :columns="bedTypeColumns" @confirm="confirmBedType" @cancel="bedPickerVisible=false"></van-picker>
</van-popup>
<van-popup v-model="relationshipPickerVisible" position="bottom">
<van-picker show-toolbar :columns="relationshipColumns" @confirm="confirmRelationship" @cancel="relationshipPickerVisible=false"></van-picker>
</van-popup>
<van-popup v-model="directRelativePickerVisible" position="bottom">
<van-picker show-toolbar :columns="directRelativeColumns" @confirm="confirmDirectRelative" @cancel="directRelativePickerVisible=false"></van-picker>
</van-popup>
<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!}">
const vue = new Vue({
el: "#app",
store,
data() {
return {
pageLoading: true,
submitLoading: false,
allowFamily: false,
fillBedInfo: true,
signupForm: {},
directRelativeForm: {},
familyData: [],
familyActive: 0,
bedTypeOptions: [],
familyRelationshipOptions: [],
directRelativeOptions: [],
boardingPlaceOptions: [],
bedPickerVisible: false,
relationshipPickerVisible: false,
directRelativePickerVisible: false,
boardingPlacePickerVisible: false,
bedPickerTarget: "self",
matterId: GetQueryString("matterId") || ""
}
},
computed: {
currentFamily() {
return this.familyData[this.familyActive] || {}
},
bedTypeColumns() {
return this.bedTypeOptions.map(item => item.name || item.code).filter(Boolean)
},
relationshipColumns() {
return this.familyRelationshipOptions.map(item => item.name || item.code).filter(Boolean)
},
directRelativeColumns() {
return this.directRelativeOptions.map(item => ({ text: item.name || item.code, item }))
},
boardingPlaceColumns() {
return this.boardingPlaceOptions
}
},
methods: {
formatDateTime(date) {
const pad = (value) => String(value).padStart(2, "0")
return date.getFullYear()
+ "-"
+ pad(date.getMonth() + 1)
+ "-"
+ pad(date.getDate())
+ " "
+ pad(date.getHours())
+ ":"
+ pad(date.getMinutes())
+ ":"
+ pad(date.getSeconds())
},
init() {
if (!this.matterId) {
vant.Dialog.alert({ title: "提示", message: "报名事项参数缺失", confirmButtonColor: "#1867b0" })
this.pageLoading = false
return
}
Promise.all([this.loadOptions(), this.loadSignupDetail()]).finally(() => {
this.pageLoading = false
})
},
loadOptions() {
return Promise.all([
this.$axios.post("/platform/tour/signup/bedTypeOptions").then(res => {
if (res.code === 0) this.bedTypeOptions = res.data || []
}),
this.$axios.post("/platform/tour/signup/familyRelationshipOptions").then(res => {
if (res.code === 0) this.familyRelationshipOptions = res.data || []
}),
this.$axios.post("/platform/tour/signup/directRelativeOptions").then(res => {
if (res.code === 0) this.directRelativeOptions = res.data || []
})
])
},
loadSignupDetail() {
return this.$axios.post("/platform/tour/signup/signupDetail", { matterId: this.matterId }).then((res) => {
if (res.code !== 0) {
vant.Dialog.alert({
title: "提示",
message: res.msg || "报名信息加载失败",
confirmButtonColor: "#1867b0"
})
return
}
const data = res.data || {}
const matter = data.matter || {}
const staff = data.staff || {}
const ledger = data.ledger || {}
const directRelative = data.directRelative || {}
this.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({
id: "",
year: matter.year,
jobNo: "",
userName: "",
gender: "",
idCard: "",
mobile: "",
unitId: "",
unitName: "",
unionId: "",
unionName: "",
signupTime: this.formatDateTime(new Date()),
matterId: matter.matterId || "",
lineId: matter.lineId || "",
lineName: matter.lineName || "",
lineType: matter.lineType || "",
directFamilyUnitLine: matter.directFamilyUnitLine,
hotelName: "",
travelAgencyId: matter.travelAgencyId || "",
travelAgencyName: matter.travelAgencyName || "",
boardingPlace: matter.defaultBoardingPlace || "",
travelPeriod: matter.travelPeriod || "",
travelStartTime: matter.travelStartTime || "",
travelEndTime: matter.travelEndTime || "",
allowOverReimbursement: matter.allowOverReimbursement,
overCostReimbursed: false,
hasFamily: false,
intendedRoommate: "",
bedType: "",
bedInfo: ""
}, staff, ledger, {
year: matter.year,
matterId: matter.matterId || ledger.matterId || "",
lineId: matter.lineId || ledger.lineId || "",
lineName: matter.lineName || ledger.lineName || "",
lineType: matter.lineType || ledger.lineType || "",
directFamilyUnitLine: matter.directFamilyUnitLine,
travelAgencyId: matter.travelAgencyId || ledger.travelAgencyId || "",
travelAgencyName: matter.travelAgencyName || ledger.travelAgencyName || "",
boardingPlace: ledger.boardingPlace || matter.defaultBoardingPlace || "",
travelPeriod: matter.travelPeriod || "",
travelStartTime: matter.travelStartTime || "",
travelEndTime: matter.travelEndTime || "",
allowOverReimbursement: matter.allowOverReimbursement,
overCostReimbursed: ledger.overCostReimbursed === true || ledger.overCostReimbursed === 1 || ledger.overCostReimbursed === "1",
hasFamily: ledger.hasFamily === true || ledger.hasFamily === 1 || ledger.hasFamily === "1"
})
this.directRelativeForm = Object.assign(this.emptyDirectRelative(), {
lineId: matter.lineId || "",
lineName: matter.lineName || "",
travelStartTime: matter.travelStartTime || "",
travelEndTime: matter.travelEndTime || ""
}, directRelative)
this.familyData = (data.families || []).map(item => Object.assign(this.emptyFamily(), item))
if (this.familyData.length > 0) {
this.signupForm.hasFamily = true
}
if (!this.allowFamily || this.isDirectFamilyLine(this.signupForm)) {
this.familyData = []
this.signupForm.hasFamily = false
}
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: "",
gender: "",
idCard: "",
mobile: "",
age: null,
bedType: "",
bedInfo: "",
intendedRoommate: "",
staffJobNo: this.signupForm.jobNo || "",
staffName: this.signupForm.userName || "",
relationship: ""
}
},
emptyDirectRelative() {
return {
relativeName: "",
unitName: "",
relationshipCode: "",
relationshipName: "",
lineId: this.signupForm.lineId || "",
lineName: this.signupForm.lineName || "",
travelStartTime: this.signupForm.travelStartTime || "",
travelEndTime: this.signupForm.travelEndTime || ""
}
},
isDirectFamilyLine(row) {
return row && (row.directFamilyUnitLine === true || row.directFamilyUnitLine === 1 || row.directFamilyUnitLine === "1")
},
canApplyOverReimbursement(row) {
return row && (row.allowOverReimbursement === true || row.allowOverReimbursement === 1 || row.allowOverReimbursement === "1")
},
addFamily() {
if (!this.allowFamily || this.isDirectFamilyLine(this.signupForm)) return
this.familyData.push(this.emptyFamily())
this.familyActive = this.familyData.length - 1
this.signupForm.hasFamily = true
},
removeFamily() {
if (this.familyData.length === 0) return
this.familyData.splice(this.familyActive, 1)
this.familyActive = Math.max(0, this.familyActive - 1)
this.signupForm.hasFamily = this.familyData.length > 0
},
openBedPicker(target) {
if (this.bedTypeColumns.length === 0) {
vant.Toast("暂无床型选项")
return
}
this.bedPickerTarget = target
this.bedPickerVisible = true
},
confirmBedType(value) {
if (this.bedPickerTarget === "family") {
this.currentFamily.bedType = value
} else {
this.signupForm.bedType = value
}
this.bedPickerVisible = false
},
openRelationshipPicker() {
if (this.relationshipColumns.length === 0) {
vant.Toast("暂无关系选项")
return
}
this.relationshipPickerVisible = true
},
confirmRelationship(value) {
this.currentFamily.relationship = value
this.relationshipPickerVisible = false
},
openDirectRelativePicker() {
if (this.directRelativeColumns.length === 0) {
vant.Toast("暂无亲属关系选项")
return
}
this.directRelativePickerVisible = true
},
confirmDirectRelative(value) {
const item = value && value.item ? value.item : {}
this.directRelativeForm.relationshipCode = item.code || ""
this.directRelativeForm.relationshipName = item.name || value.text || ""
this.directRelativePickerVisible = false
},
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()
},
isValidIdCard(value) {
if (!value) return false
const idCard = String(value).trim().toUpperCase()
return /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dX]$/.test(idCard)
},
validateFamilies() {
if (!this.allowFamily || !this.signupForm.hasFamily) return true
if (this.familyData.length === 0) {
vant.Toast("请添加亲属信息")
return false
}
const invalid = this.familyData.some(item => !item.familyName || !item.gender || !item.idCard || !item.relationship)
if (invalid) {
vant.Toast("请完善亲属姓名、性别、身份证号码和关系")
return false
}
const invalidIdCard = this.familyData.some(item => !this.isValidIdCard(item.idCard))
if (invalidIdCard) {
vant.Toast("请输入正确的亲属身份证号码")
return false
}
return true
},
validateDirectRelative() {
if (!this.isDirectFamilyLine(this.signupForm)) return true
if (!this.directRelativeForm.relativeName) {
vant.Toast("请输入亲属姓名")
return false
}
if (!this.directRelativeForm.unitName) {
vant.Toast("请输入亲属所在单位")
return false
}
if (!this.directRelativeForm.relationshipCode) {
vant.Toast("请选择亲属关系")
return false
}
if (!this.directRelativeForm.lineName) {
vant.Toast("请输入直系亲属线路名称")
return false
}
if (!this.directRelativeForm.travelStartTime || !this.directRelativeForm.travelEndTime) {
vant.Toast("请选择直系亲属出行时间")
return false
}
return true
},
submitSignup() {
if (this.pageLoading || this.submitLoading) return
if (this.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 : []
const form = Object.assign({}, this.signupForm, {
families: JSON.stringify(families),
directRelative: this.isDirectFamilyLine(this.signupForm) ? JSON.stringify(this.directRelativeForm) : ""
})
this.submitLoading = true
this.$axios.post("/platform/tour/signup/doSignup", form).then((res) => {
this.submitLoading = false
if (res.code === 0) {
vant.Dialog.alert({
title: "提示",
message: res.msg || "报名成功",
confirmButtonColor: "#1867b0"
}).then(() => {
window.location.replace("/platform/tour/signup/h5/signup")
})
} else {
vant.Dialog.alert({
title: "提示",
message: res.msg || "报名失败",
confirmButtonColor: "#1867b0"
})
}
}).catch(() => {
this.submitLoading = false
vant.Toast("报名提交失败")
})
}
},
created() {
this.init()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,403 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.tour-signup-h5 {
min-height: 100vh;
background: #f5f7fb;
padding-bottom: 80px;
box-sizing: border-box;
}
.tour-signup-banner {
position: relative;
height: 193px;
overflow: hidden;
background: #0f74bc;
color: #ffffff;
}
.tour-signup-swipe {
width: 100%;
height: 100%;
}
.tour-signup-swipe img {
display: block;
width: 100%;
height: 193px;
object-fit: cover;
}
.tour-signup-banner::after {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(90deg, rgba(5, 83, 143, 0.72) 0%, rgba(5, 83, 143, 0.32) 48%, rgba(5, 83, 143, 0.08) 100%);
pointer-events: none;
}
.tour-signup-banner__text {
position: absolute;
left: 16px;
right: 16px;
bottom: 20px;
z-index: 1;
}
.tour-signup-title {
font-size: 22px;
font-weight: 700;
line-height: 1.35;
}
.tour-signup-subtitle {
margin-top: 8px;
color: rgba(255, 255, 255, 0.86);
font-size: 13px;
line-height: 1.5;
}
.tour-signup-card {
margin: 10px 12px 0;
padding: 14px 14px 16px;
border-radius: 8px;
background: #ffffff;
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.08);
}
.tour-signup-card__header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding-bottom: 12px;
border-bottom: 1px solid #eef2f7;
}
.tour-signup-card__title {
color: #111827;
font-size: 17px;
font-weight: 700;
line-height: 1.4;
}
.tour-signup-card__year {
flex-shrink: 0;
color: #0f74bc;
font-size: 13px;
font-weight: 600;
}
.tour-signup-notice {
margin-top: 14px;
color: #334155;
font-size: 15px;
line-height: 1.8;
word-break: break-word;
}
.tour-signup-notice /deep/ img,
.tour-signup-notice /deep/ video {
max-width: 100%;
height: auto;
}
.tour-pdf-preview {
min-height: 560px;
}
.tour-pdf-status {
padding: 16px 0;
color: #64748b;
font-size: 13px;
text-align: center;
}
.tour-pdf-toolbar {
margin-bottom: 8px;
color: #64748b;
font-size: 12px;
line-height: 1.5;
}
.tour-pdf-pages {
min-height: 180px;
}
.tour-pdf-page {
margin-bottom: 10px;
border-radius: 6px;
overflow: hidden;
background: #f8fafc;
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.08);
}
.tour-pdf-page canvas {
width: 100%;
display: block;
}
.tour-signup-empty {
padding: 34px 0 26px;
}
.tour-signup-footer {
position: fixed;
right: 0;
bottom: 0;
left: 0;
padding: 10px 12px calc(10px + env(safe-area-inset-bottom));
background: rgba(255, 255, 255, 0.96);
box-shadow: 0 -6px 18px rgba(15, 23, 42, 0.08);
box-sizing: border-box;
}
</style>
<div id="app" class="tour-signup-h5">
<van-nav-bar title="疗休养报名" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<div class="tour-signup-banner">
<van-swipe class="tour-signup-swipe" :autoplay="3500" indicator-color="white">
<van-swipe-item v-for="item in bannerList" :key="item">
<img :src="item" alt="疗休养报名">
</van-swipe-item>
</van-swipe>
<div class="tour-signup-banner__text">
<div class="tour-signup-title">疗休养报名</div>
</div>
</div>
<div class="tour-signup-card">
<div class="tour-signup-card__header">
<div class="tour-signup-card__title">服务须知</div>
<div class="tour-signup-card__year">{{ setting.year || currentYear }}年度</div>
</div>
<van-skeleton title :row="8" :loading="loading">
<div v-if="setting.serviceNotice" class="tour-signup-notice">
<div v-if="serviceNoticePdfHref" class="tour-pdf-preview">
<div v-if="pdfPageCount" class="tour-pdf-toolbar">
已加载 {{ pdfRenderedPages }} / {{ pdfPageCount }} 页
</div>
<div ref="pdfPreviewContainer" class="tour-pdf-pages"></div>
<div v-if="pdfLoading && pdfRenderedPages === 0" class="tour-pdf-status">
<van-loading size="22px" vertical>正在加载第一页...</van-loading>
</div>
<div v-else-if="pdfLoading" class="tour-pdf-status">
正在继续加载后续页面...
</div>
<div v-if="pdfError" class="tour-pdf-status">{{ pdfError }}</div>
</div>
<div v-else v-html="setting.serviceNotice"></div>
</div>
<van-empty v-else class="tour-signup-empty" description="暂无服务须知"></van-empty>
</van-skeleton>
</div>
<div class="tour-signup-footer">
<van-button block type="info" color="#0f74bc" round @click="confirmRead">我已阅读</van-button>
</div>
</div>
<script nonce="${cspNonce!}">
const vue = new Vue({
el: "#app",
store,
data() {
return {
loading: false,
currentYear: new Date().getFullYear(),
serviceNoticePdfHref: "",
pdfDoc: null,
pdfLoadingTask: null,
pdfLoading: false,
pdfError: "",
pdfPageCount: 0,
pdfRenderedPages: 0,
pdfRenderToken: 0,
bannerList: [
"/assets/platform/images/tour/tour-h5-banner-1.jpg",
"/assets/platform/images/tour/tour-h5-banner-2.jpg"
],
setting: {
year: "",
configName: "",
serviceNotice: ""
}
}
},
methods: {
parseContentHref(content) {
if (!content) {
return ""
}
try {
const parser = new DOMParser()
const doc = parser.parseFromString(content, "text/html")
const link = doc.querySelector("a[href]")
return link ? link.getAttribute("href") : ""
} catch (e) {
return ""
}
},
getPdfLib() {
return window.pdfjsLib || window["pdfjs-dist/build/pdf"]
},
loadPdfLib() {
const pdfLib = this.getPdfLib()
if (pdfLib && pdfLib.getDocument) {
return Promise.resolve(pdfLib)
}
return window.loadScriptOnce("/assets/platform/plugins/pdfJs/h5/pdf.js?v=20260524").then(() => {
const loadedPdfLib = this.getPdfLib()
if (!loadedPdfLib || !loadedPdfLib.getDocument) {
throw new Error("pdf lib load failed")
}
return loadedPdfLib
})
},
clearPdfPreview() {
this.pdfRenderToken += 1
this.pdfDoc = null
this.pdfLoading = false
this.pdfError = ""
this.pdfPageCount = 0
this.pdfRenderedPages = 0
if (this.pdfLoadingTask && this.pdfLoadingTask.destroy) {
try {
this.pdfLoadingTask.destroy()
} catch (e) {
}
}
this.pdfLoadingTask = null
if (this.$refs.pdfPreviewContainer) {
this.$refs.pdfPreviewContainer.innerHTML = ""
}
},
renderServiceNoticePdf() {
this.clearPdfPreview()
if (!this.serviceNoticePdfHref) {
return
}
const token = this.pdfRenderToken
this.pdfLoading = true
this.loadPdfLib().then((pdfLib) => {
if (token !== this.pdfRenderToken) {
return null
}
if (pdfLib.GlobalWorkerOptions) {
pdfLib.GlobalWorkerOptions.workerSrc = "/assets/platform/plugins/pdfJs/h5/pdf.worker.js?v=20260524"
}
this.pdfLoadingTask = pdfLib.getDocument({
url: this.serviceNoticePdfHref,
disableAutoFetch: false,
disableStream: false,
disableRange: false
})
return this.pdfLoadingTask.promise
}).then((pdf) => {
if (token !== this.pdfRenderToken || !pdf) {
return
}
this.pdfDoc = pdf
this.pdfPageCount = pdf.numPages || 0
return this.renderPdfPage(1, token)
}).then(() => {
if (token !== this.pdfRenderToken) {
return
}
this.renderRestPdfPages(2, token)
}).catch(() => {
if (token !== this.pdfRenderToken) {
return
}
this.pdfLoading = false
this.pdfError = "PDF加载失败,请稍后重试"
})
},
renderPdfPage(pageNumber, token) {
if (!this.pdfDoc || token !== this.pdfRenderToken || pageNumber > this.pdfPageCount) {
return Promise.resolve()
}
return this.pdfDoc.getPage(pageNumber).then((page) => {
if (token !== this.pdfRenderToken || !this.$refs.pdfPreviewContainer) {
return
}
const container = this.$refs.pdfPreviewContainer
const baseViewport = page.getViewport({scale: 1})
const containerWidth = Math.max(container.clientWidth || 0, window.innerWidth - 44)
const cssScale = containerWidth / baseViewport.width
const outputScale = Math.min(window.devicePixelRatio || 1, 2)
const viewport = page.getViewport({scale: cssScale})
const pageEl = document.createElement("div")
pageEl.className = "tour-pdf-page"
const canvas = document.createElement("canvas")
const context = canvas.getContext("2d")
canvas.width = Math.floor(viewport.width * outputScale)
canvas.height = Math.floor(viewport.height * outputScale)
canvas.style.width = Math.floor(viewport.width) + "px"
canvas.style.height = Math.floor(viewport.height) + "px"
pageEl.appendChild(canvas)
container.appendChild(pageEl)
return page.render({
canvasContext: context,
viewport: viewport,
transform: outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null
}).promise.then(() => {
if (token === this.pdfRenderToken) {
this.pdfRenderedPages = Math.max(this.pdfRenderedPages, pageNumber)
}
})
})
},
renderRestPdfPages(pageNumber, token) {
if (token !== this.pdfRenderToken || !this.pdfDoc || pageNumber > this.pdfPageCount) {
this.pdfLoading = false
return
}
setTimeout(() => {
this.renderPdfPage(pageNumber, token).then(() => {
this.renderRestPdfPages(pageNumber + 1, token)
}).catch(() => {
if (token === this.pdfRenderToken) {
this.pdfLoading = false
this.pdfError = "部分页面加载失败"
}
})
}, 120)
},
loadServiceNotice() {
this.loading = true
this.$axios.post("/platform/tour/signup/h5/serviceNotice").then((res) => {
this.loading = false
if (res.code === 0) {
this.setting = Object.assign({}, this.setting, res.data || {})
this.serviceNoticePdfHref = this.parseContentHref(this.setting.serviceNotice)
this.$nextTick(() => {
this.renderServiceNoticePdf()
})
} else {
vant.Toast(res.msg || "服务须知加载失败")
}
}).catch(() => {
this.loading = false
})
},
confirmRead() {
window.location.href = "/platform/tour/signup/h5/signup"
}
},
created() {
this.loadServiceNotice()
},
beforeDestroy() {
this.clearPdfPreview()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,587 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
#app {
height: 100vh;
overflow: hidden;
}
.tour-detail-page {
height: calc(100vh - 46px);
overflow: hidden;
display: flex;
flex-direction: column;
padding: 10px 10px 70px;
background: #f4f6f8;
box-sizing: border-box;
}
.tour-detail-header {
padding: 14px 12px;
border-radius: 8px;
background: #ffffff;
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05);
}
.tour-detail-title {
color: #0f172a;
font-size: 18px;
font-weight: 700;
line-height: 1.35;
word-break: break-word;
}
.tour-detail-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 8px;
}
.tour-line-tag {
display: inline-flex;
align-items: center;
height: 20px;
padding: 0 7px;
border-radius: 4px;
background: #0b75bd;
color: #ffffff;
font-size: 12px;
font-weight: 600;
line-height: 20px;
}
.tour-detail-meta {
margin-top: 10px;
color: #334155;
font-size: 13px;
line-height: 1.7;
word-break: break-word;
}
.tour-detail-meta-row {
display: flex;
align-items: flex-start;
min-width: 0;
}
.tour-detail-meta-label {
width: 72px;
flex-shrink: 0;
display: flex;
justify-content: space-between;
color: #64748b;
white-space: nowrap;
}
.tour-detail-meta-value {
min-width: 0;
flex: 1;
}
.tour-detail-section {
margin-top: 10px;
min-height: 0;
flex: 1;
display: flex;
flex-direction: column;
padding: 12px;
border-radius: 8px;
background: #ffffff;
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05);
box-sizing: border-box;
}
.tour-detail-section-title {
flex-shrink: 0;
margin-bottom: 8px;
padding-left: 8px;
border-left: 3px solid #0b75bd;
color: #0f172a;
font-size: 15px;
font-weight: 700;
line-height: 1.2;
}
.tour-detail-content /deep/ img {
max-width: 100%;
height: auto;
}
.tour-detail-section-scroll {
min-height: 0;
flex: 1;
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.tour-pdf-preview {
min-height: 220px;
}
.tour-pdf-status {
padding: 16px 0;
color: #64748b;
font-size: 13px;
text-align: center;
}
.tour-pdf-toolbar {
margin-bottom: 8px;
color: #64748b;
font-size: 12px;
line-height: 1.5;
}
.tour-pdf-pages {
min-height: 180px;
}
.tour-pdf-page {
margin-bottom: 10px;
border-radius: 6px;
overflow: hidden;
background: #f8fafc;
box-shadow: 0 1px 4px rgba(15, 23, 42, 0.08);
}
.tour-pdf-page canvas {
width: 100%;
display: block;
}
.tour-detail-empty {
padding: 18px 0;
color: #94a3b8;
font-size: 13px;
text-align: center;
}
.tour-detail-footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 10;
padding: 9px 12px calc(9px + env(safe-area-inset-bottom));
border-top: 1px solid #edf0f4;
background: #ffffff;
box-sizing: border-box;
}
</style>
<div id="app">
<van-nav-bar title="线路详情" left-text="返回" left-arrow @click-left="historyBack('/platform/tour/signup/h5/signup')" fixed placeholder></van-nav-bar>
<div class="tour-detail-page">
<van-loading v-if="detailLoading" size="24px" vertical>加载中...</van-loading>
<template v-else>
<div class="tour-detail-header">
<div class="tour-detail-title">{{ lineDetail.lineName || signupDetail.lineName || '线路详情' }}</div>
<div class="tour-detail-tags">
<span v-if="lineDetail.lotName || signupDetail.lotName" class="tour-line-tag">{{ lineDetail.lotName || signupDetail.lotName }}</span>
<span v-if="lineDetail.lineType || signupDetail.lineType" class="tour-line-tag">{{ lineDetail.lineType || signupDetail.lineType }}</span>
<span v-if="signupDetail.unionName" class="tour-line-tag">{{ signupDetail.unionName }}</span>
</div>
<div class="tour-detail-meta">
<div v-if="travelPeriod(signupDetail)" class="tour-detail-meta-row">
<span class="tour-detail-meta-label"><span>出行时间</span><span></span></span>
<span class="tour-detail-meta-value">{{ travelPeriod(signupDetail) }}</span>
</div>
<div class="tour-detail-meta-row">
<span class="tour-detail-meta-label"><span>已报人数</span><span></span></span>
<span class="tour-detail-meta-value">{{ signupDetail.signupCount || 0 }}(家属{{ signupDetail.familyCount || 0 }}人)</span>
</div>
<div class="tour-detail-meta-row">
<span class="tour-detail-meta-label"><span>联系人</span><span></span></span>
<span class="tour-detail-meta-value">{{ signupDetail.contactName || '暂无' }}</span>
</div>
<div class="tour-detail-meta-row">
<span class="tour-detail-meta-label"><span>联系方式</span><span></span></span>
<span class="tour-detail-meta-value">{{ lineDetail.contactPhone || signupDetail.contactPhone || '暂无' }}</span>
</div>
</div>
</div>
<div class="tour-detail-section">
<div class="tour-detail-section-title">线路介绍</div>
<div class="tour-detail-section-scroll">
<div v-if="lineDetail.lineContent && lineContentHref" class="tour-pdf-preview">
<div v-if="pdfPageCount" class="tour-pdf-toolbar">
已加载 {{ pdfRenderedPages }} / {{ pdfPageCount }} 页
</div>
<div ref="pdfPreviewContainer" class="tour-pdf-pages"></div>
<div v-if="pdfLoading && pdfRenderedPages === 0" class="tour-pdf-status">
<van-loading size="22px" vertical>正在加载第一页...</van-loading>
</div>
<div v-else-if="pdfLoading" class="tour-pdf-status">
正在继续加载后续页面...
</div>
<div v-if="pdfError" class="tour-pdf-status">{{ pdfError }}</div>
</div>
<div v-else-if="lineDetail.lineContent" class="tour-detail-content" v-html="lineDetail.lineContent">
</div>
<div v-else class="tour-detail-empty">暂无线路介绍</div>
</div>
</div>
</template>
</div>
<div class="tour-detail-footer">
<van-button :type="detailActionType()" block round :disabled="detailActionDisabled()" @click="handleDetailAction">
{{ detailActionText() }}
</van-button>
</div>
</div>
<script nonce="${cspNonce!}">
const vue = new Vue({
el: "#app",
store,
data() {
return {
detailLoading: true,
lineDetail: {},
signupDetail: {},
lineContentHref: "",
pdfDoc: null,
pdfLoadingTask: null,
pdfLoading: false,
pdfError: "",
pdfPageCount: 0,
pdfRenderedPages: 0,
pdfRenderToken: 0,
matterId: GetQueryString("matterId") || "",
lineId: GetQueryString("lineId") || ""
}
},
methods: {
travelPeriod(row) {
if (!row) {
return ""
}
if (row.travelStartTime && row.travelEndTime) {
return row.travelStartTime + " 至 " + row.travelEndTime
}
return row.travelPeriod || row.travelStartTime || row.travelEndTime || ""
},
toBoolean(value) {
return value === true || value === 1 || value === "1" || value === "true" || value === "TRUE"
},
isSigned() {
return !!(this.signupDetail && this.signupDetail.signed)
},
isApprovalSignup() {
return this.isSigned() && !!this.signupDetail.instanceId
},
canRevokeSignup() {
return this.isApprovalSignup() && this.toBoolean(this.signupDetail.canRevoke) && !!this.signupDetail.startTaskId
},
isFinishedSignup() {
return this.isApprovalSignup() && Number(this.signupDetail.instanceState) === 20
},
isRejectedSignup() {
return this.isApprovalSignup() && Number(this.signupDetail.instanceState) === 45
},
canModifySignup() {
if (!this.isSigned()) {
return true
}
if (!this.isApprovalSignup()) {
return true
}
return !this.isFinishedSignup() && !this.isRejectedSignup() && this.signupDetail.taskKey === "startTask"
},
detailActionText() {
if (this.isSigned()) {
if (this.canRevokeSignup()) {
return "撤回"
}
if (this.isFinishedSignup()) {
return "已完成"
}
if (this.isRejectedSignup()) {
return "已拒绝"
}
return this.canModifySignup() ? "修改报名" : "审核中"
}
return "我要报名"
},
detailActionType() {
return this.canRevokeSignup() ? "danger" : "info"
},
detailActionDisabled() {
return this.isSigned() && this.isApprovalSignup() && (this.isFinishedSignup() || this.isRejectedSignup() || (!this.canRevokeSignup() && !this.canModifySignup()))
},
handleDetailAction() {
if (this.detailActionDisabled()) {
return
}
if (this.canRevokeSignup()) {
this.revokeSignup()
return
}
this.onApply()
},
parseLineContentHref(content) {
if (!content) {
return ""
}
try {
const parser = new DOMParser()
const doc = parser.parseFromString(content, "text/html")
const link = doc.querySelector("a[href]")
return link ? link.getAttribute("href") : ""
} catch (e) {
return ""
}
},
getPdfLib() {
return window.pdfjsLib || window["pdfjs-dist/build/pdf"]
},
loadPdfLib() {
const pdfLib = this.getPdfLib()
if (pdfLib && pdfLib.getDocument) {
return Promise.resolve(pdfLib)
}
return window.loadScriptOnce("/assets/platform/plugins/pdfJs/h5/pdf.js?v=20260524").then(() => {
const loadedPdfLib = this.getPdfLib()
if (!loadedPdfLib || !loadedPdfLib.getDocument) {
throw new Error("pdf lib load failed")
}
return loadedPdfLib
})
},
clearPdfPreview() {
this.pdfRenderToken += 1
this.pdfDoc = null
this.pdfLoading = false
this.pdfError = ""
this.pdfPageCount = 0
this.pdfRenderedPages = 0
if (this.pdfLoadingTask && this.pdfLoadingTask.destroy) {
try {
this.pdfLoadingTask.destroy()
} catch (e) {
}
}
this.pdfLoadingTask = null
if (this.$refs.pdfPreviewContainer) {
this.$refs.pdfPreviewContainer.innerHTML = ""
}
},
renderLinePdf() {
this.clearPdfPreview()
if (!this.lineContentHref) {
return
}
if (!this.getPdfLib() || !this.getPdfLib().getDocument) {
const token = this.pdfRenderToken
this.pdfLoading = true
this.loadPdfLib().then(() => {
if (token === this.pdfRenderToken) {
this.renderLinePdf()
}
}).catch(() => {
if (token === this.pdfRenderToken) {
this.pdfLoading = false
this.pdfError = "PDF预览组件加载失败"
}
})
return
}
const pdfLib = this.getPdfLib()
if (!pdfLib || !pdfLib.getDocument) {
this.pdfError = "PDF预览组件加载失败"
return
}
if (pdfLib.GlobalWorkerOptions) {
pdfLib.GlobalWorkerOptions.workerSrc = "/assets/platform/plugins/pdfJs/h5/pdf.worker.js?v=20260524"
}
const token = this.pdfRenderToken
this.pdfLoading = true
this.pdfLoadingTask = pdfLib.getDocument({
url: this.lineContentHref,
disableAutoFetch: false,
disableStream: false,
disableRange: false
})
this.pdfLoadingTask.promise.then((pdf) => {
if (token !== this.pdfRenderToken) {
return
}
this.pdfDoc = pdf
this.pdfPageCount = pdf.numPages || 0
return this.renderPdfPage(1, token)
}).then(() => {
if (token !== this.pdfRenderToken) {
return
}
this.renderRestPdfPages(2, token)
}).catch(() => {
if (token !== this.pdfRenderToken) {
return
}
this.pdfLoading = false
this.pdfError = "PDF加载失败,请稍后重试"
})
},
renderPdfPage(pageNumber, token) {
if (!this.pdfDoc || token !== this.pdfRenderToken || pageNumber > this.pdfPageCount) {
return Promise.resolve()
}
return this.pdfDoc.getPage(pageNumber).then((page) => {
if (token !== this.pdfRenderToken || !this.$refs.pdfPreviewContainer) {
return
}
const container = this.$refs.pdfPreviewContainer
const baseViewport = page.getViewport({scale: 1})
const containerWidth = Math.max(container.clientWidth || 0, window.innerWidth - 44)
const cssScale = containerWidth / baseViewport.width
const outputScale = Math.min(window.devicePixelRatio || 1, 2)
const viewport = page.getViewport({scale: cssScale})
const pageEl = document.createElement("div")
pageEl.className = "tour-pdf-page"
const canvas = document.createElement("canvas")
const context = canvas.getContext("2d")
canvas.width = Math.floor(viewport.width * outputScale)
canvas.height = Math.floor(viewport.height * outputScale)
canvas.style.width = Math.floor(viewport.width) + "px"
canvas.style.height = Math.floor(viewport.height) + "px"
pageEl.appendChild(canvas)
container.appendChild(pageEl)
const renderContext = {
canvasContext: context,
viewport: viewport,
transform: outputScale !== 1 ? [outputScale, 0, 0, outputScale, 0, 0] : null
}
return page.render(renderContext).promise.then(() => {
if (token === this.pdfRenderToken) {
this.pdfRenderedPages = Math.max(this.pdfRenderedPages, pageNumber)
}
})
})
},
renderRestPdfPages(pageNumber, token) {
if (token !== this.pdfRenderToken || !this.pdfDoc || pageNumber > this.pdfPageCount) {
this.pdfLoading = false
return
}
setTimeout(() => {
this.renderPdfPage(pageNumber, token).then(() => {
this.renderRestPdfPages(pageNumber + 1, token)
}).catch(() => {
if (token === this.pdfRenderToken) {
this.pdfLoading = false
this.pdfError = "部分页面加载失败"
}
})
}, 120)
},
loadDetail() {
if (!this.matterId || !this.lineId) {
this.detailLoading = false
vant.Dialog.alert({
title: "提示",
message: "线路参数不完整",
confirmButtonColor: "#1867b0"
})
return
}
Promise.all([
this.$axios.post("/platform/tour/signup/lineDetail", { lineId: this.lineId }),
this.$axios.post("/platform/tour/signup/signupDetail", { matterId: this.matterId })
]).then(([lineRes, signupRes]) => {
this.detailLoading = false
if (lineRes.code !== 0) {
vant.Toast(lineRes.msg || "线路详情加载失败")
} else {
this.lineDetail = lineRes.data || {}
this.lineContentHref = this.parseLineContentHref(this.lineDetail.lineContent)
this.$nextTick(() => {
this.renderLinePdf()
})
}
if (signupRes.code !== 0) {
vant.Toast(signupRes.msg || "报名信息加载失败")
} else {
const data = signupRes.data || {}
const process = data.process || {}
this.signupDetail = Object.assign({}, data.matter || {}, {
signed: !!(data.ledger && data.ledger.id)
}, process)
}
}).catch(() => {
this.detailLoading = false
vant.Toast("线路详情加载失败")
})
},
onApply() {
if (!this.matterId) {
vant.Toast("报名事项信息不完整")
return
}
const goApply = () => {
window.location.href = "/platform/tour/signup/h5/apply?matterId=" + encodeURIComponent(this.matterId)
}
this.$axios.post("/platform/tour/signup/signupEligibilityNotice", { matterId: this.matterId }).then((res) => {
if (res.code !== 0) {
vant.Dialog.alert({
title: "温馨提醒",
message: res.msg || "当前不能报名",
confirmButtonColor: "#1867b0"
})
return
}
const data = res.data || {}
if (!data.noticeRequired) {
goApply()
return
}
if (data.canApply) {
vant.Dialog.confirm({
title: "温馨提醒",
message: data.message || "当前可报名,是否继续报名?",
confirmButtonText: "继续报名",
cancelButtonText: "稍后报名",
confirmButtonColor: "#1867b0"
}).then(goApply).catch(() => {})
} else {
vant.Dialog.alert({
title: "温馨提醒",
message: data.message || "当前不能报名",
confirmButtonColor: "#1867b0"
})
}
})
},
revokeSignup() {
vant.Dialog.confirm({
title: "提示",
message: "您确定要撤回吗?",
confirmButtonColor: "#ee2f2f"
}).then(() => {
this.$axios.post("/flow/common/revokeTask", { taskId: this.signupDetail.startTaskId }).then((resp) => {
if (resp.code === 0) {
vant.Toast.success(resp.msg || "撤回成功")
this.loadDetail()
} else {
vant.Dialog.alert({ title: "提示", message: resp.msg || "撤回失败", confirmButtonColor: "#1867b0" })
}
})
}).catch(() => {})
}
},
created() {
this.loadDetail()
},
beforeDestroy() {
this.clearPdfPreview()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,794 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.tour-line-page {
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
background: #f4f6f8;
box-sizing: border-box;
}
.tour-line-scroll {
min-height: 0;
flex: 1;
overflow-y: auto;
padding-bottom: 18px;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
}
.tour-line-toolbar {
padding: 10px 0 8px 16px;
background: #f4f6f8;
}
.tour-line-toolbar /deep/ .van-search {
flex: 1;
min-width: 0;
padding: 0;
background: transparent;
}
.tour-line-toolbar .van-search {
flex: 1;
min-width: 0;
padding: 0;
background: transparent;
}
.tour-line-toolbar /deep/ .van-search__content {
height: 38px;
border-radius: 8px;
background: #ffffff;
}
.tour-line-toolbar .van-search__content {
height: 38px;
border-radius: 8px;
background: #ffffff;
}
.tour-line-toolbar /deep/ .van-search__action {
padding: 0 0 0 6px;
}
.tour-line-toolbar .van-search__action {
padding: 0 0 0 6px;
}
.tour-line-search-button {
width: 42px;
height: 38px;
border: 0;
border-radius: 8px;
background: #1e88e5;
color: #ffffff;
display: flex;
align-items: center;
justify-content: center;
font-size: 20px;
line-height: 1;
}
.tour-line-search-row {
width: 100%;
display: flex;
align-items: center;
justify-content: flex-end;
gap: 4px;
box-sizing: border-box;
}
.tour-line-union-entry {
width: 48px;
min-height: 42px;
flex-shrink: 0;
border: 0;
padding: 0;
background: transparent;
color: #64748b;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
font-size: 11px;
line-height: 1.2;
}
.tour-line-union-entry .van-icon {
color: #64748b;
font-size: 24px;
line-height: 1;
}
.tour-line-union-entry--active,
.tour-line-union-entry--active .van-icon {
color: #0b75bd;
}
.tour-line-union-popup {
max-height: 68vh;
display: flex;
flex-direction: column;
}
.tour-line-union-popup__title {
position: sticky;
top: 0;
z-index: 1;
padding: 14px 16px 10px;
background: #ffffff;
color: #0f172a;
font-size: 16px;
font-weight: 700;
text-align: center;
box-sizing: border-box;
}
.tour-line-union-popup__list {
overflow-y: auto;
padding-bottom: env(safe-area-inset-bottom);
}
.tour-line-union-popup__item {
min-height: 48px;
transition: background-color 0.18s ease, color 0.18s ease;
}
.tour-line-union-popup__item:hover,
.tour-line-union-popup__item:active {
background: rgba(30, 136, 229, 0.1);
}
.tour-line-union-popup__item:hover /deep/ .van-cell__title,
.tour-line-union-popup__item:active /deep/ .van-cell__title {
color: #0b75bd;
}
.tour-line-union-popup__item--active /deep/ .van-cell__title {
color: #0b75bd;
font-weight: 700;
}
.tour-line-shortcuts {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
margin-top: 10px;
}
.tour-line-shortcut {
min-height: 76px;
padding: 12px 8px;
border: 1px solid transparent;
border-radius: 8px;
background: #ffffff;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 5px;
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05);
box-sizing: border-box;
}
.tour-line-shortcut--active {
border-color: #0b75bd;
background: rgba(11, 117, 189, 0.08);
}
.tour-line-shortcut__icon {
width: 42px;
height: 42px;
flex-shrink: 0;
display: block;
border-radius: 50%;
object-fit: cover;
}
.tour-line-shortcut__text {
min-width: 0;
color: #0f172a;
font-size: 13px;
font-weight: 700;
line-height: 1.3;
text-align: center;
}
.tour-line-shortcut__hint {
margin-top: 2px;
color: #64748b;
font-size: 11px;
font-weight: 400;
}
.tour-line-list {
padding: 0 8px;
}
.tour-line-card {
position: relative;
display: flex;
gap: 12px;
min-height: 112px;
margin: 8px 0;
padding: 10px;
border-radius: 6px;
background: #ffffff;
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05);
box-sizing: border-box;
}
.tour-line-thumb {
width: 145px;
height: 98px;
flex-shrink: 0;
overflow: hidden;
border-radius: 6px;
background: #e5edf6;
}
.tour-line-thumb img,
.tour-line-thumb .van-image {
width: 100%;
height: 100%;
display: block;
}
.tour-line-thumb-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #8a98a8;
font-size: 12px;
}
.tour-line-main {
min-width: 0;
flex: 1;
}
.tour-line-signed-icon {
width: 20px;
height: 20px;
flex-shrink: 0;
border-radius: 50%;
background: #a78bfa;
color: #ffffff;
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 13px;
line-height: 1;
box-shadow: 0 2px 5px rgba(167, 139, 250, 0.32);
}
.tour-line-title-row {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
.tour-line-title {
min-width: 0;
color: #0f172a;
font-size: 16px;
font-weight: 700;
line-height: 1.35;
word-break: break-word;
}
.tour-line-type-badge {
height: 18px;
flex-shrink: 0;
padding: 0 5px;
border: 1px solid #ffd6a8;
border-radius: 4px;
background: #fff3e5;
color: #d97706;
font-size: 11px;
font-weight: 700;
line-height: 17px;
box-sizing: border-box;
}
.tour-line-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-top: 5px;
}
.tour-line-tag {
display: inline-flex;
align-items: center;
height: 20px;
padding: 0 7px;
border-radius: 4px;
background: #39a9ed;
color: #ffffff;
font-size: 12px;
font-weight: 600;
line-height: 20px;
}
.tour-line-info {
margin-top: 5px;
color: #111827;
font-size: 13px;
line-height: 1.55;
word-break: break-word;
}
.tour-line-info span {
color: #64748b;
}
.tour-line-actions {
display: flex;
justify-content: flex-end;
margin-top: 8px;
}
.tour-line-action {
min-width: 70px;
height: 28px;
padding: 0 10px;
border: 1px solid #1e88e5;
border-radius: 14px;
background: #1e88e5;
color: #ffffff;
font-size: 12px;
line-height: 26px;
}
.tour-line-action--danger {
border-color: #ee2f2f;
background: #ee2f2f;
}
.tour-line-action--disabled {
border-color: #d1d5db;
background: #e5e7eb;
color: #94a3b8;
}
@media (max-width: 360px) {
.tour-line-card {
gap: 9px;
padding: 8px;
}
.tour-line-thumb {
width: 126px;
height: 92px;
}
.tour-line-title {
font-size: 15px;
}
}
</style>
<div id="app" class="tour-line-page">
<van-nav-bar title="疗休养线路" left-text="返回" left-arrow @click-left="historyBack('/platform/tour/signup/h5')" fixed placeholder></van-nav-bar>
<div class="tour-line-toolbar">
<div class="tour-line-search-row">
<van-search
v-model="pageForm.lineName"
show-action
clearable
placeholder="请输入线路名称搜索"
@search="doSearch"
@clear="doSearch">
<template #action>
<button type="button" class="tour-line-search-button" @click="doSearch">
<van-icon name="search"></van-icon>
</button>
</template>
</van-search>
<button
type="button"
class="tour-line-union-entry"
:class="{'tour-line-union-entry--active': !!pageForm.unionId}"
@click="openUnionPopup">
<van-icon name="friends-o"></van-icon>
<span>分工会</span>
</button>
</div>
<div class="tour-line-shortcuts">
<div
class="tour-line-shortcut"
:class="{'tour-line-shortcut--active': pageForm.lineType === '省内线路' && pageForm.directFamilyUnitLine === null}"
@click="toggleLineType('省内线路')">
<img class="tour-line-shortcut__icon" src="/assets/platform/images/tour/tour-in-province.png" alt="省内线路">
<div class="tour-line-shortcut__text">
<div>省内线路</div>
</div>
</div>
<div
class="tour-line-shortcut"
:class="{'tour-line-shortcut--active': pageForm.lineType === '省外线路' && pageForm.directFamilyUnitLine === null}"
@click="toggleLineType('省外线路')">
<img class="tour-line-shortcut__icon" src="/assets/platform/images/tour/tour-out-province.png" alt="省外线路">
<div class="tour-line-shortcut__text">
<div>省外线路</div>
</div>
</div>
<div
class="tour-line-shortcut"
:class="{'tour-line-shortcut--active': pageForm.directFamilyUnitLine === true}"
@click="toggleDirectFamilyLine">
<img class="tour-line-shortcut__icon" src="/assets/platform/images/tour/tour-direct-family-icon.png?v=20260524" alt="直系亲属">
<div class="tour-line-shortcut__text">
<div>直系亲属</div>
</div>
</div>
</div>
</div>
<div class="tour-line-scroll">
<van-list
v-model="loading"
:finished="finished"
finished-text="没有更多了"
@load="loadData">
<div class="tour-line-list">
<div class="tour-line-card" v-for="row in list" :key="row.matterId" @click="onLine(row)">
<div class="tour-line-thumb">
<van-image v-if="thumbUrl(row)" :src="thumbUrl(row)" fit="cover" lazy-load>
<template #loading>
<div class="tour-line-thumb-empty">图片加载中</div>
</template>
<template #error>
<div class="tour-line-thumb-empty">暂无图片</div>
</template>
</van-image>
<div v-else class="tour-line-thumb-empty">暂无图片</div>
</div>
<div class="tour-line-main">
<div class="tour-line-title-row">
<div class="tour-line-title">{{ row.lineName || '未命名线路' }}</div>
<span v-if="lineTypeBadge(row.lineType)" class="tour-line-type-badge">{{ lineTypeBadge(row.lineType) }}</span>
<span v-if="isSigned(row)" class="tour-line-signed-icon">
<van-icon name="good-job"></van-icon>
</span>
</div>
<div class="tour-line-tags">
<span v-if="row.lotName" class="tour-line-tag">{{ row.lotName }}</span>
<span v-if="row.unionName" class="tour-line-tag">{{ row.unionName }}</span>
</div>
<div class="tour-line-info">
<div><span>出行开始时间:</span>{{ row.travelStartTime || '暂无' }}</div>
<div><span>出行结束时间:</span>{{ row.travelEndTime || '暂无' }}</div>
<!-- 临时屏蔽旅行社信息,保留字段和接口便于后续恢复。
<div><span>旅行社:</span>{{ row.travelAgencyName || '暂无' }}</div>
-->
</div>
<div class="tour-line-actions">
<button
v-if="showSignupAction(row)"
type="button"
class="tour-line-action"
:class="{
'tour-line-action--danger': canRevokeSignup(row),
'tour-line-action--disabled': signupActionDisabled(row)
}"
:disabled="signupActionDisabled(row)"
@click.stop="handleSignupAction(row)">
{{ signupActionText(row) }}
</button>
</div>
</div>
</div>
</div>
</van-list>
<van-empty v-if="!loading && list.length === 0" description="暂无线路"></van-empty>
</div>
<van-popup v-model="showUnionPopup" position="bottom" round>
<div class="tour-line-union-popup">
<div class="tour-line-union-popup__title">选择分工会</div>
<div class="tour-line-union-popup__list">
<van-cell
v-for="item in unionList"
:key="item.id || 'all'"
clickable
class="tour-line-union-popup__item"
:class="{'tour-line-union-popup__item--active': pageForm.unionId === item.id}"
:title="item.name"
@click="selectUnion(item)">
<template #right-icon>
<van-icon v-if="pageForm.unionId === item.id" name="success" color="#0b75bd"></van-icon>
</template>
</van-cell>
</div>
</div>
</van-popup>
</div>
<script nonce="${cspNonce!}">
const vue = new Vue({
el: "#app",
store,
data() {
return {
list: [],
loading: false,
finished: false,
lineChecking: false,
showUnionPopup: false,
unionList: [
{id: "", name: "全部分工会"}
],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
year: new Date().getFullYear(),
lineName: "",
lineType: "",
unionId: "",
directFamilyUnitLine: null,
pageOrderName: "lineName",
pageOrderBy: "ascending"
}
}
},
methods: {
thumbUrl(row) {
return row.lineMobileThumb || row.agencyMobileThumb || ""
},
lineTypeBadge(lineType) {
if (lineType === "省内线路" || lineType === "省内") {
return "省内"
}
if (lineType === "省外线路" || lineType === "省外") {
return "省外"
}
return ""
},
isSigned(row) {
return !!(row && (row.signed === true || row.signed === 1 || row.signed === "1" || row.ledgerId))
},
isApprovalSignup(row) {
return !!(row && this.isSigned(row) && row.instanceId)
},
canRevokeSignup(row) {
return this.isApprovalSignup(row) && this.toBoolean(row.canRevoke) && !!row.startTaskId
},
isFinishedSignup(row) {
return this.isApprovalSignup(row) && Number(row.instanceState) === 20
},
isRejectedSignup(row) {
return this.isApprovalSignup(row) && Number(row.instanceState) === 45
},
canModifySignup(row) {
if (!row || !this.isSigned(row)) {
return true
}
if (!this.isApprovalSignup(row)) {
return true
}
return !this.isFinishedSignup(row) && !this.isRejectedSignup(row) && row.taskKey === "startTask"
},
signupActionText(row) {
if (this.isSigned(row)) {
if (this.canRevokeSignup(row)) {
return "撤回"
}
if (this.isRejectedSignup(row)) {
return "已拒绝"
}
return this.canModifySignup(row) ? "修改" : "审核中"
}
return "报名"
},
showSignupAction(row) {
return this.isSigned(row) && this.canModifySignup(row)
},
signupActionDisabled(row) {
return !!(this.isSigned(row) && this.isApprovalSignup(row) && (this.isRejectedSignup(row) || (!this.canRevokeSignup(row) && !this.canModifySignup(row))))
},
toBoolean(value) {
return value === true || value === 1 || value === "1" || value === "true" || value === "TRUE"
},
handleSignupAction(row) {
if (this.signupActionDisabled(row)) {
return
}
if (this.canRevokeSignup(row)) {
this.revokeSignup(row)
return
}
this.goApply(row)
},
goApply(row) {
if (!row || !row.matterId) {
vant.Toast("报名事项信息不完整")
return
}
const go = () => {
window.location.href = "/platform/tour/signup/h5/apply?matterId=" + encodeURIComponent(row.matterId)
}
this.$axios.post("/platform/tour/signup/signupEligibilityNotice", { matterId: row.matterId }).then((res) => {
if (res.code !== 0) {
vant.Dialog.alert({
title: "温馨提醒",
message: res.msg || "当前不能报名",
confirmButtonColor: "#1867b0"
})
return
}
const data = res.data || {}
if (!data.noticeRequired) {
go()
return
}
if (data.canApply) {
vant.Dialog.confirm({
title: "温馨提醒",
message: data.message || "当前可报名,是否继续报名?",
confirmButtonText: "继续报名",
cancelButtonText: "稍后报名",
confirmButtonColor: "#1867b0"
}).then(go).catch(() => {})
} else {
vant.Dialog.alert({
title: "温馨提醒",
message: data.message || "当前不能报名",
confirmButtonColor: "#1867b0"
})
}
})
},
revokeSignup(row) {
vant.Dialog.confirm({
title: "提示",
message: "您确定要撤回吗?",
confirmButtonColor: "#ee2f2f"
}).then(() => {
this.$axios.post("/flow/common/revokeTask", { taskId: row.startTaskId }).then((resp) => {
if (resp.code === 0) {
vant.Toast.success(resp.msg || "撤回成功")
this.doSearch()
} else {
vant.Dialog.alert({ title: "提示", message: resp.msg || "撤回失败", confirmButtonColor: "#1867b0" })
}
})
}).catch(() => {})
},
doSearch() {
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.list = []
this.finished = false
this.loadData()
},
toggleLineType(lineType) {
const active = this.pageForm.lineType === lineType && this.pageForm.directFamilyUnitLine === null
this.pageForm.lineType = active ? "" : lineType
this.pageForm.directFamilyUnitLine = null
this.doSearch()
},
toggleDirectFamilyLine() {
const active = this.pageForm.directFamilyUnitLine === true
this.pageForm.directFamilyUnitLine = active ? null : true
this.pageForm.lineType = ""
this.doSearch()
},
openUnionPopup() {
this.showUnionPopup = true
if (this.unionList.length <= 1) {
this.loadUnionOptions()
}
},
selectUnion(item) {
const unionId = item && item.id ? item.id : ""
this.showUnionPopup = false
if (this.pageForm.unionId === unionId) {
return
}
this.pageForm.unionId = unionId
this.doSearch()
},
loadUnionOptions() {
this.$axios.post("/platform/tour/signup/unionOptions").then((res) => {
if (res.code !== 0) {
return
}
const rows = res.data || []
this.unionList = [{id: "", name: "全部分工会"}].concat(rows.map((item) => {
return {
id: item.id || "",
name: item.name || "未命名分工会"
}
}))
})
},
loadData() {
if (this.finished) {
this.loading = false
return
}
this.loading = true
this.$axios.post("/platform/tour/signup/h5/pageData", this.pageForm).then((res) => {
this.loading = false
if (res.code !== 0) {
vant.Toast(res.msg || "线路加载失败")
this.finished = true
return
}
const data = res.data || {}
const rows = data.list || []
this.pageForm.totalCount = data.totalCount || 0
this.list = this.pageForm.pageNumber === 1 ? rows : this.list.concat(rows)
if (this.list.length >= this.pageForm.totalCount || rows.length === 0) {
this.finished = true
} else {
this.pageForm.pageNumber += 1
}
}).catch(() => {
this.loading = false
this.finished = true
})
},
onLine(row) {
if (!row || !row.lineId || !row.matterId) {
vant.Toast("线路信息不完整")
return
}
if (this.lineChecking) {
return
}
this.lineChecking = true
axios.post("/platform/tour/signup/signupDetail", $.param({ matterId: row.matterId }), {
headers: {
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
"x-requested-with": "XMLHttpRequest"
}
}).then((resp) => {
this.lineChecking = false
const res = resp.data || {}
if (res.code !== 0) {
vant.Dialog.alert({
title: "温馨提醒",
message: res.msg || "当前线路暂不能报名",
confirmButtonColor: "#1867b0"
})
return
}
window.location.href = "/platform/tour/signup/h5/lineInfo?matterId="
+ encodeURIComponent(row.matterId)
+ "&lineId="
+ encodeURIComponent(row.lineId)
}).catch((err) => {
this.lineChecking = false
vant.Dialog.alert({
title: "温馨提醒",
message: err && err.msg ? err.msg : "当前线路暂不能报名",
confirmButtonColor: "#1867b0"
})
})
}
},
created() {
this.loadUnionOptions()
this.loadData()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,455 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.tour-approval-page {
height: 100vh;
overflow: hidden;
display: flex;
flex-direction: column;
background: #f4f6f8;
box-sizing: border-box;
}
.tour-approval-sticky {
flex-shrink: 0;
background: #f4f6f8;
}
.tour-approval-list-scroll {
min-height: 0;
flex: 1;
overflow-y: auto;
padding-bottom: 18px;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
}
.tour-approval-search /deep/ .van-search__content,
.tour-approval-search .van-search__content {
border-radius: 8px;
background: #ffffff;
}
.tour-card-tags {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 8px;
}
.tour-tag {
display: inline-flex;
align-items: center;
min-height: 22px;
padding: 0 8px;
border-radius: 4px;
background: #eef6ff;
color: #0b75bd;
font-size: 12px;
}
.tour-tag.gray {
background: #f1f5f9;
color: #64748b;
}
.tour-tag.warn {
background: #fff3e5;
color: #d97706;
}
.tour-approval-sheet {
max-height: 86vh;
overflow-y: auto;
padding-bottom: env(safe-area-inset-bottom);
background: #f4f6f8;
}
.tour-section {
margin: 10px 12px;
overflow: hidden;
border-radius: 8px;
background: #ffffff;
}
.tour-section-title {
position: relative;
padding: 14px 16px;
border-bottom: 1px solid #edf0f4;
color: #0b75bd;
font-size: 16px;
font-weight: 700;
line-height: 1.2;
}
.tour-section-title::before {
content: "";
position: absolute;
left: 16px;
top: 0;
width: 30px;
height: 4px;
border-radius: 0 0 3px 3px;
background: #0b75bd;
}
.tour-approval-actions {
display: flex;
gap: 8px;
padding: 10px 12px 14px;
background: #ffffff;
}
.tour-process-opinion {
white-space: pre-wrap;
word-break: break-word;
}
</style>
<div id="app" class="tour-approval-page" v-cloak>
<van-nav-bar title="分工会审核" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<van-sticky offset-top="46px" class="tour-approval-sticky">
<van-search
class="tour-approval-search"
v-model="pageForm.keyword"
placeholder="请输入工号/姓名/线路搜索"
show-action
clearable
input-align="left"
@search="doSearch"
@clear="doSearch">
<template #action>
<div @click="doSearch">搜索</div>
</template>
</van-search>
<van-dropdown-menu :close-on-click-outside="false" :close-on-click-overlay="false">
<van-dropdown-item :options="auditOptions" @change="doSearch" v-model="pageForm.audit"></van-dropdown-item>
<van-dropdown-item :options="lineTypeOptions" @change="doSearch" v-model="pageForm.lineType"></van-dropdown-item>
</van-dropdown-menu>
</van-sticky>
<div class="tour-approval-list-scroll">
<table-list api="/platform/tour/unionApproval/pageData" :page_form.sync="pageForm" ref="tableListRef" title="userName" @ready="onReady">
<template v-slot="{index,row}">
<div class="tour-card-tags">
<span v-if="row.jobNo" class="tour-tag">{{ row.jobNo }}</span>
<span v-if="row.lineType" class="tour-tag gray">{{ row.lineType }}</span>
<span v-if="row.overCostReimbursed" class="tour-tag warn">报销超出费用</span>
</div>
<table-column label="线路名称">{{ row.lineName || '' }}</table-column>
<table-column label="出行时段">{{ row.travelPeriod || '' }}</table-column>
<table-column label="报销超出费用">{{ row.overCostReimbursed ? '是' : '否' }}</table-column>
<table-column label="当前节点">{{ row.curTaskName || '' }}</table-column>
<table-column label="流程状态">
<enum-tag
v-if="row.instanceState !== null && row.instanceState !== undefined && row.instanceState !== ''"
:value="row.instanceState"
name="ProcessInstanceStateEnum"
label_key="message"
size="small">
</enum-tag>
</table-column>
</template>
<template #actions="{index,row}">
<div class="action-btn" @click="onView(row)">
<i class="fa fa-eye"></i>
<span>查看</span>
</div>
<div class="action-btn" v-if="row.taskState === 10" @click="onApproval(row)">
<i class="fa fa-check"></i>
<span>审核</span>
</div>
</template>
</table-list>
</div>
<tour-info ref="infoRef">
<div v-if="showApprovalForm" class="tour-section">
<div class="tour-section-title">{{ formData.taskName || '审核' }}</div>
<van-form ref="formRef">
<van-field
v-model="formData.tf_opinion"
name="tf_opinion"
label="审批意见"
placeholder="请输入审批意见"
:rules="[{ required: true, message: '请填写审批意见' }]"
required
type="textarea"
rows="3"
maxlength="200"
show-word-limit>
</van-field>
</van-form>
<div class="tour-approval-actions">
<van-button type="danger" block @click="handleTaskAction(6)">退回</van-button>
<van-button type="danger" block @click="handleTaskAction(2)">拒绝</van-button>
<van-button type="primary" block @click="handleTaskAction(1)">同意</van-button>
</div>
</div>
</tour-info>
</div>
<script nonce="${cspNonce!}">
const TOUR_INFO = {
template: [
"\n",
" <van-action-sheet v-model=\"visible\" title=\"报名详情\">\n",
" <div class=\"tour-approval-sheet\">\n",
" <div class=\"tour-section\">\n",
" <div class=\"tour-section-title\">教职工信息</div>\n",
" <van-cell title=\"工号\" :value=\"detail.jobNo || ''\"></van-cell>\n",
" <van-cell title=\"姓名\" :value=\"detail.userName || ''\"></van-cell>\n",
" <van-cell title=\"性别\" :value=\"detail.gender || ''\"></van-cell>\n",
" <van-cell title=\"身份证号\" :value=\"detail.idCard || ''\"></van-cell>\n",
" <van-cell title=\"所在单位\" :value=\"detail.unitName || ''\"></van-cell>\n",
" <van-cell title=\"所在工会\" :value=\"detail.unionName || ''\"></van-cell>\n",
" </div>\n",
"\n",
" <div class=\"tour-section\">\n",
" <div class=\"tour-section-title\">报名信息</div>\n",
" <van-cell title=\"报名线路\" :value=\"detail.lineName || ''\"></van-cell>\n",
" <van-cell title=\"线路类型\" :value=\"detail.lineType || ''\"></van-cell>\n",
" <van-cell title=\"出行时段\" :value=\"detail.travelPeriod || ''\"></van-cell>\n",
" <van-cell title=\"报名时间\" :value=\"detail.signupTime || ''\"></van-cell>\n",
" <van-cell title=\"报名酒店\" :value=\"detail.hotelName || ''\"></van-cell>\n",
" <van-cell title=\"旅行社\" :value=\"detail.travelAgencyName || ''\"></van-cell>\n",
" <van-cell v-if=\"fillBedInfo\" title=\"床型\" :value=\"detail.bedType || ''\"></van-cell>\n",
" <van-cell v-if=\"fillBedInfo\" title=\"床位信息\" :value=\"detail.bedInfo || ''\"></van-cell>\n",
" <van-cell v-if=\"fillBedInfo\" title=\"意向拼床人\" :value=\"detail.intendedRoommate || ''\"></van-cell>\n",
" <van-cell title=\"携带家属\" :value=\"familyText()\"></van-cell>\n",
" <van-cell title=\"报销超出费用\" :value=\"detail.overCostReimbursed ? '是' : '否'\"></van-cell>\n",
" </div>\n",
"\n",
" <div v-if=\"isDirectFamilyLine()\" class=\"tour-section\">\n",
" <div class=\"tour-section-title\">直系亲属线路</div>\n",
" <van-cell title=\"亲属姓名\" :value=\"directRelative.relativeName || ''\"></van-cell>\n",
" <van-cell title=\"所在单位\" :value=\"directRelative.unitName || ''\"></van-cell>\n",
" <van-cell title=\"亲属关系\" :value=\"directRelative.relationshipName || ''\"></van-cell>\n",
" <van-cell title=\"线路名称\" :value=\"directRelative.lineName || ''\"></van-cell>\n",
" <van-cell title=\"出行开始\" :value=\"directRelative.travelStartTime || ''\"></van-cell>\n",
" <van-cell title=\"出行结束\" :value=\"directRelative.travelEndTime || ''\"></van-cell>\n",
" </div>\n",
"\n",
" <div v-if=\"showFamilySection()\" class=\"tour-section\">\n",
" <div class=\"tour-section-title\">家属信息</div>\n",
" <template v-for=\"(item,index) in familyData\">\n",
" <van-cell :key=\"'title_' + index\" :title=\"'家属' + (index + 1)\"></van-cell>\n",
" <van-cell :key=\"'name_' + index\" title=\"姓名\" :value=\"item.familyName || ''\"></van-cell>\n",
" <van-cell :key=\"'gender_' + index\" title=\"性别\" :value=\"item.gender || ''\"></van-cell>\n",
" <van-cell :key=\"'id_' + index\" title=\"身份证号\" :value=\"item.idCard || ''\"></van-cell>\n",
" <van-cell :key=\"'rel_' + index\" title=\"关系\" :value=\"item.relationship || ''\"></van-cell>\n",
" <van-cell v-if=\"fillBedInfo\" :key=\"'bed_' + index\" title=\"床型\" :value=\"item.bedType || ''\"></van-cell>\n",
" </template>\n",
" </div>\n",
"\n",
" <div v-if=\"doneTasks.length > 0\" class=\"tour-section\">\n",
" <div class=\"tour-section-title\">流程记录</div>\n",
" <template v-for=\"task in doneTasks\">\n",
" <van-cell-group :key=\"task.id\" :title=\"task.displayName || task.taskName\">\n",
" <van-cell title=\"办理时间\" :value=\"task.finishTime || ''\"></van-cell>\n",
" <van-cell title=\"办理结果\">\n",
" <dict-tag :options=\"dict.type.PROCESS_TASK_SUBMIT_TYPE\" :value=\"task.ext && task.ext.submitType\"></dict-tag>\n",
" </van-cell>\n",
" <van-cell title=\"办理意见\" v-if=\"task.taskFormData\" class=\"direction-column-cell\">\n",
" <div class=\"tour-process-opinion\">{{ task.taskFormData.opinion || task.taskFormData.tf_opinion || '' }}</div>\n",
" </van-cell>\n",
" </van-cell-group>\n",
" </template>\n",
" </div>\n",
"\n",
" <slot></slot>\n",
" </div>\n",
" </van-action-sheet>\n",
" \n"
].join(""),
dicts: ["PROCESS_TASK_SUBMIT_TYPE"],
data() {
return {
visible: false,
row: {},
detail: {},
familyData: [],
directRelative: {},
allowFamily: false,
fillBedInfo: true,
directFamilyUnitLine: false,
doneTasks: []
}
},
methods: {
onOpen(row) {
this.row = row || {}
this.visible = true
this.detail = {}
this.familyData = []
this.directRelative = {}
this.allowFamily = false
this.fillBedInfo = true
this.directFamilyUnitLine = false
this.doneTasks = []
this.info()
this.getDoneTasks()
},
onClose() {
this.visible = false
},
info() {
this.$axios.post("/platform/tour/unionApproval/detail", { id: this.row.id }).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.detail = data.ledger || {}
this.detail.travelPeriod = data.travelPeriod || this.row.travelPeriod || ""
this.familyData = data.families || []
this.directRelative = data.directRelative || {}
this.allowFamily = this.toBoolean(data.allowFamily)
this.fillBedInfo = data.fillBedInfo === undefined || data.fillBedInfo === null || this.toBoolean(data.fillBedInfo)
this.directFamilyUnitLine = this.toBoolean(data.directFamilyUnitLine)
} else {
vant.Toast(res.msg || "查询失败")
}
})
},
getDoneTasks() {
this.$axios.post("/flow/common/doneTasks", { bizId: this.row.id }).then((res) => {
if (res.code === 0) {
this.doneTasks = res.data || []
}
})
},
toBoolean(value) {
return value === true || value === 1 || value === "1" || value === "true" || value === "TRUE"
},
isDirectFamilyLine() {
return this.directFamilyUnitLine || this.toBoolean(this.detail.directFamilyUnitLine) || !!(this.directRelative && this.directRelative.id)
},
hasFamily() {
return this.toBoolean(this.detail.hasFamily) || this.familyData.length > 0
},
showFamilySection() {
return this.allowFamily && this.hasFamily() && !this.isDirectFamilyLine()
},
familyText() {
if (this.isDirectFamilyLine() || !this.allowFamily) {
return "否"
}
return this.hasFamily() ? "是" : "否"
}
}
}
new Vue({
el: "#app",
store,
components: {
"tour-info": TOUR_INFO
},
data() {
return {
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "signupTime",
pageOrderBy: "descending",
audit: false,
keyword: "",
lineType: ""
},
auditOptions: [
{ text: "未审核", value: false },
{ text: "已审核", value: true }
],
lineTypeOptions: [
{ text: "全部线路类型", value: "" }
],
formData: {},
showApprovalForm: false
}
},
methods: {
historyBack,
onReady() {
this.doSearch()
},
doSearch() {
this.$nextTick(async () => {
await this.loadLineTypeOptions()
this.pageForm.pageNumber = 1
this.pageForm.totalCount = 0
this.$refs.tableListRef.doSearch()
})
},
loadLineTypeOptions() {
return this.$axios.post("/platform/tour/unionApproval/lineTypeOptions", {
audit: this.pageForm.audit,
keyword: this.pageForm.keyword
}).then((res) => {
if (res.code === 0) {
const options = (res.data || []).map(item => ({
text: item.lineType,
value: item.lineType
})).filter(item => item.value)
this.lineTypeOptions = [{ text: "全部线路类型", value: "" }].concat(options)
if (this.pageForm.lineType && !this.lineTypeOptions.some(item => item.value === this.pageForm.lineType)) {
this.pageForm.lineType = ""
}
}
})
},
onView(row) {
this.showApprovalForm = false
this.$refs.infoRef.onOpen(row)
},
onApproval(row) {
this.showApprovalForm = true
this.formData = {
processTaskId: row.taskId,
taskName: row.curTaskName || row.taskName,
tf_opinion: ""
}
this.$refs.infoRef.onOpen(row)
},
async handleTaskAction(val) {
try {
await this.$refs.formRef.validate()
} catch (e) {
return
}
this.$dialog.confirm({
title: "提示",
message: "您确定要提交吗?"
}).then(() => {
const loading = this.$toast.loading({
message: "加载中...",
forbidClick: true,
overlay: true,
duration: 0
})
this.$axios.post("/flow/common/executeTask", {
data: JSON.stringify({
...this.formData,
submitType: val
})
}).then((res) => {
if (res.code === 0) {
this.$refs.infoRef.onClose()
this.$toast.success(res.msg || "操作成功")
this.doSearch()
} else {
this.$toast(res.msg || "操作失败")
}
}).finally(() => {
loading.close()
})
}).catch(() => {})
}
}
})
</script>
<!--#
}
#-->