首页 > 编程语言 >基于uniapp框架开发飞书小程序总结

基于uniapp框架开发飞书小程序总结

时间:2023-02-01 14:44:14浏览次数:51  
标签:uniapp code 框架 res state 飞书 uni pages store

前期准备

飞书官方客户端文档:https://open.feishu.cn/document/home/intro

飞书官方工具资源文档:https://open.feishu.cn/document/uYjL24iN/uEzMzUjLxMzM14SMzMTN/develop-gadget-with-uni-app

经过对比选型,决定使用uniapp框架进行开发,因为需求较简单,所以ui库就直接用了uniapp官方提供的库。

uniapp官方文档:https://uniapp.dcloud.net.cn/tutorial/

uniapp的论坛也提供了一些轮子:https://ext.dcloud.net.cn/

附:

Taro 和 uni-app选型对比:http://t.zoukankan.com/yjiangling-p-10788910.html (taro官网和ui库打不开:https://taro.jd.com/) (uniapp官网:https://uniapp.dcloud.net.cn/component/uniui/uni-table.html#%E7%A4%BA%E4%BE%8B) (uniapp选择ui库:https://blog.csdn.net/qq_47443027/article/details/119734230) (推荐的ui库汇总:https://blog.csdn.net/weixin_44070058/article/details/124734057) ps:muse-ui没有日期范围组件,uView没有表格组件,vant没有飞书小程序版本,uniapp的ui库有一丢丢古早  

开始开发

根据官方文档的步骤一路操作下来后,已经可以用hbuilder搭建一个新项目,配置好飞书开发者工具的路径后,通过运行将飞书开发者工具唤醒了。

导入项目后,就可以正式开发了。

由于基础的api,飞书和uniapp的官方文档中已经写得很清楚,可以直接参阅文档。

引入官方ui库:https://uniapp.dcloud.net.cn/component/uniui/quickstart.html

接下来开始配置store。

uniaap生成的项目中,已经内嵌了vuex,我因为一直使用React开发,已经很久没有接触过vue了,因此对照着文档进行了学习:https://uniapp.dcloud.net.cn/tutorial/vue3-vuex.html

整理一下配置步骤:

1.首先在项目根目录下新建store文件夹,其下新建index.js:

 

 

 

2.index.js的内容为:

// // 组装模块并导出 store 的地方
import {
	createStore
} from 'vuex'
import {
	tabbarList
} from '@/utils.js';

const store = createStore({
	// 存放状态
	state: {
		"code": '',
		"openId": '',
		"userInfo": {},
	},
	getters: {
		getCode(state) {
			return state.code || ''
		},
		getToken(state) {
			return state.openId || ''
		},
		getUserInfo(state) {
			return state.userInfo || {}
		},
	},
	// 同步函数
	mutations: {
		setCode(state, payload) {
			state.code = payload.code || ''
		},
		setUserInfo(state, payload) {
			state.userInfo = payload || {}
		},
		setOpenId(state, payload) {
			state.openId = payload || ''
		},
	},
	// 提交 mutation,通过 mutation 改变 state ,而不是直接变更状态,可以包含任意异步操作
	actions: {
		// 登录系统
		adsLogin({
			commit,
			state
		}, payload) {
			// 清理本地ads登录相关的缓存
			uni.removeStorageSync('OPEN_ID');
			uni.removeStorageSync('USER_INFO');
			return new Promise((resolve, reject) => {
				uni.request({
					url: '/login',
					method: 'POST',
					data: {
						code: state.code,
					},
					success: (res) => {
						const {
							code,
							message,
							result
						} = res.data;
						if (code === 0 && result) {
							commit('setUserInfo', result)
							commit('setOpenId', result.open_id)
							uni.setStorageSync('USER_INFO', result) // 存储userInfo
							uni.setStorageSync('OPEN_ID', result.open_id) // 存储open_id
							if (resolve) resolve(result)
						} else {
							uni.showToast({
								title: message || '操作失败',
								icon: 'error',
								duration: 3000
							})
							if (reject) reject(res)
						}
					},
					fail: err => {
						console.log(err, 'err');
						uni.showToast({
							title: err.errMsg || '请求错误',
							icon: 'fail',
							duration: 2000
						})
						if (reject) reject(err)
					}
				});
			})

		}
	}
})

export default store

  

其中的一些API,文档中都有很详细的介绍:

------------------------------------

