部门调整为树结构

This commit is contained in:
aixianling
2023-05-25 14:54:46 +08:00
parent 773913ef89
commit 5f8850dc00
6 changed files with 165 additions and 40 deletions

View File

@@ -20,6 +20,7 @@
"src/common/http.js", "src/common/http.js",
"src/common/crypto-js.js", "src/common/crypto-js.js",
"src/common/observer.js", "src/common/observer.js",
"src/common/tree.js",
"src/apps", "src/apps",
"src/styles" "src/styles"
], ],

57
src/common/tree.js Normal file
View File

@@ -0,0 +1,57 @@
class Tree {
constructor(list = [], config) {
this.config = {
key: 'id', parent: 'parentId', children: 'children',
...config
}
this.list = list
if (Array.isArray(list)) this.tree = this.arr2tree(list)
}
arr2tree(list) {
const {key, parent, children} = this.config
const result = []
this.map = {}
const ids = list?.map(e => `#${e[key]}#`)?.toString()
for (const e of list) {
const id = e[key], pid = e[parent]
this.map[id] = {...e, [children]: [this.map[id]?.[children]].flat().filter(Boolean)}
const treeItem = this.map[id]
if (!!pid && ids.indexOf(`#${pid}#`) > -1) {
if (!this.map[pid]) {
this.map[pid] = {
children: []
}
}
this.map[pid].children.push(treeItem)
} else result.push(treeItem)
}
const removeNullChildren = node => {
if (node[children] && node[children].length > 0) {
node[children].map(c => removeNullChildren(c))
} else delete node[children]
}
result.forEach(removeNullChildren)
return result
}
root(id) {
return this.map[id]
}
find(id) {
return this.map[id]
}
every(cb) {
const iterate = list => {
list?.map(e => {
cb(e)
iterate(e.children)
})
}
iterate(this.tree)
}
}
export default Tree

View File

@@ -308,5 +308,31 @@ export default {
qs, qs,
permissions, permissions,
copy, copy,
reg reg,
arr2tree(list, config = {}) {
const {key = 'id', parent = 'parentId', children = 'children'} = config
const result = []
const itemMap = {}
const ids = list?.map(e => `#${e[key]}#`)?.toString()
for (const e of list) {
const id = e[key], pid = e[parent]
itemMap[id] = {...e, [children]: [itemMap[id]?.[children]].flat().filter(Boolean)}
const treeItem = itemMap[id]
if (!!pid && ids.indexOf(`#${pid}#`) > -1) {
if (!itemMap[pid]) {
itemMap[pid] = {
children: []
}
}
itemMap[pid].children.push(treeItem)
} else result.push(treeItem)
}
const removeNullChildren = node => {
if (node[children] && node[children].length > 0) {
node[children].map(c => removeNullChildren(c))
} else delete node[children]
}
result.forEach(removeNullChildren)
return result
}
} }

View File

@@ -0,0 +1,38 @@
<template>
<section class="AiTreePath">
<AiTreePath v-if="parent" v-bind="$props" :current="parent" v-on="$listeners"/>
<div v-else v-text="`全部`" @click="$emit('click','all')"/>
<u-icon v-if="label" name="arrow-right" size="32" color="#ccc"/>
<div v-text="label" @click="$emit('click',current)"/>
</section>
</template>
<script>
export default {
name: "AiTreePath",
props: {
current: {default: null},
prop: {default: () => ({})},
paths: {default: () => []}
},
computed: {
options: v => ({
id: 'id',
label: 'name',
parent: 'parentId',
...v.prop
}),
label: v => v.current?.[v.options.label] || "",
parent: v => v.paths?.[v.current?.[v.options.parent]]
}
}
</script>
<style scoped lang="scss">
.AiTreePath {
display: flex;
align-items: center;
gap: 8px;
font-size: 36px;
line-height: 40px;
font-family: PingFang-SC;
}
</style>

View File

