Bootstrap

Unit3 使用 uniCloud 制作书籍管理移动端应用项目

1 构建项目并关联云服务空间

  • uniCloud 为开发人员提供了“阿里云”和“腾讯云”两种云服务空间供应商。其中“阿里云”可以创建一个免费的云服务空间,“腾讯云”不提供免费服务。
  • 不管是“阿里云”还是“腾讯云”,如果要使用云服务功能,都必须进行实名制认证。
  • “阿里云”的免费云服务空间有效期是 1 个月,如果需要延长免费使用时间,需要在临到期之前继续以 0 元支付该空间的费用,否则将失去“免费”资格,只能创建付费的云服务空间。

以下是构建一个新的项目,并与uniCloud云服务空间关联的操作步骤。

[1] 新建一个 uni-ui 项目 uniCloudBooks,并启用 uniCloud。

在这里插入图片描述

[2] 项目创建后,在项目管理器中“右键单击” uniCloud 目录,在弹出的快捷菜单中选择 “关联云服务空间或项目”。

在这里插入图片描述

[3] 初次使用 uniCloud 时,由于并没有任何云服务空间,所以在 “关联云服务空间或项目”窗口中看到的列表项应该是空的。需要点击窗口上的“新建”按钮,进入新建云服务空间页面。

在这里插入图片描述

[4] 进入 uniCloud 控制台页面,点击 “新建服务空间” 按钮。

在这里插入图片描述

[5] 免费的阿里云服务空间

在这里插入图片描述

[6] 在 HX 中完成云服务空间与项目之间的关联

在这里插入图片描述

2 为项目准备数据库表

一本书的信息应该包含(但不限于):书名、作者、出版社、封面照片、单价、内容简介等。

为了让小程序更加适用于一个线下书店对所有图书的管理功能,这里可以考虑增加 “剩余数量” 和 “购置日期”两个信息。

uniCloud 的云数据库是一个基于 JSON 格式的文档型数据库。官网的介绍如下:

在这里插入图片描述

以下是在 HX 中创建数据库表文件的操作步骤。

[1] 新建DB Schema

在这里插入图片描述

[2] 选择 default 模板创建 DB Schema 文件

在这里插入图片描述

[3] 一个基本的数据库表文件结构

{
	"bsonType": "object",
	"required": [],
	"permission": {
		"read": true,
		"create": true,
		"update": true,
		"delete": true
	},
	"properties": {
		"_id": {
			"description": "ID,系统自动生成"
		}
  }
}

在 uniCloud 的云数据库中,使用 json 格式的数据来描述一个数据库表的表结构。其中:

  • "bsonType" 表示当前是一个表对象实例。
  • "required"表示在 “properties” 项里定义的哪些字段是必填项。
  • "permission" 表示可以在当前的数据库表实例上进行“增、删、改、查”的权限,默认创建时全是“false”
  • "properties" 表示这里是表字段的描述。默认情况自动创建了“主键”字段 “_id”。该字段不可删除和修改。用户需要添加的其他字段也都写在这里。

[4] 该项目的数据库表文件 mybooks.schema.json

