UserManage.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. <script setup lang="ts">
  2. import { ref, useTemplateRef } from 'vue';
  3. import { message } from 'ant-design-vue';
  4. import dayjs, { Dayjs } from 'dayjs';
  5. import ConfirmModal from '@/components/ConfirmModal.vue';
  6. import OrganizationalStructure from '@/components/OrganizationalStructure.vue';
  7. import { useRequest } from '@/hooks/request';
  8. import { t } from '@/i18n';
  9. import { addAccount, batchDeleteAccount, getFindRolesByOrgIds, getUserPageList, updateAccount } from '@/api';
  10. import type { FormInstance, Rule } from 'ant-design-vue/es/form';
  11. import type { Key } from 'ant-design-vue/es/table/interface';
  12. import type { AccountForm, CharacterPageItem, UserPageItem, UserPageParams } from '@/types';
  13. const modalComponentRef = useTemplateRef('modalComponent');
  14. const { handleRequest } = useRequest();
  15. const accountKeys = ref<Key[]>([]);
  16. const searchContent = ref<string>('');
  17. const accountTerm = ref<[Dayjs, Dayjs]>();
  18. const orgId = ref<number>();
  19. const formRef = ref<FormInstance>();
  20. const titleAccount = ref<boolean>(true);
  21. const accountId = ref<number>(0);
  22. const accountPageParam = ref<UserPageParams>({
  23. pageIndex: 1,
  24. pageSize: 10,
  25. userName: '',
  26. mobile: '',
  27. startTenancy: '',
  28. endTenancy: '',
  29. enabled: undefined,
  30. orgId: undefined,
  31. roleId: undefined,
  32. });
  33. const accountTotal = ref<number>();
  34. const accountOpen = ref<boolean>(false);
  35. const characterPageList = ref<CharacterPageItem[]>([]);
  36. const accountForm = ref<AccountForm>({
  37. userName: '',
  38. mobile: '',
  39. password: '',
  40. enabled: true,
  41. roleId: undefined,
  42. accountTerm: undefined,
  43. });
  44. const accountList = ref<UserPageItem[]>([]);
  45. const accountColumns = [
  46. {
  47. title: '手机号',
  48. dataIndex: 'mobile',
  49. key: 'mobile',
  50. ellipsis: true,
  51. },
  52. {
  53. title: '姓名',
  54. dataIndex: 'userName',
  55. key: 'userName',
  56. ellipsis: true,
  57. },
  58. {
  59. title: '角色',
  60. dataIndex: 'roleName',
  61. key: 'roleName',
  62. ellipsis: true,
  63. },
  64. {
  65. title: '创建日期',
  66. dataIndex: 'startTenancy',
  67. key: 'startTenancy',
  68. ellipsis: true,
  69. },
  70. {
  71. title: '到期日期',
  72. dataIndex: 'endTenancy',
  73. key: 'endTenancy',
  74. ellipsis: true,
  75. },
  76. {
  77. title: '状态',
  78. dataIndex: 'enabled',
  79. key: 'enabled',
  80. ellipsis: true,
  81. },
  82. ];
  83. const rules: Record<string, Rule[]> = {
  84. userName: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
  85. mobile: [
  86. { required: true, message: t('common.cannotEmpty'), trigger: 'change' },
  87. {
  88. validator: (_rule: unknown, value: string) => {
  89. if (!isValidPhone(value)) {
  90. return Promise.reject('手机号格式错误');
  91. }
  92. return Promise.resolve();
  93. },
  94. },
  95. ],
  96. password: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
  97. enabled: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
  98. roleId: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
  99. accountTerm: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
  100. };
  101. // 手机号校验函数
  102. const isValidPhone = (phone: string): boolean => {
  103. return /^1[3-9]\d{9}$/.test(phone);
  104. };
  105. const accountChange = (selectedRowKeys: Key[]) => {
  106. accountKeys.value = selectedRowKeys;
  107. };
  108. const confirm = () => {
  109. handleRequest(async () => {
  110. await batchDeleteAccount(accountKeys.value as number[]);
  111. getUserList();
  112. modalComponentRef.value?.hideView();
  113. });
  114. };
  115. const addDelete = () => {
  116. if (accountKeys.value.length === 0) {
  117. return message.warning(t('deviceList.pleaseSelectItemDelete'));
  118. }
  119. modalComponentRef.value?.showView();
  120. };
  121. const addOpenAccount = () => {
  122. accountOpen.value = true;
  123. titleAccount.value = true;
  124. };
  125. const addCheck = (item: UserPageItem) => {
  126. accountOpen.value = true;
  127. titleAccount.value = false;
  128. const { userName, mobile, roleId, enabled, startTenancy, endTenancy, id } = item;
  129. accountId.value = id;
  130. Object.assign(accountForm.value, {
  131. userName,
  132. mobile,
  133. roleId,
  134. password: '',
  135. enabled: enabled === 1 ? true : false,
  136. accountTerm: [dayjs(startTenancy, 'YYYY-MM-DD'), dayjs(endTenancy, 'YYYY-MM-DD')],
  137. });
  138. };
  139. const addQuery = () => {
  140. if (accountTerm.value) {
  141. accountPageParam.value.startTenancy = accountTerm.value[0].format('YYYY-MM-DD');
  142. accountPageParam.value.endTenancy = accountTerm.value[1].format('YYYY-MM-DD');
  143. } else {
  144. accountPageParam.value.startTenancy = '';
  145. accountPageParam.value.endTenancy = '';
  146. }
  147. console.log(searchContent.value);
  148. accountPageParam.value.mobile = searchContent.value;
  149. accountPageParam.value.userName = searchContent.value;
  150. accountPageParam.value.pageIndex = 1;
  151. getUserList();
  152. };
  153. const resetItem = () => {
  154. searchContent.value = '';
  155. accountTerm.value = undefined;
  156. accountPageParam.value.startTenancy = '';
  157. accountPageParam.value.endTenancy = '';
  158. accountPageParam.value.pageIndex = 1;
  159. accountPageParam.value.enabled = undefined;
  160. accountPageParam.value.roleId = undefined;
  161. getUserList();
  162. };
  163. const handleClose = () => {
  164. accountForm.value = {
  165. userName: '',
  166. mobile: '',
  167. password: '',
  168. enabled: true,
  169. roleId: undefined,
  170. accountTerm: undefined,
  171. };
  172. };
  173. const addReset = () => {
  174. resetItem();
  175. };
  176. const switchPages = () => {};
  177. const bindingAccount = () => {
  178. formRef.value
  179. ?.validate()
  180. .then(() => {
  181. handleRequest(async () => {
  182. const { userName, mobile, password, enabled, roleId, accountTerm } = accountForm.value;
  183. if (titleAccount.value) {
  184. await addAccount({
  185. userName,
  186. mobile,
  187. password,
  188. roleId: roleId as number,
  189. enabled: enabled ? '1' : '0',
  190. startTenancy: accountTerm![0].format('YYYY-MM-DD'),
  191. endTenancy: accountTerm![1].format('YYYY-MM-DD'),
  192. orgId: orgId.value as number,
  193. });
  194. } else {
  195. await updateAccount({
  196. id: accountId.value,
  197. userName,
  198. mobile,
  199. password,
  200. roleId: roleId as number,
  201. enabled: enabled ? '1' : '0',
  202. startTenancy: accountTerm![0].format('YYYY-MM-DD'),
  203. endTenancy: accountTerm![1].format('YYYY-MM-DD'),
  204. orgId: orgId.value as number,
  205. });
  206. }
  207. getUserList();
  208. accountOpen.value = false;
  209. });
  210. })
  211. .catch(() => {});
  212. };
  213. const clickOrganizationChange = (id: number) => {
  214. orgId.value = id;
  215. searchContent.value = '';
  216. accountTerm.value = undefined;
  217. accountPageParam.value.startTenancy = '';
  218. accountPageParam.value.endTenancy = '';
  219. handleRequest(async () => {
  220. characterPageList.value = await getFindRolesByOrgIds([id]);
  221. getUserList();
  222. });
  223. };
  224. const getUserList = () => {
  225. handleRequest(async () => {
  226. accountPageParam.value.orgId = orgId.value;
  227. accountPageParam.value.mobile = searchContent.value;
  228. accountPageParam.value.userName = searchContent.value;
  229. const { records, total } = await getUserPageList(accountPageParam.value);
  230. accountList.value = records;
  231. accountTotal.value = total;
  232. });
  233. };
  234. </script>
  235. <template>
  236. <div>
  237. <AFlex justify="space-between" class="account-header">
  238. <div class="text-top">账号管理</div>
  239. <div>
  240. <AButton class="icon-button default-button" @click="addDelete">
  241. <AFlex align="center">
  242. <SvgIcon name="delete" />
  243. <span>删除</span>
  244. </AFlex>
  245. </AButton>
  246. <AButton type="primary" class="icon-button button-monitoring" @click="addOpenAccount">
  247. <AFlex align="center">
  248. <SvgIcon name="plus" />
  249. <span> 添加 </span>
  250. </AFlex>
  251. </AButton>
  252. </div>
  253. </AFlex>
  254. <AFlex>
  255. <OrganizationalStructure @change="clickOrganizationChange" />
  256. <div class="account-content">
  257. <AFlex wrap="wrap" justify="space-between">
  258. <AFlex align="center" class="margin-bottom">
  259. <div class="account-content-text">搜索</div>
  260. <AInput v-model:value="searchContent" class="input-width" placeholder="请输入手机号、姓名" />
  261. </AFlex>
  262. <AFlex align="center" class="margin-bottom">
  263. <div class="account-content-text">角色</div>
  264. <ASelect
  265. class="input-width"
  266. v-model:value="accountPageParam.roleId"
  267. :options="characterPageList"
  268. :field-names="{ label: 'roleName', value: 'id' }"
  269. :placeholder="t('common.plzSelect')"
  270. :allow-clear="true"
  271. />
  272. </AFlex>
  273. <AFlex align="center" class="margin-bottom">
  274. <div class="account-content-text">创建日期</div>
  275. <ARangePicker
  276. v-model:value="accountTerm"
  277. class="input-width"
  278. :allow-clear="true"
  279. :separator="$t('common.to')"
  280. />
  281. </AFlex>
  282. <AFlex align="center" class="margin-bottom">
  283. <div class="account-content-text">状态</div>
  284. <ASelect
  285. class="input-width"
  286. v-model:value="accountPageParam.enabled"
  287. :placeholder="$t('common.plzSelect')"
  288. :allow-clear="true"
  289. >
  290. <ASelectOption value="1">正常</ASelectOption>
  291. <ASelectOption value="0">停用</ASelectOption>
  292. </ASelect>
  293. </AFlex>
  294. </AFlex>
  295. <AFlex class="margin-bottom" justify="flex-end">
  296. <AButton type="primary" @click="addQuery"> 查询 </AButton>
  297. <AButton class="default-button margin-left" @click="addReset"> 重置 </AButton>
  298. </AFlex>
  299. <ATable
  300. :row-selection="{
  301. type: 'checkbox',
  302. selectedRowKeys: accountKeys,
  303. onChange: accountChange,
  304. }"
  305. row-key="id"
  306. :columns="accountColumns"
  307. :data-source="accountList"
  308. :pagination="false"
  309. >
  310. <template #bodyCell="{ column, record }">
  311. <template v-if="column.key === 'mobile'">
  312. <div @click="addCheck(record as UserPageItem)" class="mobile-phone">{{ record.mobile }}</div>
  313. </template>
  314. <template v-if="column.key === 'enabled'">
  315. <div v-if="record.enabled == 1" class="tag-style success">启用中</div>
  316. <div v-else class="tag-style default">停用</div>
  317. </template>
  318. </template>
  319. </ATable>
  320. <br />
  321. <br />
  322. <AFlex justify="flex-end" class="footer">
  323. <APagination
  324. v-model:current="accountPageParam.pageIndex"
  325. v-model:page-size="accountPageParam.pageSize"
  326. :total="accountTotal"
  327. :show-size-changer="true"
  328. @change="switchPages"
  329. show-quick-jumper
  330. :show-total="(total) => $t('common.pageTotal', { total })"
  331. />
  332. </AFlex>
  333. </div>
  334. </AFlex>
  335. <AModal
  336. v-model:open="accountOpen"
  337. :title="titleAccount ? '添加' : '编辑'"
  338. width="460px"
  339. :footer="null"
  340. :mask-closable="false"
  341. :keyboard="false"
  342. :after-close="handleClose"
  343. >
  344. <AForm
  345. ref="formRef"
  346. :colon="false"
  347. label-align="left"
  348. :model="accountForm"
  349. :rules="rules"
  350. :label-col="{ span: 5 }"
  351. class="form-style"
  352. >
  353. <AFlex :vertical="true">
  354. <AFormItem label="姓名" name="userName">
  355. <AInput class="input-width" v-model:value="accountForm.userName" placeholder="请输入" />
  356. </AFormItem>
  357. <AFormItem label="手机号" name="mobile">
  358. <AInputNumber class="input-width" v-model:value="accountForm.mobile" placeholder="请输入" />
  359. </AFormItem>
  360. <AFormItem label="角色" name="roleId">
  361. <ASelect
  362. class="input-width"
  363. v-model:value="accountForm.roleId"
  364. :options="characterPageList"
  365. :field-names="{ label: 'roleName', value: 'id' }"
  366. :placeholder="t('common.plzSelect')"
  367. />
  368. </AFormItem>
  369. <AFormItem label="到期日期" name="accountTerm">
  370. <ARangePicker
  371. v-model:value="accountForm.accountTerm"
  372. class="input-width"
  373. :allow-clear="false"
  374. :separator="$t('common.to')"
  375. />
  376. </AFormItem>
  377. <AFormItem label="密码" name="password">
  378. <AInput class="input-width" v-model:value="accountForm.password" placeholder="默认密码8个0" />
  379. </AFormItem>
  380. <AFormItem label="启用" name="enabled">
  381. <ASwitch v-model:checked="accountForm.enabled" />
  382. </AFormItem>
  383. </AFlex>
  384. </AForm>
  385. <AFlex justify="flex-end">
  386. <AButton class="button-style" type="primary" ghost @click="accountOpen = false">{{
  387. $t('common.cancel')
  388. }}</AButton>
  389. <AButton class="button-style" type="primary" @click="bindingAccount">{{ $t('common.save') }}</AButton>
  390. </AFlex>
  391. </AModal>
  392. <ConfirmModal
  393. ref="modalComponent"
  394. :title="t('common.deleteConfirmation')"
  395. :description-text="t('common.confirmDeletion')"
  396. :icon="{ name: 'delete' }"
  397. :icon-bg-color="'#F56C6C'"
  398. @confirm="confirm"
  399. />
  400. </div>
  401. </template>
  402. <style lang="scss" scoped>
  403. .check-div {
  404. margin-top: 24px;
  405. font-size: 14px;
  406. font-style: normal;
  407. font-weight: 400;
  408. line-height: 22px;
  409. color: #666;
  410. text-align: justify;
  411. }
  412. .check-text {
  413. color: #333;
  414. }
  415. .check-div > div {
  416. margin-bottom: 16px;
  417. }
  418. .check-div .margin-top {
  419. margin-bottom: 24px;
  420. }
  421. .mobile-phone {
  422. color: var(--antd-color-primary-hover);
  423. cursor: pointer;
  424. }
  425. .form-style {
  426. margin-top: 24px;
  427. }
  428. .button-style {
  429. width: 76px;
  430. margin-left: 16px;
  431. }
  432. .default {
  433. width: 40px;
  434. color: #666;
  435. background: #f8f8f8;
  436. border: 1px solid #e5e5e5;
  437. }
  438. .success {
  439. width: 52px;
  440. color: #52c41a;
  441. background: #f6ffed;
  442. border: 1px solid #b7eb8f;
  443. }
  444. .tag-style {
  445. display: flex;
  446. align-items: center; /* 垂直居中 */
  447. justify-content: center; /* 水平居中 */
  448. height: 24px;
  449. font-size: 12px;
  450. border-radius: 4px;
  451. }
  452. .margin-left {
  453. margin-left: 12px;
  454. }
  455. .margin-bottom {
  456. margin-bottom: 16px;
  457. }
  458. .input-width {
  459. width: 256px;
  460. }
  461. .account-content-text {
  462. width: 56px;
  463. margin-right: 12px;
  464. font-size: 14px;
  465. font-style: normal;
  466. font-weight: 400;
  467. line-height: 22px;
  468. color: rgb(0 0 0 / 85%);
  469. text-align: right;
  470. }
  471. .account-content {
  472. width: 100%;
  473. height: calc(100vh - 80px);
  474. padding: 24px;
  475. background: #fff;
  476. border-radius: 16px;
  477. }
  478. .account-header {
  479. margin-bottom: 16px;
  480. }
  481. .button-monitoring {
  482. margin-left: 16px;
  483. }
  484. .text-top {
  485. font-size: 20px;
  486. font-style: normal;
  487. font-weight: 500;
  488. line-height: 32px;
  489. color: rgb(0 0 0 / 85%);
  490. text-align: left;
  491. }
  492. </style>