基于 uni-app 的年会抽奖应用

707 阅读3分钟

uni-app运行到浏览器没有响应式

默认情况,需要在敲完代码以后,保存一下,浏览器才会有响应。也可以设置保存。

uni-app 与 Vue 开发的比较

不同点uni-appVue
搭建项目HBuilder新建项目vue/cli
UI原生组件 或 uni-uiui组件库
路由管理page.json中直接配置vue router
状态管理内置 直接用vuex
组件标签名<view><text><navigator><div><span><a>
JS API内置 uni.request( )AJAX axios

uni-app路由配置

// pages.json
{
	"pages": [ //pages数组中第一项表示应用启动页
		{
			"path": "pages/index/index",
			"style": {
				"navigationBarTitleText": "抽奖"
			}
		},
		{
			"path": "pages/setting/setting",
			"style": {
				"navigationBarTitleText": "设置",
				"enablePullDownRefresh": false
			}
		}
	],
	"tabBar": { //配置页面tab切换导航
		"color": "#7A7E83",
		"selectedColor": "#3cc51f",
		"borderStyle": "black",
		"backgroundColor": "#ffffff",
		"list": [{
			"pagePath": "pages/index/index",
			"iconPath": "static/home.png",
			"selectedIconPath": "static/home.png",
			"text": "抽奖"
		}, {
			"pagePath": "pages/setting/setting",
			"iconPath": "static/setting-select.png",
			"selectedIconPath": "static/setting-select.png",
			"text": "设置"
		}]
	},
	"globalStyle": {
		"navigationBarTextStyle": "black",
		"navigationBarTitleText": "uni-app",
		"navigationBarBackgroundColor": "#F8F8F8",
		"backgroundColor": "#F8F8F8"
	},
	"uniIdRouter": {}
}

写 抽奖UI-数字呈现

// pages/index/index.vue
<template>
	<view class="content">
		<view class="panel">
			<text>{{num}}</text>
		</view>
		<view class="button-area">
			<button	v-show="isOver" @click="start">开始抽奖</button	>
			<button	v-show="!isOver" @click="stop">结束抽奖</button	>
		</view>
	</view>
</template>
<script>
	export default {
		data() {
			return {
				title: 'Hello 突破',
				num:40,
				min:0,   // 随机范围 [0, 100)
				max:100,  
				interval:100, //ms
				isOver:true,
				clock:null
			}
		},
		onLoad() {

		},
		onShow() {
			let setting = uni.getStorageSync('setting') || {max:this.max, min:this.min, interval:this.interval} 
			this.max = setting.max
			this.min = setting.min
			this.interval = setting.interval
		},
		methods: {
			start(){
				this.isOver = false
				this.clock = setInterval(()=>{
					this.num = this.getRandomNum()
				}, this.interval)
			},
			stop(){
				clearInterval(this.clock)
				this.isOver = true
			},
			getRandomNum(){
				return parseInt(this.min) + Math.floor(Math.random()*(parseInt(this.max) - parseInt(this.min))) 
			}
		}
	}
</script>
<style lang="scss">
	.panel{
		width: 600rpx;
		height:400rpx;
		border-radius: 8rpx;
		margin-top: 60rpx;
		display: flex;
		align-items: center;
		justify-content: center;
		border:1rpx solid $uni-border-color;
		box-shadow: 0 0 40rpx 0 rgba(0, 0, 0, 0.1);
		text{font-size: 100rpx;}
	}
	.button-area{
		margin-top: 120rpx;
	}
	.content {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
	}	
</style>
  • 最终效果

image.png

写设置页面

