securityOrganizationalStructure.vue 15 KB

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