Settings.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. <template>
  2. <div>
  3. <template v-if="ready">
  4. <el-tabs
  5. :value="active"
  6. class="c-tabs has-bottom-padding"
  7. @tab-click="onTabClick"
  8. >
  9. <el-tab-pane
  10. label="凭证"
  11. name="Credentials"
  12. />
  13. <el-tab-pane
  14. label="分组"
  15. name="Group"
  16. />
  17. <el-tab-pane
  18. label="角色"
  19. name="Role"
  20. />
  21. </el-tabs>
  22. <template v-if="active === 'Credentials'">
  23. <div class="c-grid-form">
  24. <label class="c-grid-form__label c-grid-form__auto u-bold">启用</label>
  25. <div class="l-flex--row">
  26. <div
  27. class="c-switch"
  28. :class="{ checked: user.enabled }"
  29. @click="onToggleEnable"
  30. >
  31. <span class="c-switch__inner">
  32. <span class="c-switch__active">ON</span>
  33. <span class="c-switch__inactive">OFF</span>
  34. </span>
  35. <span class="c-switch__switch" />
  36. </div>
  37. </div>
  38. <template v-if="credentials && credentials.otp">
  39. <label class="c-grid-form__label c-grid-form__auto u-bold">OTP</label>
  40. <div>
  41. <el-button
  42. type="danger"
  43. size="small"
  44. @click="onResetOTP"
  45. >
  46. 重置
  47. </el-button>
  48. </div>
  49. </template>
  50. <label class="c-grid-form__label c-grid-form__auto u-bold">密码</label>
  51. <div>
  52. <el-button
  53. type="danger"
  54. size="small"
  55. @click="onResetPassword"
  56. >
  57. 重置
  58. </el-button>
  59. </div>
  60. <label />
  61. <div class="has-top-padding">
  62. <el-button
  63. type="danger"
  64. size="small"
  65. @click="onDel"
  66. >
  67. 注销
  68. </el-button>
  69. </div>
  70. </div>
  71. </template>
  72. <template v-if="active === 'Group'">
  73. <el-tree
  74. ref="tree"
  75. class="l-flex__auto"
  76. :data="userGroups"
  77. node-key="path"
  78. :props="treeProps"
  79. :expand-on-click-node="false"
  80. accordion
  81. highlight-current
  82. @node-click="onGroupClick"
  83. />
  84. <div class="l-flex__none has-top-padding">
  85. <el-button
  86. type="danger"
  87. size="small"
  88. :disabled="!groupChanged"
  89. @click="onSaveGroup"
  90. >
  91. 保存
  92. </el-button>
  93. </div>
  94. </template>
  95. <template v-if="active === 'Role'">
  96. <el-transfer
  97. v-model="role.roles"
  98. class="c-transfer"
  99. :titles="['可选', '已分配']"
  100. :props="{ key: 'id', label: 'description' }"
  101. :data="role.available"
  102. />
  103. <div class="l-flex__none has-top-padding">
  104. <el-button
  105. type="danger"
  106. size="small"
  107. :disabled="!roleChanged"
  108. @click="onSaveRole"
  109. >
  110. 保存
  111. </el-button>
  112. </div>
  113. </template>
  114. </template>
  115. <div
  116. v-else
  117. class="u-text-center"
  118. >
  119. <div
  120. v-if="ignore"
  121. class="u-bold"
  122. >
  123. 您暂无设置该账号的权限
  124. </div>
  125. <warning
  126. v-if="error"
  127. @click="getSettings"
  128. />
  129. </div>
  130. </div>
  131. </template>
  132. <script>
  133. import { mapGetters } from 'vuex'
  134. import {
  135. updateUser,
  136. deleteUser,
  137. getUserGroups,
  138. getUserCredentials,
  139. resetPassword,
  140. deleteUserCredentials,
  141. moveUserGroup,
  142. getUserRoleMapping,
  143. updateUserRoles
  144. } from '@/api/user'
  145. import { Role } from '@/constant'
  146. export default {
  147. name: 'UserSettings',
  148. props: {
  149. user: {
  150. type: Object,
  151. required: true
  152. },
  153. groups: {
  154. type: Array,
  155. required: true
  156. }
  157. },
  158. data () {
  159. return {
  160. ready: false,
  161. ignore: false,
  162. error: false,
  163. active: 'Credentials',
  164. credentials: null,
  165. group: null,
  166. userGroups: null,
  167. groupChanged: false,
  168. role: null,
  169. treeProps: { children: 'subGroups', label: 'label' }
  170. }
  171. },
  172. computed: {
  173. ...mapGetters(['isSuperAdmin', 'isTenantAdmin']),
  174. roleChanged () {
  175. if (this.role) {
  176. const { stash, roles } = this.role
  177. if (stash.length !== roles.length) {
  178. return true
  179. }
  180. if (stash.length !== new Set([...roles, ...stash]).size) {
  181. return true
  182. }
  183. }
  184. return false
  185. }
  186. },
  187. mounted () {
  188. this.$nextTick(() => {
  189. this.getSettings()
  190. })
  191. },
  192. methods: {
  193. async getSettings () {
  194. const loading = this.$showLoading()
  195. this.ready = false
  196. this.error = false
  197. try {
  198. if (!this.role) {
  199. const { roles, available } = await getUserRoleMapping(this.user.id)
  200. const superAdmin = roles.find(({ name }) => name === Role.SUPER_ADMIN)
  201. if (superAdmin && !this.isSuperAdmin) {
  202. this.ignore = true
  203. this.$closeLoading(loading)
  204. return
  205. }
  206. const tenantAdmin = roles.find(({ name }) => name === Role.ADMIN)
  207. if (tenantAdmin && !this.isSuperAdmin) {
  208. this.ignore = true
  209. this.$closeLoading(loading)
  210. return
  211. }
  212. const roleKeys = roles.map(({ id }) => id)
  213. this.role = {
  214. available,
  215. roles: roleKeys,
  216. stash: [...roleKeys]
  217. }
  218. }
  219. if (!this.credentials) {
  220. const credentials = await getUserCredentials(this.user)
  221. this.credentials = {
  222. otp: credentials.find(({ type }) => type === 'otp')
  223. }
  224. }
  225. if (!this.group) {
  226. const groups = await getUserGroups(this.user.id)
  227. this.group = this.findLastGroup(groups)
  228. this.userGroups = this.getRootGroups(this.group)
  229. }
  230. this.ready = true
  231. } catch (e) {
  232. console.log(e)
  233. this.error = true
  234. }
  235. this.$closeLoading(loading)
  236. },
  237. onTabClick (tab) {
  238. if (this.active !== tab.name) {
  239. switch (tab.name) {
  240. case 'Group':
  241. this.expandTree()
  242. break
  243. default:
  244. break
  245. }
  246. this.active = tab.name
  247. }
  248. },
  249. onToggleEnable () {
  250. const enabled = !this.user.enabled
  251. updateUser(
  252. this.user.id,
  253. { enabled },
  254. enabled ? '启用' : '禁用'
  255. ).then(() => {
  256. this.user.enabled = enabled
  257. })
  258. },
  259. onResetPassword () {
  260. this.$confirm(
  261. `将${this.user.username}的密码重置为默认密码?`,
  262. { type: 'warning' }
  263. ).then(() => {
  264. resetPassword(this.user)
  265. })
  266. },
  267. onResetOTP () {
  268. this.$confirm(
  269. `将${this.user.username}当前绑定的OTP重置?`,
  270. { type: 'warning' }
  271. ).then(() => {
  272. deleteUserCredentials(this.user.id, this.credentials.otp.id).then(() => {
  273. this.credentials.otp = null
  274. })
  275. })
  276. },
  277. onDel () {
  278. this.$confirm(
  279. `注销账号${this.user.username}?`,
  280. { type: 'warning' }
  281. ).then(() => {
  282. const loading = this.$showLoading()
  283. deleteUser(this.user)
  284. .then(() => this.$emit('del'))
  285. .finally(() => {
  286. this.$closeLoading(loading)
  287. })
  288. })
  289. },
  290. onGroupClick (group) {
  291. this.$group = group
  292. this.groupChanged = this.$group.path !== this.group.path
  293. },
  294. expandTree () {
  295. this.$nextTick(() => {
  296. this.$refs.tree.setCurrentKey(this.group.path)
  297. let parentGroup = this.group.parentGroup
  298. while (parentGroup) {
  299. this.$refs.tree.getNode(parentGroup.path).expanded = true
  300. parentGroup = parentGroup.parentGroup
  301. }
  302. })
  303. },
  304. getRootGroups (group) {
  305. let target = group
  306. while (target.parentGroup) {
  307. target = target.parentGroup
  308. }
  309. return [target]
  310. },
  311. findLastGroup (groups) {
  312. let result = { subGroups: this.groups }
  313. groups.forEach(({ path }) => {
  314. if (result) {
  315. result = result.subGroups.find(group => group.path === path)
  316. }
  317. })
  318. return result
  319. },
  320. onSaveGroup () {
  321. if (!this.groupChanged) {
  322. return
  323. }
  324. if (!this.$group) {
  325. this.$message({
  326. type: 'warning',
  327. message: '请选择用户所属的分组'
  328. })
  329. return
  330. }
  331. this.$confirm(
  332. `将${this.user.username}移动至${this.$group.label}?`,
  333. { type: 'warning' }
  334. ).then(() => {
  335. const loading = this.$showLoading()
  336. moveUserGroup(this.user.id, this.group, this.$group).then(
  337. () => {
  338. this.$closeLoading(loading)
  339. this.group = this.$group
  340. this.$group = null
  341. this.$message({
  342. type: 'success',
  343. message: '修改成功'
  344. })
  345. this.$emit('group', this.group)
  346. },
  347. () => {
  348. this.group = null
  349. this.groupChanged = false
  350. this.getSettings().then(() => {
  351. if (!this.error) {
  352. this.expandTree()
  353. }
  354. })
  355. }
  356. )
  357. })
  358. },
  359. onSaveRole () {
  360. if (!this.roleChanged) {
  361. return
  362. }
  363. const { available, stash, roles } = this.role
  364. const loading = this.$showLoading()
  365. updateUserRoles(this.user.id, available, stash, roles).then(
  366. () => {
  367. this.$closeLoading(loading)
  368. this.$message({
  369. type: 'success',
  370. message: '修改成功'
  371. })
  372. },
  373. () => {
  374. this.ready = false
  375. this.role = null
  376. this.getSettings()
  377. }
  378. )
  379. }
  380. }
  381. }
  382. </script>
  383. <style lang="scss" scoped>
  384. .c-switch {
  385. display: inline-block;
  386. position: relative;
  387. width: 62px;
  388. border: 1px solid #bbb;
  389. border-radius: 2px;
  390. overflow: hidden;
  391. cursor: pointer;
  392. &.checked {
  393. .c-switch__inner {
  394. margin-left: 0;
  395. }
  396. .c-switch__switch {
  397. right: 0;
  398. }
  399. }
  400. &__inner {
  401. display: block;
  402. margin-left: -100%;
  403. transition: margin 0.3s ease-in 0s;
  404. width: 200%;
  405. & > span {
  406. float: left;
  407. width: 50%;
  408. height: 24px;
  409. font-size: 12px;
  410. font-weight: bold;
  411. line-height: 24px;
  412. }
  413. }
  414. &__active {
  415. padding-left: 10px;
  416. color: #ffffff;
  417. background-image: linear-gradient(to bottom, #00a9ec 0%, #009bd3 100%);
  418. }
  419. &__inactive {
  420. padding-right: 10px;
  421. color: #4d5258;
  422. text-align: right;
  423. background-image: linear-gradient(to bottom, #fefefe 0%, #e8e8e8 100%);
  424. }
  425. &__switch {
  426. position: absolute;
  427. top: 0;
  428. bottom: 0;
  429. width: 23px;
  430. right: 39px;
  431. margin: 0;
  432. border-radius: 2px;
  433. transition: all 0.3s ease-in 0s;
  434. border: 1px solid #aaa;
  435. background-image: linear-gradient(to bottom, #fafafa 0%, #ededed 100%);
  436. }
  437. }
  438. .c-transfer {
  439. display: flex;
  440. width: 100%;
  441. ::v-deep {
  442. .el-transfer-panel {
  443. flex: 1 1 0;
  444. min-width: 0;
  445. width: auto;
  446. }
  447. .el-transfer__buttons {
  448. flex: none;
  449. display: flex;
  450. justify-content: center;
  451. align-items: center;
  452. }
  453. .el-transfer__button:first-child {
  454. margin: 0;
  455. }
  456. }
  457. }
  458. </style>