// pages/setting/setting.vue
<template>
	<view class="content">
		<view class="list">
			<view class="uni-form-item uni-column">
				<view class="title">最大值</view>
				<input class="uni-input" v-model="max" type="number" placeholder="最大值" />	
			</view>
			<view class="uni-form-item uni-column">
				<view class="title">最小值</view>
				<input class="uni-input" v-model="min" type="number" placeholder="最小值" />	
			</view>
			<view class="uni-form-item uni-column">
				<view class="title">时间间隔(ms)</view>
				<input class="uni-input" v-model="interval" type="number" placeholder="时间间隔" />	
			</view>
			<view class="button-area">
				<button @click="onSave">保存</button>
			</view>
		</view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				max:100,
				min:0,
				interval:100
			}
		},
		created() {
			let setting = uni.getStorageSync('setting') || {max:this.max, min:this.min, interval:this.interval}
			this.max = setting.max
			this.min = setting.min
			this.interval = setting.interval
		},	
		methods: {
			onSave(){
				let setting = {max:this.max, min:this.min, interval:this.interval}
				uni.setStorageSync('setting', setting)
			}
		}
	}
</script>

<style lang="scss">
 .content{
	 padding: 40rpx;
	 
 }
 .uni-form-item{
	 padding: 20rpx;
 }
 .uni-input{
	 border-bottom: 1px solid $uni-border-color;
	 margin-top: 16rpx;
	 font-size: 26rpx;
 }
 .button-area{
	 margin-top: 120rpx;
	 display: flex;
	 justify-content: center;
	 button{
		 width: 400rpx;
	 }
 }
</style>
  • 最终效果:

image.png

uniCloud

基于serverless模式和js编程的云开发平台

  • 服务空间
  • 云函数/云对象
  • 云数据库

首次登陆账号使用uniCloud需要实名认证

  1. 创建云服务空间
  2. uniCloud上创建数据库表schema,配置好点保存

image.png

  • 表结构为
{
  "bsonType": "object",
  "permission": {
    "read": true,
    "create": true,
    "update": true,
    "delete": true
  },
  "required": [  // schema2code时加校验,选项是必填项
    "title"
  ],
  "properties": {
    "_id": {
      "description": "存储文档 ID(用户 ID),系统自动生成"
    },
    "title":{
      "bsontype":"string",
      "title":"名称",
      "description":"输入奖品名称"
    }
  },
  "version": "0.0.1"
}
  1. HBuilder上创建数据库表schema,创建好上传,同步uniCloud
  • 新建项目时,在模板页勾选 启用uniCloud
  • 创建时未勾选的,项目目录上右键 创建uniCloud云开发环境(aliyun),直接生成后端部分,云函数、云数据库;右键文件夹关联云服务空间或项目
  • 在云函数文件夹上,右键下载所有云函数、公共模块及actions
  • 在云数据库文件夹上,右键下载所有DB Schema及扩展校验函数,此时就会把之前在云端的配置,同步到编辑器中lottery-schema.json

image.png

schema2code 自动生成代码

  • 本地lottery-schema.json文件上右键 选择schema2code ,会自动生成pages/lottery文件夹,会提示把新增的page注册到路由表中
  • pages文件夹下,新生成文件:

image.png

  • 运行到浏览器查看上述自动生成的文件的效果是否实现。

前端操作数据库

unicloud-db组件,把前端和数据库连接。

  • List列表页为例:

unicloud-db组件通过绑定collection对应的collectionList,建立数据表lottery的链接;也是clientDB请求,表名为lottery,返回数据:

{
	"code": 0,
	"errCode": 0,
	"message": "",
	"errMsg": "",
	"systemInfo": [],
	"affectedDocs": 2,
	"data": [{
		"_id": "634037246758c00001cb24ae",
		"title": "air 笔记本"
	}, {
		"_id": "6340374960cd3e000116e946",
		"title": "靠枕"
	}],
	"timeCost": 166
}
// pages/lottery/list.vue
<template>
  <view class="container">
    <unicloud-db ref="udb" v-slot:default="{data, pagination, loading, hasMore, error}" :collection="collectionList" field="title">
      <view v-if="error">{{error.message}}</view>
      <view v-else-if="data">
        <uni-list>
          <uni-list-item v-for="(item, index) in data" :key="index" showArrow :clickable="true" @click="handleItemClick(item._id)">
            <template v-slot:body>
              <text>
                <!-- 此处默认显示为_id,请根据需要自行修改为其他字段 -->
                <!-- 如果使用了联表查询,请参考生成的 admin 项目中 list.vue 页面 -->
                {{item._id}}
              </text>
            </template>
          </uni-list-item>
        </uni-list>
      </view>
      <uni-load-more :status="loading?'loading':(hasMore ? 'more' : 'noMore')"></uni-load-more>
    </unicloud-db>
    <uni-fab ref="fab" horizontal="right" vertical="bottom" :pop-menu="false" @fabClick="fabClick" />
  </view>
