瀏覽代碼

公告 注意事项 公告列表 公告详情

zhnghongrui 1 年之前
父節點
當前提交
403f1d0498

+ 14 - 1
api/common.js

@@ -2,7 +2,20 @@ import request from '@/utils/request'
 
 
 
-// 获取基建工程历史
+// 获取公告列表
+export function getNoticeList(noticeType,pageNum,pageSize) {
+	const data = {
+		noticeType,
+		pageNum,
+		pageSize
+
+	}
+	return request({
+		'url': '/system/notice/listApp',
+		'method': 'get',
+		'data': data
+	})
+}
 export function getInfrastructureList(id, type) {
 	return request({
 		'url': '/zdsz/engineeringInfrastructure/' + id + '/' + type,

+ 223 - 0
components/yxp-txt-scroll/yxp-txt-scroll.nvue

@@ -0,0 +1,223 @@
+<template>
+	<view class="container" :style="{minHeight: fontSize + 'rpx'}">
+		<view class="notice-text-box" ref="yxpTxtBox" id="yxpTxtBox">
+			<view class="notice-text-con" ref="yxpTxt" id="yxpTxt"
+				:style="{animationDuration: (durationB || duration) / 1000 + 's',animationDelay: delay / 1000 + 's', paddingLeft: boxWidth + 'px'}">
+				<text class="notice-text" v-for="(item,index) in text" :key="index" :style="{fontSize:fontSize+'rpx', color: fontColor, 
+					paddingRight: (index === text.length - 1) ? '0' : txtPadding + 'rpx'}"
+					@click="Select(item)">{{item.noticeTitle}}</text>
+			</view>
+		</view>
+	</view>
+</template>
+<script>
+	// #ifdef APP-NVUE
+	const Binding = uni.requireNativePlugin('bindingx');
+	const dom = weex.requireModule('dom');
+	// #endif
+	export default {
+		props: {
+			text: {
+				type: Array,
+				default: []
+			},
+			fontSize: {
+				type: String,
+				default: '24'
+			},
+			fontColor: {
+				type: String,
+				default: '#333333'
+			},
+			duration: {
+				type: String,
+				default: '0'
+			},
+			delay: {
+				type: String,
+				default: '0'
+			},
+			txtPadding: {
+				type: String,
+				default: '30'
+			},
+			speed: {
+				type: String,
+				default: '200'
+			}
+		},
+		computed: {},
+		data() {
+			return {
+				x: 0,
+				gesToken: 0,
+				textWidth: 0,
+				boxWidth: 375,
+				durationB: 0
+			}
+		},
+		mounted() {
+			this.initScrollWidth()
+		},
+		methods: {
+			Select(item) {
+				this.$emit("onClick", item)
+				// this.$parent.open=false;
+			},
+			initScrollWidth: function() {
+				let self = this;
+				// #ifdef APP-NVUE
+				setTimeout(() => {
+					dom.getComponentRect(this.$refs.yxpTxtBox, res => {
+						this.boxWidth = res.size.width;
+						dom.getComponentRect(this.$refs.yxpTxt, res => {
+							this.textWidth = res.size.width - 375 + this.boxWidth;
+							if (!this.duration || this.duration === 0 || this.duration ===
+								'0') {
+								this.durationB = parseInt(this.textWidth / parseFloat(this
+									.speed) * 1000)
+							}
+							this.bindTiming()
+						});
+					});
+
+				}, 500);
+				// #endif
+				// #ifndef APP-NVUE
+				let viewBox = uni.createSelectorQuery().in(this).select('#yxpTxtBox');
+				viewBox.fields({
+					size: true,
+					rect: true
+				}, data => {
+					self.boxWidth = data.width
+					let view = uni.createSelectorQuery().in(self).select('#yxpTxt');
+					view.fields({
+						size: true,
+						rect: true
+					}, data => {
+						self.textWidth = data.width
+						if (!self.duration || self.duration === 0 || self.duration === '0') {
+							self.durationB = parseInt(self.textWidth / parseFloat(self.speed) * 1000)
+						}
+					}).exec();
+				}).exec();
+
+				// #endif
+			},
+			getEl: function(el) {
+				if (typeof el === 'string' || typeof el === 'number') return el;
+				// #ifdef APP-PLUS
+				if (WXEnvironment) {
+					return el.ref;
+				} else {
+					return el instanceof HTMLElement ? el : el.$el;
+				}
+				// #endif
+				// #ifdef H5
+				return el instanceof HTMLElement ? el : el.$el;
+				// #endif
+			},
+			bindTiming: function() {
+				this.isInAnimation = true;
+				var my = this.getEl(this.$refs.yxpTxt);
+				var self = this;
+				var textWidth = -this.textWidth;
+				var delay = parseInt(this.delay);
+				var duration = this.durationB || this.duration;
+				var translate_x_origin = 'linear(t,0,' + textWidth + ',' + duration + ')';
+				setTimeout(() => {
+					var result = Binding.bind({
+						eventType: 'timing',
+						exitExpression: 't>' + duration,
+						props: [{
+							element: my,
+							property: 'transform.translateX',
+							expression: translate_x_origin
+						}]
+					}, function(e) {
+						if (e.state === 'end' || e.state === 'exit') {
+							Binding.unbind({
+								eventType: 'timing',
+								token: result.gesToken
+							});
+							self.bindTiming()
+						}
+					});
+					self.gesToken = result.token;
+				}, delay)
+
+			}
+		},
+	}
+</script>
+<style>
+	.container {
+		flex: 1;
+		display: flex;
+		flex-direction: column;
+		overflow: hidden;
+		/* #ifndef APP-PLUS */
+		height: 100%;
+		width: 100%;
+		/* #endif */
+	}
+
+	.notice-text-box {
+		display: flex;
+		flex-direction: column;
+		position: relative;
+		/* #ifndef APP-PLUS */
+		height: 100%;
+		width: 100%;
+		/* #endif */
+		flex: 1;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.notice-text-con {
+		flex: 1;
+		display: flex;
+		flex-direction: row;
+		align-items: center;
+		flex-wrap: nowrap;
+		/* #ifndef APP-PLUS */
+		white-space: pre;
+		-webkit-animation: scroll-left 3s infinite linear;
+		animation: scroll-left 3s infinite linear;
+		left: 0;
+		padding-left: 100%;
+		/* #endif */
+		position: absolute;
+		/* #ifdef APP-NVUE */
+		left: 0;
+		/* #endif */
+	}
+
+	/* #ifndef APP-NVUE */
+	@-webkit-keyframes scroll-left {
+		0% {
+			-webkit-transform: translateX(0);
+			transform: translateX(0);
+		}
+
+		100% {
+			-webkit-transform: translateX(-100%);
+			transform: translateX(-100%);
+		}
+	}
+
+	@keyframes scroll-left {
+		0% {
+			-webkit-transform: translateX(0);
+			transform: translateX(0);
+		}
+
+		100% {
+			-webkit-transform: translateX(-100%);
+			transform: translateX(-100%);
+		}
+	}
+
+	/* #endif */
+</style>

+ 1 - 4
config.js

@@ -2,12 +2,9 @@
 module.exports = {
   
 
-  baseUrl: 'http://192.168.4.11:8080',
-  // baseUrl: 'http://192.168.4.20:8080',
-  // baseUrl: 'http://192.168.4.6:8080',
   // baseUrl: 'http://192.168.4.11:8080',
   //baseUrl: 'http://192.168.4.20:8080',
-   // baseUrl: 'http://192.168.4.6:8080',
+    baseUrl: 'http://192.168.4.6:8080',
 
    // baseUrl: 'http://192.168.4.14:8089',
   // 应用信息

+ 18 - 5
pages.json

@@ -12,16 +12,15 @@
 		}, {
 			"path": "pages/index",
 			"style": {
-				"navigationBarTitleText": "若依移动端框架",
-				"navigationStyle": "custom"
+				"navigationBarTitleText": "通知公告"
 			}
 		}, {
 			"path": "pages/work/index",
 			"style": {
 				"navigationBarTitleText": "工作台"
 			}
-		},   
-		
+		},
+
 		{
 			"path": "pages/oldrenovation/indoor/indoor",
 			"style": {
@@ -41,7 +40,7 @@
 			}
 		},
 
-		 {
+		{
 			"path": "pages/statistics/statistics",
 			"style": {
 				"navigationBarTitleText": ""
@@ -76,6 +75,20 @@
 			"style": {
 				"navigationBarTitleText": "顶管工程"
 			}
+		},
+		{
+			"path": "pages/notice/noticeList",
+			"style": {
+				"navigationBarTitleText": "公告列表",
+
+				"enablePullDownRefresh ": true
+			}
+		}, {
+			"path": "pages/notice/noticeDetail",
+			"style": {
+				"navigationBarTitleText": "公告详情"
+
+			}
 		}
 
 	],

+ 128 - 36
pages/index.vue

@@ -1,43 +1,135 @@
 <template>
-  <view class="content">
-    <image class="logo" src="@/static/logo.png"></image>
-    <view class="text-area">
-      <text class="title">Hello RuoYi</text>
-    </view>
-  </view>
+
+
+	<view>
+		<view style="background-color: white;">
+			<view style=" display: flex;">
+				<text style="text-align:center;width: 35px;margin-left: 15px; margin-top: 10px;">
+					公告
+				</text>
+
+
+				<yxp-txt-scroll :text="content" fontSize="28" duration="5000" delay="500" txtPadding="10"
+					style=" margin: auto; width: 100%;" @onClick="goDetails"></yxp-txt-scroll>
+
+				<view style=" text-align: right;width: 70px;margin-top: 10px;color: darkgray" @click="more()"> 更多
+					<uni-icons type="right" color="darkgray" size="15"></uni-icons>
+				</view>
+			</view>
+			<view class="notice" v-for="(item,index) in list" :key="index" v-if="index==0">
+				<view class="justify-content">
+					<view class="font-forty">
+						{{item.noticeTitle}}
+					</view>
+					<view class="font-twenty-eight gray">
+						{{item.createTime}}
+					</view>
+				</view>
+				<view class="font-thirty-two black" style="margin: 30rpx 0;">
+					<rich-text :nodes="item.noticeContent"></rich-text>
+				</view>
+			</view>
+		</view>
+	</view>
 </template>
 
 <script>
-  export default {
-    onLoad: function() {
-    }
-  }
+	import uniIcons from '../uni_modules/uni-icons/components/uni-icons/uni-icons.vue'
+	import yxpTxtScroll from '../components/yxp-txt-scroll/yxp-txt-scroll.nvue'
+	import {
+		getNoticeList,
+	} from '@/api/common'
+	export default {
+		components: {
+			uniIcons,
+			yxpTxtScroll
+		},
+		data() {
+
+			return {
+				list: [],
+				content: [],
+				noticeDetails: {},
+			}
+		},
+		onLoad() {
+			this.lists()
+			uni.setNavigationBarTitle({
+				title: ''
+			})
+		},
+		methods: {
+			goDetails(item) {
+				uni.navigateTo({
+					url: '/pages/notice/noticeDetail?params=' + encodeURIComponent(JSON.stringify(
+						item))
+				})
+			},
+			more() {
+				uni.navigateTo({
+					url: '/pages/notice/noticeList'
+
+				});
+			},
+			lists() {
+				getNoticeList('3', '1', '').then(res => { //查注意事项
+
+					this.list = res.rows
+
+				})
+				getNoticeList('1', '1', '').then(res => { //通知公告
+
+					if (res.rows.length > 0) {
+						this.noticeDetails = res.rows[0]
+						this.content.push(
+							res.rows[0]
+						)
+					}
+
+				})
+			}
+		}
+	}
 </script>
 
 <style>
-  .content {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-  }
-
-  .logo {
-    height: 200rpx;
-    width: 200rpx;
-    margin-top: 200rpx;
-    margin-left: auto;
-    margin-right: auto;
-    margin-bottom: 50rpx;
-  }
-
-  .text-area {
-    display: flex;
-    justify-content: center;
-  }
-
-  .title {
-    font-size: 36rpx;
-    color: #8f8f94;
-  }
-</style>
+	.content {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+	}
+
+	.background {
+		border: 15px solid hsla(0, 0%, 100%, .5);
+		background: white;
+		background-clip: padding-box;
+		/*从padding开始往外面裁剪背景*/
+
+	}
+
+	.container {
+		display: flex;
+		margin-left: 10px;
+		margin-top: 10px;
+		margin-right: 10px;
+		align-items: flex-start;
+		justify-content: space-between;
+	}
+
+
+
+
+	.notice {
+		margin: 20rpx;
+		padding: 20rpx;
+		background: #FFFFFF;
+		box-shadow: 0rpx 8rpx 17rpx 0rpx rgba(0, 0, 0, 0.04);
+		border-radius: 10rpx;
+	}
+
+	.text {
+		border-left: 15rpx solid #3857F3;
+		padding-left: 20rpx;
+	}
+</style>

+ 67 - 0
pages/notice/noticeDetail.vue

@@ -0,0 +1,67 @@
+<template>
+	<view>
+		<view class="container">
+			<view class="notice">
+				<view class="justify-content">
+					<view class="font-forty">
+						标题:{{title}}
+					</view>
+					<view class="font-twenty-eight gray" style="margin-top: 10px;">
+						创建时间:{{time}}
+					</view>
+				</view>
+				<view class="font-thirty-two black" style="margin: 30rpx 0;">
+					<rich-text :nodes="text"></rich-text>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				text: '',
+				title: '',
+				time: ''
+			}
+		},
+		onLoad(options) {
+			if ('params' in options) {
+				let e = JSON.parse(decodeURIComponent(options.params));
+				this.text = e.noticeContent;
+				this.title = e.noticeTitle;
+				this.time = e.createTime;
+			}
+		},
+		methods: {
+
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.container {
+		display: flex;
+		margin-left: 10px;
+		margin-top: 10px;
+		margin-right: 10px;
+		align-items: flex-start;
+		justify-content: space-between;
+	}
+
+	.notice {
+		margin: 20rpx;
+		width: 100%;
+		padding: 20rpx;
+		background: #FFFFFF;
+		box-shadow: 0rpx 8rpx 17rpx 0rpx rgba(0, 0, 0, 0.04);
+		border-radius: 10rpx;
+	}
+
+	.text {
+		border-left: 15rpx solid #3857F3;
+		padding-left: 20rpx;
+	}
+</style>

+ 165 - 0
pages/notice/noticeList.vue

@@ -0,0 +1,165 @@
+<template>
+	<view class="container">
+		<cc-pullScroolView class="pullScrollView" ref="pullScroll" :pullDown="pullDown" :isDownLoading="true">
+			<view class="notice" v-for="(item,index) in list" :key="index">
+				<view class="justify-content" @click="goDetails(item)">
+					<view class="font-forty">
+						{{item.noticeTitle}}
+					</view>
+					<view class="font-twenty-eight gray">
+						{{item.createTime}}
+					</view>
+				</view>
+
+
+			</view>
+		</cc-pullScroolView>
+		<!-- 
+
+		<view v-if="isLoadMore">
+			<uni-load-more :status="loadStatus"></uni-load-more>
+		</view>
+	</view>
+	</view> -->
+	</view>
+</template>
+
+<script>
+	// import {
+	// 	onPullDownRefresh
+	// } from '@dcloudio/uni-app' // 下拉刷新
+	import CCBProjectList from '../../uni_modules/cc-pullScroolView/components/cc-pullScroolView/cc-pullScroolView.vue';
+	import {
+		getNoticeList,
+	} from '@/api/common'
+
+
+	export default {
+		components: {
+
+			CCBProjectList,
+
+		},
+		data() {
+			return {
+				list: [],
+				pageSize: 10, // 条数
+				totalNum: '',
+				pageNum: 1, // 页数
+			}
+		},
+		onLoad() {
+
+			this.getList()
+
+		},
+
+
+		// 上拉加载
+		onReachBottom() {
+			// 数据全部加载完
+
+			if (this.pageNum * 10 >= this.totalNum) {
+
+			} else {
+				// 显示加载中
+				this.$refs.pullScroll.showUpLoading();
+				this.pageNum++;
+				this.requestData();
+			}
+
+		},
+		methods: {
+			goDetails(item) {
+				uni.navigateTo({
+					url: '/pages/notice/noticeDetail?params=' + encodeURIComponent(JSON.stringify(
+						item))
+				})
+			},
+
+			// 下拉刷新
+			pullDown(pullScroll) {
+
+				console.log('下拉刷新');
+				//this.list = [];
+				this.pageNum = 1;
+				setTimeout(() => {
+					this.requestData(pullScroll);
+				}, 1000);
+
+			},
+			getList() {
+				let myThis = this;
+				this.$nextTick(() => {
+
+					myThis.$refs.pullScroll.refresh();
+
+				});
+			},
+			requestData() {
+
+				uni.showLoading()
+				getNoticeList('1', this.pageNum, this.pageSize).then(res => {
+					// console.log('getNoticeList',res)
+
+					this.totalNum = res.total
+
+					if (res.code == '200') {
+						uni.hideLoading()
+						if (res.rows.length !== 0) {
+							//首次加载10条数据,后进行拼接
+							if (this.pageNum == 1) {
+								this.list = [];
+								this.list = res.rows
+							} else {
+								this.list = this.list.concat(res.rows)
+							}
+
+							// 如果是最后一页
+							if (this.pageNum * 10 >= this.totalNum) {
+								this.$refs.pullScroll.finish();
+
+							} else {
+
+								// 不是最后一页
+								this.$refs.pullScroll.success();
+							}
+
+						} else {
+							this.$modal.msg("暂无数据")
+						}
+					} else {
+						this.$modal.msg(res.msg + "")
+					}
+
+				})
+
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.container {
+		display: flex;
+		margin-left: 10px;
+		margin-top: 10px;
+		margin-right: 10px;
+		align-items: flex-start;
+		justify-content: space-between;
+	}
+
+	.notice {
+		margin: 20rpx;
+		width: 100%;
+		padding: 20rpx;
+		background: #FFFFFF;
+		box-shadow: 0rpx 8rpx 17rpx 0rpx rgba(0, 0, 0, 0.04);
+		border-radius: 10rpx;
+	}
+
+	.text {
+		border-left: 15rpx solid #3857F3;
+		padding-left: 20rpx;
+	}
+</style>

+ 7 - 2
pages/oldrenovation/indoor/indoor.vue

@@ -8,10 +8,15 @@
 						</view>
 						<view v-if="this.isEmpty(this.projectValue.dictValue)" style="margin-top: 10;"
 							@click="pickerShow('gczq')">
-							<span style="color: darkgray;">请选择工程周期</span>
+							<span style="color: darkgray;">请选择工程周期
+						
+							</span>
+								<uni-icons type="right" color="darkgray" size="15"></uni-icons>
 						</view>
 						<view v-else class="uni-list-cell-db" style="margin-top: 10;" @click="pickerShow('gczq')">
-							<span style="color: black;">{{projectValue.dictLabel}}</span>
+							<span style="color: black;">{{projectValue.dictLabel}}
+							</span>
+							<uni-icons type="right" color="darkgray" size="15"></uni-icons>
 						</view>
 
 					</view>

+ 10 - 5
pages/work/index.vue

@@ -96,12 +96,14 @@
 				<view class="share-to">
 					<text>请选择</text>
 				</view>
-				<view class="content">
-					<view class="block" v-for="(item, index) in typeList" :key="index" @click="showTypeSheet(item)">
-						<!-- <image :src="item.image" mode="aspectFill"></image> -->
-						<text>{{item.dictLabel}}</text>
+				<scroll-view scroll-y="true" class="scroll-Y">
+					<view class="content">
+						<view class="block" v-for="(item, index) in typeList" :key="index" @click="showTypeSheet(item)">
+							<!-- <image :src="item.image" mode="aspectFill"></image> -->
+							<text>{{item.dictLabel}}</text>
+						</view>
 					</view>
-				</view>
+				</scroll-view>
 				<view class="cancel" @click.stop="handleHiddenShare">
 					<text>取消</text>
 				</view>
@@ -527,6 +529,9 @@
 		transition: all 0.3s ease;
 		transform: translateY(0%) !important;
 	}
+	.scroll-Y {
+		height: 350rpx;
+	}
 
 	// 离开分享动画
 	.share-item {

+ 16 - 0
uni_modules/cc-pullScroolView/changelog.md

@@ -0,0 +1,16 @@
+## 3.2.0(2024-01-07)
+组件优化
+## 3.2(2023-10-28)
+适配微信小程序
+## 3.1.0(2023-07-27)
+组件说明优化
+## 3.0.1(2023-07-14)
+组件优化 兼容vue3
+## 3.0(2023-07-11)
+优化上拉加载及使用说明
+## 2.0.2(2023-07-11)
+增加上拉加载指示
+## 2.0.1(2023-07-03)
+优化组件说明
+## 2.0.0(2023-06-21)
+组件优化

二進制
uni_modules/cc-pullScroolView/components/cc-pullScroolView/back-top.png


+ 624 - 0
uni_modules/cc-pullScroolView/components/cc-pullScroolView/cc-pullScroolView.vue

@@ -0,0 +1,624 @@
+<template>
+	<view class="ccPullScroll" :class="customClass" :style="{height}">
+		<scroll-view :id="scrollId" class="ccPullScrollview" :scroll-top="scrollTop" :scroll-with-animation="false"
+			:scroll-y="scrollAble" :enable-back-to-top="true" @scroll="scroll" @touchstart="touchstart"
+			@touchmove="touchmove" @touchend="touchend" :lower-threshold="upOffset" @touchcancel="touchend">
+			<view :style="{'transform': translateY, 'transition': transition}">
+				<view class="cc-pull-down-wrap"
+					:class="[{'is-success': isShowDownTip && isDownSuccess},{'is-error': isShowDownTip && isDownError}]"
+					:style="{'height':downOffset+'rpx'}">
+					<view class="cc-pull-loading-icon" v-if="!isShowDownTip"
+						:class="{'cc-pull-loading-rotate':isDownLoading}" :style="{'transform':downRotate}"></view>
+					<view>{{downText}}</view>
+				</view>
+
+				<slot></slot>
+
+				<slot name="empty" v-if="isEmpty"></slot>
+
+				<view class="cc-pull-up-wrap">
+					<slot name="up-loading" v-if="isUpLoading">
+						<view class="cc-pull-loading-icon cc-pull-loading-rotate"></view>
+						<view>{{upLoadingText}}</view>
+					</slot>
+					<slot name="up-error" v-if="isUpError && showUpError">
+						<view v-if="upErrorText" @click="onUpErrorClick">{{upErrorText}}</view>
+					</slot>
+					<slot name="up-finish" v-else-if="isUpFinish && showUpFinish">
+						<view v-if="upFinishText">{{upFinishText}}</view>
+					</slot>
+				</view>
+			</view>
+		</scroll-view>
+		<!-- 回到顶部按钮 -->
+		<view class="cc-pull-back-top" v-if="backTop" :class="{'is-show':isShowBackTop}" @click="onBackTop">
+			<slot name="backtop">
+				<image class="default-back-top" :src="defaultBackTopImgSrc" mode="aspectFill" />
+			</slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	// #ifndef VUE3
+	const defaultBackTopImgSrc = require('./back-top.png');
+	// #endif
+
+	// #ifdef VUE3
+	// const defaultBackTopImgSrc = new URL('./back-top.png', import.meta.url).href;
+	// 适配小程序
+	import defaultBackTopImgSrc from './back-top.png';
+	// #endif
+
+	export default {
+		name: 'ccPullScroll',
+		data() {
+			Object.assign(this, {
+				pullType: '',
+				scrollRealTop: 0, // 滚动条的位置
+				scrollHeight: 0,
+				page: 1,
+				startPoint: null,
+				lastPoint: null,
+				startTop: 0,
+				inTouchend: false,
+				movetype: 0,
+				startAngle: 0,
+				isMoveDown: false
+			});
+			return {
+				scrollId: 'ccPullScrollview-id-' + Math.random().toString(36).substr(2), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
+				defaultBackTopImgSrc,
+				downHight: 0, // 下拉刷新: 容器高度
+				downRotate: 0, // 下拉刷新: 圆形进度条旋转的角度
+				downText: '', // 下拉刷新: 提示的文本
+				isEmpty: false, // 是否显示空布局
+				isShowDownTip: false, // 下拉刷新提示结果
+				isDownSuccess: false, // 下拉刷新成功
+				isDownError: false, // 下拉刷新失败
+				isDownReset: false, // 下拉刷新: 是否显示重置的过渡动画
+				isDownLoading: false, // 下拉刷新: 是否显示加载中
+				isUpLoading: false, // 上拉加载: 是否显示 "加载中..."
+				isUpFinish: false, // 是否加载完毕
+				isUpError: false, // 是否上拉加载出错
+				isShowBackTop: false, // 是否显示回到顶部按钮
+				scrollAble: true, // 是否禁止下滑 (下拉时禁止,避免抖动)
+				scrollTop: 0 // 滚动条的位置
+			};
+		},
+		props: {
+			// class
+			customClass: {
+				type: String,
+				default: ''
+			},
+			// 设置高度,默认继承父级高度
+			height: {
+				default: '100%'
+			},
+			// 下拉时文案
+			pullingText: {
+				type: String,
+				default: '下拉刷新'
+			},
+			// 下拉释放时文案
+			loosingText: {
+				type: String,
+				default: '释放刷新'
+			},
+			// 下拉释放后文案
+			downLoadingText: {
+				type: String,
+				default: '正在刷新 ...'
+			},
+			// 上拉加载时文案
+			upLoadingText: {
+				type: String,
+				default: '加载中 ...'
+			},
+			// 是否显示下拉刷新成功
+			showDownSuccess: {
+				type: Boolean,
+				default: false
+			},
+			// 下拉刷新成功文案
+			downSuccessText: {
+				type: String,
+				default: '刷新成功'
+			},
+			// 是否显示下拉刷新失败
+			showDownError: {
+				type: Boolean,
+				default: false
+			},
+			// 下拉刷新失败文案
+			downErrorText: {
+				type: String,
+				default: '刷新失败'
+			},
+			// 是否显示上拉加载时失败
+			showUpError: {
+				type: Boolean,
+				default: true
+			},
+			// 上拉加载失败文案
+			upErrorText: {
+				type: String,
+				default: '加载失败,点击重新加载'
+			},
+			// 是否显示上拉加载数据全部完成
+			showUpFinish: {
+				type: Boolean,
+				default: true
+			},
+			// 上拉加载完毕文案
+			upFinishText: {
+				type: String,
+				default: '暂无更多了'
+			},
+			// 下拉配置
+			// 下拉回掉,参数为vm
+			pullDown: Function,
+			// 是否允许下拉刷新
+			enablePullDown: {
+				type: Boolean,
+				default: true
+			},
+			downOffset: {
+				type: Number,
+				default: 100
+			},
+			downMinAngle: {
+				type: Number,
+				default: 45
+			},
+			downInOffsetRate: {
+				type: Number,
+				default: 1
+			},
+			downOutOffsetRate: {
+				type: Number,
+				default: 0.2
+			},
+			downStartTop: {
+				type: Number,
+				default: 100
+			},
+			// 下拉释放失效高度
+			downTouchHeight: {
+				type: Number,
+				default: 1200
+			},
+
+
+			upOffset: {
+				type: Number,
+				default: 100
+			},
+			// 回到顶部
+			backTop: Boolean,
+			// 滚动距离大于多少rpx时触发
+			backTopOffset: {
+				type: Number,
+				default: 1000
+			}
+		},
+		computed: {
+			numBackTopOffset() {
+				return uni.upx2px(this.backTopOffset);
+			},
+			numDownStartTop() {
+				return uni.upx2px(this.downStartTop);
+			},
+			numDownOffset() {
+				return uni.upx2px(this.downOffset);
+			},
+			numDownTouchHeight() {
+				return uni.upx2px(this.downTouchHeight);
+			},
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : '';
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : '';
+			}
+		},
+		methods: {
+			// 注册列表滚动事件,用于下拉刷新
+			scroll(e) {
+				e = e.detail;
+				// 更新滚动条的位置
+				this.scrollRealTop = e.scrollTop;
+				// 更新滚动内容高度
+				this.scrollHeight = e.scrollHeight;
+				// 回到顶部功能
+				if (this.backTop) {
+					// 返回顶部按钮的显示隐藏
+					if (e.scrollTop >= this.numBackTopOffset) {
+						this.isShowBackTop = true;
+					} else {
+						this.isShowBackTop = false;
+					}
+				}
+			},
+			// 注册列表touchstart事件,用于下拉刷新
+			touchstart(e) {
+				if (!this.pullDown || !this.enablePullDown) return;
+				this.startPoint = this.getPoint(e); // 记录起点
+				this.startTop = this.scrollRealTop; // 记录此时的滚动条位置
+				this.startAngle = 0; // 初始角度
+				this.lastPoint = this.startPoint; // 重置上次move的点
+				this.inTouchend = false; // 标记不是touchend
+			},
+			// 注册列表touchmove事件,用于下拉刷新
+			touchmove(e) {
+				if (!this.pullDown || !this.enablePullDown) return;
+
+				const scrollTop = this.scrollRealTop; // 当前滚动条的距离
+				const curPoint = this.getPoint(e); // 当前点
+
+				const moveY = curPoint.y - this.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
+
+				// (向下拉&&在顶部) scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
+				// scroll-view滚动到顶部时,scrollTop不一定为0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
+				if (moveY > 0 && (scrollTop <= 0 || (scrollTop <= this.numDownStartTop && scrollTop === this.startTop))) {
+					// 可下拉的条件
+					if (this.pullDown && this.enablePullDown && !this.inTouchend && !this.isDownLoading && !this
+						.isUpLoading) {
+						// 下拉的初始角度是否在配置的范围内
+						if (!this.startAngle) this.startAngle = this.getAngle(this.lastPoint,
+							curPoint); // 两点之间的角度,区间 [0,90]
+						if (this.startAngle < this.downMinAngle) return; // 如果小于配置的角度,则不往下执行下拉刷新
+
+						// 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
+						if (this.numDownTouchHeight > 0 && curPoint.y >= this.numDownTouchHeight) {
+							this.inTouchend = true; // 标记执行touchend
+							this.touchend(); // 提前触发touchend
+							return;
+						}
+
+						this.preventDefault(e); // 阻止默认事件
+
+						const diff = curPoint.y - this.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
+
+						// 下拉距离  < 指定距离
+						if (this.downHight < this.numDownOffset) {
+							if (this.movetype !== 1) {
+								this.movetype = 1; // 加入标记,保证只执行一次
+								// 下拉的距离进入offset范围内那一刻的回调
+								this.scrollAble = false; // 禁止下拉,避免抖动
+								this.isDownReset = false; // 不重置高度
+								this.isDownLoading = false; // 不显示加载中
+								this.downText = this.pullingText; // 设置文本
+								this.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+							}
+							this.downHight += diff * this.downInOffsetRate; // 越往下,高度变化越小
+							// 指定距离  <= 下拉距离
+						} else {
+							if (this.movetype !== 2) {
+								this.movetype = 2; // 加入标记,保证只执行一次
+								// 下拉的距离大于offset那一刻的回调
+								this.scrollAble = false; // 禁止下拉,避免抖动
+								this.isDownReset = false; // 不重置高度
+								this.isDownLoading = false; // 不显示加载中
+								this.downText = this.loosingText; // 设置文本
+								this.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
+							}
+							if (diff > 0) { // 向下拉
+								this.downHight += Math.round(diff * this.downOutOffsetRate); // 越往下,高度变化越小
+							} else { // 向上收
+								this.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
+							}
+						}
+						// 设置旋转角度
+						this.downRotate = 'rotate(' + 360 * (this.downHight / this.numDownOffset) + 'deg)';
+					}
+				}
+				// 记录本次移动的点
+				this.lastPoint = curPoint;
+			},
+			// 注册列表touchend事件,用于下拉刷新
+			touchend(e) {
+				if (!this.pullDown || !this.enablePullDown) return;
+				// 如果下拉区域高度已改变,则需重置回来
+				if (this.isMoveDown) {
+					if (this.downHight >= this.numDownOffset) {
+						// 符合触发刷新的条件
+						this.triggerPullDown();
+					} else {
+						// 不符合的话 则重置
+						this.downHight = 0;
+						this.scrollAble = true; // 开启下拉
+						this.isDownReset = true; // 重置高度
+						this.isDownLoading = false; // 不显示加载中
+						this.scrollTo(0);
+					}
+					this.movetype = 0;
+					this.isMoveDown = false;
+				}
+			},
+			/* 计算两点之间的角度: 区间 [0,90] */
+			getAngle(p1, p2) {
+				const x = Math.abs(p1.x - p2.x);
+				const y = Math.abs(p1.y - p2.y);
+				const z = Math.sqrt(x * x + y * y);
+				let angle = 0;
+				if (z !== 0) {
+					angle = Math.asin(y / z) / Math.PI * 180;
+				}
+				return angle;
+			},
+			preventDefault(e) {
+				// 小程序不支持e.preventDefault
+				// app的bounce只能通过配置pages.json的style.app-plus.bounce为"none"来禁止
+				// cancelable:是否可以被禁用; defaultPrevented:是否已经被禁用
+				if (e && e.cancelable && !e.defaultPrevented) e.preventDefault();
+			},
+			// 点击回到顶部的按钮回调
+			onBackTop() {
+				this.isShowBackTop = false; // 回到顶部按钮需要先隐藏,再执行回到顶部,避免闪动
+				this.scrollTo(0); // 执行回到顶部
+			},
+			// 点击失败重新加载
+			onUpErrorClick() {
+				this.isUpError = false;
+				this.triggerPullDown();
+			},
+			scrollTo(y) {
+				this.scrollTop = this.scrollRealTop;
+				this.$nextTick(() => {
+					this.scrollTop = y;
+				});
+			},
+			/* 根据点击滑动事件获取第一个手指的坐标 */
+			getPoint(e) {
+				if (!e) {
+					return {
+						x: 0,
+						y: 0
+					};
+				}
+				if (e.touches && e.touches[0]) {
+					return {
+						x: e.touches[0].pageX,
+						y: e.touches[0].pageY
+					};
+				} else if (e.changedTouches && e.changedTouches[0]) {
+					return {
+						x: e.changedTouches[0].pageX,
+						y: e.changedTouches[0].pageY
+					};
+				} else {
+					return {
+						x: e.clientX,
+						y: e.clientY
+					};
+				}
+			},
+			/* 显示上拉加载中 */
+			showUpLoading() {
+				this.isEmpty = false;
+				this.isUpError = false;
+				this.isUpFinish = false;
+				this.isUpLoading = true;
+			},
+			/* 显示下拉进度布局 */
+			showDownLoading() {
+				this.isEmpty = false;
+				this.isUpLoading = false;
+				this.isUpError = false;
+				this.isUpFinish = false;
+
+				this.isShowDownTip = false;
+				this.isDownSuccess = false;
+				this.isDownError = false;
+				this.isDownLoading = true; // 显示加载中
+				this.downHight = this.numDownOffset; // 更新下拉区域高度
+				this.scrollAble = true; // 开启下拉
+				this.isDownReset = true; // 重置高度
+				this.downText = this.downLoadingText; // 设置文本
+			},
+			/* 结束下拉刷新 */
+			hideDownLoading() {
+				if (this.isDownLoading) {
+					if (this.isDownSuccess && this.showDownSuccess) {
+						this.downText = this.downSuccessText;
+						this.isShowDownTip = true;
+					} else if (this.isDownError && this.showDownError) {
+						this.downText = this.downErrorText;
+						this.isShowDownTip = true;
+					}
+					if (this.isShowDownTip) {
+						setTimeout(() => {
+							this.downHight = 0;
+							this.isDownReset = true; // 重置高度
+							this.scrollHeight = 0; // 重置滚动区域,使数据不满屏时仍可检查触发翻页
+							setTimeout(() => {
+								this.scrollAble = true; // 开启下拉
+								this.isDownLoading = false; // 不显示加载中
+								this.isShowDownTip = false;
+							}, 300);
+						}, 1000);
+					} else {
+						this.downHight = 0;
+						this.isDownReset = true; // 重置高度
+						this.scrollHeight = 0; // 重置滚动区域,使数据不满屏时仍可检查触发翻页
+						this.scrollAble = true; // 开启下拉
+						this.isDownLoading = false; // 不显示加载中
+						this.isShowDownTip = false;
+					}
+				}
+			},
+			/* 显示上拉加载中 */
+			showUpLoading() {
+				this.isEmpty = false;
+				this.isUpError = false;
+				this.isUpFinish = false;
+				this.isUpLoading = true;
+			},
+			/* 结束上拉加载 */
+			hideUpLoading() {
+				if (this.isUpLoading) {
+					this.$nextTick(() => {
+						this.isUpLoading = false;
+					});
+				}
+			},
+			/* 触发下拉刷新 */
+			triggerPullDown() {
+				if (this.pullDown && this.enablePullDown && !this.isDownLoading && !this.isUpLoading) {
+					// 下拉加载中...
+					this.showDownLoading(); // 下拉刷新中...
+					this.page = 1; // 预先加一页
+					this.pullType = 'down';
+					this.pullDown && this.pullDown.call(this.$parent, this);
+				}
+			},
+
+			refresh() {
+				this.scrollTo(0);
+				this.page = 1;
+				this.isEmpty = false;
+				this.isDownSuccess = false;
+				this.isDownError = false;
+				this.isShowDownTip = false;
+				this.isUpError = false;
+				this.isUpFinish = false;
+				this.isDownLoading = false;
+				this.isUpLoading = false;
+				if (this.pullDown && this.enablePullDown) {
+					this.triggerPullDown();
+				}
+			},
+			/* 正常加载成功 */
+			success() {
+				this.page++;
+				if (this.isDownLoading) {
+					this.isDownSuccess = true;
+				}
+				this.hideDownLoading();
+				this.hideUpLoading();
+			},
+			/* 加载失败 */
+			error() {
+				if (this.isDownLoading) {
+					this.isDownError = true;
+				} else if (this.isUpLoading) {
+					this.isUpError = true;
+				}
+				this.hideDownLoading();
+				this.hideUpLoading();
+			},
+			/* 没有数据 */
+			empty() {
+				if (this.isDownLoading) {
+					this.isDownSuccess = true;
+				}
+				this.isEmpty = true;
+				this.isUpFinish = true;
+				this.hideDownLoading();
+				this.hideUpLoading();
+			},
+			/* 全部数据加载完毕 */
+			finish() {
+				this.hideDownLoading();
+				this.hideUpLoading();
+				this.isUpFinish = true;
+			}
+		}
+	};
+</script>
+
+<style lang="scss" scoped>
+	.ccPullScroll {
+		height: 100%;
+		position: relative;
+
+		.ccPullScrollview {
+			position: relative;
+			width: 100%;
+			height: 100%;
+			overflow-y: auto;
+			box-sizing: border-box;
+		}
+
+		/* 定位的方式固定高度 */
+		.is-fixed {
+			z-index: 1;
+			position: fixed;
+			top: 0;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			width: auto;
+			height: auto;
+		}
+
+		.cc-pull-down-wrap,
+		.cc-pull-up-wrap {
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			font-size: 28rpx;
+			color: gray;
+		}
+
+		.cc-pull-down-wrap {
+			position: absolute;
+			left: 0;
+			width: 100%;
+			transform: translateY(-100%);
+		}
+
+		.cc-pull-up-wrap {
+			min-height: 100rpx;
+		}
+
+		/* 旋转loading */
+		.cc-pull-loading-icon {
+			width: 28rpx;
+			height: 28rpx;
+			border-radius: 50%;
+			margin-right: 16rpx;
+			border: 2rpx solid currentColor;
+			border-bottom-color: transparent !important;
+		}
+
+		/* 旋转动画 */
+		.cc-pull-loading-rotate {
+			animation: cc-pull-loading-rotate 0.6s linear infinite;
+		}
+
+		@keyframes cc-pull-loading-rotate {
+			0% {
+				transform: rotate(0deg);
+			}
+
+			100% {
+				transform: rotate(360deg);
+			}
+		}
+
+		/* 回到顶部的按钮 */
+		.cc-pull-back-top {
+			opacity: 0;
+			pointer-events: none;
+			transition: opacity 0.3s linear;
+
+			&.is-show {
+				opacity: 1;
+				pointer-events: auto;
+			}
+
+			.default-back-top {
+				position: absolute;
+				right: 20rpx;
+				width: 72rpx;
+				height: 72rpx;
+				border-radius: 50%;
+				bottom: 30rpx;
+				z-index: 99;
+			}
+		}
+	}
+</style>

+ 85 - 0
uni_modules/cc-pullScroolView/package.json

@@ -0,0 +1,85 @@
+{
+  "id": "cc-pullScroolView",
+  "displayName": "简单好用的滚动列表【自动加载,上拉加载,下拉刷新,可自定义】",
+  "version": "3.2.0",
+  "description": "简单好用的滚动列表【自动加载,上拉加载,下拉刷新,可自定义】,支持列表分页 本地分页",
+  "keywords": [
+    "下拉刷新",
+    "上拉加载",
+    "分页器",
+    "加载器",
+    "虚拟列表"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.8.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": "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",
+          "钉钉": "y",
+          "快手": "y",
+          "飞书": "y",
+          "京东": "y"
+        },
+        "快应用": {
+          "华为": "y",
+          "联盟": "y"
+        }
+      }
+    }
+  }
+}

+ 197 - 0
uni_modules/cc-pullScroolView/readme.md

@@ -0,0 +1,197 @@
+### 我的技术微信公众号
+
+查看更多前端组件和框架信息,请关注我的技术微信公众号【前端组件开发】
+
+![图片](https://i.postimg.cc/RZ0sjnYP/front-End-Component.jpg)
+
+# cc-pullScroolView
+
+#### 使用方法 
+```使用方法
+	
+<!--   ref:唯一ref  pullDown:下拉刷新事件  上拉加载方法写在生命周期onReachBottom方法内  -->
+<cc-pullScroolView class="pullScrollView" ref="pullScroll" :pullDown="pullDown">
+</cc-pullScroolView>	
+				
+<!-- 注意: 上拉加载方法写在onReachBottom方法内 -->	
+onReachBottom() {
+	// 数据全部加载完
+	if (this.curPageNum * 10 >= this.totalNum) {
+
+		} else {
+
+			// 显示加载中
+			this.$refs.pullScroll.showUpLoading();
+			this.curPageNum++;
+			this.requestData();
+		}
+
+	},
+
+```
+
+#### HTML代码实现部分
+```html
+<template>
+	<view class="content">
+
+		<div class="mui-content-padded">
+
+			<!--  ref:唯一ref pullDown:下拉刷新事件  onReachBottom:上拉加载事件 -->
+			<cc-pullScroolView class="pullScrollView" ref="pullScroll" :back-top="true" :pullDown="pullDown">
+
+				<!-- 列表组件 -->
+				<CCBProjectList :productList="projectList" @click="goProDetail"></CCBProjectList>
+
+			</cc-pullScroolView>
+
+		</div>
+
+
+	</view>
+</template>
+
+
+<script>
+	import CCBProjectList from '../../components/ccPageView/CCProjectList.vue';
+
+	export default {
+		components: {
+
+			CCBProjectList,
+
+
+		},
+		data() {
+			return {
+				// 列表总数量
+				totalNum: 60,
+				//  页码 默认1开始
+				curPageNum: 1,
+
+				// 列表数组
+				projectList: []
+			}
+		},
+		onLoad() {
+
+			// 页面刷新方法 会自动调用pulldown一次
+			this.pageRefresh();
+		},
+		// 上拉加载
+		onReachBottom() {
+			// 数据全部加载完
+			if (this.curPageNum * 10 >= this.totalNum) {
+
+			} else {
+
+				// 显示加载中
+				this.$refs.pullScroll.showUpLoading();
+				this.curPageNum++;
+				this.requestData();
+			}
+
+		},
+
+		methods: {
+
+			pageRefresh() {
+				let myThis = this;
+				this.$nextTick(() => {
+
+					myThis.$refs.pullScroll.refresh();
+
+
+
+				});
+			},
+			// 下拉刷新
+			pullDown(pullScroll) {
+
+				console.log('下拉刷新');
+				this.projectList = [];
+				this.curPageNum = 1;
+				setTimeout(() => {
+					this.requestData(pullScroll);
+				}, 300);
+
+			},
+
+
+			// 列表条目点击事件
+			goProDetail(item) {
+
+			},
+
+
+			requestData() {
+
+				// 模拟请求参数设置
+				let reqData = {
+
+					'area': '',
+					"pageSize": 10,
+					"pageNo": this.curPageNum
+				}
+
+				let myThis = this;
+				setTimeout(function() {
+
+
+					// 模拟请求接口
+					for (let i = 0; i < 10; i++) {
+
+						myThis.projectList.push({
+							'proName': '产品名称' + i,
+							'proUnit': '公司名称' + i,
+							'area': '广东省',
+							'proType': '省级项目',
+							'stage': '已开工',
+							'id': 10 * (myThis.curPageNum + i) + myThis.curPageNum + ''
+						});
+					}
+					// 列表总数量
+					myThis.totalNum = 60;
+					// 如果是最后一页
+					if (myThis.curPageNum * 10 >= myThis.totalNum) {
+						myThis.$refs.pullScroll.finish();
+
+					} else {
+						// 不是最后一页
+						myThis.$refs.pullScroll.success();
+					}
+
+				}, 600);
+
+
+
+			}
+		}
+	}
+</script>
+
+
+<style>
+	page {
+
+		background-color: #f2f2f2;
+	}
+
+	.content {
+		display: flex;
+		flex-direction: column;
+
+	}
+
+	.mui-content-padded {
+		margin: 0px 14px;
+		/* background-color: #ffffff; */
+	}
+
+	.pullScrollView {
+		display: flex;
+		flex-direction: column;
+
+	}
+</style>
+```