变量名称修改

用户增加部门字段
移除无用代码
修复OrgTree的一些问题
使用原本的el-select
master
toesbieya 6 years ago
parent 90097bfe0e
commit 0e8f4dc504
  1. 4
      vue/element-ui-personal/index.js
  2. 2
      vue/mock/controller/account/index.js
  3. 4
      vue/src/api/account.js
  4. 8
      vue/src/api/system/department.js
  5. 20
      vue/src/assets/styles/index.scss
  6. 127
      vue/src/components/OrgTree/OrgTreeNode.vue
  7. 266
      vue/src/components/OrgTree/index.vue
  8. 398
      vue/src/components/OrgTree/tree.scss
  9. 337
      vue/src/components/TreeSelect/index.vue
  10. 2
      vue/src/config/request.js
  11. 4
      vue/src/layout/components/Navbar/index.vue
  12. 2
      vue/src/mixins/bizDocumentTableMixin.js
  13. 4
      vue/src/store/modules/resource.js
  14. 2
      vue/src/store/modules/socket.js
  15. 15
      vue/src/store/modules/user.js
  16. 2
      vue/src/utils/auth.js
  17. 66
      vue/src/utils/index.js
  18. 178
      vue/src/views/app/login/LoginForm.vue
  19. 6
      vue/src/views/example/components/treeSelectExample.vue
  20. 4
      vue/src/views/purchase/inbound/components/OrderSelector.vue
  21. 6
      vue/src/views/purchase/inbound/detail.vue
  22. 6
      vue/src/views/purchase/inbound/index.vue
  23. 6
      vue/src/views/purchase/order/detail.vue
  24. 4
      vue/src/views/purchase/order/index.vue
  25. 2
      vue/src/views/sell/order/components/StockSelector.vue
  26. 26
      vue/src/views/sell/order/detail.vue
  27. 12
      vue/src/views/sell/order/index.vue
  28. 4
      vue/src/views/sell/outbound/components/OrderSelector.vue
  29. 14
      vue/src/views/sell/outbound/detail.vue
  30. 6
      vue/src/views/sell/outbound/index.vue
  31. 16
      vue/src/views/stock/current/index.vue
  32. 184
      vue/src/views/system/department/components/OrgTreeView.vue
  33. 315
      vue/src/views/system/department/index.vue
  34. 16
      vue/src/views/system/resource/EditDialog.vue
  35. 4
      vue/src/views/system/resource/index.vue
  36. 190
      vue/src/views/system/role/EditDialog.vue
  37. 158
      vue/src/views/system/role/components/EditDialog.vue
  38. 59
      vue/src/views/system/role/components/RoleResource.vue
  39. 215
      vue/src/views/system/role/index.vue
  40. 39
      vue/src/views/system/user/components/DepartmentSelector.vue
  41. 231
      vue/src/views/system/user/components/EditDialog.vue
  42. 9
      vue/src/views/system/user/components/RoleSelector.vue
  43. 299
      vue/src/views/system/user/index.vue
  44. 26
      vue/src/views/userCenter/components/Account.vue
  45. 77
      vue/src/views/userCenter/components/UserCard.vue
  46. 33
      vue/src/views/userCenter/index.vue