</template>

<script>
  const db = uniCloud.database()
  export default {
    data() {
      return {
        collectionList: "lottery",
        loadMore: {
          contentdown: '',
          contentrefresh: '',
          contentnomore: ''
        }
      }
    },
    onPullDownRefresh() {    // 手机端 下拉刷新
      this.$refs.udb.loadData({  
        clear: true
      }, () => {
        uni.stopPullDownRefresh()
      })
    },
    onReachBottom() {
      this.$refs.udb.loadMore()
    },
    methods: {
      handleItemClick(id) {
        uni.navigateTo({
          url: './detail?id=' + id
        })
      },
      fabClick() {
        // 打开新增页面
        uni.navigateTo({
          url: './add',
          events: {
            // 监听新增数据成功后, 刷新当前页面数据
            refreshData: () => {
              this.$refs.udb.loadData({
                clear: true
              })
            }
          }
        })
      }
    }
  }
</script>

重新生成代码合并

可以手动选择是否要修改

schema上增加图片上传的操作,重新生成schema2code勾选全部文件合并后

// lottery.schema.json
{
	"bsonType": "object",
	"permission": {
		"read": true,
		"create": true,
		"update": true,
		"delete": true
	},
	"required": ["title", "photo"],  // 新增 photo
	"properties": {
		"_id": {
			"description": "存储文档 ID(用户 ID),系统自动生成"
		},
		"title": {
			"bsonType": "string",
			"title": "名称",
			"description": "输入奖品名称"
		},
		"photo":{                 // 新增 photo
			"bsonType":"file",
			"title": "图片",
			"fileMediaType": "image",
			"fileExtName":"jpg,png"
		}
	},
	"version": "0.0.1"
}
  • 图片上传完毕后,列表展示每个item
// pages/lottery/list.vue
<template v-slot:body>
    <text>
       {{item.title}}
       <image :src="item.photo.url" model="aspectFill"></image>
    </text>
</template>

写 抽奖UI-图片呈现

  • 借鉴list.vue的结构
// pages/index/index.vue
<template>
	<view class="content">
		
		  <unicloud-db ref="udb" v-slot:default="{data, pagination, loading, hasMore, error}" :collection="collectionList" field="title,photo">
		    <view v-if="error">{{error.message}}</view>
		    <view v-else-if="data">
		      <uni-list>
		        <uni-list-item v-for="(item, index) in data" :key="index">
		          <template v-slot:body>
		            <text>
		              <!-- 此处默认显示为_id,请根据需要自行修改为其他字段 -->
		              <!-- 如果使用了联表查询,请参考生成的 admin 项目中 list.vue 页面 -->
		              {{item.title}}
						<image :src="item.photo.url" model="aspectFill"></image>
		            </text>
		          </template>
		        </uni-list-item>
		      </uni-list>
		    </view>
		    <uni-load-more :status="loading?'loading':(hasMore ? 'more' : 'noMore')"></uni-load-more>
		  </unicloud-db>
		
		<view class="panel">
			<text>{{num}}</text>
		</view>
		<view class="button-area">
			<button	v-show="isOver" @click="start">开始抽奖</button	>
			<button	v-show="!isOver" @click="stop">结束抽奖</button	>
		</view>
	</view>
