dev4
Panzihang 2023-11-21 17:43:02 +08:00
parent 2d3e6f1c30
commit c3e507747c
47 changed files with 3885 additions and 1213 deletions

View File

@ -572,7 +572,7 @@
.borContent {
width: 100%;
height: 120px;
height: 140px;
overflow: scroll;
display: flex;
flex-direction: column;

View File

@ -0,0 +1,225 @@
<template>
<view class="progress-bar">
<view class="progress-bar-title" :style="titleStyle">{{ title }}</view>
<view v-if="!secondValue" class="bar">
<view
class="bar-single"
:style="{
backgroundColor: backColor,
borderRadius: barRadius,
height: barHeight,
lineHeight: barHeight
}"
>
<view
class="bar-content"
:style="{ width: firstWidth, minWidth: minWidth }"
>
<view
:class="{ 'bar-content-inner': animationReal }"
:style="{ background: contentColor }"
>
<text
v-if="textPosition === 'inside'"
class="bar-text"
:style="{ color: textColor, fontSize: textSize }"
>
{{ singleText }}
</text>
<text v-else class="placeholder">1</text>
</view>
</view>
</view>
<view
v-if="textPosition === 'outside'"
class="bar-text"
:style="{ color: textColor, fontSize: textSize }"
>
{{ singleText }}
</view>
</view>
<view v-else class="bar">
<view
class="bar-single bar-double"
:style="{
backgroundColor: backColor,
borderRadius: barRadius,
height: barHeight,
lineHeight: barHeight
}"
>
<view
class="bar-content"
:style="{ width: firstWidth, minWidth: minWidth }"
>
<view
:class="{ 'bar-content-inner': animationReal }"
:style="{ background: contentColor }"
>
<text
v-if="textPosition !== 'none'"
class="bar-text"
:style="{ color: textColor, fontSize: textSize }"
>
{{ singleText }}
</text>
<text v-else class="placeholder">1</text>
</view>
</view>
<view class="bar-interval placeholder">1</view>
<view
class="bar-content"
:style="{ width: secondWidth, minWidth: minWidth }"
>
<view
:class="{ 'bar-content-inner': animationReal }"
:style="{ background: contentColor2 }"
>
<text
v-if="textPosition !== 'none'"
class="bar-text"
:style="{ color: textColor, fontSize: textSize }"
>
{{ doubleText }}
</text>
<text v-else class="placeholder">1</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import props from './props.js';
export default {
mixins: [props],
data() {
return {
singleText: '',
doubleText: '',
firstWidth: '',
secondWidth: '',
animationReal: false
};
},
watch: {
firstValue: {
handler(newVal) {
// ,
if (this.animationReal) {
this.animationReal = false;
this.firstWidth = this.manageWidth(0, 'singleText');
setTimeout(() => {
this.animationReal = true;
this.firstWidth = this.manageWidth(
newVal,
'singleText'
);
}, 100);
} else {
this.firstWidth = this.manageWidth(newVal, 'singleText');
}
},
immediate: true
},
secondValue: {
handler(newVal) {
if (this.animationReal) {
this.animationReal = false;
this.secondWidth = this.manageWidth(0, 'doubleText');
setTimeout(() => {
this.animationReal = true;
this.secondWidth = this.manageWidth(
newVal,
'doubleText'
);
}, 100);
} else {
this.secondWidth = this.manageWidth(newVal, 'doubleText');
}
},
immediate: true
},
animation: {
handler(newVal) {
this.animationReal = newVal;
},
immediate: true
}
},
methods: {
manageWidth(target, flag) {
let value =
((target / this.total) * 100).toFixed(this.precision) + '%';
this.percentum ? (this[flag] = value) : (this[flag] = target);
//
this.secondValue ? (value = `calc(${value} - 1px)`) : '';
return value;
}
}
};
</script>
<style lang="scss" scoped>
.progress-bar {
&-title {
font-size: 4vw;
font-weight: bold;
margin-bottom: 1vw;
}
.bar {
/* #ifdef H5 */
cursor: pointer;
/* #endif */
display: flex;
align-items: center;
justify-content: space-between;
&-single {
overflow: hidden;
width: 100vw;
}
&-double {
display: flex;
align-items: center;
justify-content: left;
}
&-content {
&-inner {
animation: addWidth 1.5s 0s;
}
}
&-interval {
background-color: #ffffff;
width: 2px;
}
&-text {
margin: 0 5px;
font-size: 4vw;
}
.placeholder {
color: rgba(0, 0, 0, 0);
font-size: 4vw;
}
}
}
@keyframes addWidth {
from {
width: 0;
}
to {
width: 100%;
}
}
</style>

View File

@ -0,0 +1,84 @@
export default {
props: {
// 标题
title: {
type: String,
required: true
},
// 标题样式
titleStyle: {
type: String,
},
// 总数,计算的分母
total: {
type: Number,
required: true
},
// 第一个值
firstValue: {
type: Number,
required: true
},
// 第二个值
secondValue: {
type: Number
},
// 进度条背景颜色
backColor: {
type: String,
default: '#f2f2f2'
},
// 进度条高度
barHeight: {
type: String
},
// 进度条圆角
barRadius: {
type: String
},
// 进度条颜色
contentColor: {
type: String,
default: '#4cd964'
},
// 进度条2颜色
contentColor2: {
type: String,
default: '#f0ad4e'
},
// 进度条最小长度
minWidth: {
type: String
},
// 文字大小
textSize: {
type: String,
default: '16px'
},
// 文字颜色
textColor: {
type: String,
default: '#333333'
},
// 文字内显、外显、不显可选值inside/outside/none
textPosition: {
type: String,
default: 'inside'
},
// 展示百分比或值
percentum: {
type: Boolean,
default: false
},
// 百分比精确到小数点后几位
precision: {
type: [String, Number],
default: 2
},
// 是否选择动画加载
animation: {
type: Boolean,
default: false
}
}
}

View File

@ -151,6 +151,12 @@
//
shipInput(e) {
this.shipValue = e
if (e == "") {
this.vvyId = ""
this.vvyShip = ""
this.shipId = ""
this.shipName = ""
}
this.getShip()
},
//
@ -254,7 +260,7 @@
line-height: 35px;
padding-left: 10px;
margin-right: 15px;
margin-top: 8px;
margin-top: 10px;
}
.btn {
@ -265,7 +271,7 @@
color: #fff;
background-color: #0067CF;
margin-right: 10px;
margin-top: 8px;
margin-top: 10px;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -151,6 +151,12 @@
//
shipInput(e) {
this.shipValue = e
if (e == "") {
this.vvyId = ""
this.vvyShip = ""
this.shipId = ""
this.shipName = ""
}
this.getShip()
},
//
@ -206,10 +212,6 @@
this.lotusLoadingData.isShow = false
this.total = res.data.data.total
this.itemList.push(...res.data.data.records)
this.vvyId = ""
this.vvyShip = ""
this.shipId = ""
this.shipName = ""
}
})
},
@ -265,7 +267,7 @@
line-height: 35px;
padding-left: 10px;
margin-right: 15px;
margin-top: 8px;
margin-top: 10px;
}
.btn {
@ -276,7 +278,7 @@
color: #fff;
background-color: #0067CF;
margin-right: 10px;
margin-top: 8px;
margin-top: 10px;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -29,8 +29,8 @@
<text>板车照片</text>
</view>
<view class="picture">
<template v-for="item in boardCarPhotos">
<image :key="item" :src="item"></image>
<template v-for="(item,index) in boardCarPhotos">
<image :key="item" :src="item" @click="clickImg(boardCarPhotos,index)"></image>
</template>
</view>
</view>
@ -40,8 +40,8 @@
<text>板车车牌照</text>
</view>
<view class="picture">
<template v-for="item in boardCarList">
<image :key="item" :src="item"></image>
<template v-for="(item,index) in boardCarList">
<image :key="item" :src="item" @click="clickImg(boardCarList,index)"></image>
</template>
</view>
</view>
@ -122,8 +122,8 @@
<text>质损照片</text>
</view>
<view class="picture">
<template v-for="item in qualityDamage">
<image :key="item" :src="item"></image>
<template v-for="(item,index) in qualityDamage">
<image :key="item" :src="item" @click="clickImg(qualityDamage,index)"></image>
</template>
</view>
</view>
@ -133,8 +133,8 @@
<text>车架号图片</text>
</view>
<view class="picture">
<template v-for="item in carFrameNumber">
<image :key="item" :src="item"></image>
<template v-for="(item,index) in carFrameNumber">
<image :key="item" :src="item" @click="clickImg(carFrameNumber,index)"></image>
</template>
</view>
</view>
@ -147,9 +147,6 @@
<image :src="signImg"></image>
</view>
</view>
<!-- <view class="btnList">
<van-button type="default" @click="cancel"></van-button>
</view> -->
</view>
</view>
</template>
@ -248,12 +245,13 @@
}
})
},
//
cancel() {
uni.navigateTo({
url: '/pages/quality/index'
//
clickImg(urlList, index) {
uni.previewImage({
current: index,
urls: urlList,
})
}
},
}
}
</script>
@ -367,17 +365,6 @@
}
}
}
.btnList {
display: flex;
justify-content: center;
/deep/ .van-button {
margin: 30px 0;
width: 120px;
height: 50px;
}
}
}
}
</style>

View File

