UniApp官网地址:https://uniapp.dcloud.net.cn/
HBuilderX官网地址:https://www.dcloud.io/hbuilderx.html
微信开发者工具下载地址:
https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html
为啥我们要学习uniapp!!!
1、更高的百度指数,跨端完善度更高,真正落地的提高生产力
2、平台能力不受限,在跨端的同时,通过条件编译+平台特有API调用,可以优雅的为某平台写个性化代码,调用专有能力而不影响其他平台。支持原生代码混写和原生sdk集成。
3、性能体验优秀,体验更好的Hybrid框架,加载新页面速度更快。App端支持weex原生渲染,可支撑更流畅的用户体验。小程序端的性能优于市场其他框架。
4、周边生态丰富,丰富的插件市场,各种轮子拿来即用。支持NPM、支持小程序组件和SDK、兼容mpvue组件和项目、兼容weex组件。微信生态的各种sdk可直接用于跨平台App。
5、学习成本低,基于通用的前端技术栈,采用vue语法+微信小程序api,无额外学习成本。
6、开发成本低,不止开发成本,招聘、管理、测试各方面成本都大幅下降。HBuilderX是高效开发神器,熟练掌握后研发效率至少翻倍(即便只开发一个平台)。
引入uni-request封装request.js,我来给你规范一下!!!
npm install uni-request --save // 安装依赖
request.js | 与axios封装api.js类似
import uniRequest from "uni-request";
/* url前缀 */
let base = 'http://localhost:9101';
/* 是否需要登录 */
let needLogin = true;
/* 请求拦截器 */
uniRequest.interceptors.request.use(request=>{
console.log(`请求【${request.url}】${request.needLogin?'需要':'不需要'}登录`);
if(request.needLogin){
if(uni.getStorageSync('userInfo')){
request.headers.Authorization = 'Bearer '+ uni.getStorageSync('userInfo').access_token;
}else{
uni.showToast({
icon:'none',
title:'请重新登录'
})
uni.navigateTo({
url:'/pages/login/login.vue'
})
}
}
return request;
},err=>{
return Promise.reject(err);
});
/* 响应拦截器 */
uniRequest.interceptors.response.use(success=> {
if (success.status && success.status == 200 && success.data.status == 500){
uni.showToast({
icon:'none',
title:success.data.msg
})
return;
}
if (success.status && success.status == 200 && success.data.code == 401){
uni.showToast({
icon:'error',
title:'请重新登录'
})
uni.navigateTo({
url:'/pages/login/login.vue'
})
return;
}
if (success.status && success.status == 200 && success.data.code == 403){
uni.showToast({
icon:'error',
title:'权限不足,请联系管理员!'
})
return;
}
if (success.data.msg){
uni.showToast({
icon:'success',
title:success.data.msg
})
}
// 下载文件资源 type:文件类型
if(success.data.type){
return success;
}
return success.data;
}, err=> {
if (error.response.status == 504){
uni.showToast({
icon:'error',
title:'技术人员正在更新,请稍后...'
})
}else if (error.response.status == 403){
uni.showToast({
icon:'error',
title:'权限不足,请联系管理员!'
})
}else if (error.response.status == 401){
uni.showToast({
icon:'error',
title:'请重新登录'
})
uni.navigateTo({
url:'/pages/login/login.vue'
})
}else if (error.response.status == 500 && error.response.data.code == 504){
uni.showToast({
icon:'error',
title:error.response.data.message
})
}else {
if (error.response.data.msg){
uni.showToast({
icon:'error',
title:error.response.data.msg
})
}else {
uni.showToast({
icon:'error',
title:'未知错误!'
})
}
}
return;
});
/*封装请求方式,这里封装两个post请求,一个专门用来登录使用key-value的方式传参,
因为Spring Security登录默认不支持Json参数*/
export const postKeyValueRequest=(url,params)=>{
return uniRequest({
needLogin: false,
method:'POST',
url:`${base}${url}`,
data:params,
headers:{
'Content-Type':'application/x-www-form-urlencoded'
}
})
}
// Post请求
export const postRequest=(url,params)=>{
return uniRequest({
needLogin: true,
method: 'POST',
url:`${base}${url}`,
data: params,
headers:{
'Content-Type':'application/json'
}
})
}
// Get请求
export const getRequest=(url,params)=>{
return uniRequest({
needLogin: true,
method:'GET',
url:`${base}${url}`,
data:params
})
}
// Put请求
export const putRequest=(url,params)=>{
return uniRequest({
needLogin: true,
method:'PUT',
url:`${base}${url}`,
data:params,
headers:{
'Content-Type':'application/json'
}
})
}
// Delete请求
export const deleteRequest=(url,params)=>{
return uniRequest({
needLogin: true,
method:'DELETE',
url:`${base}${url}`,
data:params
})
}
main.js全局挂载request.js中的请求方法
import {postKeyValueRequest} from "./utils/request.js";
import {postRequest} from "./utils/request.js";
import {getRequest} from "./utils/request.js";
import {putRequest} from "./utils/request.js";
import {deleteRequest} from "./utils/request.js";
Vue.prototype.postKeyValueRequest = postKeyValueRequest;
Vue.prototype.postRequest = postRequest;
Vue.prototype.getRequest = getRequest;
Vue.prototype.putRequest = putRequest;
Vue.prototype.deleteRequest = deleteRequest;
uni-section导航组件在哪啊!!!
uni-section导航组件在官网中没有单独拎出来,但是在官网其他组件介绍中有混合使用该组件,在创建uniapp项目时选择默认模板,会发现怎么没有该组件,那对于第一次接触uniapp开发的小白们会懵逼,其实uni-section导航组件是自定义组件。
我们可以重新新建一个项目,这个时候选择uni-app项目创建,会发现在项目自定义components目录下有uni-section组件,那对于使用默认模板创建的也不必惊慌,我给大家粘贴一下代码,手动创建添加即可,至于组件如何应用就不在这里赘述,和在Vue中的方法一模一样。
<template>
<view class="uni-section">
<view class="uni-section-header" nvue>
<view v-if="type" class="uni-section__head">
<view :class="type" class="uni-section__head-tag"/>
</view>
<view class="uni-section__content">
<text :class="{'distraction':!subTitle}" :style="{color:color}" class="uni-section__content-title">{{ title }}</text>
<text v-if="subTitle" class="uni-section__content-sub">{{ subTitle }}</text>
</view>
</view>
<view :style="{padding: padding ? '10px' : ''}">
<slot/>
</view>
</view>
</template>
<script>
/**
* Section 标题栏
* @description 标题栏
* @property {String} type = [line|circle] 标题装饰类型
* @value line 竖线
* @value circle 圆形
* @property {String} title 主标题
* @property {String} subTitle 副标题
*/
export default {
name: 'UniSection',
emits:['click'],
props: {
type: {
type: String,
default: ''
},
title: {
type: String,
default: ''
},
color:{
type: String,
default: '#333'
},
subTitle: {
type: String,
default: ''
},
padding: {
type: Boolean,
default: false
}
},
data() {
return {}
},
watch: {
title(newVal) {
if (uni.report && newVal !== '') {
uni.report('title', newVal)
}
}
},
methods: {
onClick() {
this.$emit('click')
}
}
}
</script>
<style lang="scss" >
$uni-primary:
.uni-section {
background-color:
// overflow: hidden;
margin-top: 10px;
}
.uni-section-header {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
padding: 12px 10px;
// height: 50px;
font-weight: normal;
}
.uni-section__head {
flex-direction: row;
justify-content: center;
align-items: center;
margin-right: 10px;
}
.line {
height: 12px;
background-color: $uni-primary;
border-radius: 10px;
width: 4px;
}
.circle {
width: 8px;
height: 8px;
border-top-right-radius: 50px;
border-top-left-radius: 50px;
border-bottom-left-radius: 50px;
border-bottom-right-radius: 50px;
background-color: $uni-primary;
}
.uni-section__content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
flex: 1;
color:
}
.uni-section__content-title {
font-size: 14px;
color: $uni-primary;
}
.distraction {
flex-direction: row;
align-items: center;
}
.uni-section__content-sub {
font-size: 12px;
color:
line-height: 16px;
margin-top: 2px;
}
</style>
准备工作就绪,我们来看看登录页面!!!
站在后端的角度,不用在乎界面的美观,我们在乎的是功能!!!
登录页login.vue
<template>
<view>
<view style="width: 90%; margin: 200rpx auto;">
<view style="margin-bottom: 70rpx; font-size: 60rpx; color: royalblue; text-align: center;">程序猿侠</view>
<uni-forms ref="form" :modelValue="loginForm" :rules="rules">
<uni-forms-item name="username">
<uni-easyinput v-model="loginForm.username" prefixIcon="person" placeholder="请输入登录账号"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="password">
<uni-easyinput type="password" v-model="loginForm.password" prefixIcon="locked" placeholder="请输入登录密码"></uni-easyinput>
</uni-forms-item>
</uni-forms>
<view>
<button type="primary" @click="loginBtn">登 录</button>
</view>
</view>
</view>
</template>
<script>
import {Encrypt} from '../../utils/aes.js'
export default {
data() {
return {
loginForm: {
username:'chengqiang',
password:'chengqiang',
grant_type:'password',
client_id:'client-app',
client_secret:'123456'
},
rules: {
username: {
rules:[{required: true,errorMessage: '请输入账号'}],
validateTrigger:'submit',
},
password: {
rules:[{required: true,errorMessage: '请输入密码'}],
validateTrigger:'submit',
}
}
}
},
onShow() {
/* 隐藏底部导航栏 */
uni.hideTabBar();
},
methods: {
/* 登录请求 */
loginBtn() {
this.loginForm.password = Encrypt(this.loginForm.password);
this.$refs.form.validate().then(res=>{
this.postKeyValueRequest('/authorization/oauth/token',this.loginForm).then(resp=>{
if (resp){
/* 存储当前登录用户 */
uni.setStorageSync('userInfo',resp.data);
/* 提示登录成功 */
uni.showToast({
title:'登陆成功'
});
/* 登录成功跳转主页 */
let timer = setTimeout(()=>{
clearTimeout(timer);
uni.switchTab({
url:'/pages/index/index'
});
},1000);
}
})
}).catch(err =>{
})
}
}
}
</script>
<style scoped>
</style>
浏览代码发现对登录密码Aes加密了,引入crypto-js封装aes.js
npm install crypto-js
import CryptoJS from 'crypto-js'
const KEY = CryptoJS.enc.Utf8.parse("1234567890123456");
const IV = CryptoJS.enc.Utf8.parse('1234567890123456');
export function Encrypt(word, keyStr, ivStr) {
let key = KEY
let iv = IV
if (keyStr) {
key = CryptoJS.enc.Utf8.parse(keyStr);
iv = CryptoJS.enc.Utf8.parse(ivStr);
}
let srcs = CryptoJS.enc.Utf8.parse(word);
var encrypted = CryptoJS.AES.encrypt(srcs, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
});
return CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
}
export function Decrypt(word, keyStr, ivStr) {
let key = KEY
let iv = IV
if (keyStr) {
key = CryptoJS.enc.Utf8.parse(keyStr);
iv = CryptoJS.enc.Utf8.parse(ivStr);
}
let base64 = CryptoJS.enc.Base64.parse(word);
let src = CryptoJS.enc.Base64.stringify(base64);
var decrypt = CryptoJS.AES.decrypt(src, key, {
iv: iv,
mode: CryptoJS.mode.CBC,
padding: CryptoJS.pad.ZeroPadding
});
var decryptedStr = decrypt.toString(CryptoJS.enc.Utf8);
return decryptedStr.toString();
}
登录成功,跳转至首页!!!
顶部是轮播图广告位,中间是动态导航栏,底部导航栏
index.vue
<template>
<view>
<view>
<!-- 轮播图 -->
<uni-swiper-dot class="uni-swiper-dot-box" @clickItem=clickItem :info="info" :current="current" :mode="mode"
:dots-styles="dotsStyles" field="content">
<swiper class="swiper-box" @change="changeImage" :current="swiperDotIndex" :autoplay="true" :duration="1000" :interval="5000">
<swiper-item v-for="(item, index) in info" :key="index">
<view class="swiper-item" :class="'swiper-item' + index">
<navigator open-type="navigate" :url="'/pages/webview/webview?url=' + encodeURIComponent('https://www.btks.cn')">
<image style="width: 750rpx" :src="item.url"></image>
</navigator>
</view>
</swiper-item>
</swiper>
</uni-swiper-dot>
</view>
<view>
<!-- 动态导航 -->
<view v-for="(office,pos) in officeList[0].childrenList" :index="pos" :key="pos">
<UniSection :title="office.title" type="line" padding>
<uni-grid :column="4" :highlight="true" :showBorder="false">
<uni-grid-item v-for="(item, index) in office.childrenList" :index="index" :key="index">
<view class="grid-item-box" style="background-color: #fff;" @click="changeOffice(item)">
<uni-icons :type="item.image" :size="30" color="#000" />
<text style="font-size: 30rpx;">{{item.title}}</text>
</view>
</uni-grid-item>
</uni-grid>
</UniSection>
</view>
</view>
</view>
</template>
<script>
import UniSection from '../../components/uni-section/uni-section'
export default {
components:{
UniSection
},
data() {
return {
officeList:
[
{
title:"首页",
childrenList:[
{
title:'综合办公',
childrenList:[
{
path:'/subpages/office/office',
title:'待办任务',
image:'wallet'
},{
path:'/subpages/task/task',
title:'已办任务',
image:'settings-filled'
},{
path:'/subpages/initiate/initiate',
title:'发起流程',
image:'paperplane'
},{
path:'/subpages/relate/relate',
title:'我的流程',
image:'person-filled'
},{
path:'/subpages/assignment/assignment',
title:'待签收任务',
image:'mail-open-filled'
}
]
},
{
title:'新闻展示',
childrenList:[]
}
]
},
{
title:"个人中心",
childrenList:[
{
title:'个人中心',
childrenList:[
{
path:'/subpages/edit/edit',
title:'编辑信息',
image:'wallet'
},{
path:'/subpages/password/password',
title:'修改密码',
image:'settings-filled'
},{
path:'/subpages/userface/userface',
title:'历史头像',
image:'contact-filled'
},{
path:'/subpages/signature/signature',
title:'电子签名',
image:'image'
}
]
},
{
title:'辅助功能',
childrenList:[
{
path:'/subpages/system/system',
title:'系统设置',
image:'gear'
},{
path:'/subpages/location/location',
title:'共享位置',
image:'location-filled'
},{
path:'/subpages/customer/customer',
title:'联系客服',
image:'chatbubble'
}
]
}
]
}
],
info: [{
url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/094a9dc0-50c0-11eb-b680-7980c8a877b8.jpg',
imgName: '内容 A'
},
{
url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/094a9dc0-50c0-11eb-b680-7980c8a877b8.jpg',
imgName: '内容 B'
},
{
url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/094a9dc0-50c0-11eb-b680-7980c8a877b8.jpg',
imgName: '内容 C'
}],
current: 0,
mode: 'indexes',
dotsStyles: {
backgroundColor: 'rgba(83, 200, 249,0.3)',
border: '1px rgba(83, 200, 249,0.3) solid',
color: '#fff',
selectedBackgroundColor: 'rgba(83, 200, 249,0.9)',
selectedBorder: '1px rgba(83, 200, 249,0.9) solid'
},
swiperDotIndex: 0
}
},
onShow() {
},
onLoad() {
uni.setStorageSync("officeList",this.officeList);
},
onPullDownRefresh() {
},
methods: {
/* 轮播图当前下标 */
changeImage(image) {
this.current = image.detail.current;
},
/* 导航跳转 */
changeOffice(item){
uni.navigateTo({
url:item.path
})
}
}
}
</script>
<style lang="scss" scoped>
/* 轮播图布局高度 */
.swiper-box {
height: 400upx;
}
/* 轮播项样式 */
.swiper-item {
/* 注释判断终端样式单独设置 */
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: column;
justify-content: center;
align-items: center;
height: 400upx;
}
/* 动态导航样式 */
.grid-item-box {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 10rpx 0;
}
</style>
个人中心
顶部背景图、头像、昵称,中间是动态导航栏,底部导航栏
personal.vue
<template>
<view>
<!-- 头像、名称 -->
<view class="content-item">
<image src="/static/image/person_bck.png"></image>
<view class="user-info">
<view>
<image src="/static/image/person_face.png"></image>
</view>
<view class="name">
<text>你好,游客</text>
</view>
</view>
</view>
<!-- 动态导航 -->
<view v-for="(office,pos) in officeList[1].childrenList" :index="pos" :key="pos">
<UniSection :title="office.title" type="line" padding>
<uni-grid :column="4" :highlight="true" :showBorder="false">
<uni-grid-item v-for="(item, index) in office.childrenList" :index="index" :key="index">
<view class="grid-item-box" style="background-color: #fff;" @click="changeOffice(item)">
<uni-icons :type="item.image" :size="30" color="#000" />
<text style="font-size: 30rpx;">{{item.title}}</text>
</view>
</uni-grid-item>
</uni-grid>
</UniSection>
</view>
</view>
</template>
<script>
import UniSection from '../../components/uni-section/uni-section'
export default {
components:{
UniSection
},
data() {
return {
officeList:uni.getStorageSync("officeList")
}
},
onLoad() {
},
methods: {
/* 导航跳转 */
changeOffice(item){
uni.navigateTo({
url:item.path
})
}
}
}
</script>
<style lang="scss" scoped>
/* 顶部高度 */
.content-item image{
height: 400rpx;
width: 750rpx;
}
/* 头像、名称 */
.user-info{
position: absolute;
top: 150rpx;
left: 50rpx;
display: flex;
align-items: center;
}
/* 头像 */
.user-info image{
height: 120rpx;
width: 120rpx;
border-radius:60rpx;
}
/* 名称 */
.user-info text{
margin-left: 16rpx;
}
/* 动态导航样式 */
.grid-item-box {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 10rpx 0;
}
</style>
本篇到此结束,赘述了如何从一名小白搭建uniapp脚手架,显而易见,vue转uniapp就是无缝衔接,没有成本可言!!!
灵魂拷问:
1、uniapp中路由和vue路由的区别?
2、webview组件的应用?
3、404页面配置方式?
4、路由分包策略的原理是什么?
文章评论