知識
不管是網(wǎng)站,軟件還是小程序,都要直接或間接能為您產(chǎn)生價值,我們在追求其視覺表現(xiàn)的同時,更側(cè)重于功能的便捷,營銷的便利,運營的高效,讓網(wǎng)站成為營銷工具,讓軟件能切實提升企業(yè)內(nèi)部管理水平和效率。優(yōu)秀的程序為后期升級提供便捷的支持!
您當(dāng)前位置>首頁 » 新聞資訊 » 小程序相關(guān) >
技術(shù)提煉|盤點那些Vue項目中的優(yōu)秀實踐-小程序篇
發(fā)表時間:2021-1-5
發(fā)布人:葵宇科技
瀏覽次數(shù):90
這一篇我們來聊聊小程序,這里的小程序開發(fā)使用的是uniapp。
cross-env切換環(huán)境在pc端的vue項目中,我們通常會使用vue的環(huán)境變量來控制程序在不同環(huán)境的切換,例如:
目錄結(jié)構(gòu):
├── .env.development
├── .env.test
├── vue.config.js
復(fù)制代碼
vue.config.js:
module.exports = {
devServer: {
port: 8062,
proxy: {
'/api': {
target: process.env.BASEURL,
pathRewrite: {
'^/api': '/api'
}
}
}
}
}
復(fù)制代碼
.env.development:
BASEURL=http://developapi.com/
復(fù)制代碼
.env.test:
BASEURL=http://testapi.com/
復(fù)制代碼
這樣我們只需要通過package.json
里不同環(huán)境的script運行,就可以切換到想要的環(huán)境。在小程序里,我們通常都是在微信開發(fā)工具里編譯運行,這個時候我們就可以借助cross-env
和script
來進行環(huán)境的切換。
首先,我們需要先安裝cross-env
npm install --save-dev cross-env
復(fù)制代碼
然后我們改寫package.json
的script,讓我們在運行script的時候額外用node執(zhí)行一個js文件
目錄結(jié)構(gòu):
├── script
│ └── build.js # 用來修改baseurl的腳本文件
├── utils
│ ├── http.js # 對axios的再次封裝,引入config中的url
│ └── config.js # 指定不同環(huán)境的baseurl
├── package.json
├── manifest.json
復(fù)制代碼
package.json:
"scripts": {
"dev": "cross-env NODE_ENV=development node ./script/build.js",
"test": "cross-env NODE_ENV=test node ./script/build.js",
"pro": "cross-env NODE_ENV=production node ./script/build.js"
}
復(fù)制代碼
build.js:
const fs = require('fs')
const path = require('path')
const manifest = require("../manifest.json")
const config = require("../utils/config.json")
switch (process.env.NODE_ENV) {
case 'development':
manifest["mp-weixin"].appid = 'somewxid1'
config.DEV = 'https://devapi1.com/miniapp/'
config.PRO = 'https://proapi1.com/miniapp/'
break;
case 'test':
manifest["mp-weixin"].appid = 'somewxid2'
config.DEV = 'https://devapi2.com/miniapp/'
config.PRO = 'https://proapi2.com/miniapp/'
break;
case 'production':
manifest["mp-weixin"].appid = 'somewxid3'
config.DEV = 'https://devapi3.com/miniapp/'
config.PRO = 'https://proapi3.com/miniapp/'
break;
}
try {
fs.writeFileSync(path.resolve(__dirname, '../manifest.json'), JSON.stringify(manifest, null, 4))
fs.writeFileSync(path.resolve(__dirname, '../utils/config.json'), JSON.stringify(config, null, 4))
} catch (error) {
console.error(error)
}
console.log('修改成功')
復(fù)制代碼
config.json:
{
"DEV": "https://devapi1.com/miniapp/",
"PRO": "https://proapi1.com/miniapp/"
}
復(fù)制代碼
移動端開發(fā)中的css在移動端開發(fā)中,我們通常會有一份設(shè)計規(guī)范,這份規(guī)范通常會包含一下內(nèi)容:
- 項目中所使用的字體大小樣式及其對應(yīng)應(yīng)用場景
- 項目中使用到的顏色及其對應(yīng)場景
- 項目中一些通用組件的樣式
對于這個規(guī)范,我們的最佳實踐方式是用一個css預(yù)編譯器文件把這些樣式寫成常量,在頁面中直接取用,例如:
// mixins
.ellipsis(@line: 2) {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: @line;
overflow: hidden;
}
// Color Palette
@primary: #E20000;
@black: #000;
@white: #fff;
@gray-1: #f7f8fa;
@gray-2: #f2f3f5;
@gray-3: #ebedf0;
@gray-4: #dcdee0;
@gray-5: #c8c9cc;
@gray-6: #969799;
@gray-7: #646566;
@gray-8: #323233;
@red: #ee0a24;
@blue: #1989fa;
@orange: #ff976a;
@orange-dark: #ed6a0c;
@orange-light: #fffbe8;
@green: #07c160;
// Gradient Colors
@gradient-red: linear-gradient(to right, #ff6034, #ee0a24);
@gradient-orange: linear-gradient(to right, #ffd01e, #ff8917);
// Component Colors
@text-color: @gray-8;
@active-color: @gray-2;
@active-opacity: .7;
@disabled-opacity: .5;
@background-color: @gray-1;
@background-color-light: #fafafa;
// Padding
@padding-base: 4px;
@padding-xs: @padding-base * 2;
@padding-sm: @padding-base * 3;
@padding-md: @padding-base * 4;
@padding-lg: @padding-base * 6;
@padding-xl: @padding-base * 8;
// Font
@font-size-xs: 10px;
@font-size-sm: 12px;
@font-size-md: 14px;
@font-size-lg: 16px;
@font-weight-bold: 500;
@price-integer-font-family: Avenir-Heavy, PingFang SC, Helvetica Neue, Arial, sans-serif;
// Animation
@animation-duration-base: .3s;
@animation-duration-fast: .2s;
// Border
@border-color: @gray-3;
@border-width-base: 1px;
@border-radius-sm: 2px;
@border-radius-md: 4px;
@border-radius-lg: 8px;
@border-radius-max: 999px;
復(fù)制代碼
適配不同手機的css顯示,無疑是一項繞不開的課題,這里主要想說說如何用好相對布局。
px,em,rem
px:
- 解釋:px像素(Pixel)。相對長度單位。像素px是相對于顯示器屏幕分辨率而言的。
em:
- 解釋:em是相對長度單位。相對于當(dāng)前對象內(nèi)文本的字體尺寸。如當(dāng)前對行內(nèi)文本的字體尺寸未被人為設(shè)置,則相對于瀏覽器的默認(rèn)字體尺寸。
- 缺點:em會繼承父級元素的字體大?。▍⒖嘉锸歉冈氐膄ont-size)所以會逐級嵌套計算。
rem:
- 解釋:rem是css3中提出來基于em的優(yōu)化,rem依舊是相對長度單位,但它相對的是根元素。
- 優(yōu)點:只修改根元素就成比例地調(diào)整所有字體大小,避免字體大小逐層復(fù)合的連鎖反應(yīng)。
合理的使用px,em,rem可以幫助我們更好的控制大小。在各大小程序里也有諸如rpx
這樣小程序提供的相對長度單位可以使用。
百分比和flex
在實現(xiàn)相對布局上,早時候我們最常使用百分比結(jié)合行內(nèi)元素的方法,但這類方法的缺點也十分明顯,當(dāng)我們行內(nèi)的元素變多,我們需要手動重新計算百分比,動態(tài)的增減元素也需要重新計算,所以flex就變得更加受追捧。
有關(guān)flex的教程,推薦大家看一看阮大神的這篇博客。
大多數(shù)小程序都有分享進行推廣的業(yè)務(wù)場景,雖然小程序自帶分享卡片的功能,但因為它的不夠直觀和相對死板,實際開發(fā)中我們更多會使用生成海報分享。對于一些電商小程序,生成的海報還會附帶一些額外的功能,例如用戶分銷上下級綁定,這里我們就來簡單介紹一個分享海報的實現(xiàn)。(因為這里我們只專注實現(xiàn)邏輯,所以css的部分就不做展示了)
目錄結(jié)構(gòu):
├── components # 全局公共組件
│ ├── share-popup
│ │ └── share-popup.vue # 選擇微信分享或海報分享上拉菜單
│ └── poster-item
│ └── poster-item.vue # 生成海報組件
├── utils
│ └── message.js # 指定分享參數(shù)
復(fù)制代碼
首先我們先來看看share-popup:
<template>
<uni-popup ref="popup">
<div class="icon-content">
<div class="item">
<img class="icon" src="http://www.wxapp-union.com/~@/static/poster.png" @click="clickPoster"/>
<span>生成海報</span>
</div>
<div class="item" @click="close">
<button
class="share-btn"
open-type="share"
:data-title="shareInfo.name"
:data-imgurl="shareInfo.image"
:data-path="shareInfo.path">
<img class="icon" src="http://www.wxapp-union.com/~@/static/wechat_icon.png" />
</button>
<span>分享到微信</span>
</div>
</div>
</uni-popup>
</template>
<script>
export default {
name: 'share-popup',
props: {
shareInfo: {
type: Object,
defalut () {
return {
title: '',
path: '',
image: ''
}
}
}
},
data () {
return {
}
},
computed: {
info () {
return this.$store.state.user.userInfo
}
},
methods: {
clickPoster () {
this.$emit('sharePoster')
this.close()
},
open() {
this.$refs.popup.open()
},
close() {
this.$refs.popup.close()
}
}
}
</script>
復(fù)制代碼
分享到微信其實就是用了微信button
的開放接口,這里的關(guān)鍵在于我們調(diào)用組件時傳入的參數(shù)。
接下來我們看下poster-item,在這個組件里,我們將會在海報中展示這些信息:
- 用戶昵稱
- 用戶頭像
- 設(shè)計好的海報背景圖
- 小程序分享二維碼
- 海報中需要展示的分享詳情(這里是商品價格、劃線價、名稱)
<template>
<uni-popup ref="popup" :maskClick="false" :animation="false">
<div class="flex column">
<div class="btn-close-wrapper">
<div class="btn-close" @click="close"></div>
</div>
<canvas class="canvas-code" canvas-id="canvas" style="width: 300px;height: 452px;">
</canvas>
<div class="btn-save" @click.stop="save">保存到相冊</div>
</div>
</uni-popup>
</template>
<script>
const promisify = (fn) => {
return function(args = {}) {
return new Promise((resolve, reject) => {
fn.call(null, {
...args,
success (data) {
console.log('data', data)
resolve(data)
},
fail (err) {
console.log('err', err)
reject(err)
}
})
})
}
}
const downloadFile = promisify(uni.downloadFile)
export default {
name: 'poster-item',
props: {
item: {
type: Object,
defalut () {
return {
realPrice: '0.00',
price: '0.00',
name: '',
image: ''
}
}
},
info: {
page: '',
scene: ''
}
},
data () {
return {
imgSrc: '',
ctx: {}
}
},
computed: {
username () {
return this.$store.state.user.userInfo.nickname
},
avatar () {
const avatar = this.$store.state.user.userInfo.avatar
if (/^http/.test(avatar)) {
return avatar
}
return false
}
},
methods: {
draw ({ realPrice, price, name, username, itemImage, avatar, qrcode }) {
const ctx = this.ctx
ctx.drawImage(itemImage,0, 0, 300, 300);
ctx.setFillStyle('#F0F0F0')
ctx.fillRect(0, 300, 300, 152)
ctx.drawImage(require("@/static/bg.png"), 0, 322, 202, 131);
ctx.font = 'bold 14px "HelveticaNeue-Bold,HelveticaNeue"'
ctx.setFillStyle('#D0021B')
ctx.fillText('¥', 24, 334)
ctx.setFontSize(20)
// realPrice
ctx.fillText(realPrice, 38, 334)
if (realPrice < price) {
const w1 = ctx.measureText(realPrice).width
ctx.font = 'normal 12px "HelveticaNeue"'
ctx.setFillStyle('#4A4A4A')
ctx.fillText(`¥${price}`, 40 + w1, 334)
const w2 = ctx.measureText(`¥${price}`).width
ctx.beginPath()
ctx.moveTo(42 + w1, 330)
ctx.lineTo(42 + w1 + w2, 330)
ctx.stroke()
ctx.closePath()
}
ctx.font = '12px PingFangSC-Regular,PingFang SC'
ctx.setFillStyle('#4A4A4A')
ctx.fillText(name.substring(0, 15), 24, 356, 152)
ctx.fillText(name.substring(15, 30), 24, 372, 152)
ctx.drawImage(qrcode, 205, 322, 67, 67);
ctx.setFillStyle('#4A4A4A')
ctx.fillText('長按識別', 216, 416, 67)
ctx.save()
const self = this
ctx.draw(true, setTimeout((e) => {
uni.canvasToTempFilePath({
canvasId: 'canvas',
success ({ tempFilePath }) {
self.imgSrc = http://www.wxapp-union.com/tempFilePath
}
}, self)
}, 100))
},
open () {
const { realPrice, price, name, image } = this.item
this.$refs.popup.open()
uni.showLoading()
Promise.all([
downloadFile({ url: image }), // 商品圖
downloadFile({ url: this.avatar || image }), // 用戶頭像
// 從后臺獲取二維碼
downloadFile({ url: `someapi/getWxacode?page=${this.info.page}&scene=${encodeURIComponent(this.info.scene)}`, header: {'Authorization': `Bearer ${uni.getStorageSync('token')}` } })
]).then(([
{ tempFilePath: itemImage },
{ tempFilePath: avatar },
{ tempFilePath: qrcode }
]) => {
uni.hideLoading()
this.draw({ realPrice, price, name, username: this.username, itemImage, avatar, qrcode })
})
},
close () {
this.$refs.popup.close()
this.ctx.clearRect(0, 0, 300, 452)
},
save () {
const self = this
uni.showLoading()
uni.saveImageToPhotosAlbum({
filePath: this.imgSrc,
success () {
uni.showToast({ title: '保存成功' })
self.close()
},
complete (res) {
uni.hideLoading()
console.log('complete', res)
}
})
}
},
mounted () {
this.ctx = uni.createCanvasContext('canvas', this)
}
}
</script>
復(fù)制代碼
這里我們主要是利用了canvas進行海報的繪制。二維碼實現(xiàn)用戶分銷綁定的原理是二維碼包含的跳轉(zhuǎn)鏈接里有參數(shù),在訪問這些頁面的時候,我們可以提前獲取這些參數(shù),完成綁定邏輯。
在完成了這兩個組件后,我們會在頁面中這樣使用:
<template>
<div>
<!-- some other content -->
<button @click="showShare">點擊分享</button>
<share-popup ref="share" @sharePoster="showPoster">
</share-popup>
<poster-item
ref="poster"
:item="{
realPrice: product.realPrice,
price: product.price,
name: product.title,
image: product.icons[0]
}"
:info="posterInfo">
</poster-item>
</div>
</template>
<script>
export default {
data () {
return {
product: {}
}
},
computed: {
query () {
return {
id: this.productId,
memberId: this.$store.state.user.userInfo.id
}
},
posterInfo () {
return this.$message.makePoster(this.query)
}
},
async onLoad(query){
// query中包含我們二維碼里的參數(shù)
// 可以利用query里的值完成綁定
},
onShareAppMessage() {
return {
title: this.product.title,
imageUrl: this.product.icons[0],
path: this.$message.makeShare(this.query)
}
},
methods: {
showPoster () {
this.$refs.poster.open()
},
showShare () {
this.$refs.share.open()
}
}
}
</script>
復(fù)制代碼
在頁面使用的時候,我們其實做了兩件事:
- 頁面加載的時候獲取二維碼里攜帶的信息
- 將需要的信息傳遞給
poster-item
組件
這里對信息的處理,我們用了$message
方法做了個過濾,下面看看這個方法:
const _dealPath = (path) => {
if (path) {
return path
}
const pages = getCurrentPages()
console.log('_dealPath', pages[pages.length - 1].route)
return pages[pages.length - 1].route
}
const message = {
array: [],
register ({ page, keys }) {
this.array.push({ page, keys})
},
makeShare (params, path) {
path = _dealPath(path)
const index = this.array.findIndex(item => item.page === path)
if (index > -1) {
const { page, keys } = this.array[index]
const query = keys.map(_key => {
return `${_key}=${params[_key]}`
}).join('&')
console.log(`makeShare page: ${page}, query: ${query}`)
return `/${page}?${query}`
}
return ''
},
makePoster (params, path) {
path = _dealPath(path)
const index = this.array.findIndex(item => item.page === path)
if (index > -1) {
const { page, keys } = this.array[index]
const scene = keys.map(_key => {
return params[_key]
}).join('&')
console.log(`makePoster page: ${page}, scene: ${scene}`)
return { page, scene }
}
return null
},
resolveQuery (query) {
const path = _dealPath()
const index = this.array.findIndex(item => item.page === path)
if (index > -1) {
const { keys } = this.array[index]
// 如果是海報
if (query.scene) {
const values = decodeURIComponent(query.scene).split('&')
let res = {}
keys.forEach((key, index) => {
res[key] = values[index]
})
return res
}
// 如果是分享
if (Object.keys(query).length === keys.length) {
return query
}
return false
}
return false
}
}
message.register({ page: 'pages/Search/detail', keys: ['id', 'memberId', 'timestamp'] })
message.register({ page: 'pages/Home/index', keys: ['memberId'] })
export default message
復(fù)制代碼
這個方法的目的是指定不同的跳轉(zhuǎn)頁面生成二維碼需要的參數(shù),并進行拼接。使用這個方法的好處在于,以后我們可能會有很多頁面需要有生成分享海報的功能,僅僅是每個頁面上調(diào)用一個拼接參數(shù)的函數(shù),會導(dǎo)致我們遺漏或多傳遞了參數(shù),用這個函數(shù)進行過濾可以提前檢測我們傳遞的參數(shù)是否正確。
uniapp分包由于小程序有體積和資源加載限制,所以小程序平臺提供了分包方式,優(yōu)化小程序的下載和啟動速度。
所謂的主包,即放置默認(rèn)啟動頁面/TabBar 頁面,以及一些所有分包都需用到公共資源/JS 腳本;而分包則是根據(jù)pages.json的配置進行劃分。
在小程序啟動時,默認(rèn)會下載主包并啟動主包內(nèi)頁面,當(dāng)用戶進入分包內(nèi)某個頁面時,會把對應(yīng)分包自動下載下來,下載完成后再進行展示。此時終端界面會有等待提示。
注意點:
- subPackages 里的pages的路徑是 root 下的相對路徑,不是全路徑。
- 微信小程序每個分包的大小是2M,總體積一共不能超過16M。
- 百度小程序每個分包的大小是2M,總體積一共不能超過8M。
- 支付寶小程序每個分包的大小是2M,總體積一共不能超過4M。
- QQ小程序每個分包的大小是2M,總體積一共不能超過24M。
- 分包下支持獨立的 static 目錄,用來對靜態(tài)資源進行分包。
- 分包是按照分包的順序進行打包的,所有的subpackages配置以外的文件路徑,全部都被打包在主包(App)內(nèi)。
- subpackages無法嵌入另一個subpackages。
- tabBar頁面必須在App主包內(nèi)。
支持分包的目錄結(jié)構(gòu):
┌─pages
│ ├─index
│ │ └─index.vue
│ └─login
│ └─login.vue
├─pagesA
│ ├─static
│ └─list
│ └─list.vue
├─pagesB
│ ├─static
│ └─detail
│ └─detail.vue
├─static
├─main.js
├─App.vue
├─manifest.json
└─pages.json
復(fù)制代碼
pages.json:
{
"pages": [{
"path": "pages/index/index",
"style": { ...}
}, {
"path": "pages/login/login",
"style": { ...}
}],
"subPackages": [{
"root": "pagesA",
"pages": [{
"path": "list/list",
"style": { ...}
}]
}, {
"root": "pagesB",
"pages": [{
"path": "detail/detail",
"style": { ...}
}]
}],
// 預(yù)加載
"preloadRule": {
"pagesA/list/list": {
"network": "all",
"packages": ["__APP__"]
},
"pagesB/detail/detail": {
"network": "all",
"packages": ["pagesA"]
}
}
}
相關(guān)案例查看更多
相關(guān)閱讀
- 云南建設(shè)廳網(wǎng)站
- 云南網(wǎng)站建設(shè)價格
- 手機網(wǎng)站建設(shè)
- 云南網(wǎng)站建設(shè)服務(wù)公司
- 小程序開發(fā)公司
- 云南網(wǎng)站建設(shè)百度
- 小程序
- 云南網(wǎng)站建設(shè)哪家好
- 云南小程序開發(fā)公司推薦
- 保險網(wǎng)站建設(shè)公司
- 云南網(wǎng)站建設(shè)專業(yè)品牌
- 分銷系統(tǒng)
- 曲靖小程序開發(fā)
- php網(wǎng)站
- 云南網(wǎng)站建設(shè)高手
- 小程序被騙
- 云南小程序開發(fā)首選品牌
- 網(wǎng)站建設(shè)高手
- 關(guān)鍵詞快速排名
- 網(wǎng)站維護
- 怎么做網(wǎng)站
- 網(wǎng)站建設(shè)特性
- 云南網(wǎng)站建設(shè)招商
- 昆明網(wǎng)站建設(shè)公司
- 人人商城
- 云南網(wǎng)絡(luò)推廣
- 小程序開發(fā)排名前十名
- 小程序退款
- 報廢車拆解管理系統(tǒng)
- 報廢車拆解系統(tǒng)