@ -431,6 +431,7 @@
success: (res) => {
if (res.statusCode == 200) {
this.infoData = res.data.data
console.log(this.infoData)
//
this.bcPhoto = this.infoData.boardCarPhotos
this.infoData.boardCarPhotos.forEach(v => {
@ -1307,6 +1308,7 @@
this.carPhoto2.push(e.tempFiles[0])
let data = JSON.parse(res.data).data
this.carPhoto.push(data)
console.log(this.carPhoto)
},
})
},
@ -1345,6 +1347,12 @@
if (this.current2 != '其他') {
this.other = ""
}
let editComplete = ""
if (this.type == 'add') {
editComplete = "1"
} else if (this.type == "edit") {
editComplete = "0"
}
let editReqDTO = {
"boardCarLicensePlates": this.bcLicense, //
"boardCarPhotos": this.bcPhoto, //
@ -1354,7 +1362,7 @@
"dsOther": this.other2, //
"dispList": this.checkData3, //
"dpsOther": this.other3, //
"editComplete": "1", // pad 1
"editComplete": editComplete, // pad 1
"godId": this.godId, // id
"goodsType": this.carId, // id
"linkFeedback": this.feed, //

View File

@ -376,7 +376,7 @@
background: #FAFAFA;
border-top: 1px solid #EEEEEE;
border-bottom: 1px solid #EEEEEE;
padding: 6px 10px;
padding: 10px;
position: fixed;
top: 66px;
z-index: 995;
@ -458,7 +458,7 @@
justify-content: space-between;
font-family: PingFangSC-Regular;
font-size: 14px;
color: #23262E;
color: #23262E;
margin-bottom: 16px;
.headTop {

View File

@ -6,7 +6,7 @@
<canvas class="mycanvas" canvas-id="mycanvas" @touchstart="touchstart" @touchmove="touchmove"
@touchend="touchend" disable-scroll="true"></canvas>
<view class="canvasBg">
<image src="../../static/images/zs5.jpg" mode=""></image>
<image src="../../static/images/zs2.png" mode=""></image>
</view>
</view>
<view class="sigh-btns">
@ -51,28 +51,31 @@
methods: {
init() {
this.ctx = uni.createCanvasContext('mycanvas', this); //
this.ctx.setFillStyle('#ff0000');
//
this.ctx.lineWidth = 4;
this.ctx.lineCap = 'round';
this.ctx.lineJoin = 'round';
var that = this
uni.getSystemInfo({
success: function(res) {
that.ctx.drawImage("../../static/images/zs5.jpg", 0, 0, 1000, 222)
that.width = res.windowWidth;
that.height = res.windowHeight;
uni.getImageInfo({
src: '../../static/images/zs2.png',
success(res1) {
//
that.ctx.drawImage('../../static/images/zs2.png', 0, 0, 624, 224);
let pattern = that.ctx.createPattern(img, 'no-repeat')
that.ctx.fillStyle = pattern;
},
fail() {
console.log("fail");
}
});
},
//
touchstart: function(e) {
let startX = e.changedTouches[0].x;
let startY = e.changedTouches[0].y;
let startPoint = {
X: startX,
Y: startY
X: startX / 1.75,
Y: startY / 1.75
};
/* **************************************************
@ -89,8 +92,8 @@
let moveX = e.changedTouches[0].x;
let moveY = e.changedTouches[0].y;
let movePoint = {
X: moveX,
Y: moveY
X: moveX / 1.75,
Y: moveY / 1.75
};
this.points.push(movePoint); //
let len = this.points.length;
@ -115,6 +118,8 @@
let point1 = this.points[0];
let point2 = this.points[1];
this.points.shift();
this.ctx.strokeStyle = 'red';
this.ctx.lineWidth = 2;
this.ctx.moveTo(point1.X, point1.Y);
this.ctx.lineTo(point2.X, point2.Y);
this.ctx.stroke();
@ -130,7 +135,7 @@
//
handleReset: function() {
var that = this
that.ctx.clearRect(0, 0, 1000, 222);
that.ctx.clearRect(0, 0, 624, 224);
that.ctx.draw(true);
tempPoint = [];
that.init()
@ -151,30 +156,6 @@
uni.navigateBack({
delta: 1
});
// let timestamp = new Date().getTime();
// let sunumber = Math.floor(Math.random() * 999);
// var file = that.base64ToFile(that.url, timestamp + sunumber)
// uni.uploadFile({
// url: `${that.$local}/api/file/upload`, //api
// header: {
// 'Authorization': `Bearer ${that.loginObj.access_token}`
// },
// file: file,
// fileType: 'image',
// name: 'file',
// success: (res) => {
// console.log(res)
// console.log(JSON.parse(res.data))
// that.signImg = JSON.parse(res.data).data.filePath
// console.log(that.signImg)
// // uni.navigateBack({
// // delta: 1
// // });
// },
// fail: (err) => {
// console.log(err)
// }
// })
})
.catch(error => {
console.error(error)
@ -216,6 +197,7 @@
display: flex;
justify-content: center;
position: relative;
margin-top: 48px;
}
.sign-view {
@ -228,7 +210,7 @@
}
.btn {
margin: 10px 10px 0;
margin: 100px 10px 0;
}
/deep/.van-button {
@ -240,17 +222,19 @@
.mycanvas {
margin: auto 0rpx;
background-color: transparent;
margin-top: 80px;
width: 1000px;
height: 222px;
margin-top: 130px;
width: 624px;
height: 224px;
z-index: 999;
transform: scale(1.75)
}
.canvasBg {
width: 1000px;
height: 222px;
width: 624px;
height: 224px;
position: absolute;
top: 80px;
top: 130px;
transform: scale(1.75)
}
.canvsborder {

View File

@ -20,7 +20,7 @@
</view>
<view class="li">
<p>贸易类型</p>
<text>{{shipInfo.spmTradeName}}</text>
<text>{{spmTradeName}}</text>
</view>
<view class="li">
<p><text class="required" v-if="obj.state != 'look'">*</text></p>
@ -87,6 +87,7 @@
vvyInfo: [],
importExportFlagName: "",
spmTradeName: "",
//
optionData: [],
@ -165,6 +166,7 @@
this.vvyInfo.forEach(v => {
if (v.vvyId == e) {
this.importExportFlagName = v.importExportFlagName
this.spmTradeName = v.tradeTypeName
}
})
},

View File

@ -101,7 +101,7 @@
],
otherListh: [{
name: "安全巡检",
url: "",
url: "patrol",
imgUrl: "../../static/images/shipWork/aqxj.png",
tableName: "safetyInspectionRespList",
},

View File

@ -85,10 +85,10 @@
</template>
<o-empty v-else height="70vh" bg="#f5f6fa" />
</view>
<view class="pageBox" v-if="itemList.length > 0">
<!-- <view class="pageBox" v-if="itemList.length > 0">
<uni-pagination :show-icon="true" :total="total" :pageSize="pageSize" :current="current"
@change="changePage" />
</view>
</view> -->
</view>
</view>
<LotusLoading :lotusLoadingData="lotusLoadingData"></LotusLoading>
@ -133,7 +133,7 @@
vtpList: [],
//
total: 0,
pageSize: 6,
pageSize: 12,
current: 1,
//
@ -197,6 +197,11 @@
HeadInfo,
LotusLoading
},
onReachBottom() {
this.current++
this.initData()
this.lotusLoadingData.isShow = true
},
onBackPress(options) {
// uni.navigateBack 使
if (options.from == 'backbutton') {
@ -254,8 +259,9 @@
method: 'GET', //
success: (res) => {
if (res.statusCode === 200) {
this.lotusLoadingData.isShow = false
this.total = res.data.data.total
this.itemList = res.data.data.records
this.itemList.push(...res.data.data.records)
}
}
})
@ -335,7 +341,6 @@
},
//
shipDataInfo(info, vtpId) {
console.log(vtpId)
console.log(info)
let date = new Date().getTime()
let webId = uuidv4()
@ -398,6 +403,7 @@
vvyIds.push(item.inVvyId)
vvyIds.push(item.outVvyId)
}
console.log(vvyIds)
uni.request({
url: `${this.$local}/api/shipOperate/download?vvyIds=${vvyIds}`,
header: {
@ -994,11 +1000,18 @@
}
.content {
padding: 16px;
min-height: calc(100vh - 68px);
.form {
width: 100%;
background: #f5f6fa;
display: flex;
height: ;
position: fixed;
top: 66px;
right: 0;
z-index: 995;
padding: 16px;
.select {
width: 200px;
@ -1028,6 +1041,8 @@
display: flex;
justify-content: space-between;
flex-wrap: wrap;
padding: 16px;
margin-top: 30px;
/deep/.o-empty {
width: 100%;

View File

@ -20,7 +20,7 @@
</view>
<view class="li">
<p>贸易类型</p>
<text>{{shipInfo.spmTradeName}}</text>
<text>{{spmTradeName}}</text>
</view>
<view class="li">
<p><text class="required" v-if="obj.state != 'look'">*</text></p>
@ -128,6 +128,7 @@
vvyInfo: [],
importExportFlagName: "",
spmTradeName: "",
//
optionData: [],
@ -211,6 +212,7 @@
this.vvyInfo.forEach(v => {
if (v.vvyId == e) {
this.importExportFlagName = v.importExportFlagName
this.spmTradeName = v.tradeTypeName
}
})
},
@ -352,7 +354,7 @@
line-height: 35px;
background-color: #fff !important;
}
/deep/.is-input-border {
margin-top: 8px;
height: 40px;

View File

@ -20,7 +20,7 @@
</view>
<view class="li">
<p>贸易类型</p>
<text>{{shipInfo.spmTradeName}}</text>
<text>{{spmTradeName}}</text>
</view>
<view class="li">
<p><text class="required" v-if="obj.state != 'look'">*</text></p>
@ -72,6 +72,7 @@
vvyInfo: [],
importExportFlagName: "",
spmTradeName: "",
//
optionData: [],
@ -148,6 +149,7 @@
this.vvyInfo.forEach(v => {
if (v.vvyId == e) {
this.importExportFlagName = v.importExportFlagName
this.spmTradeName = v.tradeTypeName
}
})
},

View File

@ -20,7 +20,7 @@
</view>
<view class="li">
<p>贸易类型</p>
<text>{{shipInfo.spmTradeName}}</text>
<text>{{spmTradeName}}</text>
</view>
<view class="li">
<p><text class="required" v-if="obj.state != 'look'">*</text></p>
@ -106,6 +106,7 @@
vvyInfo: [],
importExportFlagName: "",
spmTradeName: "",
//
optionData: [],
@ -214,6 +215,7 @@
this.vvyInfo.forEach(v => {
if (v.vvyId == e) {
this.importExportFlagName = v.importExportFlagName
this.spmTradeName = v.tradeTypeName
}
})
},

View File

@ -12,11 +12,11 @@
<view class="liInfo">
<p>类型{{item.type}}</text></p>
<p>贸易类型{{item.tradeTypeName}}</p>
<p>进出口{{item.importExportFlagName}}</p>
<p>进出口{{item.importExportName}}</p>
</view>
<view class="status didNot">
<!-- <view class="status didNot">
<p>未上传</p>
</view>
</view> -->
<!-- <view class="status success">
<p>已上传</p>
</view>
@ -38,40 +38,68 @@
data() {
return {
title: "船只 - 安全巡检",
vtpId: "",
shipInfo: {},
infoList: []
infoList: [],
portObj: {},
loginObj: {},
vvyList: "",
}
},
mounted() {
// this.shipInfo = uni.getStorageSync('shipInfo')
// this.infoList = uni.getStorageSync('addPatrolArr')
this.executeSql1('safetyInspectionRespList')
this.executeSql1('shipInfoTable')
let title = uni.getStorageSync('shipWorkTitle')
this.title = `${title} / 安全巡检`
this.portObj = uni.getStorageSync('portObj')
this.loginObj = uni.getStorageSync('loginObj')
this.vtpId = uni.getStorageSync('vtpId')
this.executeSql1('voyageScheduleDataDetailRespDTOList')
},
methods: {
//
executeSql1(tableName) {
let sql = `select * from ${tableName}`
let sql = `select * from ${tableName} WHERE vtpId = '${this.vtpId}'`
sqlite.executeSqlCeshi(sql).then((value) => {
// resolve
if (tableName == 'shipInfoTable') {
this.shipInfo = value[0]
this.title = `${this.shipInfo.vslCnname} - 安全巡检`
} else {
this.infoList = value
}
this.vvyList = value
let vvyIds = []
this.vvyList.forEach(v => {
vvyIds.push(v.vvyId)
})
this.initData(vvyIds)
}).catch((error) => {
// reject
console.error(error);
});
},
initData(vvyIds) {
// console.log(`pamId=${this.portObj.portId}&vvyIds=${vvyIds}`)
uni.request({
url: `${this.$local}/api/safetyInspection/page?pamId=${this.portObj.portId}&vvyIds=${vvyIds}`,
header: {
'Content-Type': 'application/json', //
'Authorization': `Bearer ${this.loginObj.access_token}`
},
method: 'GET', //
success: (res) => {
this.infoList = res.data.data.records
},
fail: () => {
}
})
},
add(state, item, index) {
if (state != 'add') {
uni.setStorageSync('patrolRow', item);
uni.setStorageSync('patrolRowIndex', index);
}
let id = ""
if (state != 'add') {
id = item.vsiId
}
const obj = {
state: state,
id: id,
}
const params = encodeURIComponent(JSON.stringify(obj));
uni.navigateTo({

View File

@ -11,32 +11,32 @@
<p><text class="required" v-if="obj.state != 'look'">*</text></p>
<uni-data-select v-model="vvyId" :localdata="hcList" @change="hcChange"
v-if="obj.state != 'look'"></uni-data-select>
<text v-else>{{vvyName}}</text>
<text v-else>{{patrolRow.vvyName}}</text>
</view>
<view class="li">
<p>进出口</p>
<text>{{shipInfo.importExportFlagName}}</text>
<text>{{importExportName}}</text>
</view>
<view class="li">
<p>贸易类型</p>
<text>{{shipInfo.spmTradeName}}</text>
<text>{{spmTradeName}}</text>
</view>
<view class="li">
<p><text class="required" v-if="obj.state != 'look'">*</text></p>
<uni-easyinput v-if="obj.state != 'look'" v-model="type" placeholder="请输入"></uni-easyinput>
<text v-else>{{type}}</text>
<text v-else>{{patrolRow.type}}</text>
</view>
<view class="li tpLi">
<p><text class="required" v-if="obj.state != 'look'">*</text></p>
<template v-if="obj.state != 'look'">
<view class="picture">
<uni-file-picker limit="9" v-model="urlList" fileMediatype="image" @select="select"
<uni-file-picker limit="9" v-model="urlList2" fileMediatype="image" @select="select"
@delete="delUrl" title="最多选择9张图片"></uni-file-picker>
</view>
</template>
<view class="pictureLook" v-else>
<view v-for="(item,index) in urlList" :key="index">
<image :src="item.url" mode="widthFix">
<view v-for="(item,index) in urlList2" :key="index">
<image :src="item.url" mode="widthFix" @click="clickImg(urlList2,index)">
</view>
</view>
</view>
@ -44,7 +44,7 @@
<p><text class="required" v-if="obj.state != 'look'">*</text></p>
<uni-easyinput type="textarea" autoHeight v-model="remark" placeholder="请输入任务描述(200字以内)"
maxlength="200" v-if="obj.state != 'look'"></uni-easyinput>
<text v-else>{{remark}}</text>
<text v-else>{{patrolRow.remark}}</text>
</view>
</view>
<uni-popup ref="popup" type="dialog">
@ -79,10 +79,14 @@
data() {
return {
title: "",
vtpId: "",
portObj: {},
loginObj: {},
shipInfo: {},
obj: {},
importExportName: "",
spmTradeName: "",
patrolRow: {},
patrolRowIndex: {},
//
vvyId: "",
vvyName: "",
@ -91,7 +95,7 @@
type: "",
//
urlList: [],
ysUrl: [],
urlList2: [],
delUrlList: [],
//
remark: "",
@ -108,6 +112,12 @@
},
mounted() {
that = this
this.portObj = uni.getStorageSync('portObj')
this.loginObj = uni.getStorageSync('loginObj')
this.vtpId = uni.getStorageSync('vtpId')
this.executeSql1('shipOption')
this.executeSql1("shipInfoTable")
this.executeSql1("voyageScheduleDataDetailRespDTOList")
if (this.obj.state == 'edit' || this.obj.state == 'look') {
this.getRow();
}
@ -118,14 +128,11 @@
} else {
this.title = "新增安全巡检"
}
this.executeSql1('shipOption')
this.executeSql1("shipInfoTable")
},
methods: {
//
executeSql1(tableName) {
let sql = `select * from ${tableName}`
let sql = `select * from ${tableName} WHERE vtpId = '${this.vtpId}'`
sqlite.executeSqlCeshi(sql).then((value) => {
// resolve
if (tableName == 'shipOption') {
@ -133,22 +140,21 @@
this.getShip()
} else if (tableName == 'shipInfoTable') {
this.shipInfo = value[0]
} else if (tableName == 'voyageScheduleDataDetailRespDTOList') {
this.vvyInfo = value
this.hcList = []
value.forEach((v, index) => {
this.hcList.push({
text: v.vvyName,
value: v.vvyId
})
})
}
}).catch((error) => {
// reject
console.error(error);
});
},
executeSql2(tableName) {
let sql = `select * from ${tableName} WHERE contactId = '${this.patrolRow.webId}'`
sqlite.executeSqlCeshi(sql).then((value) => {
// resolve
this.urlList = value
}).catch((error) => {
// reject
console.error(error);
});
},
//
getShip() {
// this.shipInfo = uni.getStorageSync('shipInfo')
@ -168,13 +174,49 @@
},
//
getRow() {
this.patrolRow = uni.getStorageSync('patrolRow');
this.patrolRowIndex = uni.getStorageSync('patrolRowIndex');
this.executeSql2('safetyInspectionRespUrlList')
this.vvyId = this.patrolRow.vvyId
this.vvyName = this.patrolRow.vvyName
this.type = this.patrolRow.type
this.remark = this.patrolRow.remark
uni.request({
url: `${this.$local}/api/safetyInspection/details/${this.obj.id}`,
header: {
'Content-Type': 'application/json', //
'Authorization': `Bearer ${this.loginObj.access_token}`
},
method: 'GET', //
success: (res) => {
this.patrolRow = res.data.data
this.vvyId = this.patrolRow.vvyId
this.vvyName = this.patrolRow.vvyName
this.importExportName = this.patrolRow.importExportName
this.spmTradeName = this.patrolRow.tradeTypeName
this.type = this.patrolRow.type
this.remark = this.patrolRow.remark
if (this.patrolRow.url != null) {
this.urlList = this.patrolRow.url
} else {
this.urlList = []
}
if (this.patrolRow.url.length > 0 || this.patrolRow.url != null) {
this.patrolRow.url.forEach(v => {
this.initImg(v)
})
}
},
fail: (error) => {}
})
},
initImg(fileName) {
uni.request({
url: `${this.$local}/api/file/url/?fileName=${fileName}`,
header: {
'Content-Type': 'application/json', //
'Authorization': `Bearer ${this.loginObj.access_token}`
},
method: 'GET', //
success: (res) => {
if (res.statusCode == 200) {
this.urlList2.push(res.data)
}
}
})
},
//
hcChange(e) {
@ -184,39 +226,53 @@
this.vvyName = v.text
}
})
this.vvyInfo.forEach(v => {
if (v.vvyId == e) {
this.importExportName = v.importExportFlagName
this.spmTradeName = v.tradeTypeName
}
})
},
//
select(e) {
pathToBase64(e.tempFiles[0].path)
.then(base64 => {
compressImgBySize(base64, 1200).then(baseImg => {
// base64
that.ysUrl.push(baseImg)
uni.uploadFile({
url: `${this.$local}/api/file/upload`, //api
header: {
'Authorization': `Bearer ${this.loginObj.access_token}`
},
filePath: e.tempFilePaths[0],
name: 'file',
success: (res) => {
this.urlList2.push(e.tempFiles[0])
let data = JSON.parse(res.data).data
this.urlList.push(data.filePath)
},
fail(e) {
uni.showToast({
title: `${e.errMsg}`,
icon: 'none',
duration: 2000
})
})
.catch(error => {
console.error(error)
})
}
})
},
//
delUrl(e) {
if (this.obj.state == 'edit') {
this.delUrlList.push(e.tempFile.webId)
}
pathToBase64(e.tempFile.path)
.then(base64 => {
compressImgBySize(base64, 1200).then(baseImg => {
// base64
this.ysUrl.forEach((v, index) => {
if (v == base64) {
this.ysUrl.splice(index, 1)
}
})
})
})
.catch(error => {
console.error(error)
})
let delIndex = 0
this.urlList2.forEach((v, index) => {
if (v.uuid == e.tempFile.uuid) {
delIndex = index
}
})
this.urlList.splice(delIndex, 1)
this.urlList2.splice(delIndex, 1)
},
//
clickImg(urlList, index) {
uni.previewImage({
current: index,
urls: urlList,
})
},
//
cancel() {
@ -226,14 +282,22 @@
},
//
delConfirm() {
for (var i = 0; i < this.urlList.length; i++) {
let delSql = `DELETE FROM safetyInspectionRespUrlList WHERE webId = '${this.urlList[i].webId}';`
this.executeSql(delSql)
}
let sql = `DELETE FROM safetyInspectionRespList WHERE webId = '${this.patrolRow.webId}';`
this.executeSql(sql)
uni.navigateTo({
url: '/pages/shipWork/patrol'
uni.request({
url: `${this.$local}/api/safetyInspection/${this.obj.id}`,
header: {
'Content-Type': 'application/json', //
'Authorization': `Bearer ${this.loginObj.access_token}`
},
method: 'DELETE', //
success: (res) => {
console.log(res)
if (res.statusCode == 200) {
uni.navigateTo({
url: '/pages/shipWork/patrol'
})
}
},
fail: () => {}
})
},
//
@ -242,43 +306,70 @@
},
//
save() {
let date = new Date().getTime()
let webId = uuidv4()
let webStatus = 0
let webDate = api.getDate(date)
if (this.obj.state == "edit") {
let sql =
`UPDATE safetyInspectionRespList SET vvyId = '${this.vvyId}', vvyName = '${this.vvyName}',
type = '${this.type}',remark = '${this.remark}', webStatus = '${webStatus}',
webDate = '${webDate}' WHERE webId = '${this.patrolRow.webId}';`
this.executeSql(sql)
if (this.delUrlList.length > 0) {
for (var i = 0; i < this.delUrlList.length; i++) {
let delSql = `DELETE FROM safetyInspectionRespUrlList WHERE webId = '${this.delUrlList[i]}';`
this.executeSql(delSql)
}
}
for (var i = 0; i < this.ysUrl.length; i++) {
let webId2 = uuidv4()
let sql2 = `insert into safetyInspectionRespUrlList values('${webId2}','${this.patrolRow.webId}','${this.ysUrl[i]}',
'${webStatus}','${webDate}')`
this.executeSql(sql2)
}
} else if (this.obj.state == "add") {
let sql = `insert into safetyInspectionRespList values('${webId}','${this.vvyId}','${this.vvyName}',
'${this.type}','${this.remark}','${this.shipInfo.tradeTypeName}','${this.shipInfo.importExportFlagName}',
'${this.shipInfo.spmName}','${webStatus}','${webDate}')`
this.executeSql(sql)
for (var i = 0; i < this.ysUrl.length; i++) {
let webId2 = uuidv4()
let sql2 = `insert into safetyInspectionRespUrlList values('${webId2}','${webId}','${this.ysUrl[i]}',
'${webStatus}','${webDate}')`
this.executeSql(sql2)
}
let spmTradeName = ""
if (this.spmTradeName == "内贸") {
spmTradeName = "N"
} else {
spmTradeName = "W"
}
let importExportName = ""
if (this.importExportName == "进口") {
importExportName = "I"
} else {
importExportName = "E"
}
let dto = {
"importExport": importExportName,
"remark": this.remark,
"spmId": this.vtpId,
"tradeType": spmTradeName,
"type": this.type,
"uploadStatus": "",
"url": this.urlList,
"vsiId": this.obj.id,
"vvyId": this.vvyId
}
if (this.obj.state == "add") {
uni.request({
url: `${this.$local}/api/safetyInspection`,
data: dto,
header: {
'Content-Type': 'application/json', //
'Authorization': `Bearer ${this.loginObj.access_token}`
},
method: 'POST', //
success: (res) => {
if (res.statusCode == 200) {
uni.navigateTo({
url: '/pages/shipWork/patrol'
})
}
},
fail: (e) => {
console.log(e)
}
})
} else if (this.obj.state == 'edit') {
uni.request({
url: `${this.$local}/api/safetyInspection/${this.obj.id}`,
data: dto,
header: {
'Content-Type': 'application/json', //
'Authorization': `Bearer ${this.loginObj.access_token}`
},
method: 'PUT', //
success: (res) => {
if (res.statusCode == 200) {
uni.navigateTo({
url: '/pages/shipWork/patrol'
})
}
},
fail: (e) => {
console.log(e)
}
})
}
uni.navigateTo({
url: '/pages/shipWork/patrol'
})
},
//
toGo(state) {

View File

@ -20,7 +20,7 @@
</view>
<view class="li">
<p>贸易类型</p>
<text>{{shipInfo.spmTradeName}}</text>
<text>{{spmTradeName}}</text>
</view>
<view class="li">
<p><text class="required" v-if="obj.state != 'look'">*</text></p>
@ -131,6 +131,7 @@
vvyInfo: [],
importExportFlagName: "",
spmTradeName: "",
//
optionData: [],
@ -250,6 +251,7 @@
this.vvyInfo.forEach(v => {
if (v.vvyId == e) {
this.importExportFlagName = v.importExportFlagName
this.spmTradeName = v.tradeTypeName
}
})
},

View File

@ -19,7 +19,7 @@
</view>
<view class="li">
<p>贸易类型</p>
<text>{{shipInfo.spmTradeName}}</text>
<text>{{spmTradeName}}</text>
</view>
<view class="li">
<p><text class="required">*</text>装卸类型</p>
@ -311,6 +311,7 @@
vvyInfo: [],
importExportFlagName: "",
spmTradeName: "",
contactId: "",
aId: "",
@ -625,6 +626,7 @@
this.vvyInfo.forEach(v => {
if (v.vvyId == e) {
this.importExportFlagName = v.importExportFlagName
this.spmTradeName = v.tradeTypeName
}
})
},

View File

@ -20,7 +20,7 @@
</view>
<view class="li">
<p>贸易类型</p>
<text>{{shipInfo.spmTradeName}}</text>
<text>{{spmTradeName}}</text>
</view>
<view class="li">
<p><text class="required" v-if="obj.state != 'look'">*</text></p>
@ -103,6 +103,7 @@
vvyInfo: [],
importExportFlagName: "",
spmTradeName: "",
//
optionData: [],
@ -211,6 +212,7 @@
this.vvyInfo.forEach(v => {
if (v.vvyId == e) {
this.importExportFlagName = v.importExportFlagName
this.spmTradeName = v.tradeTypeName
}
})
},

View File

@ -25,7 +25,7 @@
</view>
<view class="li">
<p>贸易类型</p>
<text>{{shipInfo.spmTradeName}}</text>
<text>{{spmTradeName}}</text>
</view>
<view class="li">
<p><text class="required" v-if="obj.state != 'look'">*</text></p>
@ -120,6 +120,7 @@
vvyInfo: [],
importExportFlagName: "",
spmTradeName: "",
//
optionData: [],
@ -227,6 +228,7 @@
this.vvyInfo.forEach(v => {
if (v.vvyId == e) {
this.importExportFlagName = v.importExportFlagName
this.spmTradeName = v.tradeTypeName
}
})
},

View File

@ -20,7 +20,7 @@
</view>
<view class="li">
<p>贸易类型</p>
<text>{{shipInfo.spmTradeName}}</text>
<text>{{spmTradeName}}</text>
</view>
<view class="li">
<p><text class="required" v-if="obj.state != 'look'">*</text></p>
@ -125,6 +125,7 @@
vvyInfo: [],
importExportFlagName: "",
spmTradeName: "",
//
optionData: [],
@ -251,6 +252,7 @@
this.vvyInfo.forEach(v => {
if (v.vvyId == e) {
this.importExportFlagName = v.importExportFlagName
this.spmTradeName = v.tradeTypeName
}
})
},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 159 KiB

View File

@ -0,0 +1,17 @@
## 1.0.72022-05-26
1. 优化局部改变数据更新问题,避免重新加载数据,只改变局部
## 1.0.62022-04-18
1. 修改tab快速切换时会出现的BUG
## 1.0.52022-04-18
1. 修复可能存在数据错误的BUG
2. 兼容今后可以无需调用refresh()就可以更新数据;
## 1.0.42022-04-18
1. 修复BUG
## 1.0.32022-04-15
1. 优化代码;
2. 修改懒加载数据存在的BUG
## 1.0.12022-03-11
1. 增加隐藏图片字段的键名字段hideImageKey默认hide
2. 支持在列表中配置hide参数进行隐藏图片
## 1.0.02022-03-09
使用最简单的思想实现瀑布流

View File

@ -0,0 +1,323 @@
<template>
<view class="waterfalls-flow">
<view v-for="(item,index) in data.column" :key="index" class="waterfalls-flow-column" :id="`waterfalls_flow_column_${index+1}`" :msg="msg" :style="{'width':w,'margin-left':index==0?0:m}">
<view :class="['column-value',{'column-value-show':item2.o}]" v-for="(item2,index2) in columnValue(index)" :key="index2" :style="[s1]" @click.stop="wapperClick(item2)">
<view class="inner" v-if="data.seat==1">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef VUE2 -->
<slot name="slot{{item2.index}}"></slot>
<!-- #endif -->
<!-- #ifdef VUE3 -->
<slot :name="`slot${item2.index}`"></slot>
<!-- #endif -->
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<slot v-bind="item2"></slot>
<!-- #endif -->
</view>
<image :class="['img',{'img-hide':item2[hideImageKey]==true||item2[hideImageKey]==1},{'img-error':!item2[data.imageKey]}]" :src="item2[data.imageKey]" mode="widthFix" @load="imgLoad(item2,index+1)" @error="imgError(item2,index+1)" @click.stop="imageClick(item2)"></image>
<view class="inner" v-if="data.seat==2">
<!-- #ifdef MP-WEIXIN -->
<!-- #ifdef VUE2 -->
<slot name="slot{{item2.index}}"></slot>
<!-- #endif -->
<!-- #ifdef VUE3 -->
<slot :name="`slot${item2.index}`"></slot>
<!-- #endif -->
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<slot v-bind="item2"></slot>
<!-- #endif -->
</view>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
value: Array,
column: { //
type: [String, Number],
default: 2
},
maxColumn: { //
type: [String, Number],
default: 5
},
columnSpace: { //
type: [String, Number],
default: 2
},
imageKey: { // key
type: [String],
default: 'image'
},
hideImageKey: { // key
type: [String],
default: 'hide'
},
seat: { // 1 2
type: [String, Number],
default: 2
},
listStyle: { // eg:{'background':'red'}
type: Object
}
},
data() {
return {
data: {
list: this.value ? this.value : [],
column: this.column < 2 ? 2 : this.column,
columnSpace: this.columnSpace <= 5 ? this.columnSpace : 5,
imageKey: this.imageKey,
seat: this.seat
},
msg: 0,
listInitStyle: {
'border-radius': '12rpx',
'margin-bottom': '20rpx',
'background-color': '#fff'
},
adds: [], //
isLoaded: true,
curIndex: 0,
isRefresh: true,
flag: false,
refreshDatas: []
}
},
computed: {
//
w() {
const column_rate = `${100 / this.data.column - (+this.data.columnSpace)}%`;
return column_rate;
},
// margin
m() {
const column_margin = `${(100-(100 / this.data.column - (+this.data.columnSpace)).toFixed(5)*this.data.column)/(this.data.column-1)}%`;
return column_margin;
},
// list
s1() {
return { ...this.listInitStyle, ...this.listStyle };
}
},
created() {
//
this.refresh();
},
methods: {
//
loadImages(idx = 0) {
let count = 0;
const newList = this.data.list.filter((item, index) => index >= idx);
for (let i = 0; i < newList.length; i++) {
// #ifndef APP-PLUS
uni.getImageInfo({
src: `${newList[i][this.imageKey]}.jpg`,
complete: res => {
count++;
if (count == newList.length) this.initValue(idx);
}
})
// #endif
// #ifdef APP-PLUS
plus.io.getImageInfo({
src: `${newList[i][this.imageKey]}.jpg`,
complete: res => {
count++;
if (count == newList.length) this.initValue(idx);
}
})
// #endif
}
},
//
refresh() {
if (!this.isLoaded) {
this.refreshDatas = this.value;
return false;
};
setTimeout(() => {
this.refreshDatas = [];
this.isRefresh = true;
this.adds = [];
this.data.list = this.value ? this.value : [];
this.data.column = this.column < 2 ? 2 : this.column >= this.maxColumn ? this.maxColumn : this.column;
this.data.columnSpace = this.columnSpace <= 5 ? this.columnSpace : 5;
this.data.imageKey = this.imageKey;
this.data.seat = this.seat;
this.curIndex = 0;
//
for (let i = 1; i <= this.data.column; i++) {
this.data[`column_${i}_values`] = [];
this.msg++;
}
this.$nextTick(() => {
this.initValue(this.curIndex, 'refresh==>');
})
}, 1)
},
columnValue(index) {
return this.data[`column_${index+1}_values`];
},
change(newValue) {
for (let i = 0; i < this.data.list.length; i++) {
const cv = this.data[`column_${this.data.list[i].column}_values`];
for (let j = 0; j < cv.length; j++) {
if (newValue[i] && i === cv[j].index) {
this.data[`column_${this.data.list[i].column}_values`][j] = Object.assign(cv[j], newValue[i]);
this.msg++;
break;
}
}
}
},
getMin(a, s) {
let m = a[0][s];
let mo = a[0];
for (var i = a.length - 1; i >= 0; i--) {
if (a[i][s] < m) {
m = a[i][s];
}
}
mo = a.filter(i => i[s] == m);
return mo[0];
},
//
getMinColumnHeight() {
return new Promise(resolve => {
const heightArr = [];
for (let i = 1; i <= this.data.column; i++) {
const query = uni.createSelectorQuery().in(this);
query.select(`#waterfalls_flow_column_${i}`).boundingClientRect(data => {
heightArr.push({ column: i, height: data.height });
}).exec(() => {
if (this.data.column <= heightArr.length) {
resolve(this.getMin(heightArr, 'height'));
}
});
}
})
},
async initValue(i, from) {
this.isLoaded = false;
if (i >= this.data.list.length || this.refreshDatas.length) {
this.msg++;
this.loaded();
return false;
}
const minHeightRes = await this.getMinColumnHeight();
const c = this.data[`column_${minHeightRes.column}_values`];
this.data.list[i].column = minHeightRes.column;
c.push({ ...this.data.list[i], cIndex: c.length, index: i, o: 0 });
this.msg++;
},
//
imgLoad(item, c) {
const i = item.index;
item.o = 1;
this.$set(this.data[`column_${c}_values`], item.cIndex, JSON.parse(JSON.stringify(item)));
this.initValue(i + 1);
},
//
imgError(item, c) {
const i = item.index;
item.o = 1;
item[this.data.imageKey] = null;
this.$set(this.data[`column_${c}_values`], item.cIndex, JSON.parse(JSON.stringify(item)));
this.initValue(i + 1);
},
//
loaded() {
if (this.refreshDatas.length) {
this.isLoaded = true;
this.refresh();
return false;
}
this.curIndex = this.data.list.length;
if (this.adds.length) {
this.data.list = this.adds[0];
this.adds.splice(0, 1);
this.initValue(this.curIndex);
} else {
if (this.data.list.length) this.$emit('loaded');
this.isLoaded = true;
this.isRefresh = false;
}
},
//
wapperClick(item) {
this.$emit('wapperClick', item);
},
//
imageClick(item) {
this.$emit('imageClick', item);
}
},
watch: {
value: {
deep: true,
handler(newValue, oldValue) {
setTimeout(() => {
this.$nextTick(() => {
if (this.isRefresh) return false;
if (this.isLoaded) {
// if (newValue.length <= this.curIndex) return this.refresh();
if (newValue.length <= this.curIndex) return this.change(newValue);
this.data.list = newValue;
this.$nextTick(() => {
this.initValue(this.curIndex, 'watch==>');
})
} else {
this.adds.push(newValue);
}
})
}, 10)
}
},
column(newValue) {
this.refresh();
}
}
}
</script>
<style lang="scss" scoped>
.waterfalls-flow {
overflow: hidden;
&-column {
float: left;
}
}
.column-value {
width: 100%;
font-size: 0;
overflow: hidden;
transition: opacity .4s;
opacity: 0;
&-show {
opacity: 1;
}
.inner {
font-size: 30rpx;
}
.img {
width: 100%;
&-hide {
display: none;
}
&-error {
background: #f2f2f2 url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAC4AAAAiAQMAAAAatXkPAAAABlBMVEUAAADMzMzIT8AyAAAAAXRSTlMAQObYZgAAAIZJREFUCNdlzjEKwkAUBNAfEGyCuYBkLyLuxRYW2SKlV1JSeA2tUiZg4YrLjv9PGsHqNTPMSAQuyAJgRDHSyvBPwtZoSJXakeJI9iuRLGDygdl6V0yKDtyMAeMPZySj8yfD+UapvRPj2JOwkyAooSV5IwdDjPdCPspe8LyTl9IKJvDETKKRv6vnlUasgg0fAAAAAElFTkSuQmCC) no-repeat center center;
}
}
}
</style>

View File

@ -0,0 +1,80 @@
{
"id": "custom-waterfalls-flow",
"displayName": "瀑布流 灵活配置 简单易用 兼容vue2vue3小程序、H5、app等多端",
"version": "1.0.7",
"description": "瀑布流根据内容自动计算进行流式布局简单参数配置实现兼容多端及vue2和vue3的瀑布流布局uv-ui发布https://ext.dcloud.net.cn/plugin?name=uv-ui",
"keywords": [
"瀑布流",
"瀑布流式布局"
],
"repository": "https://gitee.com/my_dear_li_pan/my-uni-modules.git",
"engines": {
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"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": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "u",
"Edge": "u",
"Firefox": "y",
"Safari": "u"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "y",
"字节跳动": "y",
"QQ": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,445 @@
- <a href="#c1" title="概要">概要</a>
- <a href="#c2" title="支持的平台">支持的平台</a>
- <a href="#c3" title="使用方式">使用方式</a>
- <a href="#c4" title="属性说明">属性说明</a>
- <a href="#c5" title="事件说明">事件说明</a>
- <a href="#c6" title="组件方法">组件方法</a>
- <a href="#c7" title="refresh的使用示例">refresh的使用示例</a>
- <a href="#c8" title="隐藏单项图片示例">隐藏单项图片示例</a>
- <a href="#c9" title="完整示例">完整示例</a>
- <a href="#c10" title="温馨提示">温馨提示</a>
- <a href="#c11" title="关注我,不迷路">关注我,不迷路</a>
- <a href="#c12" title="个人作品展示">个人作品展示</a>
<div id="c1"></div>
#### 概要
custom-waterfalls-flow是一个瀑布流插件灵活配置、简单易用、兼容多端、同时兼容vue2和vue3。
最近在做项目的时候需要用到瀑布流,于是在插件市场找了一些,下载量最高的是用了定位来做的,我认为瀑布流可以不用定位去实现,于是我就自己写了该插件。经过反复的测试优化,最终搞定!
**设置列数:** 瀑布流的列数可以通过参数直接控制实时监听随改随生效。列数最小为2最大默认为5可以通过maxColumn参数去控制最大列数理论上可以设置无限大具体值自己拿捏。
**更新数据:** 瀑布流的每项数据可以直接通过修改value随改随生效这样可以实现加载更多数据。已经渲染过的数据不会再次渲染每次只会渲染新增的数据这样避免了数据越多渲染越慢的情况。可以调用组件的```refresh()```方法进行数据刷新注意vue2和vue3中调用子组件的方法有区别也会在下面进行说明。
**展示方式:** 瀑布流可以是纯图片可以使用插槽自定义文字描述微信小程序与app、h5使用会有些区别也会在下面具体说明。内容高度及排序都不用担心会根据每项的内容高度自动计算。
**实现思路:** 通过配置列数,先渲染出每列,再计算每列的高度,最小的那列就加入一条数据进行渲染,然后再重复计算每列,高度小的加入数据...其实思路是很简单的。
uniapp插件市场地址[https://ext.dcloud.net.cn/plugin?id=7594](https://ext.dcloud.net.cn/plugin?id=7594)
<div id="c2"></div>
#### 支持的平台
H5、app、微信小程序这三个平台经过反复测试优化兼容vue2和vue3
百度小程序:由于插槽不能循环渲染的限制,只支持纯图片瀑布流。
其他小程序:暂未测试,需要的可以自己测试和修改,思路肯定是没错的,主要是兼容插槽的问题。
nvue暂不支持后期可能会支持目前需要的可以自己修改源码。
<div id="c3"></div>
#### 使用方式
**1、导入插件**
该组件符合uni_modules规范使用Hbuilderx导入插件导入到项目根目录中的uni_modules文件夹中。
**2、template中使用**
uni_modules规范在项目页面中直接使用不需要单独引入注册组件。
***纯图片瀑布流使用***
```
<template>
<custom-waterfalls-flow :value="data.list"></custom-waterfalls-flow>
</template>
```
***微信小程序自定义内容使用***
微信小程序没有动态模板使用for循环的方式进行渲染。
```
<template>
<custom-waterfalls-flow :value="data.list">
<view class="item" v-for="(item,index) in data.list" :key="index" slot="slot{{index}}">
<view class="title">{{item.title}}</view>
<view class="desc">{{item.desc}}</view>
</view>
</custom-waterfalls-flow>
</template>
```
***h5、app端自定义内容使用***
使用作用域插槽实现
```
<template>
<custom-waterfalls-flow :value="data.list">
<template v-slot:default="item">
<view class="item">
<view class="title">{{item.title}}</view>
<view class="desc">{{item.desc}}</view>
</view>
</template>
</custom-waterfalls-flow>
</template>
```
***小程序、h5、app等多端自定义内容使用***
条件渲染-多端同时兼容
```
<template>
<custom-waterfalls-flow :value="data.list">
<!-- #ifdef MP-WEIXIN -->
<view class="item" v-for="(item,index) in data.list" :key="index" slot="slot{{index}}">
<view class="title">{{item.title}}</view>
<view class="desc">{{item.desc}}</view>
</view>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<template v-slot:default="item">
<view class="item">
<view class="title">{{item.title}}</view>
<view class="desc">{{item.desc}}</view>
</view>
</template>
<!-- #endif -->
</custom-waterfalls-flow>
</template>
```
<div id="c4"></div>
#### 属性说明
参数|说明|类型|是否必填|可选值|默认值
-|-|-|-|-|-|
value|渲染的列表|Array|是|-|-
column|列数|Number|否|2-maxColumn|2
maxColumn|最大列数|Number|否|>2|5
columnSpace|列之间的间距(单位是百分比)|Number|否|-|2
imageKey|列表中的图片字段的键名|String|否|-|image
hideImageKey|隐藏图片字段的键名|String|否|-|hide
seat|自定义文字的位置,1-图片上方2-图片下方|Number|否|1/2|2
listStyle|单个展示项的样式|Object|否|示例:```{'background':'red'}```|-
<div id="c5"></div>
#### 事件说明
事件名称|说明|回调参数
-|-|-|
@loaded|图片加载完成事件|-
@wapperClick|单项点击事件|单项对应参数
@imageClick|图片点击事件|单项对应参数
<div id="c6"></div>
#### 组件方法
事件名称|说明|参数|使用场景
-|-|-|-
refresh|刷新数据数据初始化vue2中使用```this.$refs.waterfallsFlowRef.refresh();```vue3中使用```const waterfallsFlowRef = ref(null);waterfallsFlowRef.value.refresh();```|-|下拉刷新等
<div id="c7"></div>
#### refresh的使用示例
***vue2中使用***
```
<template>
<view>
<button class="btn" type="default" @click="reset()">刷新数据</button>
<custom-waterfalls-flow ref="waterfallsFlowRef" :value="data.list"></custom-waterfalls-flow>
</view>
</template>
<script>
export default {
data() {
return {
data:{
list: [
{ image: 'https://via.placeholder.com/200x500.png/ff0000', title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' },
{ image: 'https://via.placeholder.com/200x200.png/2878ff', title: '我是标题2', desc: '描述描述描述描述描述描述描述描述2' }
]
}
}
},
reset(){
this.data.list = [{ image: 'https://via.placeholder.com/200x500.png/ff0000', title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' }]
this.$refs.waterfallsFlowRef.refresh();
}
}
</script>
```
***vue3中使用***
```
<template>
<view>
<button class="btn" type="default" @click="reset()">刷新数据</button>
<custom-waterfalls-flow ref="waterfallsFlowRef" :value="data.list"></custom-waterfalls-flow>
</view>
</template>
<script setup>
import { reactive, ref } from 'vue';
const data = reactive({
list: [
{ image: 'https://via.placeholder.com/200x500.png/ff0000', title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' },
{ image: 'https://via.placeholder.com/200x200.png/2878ff', title: '我是标题2', desc: '描述描述描述描述描述描述描述描述2' }
]
});
const waterfallsFlowRef = ref(null);
function reset(){
data.list = [{ image: 'https://via.placeholder.com/200x500.png/ff0000', title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' }]
waterfallsFlowRef.value.refresh();
}
</script>
```
<div id="c8"></div>
#### 隐藏单项图片示例
在数据列表中配置```hide:true```或者```hide:1```就可以达到不显示图片的效果。支持使用参数hideImageKey自定义键名称那就使用```定义的键名称:true```或者```定义的键名称:1```。
```
<template>
<custom-waterfalls-flow :value="data.list">
<!-- #ifdef MP-WEIXIN -->
<view class="item" v-for="(item,index) in data.list" :key="index" slot="slot{{index}}">
<view class="title">{{item.title}}</view>
<view class="desc">{{item.desc}}</view>
</view>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<template v-slot:default="item">
<view class="item">
<view class="title">{{item.title}}</view>
<view class="desc">{{item.desc}}</view>
</view>
</template>
<!-- #endif -->
</custom-waterfalls-flow>
</template>
<script setup>
import { reactive, ref } from 'vue';
const data = reactive({
list: [
{ image: 'https://via.placeholder.com/200x500.png/ff0000',
hide:1,title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' },
{ image: 'https://via.placeholder.com/200x200.png/2878ff', title: '我是标题2', desc: '描述描述描述描述描述描述描述描述2' }
]
});
</script>
```
<div id="c9"></div>
#### 完整示例
```
<template>
<view style="padding: 0 10rpx;">
<view class="handle">
<button class="btn" type="default" @click="add()">增加数据</button>
<button class="btn" type="default" @click="changeColumn(1)">+列数({{column}})</button>
<button class="btn" type="default" @click="changeColumn(0)">-列数({{column}})</button>
<button class="btn" type="default" @click="reset()">刷新数据</button>
</view>
<custom-waterfalls-flow ref="waterfallsFlowRef" :value="data.list" :column="column" :columnSpace="1.5" :seat="2" @wapperClick="wapperClick" @imageClick="imageClick" @loaded="loaded">
<!-- #ifdef MP-WEIXIN -->
<view class="item" v-for="(item,index) in data.list" :key="index" slot="slot{{index}}">
<view class="title">{{item.title}}</view>
<view class="desc">{{item.desc}}</view>
</view>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<template v-slot:default="item">
<view class="item">
<view class="title">{{item.title}}</view>
<view class="desc">{{item.desc}}</view>
</view>
</template>
<!-- #endif -->
</custom-waterfalls-flow>
</view>
</template>
<script setup>
// #ifdef VUE3
import { reactive, ref, onMounted } from 'vue';
const data = reactive({
list: [{ image: 'https://via.placeholder.com/200x500.png/ff0000', title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' },
{ image: 'https://via.placeholder.com/200x200.png/2878ff', title: '我是标题2', desc: '描述描述描述描述描述描述描述描述2' },
{ image: 'https://via.placeholder.com/200x100.png/FFB6C1', title: '我是标题3', desc: '描述描述描述描述描述描述描述描述3' },
{ image: 'https://via.placeholder.com/200x300.png/9400D3', title: '我是标题4', desc: '描述描述描述描述描述描述描述描述4' },
{ image: 'https://via.placeholder.com/100x240.png/B0E0E6', title: '我是标题5', desc: '描述描述描述描述描述描述描述描述5' },
{ image: 'https://via.placeholder.com/140x280.png/7FFFAA', title: '我是标题6', desc: '描述描述描述描述描述描述描述描述6' },
{ image: 'https://via.placeholder.com/40x60.png/EEE8AA', title: '我是标题7', desc: '描述描述描述描述描述描述描述描述7' }]
});
const column = ref(3);
function add() {
const newArr = [{ image: 'https://via.placeholder.com/58x100.png/FF7F50', title: '我是标题8', desc: '描述描述描述描述描述描述描述描述8' },
{ image: 'https://via.placeholder.com/59x100.png/C0C0C0', title: '我是标题9', desc: '描述描述描述描述描述描述描述描述9' },
{ image: 'https://via.placeholder.com/60x100.png/FAEBD7', title: '我是标题10', desc: '描述描述描述描述描述描述描述描述10' }];
data.list = data.list.concat(newArr);
}
function changeColumn(h) {
column.value = !h ? column.value - 1 : column.value + 1;
}
function loaded() {
console.log('加载完成')
}
function wapperClick(item) {
console.log('单项点击事件', item)
}
function imageClick(item) {
console.log('图片点击事件', item)
}
const waterfallsFlowRef = ref(null);
function reset() {
data.list = [{ image: 'https://via.placeholder.com/200x500.png/ff0000', title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' }]
waterfallsFlowRef.value.refresh();
}
// #endif
</script>
<script>
// #ifdef VUE2
export default {
data() {
return {
data: {
list: [{ image: 'https://via.placeholder.com/200x500.png/ff0000', title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' },
{ image: 'https://via.placeholder.com/200x200.png/2878ff', title: '我是标题2', desc: '描述描述描述描述描述描述描述描述2' },
{ image: 'https://via.placeholder.com/200x100.png/FFB6C1', title: '我是标题3', desc: '描述描述描述描述描述描述描述描述3' },
{ image: 'https://via.placeholder.com/200x300.png/9400D3', title: '我是标题4', desc: '描述描述描述描述描述描述描述描述4' },
{ image: 'https://via.placeholder.com/100x240.png/B0E0E6', title: '我是标题5', desc: '描述描述描述描述描述描述描述描述5' },
{ image: 'https://via.placeholder.com/140x280.png/7FFFAA', title: '我是标题6', desc: '描述描述描述描述描述描述描述描述6' },
{ image: 'https://via.placeholder.com/40x60.png/EEE8AA', title: '我是标题7', desc: '描述描述描述描述描述描述描述描述7' }]
},
column: 3
}
},
methods: {
add() {
const newArr = [{ image: 'https://via.placeholder.com/58x100.png/FF7F50', title: '我是标题8', desc: '描述描述描述描述描述描述描述描述8' },
{ image: 'https://via.placeholder.com/59x100.png/C0C0C0', title: '我是标题9', desc: '描述描述描述描述描述描述描述描述9' },
{ image: 'https://via.placeholder.com/60x100.png/FAEBD7', title: '我是标题10', desc: '描述描述描述描述描述描述描述描述10' }]
this.data.list = this.data.list.concat(newArr);
},
changeColumn(h) {
this.column = !h ? this.column - 1 : this.column + 1;
},
loaded() {
console.log('加载完成')
},
wapperClick(item) {
console.log('单项点击事件', item)
},
imageClick(item) {
console.log('图片点击事件', item)
},
reset() {
this.data.list = [{ image: 'https://via.placeholder.com/200x500.png/ff0000', title: '我是标题1', desc: '描述描述描述描述描述描述描述描述1' }]
this.$refs.waterfallsFlowRef.refresh();
}
}
}
// #endif
</script>
<style>
page {
background-color: #f2f5f9;
}
</style>
<style lang="scss" scoped>
.handle {
display: flex;
flex-direction: row;
flex-wrap: wrap;
margin-bottom: 20rpx;
padding: 10rpx;
.btn {
margin: 20rpx 10rpx;
padding: 0 20rpx;
background: #2878FF;
font-size: 28rpx;
color: #fff;
&::after {
border: 0;
}
}
}
.item {
padding: 10rpx 10rpx 20rpx;
.title {
line-height: 48rpx;
font-size: 28rpx;
color: #222;
}
.desc {
font-size: 24rpx;
color: #666;
}
}
</style>
```
<div id="c10"></div>
#### 温馨提示
1、该插件反复测试过微信小程序、h5、app-vue三个端vue2和vue3都兼容其他端可能需要测试改进。
2、该插件的使用hbuilderx版本最好升级到较新版本我开发的版本是hbuilderx3.3.11.20220209。
3、对此插件或相关问题有好的建议可以直接在评论区进行讨论。
4、希望遇到问题不要喷也不要骂人其实这种心情我能理解写该插件也不是一时半会就完成了的所以希望互相理解。只要有问题我会第一时间回复解决。
5、对此插件有任何问题的可以在下方留言我会第一时间回复和解决问题。还可以加QQ群进行前端技术交流 568984539加群备注地区-名字-技术类型’。
#### 最后我想说:认为该插件对你有帮助的,记得收藏、好评,这样可以帮助到更多人哟!
---
<div id="c11"></div>
#### 关注我,不迷路
如果任何疑问的可以在评论区留言还可以加QQ群交流568984539加群备注地区-名字-技术类型’。
更多前端等相关知识可关注我个人博客https://blog.csdn.net/qq_42961150?spm=1011.2124.3001.5343
<div id="c12"></div>
#### 个人作品展示
uniapp+vue3.2+unicloud开发微信小程序**皮皮虎去水印**。
关注下方公众号:【**全网免费网盘资源**】、【**美团外卖饿了么天天领红包**】、【**去水印**】
![image](https://vkceyugu.cdn.bspapp.com/VKCEYUGU-bb657efd-fece-483e-a715-5daea480fde8/6e029310-aec8-46e9-9883-1c88dc1925ad.jpg)

View File

@ -0,0 +1,22 @@
## 1.1.62022-10-24
修复 初始化渲染变量取值错误问题
## 1.1.52022-10-24
初始化渲染增加状态条件判断
## 1.1.42022-10-24
修改空数据提示
## 1.1.32022-10-24
1、增加内容插槽
2、删除状态文本属性
3、组件创建时可触发渲染条件
## 1.1.22022-09-26
修改了开启布局的判断条件
## 1.1.12022-08-28
1、加强组件化封装
2、 完善注释和优化使用逻辑
## 1.1.02022-08-22
重写渲染列表逻辑
## 1.0.12021-06-08
修改插入方向计算方式

View File

@ -0,0 +1,98 @@
<template>
<view class="waterfall-item-container">
<view class="waterfall-item" @tap="onTap">
<image :src="params.url" mode="widthFix" @load="emitHeight" @error="emitHeight"></image>
<view class="content">
<view>{{params.title}}</view>
<view class="money">{{params.money}}</view>
<view style="margin: 0 0 8rpx 0;">
<text class="label">{{params.label}}</text>
</view>
<view class="shop-name">{{params.shop}}</view>
123
</view>
</view>
</view>
</template>
<script>
export default {
name:"helangWaterfallItem",
options:{
virtualHost: true
},
props:{
params:{
type: Object,
default(){
return {}
}
},
tag:{
type:String | Number,
default:''
},
index:{
type:Number,
default:-1
}
},
data() {
return {
};
},
methods:{
//
emitHeight(e){
const query = uni.createSelectorQuery().in(this);
query.select('.waterfall-item-container').boundingClientRect(data => {
let height = Math.floor(data.height);
this.$emit("height",height,this.$props.tag);
}).exec();
},
onTap(){
this.$emit("click",this.$props.index,this.$props.tag);
}
}
}
</script>
<style lang="less" scoped>
.waterfall-item{
padding: 16rpx;
background-color: #fff;
border-radius: 4px;
font-size: 28rpx;
color: #666;
image{
display: block;
width: 100%;
//
height: 350rpx;
}
.content{
margin-top: 16rpx;
.money{
color: #fa3534;
margin-top: 8rpx;
}
.label{
background-color: #fa3534;
color: #fff;
font-size: 20rpx;
padding: 4rpx 16rpx;
border-radius: 20rpx;
}
.shop-name{
font-size: 20rpx;
color: #999;
}
}
}
</style>

View File

@ -0,0 +1,241 @@
<template>
<view>
<view class="waterfall-box h-flex-x h-flex-2">
<view>
<view v-for="(item,index) in leftList" :key="item._render_id"
class="list-item"
>
<!-- :class="{'show': showPage > item._current_page }" -->
456
<helang-waterfall-item
:params="item"
tag="left"
:index="index"
@height="onHeight"
@click="onClick"
></helang-waterfall-item>
</view>
</view>
<view>
<view v-for="(item,index) in rightList" :key="item._render_id"
class="list-item"
>
456
<helang-waterfall-item
:params="item"
@height="onHeight"
@click="onClick"
tag="right"
:index="index"
></helang-waterfall-item>
</view>
</view>
</view>
<slot name="default"></slot>
</view>
</template>
<script>
import helangWaterfallItem from "./waterfall-item.vue"
export default {
name:"helangWaterfallList",
options:{
virtualHost: true
},
components: {
"helang-waterfall-item": helangWaterfallItem
},
props:{
//
status:{
type: String,
default:''
},
//
list:{
type: Array,
default(){
return [];
}
},
// true
reset:{
type: Boolean,
default:false
},
},
watch:{
"$props.status"(newValue,oldValue){
//
if(newValue == 'success'){
this.startRender();
}else if(!this.showList){
this.resetData();
}
}
},
computed:{
showList(){
return !["fail","empty"].includes(this.$props.status);
}
},
data() {
return {
//
leftHeight: 0,
//
rightHeight: 0,
//
leftList: [],
//
rightList: [],
//
awaitRenderList:[],
//
showPage:1
}
},
mounted() {
if(this.$props.status == 'success'){
this.startRender();
}
console.log('-----',this.list);
},
methods: {
//
onHeight(height, tag) {
/**
* 这个为实际渲染后 CSS margin-buttom 的值本示例默认为20rpx
* 用于解决实际渲染后因为数据条数关系高度差计算偏差的问题
* */
let marginBottom = uni.upx2px(20);
// console.log(`${this.leftHeight},${this.rightHeight},${height},${tag}`)
if (tag == 'left') {
this.leftHeight += (height + marginBottom);
} else {
this.rightHeight += (height + marginBottom);
}
this.renderList();
},
//
onClick(index, tag){
//
if(tag == 'left'){
this.$emit("click",this.leftList[index],index,tag);
}else{
this.$emit("click",this.rightList[index],index,tag);
}
},
//
renderList() {
// 0
if(this.awaitRenderList.length < 1){
this.showPage++;
this.$emit("done");
// js 1亿
if(this.leftHeight > 100000000){
if(this.leftHeight > this.rightHeight){
this.leftHeight = 2;
this.rightHeight = 1;
}else{
this.leftHeight = 1;
this.rightHeight = 2;
}
}
return;
}
let item = {
...this.awaitRenderList.splice(0,1)[0],
//
_current_page:this.showPage,
// id v-for load BUG
_render_id:new Date().getTime()
};
if(this.leftHeight > this.rightHeight){
this.rightList.push(item);
}else{
this.leftList.push(item);
}
console.log('r',this.rightList,this.leftList);
},
//
resetData(){
this.leftHeight = 0;
this.rightHeight = 0;
this.leftList = [];
this.rightList = [];
this.awaitRenderList = [];
//
this.showPage = 1;
},
//
startRender(){
if(!this.showList){
this.resetData();
return;
}
if(!this.$props.list || this.$props.list.length < 1){
console.log('河浪瀑布流插件提示:当前数据为空,不会触发列表渲染');
return;
}
//
if(this.$props.reset){
this.resetData();
}
this.awaitRenderList = [...this.$props.list];
this.renderList();
}
}
}
</script>
<style lang="less" scoped>
.waterfall-box {
padding: 20rpx 10rpx;
box-sizing: border-box;
>view {
padding: 0 10rpx;
}
.list-item{
margin-bottom: 0;
//
opacity: 0;
//
overflow: hidden;
height: 0;
.show{
margin-bottom: 20rpx;
opacity: 1;
overflow: auto;
height: auto;
}
}
}
.h-flex-x {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-start;
align-items: flex-start;
align-content: flex-start;
&.h-flex-2 {
>view {
width: 50%;
}
}
}
</style>

View File

@ -0,0 +1,39 @@
let list = ()=>{
return new Promise((resolve,reject)=>{
setTimeout(() => {
// 生成随机数方法
let random = (min = 0, max) => {
return Math.floor(Math.random() * max) + min;
}
// 待选的图片数据
let imgs = [];
// 待选的标题数据
let titles = [
'桃花坞里桃花庵,桃花庵里桃花仙;',
'桃花仙人种桃树,又摘桃花卖酒钱。',
'酒醒只在花前坐,酒醉还来花下眠;半醒半醉日复日,花落花开年复年。',
'但愿老死花酒间,不愿鞠躬车马前;',
'车尘马足富者趣,酒盏花枝贫者缘。若将富贵比贫贱,',
'一在平地一在天;若将贫贱比车马,他得驱驰我得闲。',
'别人笑我太疯癫,我笑他人看不穿;不见五陵豪杰墓,无花无酒锄作田。'
];
let res = [];
for (let i = 0; i < 10; i++) {
res.push({
id:i+1,
url:`/uni_modules/helang-waterfall/static/waterfall/${random(0,3)}.jpg?t=${new Date().getTime()}`,
title: titles[random(0, titles.length)],
money: random(9, 9999),
label:'官方自营',
shop:'唐诗三百首旗舰店'
})
}
resolve(res);
}, 500);
})
}
export default {
getList:list
}

View File

@ -0,0 +1,76 @@
{
"id": "helang-waterfall",
"displayName": "瀑布流布局-waterfall",
"version": "1.1.6",
"description": "这是一款简单又好用的瀑布流布局模板,通过页面模板+组件的方式。充分利用组件的复用性和页面的生命周期。",
"keywords": [
"瀑布流",
"布局",
"列表",
"waterfall"
],
"repository": "https://gitee.com/myDarling/uniapp-extend",
"engines": {
"HBuilderX": "^3.2.11"
},
"dcloudext": {
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "",
"type": "uniapp-template-page"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"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": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
}
}
}
}
}

View File

@ -0,0 +1,247 @@
<template>
<view>
<helang-waterfall-list
:status="waterfall.status"
:list="waterfall.list"
:reset="waterfall.reset"
@click="onClick"
@done="onDone"
>
<template>
<view v-if="waterfall.status == 'await'">
<view class="load-txt">上拉加载更多</view>
</view>
<view v-else-if="waterfall.status == 'loading'">
<view class="load-txt">加载中</view>
</view>
<view v-else-if="waterfall.status == 'success'">
<view class="load-txt">加载中</view>
</view>
<view v-else-if="waterfall.status == 'finish'">
<view class="load-txt">没有更多了</view>
</view>
<view v-else-if="waterfall.status == 'fail'">
<image class="load-icon" src="../../static/waterfall/fail.png"></image>
<view class="load-txt">出错了请刷新重试</view>
</view>
<view v-else-if="waterfall.status == 'empty'">
<image class="load-icon" src="../../static/waterfall/empty.png"></image>
<view class="load-txt">暂无数据</view>
</view>
<view v-else><!-- v-else view,H5CSSBUG --></view>
</template>
</helang-waterfall-list>
<view class="status-change" @tap="onStatusChange">
<view>切换<br />状态</view>
</view>
</view>
</template>
<script>
import helangWaterfallList from "@/uni_modules/helang-waterfall/components/waterfall/waterfall-list"
//
import mockData from '../../mock-data/waterfall-list.js'
export default {
components: {
"helang-waterfall-list": helangWaterfallList
},
data() {
return {
//
ajax: {
//
load: true,
//
rows:10,
//
page:1,
//
dataList:[]
},
//
waterfall:{
status:"",
reset:false,
list:[]
}
}
},
onReady() {
this.getList();
},
//
onReachBottom() {
this.getList();
},
//
onPullDownRefresh(){
//
setTimeout(()=>{
this.ajax.page = 1;
this.ajax.load = true;
this.getList();
},800);
},
methods: {
//
onClick(data,index, tag){
console.log(data);
let direction = {
"left":'左',
"right":'右'
}
uni.showToast({
title:`${direction[tag]}侧列表第${index+1}个被点击`,
icon:'none'
})
},
//
onDone(){
//
this.waterfall.reset = false;
// getList
this.ajax.load = true;
this.ajax.page++;
//
this.waterfall.status = 'await';
/**
* 如果你是一个追求完美的开发者可以通过判断当前数据的长度和请求的数据长度来优化前端请求减少不必要请求
* 不需要则删除
* */
/**
if(this.ajax.dataCount >= this.ajax.rows){
this.ajax.load = true;
this.ajax.page++;
}
*/
},
//
getList() {
if (!this.ajax.load) {
return;
}
this.ajax.load = false;
//
this.waterfall.status = 'loading';
// mockData.getList ajax
mockData.getList({
pageNum:this.ajax.page,
pageSize:this.ajax.rows
}).then(res=>{
//
console.log(res);
//
if(this.ajax.page == 1){
//
uni.stopPullDownRefresh();
// 使
this.waterfall.reset = true;
}
//
if(!res || res.length < 1){
//
this.waterfall.status = 'finish';
return;
}
// list
this.waterfall.list = res;
//
this.waterfall.status = 'success';
/**
* 下方的代码为扩展其他功能的示例代码 可做参考不需要可删除
* */
// 使
if(this.ajax.page == 1){
this.ajax.dataList = res;
}else{
this.ajax.dataList = [...this.ajax.dataList,...res];
}
// done
this.ajax.dataCount = res.length || 0;
//
})
},
//
onStatusChange(){
uni.showActionSheet({
itemList: ['常规', '加载异常', '加载错误'],
success: (res)=> {
switch(res.tapIndex){
case 0:
this.ajax.page = 1;
this.ajax.load = true;
this.getList();
break;
case 1:
// alert(111)
this.waterfall.status = 'fail';
break;
case 2:
this.waterfall.status = 'empty';
break;
default:
}
}
});
},
}
}
</script>
<style lang="scss">
page {
background-color: #f3f3f3;
}
.load-txt{
padding: 0 0 20rpx 0;
text-align: center;
color: #999;
font-size: 24rpx;
}
.load-icon{
width: 300rpx;
height: 300rpx;
margin: 0 auto 20rpx auto;
display: block;
}
.status-change{
position: fixed;
right: 10rpx;
top: 60%;
width: 80rpx;
height: 80rpx;
z-index: 100;
font-size: 24rpx;
border-radius: 50%;
background-color: #0089ff;
color: #fff;
line-height: 1;
opacity: .33;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
justify-content: center;
align-items: center;
align-content: center;
}
</style>

View File

@ -0,0 +1 @@
# helang-waterfall

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

View File

@ -0,0 +1,24 @@
## 1.1.12023-08-01
优化文档
## 1.1.02023-08-01
优化文档
## 1.0.92023-07-10
优化文档
## 1.0.82023-06-25
优化文档
## 1.0.72023-06-25
优化文档
## 1.0.62023-05-26
优化文档
## 1.0.52023-05-22
优化文档
## 1.0.42023-04-30
新增图片放大功能解决原生组件和tabbar导航栏等无法覆盖的问题
## 1.0.32023-04-28
优化文档
## 1.0.22023-04-28
优化文档
## 1.0.12023-04-28
新增长按事件
## 1.0.02023-04-28
插件上线

View File

@ -0,0 +1,121 @@
<template>
<view class="previewImage" v-if="show" @tap="close">
<view class="page" v-if="urls.length > 0">
<text class="text">{{ current + 1 }} / {{ urls.length }}</text>
</view>
<swiper class="swiper" :current="current" @change="swiperChange" @touchstart="handleTouchStart" @touchend="handleTouchEnd">
<swiper-item v-for="(item, index) in urls" :key="index">
<movable-area class="movable-area" scale-area>
<movable-view class="movable-view" direction="all" :inertia="true" damping="100" scale="true" scale-min="1" scale-max="4" :scale-value="scale">
<scroll-view scroll-y="true" class="uni-scroll-view">
<view class="scroll-view"><image :key="index" class="image" :src="item" mode="widthFix" @longpress="onLongpress(item)" /></view>
</scroll-view>
</movable-view>
</movable-area>
</swiper-item>
</swiper>
</view>
</template>
<script>
export default {
props: {
urls: {
type: Array,
required: true,
default: () => {
return [];
}
}
},
data() {
return {
show: false,
current: 0, //
scale: 1,
isZooming: false //
};
},
methods: {
//
open(current) {
this.current = this.urls.findIndex(item => item === current);
this.show = true;
this.$emit('open');
},
//
close() {
if (!this.isZooming) {
this.show = false;
this.current = 0;
this.$emit('close');
}
},
//
swiperChange(e) {
this.current = e.detail.current;
},
//
onLongpress(e) {
this.$emit('onLongpress', e);
},
handleTouchStart() {
this.isZooming = true;
},
handleTouchEnd() {
this.isZooming = false;
}
}
};
</script>
<style lang="scss" scoped>
.previewImage {
z-index: 9999;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #000000;
.swiper {
width: 100%;
height: 100vh;
swiper-item {
.movable-area {
height: 100%;
width: 100%;
.movable-view {
width: 100%;
min-height: 100%;
.scroll-view {
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
.image {
width: 100%;
height: auto;
}
}
}
}
}
}
.page {
position: absolute;
z-index: 9999;
width: 100%;
top: 60rpx;
text-align: center;
.text {
color: #fff;
font-size: 32rpx;
background-color: rgba(0, 0, 0, 0.5);
padding: 3rpx 16rpx;
border-radius: 20rpx;
user-select: none;
}
}
}
</style>

View File

@ -0,0 +1,81 @@
{
"id": "q-previewImage",
"displayName": "图片预览、多图左右滑动、图片放大、支持覆盖原生组件、原生导航栏、tabbar",
"version": "1.1.1",
"description": "最简洁的模拟图片预览,支持长按事件,多图左右滑动,大图上下滑动查看,支持图片放大,支持覆盖原生组件/原生导航栏/tabbar 支持vue2/vue3/app/小程序/h5",
"keywords": [
"图片预览"
],
"repository": "",
"engines": {
"HBuilderX": "^3.4.14"
},
"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": "y"
},
"App": {
"app-vue": "y",
"app-nvue": "n"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "u",
"微信浏览器(Android)": "u",
"QQ浏览器(Android)": "u"
},
"H5-pc": {
"Chrome": "u",
"IE": "u",
"Edge": "u",
"Firefox": "u",
"Safari": "u"
},
"小程序": {
"微信": "y",
"阿里": "u",
"百度": "u",
"字节跳动": "u",
"QQ": "u",
"钉钉": "u",
"快手": "u",
"飞书": "u",
"京东": "u"
},
"快应用": {
"华为": "u",
"联盟": "u"
}
}
}
}
}

