【前端】智能库房综合管理系统前端项目
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

483 lines
18 KiB

3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
  1. <template>
  2. <div class="app-container">
  3. <el-row class="container-main" :gutter="20">
  4. <!--侧边部门数据-->
  5. <el-col :xs="9" :sm="6" :md="5" :lg="4" :xl="4">
  6. <div class="head-container" style="padding-left:0">
  7. <el-input v-model="deptName" clearable size="small" placeholder="输入部门名称搜索" prefix-icon="el-icon-search" class="filter-item" @input="getDeptDatas" />
  8. </div>
  9. <div class="container-left" style="min-height: calc(100vh - 262px)">
  10. <span class="right-top-line" />
  11. <span class="left-bottom-line" />
  12. <el-tree :data="deptDatas" :load="getDeptDatas" :props="defaultProps" :expand-on-click-node="false" lazy @node-click="handleNodeClick" />
  13. </div>
  14. </el-col>
  15. <!--用户数据-->
  16. <el-col :xs="15" :sm="18" :md="19" :lg="20" :xl="20">
  17. <!--工具栏-->
  18. <div class="head-container" style="padding:20px 0">
  19. <div v-if="crud.props.searchToggle" class="head-search">
  20. <!-- 搜索 -->
  21. <el-input v-model="query.blurry" clearable size="small" placeholder="输入名称或者邮箱搜索" prefix-icon="el-icon-search" style="width: 200px;" class="filter-item" @keyup.enter.native="crud.toQuery" />
  22. <date-range-picker v-model="query.createTime" class="date-item" />
  23. <el-select v-model="query.enabled" clearable size="small" placeholder="状态" class="filter-item" style="width: 100px" @change="crud.toQuery">
  24. <i slot="prefix" class="iconfont icon-zhuangtai-fanbai" />
  25. <el-option v-for="item in enabledTypeOptions" :key="item.key" :label="item.display_name" :value="item.key" />
  26. </el-select>
  27. <rrOperation />
  28. </div>
  29. <crudOperation show="" :permission="permission">
  30. <el-button slot="right" icon="el-icon-refresh-left" size="mini" :disabled="!crud.selections.length" @click="handlePwdReset(crud.selections)">密码重置</el-button>
  31. </crudOperation>
  32. </div>
  33. <div class="container-right" style="min-height: calc(100vh - 312px);">
  34. <span class="right-top-line" />
  35. <span class="left-bottom-line" />
  36. <!--表单渲染-->
  37. <el-dialog append-to-body :close-on-click-modal="false" :before-close="crud.cancelCU" :visible.sync="crud.status.cu > 0" :title="crud.status.title">
  38. <span class="dialog-right-top" />
  39. <span class="dialog-left-bottom" />
  40. <div class="setting-dialog">
  41. <el-form ref="form" :inline="true" :model="form" :rules="rules" size="small" label-width="66px">
  42. <el-form-item label="用户名" prop="username">
  43. <el-input v-model="form.username" @keydown.native="keydown($event)" />
  44. </el-form-item>
  45. <el-form-item label="电话" prop="phone">
  46. <el-input v-model.number="form.phone" />
  47. </el-form-item>
  48. <el-form-item label="昵称" prop="nickName">
  49. <el-input v-model="form.nickName" @keydown.native="keydown($event)" />
  50. </el-form-item>
  51. <el-form-item label="邮箱" prop="email">
  52. <el-input v-model="form.email" />
  53. </el-form-item>
  54. <el-form-item label="部门" prop="dept.id">
  55. <treeselect v-model="form.dept.id" :options="depts" :load-options="loadDepts" style="width: 184px" placeholder="选择部门" />
  56. </el-form-item>
  57. <!-- class="is-required" -->
  58. <el-form-item label="岗位" prop="jobs">
  59. <el-select v-model="jobDatas" style="width: 184px" multiple placeholder="请选择" @remove-tag="deleteTag" @change="changeJob">
  60. <el-option v-for="item in jobs" :key="item.name" :label="item.name" :value="item.id" />
  61. </el-select>
  62. </el-form-item>
  63. <el-form-item label="性别">
  64. <el-radio-group v-model="form.gender" style="width: 184px">
  65. <el-radio label="男"></el-radio>
  66. <el-radio label="女"></el-radio>
  67. </el-radio-group>
  68. </el-form-item>
  69. <el-form-item label="状态">
  70. <el-radio-group v-model="form.enabled" :disabled="form.id === user.id">
  71. <el-radio v-for="item in dict.user_status" :key="item.id" :label="item.value">{{ item.label }}</el-radio>
  72. </el-radio-group>
  73. </el-form-item>
  74. <el-form-item style="margin-bottom: 0;" label="角色" prop="roles" class="is-required">
  75. <el-select v-model="roleDatas" style="width: 437px" multiple placeholder="请选择" @remove-tag="deleteTag" @change="changeRole">
  76. <el-option v-for="item in roles" :key="item.name" :disabled="level !== 1 && item.level <= level" :label="item.name" :value="item.id" />
  77. </el-select>
  78. </el-form-item>
  79. </el-form>
  80. <div slot="footer" class="dialog-footer">
  81. <el-button type="text" @click="crud.cancelCU">取消</el-button>
  82. <el-button :loading="crud.status.cu === 2" type="primary" @click="crud.submitCU">确认</el-button>
  83. </div>
  84. </div>
  85. </el-dialog>
  86. <!-- 密码 -->
  87. <el-dialog :visible.sync="resetVisible" title="确认密码重置">
  88. <span class="dialog-right-top" />
  89. <span class="dialog-left-bottom" />
  90. <div class="setting-dialog">
  91. <p class="delMsg">确认重置所选{{ crud.selections.length }}个用户的密码用户默认密码为123456</p>
  92. <div slot="footer" class="dialog-footer">
  93. <el-button v-loading="btnLoading" type="primary" @click="pwdReset(crud.selections)">确定</el-button>
  94. </div>
  95. </div>
  96. </el-dialog>
  97. <!--表格渲染-->
  98. <el-table ref="table" v-loading="crud.loading" :data="crud.data" style="width: 100%;" @row-click="clickRowHandler" @selection-change="crud.selectionChangeHandler">
  99. <el-table-column :selectable="checkboxT" type="selection" align="center" width="55" />
  100. <el-table-column :show-overflow-tooltip="true" prop="username" label="用户名" />
  101. <el-table-column :show-overflow-tooltip="true" prop="nickName" label="昵称" />
  102. <el-table-column prop="gender" label="性别" />
  103. <el-table-column :show-overflow-tooltip="true" prop="phone" width="100" label="电话" />
  104. <el-table-column :show-overflow-tooltip="true" width="135" prop="email" label="邮箱" />
  105. <el-table-column :show-overflow-tooltip="true" prop="dept" label="部门">
  106. <template slot-scope="scope">
  107. <div>{{ scope.row.dept.name }}</div>
  108. </template>
  109. </el-table-column>
  110. <el-table-column label="状态" align="center" prop="enabled">
  111. <template slot-scope="scope">
  112. <el-switch v-model="scope.row.enabled" :disabled="user.id === scope.row.id" active-color="#409EFF" inactive-color="#F56C6C" @change="changeEnabled(scope.row, scope.row.enabled)" />
  113. </template>
  114. </el-table-column>
  115. <el-table-column :show-overflow-tooltip="true" prop="createTime" width="145" label="创建日期">
  116. <template slot-scope="scope">
  117. <div>{{ scope.row.createTime | parseTime }}</div>
  118. </template>
  119. </el-table-column>
  120. <el-table-column v-if="checkPer(['admin','user:edit','user:del'])" label="操作" width="120" align="center" fixed="right">
  121. <template slot-scope="scope">
  122. <udOperation :data="scope.row" :permission="permission" :disabled-dle="scope.row.id === user.id" />
  123. </template>
  124. </el-table-column>
  125. </el-table>
  126. <!--分页组件-->
  127. <pagination />
  128. </div>
  129. </el-col>
  130. </el-row>
  131. </div>
  132. </template>
  133. <script>
  134. import { getToken } from '@/utils/auth'
  135. import crudUser, { resetpassword } from '@/api/system/user'
  136. // import { isvalidPhone } from '@/utils/validate'
  137. import { getDepts, getDeptSuperior } from '@/api/system/dept'
  138. import { getAll, getLevel } from '@/api/system/role'
  139. import { getAllJob } from '@/api/system/job'
  140. import CRUD, { presenter, header, form, crud } from '@crud/crud'
  141. import rrOperation from '@crud/RR.operation'
  142. import crudOperation from '@crud/CRUD.operation'
  143. import udOperation from '@crud/UD.operation'
  144. import pagination from '@crud/Pagination'
  145. import DateRangePicker from '@/components/DateRangePicker'
  146. import Treeselect from '@riophae/vue-treeselect'
  147. import { mapGetters } from 'vuex'
  148. import '@riophae/vue-treeselect/dist/vue-treeselect.css'
  149. import { LOAD_CHILDREN_OPTIONS } from '@riophae/vue-treeselect'
  150. let userRoles = []
  151. let userJobs = []
  152. const defaultForm = { id: null, username: null, nickName: null, gender: '男', email: null, enabled: 'true', roles: [], jobs: [], dept: { id: null }, phone: null }
  153. export default {
  154. name: 'User',
  155. components: { Treeselect, crudOperation, rrOperation, udOperation, pagination, DateRangePicker },
  156. cruds() {
  157. return CRUD({ title: '用户', url: 'api/users', crudMethod: { ...crudUser }})
  158. },
  159. mixins: [presenter(), header(), form(defaultForm), crud()],
  160. // 数据字典
  161. dicts: ['user_status'],
  162. data() {
  163. // 自定义验证
  164. // const validPhone = (rule, value, callback) => {
  165. // if (!value) {
  166. // callback(new Error('请输入电话号码'))
  167. // } else if (!isvalidPhone(value)) {
  168. // callback(new Error('请输入正确的11位手机号码'))
  169. // } else {
  170. // callback()
  171. // }
  172. // }
  173. // var validateJob = (rule, value, callback) => {
  174. // console.log(value)
  175. // if (this.jobDatas.length > 0) {
  176. // callback()
  177. // } else {
  178. // callback(new Error('请选择岗位'))
  179. // }
  180. // }
  181. return {
  182. headers: {
  183. Authorization: getToken()
  184. },
  185. btnLoading: false,
  186. resetVisible: false,
  187. height: document.documentElement.clientHeight - 180 + 'px;',
  188. deptName: '', depts: [], deptDatas: [], jobs: [], level: 3, roles: [],
  189. jobDatas: [], roleDatas: [], // 多选时使用
  190. defaultProps: { children: 'children', label: 'name', isLeaf: 'leaf' },
  191. permission: {
  192. add: ['admin', 'user:add'],
  193. edit: ['admin', 'user:edit'],
  194. del: ['admin', 'user:del']
  195. },
  196. enabledTypeOptions: [
  197. { key: 'true', display_name: '激活' },
  198. { key: 'false', display_name: '锁定' }
  199. ],
  200. rules: {
  201. username: [
  202. { required: true, message: '请输入用户名', trigger: 'blur' },
  203. { min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
  204. ],
  205. nickName: [
  206. { required: true, message: '请输入用户昵称', trigger: 'blur' },
  207. { min: 2, max: 20, message: '长度在 2 到 20 个字符', trigger: 'blur' }
  208. ],
  209. // email: [
  210. // { required: true, message: '请输入邮箱地址', trigger: 'blur' },
  211. // { type: 'email', message: '请输入正确的邮箱地址', trigger: 'blur' }
  212. // ],
  213. // phone: [
  214. // { required: true, trigger: 'blur', validator: validPhone }
  215. // ],
  216. 'dept.id': [
  217. { required: true, trigger: 'blur', message: '请选择部门' }
  218. ]
  219. // jobs: [
  220. // { trigger: 'blur', validator: validateJob }
  221. // ]
  222. }
  223. }
  224. },
  225. computed: {
  226. ...mapGetters([
  227. 'user'
  228. ])
  229. },
  230. created() {
  231. this.crud.msg.add = '新增成功,默认密码:123456'
  232. },
  233. mounted: function() {
  234. const that = this
  235. window.onresize = function temp() {
  236. that.height = document.documentElement.clientHeight - 180 + 'px;'
  237. }
  238. },
  239. methods: {
  240. clickRowHandler(row) {
  241. // this.$refs.table.toggleRowSelection(row) // 单击选中
  242. },
  243. handlePwdReset() {
  244. this.resetVisible = true
  245. },
  246. // 重置密码
  247. pwdReset(data) {
  248. const params = data.map(item => { return item.username })
  249. this.btnLoading = true
  250. resetpassword(params).then(res => {
  251. if (res === 'SUCCESS') {
  252. this.$message({
  253. message: '重置密码成功',
  254. type: 'success'
  255. })
  256. this.resetVisible = false
  257. this.btnLoading = false
  258. } else {
  259. this.$message.error('重置密码失败')
  260. this.btnLoading = false
  261. }
  262. })
  263. },
  264. // 禁止输入空格
  265. keydown(e) {
  266. if (e.keyCode === 32) {
  267. e.returnValue = false
  268. }
  269. },
  270. changeRole(value) {
  271. userRoles = []
  272. value.forEach(function(data, index) {
  273. const role = { id: data }
  274. userRoles.push(role)
  275. })
  276. },
  277. changeJob(value) {
  278. userJobs = []
  279. value.forEach(function(data, index) {
  280. const job = { id: data }
  281. userJobs.push(job)
  282. })
  283. },
  284. deleteTag(value) {
  285. userRoles.forEach(function(data, index) {
  286. if (data.id === value) {
  287. userRoles.splice(index, value)
  288. }
  289. })
  290. },
  291. // 新增与编辑前做的操作
  292. [CRUD.HOOK.afterToCU](crud, form) {
  293. this.getRoles()
  294. if (form.id == null) {
  295. this.getDepts()
  296. } else {
  297. this.getSupDepts(form.dept.id)
  298. }
  299. this.getRoleLevel()
  300. this.getJobs()
  301. form.enabled = form.enabled.toString()
  302. },
  303. // 新增前将多选的值设置为空
  304. [CRUD.HOOK.beforeToAdd]() {
  305. this.jobDatas = []
  306. this.roleDatas = []
  307. },
  308. // 初始化编辑时候的角色与岗位
  309. [CRUD.HOOK.beforeToEdit](crud, form) {
  310. this.getJobs(this.form.dept.id)
  311. this.jobDatas = []
  312. this.roleDatas = []
  313. userRoles = []
  314. userJobs = []
  315. const _this = this
  316. form.roles.forEach(function(role, index) {
  317. _this.roleDatas.push(role.id)
  318. const rol = { id: role.id }
  319. userRoles.push(rol)
  320. })
  321. form.jobs.forEach(function(job, index) {
  322. _this.jobDatas.push(job.id)
  323. const data = { id: job.id }
  324. userJobs.push(data)
  325. })
  326. },
  327. // 提交前做的操作
  328. [CRUD.HOOK.afterValidateCU](crud) {
  329. // if (!crud.form.dept.id) {
  330. // this.$message({
  331. // message: '部门不能为空',
  332. // type: 'warning'
  333. // })
  334. // return false
  335. // } else if (this.jobDatas.length === 0) {
  336. // this.$message({
  337. // message: '岗位不能为空',
  338. // type: 'warning'
  339. // })
  340. // return false
  341. // } else if (this.roleDatas.length === 0) {
  342. // this.$message({
  343. // message: '角色不能为空',
  344. // type: 'warning'
  345. // })
  346. // return false
  347. // }
  348. if (this.roleDatas.length === 0) {
  349. this.$message({
  350. message: '角色不能为空',
  351. type: 'warning'
  352. })
  353. return false
  354. }
  355. crud.form.roles = userRoles
  356. crud.form.jobs = userJobs
  357. return true
  358. },
  359. // 获取左侧部门数据
  360. getDeptDatas(node, resolve) {
  361. const sort = 'id,desc'
  362. const params = { sort: sort }
  363. if (typeof node !== 'object') {
  364. if (node) {
  365. params['name'] = node
  366. }
  367. } else if (node.level !== 0) {
  368. params['pid'] = node.data.id
  369. }
  370. setTimeout(() => {
  371. getDepts(params).then(res => {
  372. if (resolve) {
  373. resolve(res.content)
  374. } else {
  375. this.deptDatas = res.content
  376. }
  377. })
  378. }, 100)
  379. },
  380. getDepts() {
  381. getDepts({ enabled: true }).then(res => {
  382. this.depts = res.content.map(function(obj) {
  383. if (obj.hasChildren) {
  384. obj.children = null
  385. }
  386. return obj
  387. })
  388. })
  389. },
  390. getSupDepts(deptId) {
  391. getDeptSuperior(deptId).then(res => {
  392. const date = res.content
  393. this.buildDepts(date)
  394. this.depts = date
  395. })
  396. },
  397. buildDepts(depts) {
  398. depts.forEach(data => {
  399. if (data.children) {
  400. this.buildDepts(data.children)
  401. }
  402. if (data.hasChildren && !data.children) {
  403. data.children = null
  404. }
  405. })
  406. },
  407. // 获取弹窗内部门数据
  408. loadDepts({ action, parentNode, callback }) {
  409. if (action === LOAD_CHILDREN_OPTIONS) {
  410. getDepts({ enabled: true, pid: parentNode.id }).then(res => {
  411. parentNode.children = res.content.map(function(obj) {
  412. if (obj.hasChildren) {
  413. obj.children = null
  414. }
  415. return obj
  416. })
  417. setTimeout(() => {
  418. callback()
  419. }, 200)
  420. })
  421. }
  422. },
  423. // 切换部门
  424. handleNodeClick(data) {
  425. if (data.pid === 0) {
  426. this.query.deptId = null
  427. } else {
  428. this.query.deptId = data.id
  429. }
  430. this.crud.toQuery()
  431. },
  432. // 改变状态
  433. changeEnabled(data, val) {
  434. this.$confirm('此操作将 "' + this.dict.label.user_status[val] + '" ' + data.username + ', 是否继续?', '提示', {
  435. confirmButtonText: '确定',
  436. cancelButtonText: '取消',
  437. type: 'warning'
  438. }).then(() => {
  439. crudUser.edit(data).then(res => {
  440. this.crud.notify(this.dict.label.user_status[val] + '成功', CRUD.NOTIFICATION_TYPE.SUCCESS)
  441. }).catch(() => {
  442. data.enabled = !data.enabled
  443. })
  444. }).catch(() => {
  445. data.enabled = !data.enabled
  446. })
  447. },
  448. // 获取弹窗内角色数据
  449. getRoles() {
  450. getAll().then(res => {
  451. this.roles = res
  452. }).catch(() => { })
  453. },
  454. // 获取弹窗内岗位数据
  455. getJobs() {
  456. getAllJob().then(res => {
  457. this.jobs = res.content
  458. }).catch(() => { })
  459. },
  460. // 获取权限级别
  461. getRoleLevel() {
  462. getLevel().then(res => {
  463. this.level = res.level
  464. }).catch(() => { })
  465. },
  466. checkboxT(row, rowIndex) {
  467. return row.id !== this.user.id
  468. }
  469. }
  470. }
  471. </script>
  472. <style rel="stylesheet/scss" lang="scss" scoped>
  473. ::v-deep .vue-treeselect__control,
  474. ::v-deep .vue-treeselect__placeholder,
  475. ::v-deep .vue-treeselect__single-value {
  476. height: 30px;
  477. line-height: 30px;
  478. }
  479. </style>