state 用于存放数据(be like React中的state) getters 用于获取数据 mutations 为同步函数,我理解为对数据进行处理和存储 actions 为提交mutation的一种行为,我理解为需要复杂操作操作(比如异步请求)时,可以配置在这里(be like React开发中的Redux中的dispatch,不过现在都用hooks了) ------------------------------------ 我这里只配置了一个actions,那就是登录后台系统的操作,使用Promise的两个回调把接口请求的结果拿出来,外部调用时就可以获取到。下面是App.vue的代码:  
<script>
	import store from '@/store/index.js'; // 引入store
	import {
		mapGetters,
		mapActions
	} from 'vuex';
	import qs from 'qs';

	export default {
		computed: {
			...mapGetters({
				code: 'getCode',
				token: 'getToken'
			})
		},
		// 监听小程序初始化
		onLaunch: function() {
			// 小程序初始化后全局执行一次,若【未登录ads|token过期】则触发登录,否则直接进入主页面
			const initCommon = () => {
				uni.request({
					url: '/jzData/common/init',
					header: {
						Authorization: `Bearer ${uni.getStorageSync('OPEN_ID')}`,
					},
					success: (res) => {
						const {
							code,
							message,
							result
						} = res.data;
						if (code === 0 && result) {
							uni.$emit('hasLogin');
							store.commit('setCommon', result)
						} else if (code === 50000) {
							// 如果接口返回code为50000,则说明ads登录过期,需要重新登录
							getAdsLogin()
						} else {
							uni.showToast({
								title: message || '操作失败',
								icon: 'error',
								duration: 2000
							})
						}
					}
				});
			}

			const getAdsLogin = () => {
				// 服务器问题-服务器缺省页;账号不存在-权限缺省页;网络问题-网络缺省页
				store.dispatch('adsLogin').then(() => {
						uni.$emit('hasLogin');
						initCommon()
					})
					.catch((res) => {
						uni.$emit('notLogin');
						if (res.statusCode === 500) {
							uni.redirectTo({
								url: `/pages/500/500`
							});
						} else {
							const message = res?.data?.message || '';
							//关闭当前页面,跳转到403无权限页面
							uni.redirectTo({
								url: `/pages/403/403?msg=${message}`
							});
						}
					});
			}
			// 登录并获取用户信息[每次进入小程序都执行,只对ads系统的登录状态做判断]
			tt.login({
				success(res) {
					// 存储飞书code,用于请求时传参
					store.commit({
						type: 'setCode',
						code: res.code || ''
					})
					// 如果已有openid在缓存,则不需要登录ads系统,存储userInfo&open_id
					if (uni.getStorageSync('OPEN_ID')) {
						store.commit('setUserInfo', uni.getStorageSync('USER_INFO') || {})
						store.commit('setOpenId', uni.getStorageSync('OPEN_ID') || '')
						initCommon()
					} else {
						// 使用小程序登录后返回的code登录ads系统
						// 服务器问题-服务器缺省页;账号不存在-权限缺省页;网络问题-网络缺省页
						store.dispatch('adsLogin').then((res) => {
								uni.$emit('hasLogin');
								const openId = res?.open_id;
								uni.request({
									url: '/init',
									header: {
										Authorization: `Bearer ${openId}`,
									},
									success: (res) => {
										const {
											code,
											message,
											result
										} = res.data;
										if (code === 0 && result) {
											store.commit('setCommon', result)
										} else {
											uni.showToast({
												title: message || '操作失败',
												icon: 'error',
												duration: 2000
											})
										}
									}
								});
							})
							.catch((res) => {
								uni.$emit('notLogin');
								if (res.statusCode === 500) {
									uni.redirectTo({
										url: `/pages/500/500`
									});
								} else {
									const message = res?.data?.message || '';
									//关闭当前页面,跳转到403无权限页面
									uni.redirectTo({
										url: `/pages/403/403?msg=${message}`
									});
								}
							});
					}
				},
				fail(res) {
					console.log(`飞书小程序登陆失败: ${JSON.stringify(res)}`);
					uni.$emit('failLogin');
					uni.redirectTo({
						url: `/pages/404/404`
					});
				}
			});

			// 全局添加拦截器
			uni.addInterceptor('request', {
				invoke(args) {
					const dev = 'https://xx.com';
					const pre = 'https://yy.com';
					const pro = 'https://zz.com';
					// args.url = (process.env.NODE_ENV === 'development' ? dev : pro) + args.url;
					// 发布测试版
					const params = args.data;
					if (args.method === 'GET' || !args.method) {
						args.url = pre + args.url + `?${qs.stringify(params, { arrayFormat: 'brackets' })}`;
						args.data = {}
					} else {
						args.url = pre + args.url;
					}
					console.log('请求内容:', args)
					// args.header = {
					// 	...args.header,
					// 	Authorization: `Bearer ${this.token}`,
					// }
				},
				success(args) {
					console.log('请求成功:', args)
				},
				fail(err) {
					console.log('请求失败:', err)
				},
			})
		},
		onShow: function() {
			// console.log('App Show')
		},
		onHide: function() {},
		onPageNotFound() {
			uni.redirectTo({
				url: '/pages/404/404'
			})
		},
		methods: {
			...mapActions([
				'adsLogin',
			]),
		}
	}