{
	"bsonType": "object",
	"required": ["book_name","authors","publisher","classify","count","price"],
	"permission": {
		"read": true,
		"create": true,
		"update": true,
		"delete": true
	},
	"properties": {
		"_id": {
			"description": "ID,系统自动生成"
		},
		"book_name": {
			"bsonType": "string",
			"minLength": 1,
			"maxLength": 50,
			"title": "书籍名称"
		},
		"authors": {
			"bsonType": "string",
			"minLength": 1,
			"maxLength": 50,
			"title": "作者"
		},
		"publisher": {
			"bsonType": "string",
			"minLength": 1,
			"maxLength": 50,
			"title": "出版社"
		},
		"classify": {
			"bsonType": "string",
			"enum": [
				{"text": "小说", "value": "novel"},
				{"text": "科技", "value": "technology"},
				{"text": "生活", "value": "life"},
				{"text": "社科", "value": "society"},
				{"text": "经济", "value": "economic"},
				{"text": "传记", "value": "biography"},
				{"text": "历史", "value": "history"},
				{"text": "儿童", "value": "children"}
			],
			"componentForEdit": {
				"name": "uni-data-select",
				"props": {
					"placeholder": "请选择分类"
				}
			},
			"title": "分类"
		},
		"book_cover": {
			"bsonType": "file",
			"fileMediaType": "image",
			"fileExtName": "jpg,png",
			"title": "封面"
		},
		"count": {
			"bsonType": "int",
			"minimum": 0,
			"title": "余量"
		},
		"price": {
			"bsonType": "int",
			"minimum": 0,
			"title": "单价(分)"
		},
		"brief": {
			"bsonType": "string",
			"minLength": 3,
			"maxLength": 200,
			"componentForEdit": {
				"name": "uni-easyinput",
				"props": {
					"type": "textarea"
				}
			},
			"title": "简介"
		},
		"addtime": {
			"bsonType": "timestamp",
			"forceDefaultValue": {
				"$env": "now"
			},
			"componentForShow": {
				"name": "uni-dateformat"
			},
			"title": "购置时间"
		}
	}
}

【说明】:

  • "properties" 项中定义了数据表所需要的9个字段。
  • "book_name" 字段存放的是书籍名称,字符串型,长度在 1 ~ 50 个字符之间。
  • "authors" 字段存放的是书籍作者,字符串型,长度在 1 ~ 50 个字符之间。
  • "publisher" 字段存放的是出版社名称,字符串型,长度在 1 ~ 50 个字符之间。
  • "classify" 字段存放的是书籍的种类,字符串型。
    • 这里用到了 "enum" 属性,表示这个字段的数据值是在 "enum" 中所列内容中的其中一个。
    • 搭配的 "componentForEdit" 属性,表示在对该数据进行编辑使,使用什么样的前端组件进行展示。这里使用的是数据下拉选择器组件 "uni-data-select"
  • "book_cover" 字段用于存放书籍封面图片的信息,这里用的是 file 类型,有关使用 file 类型设置图片信息的示例可以参考官网的说明。

在这里插入图片描述

  • "count" 字段用于存储书籍的数量,int 类型,最小值为 0。
  • "price" 字段用于存储书籍的单价,int 类型,最小值为 0。因为在 JavaScript 中 double 数据类型在计算时容易出现误差,因此建议使用“分”为单位进行价格数据的存储。这个可以看下图中官网的解释。

在这里插入图片描述

  • "brief" 字段用于存储书籍的简介,字符串类型,长度在 3 ~ 200 之间。因为简介内容比较多,用单行文本输入框不合适,因此使用 "componentForEdit" 属性指定了前端组件 "uni-easyinput" 为多行文本框。
  • "addtime" 字段用于存储当前这条记录添加到数据库表的时刻,用的是 timestamp 类型(时间戳)。针对这种数据官方的解释说明见下图:

在这里插入图片描述

[5] 上传 DB Schema

在这里插入图片描述

上传成功后,在 HX 的控制台调试窗口中会显示:“上传数据集合Schema(mybooks.schema.json)成功” 的字样。

在 uniCloud 的 Web 控制台中,看到云数据库中已经有了一个与本地的 mybooks 一模一样的表文件。

在这里插入图片描述

3 schema2Code

关于 schema2code 官网的介绍如下:

在这里插入图片描述

以下是对创建好的表文件 mybooks.schema.json 执行 schema2code 的操作步骤。

[1] 对表文件执行 schema2code

在这里插入图片描述

[2] 在数据表文件上右键后,选择“schema2code”,注意在下图中一定要全选所有字段后,点击“确定”按钮。

在这里插入图片描述

如果弹出来提示说未安装XX插件的话,那就直接点击安装即可。

[3] schema2code 的过程会稍微长一些,等待一下,会询问是否需要把页面合并注册的提示。一定要选择“注册”

在这里插入图片描述

[4] 之后会询问是否将文件进行合并,下图一定要选“合并”。

在这里插入图片描述

