Browse Source

标本间开发

maotao 5 months ago
parent
commit
d7c8cfc673
50 changed files with 7013 additions and 48 deletions
  1. 31 2
      App.vue
  2. 7 4
      components/uni-datetime-picker/uni-datetime-picker.vue
  3. 10 3
      components/uni-drawer/uni-drawer.vue
  4. 9 4
      pages/homePage/homePage.vue
  5. 379 1
      pages/specimenPort/detail.vue
  6. 564 1
      pages/specimenPort/scan.vue
  7. 470 26
      pages/specimenPort/specimenPort.vue
  8. 26 3
      static/font/demo_index.html
  9. 7 3
      static/font/iconfont.css
  10. 1 1
      static/font/iconfont.js
  11. 7 0
      static/font/iconfont.json
  12. BIN
      static/font/iconfont.ttf
  13. BIN
      static/font/iconfont.woff
  14. BIN
      static/font/iconfont.woff2
  15. 115 0
      uni_modules/uni-easyinput/changelog.md
  16. 54 0
      uni_modules/uni-easyinput/components/uni-easyinput/common.js
  17. 676 0
      uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue
  18. 88 0
      uni_modules/uni-easyinput/package.json
  19. 11 0
      uni_modules/uni-easyinput/readme.md
  20. 100 0
      uni_modules/uni-forms/changelog.md
  21. 632 0
      uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue
  22. 404 0
      uni_modules/uni-forms/components/uni-forms/uni-forms.vue
  23. 293 0
      uni_modules/uni-forms/components/uni-forms/utils.js
  24. 486 0
      uni_modules/uni-forms/components/uni-forms/validate.js
  25. 89 0
      uni_modules/uni-forms/package.json
  26. 23 0
      uni_modules/uni-forms/readme.md
  27. 92 0
      uni_modules/uni-popup/changelog.md
  28. 45 0
      uni_modules/uni-popup/components/uni-popup-dialog/keypress.js
  29. 316 0
      uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue
  30. 143 0
      uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue
  31. 187 0
      uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue
  32. 7 0
      uni_modules/uni-popup/components/uni-popup/i18n/en.json
  33. 8 0
      uni_modules/uni-popup/components/uni-popup/i18n/index.js
  34. 7 0
      uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json
  35. 7 0
      uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json
  36. 45 0
      uni_modules/uni-popup/components/uni-popup/keypress.js
  37. 26 0
      uni_modules/uni-popup/components/uni-popup/popup.js
  38. 90 0
      uni_modules/uni-popup/components/uni-popup/uni-popup.uvue
  39. 518 0
      uni_modules/uni-popup/components/uni-popup/uni-popup.vue
  40. 88 0
      uni_modules/uni-popup/package.json
  41. 17 0
      uni_modules/uni-popup/readme.md
  42. 18 0
      uni_modules/uni-steps/changelog.md
  43. 280 0
      uni_modules/uni-steps/components/uni-steps/uni-steps.vue
  44. 87 0
      uni_modules/uni-steps/package.json
  45. 13 0
      uni_modules/uni-steps/readme.md
  46. 24 0
      uni_modules/uni-transition/changelog.md
  47. 131 0
      uni_modules/uni-transition/components/uni-transition/createAnimation.js
  48. 286 0
      uni_modules/uni-transition/components/uni-transition/uni-transition.vue
  49. 85 0
      uni_modules/uni-transition/package.json
  50. 11 0
      uni_modules/uni-transition/readme.md

+ 31 - 2
App.vue

@@ -1,5 +1,10 @@
1 1
 <script>
2 2
   export default {
3
+		data(){
4
+			return{
5
+				lastTapTime: 0, // 记录上次点击的时间
6
+			}
7
+		},
3 8
     onLaunch: function() {
4 9
       console.log('App Launch');
5 10
       //#ifdef APP-PLUS
@@ -67,17 +72,37 @@
67 72
     },
68 73
     onShow: function() {
69 74
       console.log('App Show');
75
+			let that = this
76
+			// #ifdef APP-PLUS
77
+			plus.key.addEventListener('backbutton', that.handleBackButton)
78
+			// #endif
70 79
       // uni.redirectTo({
71 80
       //   url: "../mypage/mypage",
72 81
       // });
73 82
     },
74 83
     onHide: function() {
75 84
       console.log('App Hide');
85
+			let that = this
86
+			// #ifdef APP-PLUS
87
+			plus.key.removeEventListener('backbutton', that.handleBackButton)
88
+			// #endif
76 89
       // uni.closeSocket();
77 90
       // uni.onSocketClose(function(res) {
78 91
       //   console.log('WebSocket 已关闭!');
79 92
       // });
80
-    }
93
+    },
94
+		methods: {
95
+			handleBackButton() {
96
+				let currentTime = new Date().getTime(); // 获取当前点击时间
97
+				if (currentTime - this.lastTapTime < 500) { // 如果两次点击间隔在500毫秒以内,认为是双击
98
+					// 双击事件处理逻辑
99
+					console.log('双击了返回键');
100
+					// 你的双击事件处理代码
101
+					plus.runtime.quit(); // 示例:用户双击返回键时,执行页面返回操作
102
+				}
103
+				this.lastTapTime = currentTime; // 更新最后点击时间
104
+			}
105
+		}
81 106
   }
82 107
 </script>
83 108
 
@@ -121,7 +146,11 @@
121 146
   .uni-picker-action-confirm {
122 147
     color: #49b856 !important;
123 148
   }
124
-
149
+	
150
+	.selected-item-active{
151
+		border-color: #49b856 !important;;
152
+	}
153
+	
125 154
   .footerPadding {
126 155
     padding-bottom: 50rpx !important;
127 156
   }

+ 7 - 4
components/uni-datetime-picker/uni-datetime-picker.vue

@@ -6,18 +6,19 @@
6 6
 		'uni-date-x--border': border}">
7 7
 					<view v-if="!isRange" class="uni-date-x uni-date-single">
8 8
 						<uni-icons type="calendar" color="#c0c4cc" size="22"></uni-icons>
9
-						<input class="uni-date__x-input" type="text" v-model="singleVal"
10
-							:placeholder="singlePlaceholderText" :disabled="true" />
9
+<!-- 						<input class="uni-date__x-input" type="text" v-model="singleVal"
10
+							:placeholder="singlePlaceholderText" readonly /> -->
11
+						<view class="uni-date__x-input">{{singleVal}}</view>
11 12
 					</view>
12 13
 					<view v-else class="uni-date-x uni-date-range">
13 14
 						<uni-icons type="calendar" color="#c0c4cc" size="22"></uni-icons>
14 15
 						<input class="uni-date__x-input t-c" type="text" v-model="range.startDate"
15
-							:placeholder="startPlaceholderText" :disabled="true" />
16
+							:placeholder="startPlaceholderText" readonly />
16 17
 						<slot>
17 18
 							<view class="">{{rangeSeparator}}</view>
18 19
 						</slot>
19 20
 						<input class="uni-date__x-input t-c" type="text" v-model="range.endDate"
20
-							:placeholder="endPlaceholderText" :disabled="true" />
21
+							:placeholder="endPlaceholderText" readonly />
21 22
 					</view>
22 23
 					<view v-if="showClearIcon" class="uni-date__icon-clear" @click.stop="clear">
23 24
 						<uni-icons type="clear" color="#c0c4cc" size="24"></uni-icons>
@@ -837,6 +838,8 @@
837 838
 		line-height: 1;
838 839
 		font-size: 14px;
839 840
 		height: 35px;
841
+		display: flex;
842
+		align-items: center;
840 843
 	}
841 844
 
842 845
 	.t-c {

+ 10 - 3
components/uni-drawer/uni-drawer.vue

@@ -1,6 +1,6 @@
1 1
 <template>
2 2
 	<view v-if="visibleSync" :class="{ 'uni-drawer--visible': showDrawer }" class="uni-drawer">
3
-		<view class="uni-drawer__mask" :class="{ 'uni-drawer__mask--visible': showDrawer && mask }" @tap="close" />
3
+		<view class="uni-drawer__mask" :class="{ 'uni-drawer__mask--visible': showDrawer && mask }" @tap="close('mask')" />
4 4
 		<view class="uni-drawer__content" :class="{'uni-drawer--right': rightMode,'uni-drawer--left': !rightMode, 'uni-drawer__content--visible': showDrawer}">
5 5
 			<slot />
6 6
 		</view>
@@ -31,7 +31,11 @@
31 31
 			mask: {
32 32
 				type: Boolean,
33 33
 				default: true
34
-			}
34
+			},
35
+			maskClick:{
36
+				type: Boolean,
37
+				default: true
38
+			},
35 39
 		},