</script>

<style lang="scss">
	/*每个页面公共css */
	@import './static/font/iconfont.css';

	body {
		color: $uni-text-color;
		font-size: 28rpx;
		font-family: -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol;
		padding-bottom: 40rpx;
	}
</style>

  

逻辑处理很简单(因为真的很小一项目,请教了大佬后确定就简单做):先登录飞书,拿到飞书的code之后,请求后台系统,获取后台系统返回的openId,这个字段用于后续所有接口请求时拼接在头部。

3.store的主文件写完后,需要配置到main.js中(爷直接复制官方文档),就可以生效了:

import App from './App'
import store from './store'
import {
	createSSRApp
} from 'vue'

// #ifndef VUE3
import Vue from 'vue'

Vue.prototype.$store = store
Vue.config.productionTip = false

App.mpType = 'app'
const app = new Vue({
	store,
	...App
})
app.$mount()
// #endif

// #ifdef VUE3
export function createApp() {
	const app = createSSRApp(App)
	app.use(store)
	return {
		app
	}
}
// #endif

  

4.页面中使用:

 

方法中就可以直接获取到:

 

 同样模板代码中也可以直接拿到:

 

接下来就是页面的开发。首先明确页面配置都是在pages.json中进行,包括tabber页的各种配置,这些文档中都有提及。 但是开发过程中遇到了tabber需要权限控制的问题,所以没有用原生的tabber,自己写了个组件(但是pages.json中仍旧需要配置tabber的地址),以下是pages.json的代码:
{
	"easycom": {
		"autoscan": true,
		"custom": {
			// uni-ui 规则如下配置
			"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue"
		}
	},
	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
		{
			"path": "pages/index/index"
		},
		{
			"path": "pages/summary/summary",
			"style": {
				"enablePullDownRefresh": true
			}
		},
		// 项目概况
		{
			"path": "pages/overview/overview",
			"style": {
				"navigationBarTitleText": "项目概况",
				"enablePullDownRefresh": true
			}
		},
		// 买量概况
		{
			"path": "pages/buyVolume/buyVolume",
			"style": {
				"navigationBarTitleText": "买量概况",
				"enablePullDownRefresh": true
			}
		},
		// 媒体概况
		{
			"path": "pages/media/media",
			"style": {
				"navigationBarTitleText": "媒体概况",
				"enablePullDownRefresh": true
			}
		},
		// 人员概况
		{
			"path": "pages/person/person",
			"style": {
				"navigationBarTitleText": "人员概况",
				"enablePullDownRefresh": true
			}
		}, 
		{
			"path": "pages/500/500",
			"style": {
				"navigationStyle": "custom"
			}
		},
		{
			"path": "pages/404/404",
			"style": {
				"navigationStyle": "custom"
			}
		},
		{
			"path": "pages/403/403",
			"style": {
				"navigationStyle": "custom"
			}
		}
	],
	"globalStyle": {
		"navigationBarTextStyle": "black",
		"navigationBarTitleText": "Data(应用)",
		"navigationBarBackgroundColor": "#F8F8F8",
		"backgroundColor": "#F8F8F8"
	},
	"uniIdRouter": {},
	"tabBar": {
		"list": [{
				"pagePath": "pages/overview/overview"
			},
			{
				"pagePath": "pages/buyVolume/buyVolume"
			},
			{
				"pagePath": "pages/media/media"
			},
			{
				"pagePath": "pages/person/person"
			}
		]
	}
}

  

关于自定义组件,就记录一个自定义tabber来参考:

