Settings.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  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 c-grid-form__option">
  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 class="l-flex--row c-grid-form__option">
  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 class="l-flex--row c-grid-form__option">
  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. toggleUser,
  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. tenant: {
  154. type: Object,
  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.tenant]
  229. }
  230. this.ready = true
  231. } catch (e) {
  232. this.error = true
  233. }
  234. this.$closeLoading(loading)
  235. },
  236. onTabClick (tab) {
  237. if (this.active !== tab.name) {
  238. switch (tab.name) {
  239. case 'Group':
  240. this.expandTree()
  241. break
  242. default:
  243. break
  244. }
  245. this.active = tab.name
  246. }
  247. },
  248. onToggleEnable () {
  249. const enabled = !this.user.enabled
  250. toggleUser(
  251. this.user.id,
  252. enabled
  253. ).then(() => {
  254. this.user.enabled = enabled
  255. })
  256. },
  257. onResetPassword () {
  258. this.$confirm(
  259. `将${this.user.username}的密码重置为默认密码?`,
  260. '操作确认',
  261. { type: 'warning' }
  262. ).then(() => {
  263. resetPassword(this.user)
  264. })
  265. },
  266. onResetOTP () {
  267. this.$confirm(
  268. `将${this.user.username}当前绑定的OTP重置?`,
  269. '操作确认',
  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. '操作确认',
  281. { type: 'warning' }
  282. ).then(() => {
  283. const loading = this.$showLoading()
  284. deleteUser(this.user)
  285. .then(() => this.$emit('del'))
  286. .finally(() => {
  287. this.$closeLoading(loading)
  288. })
  289. })
  290. },
  291. onGroupClick (group) {
  292. this.$group = group
  293. this.groupChanged = this.$group.path !== this.group.path
  294. },
  295. expandTree () {
  296. this.$nextTick(() => {
  297. this.$refs.tree.setCurrentKey(this.group.path)
  298. let parentGroup = this.group.parentGroup
  299. while (parentGroup) {
  300. this.$refs.tree.getNode(parentGroup.path).expanded = true
  301. parentGroup = parentGroup.parentGroup
  302. }
  303. })
  304. },
  305. findLastGroup (groups) {
  306. let result = { subGroups: [this.tenant] }
  307. groups.forEach(({ path }) => {
  308. if (result) {
  309. result = result.subGroups.find(group => group.path === path)
  310. }
  311. })
  312. return result
  313. },
  314. onSaveGroup () {
  315. if (!this.groupChanged) {
  316. return
  317. }
  318. if (!this.$group) {
  319. this.$message({
  320. type: 'warning',
  321. message: '请选择用户所属的分组'
  322. })
  323. return
  324. }
  325. this.$confirm(
  326. `将${this.user.username}移动至${this.$group.label}?`,
  327. '操作确认',
  328. { type: 'warning' }
  329. ).then(() => {
  330. const loading = this.$showLoading()
  331. moveUserGroup(this.user.id, this.group, this.$group).then(
  332. () => {
  333. this.$closeLoading(loading)
  334. this.group = this.$group
  335. this.$group = null
  336. this.$message({
  337. type: 'success',
  338. message: '修改成功'
  339. })
  340. this.$emit('group', this.group)
  341. },
  342. () => {
  343. this.group = null
  344. this.groupChanged = false
  345. this.getSettings().then(() => {
  346. if (!this.error) {
  347. this.expandTree()
  348. }
  349. })
  350. }
  351. )
  352. })
  353. },
  354. onSaveRole () {
  355. if (!this.roleChanged) {
  356. return
  357. }
  358. const { available, stash, roles } = this.role
  359. const loading = this.$showLoading()
  360. updateUserRoles(this.user.id, available, stash, roles).then(
  361. () => {
  362. this.$closeLoading(loading)
  363. this.$message({
  364. type: 'success',
  365. message: '修改成功'
  366. })
  367. },
  368. () => {
  369. this.ready = false
  370. this.role = null
  371. this.getSettings()
  372. }
  373. )
  374. }
  375. }
  376. }
  377. </script>
  378. <style lang="scss" scoped>
  379. .c-switch {
  380. display: inline-block;
  381. position: relative;
  382. width: 62px;
  383. border: 1px solid #bbb;
  384. border-radius: 2px;
  385. overflow: hidden;
  386. cursor: pointer;
  387. &.checked {
  388. .c-switch__inner {
  389. margin-left: 0;
  390. }
  391. .c-switch__switch {
  392. right: 0;
  393. }
  394. }
  395. &__inner {
  396. display: block;
  397. margin-left: -100%;
  398. transition: margin 0.3s ease-in 0s;
  399. width: 200%;
  400. & > span {
  401. float: left;
  402. width: 50%;
  403. height: 24px;
  404. font-size: 12px;
  405. font-weight: bold;
  406. line-height: 24px;
  407. }
  408. }
  409. &__active {
  410. padding-left: 10px;
  411. color: #ffffff;
  412. background-image: linear-gradient(to bottom, #00a9ec 0%, #009bd3 100%);
  413. }
  414. &__inactive {
  415. padding-right: 10px;
  416. color: #4d5258;
  417. text-align: right;
  418. background-image: linear-gradient(to bottom, #fefefe 0%, #e8e8e8 100%);
  419. }
  420. &__switch {
  421. position: absolute;
  422. top: 0;
  423. bottom: 0;
  424. width: 23px;
  425. right: 39px;
  426. margin: 0;
  427. border-radius: 2px;
  428. transition: all 0.3s ease-in 0s;
  429. border: 1px solid #aaa;
  430. background-image: linear-gradient(to bottom, #fafafa 0%, #ededed 100%);
  431. }
  432. }
  433. .c-transfer {
  434. display: flex;
  435. width: 100%;
  436. ::v-deep {
  437. .el-transfer-panel {
  438. flex: 1 1 0;
  439. min-width: 0;
  440. width: auto;
  441. }
  442. .el-transfer__buttons {
  443. flex: none;
  444. display: flex;
  445. justify-content: center;
  446. align-items: center;
  447. }
  448. .el-transfer__button:first-child {
  449. margin: 0;
  450. }
  451. }
  452. }
  453. </style>