securityOrganizationalStructure.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573
  1. <template>
  2. <div class="safety-platform-container">
  3. <header class="safety-platform-container__header">
  4. <div class="breadcrumb-title"> 安全组织体系管理 </div>
  5. </header>
  6. <main class="safety-platform-container__main flex platform-main">
  7. <div class="nav">
  8. <el-button type="primary" :icon="Plus" @click="addTeam('parent')"> 添加组织 </el-button>
  9. <div class="collapse-wrapper">
  10. <!-- 组织树 -->
  11. <el-collapse v-model="activeName" accordion v-if="fetchSafetyOrganizationList.length > 0">
  12. <CollapseItem
  13. v-for="item in fetchSafetyOrganizationList"
  14. :key="item.id"
  15. :data="item"
  16. :level="level"
  17. @click-node="querySafetyTeamData"
  18. @create-node="handleCreateSafetySystem"
  19. @edit-node="handleEditSafetySystem"
  20. @delete-node="handleDelSafetySystem"
  21. />
  22. </el-collapse>
  23. <div v-else>
  24. <el-empty description="未添加组织" />
  25. </div>
  26. </div>
  27. <!-- 添加、编辑组织弹窗 -->
  28. <AddSafetySystem
  29. v-model:visible="addSafetySystemVisible"
  30. :data="addSafetyOrganizationSystemFormData"
  31. @confirmAddSafetySystem="confirmAddSafetySystemCallback"
  32. />
  33. </div>
  34. <div class="search-table-container table-content">
  35. <div>
  36. <p class="label-title">组织信息</p>
  37. <el-form :model="safetyOrgUser" ref="formRef" :rules="safetyOrgUserRules">
  38. <el-form-item label="组织人数" prop="userNum">
  39. <el-input placeholder="请输入组织人数" type="number" v-model="safetyOrgUser.userNum" style="width:450px" />
  40. </el-form-item>
  41. <el-form-item label="组织职责" prop="depResp">
  42. <el-input placeholder="请填写组织职责" v-model="safetyOrgUser.depResp" :autosize="{ minRows: 2, maxRows: 6 }" :maxlength="300" show-word-limit type="textarea" style="width:450px" />
  43. </el-form-item>
  44. <el-form-item>
  45. <el-button type="primary" @click="handleSave"> 保存 </el-button>
  46. </el-form-item>
  47. </el-form>
  48. </div>
  49. <div>
  50. <p class="label-title">人员信息</p>
  51. <el-button type="primary" :icon="Plus" @click="handleCreate"> 添加 </el-button>
  52. <el-button plain @click="handleImport">导入</el-button>
  53. </div>
  54. <header>
  55. <div class="act-search">
  56. <section class="select-box">
  57. <div class="select-box--item">
  58. <span>搜索工号/姓名:</span>
  59. <el-input
  60. v-model="tableQuery.queryParam.keyword"
  61. placeholder="搜索工号/姓名"
  62. class="act-search-input"
  63. />
  64. </div>
  65. <div class="select-box--item">
  66. <span>状态:</span>
  67. <el-select v-model="tableQuery.queryParam.status" placeholder="请选择状态" clearable>
  68. <el-option label="启用" :value="1" />
  69. <el-option label="禁用" :value="2" />
  70. </el-select>
  71. </div>
  72. <div class="select-box--item">
  73. <span>日期范围:</span>
  74. <el-date-picker
  75. v-model="dateRange"
  76. @change="onchangeDateRange"
  77. type="daterange"
  78. range-separator="至"
  79. start-placeholder="开始日期"
  80. end-placeholder="结束日期"
  81. value-format="YYYY-MM-DD"
  82. format="YYYY-MM-DD"
  83. />
  84. </div>
  85. </section>
  86. <section class="search-btn">
  87. <el-button type="primary" @click="handleSearch">查询</el-button>
  88. <el-button @click="handleReset">重置</el-button>
  89. <el-button plain @click="handleDownload">导出</el-button>
  90. </section>
  91. </div>
  92. </header>
  93. <div class="batch-table">
  94. <BasicTable
  95. ref="basicTableRef"
  96. :tableData="tableData"
  97. :tableConfig="tableConfig"
  98. @update:pageSize="handleSizeChange"
  99. @update:pageNumber="handleCurrentChange"
  100. >
  101. <template #status="scope">
  102. <span>
  103. {{ scope.row.status === 1 ? '启用' : scope.row.status === 2 ? '禁用' : '-' }}
  104. </span>
  105. </template>
  106. <template #action="scope">
  107. <div class="action-container--div" style="justify-content: left">
  108. <ActionButton text="编辑" @click="handleEdit(scope.row.id)" />
  109. <ActionButton
  110. text="删除"
  111. :popconfirm="{
  112. title: '确定要删除?',
  113. }"
  114. @confirm="handleDelete(scope.row.id)"
  115. />
  116. <ActionButton text="查看" @click="handleView(scope.row.id)" />
  117. </div>
  118. </template>
  119. </BasicTable>
  120. </div>
  121. </div>
  122. </main>
  123. <BatchImport
  124. v-if="batchImportVisible"
  125. :visible="batchImportVisible"
  126. :import-api-url="importApiUrl"
  127. :template-url="templateUrl"
  128. template-name="安全组织体系管理导入模版"
  129. :show-template="true"
  130. @close="batchImportVisible = false"
  131. @update="handleUpdate"
  132. />
  133. </div>
  134. </template>
  135. <script setup lang="ts">
  136. import { onMounted, reactive, ref } from 'vue';
  137. import { ElMessage, ElMessageBox } from 'element-plus';
  138. import BasicTable from '@/components/BasicTable.vue';
  139. import useTableConfig from '@/hooks/useTableConfigHook';
  140. import ActionButton from '@/components/ActionButton.vue';
  141. import { TABLE_OPTIONS, TABLE_COLUMNS } from './configs/tables';
  142. import { useRouter, useRoute } from 'vue-router';
  143. import type { QueryPageRequest } from '@/types/basic-query';
  144. import {
  145. getSafetySystemList,
  146. addSafetySystem,
  147. updateSafetySystem,
  148. deleteSafetySystem,
  149. fetchTableList,
  150. delEmployee,
  151. safetyOrgUserSave,
  152. safetyOrgUserDetail,
  153. exportSafetyOrganizationSystemManagement
  154. } from '@/api/safety-organization-management';
  155. import { downloadByData } from '@/utils/file/download';
  156. import BatchImport from '@/components/batch-import/BatchImport.vue';
  157. import { useGlobSetting } from '@/hooks/setting';
  158. import urlJoin from 'url-join';
  159. import AddSafetySystem from './components/addSafetySystem.vue';
  160. import {SafetyOrgUserRules} from "./configs/form"
  161. import {
  162. Delete,
  163. Edit,
  164. Plus,
  165. } from '@element-plus/icons-vue'
  166. import CollapseItem from '../safetyOrganizationSystemManagement/components/collapseItem.vue'
  167. const position = ref('left')
  168. const route = useRoute();
  169. const router = useRouter();
  170. // 表格
  171. const basicTableRef = ref<InstanceType<typeof BasicTable>>();
  172. const { tableConfig, pagination } = useTableConfig(TABLE_COLUMNS, TABLE_OPTIONS);
  173. const tableData = ref<any[]>([]);
  174. const fetchSafetyOrganizationList = ref<any[]>([]);
  175. const activeName = ref('');
  176. // 日期范围(用于日期选择器)
  177. const dateRange = ref<[string, string] | string>('');
  178. const level = ref(1)
  179. const tableQuery = reactive<QueryPageRequest<any>>({
  180. pageNumber: pagination.pageNumber,
  181. pageSize: pagination.pageSize,
  182. queryParam: {
  183. classifyName: '',
  184. keyword: '',
  185. status: '',
  186. startTime: '',
  187. endTime: '',
  188. },
  189. });
  190. const safetyOrgUser = reactive({
  191. id: 0,
  192. userNum: '',
  193. depResp: ''
  194. })
  195. const formRef = ref()
  196. const safetyOrgUserRules = ref(SafetyOrgUserRules)
  197. // 校验员工数量和职责
  198. const handleValidate = async () => {
  199. if (!formRef.value) return;
  200. const res = await formRef.value.validateField();
  201. return res;
  202. };
  203. // 保存员工数量和职责
  204. const handleSave = async ()=>{
  205. const res = await handleValidate()
  206. if (!res) return;
  207. try {
  208. console.log(safetyOrgUser, 'canshu')
  209. safetyOrgUser.id = Number(safetyOrgUser.id)
  210. await safetyOrgUserSave(safetyOrgUser)
  211. ElMessage.success('保存成功');
  212. } catch (error) {
  213. ElMessage.error('保存失败');
  214. }
  215. }
  216. // 查询组织详情
  217. const safetyOrgDetail = async (id)=>{
  218. try {
  219. const res = await safetyOrgUserDetail(id)
  220. Object.assign(safetyOrgUser, {
  221. id,
  222. userNum: res.userNum,
  223. depResp: res.depResp
  224. })
  225. } catch (error) {
  226. ElMessage.error('获取详情失败');
  227. }
  228. }
  229. const handleSizeChange = (value: number) => {
  230. pagination.pageSize = value;
  231. tableQuery.pageSize = value;
  232. getTableData();
  233. };
  234. const handleCurrentChange = (value: number) => {
  235. pagination.pageNumber = value;
  236. tableQuery.pageNumber = value;
  237. getTableData();
  238. };
  239. async function getTableData() {
  240. tableConfig.loading = true;
  241. try {
  242. const res = await fetchTableList(tableQuery);
  243. if (res) {
  244. tableData.value = res.records
  245. pagination.total = res.totalRow;
  246. }
  247. } catch (e) {
  248. console.error('获取列表失败:', e);
  249. tableData.value = [];
  250. pagination.total = 0;
  251. } finally {
  252. tableConfig.loading = false;
  253. }
  254. }
  255. interface addSafetyOrganizationSystemFormDataType {
  256. type: String;
  257. orgName?: String;
  258. orgId?: String | number;
  259. action?: String;
  260. parentid?: String | number;
  261. }
  262. const addSafetySystemVisible = ref(false);
  263. const addSafetyOrganizationSystemFormData = ref<addSafetyOrganizationSystemFormDataType>({
  264. type: '',
  265. orgName: '',
  266. orgId: '',
  267. action: '',
  268. parentid: '',
  269. });
  270. // 一级新增
  271. const addTeam = (type) => {
  272. addSafetyOrganizationSystemFormData.value = {
  273. type,
  274. action: 'add',
  275. };
  276. addSafetySystemVisible.value = true;
  277. };
  278. // 获取组织列表
  279. const fetchSafetyOrganizationTeamList = async () => {
  280. try {
  281. const res = await getSafetySystemList();
  282. fetchSafetyOrganizationList.value = res;
  283. } catch (error) {
  284. ElMessage.error('获取组织列表失败');
  285. }
  286. };
  287. // 定义组织数据类型
  288. interface SafetySystemFormData {
  289. value: string; // 输入的组织名称
  290. action: 'add' | 'edit'; // 操作类型:新增或编辑
  291. orgId?: string | number; // 组织ID(编辑时必传)
  292. parentid?: string | number; // 父组织ID(新增子组织时必传)
  293. type?: 'children' | 'parent'; // 组织类型(子组织或根组织)
  294. }
  295. // 保存弹窗回调
  296. const confirmAddSafetySystemCallback = async (formData: SafetySystemFormData) => {
  297. try {
  298. if (!formData.value?.trim()) {
  299. ElMessage.warning('请输入有效的组织名称!');
  300. return;
  301. }
  302. const requestData = {
  303. orgName: formData.value.trim(),
  304. id: formData.action === 'edit' ? formData.orgId : undefined,
  305. parentid: formData.action === 'add' ? formData.parentid : undefined,
  306. };
  307. // console.log(formData, 'formData')
  308. if (formData.action === 'add') {
  309. if (formData.type === 'children' && formData.orgId) {
  310. requestData.parentid = formData.orgId;
  311. }
  312. await addSafetySystem(requestData);
  313. ElMessage.success('新增组织成功!');
  314. } else {
  315. // 如果是子类,补充父级ID
  316. if (formData.type === 'children' && formData.parentid) {
  317. requestData.parentid = formData.parentid;
  318. }
  319. await updateSafetySystem(requestData);
  320. ElMessage.success('编辑组织成功!');
  321. }
  322. // 刷新列表
  323. fetchSafetyOrganizationTeamList();
  324. } catch (error) {
  325. console.error('操作失败:', error);
  326. ElMessage.error(formData.action === 'add' ? '新增组织失败!' : '编辑组织失败!');
  327. }
  328. };
  329. // 二级新增
  330. const handleCreateSafetySystem = async (type, value) => {
  331. addSafetyOrganizationSystemFormData.value = {
  332. type:'children',
  333. action: 'add',
  334. orgName: value.orgName,
  335. orgId: value.orgId,
  336. };
  337. addSafetySystemVisible.value = true;
  338. // 打开某一个
  339. // activeName.value = value.orgId;
  340. };
  341. // 编辑
  342. const handleEditSafetySystem = (type, value, parentid) => {
  343. // console.log(value)
  344. addSafetySystemVisible.value = true;
  345. addSafetyOrganizationSystemFormData.value = {
  346. type,
  347. action: 'edit',
  348. orgName: value.orgName,
  349. orgId: value.orgId,
  350. parentid,
  351. };
  352. };
  353. // 删除
  354. const handleDelSafetySystem = async (type, value) => {
  355. // console.log('删除', type, value)
  356. ElMessageBox.confirm('确认删除该组织吗?', '警告', { type: 'warning' }).then(async () => {
  357. try {
  358. if(value.children.length > 0){
  359. ElMessage.error('当前一级组织存在子级数据,无法删除,请先删除子级组织')
  360. return
  361. }
  362. await deleteSafetySystem(value.orgId);
  363. ElMessage.success('删除成功');
  364. // 刷新列表
  365. fetchSafetyOrganizationTeamList();
  366. handleReset();
  367. } catch (error) {
  368. ElMessage.error(error || '删除失败');
  369. }
  370. });
  371. };
  372. // 查询
  373. const querySafetyTeamData = (value) => {
  374. // console.log('查询', value);
  375. tableQuery.queryParam.classifyName = value.orgId;
  376. safetyOrgDetail(value.orgId)
  377. getTableData();
  378. };
  379. // 时间查询
  380. const onchangeDateRange = () => {
  381. if (dateRange.value && Array.isArray(dateRange.value) && dateRange.value.length === 2) {
  382. tableQuery.queryParam.startTime = dateRange.value[0] || '';
  383. tableQuery.queryParam.endTime = dateRange.value[1] || '';
  384. } else {
  385. tableQuery.queryParam.startTime = '';
  386. tableQuery.queryParam.endTime = '';
  387. }
  388. getTableData();
  389. };
  390. const handleSearch = () => {
  391. pagination.pageNumber = 1;
  392. tableQuery.pageNumber = 1;
  393. getTableData();
  394. };
  395. const handleReset = () => {
  396. pagination.pageNumber = 1;
  397. tableQuery.queryParam = {
  398. classifyName: '',
  399. keyword: '',
  400. status: '', // 重置为默认启用状态
  401. startTime: '',
  402. endTime: '',
  403. };
  404. dateRange.value = '';
  405. handleSearch();
  406. };
  407. // 批量导入
  408. const batchImportVisible = ref(false);
  409. const { urlPrefix } = useGlobSetting();
  410. const importApiUrl = ref(urlJoin(urlPrefix, '/safetyorguser/importSafetyOrgUser'));
  411. const templateUrl = ref('./skyeye-file-upload/sfysecurity/TEMPLATE/安全组织体系管理导入模版.xlsx');
  412. const handleImport = () => {
  413. batchImportVisible.value = true;
  414. };
  415. const handleUpdate = () => {
  416. batchImportVisible.value = false;
  417. getTableData();
  418. };
  419. const handleDownload = async () => {
  420. try {
  421. const response = await exportSafetyOrganizationSystemManagement(tableQuery.queryParam);
  422. if (response) {
  423. const fileName = `安全组织体系管理_${new Date().toISOString().split('T')[0]}.xlsx`;
  424. downloadByData(response, fileName);
  425. ElMessage.success('导出成功');
  426. }
  427. } catch (e) {
  428. console.error('导出安全组织体系管理失败:', e);
  429. ElMessage.error('导出失败,请重试');
  430. }
  431. };
  432. const handleCreate = () => {
  433. router.push({
  434. name: 'SecurityOrganizationalStructureItem',
  435. query: {
  436. operate: 'employee-create',
  437. },
  438. });
  439. };
  440. const handleEdit = (id: number) => {
  441. router.push({
  442. name: 'SecurityOrganizationalStructureItem',
  443. query: {
  444. id,
  445. operate: 'employee-edit',
  446. },
  447. });
  448. };
  449. const handleDelete = async (id: number) => {
  450. try {
  451. await delEmployee(id);
  452. ElMessage.success('删除成功');
  453. getTableData();
  454. } catch (e) {
  455. console.error('删除员工失败:', e);
  456. ElMessage.error('删除失败,请重试');
  457. }
  458. };
  459. const handleView = (id: number) => {
  460. router.push({
  461. name: 'SecurityOrganizationalStructureItem',
  462. query: {
  463. id,
  464. operate: 'employee-view',
  465. },
  466. });
  467. };
  468. onMounted(() => {
  469. fetchSafetyOrganizationTeamList();
  470. // 默认读取上个选中架构组织的员工数据
  471. const orgId = route.query.id as string;
  472. tableQuery.queryParam.classifyName = orgId || undefined
  473. getTableData();
  474. if(orgId){
  475. safetyOrgDetail(orgId)
  476. }
  477. });
  478. </script>
  479. <style scoped lang="scss">
  480. @use '@/styles/page-details-layout.scss' as *;
  481. @use '@/styles/page-main-layout.scss' as *;
  482. @use '@/styles/basic-table-action.scss' as *;
  483. @use '@/views/traffic/violation/style/act-search-table.scss' as *;
  484. .platform-main {
  485. width:100%;
  486. }
  487. .table-content {
  488. flex:1;
  489. width: 0;
  490. min-width: 0;
  491. }
  492. .nav {
  493. flex: 0 0 300px;
  494. margin-right: 15px;
  495. padding-right: 15px;
  496. border-right: 1px solid #eee;
  497. overflow-y: auto;
  498. :deep(.collapse-title) {
  499. flex: 1 0 90%;
  500. order: 1;
  501. .el-collapse-item__header {
  502. flex: 1 0 auto;
  503. order: -1;
  504. }
  505. }
  506. .collapse-wrapper {
  507. margin-top: 10px;
  508. .title-wrapper {
  509. display: flex;
  510. justify-content: space-between;
  511. align-items: center;
  512. width: 100%;
  513. .handler {
  514. flex: 1;
  515. display: flex;
  516. justify-content: flex-end;
  517. align-items: center;
  518. padding-right: 15px;
  519. }
  520. }
  521. .collapse-item-content {
  522. ul {
  523. padding-left: 40px;
  524. li {
  525. display: flex;
  526. justify-content: space-between;
  527. align-items: center;
  528. width: 100%;
  529. padding: 6px 0;
  530. border-bottom: 1px solid #eeeeeed1;
  531. span {
  532. cursor: pointer;
  533. }
  534. }
  535. }
  536. }
  537. }
  538. }
  539. .label-title{
  540. margin-bottom:16px;
  541. }
  542. </style>