activityRegistrationManagement.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  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.createdAt) || '-' }}</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.keyword" 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: 100%" 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. saveSafetyCultureActivityExecutorManagement,
  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. keyword: '',
  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. // employeeDeptName: [
  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. keyword: tableQuery.queryParam.keyword,
  195. startDate: tableQuery.queryParam.startTime,
  196. endDate: tableQuery.queryParam.endTime,
  197. activityId: activityRegistrationId.value,
  198. },
  199. };
  200. const res = await saveSafetyCultureActivityExecutorManagement(params);
  201. if (res) {
  202. tableData.value = res.records
  203. }
  204. } catch (e) {
  205. console.error('获取执行人安全文化活动报名列表失败:', e);
  206. tableData.value = [];
  207. pagination.total = 0;
  208. } finally {
  209. tableConfig.loading = false;
  210. }
  211. }
  212. function handleSearch() {
  213. if (tableQuery.queryParam.dateRange && tableQuery.queryParam.dateRange.length === 2) {
  214. tableQuery.queryParam.startTime = tableQuery.queryParam.dateRange[0];
  215. tableQuery.queryParam.endTime = tableQuery.queryParam.dateRange[1];
  216. } else {
  217. tableQuery.queryParam.startTime = '';
  218. tableQuery.queryParam.endTime = '';
  219. }
  220. pagination.pageNumber = 1;
  221. tableQuery.pageNumber = 1;
  222. getTableData();
  223. }
  224. const handleReset = () => {
  225. tableQuery.queryParam.keyword = '';
  226. tableQuery.queryParam.dateRange = null;
  227. handleSearch();
  228. };
  229. // 添加注册活动
  230. const selectDeptType = () => {
  231. isEditMode.value = true;
  232. addDialogVisible.value = true;
  233. };
  234. // 处理电话号码输入(只允许数字)
  235. const handlePhoneInput = (value: string) => {
  236. // 只保留数字
  237. normalForm.employeeContact = value.replace(/\D/g, '');
  238. };
  239. // 关闭对话框
  240. const handleDialogClose = () => {
  241. addDialogVisible.value = false;
  242. isEditMode.value = false;
  243. currentEditRow.value = null;
  244. editDetailData.value = null;
  245. // 重置表单
  246. if (normalFormRef.value) {
  247. normalFormRef.value.resetFields();
  248. normalForm.deptId = '';
  249. }
  250. };
  251. // 验证普通部门表单
  252. const validateNormalForm = (): Promise<boolean> => {
  253. return new Promise((resolve) => {
  254. if (!normalFormRef.value) {
  255. resolve(false);
  256. return;
  257. }
  258. normalFormRef.value.validate((valid) => {
  259. resolve(valid);
  260. });
  261. });
  262. };
  263. // 保存
  264. const handleSave = async () => {
  265. type ActivityUserParams = {
  266. id?: number; // 编辑时必传,新增时可选
  267. activityId?: number;
  268. deptId?: number;
  269. employeeId: string;
  270. employeeName: string;
  271. employeeContact: string;
  272. remark: string;
  273. };
  274. const isValid = await validateNormalForm();
  275. if (!isValid) {
  276. return;
  277. }
  278. const addUsers: ActivityUserParams = {
  279. ...(normalForm.id && { id: Number(normalForm.id) }),
  280. activityId: activityRegistrationDetail.value.id,
  281. deptId: normalForm.deptId ? Number(normalForm.deptId) : undefined,
  282. employeeId: normalForm.employeeCode.trim(),
  283. employeeName: normalForm.employeeName.trim(),
  284. employeeContact: normalForm.employeeContact.trim(),
  285. remark: normalForm.remark.trim(),
  286. };
  287. try {
  288. if (isEditMode.value && normalForm.id) {
  289. await updateSafetyCultureActivityManagement(addUsers);
  290. ElMessage.success('更新成功');
  291. } else {
  292. await addSafetyCultureActivityRegistration(addUsers);
  293. ElMessage.success('添加成功');
  294. }
  295. handleDialogClose();
  296. getTableData();
  297. } catch (e) {
  298. console.error('保存失败:', e);
  299. ElMessage.error(isEditMode.value ? '更新失败' : '保存失败');
  300. }
  301. };
  302. const handleEdit = async (row: any) => {
  303. if (!row.id) {
  304. ElMessage.error('缺少记录ID,无法编辑');
  305. return;
  306. }
  307. try {
  308. isEditMode.value = true;
  309. addDialogVisible.value = true;
  310. normalForm.id = row.id;
  311. normalForm.deptId = row.deptId;
  312. normalForm.employeeCode = row.employeeId;
  313. normalForm.employeeName = row.employeeName;
  314. normalForm.employeeContact = row.employeeContact;
  315. normalForm.remark = row.remark;
  316. } catch (e) {
  317. console.error('获取详情失败:', e);
  318. ElMessage.error('获取详情失败');
  319. }
  320. };
  321. const handleDelete = async (row: any) => {
  322. try {
  323. await ElMessageBox.confirm('确定要删除这条记录吗?', '提示', {
  324. confirmButtonText: '确定',
  325. cancelButtonText: '取消',
  326. type: 'warning',
  327. });
  328. if (!row.id) {
  329. ElMessage.error('缺少记录ID,无法删除');
  330. return;
  331. }
  332. await deleteSafetyCultureActivityRegistration(row.id);
  333. ElMessage.success('删除成功');
  334. getTableData();
  335. } catch (e: any) {
  336. // 用户取消删除或删除失败
  337. if (e !== 'cancel') {
  338. console.error('删除失败:', e);
  339. ElMessage.error('删除失败');
  340. }
  341. }
  342. };
  343. const handleExport = () => {
  344. console.log('export advanced person list', tableQuery);
  345. };
  346. const formatDateTime = (dateTimeStr: string | undefined): string => {
  347. if (!dateTimeStr) return '';
  348. try {
  349. const date = new Date(dateTimeStr);
  350. const year = date.getFullYear();
  351. const month = String(date.getMonth() + 1).padStart(2, '0');
  352. const day = String(date.getDate()).padStart(2, '0');
  353. const hours = String(date.getHours()).padStart(2, '0');
  354. const minutes = String(date.getMinutes()).padStart(2, '0');
  355. const seconds = String(date.getSeconds()).padStart(2, '0');
  356. return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
  357. } catch (e) {
  358. return dateTimeStr;
  359. }
  360. };
  361. const getActivityRegistrationDetail = async () => {
  362. if (!activityRegistrationId.value) return;
  363. try {
  364. const res = await querySafetyCultureActivityDetail(activityRegistrationId.value);
  365. if (res) {
  366. activityRegistrationDetail.value = res;
  367. }
  368. } catch (e) {
  369. console.error('获取考核表详情失败:', e);
  370. }
  371. };
  372. const getDeptData = () => {
  373. getAllDepartments().then((res) => {
  374. firstLevelDepts.value = formatDeptTree(res);
  375. console.log('@res:', res);
  376. });
  377. };
  378. const handleChangeDept = (val, prop) => {
  379. // const cascader = cascaderRef.value?.[prop];
  380. // const deptInfo = cascader?.getCheckedNodes();
  381. // if (deptInfo && deptInfo.length > 0) {
  382. // normalForm.employeeDeptName = deptInfo[0].label;
  383. // normalForm.deptId = val[val.length - 1]
  384. // } else {
  385. // normalForm.employeeDeptName = '';
  386. // }
  387. };
  388. onMounted(() => {
  389. getTableData();
  390. getActivityRegistrationDetail();
  391. getDeptData();
  392. });
  393. </script>
  394. <style scoped lang="scss">
  395. @use '@/styles/page-details-layout.scss' as *;
  396. @use '@/styles/page-main-layout.scss' as *;
  397. @use '@/styles/basic-table-action.scss' as *;
  398. @use '@/styles/basic-table-file.scss' as *;
  399. @use '@/views/traffic/violation/style/act-search-table.scss' as *;
  400. .safety-platform-container__header {
  401. padding-bottom: 0 !important;
  402. }
  403. .evaluation-header {
  404. width: 100%;
  405. }
  406. .evaluation-title {
  407. font-size: 20px;
  408. font-weight: bold;
  409. margin: 0 0 12px 0;
  410. color: #333;
  411. }
  412. .evaluation-meta {
  413. margin-bottom: 12px;
  414. display: flex;
  415. gap: 24px;
  416. font-size: 14px;
  417. color: #666;
  418. }
  419. .evaluation-meta span {
  420. white-space: nowrap;
  421. }
  422. .dialog-footer {
  423. text-align: right;
  424. }
  425. </style>