代码更新
|
@ -0,0 +1,427 @@
|
|||
@charset "UTF-8";
|
||||
|
||||
* {
|
||||
scrollbar-color: #e5e5e5 #f7f7f9;
|
||||
scrollbar-width: thin;
|
||||
}
|
||||
|
||||
html {
|
||||
margin: 0 auto;
|
||||
|
||||
}
|
||||
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
|
||||
}
|
||||
|
||||
.font-color,
|
||||
.font-color-red {
|
||||
color: #fc4141 !important
|
||||
}
|
||||
|
||||
.bg-color {
|
||||
background-color: #e93323
|
||||
}
|
||||
|
||||
.icon-color {
|
||||
color: #ff3c2b
|
||||
}
|
||||
|
||||
.cart-color {
|
||||
color: #ff3700 !important;
|
||||
border: 1px solid #ff3700 !important
|
||||
}
|
||||
|
||||
.padding20 {
|
||||
padding: 20rpx
|
||||
}
|
||||
|
||||
.pad20 {
|
||||
padding: 0 20rpx
|
||||
}
|
||||
|
||||
.padding30 {
|
||||
padding: 30rpx
|
||||
}
|
||||
|
||||
.pad30 {
|
||||
padding: 0 30rpx
|
||||
}
|
||||
|
||||
.pull-left {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.pull-right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.clearfix:after {
|
||||
content: '';
|
||||
display: block;
|
||||
height: 0;
|
||||
clear: both
|
||||
}
|
||||
|
||||
.clearfix {
|
||||
zoom: 1
|
||||
}
|
||||
|
||||
.acea-row {
|
||||
display: -webkit-box;
|
||||
display: -moz-box;
|
||||
display: -webkit-flex;
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-webkit-box-lines: multiple;
|
||||
-moz-box-lines: multiple;
|
||||
-o-box-lines: multiple;
|
||||
-webkit-flex-wrap: wrap;
|
||||
-ms-flex-wrap: wrap;
|
||||
flex-wrap: wrap
|
||||
}
|
||||
|
||||
.acea-row.row-middle {
|
||||
-webkit-box-align: center;
|
||||
-moz-box-align: center;
|
||||
-o-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.acea-row.row-top {
|
||||
-webkit-box-align: start;
|
||||
-moz-box-align: start;
|
||||
-o-box-align: start;
|
||||
-ms-flex-align: start;
|
||||
-webkit-align-items: flex-start;
|
||||
align-items: flex-start
|
||||
}
|
||||
|
||||
.acea-row.row-bottom {
|
||||
-webkit-box-align: end;
|
||||
-moz-box-align: end;
|
||||
-o-box-align: end;
|
||||
-ms-flex-align: end;
|
||||
-webkit-align-items: flex-end;
|
||||
align-items: flex-end
|
||||
}
|
||||
|
||||
.acea-row.row-center {
|
||||
-webkit-box-pack: center;
|
||||
-moz-box-pack: center;
|
||||
-o-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.acea-row.row-right {
|
||||
-webkit-box-pack: end;
|
||||
-moz-box-pack: end;
|
||||
-o-box-pack: end;
|
||||
-ms-flex-pack: end;
|
||||
-webkit-justify-content: flex-end;
|
||||
justify-content: flex-end
|
||||
}
|
||||
|
||||
.acea-row.row-left {
|
||||
-webkit-box-pack: start;
|
||||
-moz-box-pack: start;
|
||||
-o-box-pack: start;
|
||||
-ms-flex-pack: start;
|
||||
-webkit-justify-content: flex-start;
|
||||
justify-content: flex-start
|
||||
}
|
||||
|
||||
.acea-row.row-between {
|
||||
-webkit-box-pack: justify;
|
||||
-moz-box-pack: justify;
|
||||
-o-box-pack: justify;
|
||||
-ms-flex-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
justify-content: space-between
|
||||
}
|
||||
|
||||
.acea-row.row-around {
|
||||
justify-content: space-around;
|
||||
-webkit-justify-content: space-around
|
||||
}
|
||||
|
||||
.acea-row.row-column-around {
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
-webkit-justify-content: space-around
|
||||
}
|
||||
|
||||
.acea-row.row-column {
|
||||
-webkit-box-orient: vertical;
|
||||
-moz-box-orient: vertical;
|
||||
-o-box-orient: vertical;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column
|
||||
}
|
||||
|
||||
.acea-row.row-column-between {
|
||||
-webkit-box-orient: vertical;
|
||||
-moz-box-orient: vertical;
|
||||
-o-box-orient: vertical;
|
||||
-webkit-flex-direction: column;
|
||||
-ms-flex-direction: column;
|
||||
flex-direction: column;
|
||||
-webkit-box-pack: justify;
|
||||
-moz-box-pack: justify;
|
||||
-o-box-pack: justify;
|
||||
-ms-flex-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
justify-content: space-between
|
||||
}
|
||||
|
||||
.acea-row.row-center-wrapper {
|
||||
-webkit-box-align: center;
|
||||
-moz-box-align: center;
|
||||
-o-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: center;
|
||||
-moz-box-pack: center;
|
||||
-o-box-pack: center;
|
||||
-ms-flex-pack: center;
|
||||
-webkit-justify-content: center;
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.acea-row.row-between-wrapper {
|
||||
-webkit-box-align: center;
|
||||
-moz-box-align: center;
|
||||
-o-box-align: center;
|
||||
-ms-flex-align: center;
|
||||
-webkit-align-items: center;
|
||||
align-items: center;
|
||||
-webkit-box-pack: justify;
|
||||
-moz-box-pack: justify;
|
||||
-o-box-pack: justify;
|
||||
-ms-flex-pack: justify;
|
||||
-webkit-justify-content: space-between;
|
||||
justify-content: space-between
|
||||
}
|
||||
|
||||
.start {
|
||||
width: 122rpx;
|
||||
height: 30rpx;
|
||||
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAHoAAADMCAYAAAC8yreMAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAA4BpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNi1jMTQ1IDc5LjE2MzQ5OSwgMjAxOC8wOC8xMy0xNjo0MDoyMiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDpDMDg0NEE2QTVFNUQxMUU4QUI3RkNGOTgwNDYyRUZDOCIgeG1wTU06RG9jdW1lbnRJRD0ieG1wLmRpZDozODU5QzVENDMwRjcxMUU5OTQ0QzlEOTQ5RkE1MTlBRiIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozODU5QzVEMzMwRjcxMUU5OTQ0QzlEOTQ5RkE1MTlBRiIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgQ0MgMjAxOCAoV2luZG93cykiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpkN2ZhZTM5NC0wNmE4LTkzNGYtODA0OS0zNjBjNTcxOTU2YjAiIHN0UmVmOmRvY3VtZW50SUQ9ImFkb2JlOmRvY2lkOnBob3Rvc2hvcDpmYWI1M2NhMC04MWE1LTE5NGItYmJlYi1jMzI2MjIwNmNhOTYiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4ctYL1AAAHsElEQVR42uycS4gcVRiFq5Mm4yuKihoVlKg7IVlkI4oGFV9R4wPSJChBMW5ECYooKuIbEReCBBGJEDS+BkVJYEIMJgvJIossFATxCW58boRJEDEZz83cDk3TXc/7T9Xt+g783O7p6tP/9Km6dbvp+jpzc3MJmnwt4i0gaETQiKBRM+UWY8OVpt9XXXySakcZ34Xwp/fR3mWO6LWqW/Wilxnte5b+re29TNB3+fEeozfL0r+1vXdGTRmdTmfc9HGGG1Rd1d+qZecc/PGfcVPI2Bc19Kf3uSBHdM+/mNNpqtsD77WW/q3uvTtmDzpPw9kDxm5XW6q6b2jTB7XtrEZXc35vc/rNV7LQ/vQ+2rs75v/Zproux45yhWrniL/vUq1JeZ6lP70XmLpvVr1SYoo5qnrBrQ4ztrP0p/eiizFND9dreHdgOknTr25lqEXCvrwLGgt/ei+xGNOTP9ewUrUn48XclLGi/2J5ZelP7wVX3TJxJ/f3Mjbbpu3+KrOctPSn9+Ifr9ZWfLxOf3rPE7TOF1Mabhg68e/yy/q+1mi7bpn/xNKf3osd0deqTva3f1ZdpenCLeFXq37wfz9ddWXJvdbSn94LBH2bH9/2J/79/hzypV8wvD60XVFZ+tN7xhcmgzrRnQv0AjtHLBgOa9is6eOzCm+WpT+9p32ORpMnfmFC0IigEUEjgkYEjQgaETQiaETQBI0IGhE0ImhE0IigEUGj8oIaEEfvTtPT0ytVF5bxh3gQl/9TqkcWauqGeFCDv47kM5P5y2PP1+0lpkH7K+9v9HfX6/4JIf8ZS/+Ye/dap/pI5U4Nt1gf0RAP6vO/WrVX9YnqjqJPhnjQoN5Tpu0LNPzS6/XcZTmHdH9WdZbu/1kp6ARqQC29Kzw39d+rOqL6V7XY33Y/5n9pYNOtqpdd4BqX+G3dNqck81dgzuQN2l15766gf7zgDHHUN/RcxnaW/jH3fkC1QbVbR+v7Y88Rvd5BDZsGdhA3qzykWqXaP+o5EA8a2LuCe0DDparHFOqhHKvxLaovtO3Wcf4QDxrYuwJ7Q8Nbqu0KckVKyO5Kyu2qF/shl151Qw2ox1/BfaXhbtVrKZs9r7pT234T6uMV1IB6/N3F8N+mPP6dX7BV/xwNNaA+f786n+kvuFT3qzYMPL5n4Euaykc01ID6/N3HuL0K91yNH/odaKnuu3P3Mt1264SbQgUNNaAGfwU55fNxX3e+qXrCLbhUbpH2tF9pO4Dcqdp2cYig+1feb1LNDi1GDqs26+Y1Fc5Dlv4x977azwSX+AXXTwMLNTd7rPOzhfti5vIsM4gHDdIgxllH6XJ3tPrVd9qRf5GGWW33R/9vI78bIeh2iJ8SETQiaETQiKARQSOCRgSNCBoRNEEjgkYEjQgaETQiaETQiKARQSMnYDXheneqApSx9AZWE16lgTKW3sBqAqoqUMbSG1hNWFUCylh6A6sJq0pAGUtvYDUBYDJ+aq0MlLH0BlZTsHdLoAywmmL+1r2bAWUSYDX5/ReqdwugjKl32gv6N8xdcP1OxnTopruNg6yOPG+Whf9C9u6+wNDwrOoZvclfjwnCXfr6pOrRQdZI1vse2htYTYXeQwNlLL2B1VTvPRhQxtIbWE01mEySBATKWHoDq6nWe5IEBMpYegOrqdB7aKCMpTewmmq9BwXKWHrDMCkoU6AMsBpUVfyUiKARQSOCRgSNCBoRNCJoRNCIoAkaETQiaETQiKARQSOCRgSNMtQmholTFQ4IDJOwsva3ZIxY+8MwySNLxoi1PwyTYrJkjFj7wzApIEvGiLU/DJOcU58ZY8TaH4bJ6DfFjANi7Q/DpJi/JWPE2h+GSVKQYWLBAbH2h2FSkmESmgNi7Q/DpKS/JWPE2h+GSXFZMkas/YN5Z7I7xnBAdvsvNvpzzTEOiI66/0p8SWLqnwxxQPwixl1P/IF/vM8B+bhkGJb+wbwnnWGSJLaMEWt/GCY5v2gwY4xY+8MwKSZLxoi1PwyT1H/IkANi7Q/DBFUWPyUiaETQiKARQSOCRgSNCBoRNCJogkYEjQgaETQiaETQiKARQaMMNY1h4tRUDggMk/BqJAck5t4bxzBpMgck5t4bxTDxaiwHJObem8YwcWosByTm3mthmKRMT43mgMTcey0Mk1g5IDH3XhfDJEoOSMy918owiY0DEnPvtTNMYuKAxNx77QyTmDggMffeFIZJFByQmHuvnWHSf34SAQck5t6bwDBJkkg4IDH3XjvDJCYOSMy9N4FhEg0HJObeR56jh5bnGxM/Z6TIfXbeVxKS8b2bDcaxOvR3Z7tFe+2M/6q1Kd5R9Q7DpCXip0QEjQgaETQiaETQiKARQSOCRgRN0IigEUEjgkYEjQgamQhqQDt6L3tEt5IaEHPvhYNuMzUg5t7LHNGtpQbE3HuZoFtLDYi590UFp4/jV977yzmPXXkfcHoy8297790xxlADWkI8gBowYb13oAZAPDj+IT2BGhB975mLMagBk9F73lU31IDIe+/mfEGoAZH3nveIhhoQee95FmNT/nPbDn/OeLh/UbYeW67hVdWnyTwXdL0eO1JwQRPUn95LLsYSqAET0XueczTUgAnoHeJBS8RPiQgaETQiaNRM/S/AAOykxVBJG5QXAAAAAElFTkSuQmCC');
|
||||
background-repeat: no-repeat;
|
||||
background-size: 122rpx auto;
|
||||
}
|
||||
|
||||
.start.star5 {
|
||||
background-position: 0 3rpx;
|
||||
}
|
||||
|
||||
.start.star4 {
|
||||
background-position: 0 -30rpx;
|
||||
}
|
||||
|
||||
.start.star3 {
|
||||
background-position: 0 -70rpx;
|
||||
}
|
||||
|
||||
.start.star2 {
|
||||
background-position: 0 -105rpx;
|
||||
}
|
||||
|
||||
.start.star1 {
|
||||
background-position: 0 -140rpx;
|
||||
}
|
||||
|
||||
.start.star0 {
|
||||
background-position: 0 -175rpx;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box
|
||||
}
|
||||
|
||||
page {
|
||||
font-size: 28rpx;
|
||||
background-color: #f5f5f5;
|
||||
color: #333
|
||||
}
|
||||
|
||||
body,
|
||||
html {
|
||||
height: unset
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
line-height: normal;
|
||||
background-color: #fff
|
||||
}
|
||||
|
||||
button::after {
|
||||
border: 0
|
||||
}
|
||||
|
||||
radio .wx-radio-input {
|
||||
border-radius: 50%;
|
||||
width: 38rpx;
|
||||
height: 38rpx
|
||||
}
|
||||
|
||||
radio .wx-radio-input.wx-radio-input-checked {
|
||||
border: 1px solid #e93323;
|
||||
background-color: #e93323;
|
||||
}
|
||||
|
||||
radio .uni-radio-input {
|
||||
border-radius: 50%;
|
||||
width: 38rpx;
|
||||
height: 38rpx
|
||||
}
|
||||
|
||||
radio .uni-radio-input.uni-radio-input-checked {
|
||||
border: 1px solid #e93323;
|
||||
background-color: #e93323;
|
||||
}
|
||||
|
||||
.store-list uni-radio .uni-radio-input.uni-radio-input-checked,
|
||||
.store-list uni-radio .uni-radio-input {
|
||||
/* border-color: transparent;
|
||||
background-color: transparent; */
|
||||
}
|
||||
|
||||
.store-list uni-radio .uni-radio-input.uni-radio-input-checked:before {
|
||||
/* color: #e93323!important; */
|
||||
}
|
||||
|
||||
checkbox .wx-checkbox-input {
|
||||
width: 38rpx;
|
||||
height: 38rpx
|
||||
}
|
||||
|
||||
checkbox .wx-checkbox-input.wx-checkbox-input-checked::before {
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
checkbox .uni-checkbox-input {
|
||||
/* border-radius: 50%; */
|
||||
width: 38rpx;
|
||||
height: 38rpx
|
||||
}
|
||||
|
||||
checkbox .uni-checkbox-input.uni-checkbox-input-checked,
|
||||
checkbox .wx-checkbox-input.wx-checkbox-input-checked {
|
||||
border: 1px solid #20A162;
|
||||
background-color: #20A162;
|
||||
color: #fff !important;
|
||||
}
|
||||
|
||||
checkbox .uni-checkbox-input.uni-checkbox-input-checked::before {
|
||||
font-size: 35rpx
|
||||
}
|
||||
|
||||
.line1 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap
|
||||
}
|
||||
|
||||
.line2 {
|
||||
word-break: break-all;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
.mask {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #000;
|
||||
opacity: .5;
|
||||
z-index: 30
|
||||
}
|
||||
|
||||
@keyframes load {
|
||||
from {
|
||||
transform: rotate(0)
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg)
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes load {
|
||||
from {
|
||||
transform: rotate(0)
|
||||
}
|
||||
|
||||
to {
|
||||
transform: rotate(360deg)
|
||||
}
|
||||
}
|
||||
|
||||
.loadingpic {
|
||||
animation: load 3s linear 1s infinite;
|
||||
--webkit-animation: load 3s linear 1s infinite
|
||||
}
|
||||
|
||||
.loading-list {
|
||||
animation: load linear 1s infinite;
|
||||
-webkit-animation: load linear 1s infinite;
|
||||
font-size: 40rpx;
|
||||
margin-right: 22rpx
|
||||
}
|
||||
|
||||
.loading {
|
||||
width: 100%;
|
||||
height: 100rpx;
|
||||
line-height: 100rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
text-align: center
|
||||
}
|
||||
|
||||
.loading .line {
|
||||
position: absolute;
|
||||
width: 450rpx;
|
||||
left: 150rpx;
|
||||
top: 50rpx;
|
||||
height: 1px;
|
||||
border-top: 1px solid #eee
|
||||
}
|
||||
|
||||
.loading .text {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
padding: 0 20rpx;
|
||||
background: #fff;
|
||||
z-index: 2;
|
||||
color: #777
|
||||
}
|
||||
|
||||
.loadingicon .loading {
|
||||
animation: load linear 1s infinite;
|
||||
font-size: 45rpx;
|
||||
color: #000
|
||||
}
|
||||
|
||||
.loadingicon {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
overflow: hidden
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
$base-color: #0122c7;
|
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 6.8 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 785 B |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 69 KiB |
After Width: | Height: | Size: 24 KiB |
|
@ -0,0 +1,13 @@
|
|||
export const comonentList = [{
|
||||
id: 7,
|
||||
name: 'plant'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: 'store'
|
||||
},
|
||||
{
|
||||
id: 32,
|
||||
name: 'breeding'
|
||||
}
|
||||
]
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
export const companyContractType = [23, 24, 25, 29] // 公司合同
|
||||
export const personnerContractType = [19, 20, 21, 22] // 个人合同
|
||||
export const shareholderContractType = [40] // 股金合同
|
|
@ -0,0 +1,469 @@
|
|||
export const avatar = 'https://cdn.uviewui.com/uview/album/1.jpg'
|
||||
export const defaultAvatar =
|
||||
'https://thirdwx.qlogo.cn/mmopen/vi_32/POgEwh4mIHO4nibH0KlMECNjjGxQUq24ZEaGT4poC6icRiccVGKSyXwibcPq4BWmiaIGuG1icwxaQX6grC9VemZoJ8rg/132'
|
||||
export const prefix = 'https://lihai001.oss-cn-chengdu.aliyuncs.com/public/kk/'
|
||||
export const urls = [
|
||||
'https://cdn.uviewui.com/uview/album/1.jpg',
|
||||
'https://cdn.uviewui.com/uview/album/2.jpg',
|
||||
'https://cdn.uviewui.com/uview/album/3.jpg',
|
||||
'https://cdn.uviewui.com/uview/album/4.jpg',
|
||||
'https://cdn.uviewui.com/uview/album/7.jpg',
|
||||
'https://cdn.uviewui.com/uview/album/6.jpg',
|
||||
'https://cdn.uviewui.com/uview/album/5.jpg'
|
||||
]
|
||||
|
||||
export const wenluData = [
|
||||
'gxsy/fangsahn@2x.png',
|
||||
'gxsy/laojiao@2x.png',
|
||||
'gxsy/zbgy@2x.png'
|
||||
]
|
||||
export const marketData = [{
|
||||
title: '江阳区',
|
||||
text: '醉美泸州 • 中国酒城',
|
||||
src: 'gxsy/jiangyang@2x.png',
|
||||
bg: 'gxsy/jiangyang@2x(1).png',
|
||||
code: '510502'
|
||||
},
|
||||
{
|
||||
title: '龙马潭区',
|
||||
text: '中国酒城 • 文明泸州',
|
||||
src: 'gxsy/longmatan@2x.png',
|
||||
bg: 'gxsy/longmatan@2x(1).png',
|
||||
code: '510504'
|
||||
},
|
||||
{
|
||||
title: '纳溪区',
|
||||
text: '山水神韵 • 醇香酒城',
|
||||
src: 'gxsy/naxiqu@2x.png',
|
||||
bg: 'gxsy/naxi@2x.png',
|
||||
code: '510503'
|
||||
},
|
||||
{
|
||||
title: '泸县',
|
||||
text: '千年古县 • 宋韵龙城',
|
||||
src: 'gxsy/luxian@2x.png',
|
||||
bg: 'gxsy/luxian@2x(1).png',
|
||||
code: '510521'
|
||||
},
|
||||
{
|
||||
title: '叙永县',
|
||||
text: '康养竹乡 • 画稿叙永',
|
||||
src: 'gxsy/xuyongxian@2x.png',
|
||||
bg: 'gxsy/xuyong@2x.png',
|
||||
code: '510524'
|
||||
},
|
||||
{
|
||||
title: '古蔺县',
|
||||
text: '梦里郎酒 • 画里古蔺',
|
||||
src: 'gxsy/gulinxian@2x.png',
|
||||
bg: 'gxsy/jiangyang@2x(1).png',
|
||||
code: '510525'
|
||||
}, {
|
||||
title: '合江县',
|
||||
text: '千年荔城 • 甜美合江',
|
||||
src: 'gxsy/hejaingxian@2x.png',
|
||||
bg: 'gxsy/hejaing@2x.png',
|
||||
code: '510522'
|
||||
}
|
||||
]
|
||||
export const shichangData = [{
|
||||
url: 'img4@2x.png',
|
||||
title: 'rexiao@2x.png',
|
||||
text: '农业生产产品'
|
||||
},
|
||||
{
|
||||
url: 'img5@2x.png',
|
||||
title: 'dangji@2x.png',
|
||||
text: '村名生活用品'
|
||||
}
|
||||
]
|
||||
export const openList = [{
|
||||
title: '党建在线',
|
||||
text: '党建资讯文章',
|
||||
src: 'djzx@2x.png',
|
||||
color: '#FF614D',
|
||||
pid: 295,
|
||||
navCallBack: (pid, t, id) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/service_hall/party_building?pid=${pid}&title=${t}&village_id=${id}`
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '村务动态',
|
||||
text: '村务信息公开',
|
||||
src: 'cwdt@2x.png',
|
||||
color: '#4DB896',
|
||||
pid: 300,
|
||||
navCallBack: (pid, t, id) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/service_hall/party_building?pid=${pid}&title=${t}&village_id=${id}`
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '村镇新闻',
|
||||
text: '村镇新闻资讯',
|
||||
src: 'czxw@2x.png',
|
||||
color: '#FFAA33',
|
||||
pid: 304,
|
||||
navCallBack: (pid, t, id) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/service_hall/list?id=${pid}&title=${t}&village_id=${id}`
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '文明实践',
|
||||
text: '文明创建实践',
|
||||
src: 'wmsj@2x.png',
|
||||
color: '#FF7A3D',
|
||||
pid: '',
|
||||
navCallBack: (pid, t, id) => {
|
||||
uni.navigateTo({
|
||||
url: `/pages/service_hall/opens`
|
||||
})
|
||||
}
|
||||
}
|
||||
]
|
||||
export const quickLink = [{
|
||||
icon: 'scfw',
|
||||
src: 'scfw.png',
|
||||
name: '商超服务',
|
||||
url: '/pages/fast_track/production',
|
||||
category_id: 25
|
||||
},
|
||||
{
|
||||
icon: 'nfcp',
|
||||
src: 'nfcp.png',
|
||||
name: '农副产品',
|
||||
url: '/pages/fast_track/production',
|
||||
category_id: 26
|
||||
}, {
|
||||
icon: 'sczl',
|
||||
src: 'sczl.png',
|
||||
name: '生产资料',
|
||||
url: '/pages/fast_track/production',
|
||||
category_id: 22
|
||||
}, {
|
||||
icon: 'shfw',
|
||||
src: 'shfw.png',
|
||||
name: '生活服务',
|
||||
// url: '/pages/fast_track/service_life',
|
||||
url: '/pages/fast_track/production',
|
||||
category_id: 23
|
||||
}, {
|
||||
icon: 'hbxs',
|
||||
src: 'hbxs.png',
|
||||
name: '红白喜事',
|
||||
// url: '/pages/fast_track/red_white_thing',
|
||||
url: '/pages/fast_track/production',
|
||||
category_id: 21
|
||||
}, {
|
||||
icon: 'wyly',
|
||||
src: 'wyly.png',
|
||||
name: '文娱旅游',
|
||||
// url: '/pages/fast_track/travel'
|
||||
}, {
|
||||
icon: 'fwzx',
|
||||
src: 'fwzx.png',
|
||||
name: '房屋装修',
|
||||
// url: '/pages/fast_track/fitment'
|
||||
}, {
|
||||
icon: 'jypx',
|
||||
src: 'jypx.png',
|
||||
name: '教育资讯',
|
||||
// url: '/pages/fast_track/education'
|
||||
}, {
|
||||
icon: 'msgy',
|
||||
src: 'msgy.png',
|
||||
name: '民生资讯',
|
||||
// url: '/pages/fast_track/public_benefit'
|
||||
}, {
|
||||
icon: 'ylbj',
|
||||
src: 'ylbj.png',
|
||||
name: '医疗资讯'
|
||||
}
|
||||
]
|
||||
|
||||
// oaHOme快速入口数据
|
||||
export const oaHomeData = [{
|
||||
name: '公司信息',
|
||||
icon: '../../static/img/home/GSXX.png',
|
||||
paths: '/subpkg/companyInfo/companyInfo',
|
||||
admin: true
|
||||
},
|
||||
{
|
||||
name: '人员管理',
|
||||
icon: '../../static/img/home/RYGL.png',
|
||||
paths: '/subpkg/personnel/personnel',
|
||||
admin: true
|
||||
},
|
||||
{
|
||||
name: '固定资产',
|
||||
icon: '../../static/img/home/GDZC.png',
|
||||
paths: '/subpkg/property/index',
|
||||
admin: true
|
||||
},
|
||||
{
|
||||
name: '合同管理',
|
||||
icon: '../../static/img/home/HTGL.png',
|
||||
paths: '/subpkg/contract/contract'
|
||||
},
|
||||
// {
|
||||
// name: '公司管理',
|
||||
// icon: '../../static/img/home/GSXX.png',
|
||||
// paths: '/subpkg/companyAdmin/companyAdmin',
|
||||
// admin: true
|
||||
// },
|
||||
{
|
||||
name: '任务管理',
|
||||
icon: '../../static/img/home/RWGL.png',
|
||||
paths: '/subpkg/taskAdmin/taskAdmin',
|
||||
},
|
||||
{
|
||||
name: '档案管理',
|
||||
icon: '../../static/img/home/DAGL.png',
|
||||
paths: '/subpkg/captain/captain',
|
||||
admin: true
|
||||
},
|
||||
{
|
||||
name: '档案管理',
|
||||
icon: '../../static/img/home/DAGL.png',
|
||||
paths: '/subpkg/archives/archives',
|
||||
captain: true
|
||||
},
|
||||
// {
|
||||
// name: '片区经理',
|
||||
// icon: '../../static/img/home/GRCW.png',
|
||||
// paths: '/pages/oaManager/oaManager',
|
||||
// admin: true
|
||||
// },
|
||||
{
|
||||
name: '个人财务',
|
||||
icon: '../../static/img/home/GRCW.png',
|
||||
paths: '/subpkg/finance/finance'
|
||||
},
|
||||
{
|
||||
name: '待取驿站',
|
||||
icon: '../../static/img/home/YZ.png',
|
||||
paths: '/pages/logistics/post',
|
||||
// captain: true
|
||||
},
|
||||
// {
|
||||
// name: '出差申请',
|
||||
// icon: prefix + 'oa/ccsq@2x.png'
|
||||
// },
|
||||
// {
|
||||
// name: '外出申请',
|
||||
// icon: prefix + 'oa/wcsq@2x.png'
|
||||
// },
|
||||
// {
|
||||
// name: '采购申请',
|
||||
// icon: prefix + 'oa/cgsq@2x.png'
|
||||
// },
|
||||
// {
|
||||
// name: '物品维修',
|
||||
// icon: prefix + 'oa/bxsq@2x.png'
|
||||
// },
|
||||
// {
|
||||
// name: '用章申请',
|
||||
// icon: prefix + 'oa/yzsq@2x.png'
|
||||
// },
|
||||
// {
|
||||
// name: '报销申请',
|
||||
// icon: prefix + 'oa/gengduo@2x.png'
|
||||
// },
|
||||
{
|
||||
name: '更多',
|
||||
icon: prefix + 'oa/wpwx@2x.png',
|
||||
paths: '/pages/views/application'
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* oa-应用中心数据
|
||||
*/
|
||||
export const appDataList = [{
|
||||
title: '假勤',
|
||||
data: [{
|
||||
name: '请假申请',
|
||||
src: prefix + 'oa/qjsq@2x.png',
|
||||
url: '/pages/views/leave_request'
|
||||
},
|
||||
{
|
||||
name: '出差申请',
|
||||
src: prefix + 'oa/ccsq@2x.png',
|
||||
url: ''
|
||||
},
|
||||
{
|
||||
name: '外出申请',
|
||||
src: prefix + 'oa/wcsq@2x.png',
|
||||
url: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '行政',
|
||||
data: [{
|
||||
name: '物品维修',
|
||||
src: prefix + 'oa/bxsq@2x.png',
|
||||
url: ''
|
||||
},
|
||||
{
|
||||
name: '用章审批',
|
||||
src: prefix + 'oa/yzsq@2x.png',
|
||||
url: ''
|
||||
},
|
||||
{
|
||||
name: '领用审批',
|
||||
src: prefix + 'oa/lysp@2x.png',
|
||||
url: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '财务',
|
||||
data: [{
|
||||
name: '借款申请',
|
||||
src: prefix + 'oa/jksq@2x.png',
|
||||
url: ''
|
||||
},
|
||||
{
|
||||
name: '付款申请',
|
||||
src: prefix + 'oa/fksq@2x.png',
|
||||
url: ''
|
||||
},
|
||||
{
|
||||
name: '报销申请',
|
||||
src: prefix + 'oa/gengduo@2x.png',
|
||||
url: ''
|
||||
},
|
||||
{
|
||||
name: '采购申请',
|
||||
src: prefix + 'oa/cgsq@2x.png',
|
||||
url: ''
|
||||
},
|
||||
{
|
||||
name: '奖励申请',
|
||||
src: prefix + 'oa/jlsq@2x.png',
|
||||
url: ''
|
||||
},
|
||||
{
|
||||
name: '活动经费',
|
||||
src: prefix + 'oa/hdjf@2x.png',
|
||||
url: ''
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: '人事',
|
||||
data: [{
|
||||
name: '招聘需求',
|
||||
src: prefix + 'oa/zpxq@2x.png',
|
||||
url: ''
|
||||
}]
|
||||
},
|
||||
{
|
||||
title: '其他',
|
||||
data: [{
|
||||
name: '通用审批',
|
||||
src: prefix + 'oa/tysp@2x.png',
|
||||
url: '/pages/views/com_approve'
|
||||
}]
|
||||
}
|
||||
]
|
||||
|
||||
/**
|
||||
* oa-个人中心
|
||||
*/
|
||||
export const myOaData = [
|
||||
// {
|
||||
// name: '流水详情',
|
||||
// icon: '../../static/icons/runningWater.png',
|
||||
// url: '/subpkg/orderDetail/orderDetail'
|
||||
// },
|
||||
// {
|
||||
// name: '管理后台',
|
||||
// icon: '../../static/icons/backstage.png',
|
||||
// // url: '/pages/views/personal_center_two'
|
||||
// },
|
||||
{
|
||||
name: '片区经理',
|
||||
icon: '../../static/icons/manager.png',
|
||||
url: '/pages/oaManager/oaManager'
|
||||
},
|
||||
{
|
||||
name: '安全设置',
|
||||
icon: '../../static/icons/setting.png',
|
||||
url: '/pages/updatePassword/updatePassword'
|
||||
},
|
||||
// {
|
||||
// name: '管理后台',
|
||||
// icon: 'custom-icongongzi',
|
||||
// // url: '/pages/views/personal_center_two'
|
||||
// },
|
||||
// {
|
||||
// name: '片区经理',
|
||||
// icon: 'custom-icongongzi',
|
||||
// url: '/pages/oaManager/oaManager'
|
||||
// },
|
||||
// {
|
||||
// name: '工资详情',
|
||||
// icon: 'custom-icongongzi',
|
||||
// url: '/pages/views/personal_center_two'
|
||||
// },
|
||||
// {
|
||||
// name: '公示文档',
|
||||
// icon: 'custom-iconwendang',
|
||||
// url: '/pages/views/public_document'
|
||||
// },
|
||||
// {
|
||||
// name: '绑定公众号',
|
||||
// icon: 'custom-iconweixin'
|
||||
// },
|
||||
// {
|
||||
// name: '意见反馈',
|
||||
// icon: 'custom-iconyijian'
|
||||
// }
|
||||
]
|
||||
/*
|
||||
oa-请假类型
|
||||
*/
|
||||
export const oaLeaveData = [{
|
||||
name: '事假',
|
||||
id: 1
|
||||
},
|
||||
{
|
||||
name: '年假',
|
||||
id: 2
|
||||
},
|
||||
{
|
||||
name: '调休假',
|
||||
id: 3
|
||||
},
|
||||
{
|
||||
name: '病假',
|
||||
id: 4
|
||||
},
|
||||
{
|
||||
name: '婚假',
|
||||
id: 5
|
||||
},
|
||||
{
|
||||
name: '丧假',
|
||||
id: 6
|
||||
},
|
||||
{
|
||||
name: '产假',
|
||||
id: 7
|
||||
},
|
||||
{
|
||||
name: '陪产假',
|
||||
id: 8
|
||||
},
|
||||
{
|
||||
name: '其他',
|
||||
id: 9
|
||||
}
|
||||
]
|
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.7 KiB |
|
@ -0,0 +1,13 @@
|
|||
export default {
|
||||
token: state => state.app.token,
|
||||
isLogin: state => !!state.app.token,
|
||||
userInfo: state => state.app.userInfo || {},
|
||||
eyeType: state => state.config.eyeType || true,
|
||||
config: state => state.config.config || {}
|
||||
};
|
||||
// export default {
|
||||
// token: state => 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJrYWlmYS5jcm1lYi5uZXQiLCJhdWQiOiJrYWlmYS5jcm1lYi5uZXQiLCJpYXQiOjE1NzcwODM1MzQsIm5iZiI6MTU3NzA4MzUzNCwiZXhwIjoxNTc3MDk0MzM0LCJqdGkiOnsiaWQiOjExMCwidHlwZSI6InVzZXIifX0.U-i1pbdRjyXI1gr79Uq2XBPZ89T8f5Ai9jwrR8woTwE',
|
||||
// isLogin: state => true,
|
||||
// backgroundColor: state => state.app.backgroundColor,
|
||||
// userInfo: state => state.app.userInfo || {}
|
||||
// };
|
|
@ -0,0 +1,20 @@
|
|||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2021 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
import Vue from "vue";
|
||||
import Vuex from "vuex";
|
||||
import modules from "./modules";
|
||||
import getters from "./getters";
|
||||
|
||||
Vue.use(Vuex);
|
||||
|
||||
export default new Vuex.Store({
|
||||
modules,
|
||||
getters,
|
||||
});
|
|
@ -0,0 +1,121 @@
|
|||
import { commonAuth } from '@/api/pubic.js'
|
||||
import { loginMobile } from '@/api/user.js'
|
||||
import { loginAccount } from '@/api/oaUser.js'
|
||||
import Routine from '@/libs/routine.js'
|
||||
import Cache from '@/utils/cache';
|
||||
import encrypt from '@/utils/encrypt.js';
|
||||
import oaHttp from '@/utils/oahttp.js';
|
||||
|
||||
const state = {
|
||||
userInfo: JSON.parse(Cache.get('USER_INFO') || '{}') || null,
|
||||
token: Cache.get("TOKEN") || null
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
setUserInfo(state, data) {
|
||||
state.userInfo = data
|
||||
Cache.set("USER_INFO", data)
|
||||
},
|
||||
LOGOUT(state) {
|
||||
Cache.clear('USER_INFO')
|
||||
Cache.clear('TOKEN')
|
||||
uni.showModal({
|
||||
content: '登录已过期,是否重新登录?',
|
||||
success(e) {
|
||||
if (e.confirm) uni.reLaunch({
|
||||
url: '/pages/oaLogin/oaLogin'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
CLEAR(state) {
|
||||
state.userInfo = null;
|
||||
state.token = null;
|
||||
Cache.clear('USER_INFO')
|
||||
Cache.clear('TOKEN')
|
||||
},
|
||||
UPDATE_USERINFO(state, data) {
|
||||
let time = res.data.result.expires_time - Cache.time();
|
||||
state.userInfo = data.result.user
|
||||
state.token = data.result.token
|
||||
Cache.set("USER_INFO", data.result.user, time)
|
||||
Cache.set("TOKEN", data.result.token, time)
|
||||
},
|
||||
SET_USERINFO(state, data) {
|
||||
let time = Cache.time();
|
||||
state.userInfo = data.user
|
||||
state.token = data.token
|
||||
Cache.set("USER_INFO", data.user, time)
|
||||
Cache.set("TOKEN", data.token, time)
|
||||
},
|
||||
SET_TOKEN(state, data) {
|
||||
let time = Cache.time();
|
||||
state.token = data.token;
|
||||
Cache.set("TOKEN", data.token, time);
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
RE_LOGIN({ state, commit }, data) {
|
||||
return new Promise((resolve, reject) => {
|
||||
let fromData = encrypt.decode('ACT');
|
||||
if(fromData) {
|
||||
loginAccount({ ...fromData }, true).then((res) => {
|
||||
commit('SET_TOKEN', res.data);
|
||||
oaHttp[data.method](data.url, data.data, data.opt).then((e) => {
|
||||
resolve(e);
|
||||
}).catch((err) => {
|
||||
reject(err)
|
||||
})
|
||||
}).catch((err) => {
|
||||
commit('LOGOUT')
|
||||
reject(err)
|
||||
})
|
||||
}else {
|
||||
commit('LOGOUT')
|
||||
reject();
|
||||
}
|
||||
})
|
||||
},
|
||||
MobileLogin({ state, commit }, force) {
|
||||
let data = {
|
||||
auth_token: uni.getStorageSync('auth_token'),
|
||||
phone: force.account,
|
||||
sms_code: force.captcha,
|
||||
spread: that.$Cache.get("spread"),
|
||||
// #ifdef APP-PLUS
|
||||
user_type: 'app',
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
user_type: 'h5',
|
||||
// #endif
|
||||
}
|
||||
loginMobile(data).then(res => {
|
||||
console.log('手机号登录', res);
|
||||
})
|
||||
},
|
||||
async getWxLogin({ state, commit }, force) {
|
||||
let newCode = null
|
||||
Routine.getCode().then(code => {
|
||||
newCode = code;
|
||||
})
|
||||
Routine.getUserProfile().then(res => {
|
||||
let userInfo = res.userInfo;
|
||||
userInfo.code = newCode;
|
||||
commonAuth({
|
||||
auth: {
|
||||
type: 'routine',
|
||||
auth: userInfo
|
||||
}
|
||||
}).then(res => {
|
||||
commit("UPDATE_USERINFO", res.data);
|
||||
})
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
state,
|
||||
mutations,
|
||||
actions
|
||||
};
|
|
@ -0,0 +1,83 @@
|
|||
import Cache from '@/utils/cache';
|
||||
import { getConfig } from "@/api/config.js";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
const state = {
|
||||
eyeType: Cache.get('eyeType') || true, // 小眼睛
|
||||
request: Cache.get('request') || true, // 网络请求
|
||||
config: JSON.parse(Cache.get('config')||'{}'),
|
||||
updateFlag: true
|
||||
};
|
||||
|
||||
const mutations = {
|
||||
SET_EYE_TYPE(state){
|
||||
state.eyeType=!state.eyeType;
|
||||
Cache.set('eyeType', state.eyeType);
|
||||
},
|
||||
SET_REQUEST(state, data=true){
|
||||
state.request = data;
|
||||
Cache.set('request', state.request);
|
||||
},
|
||||
SET_CONFIG(state, data){
|
||||
state.config = {...data};
|
||||
Cache.set('config', JSON.stringify(state.config));
|
||||
},
|
||||
SET_UPDATEFLAG(state, data){
|
||||
state.updateFlag = data;
|
||||
},
|
||||
};
|
||||
|
||||
const actions = {
|
||||
async initConfig({ state, commit }, data = false) {
|
||||
let res = await getConfig();
|
||||
commit('SET_CONFIG', res.data);
|
||||
// console.log(compareVersions(res.data.version, '1.0.0')==1&&compareVersions(res.data.version, Cache.get('wgt_version'))==1);
|
||||
if(uni.getStorageSync('uniMP')||!state.updateFlag) return ;//是小程序环境时不进行更新
|
||||
let os = uni.getSystemInfoSync();
|
||||
// uni.showModal({
|
||||
// title: `当前:${os.appVersion},WGT:${Cache.get('wgt_version')},返回:${res.data.version}`
|
||||
// })
|
||||
// #ifdef APP-PLUS
|
||||
if(data) uni.showLoading({
|
||||
title: '检查更新中'
|
||||
})
|
||||
const wgt_v = uni.getStorageSync('wgt_version')||'1.0.0';
|
||||
commit('SET_UPDATEFLAG', false);
|
||||
// 版本更新
|
||||
if(compareVersions(res.data.version, os.appWgtVersion||wgt_v)==1&&compareVersions(res.data.version, wgt_v)==1){
|
||||
try{
|
||||
let info = res.data.version_info||{};
|
||||
let version = {
|
||||
title: info.title||'发现新版本',
|
||||
content: info.content||'修复了部分BUG',
|
||||
versionName: info.version||'1.0.1',
|
||||
downUrl: info.dow_url||'',
|
||||
force: info.force==1?true:false, // 是否强制更新
|
||||
quiet: info.quiet==1?true:false // 是否静默更新
|
||||
}
|
||||
Updater.update(version);;
|
||||
}catch(e){
|
||||
console.log(e);
|
||||
}
|
||||
if(data) uni.hideLoading();
|
||||
}else if(data){
|
||||
uni.hideLoading();
|
||||
uni.showToast({
|
||||
title: '已经是最新版本了',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export default {
|
||||
state,
|
||||
mutations,
|
||||
actions
|
||||
};
|
|
@ -0,0 +1,15 @@
|
|||
// +----------------------------------------------------------------------
|
||||
// | CRMEB [ CRMEB赋能开发者,助力企业发展 ]
|
||||
// +----------------------------------------------------------------------
|
||||
// | Copyright (c) 2016~2021 https://www.crmeb.com All rights reserved.
|
||||
// +----------------------------------------------------------------------
|
||||
// | Licensed CRMEB并不是自由软件,未经许可不能去掉CRMEB相关版权
|
||||
// +----------------------------------------------------------------------
|
||||
// | Author: CRMEB Team <admin@crmeb.com>
|
||||
// +----------------------------------------------------------------------
|
||||
import app from "./app";
|
||||
import config from "./config";
|
||||
export default {
|
||||
app,
|
||||
config
|
||||
};
|
|
@ -0,0 +1,2 @@
|
|||
## 1.0.0(2022-04-15)
|
||||
1.0.0
|
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<view class="lottie" :options="options" :change:options="Lottie.optionsChange" :change:fun="Lottie.funChange">
|
||||
<canvas :class="canvasId" :canvas-id="canvasId" type="2d" ref="lottie" style="width:100%;height:100%;"></canvas>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import uuid from './uuid.js'
|
||||
// #ifdef MP-WEIXIN
|
||||
import { loadAnimation, setup } from './lottie-miniprogram.min.js'
|
||||
// #endif
|
||||
|
||||
export default {
|
||||
props:{
|
||||
options:{
|
||||
type: Object,
|
||||
default:()=>{}
|
||||
}
|
||||
},
|
||||
options: {
|
||||
styleIsolation: "isolated",
|
||||
addGlobalClass: true,
|
||||
virtualHost: true,
|
||||
},
|
||||
data(){
|
||||
return {
|
||||
canvasId: uuid(10),
|
||||
fun:{
|
||||
name: null,
|
||||
args: null
|
||||
},
|
||||
}
|
||||
},
|
||||
// #ifdef MP-WEIXIN
|
||||
watch: {
|
||||
// 微信小程序方法触发
|
||||
fun:{
|
||||
deep: true,
|
||||
handler(){
|
||||
let {name, args} = this.fun
|
||||
if(this?.lottie&&name) this.lottie[name](args)
|
||||
}
|
||||
},
|
||||
// 监听 options 变化
|
||||
options:{
|
||||
deep: true,
|
||||
handler(){
|
||||
// 停止上一次动画 destroy 报错,固先用stop
|
||||
this.lottie.stop()
|
||||
// 加载新动画数据
|
||||
this.loadData()
|
||||
}
|
||||
},
|
||||
},
|
||||
// #endif
|
||||
mounted(){
|
||||
// 检测配置参数,如果开启自动播放则标记
|
||||
this.fun.name = this.options?.autoplay??true?'play':null
|
||||
// #ifdef MP-WEIXIN
|
||||
this.$nextTick(function(){
|
||||
this.init()
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
methods:{
|
||||
// lottie 相关方法
|
||||
call(name=null,args=null){
|
||||
if(name&&this.fun.name!=name) this.fun.name = name
|
||||
if(args&&this.fun.args!=args) this.fun.args = args
|
||||
},
|
||||
// #ifdef MP-WEIXIN
|
||||
init(){
|
||||
const query = uni.createSelectorQuery().in(this);
|
||||
query.select(`.${this.canvasId}`).node(res => {
|
||||
|
||||
this.canvas = res.node
|
||||
|
||||
setup(this.canvas)
|
||||
this.loadData()
|
||||
|
||||
}).exec()
|
||||
},
|
||||
loadData(){
|
||||
this.lottie = loadAnimation({
|
||||
//循环播放
|
||||
loop: this.options?.autoplay??true,
|
||||
//自动播放
|
||||
autoplay: this.options?.autoplay??true,
|
||||
// 动画json的本地数据
|
||||
// animationData: this.options?.data,
|
||||
// 动画json的网络路径
|
||||
path: this.options?.path,
|
||||
rendererSettings: {
|
||||
context: this.canvas.getContext('2d')
|
||||
},
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<script module="Lottie" lang="renderjs">
|
||||
// #ifdef APP-PLUS || H5
|
||||
import { loadAnimation } from './lottie-web.min.js'
|
||||
|
||||
export default {
|
||||
data(){
|
||||
return {
|
||||
lottie: null,
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.init()
|
||||
},
|
||||
methods:{
|
||||
// 参数变化监听
|
||||
optionsChange(newValue, oldValue, ownerInstance, instance){
|
||||
// 销毁之前动画
|
||||
this.lottie?.destroy()
|
||||
this.init()
|
||||
},
|
||||
// 监听触发函数
|
||||
funChange(newValue, oldValue, ownerInstance, instance){
|
||||
let {name, args} = newValue
|
||||
this.lottie[name](args)
|
||||
},
|
||||
// lottie 动画初始化
|
||||
init(){
|
||||
this.lottie = loadAnimation({
|
||||
// 包含动画的dom元素
|
||||
container: this.$refs.lottie.$el,
|
||||
//渲染格式
|
||||
renderer: this.options?.renderer??'canvas',
|
||||
//循环播放
|
||||
loop: this.options?.autoplay??true,
|
||||
//自动播放
|
||||
autoplay: this.options?.autoplay??true,
|
||||
// 动画json的本地数据
|
||||
animationData: this.options?.data,
|
||||
// 动画json的网络路径
|
||||
path: this.options?.path
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
</script>
|
||||
<style>
|
||||
.lottie{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,23 @@
|
|||
|
||||
export default (len = 32, radix = null) => {
|
||||
let chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('');
|
||||
let uuid = [];
|
||||
radix = radix || chars.length;
|
||||
if (len) {
|
||||
// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
|
||||
for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix];
|
||||
} else {
|
||||
let r;
|
||||
// rfc4122标准要求返回的uuid中,某些位为固定的字符
|
||||
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
|
||||
uuid[14] = '4';
|
||||
|
||||
for (let i = 0; i < 36; i++) {
|
||||
if (!uuid[i]) {
|
||||
r = 0 | Math.random() * 16;
|
||||
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r];
|
||||
}
|
||||
}
|
||||
}
|
||||
return uuid.join('');
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
"id": "hx-lottie",
|
||||
"displayName": "hx-lottie",
|
||||
"version": "1.0.0",
|
||||
"description": "uniapp lottie动画插件, 适配H5, App, 微信小程序, 其他小程序暂未测试",
|
||||
"keywords": [
|
||||
"hx-lottie",
|
||||
"lottie",
|
||||
"lottie动画",
|
||||
"微信lottie"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.3.0"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://gitee.com/Xiaohuixiong_123/hx-lottie.git"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "n"
|
||||
},
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "u"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "u",
|
||||
"百度": "u",
|
||||
"字节跳动": "u",
|
||||
"QQ": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
<h3 align="center" style="margin:30px 0 30px;font-weight: bold;font-size:32px;">hx-lottie 使用说明</h3>
|
||||
|
||||
## 预览
|
||||
[![L8dXn0.gif](https://s1.ax1x.com/2022/04/15/L8dXn0.gif)](https://imgtu.com/i/L8dXn0)
|
||||
|
||||
[![L8wmND.gif](https://s1.ax1x.com/2022/04/15/L8wmND.gif)](https://imgtu.com/i/L8wmND)
|
||||
|
||||
## 使用说明
|
||||
|
||||
* 引入使用
|
||||
|
||||
``` html
|
||||
<!-- options 动画参数 -->
|
||||
<hx-lottie :options="options" ref="lottie">
|
||||
|
||||
```
|
||||
|
||||
* 参数说明
|
||||
|
||||
|
||||
| 属性值 | 参数说明 |
|
||||
|----------|---------------------------|
|
||||
| loop | 是否循环播放动画(Boolean 默认 true) |
|
||||
| autoplay | 是否自动播放(Boolean 默认 true) |
|
||||
| data | 动画数据(Object) |
|
||||
| path | 动画网络路径 (String) |
|
||||
|
||||
* 方法说名 `组件内方法统一使用 call(funName, args) 调用
|
||||
`
|
||||
1. 使用示例
|
||||
|
||||
```js
|
||||
// 播放动画
|
||||
this.$refs.lottie.call('play')
|
||||
// 暂停动画
|
||||
this.$refs.lottie.call('pause')
|
||||
```
|
||||
|
||||
1. 说明 `方法与lottie-web 方法保持一致` [详情可参考](http://airbnb.io/lottie/#/web?id=usage)
|
||||
|
||||
| 方法名 | 说明 |
|
||||
|----------|---------------------|
|
||||
| play | 播放动画 |
|
||||
| pause | 暂停动画 |
|
||||
| stop | 停止动画 |
|
||||
| setSpeed | 设置动画速度,args = speed |
|
||||
| … | ... |
|
|
@ -0,0 +1,18 @@
|
|||
## 1.0.8(2023-06-26)
|
||||
优化
|
||||
## 1.0.7(2023-06-08)
|
||||
增加预览二维码
|
||||
## 1.0.6(2023-06-08)
|
||||
增加禁用左滑参数
|
||||
## 1.0.5(2023-06-08)
|
||||
增加禁用参数
|
||||
## 1.0.4(2023-05-31)
|
||||
增加license
|
||||
## 1.0.3(2023-05-19)
|
||||
优化代码
|
||||
## 1.0.2(2023-05-19)
|
||||
增加示例代码
|
||||
## 1.0.1(2023-05-19)
|
||||
增加示例
|
||||
## 1.0.0(2023-05-19)
|
||||
初始化发布
|
|
@ -0,0 +1,166 @@
|
|||
<template>
|
||||
<view class="liu-slide">
|
||||
<view class="liu-slide-left" :style="'position: relative;left:'+left+'rpx'" @touchstart="touchstart"
|
||||
@touchmove="touchmove" @touchend="touchend">
|
||||
<slot></slot>
|
||||
</view>
|
||||
<view class="liu-slide-right">
|
||||
<view class="btn-item" v-for="(item,index) in btnList" :key="index"
|
||||
:style="'width:'+item.width+';height:'+100+'rpx;line-height:'+100+'rpx; background-color:'+item.bgColor+';color:'+item.color+';font-size:'+item.fontSize"
|
||||
@click.stop="clickItem(item)">
|
||||
<!-- <view class="liu-slide-right-img">
|
||||
<image src="@/static/images/delete.png" mode="aspectFit"></image>
|
||||
</view> -->
|
||||
|
||||
|
||||
|
||||
<view class="">
|
||||
{{item.name}}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
//操作按钮数组
|
||||
btnList: {
|
||||
type: Array,
|
||||
default: () => {
|
||||
return [{
|
||||
id: '1',
|
||||
name: '编辑',
|
||||
width: '100rpx',
|
||||
height: '100rpx',
|
||||
bgColor: '#5f92f7',
|
||||
color: '#FFFFFF',
|
||||
fontSize: '28rpx'
|
||||
}, {
|
||||
id: '2',
|
||||
name: '删除',
|
||||
height: '100rpx',
|
||||
width: '100rpx',
|
||||
bgColor: '#ed656d',
|
||||
color: '#FFFFFF',
|
||||
fontSize: '28rpx'
|
||||
}]
|
||||
}
|
||||
},
|
||||
//当前列索引
|
||||
index: {
|
||||
type: Number,
|
||||
require: true,
|
||||
default: 0
|
||||
},
|
||||
//是否禁用
|
||||
disable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
x: 0,
|
||||
left: 0,
|
||||
operation: 0,
|
||||
height: 0,
|
||||
screenWidth: 0
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(res => {
|
||||
const systemInfo = uni.getSystemInfoSync()
|
||||
this.screenWidth = systemInfo.screenWidth
|
||||
this.getBtnWidth()
|
||||
this.getListHeight()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
clickItem(e) {
|
||||
this.$emit('clickItem', {
|
||||
index: this.index,
|
||||
id: e.id
|
||||
})
|
||||
},
|
||||
//重置方法
|
||||
reset() {
|
||||
this.left = 0
|
||||
},
|
||||
getBtnWidth() {
|
||||
let view = uni.createSelectorQuery().in(this).select(".liu-slide-right");
|
||||
view.boundingClientRect(rect => {
|
||||
this.operation = this.px2rpx(rect.width, this.screenWidth)
|
||||
}).exec()
|
||||
},
|
||||
getListHeight() {
|
||||
let view = uni.createSelectorQuery().in(this).select(".liu-slide-left");
|
||||
view.boundingClientRect(rect => {
|
||||
this.height = this.px2rpx(rect.height, this.screenWidth)
|
||||
}).exec()
|
||||
},
|
||||
px2rpx(px, screenWidth) {
|
||||
return px / (screenWidth / 750)
|
||||
},
|
||||
touchstart(e) {
|
||||
if (this.disable) return
|
||||
this.x = this.px2rpx(e.touches[0].clientX, this.screenWidth)
|
||||
},
|
||||
touchmove(e) {
|
||||
if (this.disable) return
|
||||
let clientX = this.x - this.px2rpx(e.touches[0].clientX, this.screenWidth)
|
||||
if (clientX <= 0) this.left = 0
|
||||
else if (this.operation <= clientX) this.left = this.operation * -1
|
||||
else this.left = clientX * -1
|
||||
},
|
||||
touchend(e) {
|
||||
if (this.disable) return
|
||||
let clientX = this.x - this.px2rpx(e.changedTouches[0].clientX, this.screenWidth)
|
||||
this.left = clientX > this.operation / 2 ? this.operation * -1 : 0
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.liu-slide {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
}
|
||||
|
||||
.liu-slide-right-img {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.liu-slide-left {
|
||||
width: 100%;
|
||||
|
||||
overflow: hidden;
|
||||
background-color: #FFFFFF;
|
||||
transition: left 0.2s ease-in-out;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.liu-slide-right {
|
||||
position: absolute;
|
||||
top: 35%;
|
||||
right: 0;
|
||||
z-index: 1;
|
||||
display: flex;
|
||||
|
||||
/* justify-content: flex-end; */
|
||||
}
|
||||
|
||||
.btn-item {
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,6 @@
|
|||
### 1、本插件可免费下载使用;
|
||||
### 2、未经许可,严禁复制本插件派生同类插件上传插件市场;
|
||||
### 3、未经许可,严禁在插件市场恶意复制抄袭本插件进行违规获利;
|
||||
### 4、对本软件的任何使用都必须遵守这些条款,违反这些条款的个人或组织将面临法律追究。
|
||||
|
||||
|
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"id": "liu-swipe-action",
|
||||
"displayName": "滑动操作、左滑删除、滑动删除",
|
||||
"version": "1.0.8",
|
||||
"description": "简单好用的滑动操作、左滑删除、滑动删除组件,可自定义样式,源码简单易修改",
|
||||
"keywords": [
|
||||
"滑动操作",
|
||||
"左滑删除",
|
||||
"滑动删除",
|
||||
"滑动",
|
||||
"列表"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.0"
|
||||
},
|
||||
"dcloudext": {
|
||||
"type": "component-vue",
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": ""
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "u"
|
||||
},
|
||||
"App": {
|
||||
"app-vue": "u",
|
||||
"app-nvue": "u"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "u",
|
||||
"IE": "u",
|
||||
"Edge": "u",
|
||||
"Firefox": "u",
|
||||
"Safari": "u"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "u",
|
||||
"百度": "u",
|
||||
"字节跳动": "u",
|
||||
"QQ": "u",
|
||||
"钉钉": "u",
|
||||
"快手": "u",
|
||||
"飞书": "u",
|
||||
"京东": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
# liu-watermark适用于uni-app项目的滑动操作、左滑删除、滑动删除组件
|
||||
### 本组件目前兼容微信小程序、H5
|
||||
### 本组件是简单好用的滑动操作、左滑删除、滑动删除组件,可自定义样式,源码简单易修改
|
||||
# --- 扫码预览、关注我们 ---
|
||||
|
||||
## 扫码关注公众号,查看更多插件信息,预览插件效果!
|
||||
|
||||
![](https://uni.ckapi.pro/uniapp/publicize.png)
|
||||
|
||||
### 使用示例
|
||||
```
|
||||
<template>
|
||||
<view class="slide-main">
|
||||
<view class="list" v-for="(item,index) in list" :key="index">
|
||||
<liu-swipe-action :index="index" @clickItem="clickItem">
|
||||
<view class="item">
|
||||
<image class="item-img" src="https://cdn.pixabay.com/photo/2022/03/31/14/53/camp-7103189_1280.png">
|
||||
</image>
|
||||
<view class="item-name">{{item}}</view>
|
||||
</view>
|
||||
</liu-swipe-action>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
list: ['第1条', '第2条', '第3条', '第4条', '第5条', '第6条']
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//点击操作回调事件
|
||||
clickItem(e) {
|
||||
console.log('所点击的列表索引:' + e.index)
|
||||
console.log('所点击的按钮id:' + e.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.slide-main {
|
||||
width: 100%;
|
||||
|
||||
background-color: #f0f0f0;
|
||||
}
|
||||
|
||||
.list {
|
||||
width: 100%;
|
||||
margin-top: 1px;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 100%;
|
||||
height: 120rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.item-img {
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
border-radius: 50%;
|
||||
margin-left: 28rpx;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
margin-left: 16rpx;
|
||||
font-size: 30rpx;
|
||||
color: #666666;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
### 如需图文水印可自行修改组件内容
|
||||
|
||||
### 属性说明
|
||||
| 名称 | 类型 | 默认值 | 描述 |
|
||||
| ----------------------------|--------------- | -------------------- | ---------------|
|
||||
| index | Number | 0 | 当前列索引
|
||||
| disable | Boolean | false | 是否禁用左滑
|
||||
| btnList | Array | 默认显示编辑、删除 | 操作按钮数组信息,默认显示编辑、删除,可自定义传入
|
||||
| @clickItem | Function | | 点击操作回调事件(返回当前列id和操作按钮id)
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
## 2.2.9(2023-06-01)
|
||||
优化:将是否多选与count字段解绑(原逻辑是count>1为允许多选),改为新增multiple属性控制是否多选。
|
||||
## 2.2.8(2023-06-01)
|
||||
修复上版本提交时accept测试值未删除导致h5端只能选择图片的问题。
|
||||
## 2.2.7(2023-05-06)
|
||||
应群友建议,当instantly为true时,触发change事件后延迟1000毫秒再自动上传,方便动态修改参数,其实个人还是建议想在change事件动态设置参数的伙伴将instantly设置为false,修改参数后手动调用upload()
|
||||
## 2.2.6(2023-02-09)
|
||||
修复多个文件同时选择时返回多次change回调的问题
|
||||
## 2.2.5(2022-12-27)
|
||||
1.修复多选文件时未能正常校验数量的问题;
|
||||
2.app端与H5端支持单选或多选文件,通过count数量控制,超过1开启多选。
|
||||
## 2.2.4(2022-12-27)
|
||||
1.修复多选文件时未能正常校验数量的问题;
|
||||
2.app端修复多选只取到第一个文件的问题。
|
||||
## 2.2.3(2022-12-06)
|
||||
修复手动调用show()导致count失效的问题
|
||||
## 2.2.2(2022-12-01)
|
||||
Vue3自行修改兼容
|
||||
## 2.2.1(2022-10-19)
|
||||
修复childId警告提示
|
||||
## 2.2.0(2022-10-10)
|
||||
更新app端webview窗口参数clidId,默认值添加时间戳保证唯一性
|
||||
## 2.1.9(2022-07-13)
|
||||
[修复] app端选择文件后初始化设置的文件列表被清空问题
|
||||
## 2.1.8(2022-07-13)
|
||||
[新增] ref方法初始化文件列表,用于已提交后再次编辑时需带入已上传文件:setFiles(files),可传入数组或Map对象,传入格式请与组件选择返回格式保持一致,且name为必须属性。
|
||||
## 2.1.7(2022-07-12)
|
||||
修复ios端偶现创建webview初始化参数未生效的问题
|
||||
## 2.1.6(2022-07-11)
|
||||
[修复]:修复上个版本更新导致nvue窗口组件不能选择文件的问题;
|
||||
[新增]:
|
||||
1.应群友建议(填写禁止格式太多)格式限制formats由原来填写禁止选择的格式改为填写允许被选择的格式;
|
||||
2.应群友建议(增加上传结束回调事件),上传结束回调事件@uploadEnd
|
||||
3.如能帮到你请留下你的免费好评,组件使用过程中有问题可以加QQ群交流,至于Map对象怎么使用这类前端基础问题请自行百度
|
||||
## 2.1.5(2022-07-01)
|
||||
app端组件销毁时添加自动销毁webview功能,避免v-if销毁组件的情况控件还能被点击的问题
|
||||
## 2.1.4(2022-07-01)
|
||||
修复小程序端回显问题
|
||||
## 2.1.3(2022-06-30)
|
||||
回调事件返回参数新增path字段(文件临时地址),用于回显
|
||||
## 2.1.2(2022-06-16)
|
||||
修复APP端Tabbar窗口无法选择文件的问题
|
||||
## 2.1.1(2022-06-16)
|
||||
优化:
|
||||
1.组件优化为允许在v-if中使用;
|
||||
2.允许option直接在data赋值,不再强制在onRead中初始化;
|
||||
## 2.1.0(2022-06-13)
|
||||
h5 pc端更改为单次可多选
|
||||
## 2.0.9(2022-06-10)
|
||||
更新演示内容,部分同学不知道怎么获取服务端返回的数据
|
||||
## 2.0.8(2022-06-09)
|
||||
优化动态更新上传参数函数,具体查看下方说明:动态更新参数演示
|
||||
## 2.0.7(2022-06-07)
|
||||
新增wxFileType属性,用于小程序端选择附件时可选文件类型
|
||||
## 2.0.6(2022-06-07)
|
||||
修复小程序端真机选择文件提示失败的问题
|
||||
## 2.0.5(2022-06-02)
|
||||
优化小程序端调用hide()后未阻止触发文件选择问题
|
||||
## 2.0.4(2022-06-01)
|
||||
优化APP端选择器初始定位
|
||||
## 2.0.3(2022-05-31)
|
||||
修复nvue窗口选择文件报错问题
|
||||
## 2.0.2(2022-05-20)
|
||||
修复ios端opiton设置过早未传入webview导致不自动上传问题
|
||||
## 2.0.1(2022-05-19)
|
||||
修复APP端子窗口点击选择文件不响应问题
|
||||
## 2.0.0(2022-05-18)
|
||||
此次组件更新至2.0版本,与1.0版本使用上略有差异,已使用1.0的同学请自行斟酌是否需要升级!
|
||||
部分差异:
|
||||
一、 2.0新增异步触发上传功能;
|
||||
二、2.0新增文件批量上传功能;
|
||||
三、2.0优化option,剔除属性,只保留上传接口所需字段,且允许异步更改option的值;
|
||||
四、组件增加size(文件大小限制)、count(文件个数限制)、formats(文件后缀限制)、accept(文件类型限制)、instantly(是否立即自动上传)、debug(日志打印)等属性;
|
||||
五、回调事件取消input事件、callback事件,新增change事件和progress事件;
|
||||
六、ref事件新增upload事件、clear事件;
|
||||
七、优化组件代码,show和hide函数改为显示隐藏,不再重复开关webview;
|
||||
|
||||
## 1.2.3(2022-03-22)
|
||||
修复Demo里传入待完善功能[手动上传属性manual=true]导致不自动上传的问题,手动提交上传待下个版本更新
|
||||
## 1.2.2(2022-02-21)
|
||||
修复上版本APP优化导致H5和小程序端不自动初始化的问题,此次更新仅修复此问题。异步提交功能下个版本更新~
|
||||
## 1.2.1(2022-01-25)
|
||||
QQ1群已满,已开放2群:469580165
|
||||
## 1.2.0(2021-12-09)
|
||||
优化APP端页面中DOM重排后每次需要重新定位的问题
|
||||
## 1.1.1(2021-12-09)
|
||||
优化,与上版本使用方式有改变,请检查后确认是否需要更新,create更名为show, close更名为hide,取消初始化时手动create, 传参方式改为props=>option
|
||||
## 1.1.0(2021-12-09)
|
||||
新增refresh方法,用于DOM发生重排时重新定位控件(APP端)
|
||||
## 1.0.9(2021-07-15)
|
||||
修复上传进度未同步渲染,直接返回100%的BUG
|
||||
## 1.0.8(2021-07-12)
|
||||
修复H5端传入height和width未生效的bug
|
||||
## 1.0.7(2021-07-07)
|
||||
修复h5和小程序端上传完成callback未返回fileName字段问题
|
||||
## 1.0.6(2021-07-07)
|
||||
修复h5端提示信息debug
|
||||
## 1.0.5(2021-06-29)
|
||||
感谢小伙伴找出bug,上传成功回调success未置为true,已修复
|
||||
## 1.0.4(2021-06-28)
|
||||
新增兼容APP,H5,小程序手动关闭控件,关闭后不再弹出文件选择框,需要重新create再次开启
|
||||
## 1.0.3(2021-06-28)
|
||||
close增加条件编译,除app端外不需要close
|
||||
## 1.0.2(2021-06-28)
|
||||
1.修复页面滚动位置后再create控件导致控件位置不正确的问题;
|
||||
2.修复nvue无法create控件;
|
||||
3.示例项目新增nvue使用案例;
|
||||
## 1.0.1(2021-06-28)
|
||||
因为有的朋友不清楚app端切换tab时应该怎么处理webview,现重新上传一版示例项目,需要做tab切换的朋友可以导入示例项目查看
|
||||
## 1.0.0(2021-06-25)
|
||||
此插件为l-file插件中上传功能改版,更新内容为:
|
||||
1. 按钮内嵌入页面,不再强制固定底部,可跟随页面滚动
|
||||
2.无需再单独弹框点击上传,减去中间层
|
||||
3.通过slot自定义按钮样式
|
|
@ -0,0 +1,400 @@
|
|||
export class LsjFile {
|
||||
constructor(data) {
|
||||
this.dom = null;
|
||||
// files.type = waiting(等待上传)|| loading(上传中)|| success(成功) || fail(失败)
|
||||
this.files = new Map();
|
||||
this.debug = data.debug || false;
|
||||
this.id = data.id;
|
||||
this.width = data.width;
|
||||
this.height = data.height;
|
||||
this.option = data.option;
|
||||
this.instantly = data.instantly;
|
||||
this.prohibited = data.prohibited;
|
||||
this.onchange = data.onchange;
|
||||
this.onprogress = data.onprogress;
|
||||
this.uploadHandle = this._uploadHandle;
|
||||
// #ifdef MP-WEIXIN
|
||||
this.uploadHandle = this._uploadHandleWX;
|
||||
// #endif
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 创建File节点
|
||||
* @param {string}path webview地址
|
||||
*/
|
||||
create(path) {
|
||||
if (!this.dom) {
|
||||
// #ifdef H5
|
||||
let dom = document.createElement('input');
|
||||
dom.type = 'file'
|
||||
dom.value = ''
|
||||
dom.style.height = this.height
|
||||
dom.style.width = this.width
|
||||
dom.style.position = 'absolute'
|
||||
dom.style.top = 0
|
||||
dom.style.left = 0
|
||||
dom.style.right = 0
|
||||
dom.style.bottom = 0
|
||||
dom.style.opacity = 0
|
||||
dom.style.zIndex = 999
|
||||
dom.accept = this.prohibited.accept;
|
||||
if (this.prohibited.multiple) {
|
||||
dom.multiple = 'multiple';
|
||||
}
|
||||
dom.onchange = event => {
|
||||
for (let file of event.target.files) {
|
||||
if (this.files.size >= this.prohibited.count) {
|
||||
this.toast(`只允许上传${this.prohibited.count}个文件`);
|
||||
this.dom.value = '';
|
||||
break;
|
||||
}
|
||||
this.addFile(file);
|
||||
}
|
||||
|
||||
this._uploadAfter();
|
||||
|
||||
this.dom.value = '';
|
||||
};
|
||||
this.dom = dom;
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
let styles = {
|
||||
top: '-200px',
|
||||
left: 0,
|
||||
width: '1px',
|
||||
height: '200px',
|
||||
background: 'transparent'
|
||||
};
|
||||
let extras = {
|
||||
debug: this.debug,
|
||||
instantly: this.instantly,
|
||||
prohibited: this.prohibited,
|
||||
}
|
||||
this.dom = plus.webview.create(path, this.id, styles,extras);
|
||||
this.setData(this.option);
|
||||
this._overrideUrlLoading();
|
||||
// #endif
|
||||
return this.dom;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 设置上传参数
|
||||
* @param {object|string}name 上传参数,支持a.b 和 a[b]
|
||||
*/
|
||||
setData() {
|
||||
let [name,value = ''] = arguments;
|
||||
if (typeof name === 'object') {
|
||||
Object.assign(this.option,name);
|
||||
}
|
||||
else {
|
||||
this._setValue(this.option,name,value);
|
||||
}
|
||||
|
||||
this.debug&&console.log(JSON.stringify(this.option));
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
this.dom.evalJS(`vm.setData('${JSON.stringify(this.option)}')`);
|
||||
// #endif
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传
|
||||
* @param {string}name 文件名称
|
||||
*/
|
||||
async upload(name='') {
|
||||
if (!this.option.url) {
|
||||
throw Error('未设置上传地址');
|
||||
}
|
||||
|
||||
// #ifndef APP-PLUS
|
||||
if (name && this.files.has(name)) {
|
||||
await this.uploadHandle(this.files.get(name));
|
||||
}
|
||||
else {
|
||||
for (let item of this.files.values()) {
|
||||
if (item.type === 'waiting' || item.type === 'fail') {
|
||||
await this.uploadHandle(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
this.dom&&this.dom.evalJS(`vm.upload('${name}')`);
|
||||
// #endif
|
||||
}
|
||||
|
||||
// 选择文件change
|
||||
addFile(file,isCallChange) {
|
||||
|
||||
let name = file.name;
|
||||
this.debug&&console.log('文件名称',name,'大小',file.size);
|
||||
|
||||
if (file) {
|
||||
// 限制文件格式
|
||||
let path = '';
|
||||
let suffix = name.substring(name.lastIndexOf(".")+1).toLowerCase();
|
||||
let formats = this.prohibited.formats.toLowerCase();
|
||||
|
||||
|
||||
// #ifndef MP-WEIXIN
|
||||
path = URL.createObjectURL(file);
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN
|
||||
path = file.path;
|
||||
// #endif
|
||||
if (formats&&!formats.includes(suffix)) {
|
||||
this.toast(`不支持上传${suffix.toUpperCase()}格式文件`);
|
||||
return false;
|
||||
}
|
||||
// 限制文件大小
|
||||
if (file.size > 1024 * 1024 * Math.abs(this.prohibited.size)) {
|
||||
this.toast(`附件大小请勿超过${this.prohibited.size}M`)
|
||||
return false;
|
||||
}
|
||||
this.files.set(file.name,{file,path,name: file.name,size: file.size,progress: 0,type: 'waiting'});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除文件
|
||||
* @param {string}name 不传name默认移除所有文件,传入name移除指定name的文件
|
||||
*/
|
||||
clear(name='') {
|
||||
// #ifdef APP-PLUS
|
||||
this.dom&&this.dom.evalJS(`vm.clear('${name}')`);
|
||||
// #endif
|
||||
|
||||
if (!name) {
|
||||
this.files.clear();
|
||||
}
|
||||
else {
|
||||
this.files.delete(name);
|
||||
}
|
||||
return this.onchange(this.files);
|
||||
}
|
||||
|
||||
/**
|
||||
* 提示框
|
||||
* @param {string}msg 轻提示内容
|
||||
*/
|
||||
toast(msg) {
|
||||
uni.showToast({
|
||||
title: msg,
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信小程序选择文件
|
||||
* @param {number}count 可选择文件数量
|
||||
*/
|
||||
chooseMessageFile(type,count) {
|
||||
wx.chooseMessageFile({
|
||||
count: count,
|
||||
type: type,
|
||||
success: ({ tempFiles }) => {
|
||||
for (let file of tempFiles) {
|
||||
this.addFile(file);
|
||||
}
|
||||
this._uploadAfter();
|
||||
},
|
||||
fail: () => {
|
||||
this.toast(`打开失败`);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
_copyObject(obj) {
|
||||
if (typeof obj !== "undefined") {
|
||||
return JSON.parse(JSON.stringify(obj));
|
||||
} else {
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动根据字符串路径设置对象中的值 支持.和[]
|
||||
* @param {Object} dataObj 数据源
|
||||
* @param {String} name 支持a.b 和 a[b]
|
||||
* @param {String} value 值
|
||||
* setValue(dataObj, name, value);
|
||||
*/
|
||||
_setValue(dataObj, name, value) {
|
||||
// 通过正则表达式 查找路径数据
|
||||
let dataValue;
|
||||
if (typeof value === "object") {
|
||||
dataValue = this._copyObject(value);
|
||||
} else {
|
||||
dataValue = value;
|
||||
}
|
||||
let regExp = new RegExp("([\\w$]+)|\\[(:\\d)\\]", "g");
|
||||
const patten = name.match(regExp);
|
||||
// 遍历路径 逐级查找 最后一级用于直接赋值
|
||||
for (let i = 0; i < patten.length - 1; i++) {
|
||||
let keyName = patten[i];
|
||||
if (typeof dataObj[keyName] !== "object") dataObj[keyName] = {};
|
||||
dataObj = dataObj[keyName];
|
||||
}
|
||||
// 最后一级
|
||||
dataObj[patten[patten.length - 1]] = dataValue;
|
||||
this.debug&&console.log('参数更新后',JSON.stringify(this.option));
|
||||
}
|
||||
|
||||
_uploadAfter() {
|
||||
this.onchange(this.files);
|
||||
setTimeout(()=>{
|
||||
this.instantly&&this.upload();
|
||||
},1000)
|
||||
}
|
||||
|
||||
_overrideUrlLoading() {
|
||||
this.dom.overrideUrlLoading({ mode: 'reject' }, e => {
|
||||
let {retype,item,files,end} = this._getRequest(
|
||||
e.url
|
||||
);
|
||||
let _this = this;
|
||||
switch (retype) {
|
||||
case 'updateOption':
|
||||
this.dom.evalJS(`vm.setData('${JSON.stringify(_this.option)}')`);
|
||||
break
|
||||
case 'change':
|
||||
try {
|
||||
_this.files = new Map([..._this.files,...JSON.parse(unescape(files))]);
|
||||
} catch (e) {
|
||||
return console.error('出错了,请检查代码')
|
||||
}
|
||||
_this.onchange(_this.files);
|
||||
break
|
||||
case 'progress':
|
||||
try {
|
||||
item = JSON.parse(unescape(item));
|
||||
} catch (e) {
|
||||
return console.error('出错了,请检查代码')
|
||||
}
|
||||
_this._changeFilesItem(item,end);
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
_getRequest(url) {
|
||||
let theRequest = new Object()
|
||||
let index = url.indexOf('?')
|
||||
if (index != -1) {
|
||||
let str = url.substring(index + 1)
|
||||
let strs = str.split('&')
|
||||
for (let i = 0; i < strs.length; i++) {
|
||||
theRequest[strs[i].split('=')[0]] = unescape(strs[i].split('=')[1])
|
||||
}
|
||||
}
|
||||
return theRequest
|
||||
}
|
||||
|
||||
_changeFilesItem(item,end=false) {
|
||||
this.debug&&console.log('onprogress',JSON.stringify(item));
|
||||
this.onprogress(item,end);
|
||||
this.files.set(item.name,item);
|
||||
}
|
||||
|
||||
_uploadHandle(item) {
|
||||
item.type = 'loading';
|
||||
delete item.responseText;
|
||||
return new Promise((resolve,reject)=>{
|
||||
this.debug&&console.log('option',JSON.stringify(this.option));
|
||||
let {url,name,method='POST',header,formData} = this.option;
|
||||
let form = new FormData();
|
||||
for (let keys in formData) {
|
||||
form.append(keys, formData[keys])
|
||||
}
|
||||
form.append(name, item.file);
|
||||
let xmlRequest = new XMLHttpRequest();
|
||||
xmlRequest.open(method, url, true);
|
||||
for (let keys in header) {
|
||||
xmlRequest.setRequestHeader(keys, header[keys])
|
||||
}
|
||||
|
||||
xmlRequest.upload.addEventListener(
|
||||
'progress',
|
||||
event => {
|
||||
if (event.lengthComputable) {
|
||||
let progress = Math.ceil((event.loaded * 100) / event.total)
|
||||
if (progress <= 100) {
|
||||
item.progress = progress;
|
||||
this._changeFilesItem(item);
|
||||
}
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
xmlRequest.ontimeout = () => {
|
||||
console.error('请求超时')
|
||||
item.type = 'fail';
|
||||
this._changeFilesItem(item,true);
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
xmlRequest.onreadystatechange = ev => {
|
||||
if (xmlRequest.readyState == 4) {
|
||||
if (xmlRequest.status == 200) {
|
||||
this.debug&&console.log('上传完成:' + xmlRequest.responseText)
|
||||
item['responseText'] = xmlRequest.responseText;
|
||||
item.type = 'success';
|
||||
this._changeFilesItem(item,true);
|
||||
return resolve(true);
|
||||
} else if (xmlRequest.status == 0) {
|
||||
console.error('status = 0 :请检查请求头Content-Type与服务端是否匹配,服务端已正确开启跨域,并且nginx未拦截阻止请求')
|
||||
}
|
||||
console.error('--ERROR--:status = ' + xmlRequest.status)
|
||||
item.type = 'fail';
|
||||
this._changeFilesItem(item,true);
|
||||
return resolve(false);
|
||||
}
|
||||
}
|
||||
xmlRequest.send(form)
|
||||
});
|
||||
}
|
||||
|
||||
_uploadHandleWX(item) {
|
||||
item.type = 'loading';
|
||||
delete item.responseText;
|
||||
return new Promise((resolve,reject)=>{
|
||||
this.debug&&console.log('option',JSON.stringify(this.option));
|
||||
let form = {filePath: item.file.path,...this.option };
|
||||
form['fail'] = ({ errMsg = '' }) => {
|
||||
console.error('--ERROR--:' + errMsg)
|
||||
item.type = 'fail';
|
||||
this._changeFilesItem(item,true);
|
||||
return resolve(false);
|
||||
}
|
||||
form['success'] = res => {
|
||||
if (res.statusCode == 200) {
|
||||
this.debug&&console.log('上传完成,微信端返回不一定是字符串,根据接口返回格式判断是否需要JSON.parse:' + res.data)
|
||||
item['responseText'] = res.data;
|
||||
item.type = 'success';
|
||||
this._changeFilesItem(item,true);
|
||||
return resolve(true);
|
||||
}
|
||||
item.type = 'fail';
|
||||
this._changeFilesItem(item,true);
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
let xmlRequest = uni.uploadFile(form);
|
||||
xmlRequest.onProgressUpdate(({ progress = 0 }) => {
|
||||
if (progress <= 100) {
|
||||
item.progress = progress;
|
||||
this._changeFilesItem(item);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,321 @@
|
|||
<template>
|
||||
<view class="lsj-file" :style="[getStyles]">
|
||||
<view ref="lsj" class="hFile" :style="[getStyles]" @click="onClick">
|
||||
<slot><view class="defview" :style="[getStyles]">附件上传</view></slot>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 查看文档:https://ext.dcloud.net.cn/plugin?id=5459
|
||||
import {LsjFile} from './LsjFile.js'
|
||||
export default {
|
||||
name: 'Lsj-upload',
|
||||
props: {
|
||||
// 打印日志
|
||||
debug: {type: Boolean,default: false},
|
||||
// 自动上传
|
||||
instantly: {type: Boolean,default: false},
|
||||
// 上传接口参数设置
|
||||
option: {type: Object,default: ()=>{}},
|
||||
// 文件大小上限
|
||||
size: { type: Number, default: 10 },
|
||||
// 文件选择个数上限,超出后不触发点击
|
||||
count: { type: Number, default: 9 },
|
||||
// 是否允许多选文件
|
||||
multiple: {type:Boolean, default: true},
|
||||
// 允许上传的文件格式(多个以逗号隔开)
|
||||
formats: { type: String, default:''},
|
||||
// input file选择限制
|
||||
accept: {type: String,default: ''},
|
||||
// 微信选择文件类型
|
||||
//all=从所有文件选择,
|
||||
//video=只能选择视频文件,
|
||||
//image=只能选择图片文件,
|
||||
//file=可以选择除了图片和视频之外的其它的文件
|
||||
wxFileType: { type: String, default: 'all' },
|
||||
// webviewID需唯一,不同窗口也不要同Id
|
||||
childId: { type: String, default: 'lsjUpload' },
|
||||
// 文件选择触发面宽度
|
||||
width: { type: String, default: '100%' },
|
||||
// 文件选择触发面高度
|
||||
height: { type: String, default: '80rpx' },
|
||||
|
||||
// top,left,bottom,right仅position=absolute时才需要传入
|
||||
top: { type: [String, Number], default: '' },
|
||||
left: { type: [String, Number], default: '' },
|
||||
bottom: { type: [String, Number], default: '' },
|
||||
right: { type: [String, Number], default: '' },
|
||||
// nvue不支持跟随窗口滚动
|
||||
position: {
|
||||
type: String,
|
||||
// #ifdef APP-NVUE
|
||||
default: 'absolute',
|
||||
// #endif
|
||||
// #ifndef APP-NVUE
|
||||
default: 'static',
|
||||
// #endif
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
option(v) {
|
||||
// #ifdef APP-PLUS
|
||||
this.lsjFile&&this.show();
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
updated() {
|
||||
// #ifdef APP-PLUS
|
||||
if (this.isShow) {
|
||||
this.lsjFile&&this.show();
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
computed: {
|
||||
getStyles() {
|
||||
let styles = {
|
||||
width: this.width,
|
||||
height: this.height
|
||||
}
|
||||
if (this.position == 'absolute') {
|
||||
styles['top'] = this.top
|
||||
styles['bottom'] = this.bottom
|
||||
styles['left'] = this.left
|
||||
styles['right'] = this.right
|
||||
styles['position'] = 'fixed'
|
||||
}
|
||||
|
||||
return styles
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this._size = 0;
|
||||
let WEBID = this.childId + new Date().getTime();
|
||||
this.lsjFile = new LsjFile({
|
||||
id: WEBID,
|
||||
debug: this.debug,
|
||||
width: this.width,
|
||||
height: this.height,
|
||||
option: this.option,
|
||||
instantly: this.instantly,
|
||||
// 限制条件
|
||||
prohibited: {
|
||||
// 大小
|
||||
size: this.size,
|
||||
// 允许上传的格式
|
||||
formats: this.formats,
|
||||
// 限制选择的格式
|
||||
accept: this.accept,
|
||||
count: this.count,
|
||||
// 是否多选
|
||||
multiple: this.multiple,
|
||||
},
|
||||
onchange: this.onchange,
|
||||
onprogress: this.onprogress,
|
||||
});
|
||||
this.create();
|
||||
// 需判断是否当前页显示
|
||||
uni.$on('lsjShow',this.show);
|
||||
},
|
||||
beforeDestroy() {
|
||||
uni.$off('lsjShow',this.show);
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
this.lsjFile.dom.close();
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
setFiles(array) {
|
||||
if (array instanceof Map) {
|
||||
for (let [key, item] of array) {
|
||||
item['progress'] = 100;
|
||||
item['type'] = 'success';
|
||||
this.lsjFile.files.set(key,item);
|
||||
}
|
||||
}
|
||||
else if (Array.isArray(array)) {
|
||||
array.forEach(item=>{
|
||||
if (item.name) {
|
||||
item['progress'] = 100;
|
||||
item['type'] = 'success';
|
||||
this.lsjFile.files.set(item.name,item);
|
||||
}
|
||||
});
|
||||
}
|
||||
this.onchange(this.lsjFile.files);
|
||||
},
|
||||
setData() {
|
||||
this.lsjFile&&this.lsjFile.setData(...arguments);
|
||||
},
|
||||
getDomStyles(callback) {
|
||||
// #ifndef APP-NVUE
|
||||
let view = uni
|
||||
.createSelectorQuery()
|
||||
.in(this)
|
||||
.select('.lsj-file')
|
||||
view.fields(
|
||||
{
|
||||
size: true,
|
||||
rect: true
|
||||
},
|
||||
({ height, width, top, left, right, bottom }) => {
|
||||
uni.createSelectorQuery()
|
||||
.selectViewport()
|
||||
.scrollOffset(({ scrollTop }) => {
|
||||
return callback({
|
||||
top: parseInt(top) + parseInt(scrollTop) + 'px',
|
||||
left: parseInt(left) + 'px',
|
||||
width: parseInt(width) + 'px',
|
||||
height: parseInt(height) + 'px'
|
||||
})
|
||||
})
|
||||
.exec()
|
||||
}
|
||||
).exec()
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
const dom = weex.requireModule('dom')
|
||||
dom.getComponentRect(this.$refs.lsj, ({ size: { height, width, top, left, right, bottom } }) => {
|
||||
return callback({
|
||||
top: parseInt(top) + 'px',
|
||||
left: parseInt(left) + 'px',
|
||||
width: parseInt(width) + 'px',
|
||||
height: parseInt(height) + 'px',
|
||||
right: parseInt(right) + 'px',
|
||||
bottom: parseInt(bottom) + 'px'
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
show() {
|
||||
if (this._size && (this._size >= this.count)) {
|
||||
return;
|
||||
}
|
||||
this.isShow = true;
|
||||
// #ifdef APP-PLUS
|
||||
this.lsjFile&&this.getDomStyles(styles => {
|
||||
this.lsjFile.dom.setStyle(styles)
|
||||
});
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
this.lsjFile.dom.style.display = 'inline'
|
||||
// #endif
|
||||
},
|
||||
hide() {
|
||||
this.isShow = false;
|
||||
// #ifdef APP-PLUS
|
||||
this.lsjFile&&this.lsjFile.dom.setStyle({
|
||||
top: '-100px',
|
||||
left:'0px',
|
||||
width: '1px',
|
||||
height: '100px',
|
||||
});
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
this.lsjFile.dom.style.display = 'none'
|
||||
// #endif
|
||||
},
|
||||
/**
|
||||
* 手动提交上传
|
||||
* @param {string}name 文件名称,不传则上传所有type等于waiting和fail的文件
|
||||
*/
|
||||
upload(name) {
|
||||
this.lsjFile&&this.lsjFile.upload(name);
|
||||
},
|
||||
/**
|
||||
* @returns {Map} 已选择的文件Map集
|
||||
*/
|
||||
onchange(files) {
|
||||
this.$emit('change',files);
|
||||
this._size = files.size;
|
||||
return files.size >= this.count ? this.hide() : this.show();
|
||||
},
|
||||
/**
|
||||
* @returns {object} 当前上传中的对象
|
||||
*/
|
||||
onprogress(item,end=false) {
|
||||
this.$emit('progress',item);
|
||||
if (end) {
|
||||
setTimeout(()=>{
|
||||
this.$emit('uploadEnd',item);
|
||||
},0);
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 移除组件内缓存的某条数据
|
||||
* @param {string}name 文件名称,不指定默认清除所有文件
|
||||
*/
|
||||
clear(name) {
|
||||
this.lsjFile.clear(name);
|
||||
},
|
||||
// 创建选择器
|
||||
create() {
|
||||
// 若iOS端服务端处理不了跨域就将hybrid目录内的html放到服务端去,并将此处path改成服务器上的地址
|
||||
let path = '/uni_modules/lsj-upload/hybrid/html/uploadFile.html';
|
||||
let dom = this.lsjFile.create(path);
|
||||
// #ifdef H5
|
||||
this.$refs.lsj.$el.appendChild(dom);
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
this.show();
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
dom.setStyle({position: this.position});
|
||||
dom.loadURL(path);
|
||||
setTimeout(()=>{
|
||||
// #ifdef APP-NVUE
|
||||
plus.webview.currentWebview().append(dom);
|
||||
// #endif
|
||||
// #ifndef APP-NVUE
|
||||
this.$root.$scope.$getAppWebview().append(dom);
|
||||
// #endif
|
||||
this.show();
|
||||
},300)
|
||||
// #endif
|
||||
},
|
||||
// 点击选择附件
|
||||
onClick() {
|
||||
if (this._size >= this.count) {
|
||||
this.toast(`只允许上传${this.count}个文件`);
|
||||
return;
|
||||
}
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
if (!this.isShow) {return;}
|
||||
let count = this.count - this._size;
|
||||
this.lsjFile.chooseMessageFile(this.wxFileType,count);
|
||||
// #endif
|
||||
},
|
||||
toast(msg) {
|
||||
uni.showToast({
|
||||
title: msg,
|
||||
icon: 'none'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.lsj-file {
|
||||
display: inline-block;
|
||||
}
|
||||
.defview {
|
||||
background-color: #007aff;
|
||||
color: #fff;
|
||||
border-radius: 10rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
.hFile {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,198 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="zh-cn">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title class="title">[文件管理器]</title>
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
|
||||
<style type="text/css">
|
||||
.content {background: transparent;}
|
||||
.btn {position: relative;top: 0;left: 0;bottom: 0;right: 0;}
|
||||
.btn .file {position: fixed;z-index: 93;left: 0;right: 0;top: 0;bottom: 0;width: 100%;opacity: 0;}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="content" class="content">
|
||||
<div class="btn">
|
||||
<input :multiple="multiple" @change="onChange" :accept="accept" ref="file" class="file" type="file" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" src="js/vue.min.js"></script>
|
||||
<script type="text/javascript">
|
||||
let _this;
|
||||
var vm = new Vue({
|
||||
el: '#content',
|
||||
data: {
|
||||
accept: '',
|
||||
multiple: true,
|
||||
},
|
||||
mounted() {
|
||||
console.log('加载webview');
|
||||
_this = this;
|
||||
this.files = new Map();
|
||||
document.addEventListener('plusready', (e)=>{
|
||||
let {debug,instantly,prohibited} = plus.webview.currentWebview();
|
||||
this.debug = debug;
|
||||
this.instantly = instantly;
|
||||
this.prohibited = prohibited;
|
||||
this.accept = prohibited.accept;
|
||||
if (prohibited.multiple === 'false') {
|
||||
prohibited.multiple = false;
|
||||
}
|
||||
this.multiple = prohibited.multiple;
|
||||
location.href = 'callback?retype=updateOption';
|
||||
}, false);
|
||||
},
|
||||
methods: {
|
||||
toast(msg) {
|
||||
plus.nativeUI.toast(msg);
|
||||
},
|
||||
clear(name) {
|
||||
if (!name) {
|
||||
this.files.clear();
|
||||
return;
|
||||
}
|
||||
this.files.delete(name);
|
||||
},
|
||||
setData(option='{}') {
|
||||
this.debug&&console.log('更新参数:'+option);
|
||||
try{
|
||||
_this.option = JSON.parse(option);
|
||||
}catch(e){
|
||||
console.error('参数设置错误')
|
||||
}
|
||||
},
|
||||
async upload(name=''){
|
||||
if (name && this.files.has(name)) {
|
||||
await this.createUpload(this.files.get(name));
|
||||
}
|
||||
else {
|
||||
for (let item of this.files.values()) {
|
||||
if (item.type === 'waiting' || item.type === 'fail') {
|
||||
await this.createUpload(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onChange(e) {
|
||||
let fileDom = this.$refs.file;
|
||||
for (let file of fileDom.files) {
|
||||
if (this.files.size >= this.prohibited.count) {
|
||||
this.toast(`只允许上传${this.prohibited.count}个文件`);
|
||||
fileDom.value = '';
|
||||
break;
|
||||
}
|
||||
this.addFile(file);
|
||||
}
|
||||
this.uploadAfter();
|
||||
fileDom.value = '';
|
||||
},
|
||||
addFile(file) {
|
||||
if (file) {
|
||||
let name = file.name;
|
||||
this.debug&&console.log('文件名称',name,'大小',file.size);
|
||||
// 限制文件格式
|
||||
let suffix = name.substring(name.lastIndexOf(".")+1).toLowerCase();
|
||||
let formats = this.prohibited.formats.toLowerCase();
|
||||
if (formats&&!formats.includes(suffix)) {
|
||||
this.toast(`不支持上传${suffix.toUpperCase()}格式文件`);
|
||||
return;
|
||||
}
|
||||
// 限制文件大小
|
||||
if (file.size > 1024 * 1024 * Math.abs(this.prohibited.size)) {
|
||||
this.toast(`附件大小请勿超过${this.prohibited.size}M`)
|
||||
return;
|
||||
}
|
||||
// let itemBlob = new Blob([file]);
|
||||
let path = URL.createObjectURL(file);
|
||||
this.files.set(file.name,{file,path,name: file.name,size: file.size,progress: 0,type: 'waiting'});
|
||||
}
|
||||
},
|
||||
/**
|
||||
* @returns {Map} 已选择的文件Map集
|
||||
*/
|
||||
callChange() {
|
||||
location.href = 'callback?retype=change&files=' + escape(JSON.stringify([...this.files]));
|
||||
},
|
||||
/**
|
||||
* @returns {object} 正在处理的当前对象
|
||||
*/
|
||||
changeFilesItem(item,end='') {
|
||||
this.files.set(item.name,item);
|
||||
location.href = 'callback?retype=progress&end='+ end +'&item=' + escape(JSON.stringify(item));
|
||||
},
|
||||
uploadAfter() {
|
||||
this.callChange();
|
||||
setTimeout(()=>{
|
||||
this.instantly&&this.upload();
|
||||
},1000)
|
||||
},
|
||||
createUpload(item) {
|
||||
this.debug&&console.log('准备上传,option=:'+JSON.stringify(this.option));
|
||||
item.type = 'loading';
|
||||
delete item.responseText;
|
||||
return new Promise((resolve,reject)=>{
|
||||
let {url,name,method='POST',header={},formData={}} = this.option;
|
||||
let form = new FormData();
|
||||
for (let keys in formData) {
|
||||
form.append(keys, formData[keys])
|
||||
}
|
||||
form.append(name, item.file);
|
||||
let xmlRequest = new XMLHttpRequest();
|
||||
xmlRequest.open(method, url, true);
|
||||
for (let keys in header) {
|
||||
xmlRequest.setRequestHeader(keys, header[keys])
|
||||
}
|
||||
xmlRequest.upload.addEventListener(
|
||||
'progress',
|
||||
event => {
|
||||
if (event.lengthComputable) {
|
||||
let progress = Math.ceil((event.loaded * 100) / event.total)
|
||||
if (progress <= 100) {
|
||||
item.progress = progress;
|
||||
this.changeFilesItem(item);
|
||||
}
|
||||
}
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
xmlRequest.ontimeout = () => {
|
||||
console.error('请求超时')
|
||||
item.type = 'fail';
|
||||
this.changeFilesItem(item,true);
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
xmlRequest.onreadystatechange = ev => {
|
||||
if (xmlRequest.readyState == 4) {
|
||||
this.debug && console.log('接口是否支持跨域',xmlRequest.withCredentials);
|
||||
if (xmlRequest.status == 200) {
|
||||
this.debug && console.log('上传完成:' + xmlRequest.responseText)
|
||||
item['responseText'] = xmlRequest.responseText;
|
||||
item.type = 'success';
|
||||
this.changeFilesItem(item,true);
|
||||
return resolve(true);
|
||||
} else if (xmlRequest.status == 0) {
|
||||
console.error('status = 0 :请检查请求头Content-Type与服务端是否匹配,服务端已正确开启跨域,并且nginx未拦截阻止请求')
|
||||
}
|
||||
console.error('--ERROR--:status = ' + xmlRequest.status)
|
||||
item.type = 'fail';
|
||||
this.changeFilesItem(item,true);
|
||||
return resolve(false);
|
||||
}
|
||||
}
|
||||
xmlRequest.send(form)
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,79 @@
|
|||
{
|
||||
"id": "lsj-upload",
|
||||
"displayName": "全文件上传选择非原生2.0版",
|
||||
"version": "2.2.9",
|
||||
"description": "文件选择上传-支持APP-H5网页-微信小程序",
|
||||
"keywords": [
|
||||
"附件",
|
||||
"file",
|
||||
"upload",
|
||||
"上传",
|
||||
"文件管理器"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.4.9"
|
||||
},
|
||||
"dcloudext": {
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "",
|
||||
"type": "component-vue"
|
||||
},
|
||||
"uni_modules": {
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "u",
|
||||
"百度": "u",
|
||||
"字节跳动": "u",
|
||||
"QQ": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "y",
|
||||
"联盟": "y"
|
||||
},
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,353 @@
|
|||
# lsj-upload
|
||||
|
||||
### 插件地址:https://ext.dcloud.net.cn/plugin?id=5459
|
||||
|
||||
### 不清楚使用方式可点击右侧导入示例项目运行完整示例
|
||||
### 此次更新2.0与1.0使用方式略有差异,已使用1.0的同学自行斟酌是否更新到2.0版本!!!
|
||||
|
||||
使用插件有任何问题欢迎加入QQ讨论群:
|
||||
- 群1:701468256(已满)
|
||||
- 群2:469580165(已满)
|
||||
- 群3:667530868
|
||||
|
||||
若能帮到你请高抬贵手点亮5颗星~
|
||||
------
|
||||
## 重要提示
|
||||
### 组件是窗口级滚动,不要在scroll-view内使用!!
|
||||
### 组件是窗口级滚动,不要在scroll-view内使用!!
|
||||
### 组件是窗口级滚动,不要在scroll-view内使用!!
|
||||
|
||||
### 控件的height高度应与slot自定义内容高度保持一致
|
||||
### nvue窗口只能使用固定模式position=absolute
|
||||
### show() 当DOM重排后在this.$nextTick内调用show(),控件定位会更加准确
|
||||
### hide() APP端webview层级比view高,如不希望触发点击时,应调用hide隐藏控件,反之调用show
|
||||
### 若iOS端跨域服务端同学实在配置不好,可把hybrid下html目录放到服务器去,同源则不存在跨域问题。
|
||||
### 小程序端因hybrid不能使用本地HTML,所以插件提供的是从微信消息列表拉取文件并选择,请知悉。
|
||||
### file对象不是object对象,也不能转json字符串,如果你打印file那就是{},可以打印file.name和file.size。
|
||||
### 返回的path是个blob类型,仅供用于文件回显,插件已内置好上传函数,调用上传会自动提交待上传文件,若非要自己拿path去搞上传那你自己处理。
|
||||
------
|
||||
|
||||
## 使用说明
|
||||
| 属性 | 是否必填 | 值类型 | 默认值 | 说明 |
|
||||
| --------- | -------- | -----: | --: | :------------:|
|
||||
| width | 否 | String |100% | 容器宽度 |
|
||||
| height | 是 | String |80rpx | 容器高度 |
|
||||
| debug | 否 | Boolean |false | 打印调试日志 |
|
||||
| option | 是 | Object |- | [文件上传接口相关参数](#p1)|
|
||||
| instantly | 否 | Boolean |false | true=自动上传 |
|
||||
| count | 否 | Number |10 | 附件选择上限(个)|
|
||||
| size | 否 | Number |10 | 附件大小上限(M)|
|
||||
| wxFileType | 否 | String |all | 微信小程序文件选择器格式限制(all=从所有文件选择,video=只能选择视频文件,image=只能选择图片文件,file=可以选择除了图片和视频之外的其它的文件)|
|
||||
| accept | 否 | String |- | 文件选择器input file格式限制(部分机型不兼容,建议使用formats)|
|
||||
| formats | 否 | String |- | 限制允许上传的格式,空串=不限制,默认为空,多个格式以逗号隔开,例如png,jpg,pdf|
|
||||
| childId | 否 | String |lsjUpload| 控件的id(仅APP有效,应用内每个控件命名一个唯一Id,不同窗口也不要同名Id)|
|
||||
| position | 否 | String |static | 控件的定位模式(static=控件随页面滚动;absolute=控件在页面中绝对定位,不随窗口内容滚动)|
|
||||
| top,left,right,bottom | 否 | [Number,String] |0 | 设置控件绝对位置,position=absolute时有效|
|
||||
| @change | 否 | Function |Map | 选择文件触发,返回所有已选择文件Map集合|
|
||||
| @progress | 否 | Function |Object | 上传过程中发生状态变化的文件对象,需通过set更新至Map集合|
|
||||
| @uploadEnd| 否 | Function |Object | 上传结束回调,返回参数与progress一致|
|
||||
|
||||
## <a id="p1">option说明</a>
|
||||
|参数 | 是否必填 | 说明|
|
||||
|---- | ---- | :--: |
|
||||
|url | 是 | 上传接口地址|
|
||||
|name| 否 |上传接口文件key,默认为file|
|
||||
|header| 否 |上传接口请求头|
|
||||
|formData| 否 |上传接口额外参数|
|
||||
|
||||
## ref调用
|
||||
|作用 | 方法名| 传入参数| 说明|
|
||||
|---- | --------- | -------- | :--: |
|
||||
|显示控件| show|-| 控件显示状态下可触发点击|
|
||||
|隐藏控件| hide|-| 控件隐藏状态下不触发点击|
|
||||
|动态设置文件列表| setFiles|[Array,Map] files| 传入格式请与组件选择返回格式保持一致,且name为必须属性,可查看下方演示|
|
||||
|动态更新参数| setData|[String] name,[any] value| name支持a.b 和 a[b],可查看下方演示|
|
||||
|移除选择的文件| clear|[String] name| 不传参数清空所有文件,传入文件name时删除该name的文件|
|
||||
|手动上传| upload|[String] name| 不传参数默认依次上传所有type=waiting的文件,传入文件name时不关心type是否为waiting,单独上传指定name的文件|
|
||||
|
||||
## progress返回对象字段说明
|
||||
|字段 | 说明|
|
||||
|---- | :--: |
|
||||
|file | 文件对象|
|
||||
|name |文件名称|
|
||||
|size |文件大小|
|
||||
|type |文件上传状态:waiting(等待上传)、loading(上传中)、success(成功) 、fail(失败)|
|
||||
|responseText|上传成功后服务端返回数据(仅type为success时存在)|
|
||||
|
||||
## 以下演示为vue窗口使用方式,nvue使用区别是必须传入控件绝对位置如top,bottom,left,right,且position只能为absolute,如不清楚可点击右侧导入示例项目有详细演示代码。
|
||||
|
||||
### vue:
|
||||
``` javascript
|
||||
<lsj-upload
|
||||
ref="lsjUpload"
|
||||
childId="upload1"
|
||||
:width="width"
|
||||
:height="height"
|
||||
:option="option"
|
||||
:size="size"
|
||||
:formats="formats"
|
||||
:debug="debug"
|
||||
:instantly="instantly"
|
||||
@uploadEnd="onuploadEnd"
|
||||
@progress="onprogre"
|
||||
@change="onChange">
|
||||
<view class="btn" :style="{width: width,height: height}">选择附件</view>
|
||||
</lsj-upload>
|
||||
|
||||
|
||||
<view class="padding">
|
||||
|
||||
<view>已选择文件列表:</view>
|
||||
|
||||
<!-- #ifndef MP-WEIXIN -->
|
||||
<view v-for="(item,index) in files.values()" :key="index">
|
||||
<image style="width: 100rpx;height: 100rpx;" :src="item.path" mode="widthFix"></image>
|
||||
<text>提示:【path主要用于图片视频类文件回显,他用自行处理】:{{item.path}}</text>
|
||||
<text>{{item.name}}</text>
|
||||
<text style="margin-left: 10rpx;">大小:{{item.size}}</text>
|
||||
<text style="margin-left: 10rpx;">状态:{{item.type}}</text>
|
||||
<text style="margin-left: 10rpx;">进度:{{item.progress}}</text>
|
||||
<text style="margin-left: 10rpx;" v-if="item.responseText">服务端返回演示:{{item.responseText}}</text>
|
||||
<text @click="resetUpload(item.name)" v-if="item.type=='fail'" style="margin-left: 10rpx;padding: 0 10rpx;border: 1rpx solid #007AFF;">重新上传</text>
|
||||
<text @click="clear(item.name)" style="margin-left: 10rpx;padding: 0 10rpx;border: 1rpx solid #007AFF;">删除</text>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- #ifdef MP-WEIXIN -->
|
||||
<view v-for="(item,index) in wxFiles" :key="index">
|
||||
<text>{{item.name}}</text>
|
||||
<text style="margin-left: 10rpx;">大小:{{item.size}}</text>
|
||||
<text style="margin-left: 10rpx;">状态:{{item.type}}</text>
|
||||
<text style="margin-left: 10rpx;">进度:{{item.progress}}</text>
|
||||
<view>
|
||||
<button @click="resetUpload(item.name)">重新上传</button>
|
||||
<button @click="clear(item.name)">删除</button>
|
||||
</view>
|
||||
</view>
|
||||
<!-- #endif -->
|
||||
|
||||
</view>
|
||||
|
||||
|
||||
```
|
||||
|
||||
---
|
||||
* 函数说明
|
||||
|
||||
|
||||
``` javascript
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 上传接口参数
|
||||
option: {
|
||||
// 上传服务器地址,需要替换为你的接口地址
|
||||
url: 'http://hl.j56.com/dropbox/document/upload', // 该地址非真实路径,需替换为你项目自己的接口地址
|
||||
// 上传附件的key
|
||||
name: 'file',
|
||||
// 根据你接口需求自定义请求头,默认不要写content-type,让浏览器自适配
|
||||
header: {
|
||||
// 示例参数可删除
|
||||
'Authorization': 'bearer eyJhbGciOiJSUzI1NiIsI',
|
||||
'uid': '99',
|
||||
'client': 'app',
|
||||
'accountid': 'DP',
|
||||
},
|
||||
// 根据你接口需求自定义body参数
|
||||
formData: {
|
||||
// 'orderId': 1000
|
||||
}
|
||||
},
|
||||
// 选择文件后是否立即自动上传,true=选择后立即上传
|
||||
instantly: true,
|
||||
// 必传宽高且宽高应与slot宽高保持一致
|
||||
width: '180rpx',
|
||||
height: '180rpx',
|
||||
// 限制允许上传的格式,空串=不限制,默认为空
|
||||
formats: '',
|
||||
// 文件上传大小限制
|
||||
size: 30,
|
||||
// 文件数量限制
|
||||
count: 2,
|
||||
// 文件回显列表
|
||||
files: new Map(),
|
||||
// 微信小程序Map对象for循环不显示,所以转成普通数组,不要问为什么,我也不知道
|
||||
wxFiles: [],
|
||||
// 是否打印日志
|
||||
debug: true,
|
||||
|
||||
|
||||
// 演示用
|
||||
tabIndex: 0,
|
||||
list:[],
|
||||
}
|
||||
},
|
||||
onReady() {
|
||||
setTimeout(()=>{
|
||||
console.log('----演示动态更新参数-----');
|
||||
this.$refs['lsjUpload'+this.tabIndex].setData('formData.orderId','动态设置的参数');
|
||||
|
||||
console.log('以下注释内容为-动态更新参数更多演示,放开后可查看演示效果');
|
||||
// 修改option对象的name属性
|
||||
// this.$refs.lsjUpload.setData('name','myFile');
|
||||
|
||||
// 修改option对象的formData内的属性
|
||||
// this.$refs.lsjUpload.setData('formData.appid','1111');
|
||||
|
||||
// 替换option对象的formData
|
||||
// this.$refs.lsjUpload.setData('formData',{appid:'222'});
|
||||
|
||||
// option对象的formData新增属性
|
||||
// this.$refs.lsjUpload.setData('formData.newkey','新插入到formData的属性');
|
||||
|
||||
|
||||
// ---------演示初始化值,用于已提交后再次编辑时需带入已上传文件-------
|
||||
// 方式1=传入数组
|
||||
// let files1 = [{name: '1.png'},{name: '2.png',}];
|
||||
|
||||
// 方式2=传入Map对象
|
||||
// let files2 = new Map();
|
||||
// files2.set('1.png',{name: '1.png'})
|
||||
|
||||
// 此处调用setFiles设置初始files
|
||||
// this.$refs.lsjUpload.setFiles(files1);
|
||||
|
||||
// 初始化tab
|
||||
this.onTab(0);
|
||||
},2000)
|
||||
},
|
||||
methods: {
|
||||
// 某文件上传结束回调(成功失败都回调)
|
||||
onuploadEnd(item) {
|
||||
console.log(`${item.name}已上传结束,上传状态=${item.type}`);
|
||||
|
||||
// 更新当前窗口状态变化的文件
|
||||
this.files.set(item.name,item);
|
||||
|
||||
// ---可删除--演示上传完成后取服务端数据
|
||||
if (item['responseText']) {
|
||||
console.log('演示服务器返回的字符串JSON转Object对象');
|
||||
this.files.get(item.name).responseText = JSON.parse(item.responseText);
|
||||
}
|
||||
|
||||
// 微信小程序Map对象for循环不显示,所以转成普通数组,
|
||||
// 如果你用不惯Map对象,也可以像这样转普通数组,组件使用Map主要是避免反复文件去重操作
|
||||
// #ifdef MP-WEIXIN
|
||||
this.wxFiles = [...this.files.values()];
|
||||
// #endif
|
||||
|
||||
// 强制更新视图
|
||||
this.$forceUpdate();
|
||||
|
||||
|
||||
// ---可删除--演示判断是否所有文件均已上传成功
|
||||
let isAll = [...this.files.values()].find(item=>item.type!=='success');
|
||||
if (!isAll) {
|
||||
console.log('已全部上传完毕');
|
||||
}
|
||||
else {
|
||||
console.log(isAll.name+'待上传');
|
||||
}
|
||||
|
||||
},
|
||||
// 上传进度回调,如果网页上md文档没有渲染出事件名称onprogre,请复制代码的小伙伴自行添加上哈,没有哪个事件是只(item)的
|
||||
onprogre(item) {
|
||||
// 更新当前状态变化的文件
|
||||
this.files.set(item.name,item);
|
||||
|
||||
console.log('打印对象',JSON.stringify(this.files.get(item.name)));
|
||||
// 微信小程序Map对象for循环不显示,所以转成普通数组,不要问为什么,我也不知道
|
||||
// #ifdef MP-WEIXIN
|
||||
this.wxFiles = [...this.files.values()];
|
||||
// #endif
|
||||
|
||||
// 强制更新视图
|
||||
this.$forceUpdate();
|
||||
|
||||
},
|
||||
// 文件选择回调
|
||||
onChange(files) {
|
||||
console.log('当前选择的文件列表:',JSON.stringify([...files.values()]));
|
||||
// 更新选择的文件
|
||||
this.files = files;
|
||||
// 强制更新视图
|
||||
this.$forceUpdate();
|
||||
|
||||
// 微信小程序Map对象for循环不显示,所以转成普通数组,不要问为什么,我也不知道
|
||||
// #ifdef MP-WEIXIN
|
||||
this.wxFiles = [...this.files.values()];
|
||||
// #endif
|
||||
|
||||
// ---可删除--演示重新定位覆盖层控件
|
||||
this.$nextTick(()=>{
|
||||
console.log('演示重新定位');
|
||||
this.$refs.lsjUpload0.show();
|
||||
this.$refs.lsjUpload1.show();
|
||||
this.$refs.lsjUpload2.show();
|
||||
});
|
||||
|
||||
},
|
||||
// 手动上传
|
||||
upload() {
|
||||
// name=指定文件名,不指定则上传所有type等于waiting和fail的文件
|
||||
this.$refs['lsjUpload'+this.tabIndex].upload();
|
||||
},
|
||||
// 指定上传某个文件
|
||||
resetUpload(name) {
|
||||
this.$refs['lsjUpload'+this.tabIndex].upload(name);
|
||||
},
|
||||
// 移除某个文件
|
||||
clear(name) {
|
||||
// name=指定文件名,不传name默认移除所有文件
|
||||
this.$refs['lsjUpload'+this.tabIndex].clear(name);
|
||||
},
|
||||
/**
|
||||
* ---可删除--演示在组件上方添加新内容DOM变化
|
||||
* DOM重排演示,重排后组件内部updated默认会触发show方法,若特殊情况未能触发updated也可以手动调用一次show()
|
||||
* 什么是DOM重排?自行百度去
|
||||
*/
|
||||
add() {
|
||||
this.list.push('DOM重排测试');
|
||||
},
|
||||
/**
|
||||
* ---可删除--演示Tab切换时覆盖层是否能被点击
|
||||
* APP端因为是webview,层级比view高,此时若不希望点击触发选择文件,需要手动调用hide()
|
||||
* 手动调用hide后,需要调用show()才能恢复覆盖层的点击
|
||||
*/
|
||||
onTab(tabIndex) {
|
||||
this.$refs.lsjUpload0.hide();
|
||||
this.$refs.lsjUpload1.hide();
|
||||
|
||||
this.tabIndex = tabIndex;
|
||||
|
||||
this.$nextTick(()=>{
|
||||
this.$refs['lsjUpload'+this.tabIndex].show();
|
||||
})
|
||||
|
||||
},
|
||||
/**
|
||||
* 打开nvue窗口查看非跟随窗口滚动效果
|
||||
*/
|
||||
open() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/nvue-demo/nvue-demo'
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## 温馨提示
|
||||
|
||||
* 文件上传
|
||||
0. 如说明表达还不够清楚,不清楚怎么使用可导入完整示例项目运行体验和查看
|
||||
1. APP端请优先联调Android,上传成功后再运行iOS端,如iOS返回status=0则需要后端开启允许跨域;
|
||||
2. header的Content-Type类型需要与服务端要求一致,否则收不到附件(服务端若没有明文规定则可不写,使用默认匹配)
|
||||
3. 服务端不清楚怎么配置跨域可加群咨询,具体百度~
|
||||
4. 欢迎加入QQ讨论群:701468256(已满)
|
||||
5. 欢迎加入QQ讨论群:469580165(已满)
|
||||
6. 欢迎加入QQ讨论群:667530868
|
||||
7. 若能帮到你还请点亮5颗小星星以作鼓励哈~
|
||||
8. 若能帮到你还请点亮5颗小星星以作鼓励哈~
|
||||
9. 若能帮到你还请点亮5颗小星星以作鼓励哈~
|
|
@ -0,0 +1,13 @@
|
|||
## 1.2.0(2021-07-30)
|
||||
- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
|
||||
## 1.1.1(2021-05-12)
|
||||
- 新增 示例地址
|
||||
- 修复 示例项目缺少组件的Bug
|
||||
## 1.1.0(2021-04-22)
|
||||
- 新增 通过方法自定义动画
|
||||
- 新增 custom-class 非 NVUE 平台支持自定义 class 定制样式
|
||||
- 优化 动画触发逻辑,使动画更流畅
|
||||
- 优化 支持单独的动画类型
|
||||
- 优化 文档示例
|
||||
## 1.0.2(2021-02-05)
|
||||
- 调整为uni_modules目录规范
|
|
@ -0,0 +1,128 @@
|
|||
// const defaultOption = {
|
||||
// duration: 300,
|
||||
// timingFunction: 'linear',
|
||||
// delay: 0,
|
||||
// transformOrigin: '50% 50% 0'
|
||||
// }
|
||||
// #ifdef APP-NVUE
|
||||
const nvueAnimation = uni.requireNativePlugin('animation')
|
||||
// #endif
|
||||
class MPAnimation {
|
||||
constructor(options, _this) {
|
||||
this.options = options
|
||||
this.animation = uni.createAnimation(options)
|
||||
this.currentStepAnimates = {}
|
||||
this.next = 0
|
||||
this.$ = _this
|
||||
|
||||
}
|
||||
|
||||
_nvuePushAnimates(type, args) {
|
||||
let aniObj = this.currentStepAnimates[this.next]
|
||||
let styles = {}
|
||||
if (!aniObj) {
|
||||
styles = {
|
||||
styles: {},
|
||||
config: {}
|
||||
}
|
||||
} else {
|
||||
styles = aniObj
|
||||
}
|
||||
if (animateTypes1.includes(type)) {
|
||||
if (!styles.styles.transform) {
|
||||
styles.styles.transform = ''
|
||||
}
|
||||
let unit = ''
|
||||
if(type === 'rotate'){
|
||||
unit = 'deg'
|
||||
}
|
||||
styles.styles.transform += `${type}(${args+unit}) `
|
||||
} else {
|
||||
styles.styles[type] = `${args}`
|
||||
}
|
||||
this.currentStepAnimates[this.next] = styles
|
||||
}
|
||||
_animateRun(styles = {}, config = {}) {
|
||||
let ref = this.$.$refs['ani'].ref
|
||||
if (!ref) return
|
||||
return new Promise((resolve, reject) => {
|
||||
nvueAnimation.transition(ref, {
|
||||
styles,
|
||||
...config
|
||||
}, res => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
_nvueNextAnimate(animates, step = 0, fn) {
|
||||
let obj = animates[step]
|
||||
if (obj) {
|
||||
let {
|
||||
styles,
|
||||
config
|
||||
} = obj
|
||||
this._animateRun(styles, config).then(() => {
|
||||
step += 1
|
||||
this._nvueNextAnimate(animates, step, fn)
|
||||
})
|
||||
} else {
|
||||
this.currentStepAnimates = {}
|
||||
typeof fn === 'function' && fn()
|
||||
this.isEnd = true
|
||||
}
|
||||
}
|
||||
|
||||
step(config = {}) {
|
||||
// #ifndef APP-NVUE
|
||||
this.animation.step(config)
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
|
||||
this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
|
||||
this.next++
|
||||
// #endif
|
||||
return this
|
||||
}
|
||||
|
||||
run(fn) {
|
||||
// #ifndef APP-NVUE
|
||||
this.$.animationData = this.animation.export()
|
||||
this.$.timer = setTimeout(() => {
|
||||
typeof fn === 'function' && fn()
|
||||
}, this.$.durationTime)
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this.isEnd = false
|
||||
let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref
|
||||
if(!ref) return
|
||||
this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
|
||||
this.next = 0
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
|
||||
'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
|
||||
'translateZ'
|
||||
]
|
||||
const animateTypes2 = ['opacity', 'backgroundColor']
|
||||
const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
|
||||
animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
|
||||
MPAnimation.prototype[type] = function(...args) {
|
||||
// #ifndef APP-NVUE
|
||||
this.animation[type](...args)
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this._nvuePushAnimates(type, args)
|
||||
// #endif
|
||||
return this
|
||||
}
|
||||
})
|
||||
|
||||
export function createAnimation(option, _this) {
|
||||
if(!_this) return
|
||||
clearTimeout(_this.timer)
|
||||
return new MPAnimation(option, _this)
|
||||
}
|
|
@ -0,0 +1,277 @@
|
|||
<template>
|
||||
<view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { createAnimation } from './createAnimation'
|
||||
|
||||
/**
|
||||
* Transition 过渡动画
|
||||
* @description 简单过渡动画组件
|
||||
* @tutorial https://ext.dcloud.net.cn/plugin?id=985
|
||||
* @property {Boolean} show = [false|true] 控制组件显示或隐藏
|
||||
* @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
|
||||
* @value fade 渐隐渐出过渡
|
||||
* @value slide-top 由上至下过渡
|
||||
* @value slide-right 由右至左过渡
|
||||
* @value slide-bottom 由下至上过渡
|
||||
* @value slide-left 由左至右过渡
|
||||
* @value zoom-in 由小到大过渡
|
||||
* @value zoom-out 由大到小过渡
|
||||
* @property {Number} duration 过渡动画持续时间
|
||||
* @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red`
|
||||
*/
|
||||
export default {
|
||||
name: 'uniTransition',
|
||||
emits:['click','change'],
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
modeClass: {
|
||||
type: [Array, String],
|
||||
default() {
|
||||
return 'fade'
|
||||
}
|
||||
},
|
||||
duration: {
|
||||
type: Number,
|
||||
default: 300
|
||||
},
|
||||
styles: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
customClass:{
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isShow: false,
|
||||
transform: '',
|
||||
opacity: 1,
|
||||
animationData: {},
|
||||
durationTime: 300,
|
||||
config: {}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
handler(newVal) {
|
||||
if (newVal) {
|
||||
this.open()
|
||||
} else {
|
||||
// 避免上来就执行 close,导致动画错乱
|
||||
if (this.isShow) {
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 生成样式数据
|
||||
stylesObject() {
|
||||
let styles = {
|
||||
...this.styles,
|
||||
'transition-duration': this.duration / 1000 + 's'
|
||||
}
|
||||
let transform = ''
|
||||
for (let i in styles) {
|
||||
let line = this.toLine(i)
|
||||
transform += line + ':' + styles[i] + ';'
|
||||
}
|
||||
return transform
|
||||
},
|
||||
// 初始化动画条件
|
||||
transformStyles() {
|
||||
return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
|
||||
}
|
||||
},
|
||||
created() {
|
||||
// 动画默认配置
|
||||
this.config = {
|
||||
duration: this.duration,
|
||||
timingFunction: 'ease',
|
||||
transformOrigin: '50% 50%',
|
||||
delay: 0
|
||||
}
|
||||
this.durationTime = this.duration
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* ref 触发 初始化动画
|
||||
*/
|
||||
init(obj = {}) {
|
||||
if (obj.duration) {
|
||||
this.durationTime = obj.duration
|
||||
}
|
||||
this.animation = createAnimation(Object.assign(this.config, obj))
|
||||
},
|
||||
/**
|
||||
* 点击组件触发回调
|
||||
*/
|
||||
onClick() {
|
||||
this.$emit('click', {
|
||||
detail: this.isShow
|
||||
})
|
||||
},
|
||||
/**
|
||||
* ref 触发 动画分组
|
||||
* @param {Object} obj
|
||||
*/
|
||||
step(obj, config = {}) {
|
||||
if (!this.animation) return
|
||||
for (let i in obj) {
|
||||
try {
|
||||
if(typeof obj[i] === 'object'){
|
||||
this.animation[i](...obj[i])
|
||||
}else{
|
||||
this.animation[i](obj[i])
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`方法 ${i} 不存在`)
|
||||
}
|
||||
}
|
||||
this.animation.step(config)
|
||||
return this
|
||||
},
|
||||
/**
|
||||
* ref 触发 执行动画
|
||||
*/
|
||||
run(fn) {
|
||||
if (!this.animation) return
|
||||
this.animation.run(fn)
|
||||
},
|
||||
// 开始过度动画
|
||||
open() {
|
||||
clearTimeout(this.timer)
|
||||
this.transform = ''
|
||||
this.isShow = true
|
||||
let { opacity, transform } = this.styleInit(false)
|
||||
if (typeof opacity !== 'undefined') {
|
||||
this.opacity = opacity
|
||||
}
|
||||
this.transform = transform
|
||||
// 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常
|
||||
this.$nextTick(() => {
|
||||
// TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器
|
||||
this.timer = setTimeout(() => {
|
||||
this.animation = createAnimation(this.config, this)
|
||||
this.tranfromInit(false).step()
|
||||
this.animation.run()
|
||||
this.$emit('change', {
|
||||
detail: this.isShow
|
||||
})
|
||||
}, 20)
|
||||
})
|
||||
},
|
||||
// 关闭过度动画
|
||||
close(type) {
|
||||
if (!this.animation) return
|
||||
this.tranfromInit(true)
|
||||
.step()
|
||||
.run(() => {
|
||||
this.isShow = false
|
||||
this.animationData = null
|
||||
this.animation = null
|
||||
let { opacity, transform } = this.styleInit(false)
|
||||
this.opacity = opacity || 1
|
||||
this.transform = transform
|
||||
this.$emit('change', {
|
||||
detail: this.isShow
|
||||
})
|
||||
})
|
||||
},
|
||||
// 处理动画开始前的默认样式
|
||||
styleInit(type) {
|
||||
let styles = {
|
||||
transform: ''
|
||||
}
|
||||
let buildStyle = (type, mode) => {
|
||||
if (mode === 'fade') {
|
||||
styles.opacity = this.animationType(type)[mode]
|
||||
} else {
|
||||
styles.transform += this.animationType(type)[mode] + ' '
|
||||
}
|
||||
}
|
||||
if (typeof this.modeClass === 'string') {
|
||||
buildStyle(type, this.modeClass)
|
||||
} else {
|
||||
this.modeClass.forEach(mode => {
|
||||
buildStyle(type, mode)
|
||||
})
|
||||
}
|
||||
return styles
|
||||
},
|
||||
// 处理内置组合动画
|
||||
tranfromInit(type) {
|
||||
let buildTranfrom = (type, mode) => {
|
||||
let aniNum = null
|
||||
if (mode === 'fade') {
|
||||
aniNum = type ? 0 : 1
|
||||
} else {
|
||||
aniNum = type ? '-100%' : '0'
|
||||
if (mode === 'zoom-in') {
|
||||
aniNum = type ? 0.8 : 1
|
||||
}
|
||||
if (mode === 'zoom-out') {
|
||||
aniNum = type ? 1.2 : 1
|
||||
}
|
||||
if (mode === 'slide-right') {
|
||||
aniNum = type ? '100%' : '0'
|
||||
}
|
||||
if (mode === 'slide-bottom') {
|
||||
aniNum = type ? '100%' : '0'
|
||||
}
|
||||
}
|
||||
this.animation[this.animationMode()[mode]](aniNum)
|
||||
}
|
||||
if (typeof this.modeClass === 'string') {
|
||||
buildTranfrom(type, this.modeClass)
|
||||
} else {
|
||||
this.modeClass.forEach(mode => {
|
||||
buildTranfrom(type, mode)
|
||||
})
|
||||
}
|
||||
|
||||
return this.animation
|
||||
},
|
||||
animationType(type) {
|
||||
return {
|
||||
fade: type ? 1 : 0,
|
||||
'slide-top': `translateY(${type ? '0' : '-100%'})`,
|
||||
'slide-right': `translateX(${type ? '0' : '100%'})`,
|
||||
'slide-bottom': `translateY(${type ? '0' : '100%'})`,
|
||||
'slide-left': `translateX(${type ? '0' : '-100%'})`,
|
||||
'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
|
||||
'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
|
||||
}
|
||||
},
|
||||
// 内置动画类型与实际动画对应字典
|
||||
animationMode() {
|
||||
return {
|
||||
fade: 'opacity',
|
||||
'slide-top': 'translateY',
|
||||
'slide-right': 'translateX',
|
||||
'slide-bottom': 'translateY',
|
||||
'slide-left': 'translateX',
|
||||
'zoom-in': 'scale',
|
||||
'zoom-out': 'scale'
|
||||
}
|
||||
},
|
||||
// 驼峰转中横线
|
||||
toLine(name) {
|
||||
return name.replace(/([A-Z])/g, '-$1').toLowerCase()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style></style>
|
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
"id": "uni-transition",
|
||||
"displayName": "uni-transition 过渡动画",
|
||||
"version": "1.2.0",
|
||||
"description": "元素的简单过渡动画",
|
||||
"keywords": [
|
||||
"uni-ui",
|
||||
"uniui",
|
||||
"动画",
|
||||
"过渡",
|
||||
"过渡动画"
|
||||
],
|
||||
"repository": "https://github.com/dcloudio/uni-ui",
|
||||
"engines": {
|
||||
"HBuilderX": ""
|
||||
},
|
||||
"directories": {
|
||||
"example": "../../temps/example_temps"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,397 @@
|
|||
|
||||
|
||||
## Transition 过渡动画
|
||||
> **组件名:uni-transition**
|
||||
> 代码块: `uTransition`
|
||||
|
||||
|
||||
元素过渡动画
|
||||
|
||||
> **注意事项**
|
||||
> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。
|
||||
> - 组件需要依赖 `sass` 插件 ,请自行手动安装
|
||||
> - rotate 旋转动画不需要填写 deg 单位,在小程序上填写单位动画不会执行
|
||||
> - NVUE 下修改宽高动画,不能定位到中心点
|
||||
> - 百度小程序下修改宽高 ,可能会影响其他动画,需注意
|
||||
> - nvue 不支持 costom-class , 请使用 styles
|
||||
> - 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839
|
||||
|
||||
|
||||
### 安装方式
|
||||
|
||||
本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。
|
||||
|
||||
如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55)
|
||||
|
||||
### 基本用法
|
||||
|
||||
在 ``template`` 中使用组件
|
||||
|
||||
```html
|
||||
<template>
|
||||
<view>
|
||||
<button type="primary" @click="open">fade</button>
|
||||
<uni-transition mode-class="fade" :styles="{'width':'100px','height':'100px','backgroundColor':'red'}" :show="show" @change="change" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
}
|
||||
},
|
||||
onLoad() {},
|
||||
methods: {
|
||||
open(mode) {
|
||||
this.show = !this.show
|
||||
},
|
||||
change() {
|
||||
<!-- console.log('触发动画') -->
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 样式覆盖
|
||||
|
||||
**注意:`nvue` 不支持 `custom-class` 属性 ,需要使用 `styles` 属性进行兼容**
|
||||
|
||||
使用 `custom-class` 属性绑定样式,可以自定义 `uni-transition` 的样式
|
||||
|
||||
```html
|
||||
<template>
|
||||
<view class="content">
|
||||
<uni-transition custom-class="custom-transition" mode-class="fade" :duration="0" :show="true" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* 常规样式覆盖 */
|
||||
.content >>> .custom-transition {
|
||||
width:100px;
|
||||
height:100px;
|
||||
background-color:red;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
/* 如果使用 scss 需要使用 /deep/ */
|
||||
.content /deep/ .custom-transition {
|
||||
width:100px;
|
||||
height:100px;
|
||||
background-color:red;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
|
||||
如果使用 `styles` 注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red`
|
||||
|
||||
```html
|
||||
<template>
|
||||
<view class="content">
|
||||
<uni-transition :styles="styles" mode-class="fade" :duration="0" :show="true" />
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
styles:{
|
||||
'width':'100px',
|
||||
'height':'100px',
|
||||
'backgroundColor':'red'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
### 自定义动画
|
||||
当内置动画类型不能满足需求的时候 ,可以使用 `step()` 和 `run()` 自定义动画,入参以及具体用法参考下方属性说明
|
||||
|
||||
`init()` 方法可以覆盖默认配置
|
||||
|
||||
|
||||
```html
|
||||
<template>
|
||||
<view>
|
||||
<button type="primary" @click="run">执行动画</button>
|
||||
<uni-transition ref="ani" :styles="{'width':'100px','height':'100px','backgroundColor':'red'}" :show="show" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
show: true,
|
||||
}
|
||||
},
|
||||
onReady() {
|
||||
this.$refs.ani.init({
|
||||
duration: 1000,
|
||||
timingFunction: 'linear',
|
||||
transformOrigin: '50% 50%',
|
||||
delay: 500
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
run() {
|
||||
// 同时右平移到 100px,旋转 360 读
|
||||
this.$refs.ani.step({
|
||||
translateX: '100px',
|
||||
rotate: '360'
|
||||
})
|
||||
// 上面的动画执行完成后,等待200毫秒平移到 0px,旋转到 0 读
|
||||
this.$refs.ani.step({
|
||||
translateX: '0px',
|
||||
rotate: '0'
|
||||
},
|
||||
{
|
||||
timingFunction: 'ease-in',
|
||||
duration: 200
|
||||
})
|
||||
// 开始执行动画
|
||||
this.$refs.ani.run(()=>{
|
||||
// console.log('动画支持完毕')
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
|
||||
## API
|
||||
|
||||
### Transition Props
|
||||
|
||||
|属性名 |类型 |默认值 |说明 |
|
||||
|:-: |:-: |:-: |:-:|
|
||||
|show |Boolean|false |控制组件显示或隐藏 |
|
||||
|mode-class |Array/String |- |内置过渡动画类型 |
|
||||
|custom-class |String |- |自定义类名 |
|
||||
|duration |Number |300 |过渡动画持续时间 |
|
||||
|styles |Object |- |组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red` |
|
||||
|
||||
#### mode-class 内置过渡动画类型说明
|
||||
**格式为** :`'fade'` 或者 `['fade','slide-top']`
|
||||
|
||||
|属性名 |说明 |
|
||||
|:-: |:-: |
|
||||
|fade |渐隐渐出过渡 |
|
||||
|slide-top |由上至下过渡 |
|
||||
|slide-right |由右至左过渡 |
|
||||
|slide-bottom |由下至上过渡 |
|
||||
|slide-left |由左至右过渡 |
|
||||
|zoom-in |由小到大过渡 |
|
||||
|zoom-out |由大到小过渡 |
|
||||
|
||||
**注意**
|
||||
|
||||
组合使用时,同一种类型相反的过渡动画如(slide-top、slide-bottom)同时使用时,只有最后一个生效
|
||||
|
||||
### Transition Events
|
||||
|
||||
|事件名 |说明 |返回值 |
|
||||
|:-: |:-: |:-: |
|
||||
|click |点击组件触发 |- |
|
||||
|change |过渡动画结束时触发 | e = {detail:true} |
|
||||
|
||||
### Transition Methons
|
||||
|
||||
|方法名|说明|参数|
|
||||
|:-:|:-:|:-:|
|
||||
|init()|手动初始化配置|Function(OBJECT:config)|
|
||||
|step()|动画队列|Function(OBJECT:type,OBJECT:config)|
|
||||
|run()|执行动画|Function(FUNCTION:callback) |
|
||||
|
||||
### init(OBJECT:config)
|
||||
**通过 ref 调用方法**
|
||||
|
||||
手动设置动画配置,需要在页面渲染完毕后调用
|
||||
|
||||
```javascript
|
||||
this.$refs.ani.init({
|
||||
duration: 1000,
|
||||
timingFunction:'ease',
|
||||
delay:500,
|
||||
transformOrigin:'left center'
|
||||
})
|
||||
```
|
||||
|
||||
### step(OBJECT:type,OBJECT:config) 动画队列
|
||||
**通过 ref 调用方法**
|
||||
|
||||
调用 `step()` 来表示一组动画完成,`step` 第一个参数可以传入任意多个动画方法,一组动画中的所有动画会同时开始,一组动画完成后才会进行下一组动画。`step` 第二个参数可以传入一个跟 `uni.createAnimation()` 一样的配置参数用于指定当前组动画的配置。
|
||||
|
||||
Tips
|
||||
- 第一个参数支持的动画参考下面的 `支持的动画`
|
||||
- 第二个参数参考下面的 `动画配置`,可省略,如果省略继承`init`的配置
|
||||
|
||||
|
||||
```javascript
|
||||
this.$refs.ani.step({
|
||||
translateX: '100px'
|
||||
},{
|
||||
duration: 1000,
|
||||
timingFunction:'ease',
|
||||
delay:500,
|
||||
transformOrigin:'left center'
|
||||
})
|
||||
```
|
||||
|
||||
### run(FUNCTION:callback) 执行动画
|
||||
**通过 ref 调用方法**
|
||||
|
||||
在执行 `step()` 后,需要调用 `run()` 来运行动画 ,否则动画会一直等待
|
||||
|
||||
`run()` 方法可以传入一个 `callback` 函数 ,会在所有动画执行完毕后回调
|
||||
|
||||
```javascript
|
||||
this.$refs.ani.step({
|
||||
translateX: '100px'
|
||||
})
|
||||
this.$refs.ani.run(()=>{
|
||||
// console.log('动画执行完毕')
|
||||
})
|
||||
|
||||
```
|
||||
|
||||
### 动画配置
|
||||
动画配置 , `init()` 与 `step()` 第二个参数配置相同 ,如果配置`step() `第二个参数,将会覆盖 `init()` 的配置
|
||||
|
||||
|属性名|值|必填|默认值|说明|平台差异|
|
||||
|:-:|:-:|:-:|:-:|:-:|:-:|
|
||||
|duration|Number|否|400|动画持续时间,单位ms|-|
|
||||
|timingFunction|String|否|"linear"|定义动画的效果|-|
|
||||
|delay|Number|否|0|动画延迟时间,单位 ms|-|
|
||||
|needLayout|Boolean|否|false |动画执行是否影响布局|仅 nvue 支持|
|
||||
|transformOrigin|String |否|"center center"|设置 [transform-origin](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin)|-|
|
||||
|
||||
|
||||
### timingFunction 属性说明
|
||||
|
||||
|值|说明|平台差异|
|
||||
|:-:|:-:|:-:|
|
||||
|linear|动画从头到尾的速度是相同的|-|
|
||||
|ease|动画以低速开始,然后加快,在结束前变慢|-|
|
||||
|ease-in| 动画以低速开始|-|
|
||||
|ease-in-out| 动画以低速开始和结束|-|
|
||||
|ease-out|动画以低速结束|-|
|
||||
|step-start|动画第一帧就跳至结束状态直到结束|nvue不支持|
|
||||
|step-end|动画一直保持开始状态,最后一帧跳到结束状态|nvue不支持|
|
||||
|
||||
```javascript
|
||||
// init 配置
|
||||
this.$refs.ani.init({
|
||||
duration: 1000,
|
||||
timingFunction:'ease',
|
||||
delay:500,
|
||||
transformOrigin:'left center'
|
||||
})
|
||||
// step 配置
|
||||
this.$refs.ani.step({
|
||||
translateX: '100px'
|
||||
},{
|
||||
duration: 1000,
|
||||
timingFunction:'ease',
|
||||
delay:500,
|
||||
transformOrigin:'left center'
|
||||
})
|
||||
```
|
||||
|
||||
### 支持的动画
|
||||
动画方法
|
||||
|
||||
如果同一个动画方法有多个值,多个值使用数组分隔
|
||||
|
||||
```javascript
|
||||
this.$refs.ani.step({
|
||||
width:'100px',
|
||||
scale: [1.2,0.8],
|
||||
})
|
||||
```
|
||||
|
||||
**样式:**
|
||||
|
||||
|属性名|值|说明|平台差异|
|
||||
|:-:|:-:|:-:|:-:|
|
||||
|opacity|value|透明度,参数范围 0~1|-|
|
||||
|backgroundColor|color|颜色值|-|
|
||||
|width|length|长度值,如果传入 Number 则默认使用 px,可传入其他自定义单位的长度值|-|
|
||||
|height|length|长度值,如果传入 Number 则默认使用 px,可传入其他自定义单位的长度值|-|
|
||||
|top|length|长度值,如果传入 Number 则默认使用 px,可传入其他自定义单位的长度值|nvue 不支持|
|
||||
|left|length|长度值,如果传入 Number 则默认使用 px,可传入其他自定义单位的长度值|nvue 不支持|
|
||||
|bottom|length|长度值,如果传入 Number 则默认使用 px,可传入其他自定义单位的长度值|nvue 不支持|
|
||||
|right|length|长度值,如果传入 Number 则默认使用 px,可传入其他自定义单位的长度值|nvue 不支持|
|
||||
|
||||
```javascript
|
||||
this.$refs.ani.step({
|
||||
opacity: 1,
|
||||
backgroundColor: '#ff5a5f',
|
||||
widht:'100px',
|
||||
height:'50rpx',
|
||||
})
|
||||
```
|
||||
|
||||
**旋转:**
|
||||
|
||||
旋转属性的值不需要填写单位
|
||||
|
||||
|属性名|值|说明|平台差异 |
|
||||
|:-:|:-:|:-:|:-:|
|
||||
|rotate|deg|deg的范围-180~180,从原点顺时针旋转一个deg角度 |-|
|
||||
|rotateX|deg|deg的范围-180~180,在X轴旋转一个deg角度 |-|
|
||||
|rotateY|deg|deg的范围-180~180,在Y轴旋转一个deg角度 |-|
|
||||
|rotateZ|deg|deg的范围-180~180,在Z轴旋转一个deg角度 |nvue不支持|
|
||||
|rotate3d|x,y,z,deg| 同 [transform-function rotate3d](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/rotate3d()) |nvue不支持|
|
||||
|
||||
```javascript
|
||||
this.$refs.ani.step({
|
||||
rotateX: 45,
|
||||
rotateY: 45
|
||||
})
|
||||
```
|
||||
|
||||
**缩放:**
|
||||
|
||||
|属性名|值|说明|平台差异|
|
||||
|:-:|:-:|:-: |:-:|
|
||||
|scale|sx,[sy]|一个参数时,表示在X轴、Y轴同时缩放sx倍数;两个参数时表示在X轴缩放sx倍数,在Y轴缩放sy倍数|-|
|
||||
|scaleX|sx|在X轴缩放sx倍数|-|
|
||||
|scaleY|sy|在Y轴缩放sy倍数|-|
|
||||
|scaleZ|sz|在Z轴缩放sy倍数|nvue不支持|
|
||||
|scale3d|sx,sy,sz|在X轴缩放sx倍数,在Y轴缩放sy倍数,在Z轴缩放sz倍数|nvue不支持|
|
||||
|
||||
```javascript
|
||||
this.$refs.ani.step({
|
||||
scale: [1.2,0.8]
|
||||
})
|
||||
```
|
||||
|
||||
**偏移:**
|
||||
|
||||
|属性名|值|说明|平台差异|
|
||||
|:-:|:-:|:-:|:-:|
|
||||
|translate|tx,[ty]|一个参数时,表示在X轴偏移tx,单位px;两个参数时,表示在X轴偏移tx,在Y轴偏移ty,单位px。|-|
|
||||
|translateX|tx| 在X轴偏移tx,单位px|-|
|
||||
|translateY|ty| 在Y轴偏移tx,单位px|-|
|
||||
|translateZ|tz| 在Z轴偏移tx,单位px|nvue不支持|
|
||||
|translate3d|tx,ty,tz| 在X轴偏移tx,在Y轴偏移ty,在Z轴偏移tz,单位px|nvue不支持|
|
||||
|
||||
```javascript
|
||||
this.$refs.ani.step({
|
||||
translateX: '100px'
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
|
||||
## 组件示例
|
||||
|
||||
点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/transition/transition](https://hellouniapp.dcloud.net.cn/pages/extUI/transition/transition)
|
|
@ -0,0 +1,27 @@
|
|||
## 1.0.11(2023-10-29)
|
||||
1. imgMode默认值改成aspectFit
|
||||
## 1.0.10(2023-08-13)
|
||||
1. 优化nvue,方便自定义图标
|
||||
## 1.0.9(2023-07-28)
|
||||
1. 修改几个对应错误图标的BUG
|
||||
## 1.0.8(2023-07-24)
|
||||
1. 优化 支持base64图片
|
||||
## 1.0.7(2023-07-17)
|
||||
1. 修复 uv-icon 恢复uv-empty相关的图标
|
||||
## 1.0.6(2023-07-13)
|
||||
1. 修复icon设置name属性对应图标错误的BUG
|
||||
## 1.0.5(2023-07-04)
|
||||
1. 更新图标,删除一些不常用的图标
|
||||
2. 删除base64,修改成ttf文件引入读取图标
|
||||
3. 自定义图标文档说明:https://www.uvui.cn/guide/customIcon.html
|
||||
## 1.0.4(2023-07-03)
|
||||
1. 修复主题颜色在APP不生效的BUG
|
||||
## 1.0.3(2023-05-24)
|
||||
1. 将线上ttf字体包替换成base64,避免加载时或者网络差时候显示白色方块
|
||||
## 1.0.2(2023-05-16)
|
||||
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
|
||||
2. 优化部分功能
|
||||
## 1.0.1(2023-05-10)
|
||||
1. 修复小程序中异常显示
|
||||
## 1.0.0(2023-05-04)
|
||||
新发版
|
|
@ -0,0 +1,160 @@
|
|||
export default {
|
||||
'uvicon-level': 'e68f',
|
||||
'uvicon-checkbox-mark': 'e659',
|
||||
'uvicon-folder': 'e694',
|
||||
'uvicon-movie': 'e67c',
|
||||
'uvicon-star-fill': 'e61e',
|
||||
'uvicon-star': 'e618',
|
||||
'uvicon-phone-fill': 'e6ac',
|
||||
'uvicon-phone': 'e6ba',
|
||||
'uvicon-apple-fill': 'e635',
|
||||
'uvicon-backspace': 'e64d',
|
||||
'uvicon-attach': 'e640',
|
||||
'uvicon-empty-data': 'e671',
|
||||
'uvicon-empty-address': 'e68a',
|
||||
'uvicon-empty-favor': 'e662',
|
||||
'uvicon-empty-car': 'e657',
|
||||
'uvicon-empty-order': 'e66b',
|
||||
'uvicon-empty-list': 'e672',
|
||||
'uvicon-empty-search': 'e677',
|
||||
'uvicon-empty-permission': 'e67d',
|
||||
'uvicon-empty-news': 'e67e',
|
||||
'uvicon-empty-history': 'e685',
|
||||
'uvicon-empty-coupon': 'e69b',
|
||||
'uvicon-empty-page': 'e60e',
|
||||
'uvicon-empty-wifi-off': 'e6cc',
|
||||
'uvicon-reload': 'e627',
|
||||
'uvicon-order': 'e695',
|
||||
'uvicon-server-man': 'e601',
|
||||
'uvicon-search': 'e632',
|
||||
'uvicon-more-dot-fill': 'e66f',
|
||||
'uvicon-scan': 'e631',
|
||||
'uvicon-map': 'e665',
|
||||
'uvicon-map-fill': 'e6a8',
|
||||
'uvicon-tags': 'e621',
|
||||
'uvicon-tags-fill': 'e613',
|
||||
'uvicon-eye': 'e664',
|
||||
'uvicon-eye-fill': 'e697',
|
||||
'uvicon-eye-off': 'e69c',
|
||||
'uvicon-eye-off-outline': 'e688',
|
||||
'uvicon-mic': 'e66d',
|
||||
'uvicon-mic-off': 'e691',
|
||||
'uvicon-calendar': 'e65c',
|
||||
'uvicon-trash': 'e623',
|
||||
'uvicon-trash-fill': 'e6ce',
|
||||
'uvicon-play-left': 'e6bf',
|
||||
'uvicon-play-right': 'e6b3',
|
||||
'uvicon-minus': 'e614',
|
||||
'uvicon-plus': 'e625',
|
||||
'uvicon-info-circle': 'e69f',
|
||||
'uvicon-info-circle-fill': 'e6a7',
|
||||
'uvicon-question-circle': 'e622',
|
||||
'uvicon-question-circle-fill': 'e6bc',
|
||||
'uvicon-close': 'e65a',
|
||||
'uvicon-checkmark': 'e64a',
|
||||
'uvicon-checkmark-circle': 'e643',
|
||||
'uvicon-checkmark-circle-fill': 'e668',
|
||||
'uvicon-setting': 'e602',
|
||||
'uvicon-setting-fill': 'e6d0',
|
||||
'uvicon-heart': 'e6a2',
|
||||
'uvicon-heart-fill': 'e68b',
|
||||
'uvicon-camera': 'e642',
|
||||
'uvicon-camera-fill': 'e650',
|
||||
'uvicon-more-circle': 'e69e',
|
||||
'uvicon-more-circle-fill': 'e684',
|
||||
'uvicon-chat': 'e656',
|
||||
'uvicon-chat-fill': 'e63f',
|
||||
'uvicon-bag': 'e647',
|
||||
'uvicon-error-circle': 'e66e',
|
||||
'uvicon-error-circle-fill': 'e655',
|
||||
'uvicon-close-circle': 'e64e',
|
||||
'uvicon-close-circle-fill': 'e666',
|
||||
'uvicon-share': 'e629',
|
||||
'uvicon-share-fill': 'e6bb',
|
||||
'uvicon-share-square': 'e6c4',
|
||||
'uvicon-shopping-cart': 'e6cb',
|
||||
'uvicon-shopping-cart-fill': 'e630',
|
||||
'uvicon-bell': 'e651',
|
||||
'uvicon-bell-fill': 'e604',
|
||||
'uvicon-list': 'e690',
|
||||
'uvicon-list-dot': 'e6a9',
|
||||
'uvicon-zhifubao-circle-fill': 'e617',
|
||||
'uvicon-weixin-circle-fill': 'e6cd',
|
||||
'uvicon-weixin-fill': 'e620',
|
||||
'uvicon-qq-fill': 'e608',
|
||||
'uvicon-qq-circle-fill': 'e6b9',
|
||||
'uvicon-moments-circel-fill': 'e6c2',
|
||||
'uvicon-moments': 'e6a0',
|
||||
'uvicon-car': 'e64f',
|
||||
'uvicon-car-fill': 'e648',
|
||||
'uvicon-warning-fill': 'e6c7',
|
||||
'uvicon-warning': 'e6c1',
|
||||
'uvicon-clock-fill': 'e64b',
|
||||
'uvicon-clock': 'e66c',
|
||||
'uvicon-edit-pen': 'e65d',
|
||||
'uvicon-edit-pen-fill': 'e679',
|
||||
'uvicon-email': 'e673',
|
||||
'uvicon-email-fill': 'e683',
|
||||
'uvicon-minus-circle': 'e6a5',
|
||||
'uvicon-plus-circle': 'e603',
|
||||
'uvicon-plus-circle-fill': 'e611',
|
||||
'uvicon-file-text': 'e687',
|
||||
'uvicon-file-text-fill': 'e67f',
|
||||
'uvicon-pushpin': 'e6d1',
|
||||
'uvicon-pushpin-fill': 'e6b6',
|
||||
'uvicon-grid': 'e68c',
|
||||
'uvicon-grid-fill': 'e698',
|
||||
'uvicon-play-circle': 'e6af',
|
||||
'uvicon-play-circle-fill': 'e62a',
|
||||
'uvicon-pause-circle-fill': 'e60c',
|
||||
'uvicon-pause': 'e61c',
|
||||
'uvicon-pause-circle': 'e696',
|
||||
'uvicon-gift-fill': 'e6b0',
|
||||
'uvicon-gift': 'e680',
|
||||
'uvicon-kefu-ermai': 'e660',
|
||||
'uvicon-server-fill': 'e610',
|
||||
'uvicon-coupon-fill': 'e64c',
|
||||
'uvicon-coupon': 'e65f',
|
||||
'uvicon-integral': 'e693',
|
||||
'uvicon-integral-fill': 'e6b1',
|
||||
'uvicon-home-fill': 'e68e',
|
||||
'uvicon-home': 'e67b',
|
||||
'uvicon-account': 'e63a',
|
||||
'uvicon-account-fill': 'e653',
|
||||
'uvicon-thumb-down-fill': 'e628',
|
||||
'uvicon-thumb-down': 'e60a',
|
||||
'uvicon-thumb-up': 'e612',
|
||||
'uvicon-thumb-up-fill': 'e62c',
|
||||
'uvicon-lock-fill': 'e6a6',
|
||||
'uvicon-lock-open': 'e68d',
|
||||
'uvicon-lock-opened-fill': 'e6a1',
|
||||
'uvicon-lock': 'e69d',
|
||||
'uvicon-red-packet': 'e6c3',
|
||||
'uvicon-photo-fill': 'e6b4',
|
||||
'uvicon-photo': 'e60d',
|
||||
'uvicon-volume-off-fill': 'e6c8',
|
||||
'uvicon-volume-off': 'e6bd',
|
||||
'uvicon-volume-fill': 'e624',
|
||||
'uvicon-volume': 'e605',
|
||||
'uvicon-download': 'e670',
|
||||
'uvicon-arrow-up-fill': 'e636',
|
||||
'uvicon-arrow-down-fill': 'e638',
|
||||
'uvicon-play-left-fill': 'e6ae',
|
||||
'uvicon-play-right-fill': 'e6ad',
|
||||
'uvicon-arrow-downward': 'e634',
|
||||
'uvicon-arrow-leftward': 'e63b',
|
||||
'uvicon-arrow-rightward': 'e644',
|
||||
'uvicon-arrow-upward': 'e641',
|
||||
'uvicon-arrow-down': 'e63e',
|
||||
'uvicon-arrow-right': 'e63c',
|
||||
'uvicon-arrow-left': 'e646',
|
||||
'uvicon-arrow-up': 'e633',
|
||||
'uvicon-skip-back-left': 'e6c5',
|
||||
'uvicon-skip-forward-right': 'e61f',
|
||||
'uvicon-arrow-left-double': 'e637',
|
||||
'uvicon-man': 'e675',
|
||||
'uvicon-woman': 'e626',
|
||||
'uvicon-en': 'e6b8',
|
||||
'uvicon-twitte': 'e607',
|
||||
'uvicon-twitter-circle-fill': 'e6cf'
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
export default {
|
||||
props: {
|
||||
// 图标类名
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 图标颜色,可接受主题色
|
||||
color: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// 字体大小,单位px
|
||||
size: {
|
||||
type: [String, Number],
|
||||
default: '16px'
|
||||
},
|
||||
// 是否显示粗体
|
||||
bold: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 点击图标的时候传递事件出去的index(用于区分点击了哪一个)
|
||||
index: {
|
||||
type: [String, Number],
|
||||
default: null
|
||||
},
|
||||
// 触摸图标时的类名
|
||||
hoverClass: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
// 自定义扩展前缀,方便用户扩展自己的图标库
|
||||
customPrefix: {
|
||||
type: String,
|
||||
default: 'uvicon'
|
||||
},
|
||||
// 图标右边或者下面的文字
|
||||
label: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// label的位置,只能右边或者下边
|
||||
labelPos: {
|
||||
type: String,
|
||||
default: 'right'
|
||||
},
|
||||
// label的大小
|
||||
labelSize: {
|
||||
type: [String, Number],
|
||||
default: '15px'
|
||||
},
|
||||
// label的颜色
|
||||
labelColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// label与图标的距离
|
||||
space: {
|
||||
type: [String, Number],
|
||||
default: '3px'
|
||||
},
|
||||
// 图片的mode
|
||||
imgMode: {
|
||||
type: String,
|
||||
default: 'aspectFit'
|
||||
},
|
||||
// 用于显示图片小图标时,图片的宽度
|
||||
width: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 用于显示图片小图标时,图片的高度
|
||||
height: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 用于解决某些情况下,让图标垂直居中的用途
|
||||
top: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
},
|
||||
// 是否阻止事件传播
|
||||
stop: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
...uni.$uv?.props?.icon
|
||||
}
|
||||
}
|
|
@ -0,0 +1,226 @@
|
|||
<template>
|
||||
<view
|
||||
class="uv-icon"
|
||||
@tap="clickHandler"
|
||||
:class="['uv-icon--' + labelPos]"
|
||||
>
|
||||
<image
|
||||
class="uv-icon__img"
|
||||
v-if="isImg"
|
||||
:src="name"
|
||||
:mode="imgMode"
|
||||
:style="[imgStyle, $uv.addStyle(customStyle)]"
|
||||
></image>
|
||||
<text
|
||||
v-else
|
||||
class="uv-icon__icon"
|
||||
:class="uClasses"
|
||||
:style="[iconStyle, $uv.addStyle(customStyle)]"
|
||||
:hover-class="hoverClass"
|
||||
>{{icon}}</text>
|
||||
<!-- 这里进行空字符串判断,如果仅仅是v-if="label",可能会出现传递0的时候,结果也无法显示 -->
|
||||
<text
|
||||
v-if="label !== ''"
|
||||
class="uv-icon__label"
|
||||
:style="{
|
||||
color: labelColor,
|
||||
fontSize: $uv.addUnit(labelSize),
|
||||
marginLeft: labelPos == 'right' ? $uv.addUnit(space) : 0,
|
||||
marginTop: labelPos == 'bottom' ? $uv.addUnit(space) : 0,
|
||||
marginRight: labelPos == 'left' ? $uv.addUnit(space) : 0,
|
||||
marginBottom: labelPos == 'top' ? $uv.addUnit(space) : 0
|
||||
}"
|
||||
>{{ label }}</text>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
|
||||
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
|
||||
// #ifdef APP-NVUE
|
||||
// nvue通过weex的dom模块引入字体,相关文档地址如下:
|
||||
// https://weex.apache.org/zh/docs/modules/dom.html#addrule
|
||||
import iconUrl from './uvicons.ttf';
|
||||
const domModule = weex.requireModule('dom')
|
||||
domModule.addRule('fontFace', {
|
||||
'fontFamily': "uvicon-iconfont",
|
||||
'src': "url('" + iconUrl + "')"
|
||||
})
|
||||
// #endif
|
||||
// 引入图标名称,已经对应的unicode
|
||||
import icons from './icons';
|
||||
import props from './props.js';
|
||||
/**
|
||||
* icon 图标
|
||||
* @description 基于字体的图标集,包含了大多数常见场景的图标。
|
||||
* @tutorial https://www.uvui.cn/components/icon.html
|
||||
* @property {String} name 图标名称,见示例图标集
|
||||
* @property {String} color 图标颜色,可接受主题色 (默认 color['uv-content-color'] )
|
||||
* @property {String | Number} size 图标字体大小,单位px (默认 '16px' )
|
||||
* @property {Boolean} bold 是否显示粗体 (默认 false )
|
||||
* @property {String | Number} index 点击图标的时候传递事件出去的index(用于区分点击了哪一个)
|
||||
* @property {String} hoverClass 图标按下去的样式类,用法同uni的view组件的hoverClass参数,详情见官网
|
||||
* @property {String} customPrefix 自定义扩展前缀,方便用户扩展自己的图标库 (默认 'uicon' )
|
||||
* @property {String | Number} label 图标右侧的label文字
|
||||
* @property {String} labelPos label相对于图标的位置,只能right或bottom (默认 'right' )
|
||||
* @property {String | Number} labelSize label字体大小,单位px (默认 '15px' )
|
||||
* @property {String} labelColor 图标右侧的label文字颜色 ( 默认 color['uv-content-color'] )
|
||||
* @property {String | Number} space label与图标的距离,单位px (默认 '3px' )
|
||||
* @property {String} imgMode 图片的mode
|
||||
* @property {String | Number} width 显示图片小图标时的宽度
|
||||
* @property {String | Number} height 显示图片小图标时的高度
|
||||
* @property {String | Number} top 图标在垂直方向上的定位 用于解决某些情况下,让图标垂直居中的用途 (默认 0 )
|
||||
* @property {Boolean} stop 是否阻止事件传播 (默认 false )
|
||||
* @property {Object} customStyle icon的样式,对象形式
|
||||
* @event {Function} click 点击图标时触发
|
||||
* @event {Function} touchstart 事件触摸时触发
|
||||
* @example <uv-icon name="photo" color="#2979ff" size="28"></uv-icon>
|
||||
*/
|
||||
export default {
|
||||
name: 'uv-icon',
|
||||
emits: ['click'],
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {
|
||||
colorType: [
|
||||
'primary',
|
||||
'success',
|
||||
'info',
|
||||
'error',
|
||||
'warning'
|
||||
]
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
uClasses() {
|
||||
let classes = []
|
||||
classes.push(this.customPrefix)
|
||||
classes.push(this.customPrefix + '-' + this.name)
|
||||
// 主题色,通过类配置
|
||||
if (this.color && this.colorType.includes(this.color)) classes.push('uv-icon__icon--' + this.color)
|
||||
// 阿里,头条,百度小程序通过数组绑定类名时,无法直接使用[a, b, c]的形式,否则无法识别
|
||||
// 故需将其拆成一个字符串的形式,通过空格隔开各个类名
|
||||
//#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU
|
||||
classes = classes.join(' ')
|
||||
//#endif
|
||||
return classes
|
||||
},
|
||||
iconStyle() {
|
||||
let style = {}
|
||||
style = {
|
||||
fontSize: this.$uv.addUnit(this.size),
|
||||
lineHeight: this.$uv.addUnit(this.size),
|
||||
fontWeight: this.bold ? 'bold' : 'normal',
|
||||
// 某些特殊情况需要设置一个到顶部的距离,才能更好的垂直居中
|
||||
top: this.$uv.addUnit(this.top)
|
||||
}
|
||||
// 非主题色值时,才当作颜色值
|
||||
if (this.color && !this.colorType.includes(this.color)) style.color = this.color
|
||||
return style
|
||||
},
|
||||
// 判断传入的name属性,是否图片路径,只要带有"/"均认为是图片形式
|
||||
isImg() {
|
||||
const isBase64 = this.name.indexOf('data:') > -1 && this.name.indexOf('base64') > -1;
|
||||
return this.name.indexOf('/') !== -1 || isBase64;
|
||||
},
|
||||
imgStyle() {
|
||||
let style = {}
|
||||
// 如果设置width和height属性,则优先使用,否则使用size属性
|
||||
style.width = this.width ? this.$uv.addUnit(this.width) : this.$uv.addUnit(this.size)
|
||||
style.height = this.height ? this.$uv.addUnit(this.height) : this.$uv.addUnit(this.size)
|
||||
return style
|
||||
},
|
||||
// 通过图标名,查找对应的图标
|
||||
icon() {
|
||||
// 如果内置的图标中找不到对应的图标,就直接返回name值,因为用户可能传入的是unicode代码
|
||||
const code = icons['uvicon-' + this.name];
|
||||
// #ifdef APP-NVUE
|
||||
if(!code) {
|
||||
return code ? unescape(`%u${code}`) : ['uvicon'].indexOf(this.customPrefix) > -1 ? unescape(`%u${this.name}`) : '';
|
||||
}
|
||||
// #endif
|
||||
return code ? unescape(`%u${code}`) : ['uvicon'].indexOf(this.customPrefix) > -1 ? this.name : '';
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
clickHandler(e) {
|
||||
this.$emit('click', this.index)
|
||||
// 是否阻止事件冒泡
|
||||
this.stop && this.preventEvent(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
|
||||
@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
|
||||
// 变量定义
|
||||
$uv-icon-primary: $uv-primary !default;
|
||||
$uv-icon-success: $uv-success !default;
|
||||
$uv-icon-info: $uv-info !default;
|
||||
$uv-icon-warning: $uv-warning !default;
|
||||
$uv-icon-error: $uv-error !default;
|
||||
$uv-icon-label-line-height: 1 !default;
|
||||
/* #ifndef APP-NVUE */
|
||||
// 非nvue下加载字体
|
||||
@font-face {
|
||||
font-family: 'uvicon-iconfont';
|
||||
src: url('./uvicons.ttf') format('truetype');
|
||||
}
|
||||
/* #endif */
|
||||
.uv-icon {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
align-items: center;
|
||||
&--left {
|
||||
flex-direction: row-reverse;
|
||||
align-items: center;
|
||||
}
|
||||
&--right {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
&--top {
|
||||
flex-direction: column-reverse;
|
||||
justify-content: center;
|
||||
}
|
||||
&--bottom {
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
&__icon {
|
||||
font-family: uvicon-iconfont;
|
||||
position: relative;
|
||||
@include flex;
|
||||
align-items: center;
|
||||
&--primary {
|
||||
color: $uv-icon-primary;
|
||||
}
|
||||
&--success {
|
||||
color: $uv-icon-success;
|
||||
}
|
||||
&--error {
|
||||
color: $uv-icon-error;
|
||||
}
|
||||
&--warning {
|
||||
color: $uv-icon-warning;
|
||||
}
|
||||
&--info {
|
||||
color: $uv-icon-info;
|
||||
}
|
||||
}
|
||||
&__img {
|
||||
/* #ifndef APP-NVUE */
|
||||
height: auto;
|
||||
will-change: transform;
|
||||
/* #endif */
|
||||
}
|
||||
&__label {
|
||||
/* #ifndef APP-NVUE */
|
||||
line-height: $uv-icon-label-line-height;
|
||||
/* #endif */
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,83 @@
|
|||
{
|
||||
"id": "uv-icon",
|
||||
"displayName": "uv-icon 图标 全面兼容vue3+2、app、h5、小程序等多端",
|
||||
"version": "1.0.11",
|
||||
"description": "基于字体的图标集,包含了大多数常见场景的图标,支持自定义,支持自定义图片图标等。可自定义颜色、大小。",
|
||||
"keywords": [
|
||||
"uv-ui,uvui,uv-icon,icon,图标,字体图标"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.0"
|
||||
},
|
||||
"dcloudext": {
|
||||
"type": "component-vue",
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "插件不采集任何数据",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": ""
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
"uv-ui-tools"
|
||||
],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
},
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y",
|
||||
"钉钉": "u",
|
||||
"快手": "u",
|
||||
"飞书": "u",
|
||||
"京东": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
## uv-icon 图标库
|
||||
|
||||
> **组件名:uv-icon**
|
||||
|
||||
基于字体的图标集,包含了大多数常见场景的图标,支持自定义,支持自定义图片图标等。
|
||||
|
||||
# <a href="https://www.uvui.cn/components/icon.html" target="_blank">查看文档</a>
|
||||
|
||||
## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)
|
||||
|
||||
### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
|
||||
|
||||
![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
|
||||
|
||||
#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
|
|
@ -0,0 +1,11 @@
|
|||
## 1.0.4(2023-10-13)
|
||||
1. 优化
|
||||
## 1.0.3(2023-10-13)
|
||||
1. unmounted兼容vue3
|
||||
## 1.0.2(2023-05-27)
|
||||
1. 不支持抖音小程序说明
|
||||
## 1.0.1(2023-05-16)
|
||||
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
|
||||
2. 优化部分功能
|
||||
## 1.0.0(2023-05-10)
|
||||
uv-swipe-action 滑动单元格
|
|
@ -0,0 +1,256 @@
|
|||
/**
|
||||
* 此为wxs模块,只支持APP-VUE,微信和QQ小程序以及H5平台
|
||||
* wxs内部不支持es6语法,变量只能使用var定义,无法使用解构,箭头函数等特性
|
||||
*/
|
||||
|
||||
// 开始触摸
|
||||
function touchstart(event, ownerInstance) {
|
||||
// 触发事件的组件的ComponentDescriptor实例
|
||||
var instance = event.instance
|
||||
// wxs内的局部变量快照,此快照是属于整个组件的,在touchstart和touchmove事件中都能获取到相同的结果
|
||||
var state = instance.getState()
|
||||
if (state.disable) return
|
||||
var touches = event.touches
|
||||
// 如果进行的是多指触控,不允许进行操作
|
||||
if (touches && touches.length > 1) return
|
||||
// 标识当前为滑动中状态
|
||||
state.moving = true
|
||||
// 记录触摸开始点的坐标值
|
||||
state.startX = touches[0].pageX
|
||||
state.startY = touches[0].pageY
|
||||
}
|
||||
|
||||
// 触摸滑动
|
||||
function touchmove(event, ownerInstance) {
|
||||
// 触发事件的组件的ComponentDescriptor实例
|
||||
var instance = event.instance
|
||||
// wxs内的局部变量快照
|
||||
var state = instance.getState()
|
||||
if (state.disabled || !state.moving) return
|
||||
|
||||
var touches = event.touches
|
||||
var pageX = touches[0].pageX
|
||||
var pageY = touches[0].pageY
|
||||
var moveX = pageX - state.startX
|
||||
var moveY = pageY - state.startY
|
||||
var buttonsWidth = state.buttonsWidth
|
||||
|
||||
// 移动的X轴距离大于Y轴距离,也即终点与起点位置连线,与X轴夹角小于45度时,禁止页面滚动
|
||||
if (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > state.threshold) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
}
|
||||
// 如果移动的X轴距离小于Y轴距离,也即终点位置与起点位置连线,与Y轴夹角小于45度时,认为是页面上下滑动,而不是左右滑动单元格
|
||||
if (Math.abs(moveX) < Math.abs(moveY)) return
|
||||
|
||||
// 限制右滑的距离,不允许内容部分往右偏移,右滑会导致X轴偏移值大于0,以此做判断
|
||||
// 此处不能直接return,因为滑动过程中会缺失某些关键点坐标,会导致错乱,最好的办法就是
|
||||
// 在超出后,设置为0
|
||||
if (state.status === 'open') {
|
||||
// 在开启状态下,向左滑动,需忽略
|
||||
if (moveX < 0) moveX = 0
|
||||
// 想要收起菜单,最大能移动的距离为按钮的总宽度
|
||||
if (moveX > buttonsWidth) moveX = buttonsWidth
|
||||
// 如果是已经打开了的状态,向左滑动时,移动收起菜单
|
||||
moveSwipeAction(-buttonsWidth + moveX, instance, ownerInstance)
|
||||
} else {
|
||||
// 关闭状态下,右滑动需忽略
|
||||
if (moveX > 0) moveX = 0
|
||||
// 滑动的距离不允许超过所有按钮的总宽度,此时只能是左滑,最终设置按钮的总宽度,同时为负数
|
||||
if (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth
|
||||
// 只要是在滑过程中,就不断移动菜单的内容部分,从而使隐藏的菜单显示出来
|
||||
moveSwipeAction(moveX, instance, ownerInstance)
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸结束
|
||||
function touchend(event, ownerInstance) {
|
||||
// 触发事件的组件的ComponentDescriptor实例
|
||||
var instance = event.instance
|
||||
// wxs内的局部变量快照
|
||||
var state = instance.getState()
|
||||
if (!state.moving || state.disabled) return
|
||||
var touches = event.changedTouches ? event.changedTouches[0] : {}
|
||||
var pageX = touches.pageX
|
||||
var pageY = touches.pageY
|
||||
var moveX = pageX - state.startX
|
||||
if (state.status === 'open') {
|
||||
// 在展开的状态下,继续左滑,无需操作
|
||||
if (moveX < 0) return
|
||||
// 在开启状态下,点击一下内容区域,moveX为0,也即没有进行移动,这时执行收起菜单逻辑
|
||||
if (moveX === 0) {
|
||||
return closeSwipeAction(instance, ownerInstance)
|
||||
}
|
||||
// 在开启状态下,滑动距离小于阈值,则默认为不关闭,同时恢复原来的打开状态
|
||||
if (Math.abs(moveX) < state.threshold) {
|
||||
openSwipeAction(instance, ownerInstance)
|
||||
} else {
|
||||
// 如果滑动距离大于阈值,则执行收起逻辑
|
||||
closeSwipeAction(instance, ownerInstance)
|
||||
}
|
||||
} else {
|
||||
// 在关闭的状态下,右滑,无需操作
|
||||
if (moveX > 0) return
|
||||
// 理由同上
|
||||
if (Math.abs(moveX) < state.threshold) {
|
||||
closeSwipeAction(instance, ownerInstance)
|
||||
} else {
|
||||
openSwipeAction(instance, ownerInstance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取过渡时间
|
||||
function getDuration(value) {
|
||||
if (value.toString().indexOf('s') >= 0) return value
|
||||
return value > 30 ? value + 'ms' : value + 's'
|
||||
}
|
||||
|
||||
// 滑动结束时判断滑动的方向
|
||||
function getMoveDirection(instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
}
|
||||
|
||||
// 移动滑动选择器内容区域,同时显示出其隐藏的菜单
|
||||
function moveSwipeAction(moveX, instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||
var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
|
||||
var len = buttons.length
|
||||
var previewButtonsMoveX = 0
|
||||
|
||||
// 设置菜单内容部分的偏移
|
||||
instance.requestAnimationFrame(function() {
|
||||
instance.setStyle({
|
||||
// 设置translateX的值
|
||||
'transition': 'none',
|
||||
transform: 'translateX(' + moveX + 'px)',
|
||||
'-webkit-transform': 'translateX(' + moveX + 'px)'
|
||||
})
|
||||
// 折叠按钮动画
|
||||
for (var i = len - 1; i >= 0; i--) {
|
||||
// 通过比例,得出元素自身该移动的距离
|
||||
var translateX = state.buttons[i].width / state.buttonsWidth * moveX
|
||||
// 最终移动的距离,是通过自身比例算出的距离,再加上在它之前所有按钮移动的距离之和
|
||||
var realTranslateX = translateX + previewButtonsMoveX
|
||||
buttons[i].setStyle({
|
||||
// 在移动期间,不能使用过渡效果,否则会造成卡顿,本质原因是每次移动一点,就要花一定时间去过渡这个过程
|
||||
'transition': 'none',
|
||||
'transform': 'translateX(' + realTranslateX + 'px)',
|
||||
'-webkit-transform': 'translateX(' + realTranslateX + 'px)'
|
||||
})
|
||||
// 记录本按钮之前的所有按钮的移动距离之和
|
||||
previewButtonsMoveX += translateX
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 一次性展开滑动菜单
|
||||
function openSwipeAction(instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||
var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
|
||||
var len = buttons.length
|
||||
// 处理duration单位问题
|
||||
const duration = getDuration(state.duration)
|
||||
// 展开过程中,是向左移动,所以X的偏移应该为负值
|
||||
var buttonsWidth = -state.buttonsWidth
|
||||
var previewButtonsMoveX = 0
|
||||
instance.requestAnimationFrame(function() {
|
||||
// 设置菜单主体内容
|
||||
instance.setStyle({
|
||||
'transition': 'transform ' + duration,
|
||||
'transform': 'translateX(' + buttonsWidth + 'px)',
|
||||
'-webkit-transform': 'translateX(' + buttonsWidth + 'px)',
|
||||
})
|
||||
// 设置各个隐藏的按钮为展开的状态
|
||||
for (var i = len - 1; i >= 0; i--) {
|
||||
// 通过比例,得出元素自身该移动的距离
|
||||
var translateX = state.buttons[i].width / state.buttonsWidth * buttonsWidth
|
||||
// 最终移动的距离,是通过自身比例算出的距离,再加上在它之前所有按钮移动的距离之和
|
||||
var realTranslateX = translateX + previewButtonsMoveX
|
||||
buttons[i].setStyle({
|
||||
// 在移动期间,需要加上动画效果
|
||||
'transition': 'transform ' + duration,
|
||||
'transform': 'translateX(' + realTranslateX + 'px)',
|
||||
'-webkit-transform': 'translateX(' + realTranslateX + 'px)'
|
||||
})
|
||||
// 记录本按钮之前的所有按钮的移动距离之和
|
||||
previewButtonsMoveX += translateX
|
||||
}
|
||||
})
|
||||
setStatus('open', instance)
|
||||
}
|
||||
|
||||
// 标记菜单的当前状态,open-已经打开,close-已经关闭
|
||||
function setStatus(status, instance) {
|
||||
var state = instance.getState()
|
||||
state.status = status
|
||||
}
|
||||
|
||||
// 一次性收起滑动菜单
|
||||
function closeSwipeAction(instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||
var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
|
||||
var len = buttons.length
|
||||
// 处理duration单位问题
|
||||
const duration = getDuration(state.duration)
|
||||
instance.requestAnimationFrame(function() {
|
||||
// 设置菜单主体内容
|
||||
instance.setStyle({
|
||||
'transition': 'transform ' + duration,
|
||||
'transform': 'translateX(0px)',
|
||||
'-webkit-transform': 'translateX(0px)'
|
||||
})
|
||||
// 设置各个隐藏的按钮为收起的状态
|
||||
for (var i = len - 1; i >= 0; i--) {
|
||||
buttons[i].setStyle({
|
||||
'transition': 'transform ' + duration,
|
||||
'transform': 'translateX(0px)',
|
||||
'-webkit-transform': 'translateX(0px)'
|
||||
})
|
||||
}
|
||||
})
|
||||
setStatus('close', instance)
|
||||
}
|
||||
|
||||
// show的状态发生变化
|
||||
function showChange(newValue, oldValue, ownerInstance, instance) {
|
||||
var state = instance.getState()
|
||||
if (state.disabled) return
|
||||
// 打开或关闭单元格
|
||||
if (newValue) {
|
||||
openSwipeAction(instance, ownerInstance)
|
||||
} else {
|
||||
closeSwipeAction(instance, ownerInstance)
|
||||
}
|
||||
}
|
||||
|
||||
// 菜单尺寸发生变化
|
||||
function sizeChange(newValue, oldValue, ownerInstance, instance) {
|
||||
// wxs内的局部变量快照
|
||||
var state = instance.getState()
|
||||
state.disabled = newValue.disabled
|
||||
state.duration = newValue.duration
|
||||
state.show = newValue.show
|
||||
state.threshold = newValue.threshold
|
||||
state.buttons = newValue.buttons
|
||||
|
||||
var len = state.buttons.length
|
||||
if (len) {
|
||||
var buttonsWidth = 0
|
||||
var buttons = newValue.buttons
|
||||
for (var i = 0; i < len; i++) {
|
||||
buttonsWidth += buttons[i].width
|
||||
}
|
||||
}
|
||||
state.buttonsWidth = buttonsWidth
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
touchstart: touchstart,
|
||||
touchmove: touchmove,
|
||||
touchend: touchend,
|
||||
sizeChange: sizeChange
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
/**
|
||||
* 此为wxs模块,只支持APP-VUE,微信和QQ小程序以及H5平台
|
||||
* wxs内部不支持es6语法,变量只能使用var定义,无法使用解构,箭头函数等特性
|
||||
*/
|
||||
|
||||
// 开始触摸
|
||||
function touchstart(event, ownerInstance) {
|
||||
// 触发事件的组件的ComponentDescriptor实例
|
||||
var instance = event.instance
|
||||
// wxs内的局部变量快照,此快照是属于整个组件的,在touchstart和touchmove事件中都能获取到相同的结果
|
||||
var state = instance.getState()
|
||||
if (state.disabled) return
|
||||
var touches = event.touches
|
||||
// 如果进行的是多指触控,不允许进行操作
|
||||
if (touches && touches.length > 1) return
|
||||
// 标识当前为滑动中状态
|
||||
state.moving = true
|
||||
// 记录触摸开始点的坐标值
|
||||
state.startX = touches[0].pageX
|
||||
state.startY = touches[0].pageY
|
||||
|
||||
ownerInstance.callMethod('closeOther')
|
||||
}
|
||||
|
||||
// 触摸滑动
|
||||
function touchmove(event, ownerInstance) {
|
||||
// 触发事件的组件的ComponentDescriptor实例
|
||||
var instance = event.instance
|
||||
// wxs内的局部变量快照
|
||||
var state = instance.getState()
|
||||
if (state.disabled || !state.moving) return
|
||||
var touches = event.touches
|
||||
var pageX = touches[0].pageX
|
||||
var pageY = touches[0].pageY
|
||||
var moveX = pageX - state.startX
|
||||
var moveY = pageY - state.startY
|
||||
var buttonsWidth = state.buttonsWidth
|
||||
|
||||
// 移动的X轴距离大于Y轴距离,也即终点与起点位置连线,与X轴夹角小于45度时,禁止页面滚动
|
||||
if (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > state.threshold) {
|
||||
event.preventDefault && event.preventDefault()
|
||||
event.stopPropagation && event.stopPropagation()
|
||||
}
|
||||
// 如果移动的X轴距离小于Y轴距离,也即终点位置与起点位置连线,与Y轴夹角小于45度时,认为是页面上下滑动,而不是左右滑动单元格
|
||||
if (Math.abs(moveX) < Math.abs(moveY)) return
|
||||
|
||||
// 限制右滑的距离,不允许内容部分往右偏移,右滑会导致X轴偏移值大于0,以此做判断
|
||||
// 此处不能直接return,因为滑动过程中会缺失某些关键点坐标,会导致错乱,最好的办法就是
|
||||
// 在超出后,设置为0
|
||||
if (state.status === 'open') {
|
||||
// 在开启状态下,向左滑动,需忽略
|
||||
if (moveX < 0) moveX = 0
|
||||
// 想要收起菜单,最大能移动的距离为按钮的总宽度
|
||||
if (moveX > buttonsWidth) moveX = buttonsWidth
|
||||
// 如果是已经打开了的状态,向左滑动时,移动收起菜单
|
||||
moveSwipeAction(-buttonsWidth + moveX, instance, ownerInstance)
|
||||
} else {
|
||||
// 关闭状态下,右滑动需忽略
|
||||
if (moveX > 0) moveX = 0
|
||||
// 滑动的距离不允许超过所有按钮的总宽度,此时只能是左滑,最终设置按钮的总宽度,同时为负数
|
||||
if (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth
|
||||
// 只要是在滑过程中,就不断移动单元格内容部分,从而使隐藏的菜单显示出来
|
||||
moveSwipeAction(moveX, instance, ownerInstance)
|
||||
}
|
||||
}
|
||||
|
||||
// 触摸结束
|
||||
function touchend(event, ownerInstance) {
|
||||
// 触发事件的组件的ComponentDescriptor实例
|
||||
var instance = event.instance
|
||||
// wxs内的局部变量快照
|
||||
var state = instance.getState()
|
||||
if (!state.moving || state.disabled) return
|
||||
var touches = event.changedTouches ? event.changedTouches[0] : {}
|
||||
var pageX = touches.pageX
|
||||
var pageY = touches.pageY
|
||||
var moveX = pageX - state.startX
|
||||
if (state.status === 'open') {
|
||||
// 在展开的状态下,继续左滑,无需操作
|
||||
if (moveX < 0) return
|
||||
// 在开启状态下,点击一下内容区域,moveX为0,也即没有进行移动,这时执行收起菜单逻辑
|
||||
if (moveX === 0) {
|
||||
return closeSwipeAction(instance, ownerInstance)
|
||||
}
|
||||
// 在开启状态下,滑动距离小于阈值,则默认为不关闭,同时恢复原来的打开状态
|
||||
if (Math.abs(moveX) < state.threshold) {
|
||||
openSwipeAction(instance, ownerInstance)
|
||||
} else {
|
||||
// 如果滑动距离大于阈值,则执行收起逻辑
|
||||
closeSwipeAction(instance, ownerInstance)
|
||||
}
|
||||
} else {
|
||||
// 在关闭的状态下,右滑,无需操作
|
||||
if (moveX > 0) return
|
||||
// 理由同上
|
||||
if (Math.abs(moveX) < state.threshold) {
|
||||
closeSwipeAction(instance, ownerInstance)
|
||||
} else {
|
||||
openSwipeAction(instance, ownerInstance)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 获取过渡时间
|
||||
function getDuration(value) {
|
||||
if (value.toString().indexOf('s') >= 0) return value
|
||||
return value > 30 ? value + 'ms' : value + 's'
|
||||
}
|
||||
|
||||
// 滑动结束时判断滑动的方向
|
||||
function getMoveDirection(instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
}
|
||||
|
||||
// 移动滑动选择器内容区域,同时显示出其隐藏的菜单
|
||||
function moveSwipeAction(moveX, instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||
var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
|
||||
|
||||
// 设置菜单内容部分的偏移
|
||||
instance.requestAnimationFrame(function() {
|
||||
instance.setStyle({
|
||||
// 设置translateX的值
|
||||
'transition': 'none',
|
||||
transform: 'translateX(' + moveX + 'px)',
|
||||
'-webkit-transform': 'translateX(' + moveX + 'px)'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 一次性展开滑动菜单
|
||||
function openSwipeAction(instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||
var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
|
||||
// 处理duration单位问题
|
||||
var duration = getDuration(state.duration)
|
||||
// 展开过程中,是向左移动,所以X的偏移应该为负值
|
||||
var buttonsWidth = -state.buttonsWidth
|
||||
instance.requestAnimationFrame(function() {
|
||||
// 设置菜单主体内容
|
||||
instance.setStyle({
|
||||
'transition': 'transform ' + duration,
|
||||
'transform': 'translateX(' + buttonsWidth + 'px)',
|
||||
'-webkit-transform': 'translateX(' + buttonsWidth + 'px)',
|
||||
})
|
||||
})
|
||||
setStatus('open', instance, ownerInstance)
|
||||
}
|
||||
|
||||
// 标记菜单的当前状态,open-已经打开,close-已经关闭
|
||||
function setStatus(status, instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
state.status = status
|
||||
ownerInstance.callMethod('setState', status)
|
||||
}
|
||||
|
||||
// 一次性收起滑动菜单
|
||||
function closeSwipeAction(instance, ownerInstance) {
|
||||
var state = instance.getState()
|
||||
// 获取所有按钮的实例,需要通过它去设置按钮的位移
|
||||
var buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')
|
||||
var len = buttons.length
|
||||
// 处理duration单位问题
|
||||
var duration = getDuration(state.duration)
|
||||
instance.requestAnimationFrame(function() {
|
||||
// 设置菜单主体内容
|
||||
instance.setStyle({
|
||||
'transition': 'transform ' + duration,
|
||||
'transform': 'translateX(0px)',
|
||||
'-webkit-transform': 'translateX(0px)'
|
||||
})
|
||||
// 设置各个隐藏的按钮为收起的状态
|
||||
for (var i = len - 1; i >= 0; i--) {
|
||||
buttons[i].setStyle({
|
||||
'transition': 'transform ' + duration,
|
||||
'transform': 'translateX(0px)',
|
||||
'-webkit-transform': 'translateX(0px)'
|
||||
})
|
||||
}
|
||||
})
|
||||
setStatus('close', instance, ownerInstance)
|
||||
}
|
||||
|
||||
// status的状态发生变化
|
||||
function statusChange(newValue, oldValue, ownerInstance, instance) {
|
||||
var state = instance.getState()
|
||||
if (state.disabled) return
|
||||
// 打开或关闭单元格
|
||||
if (newValue === 'close' && state.status === 'open') {
|
||||
closeSwipeAction(instance, ownerInstance)
|
||||
} else if(newValue === 'open' && state.status === 'close') {
|
||||
openSwipeAction(instance, ownerInstance)
|
||||
}
|
||||
}
|
||||
|
||||
// 菜单尺寸发生变化
|
||||
function sizeChange(newValue, oldValue, ownerInstance, instance) {
|
||||
// wxs内的局部变量快照
|
||||
var state = instance.getState()
|
||||
state.disabled = newValue.disabled
|
||||
state.duration = newValue.duration
|
||||
state.show = newValue.show
|
||||
state.threshold = newValue.threshold
|
||||
state.buttons = newValue.buttons
|
||||
|
||||
if (state.buttons) {
|
||||
var len = state.buttons.length
|
||||
var buttonsWidth = 0
|
||||
var buttons = newValue.buttons
|
||||
for (var i = 0; i < len; i++) {
|
||||
buttonsWidth += buttons[i].width
|
||||
}
|
||||
}
|
||||
state.buttonsWidth = buttonsWidth
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
touchstart: touchstart,
|
||||
touchmove: touchmove,
|
||||
touchend: touchend,
|
||||
sizeChange: sizeChange,
|
||||
statusChange: statusChange
|
||||
}
|
|
@ -0,0 +1,264 @@
|
|||
// nvue操作dom的库,用于获取dom的尺寸信息
|
||||
const dom = uni.requireNativePlugin('dom')
|
||||
// nvue中用于操作元素动画的库,类似于uni.animation,只不过uni.animation不能用于nvue
|
||||
const animation = uni.requireNativePlugin('animation')
|
||||
import { sleep } from '@/uni_modules/uv-ui-tools/libs/function/index.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 是否滑动中
|
||||
moving: false,
|
||||
// 状态,open-打开状态,close-关闭状态
|
||||
status: 'close',
|
||||
// 开始触摸点的X和Y轴坐标
|
||||
startX: 0,
|
||||
startY: 0,
|
||||
// 所有隐藏按钮的尺寸信息数组
|
||||
buttons: [],
|
||||
// 所有按钮的总宽度
|
||||
buttonsWidth: 0,
|
||||
// 记录上一次移动的位置值
|
||||
moveX: 0,
|
||||
// 记录上一次滑动的位置,用于前后两次做对比,如果移动的距离小于某一阈值,则认为前后之间没有移动,为了解决可能存在的通信阻塞问题
|
||||
lastX: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 获取过渡时间
|
||||
getDuratin() {
|
||||
let duration = String(this.duration)
|
||||
// 如果ms为单位,返回ms的数值部分
|
||||
if (duration.indexOf('ms') >= 0) return parseInt(duration)
|
||||
// 如果s为单位,为了得到ms的数值,需要乘以1000
|
||||
if (duration.indexOf('s') >= 0) return parseInt(duration) * 1000
|
||||
// 如果值传了数值,且小于30,认为是s单位
|
||||
duration = Number(duration)
|
||||
return duration < 30 ? duration * 1000 : duration
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show: {
|
||||
immediate: true,
|
||||
handler(n) {
|
||||
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
sleep(20).then(() => {
|
||||
this.queryRect()
|
||||
})
|
||||
},
|
||||
methods: {
|
||||
close() {
|
||||
this.closeSwipeAction()
|
||||
},
|
||||
// 触摸单元格
|
||||
touchstart(event) {
|
||||
if (this.disabled) return
|
||||
this.closeOther()
|
||||
const { touches } = event
|
||||
// 记录触摸开始点的坐标值
|
||||
this.startX = touches[0].pageX
|
||||
this.startY = touches[0].pageY
|
||||
},
|
||||
// // 触摸滑动
|
||||
touchmove(event) {
|
||||
if (this.disabled) return
|
||||
const { touches } = event
|
||||
const { pageX } = touches[0]
|
||||
const { pageY } = touches[0]
|
||||
let moveX = pageX - this.startX
|
||||
const moveY = pageY - this.startY
|
||||
const { buttonsWidth } = this
|
||||
const len = this.buttons.length
|
||||
|
||||
// 判断前后两次的移动距离,如果小于一定值,则不进行移动处理
|
||||
if (Math.abs(pageX - this.lastX) < 0.3) return
|
||||
this.lastX = pageX
|
||||
|
||||
// 移动的X轴距离大于Y轴距离,也即终点与起点位置连线,与X轴夹角小于45度时,禁止页面滚动
|
||||
if (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > this.threshold) {
|
||||
event.stopPropagation()
|
||||
}
|
||||
// 如果移动的X轴距离小于Y轴距离,也即终点位置与起点位置连线,与Y轴夹角小于45度时,认为是页面上下滑动,而不是左右滑动单元格
|
||||
if (Math.abs(moveX) < Math.abs(moveY)) return
|
||||
|
||||
// 限制右滑的距离,不允许内容部分往右偏移,右滑会导致X轴偏移值大于0,以此做判断
|
||||
// 此处不能直接return,因为滑动过程中会缺失某些关键点坐标,会导致错乱,最好的办法就是
|
||||
// 在超出后,设置为0
|
||||
if (this.status === 'open') {
|
||||
// 在开启状态下,向左滑动,需忽略
|
||||
if (moveX < 0) moveX = 0
|
||||
// 想要收起菜单,最大能移动的距离为按钮的总宽度
|
||||
if (moveX > buttonsWidth) moveX = buttonsWidth
|
||||
// 如果是已经打开了的状态,向左滑动时,移动收起菜单
|
||||
this.moveSwipeAction(-buttonsWidth + moveX)
|
||||
} else {
|
||||
// 关闭状态下,右滑动需忽略
|
||||
if (moveX > 0) moveX = 0
|
||||
// 滑动的距离不允许超过所有按钮的总宽度,此时只能是左滑,最终设置按钮的总宽度,同时为负数
|
||||
if (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth
|
||||
// 只要是在滑过程中,就不断移动菜单的内容部分,从而使隐藏的菜单显示出来
|
||||
this.moveSwipeAction(moveX)
|
||||
}
|
||||
},
|
||||
// 单元格结束触摸
|
||||
touchend(event) {
|
||||
if (this.disabled) return
|
||||
const touches = event.changedTouches ? event.changedTouches[0] : {}
|
||||
const { pageX } = touches
|
||||
const { pageY } = touches
|
||||
const { buttonsWidth } = this
|
||||
this.moveX = pageX - this.startX
|
||||
if (this.status === 'open') {
|
||||
// 在展开的状态下,继续左滑,无需操作
|
||||
if (this.moveX < 0) this.moveX = 0
|
||||
if (this.moveX > buttonsWidth) this.moveX = buttonsWidth
|
||||
// 在开启状态下,点击一下内容区域,moveX为0,也即没有进行移动,这时执行收起菜单逻辑
|
||||
if (this.moveX === 0) {
|
||||
return this.closeSwipeAction()
|
||||
}
|
||||
// 在开启状态下,滑动距离小于阈值,则默认为不关闭,同时恢复原来的打开状态
|
||||
if (Math.abs(this.moveX) < this.threshold) {
|
||||
this.openSwipeAction()
|
||||
} else {
|
||||
// 如果滑动距离大于阈值,则执行收起逻辑
|
||||
this.closeSwipeAction()
|
||||
}
|
||||
} else {
|
||||
// 在关闭的状态下,右滑,无需操作
|
||||
if (this.moveX >= 0) this.moveX = 0
|
||||
if (this.moveX <= -buttonsWidth) this.moveX = -buttonsWidth
|
||||
// 理由同上
|
||||
if (Math.abs(this.moveX) < this.threshold) {
|
||||
this.closeSwipeAction()
|
||||
} else {
|
||||
this.openSwipeAction()
|
||||
}
|
||||
}
|
||||
},
|
||||
// 移动滑动选择器内容区域,同时显示出其隐藏的菜单
|
||||
moveSwipeAction(moveX) {
|
||||
if (this.moving) return
|
||||
this.moving = true
|
||||
|
||||
let previewButtonsMoveX = 0
|
||||
const len = this.buttons.length
|
||||
animation.transition(this.$refs['uv-swipe-action-item__content'].ref, {
|
||||
styles: {
|
||||
transform: `translateX(${moveX}px)`
|
||||
},
|
||||
timingFunction: 'linear'
|
||||
}, () => {
|
||||
this.moving = false
|
||||
})
|
||||
// 按钮的组的长度
|
||||
for (let i = len - 1; i >= 0; i--) {
|
||||
const buttonRef = this.$refs[`uv-swipe-action-item__right__button-${i}`][0].ref
|
||||
// 通过比例,得出元素自身该移动的距离
|
||||
const translateX = this.buttons[i].width / this.buttonsWidth * moveX
|
||||
// 最终移动的距离,是通过自身比例算出的距离,再加上在它之前所有按钮移动的距离之和
|
||||
const realTranslateX = translateX + previewButtonsMoveX
|
||||
animation.transition(buttonRef, {
|
||||
styles: {
|
||||
transform: `translateX(${realTranslateX}px)`
|
||||
},
|
||||
duration: 0,
|
||||
delay: 0,
|
||||
timingFunction: 'linear'
|
||||
}, () => {})
|
||||
// 记录本按钮之前的所有按钮的移动距离之和
|
||||
previewButtonsMoveX += translateX
|
||||
}
|
||||
},
|
||||
// 关闭菜单
|
||||
closeSwipeAction() {
|
||||
if (this.status === 'close') return
|
||||
this.moving = true
|
||||
const { buttonsWidth } = this
|
||||
animation.transition(this.$refs['uv-swipe-action-item__content'].ref, {
|
||||
styles: {
|
||||
transform: 'translateX(0px)'
|
||||
},
|
||||
duration: this.getDuratin,
|
||||
timingFunction: 'ease-in-out'
|
||||
}, () => {
|
||||
this.status = 'close'
|
||||
this.moving = false
|
||||
this.closeHandler()
|
||||
})
|
||||
// 按钮的组的长度
|
||||
const len = this.buttons.length
|
||||
for (let i = len - 1; i >= 0; i--) {
|
||||
const buttonRef = this.$refs[`uv-swipe-action-item__right__button-${i}`][0].ref
|
||||
// 如果不满足边界条件,返回
|
||||
if (this.buttons.length === 0 || !this.buttons[i] || !this.buttons[i].width) return
|
||||
|
||||
animation.transition(buttonRef, {
|
||||
styles: {
|
||||
transform: 'translateX(0px)'
|
||||
},
|
||||
duration: this.getDuratin,
|
||||
timingFunction: 'ease-in-out'
|
||||
}, () => {})
|
||||
}
|
||||
},
|
||||
// 打开菜单
|
||||
openSwipeAction() {
|
||||
if (this.status === 'open') return
|
||||
this.moving = true
|
||||
const buttonsWidth = -this.buttonsWidth
|
||||
let previewButtonsMoveX = 0
|
||||
animation.transition(this.$refs['uv-swipe-action-item__content'].ref, {
|
||||
styles: {
|
||||
transform: `translateX(${buttonsWidth}px)`
|
||||
},
|
||||
duration: this.getDuratin,
|
||||
timingFunction: 'ease-in-out'
|
||||
}, () => {
|
||||
this.status = 'open'
|
||||
this.moving = false
|
||||
this.openHandler()
|
||||
})
|
||||
// 按钮的组的长度
|
||||
const len = this.buttons.length
|
||||
for (let i = len - 1; i >= 0; i--) {
|
||||
const buttonRef = this.$refs[`uv-swipe-action-item__right__button-${i}`][0].ref
|
||||
// 如果不满足边界条件,返回
|
||||
if (this.buttons.length === 0 || !this.buttons[i] || !this.buttons[i].width) return
|
||||
// 通过比例,得出元素自身该移动的距离
|
||||
const translateX = this.buttons[i].width / this.buttonsWidth * buttonsWidth
|
||||
// 最终移动的距离,是通过自身比例算出的距离,再加上在它之前所有按钮移动的距离之和
|
||||
const realTranslateX = translateX + previewButtonsMoveX
|
||||
animation.transition(buttonRef, {
|
||||
styles: {
|
||||
transform: `translateX(${realTranslateX}px)`
|
||||
},
|
||||
duration: this.getDuratin,
|
||||
timingFunction: 'ease-in-out'
|
||||
}, () => {})
|
||||
previewButtonsMoveX += translateX
|
||||
}
|
||||
},
|
||||
// 查询按钮节点信息
|
||||
queryRect() {
|
||||
// 历遍所有按钮数组,通过getRectByDom返回一个promise
|
||||
const promiseAll = this.rightOptions.map((item, index) => this.getRectByDom(this.$refs[`uv-swipe-action-item__right__button-${index}`][0]))
|
||||
// 通过promise.all方法,让所有按钮的查询结果返回一个数组的形式
|
||||
Promise.all(promiseAll).then((sizes) => {
|
||||
this.buttons = sizes
|
||||
// 计算所有按钮总宽度
|
||||
this.buttonsWidth = sizes.reduce((sum, cur) => sum + cur.width, 0)
|
||||
})
|
||||
},
|
||||
// 通过nvue的dom模块,查询节点信息
|
||||
getRectByDom(ref) {
|
||||
return new Promise((resolve) => {
|
||||
dom.getComponentRect(ref, (res) => {
|
||||
resolve(res.size)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,182 @@
|
|||
// nvue操作dom的库,用于获取dom的尺寸信息
|
||||
const dom = uni.requireNativePlugin('dom');
|
||||
const bindingX = uni.requireNativePlugin('bindingx');
|
||||
const animation = uni.requireNativePlugin('animation');
|
||||
import { getDuration, getPx } from '@/uni_modules/uv-ui-tools/libs/function/index.js'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
// 所有按钮的总宽度
|
||||
buttonsWidth: 0,
|
||||
// 是否正在移动中
|
||||
moving: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 获取过渡时间
|
||||
getDuratin() {
|
||||
let duration = String(this.duration)
|
||||
// 如果ms为单位,返回ms的数值部分
|
||||
if (duration.indexOf('ms') >= 0) return parseInt(duration)
|
||||
// 如果s为单位,为了得到ms的数值,需要乘以1000
|
||||
if (duration.indexOf('s') >= 0) return parseInt(duration) * 1000
|
||||
// 如果值传了数值,且小于30,认为是s单位
|
||||
duration = Number(duration)
|
||||
return duration < 30 ? duration * 1000 : duration
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
show(n) {
|
||||
if(n) {
|
||||
this.moveCellByAnimation('open')
|
||||
} else {
|
||||
this.moveCellByAnimation('close')
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
setTimeout(()=>{
|
||||
this.initialize()
|
||||
},20)
|
||||
},
|
||||
methods: {
|
||||
initialize() {
|
||||
this.queryRect()
|
||||
},
|
||||
// 关闭单元格,用于打开一个,自动关闭其他单元格的场景
|
||||
closeHandler() {
|
||||
if(this.status === 'open') {
|
||||
// 如果在打开状态下,进行点击的话,直接关闭单元格
|
||||
return this.moveCellByAnimation('close') && this.unbindBindingX()
|
||||
}
|
||||
},
|
||||
// 点击单元格
|
||||
clickHandler() {
|
||||
// 如果在移动中被点击,进行忽略
|
||||
if(this.moving) return
|
||||
// 尝试关闭其他打开的单元格
|
||||
this.parent && this.parent.closeOther(this)
|
||||
if(this.status === 'open') {
|
||||
// 如果在打开状态下,进行点击的话,直接关闭单元格
|
||||
return this.moveCellByAnimation('close') && this.unbindBindingX()
|
||||
}
|
||||
},
|
||||
// 滑动单元格
|
||||
onTouchstart(e) {
|
||||
// 如果当前正在移动中,或者disabled状态,则返回
|
||||
if(this.moving || this.disabled) {
|
||||
return this.unbindBindingX()
|
||||
}
|
||||
if(this.status === 'open') {
|
||||
// 如果在打开状态下,进行点击的话,直接关闭单元格
|
||||
return this.moveCellByAnimation('close') && this.unbindBindingX()
|
||||
}
|
||||
// 特殊情况下,e可能不为一个对象
|
||||
e?.stopPropagation && e.stopPropagation()
|
||||
e?.preventDefault && e.preventDefault()
|
||||
this.moving = true
|
||||
// 获取元素ref
|
||||
const content = this.getContentRef()
|
||||
let expression = `min(max(${-this.buttonsWidth}, x), 0)`
|
||||
// 尝试关闭其他打开的单元格
|
||||
this.parent && this.parent.closeOther(this)
|
||||
// 阿里为了KPI而开源的BindingX
|
||||
this.panEvent = bindingX.bind({
|
||||
anchor: content,
|
||||
eventType: 'pan',
|
||||
props: [{
|
||||
element: content,
|
||||
// 绑定width属性,设置其宽度值
|
||||
property: 'transform.translateX',
|
||||
expression
|
||||
}]
|
||||
}, (res) => {
|
||||
this.moving = false
|
||||
if (res.state === 'end' || res.state === 'exit') {
|
||||
const deltaX = res.deltaX
|
||||
if(deltaX <= -this.buttonsWidth || deltaX >= 0) {
|
||||
// 如果触摸滑动的过程中,大于单元格的总宽度,或者大于0,意味着已经动过滑动达到了打开或者关闭的状态
|
||||
// 这里直接进行状态的标记
|
||||
this.$nextTick(() => {
|
||||
this.status = deltaX <= -this.buttonsWidth ? 'open' : 'close'
|
||||
})
|
||||
} else if(Math.abs(deltaX) > getPx(this.threshold)) {
|
||||
// 在移动大于阈值、并且小于总按钮宽度时,进行自动打开或者关闭
|
||||
// 移动距离大于0时,意味着需要关闭状态
|
||||
if(Math.abs(deltaX) < this.buttonsWidth) {
|
||||
this.moveCellByAnimation(deltaX > 0 ? 'close' : 'open')
|
||||
}
|
||||
} else {
|
||||
// 在小于阈值时,进行关闭操作(如果在打开状态下,将不会执行bindingX)
|
||||
this.moveCellByAnimation('close')
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
// 释放bindingX
|
||||
unbindBindingX() {
|
||||
// 释放上一次的资源
|
||||
if (this?.panEvent?.token != 0) {
|
||||
bindingX.unbind({
|
||||
token: this.panEvent?.token,
|
||||
// pan为手势事件
|
||||
eventType: 'pan'
|
||||
})
|
||||
}
|
||||
},
|
||||
// 查询按钮节点信息
|
||||
queryRect() {
|
||||
// 历遍所有按钮数组,通过getRectByDom返回一个promise
|
||||
const promiseAll = this.options.map(async(item, index) => {
|
||||
return await this.getRectByDom(this.$refs[`uv-swipe-action-item__right__button-${index}`][0])
|
||||
})
|
||||
// 通过promise.all方法,让所有按钮的查询结果返回一个数组的形式
|
||||
Promise.all(promiseAll).then(sizes => {
|
||||
this.buttons = sizes
|
||||
// 计算所有按钮总宽度
|
||||
this.buttonsWidth = sizes.reduce((sum, cur) => sum + cur.width, 0)
|
||||
})
|
||||
},
|
||||
// 通过nvue的dom模块,查询节点信息
|
||||
getRectByDom(ref) {
|
||||
return new Promise(resolve => {
|
||||
dom.getComponentRect(ref, res => {
|
||||
resolve(res.size)
|
||||
})
|
||||
})
|
||||
},
|
||||
// 移动单元格到左边或者右边尽头
|
||||
moveCellByAnimation(status = 'open') {
|
||||
if(this.moving) return
|
||||
// 标识当前状态
|
||||
this.moveing = true
|
||||
const content = this.getContentRef()
|
||||
const x = status === 'open' ? -this.buttonsWidth : 0
|
||||
animation.transition(content, {
|
||||
styles: {
|
||||
transform: `translateX(${x}px)`,
|
||||
},
|
||||
duration: getDuration(this.duration, false),
|
||||
timingFunction: 'ease-in-out'
|
||||
}, () => {
|
||||
this.moving = false
|
||||
this.status = status
|
||||
this.unbindBindingX()
|
||||
})
|
||||
},
|
||||
// 获取元素ref
|
||||
getContentRef() {
|
||||
return this.$refs['uv-swipe-action-item__content'].ref
|
||||
}
|
||||
},
|
||||
// #ifdef VUE2
|
||||
beforeDestroy() {
|
||||
this.unbindBindingX()
|
||||
},
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
unmounted() {
|
||||
this.unbindBindingX()
|
||||
}
|
||||
// #endif
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
export default {
|
||||
props: {
|
||||
// 控制打开或者关闭
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 标识符,如果是v-for,可用index索引值
|
||||
name: {
|
||||
type: [String, Number],
|
||||
default: ''
|
||||
},
|
||||
// 是否禁用
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否自动关闭其他swipe按钮组
|
||||
autoClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 滑动距离阈值,只有大于此值,才被认为是要打开菜单
|
||||
threshold: {
|
||||
type: Number,
|
||||
default: 20
|
||||
},
|
||||
// 右侧按钮内容
|
||||
options: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 动画过渡时间,单位ms
|
||||
duration: {
|
||||
type: [String, Number],
|
||||
default: 300
|
||||
},
|
||||
...uni.$uv?.props?.swipeActionItem
|
||||
}
|
||||
}
|
|
@ -0,0 +1,200 @@
|
|||
<template>
|
||||
<view class="uv-swipe-action-item" ref="uv-swipe-action-item">
|
||||
<view class="uv-swipe-action-item__right">
|
||||
<slot name="button">
|
||||
<view v-for="(item,index) in options" :key="index" class="uv-swipe-action-item__right__button"
|
||||
:ref="`uv-swipe-action-item__right__button-${index}`" :style="[{
|
||||
alignItems: item.style && item.style.borderRadius ? 'center' : 'stretch'
|
||||
}]" @tap="buttonClickHandler(item, index)">
|
||||
<view class="uv-swipe-action-item__right__button__wrapper" :style="[{
|
||||
backgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD',
|
||||
borderRadius: item.style && item.style.borderRadius ? item.style.borderRadius : '0',
|
||||
padding: item.style && item.style.borderRadius ? '0' : '0 15px',
|
||||
}, item.style]">
|
||||
<uv-icon v-if="item.icon" :name="item.icon"
|
||||
:color="item.style && item.style.color ? item.style.color : '#ffffff'"
|
||||
:size="item.iconSize ? $uv.addUnit(item.iconSize) : item.style && item.style.fontSize ? $uv.getPx(item.style.fontSize) * 1.2 : 17"
|
||||
:customStyle="{
|
||||
marginRight: item.text ? '2px' : 0
|
||||
}"></uv-icon>
|
||||
<text v-if="item.text" class="uv-swipe-action-item__right__button__wrapper__text uv-line-1"
|
||||
:style="[{
|
||||
color: item.style && item.style.color ? item.style.color : '#ffffff',
|
||||
fontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px',
|
||||
lineHeight: item.style && item.style.fontSize ? item.style.fontSize : '16px',
|
||||
}]">{{ item.text }}</text>
|
||||
</view>
|
||||
</view>
|
||||
</slot>
|
||||
</view>
|
||||
<!-- #ifndef APP-NVUE -->
|
||||
<view class="uv-swipe-action-item__content" @touchstart="wxs.touchstart" @touchmove="wxs.touchmove"
|
||||
@touchend="wxs.touchend" :status="status" :change:status="wxs.statusChange" :size="size"
|
||||
:change:size="wxs.sizeChange">
|
||||
<!-- #endif -->
|
||||
<!-- #ifdef APP-NVUE -->
|
||||
<view class="uv-swipe-action-item__content" ref="uv-swipe-action-item__content" @panstart="onTouchstart"
|
||||
@tap="clickHandler">
|
||||
<!-- #endif -->
|
||||
<slot />
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<!-- #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ -->
|
||||
<script src="./index.wxs" module="wxs" lang="wxs"></script>
|
||||
<!-- #endif -->
|
||||
<script>
|
||||
import touch from '@/uni_modules/uv-ui-tools/libs/mixin/touch.js'
|
||||
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
|
||||
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
|
||||
import props from './props.js';
|
||||
// #ifdef APP-NVUE
|
||||
import nvue from './nvue.js';
|
||||
// #endif
|
||||
// #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ
|
||||
import wxs from './wxs.js';
|
||||
// #endif
|
||||
/**
|
||||
* SwipeActionItem 滑动单元格子组件
|
||||
* @description 该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作
|
||||
* @tutorial https://www.uvui.cn/components/swipeAction.html
|
||||
* @property {Boolean} show 控制打开或者关闭(默认 false )
|
||||
* @property {String | Number} index 标识符,如果是v-for,可用index索引
|
||||
* @property {Boolean} disabled 是否禁用(默认 false )
|
||||
* @property {Boolean} autoClose 是否自动关闭其他swipe按钮组(默认 true )
|
||||
* @property {Number} threshold 滑动距离阈值,只有大于此值,才被认为是要打开菜单(默认 30 )
|
||||
* @property {Array} options 右侧按钮内容
|
||||
* @property {String | Number} duration 动画过渡时间,单位ms(默认 350 )
|
||||
* @event {Function(index)} open 组件打开时触发
|
||||
* @event {Function(index)} close 组件关闭时触发
|
||||
* @example <uv-swipe-action><uv-swipe-action-item :options="options1" ></uv-swipe-action-item></uv-swipe-action>
|
||||
*/
|
||||
export default {
|
||||
name: 'uv-swipe-action-item',
|
||||
emits: ['click'],
|
||||
// #ifndef APP-NVUE
|
||||
mixins: [mpMixin, mixin, props, touch],
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
mixins: [mpMixin, mixin, props, nvue , touch],
|
||||
// #endif
|
||||
// #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ
|
||||
mixins: [mpMixin, mixin, props, touch, wxs],
|
||||
// #endif
|
||||
data() {
|
||||
return {
|
||||
// 按钮的尺寸信息
|
||||
size: {},
|
||||
// 父组件uv-swipe-action的参数
|
||||
parentData: {
|
||||
autoClose: true,
|
||||
},
|
||||
// 当前状态,open-打开,close-关闭
|
||||
status: 'close',
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 由于wxs无法直接读取外部的值,需要在外部值变化时,重新执行赋值逻辑
|
||||
wxsInit(newValue, oldValue) {
|
||||
this.queryRect()
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
wxsInit() {
|
||||
return [this.disabled, this.autoClose, this.threshold, this.options, this.duration]
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef MP-TOUTIAO
|
||||
this.$uv.error('抖音小程序暂不支持wxs,故该组件暂不支持抖音小程序');
|
||||
// #endif
|
||||
this.init()
|
||||
},
|
||||
methods: {
|
||||
init() {
|
||||
// 初始化父组件数据
|
||||
this.updateParentData()
|
||||
// #ifndef APP-NVUE
|
||||
this.$uv.sleep().then(() => {
|
||||
this.queryRect()
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
updateParentData() {
|
||||
// 此方法在mixin中
|
||||
this.getParentData('uv-swipe-action')
|
||||
},
|
||||
// #ifndef APP-NVUE
|
||||
// 查询节点
|
||||
queryRect() {
|
||||
this.$uvGetRect('.uv-swipe-action-item__right__button', true).then(buttons => {
|
||||
this.size = {
|
||||
buttons,
|
||||
show: this.show,
|
||||
disabled: this.disabled,
|
||||
threshold: this.threshold,
|
||||
duration: this.duration
|
||||
}
|
||||
})
|
||||
},
|
||||
// #endif
|
||||
// 按钮被点击
|
||||
buttonClickHandler(item, index) {
|
||||
this.$emit('click', {
|
||||
index,
|
||||
name: this.name
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$show-lines: 1;
|
||||
@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
|
||||
@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
|
||||
|
||||
.uv-swipe-action-item {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
/* #ifndef APP-NVUE || MP-WEIXIN */
|
||||
touch-action: pan-y;
|
||||
/* #endif */
|
||||
|
||||
&__content {
|
||||
background-color: #FFFFFF;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
&__right {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
@include flex;
|
||||
|
||||
&__button {
|
||||
@include flex;
|
||||
justify-content: center;
|
||||
overflow: hidden;
|
||||
align-items: center;
|
||||
|
||||
&__wrapper {
|
||||
@include flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 15px;
|
||||
|
||||
&__text {
|
||||
@include flex;
|
||||
align-items: center;
|
||||
color: #FFFFFF;
|
||||
font-size: 15px;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,15 @@
|
|||
export default {
|
||||
methods: {
|
||||
// 关闭时执行
|
||||
closeHandler() {
|
||||
this.status = 'close'
|
||||
},
|
||||
setState(status) {
|
||||
this.status = status
|
||||
},
|
||||
closeOther() {
|
||||
// 尝试关闭其他打开的单元格
|
||||
this.parent && this.parent.closeOther(this)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
export default {
|
||||
props: {
|
||||
// 是否自动关闭其他swipe按钮组
|
||||
autoClose: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
...uni.$uv?.props?.swipeAction
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
<template>
|
||||
<view class="uv-swipe-action">
|
||||
<slot></slot>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
|
||||
import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
|
||||
import props from './props.js';
|
||||
/**
|
||||
* SwipeAction 滑动单元格
|
||||
* @description 该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作
|
||||
* @tutorial https://www.uvui.cn/components/swipeAction.html
|
||||
* @property {Boolean} autoClose 是否自动关闭其他swipe按钮组
|
||||
* @event {Function(index)} click 点击组件时触发
|
||||
* @example <uv-swipe-action><uv-swipe-action-item :rightOptions="options1" ></uv-swipe-action-item></uv-swipe-action>
|
||||
*/
|
||||
export default {
|
||||
name: 'uv-swipe-action',
|
||||
mixins: [mpMixin, mixin, props],
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
provide() {
|
||||
return {
|
||||
swipeAction: this
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 这里computed的变量,都是子组件uv-swipe-action-item需要用到的,由于头条小程序的兼容性差异,子组件无法实时监听父组件参数的变化
|
||||
// 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(uv-swipe-action-item)
|
||||
// 拉取父组件新的变化后的参数
|
||||
parentData() {
|
||||
return [this.autoClose]
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
|
||||
parentData() {
|
||||
if (this.children.length) {
|
||||
this.children.map(child => {
|
||||
// 判断子组件(uv-swipe-action-item)如果有updateParentData方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
|
||||
typeof(child.updateParentData) === 'function' && child.updateParentData()
|
||||
})
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.children = []
|
||||
},
|
||||
methods: {
|
||||
closeOther(child) {
|
||||
if (this.autoClose) {
|
||||
// 历遍所有的单元格,找出非当前操作中的单元格,进行关闭
|
||||
this.children.map((item, index) => {
|
||||
if (child !== item) {
|
||||
item.closeHandler()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -0,0 +1,88 @@
|
|||
{
|
||||
"id": "uv-swipe-action",
|
||||
"displayName": "uv-swipe-action 滑动单元格 全面兼容小程序、nvue、vue2、vue3等多端",
|
||||
"version": "1.0.4",
|
||||
"description": "滑动单元格组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作。",
|
||||
"keywords": [
|
||||
"uv-swipe-action",
|
||||
"uvui",
|
||||
"uv-ui",
|
||||
"swipe-action",
|
||||
"滑动单元格"
|
||||
],
|
||||
"repository": "",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.0"
|
||||
},
|
||||
"dcloudext": {
|
||||
"type": "component-vue",
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "插件不采集任何数据",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": ""
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [
|
||||
"uv-ui-tools",
|
||||
"uv-icon"
|
||||
],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"Vue": {
|
||||
"vue2": "y",
|
||||
"vue3": "y"
|
||||
},
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y",
|
||||
"钉钉": "u",
|
||||
"快手": "u",
|
||||
"飞书": "u",
|
||||
"京东": "u"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "u",
|
||||
"联盟": "u"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
## SwipeAction 滑动单元格
|
||||
|
||||
> **组件名:uv-swipe-action**
|
||||
|
||||
该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作。
|
||||
|
||||
### <a href="https://www.uvui.cn/components/swipeAction.html" target="_blank">查看文档</a>
|
||||
|
||||
### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
|
||||
|
||||
#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
|
|
@ -0,0 +1,66 @@
|
|||
## 1.1.20(2023-10-30)
|
||||
1. 1.1.16版本
|
||||
## 1.1.19(2023-10-13)
|
||||
1. 兼容vue3
|
||||
## 1.1.18(2023-10-12)
|
||||
1. 1.1.15版本
|
||||
## 1.1.17(2023-09-27)
|
||||
1. 1.1.14版本发布
|
||||
## 1.1.16(2023-09-15)
|
||||
1. 1.1.13版本发布
|
||||
## 1.1.15(2023-09-15)
|
||||
1. 更新button.js相关按钮支持open-type="agreePrivacyAuthorization"
|
||||
## 1.1.14(2023-09-14)
|
||||
1. 优化dayjs
|
||||
## 1.1.13(2023-09-13)
|
||||
1. 优化,$uv中增加unit参数,方便组件中使用
|
||||
## 1.1.12(2023-09-10)
|
||||
1. 升级版本
|
||||
## 1.1.11(2023-09-04)
|
||||
1. 1.1.11版本
|
||||
## 1.1.10(2023-08-31)
|
||||
1. 修复customStyle和customClass存在冲突的问题
|
||||
## 1.1.9(2023-08-27)
|
||||
1. 版本升级
|
||||
2. 优化
|
||||
## 1.1.8(2023-08-24)
|
||||
1. 版本升级
|
||||
## 1.1.7(2023-08-22)
|
||||
1. 版本升级
|
||||
## 1.1.6(2023-08-18)
|
||||
uvui版本:1.1.6
|
||||
## 1.0.15(2023-08-14)
|
||||
1. 更新uvui版本号
|
||||
## 1.0.13(2023-08-06)
|
||||
1. 优化
|
||||
## 1.0.12(2023-08-06)
|
||||
1. 修改版本号
|
||||
## 1.0.11(2023-08-06)
|
||||
1. 路由增加events参数
|
||||
2. 路由拦截修复
|
||||
## 1.0.10(2023-08-01)
|
||||
1. 优化
|
||||
## 1.0.9(2023-06-28)
|
||||
优化openType.js
|
||||
## 1.0.8(2023-06-15)
|
||||
1. 修改支付宝报错的BUG
|
||||
## 1.0.7(2023-06-07)
|
||||
1. 解决微信小程序使用uvui提示 Some selectors are not allowed in component wxss, including tag name selectors, ID selectors, and attribute selectors
|
||||
2. 解决上述提示,需要在uni.scss配置$uvui-nvue-style: false; 然后在APP.vue下面引入uvui内置的基础样式:@import '@/uni_modules/uv-ui-tools/index.scss';
|
||||
## 1.0.6(2023-06-04)
|
||||
1. uv-ui-tools 优化工具组件,兼容更多功能
|
||||
2. 小程序分享功能优化等
|
||||
## 1.0.5(2023-06-02)
|
||||
1. 修改扩展使用mixin中方法的问题
|
||||
## 1.0.4(2023-05-23)
|
||||
1. 兼容百度小程序修改bem函数
|
||||
## 1.0.3(2023-05-16)
|
||||
1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
|
||||
2. 优化部分功能
|
||||
## 1.0.2(2023-05-10)
|
||||
1. 增加Http请求封装
|
||||
2. 优化
|
||||
## 1.0.1(2023-05-04)
|
||||
1. 修改名称及备注
|
||||
## 1.0.0(2023-05-04)
|
||||
1. uv-ui工具集首次发布
|
|
@ -0,0 +1,6 @@
|
|||
<template>
|
||||
</template>
|
||||
<script>
|
||||
</script>
|
||||
<style>
|
||||
</style>
|
|
@ -0,0 +1,79 @@
|
|||
// 全局挂载引入http相关请求拦截插件
|
||||
import Request from './libs/luch-request'
|
||||
|
||||
// 引入全局mixin
|
||||
import mixin from './libs/mixin/mixin.js'
|
||||
// 小程序特有的mixin
|
||||
import mpMixin from './libs/mixin/mpMixin.js'
|
||||
// #ifdef MP
|
||||
import mpShare from '@/uni_modules/uv-ui-tools/libs/mixin/mpShare.js'
|
||||
// #endif
|
||||
|
||||
// 路由封装
|
||||
import route from './libs/util/route.js'
|
||||
// 公共工具函数
|
||||
import * as index from './libs/function/index.js'
|
||||
// 防抖方法
|
||||
import debounce from './libs/function/debounce.js'
|
||||
// 节流方法
|
||||
import throttle from './libs/function/throttle.js'
|
||||
// 规则检验
|
||||
import * as test from './libs/function/test.js'
|
||||
|
||||
// 颜色渐变相关,colorGradient-颜色渐变,hexToRgb-十六进制颜色转rgb颜色,rgbToHex-rgb转十六进制
|
||||
import * as colorGradient from './libs/function/colorGradient.js'
|
||||
|
||||
// 配置信息
|
||||
import config from './libs/config/config.js'
|
||||
// 平台
|
||||
import platform from './libs/function/platform'
|
||||
|
||||
const $uv = {
|
||||
route,
|
||||
config,
|
||||
test,
|
||||
date: index.timeFormat, // 另名date
|
||||
...index,
|
||||
colorGradient: colorGradient.colorGradient,
|
||||
hexToRgb: colorGradient.hexToRgb,
|
||||
rgbToHex: colorGradient.rgbToHex,
|
||||
colorToRgba: colorGradient.colorToRgba,
|
||||
http: new Request(),
|
||||
debounce,
|
||||
throttle,
|
||||
platform,
|
||||
mixin,
|
||||
mpMixin
|
||||
}
|
||||
uni.$uv = $uv;
|
||||
const install = (Vue,options={}) => {
|
||||
// #ifndef APP-NVUE
|
||||
const cloneMixin = index.deepClone(mixin);
|
||||
delete cloneMixin?.props?.customClass;
|
||||
delete cloneMixin?.props?.customStyle;
|
||||
Vue.mixin(cloneMixin);
|
||||
// #ifdef MP
|
||||
if(options.mpShare){
|
||||
Vue.mixin(mpShare);
|
||||
}
|
||||
// #endif
|
||||
// #endif
|
||||
// #ifdef VUE2
|
||||
// 时间格式化,同时两个名称,date和timeFormat
|
||||
Vue.filter('timeFormat', (timestamp, format) => uni.$uv.timeFormat(timestamp, format));
|
||||
Vue.filter('date', (timestamp, format) => uni.$uv.timeFormat(timestamp, format));
|
||||
// 将多久以前的方法,注入到全局过滤器
|
||||
Vue.filter('timeFrom', (timestamp, format) => uni.$uv.timeFrom(timestamp, format));
|
||||
// 同时挂载到uni和Vue.prototype中
|
||||
// #ifndef APP-NVUE
|
||||
// 只有vue,挂载到Vue.prototype才有意义,因为nvue中全局Vue.prototype和Vue.mixin是无效的
|
||||
Vue.prototype.$uv = $uv;
|
||||
// #endif
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
Vue.config.globalProperties.$uv = $uv;
|
||||
// #endif
|
||||
}
|
||||
export default {
|
||||
install
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
// 引入公共基础类
|
||||
@import "./libs/css/common.scss";
|
||||
|
||||
// 非nvue的样式
|
||||
/* #ifndef APP-NVUE */
|
||||
@import "./libs/css/vue.scss";
|
||||
/* #endif */
|
|
@ -0,0 +1,34 @@
|
|||
// 此版本发布于2023-10-30
|
||||
const version = '1.1.16'
|
||||
|
||||
// 开发环境才提示,生产环境不会提示
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.log(`\n %c uvui V${version} https://www.uvui.cn/ \n\n`, 'color: #ffffff; background: #3c9cff; padding:5px 0; border-radius: 5px;');
|
||||
}
|
||||
|
||||
export default {
|
||||
v: version,
|
||||
version,
|
||||
// 主题名称
|
||||
type: [
|
||||
'primary',
|
||||
'success',
|
||||
'info',
|
||||
'error',
|
||||
'warning'
|
||||
],
|
||||
// 颜色部分,本来可以通过scss的:export导出供js使用,但是奈何nvue不支持
|
||||
color: {
|
||||
'uv-primary': '#2979ff',
|
||||
'uv-warning': '#ff9900',
|
||||
'uv-success': '#19be6b',
|
||||
'uv-error': '#fa3534',
|
||||
'uv-info': '#909399',
|
||||
'uv-main-color': '#303133',
|
||||
'uv-content-color': '#606266',
|
||||
'uv-tips-color': '#909399',
|
||||
'uv-light-color': '#c0c4cc'
|
||||
},
|
||||
// 默认单位,可以通过配置为rpx,那么在用于传入组件大小参数为数值时,就默认为rpx
|
||||
unit: 'px'
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
$uv-main-color: #303133 !default;
|
||||
$uv-content-color: #606266 !default;
|
||||
$uv-tips-color: #909193 !default;
|
||||
$uv-light-color: #c0c4cc !default;
|
||||
$uv-border-color: #dadbde !default;
|
||||
$uv-bg-color: #f3f4f6 !default;
|
||||
$uv-disabled-color: #c8c9cc !default;
|
||||
|
||||
$uv-primary: #3c9cff !default;
|
||||
$uv-primary-dark: #398ade !default;
|
||||
$uv-primary-disabled: #9acafc !default;
|
||||
$uv-primary-light: #ecf5ff !default;
|
||||
|
||||
$uv-warning: #f9ae3d !default;
|
||||
$uv-warning-dark: #f1a532 !default;
|
||||
$uv-warning-disabled: #f9d39b !default;
|
||||
$uv-warning-light: #fdf6ec !default;
|
||||
|
||||
$uv-success: #5ac725 !default;
|
||||
$uv-success-dark: #53c21d !default;
|
||||
$uv-success-disabled: #a9e08f !default;
|
||||
$uv-success-light: #f5fff0;
|
||||
|
||||
$uv-error: #f56c6c !default;
|
||||
$uv-error-dark: #e45656 !default;
|
||||
$uv-error-disabled: #f7b2b2 !default;
|
||||
$uv-error-light: #fef0f0 !default;
|
||||
|
||||
$uv-info: #909399 !default;
|
||||
$uv-info-dark: #767a82 !default;
|
||||
$uv-info-disabled: #c4c6c9 !default;
|
||||
$uv-info-light: #f4f4f5 !default;
|
|
@ -0,0 +1,100 @@
|
|||
// 超出行数,自动显示行尾省略号,最多5行
|
||||
// 来自uvui的温馨提示:当您在控制台看到此报错,说明需要在App.vue的style标签加上【lang="scss"】
|
||||
@for $i from 1 through 5 {
|
||||
.uv-line-#{$i} {
|
||||
/* #ifdef APP-NVUE */
|
||||
// nvue下,可以直接使用lines属性,这是weex特有样式
|
||||
lines: $i;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
/* #endif */
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
// vue下,单行和多行显示省略号需要单独处理
|
||||
@if $i == '1' {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
} @else {
|
||||
display: -webkit-box!important;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
-webkit-line-clamp: $i;
|
||||
-webkit-box-orient: vertical!important;
|
||||
}
|
||||
/* #endif */
|
||||
}
|
||||
}
|
||||
$uv-bordercolor: #dadbde;
|
||||
@if variable-exists(uv-border-color) {
|
||||
$uv-bordercolor: $uv-border-color;
|
||||
}
|
||||
|
||||
// 此处加上!important并非随意乱用,而是因为目前*.nvue页面编译到H5时,
|
||||
// App.vue的样式会被uni-app的view元素的自带border属性覆盖,导致无效
|
||||
// 综上,这是uni-app的缺陷导致我们为了多端兼容,而必须要加上!important
|
||||
// 移动端兼容性较好,直接使用0.5px去实现细边框,不使用伪元素形式实现
|
||||
.uv-border {
|
||||
border-width: 0.5px!important;
|
||||
border-color: $uv-bordercolor!important;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
.uv-border-top {
|
||||
border-top-width: 0.5px!important;
|
||||
border-color: $uv-bordercolor!important;
|
||||
border-top-style: solid;
|
||||
}
|
||||
|
||||
.uv-border-left {
|
||||
border-left-width: 0.5px!important;
|
||||
border-color: $uv-bordercolor!important;
|
||||
border-left-style: solid;
|
||||
}
|
||||
|
||||
.uv-border-right {
|
||||
border-right-width: 0.5px!important;
|
||||
border-color: $uv-bordercolor!important;
|
||||
border-right-style: solid;
|
||||
}
|
||||
|
||||
.uv-border-bottom {
|
||||
border-bottom-width: 0.5px!important;
|
||||
border-color: $uv-bordercolor!important;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
.uv-border-top-bottom {
|
||||
border-top-width: 0.5px!important;
|
||||
border-bottom-width: 0.5px!important;
|
||||
border-color: $uv-bordercolor!important;
|
||||
border-top-style: solid;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
|
||||
// 去除button的所有默认样式,让其表现跟普通的view、text元素一样
|
||||
.uv-reset-button {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
/* #ifndef APP-PLUS */
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
border-width: 0;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uv-reset-button::after {
|
||||
border: none;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
.uv-hover-class {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
@mixin flex($direction: row) {
|
||||
/* #ifndef APP-NVUE */
|
||||
display: flex;
|
||||
/* #endif */
|
||||
flex-direction: $direction;
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
// 由于uvui是基于nvue环境进行开发的,此环境中普通元素默认为flex-direction: column;
|
||||
// 所以在非nvue中,需要对元素进行重置为flex-direction: column; 否则可能会表现异常
|
||||
$uvui-nvue-style: true !default;
|
||||
@if $uvui-nvue-style == true {
|
||||
view, scroll-view, swiper-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
flex-basis: auto;
|
||||
align-items: stretch;
|
||||
align-content: flex-start;
|
||||
}
|
||||
}
|
||||
/* #endif */
|
|
@ -0,0 +1,111 @@
|
|||
// 超出行数,自动显示行尾省略号,最多5行
|
||||
// 来自uvui的温馨提示:当您在控制台看到此报错,说明需要在App.vue的style标签加上【lang="scss"】
|
||||
@if variable-exists(show-lines) {
|
||||
@for $i from 1 through 5 {
|
||||
.uv-line-#{$i} {
|
||||
/* #ifdef APP-NVUE */
|
||||
// nvue下,可以直接使用lines属性,这是weex特有样式
|
||||
lines: $i;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
flex: 1;
|
||||
/* #endif */
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
// vue下,单行和多行显示省略号需要单独处理
|
||||
@if $i == '1' {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
} @else {
|
||||
display: -webkit-box!important;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
word-break: break-all;
|
||||
-webkit-line-clamp: $i;
|
||||
-webkit-box-orient: vertical!important;
|
||||
}
|
||||
/* #endif */
|
||||
}
|
||||
}
|
||||
}
|
||||
@if variable-exists(show-border) {
|
||||
$uv-bordercolor: #dadbde;
|
||||
@if variable-exists(uv-border-color) {
|
||||
$uv-bordercolor: $uv-border-color;
|
||||
}
|
||||
// 此处加上!important并非随意乱用,而是因为目前*.nvue页面编译到H5时,
|
||||
// App.vue的样式会被uni-app的view元素的自带border属性覆盖,导致无效
|
||||
// 综上,这是uni-app的缺陷导致我们为了多端兼容,而必须要加上!important
|
||||
// 移动端兼容性较好,直接使用0.5px去实现细边框,不使用伪元素形式实现
|
||||
@if variable-exists(show-border-surround) {
|
||||
.uv-border {
|
||||
border-width: 0.5px!important;
|
||||
border-color: $uv-bordercolor!important;
|
||||
border-style: solid;
|
||||
}
|
||||
}
|
||||
@if variable-exists(show-border-top) {
|
||||
.uv-border-top {
|
||||
border-top-width: 0.5px!important;
|
||||
border-color: $uv-bordercolor!important;
|
||||
border-top-style: solid;
|
||||
}
|
||||
}
|
||||
@if variable-exists(show-border-left) {
|
||||
.uv-border-left {
|
||||
border-left-width: 0.5px!important;
|
||||
border-color: $uv-bordercolor!important;
|
||||
border-left-style: solid;
|
||||
}
|
||||
}
|
||||
@if variable-exists(show-border-right) {
|
||||
.uv-border-right {
|
||||
border-right-width: 0.5px!important;
|
||||
border-color: $uv-bordercolor!important;
|
||||
border-right-style: solid;
|
||||
}
|
||||
}
|
||||
@if variable-exists(show-border-bottom) {
|
||||
.uv-border-bottom {
|
||||
border-bottom-width: 0.5px!important;
|
||||
border-color: $uv-bordercolor!important;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
}
|
||||
@if variable-exists(show-border-top-bottom) {
|
||||
.uv-border-top-bottom {
|
||||
border-top-width: 0.5px!important;
|
||||
border-bottom-width: 0.5px!important;
|
||||
border-color: $uv-bordercolor!important;
|
||||
border-top-style: solid;
|
||||
border-bottom-style: solid;
|
||||
}
|
||||
}
|
||||
}
|
||||
@if variable-exists(show-reset-button) {
|
||||
// 去除button的所有默认样式,让其表现跟普通的view、text元素一样
|
||||
.uv-reset-button {
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
/* #ifndef APP-PLUS */
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
color: inherit;
|
||||
/* #endif */
|
||||
/* #ifdef APP-NVUE */
|
||||
border-width: 0;
|
||||
/* #endif */
|
||||
}
|
||||
|
||||
/* #ifndef APP-NVUE */
|
||||
.uv-reset-button::after {
|
||||
border: none;
|
||||
}
|
||||
/* #endif */
|
||||
}
|
||||
@if variable-exists(show-hover) {
|
||||
.uv-hover-class {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
// 历遍生成4个方向的底部安全区
|
||||
@each $d in top, right, bottom, left {
|
||||
.uv-safe-area-inset-#{$d} {
|
||||
padding-#{$d}: 0;
|
||||
padding-#{$d}: constant(safe-area-inset-#{$d});
|
||||
padding-#{$d}: env(safe-area-inset-#{$d});
|
||||
}
|
||||
}
|
||||
|
||||
//提升H5端uni.toast()的层级,避免被uvui的modal等遮盖
|
||||
/* #ifdef H5 */
|
||||
uni-toast {
|
||||
z-index: 10090;
|
||||
}
|
||||
uni-toast .uni-toast {
|
||||
z-index: 10090;
|
||||
}
|
||||
/* #endif */
|
||||
|
||||
// 隐藏scroll-view的滚动条
|
||||
::-webkit-scrollbar {
|
||||
display: none;
|
||||
width: 0 !important;
|
||||
height: 0 !important;
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
$uvui-nvue-style: true !default;
|
||||
@if $uvui-nvue-style == false {
|
||||
view, scroll-view, swiper-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
flex-basis: auto;
|
||||
align-items: stretch;
|
||||
align-content: flex-start;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/**
|
||||
* 求两个颜色之间的渐变值
|
||||
* @param {string} startColor 开始的颜色
|
||||
* @param {string} endColor 结束的颜色
|
||||
* @param {number} step 颜色等分的份额
|
||||
* */
|
||||
function colorGradient(startColor = 'rgb(0, 0, 0)', endColor = 'rgb(255, 255, 255)', step = 10) {
|
||||
const startRGB = hexToRgb(startColor, false) // 转换为rgb数组模式
|
||||
const startR = startRGB[0]
|
||||
const startG = startRGB[1]
|
||||
const startB = startRGB[2]
|
||||
|
||||
const endRGB = hexToRgb(endColor, false)
|
||||
const endR = endRGB[0]
|
||||
const endG = endRGB[1]
|
||||
const endB = endRGB[2]
|
||||
|
||||
const sR = (endR - startR) / step // 总差值
|
||||
const sG = (endG - startG) / step
|
||||
const sB = (endB - startB) / step
|
||||
const colorArr = []
|
||||
for (let i = 0; i < step; i++) {
|
||||
// 计算每一步的hex值
|
||||
let hex = rgbToHex(`rgb(${Math.round((sR * i + startR))},${Math.round((sG * i + startG))},${Math.round((sB
|
||||
* i + startB))})`)
|
||||
// 确保第一个颜色值为startColor的值
|
||||
if (i === 0) hex = rgbToHex(startColor)
|
||||
// 确保最后一个颜色值为endColor的值
|
||||
if (i === step - 1) hex = rgbToHex(endColor)
|
||||
colorArr.push(hex)
|
||||
}
|
||||
return colorArr
|
||||
}
|
||||
|
||||
// 将hex表示方式转换为rgb表示方式(这里返回rgb数组模式)
|
||||
function hexToRgb(sColor, str = true) {
|
||||
const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
|
||||
sColor = String(sColor).toLowerCase()
|
||||
if (sColor && reg.test(sColor)) {
|
||||
if (sColor.length === 4) {
|
||||
let sColorNew = '#'
|
||||
for (let i = 1; i < 4; i += 1) {
|
||||
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
|
||||
}
|
||||
sColor = sColorNew
|
||||
}
|
||||
// 处理六位的颜色值
|
||||
const sColorChange = []
|
||||
for (let i = 1; i < 7; i += 2) {
|
||||
sColorChange.push(parseInt(`0x${sColor.slice(i, i + 2)}`))
|
||||
}
|
||||
if (!str) {
|
||||
return sColorChange
|
||||
}
|
||||
return `rgb(${sColorChange[0]},${sColorChange[1]},${sColorChange[2]})`
|
||||
} if (/^(rgb|RGB)/.test(sColor)) {
|
||||
const arr = sColor.replace(/(?:\(|\)|rgb|RGB)*/g, '').split(',')
|
||||
return arr.map((val) => Number(val))
|
||||
}
|
||||
return sColor
|
||||
}
|
||||
|
||||
// 将rgb表示方式转换为hex表示方式
|
||||
function rgbToHex(rgb) {
|
||||
const _this = rgb
|
||||
const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
|
||||
if (/^(rgb|RGB)/.test(_this)) {
|
||||
const aColor = _this.replace(/(?:\(|\)|rgb|RGB)*/g, '').split(',')
|
||||
let strHex = '#'
|
||||
for (let i = 0; i < aColor.length; i++) {
|
||||
let hex = Number(aColor[i]).toString(16)
|
||||
hex = String(hex).length == 1 ? `${0}${hex}` : hex // 保证每个rgb的值为2位
|
||||
if (hex === '0') {
|
||||
hex += hex
|
||||
}
|
||||
strHex += hex
|
||||
}
|
||||
if (strHex.length !== 7) {
|
||||
strHex = _this
|
||||
}
|
||||
return strHex
|
||||
} if (reg.test(_this)) {
|
||||
const aNum = _this.replace(/#/, '').split('')
|
||||
if (aNum.length === 6) {
|
||||
return _this
|
||||
} if (aNum.length === 3) {
|
||||
let numHex = '#'
|
||||
for (let i = 0; i < aNum.length; i += 1) {
|
||||
numHex += (aNum[i] + aNum[i])
|
||||
}
|
||||
return numHex
|
||||
}
|
||||
} else {
|
||||
return _this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* JS颜色十六进制转换为rgb或rgba,返回的格式为 rgba(255,255,255,0.5)字符串
|
||||
* sHex为传入的十六进制的色值
|
||||
* alpha为rgba的透明度
|
||||
*/
|
||||
function colorToRgba(color, alpha) {
|
||||
color = rgbToHex(color)
|
||||
// 十六进制颜色值的正则表达式
|
||||
const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
|
||||
/* 16进制颜色转为RGB格式 */
|
||||
let sColor = String(color).toLowerCase()
|
||||
if (sColor && reg.test(sColor)) {
|
||||
if (sColor.length === 4) {
|
||||
let sColorNew = '#'
|
||||
for (let i = 1; i < 4; i += 1) {
|
||||
sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
|
||||
}
|
||||
sColor = sColorNew
|
||||
}
|
||||
// 处理六位的颜色值
|
||||
const sColorChange = []
|
||||
for (let i = 1; i < 7; i += 2) {
|
||||
sColorChange.push(parseInt(`0x${sColor.slice(i, i + 2)}`))
|
||||
}
|
||||
// return sColorChange.join(',')
|
||||
return `rgba(${sColorChange.join(',')},${alpha})`
|
||||
}
|
||||
|
||||
return sColor
|
||||
}
|
||||
|
||||
export {
|
||||
colorGradient,
|
||||
hexToRgb,
|
||||
rgbToHex,
|
||||
colorToRgba
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
let timeout = null
|
||||
|
||||
/**
|
||||
* 防抖原理:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数
|
||||
*
|
||||
* @param {Function} func 要执行的回调函数
|
||||
* @param {Number} wait 延时的时间
|
||||
* @param {Boolean} immediate 是否立即执行
|
||||
* @return null
|
||||
*/
|
||||
function debounce(func, wait = 500, immediate = false) {
|
||||
// 清除定时器
|
||||
if (timeout !== null) clearTimeout(timeout)
|
||||
// 立即执行,此类情况一般用不到
|
||||
if (immediate) {
|
||||
const callNow = !timeout
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null
|
||||
}, wait)
|
||||
if (callNow) typeof func === 'function' && func()
|
||||
} else {
|
||||
// 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法
|
||||
timeout = setTimeout(() => {
|
||||
typeof func === 'function' && func()
|
||||
}, wait)
|
||||
}
|
||||
}
|
||||
|
||||
export default debounce
|
|
@ -0,0 +1,167 @@
|
|||
let _boundaryCheckingState = true; // 是否进行越界检查的全局开关
|
||||
|
||||
/**
|
||||
* 把错误的数据转正
|
||||
* @private
|
||||
* @example strip(0.09999999999999998)=0.1
|
||||
*/
|
||||
function strip(num, precision = 15) {
|
||||
return +parseFloat(Number(num).toPrecision(precision));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return digits length of a number
|
||||
* @private
|
||||
* @param {*number} num Input number
|
||||
*/
|
||||
function digitLength(num) {
|
||||
// Get digit length of e
|
||||
const eSplit = num.toString().split(/[eE]/);
|
||||
const len = (eSplit[0].split('.')[1] || '').length - +(eSplit[1] || 0);
|
||||
return len > 0 ? len : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 把小数转成整数,如果是小数则放大成整数
|
||||
* @private
|
||||
* @param {*number} num 输入数
|
||||
*/
|
||||
function float2Fixed(num) {
|
||||
if (num.toString().indexOf('e') === -1) {
|
||||
return Number(num.toString().replace('.', ''));
|
||||
}
|
||||
const dLen = digitLength(num);
|
||||
return dLen > 0 ? strip(Number(num) * Math.pow(10, dLen)) : Number(num);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测数字是否越界,如果越界给出提示
|
||||
* @private
|
||||
* @param {*number} num 输入数
|
||||
*/
|
||||
function checkBoundary(num) {
|
||||
if (_boundaryCheckingState) {
|
||||
if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
|
||||
console.warn(`${num} 超出了精度限制,结果可能不正确`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 把递归操作扁平迭代化
|
||||
* @param {number[]} arr 要操作的数字数组
|
||||
* @param {function} operation 迭代操作
|
||||
* @private
|
||||
*/
|
||||
function iteratorOperation(arr, operation) {
|
||||
const [num1, num2, ...others] = arr;
|
||||
let res = operation(num1, num2);
|
||||
|
||||
others.forEach((num) => {
|
||||
res = operation(res, num);
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* 高精度乘法
|
||||
* @export
|
||||
*/
|
||||
export function times(...nums) {
|
||||
if (nums.length > 2) {
|
||||
return iteratorOperation(nums, times);
|
||||
}
|
||||
|
||||
const [num1, num2] = nums;
|
||||
const num1Changed = float2Fixed(num1);
|
||||
const num2Changed = float2Fixed(num2);
|
||||
const baseNum = digitLength(num1) + digitLength(num2);
|
||||
const leftValue = num1Changed * num2Changed;
|
||||
|
||||
checkBoundary(leftValue);
|
||||
|
||||
return leftValue / Math.pow(10, baseNum);
|
||||
}
|
||||
|
||||
/**
|
||||
* 高精度加法
|
||||
* @export
|
||||
*/
|
||||
export function plus(...nums) {
|
||||
if (nums.length > 2) {
|
||||
return iteratorOperation(nums, plus);
|
||||
}
|
||||
|
||||
const [num1, num2] = nums;
|
||||
// 取最大的小数位
|
||||
const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));
|
||||
// 把小数都转为整数然后再计算
|
||||
return (times(num1, baseNum) + times(num2, baseNum)) / baseNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 高精度减法
|
||||
* @export
|
||||
*/
|
||||
export function minus(...nums) {
|
||||
if (nums.length > 2) {
|
||||
return iteratorOperation(nums, minus);
|
||||
}
|
||||
|
||||
const [num1, num2] = nums;
|
||||
const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));
|
||||
return (times(num1, baseNum) - times(num2, baseNum)) / baseNum;
|
||||
}
|
||||
|
||||
/**
|
||||
* 高精度除法
|
||||
* @export
|
||||
*/
|
||||
export function divide(...nums) {
|
||||
if (nums.length > 2) {
|
||||
return iteratorOperation(nums, divide);
|
||||
}
|
||||
|
||||
const [num1, num2] = nums;
|
||||
const num1Changed = float2Fixed(num1);
|
||||
const num2Changed = float2Fixed(num2);
|
||||
checkBoundary(num1Changed);
|
||||
checkBoundary(num2Changed);
|
||||
// 重要,这里必须用strip进行修正
|
||||
return times(num1Changed / num2Changed, strip(Math.pow(10, digitLength(num2) - digitLength(num1))));
|
||||
}
|
||||
|
||||
/**
|
||||
* 四舍五入
|
||||
* @export
|
||||
*/
|
||||
export function round(num, ratio) {
|
||||
const base = Math.pow(10, ratio);
|
||||
let result = divide(Math.round(Math.abs(times(num, base))), base);
|
||||
if (num < 0 && result !== 0) {
|
||||
result = times(result, -1);
|
||||
}
|
||||
// 位数不足则补0
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否进行边界检查,默认开启
|
||||
* @param flag 标记开关,true 为开启,false 为关闭,默认为 true
|
||||
* @export
|
||||
*/
|
||||
export function enableBoundaryChecking(flag = true) {
|
||||
_boundaryCheckingState = flag;
|
||||
}
|
||||
|
||||
|
||||
export default {
|
||||
times,
|
||||
plus,
|
||||
minus,
|
||||
divide,
|
||||
round,
|
||||
enableBoundaryChecking,
|
||||
};
|
||||
|
|
@ -0,0 +1,734 @@
|
|||
import { number, empty } from './test.js'
|
||||
import { round } from './digit.js'
|
||||
/**
|
||||
* @description 如果value小于min,取min;如果value大于max,取max
|
||||
* @param {number} min
|
||||
* @param {number} max
|
||||
* @param {number} value
|
||||
*/
|
||||
function range(min = 0, max = 0, value = 0) {
|
||||
return Math.max(min, Math.min(max, Number(value)))
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 用于获取用户传递值的px值 如果用户传递了"xxpx"或者"xxrpx",取出其数值部分,如果是"xxxrpx"还需要用过uni.upx2px进行转换
|
||||
* @param {number|string} value 用户传递值的px值
|
||||
* @param {boolean} unit
|
||||
* @returns {number|string}
|
||||
*/
|
||||
function getPx(value, unit = false) {
|
||||
if (number(value)) {
|
||||
return unit ? `${value}px` : Number(value)
|
||||
}
|
||||
// 如果带有rpx,先取出其数值部分,再转为px值
|
||||
if (/(rpx|upx)$/.test(value)) {
|
||||
return unit ? `${uni.upx2px(parseInt(value))}px` : Number(uni.upx2px(parseInt(value)))
|
||||
}
|
||||
return unit ? `${parseInt(value)}px` : parseInt(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 进行延时,以达到可以简写代码的目的 比如: await uni.$uv.sleep(20)将会阻塞20ms
|
||||
* @param {number} value 堵塞时间 单位ms 毫秒
|
||||
* @returns {Promise} 返回promise
|
||||
*/
|
||||
function sleep(value = 30) {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve()
|
||||
}, value)
|
||||
})
|
||||
}
|
||||
/**
|
||||
* @description 运行期判断平台
|
||||
* @returns {string} 返回所在平台(小写)
|
||||
* @link 运行期判断平台 https://uniapp.dcloud.io/frame?id=判断平台
|
||||
*/
|
||||
function os() {
|
||||
return uni.getSystemInfoSync().platform.toLowerCase()
|
||||
}
|
||||
/**
|
||||
* @description 获取系统信息同步接口
|
||||
* @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync
|
||||
*/
|
||||
function sys() {
|
||||
return uni.getSystemInfoSync()
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 取一个区间数
|
||||
* @param {Number} min 最小值
|
||||
* @param {Number} max 最大值
|
||||
*/
|
||||
function random(min, max) {
|
||||
if (min >= 0 && max > 0 && max >= min) {
|
||||
const gab = max - min + 1
|
||||
return Math.floor(Math.random() * gab + min)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Number} len uuid的长度
|
||||
* @param {Boolean} firstU 将返回的首字母置为"u"
|
||||
* @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
|
||||
*/
|
||||
function guid(len = 32, firstU = true, radix = null) {
|
||||
const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
|
||||
const uuid = []
|
||||
radix = radix || chars.length
|
||||
|
||||
if (len) {
|
||||
// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
|
||||
for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
|
||||
} else {
|
||||
let r
|
||||
// rfc4122标准要求返回的uuid中,某些位为固定的字符
|
||||
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
|
||||
uuid[14] = '4'
|
||||
|
||||
for (let i = 0; i < 36; i++) {
|
||||
if (!uuid[i]) {
|
||||
r = 0 | Math.random() * 16
|
||||
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]
|
||||
}
|
||||
}
|
||||
}
|
||||
// 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
|
||||
if (firstU) {
|
||||
uuid.shift()
|
||||
return `u${uuid.join('')}`
|
||||
}
|
||||
return uuid.join('')
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取父组件的参数,因为支付宝小程序不支持provide/inject的写法
|
||||
this.$parent在非H5中,可以准确获取到父组件,但是在H5中,需要多次this.$parent.$parent.xxx
|
||||
这里默认值等于undefined有它的含义,因为最顶层元素(组件)的$parent就是undefined,意味着不传name
|
||||
值(默认为undefined),就是查找最顶层的$parent
|
||||
* @param {string|undefined} name 父组件的参数名
|
||||
*/
|
||||
function $parent(name = undefined) {
|
||||
let parent = this.$parent
|
||||
// 通过while历遍,这里主要是为了H5需要多层解析的问题
|
||||
while (parent) {
|
||||
// 父组件
|
||||
if (parent.$options && parent.$options.name !== name) {
|
||||
// 如果组件的name不相等,继续上一级寻找
|
||||
parent = parent.$parent
|
||||
} else {
|
||||
return parent
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 样式转换
|
||||
* 对象转字符串,或者字符串转对象
|
||||
* @param {object | string} customStyle 需要转换的目标
|
||||
* @param {String} target 转换的目的,object-转为对象,string-转为字符串
|
||||
* @returns {object|string}
|
||||
*/
|
||||
function addStyle(customStyle, target = 'object') {
|
||||
// 字符串转字符串,对象转对象情形,直接返回
|
||||
if (empty(customStyle) || typeof(customStyle) === 'object' && target === 'object' || target === 'string' &&
|
||||
typeof(customStyle) === 'string') {
|
||||
return customStyle
|
||||
}
|
||||
// 字符串转对象
|
||||
if (target === 'object') {
|
||||
// 去除字符串样式中的两端空格(中间的空格不能去掉,比如padding: 20px 0如果去掉了就错了),空格是无用的
|
||||
customStyle = trim(customStyle)
|
||||
// 根据";"将字符串转为数组形式
|
||||
const styleArray = customStyle.split(';')
|
||||
const style = {}
|
||||
// 历遍数组,拼接成对象
|
||||
for (let i = 0; i < styleArray.length; i++) {
|
||||
// 'font-size:20px;color:red;',如此最后字符串有";"的话,会导致styleArray最后一个元素为空字符串,这里需要过滤
|
||||
if (styleArray[i]) {
|
||||
const item = styleArray[i].split(':')
|
||||
style[trim(item[0])] = trim(item[1])
|
||||
}
|
||||
}
|
||||
return style
|
||||
}
|
||||
// 这里为对象转字符串形式
|
||||
let string = ''
|
||||
for (const i in customStyle) {
|
||||
// 驼峰转为中划线的形式,否则css内联样式,无法识别驼峰样式属性名
|
||||
const key = i.replace(/([A-Z])/g, '-$1').toLowerCase()
|
||||
string += `${key}:${customStyle[i]};`
|
||||
}
|
||||
// 去除两端空格
|
||||
return trim(string)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 添加单位,如果有rpx,upx,%,px等单位结尾或者值为auto,直接返回,否则加上px单位结尾
|
||||
* @param {string|number} value 需要添加单位的值
|
||||
* @param {string} unit 添加的单位名 比如px
|
||||
*/
|
||||
function addUnit(value = 'auto', unit = uni?.$uv?.config?.unit ? uni?.$uv?.config?.unit : 'px') {
|
||||
value = String(value)
|
||||
// 用uvui内置验证规则中的number判断是否为数值
|
||||
return number(value) ? `${value}${unit}` : value
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 深度克隆
|
||||
* @param {object} obj 需要深度克隆的对象
|
||||
* @param cache 缓存
|
||||
* @returns {*} 克隆后的对象或者原值(不是对象)
|
||||
*/
|
||||
function deepClone(obj, cache = new WeakMap()) {
|
||||
if (obj === null || typeof obj !== 'object') return obj;
|
||||
if (cache.has(obj)) return cache.get(obj);
|
||||
let clone;
|
||||
if (obj instanceof Date) {
|
||||
clone = new Date(obj.getTime());
|
||||
} else if (obj instanceof RegExp) {
|
||||
clone = new RegExp(obj);
|
||||
} else if (obj instanceof Map) {
|
||||
clone = new Map(Array.from(obj, ([key, value]) => [key, deepClone(value, cache)]));
|
||||
} else if (obj instanceof Set) {
|
||||
clone = new Set(Array.from(obj, value => deepClone(value, cache)));
|
||||
} else if (Array.isArray(obj)) {
|
||||
clone = obj.map(value => deepClone(value, cache));
|
||||
} else if (Object.prototype.toString.call(obj) === '[object Object]') {
|
||||
clone = Object.create(Object.getPrototypeOf(obj));
|
||||
cache.set(obj, clone);
|
||||
for (const [key, value] of Object.entries(obj)) {
|
||||
clone[key] = deepClone(value, cache);
|
||||
}
|
||||
} else {
|
||||
clone = Object.assign({}, obj);
|
||||
}
|
||||
cache.set(obj, clone);
|
||||
return clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description JS对象深度合并
|
||||
* @param {object} target 需要拷贝的对象
|
||||
* @param {object} source 拷贝的来源对象
|
||||
* @returns {object|boolean} 深度合并后的对象或者false(入参有不是对象)
|
||||
*/
|
||||
function deepMerge(target = {}, source = {}) {
|
||||
target = deepClone(target)
|
||||
if (typeof target !== 'object' || target === null || typeof source !== 'object' || source === null) return target;
|
||||
const merged = Array.isArray(target) ? target.slice() : Object.assign({}, target);
|
||||
for (const prop in source) {
|
||||
if (!source.hasOwnProperty(prop)) continue;
|
||||
const sourceValue = source[prop];
|
||||
const targetValue = merged[prop];
|
||||
if (sourceValue instanceof Date) {
|
||||
merged[prop] = new Date(sourceValue);
|
||||
} else if (sourceValue instanceof RegExp) {
|
||||
merged[prop] = new RegExp(sourceValue);
|
||||
} else if (sourceValue instanceof Map) {
|
||||
merged[prop] = new Map(sourceValue);
|
||||
} else if (sourceValue instanceof Set) {
|
||||
merged[prop] = new Set(sourceValue);
|
||||
} else if (typeof sourceValue === 'object' && sourceValue !== null) {
|
||||
merged[prop] = deepMerge(targetValue, sourceValue);
|
||||
} else {
|
||||
merged[prop] = sourceValue;
|
||||
}
|
||||
}
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description error提示
|
||||
* @param {*} err 错误内容
|
||||
*/
|
||||
function error(err) {
|
||||
// 开发环境才提示,生产环境不会提示
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
console.error(`uvui提示:${err}`)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 打乱数组
|
||||
* @param {array} array 需要打乱的数组
|
||||
* @returns {array} 打乱后的数组
|
||||
*/
|
||||
function randomArray(array = []) {
|
||||
// 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0
|
||||
return array.sort(() => Math.random() - 0.5)
|
||||
}
|
||||
|
||||
// padStart 的 polyfill,因为某些机型或情况,还无法支持es7的padStart,比如电脑版的微信小程序
|
||||
// 所以这里做一个兼容polyfill的兼容处理
|
||||
if (!String.prototype.padStart) {
|
||||
// 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解
|
||||
String.prototype.padStart = function(maxLength, fillString = ' ') {
|
||||
if (Object.prototype.toString.call(fillString) !== '[object String]') {
|
||||
throw new TypeError(
|
||||
'fillString must be String'
|
||||
)
|
||||
}
|
||||
const str = this
|
||||
// 返回 String(str) 这里是为了使返回的值是字符串字面量,在控制台中更符合直觉
|
||||
if (str.length >= maxLength) return String(str)
|
||||
|
||||
const fillLength = maxLength - str.length
|
||||
let times = Math.ceil(fillLength / fillString.length)
|
||||
while (times >>= 1) {
|
||||
fillString += fillString
|
||||
if (times === 1) {
|
||||
fillString += fillString
|
||||
}
|
||||
}
|
||||
return fillString.slice(0, fillLength) + str
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 格式化时间
|
||||
* @param {String|Number} dateTime 需要格式化的时间戳
|
||||
* @param {String} fmt 格式化规则 yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 默认yyyy-mm-dd
|
||||
* @returns {string} 返回格式化后的字符串
|
||||
*/
|
||||
function timeFormat(dateTime = null, formatStr = 'yyyy-mm-dd') {
|
||||
let date
|
||||
// 若传入时间为假值,则取当前时间
|
||||
if (!dateTime) {
|
||||
date = new Date()
|
||||
}
|
||||
// 若为unix秒时间戳,则转为毫秒时间戳(逻辑有点奇怪,但不敢改,以保证历史兼容)
|
||||
else if (/^\d{10}$/.test(dateTime?.toString().trim())) {
|
||||
date = new Date(dateTime * 1000)
|
||||
}
|
||||
// 若用户传入字符串格式时间戳,new Date无法解析,需做兼容
|
||||
else if (typeof dateTime === 'string' && /^\d+$/.test(dateTime.trim())) {
|
||||
date = new Date(Number(dateTime))
|
||||
}
|
||||
// 处理平台性差异,在Safari/Webkit中,new Date仅支持/作为分割符的字符串时间
|
||||
// 处理 '2022-07-10 01:02:03',跳过 '2022-07-10T01:02:03'
|
||||
else if (typeof dateTime === 'string' && dateTime.includes('-') && !dateTime.includes('T')) {
|
||||
date = new Date(dateTime.replace(/-/g, '/'))
|
||||
}
|
||||
// 其他都认为符合 RFC 2822 规范
|
||||
else {
|
||||
date = new Date(dateTime)
|
||||
}
|
||||
|
||||
const timeSource = {
|
||||
'y': date.getFullYear().toString(), // 年
|
||||
'm': (date.getMonth() + 1).toString().padStart(2, '0'), // 月
|
||||
'd': date.getDate().toString().padStart(2, '0'), // 日
|
||||
'h': date.getHours().toString().padStart(2, '0'), // 时
|
||||
'M': date.getMinutes().toString().padStart(2, '0'), // 分
|
||||
's': date.getSeconds().toString().padStart(2, '0') // 秒
|
||||
// 有其他格式化字符需求可以继续添加,必须转化成字符串
|
||||
}
|
||||
|
||||
for (const key in timeSource) {
|
||||
const [ret] = new RegExp(`${key}+`).exec(formatStr) || []
|
||||
if (ret) {
|
||||
// 年可能只需展示两位
|
||||
const beginIndex = key === 'y' && ret.length === 2 ? 2 : 0
|
||||
formatStr = formatStr.replace(ret, timeSource[key].slice(beginIndex))
|
||||
}
|
||||
}
|
||||
|
||||
return formatStr
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 时间戳转为多久之前
|
||||
* @param {String|Number} timestamp 时间戳
|
||||
* @param {String|Boolean} format
|
||||
* 格式化规则如果为时间格式字符串,超出一定时间范围,返回固定的时间格式;
|
||||
* 如果为布尔值false,无论什么时间,都返回多久以前的格式
|
||||
* @returns {string} 转化后的内容
|
||||
*/
|
||||
function timeFrom(timestamp = null, format = 'yyyy-mm-dd') {
|
||||
if (timestamp == null) timestamp = Number(new Date())
|
||||
timestamp = parseInt(timestamp)
|
||||
// 判断用户输入的时间戳是秒还是毫秒,一般前端js获取的时间戳是毫秒(13位),后端传过来的为秒(10位)
|
||||
if (timestamp.toString().length == 10) timestamp *= 1000
|
||||
let timer = (new Date()).getTime() - timestamp
|
||||
timer = parseInt(timer / 1000)
|
||||
// 如果小于5分钟,则返回"刚刚",其他以此类推
|
||||
let tips = ''
|
||||
switch (true) {
|
||||
case timer < 300:
|
||||
tips = '刚刚'
|
||||
break
|
||||
case timer >= 300 && timer < 3600:
|
||||
tips = `${parseInt(timer / 60)}分钟前`
|
||||
break
|
||||
case timer >= 3600 && timer < 86400:
|
||||
tips = `${parseInt(timer / 3600)}小时前`
|
||||
break
|
||||
case timer >= 86400 && timer < 2592000:
|
||||
tips = `${parseInt(timer / 86400)}天前`
|
||||
break
|
||||
default:
|
||||
// 如果format为false,则无论什么时间戳,都显示xx之前
|
||||
if (format === false) {
|
||||
if (timer >= 2592000 && timer < 365 * 86400) {
|
||||
tips = `${parseInt(timer / (86400 * 30))}个月前`
|
||||
} else {
|
||||
tips = `${parseInt(timer / (86400 * 365))}年前`
|
||||
}
|
||||
} else {
|
||||
tips = timeFormat(timestamp, format)
|
||||
}
|
||||
}
|
||||
return tips
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 去除空格
|
||||
* @param String str 需要去除空格的字符串
|
||||
* @param String pos both(左右)|left|right|all 默认both
|
||||
*/
|
||||
function trim(str, pos = 'both') {
|
||||
str = String(str)
|
||||
if (pos == 'both') {
|
||||
return str.replace(/^\s+|\s+$/g, '')
|
||||
}
|
||||
if (pos == 'left') {
|
||||
return str.replace(/^\s*/, '')
|
||||
}
|
||||
if (pos == 'right') {
|
||||
return str.replace(/(\s*$)/g, '')
|
||||
}
|
||||
if (pos == 'all') {
|
||||
return str.replace(/\s+/g, '')
|
||||
}
|
||||
return str
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 对象转url参数
|
||||
* @param {object} data,对象
|
||||
* @param {Boolean} isPrefix,是否自动加上"?"
|
||||
* @param {string} arrayFormat 规则 indices|brackets|repeat|comma
|
||||
*/
|
||||
function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {
|
||||
const prefix = isPrefix ? '?' : ''
|
||||
const _result = []
|
||||
if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets'
|
||||
for (const key in data) {
|
||||
const value = data[key]
|
||||
// 去掉为空的参数
|
||||
if (['', undefined, null].indexOf(value) >= 0) {
|
||||
continue
|
||||
}
|
||||
// 如果值为数组,另行处理
|
||||
if (value.constructor === Array) {
|
||||
// e.g. {ids: [1, 2, 3]}
|
||||
switch (arrayFormat) {
|
||||
case 'indices':
|
||||
// 结果: ids[0]=1&ids[1]=2&ids[2]=3
|
||||
for (let i = 0; i < value.length; i++) {
|
||||
_result.push(`${key}[${i}]=${value[i]}`)
|
||||
}
|
||||
break
|
||||
case 'brackets':
|
||||
// 结果: ids[]=1&ids[]=2&ids[]=3
|
||||
value.forEach((_value) => {
|
||||
_result.push(`${key}[]=${_value}`)
|
||||
})
|
||||
break
|
||||
case 'repeat':
|
||||
// 结果: ids=1&ids=2&ids=3
|
||||
value.forEach((_value) => {
|
||||
_result.push(`${key}=${_value}`)
|
||||
})
|
||||
break
|
||||
case 'comma':
|
||||
// 结果: ids=1,2,3
|
||||
let commaStr = ''
|
||||
value.forEach((_value) => {
|
||||
commaStr += (commaStr ? ',' : '') + _value
|
||||
})
|
||||
_result.push(`${key}=${commaStr}`)
|
||||
break
|
||||
default:
|
||||
value.forEach((_value) => {
|
||||
_result.push(`${key}[]=${_value}`)
|
||||
})
|
||||
}
|
||||
} else {
|
||||
_result.push(`${key}=${value}`)
|
||||
}
|
||||
}
|
||||
return _result.length ? prefix + _result.join('&') : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示消息提示框
|
||||
* @param {String} title 提示的内容,长度与 icon 取值有关。
|
||||
* @param {Number} duration 提示的延迟时间,单位毫秒,默认:2000
|
||||
*/
|
||||
function toast(title, duration = 2000) {
|
||||
uni.showToast({
|
||||
title: String(title),
|
||||
icon: 'none',
|
||||
duration
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 根据主题type值,获取对应的图标
|
||||
* @param {String} type 主题名称,primary|info|error|warning|success
|
||||
* @param {boolean} fill 是否使用fill填充实体的图标
|
||||
*/
|
||||
function type2icon(type = 'success', fill = false) {
|
||||
// 如果非预置值,默认为success
|
||||
if (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success'
|
||||
let iconName = ''
|
||||
// 目前(2019-12-12),info和primary使用同一个图标
|
||||
switch (type) {
|
||||
case 'primary':
|
||||
iconName = 'info-circle'
|
||||
break
|
||||
case 'info':
|
||||
iconName = 'info-circle'
|
||||
break
|
||||
case 'error':
|
||||
iconName = 'close-circle'
|
||||
break
|
||||
case 'warning':
|
||||
iconName = 'error-circle'
|
||||
break
|
||||
case 'success':
|
||||
iconName = 'checkmark-circle'
|
||||
break
|
||||
default:
|
||||
iconName = 'checkmark-circle'
|
||||
}
|
||||
// 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的
|
||||
if (fill) iconName += '-fill'
|
||||
return iconName
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 数字格式化
|
||||
* @param {number|string} number 要格式化的数字
|
||||
* @param {number} decimals 保留几位小数
|
||||
* @param {string} decimalPoint 小数点符号
|
||||
* @param {string} thousandsSeparator 千分位符号
|
||||
* @returns {string} 格式化后的数字
|
||||
*/
|
||||
function priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparator = ',') {
|
||||
number = (`${number}`).replace(/[^0-9+-Ee.]/g, '')
|
||||
const n = !isFinite(+number) ? 0 : +number
|
||||
const prec = !isFinite(+decimals) ? 0 : Math.abs(decimals)
|
||||
const sep = (typeof thousandsSeparator === 'undefined') ? ',' : thousandsSeparator
|
||||
const dec = (typeof decimalPoint === 'undefined') ? '.' : decimalPoint
|
||||
let s = ''
|
||||
|
||||
s = (prec ? round(n, prec) + '' : `${Math.round(n)}`).split('.')
|
||||
const re = /(-?\d+)(\d{3})/
|
||||
while (re.test(s[0])) {
|
||||
s[0] = s[0].replace(re, `$1${sep}$2`)
|
||||
}
|
||||
|
||||
if ((s[1] || '').length < prec) {
|
||||
s[1] = s[1] || ''
|
||||
s[1] += new Array(prec - s[1].length + 1).join('0')
|
||||
}
|
||||
return s.join(dec)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取duration值
|
||||
* 如果带有ms或者s直接返回,如果大于一定值,认为是ms单位,小于一定值,认为是s单位
|
||||
* 比如以30位阈值,那么300大于30,可以理解为用户想要的是300ms,而不是想花300s去执行一个动画
|
||||
* @param {String|number} value 比如: "1s"|"100ms"|1|100
|
||||
* @param {boolean} unit 提示: 如果是false 默认返回number
|
||||
* @return {string|number}
|
||||
*/
|
||||
function getDuration(value, unit = true) {
|
||||
const valueNum = parseInt(value)
|
||||
if (unit) {
|
||||
if (/s$/.test(value)) return value
|
||||
return value > 30 ? `${value}ms` : `${value}s`
|
||||
}
|
||||
if (/ms$/.test(value)) return valueNum
|
||||
if (/s$/.test(value)) return valueNum > 30 ? valueNum : valueNum * 1000
|
||||
return valueNum
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 日期的月或日补零操作
|
||||
* @param {String} value 需要补零的值
|
||||
*/
|
||||
function padZero(value) {
|
||||
return `00${value}`.slice(-2)
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 在uv-form的子组件内容发生变化,或者失去焦点时,尝试通知uv-form执行校验方法
|
||||
* @param {*} instance
|
||||
* @param {*} event
|
||||
*/
|
||||
function formValidate(instance, event) {
|
||||
const formItem = $parent.call(instance, 'uv-form-item')
|
||||
const form = $parent.call(instance, 'uv-form')
|
||||
// 如果发生变化的input或者textarea等,其父组件中有uv-form-item或者uv-form等,就执行form的validate方法
|
||||
// 同时将form-item的pros传递给form,让其进行精确对象验证
|
||||
if (formItem && form) {
|
||||
form.validateField(formItem.prop, () => {}, event)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取某个对象下的属性,用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式
|
||||
* @param {object} obj 对象
|
||||
* @param {string} key 需要获取的属性字段
|
||||
* @returns {*}
|
||||
*/
|
||||
function getProperty(obj, key) {
|
||||
if (!obj) {
|
||||
return
|
||||
}
|
||||
if (typeof key !== 'string' || key === '') {
|
||||
return ''
|
||||
}
|
||||
if (key.indexOf('.') !== -1) {
|
||||
const keys = key.split('.')
|
||||
let firstObj = obj[keys[0]] || {}
|
||||
|
||||
for (let i = 1; i < keys.length; i++) {
|
||||
if (firstObj) {
|
||||
firstObj = firstObj[keys[i]]
|
||||
}
|
||||
}
|
||||
return firstObj
|
||||
}
|
||||
return obj[key]
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 设置对象的属性值,如果'a.b.c'的形式进行设置
|
||||
* @param {object} obj 对象
|
||||
* @param {string} key 需要设置的属性
|
||||
* @param {string} value 设置的值
|
||||
*/
|
||||
function setProperty(obj, key, value) {
|
||||
if (!obj) {
|
||||
return
|
||||
}
|
||||
// 递归赋值
|
||||
const inFn = function(_obj, keys, v) {
|
||||
// 最后一个属性key
|
||||
if (keys.length === 1) {
|
||||
_obj[keys[0]] = v
|
||||
return
|
||||
}
|
||||
// 0~length-1个key
|
||||
while (keys.length > 1) {
|
||||
const k = keys[0]
|
||||
if (!_obj[k] || (typeof _obj[k] !== 'object')) {
|
||||
_obj[k] = {}
|
||||
}
|
||||
const key = keys.shift()
|
||||
// 自调用判断是否存在属性,不存在则自动创建对象
|
||||
inFn(_obj[k], keys, v)
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof key !== 'string' || key === '') {
|
||||
|
||||
} else if (key.indexOf('.') !== -1) { // 支持多层级赋值操作
|
||||
const keys = key.split('.')
|
||||
inFn(obj, keys, value)
|
||||
} else {
|
||||
obj[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前页面路径
|
||||
*/
|
||||
function page() {
|
||||
const pages = getCurrentPages();
|
||||
const route = pages[pages.length - 1]?.route;
|
||||
// 某些特殊情况下(比如页面进行redirectTo时的一些时机),pages可能为空数组
|
||||
return `/${route ? route : ''}`
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 获取当前路由栈实例数组
|
||||
*/
|
||||
function pages() {
|
||||
const pages = getCurrentPages()
|
||||
return pages
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取页面历史栈指定层实例
|
||||
* @param back {number} [0] - 0或者负数,表示获取历史栈的哪一层,0表示获取当前页面实例,-1 表示获取上一个页面实例。默认0。
|
||||
*/
|
||||
function getHistoryPage(back = 0) {
|
||||
const pages = getCurrentPages()
|
||||
const len = pages.length
|
||||
return pages[len - 1 + back]
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @description 修改uvui内置属性值
|
||||
* @param {object} props 修改内置props属性
|
||||
* @param {object} config 修改内置config属性
|
||||
* @param {object} color 修改内置color属性
|
||||
* @param {object} zIndex 修改内置zIndex属性
|
||||
*/
|
||||
function setConfig({
|
||||
props = {},
|
||||
config = {},
|
||||
color = {},
|
||||
zIndex = {}
|
||||
}) {
|
||||
const {
|
||||
deepMerge,
|
||||
} = uni.$uv
|
||||
uni.$uv.config = deepMerge(uni.$uv.config, config)
|
||||
uni.$uv.props = deepMerge(uni.$uv.props, props)
|
||||
uni.$uv.color = deepMerge(uni.$uv.color, color)
|
||||
uni.$uv.zIndex = deepMerge(uni.$uv.zIndex, zIndex)
|
||||
}
|
||||
|
||||
export {
|
||||
range,
|
||||
getPx,
|
||||
sleep,
|
||||
os,
|
||||
sys,
|
||||
random,
|
||||
guid,
|
||||
$parent,
|
||||
addStyle,
|
||||
addUnit,
|
||||
deepClone,
|
||||
deepMerge,
|
||||
error,
|
||||
randomArray,
|
||||
timeFormat,
|
||||
timeFrom,
|
||||
trim,
|
||||
queryParams,
|
||||
toast,
|
||||
type2icon,
|
||||
priceFormat,
|
||||
getDuration,
|
||||
padZero,
|
||||
formValidate,
|
||||
getProperty,
|
||||
setProperty,
|
||||
page,
|
||||
pages,
|
||||
getHistoryPage,
|
||||
setConfig
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* 注意:
|
||||
* 此部分内容,在vue-cli模式下,需要在vue.config.js加入如下内容才有效:
|
||||
* module.exports = {
|
||||
* transpileDependencies: ['uview-v2']
|
||||
* }
|
||||
*/
|
||||
|
||||
let platform = 'none'
|
||||
|
||||
// #ifdef VUE3
|
||||
platform = 'vue3'
|
||||
// #endif
|
||||
|
||||
// #ifdef VUE2
|
||||
platform = 'vue2'
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
platform = 'plus'
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
platform = 'nvue'
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
platform = 'h5'
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-WEIXIN
|
||||
platform = 'weixin'
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-ALIPAY
|
||||
platform = 'alipay'
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-BAIDU
|
||||
platform = 'baidu'
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-TOUTIAO
|
||||
platform = 'toutiao'
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-QQ
|
||||
platform = 'qq'
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-KUAISHOU
|
||||
platform = 'kuaishou'
|
||||
// #endif
|
||||
|
||||
// #ifdef MP-360
|
||||
platform = '360'
|
||||
// #endif
|
||||
|
||||
// #ifdef MP
|
||||
platform = 'mp'
|
||||
// #endif
|
||||
|
||||
// #ifdef QUICKAPP-WEBVIEW
|
||||
platform = 'quickapp-webview'
|
||||
// #endif
|
||||
|
||||
// #ifdef QUICKAPP-WEBVIEW-HUAWEI
|
||||
platform = 'quickapp-webview-huawei'
|
||||
// #endif
|
||||
|
||||
// #ifdef QUICKAPP-WEBVIEW-UNION
|
||||
platform = 'quckapp-webview-union'
|
||||
// #endif
|
||||
|
||||
export default platform
|
|
@ -0,0 +1,287 @@
|
|||
/**
|
||||
* 验证电子邮箱格式
|
||||
*/
|
||||
function email(value) {
|
||||
return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证手机格式
|
||||
*/
|
||||
function mobile(value) {
|
||||
return /^1([3589]\d|4[5-9]|6[1-2,4-7]|7[0-8])\d{8}$/.test(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证URL格式
|
||||
*/
|
||||
function url(value) {
|
||||
return /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/
|
||||
.test(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证日期格式
|
||||
*/
|
||||
function date(value) {
|
||||
if (!value) return false
|
||||
// 判断是否数值或者字符串数值(意味着为时间戳),转为数值,否则new Date无法识别字符串时间戳
|
||||
if (number(value)) value = +value
|
||||
return !/Invalid|NaN/.test(new Date(value).toString())
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证ISO类型的日期格式
|
||||
*/
|
||||
function dateISO(value) {
|
||||
return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证十进制数字
|
||||
*/
|
||||
function number(value) {
|
||||
return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证字符串
|
||||
*/
|
||||
function string(value) {
|
||||
return typeof value === 'string'
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证整数
|
||||
*/
|
||||
function digits(value) {
|
||||
return /^\d+$/.test(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证身份证号码
|
||||
*/
|
||||
function idCard(value) {
|
||||
return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(
|
||||
value
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否车牌号
|
||||
*/
|
||||
function carNo(value) {
|
||||
// 新能源车牌
|
||||
const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/
|
||||
// 旧车牌
|
||||
const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/
|
||||
if (value.length === 7) {
|
||||
return creg.test(value)
|
||||
} if (value.length === 8) {
|
||||
return xreg.test(value)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 金额,只允许2位小数
|
||||
*/
|
||||
function amount(value) {
|
||||
// 金额,只允许保留两位小数
|
||||
return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 中文
|
||||
*/
|
||||
function chinese(value) {
|
||||
const reg = /^[\u4e00-\u9fa5]+$/gi
|
||||
return reg.test(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 只能输入字母
|
||||
*/
|
||||
function letter(value) {
|
||||
return /^[a-zA-Z]*$/.test(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 只能是字母或者数字
|
||||
*/
|
||||
function enOrNum(value) {
|
||||
// 英文或者数字
|
||||
const reg = /^[0-9a-zA-Z]*$/g
|
||||
return reg.test(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证是否包含某个值
|
||||
*/
|
||||
function contains(value, param) {
|
||||
return value.indexOf(param) >= 0
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证一个值范围[min, max]
|
||||
*/
|
||||
function range(value, param) {
|
||||
return value >= param[0] && value <= param[1]
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证一个长度范围[min, max]
|
||||
*/
|
||||
function rangeLength(value, param) {
|
||||
return value.length >= param[0] && value.length <= param[1]
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否固定电话
|
||||
*/
|
||||
function landline(value) {
|
||||
const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/
|
||||
return reg.test(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为空
|
||||
*/
|
||||
function empty(value) {
|
||||
switch (typeof value) {
|
||||
case 'undefined':
|
||||
return true
|
||||
case 'string':
|
||||
if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true
|
||||
break
|
||||
case 'boolean':
|
||||
if (!value) return true
|
||||
break
|
||||
case 'number':
|
||||
if (value === 0 || isNaN(value)) return true
|
||||
break
|
||||
case 'object':
|
||||
if (value === null || value.length === 0) return true
|
||||
for (const i in value) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否json字符串
|
||||
*/
|
||||
function jsonString(value) {
|
||||
if (typeof value === 'string') {
|
||||
try {
|
||||
const obj = JSON.parse(value)
|
||||
if (typeof obj === 'object' && obj) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否数组
|
||||
*/
|
||||
function array(value) {
|
||||
if (typeof Array.isArray === 'function') {
|
||||
return Array.isArray(value)
|
||||
}
|
||||
return Object.prototype.toString.call(value) === '[object Array]'
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否对象
|
||||
*/
|
||||
function object(value) {
|
||||
return Object.prototype.toString.call(value) === '[object Object]'
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否短信验证码
|
||||
*/
|
||||
function code(value, len = 6) {
|
||||
return new RegExp(`^\\d{${len}}$`).test(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否函数方法
|
||||
* @param {Object} value
|
||||
*/
|
||||
function func(value) {
|
||||
return typeof value === 'function'
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否promise对象
|
||||
* @param {Object} value
|
||||
*/
|
||||
function promise(value) {
|
||||
return object(value) && func(value.then) && func(value.catch)
|
||||
}
|
||||
|
||||
/** 是否图片格式
|
||||
* @param {Object} value
|
||||
*/
|
||||
function image(value) {
|
||||
const newValue = value.split('?')[0]
|
||||
const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i
|
||||
return IMAGE_REGEXP.test(newValue)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否视频格式
|
||||
* @param {Object} value
|
||||
*/
|
||||
function video(value) {
|
||||
const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i
|
||||
return VIDEO_REGEXP.test(value)
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否为正则对象
|
||||
* @param {Object}
|
||||
* @return {Boolean}
|
||||
*/
|
||||
function regExp(o) {
|
||||
return o && Object.prototype.toString.call(o) === '[object RegExp]'
|
||||
}
|
||||
|
||||
export {
|
||||
email,
|
||||
mobile,
|
||||
url,
|
||||
date,
|
||||
dateISO,
|
||||
number,
|
||||
digits,
|
||||
idCard,
|
||||
carNo,
|
||||
amount,
|
||||
chinese,
|
||||
letter,
|
||||
enOrNum,
|
||||
contains,
|
||||
range,
|
||||
rangeLength,
|
||||
empty,
|
||||
jsonString,
|
||||
landline,
|
||||
object,
|
||||
array,
|
||||
code,
|
||||
func,
|
||||
promise,
|
||||
video,
|
||||
image,
|
||||
regExp,
|
||||
string
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
let timer; let
|
||||
flag
|
||||
/**
|
||||
* 节流原理:在一定时间内,只能触发一次
|
||||
*
|
||||
* @param {Function} func 要执行的回调函数
|
||||
* @param {Number} wait 延时的时间
|
||||
* @param {Boolean} immediate 是否立即执行
|
||||
* @return null
|
||||
*/
|
||||
function throttle(func, wait = 500, immediate = true) {
|
||||
if (immediate) {
|
||||
if (!flag) {
|
||||
flag = true
|
||||
// 如果是立即执行,则在wait毫秒内开始时执行
|
||||
typeof func === 'function' && func()
|
||||
timer = setTimeout(() => {
|
||||
flag = false
|
||||
}, wait)
|
||||
}
|
||||
} else if (!flag) {
|
||||
flag = true
|
||||
// 如果是非立即执行,则在wait毫秒内的结束处执行
|
||||
timer = setTimeout(() => {
|
||||
flag = false
|
||||
typeof func === 'function' && func()
|
||||
}, wait)
|
||||
}
|
||||
}
|
||||
export default throttle
|
|
@ -0,0 +1,97 @@
|
|||
import buildURL from '../helpers/buildURL'
|
||||
import buildFullPath from '../core/buildFullPath'
|
||||
import settle from '../core/settle'
|
||||
import { isUndefined } from '../utils'
|
||||
|
||||
/**
|
||||
* 返回可选值存在的配置
|
||||
* @param {Array} keys - 可选值数组
|
||||
* @param {Object} config2 - 配置
|
||||
* @return {{}} - 存在的配置项
|
||||
*/
|
||||
const mergeKeys = (keys, config2) => {
|
||||
const config = {}
|
||||
keys.forEach((prop) => {
|
||||
if (!isUndefined(config2[prop])) {
|
||||
config[prop] = config2[prop]
|
||||
}
|
||||
})
|
||||
return config
|
||||
}
|
||||
export default (config) => new Promise((resolve, reject) => {
|
||||
const fullPath = buildURL(buildFullPath(config.baseURL, config.url), config.params)
|
||||
const _config = {
|
||||
url: fullPath,
|
||||
header: config.header,
|
||||
complete: (response) => {
|
||||
config.fullPath = fullPath
|
||||
response.config = config
|
||||
try {
|
||||
// 对可能字符串不是json 的情况容错
|
||||
if (typeof response.data === 'string') {
|
||||
response.data = JSON.parse(response.data)
|
||||
}
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (e) {
|
||||
}
|
||||
settle(resolve, reject, response)
|
||||
}
|
||||
}
|
||||
let requestTask
|
||||
if (config.method === 'UPLOAD') {
|
||||
delete _config.header['content-type']
|
||||
delete _config.header['Content-Type']
|
||||
const otherConfig = {
|
||||
// #ifdef MP-ALIPAY
|
||||
fileType: config.fileType,
|
||||
// #endif
|
||||
filePath: config.filePath,
|
||||
name: config.name
|
||||
}
|
||||
const optionalKeys = [
|
||||
// #ifdef APP-PLUS || H5
|
||||
'files',
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
'file',
|
||||
// #endif
|
||||
// #ifdef H5 || APP-PLUS
|
||||
'timeout',
|
||||
// #endif
|
||||
'formData'
|
||||
]
|
||||
requestTask = uni.uploadFile({ ..._config, ...otherConfig, ...mergeKeys(optionalKeys, config) })
|
||||
} else if (config.method === 'DOWNLOAD') {
|
||||
// #ifdef H5 || APP-PLUS
|
||||
if (!isUndefined(config.timeout)) {
|
||||
_config.timeout = config.timeout
|
||||
}
|
||||
// #endif
|
||||
requestTask = uni.downloadFile(_config)
|
||||
} else {
|
||||
const optionalKeys = [
|
||||
'data',
|
||||
'method',
|
||||
// #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
|
||||
'timeout',
|
||||
// #endif
|
||||
'dataType',
|
||||
// #ifndef MP-ALIPAY
|
||||
'responseType',
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
'sslVerify',
|
||||
// #endif
|
||||
// #ifdef H5
|
||||
'withCredentials',
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
'firstIpv4'
|
||||
// #endif
|
||||
]
|
||||
requestTask = uni.request({ ..._config, ...mergeKeys(optionalKeys, config) })
|
||||
}
|
||||
if (config.getTask) {
|
||||
config.getTask(requestTask, config)
|
||||
}
|
||||
})
|
|
@ -0,0 +1,50 @@
|
|||
'use strict'
|
||||
|
||||
function InterceptorManager() {
|
||||
this.handlers = []
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new interceptor to the stack
|
||||
*
|
||||
* @param {Function} fulfilled The function to handle `then` for a `Promise`
|
||||
* @param {Function} rejected The function to handle `reject` for a `Promise`
|
||||
*
|
||||
* @return {Number} An ID used to remove interceptor later
|
||||
*/
|
||||
InterceptorManager.prototype.use = function use(fulfilled, rejected) {
|
||||
this.handlers.push({
|
||||
fulfilled,
|
||||
rejected
|
||||
})
|
||||
return this.handlers.length - 1
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an interceptor from the stack
|
||||
*
|
||||
* @param {Number} id The ID that was returned by `use`
|
||||
*/
|
||||
InterceptorManager.prototype.eject = function eject(id) {
|
||||
if (this.handlers[id]) {
|
||||
this.handlers[id] = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Iterate over all the registered interceptors
|
||||
*
|
||||
* This method is particularly useful for skipping over any
|
||||
* interceptors that may have become `null` calling `eject`.
|
||||
*
|
||||
* @param {Function} fn The function to call for each interceptor
|
||||
*/
|
||||
InterceptorManager.prototype.forEach = function forEach(fn) {
|
||||
this.handlers.forEach((h) => {
|
||||
if (h !== null) {
|
||||
fn(h)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export default InterceptorManager
|
|
@ -0,0 +1,198 @@
|
|||
/**
|
||||
* @Class Request
|
||||
* @description luch-request http请求插件
|
||||
* @version 3.0.7
|
||||
* @Author lu-ch
|
||||
* @Date 2021-09-04
|
||||
* @Email webwork.s@qq.com
|
||||
* 文档: https://www.quanzhan.co/luch-request/
|
||||
* github: https://github.com/lei-mu/luch-request
|
||||
* DCloud: http://ext.dcloud.net.cn/plugin?id=392
|
||||
* HBuilderX: beat-3.0.4 alpha-3.0.4
|
||||
*/
|
||||
|
||||
import dispatchRequest from './dispatchRequest'
|
||||
import InterceptorManager from './InterceptorManager'
|
||||
import mergeConfig from './mergeConfig'
|
||||
import defaults from './defaults'
|
||||
import { isPlainObject } from '../utils'
|
||||
import clone from '../utils/clone'
|
||||
|
||||
export default class Request {
|
||||
/**
|
||||
* @param {Object} arg - 全局配置
|
||||
* @param {String} arg.baseURL - 全局根路径
|
||||
* @param {Object} arg.header - 全局header
|
||||
* @param {String} arg.method = [GET|POST|PUT|DELETE|CONNECT|HEAD|OPTIONS|TRACE] - 全局默认请求方式
|
||||
* @param {String} arg.dataType = [json] - 全局默认的dataType
|
||||
* @param {String} arg.responseType = [text|arraybuffer] - 全局默认的responseType。支付宝小程序不支持
|
||||
* @param {Object} arg.custom - 全局默认的自定义参数
|
||||
* @param {Number} arg.timeout - 全局默认的超时时间,单位 ms。默认60000。H5(HBuilderX 2.9.9+)、APP(HBuilderX 2.9.9+)、微信小程序(2.10.0)、支付宝小程序
|
||||
* @param {Boolean} arg.sslVerify - 全局默认的是否验证 ssl 证书。默认true.仅App安卓端支持(HBuilderX 2.3.3+)
|
||||
* @param {Boolean} arg.withCredentials - 全局默认的跨域请求时是否携带凭证(cookies)。默认false。仅H5支持(HBuilderX 2.6.15+)
|
||||
* @param {Boolean} arg.firstIpv4 - 全DNS解析时优先使用ipv4。默认false。仅 App-Android 支持 (HBuilderX 2.8.0+)
|
||||
* @param {Function(statusCode):Boolean} arg.validateStatus - 全局默认的自定义验证器。默认statusCode >= 200 && statusCode < 300
|
||||
*/
|
||||
constructor(arg = {}) {
|
||||
if (!isPlainObject(arg)) {
|
||||
arg = {}
|
||||
console.warn('设置全局参数必须接收一个Object')
|
||||
}
|
||||
this.config = clone({ ...defaults, ...arg })
|
||||
this.interceptors = {
|
||||
request: new InterceptorManager(),
|
||||
response: new InterceptorManager()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Function
|
||||
* @param {Request~setConfigCallback} f - 设置全局默认配置
|
||||
*/
|
||||
setConfig(f) {
|
||||
this.config = f(this.config)
|
||||
}
|
||||
|
||||
middleware(config) {
|
||||
config = mergeConfig(this.config, config)
|
||||
const chain = [dispatchRequest, undefined]
|
||||
let promise = Promise.resolve(config)
|
||||
|
||||
this.interceptors.request.forEach((interceptor) => {
|
||||
chain.unshift(interceptor.fulfilled, interceptor.rejected)
|
||||
})
|
||||
|
||||
this.interceptors.response.forEach((interceptor) => {
|
||||
chain.push(interceptor.fulfilled, interceptor.rejected)
|
||||
})
|
||||
|
||||
while (chain.length) {
|
||||
promise = promise.then(chain.shift(), chain.shift())
|
||||
}
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
/**
|
||||
* @Function
|
||||
* @param {Object} config - 请求配置项
|
||||
* @prop {String} options.url - 请求路径
|
||||
* @prop {Object} options.data - 请求参数
|
||||
* @prop {Object} [options.responseType = config.responseType] [text|arraybuffer] - 响应的数据类型
|
||||
* @prop {Object} [options.dataType = config.dataType] - 如果设为 json,会尝试对返回的数据做一次 JSON.parse
|
||||
* @prop {Object} [options.header = config.header] - 请求header
|
||||
* @prop {Object} [options.method = config.method] - 请求方法
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
request(config = {}) {
|
||||
return this.middleware(config)
|
||||
}
|
||||
|
||||
get(url, options = {}) {
|
||||
return this.middleware({
|
||||
url,
|
||||
method: 'GET',
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
post(url, data, options = {}) {
|
||||
return this.middleware({
|
||||
url,
|
||||
data,
|
||||
method: 'POST',
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
// #ifndef MP-ALIPAY
|
||||
put(url, data, options = {}) {
|
||||
return this.middleware({
|
||||
url,
|
||||
data,
|
||||
method: 'PUT',
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
|
||||
delete(url, data, options = {}) {
|
||||
return this.middleware({
|
||||
url,
|
||||
data,
|
||||
method: 'DELETE',
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
// #endif
|
||||
|
||||
// #ifdef H5 || MP-WEIXIN
|
||||
connect(url, data, options = {}) {
|
||||
return this.middleware({
|
||||
url,
|
||||
data,
|
||||
method: 'CONNECT',
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
// #endif
|
||||
|
||||
// #ifdef H5 || MP-WEIXIN || MP-BAIDU
|
||||
head(url, data, options = {}) {
|
||||
return this.middleware({
|
||||
url,
|
||||
data,
|
||||
method: 'HEAD',
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
|
||||
options(url, data, options = {}) {
|
||||
return this.middleware({
|
||||
url,
|
||||
data,
|
||||
method: 'OPTIONS',
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
// #endif
|
||||
|
||||
// #ifdef H5 || MP-WEIXIN
|
||||
trace(url, data, options = {}) {
|
||||
return this.middleware({
|
||||
url,
|
||||
data,
|
||||
method: 'TRACE',
|
||||
...options
|
||||
})
|
||||
}
|
||||
|
||||
// #endif
|
||||
|
||||
upload(url, config = {}) {
|
||||
config.url = url
|
||||
config.method = 'UPLOAD'
|
||||
return this.middleware(config)
|
||||
}
|
||||
|
||||
download(url, config = {}) {
|
||||
config.url = url
|
||||
config.method = 'DOWNLOAD'
|
||||
return this.middleware(config)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* setConfig回调
|
||||
* @return {Object} - 返回操作后的config
|
||||
* @callback Request~setConfigCallback
|
||||
* @param {Object} config - 全局默认config
|
||||
*/
|
|
@ -0,0 +1,20 @@
|
|||
'use strict'
|
||||
|
||||
import isAbsoluteURL from '../helpers/isAbsoluteURL'
|
||||
import combineURLs from '../helpers/combineURLs'
|
||||
|
||||
/**
|
||||
* Creates a new URL by combining the baseURL with the requestedURL,
|
||||
* only when the requestedURL is not already an absolute URL.
|
||||
* If the requestURL is absolute, this function returns the requestedURL untouched.
|
||||
*
|
||||
* @param {string} baseURL The base URL
|
||||
* @param {string} requestedURL Absolute or relative URL to combine
|
||||
* @returns {string} The combined full path
|
||||
*/
|
||||
export default function buildFullPath(baseURL, requestedURL) {
|
||||
if (baseURL && !isAbsoluteURL(requestedURL)) {
|
||||
return combineURLs(baseURL, requestedURL)
|
||||
}
|
||||
return requestedURL
|
||||
}
|