入库单每个配件可以修改存放货架。

This commit is contained in:
xiaofajia 2024-11-25 17:27:01 +08:00
parent a6f12f483f
commit c7245d1e69
13 changed files with 3036 additions and 2 deletions

View File

@ -77,6 +77,11 @@
<view class="formLabel">数量</view> <view class="formLabel">数量</view>
<input type="number" style="text-align: right" v-model="item.inCount" placeholder="请输入数量"/> <input type="number" style="text-align: right" v-model="item.inCount" placeholder="请输入数量"/>
</view> </view>
<view class="formItem">
<view class="formLabel">仓库</view>
<uni-data-picker placeholder="请选择仓库" popup-title="请选择仓库" :localdata="waresTree" v-model="item.wares.warehouse">
</uni-data-picker>
</view>
</uni-card> </uni-card>
</uni-collapse-item> </uni-collapse-item>
</uni-collapse> </uni-collapse>
@ -104,7 +109,7 @@
<script> <script>
import VNavigationBar from "@/components/VNavigationBar.vue"; import VNavigationBar from "@/components/VNavigationBar.vue";
import request from '@/utils/request'; import request from '@/utils/request';
import {getDictTextByCodeAndValue, createUniqueCodeByHead} from "@/utils/utils"; import {getDictTextByCodeAndValue, createUniqueCodeByHead, buildTree} from "@/utils/utils";
import {getUserInfo, getJSONData} from '@/utils/auth.js' import {getUserInfo, getJSONData} from '@/utils/auth.js'
import UniCollapse from "../../uni_modules/uni-collapse/components/uni-collapse/uni-collapse.vue"; import UniCollapse from "../../uni_modules/uni-collapse/components/uni-collapse/uni-collapse.vue";
import {getStorageWithExpiry} from "../../utils/auth"; import {getStorageWithExpiry} from "../../utils/auth";
@ -143,10 +148,13 @@ export default {
unitList: [], unitList: [],
query: null, query: null,
selectRows: [], selectRows: [],
subLoading: false subLoading: false,
waresTree: [],
chooseWares: null,
}; };
}, },
onLoad(data) { onLoad(data) {
this.listWarehouse()
this.listServer() this.listServer()
this.listUnit() this.listUnit()
if (data.soId) { if (data.soId) {
@ -164,6 +172,14 @@ export default {
}, },
computed: {}, computed: {},
methods: { methods: {
listWarehouse(){
request({
url: '/admin-api/conf/baseWarehouse/list',
method: 'get'
}).then(res => {
this.waresTree = buildTree(res.data, 'id', 'parentId', {name: 'text', id: 'value'})
})
},
chooseItem(item) { chooseItem(item) {
const parentIndex = this.partList.map(([key, value]) => value).reduce((acc, value) => acc.concat(value)).findIndex(i => i.id === item.id) const parentIndex = this.partList.map(([key, value]) => value).reduce((acc, value) => acc.concat(value)).findIndex(i => i.id === item.id)
const index = this.selectRows.findIndex(i => i.id === item.id) const index = this.selectRows.findIndex(i => i.id === item.id)

View File

@ -0,0 +1,79 @@
## 2.0.12024-08-22
- 修复 uni-app-x v-model 没有更新传入值的 bug
## 2.0.02023-12-11
- 新增 支持 uni-app-x
## 1.1.22023-04-11
- 修复 更改 modelValue 报错的 bug
- 修复 v-for 未使用 key 值控制台 warning
## 1.1.12023-02-21
- 修复代码合并时引发 value 属性为空时不渲染数据的问题
## 1.1.02023-02-15
- 修复 localdata 不支持动态更新的bug
## 1.0.92023-02-15
- 修复 localdata 不支持动态更新的bug
## 1.0.82022-09-16
- 可以使用 uni-scss 控制主题色
## 1.0.72022-07-06
- 优化 pc端图标位置不正确的问题
## 1.0.62022-07-05
- 优化 显示样式
## 1.0.52022-07-04
- 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug
## 1.0.42022-04-19
- 修复 字节小程序 本地数据无法选择下一级的Bug
## 1.0.32022-02-25
- 修复 nvue 不支持的 v-show 的 bug
## 1.0.22022-02-25
- 修复 条件编译 nvue 不支持的 css 样式
## 1.0.12021-11-23
- 修复 由上个版本引发的map、v-model等属性不生效的bug
## 1.0.02021-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.92021-10-28
- 修复 VUE2 v-model 概率无效的 bug
## 0.4.82021-10-27
- 修复 v-model 概率无效的 bug
## 0.4.72021-10-25
- 新增 属性 spaceInfo 服务空间配置 HBuilderX 3.2.11+
- 修复 树型 uniCloud 数据类型为 int 时报错的 bug
## 0.4.62021-10-19
- 修复 非 VUE3 v-model 为 0 时无法选中的 bug
## 0.4.52021-09-26
- 新增 清除已选项的功能(通过 clearIcon 属性配置是否显示按钮),同时提供 clear 方法以供调用,二者等效
- 修复 readonly 为 true 时报错的 bug
## 0.4.42021-09-26
- 修复 上一版本造成的 map 属性失效的 bug
- 新增 ellipsis 属性,支持配置 tab 选项长度过长时是否自动省略
## 0.4.32021-09-24
- 修复 某些情况下级联未触发的 bug
## 0.4.22021-09-23
- 新增 提供 show 和 hide 方法,开发者可以通过 ref 调用
- 新增 选项内容过长自动添加省略号
## 0.4.12021-09-15
- 新增 map 属性 字段映射,将 text/value 映射到数据中的其他字段
## 0.4.02021-07-13
- 组件兼容 vue3如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
## 0.3.52021-06-04
- 修复 无法加载云端数据的问题
## 0.3.42021-05-28
- 修复 v-model 无效问题
- 修复 loaddata 为空数据组时加载时间过长问题
- 修复 上个版本引出的本地数据无法选择带有 children 的 2 级节点
## 0.3.32021-05-12
- 新增 组件示例地址
## 0.3.22021-04-22
- 修复 非树形数据有 where 属性查询报错的问题
## 0.3.12021-04-15
- 修复 本地数据概率无法回显时问题
## 0.3.02021-04-07
- 新增 支持云端非树形表结构数据
- 修复 根节点 parent_field 字段等于 null 时选择界面错乱问题
## 0.2.02021-03-15
- 修复 nodeclick、popupopened、popupclosed 事件无法触发的问题
## 0.1.92021-03-09
- 修复 微信小程序某些情况下无法选择的问题
## 0.1.82021-02-05
- 优化 部分样式在 nvue 上的兼容表现
## 0.1.72021-02-05
- 调整为 uni_modules 目录规范

View File

@ -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

View File

@ -0,0 +1,381 @@
<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('update:modelValue', this.selectedPaths)
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>

View File

@ -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>

View File

@ -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 []
}
}
}

View File

@ -0,0 +1,692 @@
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.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)
}
}
}
})

