UniApp 项目脚手架搭建

2022年6月15日 256点热度 0人点赞 0条评论

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: #2979ff !default; .uni-section { background-color: #fff; // 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: #333; }
.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: #999; 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、路由分包策略的原理是什么?

~完结撒花~

34360UniApp 项目脚手架搭建

这个人很懒,什么都没留下

文章评论