CreateCharacter.vue 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. <script setup lang="ts">
  2. import { onMounted, ref, useTemplateRef, watch } from 'vue';
  3. import ConfirmModal from '@/components/ConfirmModal.vue';
  4. import SvgIcon from '@/components/SvgIcon.vue';
  5. import { useRequest } from '@/hooks/request';
  6. import { t } from '@/i18n';
  7. import {
  8. addRolePermissions,
  9. addUpdateRolePermissions,
  10. deleteCharacter,
  11. getFindRolesByOrgIds,
  12. getRolePermissions,
  13. getSubPermList,
  14. } from '@/api';
  15. import type { TreeProps } from 'ant-design-vue';
  16. import type { Rule } from 'ant-design-vue/es/form';
  17. import type { DataNode } from 'ant-design-vue/es/tree';
  18. import type { CharacterForm, CharacterPageItem, CreateCustomer, TreeStructure, UseGuideStepItemProps } from '@/types';
  19. const rules: Record<string, Rule[]> = {
  20. name: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
  21. };
  22. const { handleRequest } = useRequest();
  23. const modalComponentRef = useTemplateRef('modalComponent');
  24. const characterList = ref<CharacterPageItem[]>([]);
  25. const characterOpen = ref<boolean>(false);
  26. const characterTitle = ref<boolean>(true);
  27. const checked = ref<boolean>(false);
  28. const checkedAll = ref<number[]>([]);
  29. const indeterminate = ref<boolean>(false);
  30. const characterId = ref<number>(0);
  31. const expandedKeys = ref<number[]>([]);
  32. const selectedKeys = ref<number[]>([]);
  33. const checkedKeys = ref<number[]>([]);
  34. const fieldNames: TreeProps['fieldNames'] = {
  35. children: 'subPermissions',
  36. title: 'menuName',
  37. key: 'id',
  38. };
  39. const props = defineProps<UseGuideStepItemProps<CreateCustomer>>();
  40. const treeStructure = ref<DataNode[]>([]);
  41. const characterForm = ref<CharacterForm>({
  42. roleName: '',
  43. remark: '',
  44. });
  45. const handleClose = () => {
  46. characterForm.value.roleName = '';
  47. characterForm.value.remark = '';
  48. checkedKeys.value = [];
  49. indeterminate.value = false;
  50. checked.value = false;
  51. expandedKeys.value = [];
  52. };
  53. const addCharacter = () => {
  54. characterTitle.value = true;
  55. characterOpen.value = true;
  56. };
  57. const characterSave = () => {
  58. handleRequest(async () => {
  59. if (characterTitle.value) {
  60. await addRolePermissions({
  61. ...characterForm.value,
  62. orgId: props.form.id as number,
  63. enabled: '1',
  64. permissionIds: checkedKeys.value,
  65. });
  66. } else {
  67. await addUpdateRolePermissions({
  68. id: characterId.value,
  69. ...characterForm.value,
  70. orgId: props.form.id as number,
  71. enabled: '1',
  72. permissionIds: checkedKeys.value,
  73. });
  74. }
  75. getFindRolesByOrg();
  76. characterOpen.value = false;
  77. });
  78. };
  79. const cancelSave = () => {
  80. checkedKeys.value = [];
  81. characterOpen.value = false;
  82. };
  83. const confirm = () => {
  84. handleRequest(async () => {
  85. await deleteCharacter(characterId.value);
  86. getFindRolesByOrg();
  87. modalComponentRef.value?.hideView();
  88. });
  89. };
  90. const characterEditor = (id: number) => {
  91. characterId.value = id;
  92. characterTitle.value = false;
  93. characterOpen.value = true;
  94. handleRequest(async () => {
  95. const { remark, roleName, permissionCheckTreeVos } = await getRolePermissions(id);
  96. checkedKeys.value = [];
  97. characterForm.value.roleName = roleName;
  98. characterForm.value.remark = remark;
  99. permissionCheckTreeVos[0].subPermissions[0].subPermissions.forEach((item) => {
  100. if (item.subPermissions) {
  101. item.subPermissions.forEach((i) => {
  102. if (i.checked === 1) {
  103. checkedKeys.value.push(i.id);
  104. }
  105. });
  106. expandedKeys.value.push(item.id);
  107. } else {
  108. if (item.checked === 1) {
  109. checkedKeys.value.push(item.id);
  110. }
  111. }
  112. });
  113. });
  114. };
  115. const characterDelete = (id: number) => {
  116. characterId.value = id;
  117. modalComponentRef.value?.showView();
  118. };
  119. const selectAll = () => {
  120. indeterminate.value = false;
  121. if (checked.value) {
  122. checkedKeys.value = checkedAll.value;
  123. } else {
  124. checkedKeys.value = [];
  125. }
  126. };
  127. // 获取所有节点 key 的递归方法
  128. const getAllKeys = (data: DataNode[]) => {
  129. let keys: number[] = [];
  130. data.forEach((item) => {
  131. keys.push(item.id);
  132. expandedKeys.value.push(item.id);
  133. if (item.subPermissions) {
  134. keys = keys.concat(getAllKeys(item.subPermissions));
  135. }
  136. });
  137. return keys;
  138. };
  139. const transformTreeData = (data: TreeStructure[]): DataNode[] => {
  140. return data.map((item) => ({
  141. ...item,
  142. key: item.id, // 关键:将 id 映射到 key
  143. title: item.menuName,
  144. children: item.subPermissions ? transformTreeData(item.subPermissions) : undefined,
  145. }));
  146. };
  147. const getFindRolesByOrg = () => {
  148. handleRequest(async () => {
  149. if (props.form.id) {
  150. characterList.value = [];
  151. const list = await getFindRolesByOrgIds([props.form.id]);
  152. list.forEach((item) => {
  153. if (item.roleName !== '管理员' && item.roleName !== '工程师') {
  154. characterList.value.push(item);
  155. }
  156. });
  157. }
  158. });
  159. };
  160. watch(
  161. () => checkedKeys.value,
  162. (count) => {
  163. if (count) {
  164. indeterminate.value = !!count.length && count.length < checkedAll.value.length;
  165. checked.value = count.length === checkedAll.value.length;
  166. }
  167. },
  168. );
  169. onMounted(() => {
  170. handleRequest(async () => {
  171. getFindRolesByOrg();
  172. const data = await getSubPermList(0);
  173. treeStructure.value = transformTreeData(data[0].subPermissions[0].subPermissions);
  174. checkedAll.value = getAllKeys(treeStructure.value);
  175. });
  176. });
  177. </script>
  178. <template>
  179. <div>
  180. <div class="character"><span class="character-text">*</span>创建角色</div>
  181. <AFlex :vertical="true" :gap="16">
  182. <AFlex align="center">
  183. <AFlex class="input-style" align="center"> 管理员 </AFlex>
  184. <div class="input-style-text">默认角色</div>
  185. </AFlex>
  186. <AFlex align="center">
  187. <AFlex class="input-style" align="center"> 运维人员 </AFlex>
  188. <div class="input-style-text">默认角色</div>
  189. </AFlex>
  190. <AFlex align="center" v-for="(item, index) in characterList" :key="index">
  191. <AFlex class="input-style input-background" align="center"> {{ item.roleName }} </AFlex>
  192. <div @click="characterEditor(item.id)">
  193. <AFlex class="editorial-role" align="center" justify="center">
  194. <SvgIcon name="edit-o" />
  195. </AFlex>
  196. </div>
  197. <div @click="characterDelete(item.id)">
  198. <AFlex class="editorial-role" align="center" justify="center">
  199. <SvgIcon class="icon-color" name="delete" />
  200. </AFlex>
  201. </div>
  202. </AFlex>
  203. </AFlex>
  204. <AButton type="primary" ghost class="icon-button button-style" @click="addCharacter">
  205. <AFlex align="center">
  206. <SvgIcon name="plus" />
  207. <span> 增加角色 </span>
  208. </AFlex>
  209. </AButton>
  210. <AModal
  211. v-model:open="characterOpen"
  212. :title="characterTitle ? '添加角色' : '编辑角色'"
  213. width="920px"
  214. :mask-closable="false"
  215. :footer="null"
  216. :after-close="handleClose"
  217. >
  218. <AForm
  219. ref="formRef"
  220. class="alarm-modal"
  221. :model="characterForm"
  222. label-align="left"
  223. :rules="rules"
  224. :label-col="{ span: 3 }"
  225. >
  226. <AFormItem label="角色名称" name="roleName">
  227. <AInput class="input-width" v-model:value="characterForm.roleName" placeholder="请输入角色名称" />
  228. </AFormItem>
  229. <AFormItem label="角色描述">
  230. <ATextarea
  231. class="input-width"
  232. v-model:value="characterForm.remark"
  233. :placeholder="t('common.pleaseEnter')"
  234. :auto-size="{ minRows: 4 }"
  235. />
  236. </AFormItem>
  237. <AFormItem label="菜单权限配置">
  238. <div class="permission-configuration">
  239. <ACheckbox class="select-all" :indeterminate="indeterminate" v-model:checked="checked" @change="selectAll"
  240. >全选</ACheckbox
  241. >
  242. <ATree
  243. v-model:expanded-keys="expandedKeys"
  244. v-model:selected-keys="selectedKeys"
  245. v-model:checked-keys="checkedKeys"
  246. :tree-data="treeStructure"
  247. checkable
  248. :field-names="fieldNames"
  249. />
  250. </div>
  251. </AFormItem>
  252. </AForm>
  253. <AFlex justify="flex-end" class="footer">
  254. <AButton class="button-right" type="primary" ghost @click="cancelSave">{{ $t('common.cancel') }}</AButton>
  255. <AButton type="primary" @click="characterSave">保存</AButton>
  256. </AFlex>
  257. </AModal>
  258. <ConfirmModal
  259. ref="modalComponent"
  260. :title="t('common.deleteConfirmation')"
  261. :description-text="t('common.confirmDeletion')"
  262. :icon="{ name: 'delete' }"
  263. :icon-bg-color="'#F56C6C'"
  264. @confirm="confirm"
  265. />
  266. </div>
  267. </template>
  268. <style lang="scss" scoped>
  269. .select-all {
  270. margin-bottom: 12px;
  271. margin-left: 10px;
  272. font-size: 14px;
  273. font-style: normal;
  274. font-weight: 400;
  275. line-height: 24px;
  276. color: #000;
  277. text-align: left;
  278. }
  279. .permission-configuration {
  280. width: 772px;
  281. height: 294px;
  282. padding: 16px 14px 24px;
  283. overflow: auto;
  284. background: #fff;
  285. border: 1px solid #d9d9d9;
  286. border-radius: 4px;
  287. }
  288. .input-width {
  289. width: 328px;
  290. }
  291. .button-right {
  292. margin-right: 16px;
  293. }
  294. .button-style {
  295. margin-top: 24px;
  296. }
  297. .icon-color {
  298. color: #f67f7f;
  299. }
  300. .editorial-role {
  301. width: 32px;
  302. height: 32px;
  303. margin-right: 12px;
  304. cursor: pointer;
  305. background: #fff;
  306. border: 1px solid #d9d9d9;
  307. border-radius: 4px;
  308. }
  309. .character {
  310. margin-bottom: 16px;
  311. color: rgb(0 0 0 / 85%);
  312. }
  313. .input-style-text {
  314. font-size: 12px;
  315. font-style: normal;
  316. font-weight: 400;
  317. line-height: 22px;
  318. color: #666;
  319. text-align: left;
  320. }
  321. .input-style {
  322. width: 256px;
  323. height: 32px;
  324. padding-left: 12px;
  325. margin-right: 16px;
  326. font-size: 14px;
  327. font-style: normal;
  328. font-weight: 400;
  329. line-height: 22px;
  330. color: #333;
  331. text-align: left;
  332. background: #f5f7fa;
  333. border: 1px solid rgb(0 0 0 / 15%);
  334. border-radius: 4px;
  335. }
  336. .input-background {
  337. background: #fff;
  338. }
  339. .character-text {
  340. font-size: 14px;
  341. font-style: normal;
  342. font-weight: 400;
  343. line-height: 22px;
  344. color: #e02020;
  345. text-align: left;
  346. }
  347. </style>