|
|
@@ -0,0 +1,255 @@ |
|
|
|
<!-- 级联树选择器表单组件 zhao --> |
|
|
|
<template> |
|
|
|
<div> |
|
|
|
<van-field |
|
|
|
:readonly="true" |
|
|
|
:clickable="!readonly" |
|
|
|
:name="name" |
|
|
|
:value="visibleValue" |
|
|
|
:label="label" |
|
|
|
:placeholder="placeholder" |
|
|
|
@click="openPopup" |
|
|
|
input-align="right" |
|
|
|
right-icon="arrow-down" |
|
|
|
:rules="rules" |
|
|
|
:required="required" |
|
|
|
:label-width="labelWidth || 'auto'" |
|
|
|
> |
|
|
|
</van-field> |
|
|
|
<van-popup v-model="popupVisible" position="bottom"> |
|
|
|
<van-cascader |
|
|
|
ref="picker" |
|
|
|
:title="label" |
|
|
|
v-model="internalValue" |
|
|
|
:placeholder="placeholder || '请选择'" |
|
|
|
:options="internalOptions" |
|
|
|
:readonly="readonly" |
|
|
|
:loading="loading" |
|
|
|
:field-names="{ |
|
|
|
text: textName || 'text', |
|
|
|
value: valueName || 'value', |
|
|
|
children: childrenName || 'children', |
|
|
|
}" |
|
|
|
@finish="onConfirm" |
|
|
|
@close="onCancel" |
|
|
|
@change="onChanged" |
|
|
|
/> |
|
|
|
</van-popup> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script> |
|
|
|
import request from "@/utils/request"; |
|
|
|
|
|
|
|
export default { |
|
|
|
name: "fieldCascader", |
|
|
|
props: [ |
|
|
|
'name', 'readonly', 'value', 'label', 'placeholder', 'required', 'rules', 'labelWidth', |
|
|
|
'options', // 树结构数组/普通数组 |
|
|
|
'textName', // 名称键名 String 默认text |
|
|
|
'valueName', // 值键名 String 默认value |
|
|
|
'remoteUrl', // 远程列表加载地址 String |
|
|
|
'onRemoteResponse', // 远程获取到结果的处理回调 String|Function 如果是函数需返回数组, 如果是字符串支持.分割 |
|
|
|
'childrenName', // 子级数组键名 String 默认children |
|
|
|
'parentName', // 父的值键名 String 如果不为空 则自动转数组为树树结构数组 String |
|
|
|
'showTextAndValue', // 是否显示值和键 Bool|String 字符串为分隔符, true为空字符串 |
|
|
|
'showHasChildren', // 是否显示存在子级的标识 Bool|String 字符串为标识符, true为` >` |
|
|
|
], |
|
|
|
watch: { |
|
|
|
value: function (newVal, oldVal) { |
|
|
|
if(newVal != this.internalValue) |
|
|
|
{ |
|
|
|
this.internalValue = newVal; |
|
|
|
this.syncIndex(); |
|
|
|
} |
|
|
|
}, |
|
|
|
options: function (newVal, oldVal) { |
|
|
|
this.internalOptions = this.makeTree(JSON.parse(JSON.stringify(newVal))); |
|
|
|
this.handleTree(this.internalOptions); |
|
|
|
this.syncIndex(); |
|
|
|
}, |
|
|
|
remoteUrl: function (newVal, oldVal) { |
|
|
|
this.requestRemote(); |
|
|
|
}, |
|
|
|
onRemoteResponse: function (newVal, oldVal) { |
|
|
|
this.parseRemote(); |
|
|
|
} |
|
|
|
}, |
|
|
|
created() { |
|
|
|
if(this.options && Array.isArray(this.options) && this.options.length > 0) |
|
|
|
{ |
|
|
|
this.internalOptions = this.makeTree(JSON.parse(JSON.stringify(this.options))); |
|
|
|
this.handleTree(this.internalOptions); |
|
|
|
this.syncIndex(); |
|
|
|
} |
|
|
|
else if(this.remoteUrl) |
|
|
|
this.requestRemote(); |
|
|
|
}, |
|
|
|
data() { |
|
|
|
return { |
|
|
|
popupVisible: false, |
|
|
|
internalValue: this.value, |
|
|
|
visibleValue: '', |
|
|
|
defaultIndex: 0, |
|
|
|
internalOptions: [], |
|
|
|
loading: false, |
|
|
|
remoteResponse: null, |
|
|
|
}; |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
openPopup() { |
|
|
|
if(!this.readonly) |
|
|
|
{ |
|
|
|
this.popupVisible = true; |
|
|
|
this.$nextTick(() => { |
|
|
|
//this.$refs.picker.setIndexes([this.defaultIndex]); |
|
|
|
}) |
|
|
|
} |
|
|
|
}, |
|
|
|
closePopup() { |
|
|
|
this.popupVisible = false; |
|
|
|
}, |
|
|
|
onChanged({ value, selectedOptions, tabIndex }) { |
|
|
|
this.$emit('change', { value, selectedOptions, }); |
|
|
|
}, |
|
|
|
onConfirm({ value, selectedOptions, tabIndex }) { |
|
|
|
this.syncValue(value, selectedOptions[selectedOptions.length - 1]); |
|
|
|
this.$emit('input', this.internalValue); |
|
|
|
this.$emit('confirm', this.internalValue); |
|
|
|
this.closePopup(); |
|
|
|
}, |
|
|
|
onCancel() { |
|
|
|
this.closePopup(); |
|
|
|
this.$emit('cancel'); |
|
|
|
}, |
|
|
|
getValue(data) { |
|
|
|
return typeof(data) === 'object' && this.valueName ? data[this.valueName] : data; |
|
|
|
}, |
|
|
|
getLabel(data) { |
|
|
|
return typeof(data) === 'object' && this.textName ? data[this.textName] : data; |
|
|
|
}, |
|
|
|
syncValue(value, data) { |
|
|
|
this.internalValue = value; |
|
|
|
this.visibleValue = this.getLabel(data); |
|
|
|
}, |
|
|
|
requestRemote() { |
|
|
|
if(!this.remoteUrl) |
|
|
|
return; |
|
|
|
this.loading = true; |
|
|
|
this.internalOptions = []; |
|
|
|
let promise = typeof(this.remoteUrl) === 'function' ? this.remoteUrl() : (this.remoteUrl instanceof Promise ? this.remoteUrl : request(this.remoteUrl)); |
|
|
|
promise.then((resp) => { |
|
|
|
this.remoteResponse = resp; |
|
|
|
this.parseRemote(); |
|
|
|
}).catch((e) => { |
|
|
|
console.error(e); |
|
|
|
}).finally(() => { |
|
|
|
this.loading = false; |
|
|
|
}) |
|
|
|
}, |
|
|
|
parseRemote() { |
|
|
|
if(!this.remoteResponse) |
|
|
|
return; |
|
|
|
let type = typeof(this.onRemoteResponse); |
|
|
|
if(type === 'function') |
|
|
|
this.internalOptions = this.makeTree(this.onRemoteResponse(this.remoteResponse)); |
|
|
|
else if(type === 'string') |
|
|
|
{ |
|
|
|
let arr = this.onRemoteResponse.split('.'); |
|
|
|
let ptr = this.remoteResponse; |
|
|
|
for(let i in arr) |
|
|
|
{ |
|
|
|
ptr = this.remoteResponse[arr[i]]; |
|
|
|
} |
|
|
|
this.internalOptions = this.makeTree(ptr); |
|
|
|
} |
|
|
|
else |
|
|
|
this.internalOptions = this.makeTree(this.remoteResponse); |
|
|
|
this.handleTree(this.internalOptions); |
|
|
|
this.syncIndex(); |
|
|
|
}, |
|
|
|
makeTree(list) { |
|
|
|
let parentName = this.parentName; |
|
|
|
let valueName = this.valueName || 'value'; |
|
|
|
let childrenName = this.childrenName || 'children'; |
|
|
|
function isnull(p) { |
|
|
|
return p === null || p === undefined || p === ''; |
|
|
|
} |
|
|
|
function makeTree_r(l, p) { |
|
|
|
const isRoot = isnull(p); |
|
|
|
let res = []; |
|
|
|
for(let v of l) |
|
|
|
{ |
|
|
|
const parentValue = v[parentName]; |
|
|
|
const value = v[valueName]; |
|
|
|
if((isRoot && isnull(parentValue)) || (!isRoot && parentValue == p)) |
|
|
|
{ |
|
|
|
let arr = makeTree_r(l, value); |
|
|
|
if(arr && arr.length > 0) |
|
|
|
v[childrenName] = arr; |
|
|
|
else |
|
|
|
delete v[childrenName]; |
|
|
|
res.push(v); |
|
|
|
} |
|
|
|
} |
|
|
|
return res; |
|
|
|
} |
|
|
|
if(this.parentName) |
|
|
|
return makeTree_r(list); |
|
|
|
else |
|
|
|
return list; |
|
|
|
}, |
|
|
|
handleTree(tree) { |
|
|
|
if(!this.showTextAndValue && !this.showHasChildren) |
|
|
|
return; |
|
|
|
let split = this.showTextAndValue === false || this.showTextAndValue === undefined || this.showTextAndValue === null ? false : (typeof(this.showTextAndValue) === 'boolean' ? "" : this.showTextAndValue); |
|
|
|
let hasChildren = this.showHasChildren === false || this.showHasChildren === undefined || this.showHasChildren === null ? false : (typeof(this.showHasChildren) === 'boolean' ? " >" : this.showHasChildren); |
|
|
|
let textName = this.textName || 'text'; |
|
|
|
let valueName = this.valueName || 'value'; |
|
|
|
let childrenName = this.childrenName || 'children'; |
|
|
|
function handleTree_r(l) { |
|
|
|
for(let v of l) |
|
|
|
{ |
|
|
|
if(split !== false) |
|
|
|
v[textName] = v[valueName] + split + v[textName]; |
|
|
|
if(v[childrenName] && Array.isArray(v[childrenName]) && v[childrenName].length > 0) |
|
|
|
{ |
|
|
|
if(hasChildren !== false) |
|
|
|
v[textName] = v[textName] + hasChildren; |
|
|
|
handleTree_r(v[childrenName]); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
handleTree_r(tree); |
|
|
|
}, |
|
|
|
findTree(tree, value) { |
|
|
|
let valueName = this.valueName || 'value'; |
|
|
|
let childrenName = this.childrenName || 'children'; |
|
|
|
function findTree_r(l) { |
|
|
|
for(let v of l) |
|
|
|
{ |
|
|
|
if(value == v[valueName]) |
|
|
|
return v; |
|
|
|
if(v[childrenName] && Array.isArray(v[childrenName]) && v[childrenName].length > 0) |
|
|
|
{ |
|
|
|
let res = findTree_r(v[childrenName]); |
|
|
|
if(res !== undefined) |
|
|
|
return res; |
|
|
|
} |
|
|
|
} |
|
|
|
return; |
|
|
|
} |
|
|
|
return findTree_r(tree); |
|
|
|
}, |
|
|
|
syncIndex() { |
|
|
|
let item = this.findTree(this.internalOptions, this.internalValue); |
|
|
|
if(item) |
|
|
|
this.visibleValue = item[this.textName || 'text']; |
|
|
|
} |
|
|
|
}, |
|
|
|
} |
|
|
|
</script> |
|
|
|
|
|
|
|
<style scoped> |
|
|
|
|
|
|
|
</style> |