【前端】智能库房综合管理系统前端项目
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.

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