uni-app平板端自定义样式合集

场景

在平板端没有很好的适配组件,所以自己写了一部分,放置可惜,发出来可以给大家借鉴

实现

1.下拉选择框
在这里插入图片描述

<view class="page-device-switch">
	<view class="device-picker flex flex-justify-between" @tap="showDeviceDropdown = !showDeviceDropdown">
		{{ nickname || '请选择设备' }}
		<i class="iconfont icon-xiajiantou" :class="{'icon-rotate': showDeviceDropdown}" style="margin-left: 8rpx;font-size: 15rpx;color:#000;"></i>
	</view>
	<view v-if="showDeviceDropdown" class="device-dropdown">
		<view v-for="(item, idx) in allDeviceList" :key="item.id" class="device-dropdown-item" @tap="selectDevice(idx)">
			{{ item.nickname }}
		</view>
	</view>
</view>
...
data() {
	return {
		allDeviceList:[], //设备列表
		showDeviceDropdown: false,
	}
},

selectDevice(idx) {
	this.selectedDeviceIndex = idx;
	this.nickname = this.allDeviceList[idx].nickname;
	this.showDeviceDropdown = false;
},
...
.page-device-switch{
	height: 30rpx;
	width: 140rpx;
	position: absolute;
	left: 10.25rpx;
	top: 54.65rpx;
}
.device-picker {
	display: flex;
	align-items: center;
	height: 20rpx;
	padding: 0 5.86rpx;
	background: rgba($color: #fff, $alpha: 0.8);
	border-radius: 5.86rpx;
	color: #000;
	font-size: 13rpx;
	min-width: 200rpx;
	position: relative;
	z-index: 2;
	
	.iconfont {
		transition: transform 0.3s ease;
	}
	
	.icon-rotate {
		transform: rotate(180deg);
	}
}
.device-picker .iconfont {
	color: #000;
}
.device-dropdown {
	position: absolute;
	margin-top: 8rpx;
	background: rgba($color: #fff, $alpha: 0.8);
	border-radius: 5.86rpx;
	box-shadow: 0 2rpx 8rpx rgba(30,144,255,0.08);
	border: 1rpx solid #ccc;
	min-width: 200rpx;
	z-index: 10;
	max-height: 300rpx;
	overflow-y: auto;
}
.device-dropdown-item {
	padding: 8rpx 14rpx;
	font-size: 13rpx;
	color: #000;
	cursor: pointer;
	transition: background 0.2s;
	background: transparent;
}
.device-dropdown-item:hover {
	background: #e6f2ff;
}

2.侧滑抽屉
在这里插入图片描述

<!-- 自定义左侧侧滑抽屉 -->
<transition name="drawer-slide">
	<view v-if="showDeviceDrawer" class="custom-drawer-mask" @tap.self="showDeviceDrawer = false">
		<view class="custom-drawer-panel" @tap.stop>
			<view class="drawer-title">选择设备</view>
			<view class="drawer-device-list">
				<view
					v-for="(item, idx) in allDeviceList"
					:key="item.id"
					class="drawer-device-item"
					:class="{ active: idx === selectedDeviceIndex }"
					@tap="handleSelectDevice(idx)"
				>
					<span class="device-dot" :class="{ active: idx === selectedDeviceIndex }"></span>
					<text class="device-name">{{ item.nickname }}</text>
					<i v-if="idx === selectedDeviceIndex" class="iconfont icon-duigou"></i>
				</view>
			</view>
		</view>
		<view class="custom-drawer-right-mask" @tap="showDeviceDrawer = false"></view>
	</view>
</transition>
...
data() {
	return {
		allDeviceList:[],
		showDeviceDrawer: false,
	}
},
handleSelectDevice(idx) {
	this.selectedDeviceIndex = idx;
	this.nickname = this.allDeviceList[idx].nickname;
	this.showDeviceDrawer = false;
},
...
.custom-drawer-mask {
	position: fixed;
	top: 0; left: 0; right: 0; bottom: 0;
	background: rgba(0,0,0,0.25);
	z-index: 9999;
	display: flex;
	align-items: stretch;
}
.custom-drawer-panel {
	width: 320rpx;
	height: 100vh;
	background: #fff;
	box-shadow: 2rpx 0 16rpx rgba(0,0,0,0.08);
	padding: 24rpx 0 0 0;
	display: flex;
	flex-direction: column;
	animation: drawerIn 0.25s;
}
.drawer-title {
	font-size: 22rpx;
	font-weight: bold;
	color: #222;
	padding: 0 24rpx 18rpx 24rpx;
	border-bottom: 1rpx solid #eee;
}
.drawer-device-list {
	flex: 1;
	overflow-y: auto;
	padding: 10rpx 0;
	max-height: calc(100vh - 60rpx);
}
.drawer-device-item {
	padding: 12rpx 18rpx;
	font-size: 18rpx;
	color: #333;
	display: flex;
	align-items: center;
	cursor: pointer;
	border-radius: 6rpx;
	margin-bottom: 2rpx;
	transition: background 0.2s, color 0.2s;
	min-height: 36rpx;
}
.drawer-device-item.active {
	background: #e6f2ff;
	color: #2979ff;
	font-weight: bold;
}
.device-dot {
	display: inline-block;
	width: 10rpx;
	height: 10rpx;
	border-radius: 50%;
	margin-right: 12rpx;
	background: #333;
	flex-shrink: 0;
	transition: background 0.2s;
}
.drawer-device-item.active .device-dot {
	background: #2979ff;
}
.device-name {
	flex: 1;
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}
.drawer-device-item .icon-duigou {
	color: #2979ff;
	margin-left: 8rpx;
	font-size: 18rpx;
}
.drawer-device-item:not(.active):hover {
	background: #f5f7fa;
}
.custom-drawer-right-mask {
	flex: 1;
	height: 100vh;
}
.drawer-slide-enter-active, .drawer-slide-leave-active {
	transition: all 0.25s;
}
.drawer-slide-enter, .drawer-slide-leave-to {
	opacity: 0;
}
@keyframes drawerIn {
	from { transform: translateX(-100%); }
	to { transform: translateX(0); }
}

3.触底加载
在这里插入图片描述

<view class="content flex flex-wrap flex-content-start">
	<scroll-view
		scroll-y="true"
		@scrolltolower="load"
		style="width: 100%; height: 100%;"
	>
		<view class="box" v-for="(item, index) in tableData" :key="index">
			<view class="title">{{ item.date }}</view>
			<view class="cards">
				<view 
					class="card" 
					v-for="v in item.children" 
					:key="v.id"
					@click="previewMedia(v)"
				>
					<view class="file">
						<!-- 图片类型 -->
						//...
						<!-- 视频类型 -->
						//...
						<!-- 文件夹类型 -->
						//...
					</view>
					<view class="name end-h">{{ v.fileName }}</view>
				</view>
			</view>
		</view>
	</scroll-view>
</view>
...
data() {
	return {
		tableData: [],
		isLoading: false,
		current: 1,
		size:10,
		total:0,
	}
},
mounted() {
	this.getDatas()
	this.load()
},
load() {
  if (this.isLoading) return;
  this.isLoading = true;
  setTimeout(() => {
	this.current += 1;
	this.getDatas();
  }, 1000);
},
getDatas(){
  if(!this.subTypeString){
	this.tableData = [];
	return;
  }
  requestName(params).then(res=>{
	if(res.data && res.data.records.length){
	  this.isLoading = false;
	  this.total = res.data.total;
	  let arr = res.data.records.map(item=>{
		item.date = item.createTime.substring(0,10);
		item.isCheck = false;
		if(item.fileUrl.includes('_T_')||item.fileUrl.includes('_T.')){
		  item.type = 'ir'
		}else {
		  item.type = 'other'
		}
		return item;
	  });
	  let data = this.tableData;
	  for(let i=0;i<arr.length;i++){
		if(!data.filter(v=> v.date == arr[i].date).length){
		  data.push({
			date:arr[i].date,
			children:[arr[i]],
		  })
		}else{
		  for(let j=0;j<data.length;j++){
			  if(data[j].date==arr[i].date){
				  data[j].children.push(arr[i])
			  }
		  }
		}
	  }
	  this.tableData = data.sort((a, b) => {
		let dateA = new Date(a.date);
		let dateB = new Date(b.date);
		return dateB - dateA;
	  });;
	  if (this.tableData.length >= this.total) {
		this.finished = true;
	  }
	}
  }).catch(()=>{
	  this.isLoading = true;
  })
},

4.树状数据表
内嵌图片和视频两种媒体文件;图片文件点击预览,打开可缩放,长按可下载;视频文件自动播放初始桢,可全屏可下载
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
5.弹窗+功能
在这里插入图片描述

请添加图片描述
6. 悬浮按钮
在这里插入图片描述

<!-- 扩展图标按钮 -->
<view class="more flex flex-align-center flex-justify-center" @longpress="showActionMenu($event, item, index)">
	<i class="iconfont icon-gengduo2" style="color:#8C8C8C;font-size: 10.25rpx;"></i>
</view>

<!-- 自定义悬浮菜单 -->
<view v-if="showMenu" class="action-menu" :style="menuStyle" @tap.stop>
	<view class="action-menu-item" @tap="handleMenuAction('view', currentItem, currentIndex)">
		<text>查看航线</text>
	</view>
	<view class="action-menu-item" @tap="handleMenuAction('execute', currentItem, currentIndex)">
		<text>执行航线</text>
	</view>
	<view class="action-menu-item" @tap="handleMenuAction('delete', currentItem, currentIndex)">
		<text>删除航线</text>
	</view>
</view>

<!-- 遮罩层 -->
<view v-if="showMenu" class="menu-mask" @tap="hideActionMenu"></view>
...
data(){
	return {
		showMenu: false,
		currentItem: null,
		currentIndex: -1,
		menuStyle: {},
	}
}
showActionMenu(event, item, index) {
	this.currentItem = item
	this.currentIndex = index
	this.showMenu = true
	
	// 获取点击位置
	const touch = event.touches[0] || event.changedTouches[0]
	const x = touch.clientX
	const y = touch.clientY
	
	// 获取系统信息
	const systemInfo = uni.getSystemInfoSync()
	const windowWidth = systemInfo.windowWidth
	const windowHeight = systemInfo.windowHeight
	
	// 菜单尺寸(预估)
	const menuWidth = 120 // rpx转px大约60px
	const menuHeight = 120 // 三个选项的高度
	
	// 计算菜单位置,避免超出屏幕边界
	let left = x - menuWidth / 2
	let top = y - menuHeight - 10 // 在点击位置上方显示
	
	// 边界检测
	if (left < 10) left = 10
	if (left + menuWidth > windowWidth - 10) left = windowWidth - menuWidth - 10
	if (top < 10) top = y + 10 // 如果上方空间不够,显示在下方
	
	this.menuStyle = {
		position: 'fixed',
		left: left + 'px',
		top: top + 'px',
		zIndex: 9999
	}
},
hideActionMenu() {
	this.showMenu = false
	this.currentItem = null
	this.currentIndex = -1
},
handleMenuAction(action, item, index) {
	this.hideActionMenu()
	switch(action) {
		case 'view':
			this.clickCard(item)
			break
		case 'execute':
			this.executeWayline(item)
			break
		case 'delete':
			this.deleteWayline(item, index)
			break
	}
},
...
// 自定义悬浮菜单样式
.action-menu {
	background: #333333;
	border-radius: 6rpx;
	padding: 6rpx 0;
	box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.2);
	min-width: 120rpx;
	
	.action-menu-item {
		display: flex;
		align-items: center;
		justify-content: center;
		padding: 8rpx 12rpx;
		color: #FFFFFF;
		font-size: 10rpx;
		white-space: nowrap;
		
		&:active {
			background-color: #58595B;
		}
		
		&:not(:last-child) {
			border-bottom: 1rpx solid #4A4A4A;
		}
		
		text {
			font-size: 10rpx;
			color: #FFFFFF;
		}
	}
}

.menu-mask {
	position: fixed;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
	background: transparent;
	z-index: 9998;
}
  1. 模块配置器
    在这里插入图片描述
<!-- 模块配置按钮 -->
<view class="module-config-btn" :class="{ active: showModuleConfig }" @tap="showModuleConfig = true">
	<i class="iconfont icon-shezhi" style="font-size: 16rpx;color: #333333;"></i>
</view>
<!-- 模块展示卡片 部分代码-->
<view class="entrance-box flex flex-wrap flex-justify-between">
	<view 
		v-for="module in visibleModules" 
		:key="module.id"
		:class="['entrance-card', module.bgClass]" 
		@tap="goOtherPage(module.id)"
	>
		<text class="entrance-title">{{ module.title }}</text>
		<view class="entrance-icon flex flex-justify-center flex-align-center">
			<i :class="['iconfont', module.icon]" style="font-size: 26.37rpx;color: #333333"></i>
		</view>
	</view>
</view>
<!-- 模块配置面板 -->
<transition name="modal-fade">
	<view v-if="showModuleConfig" class="module-config-mask" @tap.self="showModuleConfig = false">
		<view class="module-config-panel" @tap.stop>
			<view class="config-header">
				<text class="config-title">模块配置</text>
				<view class="config-close" @tap="showModuleConfig = false">
					<i class="iconfont icon-guanbi" style="font-size: 16rpx;"></i>
				</view>
			</view>
			<view class="config-content">
				<view class="config-tip">拖拽排序,点击开关,最多选择4个模块</view>
				<view class="module-list">
					<view 
						v-for="module in allModules" 
						:key="module.id"
						class="module-item"
						:class="{ disabled: !module.visible && visibleModules.length >= 4 }"
						@tap="toggleModule(module.id)"
					>
						<view class="module-drag-handle">
							<i class="iconfont icon-drag" style="font-size: 14rpx;color: #999;"></i>
						</view>
						<view class="module-icon">
							<i :class="['iconfont', module.icon]" style="font-size: 20rpx;color: #333;"></i>
						</view>
						<text class="module-name">{{ module.title }}</text>
						<view class="module-switch" :class="{ active: module.visible }">
							<view class="switch-dot"></view>
						</view>
					</view>
				</view>
			</view>
			<view class="config-footer">
				<view class="config-btn cancel-btn" @tap="resetModuleConfig">重置</view>
				<view class="config-btn confirm-btn" @tap="saveModuleConfig">确定</view>
			</view>
		</view>
	</view>
</transition>
...
data() {
	return {
		// 所有可用模块
		allModules: [
			{
				id: 1,
				title: '设备',
				icon: 'icon-wurenji',
				bgClass: 'entrancebg1',
				visible: true
			},
			{
				id: 2,
				title: '航线库',
				icon: 'icon-hangxian3',
				bgClass: 'entrancebg2',
				visible: true
			},
			{
				id: 3,
				title: '飞行任务',
				icon: 'icon-renwujincheng',
				bgClass: 'entrancebg3',
				visible: true
			},
			{
				id: 4,
				title: '媒体库',
				icon: 'icon-meitiku2',
				bgClass: 'entrancebg4',
				visible: true
			},
			{
				id: 5,
				title: '营区资质',
				icon: 'icon-yingqu_ic',
				bgClass: 'entrancebg5',
				visible: false
			}
		]
	}
},
computed: {
	// 计算可见的模块(最多4个)
	visibleModules() {
		return this.allModules.filter(module => module.visible).slice(0, 4);
	}
},
mounted() {
	this.loadModuleConfig()
},
goOtherPage(val){
	if(val==1){
		uni.navigateTo({
			url: '../shebeigl/shebeigl'
		});
	}
	if(val==2){
		uni.navigateTo({
			url: '../hangxiank/hangxiank'
		});
	}
	if(val==3){
		uni.navigateTo({
			url: '../feixingrw/feixingrw'
		});
	}
	if(val==4){
		uni.navigateTo({
			url: '../meitik/meitik'
		});
	}
	if(val==5){
		uni.navigateTo({
			url: '../yingquzz/yingquzz'
		});
	}
},
// 模块配置相关方法
toggleModule(moduleId) {
	const module = this.allModules.find(m => m.id === moduleId);
	if (!module) return;
	
	// 如果当前模块已显示,直接关闭
	if (module.visible) {
		module.visible = false;
		return;
	}
	
	// 如果当前模块未显示,但已达到4个限制,需要先关闭一个
	if (this.visibleModules.length >= 4) {
		uni.showToast({
			title: '最多只能选择4个模块',
			icon: 'none',
			duration: 2000
		});
		return;
	}
	
	module.visible = true;
},
saveModuleConfig() {
	// 保存配置到本地存储
	const config = this.allModules.map(module => ({
		id: module.id,
		visible: module.visible
	}));
	uni.setStorageSync('moduleConfig', config);
	
	this.showModuleConfig = false;
	uni.showToast({
		title: '配置已保存',
		icon: 'success',
		duration: 1500
	});
},
resetModuleConfig() {
	// 重置为默认配置
	this.allModules.forEach((module, index) => {
		module.visible = index < 4; //4个默认显示
	});
	
	uni.showToast({
		title: '已重置为默认配置',
		icon: 'success',
		duration: 1500
	});
},
loadModuleConfig() {
	// 从本地存储加载配置
	const savedConfig = uni.getStorageSync('moduleConfig');
	if (savedConfig && Array.isArray(savedConfig)) {
		savedConfig.forEach(config => {
			const module = this.allModules.find(m => m.id === config.id);
			if (module) {
				module.visible = config.visible;
			}
		});
	}
},
...
.module-config-btn {
	height: 29.3rpx;
	width: 29.3rpx;
	position: absolute;
	left: 13.92rpx;
	top: 24.17rpx;
	border-radius: 50%;
	background: #FFFFFF;
	display: flex;
	align-items: center;
	justify-content: center;
	box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
	transition: all 0.3s ease;
	
	&:active {
		transform: scale(0.95);
	}
	
	&.active {
		background: #FF4444;
		
		.iconfont {
			color: #FFFFFF !important;
		}
	}
}
.entrance-box{
	width: 402.83rpx;
	height: 100%;
	.entrance-card{
		width: 197.02rpx;
		height: 191.16rpx;
		border-radius: 5.86rpx;
		padding: 11.72rpx;
		position: relative;
		.entrance-title{
			font-family: 'PingFang SC';
			font-weight: 500;
			font-size: 17.58rpx;
			color: #FFFFFF;
		}
		.entrance-icon{
			position: absolute;
			bottom: 11.72rpx;
			right: 11.72rpx;
			height: 46.88rpx;
			width: 46.88rpx;
			border-radius: 50%;
			background-color: #EDEFF2;
		}
	}
	.entrance-card:nth-child(3){
		margin-top: 8.79rpx;
	}
	.entrance-card:nth-child(4){
		margin-top: 8.79rpx;
	}
	.entrancebg1{
		background: url('../../static/images/s-shebei.png') center center / 100% 100%;
	}
	.entrancebg2{
		background: url('../../static/images/s-hangxian.png') center center / 100% 100%;
	}
	.entrancebg3{
		background: url('../../static/images/s-feixing.png') center center / 100% 100%;
	}
	.entrancebg4{
		background: url('../../static/images/s-meitik.png') center center / 100% 100%;
	}
	.entrancebg5{
		background: url('../../static/images/s-yingquzz.jpg') center center / 100% 100%;
	}
}
/* 模块配置面板样式 */
.module-config-mask {
	position: fixed;
	top: 0; left: 0; right: 0; bottom: 0;
	background: rgba(0, 0, 0, 0.5);
	z-index: 10000;
	display: flex;
	align-items: center;
	justify-content: center;
	padding: 40rpx;
}

.module-config-panel {
	width: 500rpx;
	max-height: 70vh;
	background: #fff;
	border-radius: 12rpx;
	overflow: hidden;
	animation: modalIn 0.3s ease-out;
}
.config-header {
	height: 60rpx;
	background: #f8f9fa;
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 0 20rpx;
	border-bottom: 1rpx solid #eee;
	
	.config-title {
		font-size: 16rpx;
		font-weight: bold;
		color: #333;
	}
	
	.config-close {
		width: 28rpx;
		height: 28rpx;
		display: flex;
		align-items: center;
		justify-content: center;
		border-radius: 50%;
		background: #f0f0f0;
		color: #666;
		transition: all 0.2s;
		
		&:active {
			background: #e0e0e0;
			transform: scale(0.95);
		}
	}
}

.config-content {
	padding: 16rpx 20rpx;
	max-height: 50vh;
	overflow-y: auto;
	
	.config-tip {
		font-size: 11rpx;
		color: #666;
		text-align: center;
		margin-bottom: 12rpx;
		padding: 8rpx 12rpx;
		background: #f8f9fa;
		border-radius: 6rpx;
	}
	
	.module-list {
		.module-item {
			height: 48rpx;
			display: flex;
			align-items: center;
			padding: 0 12rpx;
			margin-bottom: 8rpx;
			background: #f8f9fa;
			border-radius: 8rpx;
			transition: all 0.2s;
			position: relative;
			
			&:active {
				transform: scale(0.98);
			}
			
			&.disabled {
				opacity: 0.5;
				background: #f0f0f0;
			}
			
			.module-drag-handle {
				width: 20rpx;
				height: 20rpx;
				display: flex;
				align-items: center;
				justify-content: center;
				margin-right: 8rpx;
				opacity: 0.6;
			}
			
			.module-icon {
				width: 28rpx;
				height: 28rpx;
				display: flex;
				align-items: center;
				justify-content: center;
				background: #fff;
				border-radius: 6rpx;
				margin-right: 8rpx;
				box-shadow: 0 1rpx 3rpx rgba(0,0,0,0.1);
			}
			
			.module-name {
				flex: 1;
				font-size: 13rpx;
				color: #333;
				font-weight: 500;
			}
			
			.module-switch {
				width: 36rpx;
				height: 20rpx;
				background: #ddd;
				border-radius: 10rpx;
				position: relative;
				transition: all 0.3s ease;
				
				&.active {
					background: #007AFF;
					
					.switch-dot {
						transform: translateX(16rpx);
					}
				}
				
				.switch-dot {
					width: 16rpx;
					height: 16rpx;
					background: #fff;
					border-radius: 50%;
					position: absolute;
					top: 2rpx;
					left: 2rpx;
					transition: all 0.3s ease;
					box-shadow: 0 1rpx 3rpx rgba(0,0,0,0.2);
				}
			}
		}
	}
}
.config-footer {
	height: 60rpx;
	display: flex;
	align-items: center;
	justify-content: space-between;
	padding: 0 20rpx;
	border-top: 1rpx solid #eee;
	background: #f8f9fa;
	
	.config-btn {
		height: 36rpx;
		padding: 0 20rpx;
		border-radius: 18rpx;
		display: flex;
		align-items: center;
		justify-content: center;
		font-size: 13rpx;
		font-weight: 500;
		transition: all 0.2s;
		
		&.cancel-btn {
			background: #f0f0f0;
			color: #666;
			
			&:active {
				background: #e0e0e0;
				transform: scale(0.95);
			}
		}
		
		&.confirm-btn {
			background: #007AFF;
			color: #fff;
			
			&:active {
				background: #0056CC;
				transform: scale(0.95);
			}
		}
	}
}
/* 动画效果 */
.modal-fade-enter-active, .modal-fade-leave-active {
	transition: all 0.3s ease;
}
.modal-fade-enter, .modal-fade-leave-to {
	opacity: 0;
	transform: scale(0.9);
}
@keyframes modalIn {
	from {
		opacity: 0;
		transform: scale(0.9) translateY(-20rpx);
	}
	to {
		opacity: 1;
		transform: scale(1) translateY(0);
	}
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值