TL;DR

上一篇教程讲解了获取并使用MemFireCloud基于表结构生成的CRUD接口,同时演示了其云存储的基本使用方式。
到目前为止,应用数据、业务接口、界面设计均已准备完毕,本篇将介绍如何进行界面开发,并最终利用MemFireCloud提供的静态托管能力。

仓库地址

https://github.com/key7men/e-bookshelf

开发依赖

  • next.js 核心开发框架
  • mui 组件库
  • react-reader epub在线阅读组件

核心组件开发

针对功能界面进行分析,判定需要开发的组件主要如图:

业务接口代码

import { createClient } from '@supabase/supabase-js'export const url = 'your_memfire_application_url';
const key = 'your_memfire_application_anon_key';
const client = createClient(url, key);export interface BookItem {id: string;title: string;link: string;cover_link: string;date: string;publisher: string;
}export interface ISearchParam {page: number;search?: string;
}export const queryBooks = async (param: ISearchParam) => {const {data, error, count} = await client.from('book').select('*', {count: 'exact'}).range((param.page - 1) * 24, param.page * 23).ilike('title', `%${param.search}%`)if (!error) {return {data,count}} else {return {data: [],count: 0}}}export const queryIds = async () => {const {data, error} = await client.from('book').select('id')if (!error) {return data || [];} else {return []}
}export const queryBookLink = async (id: string | string[] | undefined) => {const {data, error } = await client.from('book').select('title, link').eq('id', id).single()if (!error) {return data} else {return {title: '',link: ''}}
}

组件代码示例

以搜索组件和首页列表布局为样例,具体可执行代码请查看Github仓库

搜索组件

const Navbar: FC = () => {const router = useRouter();// 防抖const doSearch = debounce(async (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {const {value} = event.target;const query = router.query;if (value === '') {delete query.search;} else {query.search = value;}await router.push({query, pathname: '/'});}, 200);return (<AppBar position='sticky' color='transparent'sx={{backgroundColor: 'white', borderBottom: '1px solid rgba(0, 0, 0, .12)', boxShadow: 'none'}}><Container maxWidth='xl'><Toolbar disableGutters>{/* icon与应用名称,在小屏幕下隐藏 */}<SvgIcon sx={{mr: 1}} viewBox="0 0 490 490"><g><g><g><path d='M415,0H75c-5.523,0-10,4.478-10,10v420c0,5.523,4.477,10,10,10h10v40c0,5.523,4.477,10,10,10h40c5.523,0,10-4.477,10-10v-40h200v40c0,5.523,4.477,10,10,10h40c5.523,0,10-4.477,10-10v-40h10c5.523,0,10-4.477,10-10V10C425,4.478,420.523,0,415,0zM125,470h-20v-30h20V470z M235,420H85V315h150V420z M385,470h-20v-30h20V470z M405,420H255V315h150V420z M405,295H85v-60h320V295z M405,215H85v-10h320V215z M405,185H85v-60h320V185z M405,105H85V95h320V105z M405,75H85V20h320V75z' /><rect x='270' y='355' width='30' height='20' /><rect x='190' y='355' width='30' height='20' /></g></g></g></SvgIcon><Typographyvariant='h6'noWrapcomponent='a'href='/'sx={{mr: 2,display: {xs: 'none', md: 'flex'},fontWeight: 300,letterSpacing: '.1rem',color: 'black',textDecoration: 'none',}}>E-BookShelf</Typography>{/* 搜索框 */}<Search sx={{flexGrow: 1}}><SearchIconWrapper><SearchIcon /></SearchIconWrapper><StyledInputBaseplaceholder='书籍名称关键字'inputProps={{'aria-label': 'search'}}onChange={(e) => doSearch(e)}/></Search></Toolbar></Container></AppBar>);
};

首页布局