@ -1,9 +1,9 @@
import MenuItem from "./components/Menu/MenuItem" import MenuItem from "./components/Menu/MenuItem"
import Submenu from "./components/Menu/Submenu" import Submenu from "./components/Menu/Submenu"
import Message from "./components/Message" import Message from "./components/Message"
import Select from './components/Select/Select' //import Select from './components/Select/Select'
const components = [MenuItem, Submenu, Select] const components = [MenuItem, Submenu]
export default function (Vue) { export default function (Vue) {
components.forEach(component => { components.forEach(component => {

@ -13,7 +13,7 @@ module.exports = [
else res.send(success({ else res.send(success({
id: 1, id: 1,
name: 'admin', name: 'admin',
admin: 1, admin: true,
token: 'test' token: 'test'
})) }))
} }

@ -26,6 +26,6 @@ export function validate(pwd) {
return request.get(`${baseUrl}/validate?pwd=${pwd}`).then(({data}) => data) return request.get(`${baseUrl}/validate?pwd=${pwd}`).then(({data}) => data)
} }
export function checkName(name) { export function checkName(name, id) {
return request.get(`${baseUrl}/checkName?name=${name}`).then(({data}) => data) return request.get(`${baseUrl}/checkName?name=${name}&id=${id}`).then(({data}) => data)
} }

@ -2,12 +2,8 @@ import request from "@/config/request"
export const baseUrl = `/system/department` export const baseUrl = `/system/department`
export function getDepartments() { export function getDepartments(all = true) {
return request.get(`${baseUrl}/get`).then(({data}) => data.data) return request.get(`${baseUrl}/get?all=${all}`).then(({data}) => data.data)
}
export function getAllDepartments() {
return request.get(`${baseUrl}/getAll`).then(({data}) => data.data)
} }
export function addDepartment(data) { export function addDepartment(data) {

@ -165,22 +165,6 @@ a:hover {
} }
} }
//印章
.seal {
float: left;
border: solid .1em $--color-danger;
border-radius: .2em;
color: $--color-danger;
font-size: 23px;
font-weight: bold;
padding: .1em .5em;
bottom: 15px;
& + & {
margin-left: 20px;
}
}
.button-group { .button-group {
.el-button { .el-button {
margin-top: 10px; margin-top: 10px;
@ -200,4 +184,8 @@ a:hover {
} }
} }
.el-form-item__content > .el-tree {
margin-top: 8px;
}

@ -1,10 +1,11 @@
<script type="text/jsx">
// //
const isLeaf = (data, prop) => { const isLeaf = (data, prop) => {
return !(Array.isArray(data[prop]) && data[prop].length > 0) return !Array.isArray(data[prop]) || data[prop].length === 0
} }
// node // node
export const renderNode = (h, data, context) => { const renderNode = (h, data, context) => {
const {props} = context const {props} = context
const cls = ['org-tree-node'] const cls = ['org-tree-node']
const childNodes = [] const childNodes = []
@ -23,44 +24,11 @@ export const renderNode = (h, data, context) => {
childNodes.push(renderChildren(h, children, context)) childNodes.push(renderChildren(h, children, context))
} }
return h('div', { return <div class={cls.join(' ')}>{childNodes}</div>
domProps: {
className: cls.join(' ')
}
}, childNodes)
}
//
export const renderBtn = (h, data, context) => {
const {props} = context
const expandHandler = context.listeners['on-expand']
let cls = ['org-tree-node-btn']
if (data[props.props.expand]) {
cls.push('expanded')
}
return h('span', {
'class': 'org-tree-button-wrapper',
on: {
click: e => {
e.stopPropagation()
expandHandler && expandHandler(data)
},
mousedown: e => e.stopPropagation()
}
}, [
props.buttonRender
? props.buttonRender(h, data)
: h('span', {
'class': cls.join(' ')
})
])
} }
// label // label
export const renderLabel = (h, data, context) => { const renderLabel = (h, data, context) => {
const {props} = context const {props} = context
const label = data[props.props.label] const label = data[props.props.label]
const nodeRender = props.nodeRender const nodeRender = props.nodeRender
@ -77,8 +45,7 @@ export const renderLabel = (h, data, context) => {
const childNodes = [] const childNodes = []
if (typeof nodeRender === 'function') { if (typeof nodeRender === 'function') {
let vnode = nodeRender(h, data) childNodes.push(nodeRender(h, data))
vnode && childNodes.push(vnode)
} }
else if (context.parent.$scopedSlots.default) { else if (context.parent.$scopedSlots.default) {
childNodes.push(context.parent.$scopedSlots.default(data)) childNodes.push(context.parent.$scopedSlots.default(data))
@ -99,44 +66,60 @@ export const renderLabel = (h, data, context) => {
} }
labelClassName && cls.push(labelClassName) labelClassName && cls.push(labelClassName)
return h('div', { return (
domProps: { <div
className: 'org-tree-node-label' class="org-tree-node-label"
}, on-click={e => clickHandler && clickHandler(e, data)}
on: { on-mousedown={mousedownHandler}
click: e => clickHandler && clickHandler(e, data), on-mouseup={e => mouseupHandler && mouseupHandler(e, data)}
mousedown: mousedownHandler, on-touchstart={e => touchstartHandler && touchstartHandler(e, data)}
mouseup: e => mouseupHandler && mouseupHandler(e, data), on-touchleave={e => touchleaveHandler && touchleaveHandler(e, data)}
touchstart: e => touchstartHandler && touchstartHandler(e, data), >
touchleave: e => touchleaveHandler && touchleaveHandler(e, data) <div class={cls.join(' ')} style={{width: labelWidth}}>
} {childNodes}
}, [h('div', { </div>
domProps: { </div>
className: cls.join(' ') )
}, }
style: {width: labelWidth}
}, childNodes)]) //
const renderBtn = (h, data, context) => {
const {props} = context
const expandHandler = context.listeners['on-expand']
const onClock = e => {
e.stopPropagation()
expandHandler && expandHandler(data)
}
const onMousedown = e => e.stopPropagation()
const cls = ['org-tree-node-btn']
data[props.props.expand] && cls.push('expanded')
return (
<span class="org-tree-button-wrapper" on-click={onClock} on-mousedown={onMousedown}>
{props.buttonRender ? props.buttonRender(h, data) : <span class={cls.join(' ')}/>}
</span>
)
} }
// node // node
export const renderChildren = (h, list, context) => { const renderChildren = (h, list, context) => {
if (Array.isArray(list) && list.length) { if (Array.isArray(list) && list.length) {
const children = list.map(item => { return (
return renderNode(h, item, context) <div class="org-tree-node-children">
}) {list.map(item => renderNode(h, item, context))}
</div>
return h('div', { )
domProps: {
className: 'org-tree-node-children'
}
}, children)
} }
return ''
} }
export const render = (h, context) => { export default {
const {props} = context name: "OrgTreeNode",
return renderNode(h, props.data, context)
}
export default render functional: true,
render(h, context) {
return renderNode(h, context.props.data, context)
}
}
</script>

@ -1,187 +1,143 @@
<template> <template>
<div class="org-tree-container"> <div class="org-tree-container">
<div :class="{horizontal, collapsable}" class="org-tree"> <div class="org-tree" :class="{horizontal, collapsable}">
<org-tree-node <org-tree-node
v-bind="$listeners" :data="dataCloned"
:button-render="buttonRender" :props="props"
:collapsable="collapsable" :horizontal="horizontal"
:data="dataCloned" :label-width="labelWidth"
:horizontal="horizontal" :collapsable="collapsable"
:label-class-name="labelClassName" :label-class-name="labelClassName"
:label-width="labelWidth" :node-render="nodeRender"
:node-render="nodeRender" :button-render="buttonRender"
:props="props" @on-expand="handleExpand"
@on-expand="handleExpand" @on-node-click="handleNodeClick"
@on-node-click="handleNodeClick"
/> />
</div> </div>
</div> </div>
</template> </template>
<script> <script>
import render from './node' import OrgTreeNode from "./OrgTreeNode"
import {deepClone as clonedeep} from '@/utils'
export default { export default {
name: 'OrgTree', name: 'OrgTree',
components: { components: {OrgTreeNode},
OrgTreeNode: {
render,
functional: true
}
},
props: {
data: {required: true},
props: { props: {
data: { type: Object,
type: Object, default: () => ({
required: true id: 'id',
}, label: 'label',
props: { expand: 'expand',
type: Object, children: 'children'
default: () => ({ })
id: 'id',
label: 'label',
expand: 'expand',
children: 'children'
})
},
horizontal: Boolean,
collapsable: Boolean,
nodeRender: Function,
buttonRender: Function,
labelWidth: [String, Number],
labelClassName: [Function, String],
expandAll: {
type: Boolean,
default: false
}
}, },
horizontal: Boolean,
collapsable: Boolean,
nodeRender: Function,
buttonRender: Function,
labelWidth: [String, Number],
labelClassName: [Function, String],
expandAll: Boolean
},
data() { data() {
return { return {
flatData: {}, flatData: {},
dataCloned: {} dataCloned: {}
} }
}, },
watch: { watch: {
data(newData) { data(newData) {
this._handleData(newData) this._cloneData(newData)
this._mapData(this.dataCloned, item => { this._mapData(this.dataCloned, item => {
const {expand} = this.flatData[item[this.prop_id]] || {} const {expand} = this.flatData[item[this.props.id]] || {}
if (expand) this.$set(item, this.prop_expand, true) if (expand) this.$set(item, this.props.expand, true)
}) })
this._toggleExpand(this.dataCloned, this.expandAll) this._toggleExpand(this.dataCloned, this.expandAll)
},
expandAll(status) {
this._toggleExpand(this.dataCloned, status)
}
}, },
expandAll(status) {
this._toggleExpand(this.dataCloned, status)
}
},
computed: { methods: {
prop_id() { _cloneData(newData) {
return this.props.id this.dataCloned = JSON.parse(JSON.stringify(newData))
},
prop_label() {
return this.props.label
},
prop_expand() {
return this.props.expand
},
prop_children() {
return this.props.children
}
}, },
_setFlatData(data) {
methods: { this.flatData[data[this.props.id]] = data
_handleData(data) { },
this._cloneData(data) /**
}, * @description 工具方法用于遍历树状数据的每个节点 fn为在该节点做的操作其有一个参数即当前节点数据
_cloneData(newData) { */
this.dataCloned = clonedeep(newData) _mapData(data, fn) {
}, fn(data)
_setFlatData(data) { const children = data[this.props.children]
this.flatData[data[this.prop_id]] = data children && children.forEach(child => this._mapData(child, fn))
}, },
/** /**
* @description 工具方法用于遍历树状数据的每个节点 fn为在该节点做的操作其有一个参数即当前节点数据 * @description 用来便利所有节点数据将树状数据扁平化存放到flatData用于数据更新后展开状态的恢复
*/ */
_mapData(data, fn) { _updateExpandStatus() {
fn(data) this._mapData(this.dataCloned, this._setFlatData)
const children = data[this.prop_children] },
if (children) { _toggleExpand(data, status) {
children.forEach(child => { if (Array.isArray(data)) {
this._mapData(child, fn) data.forEach(item => {
}) this.$set(item, this.props.expand, status)
} const children = item[this.props.children]
}, if (children) {
/** this._toggleExpand(children, status)
* @description 用来便利所有节点数据将树状数据扁平化存放到flatData用于数据更新后展开状态的恢复
*/
_updateExpandStatus() {
this._mapData(this.dataCloned, this._setFlatData)
},
collapse(list) {
var _this = this
list.forEach(child => {
if (child[this.prop_expand]) {
child[this.prop_expand] = false
} }
const children = child[this.prop_children]
children && _this.collapse(children)
}) })
}, }
handleExpand(data) { else {
if (this.prop_expand in data) { this.$set(data, this.props.expand, status)
data[this.prop_expand] = !data[this.prop_expand] const children = data[this.props.children]
const children = data[this.prop_children] if (children) {
if (!data[this.prop_expand] && children) { this._toggleExpand(children, status)
this.collapse(children)
}
}
else {
this.$set(data, this.prop_expand, true)
} }
this.$emit('on-expand', data, data[this.prop_expand]) }
this._updateExpandStatus() },
}, collapse(list) {
_toggleExpand(data, status) { list.forEach(child => {
var _this = this if (child[this.props.expand]) {
if (Array.isArray(data)) { child[this.props.expand] = false
data.forEach(item => {
_this.$set(item, this.prop_expand, status)
const children = item[this.prop_children]
if (children) {
_this._toggleExpand(children, status)
}
})
} }
else { const children = child[this.props.children]
_this.$set(data, this.prop_expand, status) children && this.collapse(children)
const children = data[this.prop_children] })
if (children) { },
_this._toggleExpand(children, status) handleExpand(data) {
} if (this.props.expand in data) {
data[this.props.expand] = !data[this.props.expand]
const children = data[this.props.children]
if (!data[this.props.expand] && children) {
this.collapse(children)
} }
},
handleNodeClick(e, data) {
this.$emit('on-node-click', e, data, () => {
this.handleExpand(data)
})
},
toggleExpand() {
this._toggleExpand(this.dataCloned, this.expandAll)
this._updateExpandStatus()
} }
}, else this.$set(data, this.props.expand, true)
mounted() { this.$emit('on-expand', data, data[this.props.expand])
this._handleData(this.data)
this._updateExpandStatus() this._updateExpandStatus()
this._toggleExpand(this.dataCloned, this.expandAll) },
handleNodeClick(e, data) {
this.$emit('on-node-click', e, data, () => this.handleExpand(data))
} }
},
mounted() {
this._cloneData(this.data)
this._updateExpandStatus()
this._toggleExpand(this.dataCloned, this.expandAll)
} }
}
</script> </script>
<style lang="scss" src="./tree.scss"></style> <style lang="scss" src="./tree.scss"></style>

@ -1,261 +1,261 @@
.org-tree-container { .org-tree-container {
display: inline-block; display: inline-block;
padding: 15px; padding: 15px;
}
.org-tree {
display: table;
text-align: center;
&:before, &:after { .org-tree {
content: '';
display: table; display: table;
}
&:after {
clear: both;
}
}
.org-tree-node,
.org-tree-node-children {
position: relative;
margin: 0;
padding: 0;
list-style-type: none;
&:before, &:after {
transition: all .35s;
}
}
.org-tree-node-label {
position: relative;
display: inline-block;
z-index: 1;
.org-tree-node-label-inner {
padding: 10px 15px;
text-align: center; text-align: center;
border-radius: 3px;
box-shadow: 0 1px 5px rgba(0, 0, 0, .15);
}
}
.org-tree-node-btn { &:before, &:after {
position: absolute; content: '';
top: 100%; display: table;
left: 50%; }
width: 20px;
height: 20px;
z-index: 10;
margin-left: -10px;
margin-top: 10px;
background-color: #fff;
border: 1px solid #ccc;
border-radius: 50%;
box-shadow: 0 0 2px rgba(0, 0, 0, .15);
cursor: pointer;
transition: all .35s ease;
&:hover {
background-color: #e7e8e9;
transform: scale(1.15);
}
&:before, &:after {
content: '';
position: absolute;
}
&:before {
top: 50%;
left: 4px;
right: 4px;
height: 0;
border-top: 1px solid #ccc;
}
&:after {
top: 4px;
left: 50%;
bottom: 4px;
width: 0;
border-left: 1px solid #ccc;
}
&.expanded:after {
border: none;
}
}
.org-tree-node {
padding-top: 20px;
display: table-cell;
vertical-align: top;
&.is-leaf, &.collapsed { &:after {
padding-left: 10px; clear: both;
padding-right: 10px; }
} }
&:before, &:after { .org-tree-node,
content: ''; .org-tree-node-children {
position: absolute; position: relative;
top: 0; margin: 0;
left: 0; padding: 0;
width: 50%; list-style-type: none;
height: 19px;
}
&:after { &:before, &:after {
left: 50%; transition: all .35s;
border-left: 1px solid #ddd; }
} }
&:not(:first-child):before, .org-tree-node-label {
&:not(:last-child):after { position: relative;
border-top: 1px solid #ddd; display: inline-block;
z-index: 1;
.org-tree-node-label-inner {
padding: 10px 15px;
text-align: center;
border-radius: 3px;
box-shadow: 0 1px 5px rgba(0, 0, 0, .15);
}
} }
} .org-tree-node-btn {
.collapsable .org-tree-node.collapsed {
padding-bottom: 30px;
.org-tree-node-label:after {
content: '';
position: absolute; position: absolute;
top: 100%; top: 100%;
left: 0; left: 50%;
width: 50%; width: 20px;
height: 20px; height: 20px;
border-right: 1px solid #ddd; z-index: 10;
} margin-left: -10px;
} margin-top: 10px;
background-color: #fff;
.org-tree > .org-tree-node { border: 1px solid #ccc;
padding-top: 0; border-radius: 50%;
box-shadow: 0 0 2px rgba(0, 0, 0, .15);
cursor: pointer;
transition: all .35s ease;
&:hover {
background-color: #e7e8e9;
transform: scale(1.15);
}
&:after { &:before, &:after {
border-left: 0; content: '';
} position: absolute;
} }
.org-tree-node-children { &:before {
padding-top: 20px; top: 50%;
display: table; left: 4px;
right: 4px;
height: 0;
border-top: 1px solid #ccc;
}
&:before { &:after {
content: ''; top: 4px;
position: absolute; left: 50%;
top: 0; bottom: 4px;
left: 50%; width: 0;
width: 0; border-left: 1px solid #ccc;
height: 20px; }
border-left: 1px solid #ddd;
}
&:after { &.expanded:after {
content: ''; border: none;
display: table; }
clear: both;
} }
}
.horizontal {
.org-tree-node { .org-tree-node {
padding-top: 20px;
display: table-cell; display: table-cell;
float: none; vertical-align: top;
padding-top: 0;
padding-left: 20px;
&.is-leaf, &.collapsed { &.is-leaf, &.collapsed {
padding-top: 10px; padding-left: 10px;
padding-bottom: 10px; padding-right: 10px;
} }
&:before, &:after { &:before, &:after {
width: 19px; content: '';
height: 50%; position: absolute;
} top: 0;
&:after {
top: 50%;
left: 0; left: 0;
border-left: 0; width: 50%;
height: 19px;
} }
&:only-child:before { &:after {
top: 1px; left: 50%;
border-bottom: 1px solid #ddd; border-left: 1px solid #ddd;
} }
&:not(:first-child):before, &:not(:first-child):before,
&:not(:last-child):after { &:not(:last-child):after {
border-top: 0;
border-left: 1px solid #ddd;
}
&:not(:only-child):after {
border-top: 1px solid #ddd; border-top: 1px solid #ddd;
} }
.org-tree-node-inner {
display: table;
}
} }
.org-tree-node-label { .collapsable .org-tree-node.collapsed {
display: table-cell; padding-bottom: 30px;
vertical-align: middle;
}
&.collapsable .org-tree-node.collapsed {
padding-right: 30px;
.org-tree-node-label:after { .org-tree-node-label:after {
top: 0; content: '';
left: 100%; position: absolute;
width: 20px; top: 100%;
height: 50%; left: 0;
border-right: 0; width: 50%;
border-bottom: 1px solid #ddd; height: 20px;
border-right: 1px solid #ddd;
} }
} }
.org-tree-node-btn { .org-tree > .org-tree-node {
top: 50%; padding-top: 0;
left: 100%;
margin-top: -11px;
margin-left: 9px;
}
& > .org-tree-node:only-child:before { &:after {
border-bottom: 0; border-left: 0;
}
} }
.org-tree-node-children { .org-tree-node-children {
display: table-cell; padding-top: 20px;
padding-top: 0; display: table;
padding-left: 20px;
&:before { &:before {
top: 50%; content: '';
left: 0; position: absolute;
width: 20px; top: 0;
height: 0; left: 50%;
border-left: 0; width: 0;
border-top: 1px solid #ddd; height: 20px;
border-left: 1px solid #ddd;
} }
&:after { &:after {
display: none; content: '';
display: table;
clear: both;
}
}
.horizontal {
.org-tree-node {
display: table-cell;
float: none;
padding-top: 0;
padding-left: 20px;
&.is-leaf, &.collapsed {
padding-top: 10px;
padding-bottom: 10px;
}
&:before, &:after {
width: 19px;
height: 50%;
}
&:after {
top: 50%;
left: 0;
border-left: 0;
}
&:only-child:before {
top: 1px;
border-bottom: 1px solid #ddd;
}
&:not(:first-child):before,
&:not(:last-child):after {
border-top: 0;
border-left: 1px solid #ddd;
}
&:not(:only-child):after {
border-top: 1px solid #ddd;
}
.org-tree-node-inner {
display: table;
}
}
.org-tree-node-label {
display: table-cell;
vertical-align: middle;
}
&.collapsable .org-tree-node.collapsed {
padding-right: 30px;
.org-tree-node-label:after {
top: 0;
left: 100%;
width: 20px;
height: 50%;
border-right: 0;
border-bottom: 1px solid #ddd;
}
}
.org-tree-node-btn {
top: 50%;
left: 100%;
margin-top: -11px;
margin-left: 9px;
}
& > .org-tree-node:only-child:before {
border-bottom: 0;
} }
& > .org-tree-node { .org-tree-node-children {
display: block; display: table-cell;
padding-top: 0;
padding-left: 20px;
&:before {
top: 50%;
left: 0;
width: 20px;
height: 0;
border-left: 0;
border-top: 1px solid #ddd;
}
&:after {
display: none;
}
& > .org-tree-node {
display: block;
}
} }
} }
} }

@ -1,45 +1,45 @@
<template> <template>
<el-select <el-select
ref="select" ref="select"
:value="value" :value="value"
:multiple="multiple" :multiple="multiple"
:disabled="disabled" :disabled="disabled"
:size="size" :size="size"
:clearable="clearable" :clearable="clearable"
:placeholder="placeholder" :placeholder="placeholder"
:popper-append-to-body="popperAppendToBody" :popper-append-to-body="popperAppendToBody"
:automatic-dropdown="automaticDropdown" :automatic-dropdown="automaticDropdown"
@input="$emit('input',$event)" @input="e => $emit('input',e)"
@change="$emit('change',$event)" @change="e => $emit('change',e)"
@remove-tag="removeTag" @remove-tag="removeTag"
> >
<div class="tree-select-container" slot="empty"> <div class="tree-select-container" slot="empty">
<el-input <el-input
ref="search" ref="search"
v-if="filterable && filterMethod" v-if="filterable && filterMethod"
v-model="search" v-model="search"
size="mini" size="mini"
clearable clearable
@input="onSearch" @input="onSearch"
/> />
<el-tree <el-tree
ref="tree" ref="tree"
:data="data" :data="data"
:node-key="nodeKey" :node-key="nodeKey"
:props="props" :props="props"
:show-checkbox="multiple" :show-checkbox="multiple"
:default-checked-keys="defaultCheckedKeys" :default-checked-keys="defaultCheckedKeys"
:current-node-key="multiple ? undefined : value" :current-node-key="multiple ? undefined : value"
:highlight-current="!multiple" :highlight-current="!multiple"
:expand-on-click-node="false" :expand-on-click-node="false"
:check-on-click-node="multiple" :check-on-click-node="multiple"
:filter-node-method="filterMethod" :filter-node-method="filterMethod"
:accordion="accordion" :accordion="accordion"
@node-click="nodeClick" @node-click="nodeClick"
@check="check" @check="check"
> >
<slot slot-scope="{node,data}"> <slot slot-scope="{node,data}">
<span :class="{'el-tree-node__label':true,'is-disabled':node.disabled}">{{node.label}}</span> <span :class="{'el-tree-node__label':true,'is-disabled':node.disabled}">{{ node.label }}</span>
</slot> </slot>
</el-tree> </el-tree>
</div> </div>
@ -47,168 +47,169 @@
</template> </template>
<script> <script>
import {debounce} from "@/utils" import {debounce} from "@/utils"
import {flatTree} from "@/utils/tree" import {flatTree} from "@/utils/tree"
export default { export default {
name: "TreeSelect", name: "TreeSelect",
props: {
value: {required: true},
data: {type: Array, default: () => []},
multiple: Boolean,
disabled: Boolean,
size: String,
clearable: {type: Boolean, default: true},
placeholder: String,
filterable: Boolean,
filterMethod: Function,
nodeKey: {type: String, default: 'id'},
props: { props: {
value: {required: true}, default() {
data: {type: Array, default: () => []}, return {
multiple: Boolean, children: 'children',
disabled: Boolean, label: 'label',
size: String, disabled: 'disabled'
clearable: {type: Boolean, default: true},
placeholder: String,
filterable: Boolean,
filterMethod: Function,
nodeKey: {type: String, default: 'id'},
props: {
default() {
return {
children: 'children',
label: 'label',
disabled: 'disabled'
}
} }
},
accordion: {type: Boolean, default: true},
popperAppendToBody: Boolean,
automaticDropdown: Boolean
},
data() {
return {
currentNode: null,
search: ''
} }
}, },
accordion: {type: Boolean, default: true},
popperAppendToBody: Boolean,
automaticDropdown: Boolean
},
data() {
return {
currentNode: null,
search: ''
}
},
computed: { computed: {
defaultCheckedKeys() { defaultCheckedKeys() {
return this.multiple ? this.value : [] return this.multiple ? this.value : []
} }
},
watch: {
value(v) {
const method = this.multiple ? 'setCheckedKeys' : 'setCurrentKey'
this.$refs.tree[method](v || null)
if (!v && !this.multiple) this.currentNode = null
}, },
watch: { data() {
value(v) { this.initSelectOptions()
const method = this.multiple ? 'setCheckedKeys' : 'setCurrentKey' }
this.$refs.tree[method](v || null) },
},
methods: {
removeTag(v) {
const tree = this.$refs.tree
const value = [...this.value]
//ID
const parents = []
let removeNode = tree.getNode(v)
while (removeNode && removeNode.parent) {
parents.push(removeNode.parent.key)
removeNode = removeNode.parent
}
data() { //
this.initSelectOptions() for (let i = value.length - 1; i >= 0; i--) {
const isChild = this.getNodeParent(tree.getNode(value[i]), ({key}) => key === v)
if (isChild || parents.includes(value[i])) {
value.splice(i, 1)
}
} }
value.length < this.value.length && this.$emit('input', value)
}, },
methods: { onSearch(v) {
removeTag(v) { this.$refs.tree.filter(v)
const tree = this.$refs.tree },
const value = [...this.value]
//ID
const parents = []
let removeNode = tree.getNode(v)
while (removeNode && removeNode.parent) {
parents.push(removeNode.parent.key)
removeNode = removeNode.parent
}
// nodeClick(data, node) {
for (let i = value.length - 1; i >= 0; i--) { if (this.multiple) return
const isChild = this.getNodeParent(tree.getNode(value[i]), ({key}) => key === v)
if (isChild || parents.includes(value[i])) {
value.splice(i, 1)
}
}
value.length < this.value.length && this.$emit('input', value) if (node.disabled) {
}, return this.$refs.tree.setCurrentKey(null)
}
onSearch(v) { if (this.currentNode === data) {
this.$refs.tree.filter(v) this.currentNode = null
}, this.$refs.tree.setCurrentKey(null)
}
else this.currentNode = data
nodeClick(data, node) { this.$emit('input', this.currentNode && this.currentNode[this.nodeKey], this.currentNode)
if (this.multiple) return this.$refs.select.blur()
},
if (node.disabled) { check(data, {checkedKeys}) {
return this.$refs.tree.setCurrentKey() this.multiple && this.$emit('input', checkedKeys)
} },
if (this.currentNode === data) { getNodeParent(node, predicate) {
this.currentNode = null if (!node || predicate(node)) return node
this.$refs.tree.setCurrentKey() return this.getNodeParent(node.parent, predicate)
}
else this.currentNode = data
this.$emit('input', this.currentNode && this.currentNode[this.nodeKey])
this.$refs.select.blur()
},
check(data, {checkedKeys}) {
this.multiple && this.$emit('input', checkedKeys)
},
getNodeParent(node, predicate) {
if (!node || predicate(node)) return node
return this.getNodeParent(node.parent, predicate)
},
initSelectOptions() {
this.$refs.select.cachedOptions =
flatTree(this.data, this.props.children)
.map(i => ({
value: i[this.nodeKey],
currentLabel: i[this.props.label]
}))
this.selectOptionsTrigger()
},
//el-selectoptionswatch
selectOptionsTrigger() {
const select = this.$refs.select
if (select.$isServer) return
this.$nextTick(() => {
select.broadcast('ElSelectDropdown', 'updatePopper')
})
if (select.multiple) {
select.resetInputHeight()
}
let inputs = select.$el.querySelectorAll('input')
if ([].indexOf.call(inputs, document.activeElement) === -1) {
select.setSelected()
}
if (select.defaultFirstOption && (select.filterable || select.remote) && select.filteredOptionsCount) {
select.checkDefaultFirstOption()
}
}
}, },
created() { initSelectOptions() {
this.onSearch = debounce(this.onSearch) this.$refs.select.cachedOptions =
flatTree(this.data, this.props.children)
.map(i => ({
value: i[this.nodeKey],
currentLabel: i[this.props.label]
}))
this.selectOptionsTrigger()
}, },
mounted() { //el-selectoptionswatch
this.currentNode = this.$refs.tree.getCurrentNode() selectOptionsTrigger() {
this.initSelectOptions() const select = this.$refs.select
if (select.$isServer) return
this.$nextTick(() => {
select.broadcast('ElSelectDropdown', 'updatePopper')
})
if (select.multiple) {
select.resetInputHeight()
}
let inputs = select.$el.querySelectorAll('input')
if ([].indexOf.call(inputs, document.activeElement) === -1) {
select.setSelected()
}
if (select.defaultFirstOption && (select.filterable || select.remote) && select.filteredOptionsCount) {
select.checkDefaultFirstOption()
}
} }
},
created() {
this.onSearch = debounce(this.onSearch)
},
mounted() {
this.currentNode = this.$refs.tree.getCurrentNode()
this.initSelectOptions()
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
.tree-select-container { .tree-select-container {
padding: 8px; padding: 8px;
> .el-input { > .el-input {
margin-bottom: 8px; margin-bottom: 8px;
} }
.el-tree-node__label.is-disabled { .el-tree-node__label.is-disabled {
background-color: #edf2fc; background-color: #edf2fc;
border-color: #dcdfe6; border-color: #dcdfe6;
cursor: not-allowed; cursor: not-allowed;
}
} }
}
</style> </style>

@ -44,7 +44,7 @@ service.interceptors.response.use(
//未登录 //未登录
if (res.status === 401) { if (res.status === 401) {
if (store.state.user.prepare_logout) return Promise.reject() if (store.state.user.prepareLogout) return Promise.reject()
return MessageBox.alert('请登录后重试', { return MessageBox.alert('请登录后重试', {
type: 'warning', type: 'warning',
beforeClose: (action, instance, done) => { beforeClose: (action, instance, done) => {

@ -72,7 +72,7 @@
}, },
computed: { computed: {
...mapState('user', ['avatar', 'name', 'prepare_logout']), ...mapState('user', ['avatar', 'name', 'prepareLogout']),
...mapState('setting', ['sidebarCollapse', 'showBreadcrumb']), ...mapState('setting', ['sidebarCollapse', 'showBreadcrumb']),
@ -91,7 +91,7 @@
}, },
logout() { logout() {
if (this.prepare_logout) return if (this.prepareLogout) return
elConfirm('确认退出?') elConfirm('确认退出?')
.then(() => this.$store.dispatch('user/logout')) .then(() => this.$store.dispatch('user/logout'))
} }

@ -38,7 +38,7 @@ export default {
data() { data() {
return { return {
searchForm: { searchForm: {
id_fuzzy: null, idFuzzy: null,
cname: null, cname: null,
vname: null vname: null
}, },

@ -40,7 +40,7 @@ const mutations = {
map[item.url] = item.id map[item.url] = item.id
return map return map
}, {}) }, {})
state.tree = createTree(data.filter(resource => resource.admin !== 1)) state.tree = createTree(data.filter(resource => resource.admin === false))
}, },
hasInitRoutes(state, sign) { hasInitRoutes(state, sign) {
state.hasInitRoutes = sign state.hasInitRoutes = sign
@ -114,7 +114,7 @@ function addFullPath(routes, basePath = '/') {
//获取经过权限控制后的路由 //获取经过权限控制后的路由
function getAuthorizedRoutes({resources, admin}) { function getAuthorizedRoutes({resources, admin}) {
if (admin === 1) return finalAuthorityRoutes if (admin === true) return finalAuthorityRoutes
if (!resources) return [] if (!resources) return []
const arr = JSON.parse(JSON.stringify(finalAuthorityRoutes)) const arr = JSON.parse(JSON.stringify(finalAuthorityRoutes))
filter(arr, i => !needAuth(i) || i.fullPath in resources) filter(arr, i => !needAuth(i) || i.fullPath in resources)

@ -74,7 +74,7 @@ function defaultEventBind(socket, {state, commit, dispatch}) {
function customEventBind(socket, {state, commit, dispatch, rootState}) { function customEventBind(socket, {state, commit, dispatch, rootState}) {
socket.on('logout', msg => { socket.on('logout', msg => {
if (rootState.user.prepare_logout) return if (rootState.user.prepareLogout) return
MessageBox.alert(msg || '你已被强制下线,请重新登陆', { MessageBox.alert(msg || '你已被强制下线,请重新登陆', {
type: 'warning', type: 'warning',
beforeClose: (action, instance, done) => { beforeClose: (action, instance, done) => {

@ -8,14 +8,15 @@ const user = getUser()
const state = { const state = {
//是否在退出的过程中,避免重复弹框 //是否在退出的过程中,避免重复弹框
prepare_logout: '', prepareLogout: '',
/*用户基本信息*/ /*用户基本信息*/
id: emptyOrDefault(user.id, null), id: emptyOrDefault(user.id, null),
name: emptyOrDefault(user.name), name: emptyOrDefault(user.name),
role_name: emptyOrDefault(user.role_name), roleName: emptyOrDefault(user.roleName),
deptName: emptyOrDefault(user.deptName),
avatar: emptyOrDefault(user.avatar), avatar: emptyOrDefault(user.avatar),
admin: emptyOrDefault(user.admin), admin: emptyOrDefault(user.admin, false),
token: emptyOrDefault(user.token), token: emptyOrDefault(user.token),
resources: emptyOrDefault(user.resources, {}) resources: emptyOrDefault(user.resources, {})
} }
@ -28,7 +29,7 @@ const actions = {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
login({username: username.trim(), password}) login({username: username.trim(), password})
.then(user => { .then(user => {
user.admin === 1 && (user.role_name = '超级管理员') user.admin === true && (user.roleName = '超级管理员')
user.avatar = autoCompleteUrl(user.avatar) user.avatar = autoCompleteUrl(user.avatar)
commit('$all', user) commit('$all', user)
setUser(user) setUser(user)
@ -41,8 +42,8 @@ const actions = {
logout({commit, state, dispatch}) { logout({commit, state, dispatch}) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (state.prepare_logout) return Promise.reject() if (state.prepareLogout) return Promise.reject()
commit('prepare_logout', 'yes') commit('prepareLogout', 'yes')
logout(state.token) logout(state.token)
.then(() => { .then(() => {
commit('resource/hasInitRoutes', false, {root: true}) commit('resource/hasInitRoutes', false, {root: true})
@ -57,7 +58,7 @@ const actions = {
window.location.reload() window.location.reload()
}) })
.catch(error => reject(error)) .catch(error => reject(error))
.finally(() => commit('prepare_logout', '')) .finally(() => commit('prepareLogout', ''))
}) })
}, },

@ -6,7 +6,7 @@ export function needAuth(route) {
} }
export function auth(path) { export function auth(path) {
if (store.state.user.admin === 1) return true if (store.state.user.admin === true) return true
const resourceMap = store.state.resource.dataMap const resourceMap = store.state.resource.dataMap
if (!(path in resourceMap)) return true if (!(path in resourceMap)) return true

@ -173,69 +173,3 @@ export function delAllUrlParam() {
history.replaceState(null, null, [...href].join('')) history.replaceState(null, null, [...href].join(''))
} }
} }
export function deepClone(data) {
if (data === null || data === undefined) {
return undefined
}
const dataType = getDataType(data)
if (dataType === "Date") {
let clonedDate = new Date()
clonedDate.setTime(data.getTime())
return clonedDate
}
if (dataType === "Object") {
if (isCyclic(data) === true) {
return data
}
let copiedObject = {}
Object.keys(data).forEach(key => copiedObject[key] = deepClone(data[key]))
return copiedObject
}
if (dataType === "Array") {
let copiedArray = []
for (let i = 0; i < data.length; i++) {
copiedArray.push(deepClone(data[i]))
}
return copiedArray
}
else return data
}
function getDataType(data) {
return Object.prototype.toString.call(data).slice(8, -1)
}
function isCyclic(data) {
let seenObjects = []
function detect(data) {
if (data && getDataType(data) === "Object") {
if (seenObjects.indexOf(data) !== -1) {
return true
}
seenObjects.push(data)
for (let key in data) {
if (data.hasOwnProperty(key) && detect(data[key])) {
return true
}
}
}
return false
}
return detect(data)
}

@ -6,12 +6,12 @@
</span> </span>
<el-input <el-input
ref="username" ref="username"
v-model="form.username" v-model="form.username"
:maxlength="20" :maxlength="20"
name="username" name="username"
placeholder="请输入用户名" placeholder="请输入用户名"
type="text" type="text"
/> />
</el-form-item> </el-form-item>
@ -23,14 +23,14 @@
</el-tooltip> </el-tooltip>
<el-input <el-input
ref="password" ref="password"
v-model="form.password" v-model="form.password"
:key="passwordType" :key="passwordType"
:type="passwordType" :type="passwordType"
:maxlength="20" :maxlength="20"
name="password" name="password"
placeholder="请输入密码" placeholder="请输入密码"
@keyup.enter.native="login" @keyup.enter.native="login"
/> />
<span @click="showPwd" class="show-pwd"> <span @click="showPwd" class="show-pwd">
@ -38,19 +38,14 @@
</span> </span>
</el-form-item> </el-form-item>
<el-button <el-button :loading="loading" class="submit-btn" type="primary" @click="login">
:loading="loading" {{ loading ? '登 录 中...' : '登 录' }}
class="submit-btn"
type="primary"
@click="login"
>
{{loading ? '登 录 中...' : '登 录'}}
</el-button> </el-button>
<div class="flex" style="margin-top: 20px"> <div class="flex" style="margin-top: 20px">
<p class="other-ways"> <p class="other-ways">
其他方式登录 其他方式登录
<svg-icon v-for="i in otherWays" :key="i" :icon="i" @click="thirdPartyLogin(i)"/> <svg-icon v-for="i in otherWays" :key="i" :icon="i" @click="() => thirdPartyLogin(i)"/>
</p> </p>
<el-button type="text" @click="register">注册账户</el-button> <el-button type="text" @click="register">注册账户</el-button>
@ -59,88 +54,85 @@
</template> </template>
<script> <script>
import md5 from "js-md5" import md5 from "js-md5"
import {elSuccess} from "@/utils/message" import {elSuccess} from "@/utils/message"
import {isEmpty} from "@/utils" import {isEmpty} from "@/utils"
export default { export default {
name: "LoginForm", name: "LoginForm",
data() { data() {
return { return {
loading: false, loading: false,
form: { form: {
username: '', username: '',
password: '' password: ''
},
rules: {
username: [{required: true, message: '请输入用户名', trigger: 'change'}],
password: [
{required: true, message: '请输入密码', trigger: 'change'},
{min: 6, max: 32, message: '请输入6-32位的密码', trigger: 'change'}
]
},
passwordType: 'password',
capsTooltip: false,
otherWays: ['qq']
}
},
methods: {
showPwd() {
this.passwordType = this.passwordType === 'password' ? '' : 'password'
this.$nextTick(() => this.$refs.password.focus())
}, },
rules: {
login() { username: [{required: true, message: '请输入用户名', trigger: 'change'}],
if (this.loading) return password: [{required: true, message: '请输入密码', trigger: 'change'}]
this.$refs.form.validate(valid => {
if (!valid) return
this.loading = true
this.$puzzleVerify()
.then(() => this.$store.dispatch('user/login', {
...this.form,
password: md5(this.form.password)
}))
.then(() => this.success())
.catch(() => this.loading = false)
})
}, },
passwordType: 'password',
capsTooltip: false,
otherWays: ['qq']
}
},
register() { methods: {
!this.loading && this.$router.push('/register') showPwd() {
}, this.passwordType = this.passwordType === 'password' ? '' : 'password'
this.$nextTick(() => this.$refs.password.focus())
},
success() { login() {
elSuccess('登陆成功') if (this.loading) return
const redirect = this.$route.query.redirect || '/' this.$refs.form.validate(valid => {
//0.2s if (!valid) return
setTimeout(() => this.$router.push(redirect), 200) this.loading = true
}, this.$puzzleVerify()
.then(() => this.$store.dispatch('user/login', {
...this.form,
password: md5(this.form.password)
}))
.then(() => this.success())
.catch(() => this.loading = false)
})
},
thirdPartyLogin(channel) { register() {
this.$message.info('假装可以第三方登录') !this.loading && this.$router.push('/register')
}, },
capsLockTip({keyCode}) { success() {
if (keyCode === 20) this.capsTooltip = !this.capsTooltip elSuccess('登陆成功')
}, const redirect = this.$route.query.redirect || '/'
//0.2s
setTimeout(() => this.$router.push(redirect), 200)
},
addCapsLockEvent() { thirdPartyLogin(channel) {
document.addEventListener('keyup', this.capsLockTip) this.$message.info('假装可以第三方登录')
}, },
removeEvent() { capsLockTip({keyCode}) {
document.removeEventListener('keyup', this.addCapsLockEvent) if (keyCode === 20) this.capsTooltip = !this.capsTooltip
}
}, },
mounted() { addCapsLockEvent() {
this.addCapsLockEvent() document.addEventListener('keyup', this.capsLockTip)
const key = isEmpty(this.form.username) ? 'username' : 'password' },
this.$refs[key].focus()
this.$once('hook:beforeDestroy', this.removeEvent) removeEvent() {
document.removeEventListener('keyup', this.addCapsLockEvent)
} }
},
mounted() {
this.addCapsLockEvent()
const key = isEmpty(this.form.username) ? 'username' : 'password'
this.$refs[key].focus()
this.$once('hook:beforeDestroy', this.removeEvent)
} }
}
</script> </script>

@ -1,7 +1,7 @@
<template> <template>
<div style="width: 300px"> <div style="width: 300px">
<div class="tip-row">单选</div> <div class="tip-row">单选</div>
<tree-select v-model="single" :data="data" filterable :filter-method="filter"/> <tree-select v-model="single" :data="data" filterable :filter-method="filter" @input="singleInput"/>
<div class="tip-row">多选</div> <div class="tip-row">多选</div>
<tree-select v-model="multiple" :data="data" multiple filterable :filter-method="filter"/> <tree-select v-model="multiple" :data="data" multiple filterable :filter-method="filter"/>
@ -30,6 +30,10 @@
methods: { methods: {
filter(v, data) { filter(v, data) {
return data.label.includes(v) return data.label.includes(v)
},
singleInput(v,data) {
console.log(data)
} }
} }
} }

@ -28,7 +28,7 @@
@selection-change="row._selection=$event" @selection-change="row._selection=$event"
> >
<el-table-column <el-table-column
:selectable="p=>p.remain_num>0" :selectable="p=>p.remainNum>0"
align="center" align="center"
type="selection" type="selection"
width="60" width="60"
@ -36,7 +36,7 @@
<el-table-column align="center" label="#" type="index" width="80"/> <el-table-column align="center" label="#" type="index" width="80"/>
<el-table-column align="center" label="商品" prop="cname" show-overflow-tooltip/> <el-table-column align="center" label="商品" prop="cname" show-overflow-tooltip/>
<el-table-column align="center" label="采购数量" prop="num"/> <el-table-column align="center" label="采购数量" prop="num"/>
<el-table-column align="center" label="未入库数量" prop="remain_num"/> <el-table-column align="center" label="未入库数量" prop="remainNum"/>
</el-table> </el-table>
<el-button <el-button
size="small" size="small"

@ -140,12 +140,12 @@
pid: this.form.id, pid: this.form.id,
cid: i.cid, cid: i.cid,
cname: i.cname, cname: i.cname,
num: i.remain_num num: i.remainNum
})) }))
}, },
changeInboundNum(nv, ov, row) { changeInboundNum(nv, ov, row) {
let parentSub = this.parentSubList.find(i => i.cid === row.cid) let parentSub = this.parentSubList.find(i => i.cid === row.cid)
if (!parentSub || nv > parentSub.remain_num) { if (!parentSub || nv > parentSub.remainNum) {
return elAlert(`${row.cname}的入库数量超出采购数量`, () => row.num = ov) return elAlert(`${row.cname}的入库数量超出采购数量`, () => row.num = ov)
} }
}, },
@ -171,7 +171,7 @@
if (!parentSub) { if (!parentSub) {
return `${index}个商品不在采购订单中` return `${index}个商品不在采购订单中`
} }
if (parentSub.remain_num < sub.num) { if (parentSub.remainNum < sub.num) {
return `${index}个商品的入库数量超出采购数量` return `${index}个商品的入库数量超出采购数量`
} }
index++ index++

@ -2,10 +2,10 @@
<el-card v-loading="config.operating"> <el-card v-loading="config.operating">
<search-form> <search-form>
<search-form-item label="单 号:"> <search-form-item label="单 号:">
<el-input v-model="searchForm.id_fuzzy" clearable maxlength="50"/> <el-input v-model="searchForm.idFuzzy" clearable maxlength="50"/>
</search-form-item> </search-form-item>
<search-form-item label="采购订单:"> <search-form-item label="采购订单:">
<el-input v-model="searchForm.pid_fuzzy" clearable maxlength="50"/> <el-input v-model="searchForm.pidFuzzy" clearable maxlength="50"/>
</search-form-item> </search-form-item>
<search-form-item label="创建人:"> <search-form-item label="创建人:">
<el-input v-model="searchForm.cname" clearable maxlength="50"/> <el-input v-model="searchForm.cname" clearable maxlength="50"/>
@ -113,7 +113,7 @@
baseUrl, baseUrl,
api: {search, del, getSubById}, api: {search, del, getSubById},
searchForm: { searchForm: {
pid_fuzzy: null pidFuzzy: null
}, },
excel: { excel: {
column: [ column: [

@ -59,11 +59,11 @@
<span v-else>{{row.price}}</span> <span v-else>{{row.price}}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column v-if="form.id" align="center" label="剩余未入库" prop="remain_num"/> <el-table-column v-if="form.id" align="center" label="剩余未入库" prop="remainNum"/>
<el-table-column v-if="form.id" align="center" label="入库情况" width="120"> <el-table-column v-if="form.id" align="center" label="入库情况" width="120">
<template v-slot="{row}"> <template v-slot="{row}">
<span :class="{success:row.remain_num===0}" class="dot"/> <span :class="{success:row.remainNum===0}" class="dot"/>
{{row.remain_num===0?'已全部入库':'未全部入库'}} {{row.remainNum===0?'已全部入库':'未全部入库'}}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column v-if="canSave" align="center" label="操作" width="140"> <el-table-column v-if="canSave" align="center" label="操作" width="140">

@ -2,7 +2,7 @@
<el-card v-loading="config.operating"> <el-card v-loading="config.operating">
<search-form> <search-form>
<search-form-item label="单 号:"> <search-form-item label="单 号:">
<el-input v-model="searchForm.id_fuzzy" clearable maxlength="50"/> <el-input v-model="searchForm.idFuzzy" clearable maxlength="50"/>
</search-form-item> </search-form-item>
<search-form-item label="供应商:"> <search-form-item label="供应商:">
<el-input v-model="searchForm.sname" clearable maxlength="50"/> <el-input v-model="searchForm.sname" clearable maxlength="50"/>
@ -80,7 +80,7 @@
<el-table-column align="center" label="商品" prop="cname" show-overflow-tooltip/> <el-table-column align="center" label="商品" prop="cname" show-overflow-tooltip/>
<el-table-column align="center" label="采购数量" prop="num"/> <el-table-column align="center" label="采购数量" prop="num"/>
<el-table-column align="center" label="采购单价" prop="price"/> <el-table-column align="center" label="采购单价" prop="price"/>
<el-table-column align="center" label="未入库数量" prop="remain_num"/> <el-table-column align="center" label="未入库数量" prop="remainNum"/>
</el-table> </el-table>
</template> </template>
</el-table-column> </el-table-column>

@ -28,7 +28,7 @@
> >
<el-table-column align="center" type="selection" reserve-selection width="80"/> <el-table-column align="center" type="selection" reserve-selection width="80"/>
<el-table-column align="center" label="商品分类" prop="cname" show-overflow-tooltip/> <el-table-column align="center" label="商品分类" prop="cname" show-overflow-tooltip/>
<el-table-column align="center" label="库存数量" prop="total_num" show-overflow-tooltip/> <el-table-column align="center" label="库存数量" prop="totalNum" show-overflow-tooltip/>
</el-table> </el-table>
<el-pagination <el-pagination

@ -13,11 +13,11 @@
</collapse-card> </collapse-card>
<collapse-card header="基础信息"> <collapse-card header="基础信息">
<abstract-form-item label="客 户:" prop="customer_name"> <abstract-form-item label="客 户:" prop="customerName">
<el-input v-if="canSave" :value="form.customer_name" readonly> <el-input v-if="canSave" :value="form.customerName" readonly>
<el-button slot="append" @click="customerDialog=true">选择</el-button> <el-button slot="append" @click="customerDialog=true">选择</el-button>
</el-input> </el-input>
<template v-else>{{form.customer_name}}</template> <template v-else>{{form.customerName}}</template>
</abstract-form-item> </abstract-form-item>
</collapse-card> </collapse-card>
@ -49,11 +49,11 @@
<span v-else>{{row.price}}</span> <span v-else>{{row.price}}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="剩余未出库" prop="remain_num"/> <el-table-column align="center" label="剩余未出库" prop="remainNum"/>
<el-table-column v-if="form.id" align="center" label="出库情况" width="120"> <el-table-column v-if="form.id" align="center" label="出库情况" width="120">
<template v-slot="{row}"> <template v-slot="{row}">
<span :class="{success:row.remain_num===0}" class="dot"/> <span :class="{success:row.remainNum===0}" class="dot"/>
{{row.remain_num===0?'已全部出库':'未全部出库'}} {{row.remainNum===0?'已全部出库':'未全部出库'}}
</template> </template>
</el-table-column> </el-table-column>
<div v-if="canSave" slot="append" class="table-add-btn"> <div v-if="canSave" slot="append" class="table-add-btn">
@ -132,14 +132,14 @@
getById, add, update, commit, withdraw, pass, reject getById, add, update, commit, withdraw, pass, reject
}, },
form: { form: {
customer_id: null, customerId: null,
customer_name: null, customerName: null,
finish: 0, finish: 0,
ftime: null, ftime: null,
total: 0 total: 0
}, },
rules: { rules: {
customer_name: [{required: true, message: '客户不能为空', trigger: 'change'}] customerName: [{required: true, message: '客户不能为空', trigger: 'change'}]
}, },
customerDialog: false, customerDialog: false,
stockDialog: false stockDialog: false
@ -173,12 +173,12 @@
methods: { methods: {
selectCustomer(row) { selectCustomer(row) {
this.form.customer_id = row.id this.form.customerId = row.id
this.form.customer_name = row.name this.form.customerName = row.name
this.customerDialog = false this.customerDialog = false
}, },
selectStock(stocks) { selectStock(stocks) {
this.form.data = stocks.map(i => ({cid: i.cid, cname: i.cname, num: i.total_num, price: 0})) this.form.data = stocks.map(i => ({cid: i.cid, cname: i.cname, num: i.totalNum, price: 0}))
this.stockDialog = false this.stockDialog = false
}, },
addSub() { addSub() {
@ -187,7 +187,7 @@
cname: null, cname: null,
num: 0, num: 0,
price: 0, price: 0,
remain_num: 0 remainNum: 0
}) })
}, },
delSub(row, index) { delSub(row, index) {

@ -2,10 +2,10 @@
<el-card v-loading="config.operating"> <el-card v-loading="config.operating">
<search-form> <search-form>
<search-form-item label="单 号:"> <search-form-item label="单 号:">
<el-input v-model="searchForm.id_fuzzy" clearable maxlength="50"/> <el-input v-model="searchForm.idFuzzy" clearable maxlength="50"/>
</search-form-item> </search-form-item>
<search-form-item label="客 户:"> <search-form-item label="客 户:">
<el-input v-model="searchForm.customer_name" clearable maxlength="50"/> <el-input v-model="searchForm.customerName" clearable maxlength="50"/>
</search-form-item> </search-form-item>
<search-form-item label="创建人:"> <search-form-item label="创建人:">
<el-input v-model="searchForm.cname" clearable maxlength="50"/> <el-input v-model="searchForm.cname" clearable maxlength="50"/>
@ -83,13 +83,13 @@
<el-table-column align="center" label="商品" prop="cname" show-overflow-tooltip/> <el-table-column align="center" label="商品" prop="cname" show-overflow-tooltip/>
<el-table-column align="center" label="销售数量" prop="num"/> <el-table-column align="center" label="销售数量" prop="num"/>
<el-table-column align="center" label="销售单价" prop="price"/> <el-table-column align="center" label="销售单价" prop="price"/>
<el-table-column align="center" label="未出库数量" prop="remain_num"/> <el-table-column align="center" label="未出库数量" prop="remainNum"/>
</el-table> </el-table>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="#" type="index" width="80"/> <el-table-column align="center" label="#" type="index" width="80"/>
<el-table-column align="center" label="单 号" prop="id" width="160" show-overflow-tooltip/> <el-table-column align="center" label="单 号" prop="id" width="160" show-overflow-tooltip/>
<el-table-column align="center" label="客 户" prop="customer_name" show-overflow-tooltip/> <el-table-column align="center" label="客 户" prop="customerName" show-overflow-tooltip/>
<el-table-column align="center" label="创建人" prop="cname" show-overflow-tooltip/> <el-table-column align="center" label="创建人" prop="cname" show-overflow-tooltip/>
<el-table-column align="center" label="创建时间" width="150" show-overflow-tooltip> <el-table-column align="center" label="创建时间" width="150" show-overflow-tooltip>
<template v-slot="{row}">{{row.ctime | timestamp2Date}}</template> <template v-slot="{row}">{{row.ctime | timestamp2Date}}</template>
@ -144,7 +144,7 @@
return { return {
baseUrl, baseUrl,
searchForm: { searchForm: {
customer_name: null customerName: null
}, },
temp: { temp: {
finish: [], finish: [],
@ -154,7 +154,7 @@
excel: { excel: {
column: [ column: [
{header: '序号', prop: 'id'}, {header: '序号', prop: 'id'},
{header: '客户', prop: 'customer_name', width: 30}, {header: '客户', prop: 'customerName', width: 30},
{header: '创建人', prop: 'cname'}, {header: '创建人', prop: 'cname'},
{header: '创建时间', prop: 'ctime'}, {header: '创建时间', prop: 'ctime'},
{header: '审核人', prop: 'vname'}, {header: '审核人', prop: 'vname'},

@ -28,7 +28,7 @@
@selection-change="row._selection=$event" @selection-change="row._selection=$event"
> >
<el-table-column <el-table-column
:selectable="p=>p.remain_num>0" :selectable="p=>p.remainNum>0"
align="center" align="center"
type="selection" type="selection"
width="60" width="60"
@ -36,7 +36,7 @@
<el-table-column align="center" label="#" type="index" width="80"/> <el-table-column align="center" label="#" type="index" width="80"/>
<el-table-column align="center" label="商品" prop="cname" show-overflow-tooltip/> <el-table-column align="center" label="商品" prop="cname" show-overflow-tooltip/>
<el-table-column align="center" label="销售数量" prop="num"/> <el-table-column align="center" label="销售数量" prop="num"/>
<el-table-column align="center" label="未出库数量" prop="remain_num"/> <el-table-column align="center" label="未出库数量" prop="remainNum"/>
</el-table> </el-table>
<el-button <el-button
size="small" size="small"

@ -52,7 +52,7 @@
<el-table-column align="center" label="#" type="index" width="80"/> <el-table-column align="center" label="#" type="index" width="80"/>
<el-table-column align="center" label="商 品" prop="cname"/> <el-table-column align="center" label="商 品" prop="cname"/>
<el-table-column align="center" label="出库数量" prop="num" width="150"/> <el-table-column align="center" label="出库数量" prop="num" width="150"/>
<el-table-column align="center" label="订单剩余未出库数量" prop="remain_num" width="250"/> <el-table-column align="center" label="订单剩余未出库数量" prop="remainNum" width="250"/>
</abstract-table> </abstract-table>
</collapse-card> </collapse-card>
@ -203,19 +203,19 @@
selectStock(stocks) { selectStock(stocks) {
let res = [] let res = []
let outboundNum = 0 let outboundNum = 0
let remain_num = this.row.remain_num let remainNum = this.row.remainNum
for (let stock of stocks) { for (let stock of stocks) {
if (remain_num <= 0) break if (remainNum <= 0) break
let item = { let item = {
...stock, ...stock,
sid: stock.id, sid: stock.id,
pid: this.form.id, pid: this.form.id,
max_num: stock.num, max_num: stock.num,
num: stock.num > remain_num ? remain_num : stock.num num: stock.num > remainNum ? remainNum : stock.num
} }
outboundNum = plus(outboundNum, item.num) outboundNum = plus(outboundNum, item.num)
res.push(item) res.push(item)
remain_num -= stock.num remainNum -= stock.num
} }
this.row.num = outboundNum this.row.num = outboundNum
this.row.data = res this.row.data = res
@ -243,8 +243,8 @@
if (!outboundNum) return if (!outboundNum) return
if (outboundNum > displayTableRow.remain_num) { if (outboundNum > displayTableRow.remainNum) {
let correctNum = sub(displayTableRow.remain_num, sub(outboundNum, nv)) let correctNum = sub(displayTableRow.remainNum, sub(outboundNum, nv))
if (correctNum < 0) correctNum = 0 if (correctNum < 0) correctNum = 0
else if (correctNum > row.max_num) correctNum = row.max_num else if (correctNum > row.max_num) correctNum = row.max_num
return elAlert(`${displayTableRow.cname}的出库数量超出销售数量`, () => row.num = ov) return elAlert(`${displayTableRow.cname}的出库数量超出销售数量`, () => row.num = ov)

@ -2,10 +2,10 @@
<el-card v-loading="config.operating"> <el-card v-loading="config.operating">
<search-form> <search-form>
<search-form-item label="单 号:"> <search-form-item label="单 号:">
<el-input v-model="searchForm.id_fuzzy" clearable maxlength="50"/> <el-input v-model="searchForm.idFuzzy" clearable maxlength="50"/>
</search-form-item> </search-form-item>
<search-form-item label="销售订单:"> <search-form-item label="销售订单:">
<el-input v-model="searchForm.pid_fuzzy" clearable maxlength="50"/> <el-input v-model="searchForm.pidFuzzy" clearable maxlength="50"/>
</search-form-item> </search-form-item>
<search-form-item label="创建人:"> <search-form-item label="创建人:">
<el-input v-model="searchForm.cname" clearable maxlength="50"/> <el-input v-model="searchForm.cname" clearable maxlength="50"/>
@ -117,7 +117,7 @@
baseUrl, baseUrl,
api: {search, del, getSubById}, api: {search, del, getSubById},
searchForm: { searchForm: {
pid_fuzzy: null pidFuzzy: null
}, },
excel: { excel: {
column: [ column: [

@ -47,8 +47,8 @@
<el-table-column align="center" label="商品分类"> <el-table-column align="center" label="商品分类">
<el-link slot-scope="{row}" @click="more(row)">{{row.cname}}</el-link> <el-link slot-scope="{row}" @click="more(row)">{{row.cname}}</el-link>
</el-table-column> </el-table-column>
<el-table-column align="center" label="库存数量" prop="total_num" show-overflow-tooltip/> <el-table-column align="center" label="库存数量" prop="totalNum" show-overflow-tooltip/>
<el-table-column align="center" label="总 值" prop="total_price" show-overflow-tooltip/> <el-table-column align="center" label="总 值" prop="totalPrice" show-overflow-tooltip/>
</abstract-table> </abstract-table>
<el-pagination <el-pagination
@ -104,14 +104,14 @@
columns: [ columns: [
{header: '序号', prop: 'id', width: 20, merge: true}, {header: '序号', prop: 'id', width: 20, merge: true},
{header: '分类名称', prop: 'cname', width: 20, merge: true}, {header: '分类名称', prop: 'cname', width: 20, merge: true},
{header: '库存总数', prop: 'total_num', width: 20, merge: true}, {header: '库存总数', prop: 'totalNum', width: 20, merge: true},
{header: '库存总值', prop: 'total_price', width: 20, merge: true}, {header: '库存总值', prop: 'totalPrice', width: 20, merge: true},
{header: '采购订单号', prop: 'cgddid', width: 20, merge: true}, {header: '采购订单号', prop: 'cgddid', width: 20, merge: true},
{header: '采购单价', prop: 'cg_price', width: 20, merge: true}, {header: '采购单价', prop: 'cgPrice', width: 20, merge: true},
{header: '采购数量', prop: 'cg_num', width: 20, merge: true}, {header: '采购数量', prop: 'cgNum', width: 20, merge: true},
{header: '采购入库单号', prop: 'cgrkid', width: 20}, {header: '采购入库单号', prop: 'cgrkid', width: 20},
{header: '入库时间', prop: 'ctime', width: 20}, {header: '入库时间', prop: 'ctime', width: 20},
{header: '入库数量', prop: 'rk_num', width: 20}, {header: '入库数量', prop: 'rkNum', width: 20},
], ],
merge: {primaryKey: 'cid', orderKey: 'id'} merge: {primaryKey: 'cid', orderKey: 'id'}
} }
@ -132,7 +132,7 @@
summary({data}) { summary({data}) {
let sum = ['合计', '', '', 0] let sum = ['合计', '', '', 0]
data.forEach(i => { data.forEach(i => {
sum[3] = plus(sum[3], i.total_price) sum[3] = plus(sum[3], i.totalPrice)
}) })
return sum return sum
}, },

@ -1,125 +1,127 @@
<template> <template>
<div :style="orgTreeStyle" class="org-tree-wrapper" @mousedown="mousedownView"> <div :style="orgTreeStyle" class="org-tree-wrapper" @mousedown="mousedownView">
<org-tree :data="data" :horizontal="horizontal" collapsable expand-all> <org-tree :data="data" :horizontal="horizontal" collapsable expand-all @on-node-click="nodeClick">
<div <div
slot-scope="data" slot-scope="data"
:class="{'has-children-label':data.children && data.children.length}" :class="{'has-children-label':data.children && data.children.length}"
class="custom-org-node" class="custom-org-node"
@contextmenu.prevent="$emit('node-contextmenu',$event,data)" @contextmenu.prevent="e => $emit('node-contextmenu',e,data)"
> >
{{data.label}} {{ data.label }}
</div> </div>
</org-tree> </org-tree>
</div> </div>
</template> </template>
<script> <script>
import OrgTree from '@/components/OrgTree' import OrgTree from '@/components/OrgTree'
export default { export default {
name: "OrgTreeView", name: "OrgTreeView",
components: {OrgTree}, components: {OrgTree},
props: { props: {
data: {type: Object, default: () => ({})}, data: {type: Object, default: () => ({})},
horizontal: Boolean, horizontal: Boolean,
collapsable: Boolean, collapsable: Boolean,
zoom: {type: Number, default: 1}, zoom: {type: Number, default: 1},
}, },
data() {
return {
orgTreeOffsetLeft: 0,
orgTreeOffsetTop: 0,
initPageX: 0,
initPageY: 0,
oldMarginLeft: 0,
oldMarginTop: 0,
canMove: false,
expandAll: true
}
},
data() { computed: {
orgTreeStyle() {
return { return {
orgTreeOffsetLeft: 0, transform: `translate(-50%, -50%) scale(${this.zoom}, ${this.zoom})`,
orgTreeOffsetTop: 0, marginLeft: `${this.orgTreeOffsetLeft}px`,
initPageX: 0, marginTop: `${this.orgTreeOffsetTop}px`
initPageY: 0,
oldMarginLeft: 0,
oldMarginTop: 0,
canMove: false,
expandAll: true
} }
}, }
},
computed: { methods: {
orgTreeStyle() { nodeClick(e, data, expand) {
return { expand()
transform: `translate(-50%, -50%) scale(${this.zoom}, ${this.zoom})`,
marginLeft: `${this.orgTreeOffsetLeft}px`,
marginTop: `${this.orgTreeOffsetTop}px`
}
}
}, },
methods: { mousedownView(event) {
mousedownView(event) { this.canMove = true
this.canMove = true this.initPageX = event.pageX
this.initPageX = event.pageX this.initPageY = event.pageY
this.initPageY = event.pageY this.oldMarginLeft = this.orgTreeOffsetLeft
this.oldMarginLeft = this.orgTreeOffsetLeft this.oldMarginTop = this.orgTreeOffsetTop
this.oldMarginTop = this.orgTreeOffsetTop document.addEventListener('mousemove', this.mousemoveView)
document.addEventListener('mousemove', this.mousemoveView) document.addEventListener('mouseup', this.mouseupView)
document.addEventListener('mouseup', this.mouseupView) },
}, mousemoveView(event) {
if (!this.canMove) return
mousemoveView(event) { const {pageX, pageY} = event
if (!this.canMove) return this.orgTreeOffsetLeft = this.oldMarginLeft + pageX - this.initPageX
const {pageX, pageY} = event this.orgTreeOffsetTop = this.oldMarginTop + pageY - this.initPageY
this.orgTreeOffsetLeft = this.oldMarginLeft + pageX - this.initPageX },
this.orgTreeOffsetTop = this.oldMarginTop + pageY - this.initPageY mouseupView() {
}, this.canMove = false
document.removeEventListener('mousemove', this.mousemoveView)
mouseupView() { document.removeEventListener('mouseup', this.mouseupView)
this.canMove = false
document.removeEventListener('mousemove', this.mousemoveView)
document.removeEventListener('mouseup', this.mouseupView)
}
} }
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
.org-tree-wrapper { .org-tree-wrapper {
cursor: move; cursor: move;
display: inline-block; display: inline-block;
position: absolute; position: absolute;
left: 50%; left: 50%;
top: 50%; top: 50%;
transition: transform 0.2s ease-out; transition: transform 0.2s ease-out;
z-index: 2; z-index: 2;
.org-tree-node-label { .org-tree-node-label {
.org-tree-node-label-inner { .org-tree-node-label-inner {
padding: 0; padding: 0;
.custom-org-node { .custom-org-node {
padding: 14px 41px; padding: 14px 41px;
background: #738699; background: #738699;
user-select: none; user-select: none;
word-wrap: normal; word-wrap: normal;
white-space: nowrap; white-space: nowrap;
border-radius: 4px; border-radius: 4px;
color: #ffffff; color: #ffffff;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
line-height: 20px; line-height: 20px;
transition: background 0.1s ease-in; transition: background 0.1s ease-in;
cursor: default; cursor: default;
&:hover { &:hover {
background: #5d6c7b; background: #5d6c7b;
transition: background 0.1s ease-in; transition: background 0.1s ease-in;
} }
&.has-children-label { &.has-children-label {
cursor: pointer; cursor: pointer;
}
} }
} }
} }
}
.horizontal .custom-org-node { .horizontal .custom-org-node {
padding: 14px 19px; padding: 14px 19px;
}
} }
}
</style> </style>

@ -1,20 +1,14 @@
<template> <template>
<el-card v-loading="loading" class="card-container max-view-height"> <el-card v-loading="loading" class="card-container max-view-height">
<div class="tip-row">
<a href="https://admin.iviewui.com/components/org_tree_page" target="_blank">
组织结构树演示详情见iview-admin
</a>
</div>
<div class="department-outer"> <div class="department-outer">
<div class="view-box"> <div class="view-box">
<org-tree-view <org-tree-view
:data="data" :data="data"
:horizontal="horizontal" :horizontal="horizontal"
:zoom="realZoom" :zoom="realZoom"
collapsable collapsable
@node-contextmenu="openMenu" @node-contextmenu="openMenu"
@wheel.native.prevent="wheel" @wheel.native.prevent="wheel"
/> />
</div> </div>
@ -25,13 +19,13 @@
</el-radio-group> </el-radio-group>
<el-button <el-button
circle circle
icon="el-icon-refresh" icon="el-icon-refresh"
size="small" size="small"
style="margin-right: 20px" style="margin-right: 20px"
title="刷新" title="刷新"
type="info" type="info"
@click="search" @click="search"
/> />
<zoom-control v-model="zoom" :max="200" :min="20"/> <zoom-control v-model="zoom" :max="200" :min="20"/>
@ -39,14 +33,14 @@
</div> </div>
<context-menu <context-menu
v-if="canAdd||canUpdate||canDel" v-if="canAdd || canUpdate || canDel"
v-model="contextmenu.show" v-model="contextmenu.show"
:left="contextmenu.left" :left="contextmenu.left"
:top="contextmenu.top" :top="contextmenu.top"
> >
<context-menu-item v-show="canAdd" @click="add">添加部门</context-menu-item> <context-menu-item v-if="canAdd" @click="add">添加部门</context-menu-item>
<context-menu-item v-show="canUpdate" @click="update">编辑部门</context-menu-item> <context-menu-item v-if="canUpdate" @click="update">编辑部门</context-menu-item>
<context-menu-item v-show="canDel" @click="del">删除部门</context-menu-item> <context-menu-item v-if="canDel" @click="del">删除部门</context-menu-item>
</context-menu> </context-menu>
<edit-dialog v-model="editDialog" :data="currentNode" :type="type" @success="search"/> <edit-dialog v-model="editDialog" :data="currentNode" :type="type" @success="search"/>
@ -54,159 +48,156 @@
</template> </template>
<script> <script>
import OrgTreeView from "./components/OrgTreeView" /**
import ZoomControl from '@/components/ZoomControl' * 详情见iview-admin
import ContextMenu from "@/components/ContextMenu" * https://admin.iviewui.com/components/org_tree_page
import ContextMenuItem from "@/components/ContextMenu/ContextMenuItem" */
import EditDialog from "./components/EditDialog" import ContextMenu from "@/components/ContextMenu"
import {auth} from "@/utils/auth" import ContextMenuItem from "@/components/ContextMenu/ContextMenuItem"
import {createTreeByWorker} from "@/utils/tree" import EditDialog from "./components/EditDialog"
import {isEmpty} from '@/utils' import OrgTreeView from "./components/OrgTreeView"
import {elConfirm, elError, elSuccess} from "@/utils/message" import ZoomControl from '@/components/ZoomControl'
import {delDepartment, getAllDepartments} from "@/api/system/department" import {baseUrl, delDepartment, getDepartments} from "@/api/system/department"
import {isEmpty} from '@/utils'
const baseUrl = '/system/department' import {auth} from "@/utils/auth"
import {elConfirm, elError, elSuccess} from "@/utils/message"
export default { import {createTreeByWorker} from "@/utils/tree"
name: "departmentManagement",
export default {
components: {OrgTreeView, ZoomControl, ContextMenu, ContextMenuItem, EditDialog}, name: "departmentManagement",
data() { components: {ContextMenu, ContextMenuItem, EditDialog, OrgTreeView, ZoomControl},
return {
loading: false, data() {
data: {}, return {
zoom: 100, loading: false,
horizontal: false, data: {},
currentNode: null, zoom: 100,
type: 'add', horizontal: false,
editDialog: false, currentNode: null,
contextmenu: { type: 'add',
show: false, editDialog: false,
left: 0, contextmenu: {
top: 0 show: false,
} left: 0,
top: 0
} }
}
},
computed: {
realZoom() {
return this.zoom / 100
},
canAdd() {
return auth(`${baseUrl}/add`)
},
canUpdate() {
return auth(`${baseUrl}/update`)
},
canDel() {
return auth(`${baseUrl}/del`)
},
},
methods: {
//
completeDepartment(arr) {
arr.forEach(i => {
i.label = i.name
i.parentName = i.fullname
i.children && this.completeDepartment(i.children)
})
}, },
computed: { //
realZoom() { openMenu(e, obj) {
return this.zoom / 100 e.preventDefault()
}, this.currentNode = obj
const {left, top} = this.$el.getBoundingClientRect()
this.contextmenu.left = e.clientX - left + 15
this.contextmenu.top = e.clientY - top
this.contextmenu.show = true
},
canAdd() { //
return auth(baseUrl + '/add') wheel(e) {
}, const eventDelta = e.wheelDelta || -(e.detail || 0)
const zoom = this.zoom + eventDelta / 12 * 2
if (zoom >= 20 && zoom <= 200) {
this.zoom = zoom
}
},
canUpdate() { add() {
return auth(baseUrl + '/update') this.type = 'add'
}, this.editDialog = true
},
canDel() { update() {
return auth(baseUrl + '/del') this.type = 'edit'
}, this.editDialog = true
}, },
methods: { del() {
openMenu(e, obj) { if (this.loading || !this.currentNode) return
e.preventDefault() if (this.currentNode.id === 1) return elError('根节点不能删除')
this.currentNode = obj if (this.currentNode.children && this.currentNode.children.length > 0) {
const {left, top} = this.$el.getBoundingClientRect() return elError('请先删除子部门')
this.contextmenu.left = e.clientX - left + 15
this.contextmenu.top = e.clientY - top
this.contextmenu.show = true
},
wheel(e) {
const eventDelta = e.wheelDelta || -(e.detail || 0)
const zoom = this.zoom + eventDelta / 12 * 2
if (zoom >= 20 && zoom <= 200) {
this.zoom = zoom
}
},
add() {
this.type = 'add'
this.editDialog = true
},
update() {
this.type = 'edit'
this.editDialog = true
},
del() {
if (this.loading || !this.currentNode) return
if (this.currentNode.id === 1) return elError('根节点不能删除')
if (this.currentNode.children && this.currentNode.children.length > 0) {
return elError('请先删除子部门')
}
elConfirm(`确定删除部门【${this.currentNode.name}】?`)
.then(() => {
this.loading = true
return delDepartment(this.currentNode)
})
.then(({msg}) => {
elSuccess(msg)
this.search()
})
.finally(() => this.loading = false)
},
search() {
this.currentNode = null
this.loading = true
getAllDepartments()
.then(data => createTreeByWorker(data))
.then(data => {
completeDepartment(data)
this.data = data[0]
})
.finally(() => this.loading = false)
} }
elConfirm(`确定删除部门【${this.currentNode.name}】?`)
.then(() => {
this.loading = true
return delDepartment(this.currentNode)
})
.then(({msg}) => {
elSuccess(msg)
this.search()
})
.finally(() => this.loading = false)
}, },
mounted() { search() {
this.search() this.currentNode = null
this.loading = true
getDepartments()
.then(data => createTreeByWorker(data))
.then(data => {
this.completeDepartment(data)
this.data = data[0]
})
.finally(() => this.loading = false)
} }
} },
//
function completeDepartment(arr, name) {
arr.forEach(i => {
i.label = i.name
i.parentName = name
if (isEmpty(name)) i.fullname = i.name mounted() {
else i.fullname = name + ' > ' + i.name this.search()
if (i.children) completeDepartment(i.children, i.fullname)
})
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
.department-outer { .department-outer {
width: 100%; width: 100%;
height: 100%; height: 100%;
overflow: hidden; overflow: hidden;
.tooltip-box { .tooltip-box {
position: absolute; position: absolute;
right: 30px; right: 30px;
bottom: 30px; bottom: 30px;
z-index: 2; z-index: 2;
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
} }
.view-box { .view-box {
position: absolute; position: absolute;
top: 0; top: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
right: 0; right: 0;
}
} }
}
</style> </style>

@ -14,18 +14,18 @@
<el-form-item label="访问路径:" prop="url"> <el-form-item label="访问路径:" prop="url">
<el-input :value="form.url" readonly/> <el-input :value="form.url" readonly/>
</el-form-item> </el-form-item>
<el-form-item label="总频率:" prop="total_rate"> <el-form-item label="总频率:" prop="totalRate">
<el-input-number <el-input-number
v-model="form.total_rate" v-model="form.totalRate"
controls-position="right" controls-position="right"
:min="1" :min="1"
:step="1" :step="1"
step-strictly size="small" step-strictly size="small"
/> />
</el-form-item> </el-form-item>
<el-form-item label="单个IP频率:" prop="ip_rate"> <el-form-item label="单个IP频率:" prop="ipRate">
<el-input-number <el-input-number
v-model="form.ip_rate" v-model="form.ipRate"
controls-position="right" controls-position="right"
:min="1" :min="1"
:step="1" :step="1"
@ -66,12 +66,12 @@
id: null, id: null,
fullName: null, fullName: null,
url: null, url: null,
total_rate: 0, totalRate: 0,
ip_rate: 0 ipRate: 0
}, },
rules: { rules: {
total_rate: [{required: true, message: '总频率不能为空', trigger: 'change'}], totalRate: [{required: true, message: '总频率不能为空', trigger: 'change'}],
ip_rate: [{required: true, message: '单个ip的频率不能为空', trigger: 'change'}], ipRate: [{required: true, message: '单个ip的频率不能为空', trigger: 'change'}],
} }
} }
}, },

@ -9,8 +9,8 @@
<abstract-table :data="tableData" @row-click="rowClick"> <abstract-table :data="tableData" @row-click="rowClick">
<el-table-column label="名称" prop="fullName" show-overflow-tooltip/> <el-table-column label="名称" prop="fullName" show-overflow-tooltip/>
<el-table-column label="访问路径" prop="url" show-overflow-tooltip/> <el-table-column label="访问路径" prop="url" show-overflow-tooltip/>
<el-table-column label="总频率" prop="total_rate" show-overflow-tooltip/> <el-table-column label="总频率" prop="totalRate" show-overflow-tooltip/>
<el-table-column label="单个IP频率" prop="ip_rate" show-overflow-tooltip/> <el-table-column label="单个IP频率" prop="ipRate" show-overflow-tooltip/>
</abstract-table> </abstract-table>
</el-row> </el-row>

@ -0,0 +1,190 @@
<template>
<form-dialog :loading="loading" :title="title" :value="value" width="40%" @close="cancel" @open="open">
<el-form
ref="form"
:model="form"
:rules="rules"
label-position="right"
label-width="100px"
status-icon
>
<el-form-item label="名 称:" prop="name">
<el-input v-model="form.name" maxlength="20"/>
</el-form-item>
<el-form-item label="状 态:" prop="status">
<el-radio-group v-model="form.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="数据范围:" prop="scope">
<el-select v-model="form.scope">
<el-option :value="1" label="全部"/>
<el-option :value="2" label="本部门"/>
<el-option :value="3" label="指定部门"/>
</el-select>
</el-form-item>
<el-form-item v-if="form.scope === 3" label="指定部门:">
<el-tree
ref="dept"
:data="departments"
:props="{label:'name'}"
:default-checked-keys="form.departmentId"
node-key="id"
show-checkbox
/>
</el-form-item>
<el-form-item label="权 限:">
<el-tree
ref="perm"
:data="resource"
:props="{label:'name'}"
node-key="id"
show-checkbox
/>
</el-form-item>
</el-form>
<template v-slot:footer>
<el-dropdown placement="top" style="float: left" @command="treeCommand">
<el-button plain size="small">
权限树操作
<i class="el-icon-arrow-up el-icon--right"/>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item :command="{action:'expand',level:0}">展开全部</el-dropdown-item>
<el-dropdown-item :command="{action:'collapse',level:0}">收起全部</el-dropdown-item>
<el-dropdown-item :command="{action:'expand',level:1}">仅展开一级</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-button plain size="small" @click="closeDialog"> </el-button>
<el-button size="small" type="primary" @click="confirm"> </el-button>
</template>
</form-dialog>
</template>
<script>
import dialogMixin from "@/mixins/dialogMixin"
import FormDialog from '@/components/FormDialog'
import {getDepartments} from "@/api/system/department"
import {addRole, updateRole} from "@/api/system/role"
import {isEmpty, mergeObj} from '@/utils'
import {createTreeByWorker, elTreeControl} from '@/utils/tree'
export default {
name: "EditDialog",
mixins: [dialogMixin],
components: {FormDialog},
props: {
value: {type: Boolean, default: false},
type: {type: String, default: 'add'},
data: {type: Object, default: () => ({})},
},
data() {
return {
loading: false,
form: {
id: null,
name: null,
status: 1,
scope: 1,
departmentId: [],
resourceId: []
},
rules: {
name: [{required: true, message: '角色名称不能为空', trigger: 'change'}],
status: [{required: true, message: '角色状态不能为空', trigger: 'change'}],
scope: [{required: true, message: '数据范围不能为空', trigger: 'change'}],
},
departments: []
}
},
computed: {
title() {
if (isEmpty(this.type)) return ''
switch (this.type) {
case 'add':
return '添加角色'
case 'edit':
return '编辑角色'
}
},
confirmMessage() {
switch (this.type) {
case 'add':
return `确认添加新的角色【${this.form.name}】?`
case 'edit':
return `确认提交对【${this.data.name}】作出的修改?`
default:
return ''
}
},
resource() {
return this.$store.state.resource.tree
}
},
methods: {
treeCommand({action, level}) {
elTreeControl(this.$refs.perm, action, level)
},
open() {
if (this.type !== 'edit') return
this.$nextTick(() => {
mergeObj(this.form, this.data)
this.$refs.perm.setCheckedKeys(this.form.resourceId)
})
},
clearForm() {
this.form.id = null
this.form.name = null
this.form.status = 1
this.form.scope = 1
this.form.departmentId = []
this.form.resourceId = []
this.$refs.perm.setCheckedKeys([])
this.$refs.dept && this.$refs.dept.setCheckedKeys([])
this.$nextTick(() => this.$refs.form.clearValidate())
},
cancel() {
this.closeDialog()
this.clearForm()
},
confirm() {
if (this.loading) return
this.$refs.form.validate(v => {
if (!v) return
this.loading = true
const obj = {
...this.form,
departmentId: this.$refs.dept ? this.$refs.dept.getCheckedKeys().join(',') : [],
resourceId: this.$refs.perm.getCheckedKeys().join(',')
}
const promise = this.type === 'add' ? addRole(obj) : updateRole(obj)
promise
.then(({msg}) => this.$emit('success', msg))
.finally(() => this.loading = false)
})
}
},
mounted() {
getDepartments(false)
.then(data => createTreeByWorker(data))
.then(data => {
this.departments = data
})
}
}
</script>

@ -1,158 +0,0 @@
<template>
<form-dialog :loading="loading" :title="title" :value="value" @close="cancel" @open="open">
<el-form
ref="form"
:model="form"
:rules="rules"
label-position="right"
label-width="75px"
status-icon
>
<el-form-item label="名 称:" prop="name">
<el-input v-model="form.name" maxlength="20"/>
</el-form-item>
<el-form-item label="状 态:" prop="status">
<el-radio-group v-model="form.status">
<el-radio :label="1">启用</el-radio>
<el-radio :label="0">禁用</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="权 限:">
<el-tree
ref="tree"
:data="resource"
:expand-on-click-node="false"
:props="{label:'name'}"
node-key="id"
show-checkbox
/>
</el-form-item>
</el-form>
<template v-slot:footer>
<el-dropdown placement="top" style="float: left" @command="treeCommand">
<el-button plain size="small">
树形操作
<i class="el-icon-arrow-up el-icon--right"/>
</el-button>
<el-dropdown-menu slot="dropdown">
<el-dropdown-item :command="{action:'expand',level:0}">展开全部</el-dropdown-item>
<el-dropdown-item :command="{action:'collapse',level:0}">收起全部</el-dropdown-item>
<el-dropdown-item :command="{action:'expand',level:1}">仅展开一级</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
<el-button plain size="small" @click="closeDialog"> </el-button>
<el-button size="small" type="primary" @click="confirm"> </el-button>
</template>
</form-dialog>
</template>
<script>
import FormDialog from '@/components/FormDialog'
import dialogMixin from "@/mixins/dialogMixin"
import {addRole, updateRole} from "@/api/system/role"
import {isEmpty, mergeObj} from '@/utils'
import {elTreeControl} from '@/utils/tree'
export default {
name: "EditDialog",
mixins: [dialogMixin],
components: {FormDialog},
props: {
value: {type: Boolean, default: false},
type: {type: String, default: 'add'},
data: {type: Object, default: () => ({})},
},
data() {
return {
loading: false,
form: {
id: null,
name: null,
status: 1,
resource_id: []
},
rules: {
name: [{required: true, message: '角色名称不能为空', trigger: 'change'}],
status: [{required: true, message: '角色状态不能为空', trigger: 'change'}],
}
}
},
computed: {
title() {
if (isEmpty(this.type)) return ''
switch (this.type) {
case 'add':
return '添加角色'
case 'edit':
return '编辑角色'
}
},
confirmMessage() {
switch (this.type) {
case 'add':
return `确认添加新的角色【${this.form.name}】?`
case 'edit':
return `确认提交对【${this.data.name}】作出的修改?`
default:
return ''
}
},
resource() {
return this.$store.state.resource.tree
}
},
methods: {
treeCommand({action, level}) {
elTreeControl(this.$refs.tree, action, level)
},
open() {
this.$nextTick(() => {
if (this.type === 'edit') {
mergeObj(this.form, this.data)
this.$refs.tree.setCheckedKeys(this.form.resource_id)
}
})
},
clearForm() {
this.form.id = null
this.form.name = null
this.form.status = 1
this.form.resource_id = []
this.$refs.tree.setCheckedKeys([])
this.$nextTick(() => this.$refs.form.clearValidate())
},
cancel() {
this.closeDialog()
this.clearForm()
},
confirm() {
if (this.loading) return
this.$refs.form.validate(v => {
if (!v) return
this.loading = true
let obj = {
...this.form,
resource_id: this.$refs.tree.getCheckedKeys().join(',')
}
let promise = this.type === 'add' ? addRole(obj) : updateRole(obj)
promise
.then(({msg}) => this.$emit('success', msg))
.finally(() => this.loading = false)
})
},
}
}
</script>

@ -1,59 +0,0 @@
<script type="text/jsx">
import {createTree} from "@/utils/tree"
export default {
name: "RoleResource",
functional: true,
props: {
ids: {type: Array, default: () => []},
map: {type: Object, default: () => ({})}
},
render(h, context) {
const {ids, map} = context.props
let tree = []
for (let id of ids) map[id] && tree.push(map[id])
tree = createTree(tree)
const nodes = []
const fun = (arr, left) => {
for (let r of arr) {
nodes.push(
<span style={'margin-left:' + left + 'px'}>
<svg-icon icon="check"/>
{r.name}
</span>
)
if (r.children.length > 0) {
if (r.children[0].children.length > 0) nodes.push(<p/>)
fun(r.children, left + 20)
}
}
nodes.push(<p/>)
}
fun(tree, 0)
return nodes
}
}
</script>
<style lang="scss" scoped>
@import "~@/assets/styles/variables.scss";
span {
.svg-icon {
color: $--color-primary
}
}
span + span {
margin-left: 10px;
}
</style>

@ -15,11 +15,11 @@
</search-form-item> </search-form-item>
<search-form-item label="创建时间:"> <search-form-item label="创建时间:">
<el-date-picker <el-date-picker
v-model="temp.ctime" v-model="temp.ctime"
format="yyyy-MM-dd" format="yyyy-MM-dd"
range-separator="-" range-separator="-"
type="daterange" type="daterange"
value-format="timestamp" value-format="timestamp"
/> />
</search-form-item> </search-form-item>
</search-form> </search-form>
@ -33,30 +33,30 @@
<el-row v-loading="config.loading" class="table-container"> <el-row v-loading="config.loading" class="table-container">
<abstract-table <abstract-table
:data="tableData" :data="tableData"
@row-click="rowClick" @row-click="rowClick"
> >
<el-table-column align="center" label="#" type="index" width="80"/> <el-table-column align="center" label="#" type="index" width="80"/>
<el-table-column align="center" label="角色名" prop="name" show-overflow-tooltip/> <el-table-column align="center" label="角色名" prop="name" show-overflow-tooltip/>
<el-table-column align="center" label="创建人" prop="cname" show-overflow-tooltip/> <el-table-column align="center" label="创建人" prop="cname" show-overflow-tooltip/>
<el-table-column align="center" label="创建时间" width="150" show-overflow-tooltip> <el-table-column align="center" label="创建时间" width="150" show-overflow-tooltip>
<template v-slot="{row}">{{row.ctime | timestamp2Date}}</template> <template v-slot="{row}">{{ row.ctime | timestamp2Date }}</template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="状 态" width="120"> <el-table-column align="center" label="状 态" width="120">
<template v-slot="{row}"> <template v-slot="{row}">
<span :class="row.status===1?'success':'error'" class="dot"/> <span :class="row.status===1?'success':'error'" class="dot"/>
<span>{{row.status===1?'启用':'禁用'}}</span> <span>{{ row.status === 1 ? '启用' : '禁用' }}</span>
</template> </template>
</el-table-column> </el-table-column>
</abstract-table> </abstract-table>
<el-pagination <el-pagination
background background
:current-page="searchForm.page" :current-page="searchForm.page"
:page-size="searchForm.pageSize" :page-size="searchForm.pageSize"
:total="searchForm.total" :total="searchForm.total"
layout="total, prev, pager, next, jumper" layout="total, prev, pager, next, jumper"
@current-change="pageChange" @current-change="pageChange"
/> />
</el-row> </el-row>
@ -65,110 +65,113 @@
</template> </template>
<script> <script>
import SearchForm from "@/components/SearchForm" import tableMixin from '@/mixins/tablePageMixin'
import SearchFormItem from "@/components/SearchForm/SearchFormItem" import EditDialog from './EditDialog'
import EditDialog from './components/EditDialog' import SearchForm from "@/components/SearchForm"
import {delRole, searchRoles} from "@/api/system/role" import SearchFormItem from "@/components/SearchForm/SearchFormItem"
import {isEmpty} from '@/utils' import {baseUrl, delRole, searchRoles} from "@/api/system/role"
import {elConfirm, elError, elSuccess} from "@/utils/message" import {isEmpty} from '@/utils'
import {auth} from "@/utils/auth" import {auth} from "@/utils/auth"
import tableMixin from '@/mixins/tablePageMixin' import {elConfirm, elError, elSuccess} from "@/utils/message"
const baseUrl = '/system/role' export default {
name: "roleManagement",
export default {
name: "roleManagement", mixins: [tableMixin],
mixins: [tableMixin], components: {EditDialog, SearchForm, SearchFormItem},
data() {
return {
searchForm: {
name: '',
cname: '',
status: null
},
temp: {
ctime: []
},
editDialog: false
}
},
components: {SearchForm, SearchFormItem, EditDialog}, computed: {
canAdd() {
return auth(`${baseUrl}/add`)
},
canUpdate() {
return auth(`${baseUrl}/update`)
},
canDel() {
return auth(`${baseUrl}/del`)
}
},
data() { methods: {
mergeSearchForm() {
return { return {
searchForm: { ...this.searchForm,
name: '', startTime: this.temp.ctime ? this.temp.ctime[0] : null,
cname: '', endTime: this.temp.ctime ? this.temp.ctime[1] + 86400000 : null,
status: null
},
temp: {
ctime: []
},
editDialog: false
} }
}, },
computed: { str2intArray(str) {
canAdd() { if (isEmpty(str)) return []
return auth(baseUrl + '/add') return str.split(',').map(i => parseInt(i))
},
canUpdate() {
return auth(baseUrl + '/update')
},
canDel() {
return auth(baseUrl + '/del')
}
}, },
methods: { search() {
mergeSearchForm() { if (this.config.loading) return
return { this.config.loading = true
...this.searchForm, this.row = null
startTime: this.temp.ctime ? this.temp.ctime[0] : null, this.type = 'see'
endTime: this.temp.ctime ? this.temp.ctime[1] + 86400000 : null, searchRoles(this.mergeSearchForm())
} .then(({list, total}) => {
}, list.forEach(i => {
i.departmentId = this.str2intArray(i.departmentId)
search() { i.resourceId = this.str2intArray(i.resourceId)
if (this.config.loading) return
this.config.loading = true
this.row = null
this.type = 'see'
searchRoles(this.mergeSearchForm())
.then(({list, total}) => {
list.forEach(i => {
i.resource_id = isEmpty(i.resource_id) ? [] : i.resource_id.split(',').map(i => parseInt(i))
})
this.searchForm.total = total
this.tableData = list
}) })
.finally(() => this.config.loading = false) this.searchForm.total = total
}, this.tableData = list
})
.finally(() => this.config.loading = false)
},
add() { add() {
this.row = null this.row = null
this.type = 'add' this.type = 'add'
this.editDialog = true this.editDialog = true
}, },
edit() { edit() {
if (!this.row) return elError('请选择要编辑的角色') if (!this.row) return elError('请选择要编辑的角色')
this.type = 'edit' this.type = 'edit'
this.editDialog = true this.editDialog = true
}, },
del() { del() {
if (!this.row) return elError('请选择要删除的角色') if (!this.row) return elError('请选择要删除的角色')
if (this.row.status === 1) return elError('不能删除已启用的角色') const {id, name, status} = this.row
if (this.config.operating) return if (status === 1) return elError('不能删除已启用的角色')
elConfirm(`确定删除角色【${this.row.name}】?`) if (this.config.operating) return
.then(() => { elConfirm(`确定删除角色【${name}】?`)
this.config.operating = true .then(() => {
return delRole({id: this.row.id, name: this.row.name}) this.config.operating = true
}) return delRole({id, name})
.then(() => { })
elSuccess('删除成功') .then(() => {
this.search() elSuccess('删除成功')
}) this.search()
.finally(() => this.config.operating = false) })
}, .finally(() => this.config.operating = false)
},
success(msg) { success(msg) {
elSuccess(msg) elSuccess(msg)
this.editDialog = false this.editDialog = false
this.search() this.search()
}
} }
} }
}
</script> </script>

@ -0,0 +1,39 @@
<template>
<tree-select :value="value" :data="data" :props="{label:'name'}" popper-append-to-body @input="input"/>
</template>
<script>
import TreeSelect from '@/components/TreeSelect'
import {getDepartments} from "@/api/system/department"
import {createTreeByWorker} from "@/utils/tree"
import {isEmpty} from "@/utils"
export default {
name: "DepartmentSelector",
components: {TreeSelect},
props: {value: Number},
data() {
return {
data: []
}
},
methods: {
input(v, data) {
this.$emit('input', v)
this.$emit('get-name', data && data.fullname)
},
},
mounted() {
getDepartments(false)
.then(data => createTreeByWorker(data))
.then(data => {
this.data = data
})
}
}
</script>

@ -1,22 +1,31 @@
<template> <template>
<form-dialog :loading="loading" :title="title" :value="value" @close="cancel" @open="open"> <form-dialog :loading="loading" :title="title" :value="value" @close="cancel" @open="open">
<el-form <el-form
ref="form" ref="form"
:model="form" :model="form"
:rules="rules" :rules="rules"
label-position="right" label-position="right"
label-width="75px" label-width="75px"
status-icon status-icon
> >
<el-form-item label="名 称:" prop="name"> <el-form-item label="名 称:" prop="name">
<el-input v-model="form.name" :readonly="!canEdit" maxlength="20"/> <el-input v-model="form.name" :readonly="!canEdit" maxlength="20"/>
</el-form-item> </el-form-item>
<el-form-item label="角 色:" prop="role"> <el-form-item label="角 色:" prop="role">
<role-selector <role-selector
v-model="form.role" v-if="canEdit"
:disabled="!canEdit" v-model="form.role"
@get-name="e => form.role_name = e" @get-name="e => form.roleName = e"
/> />
<template v-else>{{ form.roleName }}</template>
</el-form-item>
<el-form-item label="部 门:" prop="dept">
<department-selector
v-if="canEdit"
v-model="form.dept"
@get-name="e => form.deptName = e"
/>
<template v-else>{{ form.deptName }}</template>
</el-form-item> </el-form-item>
<el-form-item label="状 态:" prop="status"> <el-form-item label="状 态:" prop="status">
<el-radio-group v-model="form.status" :disabled="!canEdit"> <el-radio-group v-model="form.status" :disabled="!canEdit">
@ -34,120 +43,126 @@
</template> </template>
<script> <script>
import FormDialog from '@/components/FormDialog' import dialogMixin from "@/mixins/dialogMixin"
import RoleSelector from './RoleSelector' import DepartmentSelector from "./DepartmentSelector"
import dialogMixin from "@/mixins/dialogMixin" import FormDialog from '@/components/FormDialog'
import {checkName} from "@/api/account" import RoleSelector from './RoleSelector'
import {addUser, updateUser} from "@/api/system/user" import {checkName} from "@/api/account"
import {isEmpty} from '@/utils' import {addUser, updateUser} from "@/api/system/user"
import {elConfirm} from "@/utils/message" import {isEmpty} from '@/utils'
import {elConfirm} from "@/utils/message"
export default { export default {
name: "EditDialog", name: "EditDialog",
mixins: [dialogMixin], mixins: [dialogMixin],
components: {FormDialog, RoleSelector}, components: {DepartmentSelector, FormDialog, RoleSelector},
props: { props: {
value: {type: Boolean, default: false}, value: {type: Boolean, default: false},
type: {type: String, default: 'see'}, type: {type: String, default: 'see'},
data: {type: Object, default: () => ({})}, data: {type: Object, default: () => ({})},
}, },
data() { data() {
const validateName = (r, v, c) => { const validateName = (r, v, c) => {
checkName(this.form.name, this.form.id) checkName(this.form.name, this.form.id)
.then(({msg}) => msg ? c(msg) : c()) .then(({msg}) => msg ? c(msg) : c())
.catch(e => c(e)) .catch(e => c(e))
}
return {
loading: false,
form: {
id: null,
name: null,
role: null,
roleName: null,
dept: null,
deptName: null,
status: 1,
},
rules: {
name: [
{required: true, message: '用户名称不能为空', trigger: 'change'},
{validator: validateName, trigger: 'change'}
],
role: [{required: true, message: '用户角色不能为空', trigger: 'change'}],
dept: [{required: true, message: '用户部门不能为空', trigger: 'change'}],
status: [{required: true, message: '用户状态不能为空', trigger: 'change'}],
} }
return { }
loading: false, },
form: {
id: null, computed: {
name: null, title() {
role: null, if (isEmpty(this.type)) return ''
role_name: null, switch (this.type) {
status: 1, case 'see':
}, return '用户信息'
rules: { case 'add':
name: [ return '添加用户'
{required: true, message: '用户名称不能为空', trigger: 'change'}, case 'edit':
{validator: validateName, trigger: 'change'} return '编辑用户'
],
role: [{required: true, message: '用户角色不能为空', trigger: 'change'}],
status: [{required: true, message: '用户状态不能为空', trigger: 'change'}],
}
} }
}, },
computed: { confirmMessage() {
title() { switch (this.type) {
if (isEmpty(this.type)) return '' case 'add':
switch (this.type) { return `确认添加新的用户【${this.form.name}】?`
case 'see': case 'edit':
return '用户信息' return `确认提交对【${this.data.name}】作出的修改?`
case 'add': default:
return '添加用户' return ''
case 'edit':
return '编辑用户'
}
},
confirmMessage() {
switch (this.type) {
case 'add':
return `确认添加新的用户【${this.form.name}】?`
case 'edit':
return `确认提交对【${this.data.name}】作出的修改?`
default:
return ''
}
},
canEdit() {
return ['add', 'edit'].includes(this.type)
} }
}, },
methods: { canEdit() {
open() { return ['add', 'edit'].includes(this.type)
this.$nextTick(() => { }
if (this.type === 'edit') { },
Object
.keys(this.data)
.forEach(key => key in this.form && (this.form[key] = this.data[key]))
}
})
},
clearForm() { methods: {
this.form.id = null open() {
this.form.name = null this.$nextTick(() => {
this.form.role = null if (this.type === 'edit') {
this.form.role_name = null Object
this.form.status = 1 .keys(this.data)
this.$nextTick(() => this.$refs.form.clearValidate()) .forEach(key => key in this.form && (this.form[key] = this.data[key]))
}, }
})
},
cancel() { clearForm() {
this.closeDialog() this.form.id = null
this.clearForm() this.form.name = null
}, this.form.role = null
this.form.roleName = null
this.form.dept = null
this.form.deptName = null
this.form.status = 1
this.$nextTick(() => this.$refs.form.clearValidate())
},
confirm() { cancel() {
if (this.loading) return this.closeDialog()
this.$refs.form.validate(v => { this.clearForm()
if (!v) return },
elConfirm(this.confirmMessage)
.then(() => { confirm() {
this.loading = true if (this.loading) return
return this.type === 'add' ? addUser(this.form) : updateUser(this.form) this.$refs.form.validate(v => {
}) if (!v) return
.then(({msg}) => this.$emit('success', msg)) elConfirm(this.confirmMessage)
.finally(() => this.loading = false) .then(() => {
}) this.loading = true
} return this.type === 'add' ? addUser(this.form) : updateUser(this.form)
})
.then(({msg}) => this.$emit('success', msg))
.finally(() => this.loading = false)
})
} }
} }
}
</script> </script>

@ -1,5 +1,5 @@
<template> <template>
<el-select :disabled="disabled" :value="value" clearable @clear="emit(null)" @input="emit"> <el-select :value="value" clearable @clear="emit(null)" @input="emit">
<el-option <el-option
v-for="item in roles" v-for="item in roles"
:key="item.id" :key="item.id"
@ -15,12 +15,7 @@
export default { export default {
name: "RoleSelector", name: "RoleSelector",
props: { props: {value: Number},
value: Number,
disabled: {
type: Boolean, default: false
}
},
data() { data() {
return { return {

@ -8,18 +8,18 @@
<role-selector v-model="searchForm.role"/> <role-selector v-model="searchForm.role"/>
</search-form-item> </search-form-item>
<search-form-item label="状 态:"> <search-form-item label="状 态:">
<el-select v-model="searchForm.status" clearable @clear="searchForm.status=null"> <el-select v-model="searchForm.status" clearable @clear="searchForm.status = null">
<el-option :value="1" label="启用"/> <el-option :value="1" label="启用"/>
<el-option :value="0" label="禁用"/> <el-option :value="0" label="禁用"/>
</el-select> </el-select>
</search-form-item> </search-form-item>
<search-form-item label="创建时间:"> <search-form-item label="创建时间:">
<el-date-picker <el-date-picker
v-model="temp.ctime" v-model="temp.ctime"
format="yyyy-MM-dd" format="yyyy-MM-dd"
range-separator="-" range-separator="-"
type="daterange" type="daterange"
value-format="timestamp" value-format="timestamp"
/> />
</search-form-item> </search-form-item>
</search-form> </search-form>
@ -40,43 +40,44 @@
<el-table-column align="center" label="#" type="index" width="80"/> <el-table-column align="center" label="#" type="index" width="80"/>
<el-table-column align="center" label="头像" width="100"> <el-table-column align="center" label="头像" width="100">
<img <img
slot-scope="{row}" slot-scope="{row}"
v-if="row.avatar" v-if="row.avatar"
:src="row.avatar" :src="row.avatar"
class="table-image" class="table-image"
@click.stop="previewAvatar(row.avatar)" @click.stop="() => previewAvatar(row.avatar)"
> >
</el-table-column> </el-table-column>
<el-table-column align="center" label="用户名" prop="name" show-overflow-tooltip/> <el-table-column align="center" label="用户名" prop="name" show-overflow-tooltip/>
<el-table-column align="center" label="角 色" show-overflow-tooltip> <el-table-column align="center" label="角 色" show-overflow-tooltip>
<template v-slot="{row}"> <template v-slot="{row}">
{{row.admin===1?'超级管理员':row.role_name}} {{ row.admin === true ? '超级管理员' : row.roleName }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="部 门" prop="deptName" show-overflow-tooltip/>
<el-table-column align="center" label="创建时间" width="150" show-overflow-tooltip> <el-table-column align="center" label="创建时间" width="150" show-overflow-tooltip>
<template v-slot="{row}">{{row.ctime | timestamp2Date}}</template> <template v-slot="{row}">{{ row.ctime | timestamp2Date }}</template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="在线情况" width="120"> <el-table-column align="center" label="在线情况" width="120">
<template v-slot="{row}"> <template v-slot="{row}">
<span :class="row.online?'success':'error'" class="dot"/> <span :class="row.online ? 'success' : 'error'" class="dot"/>
<span>{{row.online?'在线':'离线'}}</span> <span>{{ row.online ? '在线' : '离线' }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="状 态" width="120"> <el-table-column align="center" label="状 态" width="120">
<template v-slot="{row}"> <template v-slot="{row}">
<span :class="row.status===1?'success':'error'" class="dot"/> <span :class="row.status === 1 ? 'success' : 'error'" class="dot"/>
<span>{{row.status===1?'启用':'禁用'}}</span> <span>{{ row.status === 1 ? '启用' : '禁用' }}</span>
</template> </template>
</el-table-column> </el-table-column>
</abstract-table> </abstract-table>
<el-pagination <el-pagination
background background
:current-page="searchForm.page" :current-page="searchForm.page"
:page-size="searchForm.pageSize" :page-size="searchForm.pageSize"
:total="searchForm.total" :total="searchForm.total"
layout="total, prev, pager, next, jumper" layout="total, prev, pager, next, jumper"
@current-change="pageChange" @current-change="pageChange"
/> />
</el-row> </el-row>
@ -85,149 +86,149 @@
</template> </template>
<script> <script>
import SearchForm from "@/components/SearchForm" import SearchForm from "@/components/SearchForm"
import SearchFormItem from "@/components/SearchForm/SearchFormItem" import SearchFormItem from "@/components/SearchForm/SearchFormItem"
import RoleSelector from './components/RoleSelector' import RoleSelector from './components/RoleSelector'
import EditDialog from './components/EditDialog' import EditDialog from './components/EditDialog'
import {delUser, getUsers, kick, resetUserPwd} from "@/api/system/user" import {delUser, getUsers, kick, resetUserPwd} from "@/api/system/user"
import {isEmpty} from '@/utils' import {isEmpty} from '@/utils'
import {autoCompleteUrl} from "@/utils/file" import {autoCompleteUrl} from "@/utils/file"
import {elConfirm, elError, elSuccess} from "@/utils/message" import {elConfirm, elError, elSuccess} from "@/utils/message"
import {auth} from "@/utils/auth" import {auth} from "@/utils/auth"
import tableMixin from '@/mixins/tablePageMixin' import tableMixin from '@/mixins/tablePageMixin'
const baseUrl = '/system/user'
export default {
name: "userManagement",
mixins: [tableMixin],
components: {SearchForm, SearchFormItem, RoleSelector, EditDialog},
data() {
return {
searchForm: {
name: '',
role: null,
status: null
},
temp: {ctime: []},
editDialog: false
}
},
const baseUrl = '/system/user' computed: {
canAdd() {
return auth(`${baseUrl}/add`)
},
export default { canUpdate() {
name: "userManagement", return auth(`${baseUrl}/update`)
},
mixins: [tableMixin], canDel() {
return auth(`${baseUrl}/del`)
},
components: {SearchForm, SearchFormItem, RoleSelector, EditDialog}, canKick() {
return auth(`${baseUrl}/kick`)
},
canResetPwd() {
return auth(`${baseUrl}/resetPwd`)
},
},
data() { methods: {
mergeSearchForm() {
return { return {
searchForm: { ...this.searchForm,
name: '', startTime: this.temp.ctime ? this.temp.ctime[0] : null,
role: null, endTime: this.temp.ctime ? this.temp.ctime[1] + 86400000 : null,
status: null
},
temp: {ctime: []},
editDialog: false
} }
}, },
computed: { search() {
canAdd() { if (this.config.loading) return
return auth(baseUrl + '/add') this.config.loading = true
}, this.row = null
this.type = 'see'
canUpdate() { getUsers(this.mergeSearchForm())
return auth(baseUrl + '/update') .then(({list, total}) => {
}, list.forEach(u => u.avatar = autoCompleteUrl(u.avatar))
this.searchForm.total = total
canDel() { this.tableData = list
return auth(baseUrl + '/del') })
}, .finally(() => this.config.loading = false)
canKick() {
return auth(baseUrl + '/kick')
},
canResetPwd() {
return auth(baseUrl + '/resetPwd')
},
}, },
methods: { add() {
mergeSearchForm() { this.type = 'add'
return { this.editDialog = true
...this.searchForm, },
startTime: this.temp.ctime ? this.temp.ctime[0] : null,
endTime: this.temp.ctime ? this.temp.ctime[1] + 86400000 : null,
}
},
search() {
if (this.config.loading) return
this.config.loading = true
this.row = null
this.type = 'see'
getUsers(this.mergeSearchForm())
.then(({list, total}) => {
list.forEach(u => u.avatar = autoCompleteUrl(u.avatar))
this.searchForm.total = total
this.tableData = list
})
.finally(() => this.config.loading = false)
},
add() {
this.type = 'add'
this.editDialog = true
},
edit() { edit() {
if (isEmpty(this.row)) return elError('请选择要编辑的用户') if (isEmpty(this.row)) return elError('请选择要编辑的用户')
this.type = 'edit' this.type = 'edit'
this.editDialog = true this.editDialog = true
}, },
resetPwd() { resetPwd() {
if (isEmpty(this.row)) return elError('请选择要重置密码的用户') if (isEmpty(this.row)) return elError('请选择要重置密码的用户')
if (this.config.operating) return if (this.config.operating) return
elConfirm(`确定重置用户【${this.row.name}】的密码?`) elConfirm(`确定重置用户【${this.row.name}】的密码?`)
.then(() => { .then(() => {
this.config.operating = true this.config.operating = true
return resetUserPwd({id: this.row.id, name: this.row.name}) return resetUserPwd({id: this.row.id, name: this.row.name})
}) })
.then(({msg}) => { .then(({msg}) => {
elSuccess(msg) elSuccess(msg)
this.search() this.search()
}) })
.finally(() => this.config.operating = false) .finally(() => this.config.operating = false)
}, },
del() { del() {
if (isEmpty(this.row)) return elError('请选择要删除的用户') if (isEmpty(this.row)) return elError('请选择要删除的用户')
if (this.config.operating) return if (this.config.operating) return
elConfirm(`确定删除用户【${this.row.name}】?`) elConfirm(`确定删除用户【${this.row.name}】?`)
.then(() => { .then(() => {
this.config.operating = true this.config.operating = true
return delUser({id: this.row.id, name: this.row.name}) return delUser({id: this.row.id, name: this.row.name})
}) })
.then(({msg}) => { .then(({msg}) => {
elSuccess(msg) elSuccess(msg)
this.search() this.search()
}) })
.finally(() => this.config.operating = false) .finally(() => this.config.operating = false)
}, },
kick() { kick() {
if (isEmpty(this.row)) return elError('请选择要踢出的用户') if (isEmpty(this.row)) return elError('请选择要踢出的用户')
if (!this.row.online) return elError('只有在线用户才可以踢出') if (!this.row.online) return elError('只有在线用户才可以踢出')
if (this.config.operating) return if (this.config.operating) return
elConfirm(`确定踢出用户【${this.row.name}】?`) elConfirm(`确定踢出用户【${this.row.name}】?`)
.then(() => { .then(() => {
this.config.operating = true this.config.operating = true
return kick([{id: this.row.id, name: this.row.name}]) return kick([{id: this.row.id, name: this.row.name}])
}) })
.then(() => { .then(() => {
elSuccess('踢出成功') elSuccess('踢出成功')
this.search() this.search()
}) })
.finally(() => this.config.operating = false) .finally(() => this.config.operating = false)
}, },
success(msg) { success(msg) {
elSuccess(msg) elSuccess(msg)
this.editDialog = false this.editDialog = false
this.search() this.search()
}, },
previewAvatar(src) { previewAvatar(src) {
this.$image({index: 0, urlList: [src]}) this.$image({index: 0, urlList: [src]})
}
} }
} }
}
</script> </script>

@ -9,11 +9,11 @@
label-width="100px" label-width="100px"
status-icon status-icon
> >
<el-form-item label="旧密码:" prop="old_pwd"> <el-form-item label="旧密码:" prop="oldPwd">
<el-input v-model.trim="form.old_pwd" maxlength="100" type="password"/> <el-input v-model.trim="form.oldPwd" maxlength="100" type="password"/>
</el-form-item> </el-form-item>
<el-form-item label="新密码:" prop="new_pwd"> <el-form-item label="新密码:" prop="newPwd">
<el-input v-model.trim="form.new_pwd" maxlength="100" type="password"/> <el-input v-model.trim="form.newPwd" maxlength="100" type="password"/>
</el-form-item> </el-form-item>
</el-form> </el-form>
@ -40,15 +40,15 @@
return { return {
loading: false, loading: false,
form: { form: {
old_pwd: null, oldPwd: null,
new_pwd: null newPwd: null
}, },
rules: { rules: {
old_pwd: [{required: true, message: '请输入原密码', trigger: 'change'}], oldPwd: [{required: true, message: '请输入原密码', trigger: 'change'}],
new_pwd: [ newPwd: [
{required: true, message: '请输入新密码', trigger: 'change'}, {required: true, message: '请输入新密码', trigger: 'change'},
{ {
validator: (r, v, c) => v !== this.form.old_pwd ? c() : c('新密码不得与旧密码相同'), validator: (r, v, c) => v !== this.form.oldPwd ? c() : c('新密码不得与旧密码相同'),
trigger: 'change' trigger: 'change'
}, },
{ {
@ -68,8 +68,8 @@
methods: { methods: {
clearForm() { clearForm() {
this.form.old_pwd = null this.form.oldPwd = null
this.form.new_pwd = null this.form.newPwd = null
this.$nextTick(() => this.$refs.form.clearValidate()) this.$nextTick(() => this.$refs.form.clearValidate())
}, },
@ -79,8 +79,8 @@
if (!v) return if (!v) return
this.loading = true this.loading = true
updateUserPwd({ updateUserPwd({
old_pwd: md5(this.form.old_pwd), oldPwd: md5(this.form.oldPwd),
new_pwd: md5(this.form.new_pwd), newPwd: md5(this.form.newPwd),
}) })
.then(() => elSuccess('修改成功')) .then(() => elSuccess('修改成功'))
.finally(() => { .finally(() => {

@ -6,8 +6,11 @@
<li>登录账号 <li>登录账号
<div class="user-right">{{ name }}</div> <div class="user-right">{{ name }}</div>
</li> </li>
<li>所属部门
<div class="user-right">{{ deptName }}</div>
</li>
<li>用户角色 <li>用户角色
<div class="user-right">{{ role_name }}</div> <div class="user-right">{{ roleName }}</div>
</li> </li>
<li>安全设置 <li>安全设置
<div class="user-right"><a @click="modifyPasswordDialog=true">修改密码</a></div> <div class="user-right"><a @click="modifyPasswordDialog=true">修改密码</a></div>
@ -21,56 +24,58 @@
</template> </template>
<script> <script>
import UserAccount from './Account' import {mapState} from 'vuex'
import UploadAvatar from './Avatar' import UploadAvatar from './Avatar'
import {mapState} from 'vuex' import UserAccount from './Account'
export default { export default {
components: {UserAccount, UploadAvatar}, name: 'UserCard',
data() { components: {UploadAvatar, UserAccount},
return {
modifyPasswordDialog: false,
uploadAvatarDialog: false
}
},
computed: { data() {
...mapState('user', ['name', 'avatar', 'role_name', 'admin']) return {
modifyPasswordDialog: false,
uploadAvatarDialog: false
} }
},
computed: {
...mapState('user', ['name', 'avatar', 'roleName', 'deptName', 'admin'])
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
.user-card { .user-card {
.el-avatar { .el-avatar {
margin: 0 auto; margin: 0 auto;
display: block; display: block;
cursor: pointer; cursor: pointer;
i { i {
line-height: 180px; line-height: 180px;
font-size: 150px; font-size: 150px;
}
} }
}
.user-info { .user-info {
padding-left: 0; padding-left: 0;
list-style: none; list-style: none;
li { li {
border-bottom: 1px solid #F0F3F4; border-bottom: 1px solid #F0F3F4;
padding: 11px 0; padding: 11px 0;
font-size: 13px; font-size: 13px;
} }
.user-right { .user-right {
float: right; float: right;
a { a {
color: #317EF3; color: #317EF3;
}
} }
} }
} }
}
</style> </style>

@ -9,7 +9,7 @@
<el-tabs v-model="activeTab" stretch> <el-tabs v-model="activeTab" stretch>
<el-tab-pane v-for="i in tabs" v-once :key="i.label" :name="i.name"> <el-tab-pane v-for="i in tabs" v-once :key="i.label" :name="i.name">
<span slot="label"> <span slot="label">
{{i.label}} {{ i.label }}
<el-tooltip :content="i.intro" placement="top"> <el-tooltip :content="i.intro" placement="top">
<i v-if="i.intro" class="el-icon-question" style="color: #4AB7BD"/> <i v-if="i.intro" class="el-icon-question" style="color: #4AB7BD"/>
</el-tooltip> </el-tooltip>
@ -28,26 +28,23 @@
</template> </template>
<script> <script>
import UserCard from "./components/UserCard" import LoginHistory from "./components/LoginHistory"
import LoginHistory from "./components/LoginHistory" import UserAction from "./components/UserAction"
import UserCard from "./components/UserCard"
export default { export default {
name: 'userCenter', name: 'userCenter',
components: { components: {UserCard, LoginHistory, UserAction},
UserCard,
LoginHistory,
UserAction: () => import('./components/UserAction')
},
data() { data() {
return { return {
activeTab: 'login-history', activeTab: 'login-history',
tabs: [ tabs: [
{label: '登录历史', name: 'login-history', intro: '只保留最近7天的记录'}, {label: '登录历史', name: 'login-history', intro: '只保留最近7天的记录'},
{label: '操作记录', name: 'user-action', intro: '只保留最近7天的记录'}, {label: '操作记录', name: 'user-action', intro: '只保留最近7天的记录'},
] ]
}
} }
} }
}
</script> </script>

Loading…
Cancel
Save