activityRegistrationManagement.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. <template>
  2. <div class="safety-platform-container">
  3. <header class="safety-platform-container__header">
  4. <div class="evaluation-header">
  5. <h1 class="evaluation-title">{{ activityRegistrationDetail.planName || '' }}</h1>
  6. <div class="evaluation-meta">
  7. <span>考核部门: {{ activityRegistrationDetail.responsibleDeptName || '-' }}</span>
  8. <span>创建人: {{ activityRegistrationDetail.createdByName || '-' }}</span>
  9. <span>创建时间: {{ formatDateTime(activityRegistrationDetail.startTime) || '-' }}</span>
  10. </div>
  11. </div>
  12. </header>
  13. <main class="safety-platform-container__main">
  14. <div class="search-table-container">
  15. <header>
  16. <div class="act-search">
  17. <section class="select-box">
  18. <div class="select-box--item">
  19. <span>员工工号/名称:</span>
  20. <el-input v-model="tableQuery.queryParam.userName" placeholder="搜索员工工号或名称" class="act-search-input" />
  21. </div>
  22. <div class="select-box--item">
  23. <span>报名日期:</span>
  24. <el-date-picker v-model="tableQuery.queryParam.dateRange" type="daterange" range-separator="至"
  25. start-placeholder="开始日期" end-placeholder="结束日期" value-format="YYYY-MM-DD" format="YYYY-MM-DD" />
  26. </div>
  27. </section>
  28. <section class="search-btn">
  29. <el-button type="primary" @click="handleSearch">查询</el-button>
  30. <el-button @click="handleReset">重置</el-button>
  31. <el-button type="primary" style="margin:0 10px;" @click="selectDeptType">
  32. 添加
  33. </el-button>
  34. <el-button plain @click="handleExport">
  35. 导出
  36. </el-button>
  37. </section>
  38. </div>
  39. </header>
  40. <div class="batch-table">
  41. <BasicTable ref="basicTableRef" :tableData="tableData" :tableConfig="tableConfig"
  42. @update:pageSize="handleSizeChange" @update:pageNumber="handleCurrentChange">
  43. <template #action="scope">
  44. <div class="action-container--div" style="justify-content: left">
  45. <ActionButton text="编辑" @click="handleEdit(scope.row)" />
  46. <ActionButton text="删除" @click="handleDelete(scope.row)" />
  47. </div>
  48. </template>
  49. </BasicTable>
  50. </div>
  51. </div>
  52. </main>
  53. <!-- 添加/编辑先进个人对话框 -->
  54. <el-dialog v-model="addDialogVisible" :title="`${isEditMode && normalForm.id ? '编辑' : '添加'}活动报名信息`" width="800px"
  55. :close-on-click-modal="false" @close="handleDialogClose">
  56. <div class="add-dialog-content">
  57. <!-- 普通部门表单 -->
  58. <el-form ref="normalFormRef" :model="normalForm" :rules="normalFormRules" label-width="140px">
  59. <el-form-item label="部门名称" prop="employeeDeptName">
  60. <el-cascader style="width: 50%" size="large" :ref="(el) => (cascaderRef['employeeDeptName'] = el)"
  61. :options="firstLevelDepts" :props="cascaderProp" :show-all-levels="false" placeholder="请选择安全责任部门"
  62. filterable v-model="normalForm.deptId" @change="(val) => handleChangeDept(val, 'employeeDeptName')" />
  63. </el-form-item>
  64. <el-form-item label="员工工号:" prop="employeeCode">
  65. <el-input v-model="normalForm.employeeCode" placeholder="请输入员工工号" maxlength="50" />
  66. </el-form-item>
  67. <el-form-item label="员工姓名:" prop="employeeName">
  68. <el-input v-model="normalForm.employeeName" placeholder="请输入员工姓名" maxlength="50" />
  69. </el-form-item>
  70. <el-form-item label="员工联系方式:" prop="employeeContact">
  71. <el-input v-model="normalForm.employeeContact" placeholder="请输入11位手机号码" maxlength="11"
  72. @input="handlePhoneInput" />
  73. </el-form-item>
  74. <el-form-item label="个人先进描述:" prop="remark">
  75. <el-input v-model="normalForm.remark" type="textarea" :rows="4" placeholder="请填写个人先进获取内容描述。" maxlength="300"
  76. show-word-limit />
  77. </el-form-item>
  78. </el-form>
  79. </div>
  80. <template #footer>
  81. <div class="dialog-footer">
  82. <el-button @click="handleDialogClose">取消</el-button>
  83. <el-button type="primary" @click="handleSave">保存</el-button>
  84. </div>
  85. </template>
  86. </el-dialog>
  87. </div>
  88. </template>
  89. <script lang="ts" setup>
  90. import { computed, onMounted, reactive, ref } from 'vue';
  91. import { useRoute } from 'vue-router';
  92. import { ElMessage, ElMessageBox } from 'element-plus';
  93. import type { FormInstance, FormRules } from 'element-plus';
  94. import BasicTable from '@/components/BasicTable.vue';
  95. import useTableConfig from '@/hooks/useTableConfigHook';
  96. import ActionButton from '@/components/ActionButton.vue';
  97. import { TABLE_OPTIONS } from '../configs/activityTables';
  98. import { ACTIVITY_REGISTRATION_ADVANCED_PERSON_TABLE_COLUMNS } from '../configs/activityTargetTables';
  99. import type { QueryPageRequest } from '@/types/basic-query';
  100. import {
  101. querySafetyCultureActivityDetail,
  102. saveSafetyCultureActivityManagement,
  103. getAllDepartments,
  104. addSafetyCultureActivityRegistration,
  105. deleteSafetyCultureActivityRegistration,
  106. updateSafetyCultureActivityManagement,
  107. type ActivityRegistrationTargetItem,
  108. type safetyCultureFilePageQuery,
  109. } from '@/api/safety-culture';
  110. import { formatDeptTree } from '@/views/disaster/utils/formatDeptTree';
  111. import { id } from 'element-plus/es/locale';
  112. const route = useRoute();
  113. const currentId = computed(() => Number(route.query.id));
  114. // 表格
  115. const basicTableRef = ref<InstanceType<typeof BasicTable>>();
  116. const { tableConfig, pagination } = useTableConfig(ACTIVITY_REGISTRATION_ADVANCED_PERSON_TABLE_COLUMNS, TABLE_OPTIONS);
  117. const tableData = ref<any[]>([]);
  118. const tableQuery = reactive<QueryPageRequest<any>>({
  119. pageNumber: pagination.pageNumber,
  120. pageSize: pagination.pageSize,
  121. queryParam: {
  122. userName: '',
  123. dateRange: null as any,
  124. startTime: '',
  125. endTime: '',
  126. },
  127. });
  128. const activityRegistrationId = computed(() => {
  129. const id = route.query.id;
  130. return id ? Number(id) : undefined;
  131. });
  132. // 考核表详情
  133. const activityRegistrationDetail = ref<Partial<ActivityRegistrationTargetItem>>({});
  134. // 添加对话框相关
  135. const addDialogVisible = ref(false);
  136. const isEditMode = ref(false);
  137. const currentEditRow = ref<any>(null);
  138. const editDetailData = ref(null);
  139. const normalFormRef = ref<FormInstance>();
  140. const normalForm = reactive({
  141. id: '',
  142. employeeDeptName: '',
  143. employeeCode: '',
  144. employeeName: '',
  145. employeeContact: '',
  146. remark: '',
  147. deptId: '',
  148. });
  149. const firstLevelDepts = ref<any[]>([]);
  150. const normalFormRules: FormRules = {
  151. deptId: [
  152. { required: true, message: '请选择部门名称', trigger: 'blur' },
  153. ],
  154. employeeCode: [
  155. { required: true, message: '请输入员工工号', trigger: 'blur' },
  156. ],
  157. employeeName: [
  158. { required: true, message: '请输入员工姓名', trigger: 'blur' },
  159. ],
  160. employeeContact: [
  161. { required: true, message: '请输入员工联系方式', trigger: 'blur' },
  162. { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的11位手机号码', trigger: 'blur' },
  163. ],
  164. remark: [
  165. { required: true, message: '请输入个人先进描述', trigger: 'blur' },
  166. { max: 300, message: '最多输入300个字符', trigger: 'blur' },
  167. ],
  168. };
  169. const cascaderProp = {
  170. expandTrigger: 'click',
  171. checkStrictly: true,
  172. // emitPath: false,
  173. value: 'id',
  174. label: 'deptName',
  175. };
  176. const cascaderRef = ref({});
  177. const handleSizeChange = (value: number) => {
  178. pagination.pageSize = value;
  179. tableQuery.pageSize = value;
  180. getTableData();
  181. };
  182. const handleCurrentChange = (value: number) => {
  183. pagination.pageNumber = value;
  184. tableQuery.pageNumber = value;
  185. getTableData();
  186. };
  187. async function getTableData() {
  188. tableConfig.loading = true;
  189. try {
  190. const params = {
  191. pageNumber: tableQuery.pageNumber,
  192. pageSize: tableQuery.pageSize,
  193. queryParam: {
  194. employeeKeyword: tableQuery.queryParam.userName,
  195. registrationDateStart: tableQuery.queryParam.startTime,
  196. registrationDateEnd: tableQuery.queryParam.endTime,
  197. },
  198. };
  199. const res = await saveSafetyCultureActivityManagement(params);
  200. if (res) {
  201. tableData.value = res.records
  202. }
  203. } catch (e) {
  204. console.error('获取先进个人信息列表失败:', e);
  205. tableData.value = [];
  206. pagination.total = 0;
  207. } finally {
  208. tableConfig.loading = false;
  209. }
  210. }
  211. function handleSearch() {
  212. if (tableQuery.queryParam.dateRange && tableQuery.queryParam.dateRange.length === 2) {
  213. tableQuery.queryParam.startTime = tableQuery.queryParam.dateRange[0];
  214. tableQuery.queryParam.endTime = tableQuery.queryParam.dateRange[1];
  215. } else {
  216. tableQuery.queryParam.startTime = '';
  217. tableQuery.queryParam.endTime = '';
  218. }
  219. pagination.pageNumber = 1;
  220. tableQuery.pageNumber = 1;
  221. getTableData();
  222. }
  223. const handleReset = () => {
  224. tableQuery.queryParam.userName = '';
  225. tableQuery.queryParam.dateRange = null;
  226. handleSearch();
  227. };
  228. // 添加注册活动
  229. const selectDeptType = () => {
  230. isEditMode.value = true;
  231. addDialogVisible.value = true;
  232. };
  233. // 处理电话号码输入(只允许数字)
  234. const handlePhoneInput = (value: string) => {
  235. // 只保留数字
  236. normalForm.employeeContact = value.replace(/\D/g, '');
  237. };
  238. // 关闭对话框
  239. const handleDialogClose = () => {
  240. addDialogVisible.value = false;
  241. isEditMode.value = false;
  242. currentEditRow.value = null;
  243. editDetailData.value = null;
  244. // 重置表单
  245. if (normalFormRef.value) {
  246. normalFormRef.value.resetFields();
  247. normalForm.deptId = '';
  248. }
  249. };
  250. // 验证普通部门表单
  251. const validateNormalForm = (): Promise<boolean> => {
  252. return new Promise((resolve) => {
  253. if (!normalFormRef.value) {
  254. resolve(false);
  255. return;
  256. }
  257. normalFormRef.value.validate((valid) => {
  258. resolve(valid);
  259. });
  260. });
  261. };
  262. // 保存
  263. const handleSave = async () => {
  264. type ActivityUserParams = {
  265. id?: number; // 编辑时必传,新增时可选
  266. activityId?: number;
  267. deptId?: number;
  268. employeeId: string;
  269. employeeName: string;
  270. employeeContact: string;
  271. remark: string;
  272. };
  273. const isValid = await validateNormalForm();
  274. if (!isValid) {
  275. return;
  276. }
  277. const addUsers: ActivityUserParams = {
  278. ...(normalForm.id && { id: Number(normalForm.id) }),
  279. activityId: activityRegistrationDetail.value.id,
  280. deptId: normalForm.deptId ? Number(normalForm.deptId) : undefined,
  281. employeeId: normalForm.employeeCode.trim(),
  282. employeeName: normalForm.employeeName.trim(),
  283. employeeContact: normalForm.employeeContact.trim(),
  284. remark: normalForm.remark.trim(),
  285. };
  286. try {
  287. if (isEditMode.value && normalForm.id) {
  288. await updateSafetyCultureActivityManagement(addUsers);
  289. ElMessage.success('更新成功');
  290. } else {
  291. await addSafetyCultureActivityRegistration(addUsers);
  292. ElMessage.success('添加成功');
  293. }
  294. handleDialogClose();
  295. getTableData();
  296. } catch (e) {
  297. console.error('保存失败:', e);
  298. ElMessage.error(isEditMode.value ? '更新失败' : '保存失败');
  299. }
  300. };
  301. const handleEdit = async (row: any) => {
  302. if (!row.id) {
  303. ElMessage.error('缺少记录ID,无法编辑');
  304. return;
  305. }
  306. try {
  307. isEditMode.value = true;
  308. addDialogVisible.value = true;
  309. normalForm.id = row.id;
  310. normalForm.deptId = row.deptId;
  311. normalForm.employeeCode = row.employeeId;
  312. normalForm.employeeName = row.employeeName;
  313. normalForm.employeeContact = row.employeeContact;
  314. normalForm.remark = row.remark;
  315. } catch (e) {
  316. console.error('获取详情失败:', e);
  317. ElMessage.error('获取详情失败');
  318. }
  319. };
  320. const handleDelete = async (row: any) => {
  321. try {
  322. await ElMessageBox.confirm('确定要删除这条记录吗?', '提示', {
  323. confirmButtonText: '确定',
  324. cancelButtonText: '取消',
  325. type: 'warning',
  326. });
  327. if (!row.id) {
  328. ElMessage.error('缺少记录ID,无法删除');
  329. return;
  330. }
  331. await deleteSafetyCultureActivityRegistration(row.id);
  332. ElMessage.success('删除成功');
  333. getTableData();
  334. } catch (e: any) {
  335. // 用户取消删除或删除失败
  336. if (e !== 'cancel') {
  337. console.error('删除失败:', e);
  338. ElMessage.error('删除失败');
  339. }
  340. }
  341. };
  342. const handleExport = () => {
  343. console.log('export advanced person list', tableQuery);
  344. };
  345. const formatDateTime = (dateTimeStr: string | undefined): string => {
  346. if (!dateTimeStr) return '';
  347. try {
  348. const date = new Date(dateTimeStr);
  349. const year = date.getFullYear();
  350. const month = String(date.getMonth() + 1).padStart(2, '0');
  351. const day = String(date.getDate()).padStart(2, '0');
  352. const hours = String(date.getHours()).padStart(2, '0');
  353. const minutes = String(date.getMinutes()).padStart(2, '0');
  354. const seconds = String(date.getSeconds()).padStart(2, '0');
  355. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  356. } catch (e) {
  357. return dateTimeStr;
  358. }
  359. };
  360. const getActivityRegistrationDetail = async () => {
  361. if (!activityRegistrationId.value) return;
  362. try {
  363. const res = await querySafetyCultureActivityDetail(activityRegistrationId.value);
  364. if (res) {
  365. activityRegistrationDetail.value = res;
  366. }
  367. } catch (e) {
  368. console.error('获取考核表详情失败:', e);
  369. }
  370. };
  371. const getDeptData = () => {
  372. getAllDepartments().then((res) => {
  373. firstLevelDepts.value = formatDeptTree(res);
  374. console.log('@res:', res);
  375. });
  376. };
  377. const handleChangeDept = (val, prop) => {
  378. const cascader = cascaderRef.value?.[prop];
  379. const deptInfo = cascader?.getCheckedNodes();
  380. if (deptInfo && deptInfo.length > 0) {
  381. normalForm.employeeDeptName = deptInfo[0].label;
  382. normalForm.deptId = val[val.length - 1]
  383. } else {
  384. normalForm.employeeDeptName = '';
  385. }
  386. };
  387. onMounted(() => {
  388. getTableData();
  389. getActivityRegistrationDetail();
  390. getDeptData();
  391. });
  392. </script>
  393. <style scoped lang="scss">
  394. @use '@/styles/page-details-layout.scss' as *;
  395. @use '@/styles/page-main-layout.scss' as *;
  396. @use '@/styles/basic-table-action.scss' as *;
  397. @use '@/styles/basic-table-file.scss' as *;
  398. @use '@/views/traffic/violation/style/act-search-table.scss' as *;
  399. .safety-platform-container__header {
  400. padding-bottom: 0 !important;
  401. }
  402. .evaluation-header {
  403. width: 100%;
  404. }
  405. .evaluation-title {
  406. font-size: 20px;
  407. font-weight: bold;
  408. margin: 0 0 12px 0;
  409. color: #333;
  410. }
  411. .evaluation-meta {
  412. margin-bottom: 12px;
  413. display: flex;
  414. gap: 24px;
  415. font-size: 14px;
  416. color: #666;
  417. }
  418. .evaluation-meta span {
  419. white-space: nowrap;
  420. }
  421. .dialog-footer {
  422. text-align: right;
  423. }
  424. </style>