[5] 一切结束后,在 HX 的项目管理器窗口中会看到 schema2code 自动生成的四个页面。

在这里插入图片描述

4 遇到了错误

以下的问题主要是由 HX 的版本问题引起的,如果开发过程中没有遇到,可以忽略这里。

[错误1] 提示 “未安装公共模块”

在 HX 的编辑器打开 list.vue 页面后,点击“预览”按钮,在 HX 的内置浏览器中“第一次”运行时,会报“未找到公共模块[uni-id-common]”的错误,见下图:

在这里插入图片描述

参考下图的指引,下载所有云函数、公共模块及actions

在这里插入图片描述

[错误2] Invaild uni-id config file

当执行完“下载所有云函数、公共模块及 action”后,再次运行 list.vue 页面,报错:“Invalid uni-id config file”。

在这里插入图片描述

这种情况就需要手动创建 uni-id 目录及 config.json 文件,在 uniCloud/cloudfunctions/common/uni-config-center/ 目录下新建 uni-id 目录,并继续创建 config.json 文件。

在这里插入图片描述

上述代码从https://uniapp.dcloud.net.cn/uniCloud/uni-id-summary.html#config 页面上找到文件源码,直接按照官网提示复制文件内容。

【重要提示】:

  • json 文件不支持注释语句,一定要把所有的注释代码删掉。
  • "tokenSecret" 项必须填写,自己拟定一个比较长的字符串即可。

config.json 示例代码如下:

{
  "passwordSecret": [
    {
      "type": "hmac-sha256",
      "version": 1
    }
  ], 
  "passwordStrength": "medium", 
  "tokenSecret": "sjzceducnuniapp2023mobileapplicationdevelopclass", 
  "requestAuthSecret": "", 
  "tokenExpiresIn": 7200, 
  "tokenExpiresThreshold": 3600, 
  "maxTokenLength": 10, 
  "passwordErrorLimit": 6, 
  "passwordErrorRetryTime": 3600, 
  "autoSetInviteCode": false, 
  "forceInviteCode": false, 
  "idCardCertifyLimit": 1, 
  "realNameCertifyLimit": 5, 
  "sensitiveInfoEncryptSecret": "", 
  "frvNeedAlivePhoto": false, 
  "userRegisterDefaultRole": [], 
  "app": { 
    "tokenExpiresIn": 2592000,
    "tokenExpiresThreshold": 864000,
    "oauth": {
      "weixin": {
        "appid": "",
        "appsecret": ""
      },
      "qq": {
        "appid": "",
        "appsecret": ""
      },
      "apple": { 
        "bundleId": ""
      }
    }
  },
  "web": { 
    "tokenExpiresIn": 7200,
    "tokenExpiresThreshold": 3600,
    "oauth": {
      "weixin-h5": { 
        "appid": "",
        "appsecret": ""
      },
      "weixin-web": {
        "appid": "",
        "appsecret": ""
      }
    }
  },
  "mp-weixin": {
    "tokenExpiresIn": 259200,
    "tokenExpiresThreshold": 86400,
    "oauth": {
      "weixin": {
        "appid": "",
        "appsecret": ""
      }
    }
  },
  "mp-qq": {
    "tokenExpiresIn": 259200,
    "tokenExpiresThreshold": 86400,
    "oauth": {
      "qq": {
        "appid": "",
        "appsecret": ""
      }
    }
  },
  "mp-alipay": {
    "tokenExpiresIn": 259200,
    "tokenExpiresThreshold": 86400,
    "oauth": {
      "alipay": {
        "appid": "",
        "privateKey": "", 
        "keyType": "PKCS8"
      }
    }
  },
  "service": {
    "sms": {
      "name": "", 
      "codeExpiresIn": 180, 
      "smsKey": "", 
      "smsSecret": "",
      "scene": {
        "bind-mobile-by-sms": {
          "templateId": "",
          "codeExpiresIn": 240
        }
      }
    },
    "univerify": {
      "appid": "", 
      "apiKey": "",
      "apiSecret": ""
    }
  }
}

故障排除,预览正常

