修改bug
This commit is contained in:
parent
37fc4d0a45
commit
8006af2092
|
@ -0,0 +1,113 @@
|
|||
<template>
|
||||
<view>
|
||||
<uni-forms ref="baseForm" >
|
||||
<uni-forms-item label="选择城市" required>
|
||||
<picker class="addRess" @change="bindPickerChange" @columnchange="pluginclass" :value="pickVal" :range="cityArr"
|
||||
range-key="AreaName" mode="multiSelector">
|
||||
<view v-if="pickVal2.length==0">请选择城市</view>
|
||||
<view v-else class="uni-input">
|
||||
<text>{{cityArr[0][pickVal2[0]].AreaName}}</text>
|
||||
<text>{{cityArr[1][pickVal2[1]].AreaName}}</text>
|
||||
<text>{{cityArr[2][pickVal2[2]].AreaName}}</text>
|
||||
</view>
|
||||
</picker>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
pickVal: [0, 0, 0], // 选择器默认值
|
||||
pickVal2:[], // 辅助城市名字线索
|
||||
cityArr: [], // 所有城市
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 页面初始化
|
||||
this.loadProvinces()
|
||||
},
|
||||
methods: {
|
||||
loadProvinces() { // 加载省份
|
||||
uni.request({
|
||||
url: 'http://test-api.tiananhub.com/api/province/GetListProvince',
|
||||
method: 'get',
|
||||
success: async (res) => {
|
||||
let {
|
||||
data
|
||||
} = res.data
|
||||
console.log(data)
|
||||
this.cityArr[0] = data
|
||||
this.loadCities(data[0].AreaId)
|
||||
this.$forceUpdate()
|
||||
},
|
||||
fail: async (res) => {}
|
||||
})
|
||||
},
|
||||
// 请求地级市
|
||||
loadCities(AreaId) {
|
||||
uni.request({
|
||||
url: 'http://test-api.tiananhub.com/api/province/GetListCity',
|
||||
data: {
|
||||
AreaId
|
||||
},
|
||||
method: 'get',
|
||||
success: async (res) => {
|
||||
let {
|
||||
data
|
||||
} = res.data
|
||||
this.cityArr[1] = data
|
||||
this.loadAreas(data[0].AreaId)
|
||||
|
||||
this.$forceUpdate()
|
||||
},
|
||||
fail: async (res) => {}
|
||||
})
|
||||
},
|
||||
// 请求县区市
|
||||
loadAreas(AreaId) {
|
||||
uni.request({
|
||||
url: 'http://test-api.tiananhub.com/api/province/GetListCity',
|
||||
data: {
|
||||
AreaId
|
||||
},
|
||||
method: 'get',
|
||||
success: async (res) => {
|
||||
let {
|
||||
data
|
||||
} = res.data
|
||||
this.cityArr[2] = data
|
||||
|
||||
this.$forceUpdate()
|
||||
},
|
||||
fail: async (res) => {}
|
||||
})
|
||||
},
|
||||
// 确定其他选择的值
|
||||
bindPickerChange(data) {
|
||||
this.pickVal=data.target.value;
|
||||
this.pickVal2=data.target.value;
|
||||
console.log("000000000000",this.cityArr[0][this.pickVal2[0]].AreaName)
|
||||
console.log("11111111111",this.cityArr[1][this.pickVal2[1]].AreaName)
|
||||
console.log("222222222222",this.cityArr[2][this.pickVal2[2]].AreaName)
|
||||
// 以下拓展区域业务逻辑
|
||||
},
|
||||
// 选择时且未点击确定是的值
|
||||
pluginclass(e) {
|
||||
if (e.detail.column == 0) {
|
||||
this.loadCities(this.cityArr[0][e.detail.value].AreaId);
|
||||
}
|
||||
if (e.detail.column == 1) {
|
||||
this.loadAreas(this.cityArr[1][e.detail.value].AreaId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.addRess{
|
||||
line-height: 80rpx;
|
||||
}
|
||||
</style>
|
|
@ -72,7 +72,7 @@
|
|||
</image>
|
||||
<view class="therapist-mian-bottom-text">{{item.consortiaName}}</view>
|
||||
</view>
|
||||
<view class="text-center text-sm" v-if="item.status == 1"
|
||||
<!-- <view class="text-center text-sm" v-if="item.status == 1"
|
||||
style="background: linear-gradient(90deg, #019C88, #0FA78B, #35C495);color: #ffffff;width: 30%;border-radius: 30upx;height: 60upx;line-height: 60upx;margin-top: 10rpx;"
|
||||
@click.stop="goOrderDetail(item)">
|
||||
立即预约
|
||||
|
@ -80,7 +80,7 @@
|
|||
<view class="text-center text-sm" v-else
|
||||
style="background: #d9d9d9;color: #ffffff;width: 30%;border-radius: 30upx;height: 60upx;line-height: 60upx;margin-top: 20rpx;">
|
||||
立即预约
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
|
||||
</view>
|
||||
|
|
28
package.json
28
package.json
|
@ -1,13 +1,19 @@
|
|||
{
|
||||
"id": "jc-record",
|
||||
"name": "基于jsfun-record修正并优化的声音录制插件",
|
||||
"version": "1.0.5",
|
||||
"description": "基于jsfun-record修正并优化的声音录制插件",
|
||||
"id": "xlh-picker",
|
||||
"name": "APP、小程序城市选择器、三级联动、二级联动",
|
||||
"displayName": "APP、小程序城市选择器、三级联动、二级联动",
|
||||
"version": "1.0.0",
|
||||
"description": "适于用于各端的城市选择器、地址选择器、三级联动耳机联动",
|
||||
"keywords": [
|
||||
"录制",
|
||||
"声音",
|
||||
"音频",
|
||||
"录音",
|
||||
"长按录音"
|
||||
]
|
||||
}
|
||||
"picker",
|
||||
"选择器",
|
||||
"城市",
|
||||
"三级联动"
|
||||
],
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -14,11 +14,35 @@
|
|||
<u-input placeholder="请输入联系人手机号" v-model="applyPhone" :clearable="true"></u-input>
|
||||
</view>
|
||||
<view class="line"></view>
|
||||
<view class="info-box-item">
|
||||
<!-- <view class="info-box-item">
|
||||
<view class="info-box-item-name" @click="getcity()">选择城市</view>
|
||||
<u-input placeholder="请选择所在区域地址" v-model="applyContent" :disabled="false"
|
||||
:clearable="true">
|
||||
</u-input>
|
||||
</view> -->
|
||||
<view class="info-box-item" v-if="applyContent">
|
||||
<uni-forms ref="baseForm" :disabled="true">
|
||||
<uni-forms-item label="选择城市" required :disabled="true">
|
||||
<picker class="addRess" :disabled="true" range-key="AreaName" mode="multiSelector">
|
||||
<view class="uni-input">
|
||||
<text>{{applyContent}}</text>
|
||||
</view>
|
||||
</picker>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
</view>
|
||||
<view class="info-box-item" v-else>
|
||||
<uni-forms ref="baseForm" >
|
||||
<uni-forms-item label="选择城市" required>
|
||||
<picker class="addRess" @change="bindPickerChange" @columnchange="pluginclass" :value="pickVal" :range="cityArr"
|
||||
range-key="AreaName" mode="multiSelector">
|
||||
<view class="qing" v-if="pickVal2.length==0">请选择城市</view>
|
||||
<view v-else class="uni-input">
|
||||
<text>{{cityData}}</text>
|
||||
</view>
|
||||
</picker>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
@ -33,24 +57,106 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import xlhPicker from '@/components/xlh-picker/xlh-picker.vue'
|
||||
export default {
|
||||
components:{xlhPicker},
|
||||
data() {
|
||||
return {
|
||||
pickVal: [0, 0, 0], // 选择器默认值
|
||||
pickVal2:[], // 辅助城市名字线索
|
||||
cityArr: [], // 所有城市
|
||||
applyName: '',
|
||||
applyPhone: '',
|
||||
applyContent: '',
|
||||
applyId: '',
|
||||
latitude:'',
|
||||
longitude:''
|
||||
longitude:'',
|
||||
cityData:''
|
||||
};
|
||||
},
|
||||
onLoad() {
|
||||
this.getDetail()
|
||||
this.getDetail();
|
||||
this.loadProvinces()
|
||||
},
|
||||
onShow() {
|
||||
|
||||
},
|
||||
mounted() {
|
||||
// 页面初始化
|
||||
|
||||
},
|
||||
methods: {
|
||||
loadProvinces() { // 加载省份
|
||||
uni.request({
|
||||
url: 'http://test-api.tiananhub.com/api/province/GetListProvince',
|
||||
method: 'get',
|
||||
success: async (res) => {
|
||||
let {
|
||||
data
|
||||
} = res.data
|
||||
console.log(data)
|
||||
this.cityArr[0] = data
|
||||
this.loadCities(data[0].AreaId)
|
||||
this.$forceUpdate()
|
||||
},
|
||||
fail: async (res) => {}
|
||||
})
|
||||
},
|
||||
// 请求地级市
|
||||
loadCities(AreaId) {
|
||||
uni.request({
|
||||
url: 'http://test-api.tiananhub.com/api/province/GetListCity',
|
||||
data: {
|
||||
AreaId
|
||||
},
|
||||
method: 'get',
|
||||
success: async (res) => {
|
||||
let {
|
||||
data
|
||||
} = res.data
|
||||
this.cityArr[1] = data
|
||||
this.loadAreas(data[0].AreaId)
|
||||
|
||||
this.$forceUpdate()
|
||||
},
|
||||
fail: async (res) => {}
|
||||
})
|
||||
},
|
||||
// 请求县区市
|
||||
loadAreas(AreaId) {
|
||||
uni.request({
|
||||
url: 'http://test-api.tiananhub.com/api/province/GetListCity',
|
||||
data: {
|
||||
AreaId
|
||||
},
|
||||
method: 'get',
|
||||
success: async (res) => {
|
||||
let {
|
||||
data
|
||||
} = res.data
|
||||
this.cityArr[2] = data
|
||||
|
||||
this.$forceUpdate()
|
||||
},
|
||||
fail: async (res) => {}
|
||||
})
|
||||
},
|
||||
// 确定其他选择的值
|
||||
bindPickerChange(data) {
|
||||
this.pickVal=data.target.value;
|
||||
this.pickVal2=data.target.value;
|
||||
this.cityData=this.cityArr[0][this.pickVal2[0]].AreaName+'-'+this.cityArr[1][this.pickVal2[1]].AreaName+'-'+this.cityArr[2][this.pickVal2[2]].AreaName
|
||||
|
||||
},
|
||||
// 选择时且未点击确定是的值
|
||||
pluginclass(e) {
|
||||
if (e.detail.column == 0) {
|
||||
this.loadCities(this.cityArr[0][e.detail.value].AreaId);
|
||||
}
|
||||
if (e.detail.column == 1) {
|
||||
this.loadAreas(this.cityArr[1][e.detail.value].AreaId);
|
||||
}
|
||||
},
|
||||
getDetail() {
|
||||
let data = {
|
||||
classify: 1
|
||||
|
@ -134,21 +240,21 @@
|
|||
submit() {
|
||||
if (!this.applyName) {
|
||||
uni.showToast({
|
||||
title: '请输入联系人姓名',
|
||||
title: '请输入联系人姓名!',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.applyPhone) {
|
||||
uni.showToast({
|
||||
title: '请输入联系人手机号码',
|
||||
title: '请输入联系人手机号码!',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
if (!this.applyContent) {
|
||||
if (!this.cityData) {
|
||||
uni.showToast({
|
||||
title: '请输入地址',
|
||||
title: '请选择城市!',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
|
@ -156,10 +262,9 @@
|
|||
let data = {
|
||||
applyName: this.applyName,
|
||||
applyPhone: this.applyPhone,
|
||||
applyContent: this.applyContent,
|
||||
applyContent: this.cityData,
|
||||
classify: 1
|
||||
}
|
||||
|
||||
this.$Request.postJson("/app/apply/insertApply", data).then(res => {
|
||||
if (res.code == 0) {
|
||||
uni.showToast({
|
||||
|
@ -182,7 +287,22 @@
|
|||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
||||
/deep/.is-required{
|
||||
display: none;
|
||||
}
|
||||
/deep/.uni-forms-item__label{
|
||||
width:100% !important;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
font-weight: 800;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
/deep/.uni-forms-item{
|
||||
flex-direction: column !important;
|
||||
}
|
||||
.qing{
|
||||
color: rgb(192, 196, 204);
|
||||
}
|
||||
.hehuo_view {
|
||||
width: 100%;
|
||||
padding-top: 10px;
|
||||
|
|
|
@ -4,11 +4,31 @@
|
|||
<image style="width: 35rpx; height: 35rpx; margin-right:10rpx; margin-top: 5rpx;" src="../../static/technician.png"></image>应聘技师</view>
|
||||
<view class="advantage-bor" style="margin-right: 10rpx;"></view>
|
||||
<view class="text_view">
|
||||
<view class="item_view">
|
||||
<!-- <view class="item_view">
|
||||
<view class="item_title">服务城市 </view>
|
||||
<u-input @click="goCity" type="text" v-model="city" placeholder="请输入服务城市 " />
|
||||
<view class="xian"></view>
|
||||
</view>
|
||||
</view> -->
|
||||
<uni-forms ref="baseForm" :disabled="true" v-if="city">
|
||||
<uni-forms-item label="选择城市" required :disabled="true">
|
||||
<picker class="addRess" :disabled="true" range-key="AreaName" mode="multiSelector">
|
||||
<view class="uni-input">
|
||||
<text>{{city}}</text>
|
||||
</view>
|
||||
</picker>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
<uni-forms ref="baseForm" v-else>
|
||||
<uni-forms-item label="服务城市" required>
|
||||
<picker class="addRess" @change="bindPickerChange" @columnchange="pluginclass" :value="pickVal" :range="cityArr"
|
||||
range-key="AreaName" mode="multiSelector">
|
||||
<view class="qing" v-if="pickVal2.length==0">请选择城市</view>
|
||||
<view v-else class="uni-input">
|
||||
<text>{{cityData}}</text>
|
||||
</view>
|
||||
</picker>
|
||||
</uni-forms-item>
|
||||
</uni-forms>
|
||||
<view class="item_view">
|
||||
<view class="item_title">姓名</view>
|
||||
<u-input type="text" v-model="userName" placeholder="请输入姓名" />
|
||||
|
@ -26,7 +46,7 @@
|
|||
</view>
|
||||
<view class="item_view">
|
||||
<view class="item_title" style="margin-bottom: 10px;">头像上传</view>
|
||||
<view class="flex" style="overflow: hidden;flex-direction: initial;">
|
||||
<view class="flex" v-if="bb=='1'" style="overflow: hidden;flex-direction: initial;">
|
||||
<view v-if="headImg.length">
|
||||
<view class="margin-top flex margin-right-sm">
|
||||
<view class="flex"
|
||||
|
@ -50,6 +70,16 @@
|
|||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex" v-if="bb=='0'" style="overflow: hidden;flex-direction: initial;">
|
||||
<view v-if="headImg.length">
|
||||
<view class="margin-top flex margin-right-sm">
|
||||
<view class="flex"
|
||||
style="width: 150upx;height: 150upx;margin-right: 10rpx;position: relative;">
|
||||
<image :src="headImg" style="width: 100%;height: 100%;"></image>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- <view class="audit_message" v-if="auditContent != '' && bb == 3">拒绝原因:{{auditContent}}</view> -->
|
||||
|
@ -67,6 +97,9 @@
|
|||
export default {
|
||||
data() {
|
||||
return {
|
||||
pickVal: [0, 0, 0], // 选择器默认值
|
||||
pickVal2:[], // 辅助城市名字线索
|
||||
cityArr: [], // 所有城市
|
||||
hotCitys: ['杭州', '天津', '北京', '上海', '深圳', '广州', '成都', '重庆', '厦门'],
|
||||
locationValue: '正在定位...',
|
||||
// auditContent: '',
|
||||
|
@ -77,13 +110,86 @@
|
|||
phone: '',
|
||||
age: '',
|
||||
headImg: [],
|
||||
bb:true,
|
||||
bb:'1',
|
||||
cityData:''
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
this.getChannel();
|
||||
this.loadProvinces()
|
||||
},
|
||||
methods: {
|
||||
loadProvinces() { // 加载省份
|
||||
uni.request({
|
||||
url: 'http://test-api.tiananhub.com/api/province/GetListProvince',
|
||||
method: 'get',
|
||||
success: async (res) => {
|
||||
let {
|
||||
data
|
||||
} = res.data
|
||||
console.log(data)
|
||||
this.cityArr[0] = data
|
||||
this.loadCities(data[0].AreaId)
|
||||
this.$forceUpdate()
|
||||
},
|
||||
fail: async (res) => {}
|
||||
})
|
||||
},
|
||||
// 请求地级市
|
||||
loadCities(AreaId) {
|
||||
uni.request({
|
||||
url: 'http://test-api.tiananhub.com/api/province/GetListCity',
|
||||
data: {
|
||||
AreaId
|
||||
},
|
||||
method: 'get',
|
||||
success: async (res) => {
|
||||
let {
|
||||
data
|
||||
} = res.data
|
||||
this.cityArr[1] = data
|
||||
this.loadAreas(data[0].AreaId)
|
||||
|
||||
this.$forceUpdate()
|
||||
},
|
||||
fail: async (res) => {}
|
||||
})
|
||||
},
|
||||
// 请求县区市
|
||||
loadAreas(AreaId) {
|
||||
uni.request({
|
||||
url: 'http://test-api.tiananhub.com/api/province/GetListCity',
|
||||
data: {
|
||||
AreaId
|
||||
},
|
||||
method: 'get',
|
||||
success: async (res) => {
|
||||
let {
|
||||
data
|
||||
} = res.data
|
||||
this.cityArr[2] = data
|
||||
|
||||
this.$forceUpdate()
|
||||
},
|
||||
fail: async (res) => {}
|
||||
})
|
||||
},
|
||||
// 确定其他选择的值
|
||||
bindPickerChange(data) {
|
||||
this.pickVal=data.target.value;
|
||||
this.pickVal2=data.target.value;
|
||||
this.cityData=this.cityArr[0][this.pickVal2[0]].AreaName+'-'+this.cityArr[1][this.pickVal2[1]].AreaName+'-'+this.cityArr[2][this.pickVal2[2]].AreaName
|
||||
|
||||
},
|
||||
// 选择时且未点击确定是的值
|
||||
pluginclass(e) {
|
||||
if (e.detail.column == 0) {
|
||||
this.loadCities(this.cityArr[0][e.detail.value].AreaId);
|
||||
}
|
||||
if (e.detail.column == 1) {
|
||||
this.loadAreas(this.cityArr[1][e.detail.value].AreaId);
|
||||
}
|
||||
},
|
||||
// 本人近期照删除
|
||||
headImgremove(index) {
|
||||
this.headImg = ''
|
||||
|
@ -93,7 +199,7 @@
|
|||
this.$Request.getT('/app/artificer/selectAgencyById?userId=' + userId).then(res => {
|
||||
if (res.code == 0) {
|
||||
if (res.data == null) {
|
||||
this.bb = 1;
|
||||
this.bb = '1';
|
||||
} else {
|
||||
this.bb = res.data.status;
|
||||
this.city = res.data.city;
|
||||
|
@ -126,41 +232,12 @@
|
|||
console.log('详细地址:' + res.address);
|
||||
console.log('纬度:' + res.latitude);
|
||||
console.log('经度:' + res.longitude);
|
||||
// that.city = res.address || '郑州'
|
||||
that.Getcity(res.latitude, res.longitude)
|
||||
}
|
||||
});
|
||||
// uni.getLocation({
|
||||
// type: 'wgs84',
|
||||
// geocode: true,
|
||||
// success: function(res) {
|
||||
// console.log('当前位置:' + res.address.city);
|
||||
// that.city = res.address.city || '郑州'
|
||||
// }
|
||||
// });
|
||||
},
|
||||
save() {
|
||||
// let isStudent = this.$queue.getData("isStudent");
|
||||
// if (isStudent != 2) {
|
||||
// uni.showModal({
|
||||
// title: '温馨提示',
|
||||
// content: '您还没有进行实名认证,请认证完成之后再来操作吧!',
|
||||
// showCancel: true,
|
||||
// cancelText: '取消',
|
||||
// confirmText: '确认',
|
||||
// success: res => {
|
||||
// if (res.confirm) {
|
||||
// uni.navigateTo({
|
||||
// url: '/offlinetask/pages/public/authentication'
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// return;
|
||||
// }
|
||||
// this.form.headImg = this.headImg
|
||||
// this.headImg = this.headImg.toString();
|
||||
if (this.city === '') {
|
||||
if (this.cityData === '') {
|
||||
this.$queue.showToast('请输入服务城市 ')
|
||||
return;
|
||||
}
|
||||
|
@ -186,7 +263,7 @@
|
|||
name: this.userName,
|
||||
phone: this.phone,
|
||||
age: this.age,
|
||||
city: this.city,
|
||||
city: this.cityData,
|
||||
img: this.headImg,
|
||||
}
|
||||
this.$Request.postJson('/app/artificer/insertAgency', data).then(res => {
|
||||
|
@ -206,26 +283,63 @@
|
|||
let that = this
|
||||
uni.chooseImage({
|
||||
count: 1,
|
||||
sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认两者都有
|
||||
sourceType: ['album', 'camera'],
|
||||
success: res => {
|
||||
for (let i = 0; i < 1; i++) {
|
||||
that.$queue.showLoading("上传中...");
|
||||
uni.uploadFile({ // 上传接口
|
||||
url: websocketUtils.uploadFileUrl(), //真实的接口地址
|
||||
// url: 'https://admin.sjajk.com/sqx_fast/alioss/upload', //真实的接口地址
|
||||
filePath: res.tempFilePaths[i],
|
||||
name: 'file',
|
||||
success: (uploadFileRes) => {
|
||||
console.log(uploadFileRes.data)
|
||||
that.headImg = JSON.parse(uploadFileRes.data).data
|
||||
console.log(that.headImg)
|
||||
uni.hideLoading();
|
||||
}
|
||||
});
|
||||
}
|
||||
that.$queue.showLoading("上传中...");
|
||||
var tempFilePaths=res.tempFilePaths[0]
|
||||
console.log("tempFilePaths----",res)
|
||||
that.checkImageSize(tempFilePaths, (isValid) => {
|
||||
if (isValid) {
|
||||
// 图片校验通过后的逻辑
|
||||
console.log('图片校验通过,可以上传');
|
||||
} else {
|
||||
console.log('图片超出大小限制,请重新选择');
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
},
|
||||
// 检查图片尺寸
|
||||
checkImageSize(filePath, callback) {
|
||||
let that = this
|
||||
uni.getFileInfo({
|
||||
filePath: filePath,
|
||||
success: (infoRes) => {
|
||||
const size = infoRes.size;
|
||||
if (size / 1024 / 1024 > 2) {
|
||||
// 图片大于2MB
|
||||
uni.showToast({
|
||||
icon:'error',
|
||||
title:'图片超出2MB限制,请重新选择'
|
||||
})
|
||||
callback(false);
|
||||
} else {
|
||||
// 图片小于等于2MB
|
||||
this.uploadImage(filePath)
|
||||
callback(true);
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
callback(false);
|
||||
}
|
||||
});
|
||||
},
|
||||
// 上传图片
|
||||
uploadImage(filePath) {
|
||||
let that = this
|
||||
uni.uploadFile({ // 上传接口
|
||||
url: websocketUtils.uploadFileUrl(), //真实的接口地址
|
||||
filePath: filePath,
|
||||
name: 'file',
|
||||
success: (uploadFileRes) => {
|
||||
console.log(uploadFileRes.data)
|
||||
that.headImg = JSON.parse(uploadFileRes.data).data
|
||||
console.log(that.headImg)
|
||||
uni.hideLoading();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -233,6 +347,22 @@
|
|||
<style lang="less">
|
||||
@import '../../static/less/index.less';
|
||||
@import '../../static/css/index.css';
|
||||
/deep/.is-required{
|
||||
display: none;
|
||||
}
|
||||
/deep/.uni-forms-item__label{
|
||||
width:100% !important;
|
||||
font-size: 14px;
|
||||
color: #333333;
|
||||
font-weight: 800;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
/deep/.uni-forms-item{
|
||||
flex-direction: column !important;
|
||||
}
|
||||
.qing{
|
||||
color: rgb(192, 196, 204);
|
||||
}
|
||||
.push-button{
|
||||
width: 100%;
|
||||
height: 48px;
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
## 2.0.0(2023-12-14)
|
||||
- 新增 支持 uni-app-x
|
||||
## 1.1.2(2023-04-11)
|
||||
- 修复 更改 modelValue 报错的 bug
|
||||
- 修复 v-for 未使用 key 值控制台 warning
|
||||
## 1.1.1(2023-02-21)
|
||||
- 修复代码合并时引发 value 属性为空时不渲染数据的问题
|
||||
## 1.1.0(2023-02-15)
|
||||
- 修复 localdata 不支持动态更新的bug
|
||||
## 1.0.9(2023-02-15)
|
||||
- 修复 localdata 不支持动态更新的bug
|
||||
## 1.0.8(2022-09-16)
|
||||
- 可以使用 uni-scss 控制主题色
|
||||
## 1.0.7(2022-07-06)
|
||||
- 优化 pc端图标位置不正确的问题
|
||||
## 1.0.6(2022-07-05)
|
||||
- 优化 显示样式
|
||||
## 1.0.5(2022-07-04)
|
||||
- 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug
|
||||
## 1.0.4(2022-04-19)
|
||||
- 修复 字节小程序 本地数据无法选择下一级的Bug
|
||||
## 1.0.3(2022-02-25)
|
||||
- 修复 nvue 不支持的 v-show 的 bug
|
||||
## 1.0.2(2022-02-25)
|
||||
- 修复 条件编译 nvue 不支持的 css 样式
|
||||
## 1.0.1(2021-11-23)
|
||||
- 修复 由上个版本引发的map、v-model等属性不生效的bug
|
||||
## 1.0.0(2021-11-19)
|
||||
- 优化 组件 UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-data-picker](https://uniapp.dcloud.io/component/uniui/uni-data-picker)
|
||||
## 0.4.9(2021-10-28)
|
||||
- 修复 VUE2 v-model 概率无效的 bug
|
||||
## 0.4.8(2021-10-27)
|
||||
- 修复 v-model 概率无效的 bug
|
||||
## 0.4.7(2021-10-25)
|
||||
- 新增 属性 spaceInfo 服务空间配置 HBuilderX 3.2.11+
|
||||
- 修复 树型 uniCloud 数据类型为 int 时报错的 bug
|
||||
## 0.4.6(2021-10-19)
|
||||
- 修复 非 VUE3 v-model 为 0 时无法选中的 bug
|
||||
## 0.4.5(2021-09-26)
|
||||
- 新增 清除已选项的功能(通过 clearIcon 属性配置是否显示按钮),同时提供 clear 方法以供调用,二者等效
|
||||
- 修复 readonly 为 true 时报错的 bug
|
||||
## 0.4.4(2021-09-26)
|
||||
- 修复 上一版本造成的 map 属性失效的 bug
|
||||
- 新增 ellipsis 属性,支持配置 tab 选项长度过长时是否自动省略
|
||||
## 0.4.3(2021-09-24)
|
||||
- 修复 某些情况下级联未触发的 bug
|
||||
## 0.4.2(2021-09-23)
|
||||
- 新增 提供 show 和 hide 方法,开发者可以通过 ref 调用
|
||||
- 新增 选项内容过长自动添加省略号
|
||||
## 0.4.1(2021-09-15)
|
||||
- 新增 map 属性 字段映射,将 text/value 映射到数据中的其他字段
|
||||
## 0.4.0(2021-07-13)
|
||||
- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 0.3.5(2021-06-04)
|
||||
- 修复 无法加载云端数据的问题
|
||||
## 0.3.4(2021-05-28)
|
||||
- 修复 v-model 无效问题
|
||||
- 修复 loaddata 为空数据组时加载时间过长问题
|
||||
- 修复 上个版本引出的本地数据无法选择带有 children 的 2 级节点
|
||||
## 0.3.3(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 0.3.2(2021-04-22)
|
||||
- 修复 非树形数据有 where 属性查询报错的问题
|
||||
## 0.3.1(2021-04-15)
|
||||
- 修复 本地数据概率无法回显时问题
|
||||
## 0.3.0(2021-04-07)
|
||||
- 新增 支持云端非树形表结构数据
|
||||
- 修复 根节点 parent_field 字段等于 null 时选择界面错乱问题
|
||||
## 0.2.0(2021-03-15)
|
||||
- 修复 nodeclick、popupopened、popupclosed 事件无法触发的问题
|
||||
## 0.1.9(2021-03-09)
|
||||
- 修复 微信小程序某些情况下无法选择的问题
|
||||
## 0.1.8(2021-02-05)
|
||||
- 优化 部分样式在 nvue 上的兼容表现
|
||||
## 0.1.7(2021-02-05)
|
||||
- 调整为 uni_modules 目录规范
|
|
@ -0,0 +1,45 @@
|
|||
// #ifdef H5
|
||||
export default {
|
||||
name: 'Keypress',
|
||||
props: {
|
||||
disable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
const keyNames = {
|
||||
esc: ['Esc', 'Escape'],
|
||||
tab: 'Tab',
|
||||
enter: 'Enter',
|
||||
space: [' ', 'Spacebar'],
|
||||
up: ['Up', 'ArrowUp'],
|
||||
left: ['Left', 'ArrowLeft'],
|
||||
right: ['Right', 'ArrowRight'],
|
||||
down: ['Down', 'ArrowDown'],
|
||||
delete: ['Backspace', 'Delete', 'Del']
|
||||
}
|
||||
const listener = ($event) => {
|
||||
if (this.disable) {
|
||||
return
|
||||
}
|
||||
const keyName = Object.keys(keyNames).find(key => {
|
||||
const keyName = $event.key
|
||||
const value = keyNames[key]
|
||||
return value === keyName || (Array.isArray(value) && value.includes(keyName))
|
||||
})
|
||||
if (keyName) {
|
||||
// 避免和其他按键事件冲突
|
||||
setTimeout(() => {
|
||||
this.$emit(keyName, {})
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
document.addEventListener('keyup', listener)
|
||||
this.$once('hook:beforeDestroy', () => {
|
||||
document.removeEventListener('keyup', listener)
|
||||
})
|
||||
},
|
||||
render: () => {}
|
||||
}
|
||||
// #endif
|
|
@ -0,0 +1,380 @@
|
|||
<template>
|
||||
<view class="uni-data-tree">
|
||||
<view class="uni-data-tree-input" @click="handleInput">
|
||||
<slot :data="selectedPaths" :error="error">
|
||||
<view class="input-value" :class="{'input-value-border': border}">
|
||||
<text v-if="error!=null" class="error-text">{{error!.errMsg}}</text>
|
||||
<scroll-view v-if="selectedPaths.length" class="selected-path" scroll-x="true">
|
||||
<view class="selected-list">
|
||||
<template v-for="(item, index) in selectedPaths">
|
||||
<text class="text-color">{{item[mappingTextName]}}</text>
|
||||
<text v-if="index<selectedPaths.length-1" class="input-split-line">{{split}}</text>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<text v-else-if="error==null&&!loading" class="placeholder">{{placeholder}}</text>
|
||||
<view v-if="!readonly" class="arrow-area">
|
||||
<view class="input-arrow"></view>
|
||||
</view>
|
||||
</view>
|
||||
</slot>
|
||||
<view v-if="loading && !isOpened" class="selected-loading">
|
||||
<slot name="picker-loading" :loading="loading"></slot>
|
||||
</view>
|
||||
</view>
|
||||
<view class="uni-data-tree-cover" v-if="isOpened" @click="handleClose"></view>
|
||||
<view class="uni-data-tree-dialog" v-if="isOpened">
|
||||
<view class="uni-popper__arrow"></view>
|
||||
<view class="dialog-caption">
|
||||
<view class="dialog-title-view">
|
||||
<text class="dialog-title">{{popupTitle}}</text>
|
||||
</view>
|
||||
<view class="dialog-close" @click="handleClose">
|
||||
<view class="dialog-close-plus" data-id="close"></view>
|
||||
<view class="dialog-close-plus dialog-close-rotate" data-id="close"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view ref="pickerView" class="uni-data-pickerview">
|
||||
<view v-if="error!=null" class="error">
|
||||
<text class="error-text">{{error!.errMsg}}</text>
|
||||
</view>
|
||||
<scroll-view v-if="!isCloudDataList" :scroll-x="true">
|
||||
<view class="selected-node-list">
|
||||
<template v-for="(item, index) in selectedNodes">
|
||||
<text class="selected-node-item" :class="{'selected-node-item-active':index==selectedIndex}"
|
||||
@click="onTabSelect(index)">
|
||||
{{item[mappingTextName]}}
|
||||
</text>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<list-view class="list-view" :scroll-y="true">
|
||||
<list-item class="list-item" v-for="(item, _) in currentDataList" @click="onNodeClick(item)">
|
||||
<text class="item-text" :class="{'item-text-disabled': item['disable']}">{{item[mappingTextName]}}</text>
|
||||
<text class="check" v-if="item[mappingValueName] == selectedNodes[selectedIndex][mappingValueName]"></text>
|
||||
</list-item>
|
||||
</list-view>
|
||||
<view class="loading-cover" v-if="loading">
|
||||
<slot name="pickerview-loading" :loading="loading"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { dataPicker } from "../uni-data-pickerview/uni-data-picker.uts"
|
||||
|
||||
/**
|
||||
* DataPicker 级联选择
|
||||
* @description 支持单列、和多列级联选择。列数没有限制,如果屏幕显示不全,顶部tab区域会左右滚动。
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=3796
|
||||
* @property {String} popup-title 弹出窗口标题
|
||||
* @property {Array} localdata 本地数据,参考
|
||||
* @property {Boolean} border = [true|false] 是否有边框
|
||||
* @property {Boolean} readonly = [true|false] 是否仅读
|
||||
* @property {Boolean} preload = [true|false] 是否预加载数据
|
||||
* @value true 开启预加载数据,点击弹出窗口后显示已加载数据
|
||||
* @value false 关闭预加载数据,点击弹出窗口后开始加载数据
|
||||
* @property {Boolean} step-searh = [true|false] 是否分布查询
|
||||
* @value true 启用分布查询,仅查询当前选中节点
|
||||
* @value false 关闭分布查询,一次查询出所有数据
|
||||
* @property {String|DBFieldString} self-field 分布查询当前字段名称
|
||||
* @property {String|DBFieldString} parent-field 分布查询父字段名称
|
||||
* @property {String|DBCollectionString} collection 表名
|
||||
* @property {String|DBFieldString} field 查询字段,多个字段用 `,` 分割
|
||||
* @property {String} orderby 排序字段及正序倒叙设置
|
||||
* @property {String|JQLString} where 查询条件
|
||||
* @event {Function} popupshow 弹出的选择窗口打开时触发此事件
|
||||
* @event {Function} popuphide 弹出的选择窗口关闭时触发此事件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniDataPicker',
|
||||
emits: ['popupopened', 'popupclosed', 'nodeclick', 'change', 'input', 'update:modelValue', 'inputclick'],
|
||||
mixins: [dataPicker],
|
||||
props: {
|
||||
popupTitle: {
|
||||
type: String,
|
||||
default: '请选择'
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择'
|
||||
},
|
||||
heightMobile: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
clearIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
split: {
|
||||
type: String,
|
||||
default: '/'
|
||||
},
|
||||
ellipsis: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isOpened: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isShowClearIcon() : boolean {
|
||||
if (this.readonly) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.clearIcon && this.selectedPaths.length > 0) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.load()
|
||||
},
|
||||
methods: {
|
||||
clear() {
|
||||
},
|
||||
load() {
|
||||
if (this.isLocalData) {
|
||||
this.loadLocalData()
|
||||
} else if (this.isCloudDataList || this.isCloudDataTree) {
|
||||
this.loadCloudDataPath()
|
||||
}
|
||||
},
|
||||
show() {
|
||||
this.isOpened = true
|
||||
this.$emit('popupopened')
|
||||
if (!this.hasCloudTreeData) {
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
hide() {
|
||||
this.isOpened = false
|
||||
this.$emit('popupclosed')
|
||||
},
|
||||
handleInput() {
|
||||
if (this.readonly) {
|
||||
this.$emit('inputclick')
|
||||
} else {
|
||||
this.show()
|
||||
}
|
||||
},
|
||||
handleClose() {
|
||||
this.hide()
|
||||
},
|
||||
onFinish() {
|
||||
this.selectedPaths = this.getChangeNodes()
|
||||
this.$emit('change', this.selectedPaths)
|
||||
this.hide()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import url("../uni-data-pickerview/uni-data-pickerview.css");
|
||||
|
||||
.uni-data-tree {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.uni-data-tree-input {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.selected-loading {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
flex: 1;
|
||||
font-size: 12px;
|
||||
color: #DD524D;
|
||||
}
|
||||
|
||||
.input-value {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
padding: 5px 5px;
|
||||
padding-right: 5px;
|
||||
overflow: hidden;
|
||||
min-height: 28px;
|
||||
}
|
||||
|
||||
.input-value-border {
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.selected-path {
|
||||
flex: 1;
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.selected-list {
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.selected-item {
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.text-color {
|
||||
font-size: 14px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: grey;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.input-split-line {
|
||||
opacity: .5;
|
||||
margin-left: 1px;
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
||||
.arrow-area {
|
||||
position: relative;
|
||||
padding: 0 12px;
|
||||
margin-left: auto;
|
||||
justify-content: center;
|
||||
transform: rotate(-45deg);
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.input-arrow {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-left: 2px solid #999;
|
||||
border-bottom: 2px solid #999;
|
||||
}
|
||||
|
||||
.uni-data-tree-cover {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, .4);
|
||||
flex-direction: column;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.uni-data-tree-dialog {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 20%;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #FFFFFF;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
flex-direction: column;
|
||||
z-index: 102;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.dialog-caption {
|
||||
position: relative;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.dialog-title-view {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
align-self: center;
|
||||
padding: 0 10px;
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
.dialog-close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.dialog-close-plus {
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
background-color: #666;
|
||||
border-radius: 2px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.dialog-close-rotate {
|
||||
position: absolute;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.uni-data-pickerview {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.icon-clear {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* #ifdef H5 */
|
||||
@media all and (min-width: 768px) {
|
||||
.uni-data-tree-cover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.uni-data-tree-dialog {
|
||||
position: absolute;
|
||||
top: 55px;
|
||||
height: auto;
|
||||
min-height: 400px;
|
||||
max-height: 50vh;
|
||||
background-color: #fff;
|
||||
border: 1px solid #EBEEF5;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
overflow: unset;
|
||||
}
|
||||
|
||||
.dialog-caption {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
/* #endif */
|
||||
</style>
|
|
@ -0,0 +1,551 @@
|
|||
<template>
|
||||
<view class="uni-data-tree">
|
||||
<view class="uni-data-tree-input" @click="handleInput">
|
||||
<slot :options="options" :data="inputSelected" :error="errorMessage">
|
||||
<view class="input-value" :class="{'input-value-border': border}">
|
||||
<text v-if="errorMessage" class="selected-area error-text">{{errorMessage}}</text>
|
||||
<view v-else-if="loading && !isOpened" class="selected-area">
|
||||
<uni-load-more class="load-more" :contentText="loadMore" status="loading"></uni-load-more>
|
||||
</view>
|
||||
<scroll-view v-else-if="inputSelected.length" class="selected-area" scroll-x="true">
|
||||
<view class="selected-list">
|
||||
<view class="selected-item" v-for="(item,index) in inputSelected" :key="index">
|
||||
<text class="text-color">{{item.text}}</text><text v-if="index<inputSelected.length-1"
|
||||
class="input-split-line">{{split}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<text v-else class="selected-area placeholder">{{placeholder}}</text>
|
||||
<view v-if="clearIcon && !readonly && inputSelected.length" class="icon-clear" @click.stop="clear">
|
||||
<uni-icons type="clear" color="#c0c4cc" size="24"></uni-icons>
|
||||
</view>
|
||||
<view class="arrow-area" v-if="(!clearIcon || !inputSelected.length) && !readonly ">
|
||||
<view class="input-arrow"></view>
|
||||
</view>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
<view class="uni-data-tree-cover" v-if="isOpened" @click="handleClose"></view>
|
||||
<view class="uni-data-tree-dialog" v-if="isOpened">
|
||||
<view class="uni-popper__arrow"></view>
|
||||
<view class="dialog-caption">
|
||||
<view class="title-area">
|
||||
<text class="dialog-title">{{popupTitle}}</text>
|
||||
</view>
|
||||
<view class="dialog-close" @click="handleClose">
|
||||
<view class="dialog-close-plus" data-id="close"></view>
|
||||
<view class="dialog-close-plus dialog-close-rotate" data-id="close"></view>
|
||||
</view>
|
||||
</view>
|
||||
<data-picker-view class="picker-view" ref="pickerView" v-model="dataValue" :localdata="localdata"
|
||||
:preload="preload" :collection="collection" :field="field" :orderby="orderby" :where="where"
|
||||
:step-searh="stepSearh" :self-field="selfField" :parent-field="parentField" :managed-mode="true" :map="map"
|
||||
:ellipsis="ellipsis" @change="onchange" @datachange="ondatachange" @nodeclick="onnodeclick">
|
||||
</data-picker-view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dataPicker from "../uni-data-pickerview/uni-data-picker.js"
|
||||
import DataPickerView from "../uni-data-pickerview/uni-data-pickerview.vue"
|
||||
|
||||
/**
|
||||
* DataPicker 级联选择
|
||||
* @description 支持单列、和多列级联选择。列数没有限制,如果屏幕显示不全,顶部tab区域会左右滚动。
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=3796
|
||||
* @property {String} popup-title 弹出窗口标题
|
||||
* @property {Array} localdata 本地数据,参考
|
||||
* @property {Boolean} border = [true|false] 是否有边框
|
||||
* @property {Boolean} readonly = [true|false] 是否仅读
|
||||
* @property {Boolean} preload = [true|false] 是否预加载数据
|
||||
* @value true 开启预加载数据,点击弹出窗口后显示已加载数据
|
||||
* @value false 关闭预加载数据,点击弹出窗口后开始加载数据
|
||||
* @property {Boolean} step-searh = [true|false] 是否分布查询
|
||||
* @value true 启用分布查询,仅查询当前选中节点
|
||||
* @value false 关闭分布查询,一次查询出所有数据
|
||||
* @property {String|DBFieldString} self-field 分布查询当前字段名称
|
||||
* @property {String|DBFieldString} parent-field 分布查询父字段名称
|
||||
* @property {String|DBCollectionString} collection 表名
|
||||
* @property {String|DBFieldString} field 查询字段,多个字段用 `,` 分割
|
||||
* @property {String} orderby 排序字段及正序倒叙设置
|
||||
* @property {String|JQLString} where 查询条件
|
||||
* @event {Function} popupshow 弹出的选择窗口打开时触发此事件
|
||||
* @event {Function} popuphide 弹出的选择窗口关闭时触发此事件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniDataPicker',
|
||||
emits: ['popupopened', 'popupclosed', 'nodeclick', 'input', 'change', 'update:modelValue','inputclick'],
|
||||
mixins: [dataPicker],
|
||||
components: {
|
||||
DataPickerView
|
||||
},
|
||||
props: {
|
||||
options: {
|
||||
type: [Object, Array],
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
popupTitle: {
|
||||
type: String,
|
||||
default: '请选择'
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '请选择'
|
||||
},
|
||||
heightMobile: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
clearIcon: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
split: {
|
||||
type: String,
|
||||
default: '/'
|
||||
},
|
||||
ellipsis: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isOpened: false,
|
||||
inputSelected: []
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$nextTick(() => {
|
||||
this.load();
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
localdata: {
|
||||
handler() {
|
||||
this.load()
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
clear() {
|
||||
this._dispatchEvent([]);
|
||||
},
|
||||
onPropsChange() {
|
||||
this._treeData = [];
|
||||
this.selectedIndex = 0;
|
||||
|
||||
this.load();
|
||||
},
|
||||
load() {
|
||||
if (this.readonly) {
|
||||
this._processReadonly(this.localdata, this.dataValue);
|
||||
return;
|
||||
}
|
||||
|
||||
// 回显本地数据
|
||||
if (this.isLocalData) {
|
||||
this.loadData();
|
||||
this.inputSelected = this.selected.slice(0);
|
||||
} else if (this.isCloudDataList || this.isCloudDataTree) { // 回显 Cloud 数据
|
||||
this.loading = true;
|
||||
this.getCloudDataValue().then((res) => {
|
||||
this.loading = false;
|
||||
this.inputSelected = res;
|
||||
}).catch((err) => {
|
||||
this.loading = false;
|
||||
this.errorMessage = err;
|
||||
})
|
||||
}
|
||||
},
|
||||
show() {
|
||||
this.isOpened = true
|
||||
setTimeout(() => {
|
||||
this.$refs.pickerView.updateData({
|
||||
treeData: this._treeData,
|
||||
selected: this.selected,
|
||||
selectedIndex: this.selectedIndex
|
||||
})
|
||||
}, 200)
|
||||
this.$emit('popupopened')
|
||||
},
|
||||
hide() {
|
||||
this.isOpened = false
|
||||
this.$emit('popupclosed')
|
||||
},
|
||||
handleInput() {
|
||||
if (this.readonly) {
|
||||
this.$emit('inputclick')
|
||||
return
|
||||
}
|
||||
this.show()
|
||||
},
|
||||
handleClose(e) {
|
||||
this.hide()
|
||||
},
|
||||
onnodeclick(e) {
|
||||
this.$emit('nodeclick', e)
|
||||
},
|
||||
ondatachange(e) {
|
||||
this._treeData = this.$refs.pickerView._treeData
|
||||
},
|
||||
onchange(e) {
|
||||
this.hide()
|
||||
this.$nextTick(() => {
|
||||
this.inputSelected = e;
|
||||
})
|
||||
this._dispatchEvent(e)
|
||||
},
|
||||
_processReadonly(dataList, value) {
|
||||
var isTree = dataList.findIndex((item) => {
|
||||
return item.children
|
||||
})
|
||||
if (isTree > -1) {
|
||||
let inputValue
|
||||
if (Array.isArray(value)) {
|
||||
inputValue = value[value.length - 1]
|
||||
if (typeof inputValue === 'object' && inputValue.value) {
|
||||
inputValue = inputValue.value
|
||||
}
|
||||
} else {
|
||||
inputValue = value
|
||||
}
|
||||
this.inputSelected = this._findNodePath(inputValue, this.localdata)
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.hasValue) {
|
||||
this.inputSelected = []
|
||||
return
|
||||
}
|
||||
|
||||
let result = []
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
var val = value[i]
|
||||
var item = dataList.find((v) => {
|
||||
return v.value == val
|
||||
})
|
||||
if (item) {
|
||||
result.push(item)
|
||||
}
|
||||
}
|
||||
if (result.length) {
|
||||
this.inputSelected = result
|
||||
}
|
||||
},
|
||||
_filterForArray(data, valueArray) {
|
||||
var result = []
|
||||
for (let i = 0; i < valueArray.length; i++) {
|
||||
var value = valueArray[i]
|
||||
var found = data.find((item) => {
|
||||
return item.value == value
|
||||
})
|
||||
if (found) {
|
||||
result.push(found)
|
||||
}
|
||||
}
|
||||
return result
|
||||
},
|
||||
_dispatchEvent(selected) {
|
||||
let item = {}
|
||||
if (selected.length) {
|
||||
var value = new Array(selected.length)
|
||||
for (var i = 0; i < selected.length; i++) {
|
||||
value[i] = selected[i].value
|
||||
}
|
||||
item = selected[selected.length - 1]
|
||||
} else {
|
||||
item.value = ''
|
||||
}
|
||||
if (this.formItem) {
|
||||
this.formItem.setValue(item.value)
|
||||
}
|
||||
|
||||
this.$emit('input', item.value)
|
||||
this.$emit('update:modelValue', item.value)
|
||||
this.$emit('change', {
|
||||
detail: {
|
||||
value: selected
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.uni-data-tree {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: #DD524D;
|
||||
}
|
||||
|
||||
.input-value {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
flex-wrap: nowrap;
|
||||
font-size: 14px;
|
||||
/* line-height: 35px; */
|
||||
padding: 0 10px;
|
||||
padding-right: 5px;
|
||||
overflow: hidden;
|
||||
height: 35px;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.input-value-border {
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.selected-area {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
/* #ifndef APP-NVUE */
|
||||
margin-right: auto;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
width: 40px;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.selected-list {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
/* padding: 0 5px; */
|
||||
}
|
||||
|
||||
.selected-item {
|
||||
flex-direction: row;
|
||||
/* padding: 0 1px; */
|
||||
/* #ifndef APP-NVUE */
|
||||
white-space: nowrap;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.text-color {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.placeholder {
|
||||
color: grey;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.input-split-line {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.arrow-area {
|
||||
position: relative;
|
||||
width: 20px;
|
||||
/* #ifndef APP-NVUE */
|
||||
margin-bottom: 5px;
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
/* #endif */
|
||||
justify-content: center;
|
||||
transform: rotate(-45deg);
|
||||
transform-origin: center;
|
||||
}
|
||||
|
||||
.input-arrow {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-left: 1px solid #999;
|
||||
border-bottom: 1px solid #999;
|
||||
}
|
||||
|
||||
.uni-data-tree-cover {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, .4);
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.uni-data-tree-dialog {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
/* #ifndef APP-NVUE */
|
||||
top: 20%;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
top: 200px;
|
||||
/* #endif */
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #FFFFFF;
|
||||
border-top-left-radius: 10px;
|
||||
border-top-right-radius: 10px;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
z-index: 102;
|
||||
overflow: hidden;
|
||||
/* #ifdef APP-NVUE */
|
||||
width: 750rpx;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.dialog-caption {
|
||||
position: relative;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
/* border-bottom: 1px solid #f0f0f0; */
|
||||
}
|
||||
|
||||
.title-area {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
/* #ifndef APP-NVUE */
|
||||
margin: auto;
|
||||
/* #endif */
|
||||
padding: 0 10px;
|
||||
}
|
||||
|
||||
.dialog-title {
|
||||
/* font-weight: bold; */
|
||||
line-height: 44px;
|
||||
}
|
||||
|
||||
.dialog-close {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0 15px;
|
||||
}
|
||||
|
||||
.dialog-close-plus {
|
||||
width: 16px;
|
||||
height: 2px;
|
||||
background-color: #666;
|
||||
border-radius: 2px;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.dialog-close-rotate {
|
||||
position: absolute;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
|
||||
.picker-view {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon-clear {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* #ifdef H5 */
|
||||
@media all and (min-width: 768px) {
|
||||
.uni-data-tree-cover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.uni-data-tree-dialog {
|
||||
position: absolute;
|
||||
top: 55px;
|
||||
height: auto;
|
||||
min-height: 400px;
|
||||
max-height: 50vh;
|
||||
background-color: #fff;
|
||||
border: 1px solid #EBEEF5;
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
|
||||
border-radius: 4px;
|
||||
overflow: unset;
|
||||
}
|
||||
|
||||
.dialog-caption {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.icon-clear {
|
||||
/* margin-right: 5px; */
|
||||
}
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
/* picker 弹出层通用的指示小三角, todo:扩展至上下左右方向定位 */
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-popper__arrow,
|
||||
.uni-popper__arrow::after {
|
||||
position: absolute;
|
||||
display: block;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-color: transparent;
|
||||
border-style: solid;
|
||||
border-width: 6px;
|
||||
}
|
||||
|
||||
.uni-popper__arrow {
|
||||
filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
|
||||
top: -6px;
|
||||
left: 10%;
|
||||
margin-right: 3px;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: #EBEEF5;
|
||||
}
|
||||
|
||||
.uni-popper__arrow::after {
|
||||
content: " ";
|
||||
top: 1px;
|
||||
margin-left: -6px;
|
||||
border-top-width: 0;
|
||||
border-bottom-color: #fff;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
</style>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,622 @@
|
|||
export default {
|
||||
props: {
|
||||
localdata: {
|
||||
type: [Array, Object],
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
spaceInfo: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
collection: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
action: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
field: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
orderby: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
where: {
|
||||
type: [String, Object],
|
||||
default: ''
|
||||
},
|
||||
pageData: {
|
||||
type: String,
|
||||
default: 'add'
|
||||
},
|
||||
pageCurrent: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 500
|
||||
},
|
||||
getcount: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
getone: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
gettree: {
|
||||
type: [Boolean, String],
|
||||
default: false
|
||||
},
|
||||
manual: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
type: [Array, String, Number],
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
modelValue: {
|
||||
type: [Array, String, Number],
|
||||
default () {
|
||||
return []
|
||||
}
|
||||
},
|
||||
preload: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
stepSearh: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
selfField: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
parentField: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
map: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {
|
||||
text: "text",
|
||||
value: "value"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
errorMessage: '',
|
||||
loadMore: {
|
||||
contentdown: '',
|
||||
contentrefresh: '',
|
||||
contentnomore: ''
|
||||
},
|
||||
dataList: [],
|
||||
selected: [],
|
||||
selectedIndex: 0,
|
||||
page: {
|
||||
current: this.pageCurrent,
|
||||
size: this.pageSize,
|
||||
count: 0
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
isLocalData() {
|
||||
return !this.collection.length;
|
||||
},
|
||||
isCloudData() {
|
||||
return this.collection.length > 0;
|
||||
},
|
||||
isCloudDataList() {
|
||||
return (this.isCloudData && (!this.parentField && !this.selfField));
|
||||
},
|
||||
isCloudDataTree() {
|
||||
return (this.isCloudData && this.parentField && this.selfField);
|
||||
},
|
||||
dataValue() {
|
||||
let isModelValue = Array.isArray(this.modelValue) ? (this.modelValue.length > 0) : (this.modelValue !== null ||
|
||||
this.modelValue !== undefined);
|
||||
return isModelValue ? this.modelValue : this.value;
|
||||
},
|
||||
hasValue() {
|
||||
if (typeof this.dataValue === 'number') {
|
||||
return true
|
||||
}
|
||||
return (this.dataValue != null) && (this.dataValue.length > 0)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.$watch(() => {
|
||||
var al = [];
|
||||
['pageCurrent',
|
||||
'pageSize',
|
||||
'spaceInfo',
|
||||
'value',
|
||||
'modelValue',
|
||||
'localdata',
|
||||
'collection',
|
||||
'action',
|
||||
'field',
|
||||
'orderby',
|
||||
'where',
|
||||
'getont',
|
||||
'getcount',
|
||||
'gettree'
|
||||
].forEach(key => {
|
||||
al.push(this[key])
|
||||
});
|
||||
return al
|
||||
}, (newValue, oldValue) => {
|
||||
let needReset = false
|
||||
for (let i = 2; i < newValue.length; i++) {
|
||||
if (newValue[i] != oldValue[i]) {
|
||||
needReset = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (newValue[0] != oldValue[0]) {
|
||||
this.page.current = this.pageCurrent
|
||||
}
|
||||
this.page.size = this.pageSize
|
||||
|
||||
this.onPropsChange()
|
||||
})
|
||||
this._treeData = []
|
||||
},
|
||||
methods: {
|
||||
onPropsChange() {
|
||||
this._treeData = [];
|
||||
},
|
||||
|
||||
// 填充 pickview 数据
|
||||
async loadData() {
|
||||
if (this.isLocalData) {
|
||||
this.loadLocalData();
|
||||
} else if (this.isCloudDataList) {
|
||||
this.loadCloudDataList();
|
||||
} else if (this.isCloudDataTree) {
|
||||
this.loadCloudDataTree();
|
||||
}
|
||||
},
|
||||
|
||||
// 加载本地数据
|
||||
async loadLocalData() {
|
||||
this._treeData = [];
|
||||
this._extractTree(this.localdata, this._treeData);
|
||||
|
||||
let inputValue = this.dataValue;
|
||||
if (inputValue === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(inputValue)) {
|
||||
inputValue = inputValue[inputValue.length - 1];
|
||||
if (typeof inputValue === 'object' && inputValue[this.map.value]) {
|
||||
inputValue = inputValue[this.map.value];
|
||||
}
|
||||
}
|
||||
|
||||
this.selected = this._findNodePath(inputValue, this.localdata);
|
||||
},
|
||||
|
||||
// 加载 Cloud 数据 (单列)
|
||||
async loadCloudDataList() {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
let response = await this.getCommand();
|
||||
let responseData = response.result.data;
|
||||
|
||||
this._treeData = responseData;
|
||||
|
||||
this._updateBindData();
|
||||
this._updateSelected();
|
||||
|
||||
this.onDataChange();
|
||||
} catch (e) {
|
||||
this.errorMessage = e;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载 Cloud 数据 (树形)
|
||||
async loadCloudDataTree() {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
let commandOptions = {
|
||||
field: this._cloudDataPostField(),
|
||||
where: this._cloudDataTreeWhere()
|
||||
};
|
||||
if (this.gettree) {
|
||||
commandOptions.startwith = `${this.selfField}=='${this.dataValue}'`;
|
||||
}
|
||||
|
||||
let response = await this.getCommand(commandOptions);
|
||||
let responseData = response.result.data;
|
||||
|
||||
this._treeData = responseData;
|
||||
this._updateBindData();
|
||||
this._updateSelected();
|
||||
|
||||
this.onDataChange();
|
||||
} catch (e) {
|
||||
this.errorMessage = e;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 加载 Cloud 数据 (节点)
|
||||
async loadCloudDataNode(callback) {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
let commandOptions = {
|
||||
field: this._cloudDataPostField(),
|
||||
where: this._cloudDataNodeWhere()
|
||||
};
|
||||
|
||||
let response = await this.getCommand(commandOptions);
|
||||
let responseData = response.result.data;
|
||||
|
||||
callback(responseData);
|
||||
} catch (e) {
|
||||
this.errorMessage = e;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
// 回显 Cloud 数据
|
||||
getCloudDataValue() {
|
||||
if (this.isCloudDataList) {
|
||||
return this.getCloudDataListValue();
|
||||
}
|
||||
|
||||
if (this.isCloudDataTree) {
|
||||
return this.getCloudDataTreeValue();
|
||||
}
|
||||
},
|
||||
|
||||
// 回显 Cloud 数据 (单列)
|
||||
getCloudDataListValue() {
|
||||
// 根据 field's as value标识匹配 where 条件
|
||||
let where = [];
|
||||
let whereField = this._getForeignKeyByField();
|
||||
if (whereField) {
|
||||
where.push(`${whereField} == '${this.dataValue}'`)
|
||||
}
|
||||
|
||||
where = where.join(' || ');
|
||||
|
||||
if (this.where) {
|
||||
where = `(${this.where}) && (${where})`
|
||||
}
|
||||
|
||||
return this.getCommand({
|
||||
field: this._cloudDataPostField(),
|
||||
where
|
||||
}).then((res) => {
|
||||
this.selected = res.result.data;
|
||||
return res.result.data;
|
||||
});
|
||||
},
|
||||
|
||||
// 回显 Cloud 数据 (树形)
|
||||
getCloudDataTreeValue() {
|
||||
return this.getCommand({
|
||||
field: this._cloudDataPostField(),
|
||||
getTreePath: {
|
||||
startWith: `${this.selfField}=='${this.dataValue}'`
|
||||
}
|
||||
}).then((res) => {
|
||||
let treePath = [];
|
||||
this._extractTreePath(res.result.data, treePath);
|
||||
this.selected = treePath;
|
||||
return treePath;
|
||||
});
|
||||
},
|
||||
|
||||
getCommand(options = {}) {
|
||||
/* eslint-disable no-undef */
|
||||
let db = uniCloud.database(this.spaceInfo)
|
||||
|
||||
const action = options.action || this.action
|
||||
if (action) {
|
||||
db = db.action(action)
|
||||
}
|
||||
|
||||
const collection = options.collection || this.collection
|
||||
db = db.collection(collection)
|
||||
|
||||
const where = options.where || this.where
|
||||
if (!(!where || !Object.keys(where).length)) {
|
||||
db = db.where(where)
|
||||
}
|
||||
|
||||
const field = options.field || this.field
|
||||
if (field) {
|
||||
db = db.field(field)
|
||||
}
|
||||
|
||||
const orderby = options.orderby || this.orderby
|
||||
if (orderby) {
|
||||
db = db.orderBy(orderby)
|
||||
}
|
||||
|
||||
const current = options.pageCurrent !== undefined ? options.pageCurrent : this.page.current
|
||||
const size = options.pageSize !== undefined ? options.pageSize : this.page.size
|
||||
const getCount = options.getcount !== undefined ? options.getcount : this.getcount
|
||||
const getTree = options.gettree !== undefined ? options.gettree : this.gettree
|
||||
|
||||
const getOptions = {
|
||||
getCount,
|
||||
getTree
|
||||
}
|
||||
if (options.getTreePath) {
|
||||
getOptions.getTreePath = options.getTreePath
|
||||
}
|
||||
|
||||
db = db.skip(size * (current - 1)).limit(size).get(getOptions)
|
||||
|
||||
return db
|
||||
},
|
||||
|
||||
_cloudDataPostField() {
|
||||
let fields = [this.field];
|
||||
if (this.parentField) {
|
||||
fields.push(`${this.parentField} as parent_value`);
|
||||
}
|
||||
return fields.join(',');
|
||||
},
|
||||
|
||||
_cloudDataTreeWhere() {
|
||||
let result = []
|
||||
let selected = this.selected
|
||||
let parentField = this.parentField
|
||||
if (parentField) {
|
||||
result.push(`${parentField} == null || ${parentField} == ""`)
|
||||
}
|
||||
if (selected.length) {
|
||||
for (var i = 0; i < selected.length - 1; i++) {
|
||||
result.push(`${parentField} == '${selected[i].value}'`)
|
||||
}
|
||||
}
|
||||
|
||||
let where = []
|
||||
if (this.where) {
|
||||
where.push(`(${this.where})`)
|
||||
}
|
||||
|
||||
if (result.length) {
|
||||
where.push(`(${result.join(' || ')})`)
|
||||
}
|
||||
|
||||
return where.join(' && ')
|
||||
},
|
||||
|
||||
_cloudDataNodeWhere() {
|
||||
let where = []
|
||||
let selected = this.selected;
|
||||
if (selected.length) {
|
||||
where.push(`${this.parentField} == '${selected[selected.length - 1].value}'`);
|
||||
}
|
||||
|
||||
where = where.join(' || ');
|
||||
|
||||
if (this.where) {
|
||||
return `(${this.where}) && (${where})`
|
||||
}
|
||||
|
||||
return where
|
||||
},
|
||||
|
||||
_getWhereByForeignKey() {
|
||||
let result = []
|
||||
let whereField = this._getForeignKeyByField();
|
||||
if (whereField) {
|
||||
result.push(`${whereField} == '${this.dataValue}'`)
|
||||
}
|
||||
|
||||
if (this.where) {
|
||||
return `(${this.where}) && (${result.join(' || ')})`
|
||||
}
|
||||
|
||||
return result.join(' || ')
|
||||
},
|
||||
|
||||
_getForeignKeyByField() {
|
||||
let fields = this.field.split(',');
|
||||
let whereField = null;
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const items = fields[i].split('as');
|
||||
if (items.length < 2) {
|
||||
continue;
|
||||
}
|
||||
if (items[1].trim() === 'value') {
|
||||
whereField = items[0].trim();
|
||||
break;
|
||||
}
|
||||
}
|
||||
return whereField;
|
||||
},
|
||||
|
||||
_updateBindData(node) {
|
||||
const {
|
||||
dataList,
|
||||
hasNodes
|
||||
} = this._filterData(this._treeData, this.selected)
|
||||
|
||||
let isleaf = this._stepSearh === false && !hasNodes
|
||||
|
||||
if (node) {
|
||||
node.isleaf = isleaf
|
||||
}
|
||||
|
||||
this.dataList = dataList
|
||||
this.selectedIndex = dataList.length - 1
|
||||
|
||||
if (!isleaf && this.selected.length < dataList.length) {
|
||||
this.selected.push({
|
||||
value: null,
|
||||
text: "请选择"
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
isleaf,
|
||||
hasNodes
|
||||
}
|
||||
},
|
||||
|
||||
_updateSelected() {
|
||||
let dl = this.dataList
|
||||
let sl = this.selected
|
||||
let textField = this.map.text
|
||||
let valueField = this.map.value
|
||||
for (let i = 0; i < sl.length; i++) {
|
||||
let value = sl[i].value
|
||||
let dl2 = dl[i]
|
||||
for (let j = 0; j < dl2.length; j++) {
|
||||
let item2 = dl2[j]
|
||||
if (item2[valueField] === value) {
|
||||
sl[i].text = item2[textField]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_filterData(data, paths) {
|
||||
let dataList = []
|
||||
let hasNodes = true
|
||||
|
||||
dataList.push(data.filter((item) => {
|
||||
return (item.parent_value === null || item.parent_value === undefined || item.parent_value === '')
|
||||
}))
|
||||
for (let i = 0; i < paths.length; i++) {
|
||||
let value = paths[i].value
|
||||
let nodes = data.filter((item) => {
|
||||
return item.parent_value === value
|
||||
})
|
||||
|
||||
if (nodes.length) {
|
||||
dataList.push(nodes)
|
||||
} else {
|
||||
hasNodes = false
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
dataList,
|
||||
hasNodes
|
||||
}
|
||||
},
|
||||
|
||||
_extractTree(nodes, result, parent_value) {
|
||||
let list = result || []
|
||||
let valueField = this.map.value
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let node = nodes[i]
|
||||
|
||||
let child = {}
|
||||
for (let key in node) {
|
||||
if (key !== 'children') {
|
||||
child[key] = node[key]
|
||||
}
|
||||
}
|
||||
if (parent_value !== null && parent_value !== undefined && parent_value !== '') {
|
||||
child.parent_value = parent_value
|
||||
}
|
||||
result.push(child)
|
||||
|
||||
let children = node.children
|
||||
if (children) {
|
||||
this._extractTree(children, result, node[valueField])
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_extractTreePath(nodes, result) {
|
||||
let list = result || []
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let node = nodes[i]
|
||||
|
||||
let child = {}
|
||||
for (let key in node) {
|
||||
if (key !== 'children') {
|
||||
child[key] = node[key]
|
||||
}
|
||||
}
|
||||
result.push(child)
|
||||
|
||||
let children = node.children
|
||||
if (children) {
|
||||
this._extractTreePath(children, result)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_findNodePath(key, nodes, path = []) {
|
||||
let textField = this.map.text
|
||||
let valueField = this.map.value
|
||||
for (let i = 0; i < nodes.length; i++) {
|
||||
let node = nodes[i]
|
||||
let children = node.children
|
||||
let text = node[textField]
|
||||
let value = node[valueField]
|
||||
|
||||
path.push({
|
||||
value,
|
||||
text
|
||||
})
|
||||
|
||||
if (value === key) {
|
||||
return path
|
||||
}
|
||||
|
||||
if (children) {
|
||||
const p = this._findNodePath(key, children, path)
|
||||
if (p.length) {
|
||||
return p
|
||||
}
|
||||
}
|
||||
|
||||
path.pop()
|
||||
}
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,693 @@
|
|||
export type PaginationType = {
|
||||
current : number,
|
||||
size : number,
|
||||
count : number
|
||||
}
|
||||
|
||||
export type LoadMoreType = {
|
||||
contentdown : string,
|
||||
contentrefresh : string,
|
||||
contentnomore : string
|
||||
}
|
||||
|
||||
export type SelectedItemType = {
|
||||
name : string,
|
||||
value : string,
|
||||
}
|
||||
|
||||
export type GetCommandOptions = {
|
||||
collection ?: UTSJSONObject,
|
||||
field ?: string,
|
||||
orderby ?: string,
|
||||
where ?: any,
|
||||
pageData ?: string,
|
||||
pageCurrent ?: number,
|
||||
pageSize ?: number,
|
||||
getCount ?: boolean,
|
||||
getTree ?: any,
|
||||
getTreePath ?: UTSJSONObject,
|
||||
startwith ?: string,
|
||||
limitlevel ?: number,
|
||||
groupby ?: string,
|
||||
groupField ?: string,
|
||||
distinct ?: boolean,
|
||||
pageIndistinct ?: boolean,
|
||||
foreignKey ?: string,
|
||||
loadtime ?: string,
|
||||
manual ?: boolean
|
||||
}
|
||||
|
||||
const DefaultSelectedNode = {
|
||||
text: '请选择',
|
||||
value: ''
|
||||
}
|
||||
|
||||
export const dataPicker = defineMixin({
|
||||
props: {
|
||||
localdata: {
|
||||
type: Array as PropType<Array<UTSJSONObject>>,
|
||||
default: [] as Array<UTSJSONObject>
|
||||
},
|
||||
collection: {
|
||||
type: Object,
|
||||
default: ''
|
||||
},
|
||||
field: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
orderby: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
where: {
|
||||
type: Object,
|
||||
default: ''
|
||||
},
|
||||
pageData: {
|
||||
type: String,
|
||||
default: 'add'
|
||||
},
|
||||
pageCurrent: {
|
||||
type: Number,
|
||||
default: 1
|
||||
},
|
||||
pageSize: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
getcount: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
gettree: {
|
||||
type: Object,
|
||||
default: ''
|
||||
},
|
||||
gettreepath: {
|
||||
type: Object,
|
||||
default: ''
|
||||
},
|
||||
startwith: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
limitlevel: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
groupby: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
groupField: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
distinct: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
pageIndistinct: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
foreignKey: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
loadtime: {
|
||||
type: String,
|
||||
default: 'auto'
|
||||
},
|
||||
manual: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
preload: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
stepSearh: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
selfField: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
parentField: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
value: {
|
||||
type: Object,
|
||||
default: ''
|
||||
},
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: ''
|
||||
},
|
||||
defaultProps: {
|
||||
type: Object as PropType<UTSJSONObject>,
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
error: null as UniCloudError | null,
|
||||
treeData: [] as Array<UTSJSONObject>,
|
||||
selectedIndex: 0,
|
||||
selectedNodes: [] as Array<UTSJSONObject>,
|
||||
selectedPages: [] as Array<UTSJSONObject>[],
|
||||
selectedValue: '',
|
||||
selectedPaths: [] as Array<UTSJSONObject>,
|
||||
pagination: {
|
||||
current: 1,
|
||||
size: 20,
|
||||
count: 0
|
||||
} as PaginationType
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
mappingTextName() : string {
|
||||
// TODO
|
||||
return (this.defaultProps != null) ? this.defaultProps!.getString('text', 'text') : 'text'
|
||||
},
|
||||
mappingValueName() : string {
|
||||
// TODO
|
||||
return (this.defaultProps != null) ? this.defaultProps!.getString('value', 'value') : 'value'
|
||||
},
|
||||
currentDataList() : Array<UTSJSONObject> {
|
||||
if (this.selectedIndex > this.selectedPages.length - 1) {
|
||||
return [] as Array<UTSJSONObject>
|
||||
}
|
||||
return this.selectedPages[this.selectedIndex]
|
||||
},
|
||||
isLocalData() : boolean {
|
||||
return this.localdata.length > 0
|
||||
},
|
||||
isCloudData() : boolean {
|
||||
return this._checkIsNotNull(this.collection)
|
||||
},
|
||||
isCloudDataList() : boolean {
|
||||
return (this.isCloudData && (this.parentField.length == 0 && this.selfField.length == 0))
|
||||
},
|
||||
isCloudDataTree() : boolean {
|
||||
return (this.isCloudData && this.parentField.length > 0 && this.selfField.length > 0)
|
||||
},
|
||||
dataValue() : any {
|
||||
return this.hasModelValue ? this.modelValue : this.value
|
||||
},
|
||||
hasCloudTreeData() : boolean {
|
||||
return this.treeData.length > 0
|
||||
},
|
||||
hasModelValue() : boolean {
|
||||
if (typeof this.modelValue == 'string') {
|
||||
const valueString = this.modelValue as string
|
||||
return (valueString.length > 0)
|
||||
} else if (Array.isArray(this.modelValue)) {
|
||||
const valueArray = this.modelValue as Array<string>
|
||||
return (valueArray.length > 0)
|
||||
}
|
||||
return false
|
||||
},
|
||||
hasCloudDataValue() : boolean {
|
||||
if (typeof this.dataValue == 'string') {
|
||||
const valueString = this.dataValue as string
|
||||
return (valueString.length > 0)
|
||||
}
|
||||
return false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.pagination.current = this.pageCurrent
|
||||
this.pagination.size = this.pageSize
|
||||
|
||||
this.$watch(
|
||||
() : any => [
|
||||
this.pageCurrent,
|
||||
this.pageSize,
|
||||
this.localdata,
|
||||
this.value,
|
||||
this.collection,
|
||||
this.field,
|
||||
this.getcount,
|
||||
this.orderby,
|
||||
this.where,
|
||||
this.groupby,
|
||||
this.groupField,
|
||||
this.distinct
|
||||
],
|
||||
(newValue : Array<any>, oldValue : Array<any>) => {
|
||||
this.pagination.size = this.pageSize
|
||||
if (newValue[0] !== oldValue[0]) {
|
||||
this.pagination.current = this.pageCurrent
|
||||
}
|
||||
|
||||
this.onPropsChange()
|
||||
}
|
||||
)
|
||||
},
|
||||
methods: {
|
||||
onPropsChange() {
|
||||
this.selectedIndex = 0
|
||||
this.treeData.length = 0
|
||||
this.selectedNodes.length = 0
|
||||
this.selectedPages.length = 0
|
||||
this.selectedPaths.length = 0
|
||||
|
||||
// 加载数据
|
||||
this.$nextTick(() => {
|
||||
this.loadData()
|
||||
})
|
||||
},
|
||||
|
||||
onTabSelect(index : number) {
|
||||
this.selectedIndex = index
|
||||
},
|
||||
|
||||
onNodeClick(nodeData : UTSJSONObject) {
|
||||
if (nodeData.getBoolean('disable', false)) {
|
||||
return
|
||||
}
|
||||
|
||||
const isLeaf = this._checkIsLeafNode(nodeData)
|
||||
|
||||
this._trimSelectedNodes(nodeData)
|
||||
|
||||
this.$emit('nodeclick', nodeData)
|
||||
|
||||
if (this.isLocalData) {
|
||||
if (isLeaf || !this._checkHasChildren(nodeData)) {
|
||||
this.onFinish()
|
||||
}
|
||||
} else if (this.isCloudDataList) {
|
||||
this.onFinish()
|
||||
} else if (this.isCloudDataTree) {
|
||||
if (isLeaf) {
|
||||
this.onFinish()
|
||||
} else if (!this._checkHasChildren(nodeData)) {
|
||||
// 尝试请求一次,如果没有返回数据标记为叶子节点
|
||||
this.loadCloudDataNode(nodeData)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
getChangeNodes(): Array<UTSJSONObject> {
|
||||
const nodes: Array<UTSJSONObject> = []
|
||||
this.selectedNodes.forEach((node : UTSJSONObject) => {
|
||||
const newNode: UTSJSONObject = {}
|
||||
newNode[this.mappingTextName] = node.getString(this.mappingTextName)
|
||||
newNode[this.mappingValueName] = node.getString(this.mappingValueName)
|
||||
nodes.push(newNode)
|
||||
})
|
||||
return nodes
|
||||
},
|
||||
|
||||
onFinish() { },
|
||||
|
||||
// 加载数据(自动判定环境)
|
||||
loadData() {
|
||||
if (this.isLocalData) {
|
||||
this.loadLocalData()
|
||||
} else if (this.isCloudDataList) {
|
||||
this.loadCloudDataList()
|
||||
} else if (this.isCloudDataTree) {
|
||||
this.loadCloudDataTree()
|
||||
}
|
||||
},
|
||||
|
||||
// 加载本地数据
|
||||
loadLocalData() {
|
||||
this.treeData = this.localdata
|
||||
if (Array.isArray(this.dataValue)) {
|
||||
const value = this.dataValue as Array<UTSJSONObject>
|
||||
this.selectedPaths = value.slice(0)
|
||||
this._pushSelectedTreeNodes(value, this.localdata)
|
||||
} else {
|
||||
this._pushSelectedNodes(this.localdata)
|
||||
}
|
||||
},
|
||||
|
||||
// 加载 Cloud 数据 (单列)
|
||||
loadCloudDataList() {
|
||||
this._loadCloudData(null, (data : Array<UTSJSONObject>) => {
|
||||
this.treeData = data
|
||||
this._pushSelectedNodes(data)
|
||||
})
|
||||
},
|
||||
|
||||
// 加载 Cloud 数据 (树形)
|
||||
loadCloudDataTree() {
|
||||
let commandOptions = {
|
||||
field: this._cloudDataPostField(),
|
||||
where: this._cloudDataTreeWhere(),
|
||||
getTree: true
|
||||
} as GetCommandOptions
|
||||
if (this._checkIsNotNull(this.gettree)) {
|
||||
commandOptions.startwith = `${this.selfField}=='${this.dataValue as string}'`
|
||||
}
|
||||
this._loadCloudData(commandOptions, (data : Array<UTSJSONObject>) => {
|
||||
this.treeData = data
|
||||
if (this.selectedPaths.length > 0) {
|
||||
this._pushSelectedTreeNodes(this.selectedPaths, data)
|
||||
} else {
|
||||
this._pushSelectedNodes(data)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 加载 Cloud 数据 (节点)
|
||||
loadCloudDataNode(nodeData : UTSJSONObject) {
|
||||
const commandOptions = {
|
||||
field: this._cloudDataPostField(),
|
||||
where: this._cloudDataNodeWhere()
|
||||
} as GetCommandOptions
|
||||
this._loadCloudData(commandOptions, (data : Array<UTSJSONObject>) => {
|
||||
nodeData['children'] = data
|
||||
if (data.length == 0) {
|
||||
nodeData['isleaf'] = true
|
||||
this.onFinish()
|
||||
} else {
|
||||
this._pushSelectedNodes(data)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 回显 Cloud Tree Path
|
||||
loadCloudDataPath() {
|
||||
if (!this.hasCloudDataValue) {
|
||||
return
|
||||
}
|
||||
|
||||
const command : GetCommandOptions = {}
|
||||
|
||||
// 单列
|
||||
if (this.isCloudDataList) {
|
||||
// 根据 field's as value标识匹配 where 条件
|
||||
let where : Array<string> = [];
|
||||
let whereField = this._getForeignKeyByField();
|
||||
if (whereField.length > 0) {
|
||||
where.push(`${whereField} == '${this.dataValue as string}'`)
|
||||
}
|
||||
|
||||
let whereString = where.join(' || ')
|
||||
if (this._checkIsNotNull(this.where)) {
|
||||
whereString = `(${this.where}) && (${whereString})`
|
||||
}
|
||||
|
||||
command.field = this._cloudDataPostField()
|
||||
command.where = whereString
|
||||
}
|
||||
|
||||
// 树形
|
||||
if (this.isCloudDataTree) {
|
||||
command.field = this._cloudDataPostField()
|
||||
command.getTreePath = {
|
||||
startWith: `${this.selfField}=='${this.dataValue as string}'`
|
||||
}
|
||||
}
|
||||
|
||||
this._loadCloudData(command, (data : Array<UTSJSONObject>) => {
|
||||
this._extractTreePath(data, this.selectedPaths)
|
||||
})
|
||||
},
|
||||
|
||||
_loadCloudData(options ?: GetCommandOptions, callback ?: ((data : Array<UTSJSONObject>) => void)) {
|
||||
if (this.loading) {
|
||||
return
|
||||
}
|
||||
this.loading = true
|
||||
|
||||
this.error = null
|
||||
|
||||
this._getCommand(options).then((response : UniCloudDBGetResult) => {
|
||||
callback?.(response.data)
|
||||
}).catch((err : any | null) => {
|
||||
this.error = err as UniCloudError
|
||||
}).finally(() => {
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
_cloudDataPostField() : string {
|
||||
let fields = [this.field];
|
||||
if (this.parentField.length > 0) {
|
||||
fields.push(`${this.parentField} as parent_value`)
|
||||
}
|
||||
return fields.join(',')
|
||||
},
|
||||
|
||||
_cloudDataTreeWhere() : string {
|
||||
let result : Array<string> = []
|
||||
let selectedNodes = this.selectedNodes.length > 0 ? this.selectedNodes : this.selectedPaths
|
||||
let parentField = this.parentField
|
||||
if (parentField.length > 0) {
|
||||
result.push(`${parentField} == null || ${parentField} == ""`)
|
||||
}
|
||||
if (selectedNodes.length > 0) {
|
||||
for (var i = 0; i < selectedNodes.length - 1; i++) {
|
||||
const parentFieldValue = selectedNodes[i].getString('value', '')
|
||||
result.push(`${parentField} == '${parentFieldValue}'`)
|
||||
}
|
||||
}
|
||||
|
||||
let where : Array<string> = []
|
||||
if (this._checkIsNotNull(this.where)) {
|
||||
where.push(`(${this.where as string})`)
|
||||
}
|
||||
|
||||
if (result.length > 0) {
|
||||
where.push(`(${result.join(' || ')})`)
|
||||
}
|
||||
|
||||
return where.join(' && ')
|
||||
},
|
||||
|
||||
_cloudDataNodeWhere() : string {
|
||||
const where : Array<string> = []
|
||||
if (this.selectedNodes.length > 0) {
|
||||
const value = this.selectedNodes[this.selectedNodes.length - 1].getString('value', '')
|
||||
where.push(`${this.parentField} == '${value}'`)
|
||||
}
|
||||
|
||||
let whereString = where.join(' || ')
|
||||
if (this._checkIsNotNull(this.where)) {
|
||||
return `(${this.where as string}) && (${whereString})`
|
||||
}
|
||||
|
||||
return whereString
|
||||
},
|
||||
|
||||
_getWhereByForeignKey() : string {
|
||||
let result : Array<string> = []
|
||||
let whereField = this._getForeignKeyByField();
|
||||
if (whereField.length > 0) {
|
||||
result.push(`${whereField} == '${this.dataValue as string}'`)
|
||||
}
|
||||
|
||||
if (this._checkIsNotNull(this.where)) {
|
||||
return `(${this.where}) && (${result.join(' || ')})`
|
||||
}
|
||||
|
||||
return result.join(' || ')
|
||||
},
|
||||
|
||||
_getForeignKeyByField() : string {
|
||||
const fields = this.field.split(',')
|
||||
let whereField = ''
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
const items = fields[i].split('as')
|
||||
if (items.length < 2) {
|
||||
continue
|
||||
}
|
||||
if (items[1].trim() === 'value') {
|
||||
whereField = items[0].trim()
|
||||
break
|
||||
}
|
||||
}
|
||||
return whereField
|
||||
},
|
||||
|
||||
_getCommand(options ?: GetCommandOptions) : Promise<UniCloudDBGetResult> {
|
||||
let db = uniCloud.databaseForJQL()
|
||||
|
||||
let collection = Array.isArray(this.collection) ? db.collection(...(this.collection as Array<any>)) : db.collection(this.collection)
|
||||
|
||||
let filter : UniCloudDBFilter | null = null
|
||||
if (this.foreignKey.length > 0) {
|
||||
filter = collection.foreignKey(this.foreignKey)
|
||||
}
|
||||
|
||||
const where : any = options?.where ?? this.where
|
||||
if (typeof where == 'string') {
|
||||
const whereString = where as string
|
||||
if (whereString.length > 0) {
|
||||
filter = (filter != null) ? filter.where(where) : collection.where(where)
|
||||
}
|
||||
} else {
|
||||
filter = (filter != null) ? filter.where(where) : collection.where(where)
|
||||
}
|
||||
|
||||
let query : UniCloudDBQuery | null = null
|
||||
if (this.field.length > 0) {
|
||||
query = (filter != null) ? filter.field(this.field) : collection.field(this.field)
|
||||
}
|
||||
if (this.groupby.length > 0) {
|
||||
if (query != null) {
|
||||
query = query.groupBy(this.groupby)
|
||||
} else if (filter != null) {
|
||||
query = filter.groupBy(this.groupby)
|
||||
}
|
||||
}
|
||||
if (this.groupField.length > 0) {
|
||||
if (query != null) {
|
||||
query = query.groupField(this.groupField)
|
||||
} else if (filter != null) {
|
||||
query = filter.groupField(this.groupField)
|
||||
}
|
||||
}
|
||||
if (this.distinct == true) {
|
||||
if (query != null) {
|
||||
query = query.distinct(this.field)
|
||||
} else if (filter != null) {
|
||||
query = filter.distinct(this.field)
|
||||
}
|
||||
}
|
||||
if (this.orderby.length > 0) {
|
||||
if (query != null) {
|
||||
query = query.orderBy(this.orderby)
|
||||
} else if (filter != null) {
|
||||
query = filter.orderBy(this.orderby)
|
||||
}
|
||||
}
|
||||
|
||||
const size = this.pagination.size
|
||||
const current = this.pagination.current
|
||||
if (query != null) {
|
||||
query = query.skip(size * (current - 1)).limit(size)
|
||||
} else if (filter != null) {
|
||||
query = filter.skip(size * (current - 1)).limit(size)
|
||||
} else {
|
||||
query = collection.skip(size * (current - 1)).limit(size)
|
||||
}
|
||||
|
||||
const getOptions = {}
|
||||
const treeOptions = {
|
||||
limitLevel: this.limitlevel,
|
||||
startWith: this.startwith
|
||||
}
|
||||
if (this.getcount == true) {
|
||||
getOptions['getCount'] = this.getcount
|
||||
}
|
||||
|
||||
const getTree : any = options?.getTree ?? this.gettree
|
||||
if (typeof getTree == 'string') {
|
||||
const getTreeString = getTree as string
|
||||
if (getTreeString.length > 0) {
|
||||
getOptions['getTree'] = treeOptions
|
||||
}
|
||||
} else if (typeof getTree == 'object') {
|
||||
getOptions['getTree'] = treeOptions
|
||||
} else {
|
||||
getOptions['getTree'] = getTree
|
||||
}
|
||||
|
||||
const getTreePath = options?.getTreePath ?? this.gettreepath
|
||||
if (typeof getTreePath == 'string') {
|
||||
const getTreePathString = getTreePath as string
|
||||
if (getTreePathString.length > 0) {
|
||||
getOptions['getTreePath'] = getTreePath
|
||||
}
|
||||
} else {
|
||||
getOptions['getTreePath'] = getTreePath
|
||||
}
|
||||
|
||||
return query.get(getOptions)
|
||||
},
|
||||
|
||||
_checkIsNotNull(value : any) : boolean {
|
||||
if (typeof value == 'string') {
|
||||
const valueString = value as string
|
||||
return (valueString.length > 0)
|
||||
} else if (value instanceof UTSJSONObject) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
_checkIsLeafNode(nodeData : UTSJSONObject) : boolean {
|
||||
if (this.selectedIndex >= this.limitlevel) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (nodeData.getBoolean('isleaf', false)) {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
},
|
||||
|
||||
_checkHasChildren(nodeData : UTSJSONObject) : boolean {
|
||||
const children = nodeData.getArray('children') ?? ([] as Array<any>)
|
||||
return children.length > 0
|
||||
},
|
||||
|
||||
_pushSelectedNodes(nodes : Array<UTSJSONObject>) {
|
||||
this.selectedNodes.push(DefaultSelectedNode)
|
||||
this.selectedPages.push(nodes)
|
||||
this.selectedIndex = this.selectedPages.length - 1
|
||||
},
|
||||
|
||||
_trimSelectedNodes(nodeData : UTSJSONObject) {
|
||||
this.selectedNodes.splice(this.selectedIndex)
|
||||
this.selectedNodes.push(nodeData)
|
||||
|
||||
if (this.selectedPages.length > 0) {
|
||||
this.selectedPages.splice(this.selectedIndex + 1)
|
||||
}
|
||||
|
||||
const children = nodeData.getArray<UTSJSONObject>('children') ?? ([] as Array<UTSJSONObject>)
|
||||
if (children.length > 0) {
|
||||
this.selectedNodes.push(DefaultSelectedNode)
|
||||
this.selectedPages.push(children)
|
||||
}
|
||||
|
||||
this.selectedIndex = this.selectedPages.length - 1
|
||||
},
|
||||
|
||||
_pushSelectedTreeNodes(paths : Array<UTSJSONObject>, nodes : Array<UTSJSONObject>) {
|
||||
let children : Array<UTSJSONObject> = nodes
|
||||
paths.forEach((node : UTSJSONObject) => {
|
||||
const findNode = children.find((item : UTSJSONObject) : boolean => {
|
||||
return (item.getString(this.mappingValueName) == node.getString(this.mappingValueName))
|
||||
})
|
||||
if (findNode != null) {
|
||||
this.selectedPages.push(children)
|
||||
this.selectedNodes.push(node)
|
||||
children = findNode.getArray<UTSJSONObject>('children') ?? ([] as Array<UTSJSONObject>)
|
||||
}
|
||||
})
|
||||
this.selectedIndex = this.selectedPages.length - 1
|
||||
},
|
||||
|
||||
_extractTreePath(nodes : Array<UTSJSONObject>, result : Array<UTSJSONObject>) {
|
||||
if (nodes.length == 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const node = nodes[0]
|
||||
result.push(node)
|
||||
|
||||
const children = node.getArray<UTSJSONObject>('children')
|
||||
if (Array.isArray(children) && children!.length > 0) {
|
||||
this._extractTreePath(children, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
|
@ -0,0 +1,76 @@
|
|||
.uni-data-pickerview {
|
||||
position: relative;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.loading-cover {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: rgba(150, 150, 150, .1);
|
||||
}
|
||||
|
||||
.error {
|
||||
background-color: #fff;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: #DD524D;
|
||||
}
|
||||
|
||||
.selected-node-list {
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.selected-node-item {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
padding: 8px 10px 8px 10px;
|
||||
border-bottom: 2px solid transparent;
|
||||
}
|
||||
|
||||
.selected-node-item-active {
|
||||
color: #007aff;
|
||||
border-bottom-color: #007aff;
|
||||
}
|
||||
|
||||
.list-view {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.list-item {
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 12px 15px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.item-text {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.item-text-disabled {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.item-text-overflow {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.check {
|
||||
margin-right: 5px;
|
||||
border: 2px solid #007aff;
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
height: 12px;
|
||||
width: 6px;
|
||||
transform-origin: center;
|
||||
transform: rotate(45deg);
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<view class="uni-data-pickerview">
|
||||
<view v-if="error!=null" class="error">
|
||||
<text class="error-text">{{error!.errMsg}}</text>
|
||||
</view>
|
||||
<scroll-view v-if="!isCloudDataList" :scroll-x="true">
|
||||
<view class="selected-node-list">
|
||||
<template v-for="(item, index) in selectedNodes">
|
||||
<text class="selected-node-item" :class="{'selected-node-item-active':index==selectedIndex}"
|
||||
@click="onTabSelect(index)">
|
||||
{{item[mappingTextName]}}
|
||||
</text>
|
||||
</template>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<list-view class="list-view" :scroll-y="true">
|
||||
<list-item class="list-item" v-for="(item, _) in currentDataList" @click="onNodeClick(item)">
|
||||
<text class="item-text" :class="{'item-text-disabled': item['disable']}">{{item[mappingTextName]}}</text>
|
||||
<text class="check" v-if="item[mappingValueName] == selectedNodes[selectedIndex][mappingValueName]"></text>
|
||||
</list-item>
|
||||
</list-view>
|
||||
<view class="loading-cover" v-if="loading">
|
||||
<slot name="pickerview-loading" :loading="loading"></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { dataPicker } from "./uni-data-picker.uts"
|
||||
|
||||
/**
|
||||
* DataPickerview
|
||||
* @description uni-data-pickerview
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=3796
|
||||
* @property {Array} localdata 本地数据,参考
|
||||
* @property {Boolean} step-searh = [true|false] 是否分布查询
|
||||
* @value true 启用分布查询,仅查询当前选中节点
|
||||
* @value false 关闭分布查询,一次查询出所有数据
|
||||
* @property {String|DBFieldString} self-field 分布查询当前字段名称
|
||||
* @property {String|DBFieldString} parent-field 分布查询父字段名称
|
||||
* @property {String|DBCollectionString} collection 表名
|
||||
* @property {String|DBFieldString} field 查询字段,多个字段用 `,` 分割
|
||||
* @property {String} orderby 排序字段及正序倒叙设置
|
||||
* @property {String|JQLString} where 查询条件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniDataPickerView',
|
||||
emits: ['nodeclick', 'change', 'update:modelValue'],
|
||||
mixins: [dataPicker],
|
||||
props: {
|
||||
ellipsis: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.loadData()
|
||||
},
|
||||
methods: {
|
||||
onFinish() {
|
||||
this.$emit('change', this.getChangeNodes())
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import url("uni-data-pickerview.css");
|
||||
</style>
|
|
@ -0,0 +1,323 @@
|
|||
<template>
|
||||
<view class="uni-data-pickerview">
|
||||
<scroll-view v-if="!isCloudDataList" class="selected-area" scroll-x="true">
|
||||
<view class="selected-list">
|
||||
<view
|
||||
class="selected-item"
|
||||
v-for="(item,index) in selected"
|
||||
:key="index"
|
||||
:class="{
|
||||
'selected-item-active':index == selectedIndex
|
||||
}"
|
||||
@click="handleSelect(index)"
|
||||
>
|
||||
<text>{{item.text || ''}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="tab-c">
|
||||
<scroll-view class="list" :scroll-y="true">
|
||||
<view class="item" :class="{'is-disabled': !!item.disable}" v-for="(item, j) in dataList[selectedIndex]" :key="j"
|
||||
@click="handleNodeClick(item, selectedIndex, j)">
|
||||
<text class="item-text">{{item[map.text]}}</text>
|
||||
<view class="check" v-if="selected.length > selectedIndex && item[map.value] == selected[selectedIndex].value"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view class="loading-cover" v-if="loading">
|
||||
<uni-load-more class="load-more" :contentText="loadMore" status="loading"></uni-load-more>
|
||||
</view>
|
||||
<view class="error-message" v-if="errorMessage">
|
||||
<text class="error-text">{{errorMessage}}</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import dataPicker from "./uni-data-picker.js"
|
||||
|
||||
/**
|
||||
* DataPickerview
|
||||
* @description uni-data-pickerview
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=3796
|
||||
* @property {Array} localdata 本地数据,参考
|
||||
* @property {Boolean} step-searh = [true|false] 是否分布查询
|
||||
* @value true 启用分布查询,仅查询当前选中节点
|
||||
* @value false 关闭分布查询,一次查询出所有数据
|
||||
* @property {String|DBFieldString} self-field 分布查询当前字段名称
|
||||
* @property {String|DBFieldString} parent-field 分布查询父字段名称
|
||||
* @property {String|DBCollectionString} collection 表名
|
||||
* @property {String|DBFieldString} field 查询字段,多个字段用 `,` 分割
|
||||
* @property {String} orderby 排序字段及正序倒叙设置
|
||||
* @property {String|JQLString} where 查询条件
|
||||
*/
|
||||
export default {
|
||||
name: 'UniDataPickerView',
|
||||
emits: ['nodeclick', 'change', 'datachange', 'update:modelValue'],
|
||||
mixins: [dataPicker],
|
||||
props: {
|
||||
managedMode: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
ellipsis: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
if (!this.managedMode) {
|
||||
this.$nextTick(() => {
|
||||
this.loadData();
|
||||
})
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onPropsChange() {
|
||||
this._treeData = [];
|
||||
this.selectedIndex = 0;
|
||||
this.$nextTick(() => {
|
||||
this.loadData();
|
||||
})
|
||||
},
|
||||
handleSelect(index) {
|
||||
this.selectedIndex = index;
|
||||
},
|
||||
handleNodeClick(item, i, j) {
|
||||
if (item.disable) {
|
||||
return;
|
||||
}
|
||||
|
||||
const node = this.dataList[i][j];
|
||||
const text = node[this.map.text];
|
||||
const value = node[this.map.value];
|
||||
|
||||
if (i < this.selected.length - 1) {
|
||||
this.selected.splice(i, this.selected.length - i)
|
||||
this.selected.push({
|
||||
text,
|
||||
value
|
||||
})
|
||||
} else if (i === this.selected.length - 1) {
|
||||
this.selected.splice(i, 1, {
|
||||
text,
|
||||
value
|
||||
})
|
||||
}
|
||||
|
||||
if (node.isleaf) {
|
||||
this.onSelectedChange(node, node.isleaf)
|
||||
return
|
||||
}
|
||||
|
||||
const {
|
||||
isleaf,
|
||||
hasNodes
|
||||
} = this._updateBindData()
|
||||
|
||||
// 本地数据
|
||||
if (this.isLocalData) {
|
||||
this.onSelectedChange(node, (!hasNodes || isleaf))
|
||||
} else if (this.isCloudDataList) { // Cloud 数据 (单列)
|
||||
this.onSelectedChange(node, true)
|
||||
} else if (this.isCloudDataTree) { // Cloud 数据 (树形)
|
||||
if (isleaf) {
|
||||
this.onSelectedChange(node, node.isleaf)
|
||||
} else if (!hasNodes) { // 请求一次服务器以确定是否为叶子节点
|
||||
this.loadCloudDataNode((data) => {
|
||||
if (!data.length) {
|
||||
node.isleaf = true
|
||||
} else {
|
||||
this._treeData.push(...data)
|
||||
this._updateBindData(node)
|
||||
}
|
||||
this.onSelectedChange(node, node.isleaf)
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
updateData(data) {
|
||||
this._treeData = data.treeData
|
||||
this.selected = data.selected
|
||||
if (!this._treeData.length) {
|
||||
this.loadData()
|
||||
} else {
|
||||
//this.selected = data.selected
|
||||
this._updateBindData()
|
||||
}
|
||||
},
|
||||
onDataChange() {
|
||||
this.$emit('datachange');
|
||||
},
|
||||
onSelectedChange(node, isleaf) {
|
||||
if (isleaf) {
|
||||
this._dispatchEvent()
|
||||
}
|
||||
|
||||
if (node) {
|
||||
this.$emit('nodeclick', node)
|
||||
}
|
||||
},
|
||||
_dispatchEvent() {
|
||||
this.$emit('change', this.selected.slice(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
$uni-primary: #007aff !default;
|
||||
|
||||
.uni-data-pickerview {
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.error-text {
|
||||
color: #DD524D;
|
||||
}
|
||||
|
||||
.loading-cover {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(255, 255, 255, .5);
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
z-index: 1001;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
/* #ifndef APP-NVUE */
|
||||
margin: auto;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #fff;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 15px;
|
||||
opacity: .9;
|
||||
z-index: 102;
|
||||
}
|
||||
|
||||
/* #ifdef APP-NVUE */
|
||||
.selected-area {
|
||||
width: 750rpx;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.selected-list {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
flex-wrap: nowrap;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
padding: 0 5px;
|
||||
border-bottom: 1px solid #f8f8f8;
|
||||
}
|
||||
|
||||
.selected-item {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
padding: 12px 0;
|
||||
text-align: center;
|
||||
/* #ifndef APP-NVUE */
|
||||
white-space: nowrap;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.selected-item-text-overflow {
|
||||
width: 168px;
|
||||
/* fix nvue */
|
||||
overflow: hidden;
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 6em;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
-o-text-overflow: ellipsis;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.selected-item-active {
|
||||
border-bottom: 2px solid $uni-primary;
|
||||
}
|
||||
|
||||
.selected-item-text {
|
||||
color: $uni-primary;
|
||||
}
|
||||
|
||||
.tab-c {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.list {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.item {
|
||||
padding: 12px 15px;
|
||||
/* border-bottom: 1px solid #f0f0f0; */
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.is-disabled {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.item-text {
|
||||
/* flex: 1; */
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.item-text-overflow {
|
||||
width: 280px;
|
||||
/* fix nvue */
|
||||
overflow: hidden;
|
||||
/* #ifndef APP-NVUE */
|
||||
width: 20em;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
-o-text-overflow: ellipsis;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
.check {
|
||||
margin-right: 5px;
|
||||
border: 2px solid $uni-primary;
|
||||
border-left: 0;
|
||||
border-top: 0;
|
||||
height: 12px;
|
||||
width: 6px;
|
||||
transform-origin: center;
|
||||
/* #ifndef APP-NVUE */
|
||||
transition: all 0.3s;
|
||||
/* #endif */
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"id": "uni-data-picker",
|
||||
"displayName": "uni-data-picker 数据驱动的picker选择器",
|
||||
"version": "2.0.0",
|
||||
"description": "单列、多列级联选择器,常用于省市区城市选择、公司部门选择、多级分类等场景",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
"uniui",
|
||||
"picker",
|
||||
"级联",
|
||||
"省市区",
|
||||
""
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": ""
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
"uni-load-more",
|
||||
"uni-icons",
|
||||
"uni-scss"
|
||||
],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y",
|
||||
"app-uvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y",
|
||||
"京东": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
## DataPicker 级联选择
|
||||
> **组件名:uni-data-picker**
|
||||
> 代码块: `uDataPicker`
|
||||
> 关联组件:`uni-data-pickerview`、`uni-load-more`。
|
||||
|
||||
|
||||
`<uni-data-picker>` 是一个选择类[datacom组件](https://uniapp.dcloud.net.cn/component/datacom)。
|
||||
|
||||
支持单列、和多列级联选择。列数没有限制,如果屏幕显示不全,顶部tab区域会左右滚动。
|
||||
|
||||
候选数据支持一次性加载完毕,也支持懒加载,比如示例图中,选择了“北京”后,动态加载北京的区县数据。
|
||||
|
||||
`<uni-data-picker>` 组件尤其适用于地址选择、分类选择等选择类。
|
||||
|
||||
`<uni-data-picker>` 支持本地数据、云端静态数据(json),uniCloud云数据库数据。
|
||||
|
||||
`<uni-data-picker>` 可以通过JQL直连uniCloud云数据库,配套[DB Schema](https://uniapp.dcloud.net.cn/uniCloud/schema),可在schema2code中自动生成前端页面,还支持服务器端校验。
|
||||
|
||||
在uniCloud数据表中新建表“uni-id-address”和“opendb-city-china”,这2个表的schema自带foreignKey关联。在“uni-id-address”表的表结构页面使用schema2code生成前端页面,会自动生成地址管理的维护页面,自动从“opendb-city-china”表包含的中国所有省市区信息里选择地址。
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-data-picker)
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
|
@ -0,0 +1,94 @@
|
|||
## 1.4.10(2023-11-03)
|
||||
- 优化 labelWidth 描述错误
|
||||
## 1.4.9(2023-02-10)
|
||||
- 修复 required 参数无法动态绑定
|
||||
## 1.4.8(2022-08-23)
|
||||
- 优化 根据 rules 自动添加 required 的问题
|
||||
## 1.4.7(2022-08-22)
|
||||
- 修复 item 未设置 require 属性,rules 设置 require 后,星号也显示的 bug,详见:[https://ask.dcloud.net.cn/question/151540](https://ask.dcloud.net.cn/question/151540)
|
||||
## 1.4.6(2022-07-13)
|
||||
- 修复 model 需要校验的值没有声明对应字段时,导致第一次不触发校验的bug
|
||||
## 1.4.5(2022-07-05)
|
||||
- 新增 更多表单示例
|
||||
- 优化 子表单组件过期提示的问题
|
||||
- 优化 子表单组件uni-datetime-picker、uni-data-select、uni-data-picker的显示样式
|
||||
## 1.4.4(2022-07-04)
|
||||
- 更新 删除组件日志
|
||||
## 1.4.3(2022-07-04)
|
||||
- 修复 由 1.4.0 引发的 label 插槽不生效的bug
|
||||
## 1.4.2(2022-07-04)
|
||||
- 修复 子组件找不到 setValue 报错的bug
|
||||
## 1.4.1(2022-07-04)
|
||||
- 修复 uni-data-picker 在 uni-forms-item 中报错的bug
|
||||
- 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug
|
||||
## 1.4.0(2022-06-30)
|
||||
- 【重要】组件逻辑重构,部分用法用旧版本不兼容,请注意兼容问题
|
||||
- 【重要】组件使用 Provide/Inject 方式注入依赖,提供了自定义表单组件调用 uni-forms 校验表单的能力
|
||||
- 新增 model 属性,等同于原 value/modelValue 属性,旧属性即将废弃
|
||||
- 新增 validateTrigger 属性的 blur 值,仅 uni-easyinput 生效
|
||||
- 新增 onFieldChange 方法,可以对子表单进行校验,可替代binddata方法
|
||||
- 新增 子表单的 setRules 方法,配合自定义校验函数使用
|
||||
- 新增 uni-forms-item 的 setRules 方法,配置动态表单使用可动态更新校验规则
|
||||
- 优化 动态表单校验方式,废弃拼接name的方式
|
||||
## 1.3.3(2022-06-22)
|
||||
- 修复 表单校验顺序无序问题
|
||||
## 1.3.2(2021-12-09)
|
||||
-
|
||||
## 1.3.1(2021-11-19)
|
||||
- 修复 label 插槽不生效的bug
|
||||
## 1.3.0(2021-11-19)
|
||||
- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
|
||||
- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-forms](https://uniapp.dcloud.io/component/uniui/uni-forms)
|
||||
## 1.2.7(2021-08-13)
|
||||
- 修复 没有添加校验规则的字段依然报错的Bug
|
||||
## 1.2.6(2021-08-11)
|
||||
- 修复 重置表单错误信息无法清除的问题
|
||||
## 1.2.5(2021-08-11)
|
||||
- 优化 组件文档
|
||||
## 1.2.4(2021-08-11)
|
||||
- 修复 表单验证只生效一次的问题
|
||||
## 1.2.3(2021-07-30)
|
||||
- 优化 vue3下事件警告的问题
|
||||
## 1.2.2(2021-07-26)
|
||||
- 修复 vue2 下条件编译导致destroyed生命周期失效的Bug
|
||||
- 修复 1.2.1 引起的示例在小程序平台报错的Bug
|
||||
## 1.2.1(2021-07-22)
|
||||
- 修复 动态校验表单,默认值为空的情况下校验失效的Bug
|
||||
- 修复 不指定name属性时,运行报错的Bug
|
||||
- 优化 label默认宽度从65调整至70,使required为true且四字时不换行
|
||||
- 优化 组件示例,新增动态校验示例代码
|
||||
- 优化 组件文档,使用方式更清晰
|
||||
## 1.2.0(2021-07-13)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 1.1.2(2021-06-25)
|
||||
- 修复 pattern 属性在微信小程序平台无效的问题
|
||||
## 1.1.1(2021-06-22)
|
||||
- 修复 validate-trigger属性为submit且err-show-type属性为toast时不能弹出的Bug
|
||||
## 1.1.0(2021-06-22)
|
||||
- 修复 只写setRules方法而导致校验不生效的Bug
|
||||
- 修复 由上个办法引发的错误提示文字错位的Bug
|
||||
## 1.0.48(2021-06-21)
|
||||
- 修复 不设置 label 属性 ,无法设置label插槽的问题
|
||||
## 1.0.47(2021-06-21)
|
||||
- 修复 不设置label属性,label-width属性不生效的bug
|
||||
- 修复 setRules 方法与rules属性冲突的问题
|
||||
## 1.0.46(2021-06-04)
|
||||
- 修复 动态删减数据导致报错的问题
|
||||
## 1.0.45(2021-06-04)
|
||||
- 新增 modelValue 属性 ,value 即将废弃
|
||||
## 1.0.44(2021-06-02)
|
||||
- 新增 uni-forms-item 可以设置单独的 rules
|
||||
- 新增 validate 事件增加 keepitem 参数,可以选择那些字段不过滤
|
||||
- 优化 submit 事件重命名为 validate
|
||||
## 1.0.43(2021-05-12)
|
||||
- 新增 组件示例地址
|
||||
## 1.0.42(2021-04-30)
|
||||
- 修复 自定义检验器失效的问题
|
||||
## 1.0.41(2021-03-05)
|
||||
- 更新 校验器
|
||||
- 修复 表单规则设置类型为 number 的情况下,值为0校验失败的Bug
|
||||
## 1.0.40(2021-03-04)
|
||||
- 修复 动态显示uni-forms-item的情况下,submit 方法获取值错误的Bug
|
||||
## 1.0.39(2021-02-05)
|
||||
- 调整为uni_modules目录规范
|
||||
- 修复 校验器传入 int 等类型 ,返回String类型的Bug
|
|
@ -0,0 +1,627 @@
|
|||
<template>
|
||||
<view class="uni-forms-item"
|
||||
:class="['is-direction-' + localLabelPos ,border?'uni-forms-item--border':'' ,border && isFirstBorder?'is-first-border':'']">
|
||||
<slot name="label">
|
||||
<view class="uni-forms-item__label" :class="{'no-label':!label && !required}"
|
||||
:style="{width:localLabelWidth,justifyContent: localLabelAlign}">
|
||||
<text v-if="required" class="is-required">*</text>
|
||||
<text>{{label}}</text>
|
||||
</view>
|
||||
</slot>
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<view class="uni-forms-item__content">
|
||||
<slot></slot>
|
||||
<view class="uni-forms-item__error" :class="{'msg--active':msg}">
|
||||
<text>{{msg}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<view class="uni-forms-item__nuve-content">
|
||||
<view class="uni-forms-item__content">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view class="uni-forms-item__error" :class="{'msg--active':msg}">
|
||||
<text class="error-text">{{msg}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* uni-fomrs-item 表单子组件
|
||||
* @description uni-fomrs-item 表单子组件,提供了基础布局已经校验能力
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=2773
|
||||
* @property {Boolean} required 是否必填,左边显示红色"*"号
|
||||
* @property {String } label 输入框左边的文字提示
|
||||
* @property {Number } labelWidth label的宽度,单位px(默认70)
|
||||
* @property {String } labelAlign = [left|center|right] label的文字对齐方式(默认left)
|
||||
* @value left label 左侧显示
|
||||
* @value center label 居中
|
||||
* @value right label 右侧对齐
|
||||
* @property {String } errorMessage 显示的错误提示内容,如果为空字符串或者false,则不显示错误信息
|
||||
* @property {String } name 表单域的属性名,在使用校验规则时必填
|
||||
* @property {String } leftIcon 【1.4.0废弃】label左边的图标,限 uni-ui 的图标名称
|
||||
* @property {String } iconColor 【1.4.0废弃】左边通过icon配置的图标的颜色(默认#606266)
|
||||
* @property {String} validateTrigger = [bind|submit|blur] 【1.4.0废弃】校验触发器方式 默认 submit
|
||||
* @value bind 发生变化时触发
|
||||
* @value submit 提交时触发
|
||||
* @value blur 失去焦点触发
|
||||
* @property {String } labelPosition = [top|left] 【1.4.0废弃】label的文字的位置(默认left)
|
||||
* @value top 顶部显示 label
|
||||
* @value left 左侧显示 label
|
||||
*/
|
||||
|
||||
export default {
|
||||
name: 'uniFormsItem',
|
||||
options: {
|
||||
virtualHost: true
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
uniFormItem: this
|
||||
}
|
||||
},
|
||||
inject: {
|
||||
form: {
|
||||
from: 'uniForm',
|
||||
default: null
|
||||
},
|
||||
},
|
||||
props: {
|
||||
// 表单校验规则
|
||||
rules: {
|
||||
type: Array,
|
||||
default () {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
// 表单域的属性名,在使用校验规则时必填
|
||||
name: {
|
||||
type: [String, Array],
|
||||
default: ''
|
||||
},
|
||||
required: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
label: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// label的宽度
|
||||
labelWidth: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// label 居中方式,默认 left 取值 left/center/right
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 强制显示错误信息
|
||||
errorMessage: {
|
||||
type: [String, Boolean],
|
||||
default: ''
|
||||
},
|
||||
// 1.4.0 弃用,统一使用 form 的校验时机
|
||||
// validateTrigger: {
|
||||
// type: String,
|
||||
// default: ''
|
||||
// },
|
||||
// 1.4.0 弃用,统一使用 form 的label 位置
|
||||
// labelPosition: {
|
||||
// type: String,
|
||||
// default: ''
|
||||
// },
|
||||
// 1.4.0 以下属性已经废弃,请使用 #label 插槽代替
|
||||
leftIcon: String,
|
||||
iconColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
errMsg: '',
|
||||
userRules: null,
|
||||
localLabelAlign: 'left',
|
||||
localLabelWidth: '70px',
|
||||
localLabelPos: 'left',
|
||||
border: false,
|
||||
isFirstBorder: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 处理错误信息
|
||||
msg() {
|
||||
return this.errorMessage || this.errMsg;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 规则发生变化通知子组件更新
|
||||
'form.formRules'(val) {
|
||||
// TODO 处理头条vue3 watch不生效的问题
|
||||
// #ifndef MP-TOUTIAO
|
||||
this.init()
|
||||
// #endif
|
||||
},
|
||||
'form.labelWidth'(val) {
|
||||
// 宽度
|
||||
this.localLabelWidth = this._labelWidthUnit(val)
|
||||
|
||||
},
|
||||
'form.labelPosition'(val) {
|
||||
// 标签位置
|
||||
this.localLabelPos = this._labelPosition()
|
||||
},
|
||||
'form.labelAlign'(val) {
|
||||
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.init(true)
|
||||
if (this.name && this.form) {
|
||||
// TODO 处理头条vue3 watch不生效的问题
|
||||
// #ifdef MP-TOUTIAO
|
||||
this.$watch('form.formRules', () => {
|
||||
this.init()
|
||||
})
|
||||
// #endif
|
||||
|
||||
// 监听变化
|
||||
this.$watch(
|
||||
() => {
|
||||
const val = this.form._getDataValue(this.name, this.form.localData)
|
||||
return val
|
||||
},
|
||||
(value, oldVal) => {
|
||||
const isEqual = this.form._isEqual(value, oldVal)
|
||||
// 简单判断前后值的变化,只有发生变化才会发生校验
|
||||
// TODO 如果 oldVal = undefined ,那么大概率是源数据里没有值导致 ,这个情况不哦校验 ,可能不严谨 ,需要在做观察
|
||||
// fix by mehaotian 暂时取消 && oldVal !== undefined ,如果formData 中不存在,可能会不校验
|
||||
if (!isEqual) {
|
||||
const val = this.itemSetValue(value)
|
||||
this.onFieldChange(val, false)
|
||||
}
|
||||
}, {
|
||||
immediate: false
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
},
|
||||
// #ifndef VUE3
|
||||
destroyed() {
|
||||
if (this.__isUnmounted) return
|
||||
this.unInit()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
unmounted() {
|
||||
this.__isUnmounted = true
|
||||
this.unInit()
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
/**
|
||||
* 外部调用方法
|
||||
* 设置规则 ,主要用于小程序自定义检验规则
|
||||
* @param {Array} rules 规则源数据
|
||||
*/
|
||||
setRules(rules = null) {
|
||||
this.userRules = rules
|
||||
this.init(false)
|
||||
},
|
||||
// 兼容老版本表单组件
|
||||
setValue() {
|
||||
// console.log('setValue 方法已经弃用,请使用最新版本的 uni-forms 表单组件以及其他关联组件。');
|
||||
},
|
||||
/**
|
||||
* 外部调用方法
|
||||
* 校验数据
|
||||
* @param {any} value 需要校验的数据
|
||||
* @param {boolean} 是否立即校验
|
||||
* @return {Array|null} 校验内容
|
||||
*/
|
||||
async onFieldChange(value, formtrigger = true) {
|
||||
const {
|
||||
formData,
|
||||
localData,
|
||||
errShowType,
|
||||
validateCheck,
|
||||
validateTrigger,
|
||||
_isRequiredField,
|
||||
_realName
|
||||
} = this.form
|
||||
const name = _realName(this.name)
|
||||
if (!value) {
|
||||
value = this.form.formData[name]
|
||||
}
|
||||
// fixd by mehaotian 不在校验前清空信息,解决闪屏的问题
|
||||
// this.errMsg = '';
|
||||
|
||||
// fix by mehaotian 解决没有检验规则的情况下,抛出错误的问题
|
||||
const ruleLen = this.itemRules.rules && this.itemRules.rules.length
|
||||
if (!this.validator || !ruleLen || ruleLen === 0) return;
|
||||
|
||||
// 检验时机
|
||||
// let trigger = this.isTrigger(this.itemRules.validateTrigger, this.validateTrigger, validateTrigger);
|
||||
const isRequiredField = _isRequiredField(this.itemRules.rules || []);
|
||||
let result = null;
|
||||
// 只有等于 bind 时 ,才能开启时实校验
|
||||
if (validateTrigger === 'bind' || formtrigger) {
|
||||
// 校验当前表单项
|
||||
result = await this.validator.validateUpdate({
|
||||
[name]: value
|
||||
},
|
||||
formData
|
||||
);
|
||||
|
||||
// 判断是否必填,非必填,不填不校验,填写才校验 ,暂时只处理 undefined 和空的情况
|
||||
if (!isRequiredField && (value === undefined || value === '')) {
|
||||
result = null;
|
||||
}
|
||||
|
||||
// 判断错误信息显示类型
|
||||
if (result && result.errorMessage) {
|
||||
if (errShowType === 'undertext') {
|
||||
// 获取错误信息
|
||||
this.errMsg = !result ? '' : result.errorMessage;
|
||||
}
|
||||
if (errShowType === 'toast') {
|
||||
uni.showToast({
|
||||
title: result.errorMessage || '校验错误',
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
if (errShowType === 'modal') {
|
||||
uni.showModal({
|
||||
title: '提示',
|
||||
content: result.errorMessage || '校验错误'
|
||||
});
|
||||
}
|
||||
} else {
|
||||
this.errMsg = ''
|
||||
}
|
||||
// 通知 form 组件更新事件
|
||||
validateCheck(result ? result : null)
|
||||
} else {
|
||||
this.errMsg = ''
|
||||
}
|
||||
return result ? result : null;
|
||||
},
|
||||
/**
|
||||
* 初始组件数据
|
||||
*/
|
||||
init(type = false) {
|
||||
const {
|
||||
validator,
|
||||
formRules,
|
||||
childrens,
|
||||
formData,
|
||||
localData,
|
||||
_realName,
|
||||
labelWidth,
|
||||
_getDataValue,
|
||||
_setDataValue
|
||||
} = this.form || {}
|
||||
// 对齐方式
|
||||
this.localLabelAlign = this._justifyContent()
|
||||
// 宽度
|
||||
this.localLabelWidth = this._labelWidthUnit(labelWidth)
|
||||
// 标签位置
|
||||
this.localLabelPos = this._labelPosition()
|
||||
// 将需要校验的子组件加入form 队列
|
||||
this.form && type && childrens.push(this)
|
||||
|
||||
if (!validator || !formRules) return
|
||||
// 判断第一个 item
|
||||
if (!this.form.isFirstBorder) {
|
||||
this.form.isFirstBorder = true;
|
||||
this.isFirstBorder = true;
|
||||
}
|
||||
|
||||
// 判断 group 里的第一个 item
|
||||
if (this.group) {
|
||||
if (!this.group.isFirstBorder) {
|
||||
this.group.isFirstBorder = true;
|
||||
this.isFirstBorder = true;
|
||||
}
|
||||
}
|
||||
this.border = this.form.border;
|
||||
// 获取子域的真实名称
|
||||
const name = _realName(this.name)
|
||||
const itemRule = this.userRules || this.rules
|
||||
if (typeof formRules === 'object' && itemRule) {
|
||||
// 子规则替换父规则
|
||||
formRules[name] = {
|
||||
rules: itemRule
|
||||
}
|
||||
validator.updateSchema(formRules);
|
||||
}
|
||||
// 注册校验规则
|
||||
const itemRules = formRules[name] || {}
|
||||
this.itemRules = itemRules
|
||||
// 注册校验函数
|
||||
this.validator = validator
|
||||
// 默认值赋予
|
||||
this.itemSetValue(_getDataValue(this.name, localData))
|
||||
},
|
||||
unInit() {
|
||||
if (this.form) {
|
||||
const {
|
||||
childrens,
|
||||
formData,
|
||||
_realName
|
||||
} = this.form
|
||||
childrens.forEach((item, index) => {
|
||||
if (item === this) {
|
||||
this.form.childrens.splice(index, 1)
|
||||
delete formData[_realName(item.name)]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
// 设置item 的值
|
||||
itemSetValue(value) {
|
||||
const name = this.form._realName(this.name)
|
||||
const rules = this.itemRules.rules || []
|
||||
const val = this.form._getValue(name, value, rules)
|
||||
this.form._setDataValue(name, this.form.formData, val)
|
||||
return val
|
||||
},
|
||||
|
||||
/**
|
||||
* 移除该表单项的校验结果
|
||||
*/
|
||||
clearValidate() {
|
||||
this.errMsg = '';
|
||||
},
|
||||
|
||||
// 是否显示星号
|
||||
_isRequired() {
|
||||
// TODO 不根据规则显示 星号,考虑后续兼容
|
||||
// if (this.form) {
|
||||
// if (this.form._isRequiredField(this.itemRules.rules || []) && this.required) {
|
||||
// return true
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
return this.required
|
||||
},
|
||||
|
||||
// 处理对齐方式
|
||||
_justifyContent() {
|
||||
if (this.form) {
|
||||
const {
|
||||
labelAlign
|
||||
} = this.form
|
||||
let labelAli = this.labelAlign ? this.labelAlign : labelAlign;
|
||||
if (labelAli === 'left') return 'flex-start';
|
||||
if (labelAli === 'center') return 'center';
|
||||
if (labelAli === 'right') return 'flex-end';
|
||||
}
|
||||
return 'flex-start';
|
||||
},
|
||||
// 处理 label宽度单位 ,继承父元素的值
|
||||
_labelWidthUnit(labelWidth) {
|
||||
|
||||
// if (this.form) {
|
||||
// const {
|
||||
// labelWidth
|
||||
// } = this.form
|
||||
return this.num2px(this.labelWidth ? this.labelWidth : (labelWidth || (this.label ? 70 : 'auto')))
|
||||
// }
|
||||
// return '70px'
|
||||
},
|
||||
// 处理 label 位置
|
||||
_labelPosition() {
|
||||
if (this.form) return this.form.labelPosition || 'left'
|
||||
return 'left'
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 触发时机
|
||||
* @param {Object} rule 当前规则内时机
|
||||
* @param {Object} itemRlue 当前组件时机
|
||||
* @param {Object} parentRule 父组件时机
|
||||
*/
|
||||
isTrigger(rule, itemRlue, parentRule) {
|
||||
// bind submit
|
||||
if (rule === 'submit' || !rule) {
|
||||
if (rule === undefined) {
|
||||
if (itemRlue !== 'bind') {
|
||||
if (!itemRlue) {
|
||||
return parentRule === '' ? 'bind' : 'submit';
|
||||
}
|
||||
return 'submit';
|
||||
}
|
||||
return 'bind';
|
||||
}
|
||||
return 'submit';
|
||||
}
|
||||
return 'bind';
|
||||
},
|
||||
num2px(num) {
|
||||
if (typeof num === 'number') {
|
||||
return `${num}px`
|
||||
}
|
||||
return num
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.uni-forms-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
/* #ifdef APP-NVUE */
|
||||
// 在 nvue 中,使用 margin-bottom error 信息会被隐藏
|
||||
padding-bottom: 22px;
|
||||
/* #endif */
|
||||
/* #ifndef APP-NVUE */
|
||||
margin-bottom: 22px;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
|
||||
&__label {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
height: 36px;
|
||||
padding: 0 12px 0 0;
|
||||
/* #ifndef APP-NVUE */
|
||||
vertical-align: middle;
|
||||
flex-shrink: 0;
|
||||
/* #endif */
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
|
||||
/* #endif */
|
||||
&.no-label {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__content {
|
||||
/* #ifndef MP-TOUTIAO */
|
||||
// display: flex;
|
||||
// align-items: center;
|
||||
/* #endif */
|
||||
position: relative;
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
/* #ifndef APP-NVUE */
|
||||
box-sizing: border-box;
|
||||
/* #endif */
|
||||
flex-direction: row;
|
||||
|
||||
/* #ifndef APP || H5 || MP-WEIXIN || APP-NVUE */
|
||||
// TODO 因为小程序平台会多一层标签节点 ,所以需要在多余节点继承当前样式
|
||||
&>uni-easyinput,
|
||||
&>uni-data-picker {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
}
|
||||
|
||||
& .uni-forms-item__nuve-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&__error {
|
||||
color: #f56c6c;
|
||||
font-size: 12px;
|
||||
line-height: 1;
|
||||
padding-top: 4px;
|
||||
position: absolute;
|
||||
/* #ifndef APP-NVUE */
|
||||
top: 100%;
|
||||
left: 0;
|
||||
transition: transform 0.3s;
|
||||
transform: translateY(-100%);
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
bottom: 5px;
|
||||
/* #endif */
|
||||
|
||||
opacity: 0;
|
||||
|
||||
.error-text {
|
||||
// 只有 nvue 下这个样式才生效
|
||||
color: #f56c6c;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
&.msg--active {
|
||||
opacity: 1;
|
||||
transform: translateY(0%);
|
||||
}
|
||||
}
|
||||
|
||||
// 位置修饰样式
|
||||
&.is-direction-left {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
&.is-direction-top {
|
||||
flex-direction: column;
|
||||
|
||||
.uni-forms-item__label {
|
||||
padding: 0 0 8px;
|
||||
line-height: 1.5715;
|
||||
text-align: left;
|
||||
/* #ifndef APP-NVUE */
|
||||
white-space: initial;
|
||||
/* #endif */
|
||||
}
|
||||
}
|
||||
|
||||
.is-required {
|
||||
// color: $uni-color-error;
|
||||
color: #dd524d;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.uni-forms-item--border {
|
||||
margin-bottom: 0;
|
||||
padding: 10px 0;
|
||||
// padding-bottom: 0;
|
||||
border-top: 1px #eee solid;
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uni-forms-item__content {
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
align-items: flex-start;
|
||||
|
||||
.uni-forms-item__error {
|
||||
position: relative;
|
||||
top: 5px;
|
||||
left: 0;
|
||||
padding-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
/* #ifdef APP-NVUE */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.uni-forms-item__error {
|
||||
position: relative;
|
||||
top: 0px;
|
||||
left: 0;
|
||||
padding-top: 0;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
/* #endif */
|
||||
|
||||
}
|
||||
|
||||
.is-first-border {
|
||||
/* #ifndef APP-NVUE */
|
||||
border: none;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
border-width: 0;
|
||||
/* #endif */
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,397 @@
|
|||
<template>
|
||||
<view class="uni-forms">
|
||||
<form>
|
||||
<slot></slot>
|
||||
</form>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Validator from './validate.js';
|
||||
import {
|
||||
deepCopy,
|
||||
getValue,
|
||||
isRequiredField,
|
||||
setDataValue,
|
||||
getDataValue,
|
||||
realName,
|
||||
isRealName,
|
||||
rawData,
|
||||
isEqual
|
||||
} from './utils.js'
|
||||
|
||||
// #ifndef VUE3
|
||||
// 后续会慢慢废弃这个方法
|
||||
import Vue from 'vue';
|
||||
Vue.prototype.binddata = function(name, value, formName) {
|
||||
if (formName) {
|
||||
this.$refs[formName].setValue(name, value);
|
||||
} else {
|
||||
let formVm;
|
||||
for (let i in this.$refs) {
|
||||
const vm = this.$refs[i];
|
||||
if (vm && vm.$options && vm.$options.name === 'uniForms') {
|
||||
formVm = vm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
|
||||
formVm.setValue(name, value);
|
||||
}
|
||||
};
|
||||
// #endif
|
||||
/**
|
||||
* Forms 表单
|
||||
* @description 由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=2773
|
||||
* @property {Object} rules 表单校验规则
|
||||
* @property {String} validateTrigger = [bind|submit|blur] 校验触发器方式 默认 submit
|
||||
* @value bind 发生变化时触发
|
||||
* @value submit 提交时触发
|
||||
* @value blur 失去焦点时触发
|
||||
* @property {String} labelPosition = [top|left] label 位置 默认 left
|
||||
* @value top 顶部显示 label
|
||||
* @value left 左侧显示 label
|
||||
* @property {String} labelWidth label 宽度,默认 70px
|
||||
* @property {String} labelAlign = [left|center|right] label 居中方式 默认 left
|
||||
* @value left label 左侧显示
|
||||
* @value center label 居中
|
||||
* @value right label 右侧对齐
|
||||
* @property {String} errShowType = [undertext|toast|modal] 校验错误信息提示方式
|
||||
* @value undertext 错误信息在底部显示
|
||||
* @value toast 错误信息toast显示
|
||||
* @value modal 错误信息modal显示
|
||||
* @event {Function} submit 提交时触发
|
||||
* @event {Function} validate 校验结果发生变化触发
|
||||
*/
|
||||
export default {
|
||||
name: 'uniForms',
|
||||
emits: ['validate', 'submit'],
|
||||
options: {
|
||||
virtualHost: true
|
||||
},
|
||||
props: {
|
||||
// 即将弃用
|
||||
value: {
|
||||
type: Object,
|
||||
default () {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
// vue3 替换 value 属性
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default () {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
// 1.4.0 开始将不支持 v-model ,且废弃 value 和 modelValue
|
||||
model: {
|
||||
type: Object,
|
||||
default () {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
// 表单校验规则
|
||||
rules: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {};
|
||||
}
|
||||
},
|
||||
//校验错误信息提示方式 默认 undertext 取值 [undertext|toast|modal]
|
||||
errShowType: {
|
||||
type: String,
|
||||
default: 'undertext'
|
||||
},
|
||||
// 校验触发器方式 默认 bind 取值 [bind|submit]
|
||||
validateTrigger: {
|
||||
type: String,
|
||||
default: 'submit'
|
||||
},
|
||||
// label 位置,默认 left 取值 top/left
|
||||
labelPosition: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
// label 宽度
|
||||
labelWidth: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// label 居中方式,默认 left 取值 left/center/right
|
||||
labelAlign: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
border: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
uniForm: this
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 表单本地值的记录,不应该与传如的值进行关联
|
||||
formData: {},
|
||||
formRules: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// 计算数据源变化的
|
||||
localData() {
|
||||
const localVal = this.model || this.modelValue || this.value
|
||||
if (localVal) {
|
||||
return deepCopy(localVal)
|
||||
}
|
||||
return {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听数据变化 ,暂时不使用,需要单独赋值
|
||||
// localData: {},
|
||||
// 监听规则变化
|
||||
rules: {
|
||||
handler: function(val, oldVal) {
|
||||
this.setRules(val)
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// #ifdef VUE3
|
||||
let getbinddata = getApp().$vm.$.appContext.config.globalProperties.binddata
|
||||
if (!getbinddata) {
|
||||
getApp().$vm.$.appContext.config.globalProperties.binddata = function(name, value, formName) {
|
||||
if (formName) {
|
||||
this.$refs[formName].setValue(name, value);
|
||||
} else {
|
||||
let formVm;
|
||||
for (let i in this.$refs) {
|
||||
const vm = this.$refs[i];
|
||||
if (vm && vm.$options && vm.$options.name === 'uniForms') {
|
||||
formVm = vm;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
|
||||
formVm.setValue(name, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// 子组件实例数组
|
||||
this.childrens = []
|
||||
// TODO 兼容旧版 uni-data-picker ,新版本中无效,只是避免报错
|
||||
this.inputChildrens = []
|
||||
this.setRules(this.rules)
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* 外部调用方法
|
||||
* 设置规则 ,主要用于小程序自定义检验规则
|
||||
* @param {Array} rules 规则源数据
|
||||
*/
|
||||
setRules(rules) {
|
||||
// TODO 有可能子组件合并规则的时机比这个要早,所以需要合并对象 ,而不是直接赋值,可能会被覆盖
|
||||
this.formRules = Object.assign({}, this.formRules, rules)
|
||||
// 初始化校验函数
|
||||
this.validator = new Validator(rules);
|
||||
},
|
||||
|
||||
/**
|
||||
* 外部调用方法
|
||||
* 设置数据,用于设置表单数据,公开给用户使用 , 不支持在动态表单中使用
|
||||
* @param {Object} key
|
||||
* @param {Object} value
|
||||
*/
|
||||
setValue(key, value) {
|
||||
let example = this.childrens.find(child => child.name === key);
|
||||
if (!example) return null;
|
||||
this.formData[key] = getValue(key, value, (this.formRules[key] && this.formRules[key].rules) || [])
|
||||
return example.onFieldChange(this.formData[key]);
|
||||
},
|
||||
|
||||
/**
|
||||
* 外部调用方法
|
||||
* 手动提交校验表单
|
||||
* 对整个表单进行校验的方法,参数为一个回调函数。
|
||||
* @param {Array} keepitem 保留不参与校验的字段
|
||||
* @param {type} callback 方法回调
|
||||
*/
|
||||
validate(keepitem, callback) {
|
||||
return this.checkAll(this.formData, keepitem, callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* 外部调用方法
|
||||
* 部分表单校验
|
||||
* @param {Array|String} props 需要校验的字段
|
||||
* @param {Function} 回调函数
|
||||
*/
|
||||
validateField(props = [], callback) {
|
||||
props = [].concat(props);
|
||||
let invalidFields = {};
|
||||
this.childrens.forEach(item => {
|
||||
const name = realName(item.name)
|
||||
if (props.indexOf(name) !== -1) {
|
||||
invalidFields = Object.assign({}, invalidFields, {
|
||||
[name]: this.formData[name]
|
||||
});
|
||||
}
|
||||
});
|
||||
return this.checkAll(invalidFields, [], callback);
|
||||
},
|
||||
|
||||
/**
|
||||
* 外部调用方法
|
||||
* 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果
|
||||
* @param {Array|String} props 需要移除校验的字段 ,不填为所有
|
||||
*/
|
||||
clearValidate(props = []) {
|
||||
props = [].concat(props);
|
||||
this.childrens.forEach(item => {
|
||||
if (props.length === 0) {
|
||||
item.errMsg = '';
|
||||
} else {
|
||||
const name = realName(item.name)
|
||||
if (props.indexOf(name) !== -1) {
|
||||
item.errMsg = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* 外部调用方法 ,即将废弃
|
||||
* 手动提交校验表单
|
||||
* 对整个表单进行校验的方法,参数为一个回调函数。
|
||||
* @param {Array} keepitem 保留不参与校验的字段
|
||||
* @param {type} callback 方法回调
|
||||
*/
|
||||
submit(keepitem, callback, type) {
|
||||
for (let i in this.dataValue) {
|
||||
const itemData = this.childrens.find(v => v.name === i);
|
||||
if (itemData) {
|
||||
if (this.formData[i] === undefined) {
|
||||
this.formData[i] = this._getValue(i, this.dataValue[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!type) {
|
||||
console.warn('submit 方法即将废弃,请使用validate方法代替!');
|
||||
}
|
||||
|
||||
return this.checkAll(this.formData, keepitem, callback, 'submit');
|
||||
},
|
||||
|
||||
// 校验所有
|
||||
async checkAll(invalidFields, keepitem, callback, type) {
|
||||
// 不存在校验规则 ,则停止校验流程
|
||||
if (!this.validator) return
|
||||
let childrens = []
|
||||
// 处理参与校验的item实例
|
||||
for (let i in invalidFields) {
|
||||
const item = this.childrens.find(v => realName(v.name) === i)
|
||||
if (item) {
|
||||
childrens.push(item)
|
||||
}
|
||||
}
|
||||
|
||||
// 如果validate第一个参数是funciont ,那就走回调
|
||||
if (!callback && typeof keepitem === 'function') {
|
||||
callback = keepitem;
|
||||
}
|
||||
|
||||
let promise;
|
||||
// 如果不存在回调,那么使用 Promise 方式返回
|
||||
if (!callback && typeof callback !== 'function' && Promise) {
|
||||
promise = new Promise((resolve, reject) => {
|
||||
callback = function(valid, invalidFields) {
|
||||
!valid ? resolve(invalidFields) : reject(valid);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
let results = [];
|
||||
// 避免引用错乱 ,建议拷贝对象处理
|
||||
let tempFormData = JSON.parse(JSON.stringify(invalidFields))
|
||||
// 所有子组件参与校验,使用 for 可以使用 awiat
|
||||
for (let i in childrens) {
|
||||
const child = childrens[i]
|
||||
let name = realName(child.name);
|
||||
const result = await child.onFieldChange(tempFormData[name]);
|
||||
if (result) {
|
||||
results.push(result);
|
||||
// toast ,modal 只需要执行第一次就可以
|
||||
if (this.errShowType === 'toast' || this.errShowType === 'modal') break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (Array.isArray(results)) {
|
||||
if (results.length === 0) results = null;
|
||||
}
|
||||
if (Array.isArray(keepitem)) {
|
||||
keepitem.forEach(v => {
|
||||
let vName = realName(v);
|
||||
let value = getDataValue(v, this.localData)
|
||||
if (value !== undefined) {
|
||||
tempFormData[vName] = value
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// TODO submit 即将废弃
|
||||
if (type === 'submit') {
|
||||
this.$emit('submit', {
|
||||
detail: {
|
||||
value: tempFormData,
|
||||
errors: results
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.$emit('validate', results);
|
||||
}
|
||||
|
||||
// const resetFormData = rawData(tempFormData, this.localData, this.name)
|
||||
let resetFormData = {}
|
||||
resetFormData = rawData(tempFormData, this.name)
|
||||
callback && typeof callback === 'function' && callback(results, resetFormData);
|
||||
|
||||
if (promise && callback) {
|
||||
return promise;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 返回validate事件
|
||||
* @param {Object} result
|
||||
*/
|
||||
validateCheck(result) {
|
||||
this.$emit('validate', result);
|
||||
},
|
||||
_getValue: getValue,
|
||||
_isRequiredField: isRequiredField,
|
||||
_setDataValue: setDataValue,
|
||||
_getDataValue: getDataValue,
|
||||
_realName: realName,
|
||||
_isRealName: isRealName,
|
||||
_isEqual: isEqual
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.uni-forms {}
|
||||
</style>
|
|
@ -0,0 +1,293 @@
|
|||
/**
|
||||
* 简单处理对象拷贝
|
||||
* @param {Obejct} 被拷贝对象
|
||||
* @@return {Object} 拷贝对象
|
||||
*/
|
||||
export const deepCopy = (val) => {
|
||||
return JSON.parse(JSON.stringify(val))
|
||||
}
|
||||
/**
|
||||
* 过滤数字类型
|
||||
* @param {String} format 数字类型
|
||||
* @@return {Boolean} 返回是否为数字类型
|
||||
*/
|
||||
export const typeFilter = (format) => {
|
||||
return format === 'int' || format === 'double' || format === 'number' || format === 'timestamp';
|
||||
}
|
||||
|
||||
/**
|
||||
* 把 value 转换成指定的类型,用于处理初始值,原因是初始值需要入库不能为 undefined
|
||||
* @param {String} key 字段名
|
||||
* @param {any} value 字段值
|
||||
* @param {Object} rules 表单校验规则
|
||||
*/
|
||||
export const getValue = (key, value, rules) => {
|
||||
const isRuleNumType = rules.find(val => val.format && typeFilter(val.format));
|
||||
const isRuleBoolType = rules.find(val => (val.format && val.format === 'boolean') || val.format === 'bool');
|
||||
// 输入类型为 number
|
||||
if (!!isRuleNumType) {
|
||||
if (!value && value !== 0) {
|
||||
value = null
|
||||
} else {
|
||||
value = isNumber(Number(value)) ? Number(value) : value
|
||||
}
|
||||
}
|
||||
|
||||
// 输入类型为 boolean
|
||||
if (!!isRuleBoolType) {
|
||||
value = isBoolean(value) ? value : false
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表单数据
|
||||
* @param {String|Array} name 真实名称,需要使用 realName 获取
|
||||
* @param {Object} data 原始数据
|
||||
* @param {any} value 需要设置的值
|
||||
*/
|
||||
export const setDataValue = (field, formdata, value) => {
|
||||
formdata[field] = value
|
||||
return value || ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表单数据
|
||||
* @param {String|Array} field 真实名称,需要使用 realName 获取
|
||||
* @param {Object} data 原始数据
|
||||
*/
|
||||
export const getDataValue = (field, data) => {
|
||||
return objGet(data, field)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表单类型
|
||||
* @param {String|Array} field 真实名称,需要使用 realName 获取
|
||||
*/
|
||||
export const getDataValueType = (field, data) => {
|
||||
const value = getDataValue(field, data)
|
||||
return {
|
||||
type: type(value),
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表单可用的真实name
|
||||
* @param {String|Array} name 表单name
|
||||
* @@return {String} 表单可用的真实name
|
||||
*/
|
||||
export const realName = (name, data = {}) => {
|
||||
const base_name = _basePath(name)
|
||||
if (typeof base_name === 'object' && Array.isArray(base_name) && base_name.length > 1) {
|
||||
const realname = base_name.reduce((a, b) => a += `#${b}`, '_formdata_')
|
||||
return realname
|
||||
}
|
||||
return base_name[0] || name
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否表单可用的真实name
|
||||
* @param {String|Array} name 表单name
|
||||
* @@return {String} 表单可用的真实name
|
||||
*/
|
||||
export const isRealName = (name) => {
|
||||
const reg = /^_formdata_#*/
|
||||
return reg.test(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取表单数据的原始格式
|
||||
* @@return {Object|Array} object 需要解析的数据
|
||||
*/
|
||||
export const rawData = (object = {}, name) => {
|
||||
let newData = JSON.parse(JSON.stringify(object))
|
||||
let formData = {}
|
||||
for(let i in newData){
|
||||
let path = name2arr(i)
|
||||
objSet(formData,path,newData[i])
|
||||
}
|
||||
return formData
|
||||
}
|
||||
|
||||
/**
|
||||
* 真实name还原为 array
|
||||
* @param {*} name
|
||||
*/
|
||||
export const name2arr = (name) => {
|
||||
let field = name.replace('_formdata_#', '')
|
||||
field = field.split('#').map(v => (isNumber(v) ? Number(v) : v))
|
||||
return field
|
||||
}
|
||||
|
||||
/**
|
||||
* 对象中设置值
|
||||
* @param {Object|Array} object 源数据
|
||||
* @param {String| Array} path 'a.b.c' 或 ['a',0,'b','c']
|
||||
* @param {String} value 需要设置的值
|
||||
*/
|
||||
export const objSet = (object, path, value) => {
|
||||
if (typeof object !== 'object') return object;
|
||||
_basePath(path).reduce((o, k, i, _) => {
|
||||
if (i === _.length - 1) {
|
||||
// 若遍历结束直接赋值
|
||||
o[k] = value
|
||||
return null
|
||||
} else if (k in o) {
|
||||
// 若存在对应路径,则返回找到的对象,进行下一次遍历
|
||||
return o[k]
|
||||
} else {
|
||||
// 若不存在对应路径,则创建对应对象,若下一路径是数字,新对象赋值为空数组,否则赋值为空对象
|
||||
o[k] = /^[0-9]{1,}$/.test(_[i + 1]) ? [] : {}
|
||||
return o[k]
|
||||
}
|
||||
}, object)
|
||||
// 返回object
|
||||
return object;
|
||||
}
|
||||
|
||||
// 处理 path, path有三种形式:'a[0].b.c'、'a.0.b.c' 和 ['a','0','b','c'],需要统一处理成数组,便于后续使用
|
||||
function _basePath(path) {
|
||||
// 若是数组,则直接返回
|
||||
if (Array.isArray(path)) return path
|
||||
// 若有 '[',']',则替换成将 '[' 替换成 '.',去掉 ']'
|
||||
return path.replace(/\[/g, '.').replace(/\]/g, '').split('.')
|
||||
}
|
||||
|
||||
/**
|
||||
* 从对象中获取值
|
||||
* @param {Object|Array} object 源数据
|
||||
* @param {String| Array} path 'a.b.c' 或 ['a',0,'b','c']
|
||||
* @param {String} defaultVal 如果无法从调用链中获取值的默认值
|
||||
*/
|
||||
export const objGet = (object, path, defaultVal = 'undefined') => {
|
||||
// 先将path处理成统一格式
|
||||
let newPath = _basePath(path)
|
||||
// 递归处理,返回最后结果
|
||||
let val = newPath.reduce((o, k) => {
|
||||
return (o || {})[k]
|
||||
}, object);
|
||||
return !val || val !== undefined ? val : defaultVal
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 是否为 number 类型
|
||||
* @param {any} num 需要判断的值
|
||||
* @return {Boolean} 是否为 number
|
||||
*/
|
||||
export const isNumber = (num) => {
|
||||
return !isNaN(Number(num))
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为 boolean 类型
|
||||
* @param {any} bool 需要判断的值
|
||||
* @return {Boolean} 是否为 boolean
|
||||
*/
|
||||
export const isBoolean = (bool) => {
|
||||
return (typeof bool === 'boolean')
|
||||
}
|
||||
/**
|
||||
* 是否有必填字段
|
||||
* @param {Object} rules 规则
|
||||
* @return {Boolean} 是否有必填字段
|
||||
*/
|
||||
export const isRequiredField = (rules) => {
|
||||
let isNoField = false;
|
||||
for (let i = 0; i < rules.length; i++) {
|
||||
const ruleData = rules[i];
|
||||
if (ruleData.required) {
|
||||
isNoField = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return isNoField;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取数据类型
|
||||
* @param {Any} obj 需要获取数据类型的值
|
||||
*/
|
||||
export const type = (obj) => {
|
||||
var class2type = {};
|
||||
|
||||
// 生成class2type映射
|
||||
"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) {
|
||||
class2type["[object " + item + "]"] = item.toLowerCase();
|
||||
})
|
||||
if (obj == null) {
|
||||
return obj + "";
|
||||
}
|
||||
return typeof obj === "object" || typeof obj === "function" ?
|
||||
class2type[Object.prototype.toString.call(obj)] || "object" :
|
||||
typeof obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断两个值是否相等
|
||||
* @param {any} a 值
|
||||
* @param {any} b 值
|
||||
* @return {Boolean} 是否相等
|
||||
*/
|
||||
export const isEqual = (a, b) => {
|
||||
//如果a和b本来就全等
|
||||
if (a === b) {
|
||||
//判断是否为0和-0
|
||||
return a !== 0 || 1 / a === 1 / b;
|
||||
}
|
||||
//判断是否为null和undefined
|
||||
if (a == null || b == null) {
|
||||
return a === b;
|
||||
}
|
||||
//接下来判断a和b的数据类型
|
||||
var classNameA = toString.call(a),
|
||||
classNameB = toString.call(b);
|
||||
//如果数据类型不相等,则返回false
|
||||
if (classNameA !== classNameB) {
|
||||
return false;
|
||||
}
|
||||
//如果数据类型相等,再根据不同数据类型分别判断
|
||||
switch (classNameA) {
|
||||
case '[object RegExp]':
|
||||
case '[object String]':
|
||||
//进行字符串转换比较
|
||||
return '' + a === '' + b;
|
||||
case '[object Number]':
|
||||
//进行数字转换比较,判断是否为NaN
|
||||
if (+a !== +a) {
|
||||
return +b !== +b;
|
||||
}
|
||||
//判断是否为0或-0
|
||||
return +a === 0 ? 1 / +a === 1 / b : +a === +b;
|
||||
case '[object Date]':
|
||||
case '[object Boolean]':
|
||||
return +a === +b;
|
||||
}
|
||||
//如果是对象类型
|
||||
if (classNameA == '[object Object]') {
|
||||
//获取a和b的属性长度
|
||||
var propsA = Object.getOwnPropertyNames(a),
|
||||
propsB = Object.getOwnPropertyNames(b);
|
||||
if (propsA.length != propsB.length) {
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < propsA.length; i++) {
|
||||
var propName = propsA[i];
|
||||
//如果对应属性对应值不相等,则返回false
|
||||
if (a[propName] !== b[propName]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
//如果是数组类型
|
||||
if (classNameA == '[object Array]') {
|
||||
if (a.toString() == b.toString()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,486 @@
|
|||
var pattern = {
|
||||
email: /^\S+?@\S+?\.\S+?$/,
|
||||
idcard: /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/,
|
||||
url: new RegExp(
|
||||
"^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$",
|
||||
'i')
|
||||
};
|
||||
|
||||
const FORMAT_MAPPING = {
|
||||
"int": 'integer',
|
||||
"bool": 'boolean',
|
||||
"double": 'number',
|
||||
"long": 'number',
|
||||
"password": 'string'
|
||||
// "fileurls": 'array'
|
||||
}
|
||||
|
||||
function formatMessage(args, resources = '') {
|
||||
var defaultMessage = ['label']
|
||||
defaultMessage.forEach((item) => {
|
||||
if (args[item] === undefined) {
|
||||
args[item] = ''
|
||||
}
|
||||
})
|
||||
|
||||
let str = resources
|
||||
for (let key in args) {
|
||||
let reg = new RegExp('{' + key + '}')
|
||||
str = str.replace(reg, args[key])
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
function isEmptyValue(value, type) {
|
||||
if (value === undefined || value === null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (typeof value === 'string' && !value) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Array.isArray(value) && !value.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type === 'object' && !Object.keys(value).length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const types = {
|
||||
integer(value) {
|
||||
return types.number(value) && parseInt(value, 10) === value;
|
||||
},
|
||||
string(value) {
|
||||
return typeof value === 'string';
|
||||
},
|
||||
number(value) {
|
||||
if (isNaN(value)) {
|
||||
return false;
|
||||
}
|
||||
return typeof value === 'number';
|
||||
},
|
||||
"boolean": function(value) {
|
||||
return typeof value === 'boolean';
|
||||
},
|
||||
"float": function(value) {
|
||||
return types.number(value) && !types.integer(value);
|
||||
},
|
||||
array(value) {
|
||||
return Array.isArray(value);
|
||||
},
|
||||
object(value) {
|
||||
return typeof value === 'object' && !types.array(value);
|
||||
},
|
||||
date(value) {
|
||||
return value instanceof Date;
|
||||
},
|
||||
timestamp(value) {
|
||||
if (!this.integer(value) || Math.abs(value).toString().length > 16) {
|
||||
return false
|
||||
}
|
||||
return true;
|
||||
},
|
||||
file(value) {
|
||||
return typeof value.url === 'string';
|
||||
},
|
||||
email(value) {
|
||||
return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255;
|
||||
},
|
||||
url(value) {
|
||||
return typeof value === 'string' && !!value.match(pattern.url);
|
||||
},
|
||||
pattern(reg, value) {
|
||||
try {
|
||||
return new RegExp(reg).test(value);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
method(value) {
|
||||
return typeof value === 'function';
|
||||
},
|
||||
idcard(value) {
|
||||
return typeof value === 'string' && !!value.match(pattern.idcard);
|
||||
},
|
||||
'url-https'(value) {
|
||||
return this.url(value) && value.startsWith('https://');
|
||||
},
|
||||
'url-scheme'(value) {
|
||||
return value.startsWith('://');
|
||||
},
|
||||
'url-web'(value) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class RuleValidator {
|
||||
|
||||
constructor(message) {
|
||||
this._message = message
|
||||
}
|
||||
|
||||
async validateRule(fieldKey, fieldValue, value, data, allData) {
|
||||
var result = null
|
||||
|
||||
let rules = fieldValue.rules
|
||||
|
||||
let hasRequired = rules.findIndex((item) => {
|
||||
return item.required
|
||||
})
|
||||
if (hasRequired < 0) {
|
||||
if (value === null || value === undefined) {
|
||||
return result
|
||||
}
|
||||
if (typeof value === 'string' && !value.length) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
var message = this._message
|
||||
|
||||
if (rules === undefined) {
|
||||
return message['default']
|
||||
}
|
||||
|
||||
for (var i = 0; i < rules.length; i++) {
|
||||
let rule = rules[i]
|
||||
let vt = this._getValidateType(rule)
|
||||
|
||||
Object.assign(rule, {
|
||||
label: fieldValue.label || `["${fieldKey}"]`
|
||||
})
|
||||
|
||||
if (RuleValidatorHelper[vt]) {
|
||||
result = RuleValidatorHelper[vt](rule, value, message)
|
||||
if (result != null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (rule.validateExpr) {
|
||||
let now = Date.now()
|
||||
let resultExpr = rule.validateExpr(value, allData, now)
|
||||
if (resultExpr === false) {
|
||||
result = this._getMessage(rule, rule.errorMessage || this._message['default'])
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (rule.validateFunction) {
|
||||
result = await this.validateFunction(rule, value, data, allData, vt)
|
||||
if (result !== null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result !== null) {
|
||||
result = message.TAG + result
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
async validateFunction(rule, value, data, allData, vt) {
|
||||
let result = null
|
||||
try {
|
||||
let callbackMessage = null
|
||||
const res = await rule.validateFunction(rule, value, allData || data, (message) => {
|
||||
callbackMessage = message
|
||||
})
|
||||
if (callbackMessage || (typeof res === 'string' && res) || res === false) {
|
||||
result = this._getMessage(rule, callbackMessage || res, vt)
|
||||
}
|
||||
} catch (e) {
|
||||
result = this._getMessage(rule, e.message, vt)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
_getMessage(rule, message, vt) {
|
||||
return formatMessage(rule, message || rule.errorMessage || this._message[vt] || message['default'])
|
||||
}
|
||||
|
||||
_getValidateType(rule) {
|
||||
var result = ''
|
||||
if (rule.required) {
|
||||
result = 'required'
|
||||
} else if (rule.format) {
|
||||
result = 'format'
|
||||
} else if (rule.arrayType) {
|
||||
result = 'arrayTypeFormat'
|
||||
} else if (rule.range) {
|
||||
result = 'range'
|
||||
} else if (rule.maximum !== undefined || rule.minimum !== undefined) {
|
||||
result = 'rangeNumber'
|
||||
} else if (rule.maxLength !== undefined || rule.minLength !== undefined) {
|
||||
result = 'rangeLength'
|
||||
} else if (rule.pattern) {
|
||||
result = 'pattern'
|
||||
} else if (rule.validateFunction) {
|
||||
result = 'validateFunction'
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
const RuleValidatorHelper = {
|
||||
required(rule, value, message) {
|
||||
if (rule.required && isEmptyValue(value, rule.format || typeof value)) {
|
||||
return formatMessage(rule, rule.errorMessage || message.required);
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
range(rule, value, message) {
|
||||
const {
|
||||
range,
|
||||
errorMessage
|
||||
} = rule;
|
||||
|
||||
let list = new Array(range.length);
|
||||
for (let i = 0; i < range.length; i++) {
|
||||
const item = range[i];
|
||||
if (types.object(item) && item.value !== undefined) {
|
||||
list[i] = item.value;
|
||||
} else {
|
||||
list[i] = item;
|
||||
}
|
||||
}
|
||||
|
||||
let result = false
|
||||
if (Array.isArray(value)) {
|
||||
result = (new Set(value.concat(list)).size === list.length);
|
||||
} else {
|
||||
if (list.indexOf(value) > -1) {
|
||||
result = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!result) {
|
||||
return formatMessage(rule, errorMessage || message['enum']);
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
rangeNumber(rule, value, message) {
|
||||
if (!types.number(value)) {
|
||||
return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
|
||||
}
|
||||
|
||||
let {
|
||||
minimum,
|
||||
maximum,
|
||||
exclusiveMinimum,
|
||||
exclusiveMaximum
|
||||
} = rule;
|
||||
let min = exclusiveMinimum ? value <= minimum : value < minimum;
|
||||
let max = exclusiveMaximum ? value >= maximum : value > maximum;
|
||||
|
||||
if (minimum !== undefined && min) {
|
||||
return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMinimum ?
|
||||
'exclusiveMinimum' : 'minimum'
|
||||
])
|
||||
} else if (maximum !== undefined && max) {
|
||||
return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMaximum ?
|
||||
'exclusiveMaximum' : 'maximum'
|
||||
])
|
||||
} else if (minimum !== undefined && maximum !== undefined && (min || max)) {
|
||||
return formatMessage(rule, rule.errorMessage || message['number'].range)
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
rangeLength(rule, value, message) {
|
||||
if (!types.string(value) && !types.array(value)) {
|
||||
return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
|
||||
}
|
||||
|
||||
let min = rule.minLength;
|
||||
let max = rule.maxLength;
|
||||
let val = value.length;
|
||||
|
||||
if (min !== undefined && val < min) {
|
||||
return formatMessage(rule, rule.errorMessage || message['length'].minLength)
|
||||
} else if (max !== undefined && val > max) {
|
||||
return formatMessage(rule, rule.errorMessage || message['length'].maxLength)
|
||||
} else if (min !== undefined && max !== undefined && (val < min || val > max)) {
|
||||
return formatMessage(rule, rule.errorMessage || message['length'].range)
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
pattern(rule, value, message) {
|
||||
if (!types['pattern'](rule.pattern, value)) {
|
||||
return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
format(rule, value, message) {
|
||||
var customTypes = Object.keys(types);
|
||||
var format = FORMAT_MAPPING[rule.format] ? FORMAT_MAPPING[rule.format] : (rule.format || rule.arrayType);
|
||||
|
||||
if (customTypes.indexOf(format) > -1) {
|
||||
if (!types[format](value)) {
|
||||
return formatMessage(rule, rule.errorMessage || message.typeError);
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
},
|
||||
|
||||
arrayTypeFormat(rule, value, message) {
|
||||
if (!Array.isArray(value)) {
|
||||
return formatMessage(rule, rule.errorMessage || message.typeError);
|
||||
}
|
||||
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
const element = value[i];
|
||||
let formatResult = this.format(rule, element, message)
|
||||
if (formatResult !== null) {
|
||||
return formatResult
|
||||
}
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
class SchemaValidator extends RuleValidator {
|
||||
|
||||
constructor(schema, options) {
|
||||
super(SchemaValidator.message);
|
||||
|
||||
this._schema = schema
|
||||
this._options = options || null
|
||||
}
|
||||
|
||||
updateSchema(schema) {
|
||||
this._schema = schema
|
||||
}
|
||||
|
||||
async validate(data, allData) {
|
||||
let result = this._checkFieldInSchema(data)
|
||||
if (!result) {
|
||||
result = await this.invokeValidate(data, false, allData)
|
||||
}
|
||||
return result.length ? result[0] : null
|
||||
}
|
||||
|
||||
async validateAll(data, allData) {
|
||||
let result = this._checkFieldInSchema(data)
|
||||
if (!result) {
|
||||
result = await this.invokeValidate(data, true, allData)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async validateUpdate(data, allData) {
|
||||
let result = this._checkFieldInSchema(data)
|
||||
if (!result) {
|
||||
result = await this.invokeValidateUpdate(data, false, allData)
|
||||
}
|
||||
return result.length ? result[0] : null
|
||||
}
|
||||
|
||||
async invokeValidate(data, all, allData) {
|
||||
let result = []
|
||||
let schema = this._schema
|
||||
for (let key in schema) {
|
||||
let value = schema[key]
|
||||
let errorMessage = await this.validateRule(key, value, data[key], data, allData)
|
||||
if (errorMessage != null) {
|
||||
result.push({
|
||||
key,
|
||||
errorMessage
|
||||
})
|
||||
if (!all) break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
async invokeValidateUpdate(data, all, allData) {
|
||||
let result = []
|
||||
for (let key in data) {
|
||||
let errorMessage = await this.validateRule(key, this._schema[key], data[key], data, allData)
|
||||
if (errorMessage != null) {
|
||||
result.push({
|
||||
key,
|
||||
errorMessage
|
||||
})
|
||||
if (!all) break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
_checkFieldInSchema(data) {
|
||||
var keys = Object.keys(data)
|
||||
var keys2 = Object.keys(this._schema)
|
||||
if (new Set(keys.concat(keys2)).size === keys2.length) {
|
||||
return ''
|
||||
}
|
||||
|
||||
var noExistFields = keys.filter((key) => {
|
||||
return keys2.indexOf(key) < 0;
|
||||
})
|
||||
var errorMessage = formatMessage({
|
||||
field: JSON.stringify(noExistFields)
|
||||
}, SchemaValidator.message.TAG + SchemaValidator.message['defaultInvalid'])
|
||||
return [{
|
||||
key: 'invalid',
|
||||
errorMessage
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
function Message() {
|
||||
return {
|
||||
TAG: "",
|
||||
default: '验证错误',
|
||||
defaultInvalid: '提交的字段{field}在数据库中并不存在',
|
||||
validateFunction: '验证无效',
|
||||
required: '{label}必填',
|
||||
'enum': '{label}超出范围',
|
||||
timestamp: '{label}格式无效',
|
||||
whitespace: '{label}不能为空',
|
||||
typeError: '{label}类型无效',
|
||||
date: {
|
||||
format: '{label}日期{value}格式无效',
|
||||
parse: '{label}日期无法解析,{value}无效',
|
||||
invalid: '{label}日期{value}无效'
|
||||
},
|
||||
length: {
|
||||
minLength: '{label}长度不能少于{minLength}',
|
||||
maxLength: '{label}长度不能超过{maxLength}',
|
||||
range: '{label}必须介于{minLength}和{maxLength}之间'
|
||||
},
|
||||
number: {
|
||||
minimum: '{label}不能小于{minimum}',
|
||||
maximum: '{label}不能大于{maximum}',
|
||||
exclusiveMinimum: '{label}不能小于等于{minimum}',
|
||||
exclusiveMaximum: '{label}不能大于等于{maximum}',
|
||||
range: '{label}必须介于{minimum}and{maximum}之间'
|
||||
},
|
||||
pattern: {
|
||||
mismatch: '{label}格式不匹配'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
SchemaValidator.message = new Message();
|
||||
|
||||
export default SchemaValidator
|
|
@ -0,0 +1,88 @@
|
|||
{
|
||||
"id": "uni-forms",
|
||||
"displayName": "uni-forms 表单",
|
||||
"version": "1.4.10",
|
||||
"description": "由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
"表单",
|
||||
"校验",
|
||||
"表单校验",
|
||||
"表单验证"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": ""
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
"uni-scss",
|
||||
"uni-icons"
|
||||
],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y",
|
||||
"京东": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
|
||||
## Forms 表单
|
||||
|
||||
> **组件名:uni-forms**
|
||||
> 代码块: `uForms`、`uni-forms-item`
|
||||
> 关联组件:`uni-forms-item`、`uni-easyinput`、`uni-data-checkbox`、`uni-group`。
|
||||
|
||||
|
||||
uni-app的内置组件已经有了 `<form>`组件,用于提交表单内容。
|
||||
|
||||
然而几乎每个表单都需要做表单验证,为了方便做表单验证,减少重复开发,`uni ui` 又基于 `<form>`组件封装了 `<uni-forms>`组件,内置了表单验证功能。
|
||||
|
||||
`<uni-forms>` 提供了 `rules`属性来描述校验规则、`<uni-forms-item>`子组件来包裹具体的表单项,以及给原生或三方组件提供了 `binddata()` 来设置表单值。
|
||||
|
||||
每个要校验的表单项,不管input还是checkbox,都必须放在`<uni-forms-item>`组件中,且一个`<uni-forms-item>`组件只能放置一个表单项。
|
||||
|
||||
`<uni-forms-item>`组件内部预留了显示error message的区域,默认是在表单项的底部。
|
||||
|
||||
另外,`<uni-forms>`组件下面的各个表单项,可以通过`<uni-group>`包裹为不同的分组。同一`<uni-group>`下的不同表单项目将聚拢在一起,同其他group保持垂直间距。`<uni-group>`仅影响视觉效果。
|
||||
|
||||
### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-forms)
|
||||
#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
Loading…
Reference in New Issue