首先在components文件夹下新建组件:

 

 

功能较简单,就不赘述了,贴一下代码万一以后拿去复制:

<template>
	<view class="tab-bar">
		<view class="tab-bar-border"></view>
		<view v-for="(item,index) in tabBarList" :key="index" class="tab-bar-item" :data-id="index" @click="jump(item)">
			<image :src="current === item.index ? item.selectedIconPath : item.iconPath"></image>
			<view :style="{'color':current === item.index ? '#70b603' : '#909399'}" style="margin-top: 10rpx;">
				{{item.text}}
			</view>
		</view>
	</view>

</template>

<script>
	
	export default {
		name: "footer-tabbar",
		props: {
			tabBarList: {
				type: Array,
				default: uni.getStorageSync('tabBarList')
			},
			current: Number,
			gameId: String | Number
		},
		data() {
			return {
				value1: 0, // 默认页面
				inactiveColor: '#909399' // 高亮颜色
			}
		},
		onShow() {
		},
		methods: {
			// 点击跳转对应tabbar页面
			jump(e) {
				uni.switchTab({
					url: e.pagePath
				})
			}
		}
	}
</script>

<style lang="scss" scoped>
	.tab-bar {
		position: fixed;
		bottom: 0;
		left: 0;
		right: 0;
		height: 48px;
		border-top: 1px solid #ccc;
		background: white;
		display: flex;
		z-index: 98;
	}

	.tab-bar-border {
		// background-color: rgba(0, 0, 0, 0.33);
		background-color: white;
		position: absolute;
		left: 0;
		top: 0;
		width: 100%;
		height: 1px;
		border-top: 2rpx solid rgba(187, 187, 187, 0.3);
		transform: scaleY(0.5);
	}

	.tab-bar-item {
		flex: 1;
		text-align: center;
		display: flex;
		justify-content: center;
		align-items: center;
		flex-direction: column;
	}

	.tab-bar-item image {
		width: 24px;
		height: 24px;
	}

	.tab-bar-item view {
		font-size: 10px;
	}
</style>

  

默认配置:

export function tabbarList() {
	return [{
			iconPath: "/static/biaoqian.png",
			selectedIconPath: "/static/biaoqian_active.png",
			text: '项目概况',
			pagePath: "/pages/overview/overview",
			name: "overview",
			index: 0,
			permission: "JzDataSummaryGame"
		},
		{
			iconPath: "/static/shezhi.png",
			selectedIconPath: "/static/shezhi_active.png",
			text: '买量概况',
			pagePath: "/pages/buyVolume/buyVolume",
			name: "buyVolume",
			index: 1,
			permission: "JzDataSummaryAdvertise"
		}, {
			iconPath: "/static/wenjian.png",
			selectedIconPath: "/static/wenjian_active.png",
			text: '媒体概况',
			pagePath: "/pages/media/media",
			name: "media",
			index: 2,
			permission: "JzDataSummaryChannel"
		}, {
			iconPath: "/static/bianxie.png",
			selectedIconPath: "/static/bianxie_active.png",
			text: '人员概况',
			pagePath: "/pages/person/person",
			name: "person",
			index: 3,
			permission: "JzDataSummaryUser"
		},
	]
}

当接口返回权限时,就可以直接进行处理,存储起来使用

页面中引用:

点击的时候就可以切换到对应页面了。

 

关于下拉刷新,文档中有示例,使用也很简单:

 

 

 需要注意的是最后要关闭。

 

 其次是关于登陆与否的监听,当没有登录/登陆失败时,进入首页时应当要进行页面跳转。前面登录相关的代码中,已经用了uni提供的监听方法进行登录状态的监听,接下来就是在首页中进行监听:

 

 需要注意的是,页面卸载时需要关闭监听,否则会出问题:

 

 

关于字体图标,因为我引入后发现uni-icon提供的还蛮好看的,所以配置了也暂时没用,如需使用的话参考文档就好,阿里图标库也可以直接进行下载,很方便(但某种意义上还挺麻烦),使用的话也是按文档写法即可:

 

 

关于颜色,uniapp内置了一个uni.scss的文件,其中配置了许多常用样式变量,可以直接在代码中使用:

 

 

 

 

还有一个是获取跳转时携带的参数,这里贴一下403页面的代码:
<template>
	<view>
		<default-page :imgUrl="imgUrl" :text="text" />
	</view>
</template>