在这里插入图片描述

5 对 “addtime” 字段对应的前端组件进行修改

schema2code 自动生成的前端页面有些组件的选配不是很智能,需要手动进行一下调整。这里仅仅以 “addtime” 字段对应生成的组件进行调整。其他页面中前端组件的显示效果,可以根据实际需要进行美化或重新布局。

“addtime” 字段在定义时选择的类型是 timestamp 时间戳,且设置了强制默认值为当前的系统时间,而这种数据本身以长整型的形式存储,在查看该数据时需要对其使用专门的组件 uni-dateformat 来实施。

以下就是对涉及到的三个页面的修改步骤。

[1] 在 HX 中打开 pages/mybooks/add.vue 页面,在页面源码中找到以下代码,将其删除。

<uni-forms-item name="addtime" label="购置时间">
  <uni-datetime-picker return-type="timestamp" v-model="formData.addtime"></uni-datetime-picker>
</uni-forms-item>

mybooks.schema.json 文件中,为存放“购置时间”的字段 "addtime" 设置了强制默认值,因此不需要在前端页面中执行时间选择。

[2] pages/mybooks/edit.vue 页面中也包含了添加时间的前端组件,同样需要删除。在页面源码中找到以下代码,将其删除即可。

<uni-forms-item name="addtime" label="购置时间">
  <uni-datetime-picker return-type="timestamp" v-model="formData.addtime"></uni-datetime-picker>
</uni-forms-item>

[3] 打开 pages/mybooks/detail.vue 页面,在页面源码中找到以下代码,

<uni-dateformat :value="data.addtime"></uni-dateformat>

将其修改为:

<uni-dateformat :date="data.addtime"  threshold="[0,0]"></uni-dateformat>

虽然在 "addtime" 字段定义时指定了使用 uni-dateformat 组件显示时间,但是自动生成的代码中属性配置有误,需要进行手动调整。

6 美化列表页

通过 schema2code 生成的页面并不美观,如果需要调整页面的显示效果,可以按照具体需求,结合 uni-ui 的扩展组件来进行修改。

以下是在列表页显示书籍的缩略图、书名、出版社、作者和价格的操作步骤:

[1] 在 list.vue 页面的 template 部分,找到如下代码:

<template v-slot:body>
  ...
  <text>
    {{item._id}}
  </text>
</template>

将其修改为:

<template v-slot:header>
  <view class="slot-box">
    <image :src="item.book_cover.path" mode="widthFix"></image>
  </view>
</template>
<template v-slot:body>
  <view class="slot-body">
    <view class="bookname">
      <text>{{item.book_name}}</text>
    </view>
    <view class="pa">
      <text>{{item.publisher}}</text>
    </view>
    <view class="pa">
      <text>{{item.authors}}</text>
    </view>
    <view class="price">
      <text>{{getYuan(item.price)}}</text>
    </view>
  </view>
</template>

[2] 显示价格数据

因为价格数据是按照人民币的“分”为单位存放的,显示时还需要以“元”为单位进行展示,因此需要编写一个自定义的函数来对价格数据进行处理。

list.vue 页面的 scriptmethods 部分添加一个自定义函数 getYuan,代码如下:

getYuan(price){
  let yuan = Math.trunc(price/100)
  let fen = price % 100
  return '¥ ' + yuan + '.' + fen
},

[3] 为 list.vue 页面的 style 标记处添加样式表

.slot-box{
  display: flex;
  flex-direction: row;
  width: 60px;
  margin-right: 10px;
  align-items: center;
  border: 1px solid #aaa;
}
.slot-body{
  display: flex;
  flex-direction: column;
}
.bookname{
  font-size: 16px;
  font-weight: bold;
  color: #666;
}
.pa{
  margin-top: 3px;
  font-size: 11px;
  color: #aaa;
}
.price{
  font-size: 14px;
  color: #aa0000;
}

[4] 运行一下看看效果

在这里插入图片描述

7 构建静态 js 文件实现代码重用