View File

@ -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);
}

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,91 @@
{
"id": "uni-data-picker",
"displayName": "uni-data-picker 数据驱动的picker选择器",
"version": "2.0.1",
"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",
"alipay": "n"
},
"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"
}
}
}
}
}

View File

@ -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

View File

@ -268,3 +268,70 @@ export function createUniqueCodeByHead(head = '') {
return head.toString() + Date.now().toString() + Math.floor(Math.random() * (max - min + 1)) + min; return head.toString() + Date.now().toString() + Math.floor(Math.random() * (max - min + 1)) + min;
} }
/**
* 构造树
* @param items 原对象数组
* @param idKey 主标识
* @param parentKey 父标识
* @param mapping 映射Map
*/
export function buildTree(items, idKey, parentKey, mapping = null) {
const result = []; // 存储最终的树形结构
const itemMap = {}; // 用于快速查找对象
// 首先将所有项放入map中便于后续快速查找
for (const item of items) {
itemMap[item[idKey]] = { ...item, children: [] };
}
// 遍历每个项,构建树形结构
for (const item of items) {
const id = item[idKey];
const parentId = item[parentKey];
const node = itemMap[id];
if (parentId !== null && itemMap[parentId]) {
// 如果有父ID并且父节点存在则将当前节点加入到其父节点的children数组中
itemMap[parentId].children.push(node);
} else {
// 如果没有父ID或者找不到对应的父节点则认为这是根节点
result.push(node);
}
}
if (mapping){
return mapTree(result, mapping)
}
return result;
}
// 映射函数
function mapTree(tree, mapping) {
if (!tree || !Array.isArray(tree)) {
return tree;
}
const mappedTree = tree.map(item => {
const mappedItem = {};
for (const key in item) {
if (key === 'children') {
// 递归处理 children 数组
if (mapping.children) {
mappedItem[mapping.children] = mapTree(item[key], mapping);
} else {
mappedItem[key] = mapTree(item[key], mapping);
}
} else if (key in mapping) {
// 根据映射规则转换属性
const targetKey = mapping[key];
mappedItem[targetKey] = item[key];
}
}
return mappedItem;
});
return mappedTree;
}