@@ -1,15 +1,18 @@
<template> <template>
<div class="selectDept"> <div class="selectDept">
<AiTopFixed> <AiTopFixed>
<u-search placeholder="搜索" v-model="name" :show-action="false" @change="getList()"/> <AiTreePath :current="cursor" :paths="depts.map" :prop="{parent:'parentid'}" @click="changeList"/>
</AiTopFixed> </AiTopFixed>
<div class="user-list"> <div class="user-list">
<template v-if="list.length>0"> <template v-if="list.length>0">
<div class="item" v-for="(item, index) in list" :key="index"> <div class="item" v-for="(item, index) in list" :key="index" flex>
<div class="select-img" @click="checkClick(index)"> <div class="select-img" @click="checkClick(item)">
<img :src="item.isCheck ? checkIcon : cirIcon" alt=""> <img :src="item.isCheck ? checkIcon : cirIcon" alt="">
</div> </div>
<div class="user-info">{{ item.name }}</div> <div class="user-info fill" flex @click="getCursor(item)">
<div class="fill" v-text="item.name"/>
<u-icon v-if="Array.isArray(item.children)" class="mar-r16" name="arrow-right" size="40" color="#999"/>
</div>
</div> </div>
</template> </template>
<template v-else> <template v-else>
@@ -26,6 +29,7 @@
<script> <script>
import {mapState} from 'vuex' import {mapState} from 'vuex'
import Tree from "../../common/tree";
export default { export default {
name: "selectDept", name: "selectDept",
@@ -36,55 +40,46 @@ export default {
total: 0, total: 0,
name: '', name: '',
list: [], list: [],
depts: [],
cirIcon: require('./img/xz.png'), cirIcon: require('./img/xz.png'),
checkIcon: require('./img/xzh.png'), checkIcon: require('./img/xzh.png'),
selected: [] selected: [],
cursor: null
} }
}, },
computed: { computed: {
...mapState(['user']), ...mapState(['user']),
isSingle() { isSingle: v => !!v.$route.query.single,
return this.$route.query.single nodeKey: v => v.$route.query.nodeKey || "idNumber",
}, isRequire: v => v.$route.query.isRequire || 1
nodeKey() {
return this.$route.query.nodeKey || "idNumber"
},
isRequire() {
return this.$route.query.isRequire || 1
}
}, },
onLoad(query) { onLoad(query) {
console.log(query)
if (query.selected) { if (query.selected) {
this.selected = query.selected?.split(",") || [] this.selected = query.selected?.split(",").map(e => Number(e)) || []
} }
this.selected.map((item, index) => {
this.selected[index] = parseInt(item)
})
this.getList() this.getList()
}, },
methods: { methods: {
getList() { getList() {
this.$http.post(`/app/wxcp/wxdepartment/listAll?name=${this.name}`).then(res => { this.$http.post(`/app/wxcp/wxdepartment/listAll?name=${this.name}`).then(res => {
if (res?.data) { if (res?.data) {
res.data.forEach(e => { res.data.forEach(e => e.isCheck = this.selected.includes(e[this.nodeKey]))
e.isCheck = this.selected.includes(e[this.nodeKey]) this.depts = new Tree(res.data, {parent: 'parentid'})
}) this.list = this.depts.tree
this.list = res.data
} }
}) })
}, },
checkClick(index) { checkClick(item) {
if (this.isSingle && this.isRequire == 1) { if (this.isSingle && this.isRequire == 1) {
this.list.map((e, i) => { this.depts.every(e => {
e.isCheck = i == index; e.isCheck = item.id == e.id
}) })
} else this.list[index].isCheck = !this.list[index].isCheck } else item.isCheck = !item.isCheck
}, },
confirm() { confirm() {
let checkList = [] let checkList = []
this.list.map((item) => { this.depts.every((item) => {
if (item.isCheck) { if (item.isCheck) {
checkList.push(item) checkList.push(item)
} }
@@ -98,6 +93,21 @@ export default {
} }
}) })
} }
},
changeList(item) {
if (item == "all") {
this.list = this.depts.tree
this.cursor = null
} else {
this.cursor = item
this.list = item.children
}
},
getCursor(item) {
if (item.children?.length > 0) {
this.cursor = item
this.list = item.children
}
} }
}, },
} }
@@ -117,6 +127,8 @@ export default {
background-color: #fff; background-color: #fff;
.item { .item {
width: 100vw;
.select-img { .select-img {
display: inline-block; display: inline-block;
@@ -129,9 +141,7 @@ export default {
} }
.user-info { .user-info {
display: inline-block;
padding: 20px 0 20px 0; padding: 20px 0 20px 0;
width: calc(100% - 114px);
height: 100%; height: 100%;
border-bottom: 1px solid #E4E5E6; border-bottom: 1px solid #E4E5E6;
font-size: 36px; font-size: 36px;
@@ -139,14 +149,6 @@ export default {
font-weight: 500; font-weight: 500;
color: #333; color: #333;
line-height: 74px; line-height: 74px;
img {
width: 74px;
height: 74px;
border-radius: 8px;
margin-right: 34px;
vertical-align: bottom;
}
} }
} }
} }

View File

@@ -14,7 +14,8 @@
"common/http.js", "common/http.js",
"common/crypto-js.js", "common/crypto-js.js",
"common/regular.js", "common/regular.js",
"common/observer.js" "common/observer.js",
"common/tree.js"
], ],
"dependencies": { "dependencies": {
} }