<script>
	import defaultPage from '../../components/default-page.vue';

	export default {
		data() {
			return {
				imgUrl: '/static/403.png',
				text: '暂无极致Data账号,请前往飞书审批提交账号权限申请',
			}
		},
		onShow() {
			// 展示后端返回的信息
			const pages = getCurrentPages();
			const curPage = pages[pages.length - 1].options;
			if (curPage.msg) {
				this.text = curPage.msg
			}
		},
		methods: {

		},
		components: {
			defaultPage
		}
	}
</script>

<style>

</style>

  

 

 其中基础组件会进行展示:

<template>
	<view class="default-page">
		<view class="default-page-icon">
			<image class="default-page-icon-img" :src="imgUrl"></image>
		</view>
		<view class="default-page-text">
			<view>{{text}}</view>
		</view>
		<view>
			<slot></slot>
		</view>
	</view>
</template>

<script>
	export default {
		name: "default-page",
		props: {
			imgUrl: String,
			text: String,
		},
		data() {
			return {};
		},

	}
</script>

<style lang="scss">
	.default-page {
		text-align: center;

		&-icon {
			&-img {
				display: inline-block;
				width: 340rpx;
				height: 340rpx;
				margin: 180rpx auto 32rpx;
			}
		}

		&-text {
			text-align: center;
			font-size: 30rpx;
			padding: 0 120rpx;
			line-height: 48rpx;
		}

		&-button {
			width: 320rpx;
		}
	}
</style>

  

项目打包

开发完后,会需要进行发布,只要在hbuilder中选择发布对应的小程序就好,跟运行差不多的步骤,但是打包好的代码是在build下面,从飞书开发者工具导入时需要注意,然后改好应用id,就可以上传代码啦~上传好后会给一个弹窗询问是否去设置,点击去设置的话就会自动打开到开发者后台,就可以更新最新版本咯。

 

 

好像也没什么特殊的了~暂时就记到这里~

 

 

标签:uniapp,code,框架,res,state,飞书,uni,pages,store
From: https://www.cnblogs.com/nangras/p/17082531.html

相关文章

  • 简单分析USB设备驱动框架
    在生活、工作中经常会接触到USB设备,如鼠标、键盘、摄像头、可移动硬盘、扫码枪等。这些设备通过USB接口连接到电脑上后,电脑会立刻提示“检测到新硬件...”、安装驱动等。这......
  • Android 下的usb框架及功能点
    ICS4.0下Framework层的usb框架 Android下的usb主要工作还是在android的framework层。主要有以下几个文件:1.1UsbDeviceManager.java/高主要完成功能切换及状态的更新,......
  • React框架运行机制
    React框架运行主流程1.JSX是JS语言的扩展,被babel编译后,会转换成React.creatElement(),这个方法返回的是一个虚拟DOM。2.将虚拟DOM渲染到真实DOM的方法是ReactDom.render()......
  • uniapp 项目的 echarts 图表本地可以展示,同事打包后 echarts 图表无法显示
    造成问题的原因本地开发环境装了百度图表echarts插件,代码提交SVN后,同事获取下来打包发布,发布后发现线上的图表无法加载出来这个同事专门负责发版本之类的,减少生产......
  • django框架之drf(部分讲解)
    restful规范(重要)一、概念REST全称是RepresentationalStateTransfer,中文意思是表述:表征性状态转移,它首次出现在2000年RoyFielding的博士论文中。RESTful是一种定义W......
  • Gin框架实战——HTML渲染
      最近使用Go的Gin框架做了个简单的前端网页,记录一下细节~1.加载静态文件    由于网页需要使用css、图片等渲染,而静态文件必须先声明:否则模板中调用加载不出......
  • django框架之drf:2、restful规范,序列、反序列化,drf安装及使用(django原生接口及drf接口
    Django之drf一、restful规范1、概念​ REST全称是RepresentationalStateTransfer,中文意思是表述:表征性状态转移,它首次出现在2000年RoyFielding的博士论文中。​ R......
  • Yolov4的框架理解
                                                        ......
  • 若依ruoyi-vue + 小程序uniapp + Android 环境安装
    ruoyi-vue安装跟随公众号王清江唷01系统环境JavaEE8Servlet3.0ApacheMaven302主框架SpringBoot2.2.xSpringFramework5.2.xSpringSecurity5.2.x03持久......
  • Yolov3的大致框架理解
                                                       ......