</template>
<script>
	export default {
		data() {
			return {
				collectionList:"lottery",
				num:40,
				min:0,   // 随机范围 [0, 100)
				max:100,  
				interval:100, //ms
				isOver:true,
				clock:null,
				loadMore: {
				  contentdown: '',
				  contentrefresh: '',
				  contentnomore: ''
				}
			}
		},
		onLoad() {

		},
		onShow() {
			let setting = uni.getStorageSync('setting') || {max:this.max, min:this.min, interval:this.interval} 
			this.max = setting.max
			this.min = setting.min
			this.interval = setting.interval
		},
		onPullDownRefresh() {
		  this.$refs.udb.loadData({
		    clear: true
		  }, () => {
		    uni.stopPullDownRefresh()
		  })
		},
		onReachBottom() {
		  this.$refs.udb.loadMore()
		},
		methods: {
			start(){
				this.isOver = false
				this.clock = setInterval(()=>{
					this.num = this.getRandomNum()
				}, this.interval)
			},
			stop(){
				clearInterval(this.clock)
				this.isOver = true
			},
			getRandomNum(){
				return parseInt(this.min) + Math.floor(Math.random()*(parseInt(this.max) - parseInt(this.min))) 
			}
		}
	}
</script>
<style lang="scss">
	.panel{
		width: 600rpx;
		height:400rpx;
		border-radius: 8rpx;
		margin-top: 60rpx;
		display: flex;
		align-items: center;
		justify-content: center;
		border:1rpx solid $uni-border-color;
		box-shadow: 0 0 40rpx 0 rgba(0, 0, 0, 0.1);
		text{font-size: 100rpx;}
	}
	.button-area{
		margin-top: 120rpx;
	}
	.content {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
	}	
</style>
  • 加样式,图片呈宫格显示
<template>
	<view class="content">
		  <unicloud-db ref="udb" v-slot:default="{data, pagination, loading, hasMore, error}" :collection="collectionList" field="title,photo">
		    <view v-if="error">{{error.message}}</view>
		    <view v-else-if="data">
		      <uni-list class="list">
		        <uni-list-item v-for="(item, index) in data" :key="index">
		          <template v-slot:body>
					  <view class="item">
		              <!-- 此处默认显示为_id,请根据需要自行修改为其他字段 -->
		              <!-- 如果使用了联表查询,请参考生成的 admin 项目中 list.vue 页面 -->
						<view>
						<image :src="item.photo.url" model="aspectFill"></image>
						</view>
						<view>
						{{item.title}}
						</view>
					</view>
		          </template>
		        </uni-list-item>
		      </uni-list>
		    </view>
		    <uni-load-more :status="loading?'loading':(hasMore ? 'more' : 'noMore')"></uni-load-more>
		  </unicloud-db>
		<view class="button-area">
			<button	v-show="isOver" @click="start">开始抽奖</button	>
			<button	v-show="!isOver" @click="stop">结束抽奖</button	>
		</view>
	</view>
</template>
<script>
	export default {
		data() {
			return {
				collectionList:"lottery",
				num:40,
				min:0,   // 随机范围 [0, 100)
				max:100,  
				interval:100, //ms
				isOver:true,
				clock:null,
				loadMore: {
				  contentdown: '',
				  contentrefresh: '',
				  contentnomore: ''
				}
			}
		},
		onLoad() {

		},
		onShow() {
			let setting = uni.getStorageSync('setting') || {max:this.max, min:this.min, interval:this.interval} 
			this.max = setting.max
			this.min = setting.min
			this.interval = setting.interval
		},
		onPullDownRefresh() {
		  this.$refs.udb.loadData({
		    clear: true
		  }, () => {
		    uni.stopPullDownRefresh()
		  })
		},
		onReachBottom() {
		  this.$refs.udb.loadMore()
		},
		methods: {
			start(){
				this.isOver = false
				this.clock = setInterval(()=>{
					this.num = this.getRandomNum()
				}, this.interval)
			},
			stop(){
				clearInterval(this.clock)
				this.isOver = true
			},
			getRandomNum(){
				return parseInt(this.min) + Math.floor(Math.random()*(parseInt(this.max) - parseInt(this.min))) 
			}
		}
	}
