Settings.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482
  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 {
  146. unbindDevices,
  147. updateBindDevices
  148. } from '@/api/device'
  149. import { Role } from '@/constant'
  150. export default {
  151. name: 'UserSettings',
  152. props: {
  153. user: {
  154. type: Object,
  155. required: true
  156. },
  157. groups: {
  158. type: Array,
  159. required: true
  160. }
  161. },
  162. data () {
  163. return {
  164. ready: false,
  165. ignore: false,
  166. error: false,
  167. active: 'Credentials',
  168. credentials: null,
  169. group: null,
  170. userGroups: null,
  171. groupChanged: false,
  172. role: null,
  173. treeProps: { children: 'subGroups', label: 'label' }
  174. }
  175. },
  176. computed: {
  177. ...mapGetters(['isSuperAdmin', 'isTenantAdmin']),
  178. roleChanged () {
  179. if (this.role) {
  180. const { stash, roles } = this.role
  181. if (stash.length !== roles.length) {
  182. return true
  183. }
  184. if (stash.length !== new Set([...roles, ...stash]).size) {
  185. return true
  186. }
  187. }
  188. return false
  189. }
  190. },
  191. mounted () {
  192. this.$nextTick(() => {
  193. this.getSettings()
  194. })
  195. },
  196. methods: {
  197. async getSettings () {
  198. const loading = this.$showLoading()
  199. this.ready = false
  200. this.error = false
  201. try {
  202. if (!this.role) {
  203. const { roles, available } = await getUserRoleMapping(this.user.id)
  204. const superAdmin = roles.find(({ name }) => name === Role.SUPER_ADMIN)
  205. if (superAdmin && !this.isSuperAdmin) {
  206. this.ignore = true
  207. this.$closeLoading(loading)
  208. return
  209. }
  210. const tenantAdmin = roles.find(({ name }) => name === Role.ADMIN)
  211. if (tenantAdmin && !this.isSuperAdmin) {
  212. this.ignore = true
  213. this.$closeLoading(loading)
  214. return
  215. }
  216. const roleKeys = roles.map(({ id }) => id)
  217. this.role = {
  218. available,
  219. roles: roleKeys,
  220. stash: [...roleKeys]
  221. }
  222. }
  223. if (!this.credentials) {
  224. const credentials = await getUserCredentials(this.user)
  225. this.credentials = {
  226. otp: credentials.find(({ type }) => type === 'otp')
  227. }
  228. }
  229. if (!this.group) {
  230. const groups = await getUserGroups(this.user.id)
  231. this.group = this.findLastGroup(groups)
  232. this.userGroups = this.getRootGroups(this.group)
  233. }
  234. this.ready = true
  235. } catch (e) {
  236. console.log(e)
  237. this.error = true
  238. }
  239. this.$closeLoading(loading)
  240. },
  241. onTabClick (tab) {
  242. if (this.active !== tab.name) {
  243. switch (tab.name) {
  244. case 'Group':
  245. this.expandTree()
  246. break
  247. default:
  248. break
  249. }
  250. this.active = tab.name
  251. }
  252. },
  253. onToggleEnable () {
  254. const enabled = !this.user.enabled
  255. updateUser(
  256. this.user.id,
  257. { enabled },
  258. enabled ? '启用' : '禁用'
  259. ).then(() => {
  260. this.user.enabled = enabled
  261. })
  262. },
  263. onResetPassword () {
  264. this.$confirm(
  265. `将${this.user.username}的密码重置为默认密码?`,
  266. { type: 'warning' }
  267. ).then(() => {
  268. resetPassword(this.user)
  269. })
  270. },
  271. onResetOTP () {
  272. this.$confirm(
  273. `将${this.user.username}当前绑定的OTP重置?`,
  274. { type: 'warning' }
  275. ).then(() => {
  276. deleteUserCredentials(this.user.id, this.credentials.otp.id).then(() => {
  277. this.credentials.otp = null
  278. })
  279. })
  280. },
  281. onDel () {
  282. this.$confirm(
  283. `注销账号${this.user.username}?`,
  284. { type: 'warning' }
  285. ).then(() => {
  286. const loading = this.$showLoading()
  287. unbindDevices(`${this.group.path}/${this.user.id}`)
  288. .then(() => deleteUser(this.user))
  289. .then(() => this.$emit('del'))
  290. .finally(() => {
  291. this.$closeLoading(loading)
  292. })
  293. })
  294. },
  295. onGroupClick (group) {
  296. this.$group = group
  297. this.groupChanged = this.$group.path !== this.group.path
  298. },
  299. expandTree () {
  300. this.$nextTick(() => {
  301. this.$refs.tree.setCurrentKey(this.group.path)
  302. let parentGroup = this.group.parentGroup
  303. while (parentGroup) {
  304. this.$refs.tree.getNode(parentGroup.path).expanded = true
  305. parentGroup = parentGroup.parentGroup
  306. }
  307. })
  308. },
  309. getRootGroups (group) {
  310. let target = group
  311. while (target.parentGroup) {
  312. target = target.parentGroup
  313. }
  314. return [target]
  315. },
  316. findLastGroup (groups) {
  317. let result = { subGroups: this.groups }
  318. groups.forEach(({ path }) => {
  319. if (result) {
  320. result = result.subGroups.find(group => group.path === path)
  321. }
  322. })
  323. return result
  324. },
  325. onSaveGroup () {
  326. if (!this.groupChanged) {
  327. return
  328. }
  329. if (!this.$group) {
  330. this.$message({
  331. type: 'warning',
  332. message: '请选择用户所属的分组'
  333. })
  334. return
  335. }
  336. this.$confirm(
  337. `将${this.user.username}移动至${this.$group.label}?`,
  338. { type: 'warning' }
  339. ).then(() => {
  340. const loading = this.$showLoading()
  341. updateBindDevices(`${this.$group.path}/${this.user.id}`, `${this.group.path}/${this.user.id}`).then(() => {
  342. moveUserGroup(this.user.id, this.group, this.$group).then(
  343. () => {
  344. this.$closeLoading(loading)
  345. this.group = this.$group
  346. this.$group = null
  347. this.$message({
  348. type: 'success',
  349. message: '修改成功'
  350. })
  351. this.$emit('group', this.group)
  352. },
  353. () => {
  354. this.group = null
  355. this.groupChanged = false
  356. this.getSettings().then(() => {
  357. if (!this.error) {
  358. this.expandTree()
  359. }
  360. })
  361. }
  362. )
  363. })
  364. })
  365. },
  366. onSaveRole () {
  367. if (!this.roleChanged) {
  368. return
  369. }
  370. const { available, stash, roles } = this.role
  371. const loading = this.$showLoading()
  372. updateUserRoles(this.user.id, available, stash, roles).then(
  373. () => {
  374. this.$closeLoading(loading)
  375. this.$message({
  376. type: 'success',
  377. message: '修改成功'
  378. })
  379. },
  380. () => {
  381. this.ready = false
  382. this.role = null
  383. this.getSettings()
  384. }
  385. )
  386. }
  387. }
  388. }
  389. </script>
  390. <style lang="scss" scoped>
  391. .c-switch {
  392. display: inline-block;
  393. position: relative;
  394. width: 62px;
  395. border: 1px solid #bbb;
  396. border-radius: 2px;
  397. overflow: hidden;
  398. cursor: pointer;
  399. &.checked {
  400. .c-switch__inner {
  401. margin-left: 0;
  402. }
  403. .c-switch__switch {
  404. right: 0;
  405. }
  406. }
  407. &__inner {
  408. display: block;
  409. margin-left: -100%;
  410. transition: margin 0.3s ease-in 0s;
  411. width: 200%;
  412. & > span {
  413. float: left;
  414. width: 50%;
  415. height: 24px;
  416. font-size: 12px;
  417. font-weight: bold;
  418. line-height: 24px;
  419. }
  420. }
  421. &__active {
  422. padding-left: 10px;
  423. color: #ffffff;
  424. background-image: linear-gradient(to bottom, #00a9ec 0%, #009bd3 100%);
  425. }
  426. &__inactive {
  427. padding-right: 10px;
  428. color: #4d5258;
  429. text-align: right;
  430. background-image: linear-gradient(to bottom, #fefefe 0%, #e8e8e8 100%);
  431. }
  432. &__switch {
  433. position: absolute;
  434. top: 0;
  435. bottom: 0;
  436. width: 23px;
  437. right: 39px;
  438. margin: 0;
  439. border-radius: 2px;
  440. transition: all 0.3s ease-in 0s;
  441. border: 1px solid #aaa;
  442. background-image: linear-gradient(to bottom, #fafafa 0%, #ededed 100%);
  443. }
  444. }
  445. .c-transfer {
  446. display: flex;
  447. width: 100%;
  448. ::v-deep {
  449. .el-transfer__panel {
  450. flex: 1 1 0;
  451. min-width: 0;
  452. width: auto;
  453. }
  454. .el-transfer__buttons {
  455. flex: none;
  456. display: flex;
  457. justify-content: center;
  458. align-items: center;
  459. }
  460. .el-transfer__button:first-child {
  461. margin: 0;
  462. }
  463. }
  464. }
  465. </style>