AVM架構擁有趨近于原生的程式設計體驗,經過一年多的發展,已成為國内主流的多端開發架構之一。本文将梳理一位開發者的投稿,詳細介紹如何使用APICloud的AVM架構開發一款應用。
以開發一款“預約”功能為主的應用為例,本文将從應用結構、應用原型、應用前端開發和應用背景搭建四個方面詳細展開,介紹應用的開發過程。
01
梳理應用結構
首先,需要先梳理“預約”應用的結構和核心功能,通過思維導圖畫出應用的基本結構。

02
畫出應用原型
根據應用結構,進一步細化應用各個頁面的功能展示,使用碼前原型工具畫出“預約”應用的原型。
原型連結可點選此處檢視。
03
應用前端開發
“預約”應用前端采用APICloud AVM多端開發架構進行開發,包括 swiper 輪播圖、網絡請求封裝等多個要點。使用 APICloud 多端技術進行開發,可以實作一套代碼多端運作,支援編譯成 Android & iOS app 以及微信小程式。
1、APICloud使用步驟
(1)下載下傳 APICloud Studio 3 作為開發工具。下載下傳位址:點選此處下載下傳。
(2)新增賬號後在控制台建立app,控制台位址: 點選此處檢視。
檢出後工作目錄
(5)修改或者送出項目源碼,并為目前項目雲編譯自定義 Loader 進行真機同步調試預覽。
使用 AppLoader 進行真機同步調試預覽,背景自動以Loader下載下傳到手機端,安裝後,點選小圓圈,輸入IP位址:192.168.2.152 端口:10916,連接配接後真機同步,可以看到剛建立後的結果。
2、AVM架構的使用
AVM的主要優勢如下:
易用:有 Vue、React 基礎,可快速上手,配套專用的開發工具APICloud Studio3。
多端:一次開發,多端渲染,一個技術棧搞定移動端開發。
功能 API 豐富:提供 1k+ 子產品和 2w+ API 可直接調用,面向行業和場景無限制。
(1)UI
官方文檔:點選此處檢視
很多UI都是自己設計,也可參考開發者流浪男做的AUI架構。字型圖示用的阿裡字型圖示。
APICloud官網組裝了一套vant的元件,可點選此處檢視。
(2)ajax網絡互動
使用下方代碼實作。
// 表單方式送出資料或檔案
api.ajax({
url: 'http://192.168.1.101:3101/upLoad',
method: 'post',
data: {
values: {
name: 'haha'
},
files: {
file: 'fs://a.gif'
}
}
}, function(ret, err) {
if (ret) {
api.alert({ msg: JSON.stringify(ret) });
} else {
api.alert({ msg: JSON.stringify(err) });
}
});
// 送出JSON資料
api.ajax({
url: 'http://192.168.1.101:3101/upLoad',
method: 'post',
headers: {
'Content-Type': 'application/json;charset=utf-8'
},
data: {
body: {
name: 'haha'
}
}
}, function(ret, err) {
if (ret) {
api.alert({ msg: JSON.stringify(ret) });
} else {
api.alert({ msg: JSON.stringify(err) });
}
});
(3)vue指令使用(v-for v-show v-if v-else v-for v-on v-bind v-model等)
1.資料綁定
使用 Mustache 文法:
<text text={{msg}}></text>
使用v-bind指令:
<text v-bind:text="msg"></text>
2.事件綁定
<template>
<text onclick="doThis('avm');">Click me!</text>
</template>
<script>
export default {
name: 'test',
methods: {
doThis(msg){
api.alert({
msg: msg
});
}
}
}
</script>
(4)注冊、登入
1.注冊接口連結:點選此處檢視。
注冊代碼
<template name='tpl'>
<view class="page">
<div class="page1">
<safe-area class="header">
<!-- <text class="header__title">APICloud</text> -->
</safe-area>
<scroll-view class="main">
<view>
<image src="https://baodinglingqian.oss-cn-beijing.aliyuncs.com/1.png" class="touxiang " />
</view>
<div class="zhanghao">
<input placeholder="請輸入賬号" v-model="zhanghao" maxlength="10" autofocus />
</div>
<div class="mima">
<input type="password" placeholder="請輸入密碼" v-model="password" />
</div>
<text class="zhuce" @click="reg()">注冊</text>
<text class="denglu" @click="handleClick()">登入</text>
</scroll-view>
</div>
<image class="originImage" mode="scaleToFill" src={src}></image>
</view>
</template>
<script>
export default {
name: "tpl",
apiready() {
api.setStatusBarStyle({
style: "light",
color: "-"
});
},
data() {
return {
zhanghao: '',
password: '',
src: "https://baodinglingqian.oss-cn-beijing.aliyuncs.com/bg.png"
};
},
computed: {
},
methods: {
handleClick(e) {
api.openWin({
name: 'main',
url: './main.stml',
pageParam: {
name: 'test'
}
});
},
reg() {
var _this = this;
// 送出JSON資料
api.ajax({
url: 'http://yy.deui.cn/api.php/index/re',
method: 'get',
// headers: {
// 'Content-Type': 'application/json;charset=utf-8'
// },
data: {
body: {
username: _this.zhanghao,
password: _this.password
}
}
}, function (ret, err) {
console.log(JSON.stringify(ret))
if (ret.msg == '傳回成功') {
api.toast({
msg: '注冊成功',
location: "middle"
});
} else {
api.alert({ msg: JSON.stringify(err) });
}
});
}
}
};
</script>
<style>
.denglu {
margin-top: 10px;
font-size: 14px;
}
.touxiang {
margin-top: 10%;
width: 150px;
height: 150px;
border-radius: 100px;
margin: 0 auto;
}
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
}
.originImage {
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
z-index: 1;
}
.page {
/* position:fixed; */
position: relative;
z-index: 9;
width: 100%;
height: 100%;
}
.page1 {
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
z-index: 2;
}
input {
padding-left: 10px;
line-height: 35px;
height: 35px;
border-radius: 5px;
}
.zhanghao {
display: block;
margin: 0 auto;
margin-top: 15%;
margin-bottom: 15px;
height: 60px;
}
.mima {
display: block;
margin: 0 auto;
margin-top: 25px;
}
.zhuce {
width: 100%;
height: 35px;
line-height: 35px;
background-color: coral;
text-align: center;
color: #fff;
margin-top: 25px;
}
.page {
height: 100%;
background-color: white;
}
.header {
background: #81a9c3;
justify-content: center;
align-items: center;
}
.header__title {
color: #fff;
font-size: 18px;
font-weight: bold;
height: 50px;
line-height: 50px;
}
.main {
flex: 1;
padding: 15px;
}
.h1 {
font-size: 24px;
}
.item {
flex-direction: row;
padding: 10px 0;
}
.item__text {
color: #333;
white-space: nowrap;
}
.item__value {
margin-left: 5px;
}
.footer {
background: #81a9c3;
flex-direction: row;
justify-content: center;
align-items: center;
}
.footer__text {
color: #fff;
font-size: 14px;
height: 30px;
line-height: 30px;
}
</style>
2.登入接口連結:點選此處檢視。
登入代碼
<template name='tpl'>
<view class="page">
<div class="page1">
<safe-area class="header">
<!-- <text class="header__title">APICloud</text> -->
</safe-area>
<scroll-view class="main">
<view>
<image src="https://baodinglingqian.oss-cn-beijing.aliyuncs.com/1.png" class="touxiang " />
</view>
<div class="zhanghao">
<input placeholder="請輸入賬号" v-model="zhanghao" maxlength="10" autofocus />
</div>
<div class="mima">
<input type="password" placeholder="請輸入mima" v-model="password" />
</div>
<text class="zhuce" @click="login()">登入</text>
<text class="denglu" @click="handleClick()">注冊</text>
</scroll-view>
</div>
<image class="originImage" mode="scaleToFill" src={src}></image>
</view>
</template>
<script>
export default {
name: "tpl",
apiready() {
api.setStatusBarStyle({
style: "light",
color: "-"
});
var value = localStorage.getItem('uid');
api.openWin({
name: 'home',
url: './home.stml'
});
},
data() {
return {
zhanghao: '',
password: '',
src: "https://baodinglingqian.oss-cn-beijing.aliyuncs.com/bg.png"
};
},
computed: {
},
methods: {
handleClick(e) {
api.openWin({
name: 'region',
url: './region.stml',
pageParam: {
name: 'test'
}
});
// api.toast({
// msg: this.data.text,
// location: "middle"
// });
},
login() {
var _this = this;
api.ajax({
url: 'http://yy.deui.cn/api.php/index/login',
method: 'get',
// headers: {
// 'Content-Type': 'application/json;charset=utf-8'
// },
data: {
body: {
username: _this.zhanghao,
password: _this.password
}
}
}, function (ret, err) {
console.log(JSON.stringify(ret))
localStorage.setItem('uid', ret.data.data[0]['id']);
if (ret.msg == '傳回成功') {
api.toast({
msg: '登入成功',
location: "middle"
});
api.openWin({
name: 'home',
url: './home.stml'
});
} else {
api.alert({ msg: JSON.stringify(err) });
}
});
}
}
};
</script>
<style>
.denglu {
margin-top: 10px;
font-size: 14px;
}
.touxiang {
margin-top: 10%;
width: 150px;
height: 150px;
border-radius: 100px;
margin: 0 auto;
}
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
}
.originImage {
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
z-index: 1;
}
.page {
/* position:fixed; */
position: relative;
z-index: 9;
width: 100%;
height: 100%;
}
.page1 {
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
z-index: 2;
}
input {
padding-left: 10px;
line-height: 35px;
height: 35px;
border-radius: 5px;
}
.zhanghao {
display: block;
margin: 0 auto;
margin-top: 15%;
margin-bottom: 15px;
height: 60px;
}
.mima {
display: block;
margin: 0 auto;
margin-top: 25px;
}
.zhuce {
width: 100%;
height: 35px;
line-height: 35px;
background-color: coral;
text-align: center;
color: #fff;
margin-top: 25px;
}
.page {
height: 100%;
background-color: white;
}
.header {
background: #81a9c3;
justify-content: center;
align-items: center;
}
.header__title {
color: #fff;
font-size: 18px;
font-weight: bold;
height: 50px;
line-height: 50px;
}
.main {
flex: 1;
padding: 15px;
}
.h1 {
font-size: 24px;
}
.item {
flex-direction: row;
padding: 10px 0;
}
.item__text {
color: #333;
white-space: nowrap;
}
.item__value {
margin-left: 5px;
}
.footer {
background: #81a9c3;
flex-direction: row;
justify-content: center;
align-items: center;
}
.footer__text {
color: #fff;
font-size: 14px;
height: 30px;
line-height: 30px;
}
</style>
(5)首頁輪播圖
輪播圖連結:點選此處檢視。
<template name='tpl'>
<view class="page">
<div class="page1">
<safe-area class="header">
<text class="header__title">首頁</text>
</safe-area>
<scroll-view class="main" scroll-y>
<swiper class="swiper" id="customSwiper" autoplay circular indicator-dots indicator-color="#ddd"
indicator-active-color="#f0f">
<swiper-item class="swiper-item" v-for="(_item,_index) in bannerlist">
<!-- <text class="desc">{_item.image}</text> -->
<image mode="scaleToFill" src={_item.image}></image>
</swiper-item>
</swiper>
<div>
<a-cell-group>
<a-cell v-bind:title="_item.name" v-bind:value="_item.content" v-bind:label="_item.address"
arrow-direction="right" @click="godetial(_item)" v-for="(_item,_index) in shangjialist" />
<!-- <a-cell title="單元格" value="内容" label="描述資訊" /> -->
</a-cell-group>
</div>
</scroll-view>
</div>
<image class="originImage" mode="scaleToFill" src={src}></image>
</view>
</template>
<script>
import ACellGroup from "../../components/act/a-cell-group";
import ACell from "../../components/act/a-cell";
export default {
name: 'test',
data() {
return {
shangjialist: [],
bannerlist: [],
current: 0,
src: "https://baodinglingqian.oss-cn-beijing.aliyuncs.com/bg.png"
}
},
methods: {
apiready() {
this.banner()
this.allstores()
// var customSwiper = document.getElementById('customSwiper');
// customSwiper.load({
// data: this.data.dataList
// });
},
onchange(e) {
this.data.current = e.detail.current;
},
godetial(item) {
console.log(JSON.stringify(item))
api.openWin({
name: 'detial',
url: './detial.stml',
pageParam: {
id: item.id
}
});
},
banner() {
var _this = this;
api.ajax({
url: 'http://yy.deui.cn/api.php/index/banner',
method: 'get',
}, function (ret, err) {
if (ret.msg == '傳回成功') {
_this.data.bannerlist = ret.data.data
// var customSwiper = document.getElementById('customSwiper');
// customSwiper.load({
// data: _this.data.bannerlist
// });
// console.log(JSON.stringify(_this.bannerlist))
} else {
api.alert({ msg: JSON.stringify(err) });
}
});
},
allstores() {
var _this = this;
api.ajax({
url: 'http://yy.deui.cn/api.php/index/stores',
method: 'get',
}, function (ret, err) {
if (ret.msg == '傳回成功') {
console.log(1)
console.log(JSON.stringify(ret.data.data))
console.log(1)
var obj = ret.data.data
for (let index = 0; index < obj.length; index++) {
const element = obj[index];
element['content'] = element['content'].substring(0, 9) + '...'
}
console.log(JSON.stringify(obj))
_this.data.shangjialist = obj
} else {
api.alert({ msg: JSON.stringify(err) });
}
});
}
}
}
</script>
<style>
.denglu {
margin-top: 10px;
font-size: 14px;
}
.touxiang {
margin-top: 10%;
width: 150px;
height: 150px;
border-radius: 100px;
margin: 0 auto;
}
html {
width: 100%;
height: 100%;
}
body {
width: 100%;
height: 100%;
}
.originImage {
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
z-index: 1;
}
.page {
/* position:fixed; */
position: relative;
z-index: 9;
width: 100%;
height: 100%;
}
.page1 {
position: absolute;
top: 0px;
left: 0px;
right: 0px;
bottom: 0px;
z-index: 2;
}
input {
padding-left: 10px;
line-height: 35px;
height: 35px;
border-radius: 5px;
}
.zhanghao {
display: block;
margin: 0 auto;
margin-top: 15%;
margin-bottom: 15px;
height: 60px;
}
.mima {
display: block;
margin: 0 auto;
margin-top: 25px;
}
.zhuce {
width: 100%;
height: 35px;
line-height: 35px;
background-color: coral;
text-align: center;
color: #fff;
margin-top: 25px;
}
.page {
height: 100%;
background-color: white;
}
.header {
background: #81a9c3;
justify-content: center;
align-items: center;
}
.header__title {
color: #fff;
font-size: 18px;
font-weight: bold;
height: 50px;
line-height: 50px;
}
.main {
flex: 1;
padding: 0px;
}
.h1 {
font-size: 24px;
}
.item {
flex-direction: row;
padding: 10px 0;
}
.item__text {
color: #333;
white-space: nowrap;
}
.item__value {
margin-left: 5px;
}
.footer {
background: #81a9c3;
flex-direction: row;
justify-content: center;
align-items: center;
}
.footer__text {
color: #fff;
font-size: 14px;
height: 30px;
line-height: 30px;
}
.main {
width: 100%;
height: 100%;
}
.swiper {
width: 100%;
height: 190px;
/* background-color: blue; */
}
.swiper-item {
justify-content: center;
}
.title {
padding: 10px 0;
font-size: 20px;
}
.desc {
width: 100%;
text-align: center;
}
.container {
width: 100%;
height: 200px;
}
.indicator {
flex-direction: row;
justify-content: center;
position: absolute;
width: 100%;
height: 20px;
bottom: 8px;
}
.indicator-item {
width: 15px;
height: 8px;
margin: 3px;
}
.indicator-item-normal {
background-color: #ddd;
}
.indicator-item-active {
background-color: red;
}
</style>
(6)首頁清單
所有店鋪連結:點選此處檢視。
<a-cell-group>
<a-cell v-bind:title="_item.name" v-bind:value="_item.content" v-bind:label="_item.address" arrow-direction="right" @click="godetial(_item)" v-for="(_item,_index) in shangjialist" />
</a-cell-group>
shangjialist: [],
allstores() {
var _this = this;
api.ajax({
url: 'http://yy.deui.cn/api.php/index/stores',
method: 'get',
}, function (ret, err) {
if (ret.msg == '傳回成功') {
console.log(1)
console.log(JSON.stringify(ret.data.data))
console.log(1)
var obj = ret.data.data
for (let index = 0; index < obj.length; index++) {
const element = obj[index];
element['content'] = element['content'].substring(0, 9) + '...'
}
console.log(JSON.stringify(obj))
_this.data.shangjialist = obj
} else {
api.alert({ msg: JSON.stringify(err) });
}
});
}
(7)頁面傳參擷取詳情
api.openWin({
name: 'detial',
url: './detial.stml',
pageParam: {
id: 123
}
});
apiready() {//like created
if (api.pageParam.id) {
this.data.id = api.pageParam.id
}
console.log(this.data.id)
}
店鋪詳情:點選此處檢視。
<template>
<view class="page">
<view>
<a-nav-bar v-bind:title="title" left-text="傳回" left-arrow @click-left="onClickLeft"
@click-right="onClickRight" />
</view>
<view>
<a-cell-group>
<a-cell title="名稱" v-bind:value="store.name" />
<a-cell title="電話" v-bind:value="store.phone" />
<a-cell title="城市" v-bind:value="store.city" />
<a-cell title="位址" v-bind:value="store.address" />
<a-cell title="内容" v-bind:value="store.content" />
</a-cell-group>
<!-- <text text={{store.name}}></text>
<text text={{store.city}}></text>
<text text={{store.content}}></text>
<text text={{store.address}}></text>
<text text={{store.phone}}></text> -->
</view>
<view>
<a-button type="success">産品</a-button>
</view>
<view>
<a-cell-group>
<a-cell v-bind:title="_item.name" v-bind:value="_item.content" label="點選可預約" v-for="(_item,_index) in product" @click="yuyuproduct(_item)" />
</a-cell-group>
</view>
</view>
</template>
<script>
import ACellGroup from "../../components/act/a-cell-group";
import ACell from "../../components/act/a-cell";
import ANavBar from "../../components/act/a-nav-bar";
import AButton from "../../components/act/a-button";
export default {
name: 'detial',
apiready() {//like created
if (api.pageParam.id) {
this.data.id = api.pageParam.id
}
console.log(this.data.id)
this.init()
},
data() {
return {
title: "詳情",
id: 1,
store: {},
product:[]
}
},
methods: {
init() {
var _this = this;
api.ajax({
url: 'http://yy.deui.cn/api.php/index/searchstore',
method: 'get',
data: {
body: {
id: _this.data.id
}
}
}, function (ret, err) {
if (ret.msg == '傳回成功') {
console.log(1)
console.log(JSON.stringify(ret.data.product))
console.log(1)
_this.data.store = ret.data.data[0]
_this.data.product = ret.data.product
} else {
api.alert({ msg: JSON.stringify(err) });
}
});
},
yuyuproduct(_item){
console.log(JSON.stringify(_item))
},
onClickLeft() {
api.closeWin();
},
onClickRight() {
}
}
}
</script>
<style>
.page {
height: 100%;
}
</style>
(8)導航欄元件
import ANavBar from "../../components/act/a-nav-bar";
<a-nav-bar v-bind:title="title" left-text="傳回" left-arrow @click-left="onClickLeft"
@click-right="onClickRight" />
注意:導航欄元件的使用,文檔中的引用
import ACell from"../../components/act/a-nav-bar.stml";
使用中建議去掉stml字尾,import ACell from"../../components/act/a-nav-bar";
(9)localStorage 對象使用
main.stml裡面的這個,就是用的這個對象
localStorage.setItem('uid', ret.data.data[0]['id']);
下面是localStorage的用法
// 設定存儲.
sessionStorage.setItem('key', 'value');
// 擷取存儲.
var value = sessionStorage.getItem('key');
// 移除存儲
sessionStorage.removeItem('key');
// 清除所有存儲項
sessionStorage.clear();
// 擷取已有存儲項數
var length = sessionStorage.length;
// 根據存儲項索引擷取存儲鍵名
var keyName = sessionStorage.key(index);
(10)APICloud元件、子產品的使用
"子產品"中添加子產品,如果是H5的需要下載下傳後,放到自己的代碼中;如果是原生的子產品,需要添加到自己應用中,去require去使用。網上有專門介紹這塊的教程,不清楚的可以去搜搜。
04
應用背景搭建
“預約”應用的背景是用tp5架構寫的php背景。
tp5下的fastadmin架構,可以根據fastadmin一鍵生成簡單背景,資料庫檔案為:
背景接口代碼
<?php
namespace app\api\controller;
use app\common\controller\Api;
use think\Db;
/**
* 首頁接口
*/
class Index extends Api
{
protected $noNeedLogin = ['*'];
protected $noNeedRight = ['*'];
/**
* 首頁
*
*/
public function index()
{
$this->success('請求成功');
}
//注冊
public function re(){
$username = $this->request->request("username");
$password = md5($this->request->request("password"));
$sql = " INSERT INTO `yuyuuser` (`id`, `group_id`, `username`, `nickname`, `password`, `salt`, `email`, `mobile`, `avatar`, `level`, `gender`, `birthday`, `bio`, `money`, `score`, `successions`, `maxsuccessions`, `prevtime`, `logintime`, `loginip`, `loginfailure`, `joinip`, `jointime`, `createtime`, `updatetime`, `token`, `status`, `verification`) VALUES (NULL, '0', '', '".$username ."', '".$password."', '', '', '', '', '0', '0', NULL, '', '0.00', '0', '1', '1', NULL, NULL, '', '0', '', NULL, NULL, NULL, '', '', '')";
$rst = Db::query($sql);
$data =1;
$this->success('傳回成功', ['data' => $data]);
}
//登入
public function login(){
$username = $this->request->request("username");
$password = md5($this->request->request("password"));
$sql = "SELECT nickname,id FROM yuyuuser where nickname='".$username."' and password='".$password."' order by id DESC LIMIT 1";
$rst = Db::query($sql);
$this->success('傳回成功', ['data' => $rst]);
}
//擷取輪播圖
public function banner()
{
$sql = "SELECT * FROM `yuyubanner`";
$rst = Db::query($sql);
$this->success('傳回成功', ['data' => $rst]);
}
//擷取網絡協定
public function xieyi()
{
$sql = "SELECT * FROM `yuyuxieyi`";
$rst = Db::query($sql);
$this->success('傳回成功', ['data' => $rst]);
}
//所有類型
public function leixing()
{
$sql = " SELECT * FROM `yuyutype`";
$rst = Db::query($sql);
$this->success('傳回成功', ['data' => $rst]);
}
//所有店面
public function stores()
{
$sql = " SELECT * FROM `yuyustore`";
$rst = Db::query($sql);
$this->success('傳回成功', ['data' => $rst]);
}
//店鋪資訊
public function searchstore()
{
$id = $this->request->request("id");
$sql = " SELECT * FROM `yuyustore` where id = ".$id;
$sql1 = "SELECT * FROM `yuyuproduct` where store_id = ".$id;
$rst = Db::query($sql);
$rst1 = Db::query($sql1);
$this->success('傳回成功', ['data' => $rst,'product'=>$rst1]);
}
}
使用AVM架構開發應用可以極大縮短開發時間,提升開發效率。感興趣的小夥伴可以來學習嘗試下~