const Home: NextPage = ({}) => {const router = useRouter();const [dataList, setDataList] = useState<BookItem[]>([]);const [total, setTotal] = useState(0);const [page, setPage] = useState(1);const [search, setSearch] = useState('');useEffect(() => {const params: ISearchParam = {page: 1,search: ''};if (router.query.page) {params.page = Number(router.query.page);setPage(Number(router.query.page));} else {setPage(1);}if (router.query.search) {params.search = String(router.query.search);setSearch(String(router.query.search));} else {setSearch('');}queryBooks(params).then(res => {setDataList(res.data || []);setTotal(Math.ceil(Number(res.count) / 24));})}, [router.query.search, router.query.page]);// 进入内页const goToDetail = (id: string) => {router.push(`/book/${id}`);}// 翻页const handlePageChange = (e: ChangeEvent<unknown>, pageIndex: number) => {const query = router.query;query.page = String(pageIndex);router.push({query});}// 下载const downloadBook = (link: string) => {window.open(`${url}/storage/v1/object/public/books/${link}`, '_blank');}// 在线阅读const readBook = (id: string) => {router.push(`/book/${id}`);}return (<><Head><title>首页</title></Head><Grid container sx={{p: 2}} spacing={4}>{/* 数据列表 */}{dataList.map((item: BookItem, index: number) =><Grid item key={item.id} xs={12} sm={4} lg={3} xl={2}><Card sx={{boxShadow: '0 0 6px 1px rgba(0,0,0,.2)'}} ><CardActionArea onClick={() => goToDetail(item.id)}><CardHeaderavatar={<Avatar sx={{backgroundColor: '#333', fontSize: '0.8rem'}} aria-label='format'>epub</Avatar>} /><CardMedia component='img' image={`${url}/storage/v1/object/public/covers/${item.cover_link}`} alt={item.title}sx={{height: {xs: 320, sm: 240, lg: 320}}} /><CardContent><Typography variant='body2' color='text.primary' sx={{whiteSpace: 'nowrap',width: '100%',overflow: 'hidden',textOverflow: 'ellipsis'}}>{item.title}</Typography><Divider sx={{my: 1}} /><Typography variant='body2' color='text.secondary' sx={{whiteSpace: 'nowrap',width: '100%',overflow: 'hidden',textOverflow: 'ellipsis'}}>出版单位: {item.publisher || '未知'}</Typography><Typography variant='body2' color='text.secondary' sx={{whiteSpace: 'nowrap',width: '100%',overflow: 'hidden',textOverflow: 'ellipsis'}}>发布时间: {item.date || '未知'}</Typography></CardContent></CardActionArea><CardActions sx={{float: 'right'}}><Button aria-label='download' sx={{color: '#000', borderColor: '#000', ':hover': 'red'}}startIcon={<DownloadForOfflineOutlined />} onClick={(e) => {downloadBook(item.link)}}>直接下载</Button><Button aria-label='read online' sx={{color: '#000', borderColor: '#000'}}startIcon={<AutoStoriesOutlined />} onClick={(e) => {readBook(item.id)}}>在线阅读</Button></CardActions></Card></Grid>)}</Grid><Box sx={{textAlign: 'center', my: 2, width: '100%', display: 'flex', justifyContent: 'center'}}>{ total > 0 ? <Pagination count={total} page={page} shape='rounded' variant='outlined' onChange={handlePageChange} /> : null }</Box></>)
}

如何部署

由于我使用MemFireBase生成的接口来完成应用的全部功能,所以是不需要部署后端服务的。
但前端打包后的页面仍然需要找个服务器部署啊!好在,MemFireCloud一并提供了静态托管服务,可以直接将前端部署上去。

step1 前端页面本地打包

step2 压缩zip的技巧

这里千万要注意,必须保证index.html在压缩根目录
这里千万要注意,必须保证index.html在压缩根目录
这里千万要注意,必须保证index.html在压缩根目录
笔者在这里卡了1个小时才弄明白:压缩的zip文件必须能够直接解压得到index.html,不然MemFireCloud找不到入口,说实话,我个人觉得这里不好理解,MemFireCloud的产品还有待改进。
我的压缩方式是这样的:右键框选所有我需要打包的内容,然后直接压缩

step3 上传即是部署

上传压缩包后,会自动解压,解压完成后,即可得到访问地址

小结

整个电子图书馆的开发部署,我个人的开发体验就一个字儿:快。
当然这得益于MemFire提供的服务,整个开发过程,不用安装数据库,不用学习ORM,不用写接口,不用买OSS,不用找服务器部署静态页面。 省下来的时间都够我上王者了。
话又说回来,其实我很早就知道国外的开发者用Google FireBase一把梭了,奈何国内的开发者无法使用。现在流行的低代码平台、CloudBase、MemFireCloud、Apifox都值得去体验,去使用,只有这样才能让国内开发者的工具链更完善,毕竟中国人不骗中国人,中国人了解中国人。

