在这个章节,你将学到 DEC App 的前端页面所需的大部分知识并且能发布带前端页面的 DEC App 到 OOD 上。结合前面已经学习过的 Dec_Service 内容,你将你能够构成一个完整的 Dec App。
前端代码在安装时已完全保存在 OOD 上,且一定和相应的后端匹配。 cyfs-sdk 完全支持 typescript,为 Web 开发者提供了云端一体的开发体验:使用 typescript 就能基本完成 DEC App 的开发。
a 链接
是用来处理 DEC App 应用页的链接,前端页面是应用页的主要形式。
a 链接
的 url 基础范式如下:
cyfs://a/$decid[/$dirid]/$inner_path[?parms]
a 链接
中的 dirid 可以用以指定特定版本的内置页的 object-id(dir-id 或者 objectmap-id)。如果不指定 dirid,那么使用当前版本,也可以使用 app 的语义版本 x.x.x。
a 链接
目前支持下面 3 种模式:
-
cyfs://a/{dec-id}/{inner-path}
-
cyfs://a/{dec-id}/{dir-id}/{inner-path}
-
cyfs://a/{dec-id}/{x.x.x}/{inner-path}
- 完整代码见 src/www/initialize.ts
前端初始化主要是完成 3 个步骤:
- 选择 OOD 环境(REAL)或者模拟器环境中的具体 Zone(FIRST or SECOND)
- MetaClient 初始化,选择 beta
- 打开并等待 runtime-stack 上线
代码如下:
export async function init() {
useSimulator(SimulatorZoneNo.REAL, SimulatorDeviceNo.FIRST);
MetaClient.init(MetaClient.EnvTarget.BETA);
await waitStackRuntime(DEC_ID);
}
- 页面组件见 src/www/pages/MessageBoard
在 DEC App 中,前端页面是用户最直接的交互终端。 为了让前端的交互尽可能简单,我们的留言板系统的只有一个前端页面,页面包含一个发布留言的输入区域和一个留言列表的展示区域。
当我们需要查询某个路径下的对象列表时,可以使用 cyfs.GlobalStateAccessStub 实例上的 list 方法进行分页获取。
这里的 list 分页查询方法是通过列举一个 objectmap 的子对象来支持分页查询,查询到的对象列表范围是 [size * index, size * (index + 1)]
。
- 发起请求函数见 src/www/apis/message.ts
查询留言列表主要是调用查询留言列表方法 listMessagesByPage
后展示留言列表。
查询留言列表
需要完成以下 3 个步骤:
- 使用 stack 上的 root_state_access_stub 方法得到 GlobalStateAccessStub 实例
- 调用 GlobalStateAccessStub 实例上的 list 方法分页获取到
messages_list
下的key 值列表
- 遍历
key 值列表
,调用 retrieveMessage 方法发起查询留言请求
,解码后得到最终的留言对象。
源码如下:
export async function retrieveMessage(msgKey: string) {
const stackWraper = checkStack();
// Create a new Message object
const messageObj = Message.create({
key: msgKey,
content: "",
decId: DEC_ID,
owner: stackWraper.checkOwner(),
});
// make a request
const ret = await stackWraper.postObject(messageObj, MessageDecoder, {
reqPath: ROUTER_PATHS.RETRIEVE_MESSAGE,
decId: DEC_ID,
});
if (ret.err) {
console.error(`reponse err, ${ret}`);
return null;
}
// Parse out the MessageObject
const msgRawObj = ret.unwrap();
if (msgRawObj) {
const msgObj: MessageItem = {
key: msgRawObj.key,
name: msgRawObj.desc().owner()!.unwrap().to_base_58(),
time: cyfs.bucky_time_2_js_time(msgRawObj.desc().create_time()),
content: msgRawObj.content,
isSelf: msgRawObj
.desc()
.owner()!
.unwrap()
.equals(checkStack().checkOwner()),
};
return msgObj;
}
return null;
}
export async function listMessagesByPage(pageIndex: number) {
const stack = checkStack();
const selfObjectId = stack.checkOwner();
// Get an instance of cyfs.GlobalStateAccessStub
const access = stack.check().root_state_access_stub(selfObjectId);
// Use the list method to list all objects under messages_list
const lr = await access.list("/messages_list", pageIndex, 10);
if (lr.err) {
if (lr.val.code !== cyfs.BuckyErrorCode.NotFound) {
console.error(`list-subdirs in(/messages_list) io failed, ${lr}`);
} else {
console.warn(`list-subdirs in(/messages_list) not found, ${lr}`);
}
return [];
}
const list = lr.unwrap();
const keyList = list.map((item) => item.map!.key);
console.log("keyList: ", keyList);
const msgList = await Promise.all(
keyList.map(async (item) => {
const msg = await retrieveMessage(item);
return msg;
})
);
const retList = msgList.filter((msg) => msg !== null) as MessageItem[];
retList.sort((a, b) => b.time - a.time);
return retList;
}
- 发起请求函数见 src/www/apis/message.ts
发布留言的页面元素包含一个文本输入框
和一个Leave A Message
按钮,见 src/www/pages/MessageBoard。
前端交互流程是在输入框输入留言的文本内容之后,点击Leave A Message
按钮即发布新留言并刷新留言列表。
发布新留言
需要完成以下 3 个步骤:
- 创建留言对象 Message
- 发起发布留言请求
- 解析响应对象
源码如下:
export async function publishMessage(content: string) {
const stackWraper = checkStack();
// 创建留言对象
const messageObj = Message.create({
content,
decId: DEC_ID,
owner: stackWraper.checkOwner(),
});
// 发起发布留言请求
const ret = await stackWraper.postObject(messageObj, ResponseObjectDecoder, {
reqPath: ROUTER_PATHS.PUBLISH_MESSAGE,
decId: DEC_ID,
});
if (ret.err) {
console.error(`reponse err, ${ret}`);
return null;
}
// 解析 ResponseObject 对象
const r = ret.unwrap();
if (r) {
const retObj = {
err: r.err,
msg: r.msg,
};
console.log(`reponse, ${retObj}`);
return JSON.stringify(retObj);
}
return null;
}
- 发起请求函数见 src/www/apis/message.ts
修改留言的页面元素包含在一个留言消息组件中,在留言消息的右下角有一个 Modify
按钮,点击该按钮后,显示一个 文本输入框
和一个 Submit
按钮,见 src/www/components/MessageItem。
前端交互流程是在输入框输入留言的文本内容之后,点击 Submit
按钮即可改变当前留言的内容并刷新留言列表。
修改留言
需要完成以下 3 个步骤:
- 根据当前要修改的留言对象的 key 值和新的 content 内容,创建留言对象 Message
- 发起更新留言请求
- 解析响应对象
源码如下:
export async function updateMessage(msgKey: string, content: string) {
const stackWraper = checkStack();
// 根据当前留言对象的key值和新的content内容,创建留言对象 Message
const MessageObj = Message.create({
key: msgKey,
content,
decId: DEC_ID,
owner: stackWraper.checkOwner(),
});
// 发起更新留言请求
const ret = await stackWraper.postObject(MessageObj, ResponseObjectDecoder, {
reqPath: ROUTER_PATHS.UPDATE_MESSAGE,
decId: DEC_ID,
});
if (ret.err) {
console.error(`reponse err, ${ret}`);
return null;
}
// 解析 ResponseObject 对象
const r = ret.unwrap();
if (r) {
const retObj = {
err: r.err,
msg: r.msg,
};
console.log(`reponse, ${retObj}`);
return JSON.stringify(retObj);
}
return null;
}
- 发起请求函数见 src/www/apis/message.ts
删除留言的页面元素包含在一个留言消息组件中,在留言消息的右下角有一个 Delete
红色按钮,见 src/www/components/MessageItem。
前端交互流程是点击 Delete
红色按钮后之后,当前留言被删除并刷新留言列表。
删除留言
需要完成以下 3 个步骤:
- 根据当前要删除留言对象的 key 值,创建留言对象 Message
- 发起删除留言请求
- 解析响应对象
源码如下:
export async function deleteMessage(msgKey: string) {
const stackWraper = checkStack();
// 根据当前留言对象的 key 值,创建留言对象 Message
const MessageObj = Message.create({
key: msgKey,
content: "",
decId: DEC_ID,
owner: stackWraper.checkOwner(),
});
// 发起删除留言请求
const ret = await stackWraper.postObject(MessageObj, ResponseObjectDecoder, {
reqPath: ROUTER_PATHS.DELETE_MESSAGE,
decId: DEC_ID,
});
if (ret.err) {
console.error(`reponse err, ${ret}`);
return null;
}
// 解析 ResponseObject 对象
const r = ret.unwrap();
if (r) {
const retObj = {
err: r.err,
msg: r.msg,
};
console.log(`reponse, ${retObj}`);
return JSON.stringify(retObj);
}
return null;
}
在项目根目录,打开终端,输入以下指令:
npm run dev
在 CYFS 浏览器中访问 http://localhost:8088,即可看到前端界面。
通过前面的学习,我们已经为留言板实现了一个前端展示页面。 现在,我们连同前端页面和 dec_service 一起发布到 OOD 上。
打开根目录下的 cyfs.config.json
文件,先修改 version
的版本号,我们对版本号+1。再修改web
配置,修改如下:
{
"version": "1.0.1",
"web": {
"folder": "src/www/dist",
"entry": "index.html"
}
}
我们的前端是使用 React 框架进行开发,使用 webpack 进行打包。 在项目根目录下,打开终端,运行如下指令:
npm run build
指令执行完,可以在项目根目录下看到新增了 dist 和 deploy 文件夹:
- dist: 前端打包产物,由 webpack 生成。
- deploy: 发布到 OOD 的文件夹,包含了项目中所有 ts 文件 编译后的 js 文件,由 tsconfig.json 配置
我们先打开 CYFS 浏览器再运行如下指令:
- mac
npm run mac-deploy-pre
npm run deploy
- windows
npm run deploy
最终,终端会显示上传的信息,上传完成后,终端显示如下信息:
Upload DecApp Finished.
CYFS App Install Link: cyfs://5r4MYfFbqqyqoA4RipKdGEKQ6ZSX3JzNRaEpMPKiKWAQ/9tGpLNnbNtojWgQ3GmU2Y7byFm7uHDr1AH2FJBoGt5YF
恭喜你,这代表我们的 DEC App 已经成功发布到了 OOD。
请把 CYFS App Install Link
对应的链接复制下来,下一节我们将会使用这个链接进行 DEC App 的安装。
- 打开 CYFS 浏览器中 DEC App Store 页面(cyfs://static/DecAppStore/app_store_list.html),点击页面顶部的
已安装
绿色按钮后,你会看到应用名称的末尾,多出来一个更新
小绿标,我们点击这个小绿标,进入应用详情页面
。 - 在
应用详情页面
,我们点击选择更新版本
后可以看到当前 DEC App 的最新版本,点击安装
绿色按钮即可。 - 返回
DEC App Store 页面
(cyfs://static/DecAppStore/app_store_list.html),点击页面顶部的已安装
绿色按钮,可以看到已经安装好的 dec_service。如果显示安装中
,请耐心的等待一会儿。 - 安装完成后,在应用信息栏的右上边,有个绿色的
进入
标志,那就是前端页面的入口
,我们点击这个图标,就可以看到 DEC App 的入口页面了!
通过本章的学习,你已经基本掌握了 DEC App 前端开发的知识。 目前为止,我们的留言板 DEC App 总算是完整了,它已经有了一个看起来还不错的前端页面和一个经过你精心调试后正常运行的 Serice。 我猜你已经迫不及待的想把你的留言板 DEC App 分享给你的好友去体验了!我非常理解你激动的心情,但在此之前,请先学习下一章的内容。