Ai
376 Star 3.6K Fork 2.7K

唛盟开源/低代码开发平台-唛盟lcode

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
Index.vue 20.25 KB
一键复制 编辑 原始数据 按行查看 历史
Admin 提交于 2025-04-16 22:02 +08:00 . 同步xm-3.5.0-RELEASE
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
<template>
<content-wrap>
<el-space wrap class="padding-bottom">
<slot name="header">
<slot name="topToolbar">
</slot>
<el-popover placement="right-start" :title="title" width="200" trigger="hover" :show-after="200">
<el-space wrap>
<el-button type="primary" v-if="!hiddenCpd.addTop" @click="addTopNode_()" icon="plus" plain>{{''+title+''}}</el-button>
<slot name="moreBtn"></slot>
<el-button icon="refresh" type="success" v-if="!hiddenCpd.refresh" plain
@click.prevent.stop="refresh()" :title="'刷新整棵树,重新加载数据,一般在数据不一致时操作'">刷新</el-button>
<el-button type="success" v-if="!hiddenCpd.importTop" @click="showImportTop()" icon="upload" plain>{{'批量导入'}}</el-button>
<mdp-export excel word @excel="toExcel" @word="toWord"/>
<el-button v-if="!hiddenCpd.batchDel" type="danger" @click="batchDel_()" icon="delete" :title="'批量删除'" plain/>
<el-text type="info" size="small">鼠标在树节点中【停留】可以【增删改查】,节点支持【拖拽】</el-text>
<el-input v-if="!hiddenCpd.filter" style="width:100%;" v-model="filterText" placeholder="本地搜索" auto-complete="off" clearable/>
</el-space>
<template #reference>
<el-button type="primary" icon="more-filled" plain />
</template>
</el-popover>
</slot>
<el-button v-if="!hiddenCpd.changePid && idLinks.size>0" type="warning" @click="changePid_()" icon="top" :title="'拖拽后必须保存才能正式生效'" plain>拖拽后保存</el-button>
<el-button v-if="!hiddenCpd.changePid && idLinks.size>0" @click="cancelChangePid()" icon="close" :title="'取消保存'" plain>取消</el-button>
<el-button v-if="showConfirm" type="warning" @click="confirm" icon="check" title="确认选择" />
</el-space>
<el-scrollbar v-adaptive>
<el-tree :data="treeData" v-if="treeVisible" v-loading="loading.list" :props="propsCpd" :filter-node-method="filterNode"
:show-checkbox="showCheckbox" :expand-on-click-node="expandOnClickNode" :indent="indent" :node-key="propsCpd.id"
:default-expanded-keys="defaultExpandedKeys" :default-checked-keys="defaultCheckedKeys" auto-expand-parent
highlight-current @check-change="handleCheckChange" @current-change="handleCurrentChange" accordion
@node-click="handleNodeClick" :check-on-click-node="true" check-strictly :lazy="lazy" :load="loadNode" ref="nodeTree"
@contextmenu.prevent
:draggable="draggable"
@node-drag-start="handleDragStart"
@node-drag-enter="handleDragEnter"
@node-drag-leave="handleDragLeave"
@node-drag-over="handleDragOver"
@node-drag-end="handleDragEnd"
@node-drop="handleDrop"
:allowDrop="myAllowDrop"
>
<template #default="{ node, data }">
<div @contextmenu.prevent>
<el-popover :show-after="400" placement="right-start" :trigger="trigger">
<slot name="nodebox" :node="node" :data="data">
<el-space wrap>
<el-button icon="plus" circle type="success" v-if="!(!data[propsCpd['id']] || data[propsCpd['id']] == rootId) && !hiddenCpd.addSub"
@click.prevent.stop="addSubNode_(data, node)" :title="'添加子' + title" />
<el-button icon="upload" circle type="success" v-if="!(!data[propsCpd['id']] || data[propsCpd['id']] == rootId) && !hiddenCpd.addSub"
@click.prevent.stop="showImportSub(data, node)" :title="'批量导入' + title" />
<el-button icon="edit" circle type="warning" v-if="!(!data[propsCpd['id']] || data[propsCpd['id']] == rootId) && !hiddenCpd.edit"
@click.prevent.stop="editNode_(data, node)" :title="'修改' + title" />
<el-button icon="delete" circle type="danger" v-if="!(!data[propsCpd['id']] || data[propsCpd['id']] == rootId) && !hiddenCpd.del"
@click.prevent.stop="deleteNode_(data, node)" :title="'删除该' + title" />
<el-button icon="refresh" circle type="success" v-if="!hiddenCpd.refresh"
@click.prevent.stop="refresh()" :title="'刷新整棵树,重新加载数据,一般在数据不一致时操作'" />
<slot name="nodeToolbar" :data="data" :node="node">
</slot>
</el-space>
</slot>
<template #reference>
<div style="max-width:300px;" :title="data[propsCpd['label']]"><slot name="nodeName" :node="node" :data="data"> {{ data[propsCpd['label']] }}</slot></div>
</template>
</el-popover>
</div>
</template>
</el-tree>
</el-scrollbar>
<el-pagination v-if="!hiddenCpd.page" layout="total, sizes" :page-size="pageInfo.pageSize" :total="pageInfo.total" />
</content-wrap>
</template>
<script>
import { mapState } from 'pinia'
import { useUserStore } from '@/store/modules/user'
import {saveAs} from 'file-saver'
import { asBlob } from 'html-docx-js-typescript'
export default {
emits:['addTopNode','importTop','importSub','editNode','addSubNode','changePid','confirm','nodeClick'],
watch: {
filterText(val) {
this.$refs.nodeTree.filter(val);
},
checkedKeys(val) {
this.$refs.nodeTree.setCheckedKeys(val);
},
},
components: {
},
computed: {
defaultExpandedKeys() {
return this.expandRowKeys
},
defaultCheckedKeys() {
return this.checkedKeys;
},
...mapState(useUserStore, [
'userInfo'
]),
hiddenCpd(){
var hidden ={
refresh: false,
batchDel: false,
del:false,
changePid: false,
addTop: false,
importTop: true,
importSub: true,
addSub: false,
edit: false,
filter: false,
page: true,
}
hidden=Object.assign(hidden,this.hidden)
return hidden
},
propsCpd(){
var props={
id: 'id',
label: 'name',
pid: 'pid',
children: 'children',
isLeaf: function (n) {
return n.childNum <= 0
}
}
props=Object.assign(props,this.props)
return props
},
treeData(){
if(!this.lazy){
return this.listToTreeHash(this.originData)
}else {
return []
}
},
},
props: {
sortFun:{type: Function,default:null},
trigger:{type:String,default:'hover'},
'allowDrop':{type: Function,default:null},
'draggable':{type: Boolean,default: false},
'showCount': { type: Boolean, default: false },
'showFilter': { type: Boolean, default: false },
'multiple': { type: Boolean, default: false },
'checkedKeys': Array,
'defaultExpandAll': { type: Boolean, default: false },
'expandOnClickNode': { type: Boolean, default: false },
showCheckbox: { type: Boolean, default: false },
showConfirm: { type: Boolean, default: false },
'indent': Number,
load: { type: Function, default: null },
/**
* 主要是针对查询接口如果做了缓存,如果不先清理缓存,刷新还是从缓存load数据
*/
clearCacheFun: {type: Function, default: null},
rootId: { type: String, default: 'A0' },
title: {
type: String,
default: ''
},
props: {
type: Object,
default: () => {
return {
id: 'id',
label: 'name',
pid: 'pid',
children: 'children',
isLeaf: function (n,node) {
return n.childNum <= 0
}
}
}
},
lazy:{
type: Boolean,
default: true,
},
del:{
type: Function,
default:null
},
batchDel:{
type: Function,
default: null,
},
hidden:{
type:Object,
default:()=>{
return {
refresh:true,
batchDel: true,
del:false,
changePid: false,
addTop: false,
addSub: false,
importTop: true,
importSub: true,
edit: false,
filter: false,
page: true,
}
}
}
},
data() {
return {
idLinks:new Map(),//存储新的上下级关系,key=id,value=pid
oldIdLinks: new Map(), //存储原来的上下级关系,key=id,value=pid,只存一次,拖拽开始的时候就存入
draging: false,//拖拽成功后变成true,保存后变成false
filterText: '',
expandRowKeys: [],
root: null,
resolve: null,
treeVisible: true,
isAddTop: false,
current: {},
pageInfo:{
total:0,
pageSize: 500,
pageNum: 1,
count: true
},
originData:[],
loading:{add:false,edit:false,list:false,del:false}
}
},
methods: {
handleDragStart(node,event){
this.$emit("node-drag-start",node,event)
},
handleDragEnter(draggingNode,dropNode,event){
this.$emit("node-drag-enter",draggingNode,dropNode,event)
},
handleDragLeave(draggingNode,dropNode,event){
this.$emit("node-drag-leave",draggingNode,dropNode,event)
},
handleDragOver(draggingNode,dropNode,event){
this.$emit("node-drag-over",draggingNode,dropNode,event)
},
handleDragEnd(draggingNode,dropNode,dropType,event){
this.$emit("node-drag-end",draggingNode,dropNode,dropType,event)
},
/**
* 拖拽时判定目标节点能否成为拖动目标位置。 如果返回 false ,拖动节点不能被拖放到目标节点。 type 参数有三种情况:'prev'、'inner' 和 'next',分别表示放置在目标节点前、插入至目标节点和放置在目标节点后
*/
myAllowDrop(draggingNode,dropNode,dropType){
return this.allowDrop?this.allowDrop(draggingNode,dropNode,dropType):true
},
handleDrop(draggingNode,dropNode,dropType,event){
this.draging=true
let idKey=this.propsCpd['id']
let pidKey=this.propsCpd['pid']
let sdata=draggingNode.data
var pnode=dropNode
if(dropType!='inner'){
pnode=dropNode.parent
}
let edata=pnode.data
let oldPid=sdata[pidKey]
let newPid=edata[idKey]
if(pnode.level==0){
newPid=this.rootId
}
if(newPid==oldPid){
this.idLinks.delete(sdata[idKey])
}else{
this.idLinks.set(sdata[idKey],newPid)
}
if(dropType=="inner" && !pnode.expanded ){
var callback=null;
if(pnode.shouldLoadData()){
callback=()=>{
//加载数据后需要手动添加一个节点,bug
pnode.insertChild({data:sdata})
}
}
pnode.expand(callback)
}
this.$emit("node-drop",draggingNode,dropNode,dropType,event)
},
getCheckedKeys(){
return this.$refs.nodeTree.getCheckedKeys();
},
getCheckedNodes(){
return this.$refs.nodeTree.getCheckedNodes()
},
updateKeyChildren(key,data){
this.$refs['nodeTree'].updateKeyChildren(key,data)
},
addTopNode(data){
if(this.clearCacheFun){
this.clearCacheFun()
}
this.$refs['nodeTree'].append(data,this.root)
},
addTopNode_() {
var callback = (data) => {
this.addTopNode({...data})
}
this.$emit('addTopNode',callback)
},
showImportTop(){
var callback = () => {
this.refresh()
}
this.$emit('importTop',callback)
},
showImportSub(parent){
var callback = () => {
this.refresh()
}
this.$emit('importSub',parent,callback)
},
addSubNode(parent,data){
if(!data){
return
}
var pnode=this.$refs.nodeTree.getNode(parent[this.propsCpd['id']])
if(!pnode.expanded ){
pnode.expand()
}else{
pnode.insertChild({data:data})
}
if(this.clearCacheFun){
this.clearCacheFun()
}
},
addSubNode_(parent, node, comp) {
var callback = (data) => {
this.addSubNode(parent,{...data})
}
this.$emit('addSubNode',parent,callback)
},
editNode_(cdata, node, comp) {
var callback = (data) => {
if(!data){
return
}
Object.assign(node.data,data)
}
var addSubCallback = (subData) => {
this.addSubNode(cdata,subData)
}
this.$emit('editNode',cdata,callback,addSubCallback)
},
deleteNode_(data, node, comp) {
// if (!this.propsCpd.isLeaf(data)) {
// this.$message.error("请先删除子元素");
// return;
// }
this.$confirm('确认删除吗?', '提示', {}).then(() => {
var params = {}
params[this.propsCpd['id']]=data[this.propsCpd['id']]
this.del(params,data,node).then(res => {
//console.log("res--"+JSON.stringify(res));
if (res.tips.isOk) {
if(this.clearCacheFun){
this.clearCacheFun()
}
this.$refs['nodeTree'].remove(data)
this.$message.success(res.tips.msg);
} else {
this.$message.error(res.tips.msg);
}
});
})
return false;
},
cancelChangePid(){
this.refresh()
},
changePid_(){
if(this.idLinks.length==0){
this.$message.error("没有数据改变,无须保存");
return;
}
var callback = (refresh) => {
this.idLinks=new Map()
if(refresh)this.refresh()
}
var list=[]
this.idLinks.forEach((value, key) => {
let d={}
d[this.propsCpd['id']]=key
d[this.propsCpd['pid']]=value
list.push(d)
});
this.$emit('changePid',list,callback)
},
batchDel_() {
let checkedKeys = this.$refs.nodeTree.getCheckedKeys();
if(checkedKeys.length==0){
this.$message.error("请选择需要删除的记录");
return;
}
this.$confirm('确认删除吗?', '提示', {}).then(() => {
var params=[]
checkedKeys.forEach(k=>{
var d={}
d[this.propsCpd['id']]=k
params.push(d)
})
this.batchDel(params).then(res => {
if (res.tips.isOk) {
this.refresh()
this.$message.success(res.tips.msg);
} else {
this.$message.error(res.tips.msg);
}
});
})
return false;
},
handleCheckChange(data, checked, indeterminate) {
let checkedKeys = this.$refs.nodeTree.getCheckedKeys();
if (this.multiple === undefined || this.multiple === false || this.multiple === 'false') {
if (checked == true) {
if (checkedKeys.length > 1) {
this.$refs.nodeTree.setCheckedKeys([data[this.propsCpd['id']]]);
this.$emit('check-change', data, checked, indeterminate);
} else {
this.$emit('check-change', data, checked, indeterminate);
}
} else {
if (checkedKeys.length == 0) {
this.$emit('check-change', data, checked, indeterminate);
}
}
} else {
this.$emit('check-change', data, checked, indeterminate);
}
},
handleCurrentChange(data, node) {
this.$emit('current-change', data, node);
},
handleNodeClick(data, node, comp) {
this.$emit('node-click', data, node, comp);
},
listToTreeHash(list) {
var pidKey = this.propsCpd['pid']
var idKey = this.propsCpd['id']
var labelKey = this.propsCpd['label']
const map = new Map(), roots = []
list.forEach(item => {
map.set(item[idKey], { ...item, children: [] })
})
list.forEach(item => {
const node = map.get(item[idKey])
if (item[pidKey] && map.has(item[pidKey])) {
map.get(item[pidKey]).children.push(node)
} else {
roots.push(node)
}
})
return roots
},
translateDataToTree(data) {
if (!data) {
return [];
}
return data
},
//重新获取部门树列表
refresh() {
if(this.clearCacheFun){
this.clearCacheFun()
}
this.idLinks=new Map()
if(this.lazy){
this.treeVisible = false
this.$nextTick(() => {
this.treeVisible = true
})
}else{
this.loadNotLazy()
}
},
filterNode(value, data) {
var pidKey = this.propsCpd['pid']
var idKey = this.propsCpd['id']
var labelKey = this.propsCpd['label']
if (!value) return true;
if (data[idKey].indexOf(value) >= 0 || data[idKey].indexOf(value) >= 0) {
return true;
} else {
if (data[labelKey] && data[labelKey].indexOf(value) >= 0) {
return true;
}
}
return false;
},
confirm() {
var nodes = this.$refs.nodeTree.getCheckedNodes(false, false)
if (this.multiple) {
this.$emit('confirm', nodes)
} else {
this.$emit('confirm', nodes && nodes.length > 0 ? nodes[0] : null)
}
},
getCodeKey(){
var itemCode=""
if(this.load){
itemCode=this.load.name
}
var codeKey='mdp-tree'+'@'+itemCode+'@'+this.rootId
return codeKey;
},
loadNotLazy(){
let params = {};
//params[this.propsCpd['pid']] = node.data[this.propsCpd['id']]
this.loading.list = true;
this.load(params).then((res) => {
var tips = res.tips;
var data = res.data;
if(this.sortFun){
data.sort(this.sortFun)
}
this.originData=data
this.loading.list = false;
}).catch(() => {
this.loading.list = false;
});
},
loadNode(node, resolve) {
if (node.level === 0) {
this.root = node
this.resolve = resolve
let params = {};
params[this.propsCpd['pid']] = this.rootId
this.loading.list = true;
this.load(params, node, resolve).then((res) => {
var tips = res.tips;
var data = res.data;
if(this.sortFun){
data.sort(this.sortFun)
}
this.loading.list = false;
var tempData = this.translateDataToTree(data);
resolve(tempData)
}).catch((err) => {
this.loading.list = false;
});
} else {
setTimeout(() => {
let params = {};
params[this.propsCpd['pid']] = node.data[this.propsCpd['id']]
this.loading.list = true;
this.load(params, node, resolve).then((res) => {
var tips = res.tips;
var data = res.data;
if(this.sortFun){
data.sort(this.sortFun)
}
this.loading.list = false;
resolve(this.translateDataToTree(data))
}).catch(() => {
this.loading.list = false;
});
}, 500);
}
},
toExcel(){
this.export2Excel()
},
toWord(){
let tree=this.$refs['nodeTree'];
let datas=[]
let callback=(curr,parent,lvl)=>{
if(!curr.data||Object.keys(curr.data).length<=0){
return;
}else{
curr.data.lvl_=lvl
datas.push(curr.data)
}
}
this.treeCfg(tree.root.childNodes,callback)
if(datas.length==0){
this.$message.error('没有数据可以导出')
return;
}
let seqNoKey=this.propsCpd['seqNo']
let nameKey=this.propsCpd['label']
let idKey=this.propsCpd['id']
let pidKey=this.propsCpd['pid']
let htmlList=datas.map((k,index)=>{
let html=`<h${k.lvl_+1}>${k[seqNoKey]||''} ${k[nameKey]}</h${k.lvl_+1}>
<p>编号: ${k[idKey]} 上级编号: ${k[pidKey]}</p>
`
return html;
})
let all=htmlList.join("\n")
import("@/components/mdp-ui/js/Export2Word").then(word => {
var fieldName=this.title + this.$mdp.formatDate(new Date(),'yyyyMMddHHmmss');
word.html2Word(all,fieldName)
});
},
export2Excel(){
;
let tree=this.$refs['nodeTree'];
let datas=[]
let callback=(curr,parent,lvl)=>{
if(!curr.data||Object.keys(curr.data).length<=0){
return;
}else{
datas.push(curr.data)
}
}
this.treeCfg(tree.root.childNodes,callback)
if(datas.length==0){
this.$message.error('没有数据可以导出')
return;
}
let oneRow=datas[0]
let propertys=Object.keys(oneRow)
let cols=[]
let idSet=new Set()
if(this.propsCpd['seqNo']){
cols.push({property:this.propsCpd['seqNo'],label:'序号'})
idSet.add(this.propsCpd['seqNo'])
}
cols.push({property:this.propsCpd['label'],label:'名称'})
idSet.add(this.propsCpd['label'])
cols.push({property:this.propsCpd['id'],label:'编号'})
idSet.add(this.propsCpd['id'])
cols.push({property:this.propsCpd['pid'],label:'上级编号'})
idSet.add(this.propsCpd['pid'])
propertys.forEach(p=>{
if(!idSet.has(p)){
cols.push({property:p,label:p})
}
})
import("@/vendor/Export2Excel").then(excel => {
var fieldName=this.title + this.$mdp.formatDate(new Date(),'YYYYMMDDHHmmss');
excel.export_json_to_excel({
header: cols.map(c=>c.label),
data:datas.map(d=>cols.map(c=>d[c.property])),
filename: fieldName,
autoWidth: true,
bookType: "xlsx"
});
});
},
/**
* 遍历一棵树,对其节点进行数据转换 先根遍历
* @param {*} tree any[{children?:[]}]
* @param {*} callback callback(currNode,parent)
* returns void
*/
treeCfg(tree,callback){
if(!tree){
return;
}
let treeCfg_ = (currNode,parent,callback,lvl2) => {
if(!currNode){
return;
}
callback(currNode,parent,lvl2)
if(currNode.childNodes){
currNode.childNodes.forEach(c=>{
treeCfg_(c,currNode,callback,lvl2+1)
})
}
}
tree.forEach(k=>{
callback(k,null,0)
if(k.childNodes){
k.childNodes.forEach(c=>{
treeCfg_(c,k,callback,1)
})
}
})
},
},
mounted() {
if(!this.lazy){
this.loadNotLazy()
}
}
}
</script>
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
JavaScript
1
https://gitee.com/maimengcloud/mdp-lcode-ui-web.git
git@gitee.com:maimengcloud/mdp-lcode-ui-web.git
maimengcloud
mdp-lcode-ui-web
低代码开发平台-唛盟lcode
master

搜索帮助