无论是列表页还是详情页,都涉及到价格数据的显示,如果同样的价格处理函数分别在两个页面中都写一遍,这样做不仅代码冗余,而且在后期维护起来会很麻烦,需要每个页面中的代码都修改一下。

解决方法是把这些代码存在外部的静态 js 文件中,将需要访问的方法用 export 一下,在需要使用的地方 import 一下。

以下是操作步骤:

[1] 在项目的 js_sdk 目录下新建一个 js 文件 utils.js

[2] 在 utils.js 文件中键入如下代码:

export function getYuan(price) {
	let yuan = Math.trunc(price / 100)
	let fen = price % 100
	return '¥ ' + yuan + '.' + fen
}

[3] 在需要使用的地方引入

例如,在 list.vue 页面中的 script 部分的最开头,引入一下

<script>
	import {getYuan} from '@/js_sdk/utils.js'
  // 这里省略了list.vue 中 script 部分的其他代码
</script>

[4] 如果要在模板视图中使用外部 js 函数,需要在 scriptmethods 部分进行声明。

export default {
  //...这里省略了一些代码
  methods: {
    getYuan,
    //...这里省略了一些代码
  }
}

8 美化 detail.vue 页面

详情页面中为了让图书信息更好的展示,考虑采用 uni-group 组件进行美化。操作步骤如下:

[1] 由于详情页中同样涉及到对“价格”数据的处理,并在视图中渲染,请参照 7 中的[3]和[4]修改 detail.vue 页面的 script 部分的代码。

[2] 修改 detail.vue 页面的 template 部分的代码。

template 部分找到如下代码:

<view>
  <text>书名</text>
  <text>{{data.book_name}}</text>
</view>
<view>
  <text>作者</text>
  <text>{{data.authors}}</text>
</view>
<view>
  <text>出版社</text>
  <text>{{data.publisher}}</text>
</view>
<view>
  <text>分类</text>
  <text>{{options.classify_valuetotext[data.classify]}}</text>
</view>
<view>
  <text>封面</text>
  <uni-file-picker v-if="data.book_cover && data.book_cover.fileType == 'image'" :value="data.book_cover" :file-mediatype="data.book_cover && data.book_cover.fileType" return-type="object" readonly></uni-file-picker>
  <uni-link v-else-if="data.book_cover" :href="data.book_cover.url" :text="data.book_cover.url"></uni-link>
  <text v-else></text>
</view>
<view>
  <text>余量</text>
  <text>{{data.count}}</text>
</view>
<view>
  <text>单价</text>
  <text>{{data.price}}</text>
</view>
<view>
  <text>简介</text>
  <text>{{data.brief}}</text>
</view>

将其修改为:

<uni-group title="书籍封面" margin-top="" mode="card">
  <uni-file-picker v-if="data.book_cover && data.book_cover.fileType == 'image'" :value="data.book_cover" :file-mediatype="data.book_cover && data.book_cover.fileType" return-type="object" readonly></uni-file-picker>
  <uni-link v-else-if="data.book_cover" :href="data.book_cover.url" :text="data.book_cover.url"></uni-link>
  <text v-else></text>
</uni-group>
<uni-group title="基本信息" margin-top="" mode="card">
  <view>
    <text>书名:</text>
    <text>{{data.book_name}}</text>
  </view>
  <view>
    <text>作者:</text>
    <text>{{data.authors}}</text>
  </view>
  <view>
    <text>出版社:</text>
    <text>{{data.publisher}}</text>
  </view>
  <view>
    <text>分类:</text>
    <text>{{options.classify_valuetotext[data.classify]}}</text>
  </view>
  <view>
    <text>余量:</text>
    <text>{{data.count}}本</text>
  </view>
  <view>
    <text>单价:</text>
    <text>{{getYuan(data.price)}}</text>
  </view>
</uni-group>
<uni-group title="内容简介" margin-top="" mode="card">
  <text>{{data.brief}}</text>
</uni-group>

[3] 运行一下看看美化后的效果

在这里插入图片描述

操作视频

以上的操作步骤已经录制成了视频发布到了 B 站里,访问入口如下:

;