南大三十年教职工疗休养

This commit is contained in:
2026-06-24 10:51:45 +08:00
parent f355f5e7a1
commit 3d0bcabc65
111 changed files with 29665 additions and 0 deletions
@@ -0,0 +1,39 @@
-- 30年教龄疗休养移动端菜单。
-- H5 菜单挂载在 30年教龄疗休养根菜单下,权限统一使用 h5.thirtyTeachTour.*,避免与 PC 端权限混用。
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 'a9a8c4f3d3bh5a10001', p.id, CONCAT(p.path, '0101'), '30年教龄疗休养报名', 'Thirty Teach Tour Signup H5', 'menu', '/platform/thirtyTeachTour/signup/h5', 'data-pjax', '', 1, 0, 'h5.thirtyTeachTour.signup', NULL, 101, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'H5', NULL, NULL, 's', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'h5.thirtyTeachTour.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 'a9a8c4f3d3bh5a10002', p.id, CONCAT(p.path, '0102'), '我的30年教龄疗休养', 'My Thirty Teach Tour H5', 'menu', '/platform/thirtyTeachTour/mysignup/h5', 'data-pjax', '', 1, 0, 'h5.thirtyTeachTour.mysignup', NULL, 102, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'H5', NULL, NULL, 'w', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'h5.thirtyTeachTour.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 'a9a8c4f3d3bh5a10003', p.id, CONCAT(p.path, '0103'), '分工会审核', 'Union Approval H5', 'menu', '/platform/thirtyTeachTour/unionApproval/h5', 'data-pjax', '', 1, 0, 'h5.thirtyTeachTour.unionApproval', NULL, 103, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'H5', NULL, NULL, 'f', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'h5.thirtyTeachTour.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 'a9a8c4f3d3bh5a10004', p.id, CONCAT(p.path, '0104'), '校工会审核', 'School Union Approval H5', 'menu', '/platform/thirtyTeachTour/schoolUnionApproval/h5', 'data-pjax', '', 1, 0, 'h5.thirtyTeachTour.schoolUnionApproval', NULL, 104, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'H5', NULL, NULL, 'x', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'h5.thirtyTeachTour.schoolUnionApproval') 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 (
'h5.thirtyTeachTour.signup',
'h5.thirtyTeachTour.mysignup',
'h5.thirtyTeachTour.unionApproval',
'h5.thirtyTeachTour.schoolUnionApproval'
)
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,170 @@
-- 30年教龄疗休养平台电脑端菜单。
-- 使用 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
'a8c4f3d3bb1a10001',
'',
LPAD(IFNULL(MAX(CAST(path AS UNSIGNED)), 0) + 1, 4, '0'),
'30年教龄疗休养',
'ThirtyTeachTour',
'menu',
'',
'',
'ti-map-alt',
1,
0,
'thirtyTeachTour',
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 = 'thirtyTeachTour') 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 'a8c4f3d3bb1a10002', p.id, CONCAT(p.path, '0001'), '疗休养设置', 'ThirtyTeachTour Setting', 'menu', '/platform/thirtyTeachTour/setting', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10003', p.id, CONCAT(p.path, '0002'), '旅行社管理', 'Travel Agency', 'menu', '/platform/thirtyTeachTour/travelAgency', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10004', p.id, CONCAT(p.path, '0003'), '线路管理', 'Route Manage', 'menu', '/platform/thirtyTeachTour/route', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10005', p.id, CONCAT(p.path, '0004'), '疗休养事项', 'ThirtyTeachTour Matter', 'menu', '/platform/thirtyTeachTour/matter', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10006', p.id, CONCAT(p.path, '0005'), '疗休养报名', 'ThirtyTeachTour Signup', 'menu', '/platform/thirtyTeachTour/signup', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10008', p.id, CONCAT(p.path, '0006'), '我的报名', 'My Signup', 'menu', '/platform/thirtyTeachTour/mysignup', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10009', p.id, CONCAT(p.path, '0007'), '分工会查询', 'Union Ledger', 'menu', '/platform/thirtyTeachTour/unionledger', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10011', p.id, CONCAT(p.path, '0010'), '分工会审核', 'Union Approval', 'menu', '/platform/thirtyTeachTour/unionApproval', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10012', p.id, CONCAT(p.path, '0011'), '校工会审核', 'School Union Approval', 'menu', '/platform/thirtyTeachTour/schoolUnionApproval', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10007', p.id, CONCAT(p.path, '0008'), '疗休养台账', 'ThirtyTeachTour Ledger', 'menu', '/platform/thirtyTeachTour/ledger', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.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 'a8c4f3d3bb1a10010', p.id, CONCAT(p.path, '0009'), '线路成团', 'ThirtyTeachTour Group', 'menu', '/platform/thirtyTeachTour/group', 'data-pjax', '', 1, 0, 'thirtyTeachTour.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 = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.group') 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 'a8c4f3d3bb1a10013', p.id, CONCAT(p.path, '0012'), '分工会人员分配', 'Branch User Assignment', 'menu', '/platform/thirtyTeachTour/branchUserAssignment', 'data-pjax', '', 1, 0, 'thirtyTeachTour.branchUserAssignment', NULL, 12, 1, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'f', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.branchUserAssignment') 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 'a8c4f3d3bb1a10101', p.id, CONCAT(p.path, '0001'), '人员分配', 'Assign', 'data', '', '', '', 0, 0, 'thirtyTeachTour.branchUserAssignment.assign', NULL, 1, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'r', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour.branchUserAssignment'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.branchUserAssignment.assign') 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 'a8c4f3d3bb1a10102', p.id, CONCAT(p.path, '0002'), '选择路线', 'Select Matter', 'data', '', '', '', 0, 0, 'thirtyTeachTour.branchUserAssignment.selectMatter', NULL, 2, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'x', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour.branchUserAssignment'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.branchUserAssignment.selectMatter') 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 'a8c4f3d3bb1a10103', p.id, CONCAT(p.path, '0003'), '切换人员类型', 'Switch Person Type', 'data', '', '', '', 0, 0, 'thirtyTeachTour.branchUserAssignment.switchPersonType', NULL, 3, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'q', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour.branchUserAssignment'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.branchUserAssignment.switchPersonType') 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 'a8c4f3d3bb1a10104', p.id, CONCAT(p.path, '0004'), '取消退出', 'Cancel Restore', 'data', '', '', '', 0, 0, 'thirtyTeachTour.branchUserAssignment.cancelRestore', NULL, 4, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 'q', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour.branchUserAssignment'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.branchUserAssignment.cancelRestore') 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 'a8c4f3d3bb1a10105', p.id, CONCAT(p.path, '0005'), '删除', 'Delete', 'data', '', '', '', 0, 0, 'thirtyTeachTour.branchUserAssignment.delete', NULL, 5, 0, '', UNIX_TIMESTAMP(NOW()) * 1000, '', UNIX_TIMESTAMP(NOW()) * 1000, 0, 'PC', NULL, NULL, 's', 0, 0
FROM sys_menu p
WHERE p.permission = 'thirtyTeachTour.branchUserAssignment'
AND NOT EXISTS (SELECT 1 FROM (SELECT id FROM sys_menu WHERE permission = 'thirtyTeachTour.branchUserAssignment.delete') t);
UPDATE sys_menu p
JOIN (SELECT DISTINCT parentId FROM sys_menu WHERE type = 'data' AND delFlag = 0) c ON c.parentId = p.id
SET p.hasChildren = 1
WHERE p.permission = 'thirtyTeachTour.branchUserAssignment';
-- 给系统管理员默认授权,其他角色可在角色管理中按需分配。
INSERT INTO sys_role_menu (roleId, menuId)
SELECT r.id, m.id
FROM sys_role r
JOIN sys_menu m ON m.permission IN (
'thirtyTeachTour',
'thirtyTeachTour.setting',
'thirtyTeachTour.travelAgency',
'thirtyTeachTour.route',
'thirtyTeachTour.matter',
'thirtyTeachTour.signup',
'thirtyTeachTour.group',
'thirtyTeachTour.mysignup',
'thirtyTeachTour.unionledger',
'thirtyTeachTour.unionApproval',
'thirtyTeachTour.schoolUnionApproval',
'thirtyTeachTour.ledger',
'thirtyTeachTour.branchUserAssignment',
'thirtyTeachTour.branchUserAssignment.assign',
'thirtyTeachTour.branchUserAssignment.selectMatter',
'thirtyTeachTour.branchUserAssignment.switchPersonType',
'thirtyTeachTour.branchUserAssignment.cancelRestore',
'thirtyTeachTour.branchUserAssignment.delete'
)
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 `thirty_teach_tour_setting`
MODIFY COLUMN `boardingPlace` text COMMENT '乘车地点列表JSON';
ALTER TABLE `thirty_teach_tour_matter`
ADD COLUMN `defaultBoardingPlace` varchar(100) DEFAULT NULL COMMENT '默认乘车地点' AFTER `lineId`;
ALTER TABLE `thirty_teach_tour_user_assignment`
ADD COLUMN `boardingPlace` varchar(100) DEFAULT NULL COMMENT '乘车地点' AFTER `lineName`;
ALTER TABLE `thirty_teach_tour_ledger`
ADD COLUMN `boardingPlace` varchar(100) DEFAULT NULL COMMENT '乘车地点' AFTER `travelAgencyName`;
@@ -0,0 +1,253 @@
-- 普惠疗休养系统字典初始化。
-- 执行后可在 /platform/sys/dict 页面看到:疗休养(ThirtyTeachTour) -> 组织形式/疗休养类型/线路类型/床型。
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'c30a63f0c73a44a9a8c4f3d3bdict001', '', '9500', '疗休养', '普惠疗休养平台字典', 'ThirtyTeachTour', 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` = 'ThirtyTeachTour') t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'c30a63f0c73a44a9a8c4f3d3bdict002', 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` = 'ThirtyTeachTour'
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
'c30a63f0c73a44a9a8c4f3d3bdict003', 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
'c30a63f0c73a44a9a8c4f3d3bdict004', 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
'c30a63f0c73a44a9a8c4f3d3bdict005', 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
'c30a63f0c73a44a9a8c4f3d3bdict006', 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` = 'ThirtyTeachTour'
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
'c30a63f0c73a44a9a8c4f3d3bdict007', p.id, CONCAT(p.path, '0001'), '普惠性疗休养', '疗休养类型', 'inclusiveThirtyTeachTour', 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` = 'inclusiveThirtyTeachTour' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'c30a63f0c73a44a9a8c4f3d3bdict008', p.id, CONCAT(p.path, '0002'), '优秀职工疗休养', '疗休养类型', 'excellentWorkerThirtyTeachTour', 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` = 'excellentWorkerThirtyTeachTour' AND `parentId` = p.id) t);
INSERT INTO `sys_dict` (
`id`, `parentId`, `path`, `name`, `remark`, `code`, `disabled`, `location`, `hasChildren`,
`createdBy`, `createdAt`, `updatedBy`, `updatedAt`, `delFlag`
)
SELECT
'c30a63f0c73a44a9a8c4f3d3bdict009', 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` = 'ThirtyTeachTour'
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
'c30a63f0c73a44a9a8c4f3d3bdict010', 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
'c30a63f0c73a44a9a8c4f3d3bdict011', 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
'c30a63f0c73a44a9a8c4f3d3bdict020', 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` = 'ThirtyTeachTour'
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
'c30a63f0c73a44a9a8c4f3d3bdict021', 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
'c30a63f0c73a44a9a8c4f3d3bdict022', 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
'c30a63f0c73a44a9a8c4f3d3bdict023', 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
'c30a63f0c73a44a9a8c4f3d3bdict012', '', '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
'c30a63f0c73a44a9a8c4f3d3bdict013', 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
'c30a63f0c73a44a9a8c4f3d3bdict014', 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
'c30a63f0c73a44a9a8c4f3d3bdict015', 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
'c30a63f0c73a44a9a8c4f3d3bdict016', 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
'c30a63f0c73a44a9a8c4f3d3bdict017', 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
'c30a63f0c73a44a9a8c4f3d3bdict018', 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
'c30a63f0c73a44a9a8c4f3d3bdict019', 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 ('ThirtyTeachTour', 'organizationType', 'tourType', 'lineType', 'bedType', 'familyRelationship', 'directRelative');
@@ -0,0 +1,29 @@
-- 疗休养退出申请表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_leave_apply_user` (`userId`),
KEY `idx_thirty_teach_tour_leave_apply_job_no` (`jobNo`),
KEY `idx_thirty_teach_tour_leave_apply_union` (`unionId`),
KEY `idx_thirty_teach_tour_leave_apply_status` (`status`),
KEY `idx_thirty_teach_tour_leave_apply_matter` (`matterId`),
KEY `idx_thirty_teach_tour_leave_apply_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疗休养退出申请';
@@ -0,0 +1,3 @@
-- Add signup ledger age field for mobile confirmation snapshot.
ALTER TABLE `thirty_teach_tour_ledger`
ADD COLUMN `age` int DEFAULT NULL COMMENT '年龄' AFTER `gender`;
@@ -0,0 +1,7 @@
-- Add matter reference so signup records are unique per travel period/matter.
ALTER TABLE `thirty_teach_tour_ledger`
ADD COLUMN `matterId` varchar(32) DEFAULT NULL COMMENT '报名事项ID' AFTER `signupTime`;
ALTER TABLE `thirty_teach_tour_ledger`
ADD KEY `idx_thirty_teach_tour_ledger_matter` (`matterId`),
ADD KEY `idx_thirty_teach_tour_ledger_matter_job` (`matterId`, `jobNo`);
@@ -0,0 +1,3 @@
-- Add signup ledger mobile field; signup mobile is stored with the ledger and does not update user profile data.
ALTER TABLE `thirty_teach_tour_ledger`
ADD COLUMN `mobile` varchar(30) DEFAULT NULL COMMENT '手机号' AFTER `idCard`;
@@ -0,0 +1,2 @@
ALTER TABLE `thirty_teach_tour_ledger`
ADD COLUMN `overCostReimbursed` tinyint(1) DEFAULT 0 COMMENT '报销超出费用' AFTER `reimbursed`;
@@ -0,0 +1,9 @@
-- 疗休养台账查询加速索引。
ALTER TABLE `thirty_teach_tour_ledger`
ADD KEY `idx_thirty_teach_tour_ledger_year_signup` (`year`, `delFlag`, `signupTime`),
ADD KEY `idx_thirty_teach_tour_ledger_year_line` (`year`, `lineId`, `delFlag`),
ADD KEY `idx_thirty_teach_tour_ledger_year_union_type` (`year`, `unionId`, `lineType`, `delFlag`),
ADD KEY `idx_thirty_teach_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,44 @@
-- 30年教龄疗休养教职工报名台账表。
-- 本表对应 ThirtyTeachTourLedger 模型,用于保存最终报名成功后的教职工报名快照。
CREATE TABLE IF NOT EXISTS `thirty_teach_tour_ledger` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`year` int DEFAULT NULL COMMENT '年度',
`jobNo` varchar(50) DEFAULT NULL COMMENT '工号',
`userName` varchar(100) DEFAULT NULL COMMENT '姓名',
`gender` varchar(20) DEFAULT NULL COMMENT '性别',
`age` int DEFAULT NULL COMMENT '年龄',
`idCard` varchar(30) DEFAULT NULL COMMENT '身份证号',
`mobile` varchar(30) DEFAULT NULL COMMENT '手机号',
`unitId` varchar(32) DEFAULT NULL COMMENT '所在单位ID',
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
`unionId` varchar(32) DEFAULT NULL COMMENT '所在工会ID',
`unionName` varchar(100) DEFAULT NULL COMMENT '所在工会',
`signupTime` varchar(30) DEFAULT NULL COMMENT '报名时间',
`matterId` varchar(32) DEFAULT NULL COMMENT '报名事项ID',
`lineId` varchar(32) DEFAULT NULL COMMENT '报名线路ID',
`lineName` varchar(100) DEFAULT NULL COMMENT '报名线路',
`lineType` varchar(50) DEFAULT NULL COMMENT '线路类型',
`hotelName` varchar(100) DEFAULT NULL COMMENT '报名酒店',
`travelAgencyId` varchar(32) DEFAULT NULL COMMENT '旅行社ID',
`travelAgencyName` varchar(100) DEFAULT NULL COMMENT '旅行社',
`boardingPlace` varchar(100) DEFAULT NULL COMMENT '乘车地点',
`hasFamily` tinyint(1) DEFAULT 0 COMMENT '是否携带家属',
`intendedRoommate` varchar(100) DEFAULT NULL COMMENT '意向拼床人',
`bedType` varchar(50) DEFAULT NULL COMMENT '床型',
`bedInfo` varchar(100) DEFAULT NULL COMMENT '床位信息',
`joined` tinyint(1) DEFAULT 0 COMMENT '是否参加',
`reimbursed` tinyint(1) DEFAULT 0 COMMENT '是否报销',
`overCostReimbursed` 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_thirty_teach_tour_ledger_year_signup` (`year`, `delFlag`, `signupTime`),
KEY `idx_thirty_teach_tour_ledger_year_line` (`year`, `lineId`, `delFlag`),
KEY `idx_thirty_teach_tour_ledger_year_union_type` (`year`, `unionId`, `lineType`, `delFlag`),
KEY `idx_thirty_teach_tour_ledger_year_job` (`year`, `jobNo`, `delFlag`),
KEY `idx_thirty_teach_tour_ledger_matter` (`matterId`),
KEY `idx_thirty_teach_tour_ledger_matter_job` (`matterId`, `jobNo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='30年教龄疗休养台账';
@@ -0,0 +1,21 @@
-- 直系亲属线路报名信息表,独立于普通携带亲属信息表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_ledger_direct_relative_ledger` (`ledgerId`),
KEY `idx_thirty_teach_tour_ledger_direct_relative_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养直系亲属线路报名信息';
@@ -0,0 +1,25 @@
-- 30年教龄疗休养台账家属信息表。
-- 本表对应 ThirtyTeachTourLedgerFamily 模型,用于保存报名台账关联的随行家属快照。
CREATE TABLE IF NOT EXISTS `thirty_teach_tour_ledger_family` (
`id` varchar(32) NOT NULL COMMENT 'ID',
`ledgerId` varchar(32) DEFAULT NULL COMMENT '台账ID',
`staffJobNo` varchar(50) DEFAULT NULL COMMENT '教职工工号',
`staffName` varchar(100) DEFAULT NULL COMMENT '教职工姓名',
`familyName` varchar(100) DEFAULT NULL COMMENT '家属姓名',
`gender` varchar(20) DEFAULT NULL COMMENT '性别',
`idCard` varchar(30) DEFAULT NULL COMMENT '身份证号码',
`age` int DEFAULT NULL COMMENT '年龄',
`bedType` varchar(50) DEFAULT NULL COMMENT '床型',
`bedInfo` varchar(100) DEFAULT NULL COMMENT '床位',
`intendedRoommate` varchar(100) DEFAULT NULL COMMENT '意向拼床人',
`relationship` varchar(50) 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_thirty_teach_tour_ledger_family_ledger` (`ledgerId`),
KEY `idx_thirty_teach_tour_ledger_family_staff` (`staffJobNo`),
KEY `idx_thirty_teach_tour_ledger_family_id_card` (`idCard`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='30年教龄疗休养台账家属信息';
@@ -0,0 +1,10 @@
-- 线路管理增加创建人和所在单位业务字段。
ALTER TABLE `thirty_teach_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 `thirty_teach_tour_line`
ADD KEY `idx_thirty_teach_tour_line_creator` (`creatorUserId`),
ADD KEY `idx_thirty_teach_tour_line_unit` (`unitId`);
@@ -0,0 +1,3 @@
-- 线路管理增加是否直系亲属线路字段,默认否。
ALTER TABLE `thirty_teach_tour_line`
ADD COLUMN `directFamilyUnitLine` tinyint(1) DEFAULT 0 COMMENT '是否直系亲属线路' AFTER `openFlag`;
@@ -0,0 +1,3 @@
-- 线路管理增加是否对外开放字段,默认是。
ALTER TABLE `thirty_teach_tour_line`
ADD COLUMN `openFlag` tinyint(1) DEFAULT 1 COMMENT '是否对外开放' AFTER `mobileThumb`;
@@ -0,0 +1,7 @@
-- 疗休养事项增加创建人字段。
ALTER TABLE `thirty_teach_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 `thirty_teach_tour_matter`
ADD KEY `idx_thirty_teach_tour_matter_creator` (`creatorUserId`);
@@ -0,0 +1,26 @@
-- 疗休养事项批次表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_matter_batch_matter` (`matterId`),
KEY `idx_thirty_teach_tour_matter_batch_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项批次';
@@ -0,0 +1,37 @@
-- 疗休养事项表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_matter_year_name` (`year`, `matterName`),
KEY `idx_thirty_teach_tour_matter_year` (`year`),
KEY `idx_thirty_teach_tour_matter_creator` (`creatorUserId`),
KEY `idx_thirty_teach_tour_matter_setting` (`settingId`),
KEY `idx_thirty_teach_tour_matter_union` (`unionId`),
KEY `idx_thirty_teach_tour_matter_org_type` (`organizationType`),
KEY `idx_thirty_teach_tour_matter_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项';
@@ -0,0 +1,6 @@
-- Add activity user scope reference for tour settings.
ALTER TABLE `thirty_teach_tour_setting`
ADD COLUMN `activityGroupId` varchar(32) DEFAULT NULL COMMENT '可参加人员范围ID' AFTER `tourType`;
ALTER TABLE `thirty_teach_tour_setting`
ADD KEY `idx_thirty_teach_tour_setting_activity_group` (`activityGroupId`);
@@ -0,0 +1,4 @@
-- 疗休养配置新增乘车地点、出行人数指标。
ALTER TABLE `thirty_teach_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 `thirty_teach_tour_setting`
ADD COLUMN `cycleAllowedTimes` int DEFAULT NULL COMMENT '周期允许次数' AFTER `cycleTotalCost`;
@@ -0,0 +1,2 @@
ALTER TABLE `thirty_teach_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 `thirty_teach_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 `thirty_teach_tour_setting`
ADD COLUMN `fillBedInfo` tinyint(1) DEFAULT 1 COMMENT '是否填报床位信息' AFTER `allowFamily`;
@@ -0,0 +1,4 @@
-- 疗休养配置增加PC首页报名浮动入口控制字段。
ALTER TABLE `thirty_teach_tour_setting`
ADD COLUMN `homeSignupEntryEnabled` tinyint(1) DEFAULT 0 COMMENT '是否展示PC首页报名入口' AFTER `enabled`,
ADD COLUMN `homeSignupEntryImage` varchar(500) DEFAULT NULL COMMENT 'PC首页报名入口图片' AFTER `homeSignupEntryEnabled`;
@@ -0,0 +1,3 @@
ALTER TABLE `thirty_teach_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 `thirty_teach_tour_setting`
ADD COLUMN `signupEligibilityMode` varchar(30) DEFAULT 'ASSIGNED_USER' COMMENT '报名资格校验方式' AFTER `activityGroupId`;
@@ -0,0 +1,261 @@
-- 普惠疗休养基础配置表。
-- 若生产环境未开启 Nutz 自动建表,请先执行本脚本再使用“疗休养设置”菜单。
CREATE TABLE IF NOT EXISTS `thirty_teach_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 '是否启用',
`homeSignupEntryEnabled` tinyint(1) DEFAULT 0 COMMENT '是否展示PC首页报名入口',
`homeSignupEntryImage` varchar(500) DEFAULT NULL COMMENT 'PC首页报名入口图片',
`serviceNotice` text COMMENT '服务须知',
`createdBy` varchar(32) DEFAULT NULL COMMENT '创建人',
`createdAt` bigint DEFAULT NULL COMMENT '创建时间',
`updatedBy` varchar(32) DEFAULT NULL COMMENT '修改人',
`updatedAt` bigint DEFAULT NULL COMMENT '修改时间',
`delFlag` tinyint(1) DEFAULT 0 COMMENT '删除标记',
PRIMARY KEY (`id`),
KEY `idx_thirty_teach_tour_setting_year` (`year`),
KEY `idx_thirty_teach_tour_setting_activity_group` (`activityGroupId`),
KEY `idx_thirty_teach_tour_setting_sort` (`sortNo`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养配置';
-- 疗休养配置标段表。
-- 标段先挂在配置上维护,后续线路、旅行社、报名等模块可继续通过 lotId 做业务关联。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_setting_lot_setting` (`settingId`),
KEY `idx_thirty_teach_tour_setting_lot_value` (`lotValue`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养配置标段';
-- 疗休养配置分工会名额分配表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_setting_union_quota_setting` (`settingId`),
KEY `idx_thirty_teach_tour_setting_union_quota_union` (`unionId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疗休养配置分工会名额分配';
-- 疗休养人员分配表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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 '性别',
`age` int DEFAULT NULL COMMENT '年龄',
`idCard` varchar(30) DEFAULT NULL COMMENT '身份证号',
`mobile` varchar(30) DEFAULT NULL COMMENT '手机号',
`unitId` varchar(32) DEFAULT NULL COMMENT '所在单位ID',
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
`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_thirty_teach_tour_user_assignment_setting_user` (`settingId`, `userId`),
KEY `idx_thirty_teach_tour_user_assignment_setting` (`settingId`),
KEY `idx_thirty_teach_tour_user_assignment_user` (`userId`),
KEY `idx_thirty_teach_tour_user_assignment_union` (`unionId`),
KEY `idx_thirty_teach_tour_user_assignment_source` (`assignSource`),
KEY `idx_thirty_teach_tour_user_assignment_person_type` (`personType`),
KEY `idx_thirty_teach_tour_user_assignment_cancelled` (`cancelled`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疗休养人员分配';
-- 旅行社管理表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_travel_agency_year_code` (`year`, `agencyCode`),
KEY `idx_thirty_teach_tour_travel_agency_name` (`agencyName`),
KEY `idx_thirty_teach_tour_travel_agency_contact` (`contactName`, `contactPhone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养旅行社';
-- 线路管理表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_line_year_code` (`year`, `lineCode`),
KEY `idx_thirty_teach_tour_line_year` (`year`),
KEY `idx_thirty_teach_tour_line_name` (`lineName`),
KEY `idx_thirty_teach_tour_line_agency` (`travelAgencyId`),
KEY `idx_thirty_teach_tour_line_creator` (`creatorUserId`),
KEY `idx_thirty_teach_tour_line_unit` (`unitId`),
KEY `idx_thirty_teach_tour_line_lot` (`lotId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养线路';
-- 疗休养事项表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_matter_year_name` (`year`, `matterName`),
KEY `idx_thirty_teach_tour_matter_year` (`year`),
KEY `idx_thirty_teach_tour_matter_creator` (`creatorUserId`),
KEY `idx_thirty_teach_tour_matter_setting` (`settingId`),
KEY `idx_thirty_teach_tour_matter_union` (`unionId`),
KEY `idx_thirty_teach_tour_matter_org_type` (`organizationType`),
KEY `idx_thirty_teach_tour_matter_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项';
-- 疗休养事项批次表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_matter_batch_matter` (`matterId`),
KEY `idx_thirty_teach_tour_matter_batch_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养事项批次';
-- 直系亲属线路报名信息表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_ledger_direct_relative_ledger` (`ledgerId`),
KEY `idx_thirty_teach_tour_ledger_direct_relative_line` (`lineId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='普惠疗休养直系亲属线路报名信息';
@@ -0,0 +1,2 @@
ALTER TABLE `thirty_teach_tour_setting_lot`
ADD COLUMN `allowOverReimbursement` tinyint(1) DEFAULT 0 COMMENT '允许超出报销' AFTER `activityCost`;
@@ -0,0 +1,17 @@
-- 疗休养配置分工会名额分配表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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_thirty_teach_tour_setting_union_quota_setting` (`settingId`),
KEY `idx_thirty_teach_tour_setting_union_quota_union` (`unionId`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='疗休养配置分工会名额分配';
@@ -0,0 +1,4 @@
-- 疗休养人员分配新增退出状态。
ALTER TABLE `thirty_teach_tour_user_assignment`
ADD COLUMN `cancelled` tinyint(1) DEFAULT 0 COMMENT '是否已退出' AFTER `personType`,
ADD KEY `idx_thirty_teach_tour_user_assignment_cancelled` (`cancelled`);
@@ -0,0 +1,4 @@
-- Add mobile H5 signup confirmation fields to tour user assignment.
ALTER TABLE `thirty_teach_tour_user_assignment`
ADD COLUMN `age` int DEFAULT NULL COMMENT '年龄' AFTER `gender`,
ADD COLUMN `idCard` varchar(30) DEFAULT NULL COMMENT '身份证号' AFTER `age`;
@@ -0,0 +1,42 @@
-- 疗休养人员分配表。
CREATE TABLE IF NOT EXISTS `thirty_teach_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 '性别',
`age` int DEFAULT NULL COMMENT '年龄',
`idCard` varchar(30) DEFAULT NULL COMMENT '身份证号',
`mobile` varchar(30) DEFAULT NULL COMMENT '手机号',
`unitId` varchar(32) DEFAULT NULL COMMENT '所在单位ID',
`unitName` varchar(100) DEFAULT NULL COMMENT '所在单位',
`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_thirty_teach_tour_user_assignment_setting_user` (`settingId`, `userId`),
KEY `idx_thirty_teach_tour_user_assignment_setting` (`settingId`),
KEY `idx_thirty_teach_tour_user_assignment_user` (`userId`),
KEY `idx_thirty_teach_tour_user_assignment_union` (`unionId`),
KEY `idx_thirty_teach_tour_user_assignment_source` (`assignSource`),
KEY `idx_thirty_teach_tour_user_assignment_person_type` (`personType`),
KEY `idx_thirty_teach_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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

@@ -0,0 +1,824 @@
<!--#
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 v-if="$auth.hasPermission('thirtyTeachTour.branchUserAssignment.assign')" type="primary" size="medium" @click="openAssign">
<i class="el-icon-user"></i>
人员分配
</el-button>
<el-button type="primary" size="medium" icon="el-icon-download" @click="exportData">导出</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="380" align="center" header-align="center" fixed="right">
<template slot-scope="{row}">
<el-button v-if="!row.matterId && row.personType !== 'BACKUP' && $auth.hasPermission('thirtyTeachTour.branchUserAssignment.selectMatter')" size="mini" type="primary" @click="openSelectMatter(row)">选择路线</el-button>
<el-button v-if="$auth.hasPermission('thirtyTeachTour.branchUserAssignment.switchPersonType')" 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('thirtyTeachTour.branchUserAssignment.cancelRestore')" size="mini" type="warning" @click="restoreCancel(row)">取消退出</el-button>
<el-button v-if="$auth.hasPermission('thirtyTeachTour.branchUserAssignment.delete')" 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" v-if="$auth.hasPermission('thirtyTeachTour.branchUserAssignment.selectMatter')">
<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>
<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-select
v-model="selectedCandidateIds"
multiple
filterable
remote
clearable
collapse-tags
reserve-keyword
:remote-method="remoteCandidateSearch"
:loading="candidateSelectLoading"
placeholder="请选择姓名/工号"
style="width: 360px"
@change="candidateUserSelectChange">
<el-option v-for="item in candidateUserOptions" :key="item.userId" :label="candidateOptionLabel(item)" :value="item.userId"></el-option>
</el-select>
<el-button type="primary" icon="el-icon-search" @click="candidateSearch">查询</el-button>
<el-button @click="resetCandidateSearch">重置</el-button>
<span class="candidate-selected">已选 {{ selectedCandidates.length }} 人</span>
</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>
<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;
}
.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,
candidateSelectLoading: false,
candidateData: [],
candidateUserOptions: [],
selectedCandidates: [],
selectedCandidateIds: [],
assignSubmitting: false,
selectMatterDialogVisible: false,
selectMatterSubmitting: false,
selectMatterOptions: [],
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: ""
},
selectMatterForm: {
id: "",
settingId: "",
matterId: "",
userName: ""
},
assignRules: {
year: [{required: true, message: "请选择年度", trigger: ["change", "blur"]}],
settingId: [{required: true, message: "请选择疗休养配置", trigger: ["change", "blur"]}],
personType: [{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: "",
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
}
},
defaultSelectMatterForm() {
return {
id: "",
settingId: "",
matterId: "",
userName: ""
}
},
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 || []
}
})
},
exportData() {
const params = new URLSearchParams()
;["year", "settingId", "matterId", "personType", "keyword", "pageOrderName", "pageOrderBy"].forEach(key => {
const value = this.pageForm[key]
if (value !== undefined && value !== null && value !== "") {
params.append(key, value)
}
})
window.location.href = loc() + "/exportData?" + params.toString()
},
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.selectedCandidateIds = []
this.candidateUserOptions = []
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.selectedCandidateIds = []
this.candidateUserOptions = []
this.assignSubmitting = false
},
assignYearChange() {
this.assignForm.settingId = ""
this.assignForm.matterId = ""
this.assignMatterOptions = []
this.quotaInfo = this.defaultQuotaInfo()
this.selectedCandidateIds = []
this.candidateUserOptions = []
this.candidateForm.keyword = ""
this.clearCandidateSelection()
this.loadAssignSettingOptions()
},
assignSettingChange() {
this.assignForm.matterId = ""
this.assignMatterOptions = []
this.selectedCandidateIds = []
this.candidateUserOptions = []
this.candidateForm.keyword = ""
this.clearCandidateSelection()
this.loadAssignMatterOptions()
this.loadQuotaInfo()
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.selectedCandidateIds || []).length > 0 ? "" : this.candidateForm.keyword,
userIds: JSON.stringify(this.selectedCandidateIds || [])
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.candidateData = data.list || []
this.candidateForm.totalCount = data.totalCount || 0
this.mergeCandidateOptions(this.candidateData)
} else {
this.$message.warning(res.msg || "候选人员查询失败")
}
}).finally(() => {
this.candidateLoading = false
})
},
candidateSearch() {
this.candidateForm.pageNumber = 1
this.clearCandidateSelection()
this.loadCandidatePageData()
},
resetCandidateSearch() {
this.candidateForm.keyword = ""
this.candidateForm.pageNumber = 1
this.selectedCandidateIds = []
this.selectedCandidates = []
this.candidateUserOptions = []
this.clearCandidateSelection()
this.loadCandidatePageData()
},
candidateSizeChange(size) {
this.candidateForm.pageSize = size
this.candidateForm.pageNumber = 1
this.loadCandidatePageData()
},
candidatePageChange(pageNumber) {
this.candidateForm.pageNumber = pageNumber
this.loadCandidatePageData()
},
candidateSelectionChange(rows) {
this.selectedCandidates = rows || []
this.mergeCandidateOptions(this.selectedCandidates)
},
remoteCandidateSearch(keyword) {
if (!this.assignForm.settingId) {
this.candidateUserOptions = []
return
}
// 人员选择器复用候选人员接口,后端会限定为当前登录人所在分工会会员。
this.candidateForm.keyword = keyword || ""
this.candidateSelectLoading = true
this.$axios.post(loc() + "/candidatePageData", {
pageNumber: 1,
pageSize: 20,
settingId: this.assignForm.settingId,
keyword: keyword || ""
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.candidateUserOptions = this.mergeOptionLists(this.selectedCandidates, data.list || [])
}
}).finally(() => {
this.candidateSelectLoading = false
})
},
candidateUserSelectChange(userIds) {
// 人员选择器仅作为查询条件;最终分配人员仍由下方列表勾选决定。
this.selectedCandidateIds = userIds || []
},
candidateOptionLabel(item) {
if (!item) {
return ""
}
return (item.userName || "") + (item.loginName ? "" + item.loginName + "" : "")
},
mergeCandidateOptions(list) {
this.candidateUserOptions = this.mergeOptionLists(this.candidateUserOptions, list || [])
},
mergeOptionLists(first, second) {
const map = {}
;(first || []).concat(second || []).forEach(item => {
if (item && item.userId) {
map[item.userId] = Object.assign({}, map[item.userId] || {}, item)
}
})
return Object.keys(map).map(key => map[key])
},
clearCandidateSelection() {
this.selectedCandidates = []
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 = ""
})
},
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) {
return item.matterName || item.lineName || ""
}
},
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,686 @@
<!--#
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="所属工会" v-if="canSelectUnion()">
<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/thirtyTeachTour/ledger/importParticipants"
template_url="/platform/thirtyTeachTour/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,
currentUnionId: "",
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 || []
},
canSelectUnion() {
return this.$auth.hasRoleOr(["SYSADMIN", "SCHOOL_UNION_ADMIN"])
},
isBranchUnionChairmanOnly() {
return !this.canSelectUnion() && this.$auth.hasRole("BRANCH_UNION_CHAIRMAN")
},
applyUnionScope() {
if (this.isBranchUnionChairmanOnly() && this.currentUnionId) {
this.pageForm.unionId = this.currentUnionId
}
},
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.isBranchUnionChairmanOnly() ? this.currentUnionId : ""
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 || []
if (this.isBranchUnionChairmanOnly()) {
this.currentUnionId = this.unionOptions.length ? this.unionOptions[0].id : ""
this.applyUnionScope()
}
}
})
},
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.applyUnionScope()
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,757 @@
<!--#
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="30" 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 validateContactPhone = (rule, value, callback) => {
// 线路联系人电话允许填写座机、分机或其他联系说明,此处只校验必填。
if (!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: validateContactPhone, 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>
<!--#
}
#-->
@@ -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,928 @@
<!--#
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-select v-model="pageForm.assignSource" clearable placeholder="请选择分配类别" style="width: 100%">
<el-option v-for="item in assignSourceOptions" :key="item.value" :label="item.label" :value="item.value"></el-option>
</el-select>
</search-item>
<search-item label="是否退出">
<el-select v-model="pageForm.cancelled" clearable placeholder="请选择是否退出" style="width: 100%">
<el-option label="已退出" value="true"></el-option>
<el-option label="未退出" value="false"></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>
<el-button type="warning" size="medium" icon="el-icon-message" @click="openRemindDialog">发送提醒</el-button>
<el-button type="primary" size="medium" icon="el-icon-download" @click="exportData">导出</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="assignSource" width="120" sortable="custom" align="center" header-align="center">
<template slot-scope="{row}">
<el-tag size="mini" :type="row.assignSource === 'BRANCH_UNION' ? 'warning' : 'primary'">{{ assignSourceText(row.assignSource) }}</el-tag>
</template>
</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="isSchoolUnionAssignment(row) && !row.matterId && row.personType !== 'BACKUP'" size="mini" type="primary" @click="openSelectMatter(row)">选择线路</el-button>
<el-button v-if="isSchoolUnionAssignment(row) && isCancelled(row) && $auth.hasPermission('thirtyTeachTour.schoolUserAssignment.cancelRestore')" size="mini" type="warning" @click="restoreCancel(row)">取消退出</el-button>
<el-button v-if="isSchoolUnionAssignment(row)" 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="remindDialogVisible"
:close-on-click-modal="false"
width="560px"
@closed="resetRemindDialog">
<el-form :model="remindForm" :rules="remindRules" ref="remindFormRef" label-width="90px">
<el-form-item label="提醒内容" prop="content">
<el-input
v-model="remindForm.content"
type="textarea"
:rows="5"
maxlength="500"
show-word-limit
placeholder="请输入需要发送给当前查询结果人员的提醒内容">
</el-input>
</el-form-item>
</el-form>
<span slot="footer" class="dialog-footer">
<el-button @click="remindDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="remindSubmitting" @click="doSendReminder">发送</el-button>
</span>
</el-dialog>
<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-select
v-model="selectedCandidateIds"
multiple
filterable
remote
clearable
collapse-tags
reserve-keyword
:remote-method="remoteCandidateSearch"
:loading="candidateSelectLoading"
placeholder="请选择姓名/工号"
style="width: 360px"
@change="candidateUserSelectChange">
<el-option v-for="item in candidateUserOptions" :key="item.userId" :label="candidateOptionLabel(item)" :value="item.userId"></el-option>
</el-select>
<el-button type="primary" icon="el-icon-search" @click="candidateSearch">查询</el-button>
<el-button @click="resetCandidateSearch">重置</el-button>
<span class="candidate-selected">已选 {{ selectedCandidates.length }} 人</span>
</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: [],
assignSourceOptions: [
{label: "校工会分配", value: "SCHOOL_UNION"},
{label: "分工会分配", value: "BRANCH_UNION"}
],
assignSettingOptions: [],
assignMatterOptions: [],
unionOptions: [],
assignDialogVisible: false,
remindDialogVisible: false,
remindSubmitting: false,
candidateLoading: false,
candidateSelectLoading: false,
candidateData: [],
candidateUserOptions: [],
selectedCandidates: [],
selectedCandidateIds: [],
assignSubmitting: false,
selectMatterDialogVisible: false,
selectMatterSubmitting: false,
selectMatterOptions: [],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
pageOrderName: "assignedAt",
pageOrderBy: "descending",
year: currentYear,
settingId: "",
matterId: "",
unionId: "",
personType: "",
assignSource: "SCHOOL_UNION",
cancelled: "false",
keyword: ""
},
assignForm: {
year: currentYear,
settingId: "",
matterId: ""
},
candidateForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
unionId: "",
keyword: ""
},
selectMatterForm: {
id: "",
settingId: "",
matterId: "",
userName: ""
},
remindForm: {
content: ""
},
assignRules: {
year: [{required: true, message: "请选择年度", trigger: ["change", "blur"]}],
settingId: [{required: true, message: "请选择疗休养配置", trigger: ["change", "blur"]}]
},
selectMatterRules: {
matterId: [{required: true, message: "请选择分配线路", trigger: ["change", "blur"]}]
},
remindRules: {
content: [{required: true, message: "请输入提醒内容", trigger: ["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: ""
}
},
defaultRemindForm() {
return {
content: ""
}
},
resetSearch() {
const currentYear = moment().format("YYYY")
this.pageForm.year = currentYear
this.pageForm.settingId = ""
this.pageForm.matterId = ""
this.pageForm.unionId = ""
this.pageForm.personType = ""
// 页面打开和重置后默认查看校工会分配,清空该筛选项时后端会查询全部来源。
this.pageForm.assignSource = "SCHOOL_UNION"
this.pageForm.cancelled = "false"
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 || []
}
})
},
pageData() {
const params = Object.assign({}, this.pageForm)
if (params.cancelled === "") {
delete params.cancelled
}
this.tableLoading = true
this.$axios.post(loc() + "/pageData", params).then((res) => {
if (res.code === 0) {
this.tableData = res.data.list
this.pageForm.totalCount = res.data.totalCount
}
}).finally(() => {
this.tableLoading = false
})
},
exportData() {
const params = new URLSearchParams()
;["year", "settingId", "matterId", "unionId", "personType", "assignSource", "cancelled", "keyword", "pageOrderName", "pageOrderBy"].forEach(key => {
const value = this.pageForm[key]
if (value !== undefined && value !== null && value !== "") {
params.append(key, value)
}
})
window.location.href = loc() + "/exportData?" + params.toString()
},
buildCurrentQueryParams() {
const params = {}
;["year", "settingId", "matterId", "unionId", "personType", "assignSource", "cancelled", "keyword", "pageOrderName", "pageOrderBy"].forEach(key => {
const value = this.pageForm[key]
if (value !== undefined && value !== null && value !== "") {
params[key] = value
}
})
return params
},
openRemindDialog() {
if (!this.pageForm.settingId) {
this.$message.warning("请先选择疗休养配置")
return
}
this.remindForm = this.defaultRemindForm()
this.remindDialogVisible = true
},
resetRemindDialog() {
this.remindForm = this.defaultRemindForm()
this.remindSubmitting = false
if (this.$refs.remindFormRef) {
this.$refs.remindFormRef.clearValidate()
}
},
doSendReminder() {
this.$refs.remindFormRef.validate((valid) => {
if (!valid) return
const params = this.buildCurrentQueryParams()
params.content = this.remindForm.content
this.$confirm("确定给当前查询结果中的人员发送提醒吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning"
}).then(() => {
this.remindSubmitting = true
this.$axios.post(loc() + "/sendReminder", params).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.$message.success("提醒发送成功,共发送" + (data.receiverCount || 0) + "人")
this.remindDialogVisible = false
} else {
this.$message.warning(res.msg || "提醒发送失败")
}
}).finally(() => {
this.remindSubmitting = false
})
}).catch(() => {})
})
},
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.selectedCandidateIds = []
this.candidateUserOptions = []
this.assignDialogVisible = true
this.loadAssignSettingOptions()
},
resetAssignDialog() {
this.assignForm = this.defaultAssignForm(moment().format("YYYY"))
this.candidateForm = this.defaultCandidateForm()
this.assignMatterOptions = []
this.candidateData = []
this.selectedCandidates = []
this.selectedCandidateIds = []
this.candidateUserOptions = []
this.assignSubmitting = false
},
assignYearChange() {
this.assignForm.settingId = ""
this.assignForm.matterId = ""
this.assignMatterOptions = []
this.selectedCandidateIds = []
this.candidateUserOptions = []
this.candidateForm.keyword = ""
this.clearCandidateSelection()
this.loadAssignSettingOptions()
this.loadCandidatePageData()
},
assignSettingChange() {
this.assignForm.matterId = ""
this.assignMatterOptions = []
this.selectedCandidateIds = []
this.candidateUserOptions = []
this.candidateForm.keyword = ""
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.selectedCandidateIds || []).length > 0 ? "" : this.candidateForm.keyword,
userIds: JSON.stringify(this.selectedCandidateIds || [])
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.candidateData = (data.list || []).map(item => Object.assign({}, item, {
assignmentMatterId: ""
}))
this.candidateForm.totalCount = data.totalCount || 0
this.mergeCandidateOptions(this.candidateData)
} 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.clearCandidateSelection()
this.loadCandidatePageData()
},
resetCandidateSearch() {
this.candidateForm.unionId = ""
this.candidateForm.keyword = ""
this.candidateForm.pageNumber = 1
this.selectedCandidateIds = []
this.selectedCandidates = []
this.candidateUserOptions = []
this.clearCandidateSelection()
this.loadCandidatePageData()
},
candidateSizeChange(size) {
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 || []
this.mergeCandidateOptions(this.selectedCandidates)
if (this.selectedCandidates.length <= 0) {
this.assignForm.matterId = ""
}
},
remoteCandidateSearch(keyword) {
if (!this.assignForm.settingId) {
this.candidateUserOptions = []
return
}
// 人员选择器复用候选人员接口,校工会可按分工会过滤,也可不选分工会查询全部会员。
this.candidateForm.keyword = keyword || ""
this.candidateSelectLoading = true
this.$axios.post(loc() + "/candidatePageData", {
pageNumber: 1,
pageSize: 20,
settingId: this.assignForm.settingId,
unionId: this.candidateForm.unionId,
keyword: keyword || ""
}).then((res) => {
if (res.code === 0) {
const data = res.data || {}
this.candidateUserOptions = this.mergeOptionLists(this.selectedCandidates, data.list || [])
}
}).finally(() => {
this.candidateSelectLoading = false
})
},
candidateUserSelectChange(userIds) {
// 人员选择器仅作为查询条件;最终分配人员仍由下方列表勾选决定。
this.selectedCandidateIds = userIds || []
},
candidateOptionLabel(item) {
if (!item) {
return ""
}
return (item.userName || "") + (item.loginName ? "" + item.loginName + "" : "")
},
mergeCandidateOptions(list) {
this.candidateUserOptions = this.mergeOptionLists(this.candidateUserOptions, list || [])
},
mergeOptionLists(first, second) {
const map = {}
;(first || []).concat(second || []).forEach(item => {
if (item && item.userId) {
map[item.userId] = Object.assign({}, map[item.userId] || {}, item)
}
})
return Object.keys(map).map(key => map[key])
},
isCandidateSelected(row) {
return !!row && this.selectedCandidates.some(item => item.userId === row.userId)
},
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" ? "替补人员" : "正式人员"
},
assignSourceText(assignSource) {
if (assignSource === "SCHOOL_UNION") {
return "校工会分配"
}
if (assignSource === "BRANCH_UNION") {
return "分工会分配"
}
return assignSource || ""
},
isSchoolUnionAssignment(row) {
return row && row.assignSource === "SCHOOL_UNION"
},
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) {
return item.matterName || item.lineName || ""
}
},
mounted() {
this.loadSettingOptions()
this.loadUnionOptions()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,335 @@
<!--#
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) => {
if (!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>
<!--#
}
#-->
@@ -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/thirtyTeachTour/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/thirtyTeachTour/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/thirtyTeachTour/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/thirtyTeachTour/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,702 @@
<!--#
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-list {
max-height: 54vh;
overflow-y: auto;
padding: 10px 12px;
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
}
.tour-family-card {
margin-bottom: 10px;
border: 1px solid #edf0f4;
border-radius: 6px;
overflow: hidden;
background: #ffffff;
}
.tour-family-card:last-child {
margin-bottom: 0;
}
.tour-family-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 10px;
background: #f8fafc;
color: #0f172a;
font-size: 14px;
font-weight: 700;
}
.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/thirtyTeachTour/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="身份证号码" required v-model="signupForm.idCard" maxlength="30" placeholder="请填写身份证号码"></van-field>
<van-field label="性别" readonly v-model="signupForm.gender"></van-field>
<van-field label="年龄" readonly :value="staffAge(signupForm)"></van-field>
<van-field label="手机号" required 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="乘车地点"
required
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="info" :disabled="!allowFamily || isDirectFamilyLine(signupForm)" @click="addFamily">新增人员</van-button>
</div>
</div>
</div>
<div v-if="!allowFamily || isDirectFamilyLine(signupForm)" class="tour-apply-empty">
当前线路不支持填写亲属
</div>
<div v-else-if="familyData.length > 0" class="tour-family-list">
<div v-for="(item,index) in familyData" :key="index" class="tour-family-card">
<div class="tour-family-head">
<span>亲属{{ index + 1 }}</span>
<van-button size="mini" type="danger" plain @click="removeFamily(index)">删除</van-button>
</div>
<van-field label="姓名" v-model="item.familyName" maxlength="100" placeholder="请填写姓名"></van-field>
<van-field label="年龄" v-model.number="item.age" type="digit" placeholder="请填写年龄"></van-field>
<van-field label="性别">
<template #input>
<van-radio-group v-model="item.gender" direction="horizontal">
<van-radio name="男"></van-radio>
<van-radio name="女"></van-radio>
</van-radio-group>
</template>
</van-field>
<van-field label="身份证号码" v-model="item.idCard" maxlength="18" placeholder="请填写身份证号码" @blur="normalizeFamilyIdCard(item)"></van-field>
<van-field label="手机号" v-model="item.mobile" maxlength="30" placeholder="请填写手机号"></van-field>
<van-field
label="与本人关系"
readonly
clickable
is-link
placeholder="请选择关系"
v-model="item.relationship"
@click="openRelationshipPicker(index)">
</van-field>
<van-field
v-if="fillBedInfo"
label="床型"
readonly
clickable
is-link
placeholder="请选择房型"
v-model="item.bedType"
@click="openBedPicker('family', index)">
</van-field>
<van-field v-if="fillBedInfo" label="床位" v-model="item.bedInfo" maxlength="100" placeholder="请输入床位数量"></van-field>
<van-field v-if="fillBedInfo" label="意向拼床人" v-model="item.intendedRoommate" maxlength="100" placeholder="请输入意向拼床人"></van-field>
<div v-if="fillBedInfo" class="tour-field-tip">提醒:如果跟报名人员同一房间,无须选择!</div>
</div>
</div>
<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: [],
bedTypeOptions: [],
familyRelationshipOptions: [],
directRelativeOptions: [],
boardingPlaceOptions: [],
bedPickerVisible: false,
relationshipPickerVisible: false,
directRelativePickerVisible: false,
boardingPlacePickerVisible: false,
bedPickerTarget: "self",
bedPickerFamilyIndex: -1,
relationshipFamilyIndex: -1,
matterId: GetQueryString("matterId") || ""
}
},
computed: {
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())
},
// 年龄根据报名人身份证出生日期实时计算,只做页面展示不写回后端。
staffAge(row) {
const idCard = row && row.idCard ? String(row.idCard).trim() : ""
if (!/^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$/.test(idCard)) {
return ""
}
const year = Number(idCard.substring(6, 10))
const month = Number(idCard.substring(10, 12))
const day = Number(idCard.substring(12, 14))
const today = new Date()
let age = today.getFullYear() - year
const currentMonth = today.getMonth() + 1
const currentDay = today.getDate()
if (currentMonth < month || (currentMonth === month && currentDay < day)) {
age--
}
return age >= 0 ? String(age) : ""
},
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/thirtyTeachTour/signup/bedTypeOptions").then(res => {
if (res.code === 0) this.bedTypeOptions = res.data || []
}),
this.$axios.post("/platform/thirtyTeachTour/signup/familyRelationshipOptions").then(res => {
if (res.code === 0) this.familyRelationshipOptions = res.data || []
}),
this.$axios.post("/platform/thirtyTeachTour/signup/directRelativeOptions").then(res => {
if (res.code === 0) this.directRelativeOptions = res.data || []
})
])
},
loadSignupDetail() {
return this.$axios.post("/platform/thirtyTeachTour/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 assignment = data.assignment || {}
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: "",
travelPeriod: matter.travelPeriod || "",
travelStartTime: matter.travelStartTime || "",
travelEndTime: matter.travelEndTime || "",
allowOverReimbursement: matter.allowOverReimbursement,
overCostReimbursed: false,
hasFamily: false,
intendedRoommate: "",
bedType: "",
bedInfo: ""
}, staff, assignment, 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 || assignment.boardingPlace || "",
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
}
})
},
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)
}
},
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.signupForm.hasFamily = true
},
removeFamily(index) {
if (this.familyData.length === 0) return
this.familyData.splice(index, 1)
this.signupForm.hasFamily = this.familyData.length > 0
},
openBedPicker(target, index) {
if (this.bedTypeColumns.length === 0) {
vant.Toast("暂无床型选项")
return
}
this.bedPickerTarget = target
this.bedPickerFamilyIndex = index === undefined ? -1 : index
this.bedPickerVisible = true
},
confirmBedType(value) {
if (this.bedPickerTarget === "family" && this.familyData[this.bedPickerFamilyIndex]) {
this.familyData[this.bedPickerFamilyIndex].bedType = value
} else {
this.signupForm.bedType = value
}
this.bedPickerVisible = false
},
openRelationshipPicker(index) {
if (this.relationshipColumns.length === 0) {
vant.Toast("暂无关系选项")
return
}
this.relationshipFamilyIndex = index
this.relationshipPickerVisible = true
},
confirmRelationship(value) {
if (this.familyData[this.relationshipFamilyIndex]) {
this.familyData[this.relationshipFamilyIndex].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
},
validateStaffRequired() {
// 报名人身份证和手机号为移动端提交必填项,提交前统一清理空格并拦截空值。
const idCard = this.signupForm && this.signupForm.idCard ? String(this.signupForm.idCard).trim().toUpperCase() : ""
const mobile = this.signupForm && this.signupForm.mobile ? String(this.signupForm.mobile).trim() : ""
this.$set(this.signupForm, "idCard", idCard)
this.$set(this.signupForm, "mobile", mobile)
if (!idCard) {
vant.Toast("请填写身份证号码")
return false
}
if (!this.isValidIdCard(idCard)) {
vant.Toast("请输入正确的身份证号码")
return false
}
if (!mobile) {
vant.Toast("请填写手机号")
return false
}
return true
},
validateDirectRelative() {
if (!this.isDirectFamilyLine(this.signupForm)) return true
if (!this.directRelativeForm.relativeName) {
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
},
showSubmitError(error) {
// Keep signup submit errors local so the global axios interceptor does not show a duplicate toast.
vant.Toast.clear()
const responseData = error && error.response && error.response.data ? error.response.data : null
const message = responseData && (responseData.msg || responseData.message)
? (responseData.msg || responseData.message)
: (error && (error.msg || error.message) ? (error.msg || error.message) : "报名提交失败")
vant.Toast({
message: message,
duration: 3000
})
},
submitSignupRequest(form) {
// Use raw axios for this submit only; the page controls backend error text and toast duration locally.
return axios.post("/platform/thirtyTeachTour/signup/doSignup", $.param(form), {
headers: {
"Content-Type": "application/x-www-form-urlencoded;charset=utf-8",
"x-requested-with": "XMLHttpRequest"
}
})
},
submitSignup() {
if (this.pageLoading || this.submitLoading) return
if (!this.validateStaffRequired()) 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.submitSignupRequest(form).then((resp) => {
this.submitLoading = false
const res = resp.data || {}
if (res.code === 0) {
vant.Dialog.alert({
title: "提示",
message: res.msg || "恭喜您已报名成功",
confirmButtonColor: "#1867b0"
}).then(() => {
window.location.replace("/platform/thirtyTeachTour/signup/h5/notice")
})
} else {
this.showSubmitError(res)
}
}).catch((error) => {
this.submitLoading = false
this.showSubmitError(error)
})
}
},
created() {
this.init()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,234 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.tour-confirm-page {
min-height: 100vh;
padding: 12px 12px 84px;
background: #f4f6f8;
box-sizing: border-box;
}
.tour-confirm-card {
overflow: hidden;
border-radius: 8px;
background: #ffffff;
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.06);
}
.tour-confirm-tip {
margin: 10px 2px 0;
color: #ee8b00;
font-size: 12px;
line-height: 1.6;
}
.tour-confirm-footer {
position: fixed;
left: 0;
right: 0;
bottom: 0;
z-index: 20;
padding: 10px 14px calc(10px + env(safe-area-inset-bottom));
border-top: 1px solid #edf0f4;
background: #ffffff;
box-sizing: border-box;
}
</style>
<div id="app">
<van-nav-bar title="信息确认" left-text="返回" left-arrow @click-left="historyBack" fixed placeholder></van-nav-bar>
<div class="tour-confirm-page">
<van-loading v-if="loading" size="24px" vertical>加载中...</van-loading>
<template v-else>
<div class="tour-confirm-card">
<van-field label="姓名" required v-model="form.userName" maxlength="100" placeholder="请填写姓名"></van-field>
<van-field label="性别" required readonly clickable is-link v-model="form.gender" placeholder="请选择性别" @click="genderPickerVisible=true"></van-field>
<van-field label="年龄" required readonly v-model="form.age" placeholder="根据身份证号自动计算"></van-field>
<van-field label="身份证号" required v-model="form.idCard" maxlength="18" placeholder="请填写身份证号" @blur="normalizeIdCard"></van-field>
<van-field label="手机号" required v-model="form.mobile" type="tel" maxlength="30" placeholder="请填写手机号"></van-field>
<van-field
label="乘车地点"
required
:readonly="boardingPlaceOptions.length > 0"
:clickable="boardingPlaceOptions.length > 0"
:is-link="boardingPlaceOptions.length > 0"
v-model="form.boardingPlace"
placeholder="请选择或填写乘车地点"
@click="openBoardingPlacePicker">
</van-field>
</div>
<div class="tour-confirm-tip">
请确认本人报名基础信息无误后,即可选择路线报名。
</div>
</template>
</div>
<div class="tour-confirm-footer">
<van-button type="info" block round :loading="submitLoading" @click="submitConfirm">我已确认</van-button>
</div>
<van-popup v-model="genderPickerVisible" position="bottom">
<van-picker show-toolbar :columns="genderColumns" @confirm="confirmGender" @cancel="genderPickerVisible=false"></van-picker>
</van-popup>
<van-popup v-model="boardingPlacePickerVisible" position="bottom">
<van-picker show-toolbar :columns="boardingPlaceOptions" @confirm="confirmBoardingPlace" @cancel="boardingPlacePickerVisible=false"></van-picker>
</van-popup>
</div>
<script nonce="${cspNonce!}">
const vue = new Vue({
el: "#app",
store,
data() {
return {
loading: false,
submitLoading: false,
existsAssignment: false,
form: {
userName: "",
gender: "",
age: "",
idCard: "",
mobile: "",
boardingPlace: ""
},
genderColumns: ["男", "女"],
boardingPlaceOptions: [],
genderPickerVisible: false,
boardingPlacePickerVisible: false
}
},
methods: {
parseBoardingPlaceOptions(value) {
if (!value) return []
try {
const list = JSON.parse(value)
if (!Array.isArray(list)) return []
return list.map(function (item) {
if (typeof item === "string") return item
return item && item.name ? item.name : ""
}).filter(function (item) {
return !!item
})
} catch (e) {
return String(value).split(",").map(function (item) {
return item.trim()
}).filter(function (item) {
return !!item
})
}
},
normalizeIdCard() {
this.form.idCard = this.form.idCard ? String(this.form.idCard).trim().toUpperCase() : ""
this.form.age = this.calcAgeByIdCard(this.form.idCard)
},
isValidIdCard(value) {
if (!value) return false
const idCard = String(value).trim().toUpperCase()
return /^[1-9]\d{5}(18|19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dX]$/.test(idCard)
},
calcAgeByIdCard(value) {
if (!this.isValidIdCard(value)) {
return ""
}
const idCard = String(value).trim().toUpperCase()
const year = Number(idCard.substring(6, 10))
const month = Number(idCard.substring(10, 12))
const day = Number(idCard.substring(12, 14))
const today = new Date()
let age = today.getFullYear() - year
const currentMonth = today.getMonth() + 1
const currentDay = today.getDate()
if (currentMonth < month || (currentMonth === month && currentDay < day)) {
age--
}
return age >= 0 ? String(age) : ""
},
loadInfo() {
this.loading = true
this.$axios.post("/platform/thirtyTeachTour/signup/h5/confirmInfo").then((res) => {
this.loading = false
if (res.code !== 0) {
vant.Toast(res.msg || "信息加载失败")
return
}
const data = res.data || {}
const form = data.form || {}
this.existsAssignment = data.existsAssignment === true || data.existsAssignment === 1 || data.existsAssignment === "1"
if (!this.existsAssignment) {
window.location.replace("/platform/thirtyTeachTour/signup/h5/signup")
return
}
this.boardingPlaceOptions = this.parseBoardingPlaceOptions(data.boardingPlaceOptions || "")
this.form = Object.assign({}, this.form, form, {
age: form.idCard ? this.calcAgeByIdCard(form.idCard) : ""
})
}).catch(() => {
this.loading = false
})
},
openBoardingPlacePicker() {
if (this.boardingPlaceOptions.length === 0) return
this.boardingPlacePickerVisible = true
},
confirmGender(value) {
this.form.gender = value
this.genderPickerVisible = false
},
confirmBoardingPlace(value) {
this.form.boardingPlace = value
this.boardingPlacePickerVisible = false
},
validateForm() {
this.normalizeIdCard()
const requiredFields = [
{ key: "userName", message: "请填写姓名" },
{ key: "gender", message: "请选择性别" },
{ key: "idCard", message: "请填写身份证号" },
{ key: "age", message: "请填写正确的身份证号" },
{ key: "mobile", message: "请填写手机号" },
{ key: "boardingPlace", message: "请填写乘车地点" }
]
for (let i = 0; i < requiredFields.length; i++) {
const item = requiredFields[i]
if (this.form[item.key] === null || this.form[item.key] === undefined || String(this.form[item.key]).trim() === "") {
vant.Toast(item.message)
return false
}
}
if (!this.isValidIdCard(this.form.idCard)) {
vant.Toast("请输入正确的身份证号")
return false
}
this.form.age = this.calcAgeByIdCard(this.form.idCard)
return true
},
submitConfirm() {
if (this.loading || this.submitLoading) return
if (!this.validateForm()) return
this.submitLoading = true
this.$axios.post("/platform/thirtyTeachTour/signup/h5/saveConfirmInfo", this.form).then((res) => {
this.submitLoading = false
if (res.code === 0) {
window.location.replace("/platform/thirtyTeachTour/signup/h5/signup")
} else {
vant.Toast(res.msg || "信息确认失败")
}
}).catch(() => {
this.submitLoading = false
})
}
},
created() {
this.loadInfo()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,42 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.tour-entry-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: #f4f6f8;
}
</style>
<div id="app" class="tour-entry-page">
<van-loading size="24px" vertical>加载中...</van-loading>
</div>
<script nonce="${cspNonce!}">
const vue = new Vue({
el: "#app",
store,
methods: {
routeByAssignment() {
this.$axios.post("/platform/thirtyTeachTour/signup/h5/confirmInfo").then((res) => {
const data = res.code === 0 ? (res.data || {}) : {}
const existsAssignment = data.existsAssignment === true || data.existsAssignment === 1 || data.existsAssignment === "1"
window.location.replace(existsAssignment ? "/platform/thirtyTeachTour/signup/h5/confirm" : "/platform/thirtyTeachTour/signup/h5/noAssignment")
}).catch(() => {
window.location.replace("/platform/thirtyTeachTour/signup/h5/noAssignment")
})
}
},
created() {
this.routeByAssignment()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,347 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.tour-signup-h5 {
min-height: 100vh;
background: #f5f7fb;
padding: 10px 10px 80px;
box-sizing: border-box;
}
.tour-signup-card {
display: flex;
flex-direction: column;
min-height: calc(100vh - 160px);
max-height: calc(100vh - 160px);
margin: 0;
padding: 14px 14px 16px;
border-radius: 8px;
background: #ffffff;
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.08);
box-sizing: border-box;
}
.tour-signup-card__header {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 10px;
padding-bottom: 12px;
border-bottom: 1px solid #eef2f7;
}
.tour-signup-card__year {
flex-shrink: 0;
color: #0f74bc;
font-size: 13px;
font-weight: 600;
}
.tour-signup-notice {
color: #334155;
font-size: 15px;
line-height: 1.8;
word-break: break-word;
}
.tour-signup-card__body {
flex: 1;
min-height: 0;
margin-top: 14px;
overflow-y: auto;
}
.tour-signup-notice /deep/ img,
.tour-signup-notice /deep/ video {
max-width: 100%;
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 {
min-height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.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-card">
<div class="tour-signup-card__header">
<div class="tour-signup-card__year">{{ setting.year || currentYear }}年度</div>
</div>
<div class="tour-signup-card__body">
<van-skeleton title :row="8" :loading="loading">
<div v-if="setting.serviceNotice" class="tour-signup-notice">
<div v-if="serviceNoticePdfHref" class="tour-pdf-preview">
<div v-if="pdfPageCount" class="tour-pdf-toolbar">
已加载 {{ pdfRenderedPages }} / {{ pdfPageCount }} 页
</div>
<div ref="pdfPreviewContainer" class="tour-pdf-pages"></div>
<div v-if="pdfLoading && pdfRenderedPages === 0" class="tour-pdf-status">
<van-loading size="22px" vertical>正在加载第一页...</van-loading>
</div>
<div v-else-if="pdfLoading" class="tour-pdf-status">
正在继续加载后续页面...
</div>
<div v-if="pdfError" class="tour-pdf-status">{{ pdfError }}</div>
</div>
<div v-else v-html="setting.serviceNotice"></div>
</div>
<van-empty v-else class="tour-signup-empty" description="暂无须知"></van-empty>
</van-skeleton>
</div>
</div>
<div class="tour-signup-footer">
<van-button block type="info" color="#0f74bc" round @click="confirmRead">我已知晓</van-button>
</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,
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/thirtyTeachTour/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/thirtyTeachTour/signup/h5/signup"
}
},
created() {
this.loadServiceNotice()
},
beforeDestroy() {
this.clearPdfPreview()
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,550 @@
<!--#
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/thirtyTeachTour/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">{{ signupDetail.contactPhone || lineDetail.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 @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"
},
handleDetailAction() {
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/thirtyTeachTour/signup/lineDetail", { lineId: this.lineId }),
this.$axios.post("/platform/thirtyTeachTour/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
}
// 线路详情底部按钮不做报名资格前置校验,所有业务规则统一放到最终提交报名时处理。
window.location.href = "/platform/thirtyTeachTour/signup/h5/apply?matterId=" + encodeURIComponent(this.matterId)
},
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,997 @@
<!--#
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: none;
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-filter-bar {
margin-top: 10px;
padding-right: 16px;
}
.tour-line-period-list {
display: flex;
align-items: center;
gap: 8px;
overflow-x: auto;
overflow-y: hidden;
padding-bottom: 2px;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
-ms-overflow-style: none;
}
.tour-line-period-list::-webkit-scrollbar {
display: none;
}
.tour-line-period-chip {
min-width: 78px;
max-width: 168px;
min-height: 34px;
flex: 0 0 auto;
border: 1px solid #dbe3ee;
border-radius: 17px;
padding: 0 12px;
background: #ffffff;
color: #334155;
font-size: 14px;
line-height: 32px;
text-align: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
box-shadow: 0 2px 10px rgba(15, 23, 42, 0.05);
box-sizing: border-box;
}
.tour-line-period-chip--active {
border-color: #0b75bd;
background: #0b75bd;
color: #ffffff;
font-weight: 700;
}
.tour-line-period-tip {
margin-top: 6px;
color: #d97706;
font-size: 12px;
line-height: 18px;
}
.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 {
position: relative;
padding-right: 76px;
min-width: 0;
}
.tour-line-title-left {
min-width: 0;
color: #0f172a;
font-size: 16px;
font-weight: 700;
line-height: 1.35;
word-break: break-word;
}
.tour-line-title {
display: inline;
}
.tour-line-type-badge {
height: 18px;
display: inline-block;
margin-left: 5px;
vertical-align: 2px;
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-signup-count {
position: absolute;
top: 0;
right: 0;
height: 22px;
padding: 0 7px;
border-radius: 11px;
background: #d9fde8;
color: #2fcf6b;
font-size: 11px;
font-weight: 700;
line-height: 22px;
white-space: nowrap;
}
.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;
flex-wrap: wrap;
justify-content: flex-end;
gap: 8px;
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--plain {
border-color: #1e88e5;
background: #ffffff;
color: #1e88e5;
}
.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="goHome" 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/thirtyTeachTour/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/thirtyTeachTour/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/thirtyTeachTour/tour-direct-family-icon.png?v=20260524" alt="直系亲属">
<div class="tour-line-shortcut__text">
<div>直系亲属</div>
</div>
</div>
</div>
<div class="tour-line-filter-bar">
<div class="tour-line-period-list">
<button
v-for="item in visibleTravelPeriodList"
:key="item.value || 'all'"
type="button"
class="tour-line-period-chip"
:class="{'tour-line-period-chip--active': pageForm.travelPeriod === item.value}"
@click="selectTravelPeriod(item)">
{{ item.name }}
</button>
</div>
<div class="tour-line-period-tip">可选择指定出行时间,快速查看对应线路。</div>
</div>
</div>
<div class="tour-line-scroll">
<van-list
v-model="loading"
:finished="finished"
:immediate-check="false"
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="row._thumbUrl" :src="row._thumbUrl" fit="cover">
<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-left">
<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>
<span v-if="hasSignupCount(row)" class="tour-line-signup-count">已报名 {{ row.signupCount }}人</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>
<button
v-if="showLeaveThirtyTeachTourAction(row)"
type="button"
class="tour-line-action tour-line-action--danger"
@click.stop="leaveThirtyTeachTour(row)">
退出疗休养
</button>
<button
v-if="showCancelLineAction(row)"
type="button"
class="tour-line-action tour-line-action--plain"
@click.stop="cancelLine(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,
showUnionPopup: false,
unionList: [
{id: "", name: "全部分工会"}
],
travelPeriodList: [],
pageForm: {
pageNumber: 1,
pageSize: 10,
totalCount: 0,
year: new Date().getFullYear(),
lineName: "",
lineType: "",
unionId: "",
travelPeriod: "",
directFamilyUnitLine: null,
pageOrderName: "lineName",
pageOrderBy: "ascending"
}
}
},
computed: {
visibleTravelPeriodList() {
return this.travelPeriodList.filter((item) => item && item.value)
}
},
methods: {
goHome() {
window.location.replace("/platform/h5/home")
},
thumbUrl(row) {
const thumb = this.resolveThumbPath(row && (row.lineMobileThumb || row.agencyMobileThumb))
if (!thumb) {
return ""
}
if (thumb.indexOf("http://") === 0 || thumb.indexOf("https://") === 0 || thumb.indexOf("//") === 0 || thumb.indexOf("data:") === 0) {
return thumb
}
const domain = "${AppFileDomain!}" || "${AppDomain!}" || ""
if (!domain) {
return thumb
}
const prefix = domain.lastIndexOf("/") === domain.length - 1 ? domain.substring(0, domain.length - 1) : domain
const path = thumb.indexOf("/") === 0 ? thumb : "/" + thumb
return prefix + path
},
resolveThumbPath(value) {
if (!value) {
return ""
}
if (Array.isArray(value)) {
return value.length ? this.resolveThumbPath(value[0]) : ""
}
if (typeof value === "object") {
return value.thumbnail || value.downloadPath || value.url || value.data || value.path || ""
}
const text = String(value).trim()
if (!text) {
return ""
}
if (text.indexOf("[") === 0 || text.indexOf("{") === 0) {
try {
return this.resolveThumbPath(JSON.parse(text))
} catch (e) {
return text
}
}
return text.split(",").map((item) => item.trim()).filter((item) => item)[0] || ""
},
normalizeLineRows(rows) {
return (rows || []).map((row) => {
row._thumbUrl = this.thumbUrl(row)
return row
})
},
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))
},
hasSignupCount(row) {
return row && row.signupCount !== undefined && row.signupCount !== null && row.signupCount !== ""
},
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 false
},
showLeaveThirtyTeachTourAction(row) {
return this.isSigned(row) && this.toBoolean(row && row.canLeaveThirtyTeachTour) && !!(row && row.assignmentId)
},
showCancelLineAction(row) {
return this.isSigned(row) && this.toBoolean(row && row.canCancelLine) && !!(row && row.ledgerId)
},
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
}
// 进入报名页不再提前做资格校验,所有业务校验统一放到最终提交报名时处理。
window.location.href = "/platform/thirtyTeachTour/signup/h5/apply?matterId=" + encodeURIComponent(row.matterId)
},
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(() => {})
},
leaveThirtyTeachTour(row) {
if (!row || !row.assignmentId) {
vant.Toast("人员分配信息不完整")
return
}
vant.Dialog.confirm({
title: "提示",
message: "是否确认退出本次疗休,退出后将取消报名资格!",
confirmButtonColor: "#ee2f2f"
}).then(() => {
this.$axios.post("/platform/thirtyTeachTour/signup/h5/doLeaveThirtyTeachTour", { assignmentId: row.assignmentId }).then((res) => {
if (res.code === 0) {
vant.Toast.success(res.msg || "退出成功")
this.doSearch()
} else {
vant.Dialog.alert({ title: "提示", message: res.msg || "退出失败", confirmButtonColor: "#1867b0" })
}
})
}).catch(() => {})
},
cancelLine(row) {
if (!row || !row.ledgerId) {
vant.Toast("报名台账信息不完整")
return
}
vant.Dialog.confirm({
title: "提示",
message: "是否确认取消本线路报名?",
confirmButtonColor: "#ee2f2f"
}).then(() => {
this.$axios.post("/platform/thirtyTeachTour/signup/h5/doCancelLine", { ledgerId: row.ledgerId }).then((res) => {
if (res.code === 0) {
vant.Toast.success(res.msg || "取消成功")
this.doSearch()
} else {
vant.Dialog.alert({ title: "提示", message: res.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.pageForm.travelPeriod = ""
this.loadTravelPeriodOptions(true).then(() => {
this.doSearch()
})
},
toggleDirectFamilyLine() {
const active = this.pageForm.directFamilyUnitLine === true
this.pageForm.directFamilyUnitLine = active ? null : true
this.pageForm.lineType = ""
this.pageForm.travelPeriod = ""
this.loadTravelPeriodOptions(true).then(() => {
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()
},
selectTravelPeriod(item) {
const travelPeriod = item && item.value ? item.value : ""
if (this.pageForm.travelPeriod === travelPeriod) {
this.pageForm.travelPeriod = ""
this.doSearch()
return
}
this.pageForm.travelPeriod = travelPeriod
this.doSearch()
},
loadUnionOptions() {
this.$axios.post("/platform/thirtyTeachTour/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 || "未命名分工会"
}
}))
})
},
loadTravelPeriodOptions(defaultFirst) {
return this.$axios.post("/platform/thirtyTeachTour/signup/h5/travelPeriodOptions", {
year: this.pageForm.year,
lineType: this.pageForm.lineType,
directFamilyUnitLine: this.pageForm.directFamilyUnitLine
}).then((res) => {
if (res.code !== 0) {
return
}
const rows = res.data || []
this.travelPeriodList = rows.map((item) => {
return {
value: item.travelPeriod || "",
name: item.travelPeriod || "未设置出行时间"
}
}).filter((item) => item.value).sort((a, b) => {
return String(a.value).localeCompare(String(b.value))
})
if (defaultFirst) {
this.pageForm.travelPeriod = this.travelPeriodList.length > 0 ? this.travelPeriodList[0].value : ""
}
}).catch(() => {
if (defaultFirst) {
this.pageForm.travelPeriod = ""
}
})
},
loadData() {
if (this.finished) {
this.loading = false
return
}
this.loading = true
this.$axios.post("/platform/thirtyTeachTour/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 = this.normalizeLineRows(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
}
// 点击线路只进入详情页,报名资格和业务规则统一在提交报名按钮处校验。
window.location.href = "/platform/thirtyTeachTour/signup/h5/lineInfo?matterId="
+ encodeURIComponent(row.matterId)
+ "&lineId="
+ encodeURIComponent(row.lineId)
}
},
created() {
this.loadUnionOptions()
this.loadTravelPeriodOptions(true).then(() => {
this.loadData()
})
}
})
</script>
<!--#
}
#-->
@@ -0,0 +1,70 @@
<!--#
layout("/layouts/platform_h5.html"){
#-->
<style scoped>
.tour-no-assignment-page {
min-height: 100vh;
padding: 16px;
background: #f5f7fb;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
}
.tour-no-assignment-card {
width: 100%;
padding: 28px 18px 22px;
border-radius: 8px;
background: #ffffff;
box-shadow: 0 8px 20px rgba(15, 23, 42, 0.08);
box-sizing: border-box;
}
.tour-no-assignment-title {
margin-bottom: 14px;
color: #0f74bc;
font-size: 18px;
font-weight: 700;
text-align: center;
line-height: 1.5;
}
.tour-no-assignment-message {
color: #334155;
font-size: 15px;
line-height: 1.8;
text-align: center;
}
.tour-no-assignment-action {
margin-top: 24px;
}
</style>
<div id="app" class="tour-no-assignment-page">
<div class="tour-no-assignment-card">
<div class="tour-no-assignment-title">温馨提示</div>
<div class="tour-no-assignment-message">您不在本次疗休养的报名范围内,请等待下次机会!</div>
<div class="tour-no-assignment-action">
<van-button block type="info" color="#0f74bc" round @click="goHome">确定</van-button>
</div>
</div>
</div>
<script nonce="${cspNonce!}">
const vue = new Vue({
el: "#app",
store,
methods: {
goHome() {
window.location.replace("/platform/h5/home")
}
}
})
</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/thirtyTeachTour/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/thirtyTeachTour/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/thirtyTeachTour/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>
<!--#
}
#-->