View File

@ -0,0 +1,244 @@
# 最简洁的模拟图片预览,支持长按事件,多图左右滑动,大图上下滑动查看,支持图片放大,支持覆盖原生组件/原生导航栏/tabbar 支持vue2/vue3/app/小程序/h5
- 为了解决项目中因一些特殊原因无法使用uni.previewImage例如App.onShow或者页面的oShow中写了方法。
- 如果用uni.previewImage每次预览图片都会进到onShow的方法里
- 可以基本实现官方的预览图片功能但是体验不如uni.previewImage()
- 如没有特殊原因还是推荐官方的uni.previewImage()
## 安装指引
##1. 在插件市场打开本插件页面,在右侧点击`使用 HBuilderX 导入插件`,选择要导入的项目点击确定
##2. 使用方法 vue2写法
```
<template>
<view>
<video v-if="videoShow" id="myVideo" src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/%E7%AC%AC1%E8%AE%B2%EF%BC%88uni-app%E4%BA%A7%E5%93%81%E4%BB%8B%E7%BB%8D%EF%BC%89-%20DCloud%E5%AE%98%E6%96%B9%E8%A7%86%E9%A2%91%E6%95%99%E7%A8%8B@20200317.mp4" controls></video>
<image v-for="(item, index) in imgs" :key="index" :src="item" @click="preview(item)"></image>
<q-previewImage ref="previewImage" :urls="imgs" @onLongpress="onLongpress" @open="open" @close="close"></q-previewImage>
</view>
</template>
<script>
export default {
data() {
return {
videoShow:true,//video组件是否显示
imgs: [],
};
},
methods: {
preview(url) {
this.imgs = ['https://web-assets.dcloud.net.cn/unidoc/zh/multiport-20210812.png', 'https://web-assets.dcloud.net.cn/unidoc/zh/uni-function-diagram.png'] //设置图片数组
// #ifdef MP-WEIXIN
this.$nextTick(()=>{
this.$refs.previewImage.open(url); // 传入当前选中的图片地址(小程序必须添加$nextTick解决组件首次加载无图)
})
// #endif
// #ifndef MP-WEIXIN
this.$refs.previewImage.open(url); // 传入当前选中的图片地址
// #endif
},
onLongpress(e){ //长按事件
console.log('当前长按的图片是' + e);
uni.showActionSheet({
itemList: ['转发给朋友', '保存到手机'],
success: function (res) {
console.log('选中了第' + (res.tapIndex + 1) + '个按钮');
},
fail: function (res) {
console.log(res.errMsg);
}
});
},
/* open和close方法一般用不到但是在一些特殊场景会用到
* 比如预览图片时你需要覆盖 NavigationBar和 TabBar
* 或者在app中需要预览图片时覆盖住原生组件比如video或者map等
* 你可以根据open和close去做一些操作例如隐藏导航栏或者隐藏一些原生组件等
*/
open(){ //监听组件显示 隐藏TabBar和NavigationBar隐藏video原生组件
// uni.hideTabBar()
// uni.setNavigationBarColor({
// frontColor: '#000000', // 设置前景色为黑色
// backgroundColor: '#000000', // 设置背景色为黑色
// })
// this.videoShow = false
},
close(){ //监听组件隐藏 显示TabBar和NavigationBar显示video原生组件
// uni.showTabBar()
// uni.setNavigationBarColor({
// frontColor: '#ffffff', // 设置前景色为白色
// backgroundColor: '#000000', // 设置背景色为黑色
// })
// this.videoShow = true
}
}
};
</script>
```
##3. vue3 setup写法
```
<template>
<view>
<video v-if="videoShow" id="myVideo" src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/%E7%AC%AC1%E8%AE%B2%EF%BC%88uni-app%E4%BA%A7%E5%93%81%E4%BB%8B%E7%BB%8D%EF%BC%89-%20DCloud%E5%AE%98%E6%96%B9%E8%A7%86%E9%A2%91%E6%95%99%E7%A8%8B@20200317.mp4" controls></video>
<image v-for="(item, index) in imgs" :key="index" :src="item" @click="preview(item)"></image>
<q-previewImage ref="previewImage" :urls="imgs" @onLongpress="onLongpress" @open="open" @close="close"></q-previewImage>
</view>
</template>
<script setup>
import { reactive, ref, toRefs,nextTick } from 'vue';
const data = reactive({
videoShow:true,//video组件是否显示
imgs: [],
});
const previewImage = ref(null);
const { imgs,videoShow } = toRefs(data)// 解构
const preview = url => {
data.imgs = ['https://web-assets.dcloud.net.cn/unidoc/zh/multiport-20210812.png', 'https://web-assets.dcloud.net.cn/unidoc/zh/uni-function-diagram.png'] //设置图片数组
// #ifdef MP-WEIXIN
nextTick(()=>{
previewImage.value.open(url); // 传入当前选中的图片地址(小程序必须添加nextTick解决组件首次加载无图)
})
// #endif
// #ifndef MP-WEIXIN
previewImage.value.open(url); // 传入当前选中的图片地址
// #endif
};
const onLongpress = e =>{
console.log('当前长按的图片是' + e);
uni.showActionSheet({
itemList: ['转发给朋友', '保存到手机'],
success: function (res) {
console.log('选中了第' + (res.tapIndex + 1) + '个按钮');
},
fail: function (res) {
console.log(res.errMsg);
}
});
}
/* open和close方法一般用不到但是在一些特殊场景会用到
* 比如预览图片时你需要覆盖 NavigationBar和 TabBar
* 或者在app中需要预览图片时覆盖住原生组件比如video或者map等
* 你可以根据open和close去做一些操作例如隐藏导航栏或者隐藏一些原生组件等
*/
const open = () => { //监听组件显示 隐藏TabBar和NavigationBar隐藏video原生组件
// uni.hideTabBar()
// uni.setNavigationBarColor({
// frontColor: '#000000', // 设置前景色为黑色
// backgroundColor: '#000000', // 设置背景色为黑色
// })
// data.videoShow = false
}
const close = () => { //监听组件隐藏 显示TabBar和NavigationBar显示video原生组件
// uni.showTabBar()
// uni.setNavigationBarColor({
// frontColor: '#ffffff', // 设置前景色为白色
// backgroundColor: '#000000', // 设置背景色为黑色
// })
// data.videoShow = true
}
</script>
```
##4. 项目示例 (一般返回的数据图片是以逗号或特殊字符分割的字符串点击时就需要传两个参数一个是图片数组一个是当前图片的index)
## 注意q-previewImage不要写在循环体中imgs其实就是用来存放当前图片的数组每次点击每次赋值就行
```
<template>
<view>
<video v-if="videoShow" id="myVideo" src="https://img.cdn.aliyun.dcloud.net.cn/guide/uniapp/%E7%AC%AC1%E8%AE%B2%EF%BC%88uni-app%E4%BA%A7%E5%93%81%E4%BB%8B%E7%BB%8D%EF%BC%89-%20DCloud%E5%AE%98%E6%96%B9%E8%A7%86%E9%A2%91%E6%95%99%E7%A8%8B@20200317.mp4" controls></video>
<view v-for="(item, index) in list" :key="index" class="list">
<image :src="i" mode="aspectFill" v-for="(i,imgindex) in item.urls.split(',')" @click.stop="preimg(item.urls.split(','),imgindex)"></image>
<view>
<q-previewImage ref="previewImage" :urls="imgs" @onLongpress="onLongpress" @open="open" @close="close"></q-previewImage>
</view>
</template>
<script>
export default {
data() {
return {
videoShow:true,//是否显示video组件
imgs: [],//imgs其实就是用来存放当前图片的数组每次点击每次赋值就行
};
},
methods: {
preimg(urls,index){
this.imgs = urls //imgs其实就是用来存放当前图片的数组每次点击每次赋值就行
// #ifdef MP-WEIXIN
this.$nextTick(()=>{
this.$refs.previewImage.open(this.imgs[index]); // 传入当前选中的图片地址(小程序必须添加$nextTick解决组件首次加载无图)
})
// #endif
// #ifndef MP-WEIXIN
this.$refs.previewImage.open(this.imgs[index]); // 传入当前选中的图片地址
// #endif
},
onLongpress(e){ //长按事件
console.log('当前长按的图片是' + e);
uni.showActionSheet({
itemList: ['转发给朋友', '保存到手机'],
success: function (res) {
console.log('选中了第' + (res.tapIndex + 1) + '个按钮');
},
fail: function (res) {
console.log(res.errMsg);
}
});
},
/* open和close方法一般用不到但是在一些特殊场景会用到
* 比如预览图片时你需要覆盖 NavigationBar和 TabBar
* 或者在app中需要预览图片时覆盖住原生组件比如video或者map等
* 你可以根据open和close去做一些操作例如隐藏导航栏或者隐藏一些原生组件等
*/
open(){ //监听组件显示 隐藏TabBar和NavigationBar隐藏video原生组件
// uni.hideTabBar()
// uni.setNavigationBarColor({
// frontColor: '#000000', // 设置前景色为黑色
// backgroundColor: '#000000', // 设置背景色为黑色
// })
// this.videoShow = false
},
close(){ //监听组件隐藏 显示TabBar和NavigationBar显示video原生组件
// uni.showTabBar()
// uni.setNavigationBarColor({
// frontColor: '#ffffff', // 设置前景色为白色
// backgroundColor: '#000000', // 设置背景色为黑色
// })
// this.videoShow = true
}
}
};
</script>
```
## 如果插件对您有一点帮助,请给个五星好评,感谢支持
## 如有问题请加qq 965969604