</script>
<style lang="scss">
	.content{
		.uni-list{
			display: flex;
			flex-wrap: wrap;
			flex-direction: row;
		}
	}
	
	.item{
		text-align: center;
		image{
			width: 200rpx;
			height: 200rpx;
		}
	}
	.panel{
		width: 600rpx;
		height:400rpx;
		border-radius: 8rpx;
		margin-top: 60rpx;
		display: flex;
		align-items: center;
		justify-content: center;
		border:1rpx solid $uni-border-color;
		box-shadow: 0 0 40rpx 0 rgba(0, 0, 0, 0.1);
		text{font-size: 100rpx;}
	}
	.button-area{
		margin-top: 120rpx;
	}
	.content {
		display: flex;
		flex-direction: column;
		align-items: center;
		justify-content: center;
	}	
</style>

抽奖页图片抽奖功能

  • 原来的抽奖UI-数字呈现 的逻辑要修改为现有图片,随机产生数据对应到图片上。借鉴uniCloud文档语法实例(传统nosql查询语法示例)
// 获取db引用
const db = uniCloud.database() //代码块为cdb
// 使用uni-clientDB
db.collection('list')
  .where({
    name: "hello-uni-app" //传统MongoDB写法,不是jql写法。实际开发中推荐使用jql写法
  }).get()
  .then((res)=>{
    // res 为数据库查询结果
  }).catch((err)=>{
    console.log(err.code); // 打印错误码
		console.log(err.message); // 打印错误内容
  })
  • 具体实现
// index.vue
created() {
	this.$nextTick(() => {
        db.collection(dbCollectionName)
	.get()
        .then((res) => {    // res 为数据库查询结果
           console.log(res)
           })
	   })
	}
  • 重新调整路由表
//pages.json
{
	"pages": [ //pages数组中第一项表示应用启动页
		{
			"path": "pages/index/index",
			"style": {
				"navigationBarTitleText": "抽奖"
			}
		},
		{
			"path": "pages/lottery/list",
			"style": {
				"navigationBarTitleText": "设置",
				"enablePullDownRefresh": false
			}
		}, {
			"path": "pages/lottery/add",
			"style": {
				"navigationBarTitleText": "新增"
			}
		}, {
			"path": "pages/lottery/edit",
			"style": {
				"navigationBarTitleText": "编辑"
			}
		}, {
			"path": "pages/lottery/list",
			"style": {
				"navigationBarTitleText": "列表"
			}
		}, {
			"path": "pages/lottery/detail",
			"style": {
				"navigationBarTitleText": "详情"
			}
		}
	],
	"tabBar": { //配置页面tab切换导航
		"color": "#7A7E83",
		"selectedColor": "#3cc51f",
		"borderStyle": "black",
		"backgroundColor": "#ffffff",
		"list": [{
			"pagePath": "pages/index/index",
			"iconPath": "static/home.png",
			"selectedIconPath": "static/home.png",
			"text": "抽奖"
		}, {
			"pagePath": "pages/lottery/list",
			"iconPath": "static/setting-select.png",
			"selectedIconPath": "static/setting-select.png",
			"text": "设置"
		}]
	},
	"globalStyle": {
		"navigationBarTextStyle": "black",
		"navigationBarTitleText": "uni-app",
		"navigationBarBackgroundColor": "#F8F8F8",
		"backgroundColor": "#F8F8F8"
	},
	"uniIdRouter": {}
}
  • 优化样式
.content {
		.uni-list {
			display: flex;
			flex-wrap: wrap;
			flex-direction: row;
			justify-content: center;
		}
	}
	.uni-list-item{
		border:4rpx solid transparent ;
	}
	.active{
		border-color: red;
	}
  • 最终效果图

image.png

image.png