基于MemFireCloud的电子图书馆开发指南(三)相关推荐

  1. 基于Asterisk的VoIP开发指南…

    原文地址:基于Asterisk的VoIP开发指南--(1)实现基本呼叫功能作者:晓晓 说明: 本文档探讨基于Asterisk如何实现VoIP的一些基本功能,包括基本呼叫功能的方案选取.主叫号码透传.如 ...

  2. 开放下载!基于PAI个性化推荐系统开发指南

    亚马逊的CEO Jeff Bezos曾经说过,他的梦想是"如果我有100万个用户,我就要为他们做100万个亚马逊网站".而智能推荐系统的出现,就是为了实现这个梦想,智能推荐系统解决 ...

  3. Android Google Map开发指南(三)百度地图、谷歌地图自如切换

    如果你是刚开始接触谷歌地图的话,推荐你先看一下文章: Android Google Map 开发指南(一)解决官方demo显示空白只展示google logo问题 Android Google Map ...

  4. 基于Asterisk的VoIP开发指南(2)——Asterisk AGI程序编写指南

    5. Asterisk AGI程序编写指南    5.1概述 很多时候,我们需要在拨号方案中做某些业务逻辑的判断或者外部数据库的查询,根据具体地需要,有几种做法: 1.使用Asterisk的通道变量. ...

  5. 基于Asterisk的VoIP开发指南——(1)实现基本呼叫功能

    说明: 本文档探讨基于Asterisk如何实现VoIP的一些基本功能,包括基本呼叫功能的方案选取.主叫号码透传.如何编写AsteriskAGI程序.Radius认证计费模块等. 本文档VoIP软终端使 ...

  6. 基于VxWorks的BSP开发指南

    1       BSP概述 一个成熟的商用操作系统,其被广泛应用的必要条件之一就是能够支持众多的硬件平台,并实现应用程序的硬件无关性.一般来说,这种无关性都是由操作系统实现的. 但对于嵌入式系统来说, ...

  7. 基于DotNet构件技术的企业级敏捷软件开发平台 - AgileEAS.NET平台开发指南 - 实现插件...

    插件契约介绍 我们知道,要基于平台(容器)加插件的这种模式进行开发,我们必须定义一组契约,用于约束模块插件开发,也就是说,模块插件需要遵守一定的标准进行开发,才能正常被容器调用,这就是IModule所 ...

  8. input自适应_【正点原子FPGA连载】第十一章基于OV5640的自适应二值化实验-领航者ZYNQ之HLS 开发指南...

    1)摘自[正点原子]领航者ZYNQ之HLS 开发指南 2)平台购买地址:https://item.taobao.com/item.htm?&id=606160108761 3)全套实验源码+手 ...

  9. 基于 Linux 和 MiniGUI 的嵌入式系统软件开发指南(一)(转)

    自 MiniGUI 从 1998 年底推出以来,越来越多的人开始选择 MiniGUI 在 Linux 上开发实时嵌入式系统.为了帮助嵌入式软件开发人员使用 MiniGUI编写出更好的应用程序,我们将撰 ...

  10. 《Three.js 开发指南》源码示例说明以及在线demo(原书第二版)附第三版的代码下载

    <Three.js 开发指南>基于原书第二版 源码来自华章出版社官网随书源码,修改替换了其中不能跑的示例,保证每个demo都可以运行. 源码以及示例说明下载: git下载地址 huazha ...

最新文章

  1. 在Java程序设计中,设置环境变量path和classpath的作用分别是什么?
  2. CodeforcesBeta Round #19 D. Points 离线线段树 单点更新 离散化
  3. 【Android 性能优化】应用启动优化 ( 方法追踪代码模板 | 示例项目 | SD 卡访问权限 | 示例代码 | 获取 Trace 文件 | Android Studio 查看文件)
  4. sift的java实现解述
  5. 【Python】全网最新最全Pyecharts可视化教程(三):制作多个子图
  6. 机器学习(一)—— 线性回归
  7. C++编译报错:重复定义
  8. Hadoop学习之yarn
  9. requirejs 定义模块中含有prototype
  10. 易华录发布蓝光存储新品
  11. 深入理解typedef
  12. printf和sprintf
  13. 自定判断代码的执行环境
  14. SLAM_局部束调整(local Bundle Adjustment, BA)步骤与代码示例
  15. NPP/VIIRS逐月夜间灯光数据(2012-2020年)
  16. LAMP架构一(介绍)
  17. UE4蓝图节点不同颜色代表
  18. BP神经网络综合评价法
  19. 一文读懂蓝绿发布、A/B 测试和金丝雀发布的优缺点
  20. 数据结构之算法——递归

热门文章

  1. 深度学习教程(12) | CNN应用:目标检测(吴恩达·完整版)
  2. 进销存系统(1):开源ECP编译安装
  3. python生成加密exe_python加密保护-加密exe文件
  4. 51单片机小车(附源码)
  5. linux ipv6 前缀 定义,家庭拨号动态前缀IPv6环境下的内部设备IPv6地址的端口放通...
  6. Android 下拉刷新控件
  7. 99%用户不知道 搜索引擎这样用才有效率
  8. 标书制作详细教程(零基础速成,助力公司中标)
  9. 手把手教你使用VGG19做图像风格迁移
  10. FFMPEG录屏(12)---- DXGI 捕获桌面