36 40
 		data() {
37 41
 			return {
@@ -58,7 +62,10 @@
58 62
 			this.rightMode = this.mode === 'right'
59 63
 		},
60 64
 		methods: {
61
-			close() {
65
+			close(type) {
66
+				if(type=='mask' && !this.maskClick){
67
+					return
68
+				}
62 69
 				this._change('showDrawer', 'visibleSync', false)
63 70
 			},
64 71
 			open() {

+ 9 - 4
pages/homePage/homePage.vue

@@ -738,7 +738,7 @@
738 738
 								let menu = res.user.menu.filter(i=>i.parentid=='278')
739 739
 								if(menu){
740 740
 									let isSpecimen = menu.filter(i=>i.title=='标本间')
741
-									if(isSpecimen){
741
+									if(isSpecimen.length>0){
742 742
 										// 建立websocket连接
743 743
 										webHandle("specimenPort", "app");
744 744
 										uni.setStorageSync("isSpecimen", '1');
@@ -747,7 +747,6 @@
747 747
 										webHandle("receiptpage", "wx");
748 748
 										uni.setStorageSync("isSpecimen", '0');
749 749
 									}
750
-									console.log(88888,menu)
751 750
 								}else{
752 751
 									// 建立websocket连接
753 752
 									webHandle("receiptpage", "wx");
@@ -827,7 +826,7 @@
827 826
 							let menu = user.menu.filter(i=>i.parentid=='278')
828 827
 							if(menu){
829 828
 								let isSpecimen = menu.filter(i=>i.title=='标本间')
830
-								if(isSpecimen){
829
+								if(isSpecimen.length>0){
831 830
 									// 建立websocket连接
832 831
 									webHandle("specimenPort", "app");
833 832
 									uni.setStorageSync("isSpecimen", '1');
@@ -836,7 +835,7 @@
836 835
 									webHandle("receiptpage", "wx");
837 836
 									uni.setStorageSync("isSpecimen", '0');
838 837
 								}
839
-								console.log(88888,menu)
838
+								console.log(88888,isSpecimen)
840 839
 							}else{
841 840
 								// 建立websocket连接
842 841
 								webHandle("receiptpage", "wx");
@@ -1093,6 +1092,12 @@
1093 1092
         return null;
1094 1093
       }
1095 1094
     },
1095
+		onBackPress(e) {
1096
+			// backbutton:物理按键返回
1097
+			if (e.from === 'backbutton') {
1098
+				return true 
1099
+			}
1100
+		},
1096 1101
     onHide() {
1097 1102
       this.zxzData = [];
1098 1103
       this.historys = [];

+ 379 - 1
pages/specimenPort/detail.vue

@@ -1,8 +1,386 @@
1 1
 <template>
2
+	<view class="conte">
3
+		<view class="header">
4
+			<view class="tabs" :class="tabsActive==index?'activeClass':''" 
5
+				v-for="(item, index) in tabs" @click="tabsClick(index)">
6
+				{{item.name}}
7
+			</view>
8
+		</view>
9
+		<view class="height-list"></view>
10
+		<view v-if="tabsActive==0 && infoData" class="info">
11
+			<view class="info-list no-center"><view class="info-title">申请单号:</view>{{infoData.applyCode}}</view>
12
+			<view class="info-list"><view class="info-title">状态:</view>{{infoData.status.name}}</view>
13
+			<view class="info-list"><view class="info-title">科室名称:</view>{{infoData.patientDTO.department.dept}}</view>
14
+			<view class="info-list no-center"><view class="info-title">患者姓名:</view>{{infoData.patientName}}({{infoData.patientDTO.bedNum}}床)</view>
15
+			<view class="info-list no-center"><view class="info-title">住院号:</view>{{infoData.patientDTO.patientCode}}</view>
16
+			<view class="info-list no-center"><view class="info-title">年龄:</view>{{infoData.patientDTO.age}}</view>
17
+			<view class="info-list"><view class="info-title">性别:</view>{{infoData.patientDTO.gender && infoData.patientDTO.gender.name || '-'}}</view>
18
+			<view class="info-list no-center">
19
+				<view class="info-title">检查项目:</view>
20
+				<view class="info-content">
21
+					{{examineName.join('、')}}
22
+				</view>
23
+			</view>
24
+			<view class="info-list"><view class="info-title">标本类型:</view>{{infoData.specimenType.name}}</view>
25
+			<view class="info-list no-center"><view class="info-title">标本数量:</view>{{infoData.specimenNum}}</view>
26
+			<view class="info-list no-center">
27
+				<view class="info-title">部位:</view>
28
+				<view class="info-content">
29
+					{{infoData.takePart}}
30
+				</view>
31
+			</view>
32
+			<view class="info-list"><view class="info-title">送检医生:</view>{{infoData.surgeryDoctorDTO.name}}</view>
33
+			<view class="info-list no-center"><view class="info-title">离体时间:</view>{{yyTimeFilter(infoData.inVitroTime)}}</view>
34
+			<view class="info-list no-center"><view class="info-title">固定时间:</view>{{yyTimeFilter(infoData.fixationTime)}}</view>
35
+			<view class="info-list no-center">
36
+				<view class="info-title">诊断:</view>
37
+				<view class="info-content">
38
+					{{infoData.diagnose}}
39
+				</view>
40
+			</view>
41
+			<view class="info-list no-center">
42
+				<view class="info-title">病历摘要:</view>
43
+				<view class="info-content">
44
+					{{infoData.medicalRecords}}
45
+				</view>
46
+			</view>
47
+			<view class="info-list no-center">
48
+				<view class="info-title">手术方案:</view>
49
+				<view class="info-content">
50
+					{{infoData.surgicalPlan}}
51
+				</view>
52
+			</view>
53
+		</view>
54
+		<view v-if="tabsActive==1" class="list-body">
55
+			<view class="list" v-for="(item, index) in list" :key="index">
56
+				<view class="df-list">
57
+					<view class="list-title">标本名称:</view>{{item.specimenName}}
58
+				</view>
59
+				<view class="df-list">
60
+					<view class="list-title">条码号:</view>{{item.specimenCode}}
61
+				</view>
62
+				<view class="list-content"></view>
63
+			</view>
64
+		</view>
65
+		<view v-if="tabsActive==2 && stepsList.length" class="info-log">
66
+			<uni-section type="line" padding>
67
+				<uni-steps :options="stepsList" active-color="#49B856" :active="stepsActive" direction="column" />
68
+			</uni-section>
69
+		</view>
70
+		<view class="btn-view">
71
+			<button class="back-btn" @click="back">返回</button>
72
+		</view>
73
+		<!-- PDA扫描 -->
74
+		<scanner></scanner>
75
+	</view>
2 76
 </template>
3 77
 
4 78
 <script>
79
+	import {
80
+		get,
81
+		post
82
+	} from "../../http/http.js";
83
+	import scanner from "../../components/scanner/scanner.vue";
84
+	export default {
85
+		data() {
86
+			return {
87
+				tabs: [
88
+					{name:'申请单'},
89
+					{name:'标本信息'},
90
+					{name:'历史记录'},
91
+				],
92
+				tabsActive:0,
93
+				infoData:null,
94
+				list:[],
95
+				idx: 0,
96
+				userInfo:uni.getStorageSync('userData').user,
97
+				hosId: uni.getStorageSync('userData').user.currentHospital.id,
98
+				stepsList: [],
99
+				stepsActive:0,
100
+				SMFlag:true,
101
+			}
102
+		},
103
+		components: {
104
+			scanner
105
+		},
106
+		methods: {
107
+			back(){
108
+				uni.navigateTo({
109
+				  url: `/pages/specimenPort/specimenPort?backType=detail`
110
+				});
111
+			},
112
+			// 时间格式化
113
+			yyTimeFilter(timestamp) {
114
+			  var date = new Date(timestamp); // 时间戳为毫秒级别
115
+				var year = date.getFullYear();
116
+				var month = date.getMonth() + 1;
117
+				var day = date.getDate();
118
+				var hours = date.getHours();
119
+				var minutes = date.getMinutes();
120
+				var seconds = date.getSeconds();
121
+			 
122
+				// 格式化月份、日期、小时、分钟、秒
123
+				month = month < 10 ? '0' + month : month;
124
+				day = day < 10 ? '0' + day : day;
125
+				hours = hours < 10 ? '0' + hours : hours;
126
+				minutes = minutes < 10 ? '0' + minutes : minutes;
127
+				seconds = seconds < 10 ? '0' + seconds : seconds;
128
+			 
129
+				return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
130
+			},
131
+			// 切换tabs
132
+			tabsClick(index) {
133
+				this.idx = 0;
134
+				this.tabsActive = index;
135
+			},
136
+			// 详情数据
137
+			getDetail(detailId) {
138
+				uni.showLoading({
139
+					title: "加载中",
140
+					mask: true,
141
+				});
142
+				get(`/data/fetchData/pathologyForm/${detailId}`).then(res => {
143
+					uni.hideLoading();
144
+					this.infoData = res.data
145
+					this.examineName = res.data.pathologyInspectDTOS.map(i=>i.inspectProject.name)
146
+					this.stepsList = res.data.formLogDTOS.map(i=>{
147
+						if(i.handoverUserDto){
148
+							return{
149
+								title:i.operationType.name+' '+ i.handoverUserDto.name,
150
+								desc:this.yyTimeFilter(i.createTime) +' '+ i.operationUserDto.name
151
+							}
152
+						}else{
153
+							return{
154
+								title:i.operationType.name,
155
+								desc:this.yyTimeFilter(i.createTime) +' '+ i.operationUserDto.name
156
+							}
157
+						}
158
+					})
159
+					this.stepsActive = this.stepsList.length-1
160
+					this.list = res.data.pathologySpecimenDTOList
161
+				});
162
+			},
163
+			// PDA扫描
164
+			padChange(scannerCode){
165
+				scannerCode = scannerCode.replace('\n','')
166
+				if (!this.SMFlag) {
167
+				  return;
168
+				}
169
+				this.SMFlag = false;
170
+				uni.showLoading({
171
+					title: "加载中",
172
+					mask: true,
173
+				});
174
+				post("/pathology/scanCode", {
175
+					barcode:scannerCode,
176
+					platform:"app"
177
+				}).then((res) => {
178
+					this.SMFlag = true
179
+					uni.hideLoading();
180
+					if(res.status==200){
181
+						if(res.data[0].status.name=='标本离体'){
182
+							uni.navigateTo({
183
+							  url: `/pages/specimenPort/scan?data=${JSON.stringify(res.data[0])}&type=${res.type}`
184
+							});
185
+						}else{
186
+							uni.showToast({
187
+								title: '查询到标本申请单信息',
188
+								duration: 1000,
189
+								icon:'none'
190
+							});
191
+							setTimeout(_=>{
192
+								uni.navigateTo({
193
+								  url: `/pages/specimenPort/detail?detailId=${res.data[0].id}`
194
+								});
195
+							},800)
196
+						}
197
+					}else{
198
+						uni.showModal({
199
+						  title: "提示",
200
+						  content: `${res.msg} \n 扫描内容:${scannerCode}`,
201
+							confirmColor:'#49b856',
202
+						  success: (res) => {
203
+						    if (res.confirm) {
204
+						      console.log("用户点击确定");
205
+						    } else if (res.cancel) {
206
+						      console.log("用户点击取消");
207
+						    }
208
+						  },
209
+						});
210
+					}
211
+				})
212
+			},
213
+		},
214
+		onBackPress(e) {
215
+			// backbutton:物理按键返回
216
+			if (e.from === 'backbutton') {
217
+				this.back()
218
+				return true 
219
+			}
220
+		},
221
+		onShow() {
222
+			let that = this
223
+			// #ifdef APP-PLUS
224
+			uni.$off('scan') // 每次进来先 移除全局自定义事件监听器
225
+			uni.$on('scan', function(data) {
226
+				that.padChange(data)
227
+			})
228
+			// #endif
229
+		},
230
+		onLoad(options) {
231
+			this.getDetail(options.detailId)
232
+		}
233
+	}
5 234
 </script>
6 235
 
7
-<style>
236
+<style lang="less" scoped>
237
+	page {
238
+		background: #F7F7F7;
239
+	}
240
+
241
+	.conte {
242
+		.header {
243
+			display: flex;
244
+			position: fixed;
245
+			width: 100%;
246
+			background: #fff;
247
+			z-index: 999;
248
+			.tabs{
249
+				height: 80rpx;
250
+				display: flex;
251
+				align-items: center;
252
+				justify-content: center;
253
+				border-bottom: 1px solid #fff;
254
+				flex: 1;
255
+			}
256
+		}
257
+		.activeClass{
258
+			border-bottom: 1px solid #49B856 !important;
259
+			color: #49B856;
260
+		}
261
+		.info{
262
+			padding: 0 20rpx;
263
+			background: #fff;
264
+			height: calc(100vh - 180rpx);
265
+			overflow-y: auto;
266
+			.info-list{
267
+				padding: 14rpx 0;
268
+				display: flex;
269
+				align-items: center;
270
+				font-size: 28rpx;
271
+				color: #555555;
272
+				.info-title{
273
+					color: #000;
274
+				}
275
+				.info-content{
276
+					flex: 1;
277
+				}
278
+			}
279
+			.no-center{
280
+				align-items: baseline;
281
+			}
282
+		}
283
+		.height-list {
284
+			height: 44px;
285
+		}
286
+		.list-body{
287
+			height: calc(100vh - 180rpx);
288
+			overflow-y: auto;
289
+			.list {
290
+				// margin-top: 20rpx;
291
+				background: #fff;
292
+				.list-content{
293
+					height: 20rpx;
294
+					background: #F7F7F7;
295
+				}
296
+				.df-list {
297
+					display: flex;
298
+					padding: 14rpx 20rpx;
299
+					font-size: 28rpx;
300
+					color: #555555;
301
+					.list-title{
302
+						color: #000;
303
+					}
304
+				}
305
+			}
306
+		}
307
+		.info-log{
308
+			background: #fff;
309
+			height: calc(100vh - 80rpx);
310
+		}
311
+		.df {
312
+			display: flex;
313
+			height: 80rpx;
314
+			align-items: center;
315
+			padding: 0 20rpx;
316
+			border-bottom: 1px solid #D2D2D2;
317
+		}
318
+		.execFilterMask{
319
+		  .execFilter{
320
+		    position: relative;
321
+		    background-color: #fff;
322
+		    height: calc(100vh - 80rpx);
323
+		    
324
+		    .execFilterHeader{
325
+		      height: 70rpx;
326
+		      display: flex;
327
+		      justify-content: center;
328
+		      align-items: center;
329
+		      border-bottom: 2rpx solid #ccc;
330
+		    }
331
+		    
332
+		    .execFilterBody{
333
+		      .execFilterItem{
334
+		        padding: 32rpx;
335
+		        border-bottom: 2rpx dashed #ccc;
336
+		        &:last-of-type{
337
+		          border-bottom: none;
338
+		        }
339
+		        .execFilterItemHeader{}
340
+		        .execFilterItemBody{
341
+		          display: flex;
342
+		          flex-wrap: wrap;
343
+		          padding: 32rpx 0 0;
344
+		          justify-content: space-between;
345
+		          text-align: left;
346
+		          
347
+		          .execFilterItemBox{
348
+		            width: 200rpx;
349
+		            height: 80rpx;
350
+		            text-align: center;
351
+		            line-height: 80rpx;
352
+		            background-color: #f6f6f6;
353
+		            margin-bottom: 20rpx;
354
+		            &.active{
355
+		              color: #fff;
356
+		              background-color: #49b856;
357
+		            }
358
+		          }
359
+		          
360
+		          .deptName {
361
+		            height: 80rpx;
362
+		            background-color: #f6f6f6;
363
+		            padding: 0 20rpx;
364
+								width: 100%;
365
+		          }
366
+		        }
367
+		      }
368
+		    }
369
+		  }
370
+		}
371
+		.btn-view{
372
+			width: 100%;
373
+			bottom: 20rpx;
374
+			position: fixed;
375
+			.back-btn{
376
+				background: #49B856;
377
+				border-radius: 10rpx;
378
+				color: #fff;
379
+				font-size: 30rpx;
380
+				text-align: center;
381
+				line-height: 80rpx;
382
+				width: 95%;
383
+			}
384
+		}
385
+	}
8 386
 </style>

+ 564 - 1
pages/specimenPort/scan.vue

@@ -1,8 +1,571 @@
1 1
 <template>
2
+	<view class="content">
3
+		<view class="info">
4
+			<view class="header">
5
+				<view class="title" :class="list.length < infoData.specimenNum?'par-top1':'par-top2'">{{list.length}}/{{infoData.specimenNum}}</view>
6
+				<view class="tip" v-if="list.length < infoData.specimenNum">请继续扫描标本!</view>
7
+			</view>
8
+			<view>
9
+				<view class="list" v-for="(item, index) in list" :key="index">
10
+					<view class="df-list">
11
+						<view class="list-title">标本名称:</view>
12
+						<view class="no-center">{{item.specimenName}}</view>
13
+					</view>
14
+					<view class="df-list">
15
+						<view class="list-title">条码号:</view>
16
+						<view class="no-center">{{item.specimenCode}}</view>
17
+					</view>
18
+				</view>
19
+			</view>
20
+			<view class="list list-top" v-if="infoData">
21
+				<view class="df-list">
22
+					<view class="list-title">患者姓名:</view>
23
+					<view class="no-center">{{infoData.patientName}}({{infoData.patientDTO.bedNum}}床)</view>
24
+				</view>
25
+				<view class="df-list">
26
+					<view class="list-title">住院号:</view>
27
+					<view class="no-center">{{infoData.patientDTO.barCode}}</view>
28
+				</view>
29
+				<view class="df-list">
30
+					<view class="list-title">科室名称:</view>
31
+					<view class="no-center">{{infoData.patientDTO.department.dept}}</view>
32
+				</view>
33
+				<view class="df-list">
34
+					<view class="list-title">申请单号:</view>
35
+					<view class="no-center">{{infoData.applyCode}}</view>
36
+				</view>
37
+			</view>
38
+		</view>
39
+		<view class="btn-view" v-if="list.length < infoData.specimenNum">
40
+			<button class="back-btn" @click="back">返回</button>
41
+		</view>
42
+		<view class="btn-view df-sb" v-if="list.length == infoData.specimenNum">
43
+			<button class="back-btn2" @click="back">返回</button>
44
+			<button class="back-btn3" @click="fixation">标本固定存放</button>
45
+		</view>
46
+		<!-- PDA扫描 -->
47
+		<scanner></scanner>
48
+		<!-- 标本固定 -->
49
+		<uni-popup ref="popup" type="dialog" background-color="#fff" :mask-click="false">
50
+			<view class="pop-content">
51
+				<view class="pop-title">提示</view>
52
+				<view class="pop-tip">您标本已扫描完成,请填写固定信息。</view>
53
+				<uni-forms ref="baseForm" :model="form" label-width="110px">
54
+					<uni-forms-item label="工号" required>
55
+						<uni-easyinput v-model="form.code" :clearable="false" @input="inputChange" primaryColor='#49b856' placeholder="请输入工号" />
56
+					</uni-forms-item>
57
+					<uni-forms-item label="姓名" required>
58
+						<view class="text-left">{{form.name}}</view>
59
+					</uni-forms-item>
60
+					<uni-forms-item label="固体液类型" required class="select">
61
+						<uni-data-picker placeholder="请选择固体液类型" :clear-icon="false" :localdata="fixedData" v-model="form.fixative"
62
+						 :map="{text:'name',value:'id'}">
63
+						</uni-data-picker>
64
+					</uni-forms-item>
65
+					<uni-forms-item label="固定时间" required class="time">
66
+						<uni-datetime-picker type="datetime" :clear-icon="false" placeholder="请选择固定时间" v-model="form.fixationTime"/>
67
+					</uni-forms-item>
68
+				</uni-forms>
69
+				<view class="pou-btn">
70
+					<view class="btn border-right" @click="fixationClose">取消</view>
71
+					<view class="btn confirm" @click="dialogConfirm">固定标本</view>
72
+				</view>
73
+			</view>
74
+		</uni-popup>
75
+	</view>
2 76
 </template>
3 77
 
4 78
 <script>
79
+	import {
80
+		get,
81
+		post
82
+	} from "../../http/http.js";
83
+	import scanner from "../../components/scanner/scanner.vue";
84
+	export default {
85
+		data(){
86
+			return{
87
+				infoData:null,
88
+				list:[],
89
+				SMFlag:true,
90
+				fixationModal:false,
91
+				fixedData:[],
92
+				styles:{
93
+					borderColor: '#49b856',
94
+				},
95
+				userInfo:uni.getStorageSync('userData').user,
96
+				hosId: uni.getStorageSync('userData').user.currentHospital.id,
97
+				form:{
98
+					name:'',
99
+					code:'',
100
+					fixationTime:'',
101
+					fixative:'',
102
+					handoverUserId:''
103
+				},
104
+				pathologyFormId:null
105
+			}
106
+		},
107
+		components: {
108
+			scanner
109
+		},
110
+		methods:{
111
+			// 时间格式化
112
+			yyTimeFilter(timestamp) {
113
+			  var date = new Date(timestamp); // 时间戳为毫秒级别
114
+				var year = date.getFullYear();
115
+				var month = date.getMonth() + 1;
116
+				var day = date.getDate();
117
+				var hours = date.getHours();
118
+				var minutes = date.getMinutes();
119
+				var seconds = date.getSeconds();
120
+			 
121
+				// 格式化月份、日期、小时、分钟、秒
122
+				month = month < 10 ? '0' + month : month;
123
+				day = day < 10 ? '0' + day : day;
124
+				hours = hours < 10 ? '0' + hours : hours;
125
+				minutes = minutes < 10 ? '0' + minutes : minutes;
126
+				seconds = seconds < 10 ? '0' + seconds : seconds;
127
+			 
128
+				return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
129
+			},
130
+			inputChange(e){
131
+				console.log(e)
132
+				if(e.length < 3) {
133
+					return
134
+				}
135
+				let query = {
136
+				  account: e
137
+				};
138
+				post('/data/isRepeat',query).then((data) => {
139
+						if(data.status==200){
140
+							this.form.name = data.userName;
141
+							this.form.handoverUserId = data.userId
142
+						}else{
143
+							this.form.name = '';
144
+							this.form.handoverUserId =null
145
+						}
146
+				  });
147
+			},
148
+			back(){
149
+				// this.padChange('86386627710976\n')
150
+				uni.setStorageSync('pathologyFormId', null)
151
+				uni.navigateTo({
152
+				  url: `/pages/specimenPort/specimenPort?backType=detail`
153
+				});
154
+			},
155
+			// 标本固定
156
+			fixation(){
157
+				this.form={
158
+					name:'',
159
+					code:'',
160
+					fixationTime: Date.now() - 2*24*1000,
161
+					fixative:this.fixedData[0].id,
162
+					handoverUserId:''
163
+				}
164
+				// this.form.fixationTime = Date.now() - 2*24*1000
165
+				this.$refs.popup.open()
166
+			},
167
+			// 获取固定液类型
168
+			getFixation(){
169
+				post('/common/common/getDictionary', {
170
+					key: "fixing_liquid_type",
171
+					type: "list"
172
+				}).then((res) => {
173
+					this.fixedData = res
174
+				});
175
+			},
176
+			fixationClose(){
177
+				this.$refs.popup.close()
178
+			},
179
+			// 确定固定标本
180
+			dialogConfirm(){
181
+				if(!this.form.code){
182
+					uni.showToast({
183
+						title: '工号不能为空',
184
+						duration: 1000,
185
+						icon:'none'
186
+					});
187
+					return
188
+				}
189
+				if(!this.form.name){
190
+					uni.showToast({
191
+						title: '姓名不能为空',
192
+						duration: 1000,
193
+						icon:'none'
194
+					});
195
+					return
196
+				}
197
+				if(!this.form.fixative){
198
+					uni.showToast({
199
+						title: '固体液类型不能为空',
200
+						duration: 1000,
201
+						icon:'none'
202
+					});
203
+					return
204
+				}
205
+				if(!this.form.fixationTime){
206
+					uni.showToast({
207
+						title: '固定时间不能为空',
208
+						duration: 1000,
209
+						icon:'none'
210
+					});
211
+					return
212
+				}
213
+				let time = null
214
+				if(Number.isInteger(this.form.fixationTime) && this.form.fixationTime > 0){
215
+					time = this.yyTimeFilter(this.form.fixationTime)
216
+				}else{
217
+					time = this.form.fixationTime
218
+				}
219
+				uni.showLoading({
220
+					title: "加载中",
221
+					mask: true,
222
+				});
223
+				let query={
224
+					hosId:this.hosId,
225
+					id:this.infoData.id,
226
+					specimenFixingLiquid:{
227
+						id:this.form.fixative
228
+					},
229
+					handoverUserId:this.form.handoverUserId,
230
+					fixationTime:time,
231
+					specimenDeptId:this.userInfo.dept.id
232
+				}
233
+				post('/simple/data/addData/pathologyForm',query).then((res) => {
234
+					uni.hideLoading();
235
+					this.$refs.popup.close()
236
+					uni.showToast({
237
+						title: '固定成功',
238
+						duration: 1000
239
+					});
240
+					setTimeout(_=>{
241
+						this.back()
242
+					},1000)
243
+				});
244
+			},
245
+			// PDA扫描
246
+			padChange(scannerCode){
247
+				scannerCode = scannerCode.replace('\n','')
248
+				if (!this.SMFlag) {
249
+				  return;
250
+				}
251
+				this.SMFlag = false;
252
+				uni.showLoading({
253
+					title: "加载中",
254
+					mask: true,
255
+				});
256
+				this.pathologyFormId = uni.getStorageSync('pathologyFormId');
257
+				let query={
258
+					barcode:scannerCode,
259
+					pathologyFormId:this.pathologyFormId,
260
+					platform:"app"
261
+				}
262
+				if(!this.pathologyFormId){
263
+					delete query.pathologyFormId
264
+				}
265
+				post("/pathology/scanCode", query).then((res) => {
266
+					this.SMFlag = true
267
+					uni.hideLoading();
268
+					if(res.status==200){
269
+						uni.setStorageSync('pathologyFormId', res.data[0].id);
270
+						if(res.data[0].status.name=='标本离体'){
271
+							uni.navigateTo({
272
+							  url: `/pages/specimenPort/scan?data=${JSON.stringify(res.data[0])}&type=${res.type}`
273
+							});
274
+						}else{
275
+							uni.showToast({
276
+								title: '查询到标本申请单信息',
277
+								duration: 1000,
278
+								icon:'none'
279
+							});
280
+							setTimeout(_=>{
281
+								uni.navigateTo({
282
+								  url: `/pages/specimenPort/detail?detailId=${res.data[0].id}`
283
+								});
284
+							},800)
285
+						}
286
+					}else{
287
+						uni.showModal({
288
+						  title: "提示",
289
+						  content: `${res.msg} \n 扫描内容:${scannerCode}`,
290
+							confirmColor:'#49b856',
291
+						  success: (res) => {
292
+						    if (res.confirm) {
293
+						      console.log("用户点击确定");
294
+						    } else if (res.cancel) {
295
+						      console.log("用户点击取消");
296
+						    }
297
+						  },
298
+						});
299
+					}
300
+				})
301
+			}
302
+		},
303
+		onBackPress(e) {
304
+			// backbutton:物理按键返回
305
+			if (e.from === 'backbutton') {
306
+				this.back()
307
+				return true 
308
+			}
309
+		},
310
+		onShow() {
311
+			let that = this
312
+			// #ifdef APP-PLUS
313
+			uni.$off('scan') // 每次进来先 移除全局自定义事件监听器
314
+			uni.$on('scan', function(data) {
315
+				that.padChange(data)
316
+			})
317
+			// #endif
318
+		},
319
+		onLoad(option){
320
+			let data = JSON.parse(option.data)
321
+			this.infoData = data
322
+			this.type = option.type
323
+			this.list = data.pathologySpecimenDTOList?data.pathologySpecimenDTOList:[]
324
+			if(this.list.length == this.infoData.specimenNum){
325
+				uni.showToast({
326
+					title: '您标本已扫描完成,请填写固定信息',
327
+					duration: 2000,
328
+					icon:'none'
329
+				});
330
+			}
331
+			this.getFixation()
332
+		}
333
+	}
5 334
 </script>
6 335
 
7
-<style>
336
+<style scoped>
337
+	>>>	.uni-forms-item__content{
338
+		display: flex;
339
+		align-items: center;
340
+	}
341
+	>>>.uni-easyinput__content-input,
342
+	>>>.uni-input-wrapper,
343
+	>>>.uni-input-placeholder{
344
+		display: flex;
345
+		align-items: center;
346
+		line-height: 1;
347
+		height: 35px;
348
+	}
349
+	.select >>>.dialog-close{
350
+		/* display: flex !important; */
351
+	}
352
+	.select >>>.dialog-close-plus{
353
+	}
354
+	.select >>>.dialog-close-rotate{
355
+		top: 20.8px;
356
+		left: 14px;
357
+	}
358
+	.time >>>.dialog-close-rotate{
359
+		top: 24px;
360
+		left: 24.5px;
361
+	}
362
+</style>
363
+<style lang="less" scoped>
364
+	page {
365
+		background: #F8F8F8;
366
+	}
367
+	.content {
368
+		.header {
369
+			width: 100%;
370
+			height: 450rpx;
371
+			text-align: center;
372
+			.title{
373
+				color: red;
374
+				font-size: 70rpx;
375
+				font-weight: bold;
376
+			}
377
+			.par-top1{
378
+				padding-top: 100rpx;
379
+			}
380
+			.par-top2{
381
+				padding-top: 150rpx;
382
+			}
383
+			.tip{
384
+				font-size: 30rpx;
385
+				font-weight: bold;
386
+				padding: 40rpx 0;
387
+			}
388
+		}
389
+		.info{
390
+			// padding: 0 20rpx;
391
+			// background: #fff;
392
+			height: calc(100vh - 120rpx);
393
+			overflow-y: auto;
394
+			background: #F8F8F8;
395
+			.info-list{
396
+				padding: 14rpx 0;
397
+				display: flex;
398
+				font-size: 28rpx;
399
+				color: #555555;
400
+				.info-title{
401
+					color: #000;
402
+				}
403
+				.info-content{
404
+					flex: 1;
405
+				}
406
+			}
407
+		}
408
+		.height-list {
409
+			height: 44px;
410
+		}
411
+		.list {
412
+			background: #fff;
413
+			border-bottom: 1px solid #D2D2D2;
414
+			.df-list {
415
+				display: flex;
416
+				align-items: center;
417
+				padding: 14rpx 20rpx;
418
+				font-size: 28rpx;
419
+				color: #555555;
420
+				.no-center{
421
+					align-items: baseline;
422
+					line-height: 1;
423
+				}
424
+				.list-title{
425
+					color: #000;
426
+					line-height: 1;
427
+				}
428
+			}
429
+		}
430
+		.list-top{
431
+			margin-top: 20rpx;
432
+		}
433
+		.info-log{
434
+			background: #fff;
435
+			height: calc(100vh - 80rpx);
436
+		}
437
+		.df {
438
+			display: flex;
439
+			height: 80rpx;
440
+			align-items: center;
441
+			padding: 0 20rpx;
442
+			border-bottom: 1px solid #D2D2D2;
443
+		}
444
+		.execFilterMask{
445
+		  .execFilter{
446
+		    position: relative;
447
+		    background-color: #fff;
448
+		    height: calc(100vh - 80rpx);
449
+		    
450
+		    .execFilterHeader{
451
+		      height: 70rpx;
452
+		      display: flex;
453
+		      justify-content: center;
454
+		      align-items: center;
455
+		      border-bottom: 2rpx solid #ccc;
456
+		    }
457
+		    
458
+		    .execFilterBody{
459
+		      .execFilterItem{
460
+		        padding: 32rpx;
461
+		        border-bottom: 2rpx dashed #ccc;
462
+		        &:last-of-type{
463
+		          border-bottom: none;
464
+		        }
465
+		        .execFilterItemHeader{}
466
+		        .execFilterItemBody{
467
+		          display: flex;
468
+		          flex-wrap: wrap;
469
+		          padding: 32rpx 0 0;
470
+		          justify-content: space-between;
471
+		          text-align: left;
472
+		          
473
+		          .execFilterItemBox{
474
+		            width: 200rpx;
475
+		            height: 80rpx;
476
+		            text-align: center;
477
+		            line-height: 80rpx;
478
+		            background-color: #f6f6f6;
479
+		            margin-bottom: 20rpx;
480
+		            &.active{
481
+		              color: #fff;
482
+		              background-color: #49b856;
483
+		            }
484
+		          }
485
+		          
486
+		          .deptName {
487
+		            height: 80rpx;
488
+		            background-color: #f6f6f6;
489
+		            padding: 0 20rpx;
490
+								width: 100%;
491
+		          }
492
+		        }
493
+		      }
494
+		    }
495
+		  }
496
+		}
497
+		.btn-view{
498
+			width: 100%;
499
+			bottom: 20rpx;
500
+			position: fixed;
501
+			.back-btn{
502
+				background: #49B856;
503
+				border-radius: 10rpx;
504
+				color: #fff;
505
+				font-size: 30rpx;
506
+				text-align: center;
507
+				line-height: 80rpx;
508
+				width: 95%;
509
+			}
510
+			.back-btn2{
511
+				background: #8F939C;
512
+				border-radius: 10rpx;
513
+				color: #fff;
514
+				font-size: 30rpx;
515
+				text-align: center;
516
+				line-height: 80rpx;
517
+				width: 45%;
518
+			}
519
+			.back-btn3{
520
+				background: #49B856;
521
+				border-radius: 10rpx;
522
+				color: #fff;
523
+				font-size: 30rpx;
524
+				text-align: center;
525
+				line-height: 80rpx;
526
+				width: 45%;
527
+			}
528
+		}
529
+		.df-sb{
530
+			display: flex;
531
+			justify-content: space-between;
532
+		}
533
+		.pop-content{
534
+			text-align: center;
535
+			.pop-title{
536
+				font-size: 32rpx;
537
+				color: #000;
538
+				margin-top: 20rpx;
539
+			}
540
+			.pop-tip{
541
+				font-size: 28rpx;
542
+				color: #6C6C6C;
543
+				margin-top: 10rpx;
544
+			}
545
+			.uni-forms{
546
+				padding: 20rpx 20rpx 0 20rpx;
547
+			}
548
+			.text-left{
549
+				text-align: left;
550
+			}
551
+			.pou-btn{
552
+				color: #000;
553
+				font-size: 30rpx;
554
+				border-top: 1px solid #E3E3E3;
555
+				display: flex;
556
+				padding: 0 20rpx;
557
+				.btn{
558
+					text-align: center;
559
+					line-height: 80rpx;
560
+					width: 50%;
561
+				}
562
+			}
563
+			.border-right{
564
+				border-right: 1px solid #E3E3E3;
565
+			}
566
+			.confirm{
567
+				color: #49B856 !important;
568
+			}
569
+		}
570
+	}
8 571
 </style>

+ 470 - 26
pages/specimenPort/specimenPort.vue

@@ -1,58 +1,414 @@
1 1
 <template>
2 2
 	<view class="conte">
3 3
 		<view class="header">
4
-			标本间
5
-	<!-- 		<view class="filter" @click="filterClick">
6
-			  <text class="newicon newicon-shaixuan"></text>
7
-			</view> -->
4
+			<text class="title">{{userInfo.dept.dept}}</text>
5
+			<view class="filter" @click="filterClick">
6
+				<text class="newicon newicon-shaixuan icon"></text>
7
+			</view>
8 8
 		</view>
9
-		<view class="list df">
9
+		<view class="height-list"></view>
10
+		<view class="list" @click="detail(item)" v-for="(item, index) in list" :key="index">
10 11
 			<view class="df-list list-border">
11
-				<view>BL110110</view>
12
-				<view>未打印</view>
12
+				<view>{{item.applyCode}}</view>
13
+				<view>{{item.status.name}}</view>
13 14
 			</view>
14 15
 			<view class="df-list">
15
-				<view>申请科室:神经内科</view>
16
-				<view>部位:口腔</view>
16
+				<view class="df-list-over-left">申请科室:{{item.patientDTO.department.dept}}</view>
17
+				<view class="df-list-over">部位:{{item.takePart}}</view>
17 18
 			</view>
18 19
 			<view class="df-list">
19
-				<view>患者:神经内科</view>
20
-				<view>住院号:口腔</view>
20
+				<view class="df-list-over-left">患者:{{item.patientName}}({{item.patientDTO.bedNum}}床)</view>
21
+				<view class="df-list-over">住院号:{{item.patientDTO.barCode}}</view>
21 22
 			</view>
22 23
 			<view class="df-list">
23
-				<view>离:神经内科</view>
24
-				<view>固:口腔</view>
24
+				<view class="df-list-over-left">离:{{yyTimeFilter(item.inVitroTime)}}</view>
25
+				<view class="df-list-over">固:{{yyTimeFilter(item.fixationTime)}}</view>
25 26
 			</view>
27
+			<view class="list-content"></view>
26 28
 		</view>
29
+		<uni-drawer width="400px" :visible="isShowFilter" mode="right" :maskClick="false" @close="filterCacel()">
30
+			<view class="execFilterMask" @touchmove.stop.prevent>
31
+				<view class="execFilter">
32
+					<radio-group @change="radioChange">
33
+						<label class="df uni-list-cell uni-list-cell-pd" v-for="(item, index) in specimenData" :key="item.id">
34
+							<view>
35
+								<radio :value="item.id+''" activeBackgroundColor="#49B856" :checked="index == current" />
36
+							</view>
37
+							<view>{{item.dept}}</view>
38
+						</label>
39
+					</radio-group>
40
+					<view class="execFilterFooter">
41
+						<view class="btn" @click="filterCacel()" v-if="isDept || specimenData.length==0">取消</view>
42
+						<view class="btn" @click="filterOk()">确认</view>
43
+					</view>
44
+				</view>
45
+			</view>
46
+		</uni-drawer>
47
+		<!-- PDA扫描 -->
48
+		<scanner></scanner>
27 49
 	</view>
28 50
 </template>
29 51
 
30 52
 <script>
53
+	import {
54
+		get,
55
+		post
56
+	} from "../../http/http.js";
57
+	import scanner from "../../components/scanner/scanner.vue";
58
+	export default {
59
+		data() {
60
+			return {
61
+				list: [],
62
+				idx: 0,
63
+				isDept:null,
64
+				userInfo:uni.getStorageSync('userData').user,
65
+				hosId: uni.getStorageSync('userData').user.currentHospital.id,
66
+				specimenData: [],
67
+				isShowFilter: false,
68
+				current: null,
69
+				deptId:null,
70
+				totalNum:0,
71
+				hasMore:true,
72
+				SMFlag:true,
73
+			}
74
+		},
75
+		components: {
76
+			scanner
77
+		},
78
+		methods: {
79
+			// 时间格式化
80
+			yyTimeFilter(timestamp) {
81
+			  var date = new Date(timestamp); // 时间戳为毫秒级别
82
+				var year = date.getFullYear();
83
+				var month = date.getMonth() + 1;
84
+				var day = date.getDate();
85
+				var hours = date.getHours();
86
+				var minutes = date.getMinutes();
87
+				var seconds = date.getSeconds();
88
+			 
89
+				// 格式化月份、日期、小时、分钟、秒
90
+				month = month < 10 ? '0' + month : month;
91
+				day = day < 10 ? '0' + day : day;
92
+				hours = hours < 10 ? '0' + hours : hours;
93
+				minutes = minutes < 10 ? '0' + minutes : minutes;
94
+				seconds = seconds < 10 ? '0' + seconds : seconds;
95
+			 
96
+				return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
97
+			},
98
+			// 打开科室选择
99
+			filterClick() {
100
+				this.isShowFilter = true
101
+				this.$nextTick(_=>{
102
+					let userInfo = uni.getStorageSync('userData').user
103
+					if(this.isDept){
104
+						for (let i = 0; i < this.specimenData.length; i++) {
105
+							if (this.specimenData[i].id == userInfo.dept.id) {
106
+								this.current = i;
107
+								this.deptId = userInfo.dept.id;
108
+								break;
109
+							}
110
+						}
111
+					}
112
+				})
113
+			},
114
+			// 关闭科室弹框
115
+			filterCacel() {
116
+				this.isShowFilter = false
117
+			},
118
+			// 确定关联科室
119
+			filterOk() {
120
+				let userInfo = uni.getStorageSync('userData').user
121
+				if(!this.isDept){
122
+					if(!this.deptId){
123
+						uni.showToast({
124
+							title: '请选择科室',
125
+							duration: 1000,
126
+							icon:'none'
127
+						});
128
+						return
129
+					}
130
+				}
131
+				uni.showLoading({
132
+					title: "加载中",
133
+					mask: true,
134
+				});
135
+				let query = {
136
+					user: {
137
+						dept:{
138
+							id:this.deptId?this.deptId:userInfo.dept.id
139
+						},
140
+						id:userInfo.id
141
+					},
142
+				}
143
+				post('/data/updData/user', query).then(res => {
144
+					uni.hideLoading();
145
+					this.filterCacel();
146
+					this.getNewDept()
147
+					this.idx = 0
148
+					this.getList()
149
+				});
150
+			},
151
+			getNewDept(){
152
+				let postData = {
153
+				  currentHosId: this.hosId,
154
+				  loginType: "Wechat",
155
+				};
156
+				post('/auth/changeHospital',postData).then((result) => {
157
+				  if (result.status == 200) {
158
+						this.getCurrentUserNow();
159
+				  }
160
+				});
161
+			},
162
+			// 获取当前用户信息
163
+			getCurrentUserNow() {
164
+			  get('/user/data/getCurrentUser').then((data) => {
165
+			    if (data["status"] == 200) {
166
+						this.loginUser = data.data 
167
+			      let user = uni.getStorageSync('userData');
168
+			      user.user.dept = data["data"].dept;
169
+			      user.user.currentHospital = data["data"].currentHospital;
170
+						this.userInfo.dept.dept = data["data"].dept.dept;
171
+						uni.setStorageSync('userData',user)
172
+						let userInfo = uni.getStorageSync('userData').user
173
+						this.isDept = this.specimenData.find(i=>i.id == userInfo.dept.id)
174
+			    }
175
+			  });
176
+			},
177
+			// 选择科室
178
+			radioChange(evt) {
179
+				this.deptId = evt.detail.value
180
+				for (let i = 0; i < this.specimenData.length; i++) {
181
+					if (this.specimenData[i].id == evt.detail.value) {
182
+						this.current = i;
183
+						break;
184
+					}
185
+				}
186
+			},
187
+			// 查看详情
188
+			detail(item){
189
+				// this.padChange('BL2410251554361\n')
190
+				uni.navigateTo({
191
+				  url: `/pages/specimenPort/detail?detailId=${item.id}`
192
+				});
193
+			},
194
+			// 获取标本间科室
195
+			getDept(backType) {
196
+				post('/common/common/getDictionary', {
197
+					key: "dept_type",
198
+					type: "list"
199
+				}).then((res2) => {
200
+					let item2 = res2.find(i => i.name == '标本间科室')
201
+					let query = {
202
+						idx: 0,
203
+						sum: 9999,
204
+						department: {
205
+							hospital: {
206
+								id: this.hosId || ""
207
+							},
208
+							type: {
209
+								id: item2.id || ""
210
+							},
211
+						},
212
+					};
213
+			
214
+					post('/data/fetchDataList/department', query).then((res) => {
215
+							this.specimenData = res.list
216
+							if(!backType){
217
+								this.isShowFilter = true
218
+							}
219
+							let userInfo = uni.getStorageSync('userData').user
220
+							this.isDept = this.specimenData.find(i=>i.id == userInfo.dept.id)
221
+							this.$nextTick(_=>{
222
+								if(this.isDept){
223
+									for (let i = 0; i < this.specimenData.length; i++) {
224
+										if (this.specimenData[i].id === userInfo.dept.id) {
225
+											this.current = i;
226
+											break;
227
+										}
228
+									}
229
+								}
230
+							})
231
+							if(this.isDept){
232
+								this.getList()
233
+							}
234
+						});
235
+				});
236
+			},
237
+			// 分页数据
238
+			getList() {
239
+				uni.showLoading({
240
+					title: "加载中",
241
+					mask: true,
242
+				});
243
+				let query = {
244
+					idx: this.idx,
245
+					pathologyForm: {
246
+						detailsType: "specimenDept",
247
+						keyWords: "",
248
+						hosId: this.hosId,
249
+					},
250
+					sum: 20
251
+				}
252
+				post('/data/fetchDataList/pathologyForm', query).then(res => {
253
+					uni.hideLoading();
254
+					if(res.list.length){
255
+						this.hasMore = true;
256
+						this.list = this.idx === 0 ? res.list : this.list.concat(res.list);
257
+					}else{
258
+						if(this.idx == 0){
259
+							this.list = []
260
+						}
261
+						this.hasMore = false;
262
+					}
263
+					this.totalNum = res.totalNum;
264
+				});
265
+			},
266
+			// PDA扫描
267
+			padChange(scannerCode){
268
+				scannerCode = scannerCode.replace('\n','')
269
+				if (!this.SMFlag) {
270
+				  return;
271
+				}
272
+				this.SMFlag = false;
273
+				uni.showLoading({
274
+					title: "加载中",
275
+					mask: true,
276
+				});
277
+				post("/pathology/scanCode", {
278
+					barcode:scannerCode,
279
+					platform:"app"
280
+				}).then((res) => {
281
+					this.SMFlag = true
282
+					uni.hideLoading();
283
+					if(res.status==200){
284
+						if(res.data[0].status.name=='标本离体'){
285
+							uni.navigateTo({
286
+							  url: `/pages/specimenPort/scan?data=${JSON.stringify(res.data[0])}&type=${res.type}`
287
+							});
288
+						}else{
289
+							uni.showToast({
290
+								title: '查询到标本申请单信息',
291
+								duration: 1000,
292
+								icon:'none'
293
+							});
294
+							setTimeout(_=>{
295
+								uni.navigateTo({
296
+								  url: `/pages/specimenPort/detail?detailId=${res.data[0].id}`
297
+								});
298
+							},800)
299
+						}
300
+					}else{
301
+						uni.showModal({
302
+						  title: "提示",
303
+						  content: `${res.msg} \n 扫描内容:${scannerCode}`,
304
+							confirmColor:'#49b856',
305
+						  success: (res) => {
306
+						    if (res.confirm) {
307
+						      console.log("用户点击确定");
308
+						    } else if (res.cancel) {
309
+						      console.log("用户点击取消");
310
+						    }
311
+						  },
312
+						});
313
+					}
314
+				})
315
+			},
316
+		},
317
+		onReachBottom() {
318
+			this.idx++;
319
+			if (this.hasMore) {
320
+				this.getList(); // 当触底时加载更多数据
321
+			}
322
+		},
323
+		onBackPress(e) {
324
+			// backbutton:物理按键返回
325
+			if (e.from === 'backbutton') {
326
+				uni.navigateTo({
327
+				  url: `/pages/homePage/homePage`
328
+				});
329
+				return true 
330
+			}
331
+		},
332
+		onShow() {
333
+			let that = this
334
+			// #ifdef APP-PLUS
335
+			uni.$off('scan') // 每次进来先 移除全局自定义事件监听器
336
+			uni.$on('scan', function(data) {
337
+				that.padChange(data)
338
+			})
339
+			// #endif
340
+		},
341
+		onLoad(option) {
342
+			let backType = option.backType ? option.backType : null
343
+			this.getDept(backType)
344
+		}
345
+	}
31 346
 </script>
32 347
 
33 348
 <style lang="less" scoped>
34
-	.conte{
35
-		.header{
349
+	page {
350
+		background: #F7F7F7;
351
+	}
352
+
353
+	.conte {
354
+		.header {
36 355
 			height: 80rpx;
37
-			width: 100%;
38 356
 			display: flex;
39 357
 			justify-content: center;
40 358
 			align-items: center;
41
-			padding: 0 20rpx;
359
+			position: relative;
360
+			position: fixed;
361
+			width: 100%;
362
+			background: #F7F7F7;
363
+
364
+			.title {
365
+				color: #57BC63;
366
+				font-size: 32rpx;
367
+				font-weight: 500;
368
+			}
369
+
370
+			.filter {
371
+				position: absolute;
372
+				right: 15rpx;
373
+				top: 24rpx;
374
+				.icon{
375
+					font-size: 34rpx;
376
+				}
377
+			}
42 378
 		}
43
-		.list{
44
-			padding: 0 20rpx;
45
-			margin-bottom: 20rpx;
46
-			.df-list{
379
+
380
+		.height-list {
381
+			height: 44px;
382
+		}
383
+
384
+		.list {
385
+			// margin-bottom: 20rpx;
386
+			background: #fff;
387
+
388
+			.df-list {
47 389
 				display: flex;
48 390
 				justify-content: space-between;
49
-				padding: 10rpx 0;
391
+				padding: 14rpx 20rpx;
50 392
 				font-size: 28rpx;
51 393
 				color: #555555;
394
+				.df-list-over-left{
395
+					overflow: hidden;
396
+					flex: 1;
397
+					text-overflow: ellipsis;
398
+					white-space: nowrap;
399
+				}
400
+				.df-list-over{
401
+					overflow: hidden;
402
+					flex: 1;
403
+					text-overflow: ellipsis;
404
+					white-space: nowrap;
405
+					margin-left: 20rpx;
406
+				}
52 407
 			}
53
-			.list-border{
408
+
409
+			.list-border {
54 410
 				border-bottom: 1px solid #D2D2D2;
55
-				height: 80rpx;
411
+				height: 46rpx;
56 412
 				display: flex;
57 413
 				justify-content: space-between;
58 414
 				align-items: center;
@@ -60,9 +416,97 @@
60 416
 				font-weight: 500;
61 417
 				color: #000;
62 418
 			}
419
+			.list-content{
420
+				height: 22rpx;
421
+				background: #F7F7F7;
422
+			}
63 423
 		}
64
-		.df{
65
-			
424
+
425
+		.df {
426
+			display: flex;
427
+			height: 80rpx;
428
+			align-items: center;
429
+			padding: 0 20rpx;
430
+			border-bottom: 1px solid #D2D2D2;
431
+		}
432
+		.execFilterMask{
433
+		  .execFilter{
434
+		    position: relative;
435
+		    background-color: #fff;
436
+		    height: 100vh;
437
+		    border-radius: 100rpx 100rpx 0 0;
438
+		    
439
+		    .execFilterHeader{
440
+		      height: 70rpx;
441
+		      display: flex;
442
+		      justify-content: center;
443
+		      align-items: center;
444
+		      border-bottom: 2rpx solid #ccc;
445
+		    }
446
+		    
447
+		    .execFilterBody{
448
+		      .execFilterItem{
449
+		        padding: 32rpx;
450
+		        border-bottom: 2rpx dashed #ccc;
451
+		        &:last-of-type{
452
+		          border-bottom: none;
453
+		        }
454
+		        .execFilterItemHeader{}
455
+		        .execFilterItemBody{
456
+		          display: flex;
457
+		          flex-wrap: wrap;
458
+		          padding: 32rpx 0 0;
459
+		          justify-content: space-between;
460
+		          text-align: left;
461
+		          
462
+		          .execFilterItemBox{
463
+		            width: 200rpx;
464
+		            height: 80rpx;
465
+		            text-align: center;
466
+		            line-height: 80rpx;
467
+		            background-color: #f6f6f6;
468
+		            margin-bottom: 20rpx;
469
+		            border-radius: 80rpx;
470
+		            &.active{
471
+		              color: #fff;
472
+		              background-color: #49b856;
473
+		            }
474
+		          }
475
+		          
476
+		          .deptName {
477
+		            height: 80rpx;
478
+		            background-color: #f6f6f6;
479
+		            border-radius: 20rpx;
480
+		            padding: 0 20rpx;
481
+								width: 100%;
482
+		          }
483
+		        }
484
+		      }
485
+		    }
486
+		    
487
+		    .execFilterFooter {
488
+		      position: absolute;
489
+		      bottom: 0;
490
+		      right: 0;
491
+		      left: 0;
492
+		      line-height: 66rpx;
493
+		      height: 100rpx;
494
+		      display: flex;
495
+		      justify-content: space-between;
496
+		    
497
+		      .btn {
498
+		        height: 66rpx;
499
+		        flex: 1;
500
+		        margin: 0 1%;
501
+		        background-image: linear-gradient(to right, #72c172, #3bb197);
502
+		        color: #fff;
503
+		        border-radius: 8rpx;
504
+		        font-size: 28rpx;
505
+		        margin-top: 16rpx;
506
+		        text-align: center;
507
+		      }
508
+		    }
509
+		  }
66 510
 		}
67 511
 	}
68 512
 </style>

+ 26 - 3
static/font/demo_index.html

@@ -55,6 +55,12 @@
55 55
           <ul class="icon_lists dib-box">
56 56
           
57 57
             <li class="dib">
58
+              <span class="icon newicon">&#xe659;</span>
59
+                <div class="name">筛选</div>
60
+                <div class="code-name">&amp;#xe659;</div>
61
+              </li>
62
+          
63
+            <li class="dib">
58 64
               <span class="icon newicon">&#xe600;</span>
59 65
                 <div class="name">科室介绍</div>
60 66
                 <div class="code-name">&amp;#xe600;</div>
@@ -708,9 +714,9 @@
708 714
 <pre><code class="language-css"
709 715
 >@font-face {
710 716
   font-family: 'newicon';
711
-  src: url('iconfont.woff2?t=1728892542465') format('woff2'),
712
-       url('iconfont.woff?t=1728892542465') format('woff'),
713
-       url('iconfont.ttf?t=1728892542465') format('truetype');
717
+  src: url('iconfont.woff2?t=1728893186075') format('woff2'),
718
+       url('iconfont.woff?t=1728893186075') format('woff'),
719
+       url('iconfont.ttf?t=1728893186075') format('truetype');
714 720
 }
715 721
 </code></pre>
716 722
           <h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
@@ -737,6 +743,15 @@
737 743
         <ul class="icon_lists dib-box">
738 744
           
739 745
           <li class="dib">
746
+            <span class="icon newicon newicon-shaixuan"></span>
747
+            <div class="name">
748
+              筛选
749
+            </div>
750
+            <div class="code-name">.newicon-shaixuan
751
+            </div>
752
+          </li>
753
+          
754
+          <li class="dib">
740 755
             <span class="icon newicon newicon-keshijieshao"></span>
741 756
             <div class="name">
742 757
               科室介绍
@@ -1719,6 +1734,14 @@
1719 1734
           
1720 1735
             <li class="dib">
1721 1736
                 <svg class="icon svg-icon" aria-hidden="true">
1737
+                  <use xlink:href="#newicon-shaixuan"></use>
1738
+                </svg>
1739
+                <div class="name">筛选</div>
1740
+                <div class="code-name">#newicon-shaixuan</div>
1741
+            </li>
1742
+          
1743
+            <li class="dib">
1744
+                <svg class="icon svg-icon" aria-hidden="true">
1722 1745
                   <use xlink:href="#newicon-keshijieshao"></use>
1723 1746
                 </svg>
1724 1747
                 <div class="name">科室介绍</div>

+ 7 - 3
static/font/iconfont.css

@@ -1,8 +1,8 @@
1 1
 @font-face {
2 2
   font-family: "newicon"; /* Project id 4304849 */
3
-  src: url('~@/static/font/iconfont.woff2?t=1728892542465') format('woff2'),
4
-       url('~@/static/font/iconfont.woff?t=1728892542465') format('woff'),
5
-       url('~@/static/font/iconfont.ttf?t=1728892542465') format('truetype');
3
+  src: url('~@/static/font/iconfont.woff2?t=1728893186075') format('woff2'),
4
+       url('~@/static/font/iconfont.woff?t=1728893186075') format('woff'),
5
+       url('~@/static/font/iconfont.ttf?t=1728893186075') format('truetype');
6 6
 }
7 7
 
8 8
 .newicon {
@@ -13,6 +13,10 @@
13 13
   -moz-osx-font-smoothing: grayscale;
14 14
 }
15 15
 
16
+.newicon-shaixuan:before {
17
+  content: "\e659";
18
+}
19
+
16 20
 .newicon-keshijieshao:before {
17 21
   content: "\e600";
18 22
 }

File diff suppressed because it is too large
+ 1 - 1
static/font/iconfont.js


+ 7 - 0
static/font/iconfont.json

@@ -6,6 +6,13 @@
6 6
   "description": "",
7 7
   "glyphs": [
8 8
     {
9
+      "icon_id": "12911869",
10
+      "name": "筛选",
11
+      "font_class": "shaixuan",
12
+      "unicode": "e659",
13
+      "unicode_decimal": 58969
14
+    },
15
+    {
9 16
       "icon_id": "3022726",
10 17
       "name": "科室介绍",
11 18
       "font_class": "keshijieshao",

BIN
static/font/iconfont.ttf


BIN
static/font/iconfont.woff


BIN
static/font/iconfont.woff2


+ 115 - 0
uni_modules/uni-easyinput/changelog.md

@@ -0,0 +1,115 @@
1
+## 1.1.19(2024-07-18)
2
+- 修复 初始值传入 null 导致input报错的bug
3
+## 1.1.18(2024-04-11)
4
+- 修复 easyinput组件双向绑定问题
5
+## 1.1.17(2024-03-28)
6
+- 修复 在头条小程序下丢失事件绑定的问题
7
+## 1.1.16(2024-03-20)
8
+- 修复 在密码输入情况下 清除和小眼睛覆盖bug 在edge浏览器下显示双眼睛bug
9
+## 1.1.15(2024-02-21)
10
+- 新增 左侧插槽:left
11
+## 1.1.14(2024-02-19)
12
+- 修复 onBlur的emit传值错误
13
+## 1.1.12(2024-01-29)
14
+- 补充 adjust-position文档属性补充
15
+## 1.1.11(2024-01-29)
16
+- 补充 adjust-position属性传递值:(Boolean)当键盘弹起时,是否自动上推页面
17
+## 1.1.10(2024-01-22)
18
+- 去除 移除无用的log输出
19
+## 1.1.9(2023-04-11)
20
+- 修复 vue3 下 keyboardheightchange 事件报错的bug
21
+## 1.1.8(2023-03-29)
22
+- 优化 trim 属性默认值
23
+## 1.1.7(2023-03-29)
24
+- 新增 cursor-spacing 属性
25
+## 1.1.6(2023-01-28)
26
+- 新增 keyboardheightchange 事件,可监听键盘高度变化
27
+## 1.1.5(2022-11-29)
28
+- 优化 主题样式
29
+## 1.1.4(2022-10-27)
30
+- 修复 props 中背景颜色无默认值的bug
31
+## 1.1.0(2022-06-30)
32
+
33
+- 新增 在 uni-forms 1.4.0 中使用可以在 blur 时校验内容
34
+- 新增 clear 事件,点击右侧叉号图标触发
35
+- 新增 change 事件 ,仅在输入框失去焦点或用户按下回车时触发
36
+- 优化 组件样式,组件获取焦点时高亮显示,图标颜色调整等
37
+
38
+## 1.0.5(2022-06-07)
39
+
40
+- 优化 clearable 显示策略
41
+
42
+## 1.0.4(2022-06-07)
43
+
44
+- 优化 clearable 显示策略
45
+
46
+## 1.0.3(2022-05-20)
47
+
48
+- 修复 关闭图标某些情况下无法取消的 bug
49
+
50
+## 1.0.2(2022-04-12)
51
+
52
+- 修复 默认值不生效的 bug
53
+
54
+## 1.0.1(2022-04-02)
55
+
56
+- 修复 value 不能为 0 的 bug
57
+
58
+## 1.0.0(2021-11-19)
59
+
60
+- 优化 组件 UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
61
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-easyinput](https://uniapp.dcloud.io/component/uniui/uni-easyinput)
62
+
63
+## 0.1.4(2021-08-20)
64
+
65
+- 修复 在 uni-forms 的动态表单中默认值校验不通过的 bug
66
+
67
+## 0.1.3(2021-08-11)
68
+
69
+- 修复 在 uni-forms 中重置表单,错误信息无法清除的问题
70
+
71
+## 0.1.2(2021-07-30)
72
+
73
+- 优化 vue3 下事件警告的问题
74
+
75
+## 0.1.1
76
+
77
+- 优化 errorMessage 属性支持 Boolean 类型
78
+
79
+## 0.1.0(2021-07-13)
80
+
81
+- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
82
+
83
+## 0.0.16(2021-06-29)
84
+
85
+- 修复 confirmType 属性(仅 type="text" 生效)导致多行文本框无法换行的 bug
86
+
87
+## 0.0.15(2021-06-21)
88
+
89
+- 修复 passwordIcon 属性拼写错误的 bug
90
+
91
+## 0.0.14(2021-06-18)
92
+
93
+- 新增 passwordIcon 属性,当 type=password 时是否显示小眼睛图标
94
+- 修复 confirmType 属性不生效的问题
95
+
96
+## 0.0.13(2021-06-04)
97
+
98
+- 修复 disabled 状态可清出内容的 bug
99
+
100
+## 0.0.12(2021-05-12)
101
+
102
+- 新增 组件示例地址
103
+
104
+## 0.0.11(2021-05-07)
105
+
106
+- 修复 input-border 属性不生效的问题
107
+
108
+## 0.0.10(2021-04-30)
109
+
110
+- 修复 ios 遮挡文字、显示一半的问题
111
+
112
+## 0.0.9(2021-02-05)
113
+
114
+- 调整为 uni_modules 目录规范
115
+- 优化 兼容 nvue 页面

+ 54 - 0
uni_modules/uni-easyinput/components/uni-easyinput/common.js

@@ -0,0 +1,54 @@
1
+/**
2
+ * @desc 函数防抖
3
+ * @param func 目标函数
4
+ * @param wait 延迟执行毫秒数
5
+ * @param immediate true - 立即执行, false - 延迟执行
6
+ */
7
+export const debounce = function(func, wait = 1000, immediate = true) {
8
+	let timer;
9
+	return function() {
10
+		let context = this,
11
+			args = arguments;
12
+		if (timer) clearTimeout(timer);
13
+		if (immediate) {
14
+			let callNow = !timer;
15
+			timer = setTimeout(() => {
16
+				timer = null;
17
+			}, wait);
18
+			if (callNow) func.apply(context, args);
19
+		} else {
20
+			timer = setTimeout(() => {
21
+				func.apply(context, args);
22
+			}, wait)
23
+		}
24
+	}
25
+}
26
+/**
27
+ * @desc 函数节流
28
+ * @param func 函数
29
+ * @param wait 延迟执行毫秒数
30
+ * @param type 1 使用表时间戳,在时间段开始的时候触发 2 使用表定时器,在时间段结束的时候触发
31
+ */
32
+export const throttle = (func, wait = 1000, type = 1) => {
33
+	let previous = 0;
34
+	let timeout;
35
+	return function() {
36
+		let context = this;
37
+		let args = arguments;
38
+		if (type === 1) {
39
+			let now = Date.now();
40
+
41
+			if (now - previous > wait) {
42
+				func.apply(context, args);
43
+				previous = now;
44
+			}
45
+		} else if (type === 2) {
46
+			if (!timeout) {
47
+				timeout = setTimeout(() => {
48
+					timeout = null;
49
+					func.apply(context, args)
50
+				}, wait)
51
+			}
52
+		}
53
+	}
54
+}

File diff suppressed because it is too large
+ 676 - 0
uni_modules/uni-easyinput/components/uni-easyinput/uni-easyinput.vue


+ 88 - 0
uni_modules/uni-easyinput/package.json

@@ -0,0 +1,88 @@
1
+{
2
+  "id": "uni-easyinput",
3
+  "displayName": "uni-easyinput 增强输入框",
4
+  "version": "1.1.19",
5
+  "description": "Easyinput 组件是对原生input组件的增强",
6
+  "keywords": [
7
+    "uni-ui",
8
+    "uniui",
9
+    "input",
10
+    "uni-easyinput",
11
+    "输入框"
12
+],
13
+  "repository": "https://github.com/dcloudio/uni-ui",
14
+  "engines": {
15
+    "HBuilderX": ""
16
+  },
17
+  "directories": {
18
+    "example": "../../temps/example_temps"
19
+  },
20
+"dcloudext": {
21
+    "sale": {
22
+      "regular": {
23
+        "price": "0.00"
24
+      },
25
+      "sourcecode": {
26
+        "price": "0.00"
27
+      }
28
+    },
29
+    "contact": {
30
+      "qq": ""
31
+    },
32
+    "declaration": {
33
+      "ads": "无",
34
+      "data": "无",
35
+      "permissions": "无"
36
+    },
37
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
38
+    "type": "component-vue"
39
+  },
40
+  "uni_modules": {
41
+    "dependencies": [
42
+			"uni-scss",
43
+      "uni-icons"
44
+    ],
45
+    "encrypt": [],
46
+    "platforms": {
47
+      "cloud": {
48
+        "tcb": "y",
49
+        "aliyun": "y",
50
+        "alipay": "n"
51
+      },
52
+      "client": {
53
+        "App": {
54
+          "app-vue": "y",
55
+          "app-nvue": "y"
56
+        },
57
+        "H5-mobile": {
58
+          "Safari": "y",
59
+          "Android Browser": "y",
60
+          "微信浏览器(Android)": "y",
61
+          "QQ浏览器(Android)": "y"
62
+        },
63
+        "H5-pc": {
64
+          "Chrome": "y",
65
+          "IE": "y",
66
+          "Edge": "y",
67
+          "Firefox": "y",
68
+          "Safari": "y"
69
+        },
70
+        "小程序": {
71
+          "微信": "y",
72
+          "阿里": "y",
73
+          "百度": "y",
74
+          "字节跳动": "y",
75
+          "QQ": "y"
76
+        },
77
+        "快应用": {
78
+          "华为": "u",
79
+          "联盟": "u"
80
+        },
81
+        "Vue": {
82
+            "vue2": "y",
83
+            "vue3": "y"
84
+        }
85
+      }
86
+    }
87
+  }
88
+}

+ 11 - 0
uni_modules/uni-easyinput/readme.md

@@ -0,0 +1,11 @@
1
+
2
+
3
+### Easyinput 增强输入框
4
+> **组件名:uni-easyinput**
5
+> 代码块: `uEasyinput`
6
+
7
+
8
+easyinput 组件是对原生input组件的增强 ,是专门为配合表单组件[uni-forms](https://ext.dcloud.net.cn/plugin?id=2773)而设计的,easyinput 内置了边框,图标等,同时包含 input 所有功能
9
+
10
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-easyinput)
11
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 

+ 100 - 0
uni_modules/uni-forms/changelog.md

@@ -0,0 +1,100 @@
1
+## 1.4.13(2024-10-08)
2
+- 修复 校验规则在抖音开发者工具上不生效的bug,详见:[https://ask.dcloud.net.cn/question/191933](https://ask.dcloud.net.cn/question/191933)
3
+## 1.4.12 (2024-9-21)
4
+- 修复 form上次修改的问题
5
+## 1.4.11 (2024-9-14)
6
+- 修复 binddata的兼容性问题
7
+## 1.4.10(2023-11-03)
8
+- 优化 labelWidth 描述错误
9
+## 1.4.9(2023-02-10)
10
+- 修复 required 参数无法动态绑定
11
+## 1.4.8(2022-08-23)
12
+- 优化 根据 rules 自动添加 required 的问题
13
+## 1.4.7(2022-08-22)
14
+- 修复 item 未设置 require 属性,rules 设置 require 后,星号也显示的 bug,详见:[https://ask.dcloud.net.cn/question/151540](https://ask.dcloud.net.cn/question/151540)
15
+## 1.4.6(2022-07-13)
16
+- 修复 model 需要校验的值没有声明对应字段时,导致第一次不触发校验的bug
17
+## 1.4.5(2022-07-05)
18
+- 新增 更多表单示例
19
+- 优化 子表单组件过期提示的问题
20
+- 优化 子表单组件uni-datetime-picker、uni-data-select、uni-data-picker的显示样式
21
+## 1.4.4(2022-07-04)
22
+- 更新 删除组件日志
23
+## 1.4.3(2022-07-04)
24
+- 修复 由 1.4.0 引发的 label 插槽不生效的bug
25
+## 1.4.2(2022-07-04)
26
+- 修复 子组件找不到 setValue 报错的bug
27
+## 1.4.1(2022-07-04)
28
+- 修复 uni-data-picker 在 uni-forms-item 中报错的bug
29
+- 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug
30
+## 1.4.0(2022-06-30)
31
+- 【重要】组件逻辑重构,部分用法用旧版本不兼容,请注意兼容问题
32
+- 【重要】组件使用 Provide/Inject 方式注入依赖,提供了自定义表单组件调用 uni-forms 校验表单的能力
33
+- 新增 model 属性,等同于原 value/modelValue 属性,旧属性即将废弃
34
+- 新增 validateTrigger 属性的 blur 值,仅 uni-easyinput 生效
35
+- 新增 onFieldChange 方法,可以对子表单进行校验,可替代binddata方法
36
+- 新增 子表单的 setRules 方法,配合自定义校验函数使用
37
+- 新增 uni-forms-item 的 setRules 方法,配置动态表单使用可动态更新校验规则
38
+- 优化 动态表单校验方式,废弃拼接name的方式
39
+## 1.3.3(2022-06-22)
40
+- 修复 表单校验顺序无序问题
41
+## 1.3.2(2021-12-09)
42
+-
43
+## 1.3.1(2021-11-19)
44
+- 修复 label 插槽不生效的bug
45
+## 1.3.0(2021-11-19)
46
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
47
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-forms](https://uniapp.dcloud.io/component/uniui/uni-forms)
48
+## 1.2.7(2021-08-13)
49
+- 修复 没有添加校验规则的字段依然报错的Bug
50
+## 1.2.6(2021-08-11)
51
+- 修复 重置表单错误信息无法清除的问题
52
+## 1.2.5(2021-08-11)
53
+- 优化 组件文档
54
+## 1.2.4(2021-08-11)
55
+- 修复 表单验证只生效一次的问题
56
+## 1.2.3(2021-07-30)
57
+- 优化 vue3下事件警告的问题
58
+## 1.2.2(2021-07-26)
59
+- 修复 vue2 下条件编译导致destroyed生命周期失效的Bug
60
+- 修复 1.2.1 引起的示例在小程序平台报错的Bug
61
+## 1.2.1(2021-07-22)
62
+- 修复 动态校验表单,默认值为空的情况下校验失效的Bug
63
+- 修复 不指定name属性时,运行报错的Bug
64
+- 优化 label默认宽度从65调整至70,使required为true且四字时不换行
65
+- 优化 组件示例,新增动态校验示例代码
66
+- 优化 组件文档,使用方式更清晰
67
+## 1.2.0(2021-07-13)
68
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
69
+## 1.1.2(2021-06-25)
70
+- 修复 pattern 属性在微信小程序平台无效的问题
71
+## 1.1.1(2021-06-22)
72
+- 修复 validate-trigger属性为submit且err-show-type属性为toast时不能弹出的Bug
73
+## 1.1.0(2021-06-22)
74
+- 修复 只写setRules方法而导致校验不生效的Bug
75
+- 修复 由上个办法引发的错误提示文字错位的Bug
76
+## 1.0.48(2021-06-21)
77
+- 修复 不设置 label 属性 ,无法设置label插槽的问题
78
+## 1.0.47(2021-06-21)
79
+- 修复 不设置label属性,label-width属性不生效的bug
80
+- 修复 setRules 方法与rules属性冲突的问题
81
+## 1.0.46(2021-06-04)
82
+- 修复 动态删减数据导致报错的问题
83
+## 1.0.45(2021-06-04)
84
+- 新增 modelValue 属性 ,value 即将废弃
85
+## 1.0.44(2021-06-02)
86
+- 新增 uni-forms-item 可以设置单独的 rules
87
+- 新增 validate 事件增加 keepitem 参数,可以选择那些字段不过滤
88
+- 优化 submit 事件重命名为 validate
89
+## 1.0.43(2021-05-12)
90
+- 新增 组件示例地址
91
+## 1.0.42(2021-04-30)
92
+- 修复 自定义检验器失效的问题
93
+## 1.0.41(2021-03-05)
94
+- 更新 校验器
95
+- 修复 表单规则设置类型为 number 的情况下,值为0校验失败的Bug
96
+## 1.0.40(2021-03-04)
97
+- 修复 动态显示uni-forms-item的情况下,submit 方法获取值错误的Bug
98
+## 1.0.39(2021-02-05)
99
+- 调整为uni_modules目录规范
100
+- 修复 校验器传入 int 等类型 ,返回String类型的Bug

+ 632 - 0
uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue

@@ -0,0 +1,632 @@
1
+<template>
2
+	<view class="uni-forms-item"
3
+		:class="['is-direction-' + localLabelPos ,border?'uni-forms-item--border':'' ,border && isFirstBorder?'is-first-border':'']">
4
+		<slot name="label">
5
+			<view class="uni-forms-item__label" :class="{'no-label':!label && !required}"
6
+				:style="{width:localLabelWidth,justifyContent: localLabelAlign}">
7
+				<text v-if="required" class="is-required">*</text>
8
+				<text>{{label}}</text>
9
+			</view>
10
+		</slot>
11
+		<!-- #ifndef APP-NVUE -->
12
+		<view class="uni-forms-item__content">
13
+			<slot></slot>
14
+			<view class="uni-forms-item__error" :class="{'msg--active':msg}">
15
+				<text>{{msg}}</text>
16
+			</view>
17
+		</view>
18
+		<!-- #endif -->
19
+		<!-- #ifdef APP-NVUE -->
20
+		<view class="uni-forms-item__nuve-content">
21
+			<view class="uni-forms-item__content">
22
+				<slot></slot>
23
+			</view>
24
+			<view class="uni-forms-item__error" :class="{'msg--active':msg}">
25
+				<text class="error-text">{{msg}}</text>
26
+			</view>
27
+		</view>
28
+		<!-- #endif -->
29
+	</view>
30
+</template>
31
+
32
+<script>
33
+	/**
34
+	 * uni-fomrs-item 表单子组件
35
+	 * @description uni-fomrs-item 表单子组件,提供了基础布局已经校验能力
36
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=2773
37
+	 * @property {Boolean} required 是否必填,左边显示红色"*"号
38
+	 * @property {String } 	label 				输入框左边的文字提示
39
+	 * @property {Number } 	labelWidth 			label的宽度,单位px(默认70)
40
+	 * @property {String } 	labelAlign = [left|center|right] label的文字对齐方式(默认left)
41
+	 * 	@value left		label 左侧显示
42
+	 * 	@value center	label 居中
43
+	 * 	@value right	label 右侧对齐
44
+	 * @property {String } 	errorMessage 		显示的错误提示内容,如果为空字符串或者false,则不显示错误信息
45
+	 * @property {String } 	name 				表单域的属性名,在使用校验规则时必填
46
+	 * @property {String } 	leftIcon 			【1.4.0废弃】label左边的图标,限 uni-ui 的图标名称
47
+	 * @property {String } 	iconColor 		【1.4.0废弃】左边通过icon配置的图标的颜色(默认#606266)
48
+	 * @property {String} validateTrigger = [bind|submit|blur]	【1.4.0废弃】校验触发器方式 默认 submit
49
+	 * 	@value bind 	发生变化时触发
50
+	 * 	@value submit 提交时触发
51
+	 * 	@value blur 	失去焦点触发
52
+	 * @property {String } 	labelPosition = [top|left] 【1.4.0废弃】label的文字的位置(默认left)
53
+	 * 	@value top	顶部显示 label
54
+	 * 	@value left	左侧显示 label
55
+	 */
56
+
57
+	export default {
58
+		name: 'uniFormsItem',
59
+		options: {
60
+			// #ifdef MP-TOUTIAO
61
+			virtualHost: false,
62
+			// #endif
63
+			// #ifndef MP-TOUTIAO
64
+			virtualHost: true
65
+			// #endif
66
+		},
67
+		provide() {
68
+			return {
69
+				uniFormItem: this
70
+			}
71
+		},
72
+		inject: {
73
+			form: {
74
+				from: 'uniForm',
75
+				default: null
76
+			},
77
+		},
78
+		props: {
79
+			// 表单校验规则
80
+			rules: {
81
+				type: Array,
82
+				default () {
83
+					return null;
84
+				}
85
+			},
86
+			// 表单域的属性名,在使用校验规则时必填
87
+			name: {
88
+				type: [String, Array],
89
+				default: ''
90
+			},
91
+			required: {
92
+				type: Boolean,
93
+				default: false
94
+			},
95
+			label: {
96
+				type: String,
97
+				default: ''
98
+			},
99
+			// label的宽度
100
+			labelWidth: {
101
+				type: [String, Number],
102
+				default: ''
103
+			},
104
+			// label 居中方式,默认 left 取值 left/center/right
105
+			labelAlign: {
106
+				type: String,
107
+				default: ''
108
+			},
109
+			// 强制显示错误信息
110
+			errorMessage: {
111
+				type: [String, Boolean],
112
+				default: ''
113
+			},
114
+			// 1.4.0 弃用,统一使用 form 的校验时机
115
+			// validateTrigger: {
116
+			// 	type: String,
117
+			// 	default: ''
118
+			// },
119
+			// 1.4.0 弃用,统一使用 form 的label 位置
120
+			// labelPosition: {
121
+			// 	type: String,
122
+			// 	default: ''
123
+			// },
124
+			// 1.4.0 以下属性已经废弃,请使用  #label 插槽代替
125
+			leftIcon: String,
126
+			iconColor: {
127
+				type: String,
128
+				default: '#606266'
129
+			},
130
+		},
131
+		data() {
132
+			return {
133
+				errMsg: '',
134
+				userRules: null,
135
+				localLabelAlign: 'left',
136
+				localLabelWidth: '70px',
137
+				localLabelPos: 'left',
138
+				border: false,
139
+				isFirstBorder: false,
140
+			};
141
+		},
142
+		computed: {
143
+			// 处理错误信息
144
+			msg() {
145
+				return this.errorMessage || this.errMsg;
146
+			}
147
+		},
148
+		watch: {
149
+			// 规则发生变化通知子组件更新
150
+			'form.formRules'(val) {
151
+				// TODO 处理头条vue3 watch不生效的问题
152
+				// #ifndef MP-TOUTIAO
153
+				this.init()
154
+				// #endif
155
+			},
156
+			'form.labelWidth'(val) {
157
+				// 宽度
158
+				this.localLabelWidth = this._labelWidthUnit(val)
159
+
160
+			},
161
+			'form.labelPosition'(val) {
162
+				// 标签位置
163
+				this.localLabelPos = this._labelPosition()
164
+			},
165
+			'form.labelAlign'(val) {
166
+
167
+			}
168
+		},
169
+		created() {
170
+			this.init(true)
171
+			if (this.name && this.form) {
172
+				// TODO 处理头条vue3 watch不生效的问题
173
+				// #ifdef MP-TOUTIAO
174
+				this.$watch('form.formRules', () => {
175
+					this.init()
176
+				})
177
+				// #endif
178
+
179
+				// 监听变化
180
+				this.$watch(
181
+					() => {
182
+						const val = this.form._getDataValue(this.name, this.form.localData)
183
+						return val
184
+					},
185
+					(value, oldVal) => {
186
+						const isEqual = this.form._isEqual(value, oldVal)
187
+						// 简单判断前后值的变化,只有发生变化才会发生校验
188
+						// TODO  如果 oldVal = undefined ,那么大概率是源数据里没有值导致 ,这个情况不哦校验 ,可能不严谨 ,需要在做观察
189
+						// fix by mehaotian 暂时取消 && oldVal !== undefined ,如果formData 中不存在,可能会不校验
190
+						if (!isEqual) {
191
+							const val = this.itemSetValue(value)
192
+							this.onFieldChange(val, false)
193
+						}
194
+					}, {
195
+						immediate: false
196
+					}
197
+				);
198
+			}
199
+
200
+		},
201
+		// #ifndef VUE3
202
+		destroyed() {
203
+			if (this.__isUnmounted) return
204
+			this.unInit()
205
+		},
206
+		// #endif
207
+		// #ifdef VUE3
208
+		unmounted() {
209
+			this.__isUnmounted = true
210
+			this.unInit()
211
+		},
212
+		// #endif
213
+		methods: {
214
+			/**
215
+			 * 外部调用方法
216
+			 * 设置规则 ,主要用于小程序自定义检验规则
217
+			 * @param {Array} rules 规则源数据
218
+			 */
219
+			setRules(rules = null) {
220
+				this.userRules = rules
221
+				this.init(false)
222
+			},
223
+			// 兼容老版本表单组件
224
+			setValue() {
225
+				// console.log('setValue 方法已经弃用,请使用最新版本的 uni-forms 表单组件以及其他关联组件。');
226
+			},
227
+			/**
228
+			 * 外部调用方法
229
+			 * 校验数据
230
+			 * @param {any} value 需要校验的数据
231
+			 * @param {boolean} 是否立即校验
232
+			 * @return {Array|null} 校验内容
233
+			 */
234
+			async onFieldChange(value, formtrigger = true) {
235
+				const {
236
+					formData,
237
+					localData,
238
+					errShowType,
239
+					validateCheck,
240
+					validateTrigger,
241
+					_isRequiredField,
242
+					_realName
243
+				} = this.form
244
+				const name = _realName(this.name)
245
+				if (!value) {
246
+					value = this.form.formData[name]
247
+				}
248
+				// fixd by mehaotian 不在校验前清空信息,解决闪屏的问题
249
+				// this.errMsg = '';
250
+
251
+				// fix by mehaotian 解决没有检验规则的情况下,抛出错误的问题
252
+				const ruleLen = this.itemRules.rules && this.itemRules.rules.length
253
+				if (!this.validator || !ruleLen || ruleLen === 0) return;
254
+
255
+				// 检验时机
256
+				// let trigger = this.isTrigger(this.itemRules.validateTrigger, this.validateTrigger, validateTrigger);
257
+				const isRequiredField = _isRequiredField(this.itemRules.rules || []);
258
+				let result = null;
259
+				// 只有等于 bind 时 ,才能开启时实校验
260
+				if (validateTrigger === 'bind' || formtrigger) {
261
+					// 校验当前表单项
262
+					result = await this.validator.validateUpdate({
263
+							[name]: value
264
+						},
265
+						formData
266
+					);
267
+
268
+					// 判断是否必填,非必填,不填不校验,填写才校验 ,暂时只处理 undefined  和空的情况
269
+					if (!isRequiredField && (value === undefined || value === '')) {
270
+						result = null;
271
+					}
272
+
273
+					// 判断错误信息显示类型
274
+					if (result && result.errorMessage) {
275
+						if (errShowType === 'undertext') {
276
+							// 获取错误信息
277
+							this.errMsg = !result ? '' : result.errorMessage;
278
+						}
279
+						if (errShowType === 'toast') {
280
+							uni.showToast({
281
+								title: result.errorMessage || '校验错误',
282
+								icon: 'none'
283
+							});
284
+						}
285
+						if (errShowType === 'modal') {
286
+							uni.showModal({
287
+								title: '提示',
288
+								content: result.errorMessage || '校验错误'
289
+							});
290
+						}
291
+					} else {
292
+						this.errMsg = ''
293
+					}
294
+					// 通知 form 组件更新事件
295
+					validateCheck(result ? result : null)
296
+				} else {
297
+					this.errMsg = ''
298
+				}
299
+				return result ? result : null;
300
+			},
301
+			/**
302
+			 * 初始组件数据
303
+			 */
304
+			init(type = false) {
305
+				const {
306
+					validator,
307
+					formRules,
308
+					childrens,
309
+					formData,
310
+					localData,
311
+					_realName,
312
+					labelWidth,
313
+					_getDataValue,
314
+					_setDataValue
315
+				} = this.form || {}
316
+				// 对齐方式
317
+				this.localLabelAlign = this._justifyContent()
318
+				// 宽度
319
+				this.localLabelWidth = this._labelWidthUnit(labelWidth)
320
+				// 标签位置
321
+				this.localLabelPos = this._labelPosition()
322
+				// 将需要校验的子组件加入form 队列
323
+				this.form && type && childrens.push(this)
324
+
325
+				if (!validator || !formRules) return
326
+				// 判断第一个 item
327
+				if (!this.form.isFirstBorder) {
328
+					this.form.isFirstBorder = true;
329
+					this.isFirstBorder = true;
330
+				}
331
+
332
+				// 判断 group 里的第一个 item
333
+				if (this.group) {
334
+					if (!this.group.isFirstBorder) {
335
+						this.group.isFirstBorder = true;
336
+						this.isFirstBorder = true;
337
+					}
338
+				}
339
+				this.border = this.form.border;
340
+				// 获取子域的真实名称
341
+				const name = _realName(this.name)
342
+				const itemRule = this.userRules || this.rules
343
+				if (typeof formRules === 'object' && itemRule) {
344
+					// 子规则替换父规则
345
+					formRules[name] = {
346
+						rules: itemRule
347
+					}
348
+					validator.updateSchema(formRules);
349
+				}
350
+				// 注册校验规则
351
+				const itemRules = formRules[name] || {}
352
+				this.itemRules = itemRules
353
+				// 注册校验函数
354
+				this.validator = validator
355
+				// 默认值赋予
356
+				this.itemSetValue(_getDataValue(this.name, localData))
357
+			},
358
+			unInit() {
359
+				if (this.form) {
360
+					const {
361
+						childrens,
362
+						formData,
363
+						_realName
364
+					} = this.form
365
+					childrens.forEach((item, index) => {
366
+						if (item === this) {
367
+							this.form.childrens.splice(index, 1)
368
+							delete formData[_realName(item.name)]
369
+						}
370
+					})
371
+				}
372
+			},
373
+			// 设置item 的值
374
+			itemSetValue(value) {
375
+				const name = this.form._realName(this.name)
376
+				const rules = this.itemRules.rules || []
377
+				const val = this.form._getValue(name, value, rules)
378
+				this.form._setDataValue(name, this.form.formData, val)
379
+				return val
380
+			},
381
+
382
+			/**
383
+			 * 移除该表单项的校验结果
384
+			 */
385
+			clearValidate() {
386
+				this.errMsg = '';
387
+			},
388
+
389
+			// 是否显示星号
390
+			_isRequired() {
391
+				// TODO 不根据规则显示 星号,考虑后续兼容
392
+				// if (this.form) {
393
+				// 	if (this.form._isRequiredField(this.itemRules.rules || []) && this.required) {
394
+				// 		return true
395
+				// 	}
396
+				// 	return false
397
+				// }
398
+				return this.required
399
+			},
400
+
401
+			// 处理对齐方式
402
+			_justifyContent() {
403
+				if (this.form) {
404
+					const {
405
+						labelAlign
406
+					} = this.form
407
+					let labelAli = this.labelAlign ? this.labelAlign : labelAlign;
408
+					if (labelAli === 'left') return 'flex-start';
409
+					if (labelAli === 'center') return 'center';
410
+					if (labelAli === 'right') return 'flex-end';
411
+				}
412
+				return 'flex-start';
413
+			},
414
+			// 处理 label宽度单位 ,继承父元素的值
415
+			_labelWidthUnit(labelWidth) {
416
+
417
+				// if (this.form) {
418
+				// 	const {
419
+				// 		labelWidth
420
+				// 	} = this.form
421
+				return this.num2px(this.labelWidth ? this.labelWidth : (labelWidth || (this.label ? 70 : 'auto')))
422
+				// }
423
+				// return '70px'
424
+			},
425
+			// 处理 label 位置
426
+			_labelPosition() {
427
+				if (this.form) return this.form.labelPosition || 'left'
428
+				return 'left'
429
+
430
+			},
431
+
432
+			/**
433
+			 * 触发时机
434
+			 * @param {Object} rule 当前规则内时机
435
+			 * @param {Object} itemRlue 当前组件时机
436
+			 * @param {Object} parentRule 父组件时机
437
+			 */
438
+			isTrigger(rule, itemRlue, parentRule) {
439
+				//  bind  submit
440
+				if (rule === 'submit' || !rule) {
441
+					if (rule === undefined) {
442
+						if (itemRlue !== 'bind') {
443
+							if (!itemRlue) {
444
+								return parentRule === '' ? 'bind' : 'submit';
445
+							}
446
+							return 'submit';
447
+						}
448
+						return 'bind';
449
+					}
450
+					return 'submit';
451
+				}
452
+				return 'bind';
453
+			},
454
+			num2px(num) {
455
+				if (typeof num === 'number') {
456
+					return `${num}px`
457
+				}
458
+				return num
459
+			}
460
+		}
461
+	};
462
+</script>
463
+
464
+<style lang="scss">
465
+	.uni-forms-item {
466
+		position: relative;
467
+		display: flex;
468
+		/* #ifdef APP-NVUE */
469
+		// 在 nvue 中,使用 margin-bottom error 信息会被隐藏
470
+		padding-bottom: 22px;
471
+		/* #endif */
472
+		/* #ifndef APP-NVUE */
473
+		margin-bottom: 22px;
474
+		/* #endif */
475
+		flex-direction: row;
476
+
477
+		&__label {
478
+			display: flex;
479
+			flex-direction: row;
480
+			align-items: center;
481
+			text-align: left;
482
+			font-size: 14px;
483
+			color: #606266;
484
+			height: 36px;
485
+			padding: 0 12px 0 0;
486
+			/* #ifndef APP-NVUE */
487
+			vertical-align: middle;
488
+			flex-shrink: 0;
489
+			/* #endif */
490
+
491
+			/* #ifndef APP-NVUE */
492
+			box-sizing: border-box;
493
+
494
+			/* #endif */
495
+			&.no-label {
496
+				padding: 0;
497
+			}
498
+		}
499
+
500
+		&__content {
501
+			/* #ifndef MP-TOUTIAO */
502
+			// display: flex;
503
+			// align-items: center;
504
+			/* #endif */
505
+			position: relative;
506
+			font-size: 14px;
507
+			flex: 1;
508
+			/* #ifndef APP-NVUE */
509
+			box-sizing: border-box;
510
+			/* #endif */
511
+			flex-direction: row;
512
+
513
+			/* #ifndef APP || H5 || MP-WEIXIN || APP-NVUE */
514
+			// TODO 因为小程序平台会多一层标签节点 ,所以需要在多余节点继承当前样式
515
+			&>uni-easyinput,
516
+			&>uni-data-picker {
517
+				width: 100%;
518
+			}
519
+
520
+			/* #endif */
521
+
522
+		}
523
+
524
+		& .uni-forms-item__nuve-content {
525
+			display: flex;
526
+			flex-direction: column;
527
+			flex: 1;
528
+		}
529
+
530
+		&__error {
531
+			color: #f56c6c;
532
+			font-size: 12px;
533
+			line-height: 1;
534
+			padding-top: 4px;
535
+			position: absolute;
536
+			/* #ifndef APP-NVUE */
537
+			top: 100%;
538
+			left: 0;
539
+			transition: transform 0.3s;
540
+			transform: translateY(-100%);
541
+			/* #endif */
542
+			/* #ifdef APP-NVUE */
543
+			bottom: 5px;
544
+			/* #endif */
545
+
546
+			opacity: 0;
547
+
548
+			.error-text {
549
+				// 只有 nvue 下这个样式才生效
550
+				color: #f56c6c;
551
+				font-size: 12px;
552
+			}
553
+
554
+			&.msg--active {
555
+				opacity: 1;
556
+				transform: translateY(0%);
557
+			}
558
+		}
559
+
560
+		// 位置修饰样式
561
+		&.is-direction-left {
562
+			flex-direction: row;
563
+		}
564
+
565
+		&.is-direction-top {
566
+			flex-direction: column;
567
+
568
+			.uni-forms-item__label {
569
+				padding: 0 0 8px;
570
+				line-height: 1.5715;
571
+				text-align: left;
572
+				/* #ifndef APP-NVUE */
573
+				white-space: initial;
574
+				/* #endif */
575
+			}
576
+		}
577
+
578
+		.is-required {
579
+			// color: $uni-color-error;
580
+			color: #dd524d;
581
+			font-weight: bold;
582
+		}
583
+	}
584
+
585
+
586
+	.uni-forms-item--border {
587
+		margin-bottom: 0;
588
+		padding: 10px 0;
589
+		// padding-bottom: 0;
590
+		border-top: 1px #eee solid;
591
+
592
+		/* #ifndef APP-NVUE */
593
+		.uni-forms-item__content {
594
+			flex-direction: column;
595
+			justify-content: flex-start;
596
+			align-items: flex-start;
597
+
598
+			.uni-forms-item__error {
599
+				position: relative;
600
+				top: 5px;
601
+				left: 0;
602
+				padding-top: 0;
603
+			}
604
+		}
605
+
606
+		/* #endif */
607
+
608
+		/* #ifdef APP-NVUE */
609
+		display: flex;
610
+		flex-direction: column;
611
+
612
+		.uni-forms-item__error {
613
+			position: relative;
614
+			top: 0px;
615
+			left: 0;
616
+			padding-top: 0;
617
+			margin-top: 5px;
618
+		}
619
+
620
+		/* #endif */
621
+
622
+	}
623
+
624
+	.is-first-border {
625
+		/* #ifndef APP-NVUE */
626
+		border: none;
627
+		/* #endif */
628
+		/* #ifdef APP-NVUE */
629
+		border-width: 0;
630
+		/* #endif */
631
+	}
632
+</style>

+ 404 - 0
uni_modules/uni-forms/components/uni-forms/uni-forms.vue

@@ -0,0 +1,404 @@
1
+<template>
2
+	<view class="uni-forms">
3
+		<form>
4
+			<slot></slot>
5
+		</form>
6
+	</view>
7
+</template>
8
+
9
+<script>
10
+	import Validator from './validate.js';
11
+	import {
12
+		deepCopy,
13
+		getValue,
14
+		isRequiredField,
15
+		setDataValue,
16
+		getDataValue,
17
+		realName,
18
+		isRealName,
19
+		rawData,
20
+		isEqual
21
+	} from './utils.js'
22
+
23
+	// #ifndef VUE3
24
+	// 后续会慢慢废弃这个方法
25
+	import Vue from 'vue';
26
+	Vue.prototype.binddata = function(name, value, formName) {
27
+		if (formName) {
28
+			this.$refs[formName].setValue(name, value);
29
+		} else {
30
+			let formVm;
31
+			for (let i in this.$refs) {
32
+				const vm = this.$refs[i];
33
+				if (vm && vm.$options && vm.$options.name === 'uniForms') {
34
+					formVm = vm;
35
+					break;
36
+				}
37
+			}
38
+			if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
39
+			formVm.setValue(name, value);
40
+		}
41
+	};
42
+	// #endif
43
+	/**
44
+	 * Forms 表单
45
+	 * @description 由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据
46
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=2773
47
+	 * @property {Object} rules	表单校验规则
48
+	 * @property {String} validateTrigger = [bind|submit|blur]	校验触发器方式 默认 submit
49
+	 * @value bind		发生变化时触发
50
+	 * @value submit	提交时触发
51
+	 * @value blur	  失去焦点时触发
52
+	 * @property {String} labelPosition = [top|left]	label 位置 默认 left
53
+	 * @value top		顶部显示 label
54
+	 * @value left	左侧显示 label
55
+	 * @property {String} labelWidth	label 宽度,默认 70px
56
+	 * @property {String} labelAlign = [left|center|right]	label 居中方式  默认 left
57
+	 * @value left		label 左侧显示
58
+	 * @value center	label 居中
59
+	 * @value right		label 右侧对齐
60
+	 * @property {String} errShowType = [undertext|toast|modal]	校验错误信息提示方式
61
+	 * @value undertext	错误信息在底部显示
62
+	 * @value toast			错误信息toast显示
63
+	 * @value modal			错误信息modal显示
64
+	 * @event {Function} submit	提交时触发
65
+	 * @event {Function} validate	校验结果发生变化触发
66
+	 */
67
+	export default {
68
+		name: 'uniForms',
69
+		emits: ['validate', 'submit'],
70
+		options: {
71
+			// #ifdef MP-TOUTIAO
72
+			virtualHost: false,
73
+			// #endif
74
+			// #ifndef MP-TOUTIAO
75
+			virtualHost: true
76
+			// #endif
77
+		},
78
+		props: {
79
+			// 即将弃用
80
+			value: {
81
+				type: Object,
82
+				default () {
83
+					return null;
84
+				}
85
+			},
86
+			// vue3 替换 value 属性
87
+			modelValue: {
88
+				type: Object,
89
+				default () {
90
+					return null;
91
+				}
92
+			},
93
+			// 1.4.0 开始将不支持 v-model ,且废弃 value 和 modelValue
94
+			model: {
95
+				type: Object,
96
+				default () {
97
+					return null;
98
+				}
99
+			},
100
+			// 表单校验规则
101
+			rules: {
102
+				type: Object,
103
+				default () {
104
+					return {};
105
+				}
106
+			},
107
+			//校验错误信息提示方式 默认 undertext 取值 [undertext|toast|modal]
108
+			errShowType: {
109
+				type: String,
110
+				default: 'undertext'
111
+			},
112
+			// 校验触发器方式 默认 bind 取值 [bind|submit]
113
+			validateTrigger: {
114
+				type: String,
115
+				default: 'submit'
116
+			},
117
+			// label 位置,默认 left 取值  top/left
118
+			labelPosition: {
119
+				type: String,
120
+				default: 'left'
121
+			},
122
+			// label 宽度
123
+			labelWidth: {
124
+				type: [String, Number],
125
+				default: ''
126
+			},
127
+			// label 居中方式,默认 left 取值 left/center/right
128
+			labelAlign: {
129
+				type: String,
130
+				default: 'left'
131
+			},
132
+			border: {
133
+				type: Boolean,
134
+				default: false
135
+			}
136
+		},
137
+		provide() {
138
+			return {
139
+				uniForm: this
140
+			}
141
+		},
142
+		data() {
143
+			return {
144
+				// 表单本地值的记录,不应该与传如的值进行关联
145
+				formData: {},
146
+				formRules: {}
147
+			};
148
+		},
149
+		computed: {
150
+			// 计算数据源变化的
151
+			localData() {
152
+				const localVal = this.model || this.modelValue || this.value
153
+				if (localVal) {
154
+					return deepCopy(localVal)
155
+				}
156
+				return {}
157
+			}
158
+		},
159
+		watch: {
160
+			// 监听数据变化 ,暂时不使用,需要单独赋值
161
+			// localData: {},
162
+			// 监听规则变化
163
+			rules: {
164
+				handler: function(val, oldVal) {
165
+					this.setRules(val)
166
+				},
167
+				deep: true,
168
+				immediate: true
169
+			}
170
+		},
171
+		created() {
172
+			// #ifdef VUE3
173
+			let getbinddata = getApp().$vm.$.appContext.config.globalProperties.binddata
174
+			if (!getbinddata) {
175
+				getApp().$vm.$.appContext.config.globalProperties.binddata = function(name, value, formName) {
176
+					if (formName) {
177
+						this.$refs[formName].setValue(name, value);
178
+					} else {
179
+						let formVm;
180
+						for (let i in this.$refs) {
181
+							const vm = this.$refs[i];
182
+							if (vm && vm.$options && vm.$options.name === 'uniForms') {
183
+								formVm = vm;
184
+								break;
185
+							}
186
+						}
187
+						if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性');
188
+						if(formVm.model)formVm.model[name] = value
189
+						if(formVm.modelValue)formVm.modelValue[name] = value
190
+						if(formVm.value)formVm.value[name] = value
191
+					}
192
+				}
193
+			}
194
+			// #endif
195
+
196
+			// 子组件实例数组
197
+			this.childrens = []
198
+			// TODO 兼容旧版 uni-data-picker ,新版本中无效,只是避免报错
199
+			this.inputChildrens = []
200
+			this.setRules(this.rules)
201
+		},
202
+		methods: {
203
+			/**
204
+			 * 外部调用方法
205
+			 * 设置规则 ,主要用于小程序自定义检验规则
206
+			 * @param {Array} rules 规则源数据
207
+			 */
208
+			setRules(rules) {
209
+				// TODO 有可能子组件合并规则的时机比这个要早,所以需要合并对象 ,而不是直接赋值,可能会被覆盖
210
+				this.formRules = Object.assign({}, this.formRules, rules)
211
+				// 初始化校验函数
212
+				this.validator = new Validator(rules);
213
+			},
214
+
215
+			/**
216
+			 * 外部调用方法
217
+			 * 设置数据,用于设置表单数据,公开给用户使用 , 不支持在动态表单中使用
218
+			 * @param {Object} key
219
+			 * @param {Object} value
220
+			 */
221
+			setValue(key, value) {
222
+				let example = this.childrens.find(child => child.name === key);
223
+				if (!example) return null;
224
+				this.formData[key] = getValue(key, value, (this.formRules[key] && this.formRules[key].rules) || [])
225
+				return example.onFieldChange(this.formData[key]);
226
+			},
227
+
228
+			/**
229
+			 * 外部调用方法
230
+			 * 手动提交校验表单
231
+			 * 对整个表单进行校验的方法,参数为一个回调函数。
232
+			 * @param {Array} keepitem 保留不参与校验的字段
233
+			 * @param {type} callback 方法回调
234
+			 */
235
+			validate(keepitem, callback) {
236
+				return this.checkAll(this.formData, keepitem, callback);
237
+			},
238
+
239
+			/**
240
+			 * 外部调用方法
241
+			 * 部分表单校验
242
+			 * @param {Array|String} props 需要校验的字段
243
+			 * @param {Function} 回调函数
244
+			 */
245
+			validateField(props = [], callback) {
246
+				props = [].concat(props);
247
+				let invalidFields = {};
248
+				this.childrens.forEach(item => {
249
+					const name = realName(item.name)
250
+					if (props.indexOf(name) !== -1) {
251
+						invalidFields = Object.assign({}, invalidFields, {
252
+							[name]: this.formData[name]
253
+						});
254
+					}
255
+				});
256
+				return this.checkAll(invalidFields, [], callback);
257
+			},
258
+
259
+			/**
260
+			 * 外部调用方法
261
+			 * 移除表单项的校验结果。传入待移除的表单项的 prop 属性或者 prop 组成的数组,如不传则移除整个表单的校验结果
262
+			 * @param {Array|String} props 需要移除校验的字段 ,不填为所有
263
+			 */
264
+			clearValidate(props = []) {
265
+				props = [].concat(props);
266
+				this.childrens.forEach(item => {
267
+					if (props.length === 0) {
268
+						item.errMsg = '';
269
+					} else {
270
+						const name = realName(item.name)
271
+						if (props.indexOf(name) !== -1) {
272
+							item.errMsg = '';
273
+						}
274
+					}
275
+				});
276
+			},
277
+
278
+			/**
279
+			 * 外部调用方法 ,即将废弃
280
+			 * 手动提交校验表单
281
+			 * 对整个表单进行校验的方法,参数为一个回调函数。
282
+			 * @param {Array} keepitem 保留不参与校验的字段
283
+			 * @param {type} callback 方法回调
284
+			 */
285
+			submit(keepitem, callback, type) {
286
+				for (let i in this.dataValue) {
287
+					const itemData = this.childrens.find(v => v.name === i);
288
+					if (itemData) {
289
+						if (this.formData[i] === undefined) {
290
+							this.formData[i] = this._getValue(i, this.dataValue[i]);
291
+						}
292
+					}
293
+				}
294
+
295
+				if (!type) {
296
+					console.warn('submit 方法即将废弃,请使用validate方法代替!');
297
+				}
298
+
299
+				return this.checkAll(this.formData, keepitem, callback, 'submit');
300
+			},
301
+
302
+			// 校验所有
303
+			async checkAll(invalidFields, keepitem, callback, type) {
304
+				// 不存在校验规则 ,则停止校验流程
305
+				if (!this.validator) return
306
+				let childrens = []
307
+				// 处理参与校验的item实例
308
+				for (let i in invalidFields) {
309
+					const item = this.childrens.find(v => realName(v.name) === i)
310
+					if (item) {
311
+						childrens.push(item)
312
+					}
313
+				}
314
+
315
+				// 如果validate第一个参数是funciont ,那就走回调
316
+				if (!callback && typeof keepitem === 'function') {
317
+					callback = keepitem;
318
+				}
319
+
320
+				let promise;
321
+				// 如果不存在回调,那么使用 Promise 方式返回
322
+				if (!callback && typeof callback !== 'function' && Promise) {
323
+					promise = new Promise((resolve, reject) => {
324
+						callback = function(valid, invalidFields) {
325
+							!valid ? resolve(invalidFields) : reject(valid);
326
+						};
327
+					});
328
+				}
329
+
330
+				let results = [];
331
+				// 避免引用错乱 ,建议拷贝对象处理
332
+				let tempFormData = JSON.parse(JSON.stringify(invalidFields))
333
+				// 所有子组件参与校验,使用 for 可以使用  awiat
334
+				for (let i in childrens) {
335
+					const child = childrens[i]
336
+					let name = realName(child.name);
337
+					const result = await child.onFieldChange(tempFormData[name]);
338
+					if (result) {
339
+						results.push(result);
340
+						// toast ,modal 只需要执行第一次就可以
341
+						if (this.errShowType === 'toast' || this.errShowType === 'modal') break;
342
+					}
343
+				}
344
+
345
+
346
+				if (Array.isArray(results)) {
347
+					if (results.length === 0) results = null;
348
+				}
349
+				if (Array.isArray(keepitem)) {
350
+					keepitem.forEach(v => {
351
+						let vName = realName(v);
352
+						let value = getDataValue(v, this.localData)
353
+						if (value !== undefined) {
354
+							tempFormData[vName] = value
355
+						}
356
+					});
357
+				}
358
+
359
+				// TODO submit 即将废弃
360
+				if (type === 'submit') {
361
+					this.$emit('submit', {
362
+						detail: {
363
+							value: tempFormData,
364
+							errors: results
365
+						}
366
+					});
367
+				} else {
368
+					this.$emit('validate', results);
369
+				}
370
+
371
+				// const resetFormData = rawData(tempFormData, this.localData, this.name)
372
+				let resetFormData = {}
373
+				resetFormData = rawData(tempFormData, this.name)
374
+				callback && typeof callback === 'function' && callback(results, resetFormData);
375
+
376
+				if (promise && callback) {
377
+					return promise;
378
+				} else {
379
+					return null;
380
+				}
381
+
382
+			},
383
+
384
+			/**
385
+			 * 返回validate事件
386
+			 * @param {Object} result
387
+			 */
388
+			validateCheck(result) {
389
+				this.$emit('validate', result);
390
+			},
391
+			_getValue: getValue,
392
+			_isRequiredField: isRequiredField,
393
+			_setDataValue: setDataValue,
394
+			_getDataValue: getDataValue,
395
+			_realName: realName,
396
+			_isRealName: isRealName,
397
+			_isEqual: isEqual
398
+		}
399
+	};
400
+</script>
401
+
402
+<style lang="scss">
403
+	.uni-forms {}
404
+</style>

+ 293 - 0
uni_modules/uni-forms/components/uni-forms/utils.js

@@ -0,0 +1,293 @@
1
+/**
2
+ * 简单处理对象拷贝
3
+ * @param {Obejct} 被拷贝对象
4
+ * @@return {Object} 拷贝对象
5
+ */
6
+export const deepCopy = (val) => {
7
+	return JSON.parse(JSON.stringify(val))
8
+}
9
+/**
10
+ * 过滤数字类型
11
+ * @param {String} format 数字类型
12
+ * @@return {Boolean} 返回是否为数字类型
13
+ */
14
+export const typeFilter = (format) => {
15
+	return format === 'int' || format === 'double' || format === 'number' || format === 'timestamp';
16
+}
17
+
18
+/**
19
+ * 把 value 转换成指定的类型,用于处理初始值,原因是初始值需要入库不能为 undefined
20
+ * @param {String} key 字段名
21
+ * @param {any} value 字段值
22
+ * @param {Object} rules 表单校验规则
23
+ */
24
+export const getValue = (key, value, rules) => {
25
+	const isRuleNumType = rules.find(val => val.format && typeFilter(val.format));
26
+	const isRuleBoolType = rules.find(val => (val.format && val.format === 'boolean') || val.format === 'bool');
27
+	// 输入类型为 number
28
+	if (!!isRuleNumType) {
29
+		if (!value && value !== 0) {
30
+			value = null
31
+		} else {
32
+			value = isNumber(Number(value)) ? Number(value) : value
33
+		}
34
+	}
35
+
36
+	// 输入类型为 boolean
37
+	if (!!isRuleBoolType) {
38
+		value = isBoolean(value) ? value : false
39
+	}
40
+
41
+	return value;
42
+}
43
+
44
+/**
45
+ * 获取表单数据
46
+ * @param {String|Array} name 真实名称,需要使用 realName 获取
47
+ * @param {Object} data 原始数据
48
+ * @param {any} value  需要设置的值
49
+ */
50
+export const setDataValue = (field, formdata, value) => {
51
+	formdata[field] = value
52
+	return value || ''
53
+}
54
+
55
+/**
56
+ * 获取表单数据
57
+ * @param {String|Array} field 真实名称,需要使用 realName 获取
58
+ * @param {Object} data 原始数据
59
+ */
60
+export const getDataValue = (field, data) => {
61
+	return objGet(data, field)
62
+}
63
+
64
+/**
65
+ * 获取表单类型
66
+ * @param {String|Array} field 真实名称,需要使用 realName 获取
67
+ */
68
+export const getDataValueType = (field, data) => {
69
+	const value = getDataValue(field, data)
70
+	return {
71
+		type: type(value),
72
+		value
73
+	}
74
+}
75
+
76
+/**
77
+ * 获取表单可用的真实name
78
+ * @param {String|Array} name 表单name
79
+ * @@return {String} 表单可用的真实name
80
+ */
81
+export const realName = (name, data = {}) => {
82
+	const base_name = _basePath(name)
83
+	if (typeof base_name === 'object' && Array.isArray(base_name) && base_name.length > 1) {
84
+		const realname = base_name.reduce((a, b) => a += `#${b}`, '_formdata_')
85
+		return realname
86
+	}
87
+	return base_name[0] || name
88
+}
89
+
90
+/**
91
+ * 判断是否表单可用的真实name
92
+ * @param {String|Array} name 表单name
93
+ * @@return {String} 表单可用的真实name
94
+ */
95
+export const isRealName = (name) => {
96
+	const reg = /^_formdata_#*/
97
+	return reg.test(name)
98
+}
99
+
100
+/**
101
+ * 获取表单数据的原始格式
102
+ * @@return {Object|Array} object 需要解析的数据
103
+ */
104
+export const rawData = (object = {}, name) => {
105
+	let newData = JSON.parse(JSON.stringify(object))
106
+	let formData = {}
107
+	for(let i in newData){
108
+		let path = name2arr(i)
109
+		objSet(formData,path,newData[i])
110
+	}
111
+	return formData
112
+}
113
+
114
+/**
115
+ * 真实name还原为 array
116
+ * @param {*} name 
117
+ */
118
+export const name2arr = (name) => {
119
+	let field = name.replace('_formdata_#', '')
120
+	field = field.split('#').map(v => (isNumber(v) ? Number(v) : v))
121
+	return field
122
+}
123
+
124
+/**
125
+ * 对象中设置值
126
+ * @param {Object|Array} object 源数据
127
+ * @param {String| Array} path 'a.b.c' 或 ['a',0,'b','c']
128
+ * @param {String} value 需要设置的值
129
+ */
130
+export const objSet = (object, path, value) => {
131
+	if (typeof object !== 'object') return object;
132
+	_basePath(path).reduce((o, k, i, _) => {
133
+		if (i === _.length - 1) { 
134
+			// 若遍历结束直接赋值
135
+			o[k] = value
136
+			return null
137
+		} else if (k in o) { 
138
+			// 若存在对应路径,则返回找到的对象,进行下一次遍历
139
+			return o[k]
140
+		} else { 
141
+			// 若不存在对应路径,则创建对应对象,若下一路径是数字,新对象赋值为空数组,否则赋值为空对象
142
+			o[k] = /^[0-9]{1,}$/.test(_[i + 1]) ? [] : {}
143
+			return o[k]
144
+		}
145
+	}, object)
146
+	// 返回object
147
+	return object;
148
+}
149
+
150
+// 处理 path, path有三种形式:'a[0].b.c'、'a.0.b.c' 和 ['a','0','b','c'],需要统一处理成数组,便于后续使用
151
+function _basePath(path) {
152
+	// 若是数组,则直接返回
153
+	if (Array.isArray(path)) return path
154
+	// 若有 '[',']',则替换成将 '[' 替换成 '.',去掉 ']'
155
+	return path.replace(/\[/g, '.').replace(/\]/g, '').split('.')
156
+}
157
+
158
+/**
159
+ * 从对象中获取值
160
+ * @param {Object|Array} object 源数据
161
+ * @param {String| Array} path 'a.b.c' 或 ['a',0,'b','c']
162
+ * @param {String} defaultVal 如果无法从调用链中获取值的默认值
163
+ */
164
+export const objGet = (object, path, defaultVal = 'undefined') => {
165
+	// 先将path处理成统一格式
166
+	let newPath = _basePath(path)
167
+	// 递归处理,返回最后结果
168
+	let val = newPath.reduce((o, k) => {
169
+		return (o || {})[k]
170
+	}, object);
171
+	return !val || val !== undefined ? val : defaultVal
172
+}
173
+
174
+
175
+/**
176
+ * 是否为 number 类型 
177
+ * @param {any} num 需要判断的值
178
+ * @return {Boolean} 是否为 number
179
+ */
180
+export const isNumber = (num) => {
181
+	return !isNaN(Number(num))
182
+}
183
+
184
+/**
185
+ * 是否为 boolean 类型 
186
+ * @param {any} bool 需要判断的值
187
+ * @return {Boolean} 是否为 boolean
188
+ */
189
+export const isBoolean = (bool) => {
190
+	return (typeof bool === 'boolean')
191
+}
192
+/**
193
+ * 是否有必填字段
194
+ * @param {Object} rules 规则
195
+ * @return {Boolean} 是否有必填字段
196
+ */
197
+export const isRequiredField = (rules) => {
198
+	let isNoField = false;
199
+	for (let i = 0; i < rules.length; i++) {
200
+		const ruleData = rules[i];
201
+		if (ruleData.required) {
202
+			isNoField = true;
203
+			break;
204
+		}
205
+	}
206
+	return isNoField;
207
+}
208
+
209
+
210
+/**
211
+ * 获取数据类型
212
+ * @param {Any} obj 需要获取数据类型的值
213
+ */
214
+export const type = (obj) => {
215
+	var class2type = {};
216
+
217
+	// 生成class2type映射
218
+	"Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) {
219
+		class2type["[object " + item + "]"] = item.toLowerCase();
220
+	})
221
+	if (obj == null) {
222
+		return obj + "";
223
+	}
224
+	return typeof obj === "object" || typeof obj === "function" ?
225
+		class2type[Object.prototype.toString.call(obj)] || "object" :
226
+		typeof obj;
227
+}
228
+
229
+/**
230
+ * 判断两个值是否相等
231
+ * @param {any} a 值  
232
+ * @param {any} b 值  
233
+ * @return {Boolean} 是否相等
234
+ */
235
+export const isEqual = (a, b) => {
236
+	//如果a和b本来就全等
237
+	if (a === b) {
238
+		//判断是否为0和-0
239
+		return a !== 0 || 1 / a === 1 / b;
240
+	}
241
+	//判断是否为null和undefined
242
+	if (a == null || b == null) {
243
+		return a === b;
244
+	}
245
+	//接下来判断a和b的数据类型
246
+	var classNameA = toString.call(a),
247
+		classNameB = toString.call(b);
248
+	//如果数据类型不相等,则返回false
249
+	if (classNameA !== classNameB) {
250
+		return false;
251
+	}
252
+	//如果数据类型相等,再根据不同数据类型分别判断
253
+	switch (classNameA) {
254
+		case '[object RegExp]':
255
+		case '[object String]':
256
+			//进行字符串转换比较
257
+			return '' + a === '' + b;
258
+		case '[object Number]':
259
+			//进行数字转换比较,判断是否为NaN
260
+			if (+a !== +a) {
261
+				return +b !== +b;
262
+			}
263
+			//判断是否为0或-0
264
+			return +a === 0 ? 1 / +a === 1 / b : +a === +b;
265
+		case '[object Date]':
266
+		case '[object Boolean]':
267
+			return +a === +b;
268
+	}
269
+	//如果是对象类型
270
+	if (classNameA == '[object Object]') {
271
+		//获取a和b的属性长度
272
+		var propsA = Object.getOwnPropertyNames(a),
273
+			propsB = Object.getOwnPropertyNames(b);
274
+		if (propsA.length != propsB.length) {
275
+			return false;
276
+		}
277
+		for (var i = 0; i < propsA.length; i++) {
278
+			var propName = propsA[i];
279
+			//如果对应属性对应值不相等,则返回false
280
+			if (a[propName] !== b[propName]) {
281
+				return false;
282
+			}
283
+		}
284
+		return true;
285
+	}
286
+	//如果是数组类型
287
+	if (classNameA == '[object Array]') {
288
+		if (a.toString() == b.toString()) {
289
+			return true;
290
+		}
291
+		return false;
292
+	}
293
+}

+ 486 - 0
uni_modules/uni-forms/components/uni-forms/validate.js

@@ -0,0 +1,486 @@
1
+var pattern = {
2
+	email: /^\S+?@\S+?\.\S+?$/,
3
+	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]$/,
4
+	url: new RegExp(
5
+		"^(?!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]*)?$",
6
+		'i')
7
+};
8
+
9
+const FORMAT_MAPPING = {
10
+	"int": 'integer',
11
+	"bool": 'boolean',
12
+	"double": 'number',
13
+	"long": 'number',
14
+	"password": 'string'
15
+	// "fileurls": 'array'
16
+}
17
+
18
+function formatMessage(args, resources = '') {
19
+	var defaultMessage = ['label']
20
+	defaultMessage.forEach((item) => {
21
+		if (args[item] === undefined) {
22
+			args[item] = ''
23
+		}
24
+	})
25
+
26
+	let str = resources
27
+	for (let key in args) {
28
+		let reg = new RegExp('{' + key + '}')
29
+		str = str.replace(reg, args[key])
30
+	}
31
+	return str
32
+}
33
+
34
+function isEmptyValue(value, type) {
35
+	if (value === undefined || value === null) {
36
+		return true;
37
+	}
38
+
39
+	if (typeof value === 'string' && !value) {
40
+		return true;
41
+	}
42
+
43
+	if (Array.isArray(value) && !value.length) {
44
+		return true;
45
+	}
46
+
47
+	if (type === 'object' && !Object.keys(value).length) {
48
+		return true;
49
+	}
50
+
51
+	return false;
52
+}
53
+
54
+const types = {
55
+	integer(value) {
56
+		return types.number(value) && parseInt(value, 10) === value;
57
+	},
58
+	string(value) {
59
+		return typeof value === 'string';
60
+	},
61
+	number(value) {
62
+		if (isNaN(value)) {
63
+			return false;
64
+		}
65
+		return typeof value === 'number';
66
+	},
67
+	"boolean": function(value) {
68
+		return typeof value === 'boolean';
69
+	},
70
+	"float": function(value) {
71
+		return types.number(value) && !types.integer(value);
72
+	},
73
+	array(value) {
74
+		return Array.isArray(value);
75
+	},
76
+	object(value) {
77
+		return typeof value === 'object' && !types.array(value);
78
+	},
79
+	date(value) {
80
+		return value instanceof Date;
81
+	},
82
+	timestamp(value) {
83
+		if (!this.integer(value) || Math.abs(value).toString().length > 16) {
84
+			return false
85
+		}
86
+		return true;
87
+	},
88
+	file(value) {
89
+		return typeof value.url === 'string';
90
+	},
91
+	email(value) {
92
+		return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255;
93
+	},
94
+	url(value) {
95
+		return typeof value === 'string' && !!value.match(pattern.url);
96
+	},
97
+	pattern(reg, value) {
98
+		try {
99
+			return new RegExp(reg).test(value);
100
+		} catch (e) {
101
+			return false;
102
+		}
103
+	},
104
+	method(value) {
105
+		return typeof value === 'function';
106
+	},
107
+	idcard(value) {
108
+		return typeof value === 'string' && !!value.match(pattern.idcard);
109
+	},
110
+	'url-https'(value) {
111
+		return this.url(value) && value.startsWith('https://');
112
+	},
113
+	'url-scheme'(value) {
114
+		return value.startsWith('://');
115
+	},
116
+	'url-web'(value) {
117
+		return false;
118
+	}
119
+}
120
+
121
+class RuleValidator {
122
+
123
+	constructor(message) {
124
+		this._message = message
125
+	}
126
+
127
+	async validateRule(fieldKey, fieldValue, value, data, allData) {
128
+		var result = null
129
+
130
+		let rules = fieldValue.rules
131
+
132
+		let hasRequired = rules.findIndex((item) => {
133
+			return item.required
134
+		})
135
+		if (hasRequired < 0) {
136
+			if (value === null || value === undefined) {
137
+				return result
138
+			}
139
+			if (typeof value === 'string' && !value.length) {
140
+				return result
141
+			}
142
+		}
143
+
144
+		var message = this._message
145
+
146
+		if (rules === undefined) {
147
+			return message['default']
148
+		}
149
+
150
+		for (var i = 0; i < rules.length; i++) {
151
+			let rule = rules[i]
152
+			let vt = this._getValidateType(rule)
153
+
154
+			Object.assign(rule, {
155
+				label: fieldValue.label || `["${fieldKey}"]`
156
+			})
157
+
158
+			if (RuleValidatorHelper[vt]) {
159
+				result = RuleValidatorHelper[vt](rule, value, message)
160
+				if (result != null) {
161
+					break
162
+				}
163
+			}
164
+
165
+			if (rule.validateExpr) {
166
+				let now = Date.now()
167
+				let resultExpr = rule.validateExpr(value, allData, now)
168
+				if (resultExpr === false) {
169
+					result = this._getMessage(rule, rule.errorMessage || this._message['default'])
170
+					break
171
+				}
172
+			}
173
+
174
+			if (rule.validateFunction) {
175
+				result = await this.validateFunction(rule, value, data, allData, vt)
176
+				if (result !== null) {
177
+					break
178
+				}
179
+			}
180
+		}
181
+
182
+		if (result !== null) {
183
+			result = message.TAG + result
184
+		}
185
+
186
+		return result
187
+	}
188
+
189
+	async validateFunction(rule, value, data, allData, vt) {
190
+		let result = null
191
+		try {
192
+			let callbackMessage = null
193
+			const res = await rule.validateFunction(rule, value, allData || data, (message) => {
194
+				callbackMessage = message
195
+			})
196
+			if (callbackMessage || (typeof res === 'string' && res) || res === false) {
197
+				result = this._getMessage(rule, callbackMessage || res, vt)
198
+			}
199
+		} catch (e) {
200
+			result = this._getMessage(rule, e.message, vt)
201
+		}
202
+		return result
203
+	}
204
+
205
+	_getMessage(rule, message, vt) {
206
+		return formatMessage(rule, message || rule.errorMessage || this._message[vt] || message['default'])
207
+	}
208
+
209
+	_getValidateType(rule) {
210
+		var result = ''
211
+		if (rule.required) {
212
+			result = 'required'
213
+		} else if (rule.format) {
214
+			result = 'format'
215
+		} else if (rule.arrayType) {
216
+			result = 'arrayTypeFormat'
217
+		} else if (rule.range) {
218
+			result = 'range'
219
+		} else if (rule.maximum !== undefined || rule.minimum !== undefined) {
220
+			result = 'rangeNumber'
221
+		} else if (rule.maxLength !== undefined || rule.minLength !== undefined) {
222
+			result = 'rangeLength'
223
+		} else if (rule.pattern) {
224
+			result = 'pattern'
225
+		} else if (rule.validateFunction) {
226
+			result = 'validateFunction'
227
+		}
228
+		return result
229
+	}
230
+}
231
+
232
+const RuleValidatorHelper = {
233
+	required(rule, value, message) {
234
+		if (rule.required && isEmptyValue(value, rule.format || typeof value)) {
235
+			return formatMessage(rule, rule.errorMessage || message.required);
236
+		}
237
+
238
+		return null
239
+	},
240
+
241
+	range(rule, value, message) {
242
+		const {
243
+			range,
244
+			errorMessage
245
+		} = rule;
246
+
247
+		let list = new Array(range.length);
248
+		for (let i = 0; i < range.length; i++) {
249
+			const item = range[i];
250
+			if (types.object(item) && item.value !== undefined) {
251
+				list[i] = item.value;
252
+			} else {
253
+				list[i] = item;
254
+			}
255
+		}
256
+
257
+		let result = false
258
+		if (Array.isArray(value)) {
259
+			result = (new Set(value.concat(list)).size === list.length);
260
+		} else {
261
+			if (list.indexOf(value) > -1) {
262
+				result = true;
263
+			}
264
+		}
265
+
266
+		if (!result) {
267
+			return formatMessage(rule, errorMessage || message['enum']);
268
+		}
269
+
270
+		return null
271
+	},
272
+
273
+	rangeNumber(rule, value, message) {
274
+		if (!types.number(value)) {
275
+			return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
276
+		}
277
+
278
+		let {
279
+			minimum,
280
+			maximum,
281
+			exclusiveMinimum,
282
+			exclusiveMaximum
283
+		} = rule;
284
+		let min = exclusiveMinimum ? value <= minimum : value < minimum;
285
+		let max = exclusiveMaximum ? value >= maximum : value > maximum;
286
+
287
+		if (minimum !== undefined && min) {
288
+			return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMinimum ?
289
+				'exclusiveMinimum' : 'minimum'
290
+			])
291
+		} else if (maximum !== undefined && max) {
292
+			return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMaximum ?
293
+				'exclusiveMaximum' : 'maximum'
294
+			])
295
+		} else if (minimum !== undefined && maximum !== undefined && (min || max)) {
296
+			return formatMessage(rule, rule.errorMessage || message['number'].range)
297
+		}
298
+
299
+		return null
300
+	},
301
+
302
+	rangeLength(rule, value, message) {
303
+		if (!types.string(value) && !types.array(value)) {
304
+			return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
305
+		}
306
+
307
+		let min = rule.minLength;
308
+		let max = rule.maxLength;
309
+		let val = value.length;
310
+
311
+		if (min !== undefined && val < min) {
312
+			return formatMessage(rule, rule.errorMessage || message['length'].minLength)
313
+		} else if (max !== undefined && val > max) {
314
+			return formatMessage(rule, rule.errorMessage || message['length'].maxLength)
315
+		} else if (min !== undefined && max !== undefined && (val < min || val > max)) {
316
+			return formatMessage(rule, rule.errorMessage || message['length'].range)
317
+		}
318
+
319
+		return null
320
+	},
321
+
322
+	pattern(rule, value, message) {
323
+		if (!types['pattern'](rule.pattern, value)) {
324
+			return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
325
+		}
326
+
327
+		return null
328
+	},
329
+
330
+	format(rule, value, message) {
331
+		var customTypes = Object.keys(types);
332
+		var format = FORMAT_MAPPING[rule.format] ? FORMAT_MAPPING[rule.format] : (rule.format || rule.arrayType);
333
+
334
+		if (customTypes.indexOf(format) > -1) {
335
+			if (!types[format](value)) {
336
+				return formatMessage(rule, rule.errorMessage || message.typeError);
337
+			}
338
+		}
339
+
340
+		return null
341
+	},
342
+
343
+	arrayTypeFormat(rule, value, message) {
344
+		if (!Array.isArray(value)) {
345
+			return formatMessage(rule, rule.errorMessage || message.typeError);
346
+		}
347
+
348
+		for (let i = 0; i < value.length; i++) {
349
+			const element = value[i];
350
+			let formatResult = this.format(rule, element, message)
351
+			if (formatResult !== null) {
352
+				return formatResult
353
+			}
354
+		}
355
+
356
+		return null
357
+	}
358
+}
359
+
360
+class SchemaValidator extends RuleValidator {
361
+
362
+	constructor(schema, options) {
363
+		super(SchemaValidator.message);
364
+
365
+		this._schema = schema
366
+		this._options = options || null
367
+	}
368
+
369
+	updateSchema(schema) {
370
+		this._schema = schema
371
+	}
372
+
373
+	async validate(data, allData) {
374
+		let result = this._checkFieldInSchema(data)
375
+		if (!result) {
376
+			result = await this.invokeValidate(data, false, allData)
377
+		}
378
+		return result.length ? result[0] : null
379
+	}
380
+
381
+	async validateAll(data, allData) {
382
+		let result = this._checkFieldInSchema(data)
383
+		if (!result) {
384
+			result = await this.invokeValidate(data, true, allData)
385
+		}
386
+		return result
387
+	}
388
+
389
+	async validateUpdate(data, allData) {
390
+		let result = this._checkFieldInSchema(data)
391
+		if (!result) {
392
+			result = await this.invokeValidateUpdate(data, false, allData)
393
+		}
394
+		return result.length ? result[0] : null
395
+	}
396
+
397
+	async invokeValidate(data, all, allData) {
398
+		let result = []
399
+		let schema = this._schema
400
+		for (let key in schema) {
401
+			let value = schema[key]
402
+			let errorMessage = await this.validateRule(key, value, data[key], data, allData)
403
+			if (errorMessage != null) {
404
+				result.push({
405
+					key,
406
+					errorMessage
407
+				})
408
+				if (!all) break
409
+			}
410
+		}
411
+		return result
412
+	}
413
+
414
+	async invokeValidateUpdate(data, all, allData) {
415
+		let result = []
416
+		for (let key in data) {
417
+			let errorMessage = await this.validateRule(key, this._schema[key], data[key], data, allData)
418
+			if (errorMessage != null) {
419
+				result.push({
420
+					key,
421
+					errorMessage
422
+				})
423
+				if (!all) break
424
+			}
425
+		}
426
+		return result
427
+	}
428
+
429
+	_checkFieldInSchema(data) {
430
+		var keys = Object.keys(data)
431
+		var keys2 = Object.keys(this._schema)
432
+		if (new Set(keys.concat(keys2)).size === keys2.length) {
433
+			return ''
434
+		}
435
+
436
+		var noExistFields = keys.filter((key) => {
437
+			return keys2.indexOf(key) < 0;
438
+		})
439
+		var errorMessage = formatMessage({
440
+			field: JSON.stringify(noExistFields)
441
+		}, SchemaValidator.message.TAG + SchemaValidator.message['defaultInvalid'])
442
+		return [{
443
+			key: 'invalid',
444
+			errorMessage
445
+		}]
446
+	}
447
+}
448
+
449
+function Message() {
450
+	return {
451
+		TAG: "",
452
+		default: '验证错误',
453
+		defaultInvalid: '提交的字段{field}在数据库中并不存在',
454
+		validateFunction: '验证无效',
455
+		required: '{label}必填',
456
+		'enum': '{label}超出范围',
457
+		timestamp: '{label}格式无效',
458
+		whitespace: '{label}不能为空',
459
+		typeError: '{label}类型无效',
460
+		date: {
461
+			format: '{label}日期{value}格式无效',
462
+			parse: '{label}日期无法解析,{value}无效',
463
+			invalid: '{label}日期{value}无效'
464
+		},
465
+		length: {
466
+			minLength: '{label}长度不能少于{minLength}',
467
+			maxLength: '{label}长度不能超过{maxLength}',
468
+			range: '{label}必须介于{minLength}和{maxLength}之间'
469
+		},
470
+		number: {
471
+			minimum: '{label}不能小于{minimum}',
472
+			maximum: '{label}不能大于{maximum}',
473
+			exclusiveMinimum: '{label}不能小于等于{minimum}',
474
+			exclusiveMaximum: '{label}不能大于等于{maximum}',
475
+			range: '{label}必须介于{minimum}and{maximum}之间'
476
+		},
477
+		pattern: {
478
+			mismatch: '{label}格式不匹配'
479
+		}
480
+	};
481
+}
482
+
483
+
484
+SchemaValidator.message = new Message();
485
+
486
+export default SchemaValidator

+ 89 - 0
uni_modules/uni-forms/package.json

@@ -0,0 +1,89 @@
1
+{
2
+  "id": "uni-forms",
3
+  "displayName": "uni-forms 表单",
4
+  "version": "1.4.13",
5
+  "description": "由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据",
6
+  "keywords": [
7
+    "uni-ui",
8
+    "表单",
9
+    "校验",
10
+    "表单校验",
11
+    "表单验证"
12
+],
13
+  "repository": "https://github.com/dcloudio/uni-ui",
14
+  "engines": {
15
+    "HBuilderX": ""
16
+  },
17
+  "directories": {
18
+    "example": "../../temps/example_temps"
19
+  },
20
+"dcloudext": {
21
+    "sale": {
22
+      "regular": {
23
+        "price": "0.00"
24
+      },
25
+      "sourcecode": {
26
+        "price": "0.00"
27
+      }
28
+    },
29
+    "contact": {
30
+      "qq": ""
31
+    },
32
+    "declaration": {
33
+      "ads": "无",
34
+      "data": "无",
35
+      "permissions": "无"
36
+    },
37
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
38
+    "type": "component-vue"
39
+  },
40
+  "uni_modules": {
41
+    "dependencies": [
42
+			"uni-scss",
43
+      "uni-icons"
44
+    ],
45
+    "encrypt": [],
46
+    "platforms": {
47
+      "cloud": {
48
+        "tcb": "y",
49
+        "aliyun": "y",
50
+        "alipay": "n"
51
+      },
52
+      "client": {
53
+        "App": {
54
+          "app-vue": "y",
55
+          "app-nvue": "y"
56
+        },
57
+        "H5-mobile": {
58
+          "Safari": "y",
59
+          "Android Browser": "y",
60
+          "微信浏览器(Android)": "y",
61
+          "QQ浏览器(Android)": "y"
62
+        },
63
+        "H5-pc": {
64
+          "Chrome": "y",
65
+          "IE": "y",
66
+          "Edge": "y",
67
+          "Firefox": "y",
68
+          "Safari": "y"
69
+        },
70
+        "小程序": {
71
+          "微信": "y",
72
+          "阿里": "y",
73
+          "百度": "y",
74
+          "字节跳动": "y",
75
+        "QQ": "y",
76
+        "京东": "u"
77
+        },
78
+        "快应用": {
79
+          "华为": "u",
80
+          "联盟": "u"
81
+        },
82
+        "Vue": {
83
+            "vue2": "y",
84
+            "vue3": "y"
85
+        }
86
+      }
87
+    }
88
+  }
89
+}

+ 23 - 0
uni_modules/uni-forms/readme.md

@@ -0,0 +1,23 @@
1
+
2
+
3
+## Forms 表单
4
+
5
+> **组件名:uni-forms**
6
+> 代码块: `uForms`、`uni-forms-item`
7
+> 关联组件:`uni-forms-item`、`uni-easyinput`、`uni-data-checkbox`、`uni-group`。
8
+
9
+
10
+uni-app的内置组件已经有了 `<form>`组件,用于提交表单内容。
11
+
12
+然而几乎每个表单都需要做表单验证,为了方便做表单验证,减少重复开发,`uni ui` 又基于 `<form>`组件封装了 `<uni-forms>`组件,内置了表单验证功能。
13
+
14
+`<uni-forms>` 提供了 `rules`属性来描述校验规则、`<uni-forms-item>`子组件来包裹具体的表单项,以及给原生或三方组件提供了 `binddata()` 来设置表单值。
15
+
16
+每个要校验的表单项,不管input还是checkbox,都必须放在`<uni-forms-item>`组件中,且一个`<uni-forms-item>`组件只能放置一个表单项。
17
+
18
+`<uni-forms-item>`组件内部预留了显示error message的区域,默认是在表单项的底部。
19
+
20
+另外,`<uni-forms>`组件下面的各个表单项,可以通过`<uni-group>`包裹为不同的分组。同一`<uni-group>`下的不同表单项目将聚拢在一起,同其他group保持垂直间距。`<uni-group>`仅影响视觉效果。
21
+
22
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-forms)
23
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 

+ 92 - 0
uni_modules/uni-popup/changelog.md

@@ -0,0 +1,92 @@
1
+## 1.9.5(2024-10-15)
2
+- 修复 微信小程序中的getSystemInfo警告
3
+## 1.9.4(2024-10-12)
4
+- 修复 微信小程序中的getSystemInfo警告
5
+## 1.9.3(2024-10-12)
6
+- 修复 微信小程序中的getSystemInfo警告
7
+## 1.9.2(2024-09-21)
8
+- 修复 uni-popup在android上的重复点击弹出位置不正确的bug
9
+## 1.9.1(2024-04-02)
10
+- 修复 uni-popup-dialog vue3下使用value无法进行绑定的bug(双向绑定兼容旧写法)
11
+## 1.9.0(2024-03-28)
12
+- 修复 uni-popup-dialog 双向绑定时初始化逻辑修正
13
+## 1.8.9(2024-03-20)
14
+- 修复 uni-popup-dialog 数据输入时修正为双向绑定
15
+## 1.8.8(2024-02-20)
16
+- 修复 uni-popup 在微信小程序下出现文字向上闪动的bug
17
+## 1.8.7(2024-02-02)
18
+- 新增 uni-popup-dialog 新增属性focus:input模式下,是否自动自动聚焦
19
+## 1.8.6(2024-01-30)
20
+- 新增 uni-popup-dialog 新增属性maxLength:限制输入框字数
21
+## 1.8.5(2024-01-26)
22
+- 新增 uni-popup-dialog 新增属性showClose:控制关闭按钮的显示
23
+## 1.8.4(2023-11-15)
24
+- 新增 uni-popup 支持uni-app-x 注意暂时仅支持 `maskClick` `@open` `@close`
25
+## 1.8.3(2023-04-17)
26
+- 修复 uni-popup 重复打开时的 bug
27
+## 1.8.2(2023-02-02)
28
+- uni-popup-dialog 组件新增 inputType 属性
29
+## 1.8.1(2022-12-01)
30
+- 修复 nvue 下 v-show 报错
31
+## 1.8.0(2022-11-29)
32
+- 优化 主题样式
33
+## 1.7.9(2022-04-02)
34
+- 修复 弹出层内部无法滚动的bug
35
+## 1.7.8(2022-03-28)
36
+- 修复 小程序中高度错误的bug
37
+## 1.7.7(2022-03-17)
38
+- 修复 快速调用open出现问题的Bug
39
+## 1.7.6(2022-02-14)
40
+- 修复 safeArea 属性不能设置为false的bug
41
+## 1.7.5(2022-01-19)
42
+- 修复 isMaskClick 失效的bug
43
+## 1.7.4(2022-01-19)
44
+- 新增 cancelText \ confirmText 属性 ,可自定义文本
45
+- 新增 maskBackgroundColor 属性 ,可以修改蒙版颜色
46
+- 优化 maskClick属性 更新为 isMaskClick ,解决微信小程序警告的问题
47
+## 1.7.3(2022-01-13)
48
+- 修复 设置 safeArea 属性不生效的bug
49
+## 1.7.2(2021-11-26)
50
+- 优化 组件示例
51
+## 1.7.1(2021-11-26)
52
+- 修复 vuedoc 文字错误
53
+## 1.7.0(2021-11-19)
54
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
55
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-popup](https://uniapp.dcloud.io/component/uniui/uni-popup)
56
+## 1.6.2(2021-08-24)
57
+- 新增 支持国际化
58
+## 1.6.1(2021-07-30)
59
+- 优化 vue3下事件警告的问题
60
+## 1.6.0(2021-07-13)
61
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
62
+## 1.5.0(2021-06-23)
63
+- 新增 mask-click 遮罩层点击事件
64
+## 1.4.5(2021-06-22)
65
+- 修复 nvue 平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug
66
+## 1.4.4(2021-06-18)
67
+- 修复 H5平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug
68
+## 1.4.3(2021-06-08)
69
+- 修复 错误的 watch 字段
70
+- 修复 safeArea 属性不生效的问题
71
+- 修复 点击内容,再点击遮罩无法关闭的Bug
72
+## 1.4.2(2021-05-12)
73
+- 新增 组件示例地址
74
+## 1.4.1(2021-04-29)
75
+- 修复 组件内放置 input 、textarea 组件,无法聚焦的问题
76
+## 1.4.0 (2021-04-29)
77
+- 新增 type 属性的 left\right 值,支持左右弹出
78
+- 新增 open(String:type) 方法参数 ,可以省略 type 属性 ,直接传入类型打开指定弹窗
79
+- 新增 backgroundColor 属性,可定义主窗口背景色,默认不显示背景色
80
+- 新增 safeArea 属性,是否适配底部安全区
81
+- 修复 App\h5\微信小程序底部安全区占位不对的Bug
82
+- 修复 App 端弹出等待的Bug
83
+- 优化 提升低配设备性能,优化动画卡顿问题
84
+- 优化 更简单的组件自定义方式
85
+## 1.2.9(2021-02-05)
86
+- 优化 组件引用关系,通过uni_modules引用组件
87
+## 1.2.8(2021-02-05)
88
+- 调整为uni_modules目录规范
89
+## 1.2.7(2021-02-05)
90
+- 调整为uni_modules目录规范
91
+- 新增 支持 PC 端
92
+- 新增 uni-popup-message 、uni-popup-dialog扩展组件支持 PC 端

+ 45 - 0
uni_modules/uni-popup/components/uni-popup-dialog/keypress.js

@@ -0,0 +1,45 @@
1
+// #ifdef H5
2
+export default {
3
+  name: 'Keypress',
4
+  props: {
5
+    disable: {
6
+      type: Boolean,
7
+      default: false
8
+    }
9
+  },
10
+  mounted () {
11
+    const keyNames = {
12
+      esc: ['Esc', 'Escape'],
13
+      tab: 'Tab',
14
+      enter: 'Enter',
15
+      space: [' ', 'Spacebar'],
16
+      up: ['Up', 'ArrowUp'],
17
+      left: ['Left', 'ArrowLeft'],
18
+      right: ['Right', 'ArrowRight'],
19
+      down: ['Down', 'ArrowDown'],
20
+      delete: ['Backspace', 'Delete', 'Del']
21
+    }
22
+    const listener = ($event) => {
23
+      if (this.disable) {
24
+        return
25
+      }
26
+      const keyName = Object.keys(keyNames).find(key => {
27
+        const keyName = $event.key
28
+        const value = keyNames[key]
29
+        return value === keyName || (Array.isArray(value) && value.includes(keyName))
30
+      })
31
+      if (keyName) {
32
+        // 避免和其他按键事件冲突
33
+        setTimeout(() => {
34
+          this.$emit(keyName, {})
35
+        }, 0)
36
+      }
37
+    }
38
+    document.addEventListener('keyup', listener)
39
+    this.$once('hook:beforeDestroy', () => {
40
+      document.removeEventListener('keyup', listener)
41
+    })
42
+  },
43
+	render: () => {}
44
+}
45
+// #endif

+ 316 - 0
uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue

@@ -0,0 +1,316 @@
1
+<template>
2
+	<view class="uni-popup-dialog">
3
+		<view class="uni-dialog-title">
4
+			<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{titleText}}</text>
5
+		</view>
6
+		<view v-if="mode === 'base'" class="uni-dialog-content">
7
+			<slot>
8
+				<text class="uni-dialog-content-text">{{content}}</text>
9
+			</slot>
10
+		</view>
11
+		<view v-else class="uni-dialog-content">
12
+			<slot>
13
+				<input class="uni-dialog-input" :maxlength="maxlength" v-model="val" :type="inputType"
14
+					:placeholder="placeholderText" :focus="focus">
15
+			</slot>
16
+		</view>
17
+		<view class="uni-dialog-button-group">
18
+			<view class="uni-dialog-button" v-if="showClose" @click="closeDialog">
19
+				<text class="uni-dialog-button-text">{{closeText}}</text>
20
+			</view>
21
+			<view class="uni-dialog-button" :class="showClose?'uni-border-left':''" @click="onOk">
22
+				<text class="uni-dialog-button-text uni-button-color">{{okText}}</text>
23
+			</view>
24
+		</view>
25
+
26
+	</view>
27
+</template>
28
+
29
+<script>
30
+	import popup from '../uni-popup/popup.js'
31
+	import {
32
+		initVueI18n
33
+	} from '@dcloudio/uni-i18n'
34
+	import messages from '../uni-popup/i18n/index.js'
35
+	const {
36
+		t
37
+	} = initVueI18n(messages)
38
+	/**
39
+	 * PopUp 弹出层-对话框样式
40
+	 * @description 弹出层-对话框样式
41
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
42
+	 * @property {String} value input 模式下的默认值
43
+	 * @property {String} placeholder input 模式下输入提示
44
+	 * @property {Boolean} focus input模式下是否自动聚焦,默认为true
45
+	 * @property {String} type = [success|warning|info|error] 主题样式
46
+	 *  @value success 成功
47
+	 * 	@value warning 提示
48
+	 * 	@value info 消息
49
+	 * 	@value error 错误
50
+	 * @property {String} mode = [base|input] 模式、
51
+	 * 	@value base 基础对话框
52
+	 * 	@value input 可输入对话框
53
+	 * @showClose {Boolean} 是否显示关闭按钮
54
+	 * @property {String} content 对话框内容
55
+	 * @property {Boolean} beforeClose 是否拦截取消事件
56
+	 * @property {Number} maxlength 输入
57
+	 * @event {Function} confirm 点击确认按钮触发
58
+	 * @event {Function} close 点击取消按钮触发
59
+	 */
60
+
61
+	export default {
62
+		name: "uniPopupDialog",
63
+		mixins: [popup],
64
+		emits: ['confirm', 'close', 'update:modelValue', 'input'],
65
+		props: {
66
+			inputType: {
67
+				type: String,
68
+				default: 'text'
69
+			},
70
+			showClose: {
71
+				type: Boolean,
72
+				default: true
73
+			},
74
+			// #ifdef VUE2
75
+			value: {
76
+				type: [String, Number],
77
+				default: ''
78
+			},
79
+			// #endif
80
+			// #ifdef VUE3
81
+			modelValue: {
82
+				type: [Number, String],
83
+				default: ''
84
+			},
85
+			// #endif
86
+
87
+
88
+			placeholder: {
89
+				type: [String, Number],
90
+				default: ''
91
+			},
92
+			type: {
93
+				type: String,
94
+				default: 'error'
95
+			},
96
+			mode: {
97
+				type: String,
98
+				default: 'base'
99
+			},
100
+			title: {
101
+				type: String,
102
+				default: ''
103
+			},
104
+			content: {
105
+				type: String,
106
+				default: ''
107
+			},
108
+			beforeClose: {
109
+				type: Boolean,
110
+				default: false
111
+			},
112
+			cancelText: {
113
+				type: String,
114
+				default: ''
115
+			},
116
+			confirmText: {
117
+				type: String,
118
+				default: ''
119
+			},
120
+			maxlength: {
121
+				type: Number,
122
+				default: -1,
123
+			},
124
+			focus: {
125
+				type: Boolean,
126
+				default: true,
127
+			}
128
+		},
129
+		data() {
130
+			return {
131
+				dialogType: 'error',
132
+				val: ""
133
+			}
134
+		},
135
+		computed: {
136
+			okText() {
137
+				return this.confirmText || t("uni-popup.ok")
138
+			},
139
+			closeText() {
140
+				return this.cancelText || t("uni-popup.cancel")
141
+			},
142
+			placeholderText() {
143
+				return this.placeholder || t("uni-popup.placeholder")
144
+			},
145
+			titleText() {
146
+				return this.title || t("uni-popup.title")
147
+			}
148
+		},
149
+		watch: {
150
+			type(val) {
151
+				this.dialogType = val
152
+			},
153
+			mode(val) {
154
+				if (val === 'input') {
155
+					this.dialogType = 'info'
156
+				}
157
+			},
158
+			value(val) {
159
+				if (this.maxlength != -1 && this.mode === 'input') {
160
+					this.val = val.slice(0, this.maxlength);
161
+				} else {
162
+					this.val = val
163
+				}
164
+			},
165
+			val(val) {
166
+				// #ifdef VUE2
167
+				// TODO 兼容 vue2
168
+				this.$emit('input', val);
169
+				// #endif
170
+				// #ifdef VUE3
171
+				// TODO 兼容 vue3
172
+				this.$emit('update:modelValue', val);
173
+				// #endif
174
+			}
175
+		},
176
+		created() {
177
+			// 对话框遮罩不可点击
178
+			this.popup.disableMask()
179
+			// this.popup.closeMask()
180
+			if (this.mode === 'input') {
181
+				this.dialogType = 'info'
182
+				this.val = this.value;
183
+				// #ifdef VUE3
184
+				this.val = this.modelValue;
185
+				// #endif
186
+			} else {
187
+				this.dialogType = this.type
188
+			}
189
+		},
190
+		methods: {
191
+			/**
192
+			 * 点击确认按钮
193
+			 */
194
+			onOk() {
195
+				if (this.mode === 'input') {
196
+					this.$emit('confirm', this.val)
197
+				} else {
198
+					this.$emit('confirm')
199
+				}
200
+				if (this.beforeClose) return
201
+				this.popup.close()
202
+			},
203
+			/**
204
+			 * 点击取消按钮
205
+			 */
206
+			closeDialog() {
207
+				this.$emit('close')
208
+				if (this.beforeClose) return
209
+				this.popup.close()
210
+			},
211
+			close() {
212
+				this.popup.close()
213
+			}
214
+		}
215
+	}
216
+</script>
217
+
218
+<style lang="scss">
219
+	.uni-popup-dialog {
220
+		width: 300px;
221
+		border-radius: 11px;
222
+		background-color: #fff;
223
+	}
224
+
225
+	.uni-dialog-title {
226
+		/* #ifndef APP-NVUE */
227
+		display: flex;
228
+		/* #endif */
229
+		flex-direction: row;
230
+		justify-content: center;
231
+		padding-top: 25px;
232
+	}
233
+
234
+	.uni-dialog-title-text {
235
+		font-size: 16px;
236
+		font-weight: 500;
237
+	}
238
+
239
+	.uni-dialog-content {
240
+		/* #ifndef APP-NVUE */
241
+		display: flex;
242
+		/* #endif */
243
+		flex-direction: row;
244
+		justify-content: center;
245
+		align-items: center;
246
+		padding: 20px;
247
+	}
248
+
249
+	.uni-dialog-content-text {
250
+		font-size: 14px;
251
+		color: #6C6C6C;
252
+	}
253
+
254
+	.uni-dialog-button-group {
255
+		/* #ifndef APP-NVUE */
256
+		display: flex;
257
+		/* #endif */
258
+		flex-direction: row;
259
+		border-top-color: #f5f5f5;
260
+		border-top-style: solid;
261
+		border-top-width: 1px;
262
+	}
263
+
264
+	.uni-dialog-button {
265
+		/* #ifndef APP-NVUE */
266
+		display: flex;
267
+		/* #endif */
268
+
269
+		flex: 1;
270
+		flex-direction: row;
271
+		justify-content: center;
272
+		align-items: center;
273
+		height: 45px;
274
+	}
275
+
276
+	.uni-border-left {
277
+		border-left-color: #f0f0f0;
278
+		border-left-style: solid;
279
+		border-left-width: 1px;
280
+	}
281
+
282
+	.uni-dialog-button-text {
283
+		font-size: 16px;
284
+		color: #333;
285
+	}
286
+
287
+	.uni-button-color {
288
+		color: #007aff;
289
+	}
290
+
291
+	.uni-dialog-input {
292
+		flex: 1;
293
+		font-size: 14px;
294
+		border: 1px #eee solid;
295
+		height: 40px;
296
+		padding: 0 10px;
297
+		border-radius: 5px;
298
+		color: #555;
299
+	}
300
+
301
+	.uni-popup__success {
302
+		color: #4cd964;
303
+	}
304
+
305
+	.uni-popup__warn {
306
+		color: #f0ad4e;
307
+	}
308
+
309
+	.uni-popup__error {
310
+		color: #dd524d;
311
+	}
312
+
313
+	.uni-popup__info {
314
+		color: #909399;
315
+	}
316
+</style>

+ 143 - 0
uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue

@@ -0,0 +1,143 @@
1
+<template>
2
+	<view class="uni-popup-message">
3
+		<view class="uni-popup-message__box fixforpc-width" :class="'uni-popup__'+type">
4
+			<slot>
5
+				<text class="uni-popup-message-text" :class="'uni-popup__'+type+'-text'">{{message}}</text>
6
+			</slot>
7
+		</view>
8
+	</view>
9
+</template>
10
+
11
+<script>
12
+	import popup from '../uni-popup/popup.js'
13
+	/**
14
+	 * PopUp 弹出层-消息提示
15
+	 * @description 弹出层-消息提示
16
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
17
+	 * @property {String} type = [success|warning|info|error] 主题样式
18
+	 *  @value success 成功
19
+	 * 	@value warning 提示
20
+	 * 	@value info 消息
21
+	 * 	@value error 错误
22
+	 * @property {String} message 消息提示文字
23
+	 * @property {String} duration 显示时间,设置为 0 则不会自动关闭
24
+	 */
25
+
26
+	export default {
27
+		name: 'uniPopupMessage',
28
+		mixins:[popup],
29
+		props: {
30
+			/**
31
+			 * 主题 success/warning/info/error	  默认 success
32
+			 */
33
+			type: {
34
+				type: String,
35
+				default: 'success'
36
+			},
37
+			/**
38
+			 * 消息文字
39
+			 */
40
+			message: {
41
+				type: String,
42
+				default: ''
43
+			},
44
+			/**
45
+			 * 显示时间,设置为 0 则不会自动关闭
46
+			 */
47
+			duration: {
48
+				type: Number,
49
+				default: 3000
50
+			},
51
+			maskShow:{
52
+				type:Boolean,
53
+				default:false
54
+			}
55
+		},
56
+		data() {
57
+			return {}
58
+		},
59
+		created() {
60
+			this.popup.maskShow = this.maskShow
61
+			this.popup.messageChild = this
62
+		},
63
+		methods: {
64
+			timerClose(){
65
+				if(this.duration === 0) return
66
+				clearTimeout(this.timer) 
67
+				this.timer = setTimeout(()=>{
68
+					this.popup.close()
69
+				},this.duration)
70
+			}
71
+		}
72
+	}
73
+</script>
74
+<style lang="scss" >
75
+	.uni-popup-message {
76
+		/* #ifndef APP-NVUE */
77
+		display: flex;
78
+		/* #endif */
79
+		flex-direction: row;
80
+		justify-content: center;
81
+	}
82
+
83
+	.uni-popup-message__box {
84
+		background-color: #e1f3d8;
85
+		padding: 10px 15px;
86
+		border-color: #eee;
87
+		border-style: solid;
88
+		border-width: 1px;
89
+		flex: 1;
90
+	}
91
+
92
+	@media screen and (min-width: 500px) {
93
+		.fixforpc-width {
94
+			margin-top: 20px;
95
+			border-radius: 4px;
96
+			flex: none;
97
+			min-width: 380px;
98
+			/* #ifndef APP-NVUE */
99
+			max-width: 50%;
100
+			/* #endif */
101
+			/* #ifdef APP-NVUE */
102
+			max-width: 500px;
103
+			/* #endif */
104
+		}
105
+	}
106
+
107
+	.uni-popup-message-text {
108
+		font-size: 14px;
109
+		padding: 0;
110
+	}
111
+
112
+	.uni-popup__success {
113
+		background-color: #e1f3d8;
114
+	}
115
+
116
+	.uni-popup__success-text {
117
+		color: #67C23A;
118
+	}
119
+
120
+	.uni-popup__warn {
121
+		background-color: #faecd8;
122
+	}
123
+
124
+	.uni-popup__warn-text {
125
+		color: #E6A23C;
126
+	}
127
+
128
+	.uni-popup__error {
129
+		background-color: #fde2e2;
130
+	}
131
+
132
+	.uni-popup__error-text {
133
+		color: #F56C6C;
134
+	}
135
+
136
+	.uni-popup__info {
137
+		background-color: #F2F6FC;
138
+	}
139
+
140
+	.uni-popup__info-text {
141
+		color: #909399;
142
+	}
143
+</style>

+ 187 - 0
uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue

@@ -0,0 +1,187 @@
1
+<template>
2
+	<view class="uni-popup-share">
3
+		<view class="uni-share-title"><text class="uni-share-title-text">{{shareTitleText}}</text></view>
4
+		<view class="uni-share-content">
5
+			<view class="uni-share-content-box">
6
+				<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
7
+					<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
8
+					<text class="uni-share-text">{{item.text}}</text>
9
+				</view>
10
+
11
+			</view>
12
+		</view>
13
+		<view class="uni-share-button-box">
14
+			<button class="uni-share-button" @click="close">{{cancelText}}</button>
15
+		</view>
16
+	</view>
17
+</template>
18
+
19
+<script>
20
+	import popup from '../uni-popup/popup.js'
21
+	import {
22
+	initVueI18n
23
+	} from '@dcloudio/uni-i18n'
24
+	import messages from '../uni-popup/i18n/index.js'
25
+	const {	t	} = initVueI18n(messages)
26
+	export default {
27
+		name: 'UniPopupShare',
28
+		mixins:[popup],
29
+		emits:['select'],
30
+		props: {
31
+			title: {
32
+				type: String,
33
+				default: ''
34
+			},
35
+			beforeClose: {
36
+				type: Boolean,
37
+				default: false
38
+			}
39
+		},
40
+		data() {
41
+			return {
42
+				bottomData: [{
43
+						text: '微信',
44
+						icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c2b17470-50be-11eb-b680-7980c8a877b8.png',
45
+						name: 'wx'
46
+					},
47
+					{
48
+						text: '支付宝',
49
+						icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/d684ae40-50be-11eb-8ff1-d5dcf8779628.png',
50
+						name: 'ali'
51
+					},
52
+					{
53
+						text: 'QQ',
54
+						icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png',
55
+						name: 'qq'
56
+					},
57
+					{
58
+						text: '新浪',
59
+						icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png',
60
+						name: 'sina'
61
+					},
62
+					// {
63
+					// 	text: '百度',
64
+					// 	icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/1ec6e920-50bf-11eb-8a36-ebb87efcf8c0.png',
65
+					// 	name: 'copy'
66
+					// },
67
+					// {
68
+					// 	text: '其他',
69
+					// 	icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/2e0fdfe0-50bf-11eb-b997-9918a5dda011.png',
70
+					// 	name: 'more'
71
+					// }
72
+				]
73
+			}
74
+		},
75
+		created() {},
76
+		computed: {
77
+			cancelText() {
78
+				return t("uni-popup.cancel")
79
+			},
80
+		shareTitleText() {
81
+				return this.title || t("uni-popup.shareTitle")
82
+			}
83
+		},
84
+		methods: {
85
+			/**
86
+			 * 选择内容
87
+			 */
88
+			select(item, index) {
89
+				this.$emit('select', {
90
+					item,
91
+					index
92
+				})
93
+				this.close()
94
+
95
+			},
96
+			/**
97
+			 * 关闭窗口
98
+			 */
99
+			close() {
100
+				if(this.beforeClose) return
101
+				this.popup.close()
102
+			}
103
+		}
104
+	}
105
+</script>
106
+<style lang="scss" >
107
+	.uni-popup-share {
108
+		background-color: #fff;
109
+		border-top-left-radius: 11px;
110
+		border-top-right-radius: 11px;
111
+	}
112
+	.uni-share-title {
113
+		/* #ifndef APP-NVUE */
114
+		display: flex;
115
+		/* #endif */
116
+		flex-direction: row;
117
+		align-items: center;
118
+		justify-content: center;
119
+		height: 40px;
120
+	}
121
+	.uni-share-title-text {
122
+		font-size: 14px;
123
+		color: #666;
124
+	}
125
+	.uni-share-content {
126
+		/* #ifndef APP-NVUE */
127
+		display: flex;
128
+		/* #endif */
129
+		flex-direction: row;
130
+		justify-content: center;
131
+		padding-top: 10px;
132
+	}
133
+
134
+	.uni-share-content-box {
135
+		/* #ifndef APP-NVUE */
136
+		display: flex;
137
+		/* #endif */
138
+		flex-direction: row;
139
+		flex-wrap: wrap;
140
+		width: 360px;
141
+	}
142
+
143
+	.uni-share-content-item {
144
+		width: 90px;
145
+		/* #ifndef APP-NVUE */
146
+		display: flex;
147
+		/* #endif */
148
+		flex-direction: column;
149
+		justify-content: center;
150
+		padding: 10px 0;
151
+		align-items: center;
152
+	}
153
+
154
+	.uni-share-content-item:active {
155
+		background-color: #f5f5f5;
156
+	}
157
+
158
+	.uni-share-image {
159
+		width: 30px;
160
+		height: 30px;
161
+	}
162
+
163
+	.uni-share-text {
164
+		margin-top: 10px;
165
+		font-size: 14px;
166
+		color: #3B4144;
167
+	}
168
+
169
+	.uni-share-button-box {
170
+		/* #ifndef APP-NVUE */
171
+		display: flex;
172
+		/* #endif */
173
+		flex-direction: row;
174
+		padding: 10px 15px;
175
+	}
176
+
177
+	.uni-share-button {
178
+		flex: 1;
179
+		border-radius: 50px;
180
+		color: #666;
181
+		font-size: 16px;
182
+	}
183
+
184
+	.uni-share-button::after {
185
+		border-radius: 50px;
186
+	}
187
+</style>

+ 7 - 0
uni_modules/uni-popup/components/uni-popup/i18n/en.json

@@ -0,0 +1,7 @@
1
+{
2
+	"uni-popup.cancel": "cancel",
3
+	"uni-popup.ok": "ok",
4
+	"uni-popup.placeholder": "pleace enter",
5
+	"uni-popup.title": "Hint",
6
+	"uni-popup.shareTitle": "Share to"
7
+}

+ 8 - 0
uni_modules/uni-popup/components/uni-popup/i18n/index.js

@@ -0,0 +1,8 @@
1
+import en from './en.json'
2
+import zhHans from './zh-Hans.json'
3
+import zhHant from './zh-Hant.json'
4
+export default {
5
+	en,
6
+	'zh-Hans': zhHans,
7
+	'zh-Hant': zhHant
8
+}

+ 7 - 0
uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json

@@ -0,0 +1,7 @@
1
+{
2
+	"uni-popup.cancel": "取消",
3
+	"uni-popup.ok": "确定",
4
+	"uni-popup.placeholder": "请输入",
5
+		"uni-popup.title": "提示",
6
+		"uni-popup.shareTitle": "分享到"
7
+}

+ 7 - 0
uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json

@@ -0,0 +1,7 @@
1
+{
2
+	"uni-popup.cancel": "取消",
3
+	"uni-popup.ok": "確定",
4
+	"uni-popup.placeholder": "請輸入",
5
+	"uni-popup.title": "提示",
6
+	"uni-popup.shareTitle": "分享到"
7
+}

+ 45 - 0
uni_modules/uni-popup/components/uni-popup/keypress.js

@@ -0,0 +1,45 @@
1
+// #ifdef H5
2
+export default {
3
+  name: 'Keypress',
4
+  props: {
5
+    disable: {
6
+      type: Boolean,
7
+      default: false
8
+    }
9
+  },
10
+  mounted () {
11
+    const keyNames = {
12
+      esc: ['Esc', 'Escape'],
13
+      tab: 'Tab',
14
+      enter: 'Enter',
15
+      space: [' ', 'Spacebar'],
16
+      up: ['Up', 'ArrowUp'],
17
+      left: ['Left', 'ArrowLeft'],
18
+      right: ['Right', 'ArrowRight'],
19
+      down: ['Down', 'ArrowDown'],
20
+      delete: ['Backspace', 'Delete', 'Del']
21
+    }
22
+    const listener = ($event) => {
23
+      if (this.disable) {
24
+        return
25
+      }
26
+      const keyName = Object.keys(keyNames).find(key => {
27
+        const keyName = $event.key
28
+        const value = keyNames[key]
29
+        return value === keyName || (Array.isArray(value) && value.includes(keyName))
30
+      })
31
+      if (keyName) {
32
+        // 避免和其他按键事件冲突
33
+        setTimeout(() => {
34
+          this.$emit(keyName, {})
35
+        }, 0)
36
+      }
37
+    }
38
+    document.addEventListener('keyup', listener)
39
+    // this.$once('hook:beforeDestroy', () => {
40
+    //   document.removeEventListener('keyup', listener)
41
+    // })
42
+  },
43
+	render: () => {}
44
+}
45
+// #endif

+ 26 - 0
uni_modules/uni-popup/components/uni-popup/popup.js

@@ -0,0 +1,26 @@
1
+
2
+export default {
3
+	data() {
4
+		return {
5
+			
6
+		}
7
+	},
8
+	created(){
9
+		this.popup = this.getParent()
10
+	},
11
+	methods:{
12
+		/**
13
+		 * 获取父元素实例
14
+		 */
15
+		getParent(name = 'uniPopup') {
16
+			let parent = this.$parent;
17
+			let parentName = parent.$options.name;
18
+			while (parentName !== name) {
19
+				parent = parent.$parent;
20
+				if (!parent) return false
21
+				parentName = parent.$options.name;
22
+			}
23
+			return parent;
24
+		},
25
+	}
26
+}

+ 90 - 0
uni_modules/uni-popup/components/uni-popup/uni-popup.uvue

@@ -0,0 +1,90 @@
1
+<template>
2
+  <view class="popup-root" v-if="isOpen" v-show="isShow" @click="clickMask">
3
+    <view @click.stop>
4
+      <slot></slot>
5
+    </view>
6
+  </view>
7
+</template>
8
+
9
+<script>
10
+  type CloseCallBack = ()=> void;
11
+  let closeCallBack:CloseCallBack = () :void => {};
12
+  export default {
13
+    emits:["close","clickMask"],
14
+    data() {
15
+      return {
16
+        isShow:false,
17
+        isOpen:false
18
+      }
19
+    },
20
+    props: {
21
+      maskClick: {
22
+        type: Boolean,
23
+        default: true
24
+      },
25
+    },
26
+    watch: {
27
+      // 设置show = true 时,如果没有 open 需要设置为 open
28
+      isShow:{
29
+        handler(isShow) {
30
+          // console.log("isShow",isShow)
31
+          if(isShow && this.isOpen == false){
32
+            this.isOpen = true
33
+          }
34
+        },
35
+        immediate:true
36
+      },
37
+      // 设置isOpen = true 时,如果没有 isShow 需要设置为 isShow
38
+      isOpen:{
39
+        handler(isOpen) {
40
+          // console.log("isOpen",isOpen)
41
+          if(isOpen && this.isShow == false){
42
+            this.isShow = true
43
+          }
44
+        },
45
+        immediate:true
46
+      }
47
+    },
48
+    methods:{
49
+      open(){
50
+        // ...funs : CloseCallBack[]
51
+        // if(funs.length > 0){
52
+        //   closeCallBack = funs[0]
53
+        // }
54
+        this.isOpen = true;
55
+      },
56
+      clickMask(){
57
+        if(this.maskClick == true){
58
+          this.$emit('clickMask')
59
+          this.close()
60
+        }
61
+      },
62
+      close(): void{
63
+        this.isOpen = false;
64
+        this.$emit('close')
65
+        closeCallBack()
66
+      },
67
+      hiden(){
68
+        this.isShow = false
69
+      },
70
+      show(){
71
+        this.isShow = true
72
+      }
73
+    }
74
+  }
75
+</script>
76
+
77
+<style>
78
+.popup-root {
79
+  position: fixed;
80
+  top: 0;
81
+  left: 0;
82
+  width: 750rpx;
83
+  height: 100%;
84
+  flex: 1;
85
+  background-color: rgba(0, 0, 0, 0.3);
86
+  justify-content: center;
87
+  align-items: center;
88
+  z-index: 99;
89
+}
90
+</style>

+ 518 - 0
uni_modules/uni-popup/components/uni-popup/uni-popup.vue

@@ -0,0 +1,518 @@
1
+<template>
2
+	<view v-if="showPopup" class="uni-popup" :class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']">
3
+		<view @touchstart="touchstart">
4
+			<uni-transition key="1" v-if="maskShow" name="mask" mode-class="fade" :styles="maskClass"
5
+				:duration="duration" :show="showTrans" @click="onTap" />
6
+			<uni-transition key="2" :mode-class="ani" name="content" :styles="transClass" :duration="duration"
7
+				:show="showTrans" @click="onTap">
8
+				<view class="uni-popup__wrapper" :style="getStyles" :class="[popupstyle]" @click="clear">
9
+					<slot />
10
+				</view>
11
+			</uni-transition>
12
+		</view>
13
+		<!-- #ifdef H5 -->
14
+		<keypress v-if="maskShow" @esc="onTap" />
15
+		<!-- #endif -->
16
+	</view>
17
+</template>
18
+
19
+<script>
20
+	// #ifdef H5
21
+	import keypress from './keypress.js'
22
+	// #endif
23
+
24
+	/**
25
+	 * PopUp 弹出层
26
+	 * @description 弹出层组件,为了解决遮罩弹层的问题
27
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
28
+	 * @property {String} type = [top|center|bottom|left|right|message|dialog|share] 弹出方式
29
+	 * 	@value top 顶部弹出
30
+	 * 	@value center 中间弹出
31
+	 * 	@value bottom 底部弹出
32
+	 * 	@value left		左侧弹出
33
+	 * 	@value right  右侧弹出
34
+	 * 	@value message 消息提示
35
+	 * 	@value dialog 对话框
36
+	 * 	@value share 底部分享示例
37
+	 * @property {Boolean} animation = [true|false] 是否开启动画
38
+	 * @property {Boolean} maskClick = [true|false] 蒙版点击是否关闭弹窗(废弃)
39
+	 * @property {Boolean} isMaskClick = [true|false] 蒙版点击是否关闭弹窗
40
+	 * @property {String}  backgroundColor 主窗口背景色
41
+	 * @property {String}  maskBackgroundColor 蒙版颜色
42
+	 * @property {String}  borderRadius 设置圆角(左上、右上、右下和左下) 示例:"10px 10px 10px 10px"
43
+	 * @property {Boolean} safeArea		   是否适配底部安全区
44
+	 * @event {Function} change 打开关闭弹窗触发,e={show: false}
45
+	 * @event {Function} maskClick 点击遮罩触发
46
+	 */
47
+
48
+	export default {
49
+		name: 'uniPopup',
50
+		components: {
51
+			// #ifdef H5
52
+			keypress
53
+			// #endif
54
+		},
55
+		emits: ['change', 'maskClick'],
56
+		props: {
57
+			// 开启动画
58
+			animation: {
59
+				type: Boolean,
60
+				default: true
61
+			},
62
+			// 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
63
+			// message: 消息提示 ; dialog : 对话框
64
+			type: {
65
+				type: String,
66
+				default: 'center'
67
+			},
68
+			// maskClick
69
+			isMaskClick: {
70
+				type: Boolean,
71
+				default: null
72
+			},
73
+			// TODO 2 个版本后废弃属性 ,使用 isMaskClick
74
+			maskClick: {
75
+				type: Boolean,
76
+				default: null
77
+			},
78
+			backgroundColor: {
79
+				type: String,
80
+				default: 'none'
81
+			},
82
+			safeArea: {
83
+				type: Boolean,
84
+				default: true
85
+			},
86
+			maskBackgroundColor: {
87
+				type: String,
88
+				default: 'rgba(0, 0, 0, 0.4)'
89
+			},
90
+			borderRadius:{
91
+				type: String,
92
+			}
93
+		},
94
+
95
+		watch: {
96
+			/**
97
+			 * 监听type类型
98
+			 */
99
+			type: {
100
+				handler: function(type) {
101
+					if (!this.config[type]) return
102
+					this[this.config[type]](true)
103
+				},
104
+				immediate: true
105
+			},
106
+			isDesktop: {
107
+				handler: function(newVal) {
108
+					if (!this.config[newVal]) return
109
+					this[this.config[this.type]](true)
110
+				},
111
+				immediate: true
112
+			},
113
+			/**
114
+			 * 监听遮罩是否可点击
115
+			 * @param {Object} val
116
+			 */
117
+			maskClick: {
118
+				handler: function(val) {
119
+					this.mkclick = val
120
+				},
121
+				immediate: true
122
+			},
123
+			isMaskClick: {
124
+				handler: function(val) {
125
+					this.mkclick = val
126
+				},
127
+				immediate: true
128
+			},
129
+			// H5 下禁止底部滚动
130
+			showPopup(show) {
131
+				// #ifdef H5
132
+				// fix by mehaotian 处理 h5 滚动穿透的问题
133
+				document.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible'
134
+				// #endif
135
+			}
136
+		},
137
+		data() {
138
+			return {
139
+				duration: 300,
140
+				ani: [],
141
+				showPopup: false,
142
+				showTrans: false,
143
+				popupWidth: 0,
144
+				popupHeight: 0,
145
+				config: {
146
+					top: 'top',
147
+					bottom: 'bottom',
148
+					center: 'center',
149
+					left: 'left',
150
+					right: 'right',
151
+					message: 'top',
152
+					dialog: 'center',
153
+					share: 'bottom'
154
+				},
155
+				maskClass: {
156
+					position: 'fixed',
157
+					bottom: 0,
158
+					top: 0,
159
+					left: 0,
160
+					right: 0,
161
+					backgroundColor: 'rgba(0, 0, 0, 0.4)'
162
+				},
163
+				transClass: {
164
+					backgroundColor: 'transparent',
165
+					borderRadius: this.borderRadius || "0",
166
+					position: 'fixed',
167
+					left: 0,
168
+					right: 0
169
+				},
170
+				maskShow: true,
171
+				mkclick: true,
172
+				popupstyle: 'top'
173
+			}
174
+		},
175
+		computed: {
176
+			getStyles() {
177
+				let res = { backgroundColor: this.bg };
178
+				if (this.borderRadius || "0") {
179
+					res = Object.assign(res, { borderRadius: this.borderRadius })
180
+				}
181
+				return res;
182
+			},
183
+			isDesktop() {
184
+				return this.popupWidth >= 500 && this.popupHeight >= 500
185
+			},
186
+			bg() {
187
+				if (this.backgroundColor === '' || this.backgroundColor === 'none') {
188
+					return 'transparent'
189
+				}
190
+				return this.backgroundColor
191
+			}
192
+		},
193
+		mounted() {
194
+			const fixSize = () => {
195
+				// #ifdef MP-WEIXIN
196
+				const {
197
+					windowWidth,
198
+					windowHeight,
199
+					windowTop,
200
+					safeArea,
201
+					screenHeight,
202
+					safeAreaInsets
203
+				} = uni.getWindowInfo()
204
+				// #endif
205
+				// #ifndef MP-WEIXIN
206
+				const {
207
+					windowWidth,
208
+					windowHeight,
209
+					windowTop,
210
+					safeArea,
211
+					screenHeight,
212
+					safeAreaInsets
213
+				} = uni.getSystemInfoSync()
214
+				// #endif
215
+				this.popupWidth = windowWidth
216
+				this.popupHeight = windowHeight + (windowTop || 0)
217
+				// TODO fix by mehaotian 是否适配底部安全区 ,目前微信ios 、和 app ios 计算有差异,需要框架修复
218
+				if (safeArea && this.safeArea) {
219
+					// #ifdef MP-WEIXIN
220
+					this.safeAreaInsets = screenHeight - safeArea.bottom
221
+					// #endif
222
+					// #ifndef MP-WEIXIN
223
+					this.safeAreaInsets = safeAreaInsets.bottom
224
+					// #endif
225
+				} else {
226
+					this.safeAreaInsets = 0
227
+				}
228
+			}
229
+			fixSize()
230
+			// #ifdef H5
231
+			// window.addEventListener('resize', fixSize)
232
+			// this.$once('hook:beforeDestroy', () => {
233
+			// 	window.removeEventListener('resize', fixSize)
234
+			// })
235
+			// #endif
236
+		},
237
+		// #ifndef VUE3
238
+		// TODO vue2
239
+		destroyed() {
240
+			this.setH5Visible()
241
+		},
242
+		// #endif
243
+		// #ifdef VUE3
244
+		// TODO vue3
245
+		unmounted() {
246
+			this.setH5Visible()
247
+		},
248
+		// #endif
249
+		activated() {
250
+   	  this.setH5Visible(!this.showPopup);
251
+    },
252
+    deactivated() {
253
+      this.setH5Visible(true);
254
+    },
255
+		created() {
256
+			// this.mkclick =  this.isMaskClick || this.maskClick
257
+			if (this.isMaskClick === null && this.maskClick === null) {
258
+				this.mkclick = true
259
+			} else {
260
+				this.mkclick = this.isMaskClick !== null ? this.isMaskClick : this.maskClick
261
+			}
262
+			if (this.animation) {
263
+				this.duration = 300
264
+			} else {
265
+				this.duration = 0
266
+			}
267
+			// TODO 处理 message 组件生命周期异常的问题
268
+			this.messageChild = null
269
+			// TODO 解决头条冒泡的问题
270
+			this.clearPropagation = false
271
+			this.maskClass.backgroundColor = this.maskBackgroundColor
272
+		},
273
+		methods: {
274
+			setH5Visible(visible = true) {
275
+				// #ifdef H5
276
+				// fix by mehaotian 处理 h5 滚动穿透的问题
277
+				document.getElementsByTagName('body')[0].style.overflow =  visible ? "visible" : "hidden";
278
+				// #endif
279
+			},
280
+			/**
281
+			 * 公用方法,不显示遮罩层
282
+			 */
283
+			closeMask() {
284
+				this.maskShow = false
285
+			},
286
+			/**
287
+			 * 公用方法,遮罩层禁止点击
288
+			 */
289
+			disableMask() {
290
+				this.mkclick = false
291
+			},
292
+			// TODO nvue 取消冒泡
293
+			clear(e) {
294
+				// #ifndef APP-NVUE
295
+				e.stopPropagation()
296
+				// #endif
297
+				this.clearPropagation = true
298
+			},
299
+
300
+			open(direction) {
301
+				// fix by mehaotian 处理快速打开关闭的情况
302
+				if (this.showPopup) {
303
+					return
304
+				}
305
+				let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share']
306
+				if (!(direction && innerType.indexOf(direction) !== -1)) {
307
+					direction = this.type
308
+				}
309
+				if (!this.config[direction]) {
310
+					console.error('缺少类型:', direction)
311
+					return
312
+				}
313
+				this[this.config[direction]]()
314
+				this.$emit('change', {
315
+					show: true,
316
+					type: direction
317
+				})
318
+			},
319
+			close(type) {
320
+				this.showTrans = false
321
+				this.$emit('change', {
322
+					show: false,
323
+					type: this.type
324
+				})
325
+				clearTimeout(this.timer)
326
+				// // 自定义关闭事件
327
+				// this.customOpen && this.customClose()
328
+				this.timer = setTimeout(() => {
329
+					this.showPopup = false
330
+				}, 300)
331
+			},
332
+			// TODO 处理冒泡事件,头条的冒泡事件有问题 ,先这样兼容
333
+			touchstart() {
334
+				this.clearPropagation = false
335
+			},
336
+
337
+			onTap() {
338
+				if (this.clearPropagation) {
339
+					// fix by mehaotian 兼容 nvue
340
+					this.clearPropagation = false
341
+					return
342
+				}
343
+				this.$emit('maskClick')
344
+				if (!this.mkclick) return
345
+				this.close()
346
+			},
347
+			/**
348
+			 * 顶部弹出样式处理
349
+			 */
350
+			top(type) {
351
+				this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top'
352
+				this.ani = ['slide-top']
353
+				this.transClass = {
354
+					position: 'fixed',
355
+					left: 0,
356
+					right: 0,
357
+					backgroundColor: this.bg,
358
+					borderRadius:this.borderRadius || "0"
359
+				}
360
+				// TODO 兼容 type 属性 ,后续会废弃
361
+				if (type) return
362
+				this.showPopup = true
363
+				this.showTrans = true
364
+				this.$nextTick(() => {
365
+					this.showPoptrans()
366
+					if (this.messageChild && this.type === 'message') {
367
+						this.messageChild.timerClose()
368
+					}
369
+				})
370
+			},
371
+			/**
372
+			 * 底部弹出样式处理
373
+			 */
374
+			bottom(type) {
375
+				this.popupstyle = 'bottom'
376
+				this.ani = ['slide-bottom']
377
+				this.transClass = {
378
+					position: 'fixed',
379
+					left: 0,
380
+					right: 0,
381
+					bottom: 0,
382
+					paddingBottom: this.safeAreaInsets + 'px',
383
+					backgroundColor: this.bg,
384
+					borderRadius:this.borderRadius || "0",
385
+				}
386
+				// TODO 兼容 type 属性 ,后续会废弃
387
+				if (type) return
388
+				this.showPoptrans()
389
+			},
390
+			/**
391
+			 * 中间弹出样式处理
392
+			 */
393
+			center(type) {
394
+				this.popupstyle = 'center'
395
+				//微信小程序下,组合动画会出现文字向上闪动问题,再此做特殊处理
396
+				// #ifdef MP-WEIXIN
397
+					this.ani = ['fade']
398
+				// #endif
399
+				// #ifndef MP-WEIXIN
400
+					this.ani = ['zoom-out', 'fade']
401
+				// #endif
402
+				this.transClass = {
403
+					position: 'fixed',
404
+					/* #ifndef APP-NVUE */
405
+					display: 'flex',
406
+					flexDirection: 'column',
407
+					/* #endif */
408
+					bottom: 0,
409
+					left: 0,
410
+					right: 0,
411
+					top: 0,
412
+					justifyContent: 'center',
413
+					alignItems: 'center',
414
+					borderRadius:this.borderRadius || "0"
415
+				}
416
+				// TODO 兼容 type 属性 ,后续会废弃
417
+				if (type) return
418
+				this.showPoptrans()
419
+			},
420
+			left(type) {
421
+				this.popupstyle = 'left'
422
+				this.ani = ['slide-left']
423
+				this.transClass = {
424
+					position: 'fixed',
425
+					left: 0,
426
+					bottom: 0,
427
+					top: 0,
428
+					backgroundColor: this.bg,
429
+					borderRadius:this.borderRadius || "0",
430
+					/* #ifndef APP-NVUE */
431
+					display: 'flex',
432
+					flexDirection: 'column'
433
+					/* #endif */
434
+				}
435
+				// TODO 兼容 type 属性 ,后续会废弃
436
+				if (type) return
437
+				this.showPoptrans()
438
+			},
439
+			right(type) {
440
+				this.popupstyle = 'right'
441
+				this.ani = ['slide-right']
442
+				this.transClass = {
443
+					position: 'fixed',
444
+					bottom: 0,
445
+					right: 0,
446
+					top: 0,
447
+					backgroundColor: this.bg,
448
+					borderRadius:this.borderRadius || "0",
449
+					/* #ifndef APP-NVUE */
450
+					display: 'flex',
451
+					flexDirection: 'column'
452
+					/* #endif */
453
+				}
454
+				// TODO 兼容 type 属性 ,后续会废弃
455
+				if (type) return
456
+				this.showPoptrans()
457
+			},
458
+			showPoptrans(){
459
+				this.$nextTick(()=>{
460
+					this.showPopup = true
461
+					this.showTrans = true
462
+				})
463
+			}
464
+		}
465
+	}
466
+</script>
467
+<style lang="scss">
468
+	.uni-popup {
469
+		position: fixed;
470
+		/* #ifndef APP-NVUE */
471
+		z-index: 99;
472
+
473
+		/* #endif */
474
+		&.top,
475
+		&.left,
476
+		&.right {
477
+			/* #ifdef H5 */
478
+			top: var(--window-top);
479
+			/* #endif */
480
+			/* #ifndef H5 */
481
+			top: 0;
482
+			/* #endif */
483
+		}
484
+
485
+		.uni-popup__wrapper {
486
+			/* #ifndef APP-NVUE */
487
+			display: block;
488
+			/* #endif */
489
+			position: relative;
490
+
491
+			/* iphonex 等安全区设置,底部安全区适配 */
492
+			/* #ifndef APP-NVUE */
493
+			// padding-bottom: constant(safe-area-inset-bottom);
494
+			// padding-bottom: env(safe-area-inset-bottom);
495
+			/* #endif */
496
+			&.left,
497
+			&.right {
498
+				/* #ifdef H5 */
499
+				padding-top: var(--window-top);
500
+				/* #endif */
501
+				/* #ifndef H5 */
502
+				padding-top: 0;
503
+				/* #endif */
504
+				flex: 1;
505
+			}
506
+		}
507
+	}
508
+
509
+	.fixforpc-z-index {
510
+		/* #ifndef APP-NVUE */
511
+		z-index: 999;
512
+		/* #endif */
513
+	}
514
+
515
+	.fixforpc-top {
516
+		top: 0;
517
+	}
518
+</style>

+ 88 - 0
uni_modules/uni-popup/package.json

@@ -0,0 +1,88 @@
1
+{
2
+	"id": "uni-popup",
3
+	"displayName": "uni-popup 弹出层",
4
+	"version": "1.9.5",
5
+	"description": " Popup 组件,提供常用的弹层",
6
+	"keywords": [
7
+        "uni-ui",
8
+        "弹出层",
9
+        "弹窗",
10
+        "popup",
11
+        "弹框"
12
+    ],
13
+	"repository": "https://github.com/dcloudio/uni-ui",
14
+	"engines": {
15
+		"HBuilderX": ""
16
+	},
17
+	"directories": {
18
+		"example": "../../temps/example_temps"
19
+	},
20
+    "dcloudext": {
21
+        "sale": {
22
+			"regular": {
23
+				"price": "0.00"
24
+			},
25
+			"sourcecode": {
26
+				"price": "0.00"
27
+			}
28
+		},
29
+		"contact": {
30
+			"qq": ""
31
+		},
32
+		"declaration": {
33
+			"ads": "无",
34
+			"data": "无",
35
+			"permissions": "无"
36
+		},
37
+        "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
38
+        "type": "component-vue"
39
+	},
40
+	"uni_modules": {
41
+		"dependencies": [
42
+			"uni-scss",
43
+			"uni-transition"
44
+		],
45
+		"encrypt": [],
46
+		"platforms": {
47
+			"cloud": {
48
+				"tcb": "y",
49
+                "aliyun": "y",
50
+                "alipay": "n"
51
+			},
52
+			"client": {
53
+				"App": {
54
+					"app-vue": "y",
55
+					"app-nvue": "y"
56
+				},
57
+				"H5-mobile": {
58
+					"Safari": "y",
59
+					"Android Browser": "y",
60
+					"微信浏览器(Android)": "y",
61
+					"QQ浏览器(Android)": "y"
62
+				},
63
+				"H5-pc": {
64
+					"Chrome": "y",
65
+					"IE": "y",
66
+					"Edge": "y",
67
+					"Firefox": "y",
68
+					"Safari": "y"
69
+				},
70
+				"小程序": {
71
+					"微信": "y",
72
+					"阿里": "y",
73
+					"百度": "y",
74
+					"字节跳动": "y",
75
+					"QQ": "y"
76
+				},
77
+				"快应用": {
78
+					"华为": "u",
79
+					"联盟": "u"
80
+                },
81
+                "Vue": {
82
+                    "vue2": "y",
83
+                    "vue3": "y"
84
+                }
85
+			}
86
+		}
87
+	}
88
+}

+ 17 - 0
uni_modules/uni-popup/readme.md

@@ -0,0 +1,17 @@
1
+
2
+
3
+## Popup 弹出层
4
+> **组件名:uni-popup**
5
+> 代码块: `uPopup`
6
+> 关联组件:`uni-transition`
7
+
8
+
9
+弹出层组件,在应用中弹出一个消息提示窗口、提示框等
10
+
11
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-popup)
12
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 
13
+
14
+
15
+
16
+
17
+

+ 18 - 0
uni_modules/uni-steps/changelog.md

@@ -0,0 +1,18 @@
1
+## 1.1.2(2024-03-28)
2
+- 修复 uni-steps为竖排列时,文本长度过长引起点错乱的bug
3
+## 1.1.1(2021-11-22)
4
+- 修复 vue3中某些scss变量无法找到的问题
5
+## 1.1.0(2021-11-19)
6
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
7
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-steps](https://uniapp.dcloud.io/component/uniui/uni-steps)
8
+## 1.0.8(2021-05-12)
9
+- 新增 项目示例地址
10
+## 1.0.7(2021-05-06)
11
+- 修复 uni-steps 横向布局时,多行文字高度不合理的 bug
12
+## 1.0.6(2021-04-21)
13
+- 优化 添加依赖 uni-icons, 导入后自动下载依赖
14
+## 1.0.5(2021-02-05)
15
+- 优化 组件引用关系,通过uni_modules引用组件
16
+
17
+## 1.0.4(2021-02-05)
18
+- 调整为uni_modules目录规范

+ 280 - 0
uni_modules/uni-steps/components/uni-steps/uni-steps.vue

@@ -0,0 +1,280 @@
1
+<template>
2
+	<view class="uni-steps">
3
+		<view :class="[direction==='column'?'uni-steps__column':'uni-steps__row']">
4
+			<view :class="[direction==='column'?'uni-steps__column-text-container':'uni-steps__row-text-container']">
5
+				<view v-for="(item,index) in options" :key="index"
6
+					:class="[direction==='column'?'uni-steps__column-text':'uni-steps__row-text']">
7
+					<text :style="{color:index === active?activeColor:deactiveColor}"
8
+						:class="[direction==='column'?'uni-steps__column-title':'uni-steps__row-title']">{{item.title}}</text>
9
+					<text :style="{color: deactiveColor}"
10
+						:class="[direction==='column'?'uni-steps__column-desc':'uni-steps__row-desc']">{{item.desc}}</text>
11
+				</view>
12
+			</view>
13
+			<view :class="[direction==='column'?'uni-steps__column-container':'uni-steps__row-container']">
14
+				<view :class="[direction==='column'?'uni-steps__column-line-item':'uni-steps__row-line-item']"
15
+					v-for="(item,index) in options" :key="index" :style="{height: direction === 'column'?heightArr[index]+'px':'14px'}">
16
+					<view
17
+						:class="[direction==='column'?'uni-steps__column-line':'uni-steps__row-line',direction==='column'?'uni-steps__column-line--before':'uni-steps__row-line--before']"
18
+						:style="{backgroundColor:index<=active&&index!==0?activeColor:index===0?'transparent':deactiveColor}">
19
+					</view>
20
+					<view :class="[direction==='column'?'uni-steps__column-check':'uni-steps__row-check']"
21
+						v-if="index === active">
22
+						<uni-icons :color="activeColor" :type="activeIcon" size="14" />
23
+					</view>
24
+					<view v-else :class="[direction==='column'?'uni-steps__column-circle':'uni-steps__row-circle']"
25
+						:style="{backgroundColor:index<active?activeColor:deactiveColor}" />
26
+					<view
27
+						:class="[direction==='column'?'uni-steps__column-line':'uni-steps__row-line',direction==='column'?'uni-steps__column-line--after':'uni-steps__row-line--after']"
28
+						:style="{backgroundColor:index<active&&index!==options.length-1?activeColor:index===options.length-1?'transparent':deactiveColor}" />
29
+				</view>
30
+			</view>
31
+		</view>
32
+	</view>
33
+</template>
34
+
35
+<script>
36
+	/**
37
+	 * Steps 步骤条
38
+	 * @description 评分组件
39
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=34
40
+	 * @property {Number} active 当前步骤
41
+	 * @property {String} direction = [row|column] 当前步骤
42
+	 * 	@value row 横向
43
+	 * 	@value column 纵向
44
+	 * @property {String} activeColor 选中状态的颜色
45
+	 * @property {Array} options 数据源,格式为:[{title:'xxx',desc:'xxx'},{title:'xxx',desc:'xxx'}]
46
+	 */
47
+
48
+	export default {
49
+		name: 'UniSteps',
50
+		props: {
51
+			direction: {
52
+				// 排列方向 row column
53
+				type: String,
54
+				default: 'row'
55
+			},
56
+			activeColor: {
57
+				// 激活状态颜色
58
+				type: String,
59
+				default: '#2979FF'
60
+			},
61
+			deactiveColor: {
62
+				// 未激活状态颜色
63
+				type: String,
64
+				default: '#B7BDC6'
65
+			},
66
+			active: {
67
+				// 当前步骤
68
+				type: Number,
69
+				default: 0
70
+			},
71
+			activeIcon: {
72
+				// 当前步骤
73
+				type: String,
74
+				default: 'checkbox-filled'
75
+			},
76
+			options: {
77
+				type: Array,
78
+				default () {
79
+					return []
80
+				}
81
+			} // 数据
82
+		},
83
+		data() {
84
+			return {
85
+				heightArr: [],
86
+			}
87
+		},
88
+		mounted() {
89
+			//根据内容设置步骤条的长度
90
+			if (this.direction === 'column') {
91
+				let that = this;
92
+				//只能用类选择器,用id选择器所获取的元素信息不准确
93
+				uni.createSelectorQuery().in(this).selectAll('.uni-steps__column-text').boundingClientRect(data => {
94
+					that.heightArr = data.map(item => item.height + 1);
95
+				}).exec()
96
+			}
97
+		},
98
+	}
99
+</script>
100
+
101
+<style lang="scss">
102
+	$uni-primary: #2979ff !default;
103
+	$uni-border-color: #EDEDED;
104
+
105
+	.uni-steps {
106
+		/* #ifndef APP-NVUE */
107
+		display: flex;
108
+		width: 100%;
109
+		/* #endif */
110
+		/* #ifdef APP-NVUE */
111
+		flex: 1;
112
+		/* #endif */
113
+		flex-direction: column;
114
+	}
115
+
116
+	.uni-steps__row {
117
+		/* #ifndef APP-NVUE */
118
+		display: flex;
119
+		/* #endif */
120
+		flex-direction: column;
121
+	}
122
+
123
+	.uni-steps__column {
124
+		/* #ifndef APP-NVUE */
125
+		display: flex;
126
+		/* #endif */
127
+		flex-direction: row-reverse;
128
+	}
129
+
130
+	.uni-steps__row-text-container {
131
+		/* #ifndef APP-NVUE */
132
+		display: flex;
133
+		/* #endif */
134
+		flex-direction: row;
135
+		align-items: flex-end;
136
+		margin-bottom: 8px;
137
+	}
138
+
139
+	.uni-steps__column-text-container {
140
+		/* #ifndef APP-NVUE */
141
+		display: flex;
142
+		/* #endif */
143
+		flex-direction: column;
144
+		flex: 1;
145
+	}
146
+
147
+	.uni-steps__row-text {
148
+		/* #ifndef APP-NVUE */
149
+		display: inline-flex;
150
+		/* #endif */
151
+		flex: 1;
152
+		flex-direction: column;
153
+	}
154
+
155
+	.uni-steps__column-text {
156
+		padding: 6px 0px;
157
+		border-bottom-style: solid;
158
+		border-bottom-width: 1px;
159
+		border-bottom-color: $uni-border-color;
160
+		/* #ifndef APP-NVUE */
161
+		display: flex;
162
+		/* #endif */
163
+		flex-direction: column;
164
+	}
165
+
166
+	.uni-steps__row-title {
167
+		font-size: 14px;
168
+		line-height: 16px;
169
+		text-align: center;
170
+	}
171
+
172
+	.uni-steps__column-title {
173
+		font-size: 14px;
174
+		text-align: left;
175
+		line-height: 18px;
176
+	}
177
+
178
+	.uni-steps__row-desc {
179
+		font-size: 12px;
180
+		line-height: 14px;
181
+		text-align: center;
182
+	}
183
+
184
+	.uni-steps__column-desc {
185
+		font-size: 12px;
186
+		text-align: left;
187
+		line-height: 18px;
188
+	}
189
+
190
+	.uni-steps__row-container {
191
+		/* #ifndef APP-NVUE */
192
+		display: flex;
193
+		/* #endif */
194
+		flex-direction: row;
195
+	}
196
+
197
+	.uni-steps__column-container {
198
+		/* #ifndef APP-NVUE */
199
+		display: inline-flex;
200
+		/* #endif */
201
+		width: 30px;
202
+		flex-direction: column;
203
+	}
204
+
205
+	.uni-steps__row-line-item {
206
+		/* #ifndef APP-NVUE */
207
+		display: inline-flex;
208
+		/* #endif */
209
+		flex-direction: row;
210
+		flex: 1;
211
+		height: 14px;
212
+		line-height: 14px;
213
+		align-items: center;
214
+		justify-content: center;
215
+	}
216
+
217
+	.uni-steps__column-line-item {
218
+		/* #ifndef APP-NVUE */
219
+		display: flex;
220
+		/* #endif */
221
+		flex-direction: column;
222
+		align-items: center;
223
+		justify-content: center;
224
+	}
225
+
226
+	.uni-steps__row-line {
227
+		flex: 1;
228
+		height: 1px;
229
+		background-color: #B7BDC6;
230
+	}
231
+
232
+	.uni-steps__column-line {
233
+		width: 1px;
234
+		background-color: #B7BDC6;
235
+	}
236
+
237
+	.uni-steps__row-line--after {
238
+		transform: translateX(1px);
239
+	}
240
+
241
+	.uni-steps__column-line--after {
242
+		flex: 1;
243
+		transform: translate(0px, 1px);
244
+	}
245
+
246
+	.uni-steps__row-line--before {
247
+		transform: translateX(-1px);
248
+	}
249
+
250
+	.uni-steps__column-line--before {
251
+		height: 6px;
252
+		transform: translate(0px, -13px);
253
+	}
254
+
255
+	.uni-steps__row-circle {
256
+		width: 5px;
257
+		height: 5px;
258
+		border-radius: 50%;
259
+		background-color: #B7BDC6;
260
+		margin: 0px 3px;
261
+	}
262
+
263
+	.uni-steps__column-circle {
264
+		width: 5px;
265
+		height: 5px;
266
+		border-radius: 50%;
267
+		background-color: #B7BDC6;
268
+		margin: 4px 0px 5px 0px;
269
+	}
270
+
271
+	.uni-steps__row-check {
272
+		margin: 0px 6px;
273
+	}
274
+
275
+	.uni-steps__column-check {
276
+		height: 14px;
277
+		line-height: 14px;
278
+		margin: 2px 0px;
279
+	}
280
+</style>

+ 87 - 0
uni_modules/uni-steps/package.json

@@ -0,0 +1,87 @@
1
+{
2
+  "id": "uni-steps",
3
+  "displayName": "uni-steps 步骤条",
4
+  "version": "1.1.2",
5
+  "description": "步骤条组件,提供横向和纵向两种布局格式。",
6
+  "keywords": [
7
+    "uni-ui",
8
+    "uniui",
9
+    "步骤条",
10
+    "时间轴"
11
+],
12
+  "repository": "https://github.com/dcloudio/uni-ui",
13
+  "engines": {
14
+    "HBuilderX": ""
15
+  },
16
+  "directories": {
17
+    "example": "../../temps/example_temps"
18
+  },
19
+"dcloudext": {
20
+    "sale": {
21
+      "regular": {
22
+        "price": "0.00"
23
+      },
24
+      "sourcecode": {
25
+        "price": "0.00"
26
+      }
27
+    },
28
+    "contact": {
29
+      "qq": ""
30
+    },
31
+    "declaration": {
32
+      "ads": "无",
33
+      "data": "无",
34
+      "permissions": "无"
35
+    },
36
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
37
+    "type": "component-vue"
38
+  },
39
+  "uni_modules": {
40
+    "dependencies": [
41
+			"uni-scss",
42
+			"uni-icons"
43
+		],
44
+    "encrypt": [],
45
+    "platforms": {
46
+      "cloud": {
47
+        "tcb": "y",
48
+        "aliyun": "y",
49
+        "alipay": "n"
50
+      },
51
+      "client": {
52
+        "App": {
53
+          "app-vue": "y",
54
+          "app-nvue": "y"
55
+        },
56
+        "H5-mobile": {
57
+          "Safari": "y",
58
+          "Android Browser": "y",
59
+          "微信浏览器(Android)": "y",
60
+          "QQ浏览器(Android)": "y"
61
+        },
62
+        "H5-pc": {
63
+          "Chrome": "y",
64
+          "IE": "y",
65
+          "Edge": "y",
66
+          "Firefox": "y",
67
+          "Safari": "y"
68
+        },
69
+        "小程序": {
70
+          "微信": "y",
71
+          "阿里": "y",
72
+          "百度": "y",
73
+          "字节跳动": "y",
74
+          "QQ": "y"
75
+        },
76
+        "快应用": {
77
+          "华为": "u",
78
+          "联盟": "u"
79
+        },
80
+        "Vue": {
81
+            "vue2": "y",
82
+            "vue3": "y"
83
+        }
84
+      }
85
+    }
86
+  }
87
+}

+ 13 - 0
uni_modules/uni-steps/readme.md

@@ -0,0 +1,13 @@
1
+
2
+
3
+## Steps 步骤条
4
+> **组件名:uni-steps**
5
+> 代码块: `uSteps`
6
+
7
+
8
+步骤条,常用于显示进度
9
+
10
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-steps)
11
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 
12
+
13
+

+ 24 - 0
uni_modules/uni-transition/changelog.md

@@ -0,0 +1,24 @@
1
+## 1.3.3(2024-04-23)
2
+- 修复 当元素会受变量影响自动隐藏的bug
3
+## 1.3.2(2023-05-04)
4
+- 修复 NVUE 平台报错的问题
5
+## 1.3.1(2021-11-23)
6
+- 修复 init 方法初始化问题
7
+## 1.3.0(2021-11-19)
8
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
9
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-transition](https://uniapp.dcloud.io/component/uniui/uni-transition)
10
+## 1.2.1(2021-09-27)
11
+- 修复 init 方法不生效的 Bug
12
+## 1.2.0(2021-07-30)
13
+- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
14
+## 1.1.1(2021-05-12)
15
+- 新增 示例地址
16
+- 修复 示例项目缺少组件的 Bug
17
+## 1.1.0(2021-04-22)
18
+- 新增 通过方法自定义动画
19
+- 新增 custom-class 非 NVUE 平台支持自定义 class 定制样式
20
+- 优化 动画触发逻辑,使动画更流畅
21
+- 优化 支持单独的动画类型
22
+- 优化 文档示例
23
+## 1.0.2(2021-02-05)
24
+- 调整为 uni_modules 目录规范

+ 131 - 0
uni_modules/uni-transition/components/uni-transition/createAnimation.js

@@ -0,0 +1,131 @@
1
+// const defaultOption = {
2
+// 	duration: 300,
3
+// 	timingFunction: 'linear',
4
+// 	delay: 0,
5
+// 	transformOrigin: '50% 50% 0'
6
+// }
7
+// #ifdef APP-NVUE
8
+const nvueAnimation = uni.requireNativePlugin('animation')
9
+// #endif
10
+class MPAnimation {
11
+	constructor(options, _this) {
12
+		this.options = options
13
+		// 在iOS10+QQ小程序平台下,传给原生的对象一定是个普通对象而不是Proxy对象,否则会报parameter should be Object instead of ProxyObject的错误
14
+		this.animation = uni.createAnimation({
15
+			...options
16
+		})
17
+		this.currentStepAnimates = {}
18
+		this.next = 0
19
+		this.$ = _this
20
+
21
+	}
22
+
23
+	_nvuePushAnimates(type, args) {
24
+		let aniObj = this.currentStepAnimates[this.next]
25
+		let styles = {}
26
+		if (!aniObj) {
27
+			styles = {
28
+				styles: {},
29
+				config: {}
30
+			}
31
+		} else {
32
+			styles = aniObj
33
+		}
34
+		if (animateTypes1.includes(type)) {
35
+			if (!styles.styles.transform) {
36
+				styles.styles.transform = ''
37
+			}
38
+			let unit = ''
39
+			if(type === 'rotate'){
40
+				unit = 'deg'
41
+			}
42
+			styles.styles.transform += `${type}(${args+unit}) `
43
+		} else {
44
+			styles.styles[type] = `${args}`
45
+		}
46
+		this.currentStepAnimates[this.next] = styles
47
+	}
48
+	_animateRun(styles = {}, config = {}) {
49
+		let ref = this.$.$refs['ani'].ref
50
+		if (!ref) return
51
+		return new Promise((resolve, reject) => {
52
+			nvueAnimation.transition(ref, {
53
+				styles,
54
+				...config
55
+			}, res => {
56
+				resolve()
57
+			})
58
+		})
59
+	}
60
+
61
+	_nvueNextAnimate(animates, step = 0, fn) {
62
+		let obj = animates[step]
63
+		if (obj) {
64
+			let {
65
+				styles,
66
+				config
67
+			} = obj
68
+			this._animateRun(styles, config).then(() => {
69
+				step += 1
70
+				this._nvueNextAnimate(animates, step, fn)
71
+			})
72
+		} else {
73
+			this.currentStepAnimates = {}
74
+			typeof fn === 'function' && fn()
75
+			this.isEnd = true
76
+		}
77
+	}
78
+
79
+	step(config = {}) {
80
+		// #ifndef APP-NVUE
81
+		this.animation.step(config)
82
+		// #endif
83
+		// #ifdef APP-NVUE
84
+		this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
85
+		this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
86
+		this.next++
87
+		// #endif
88
+		return this
89
+	}
90
+
91
+	run(fn) {
92
+		// #ifndef APP-NVUE
93
+		this.$.animationData = this.animation.export()
94
+		this.$.timer = setTimeout(() => {
95
+			typeof fn === 'function' && fn()
96
+		}, this.$.durationTime)
97
+		// #endif
98
+		// #ifdef APP-NVUE
99
+		this.isEnd = false
100
+		let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref
101
+		if(!ref) return
102
+		this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
103
+		this.next = 0
104
+		// #endif
105
+	}
106
+}
107
+
108
+
109
+const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
110
+	'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
111
+	'translateZ'
112
+]
113
+const animateTypes2 = ['opacity', 'backgroundColor']
114
+const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
115
+animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
116
+	MPAnimation.prototype[type] = function(...args) {
117
+		// #ifndef APP-NVUE
118
+		this.animation[type](...args)
119
+		// #endif
120
+		// #ifdef APP-NVUE
121
+		this._nvuePushAnimates(type, args)
122
+		// #endif
123
+		return this
124
+	}
125
+})
126
+
127
+export function createAnimation(option, _this) {
128
+	if(!_this) return
129
+	clearTimeout(_this.timer)
130
+	return new MPAnimation(option, _this)
131
+}

+ 286 - 0
uni_modules/uni-transition/components/uni-transition/uni-transition.vue

@@ -0,0 +1,286 @@
1
+<template>
2
+  <!-- #ifndef APP-NVUE -->
3
+  <view v-show="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
4
+  <!-- #endif -->
5
+  <!-- #ifdef APP-NVUE -->
6
+  <view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
7
+  <!-- #endif -->
8
+</template>
9
+
10
+<script>
11
+import { createAnimation } from './createAnimation'
12
+
13
+/**
14
+ * Transition 过渡动画
15
+ * @description 简单过渡动画组件
16
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=985
17
+ * @property {Boolean} show = [false|true] 控制组件显示或隐藏
18
+ * @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
19
+ *  @value fade 渐隐渐出过渡
20
+ *  @value slide-top 由上至下过渡
21
+ *  @value slide-right 由右至左过渡
22
+ *  @value slide-bottom 由下至上过渡
23
+ *  @value slide-left 由左至右过渡
24
+ *  @value zoom-in 由小到大过渡
25
+ *  @value zoom-out 由大到小过渡
26
+ * @property {Number} duration 过渡动画持续时间
27
+ * @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red`
28
+ */
29
+export default {
30
+	name: 'uniTransition',
31
+	emits:['click','change'],
32
+	props: {
33
+		show: {
34
+			type: Boolean,
35
+			default: false
36
+		},
37
+		modeClass: {
38
+			type: [Array, String],
39
+			default() {
40
+				return 'fade'
41
+			}
42
+		},
43
+		duration: {
44
+			type: Number,
45
+			default: 300
46
+		},
47
+		styles: {
48
+			type: Object,
49
+			default() {
50
+				return {}
51
+			}
52
+		},
53
+		customClass:{
54
+			type: String,
55
+			default: ''
56
+		},
57
+		onceRender:{
58
+			type:Boolean,
59
+			default:false
60
+		},
61
+	},
62
+	data() {
63
+		return {
64
+			isShow: false,
65
+			transform: '',
66
+			opacity: 1,
67
+			animationData: {},
68
+			durationTime: 300,
69
+			config: {}
70
+		}
71
+	},
72
+	watch: {
73
+		show: {
74
+			handler(newVal) {
75
+				if (newVal) {
76
+					this.open()
77
+				} else {
78
+					// 避免上来就执行 close,导致动画错乱
79
+					if (this.isShow) {
80
+						this.close()
81
+					}
82
+				}
83
+			},
84
+			immediate: true
85
+		}
86
+	},
87
+	computed: {
88
+		// 生成样式数据
89
+		stylesObject() {
90
+			let styles = {
91
+				...this.styles,
92
+				'transition-duration': this.duration / 1000 + 's'
93
+			}
94
+			let transform = ''
95
+			for (let i in styles) {
96
+				let line = this.toLine(i)
97
+				transform += line + ':' + styles[i] + ';'
98
+			}
99
+			return transform
100
+		},
101
+		// 初始化动画条件
102
+		transformStyles() {
103
+			return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
104
+		}
105
+	},
106
+	created() {
107
+		// 动画默认配置
108
+		this.config = {
109
+			duration: this.duration,
110
+			timingFunction: 'ease',
111
+			transformOrigin: '50% 50%',
112
+			delay: 0
113
+		}
114
+		this.durationTime = this.duration
115
+	},
116
+	methods: {
117
+		/**
118
+		 *  ref 触发 初始化动画
119
+		 */
120
+		init(obj = {}) {
121
+			if (obj.duration) {
122
+				this.durationTime = obj.duration
123
+			}
124
+			this.animation = createAnimation(Object.assign(this.config, obj),this)
125
+		},
126
+		/**
127
+		 * 点击组件触发回调
128
+		 */
129
+		onClick() {
130
+			this.$emit('click', {
131
+				detail: this.isShow
132
+			})
133
+		},
134
+		/**
135
+		 * ref 触发 动画分组
136
+		 * @param {Object} obj
137
+		 */
138
+		step(obj, config = {}) {
139
+			if (!this.animation) return
140
+			for (let i in obj) {
141
+				try {
142
+					if(typeof obj[i] === 'object'){
143
+						this.animation[i](...obj[i])
144
+					}else{
145
+						this.animation[i](obj[i])
146
+					}
147
+				} catch (e) {
148
+					console.error(`方法 ${i} 不存在`)
149
+				}
150
+			}
151
+			this.animation.step(config)
152
+			return this
153
+		},
154
+		/**
155
+		 *  ref 触发 执行动画
156
+		 */
157
+		run(fn) {
158
+			if (!this.animation) return
159
+			this.animation.run(fn)
160
+		},
161
+		// 开始过度动画
162
+		open() {
163
+			clearTimeout(this.timer)
164
+			this.transform = ''
165
+			this.isShow = true
166
+			let { opacity, transform } = this.styleInit(false)
167
+			if (typeof opacity !== 'undefined') {
168
+				this.opacity = opacity
169
+			}
170
+			this.transform = transform
171
+			// 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常
172
+			this.$nextTick(() => {
173
+				// TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器
174
+				this.timer = setTimeout(() => {
175
+					this.animation = createAnimation(this.config, this)
176
+					this.tranfromInit(false).step()
177
+					this.animation.run()
178
+					this.$emit('change', {
179
+						detail: this.isShow
180
+					})
181
+				}, 20)
182
+			})
183
+		},
184
+		// 关闭过度动画
185
+		close(type) {
186
+			if (!this.animation) return
187
+			this.tranfromInit(true)
188
+				.step()
189
+				.run(() => {
190
+					this.isShow = false
191
+					this.animationData = null
192
+					this.animation = null
193
+					let { opacity, transform } = this.styleInit(false)
194
+					this.opacity = opacity || 1
195
+					this.transform = transform
196
+					this.$emit('change', {
197
+						detail: this.isShow
198
+					})
199
+				})
200
+		},
201
+		// 处理动画开始前的默认样式
202
+		styleInit(type) {
203
+			let styles = {
204
+				transform: ''
205
+			}
206
+			let buildStyle = (type, mode) => {
207
+				if (mode === 'fade') {
208
+					styles.opacity = this.animationType(type)[mode]
209
+				} else {
210
+					styles.transform += this.animationType(type)[mode] + ' '
211
+				}
212
+			}
213
+			if (typeof this.modeClass === 'string') {
214
+				buildStyle(type, this.modeClass)
215
+			} else {
216
+				this.modeClass.forEach(mode => {
217
+					buildStyle(type, mode)
218
+				})
219
+			}
220
+			return styles
221
+		},
222
+		// 处理内置组合动画
223
+		tranfromInit(type) {
224
+			let buildTranfrom = (type, mode) => {
225
+				let aniNum = null
226
+				if (mode === 'fade') {
227
+					aniNum = type ? 0 : 1
228
+				} else {
229
+					aniNum = type ? '-100%' : '0'
230
+					if (mode === 'zoom-in') {
231
+						aniNum = type ? 0.8 : 1
232
+					}
233
+					if (mode === 'zoom-out') {
234
+						aniNum = type ? 1.2 : 1
235
+					}
236
+					if (mode === 'slide-right') {
237
+						aniNum = type ? '100%' : '0'
238
+					}
239
+					if (mode === 'slide-bottom') {
240
+						aniNum = type ? '100%' : '0'
241
+					}
242
+				}
243
+				this.animation[this.animationMode()[mode]](aniNum)
244
+			}
245
+			if (typeof this.modeClass === 'string') {
246
+				buildTranfrom(type, this.modeClass)
247
+			} else {
248
+				this.modeClass.forEach(mode => {
249
+					buildTranfrom(type, mode)
250
+				})
251
+			}
252
+
253
+			return this.animation
254
+		},
255
+		animationType(type) {
256
+			return {
257
+				fade: type ? 0 : 1,
258
+				'slide-top': `translateY(${type ? '0' : '-100%'})`,
259
+				'slide-right': `translateX(${type ? '0' : '100%'})`,
260
+				'slide-bottom': `translateY(${type ? '0' : '100%'})`,
261
+				'slide-left': `translateX(${type ? '0' : '-100%'})`,
262
+				'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
263
+				'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
264
+			}
265
+		},
266
+		// 内置动画类型与实际动画对应字典
267
+		animationMode() {
268
+			return {
269
+				fade: 'opacity',
270
+				'slide-top': 'translateY',
271
+				'slide-right': 'translateX',
272
+				'slide-bottom': 'translateY',
273
+				'slide-left': 'translateX',
274
+				'zoom-in': 'scale',
275
+				'zoom-out': 'scale'
276
+			}
277
+		},
278
+		// 驼峰转中横线
279
+		toLine(name) {
280
+			return name.replace(/([A-Z])/g, '-$1').toLowerCase()
281
+		}
282
+	}
283
+}
284
+</script>
285
+
286
+<style></style>

+ 85 - 0
uni_modules/uni-transition/package.json

@@ -0,0 +1,85 @@
1
+{
2
+  "id": "uni-transition",
3
+  "displayName": "uni-transition 过渡动画",
4
+  "version": "1.3.3",
5
+  "description": "元素的简单过渡动画",
6
+  "keywords": [
7
+    "uni-ui",
8
+    "uniui",
9
+    "动画",
10
+    "过渡",
11
+    "过渡动画"
12
+],
13
+  "repository": "https://github.com/dcloudio/uni-ui",
14
+  "engines": {
15
+    "HBuilderX": ""
16
+  },
17
+  "directories": {
18
+    "example": "../../temps/example_temps"
19
+  },
20
+"dcloudext": {
21
+    "sale": {
22
+      "regular": {
23
+        "price": "0.00"
24
+      },
25
+      "sourcecode": {
26
+        "price": "0.00"
27
+      }
28
+    },
29
+    "contact": {
30
+      "qq": ""
31
+    },
32
+    "declaration": {
33
+      "ads": "无",
34
+      "data": "无",
35
+      "permissions": "无"
36
+    },
37
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
38
+    "type": "component-vue"
39
+  },
40
+  "uni_modules": {
41
+    "dependencies": ["uni-scss"],
42
+    "encrypt": [],
43
+    "platforms": {
44
+      "cloud": {
45
+        "tcb": "y",
46
+        "aliyun": "y",
47
+        "alipay": "n"
48
+      },
49
+      "client": {
50
+        "App": {
51
+          "app-vue": "y",
52
+          "app-nvue": "y"
53
+        },
54
+        "H5-mobile": {
55
+          "Safari": "y",
56
+          "Android Browser": "y",
57
+          "微信浏览器(Android)": "y",
58
+          "QQ浏览器(Android)": "y"
59
+        },
60
+        "H5-pc": {
61
+          "Chrome": "y",
62
+          "IE": "y",
63
+          "Edge": "y",
64
+          "Firefox": "y",
65
+          "Safari": "y"
66
+        },
67
+        "小程序": {
68
+          "微信": "y",
69
+          "阿里": "y",
70
+          "百度": "y",
71
+          "字节跳动": "y",
72
+          "QQ": "y"
73
+        },
74
+        "快应用": {
75
+          "华为": "u",
76
+          "联盟": "u"
77
+        },
78
+        "Vue": {
79
+            "vue2": "y",
80
+            "vue3": "y"
81
+        }
82
+      }
83
+    }
84
+  }
85
+}

+ 11 - 0
uni_modules/uni-transition/readme.md

@@ -0,0 +1,11 @@
1
+
2
+
3
+## Transition 过渡动画
4
+> **组件名:uni-transition**
5
+> 代码块: `uTransition`
6
+
7
+
8
+元素过渡动画
9
+
10
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-transition)
11
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839