From 8006af20920a7686465351b11778d156cec6f239 Mon Sep 17 00:00:00 2001 From: "Mr.jiang" <714156421@qq.com> Date: Wed, 14 Aug 2024 15:48:32 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/xlh-picker/xlh-picker.vue | 113 +++ my/gird/shoucang.vue | 4 +- package.json | 28 +- pages/my/cooperation.vue | 140 +++- pages/my/hehuo.vue | 232 ++++-- uni_modules/uni-data-picker/changelog.md | 77 ++ .../components/uni-data-picker/keypress.js | 45 ++ .../uni-data-picker/uni-data-picker.uvue | 380 ++++++++++ .../uni-data-picker/uni-data-picker.vue | 551 ++++++++++++++ .../uni-data-pickerview/loading.uts | 1 + .../uni-data-pickerview/uni-data-picker.js | 622 ++++++++++++++++ .../uni-data-pickerview/uni-data-picker.uts | 693 ++++++++++++++++++ .../uni-data-pickerview.css | 76 ++ .../uni-data-pickerview.uvue | 69 ++ .../uni-data-pickerview.vue | 323 ++++++++ uni_modules/uni-data-picker/package.json | 91 +++ uni_modules/uni-data-picker/readme.md | 22 + uni_modules/uni-forms/changelog.md | 94 +++ .../uni-forms-item/uni-forms-item.vue | 627 ++++++++++++++++ .../components/uni-forms/uni-forms.vue | 397 ++++++++++ .../uni-forms/components/uni-forms/utils.js | 293 ++++++++ .../components/uni-forms/validate.js | 486 ++++++++++++ uni_modules/uni-forms/package.json | 88 +++ uni_modules/uni-forms/readme.md | 23 + 24 files changed, 5401 insertions(+), 74 deletions(-) create mode 100644 components/xlh-picker/xlh-picker.vue create mode 100644 uni_modules/uni-data-picker/changelog.md create mode 100644 uni_modules/uni-data-picker/components/uni-data-picker/keypress.js create mode 100644 uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker.uvue create mode 100644 uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker.vue create mode 100644 uni_modules/uni-data-picker/components/uni-data-pickerview/loading.uts create mode 100644 uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.js create mode 100644 uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.uts create mode 100644 uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.css create mode 100644 uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.uvue create mode 100644 uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.vue create mode 100644 uni_modules/uni-data-picker/package.json create mode 100644 uni_modules/uni-data-picker/readme.md create mode 100644 uni_modules/uni-forms/changelog.md create mode 100644 uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue create mode 100644 uni_modules/uni-forms/components/uni-forms/uni-forms.vue create mode 100644 uni_modules/uni-forms/components/uni-forms/utils.js create mode 100644 uni_modules/uni-forms/components/uni-forms/validate.js create mode 100644 uni_modules/uni-forms/package.json create mode 100644 uni_modules/uni-forms/readme.md diff --git a/components/xlh-picker/xlh-picker.vue b/components/xlh-picker/xlh-picker.vue new file mode 100644 index 0000000..419b23e --- /dev/null +++ b/components/xlh-picker/xlh-picker.vue @@ -0,0 +1,113 @@ + + + + diff --git a/my/gird/shoucang.vue b/my/gird/shoucang.vue index c44b727..eb6ae3c 100644 --- a/my/gird/shoucang.vue +++ b/my/gird/shoucang.vue @@ -72,7 +72,7 @@ {{item.consortiaName}} - 立即预约 @@ -80,7 +80,7 @@ 立即预约 - + --> diff --git a/package.json b/package.json index 278247b..200b866 100644 --- a/package.json +++ b/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": [ + "前端组件", + "通用组件" + ] + } +} \ No newline at end of file diff --git a/pages/my/cooperation.vue b/pages/my/cooperation.vue index 3819f37..cae5b17 100644 --- a/pages/my/cooperation.vue +++ b/pages/my/cooperation.vue @@ -14,11 +14,35 @@ - + + + + + + + {{applyContent}} + + + + + + + + + + 请选择城市 + + {{cityData}} + + + + @@ -33,24 +57,106 @@ diff --git a/uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker.vue b/uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker.vue new file mode 100644 index 0000000..179a4e0 --- /dev/null +++ b/uni_modules/uni-data-picker/components/uni-data-picker/uni-data-picker.vue @@ -0,0 +1,551 @@ + + + + + diff --git a/uni_modules/uni-data-picker/components/uni-data-pickerview/loading.uts b/uni_modules/uni-data-picker/components/uni-data-pickerview/loading.uts new file mode 100644 index 0000000..baa0dff --- /dev/null +++ b/uni_modules/uni-data-picker/components/uni-data-pickerview/loading.uts @@ -0,0 +1 @@ +export const imgbase : string = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6QzlBMzU3OTlEOUM0MTFFOUI0NTZDNERBQURBQzI4RkUiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6QzlBMzU3OUFEOUM0MTFFOUI0NTZDNERBQURBQzI4RkUiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpDOUEzNTc5N0Q5QzQxMUU5QjQ1NkM0REFBREFDMjhGRSIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpDOUEzNTc5OEQ5QzQxMUU5QjQ1NkM0REFBREFDMjhGRSIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/Pt+ALSwAAA6CSURBVHja1FsLkFZV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' \ No newline at end of file diff --git a/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.js b/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.js new file mode 100644 index 0000000..cfae22a --- /dev/null +++ b/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.js @@ -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 [] + } + } +} diff --git a/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.uts b/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.uts new file mode 100644 index 0000000..372795d --- /dev/null +++ b/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-picker.uts @@ -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>, + default: [] as Array + }, + 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, + } + }, + data() { + return { + loading: false, + error: null as UniCloudError | null, + treeData: [] as Array, + selectedIndex: 0, + selectedNodes: [] as Array, + selectedPages: [] as Array[], + selectedValue: '', + selectedPaths: [] as Array, + 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 { + if (this.selectedIndex > this.selectedPages.length - 1) { + return [] as Array + } + 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 + 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, oldValue : Array) => { + 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 { + const nodes: Array = [] + 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 + this.selectedPaths = value.slice(0) + this._pushSelectedTreeNodes(value, this.localdata) + } else { + this._pushSelectedNodes(this.localdata) + } + }, + + // 加载 Cloud 数据 (单列) + loadCloudDataList() { + this._loadCloudData(null, (data : Array) => { + 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) => { + 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) => { + 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 = []; + 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) => { + this._extractTreePath(data, this.selectedPaths) + }) + }, + + _loadCloudData(options ?: GetCommandOptions, callback ?: ((data : Array) => 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 = [] + 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 = [] + 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 = [] + 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 = [] + 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 { + let db = uniCloud.databaseForJQL() + + let collection = Array.isArray(this.collection) ? db.collection(...(this.collection as Array)) : 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) + return children.length > 0 + }, + + _pushSelectedNodes(nodes : Array) { + 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('children') ?? ([] as Array) + if (children.length > 0) { + this.selectedNodes.push(DefaultSelectedNode) + this.selectedPages.push(children) + } + + this.selectedIndex = this.selectedPages.length - 1 + }, + + _pushSelectedTreeNodes(paths : Array, nodes : Array) { + let children : Array = 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('children') ?? ([] as Array) + } + }) + this.selectedIndex = this.selectedPages.length - 1 + }, + + _extractTreePath(nodes : Array, result : Array) { + if (nodes.length == 0) { + return + } + + const node = nodes[0] + result.push(node) + + const children = node.getArray('children') + if (Array.isArray(children) && children!.length > 0) { + this._extractTreePath(children, result) + } + } + } +}) diff --git a/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.css b/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.css new file mode 100644 index 0000000..39fe1c3 --- /dev/null +++ b/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.css @@ -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); +} diff --git a/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.uvue b/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.uvue new file mode 100644 index 0000000..f4780f3 --- /dev/null +++ b/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.uvue @@ -0,0 +1,69 @@ + + + + + diff --git a/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.vue b/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.vue new file mode 100644 index 0000000..6ebced9 --- /dev/null +++ b/uni_modules/uni-data-picker/components/uni-data-pickerview/uni-data-pickerview.vue @@ -0,0 +1,323 @@ + + + + + diff --git a/uni_modules/uni-data-picker/package.json b/uni_modules/uni-data-picker/package.json new file mode 100644 index 0000000..a508162 --- /dev/null +++ b/uni_modules/uni-data-picker/package.json @@ -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" + } + } + } + } +} diff --git a/uni_modules/uni-data-picker/readme.md b/uni_modules/uni-data-picker/readme.md new file mode 100644 index 0000000..19dd0e8 --- /dev/null +++ b/uni_modules/uni-data-picker/readme.md @@ -0,0 +1,22 @@ +## DataPicker 级联选择 +> **组件名:uni-data-picker** +> 代码块: `uDataPicker` +> 关联组件:`uni-data-pickerview`、`uni-load-more`。 + + +`` 是一个选择类[datacom组件](https://uniapp.dcloud.net.cn/component/datacom)。 + +支持单列、和多列级联选择。列数没有限制,如果屏幕显示不全,顶部tab区域会左右滚动。 + +候选数据支持一次性加载完毕,也支持懒加载,比如示例图中,选择了“北京”后,动态加载北京的区县数据。 + +`` 组件尤其适用于地址选择、分类选择等选择类。 + +`` 支持本地数据、云端静态数据(json),uniCloud云数据库数据。 + +`` 可以通过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 \ No newline at end of file diff --git a/uni_modules/uni-forms/changelog.md b/uni_modules/uni-forms/changelog.md new file mode 100644 index 0000000..3d998bc --- /dev/null +++ b/uni_modules/uni-forms/changelog.md @@ -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 diff --git a/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue b/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue new file mode 100644 index 0000000..0aef9cc --- /dev/null +++ b/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue @@ -0,0 +1,627 @@ + + + + + diff --git a/uni_modules/uni-forms/components/uni-forms/uni-forms.vue b/uni_modules/uni-forms/components/uni-forms/uni-forms.vue new file mode 100644 index 0000000..9bb9ae7 --- /dev/null +++ b/uni_modules/uni-forms/components/uni-forms/uni-forms.vue @@ -0,0 +1,397 @@ + + + + + diff --git a/uni_modules/uni-forms/components/uni-forms/utils.js b/uni_modules/uni-forms/components/uni-forms/utils.js new file mode 100644 index 0000000..6da2421 --- /dev/null +++ b/uni_modules/uni-forms/components/uni-forms/utils.js @@ -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; + } +} diff --git a/uni_modules/uni-forms/components/uni-forms/validate.js b/uni_modules/uni-forms/components/uni-forms/validate.js new file mode 100644 index 0000000..1834c6c --- /dev/null +++ b/uni_modules/uni-forms/components/uni-forms/validate.js @@ -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 diff --git a/uni_modules/uni-forms/package.json b/uni_modules/uni-forms/package.json new file mode 100644 index 0000000..464b4e6 --- /dev/null +++ b/uni_modules/uni-forms/package.json @@ -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" + } + } + } + } +} diff --git a/uni_modules/uni-forms/readme.md b/uni_modules/uni-forms/readme.md new file mode 100644 index 0000000..63d5a04 --- /dev/null +++ b/uni_modules/uni-forms/readme.md @@ -0,0 +1,23 @@ + + +## Forms 表单 + +> **组件名:uni-forms** +> 代码块: `uForms`、`uni-forms-item` +> 关联组件:`uni-forms-item`、`uni-easyinput`、`uni-data-checkbox`、`uni-group`。 + + +uni-app的内置组件已经有了 `
`组件,用于提交表单内容。 + +然而几乎每个表单都需要做表单验证,为了方便做表单验证,减少重复开发,`uni ui` 又基于 ``组件封装了 ``组件,内置了表单验证功能。 + +`` 提供了 `rules`属性来描述校验规则、``子组件来包裹具体的表单项,以及给原生或三方组件提供了 `binddata()` 来设置表单值。 + +每个要校验的表单项,不管input还是checkbox,都必须放在``组件中,且一个``组件只能放置一个表单项。 + +``组件内部预留了显示error message的区域,默认是在表单项的底部。 + +另外,``组件下面的各个表单项,可以通过``包裹为不同的分组。同一``下的不同表单项目将聚拢在一起,同其他group保持垂直间距。``仅影响视觉效果。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-forms) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file