Browse Source

树穿梭框

liwenxuan_v4
liwenxuan 2 years ago
parent
commit
c485d6eb5c
  1. 27
      src/components/DesignForm/assembly/index.ts
  2. 227
      src/components/DesignForm/formControlAttr.vue
  3. 1
      src/components/DesignForm/public/form/formGroup.vue
  4. 2
      src/widget/index.ts
  5. 181
      src/widget/lowcodetransfer/index.vue
  6. 393
      src/widget/lowcodetransfer/lowcodeTransfer.vue

27
src/components/DesignForm/assembly/index.ts

@ -358,6 +358,33 @@ const selectOption: any = [
}
},
config: {}
},
{
type: 'lowcodeTransfer',
label: '穿梭框',
icon: '',
iconFont: 'fa-arrows-h',
control: {
modelValue: '',
fixedOptions: [{
id: 'thefirstrootnode',
label: '根节点1',
disabled: false,
children: []
},
{
id: 'thesecondrootnode',
label: '根节点2',
disabled: false,
children: []
}]
},
config: {
transferName:'穿梭框',
transferDataSource:'固定选项',
apiUrl:'',
method:'',
}
}
]
},

227
src/components/DesignForm/formControlAttr.vue

@ -18,6 +18,8 @@ import { uploadUrl } from '@/api/DesignForm'
import { UploadFilled } from '@element-plus/icons-vue'
import { UploadFile, UploadFiles } from 'element-plus/es/components/upload/src/upload';
import type Node from 'element-plus/es/components/tree/src/model/node'
const props = withDefaults( //
defineProps<{
formData: any
@ -598,6 +600,24 @@ const attrList = computed(() => {
vIf: state.isSearch,
vShow: ['lowcodeCarsusel']
},
{
label: '穿梭框名',
value: config.transfer,
path: 'config.transfer',
type: 'transfer_name',
vIf: state.isSearch,
vShow: ['lowcodeTransfer']
},
{
label: '选项数据源',
value: config.transfer,
path: 'config.transfer',
type: 'transfer_options_datasource',
vIf: state.isSearch,
vShow: ['lowcodeTransfer']
},
{
label: '上传视频',
value: config.uploadvideo,
@ -1333,6 +1353,9 @@ import { v4 as uuidv4 } from "uuid";
//
const dialogTableVisible = ref(false)
//穿
const transferDialogTableVisible = ref(false)
//
const showImagePreview = ref(false)
@ -1421,6 +1444,96 @@ const changeCurrentUploadImgid = (clickedRow:any) => {
//liwenxuan 20240111 carousel end
//liwenxuan 20240217 transfer start
const transferDataSourceOptions = [
{
value: '数据源',
label: '数据源',
},
{
value: '固定选项',
label: '固定选项',
},
]
//!!!!!!!!!!!!!!!start
const dataSource = ref<Tree[]>([])
interface Tree {
id: string
label: string
disabled: boolean
children?: Tree[]
}
const addRootNode = () => {
let onlyNumber = uuidv4().replaceAll('-','').toString();
controlData.value.control.fixedOptions.push({
id: onlyNumber,
label: '新根节点',
disabled: false,
children: [],
})
}
const append = (data: Tree) => {
let onlyNumber = uuidv4().replaceAll('-','').toString();
const newChild = { id: onlyNumber, label: '新节点', disabled:false,children: [] }
if (!data.children) {
data.children = []
}
data.children.push(newChild)
dataSource.value = [...dataSource.value]
}
const remove = (node: Node, data: Tree) => {
let really = confirm("确认删除吗?\n将删除本节点与本节点的所有子孙节点!");
if(really){
const parent = node.parent
const children: Tree[] = parent.data.children || parent.data
const index = children.findIndex((d) => d.id === data.id)
children.splice(index, 1)
dataSource.value = [...dataSource.value]
}
}
const changeLabel = (node:Node,data:Tree) =>{
let favDrink = prompt("请输入:");
if(favDrink!=null && favDrink.length>0){
const parent = node.parent
const children: Tree[] = parent.data.children || parent.data
const index = children.findIndex((d) => d.id === data.id)
children[index].label = favDrink
dataSource.value = [...dataSource.value]
}
}
const setNodeEnable = (node:Node,data:Tree) =>{
const parent = node.parent
const children: Tree[] = parent.data.children || parent.data
const index = children.findIndex((d) => d.id === data.id)
children[index].disabled = !children[index].disabled
dataSource.value = [...dataSource.value]
}
//!!!!!!!!!!!!!!!end
//liwenxuan 20240217 transfer end
</script>
<template>
@ -1509,7 +1622,51 @@ const changeCurrentUploadImgid = (clickedRow:any) => {
<el-button type="primary" append-to-body="true" modal="true" @click="dialogTableVisible = true" >轮播图设置</el-button>
</el-row>
<el-row v-else-if="item.type === 'transfer_name'">
<el-input
v-model="controlData.config.transferName"
placeholder="请输入穿梭框名"
style="width:221px"
/>
</el-row>
<el-row v-else-if="item.type ==='transfer_options_datasource'">
<!-- <span style="display:inline-block;width:78px">选项数据源</span> -->
<el-select
v-model="controlData.config.transferDataSource"
placeholder="选项数据源"
style="width: 150px; height:20px">
<el-option
v-for="dataSourceOption1 in transferDataSourceOptions"
:key="dataSourceOption1.value"
:label="dataSourceOption1.label"
:value="dataSourceOption1.value"
/>
</el-select>
<div v-if="controlData.config.transferDataSource==='固定选项'" style="width: auto;margin-top:33px;margin-left:-220px;padding-bottom: 6px;">
<el-button type="primary" append-to-body="true" modal="true" @click="transferDialogTableVisible = true" >编辑固定选项</el-button>
</div>
<div v-if="controlData.config.transferDataSource==='数据源'" style="width: auto;margin-top: 20px;">
<!-- <span style="display:inline-block;width:50px;margin-bottom: 25px;">接口url</span> -->
<el-input
v-model="controlData.config.apiUrl"
style="width:278px;margin-left: -68px;margin-top: -5px;"
placeholder="数据源接口url">
<template #prepend>
<el-select
v-model="controlData.config.method"
style="width: 100px">
<el-option label="get" value="get" />
<el-option label="post" value="post" />
</el-select>
</template>
</el-input>
</div>
</el-row>
<el-input
@ -1694,7 +1851,7 @@ const changeCurrentUploadImgid = (clickedRow:any) => {
<template v-else>
<el-form-item>
<el-input
v-model="controlData.config.optionsFun"
v-model="controlData.config.apiUrl"
:placeholder="
getOptionPlaceholder(controlData.config.optionsType)
"
@ -2006,7 +2163,7 @@ const changeCurrentUploadImgid = (clickedRow:any) => {
</div>
<!-- 轮播图设置弹窗 20240122 start -->
<el-dialog v-model="dialogTableVisible" title="轮播图设置" top="20px" style="margin-top:70px,margin-left:270px">
<el-dialog v-model="dialogTableVisible" title="轮播图设置" top="20px" style="margin-top:70px">
<div v-if="controlData.type=='lowcodeCarsusel'" style="margin-bottom: 30px;">
<span style="margin-right: 10px;">轮播图宽度(像素):</span><el-input-number v-model="controlData.control.config.carsuselWidth" :step="64" :max="4096" :min="178"/>
<span style="margin-left:15px;margin-right: 10px;">轮播图高度(像素):</span><el-input-number v-model="controlData.control.config.carsuselHeight" :step="36" :max="4096" :min="100"/>
@ -2057,7 +2214,7 @@ const changeCurrentUploadImgid = (clickedRow:any) => {
</el-table-column>
</el-table>
</el-dialog>
<!-- 图片预览 -->
<!-- 图片预览 961.42-->
<el-image-viewer
v-if="showImagePreview"
:zoom-rate="1.2"
@ -2066,7 +2223,71 @@ const changeCurrentUploadImgid = (clickedRow:any) => {
/>
<!-- 轮播图设置弹窗 liwenxuan 20240122 end -->
<!-- 穿梭框设置弹窗 liwenxuan 20240217 start -->
<el-dialog v-model="transferDialogTableVisible" title="创建穿梭框选项树" top="150px" style="margin-top:70px" width="50%">
<div v-if="controlData.type=='lowcodeTransfer'">
<el-button type="success" plain style="margin-top: -35px;" @click="addRootNode()">新增根节点</el-button>
<!-- 建立树 -->
<div class="custom-tree-container">
<el-tree
:data="controlData.control.fixedOptions"
show-checkbox
node-key="id"
default-expand-all
:expand-on-click-node="false"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<span>{{ node.label }}</span>
<span style="float: right;">
<a style="color: green;" @click="append(data)"> 新增子节点 </a>
<a style="margin-left: 8px;" @click="changeLabel(node,data)"> 编辑节点名 </a>
<a style="margin-left: 8px;color: orange;" @click="setNodeEnable(node,data)"> 设为可选/不可选 </a>
<a style="margin-left: 8px;color: red;" @click="remove(node, data)"> 删除本节点 </a>
</span>
</span>
</template>
</el-tree>
</div>
</div>
</el-dialog>
<!-- 穿梭框设置弹窗 liwenxuan 20240217 end -->
<!-- <el-dialog v-model="dialogFormVisible_tree_edit" title="Shipping address" width="500">
<el-input v-model="nodeLabel" autocomplete="off" />
<template #footer>
<div class="dialog-footer">
<el-button @click="dialogFormVisible = false">Cancel</el-button>
<el-button type="primary" @click="dialogFormVisible = false">
Confirm
</el-button>
</div>
</template>
</el-dialog> -->
</template>
<style lang='scss' scoped>
//!!!!!!!!!!!!!!!!!穿start
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
.custom-tree-node:hover{
background-color:antiquewhite;
}
//!!!!!!!!!!!!!!!!!穿end
</style>

1
src/components/DesignForm/public/form/formGroup.vue

@ -426,6 +426,7 @@ onMounted(()=>{
</div>
</template>
<LowcodeTransfer v-else-if="element.type === 'lowcodeTransfer'" :data="element" />
<VideoUpAndPlay v-else-if="element.type === 'videoUpAndPlay'" :data="element" />
<LowcodeCarsusel v-else-if="element.type === 'lowcodeCarsusel'" :data="element" />
<SignatureMap v-else-if="element.type === 'signaturemap'" :data="element" />

2
src/widget/index.ts

@ -9,6 +9,7 @@ import orgCentent from './org/index.vue'
import signatureMap from './writingboard/index.vue'
import videoUpAndPlay from './videoupload/index.vue'
import lowcodeCarsusel from './carousel/index.vue'
import lowcodeTransfer from './lowcodetransfer/index.vue'
export default (app: any) => {
app.component('SerialNumber', serialNumber)
@ -19,4 +20,5 @@ export default (app: any) => {
app.component('SignatureMap', signatureMap)
app.component('VideoUpAndPlay',videoUpAndPlay)
app.component('LowcodeCarsusel',lowcodeCarsusel)
app.component('LowcodeTransfer',lowcodeTransfer)
}

181
src/widget/lowcodetransfer/index.vue

@ -0,0 +1,181 @@
<!--
@ 作者: 李文轩
@ 时间: 2024-02-07 13:49:57
@ 备注:
-->
<template>
<el-form-item
v-bind="data.item"
:prop="tProp || data.name"
:class="config.className"
:rules="itemRules as any"
:label="getLabel(data.item as FormItem)"
>
<input v-model="value" type="hidden" >
</el-form-item>
<LowcodeTransfer :data="props.data"></LowcodeTransfer>
</template>
<script lang='ts' setup>
import LowcodeTransfer from './lowcodeTransfer.vue';
import {
constControlChange,
constFormProps,
} from '@/api/DesignForm/utils'
import validate from '@/api/DesignForm/validate'
import { FormItem, FormList } from '@/api/DesignForm/types'
const props = withDefaults(
defineProps<{
data: FormList
tablekey: any
numrun?: number
modelValue?: any //
tProp?: string // form-itemprop
}>(),
{}
)
const emits = defineEmits<{
(e: 'update:modelValue', numVal: any): void
}>()
const formProps = inject(constFormProps, {}) as any
const type = computed(() => {
return formProps.value.type
})
const config = computed(() => {
return props.data.config || {}
})
const changeEvent = inject(constControlChange, '') as any
const value = computed({
get() {
if (props.tProp) {
//
return props.modelValue
} else {
return formProps.value.model[props.data.name]
}
},
set(newVal: any) {
if (props.tProp) {
emits('update:modelValue', newVal)
}
updateModel(newVal)
}
})
const updateModel = (val: any) => {
let controlAttribute = ""
if(props.data.control){
if(props.data.control.type){
controlAttribute = props.data.control.type
}
}
changeEvent &&
changeEvent({
key: props.data.name,
value: val,
data: props.data,
tProp: props.tProp,
type: props.data.type,
attribute: controlAttribute
})
}
const getLabel = (ele: FormItem) => {
const showColon = formProps.value.showColon ? ':' : ''
if (ele) {
return ele.showLabel ? '' : ele.label + showColon
} else {
return ''
}
}
// item
const itemRules = computed(() => {
let temp
const itemR: any = props.data.item?.rules || []
const customR = formatCustomRules()
// undefined
if (itemR?.length || customR?.length) {
temp = [...customR, ...itemR]
}
return temp
})
// customRulesrules
const formatCustomRules = () => {
const rulesReg: any = {}
validate &&
validate.forEach(item => {
rulesReg[item.type] = item.regExp
})
// 使provide
const temp: any = []
props.data.customRules?.forEach((item: any) => {
if (!item.message && item.type !== 'methods') {
return //
}
let obj = {}
if (item.type === 'required') {
obj = { required: true }
} else if (item.type === 'rules') {
//
obj = { pattern: item.rules }
} else if (item.type === 'methods') {
//
const methods: any = item.methods
if (methods) {
obj = { validator: inject(methods, {}) }
}
} else if (item.type) {
obj = { pattern: rulesReg[item.type as string] }
}
// push
let message: any = { message: item.message }
if (!item.message) {
// 使validatormessage使 callback(new Error('x'));
message = {}
}
temp.push(
Object.assign(
{
trigger: item.trigger || 'blur'
},
obj,
message
)
)
})
return temp
}
</script>
<style lang='scss' scoped>
.imgbox{
padding: 0 5px;
max-width: 300px;
max-height: 200px;
width: 100%;
height: 200px;
.image-slot {
display: flex;
justify-content: center;
align-items: center;
width: 100%;
height: 100%;
background: var(--el-fill-color-light);
color: var(--el-text-color-secondary);
font-size: 30px;
}
}
</style>

393
src/widget/lowcodetransfer/lowcodeTransfer.vue

@ -0,0 +1,393 @@
<template>
<div v-if="dataFinished" class="transfer">
<div class="leftArea">
<div class="transferName">穿梭框名</div>
<div style="padding-left: 15px; padding-right: 25px;margin-top: 10px;margin-bottom: 8px;">
<ElInput v-model="keyword" placeholder="搜索">
<template #prefix>
<ElIcon class="el-input__icon">
<Search />
</ElIcon>
</template>
</ElInput>
</div>
<div class="leftScroll">
<ElScrollbar height="100%">
<ElTree
ref="treeRef" node-key="id" empty-text="暂无数据" :data="userList" :props="treeProps" show-checkbox
highlight-current :default-expand-all="isExpandAll" :filter-node-method="filterNode"
@check="handleCheckList" @check-change="getCheckedList" />
</ElScrollbar>
</div>
</div>
<div class="rigthArea">
<div style="margin-bottom: 8px;">
<div class="transferName"><span>已选: {{ checkList.length }}</span><ElButton link style="float: right;margin-right: 5px;" @click="clearCheckList" >清空</ElButton></div>
</div>
<ElScrollbar height="calc(100% - 50px)">
<ElTag v-for="user in checkList" :key="user.id" style="display: block; font-size: 13px; letter-spacing: 1.5px;text-align: center;margin-top: 8px;height: 30px;width: 75%;margin-left:40px;padding-top: 6px;" closable @close="handleCloseTag(user)">
{{
user.label
}}[{{
user.parent.data.label
}}]
</ElTag>
</ElScrollbar>
</div>
</div>
</template>
<script setup name="MultiTreeSelector">
import { ref, watch, onMounted } from 'vue'
import request from '@/utils/request';
const props = defineProps({
// eslint-disable-next-line vue/require-default-prop
data: {
type: Object,
}
})
const fixedOptions = props.data?.control.fixedOptions
const transferConfig = props.data?.config
const treeProps = {
value: 'id',
label: 'label',
disabled: 'disabled',
children: 'children'
}
let dataFinished = false
const treeRef = ref()
const isExpandAll = ref(true) //
const keyword = ref('') //
const checkList = ref([]) // list
const userList = ref([])
let checkedIdList = ["302697","ceshi","301625"];
const url = transferConfig.apiUrl;/* '/javasys/lowCode/transfer/getOrgAndManTree' */
function getDetail() {
console.log(11111)
if(transferConfig.transferDataSource==="数据源"){
return request({
url: url,
method: transferConfig.method,
});
}
}
if(transferConfig.transferDataSource==="数据源"){
getDetail().then(({ data }) => {
console.log(`获取穿梭框接口数据`);
let resData = ref(data.children)
// 2: node
userList.value = [{
id: '全选',
label: '全选',
children: [...resData.value]
}]
treeRef.value.setCheckedKeys(checkedIdList,true)
setTimeout(() => {
checkedIdList.forEach(element => {
const _node = treeRef?.value?.getNode(element)
if (!_node) return
const _checkList = checkList.value
if (_node.checked) { //
if (_node.checked && _node.childNodes.length === 0) {
_checkList.push(_node)
}
// new Set()
checkList.value = Array.from(new Set(_checkList))
// ,,
if (_node.childNodes.length > 0) {
_node.childNodes.map(item => handleCheckList(item.data))
}
} else if (!_node.checked) { //
checkList.value = checkList.value.filter(item => item.checked)
}
});
}, 50);
});
}
const getCheckedList = () => {
checkedIdList = treeRef.value.getCheckedKeys(true)
}
let resData1 = ref([
{
id: '3',
label: '营销部',
children: [
{
id: '3-1',
label: '威震天'
},
{
id: '3-2',
label: '嫦娥'
}
]
},
{
id: '2',
label: '业务部',
children: [
{
id: '2-1-1',
label: '业务部子部1',
children: [
{
id: '2-1-0001',
label: '李白'
},
{
id: '2-1-0002',
label: '萧峰'
}
]
},
{
id: '2-2-1',
label: '业务部子部2',
children: [
{
id: '2-2-00006',
label: '武则天'
},
{
id: '2-2-00005',
label: '拿破仑'
}
]
}
]
},
{
id: '4',
label: '软件开发部',
children: [
{
id: '5-3',
label: '张辽'
},
{
id: '5-9',
label: '吕布'
},
{
id: '5-0',
label: '许褚'
},
]
}
])
//
const handleCheckList = (val) => {
const valId = val.id;
const _node = treeRef?.value?.getNode(valId)
if (!_node) return
const _checkList = checkList.value
if (_node.checked) { //
if (_node.checked && _node.childNodes.length === 0) {
_checkList.push(_node)
}
// new Set()
checkList.value = Array.from(new Set(_checkList))
// ,,
if (_node.childNodes.length > 0) {
_node.childNodes.map(item => handleCheckList(item.data))
}
} else if (!_node.checked) { //
checkList.value = checkList.value.filter(item => item.checked)
}
}
/*
* @node 当前节点
* @result 存结果的数组
* @key
* */
const getParentNode = (node, result, key) => {
let isPass = node?.data?.label?.indexOf(key) !== -1
isPass ? result.push(isPass) : ''
if (!isPass && node.level !== 1 && node.parent) {
return getParentNode(node.parent, result, key)
}
}
/*
* @value 过滤的关键字
* @data 被过滤的tree
* @node
* */
const filterNode = (value, data, node) => {
if (!value) {
data.disabled = false
return true
} else {
//,
// array.map(k => (!k.disabled) && (_childArr.push(k)))
(data.children) && (data.disabled = true)
}
let _arr = []
getParentNode(node, _arr, value)
let result = false
_arr.forEach(item => {
result = result || item
})
return result
}
// tag
const handleCloseTag = (tag) => {
if (!treeRef?.value) return
checkList.value = checkList.value.filter(item => {
treeRef.value.setChecked(tag, false)
if (!item?.data) return
return item.data.id !== tag.data.id
})
}
// CheckList
const clearCheckList = () => {
if (!treeRef?.value) return
treeRef.value.setCheckedKeys([])
while (checkList.value.length > 0) {
checkList.value.pop()
}
}
// /
const handleCheckAll = (array) => {
treeRef.value.setCheckedNodes(array)
array.map(item => {
handleCheckList(item)
})
}
// /
const expandAll = () => {
const nodesMap = treeRef.value.store.nodesMap
for (let key in nodesMap) {
nodesMap[key].expanded = isExpandAll.value
}
}
// tree
watch(keyword, (newVal) => {
treeRef.value.filter(newVal)
})
//
if(transferConfig.transferDataSource==="固定选项"){
setTimeout(() => {
// 2: node
userList.value = [{
id: '全选',
label: '全选',
children: fixedOptions
}]
console.log(treeRef)
treeRef.value.setCheckedKeys(checkedIdList,true)
checkedIdList.forEach(element => {
const _node = treeRef?.value?.getNode(element)
if (!_node) return
const _checkList = checkList.value
if (_node.checked) { //
if (_node.checked && _node.childNodes.length === 0) {
_checkList.push(_node)
}
// new Set()
checkList.value = Array.from(new Set(_checkList))
// ,,
if (_node.childNodes.length > 0) {
_node.childNodes.map(item => handleCheckList(item.data))
}
} else if (!_node.checked) { //
checkList.value = checkList.value.filter(item => item.checked)
}
});
}, 50);
dataFinished = true
}else{
getDetail
dataFinished = true
}
onMounted(() => {
})
</script>
<style scoped>
.transfer {
display: flex;
}
.transferName {
height: 30px;
padding-top: 8px;
background-color: #F5F7FA;
padding-left: 10px;
border-radius: 5px 5px 0 0;
border-bottom: 1px solid gainsboro;
}
.buttonArea {
display: flex;
flex-direction: row;
align-items: center;
}
.buttonArea2 {
height: 33px;
}
.leftScroll {
height: 367px;
}
.leftArea {
border: 1px solid gainsboro;
width: 300px;
border-radius: 5px;
height: 456px;
position: relative;
background-color: white;
}
.rigthArea {
border: 1px solid gainsboro;
width: 300px;
border-radius: 5px;
height: 456px;
background-color: white;
}</style>
Loading…
Cancel
Save