UserListView.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. <template>
  2. <div class="full-height page-container">
  3. <SmartLayoutSeparate :show-line="false" first-size="280px" class="full-height">
  4. <template #first>
  5. <div class="full-height dept-container">
  6. <SysDeptTree async show-search @select="handleDeptSelected" />
  7. </div>
  8. </template>
  9. <template #second>
  10. <SmartTable @register="registerTable" :size="getTableSize">
  11. <template #table-operation="{ row }">
  12. <SmartVxeTableAction :actions="getTableActions(row)" />
  13. </template>
  14. <template #table-userType="{ row }">
  15. <span>
  16. {{ getUserTypeMap[row.userType] }}
  17. </span>
  18. </template>
  19. <template #search-userType="{ model, size }">
  20. <a-select style="width: 100px" :size="size" v-model:value="model.userType" allowClear>
  21. <a-select-option
  22. v-for="item in userTypeListRef"
  23. :key="'userType_' + item.dictItemCode"
  24. :value="item.dictItemCode"
  25. >
  26. {{ item.dictItemName }}
  27. </a-select-option>
  28. </a-select>
  29. </template>
  30. <template #table-accountStatus="{ row }">
  31. <a-tooltip :title="getLockedMessage(row.userAccount?.accountStatus)">
  32. <span
  33. :style="{
  34. color: getAccountData(row.userAccount?.accountStatus).color,
  35. fontWeight: 'bold',
  36. }"
  37. >
  38. {{ getAccountData(row.userAccount?.accountStatus).label }}
  39. </span>
  40. </a-tooltip>
  41. </template>
  42. </SmartTable>
  43. </template>
  44. </SmartLayoutSeparate>
  45. <UserAccountUpdateModal @register="registerAccountModal" />
  46. <UserSetRole @register="registerSetRoleModal" />
  47. <UserUseYnModal @register="registerUseYnModal" />
  48. </div>
  49. </template>
  50. <script lang="ts" setup>
  51. import { computed, ref, unref } from 'vue';
  52. import { useI18n } from '@/hooks/web/useI18n';
  53. import { storeToRefs } from 'pinia';
  54. import { useLoadDictItem } from '@/modules/smart-system/hooks/SysDictHooks';
  55. import { useSizeSetting } from '@/hooks/setting/UseSizeSetting';
  56. import { hasPermission } from '@/utils/auth';
  57. import { useModal } from '@/components/Modal';
  58. import { useUserStore } from '@/store/modules/user';
  59. import { SmartLayoutSeparate } from '@/components/SmartLayoutSeparate';
  60. import SysDeptTree from '@/modules/smart-system/components/SysDept/SysDeptTree.vue';
  61. import { useMessage } from '@/hooks/web/useMessage';
  62. import {
  63. SmartTable,
  64. useSmartTable,
  65. SmartVxeTableAction,
  66. ActionItem,
  67. } from '@/components/SmartTable';
  68. import UserAccountUpdateModal from './account/UserAccountUpdateModal.vue';
  69. import UserSetRole from './components/UserSetRole.vue';
  70. import UserUseYnModal from './components/UserUseYnModal.vue';
  71. import { getAddEditFormSchemas, getSearchSchemas, getTableColumns } from './UserListView.config';
  72. import {
  73. listApi,
  74. deleteApi,
  75. saveUpdateWithDataScopeApi,
  76. getByIdWithDataScopeApi,
  77. setUseYnApi,
  78. createAccountApi,
  79. unlockUserAccountApi,
  80. resetPassword,
  81. } from './UserListView.api';
  82. import {
  83. SYS_USER_TYPE,
  84. SystemPermissions,
  85. } from '@/modules/smart-system/constants/SystemConstants';
  86. import { copyText } from '@/utils/copyTextToClipboard';
  87. const { t } = useI18n();
  88. const { warnMessage, errorMessage, createConfirm, successMessage } = useMessage();
  89. const { getTableSize } = useSizeSetting();
  90. const { getIsPlatformTenant } = storeToRefs(useUserStore());
  91. const [registerUseYnModal, { openModal: openUseYnModal }] = useModal();
  92. const { dictData: userTypeListRef } = useLoadDictItem(ref('SYSTEM_USER_TYPE'));
  93. const getUserTypeMap = computed(() => {
  94. const result: { [index: string]: string } = {};
  95. result[SYS_USER_TYPE] = '系统用户';
  96. for (let userType of unref(userTypeListRef)) {
  97. result[userType.dictItemCode] = userType.dictItemName;
  98. }
  99. return result;
  100. });
  101. const accountLockedMessage = {
  102. LOGIN_FAIL_LOCKED: '多次登录失败锁定',
  103. LONG_TIME_LOCKED: '超出指定时间未登录锁定',
  104. LONG_TIME_PASSWORD_MODIFY_LOCKED: '超出指定时间未修改密码锁定',
  105. };
  106. const [registerSetRoleModal, { openModal: openSetRoleModal }] = useModal();
  107. const getLockedMessage = (status: string | null | undefined) => {
  108. if (!status || status === 'NORMAL') {
  109. return '正常';
  110. }
  111. return accountLockedMessage[status];
  112. };
  113. /**
  114. * 账户状态
  115. */
  116. const accountStatusMap = {
  117. empty: {
  118. label: '未创建',
  119. color: '#A9A9A9',
  120. },
  121. NORMAL: {
  122. label: '正常',
  123. color: '#228B22',
  124. },
  125. LOCKED: {
  126. label: '锁定',
  127. color: 'red',
  128. },
  129. };
  130. const getAccountData = (status: string | null | undefined) => {
  131. if (status === undefined || status === null) {
  132. return accountStatusMap.empty;
  133. }
  134. if (status === 'NORMAL') {
  135. return accountStatusMap.NORMAL;
  136. }
  137. return accountStatusMap.LOCKED;
  138. };
  139. /**
  140. * 权限处理
  141. */
  142. const permissions = SystemPermissions.user;
  143. const hasPermissionUpdateSystemUser = hasPermission('sys:systemUser:update');
  144. const hasSystemUserUpdate = (type: string) => {
  145. return hasPermissionUpdateSystemUser || type !== SYS_USER_TYPE;
  146. };
  147. /**
  148. * 选中组织架构操作
  149. * @param selectedKeys
  150. */
  151. const currentDeptId = ref<number | null>(null);
  152. const handleDeptSelected = (selectedKeys: Array<number>) => {
  153. if (selectedKeys.length > 0) {
  154. currentDeptId.value = selectedKeys[0];
  155. } else {
  156. currentDeptId.value = null;
  157. }
  158. // 重新加载数据
  159. query();
  160. };
  161. /**
  162. * 账户弹窗
  163. */
  164. const [registerAccountModal, { openModal }] = useModal();
  165. /**
  166. * table行按钮
  167. */
  168. const getTableActions = (row): ActionItem[] => {
  169. return [
  170. {
  171. label: t('common.button.edit'),
  172. onClick: () => editByRowModal(row),
  173. disabled: !hasPermission(permissions.update) || !hasSystemUserUpdate(row.userType),
  174. },
  175. {
  176. label: t('system.views.user.button.showAccount'),
  177. disabled: !hasPermission('sys:account:query') || !hasSystemUserUpdate(row.userType),
  178. onClick: () => openModal(true, row),
  179. },
  180. {
  181. label: t('system.views.user.button.unlockUserAccount'),
  182. auth: permissions.unlockUserAccount,
  183. disabled:
  184. !hasPermission(permissions.unlockUserAccount) ||
  185. (row.userAccount && row.userAccount.accountStatus === 'NORMAL'),
  186. onClick: () => handleUnlockUserAccount(row.userId),
  187. },
  188. ];
  189. };
  190. const handleUnlockUserAccount = (id: number) => {
  191. createConfirm({
  192. iconType: 'warning',
  193. content: t('system.views.user.message.confirmUnlockUserAccount'),
  194. onOk: async () => {
  195. await unlockUserAccountApi(id);
  196. successMessage(t('system.views.user.message.unlockUserAccountSuccess'));
  197. await query();
  198. },
  199. });
  200. };
  201. /**
  202. * 用户操作验证
  203. * @param userList
  204. */
  205. const validateOperateUser = (userList: Array<any>) => {
  206. if (userList.length === 0) {
  207. warnMessage({
  208. message: t('system.views.user.validate.selectUser'),
  209. });
  210. return false;
  211. }
  212. if (!hasPermissionUpdateSystemUser) {
  213. // 如果没有修改系统用户的权限,判断用户中是否有系统用户
  214. const hasSysUser = userList.some(({ userType }: any) => userType === SYS_USER_TYPE);
  215. if (hasSysUser) {
  216. errorMessage(t('system.views.user.validate.noSysUserUpdatePermission'));
  217. return false;
  218. }
  219. }
  220. return true;
  221. };
  222. /**
  223. * 创建账户
  224. */
  225. const handleCreateAccount = () => {
  226. const userList = getCheckboxRecords(false);
  227. if (userList.length === 0) {
  228. warnMessage({
  229. message: t('system.views.user.validate.selectUser'),
  230. });
  231. return false;
  232. }
  233. if (!hasPermissionUpdateSystemUser) {
  234. // 如果没有修改系统用户的权限,判断用户中是否有系统用户
  235. const hasSysUser = userList.some(({ userType }: any) => userType === SYS_USER_TYPE);
  236. if (hasSysUser) {
  237. errorMessage(t('system.views.user.validate.noSysUserUpdatePermission'));
  238. return false;
  239. }
  240. }
  241. // 判断是否有停用用户
  242. const hasNoUse = userList.some((item) => item.useYn === false);
  243. if (hasNoUse) {
  244. warnMessage(t('system.views.user.message.noUseUserNotCreateAccount'));
  245. return false;
  246. }
  247. createConfirm({
  248. iconType: 'warning',
  249. title: t('system.views.user.validate.createAccountConfirm'),
  250. onOk: () => createAccountApi(userList),
  251. });
  252. };
  253. const validateSelectRows = () => {
  254. const rows = getCheckboxRecords();
  255. if (!rows.length) {
  256. warnMessage(t('common.notice.select'));
  257. return false;
  258. }
  259. return rows;
  260. };
  261. const [
  262. registerTable,
  263. { editByRowModal, getCheckboxRecords, query, deleteByCheckbox, showAddModal, useYnByCheckbox },
  264. ] = useSmartTable({
  265. columns: getTableColumns(),
  266. stripe: true,
  267. height: 'auto',
  268. border: true,
  269. align: 'left',
  270. rowConfig: {
  271. isHover: true,
  272. },
  273. pagerConfig: true,
  274. useSearchForm: true,
  275. sortConfig: {
  276. remote: true,
  277. defaultSort: {
  278. field: 'seq',
  279. order: 'asc',
  280. },
  281. },
  282. searchFormConfig: {
  283. layout: 'inline',
  284. schemas: getSearchSchemas(t),
  285. colon: true,
  286. searchWithSymbol: true,
  287. actionColOptions: {
  288. span: undefined,
  289. },
  290. compact: true,
  291. },
  292. addEditConfig: {
  293. modalConfig: {
  294. width: '700px',
  295. },
  296. formConfig: {
  297. colon: true,
  298. schemas: getAddEditFormSchemas(t, userTypeListRef),
  299. labelCol: {
  300. span: 4,
  301. },
  302. wrapperCol: {
  303. span: 19,
  304. },
  305. baseColProps: {
  306. span: 24,
  307. },
  308. },
  309. },
  310. proxyConfig: {
  311. ajax: {
  312. query: ({ ajaxParameter }) => {
  313. const parameter = {
  314. ...ajaxParameter,
  315. };
  316. const deptId = unref(currentDeptId);
  317. if (deptId) {
  318. parameter.deptIdList = [deptId];
  319. }
  320. return listApi(parameter);
  321. },
  322. delete: deleteApi,
  323. save: saveUpdateWithDataScopeApi,
  324. getById: getByIdWithDataScopeApi,
  325. useYn: setUseYnApi,
  326. },
  327. },
  328. toolbarConfig: {
  329. refresh: true,
  330. resizable: true,
  331. buttons: [
  332. {
  333. code: 'ModalAdd',
  334. auth: permissions.add,
  335. props: {
  336. onClick: () => {
  337. showAddModal({ deptId: unref(currentDeptId) });
  338. },
  339. },
  340. },
  341. {
  342. name: t('system.views.user.button.createAccount'),
  343. customRender: 'ant',
  344. auth: permissions.createAccount,
  345. props: {
  346. onClick: () => handleCreateAccount(),
  347. type: 'primary',
  348. },
  349. },
  350. {
  351. code: 'delete',
  352. props: {
  353. onClick: () => {
  354. const userList = getCheckboxRecords(false);
  355. // 验证用户
  356. const result = validateOperateUser(userList);
  357. if (!result) {
  358. return false;
  359. }
  360. // 验证是否包含系统用户
  361. const sysUserValidate = userList.some((item: any) => item.userType === SYS_USER_TYPE);
  362. if (sysUserValidate) {
  363. errorMessage(t('system.views.user.validate.sysUserNoDelete'));
  364. return false;
  365. }
  366. // 执行删除操作
  367. deleteByCheckbox();
  368. },
  369. },
  370. },
  371. {
  372. code: 'useYnTrue',
  373. props: {
  374. onClick() {
  375. if (!unref(getIsPlatformTenant)) {
  376. useYnByCheckbox(true);
  377. } else {
  378. const rows = validateSelectRows();
  379. if (!rows) {
  380. return false;
  381. }
  382. openUseYnModal(true, { rows, useYn: true });
  383. }
  384. },
  385. },
  386. },
  387. {
  388. code: 'useYnFalse',
  389. props: {
  390. onClick() {
  391. if (!unref(getIsPlatformTenant)) {
  392. useYnByCheckbox(true);
  393. } else {
  394. const rows = validateSelectRows();
  395. if (!rows) {
  396. return false;
  397. }
  398. openUseYnModal(true, { rows, useYn: false });
  399. }
  400. },
  401. },
  402. },
  403. {
  404. name: t('system.views.user.button.resetPassword'),
  405. auth: permissions.unlockPassword,
  406. customRender: 'ant',
  407. props: {
  408. type: 'primary',
  409. preIcon: 'ant-design:unlock-outlined',
  410. onClick: () => {
  411. const selectRows = getCheckboxRecords(false);
  412. if (selectRows.length !== 1) {
  413. warnMessage('请选择一条数据');
  414. return;
  415. }
  416. createConfirm({
  417. iconType: 'warning',
  418. title: t('system.views.user.button.resetPassword'),
  419. content: t('system.views.user.validate.resetPassword'),
  420. onOk: async () => {
  421. const newPassword = await resetPassword(selectRows[0].userId);
  422. createConfirm({
  423. iconType: 'warning',
  424. okText: t('system.views.user.button.copyPassword'),
  425. onOk: () => {
  426. copyText(newPassword);
  427. },
  428. title: t('system.views.user.message.resetSavePassword'),
  429. content: newPassword,
  430. });
  431. },
  432. });
  433. },
  434. },
  435. },
  436. {
  437. name: t('system.views.user.button.setRole'),
  438. auth: permissions.setRole,
  439. customRender: 'ant',
  440. props: {
  441. type: 'primary',
  442. preIcon: 'ant-design:team-outlined',
  443. onClick: () => {
  444. const selectRows = getCheckboxRecords(false);
  445. if (selectRows.length !== 1) {
  446. warnMessage('请选择一条数据');
  447. return;
  448. }
  449. openSetRoleModal(true, { userId: selectRows[0].userId });
  450. },
  451. },
  452. },
  453. ],
  454. },
  455. });
  456. </script>
  457. <style scoped lang="less">
  458. .page-container {
  459. :deep(.smart-search-container) {
  460. .ant-col {
  461. //padding: 0 5px;
  462. }
  463. }
  464. }
  465. .dept-container {
  466. margin-right: 5px;
  467. padding: 10px;
  468. background: white;
  469. }
  470. </style>