微信小程序—魔镜 笔记&源码(微信开发者工具+JS+UI组件库+云开发)
效果展示
视频演示
0.准备工作
Wux组件库官方文档:https://www.wuxui.com/点击转到文档 微信小程序官方文档:https://developers.weixin.qq.com/miniprogram/dev/framework/点击转到文档 微信开发者工具下载:https://developers.weixin.qq.com/miniprogram/dev/devtools/download.html点击转到下载页
1.搭建环境
安装npm安装组件库
安装node
检查node版本 node -v
检查npm版本 npm -v
或者安装yarn安装组件库
npm install -g yarn
2.引入WuxWeapp组件库或者VantUI组件库
npm i wux-weapp -S --production
npm i @vant/weapp -S --production
git clone https://github.com/wux-weapp/wux-weapp.git D:\File\Project\Download
//‘cnpm‘ 不是内部或外部命令,也不是可运行的程序 或批处理文件,则安装cnpm
//npm install -g cnpm --registry=https://registry.npm.taobao.org
//cnpm -v
//'cross-env' 不是内部或外部命令,也不是可运行的程序,则安装cross-env
//cnpm i cross-env --save-dev
//'gulp'不是内部或者外部命令,也不是可运行的程序或批处理文件,则安装
//npm install gulp -g
//提示try running:yarn add gulp,则执行
//yarn add gulp
//提示error An unexpected error occurred: "ENOENT: no such file or directory, copyfile 'C:\\Users\\WangZhuo\\AppData\\Local\\Yarn\\Cache\\v6\\npm-shebang-regex-1.0.0-da42f49740c0b42db2ca9728571cb190c98efea3-integrity\\node_modules\\shebang-regex\\index.js' -> 'D:\\File\\Project\\Download\\wux-weapp\\node_modules\\cross-spawn\\node_modules\\shebang-regex\\index.js'". 则
//删除国外代理
//npm config rm proxy
//npm config rm https-proxy
//更换成国内镜像
//yarn config set sass-binary-site http://npm.taobao.org/mirrors/node-sass
安装依赖包
npm install
在wux-weapp文件夹下,编译
npm run build -- --config ./config.custom.json --output ./build
3.修改app.json
将 app.json 中的 "style": "v2" 去除
4.修改project.config.json,在settings中添加
"packNpmManually": true,
"packNpmRelationList": [
{
"packageJsonPath": "./package.json",
"miniprogramNpmDistDir": "./"
}
开发工具会默认在当前目录下创建miniprogram_npm的文件名,所以新版本的miniprogramNpmDistDir配置为’./'即可
5.构建npm包
点击 工具 -> 构建 npm,并勾选 使用 npm 模块 选项,构建完成后,即可引入组件
3.使用Wux组件库
全局使用
app.json配置
{
"usingComponents": {
"wux-button": "/miniprogram_npm/wux-weapp/button/index",
"wux-icon": "/miniprogram_npm/wux-weapp/icon/index",
"wux-badge": "/miniprogram_npm/wux-weapp/badge/index",
"wux-tabbar": "/miniprogram_npm/wux-weapp/tabbar/index",
"wux-tabbar-item": "/miniprogram_npm/wux-weapp/tabbar-item/index"
}
}
4.微信开发者工具配置
"checkSiteMap":false //project.config.json里面去进行配置,有个checkSiteMap的配置项,默认为true,改成false就不会报sitemap的警告
//2.18.1 加入了组件属性和对应数据的类型匹配检查,如果不匹配就会展示the type of property "xx" is illegal
5.魔镜主题色
@positive: #387ef5;
@calm: #6a9ef8;
@balanced: #a5c5fb;
@energized: #ffb9c1;
@assertive: #ff8b98;
@royal: #ff6476;
6.项目目录结构
正式开发
1.自定义底部导航tabbar
①app.json
"tabBar": {
"custom": true,
"list": [
{
"pagePath": "pages/home/home"
},{
"pagePath": "pages/person/person"
},{
"pagePath": "pages/shop/shop"
}
]
},
"usingComponents": {}
②根目录下创建custom-tab-bar文件夹
在custom-tab-bar下创建四个文件
custom-tab-bar/index.js
custom-tab-bar/index.json
custom-tab-bar/index.wxml
custom-tab-bar/index.wxss
③在custom-tab-bar/index.js中写switchTap方法
Page({
data: {
pagePath:[
'/pages/home/home','----忽略----','/pages/shop/shop','/pages/person/person'
]
},
onChange(e) {
const that=this;
if(e.detail.key!==1){
var pageUrl=that.data.path[e.detail.key]
wx.switchTab({url:pageUrl})
}
else{
}
},
})
④在跳转的目标页.js中写getTabBar 获取当前页面下的自定义 tabBar 组件实例
并setData()更新current,tab栏icon变为选中
page({
onShow: function () {
if (typeof this.getTabBar === 'function' &&
this.getTabBar()) {
this.getTabBar().setData({
current: 0
})
}
}
})
2.使用压屏窗
①app.json中引入
app.json
"usingComponents":{
"wux-landscape": "./components/lib/landscape/index"
}
②在custom-tab-bar/index.wxml添加
<wux-landscape visible="{{ visible1 }}" bind:close="onClose1" maskClosable="{{true}}">
<view class='SVGcontainer'>
<image class='SVG' src="{{bgImg}}" mode='aspectFit' style="height:140px;width:130px" />
<wux-white-space body-style="height: 25px" />
<wux-button block size="small" type="assertive" bind:click="navToCamera" data-way="takePhoto">拍摄照片</wux-button>
<wux-white-space body-style="height: 12px" />
<wux-button block size="small" type="assertive" bind:click="navToCamera" data-way="chooseGallery">相册选择</wux-button>
</view>
</wux-landscape>
③在custom-tab-bar/index.wxss中添加
.SVG{
top:-80px;
position: absolute;
left:41px;
}
.SVGcontainer{
margin:40px auto 0 auto;
height:200px;
width: 220px;
align-items: center;
background-color: rgb(255, 255, 255);
padding: 30px;
box-sizing: border-box;
border-radius: 15px;
border:5px solid rgb(255, 197, 210);
position: relative;
}
④在custom-tab-bar/index.js中增加
data: {
current: 0,
last: 0,
bgImg:'data:image/svg+xml;base64,77u/PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjxzdmcgdmVyc2lvbj0iMS4xIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgd2lkdGg9IjE0NHB4IiBoZWlnaHQ9IjE0NHB4IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciPg0KICA8ZGVmcz4NCiAgICA8cGF0aCBkPSJNIDAgMCAgTCAxMjkzIDAgIEwgMTI5MyAxMDI0ICBMIDAgMTAyNCAgWiAiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZmlsbD0iYmxhY2siIGlkPSJwYXRoMjAiIC8+DQogICAgPGNsaXBQYXRoIGlkPSJjbGlwMjEiPg0KICAgICAgPHVzZSB4bGluazpocmVmPSIjcGF0aDIwIiAvPg0KICAgIDwvY2xpcFBhdGg+DQogICAgPGZpbHRlciB4PSItNTAuMDAlIiB5PSItNTAuMDAlIiB3aWR0aD0iMjAwLjAwJSIgaGVpZ2h0PSIyMDAuMDAlIiBmaWx0ZXJVbml0cz0ib2JqZWN0Qm91bmRpbmdCb3giIGlkPSJmaWx0ZXIyMiI+DQogICAgICA8ZmVDb2xvck1hdHJpeCB0eXBlPSJtYXRyaXgiIHZhbHVlcz0iMSAwIDAgMCAwICAwIDEgMCAwIDAgIDAgMCAxIDAgMCAgMCAwIDAgMSAwICAiIGluPSJTb3VyY2VHcmFwaGljIiAvPg0KICAgIDwvZmlsdGVyPg0KICAgIDxmaWx0ZXIgeD0iMTE1cHgiIHk9IjE0M3B4IiB3aWR0aD0iMTQ0cHgiIGhlaWdodD0iMTQ0cHgiIGZpbHRlclVuaXRzPSJ1c2VyU3BhY2VPblVzZSIgaWQ9ImZpbHRlcjIzIj4NCiAgICAgIDxmZU9mZnNldCBkeD0iMCIgZHk9IjUiIGluPSJTb3VyY2VBbHBoYSIgcmVzdWx0PSJzaGFkb3dPZmZzZXRJbm5lciIgLz4NCiAgICAgIDxmZUdhdXNzaWFuQmx1ciBzdGREZXZpYXRpb249IjQiIGluPSJzaGFkb3dPZmZzZXRJbm5lciIgcmVzdWx0PSJzaGFkb3dHYXVzc2lhbiIgLz4NCiAgICAgIDxmZUNvbXBvc2l0ZSBpbjI9InNoYWRvd0dhdXNzaWFuIiBvcGVyYXRvcj0iYXRvcCIgaW49IlNvdXJjZUFscGhhIiByZXN1bHQ9InNoYWRvd0NvbXBvc2l0ZSIgLz4NCiAgICAgIDxmZUNvbG9yTWF0cml4IHR5cGU9Im1hdHJpeCIgdmFsdWVzPSIwIDAgMCAwIDAuMTkyMTU2ODYyNzQ1MDk4ICAwIDAgMCAwIDAuMTkyMTU2ODYyNzQ1MDk4ICAwIDAgMCAwIDAuMTkyMTU2ODYyNzQ1MDk4ICAwIDAgMCAwLjEyMTU2ODYyNzQ1MDk4IDAgICIgaW49InNoYWRvd0NvbXBvc2l0ZSIgLz4NCiAgICA8L2ZpbHRlcj4NCiAgICA8ZyBpZD0id2lkZ2V0MjQiPg0KICAgICAgPGcgdHJhbnNmb3JtPSJtYXRyaXgoMC4wOTg5OTQ1ODYyMzM1NjU0IDAgMCAwLjA5ODk5NDU4NjIzMzU2NTQgMTIzIDE1OS4zMTQ3NzE4NDg0MTUgKSIgY2xpcC1wYXRoPSJ1cmwoI2NsaXAyMSkiIGZpbHRlcj0idXJsKCNmaWx0ZXIyMikiPg0KICAgICAgICA8cGF0aCBkPSJNMjg3LjUyODM0NSAyNjAuNTgxMjU0bDQyLjg0NjMwNS0yMjMuNjYzMUExMi4xMjYzMTMgMTIuMTI2MzEzIDAgMCAxIDM0Mi4yMzE0ODkgMjYuOTQ3NjMxaDYwOS4wMTAzNjZhMTIuMTI2MzEzIDEyLjEyNjMxMyAwIDAgMSAxMS44NTY4MzkgOS45NzA1MjNsNDIuODQ2MzA0IDIyMy42NjMxeiIgZmlsbD0iI0ZEQ0E4OSIgcC1pZD0iNjEwMSIgZGF0YS1zcG0tYW5jaG9yLWlkPSJhMzEzeC43NzgxMDY5LjAuaTkiIGNsYXNzPSJzZWxlY3RlZCI+PC9wYXRoPg0KICAgICAgICA8cGF0aCBkPSJNOTM5LjExNTU0MiA1My44OTQ5OTJsMzQuMjIzMTQ5IDE3OS43Mzg5SDMyMC4xMzQ2NTNMMzU0LjM1NzgwMSA1My44OTQ5OTJoNTg0Ljc1Nzc0MW0xMi4zOTU3ODctNTMuODk0NzIzSDM0Mi4yMzE0ODlhMzkuMDczNjc0IDM5LjA3MzY3NCAwIDAgMC0zOC4yNjUyNTMgMzEuNzk3ODg3bC00OS4wNDQxOTggMjU1LjczMDQ1OWg3ODMuNjI5MjY4bC00OC43NzQ3MjQtMjU1LjczMDQ1OUEzOS4wNzM2NzQgMzkuMDczNjc0IDAgMCAwIDk1MS4yNDE4NTUgMC4wMDAyNjl6IiBmaWxsPSIjRkY2NDc2IiBwLWlkPSI2MTAyIiBkYXRhLXNwbS1hbmNob3ItaWQ9ImEzMTN4Ljc3ODEwNjkuMC5pNCIgY2xhc3M9InNlbGVjdGVkIj48L3BhdGg+DQogICAgICAgIDxwYXRoIGQ9Ik0yNi45NDczNjEgMTg4LjYzMTc5OWwxMjM5LjU3ODYyMSAwIDAgODA4LjQyMDg0LTEyMzkuNTc4NjIxIDAgMC04MDguNDIwODRaIiBmaWxsPSIjRkRBQ0JEIiBwLWlkPSI2MTAzIiBkYXRhLXNwbS1hbmNob3ItaWQ9ImEzMTN4Ljc3ODEwNjkuMC5pMCIgY2xhc3M9IiI+PC9wYXRoPg0KICAgICAgICA8cGF0aCBkPSJNMTIxMi42MzEyNiAyMTUuNTc5MTZhMjYuOTQ3MzYxIDI2Ljk0NzM2MSAwIDAgMSAyNi45NDczNjEgMjYuOTQ3MzYxdjcwMC42MzEzOTVhMjYuOTQ3MzYxIDI2Ljk0NzM2MSAwIDAgMS0yNi45NDczNjEgMjYuOTQ3MzYxSDgwLjg0MjA4NGEyNi45NDczNjEgMjYuOTQ3MzYxIDAgMCAxLTI2Ljk0NzM2MS0yNi45NDczNjFWMjQyLjUyNjUyMWEyNi45NDczNjEgMjYuOTQ3MzYxIDAgMCAxIDI2Ljk0NzM2MS0yNi45NDczNjFoMTEzMS43ODkxNzZtMC01My44OTQ3MjNIODAuODQyMDg0YTgwLjg0MjA4NCA4MC44NDIwODQgMCAwIDAtODAuODQyMDg0IDgwLjg0MjA4NHY3MDAuNjMxMzk1YTgwLjg0MjA4NCA4MC44NDIwODQgMCAwIDAgODAuODQyMDg0IDgwLjg0MjA4NGgxMTMxLjc4OTE3NmE4MC44NDIwODQgODAuODQyMDg0IDAgMCAwIDgwLjg0MjA4NC04MC44NDIwODRWMjQyLjUyNjUyMWE4MC44NDIwODQgODAuODQyMDg0IDAgMCAwLTgwLjg0MjA4NC04MC44NDIwODR6IiBmaWxsPSIjRkY2NDc2IiBwLWlkPSI2MTA0IiBkYXRhLXNwbS1hbmNob3ItaWQ9ImEzMTN4Ljc3ODEwNjkuMC5pMSIgY2xhc3M9IiI+PC9wYXRoPg0KICAgICAgICA8cGF0aCBkPSJNNjQ2LjczNjY3MiA4ODMuMzM0Nzc0QTI5MC40OTI1NTUgMjkwLjQ5MjU1NSAwIDEgMSA5MzcuMjI5MjI3IDU5Mi44NDIyMTkgMjkxLjAzMTUwMiAyOTEuMDMxNTAyIDAgMCAxIDY0Ni43MzY2NzIgODgzLjMzNDc3NHoiIGZpbGw9IiNGRkZGRkYiIHAtaWQ9IjYxMDUiPjwvcGF0aD4NCiAgICAgICAgPHBhdGggZD0iTTY0Ni43MzY2NzIgMzI5LjI5NzAyNUEyNjMuNTQ1MTk0IDI2My41NDUxOTQgMCAxIDEgMzgzLjE5MTQ3OCA1OTIuODQyMjE5IDI2My44MTQ2NjcgMjYzLjgxNDY2NyAwIDAgMSA2NDYuNzM2NjcyIDMyOS4yOTcwMjVtMC01My44OTQ3MjNBMzE3LjQzOTkxNiAzMTcuNDM5OTE2IDAgMSAwIDk2NC4xNzY1ODggNTkyLjg0MjIxOSAzMTcuNDM5OTE2IDMxNy40Mzk5MTYgMCAwIDAgNjQ2LjczNjY3MiAyNzUuNDAyMzAyeiIgZmlsbD0iI0ZGNjQ3NiIgcC1pZD0iNjEwNiIgZGF0YS1zcG0tYW5jaG9yLWlkPSJhMzEzeC43NzgxMDY5LjAuaTIiIGNsYXNzPSIiPjwvcGF0aD4NCiAgICAgICAgPHBhdGggZD0iTTY0Ni43MzY2NzIgNTkyLjg0MjIxOW0tMTIwLjE4NTIzMiAwYTEyMC4xODUyMzIgMTIwLjE4NTIzMiAwIDEgMCAyNDAuMzcwNDYzIDAgMTIwLjE4NTIzMiAxMjAuMTg1MjMyIDAgMSAwLTI0MC4zNzA0NjMgMFoiIGZpbGw9IiNGRjY0NzYiIHAtaWQ9IjYxMDciIGRhdGEtc3BtLWFuY2hvci1pZD0iYTMxM3guNzc4MTA2OS4wLmkxMCIgY2xhc3M9IiI+PC9wYXRoPg0KICAgICAgICA8cGF0aCBkPSJNNjQ2LjczNjY3MiA0OTkuNjA0MzQ5QTkzLjIzNzg3IDkzLjIzNzg3IDAgMSAxIDU1My40OTg4MDIgNTkyLjg0MjIxOSA5My4yMzc4NyA5My4yMzc4NyAwIDAgMSA2NDYuNzM2NjcyIDQ5OS42MDQzNDltMC01My44OTQ3MjNBMTQ3LjEzMjU5MyAxNDcuMTMyNTkzIDAgMSAwIDc5My44NjkyNjUgNTkyLjg0MjIxOSAxNDcuMTMyNTkzIDE0Ny4xMzI1OTMgMCAwIDAgNjQ2LjczNjY3MiA0NDUuNzA5NjI2eiIgZmlsbD0iI0ZGNjQ3NiIgcC1pZD0iNjEwOCIgZGF0YS1zcG0tYW5jaG9yLWlkPSJhMzEzeC43NzgxMDY5LjAuaTMiIGNsYXNzPSIiPjwvcGF0aD4NCiAgICAgICAgPHBhdGggZD0iTTEwMDcuMjkyMzY3IDI4MC43OTE3NzVsMTY5LjQ5ODkwMiAwIDAgODguOTI2MjkyLTE2OS40OTg5MDIgMCAwLTg4LjkyNjI5MloiIGZpbGw9IiNGRjY0NzYiIHAtaWQ9IjYxMDkiIGRhdGEtc3BtLWFuY2hvci1pZD0iYTMxM3guNzc4MTA2OS4wLmk2IiBjbGFzcz0iIj48L3BhdGg+DQogICAgICAgIDxwYXRoIGQ9Ik0xMTQ5Ljg0MzkwOCAzMDcuNzM5MTM2djM1LjAzMTU3aC0xMTUuNjA0MTh2LTM1LjAzMTU3aDExNS42MDQxOG00LjMxMTU3OC01My44OTQ3MjNoLTEyMy45NTc4NjJhNDkuNTgzMTQ1IDQ5LjU4MzE0NSAwIDAgMC00OS41ODMxNDUgNDkuNTgzMTQ1djQzLjM4NTI1MmE0OS41ODMxNDUgNDkuNTgzMTQ1IDAgMCAwIDQ5LjU4MzE0NSA0OS41ODMxNDVoMTIzLjk1Nzg2MmE0OS41ODMxNDUgNDkuNTgzMTQ1IDAgMCAwIDQ5LjU4MzE0NS00OS41ODMxNDV2LTQzLjM4NTI1MmE0OS41ODMxNDUgNDkuNTgzMTQ1IDAgMCAwLTQ5LjU4MzE0NS00OS41ODMxNDV6IiBmaWxsPSIjZmZmZmZmIiBwLWlkPSI2MTEwIiBkYXRhLXNwbS1hbmNob3ItaWQ9ImEzMTN4Ljc3ODEwNjkuMC5pNSIgY2xhc3M9IiI+PC9wYXRoPg0KICAgICAgPC9nPg0KICAgIDwvZz4NCiAgPC9kZWZzPg0KICA8ZyB0cmFuc2Zvcm09Im1hdHJpeCgxIDAgMCAxIC0xMTUgLTE0MyApIj4NCiAgICA8dXNlIHhsaW5rOmhyZWY9IiN3aWRnZXQyNCIgZmlsdGVyPSJ1cmwoI2ZpbHRlcjIzKSIgLz4NCiAgICA8dXNlIHhsaW5rOmhyZWY9IiN3aWRnZXQyNCIgLz4NCiAgPC9nPg0KPC9zdmc+',
visible1: false,
},
onOpen1() {
this.setData({
current: 1,
visible1: true,
})
},
chooseScan(){
this.setData({last:this.data.current})
this.onOpen1();
console.log('-----可以忽略此问题-----');
},
onClose1() {
const that=this;
this.setData({
current: that.data.last,
visible1: false,
})
},
async navToCamera(e){
console.log(e.target.dataset['way'])
var choice=e.target.dataset['way'];
if(choice==='takePhoto'){
await wx.navigateTo({
url: '/pages/takePhoto/takePhoto',
})
}
else if(choice==='chooseGallery'){
wx.chooseImage({
count: 1,
sizeType: ['original'],
sourceType: ['album', 'camera'],
success (res) {
const tempFilePaths = res.tempFilePaths
console.log(tempFilePaths[0]);
}
})
}
}
3.使用动画组
wxml
Switch state
<switch data-model="example.in" bindchange="switchChange" checked="{{ example.in }}" />
<wux-animation-group
in="{{ example.in }}"
enter="{{ true}}"
exit="{{ true }}"
class-names="wux-animate--fadeInDown"
>
<view>
微信小程序自定义组件 https://github.com/wux-weapp/wux-weapp
</view>
</wux-animation-group>
js
switchChange(e) {
console.log(e.currentTarget.dataset);
console.log(e.detail)
const { model } = e.currentTarget.dataset
this.setData({
[model]: e.detail.value,
})
}
6.使用svg(小程序不支持)
<view class="svg_outline">
<image class="svg_outline-inner" src="/images/取景框/outline.svg"/>
</view>
7.取景框拍照实现
takePhoto.wxml
<view class='cameraContainer'>
<wux-button class='loading' block loading type="light">相机加载中...</wux-button>
<camera class='camer' device-position="{{camera.position}}" flash="{{camera.flash}}" binderror="{{camera.binderror}}" frame-size="{{camera.framesize}}" bindstop="{{camera.bindstop}}"></camera>
<view class="svg">
<image class="svg_outline" src="{{svgOutline}}" type='aspectFill' style="width: 100%; height: 100%;" />
</view>
</view>
<view class='coverView'>
<view class='btnBar'>
<view class='littleBtn' bindtap="switchFlash"><wux-icon type="ios-flash" size="34" color="#fff"/></view>
</view>
<view class='btnBar'>
<view class='mainBtn' bindtap="takePhoto"><wux-icon type="ios-radio-button-off" size="70" color="#fff"/></view>
</view>
<view class='btnBar'>
<view class='littleBtn' bindtap="switchPosition"><wux-icon type="ios-sync" size="34" color="#fff"/></view>
</view>
</view>
<wux-toast id="wux-toast" />
takePhoto.js
data:{
camera:{
mode:"normal",
resolution:"high",
position:'back',
flash:"off",
framesize:"large",
bindstop:"exceptionalStop",
bindinitdone:"hideLoading",
binderror:"grantFail"
}
}
onShow: function () {
wx.getSetting({
success(res) {
if (!res.authSetting['scope.camera']) {
wx.authorize({
scope: 'scope.camera',
success () {
console.log('相机授权成功')
}
})
}
}
})
},
switchFlash(){
if(this.data.camera.flash=='torch'){
wx.showToast({
title: '关闭闪光灯',
icon:'none',
})
this.setData({
['camera.flash']:'off',
success:res=>{
wx.hideToast({ })
}
})
console.log(this.data.camera.flash)
}
else{
wx.showToast({
title: '打开闪光灯',
icon:'none',
})
this.setData({
['camera.flash']:'torch',
success:res=>{
wx.hideToast({ })
}})
console.log(this.data.camera.flash)
}
}
,
switchPosition(){
if(this.data.camera.position=='front'){
wx.showToast({
title: '切换后置相机',
icon:'none',
})
this.setData({
['camera.position']:'back',
success:res=>{
wx.hideToast({ })
}
})
}
else{
wx.showToast({
title: '切换前置相机',
icon:'none',
})
this.setData({
['camera.position']:'front',
success:res=>{
wx.hideToast({ })
}})
}
},
grantFail:function(){
wx.showModal({
title: '提示',
content: '请打开相机授权后继续',
success (res) {
if (res.confirm) {
wx.openSetting({
success (res) {
console.log(res.authSetting)
res.authSetting = {
"scope.userInfo": true,
"scope.camera": true
}
wx.redirectTo({ url: '/pages/takePhoto/takePhoto'});
}
})
} else if (res.cancel) {
wx.showToast({
icon:'none',
title:'授权失败'
})
wx.navigateBack({
delta: 1,
})
}
}
})
}
takePhoto.css
.cameraContainer{
box-sizing: border-box;
width:100%;
height:1260rpx;
position: relative;
}
.loading{
position: absolute;
width: 100%;
top:240rpx;
left:0;
}
.camer{
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
.svg{
width: 100%;
height:100%;
left: 0;
top:0;
display: flex;
flex-direction: column;
align-items: center;
box-sizing: border-box;
padding-left:115rpx;
padding-right: 115rpx;
position: absolute;
z-index: 10;
}
.coverView{
width: 100%;
position: fixed;
bottom: 50rpx;
display: flex;
z-index: 20;
}
.btnBar{
width: 33.3%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.littleBtn{
width:70rpx;
height: 70rpx;
padding: 5rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: rgba(168, 114, 255, 0.685);
}
.mainBtn{
width:125rpx;
height: 125rpx;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
border-radius: 50%;
background-color: rgba(168, 114, 255, 0.685);
}
8.takePhoto页
使用到的局部数据
takePhoto.js
data:{
windowHeight:0,
windowWidth:0,
screenMaxBright:0,
showCamera:false,
camera:{
mode:"normal",
resolution:"high",
flash:"off",
framesize:"large",
bindstop:"exceptionalStop",
bindinitdone:"hideLoading",
binderror:"grantFail"
},
}
使用的全局数据
app.js
globalData: {
orignalBright:0.5,
camera:{
direction:'back'
}
}
takePhoto页主要逻辑
具体实现
1.页面加载,获取屏幕的宽高,将camera的父容器和camera本身的宽高设为屏幕宽高
onLoad(){
this.getsize()
}
getsize(){
let that=this;
wx.getSystemInfo({
success(res) {
console.log("height="+ res.windowHeight)
console.log("width="+res.windowWidth)
that.setData({
windowHeight:res.windowHeight,
windowWidth:res.windowWidth
})
},
})
}
2.页面就绪,展示Modal“确认拍摄”,阻塞其他进程,等待用户确认
wx.showModal({
cancelColor: '#FF4242',
showCancel:true,
content:'确认拍摄?',
success:res=>{
}
})
success:res=>{ }
用户选择确认,showCamera置为true,渲染takePhoto.wxml页面,相机方向设为全局数据中保存的camera.direction相机方向
if(res.confirm){
that.setData({
showCamera:true,
['camera.position']: getApp().globalData.camera.direction
})
}
用户选择取消,返回上一页
if(res.cancel){
wx.navigateBack({
delta: 1,
})
}
4.切换前后置相机
1.在takePhoto页面onLoad()或者onReady()时,初始化this.data.camera.position为globalData.camera.direction
(恢复用户上次相机的(前置/后置)配置)
2.点击切换前后置事件
判断当前data.camera.position
如果是front前置
则showToast"切换后置中..." 展示文字提示
this.setData() 将'camera.position'设为'back' 后置
然后hideToast
否则
showToast"切换前置中..."
setData 将 'camera.position'设为'front' 前置
隐藏Toast 文字提示
switchPosition(){
if(this.data.camera.position=='front'){
wx.showToast({
title: '切换后置相机',
icon:'none',
})
this.setData({
['camera.position']:'back',
success:res=>{
wx.hideToast({ })
}
})
console.log(this.data.camera.position)
}
else{
wx.showToast({
title: '切换前置相机',
icon:'none',
})
this.setData({
['camera.position']:'front',
success:res=>{
wx.hideToast({ })
}})
console.log(this.data.camera.position)
}
}
3.takePhoto页面unLoad()卸载时,将当前镜头方向信息传给全局数据globalData.camera.direction(下次重新加载takePage页相机方向就会恢复成上次退出时的状态)
let app=getApp()
app.globalData.camera.direction=that.data.camera.position
5.相机授权失败事件
展示对话框
选择确认
则打开设置
设置完成则重载takePhoto页
wx.redirectTo({ url: '/pages/takePhoto/takePhoto'});(重载保证相机授权成功后显示出来)
选择取消
则展示“授权失败”
返回上一页wx.navigateBack({ delta: 1 })
grantFail:function(){
const pagePath=this.getCurrentPageFullPath;
wx.showModal({
title: '提示',
content: '请打开相机授权后继续',
success (res) {
if (res.confirm) {
wx.openSetting({
success (res) {
console.log(res.authSetting)
res.authSetting = {
"scope.userInfo": true,
"scope.camera": true
}
wx.redirectTo({ url: '/pages/takePhoto/takePhoto'});
}
})
} else if (res.cancel) {
wx.showToast({
icon:'none',
title:'授权失败'
})
wx.navigateBack({
delta: 1,
})
}
}
})
}
- 闪光灯/屏幕补光逻辑
1.在小程序onLaunch()启动时获取屏幕亮度,并保存到全局数据globalData.orignalBright中 wx.getScreenBrightness({
success(res){
getApp().globalData.orignalBright=res.value;
}
})
2.takePhoto页面onLoad()加载时或者onReady()就绪时,初始化this.data.camera.position相机方向为全局数据globalData.camera.direction的值
that.setData({
showCamera:true,
['camera.position']: getApp().globalData.camera.direction
})
3.闪光灯点击事件
首先判断当前相机方向this.data.camera.position
如果是'back'后置
如果闪光灯当前是'torch'状态
则关闭闪光灯that.setData({['camera.flash']:'off'})
否则打开闪光灯that.setData({['camera.flash']:'torch'})
否则是前置(前置情况下没有闪光灯,变为调节屏幕亮度)
如果当前data.screenMaxBright==1是屏幕最大亮度
则恢复手机原始亮度,将value设为全局数据getApp().globalData.orignalBright屏幕亮度的值,将data.screenMaxBright=0
否则将屏幕调为最大亮度,将data.screenMaxBright=1
wx.setScreenBrightness({
value:1,
success(){
that.data.screenMaxBright=1
}
})
4. takePhoto页面unLoad()卸载时,退出拍摄页,回归屏幕原来的亮度
将当前屏幕亮度设为globalData.orignalBright原始亮度,data.screenMaxBright=0
wx.setScreenBrightness({
value: getApp().globalData.orignalBright,
success(){
that.data.screenMaxBright=0
}
})
7.拍摄事件
takePhoto() {
console.log('拍照')
const ctx = wx.createCameraContext()
ctx.takePhoto({
quality: 'high',
success: (res) => {
var tmpPath=res.tempImagePath;
wx.navigateTo({
url: '/pages/takePhoto/scanning/scanning',
success: function(res) {
res.eventChannel.emit('acceptDataFromOpenerPage', { imagePath: tmpPath })
}
})
}
})
}
9.Scanning页
扫描动画
wxml
<view class="container" class="scanning">
<view class='logo'>
<image src="{{logoUrl}}" ></image>
</view>
<view class='processing'>
<progress percent="{{processValue}}" color="rgb(255, 100, 118)" active stroke-width="5" bindactiveend='onScanningFinish' />
</view>
<text style="color: #ccc;">正在扫描生成结果...</text>
<view class='uploadPhoto'>
<image src="{{facePhoto}}" mode="aspectFill"></image>
<canvas type="2d" id="canvas" style="width: {{canvasWidth}}px; height: {{canvasWidth}}px; position: absolute; border-width: 3px; border-style:dashed; border-color: pink;"></canvas>
</view>
</view>
css
.logo{
width:300px;
height:230px;
display: flex;
justify-content: center;
align-items: center;
margin-top: 20rpx;
}
.logo image{
width:140rpx;
height:140rpx;
}
.processing{
width:300px;height:50px;margin-top: 10rpx;
}
.uploadPhoto{
width:300px;
height:300px;
display: flex;
justify-content: center;
align-items: center;
margin-top: 10rpx;
position: relative;
}
.uploadPhoto image{
width:30%;
height:40%
}
.scanning{
display: flex;
flex-direction: column;
align-items: center;
}
扫描动画
scanning.js(page({ }))
data
data: {
canvasWidth:150,
lineWidth:153,
lineHeight:2,
},
onready()
onReady() {
this.position = {
x: 1,
y: this.data.canvasWidth+40,
}
wx.createSelectorQuery()
.select('#canvas')
.fields({
node: true,
size: true,
})
.exec(this.init.bind(this))
},
init(res) {
const width = res[0].width
const height = res[0].height
const canvas = res[0].node
const ctx = canvas.getContext('2d')
const dpr = wx.getSystemInfoSync().pixelRatio
canvas.width = width * dpr
canvas.height = height * dpr
ctx.scale(dpr, dpr)
const renderLoop = () => {
this.render(canvas, ctx)
canvas.requestAnimationFrame(renderLoop)
}
canvas.requestAnimationFrame(renderLoop)
const img = canvas.createImage()
img.onload = () => {
this._img = img
}
img.src = '/images/检测界面/scan.png'
},
render(canvas, ctx) {
ctx.clearRect(0, 0, this.data.canvasWidth+5, this.data.canvasWidth+30)
this.drawCar(ctx)
},
drawCar(ctx) {
if (!this._img) return
if (this.position.y < 0) {
this.position.y = this.data.lineWidth+40
}
ctx.drawImage(this._img, this.position.x, this.position.y-=1, this.data.lineWidth, this.data.lineHeight)
ctx.restore()
},
-
页面跳转并传送数据 拍照页拍照并发送图片临时地址 takePhoto.js
console.log('拍照')
const ctx = wx.createCameraContext()
ctx.takePhoto({
quality: 'high',
success: (res) => {
var tmpPath=res.tempImagePath;
wx.navigateTo({
url: '/pages/takePhoto/scanning/scanning',
success: function(res) {
res.eventChannel.emit('acceptDataFromOpenerPage', { imagePath: tmpPath })
}
})
}
})
?
? scanning.js
? 接收页onLoad并接收图片临时地址
? onLoad()中
const eventChannel= this.getOpenerEventChannel()
eventChannel.on('acceptDataFromOpenerPage', function (data) {
console.log('图片路径:'+data.imagePath)
that.setData({facePhotoUrl:data.imagePath})
})
? eventChannel.on({})中
const fs=wx.getFileSystemManager()
try {
const res = fs.readFileSync(that.data.facePhotoUrl, 'base64', 0)
console.log("scanning页成功获取到base64!")
that.data.facePhotoBase64=res;
} catch(e) {
console('scanning页base64获取失败!')
console.error(e)
}
引用的外部js文件
const util=require("../../../utils/util");
util.js
数据库的操作
①查询
查询UserPrivateInfo的skinAnalyzeInspect
const queryDbByOpenId = async (collectionName,openid) =>{
return new Promise((resolve)=>db.collection(collectionName).where({
_openid:_.eq(openid)
}).get({success:res=>{resolve(res.data)}})).then((val)=>{return val})
}
人脸检测与皮肤分析主要逻辑
app.js
wx.cloud.init({
env: '',
traceUser: true,
})
scanning.js
一、余额查询与判断
全局数据:
freeTimes (免费次数)
remainingTimes (剩余购买的次数)
局部数据:
事件逻辑
appLunch()时
查询免费次数并保存到globalData.freeTimes
查询剩余付费次数并保存到globalData.userInfo.remainingTimes
查询单价(元/次)并保存到globalData.charge
let chargeConfig=await util.queryDbById('Settings','charge_manage')
this.globalData.charge=chargeConfig.cost_each_time;
this.globalData.freeTimes=chargeConfig.free_times;
统计用户检测次数amount(已保存的用户分析记录数)
用户检测皮肤前进行判断
amount小于等于(免费次数+剩余付费次数),直接进行检测
大于(免费次数+剩余付费次数)询问支付(查询次数不足,是否前往付费?)
选择“否”,返回上一页
选择“是”,跳转到支付界面
选择支付套餐(次数*globalData.charge),选择支付数量
支付完成之后
更新数据库,更新全局数据globalData.userInfo.remainingTimes,更新页面数据
在用户个人主页显示可用次数
购买的总数+免费总数-已用总数
二、人脸检测API鉴权与请求
此处仅列出思路
1.查询settings中自己保存的的api相关请求token信息
util.js
2.api鉴权并请求人脸识别
getFaceResult()
3.人脸检测并反馈结果
faceDetect()
三、皮肤分析结果与显示
skinAnalyze()
数据库的查询
①费用相关
app.js
wx.cloud.init({
env: '你的云开发环境id',
traceUser: true,
})
util.js
const db=wx.cloud.database();
const queryDbById = async (collectionName,id) =>{
return (await db.collection(collectionName).doc(id).get()).data
}
module.exports = {
queryDbById
}
引用页.js
const util=require('../../utils/util')
onReady:async function(){
let chargeConfig=await util.queryDbById('Settings','charge_manage')
console.log(chargeConfig)
}
②API相关
util.js
const updateDbById = async (collectionName,id,keyvalue) =>{
await db.collection(collectionName).doc(id).update({
data:keyvalue
})
}
const getAPIgrant = async function (key) {
let that = this;
var client_id = key.API_Key;
var client_secret = key.Secret_Key;
var Access_Token
return new Promise(
(resolve) => {
wx.request({
url: 'https://aip.baidubce.com/oauth/2.0/token?grant_type=client_credentials&client_id=' + client_id + '&client_secret=' + client_secret + '&',
method: 'GET',
dataType: 'json',
success: res => {
console.log('人脸检测API Access Token 请求成功!')
const newTime = Math.round(Date.parse(res.header.Date)/1000)
that.updateDbById('Settings', 'bd_face_detect_token', {
['lastUpdateTime']: newTime,
['Access_Token']: res.data.access_token,
['effictiveTime']: res.data.expires_in
})
Access_Token = res.data.access_token;
console.log('新授权token:' + Access_Token);
resolve(Access_Token)
},
fail: res => {
console.log("人脸检测API Access Token请求失败:");
console.log(res)
}
})
}).then((value) => {
getApp().globalData.API.Access_Token = value
return value
})
}
const isDetectAllow = async ()=>{
var num={}; var freeTimes=0; var paidTimes=0;
return new Promise((resolve)=>{
num = db.collection("FaceInfo").count();
resolve(num)
}).then((value)=>{
freeTimes = getApp().globalData.freeTimes
paidTimes = getApp().globalData.userInfo.reaminingTimes
return ( value.total <= (freeTimes+paidTimes) )? true : false;
})
app.js
var key=await util.queryDbById('Settings','bd_face_detect_token')
getApp().globalData.API.Access_Token=key.Access_Token
var timestamp = Math.round(Date.parse(new Date())/1000);
console.log("当前时间戳:"+timestamp)
var gap=timestamp-key.lastUpdateTime
if(gap>=key.effictiveTime){
getApp().globalData.API.Access_Token = (await util.getAPIgrant(key))
}
③用户皮肤分析余额相关
applunch()时 查询当前用户余额及检测次数并存至全局globalData.remaning_times
查询当前用户皮肤分析次数并存至global.skinAnalyzeAmount
如果查询到的结果为空,
则发起注册登录
思路一:
1.用户注册时,remaining_times自动置为free_times
2.用户分析皮肤时,判断remaining_times>0,
如果True继续检测
如果False,发起支付,支付(charge*数量)完成后,remainning_times+=数量,更新数据库,更新全局变量
3.分析完成后,将remaining_times-1,并更新数据库
思路二:
1.用户注册时,remaining_times置为0
2.用户分析皮肤时,判断分析次数<=remaining_times+free_times,
如果True继续检测
如果False,发起支付,支付(charge*数量)后,remainning_times+=数量,更新数据库,更新全局变量
3.分析完成后,如果amount<=free_times,则免费次数剩余=free_times-amount,付费余额剩余remainning_times,否则免费次数=0,
免费次数剩余:次;付费次数剩余:次
1.在app.js的onlunch()查询用户数据
如果找到,则更新globalData.userInfo.remainingTimes、globalData.userInfo.skinAnalyzeInspect
更新globalData.userInfo.detectAllow
如果没有找到,则将globalData.userInfo置为undefined
var userPriInfo =await util.queryDbAll('UserPrivateInfo')
userPriInfo=userPriInfo[0];
if(userPriInfo){
console.log("剩余可用次数:"+userPriInfo.remaining_times)
this.globalData.userInfo.reaminingTimes=userPriInfo.remaining_times
console.log("皮肤分析指标:"+userPriInfo.skinAnalyzeInspect)
this.globalData.userInfo.skinAnalyzeInspect=userPriInfo.skinAnalyzeInspect
this.globalData.userInfo.detectAllow=await util.isDetectAllow()
console.log("允许直接检测:"this.globalData.userInfo.detectAllow)
}else{
this.globalData.userInfo=undefined
console.log("用户"+this.globalData.userInfo+"\n新用户需注册")
}
2.在scanning.js中
判断globalData.userInfo是否undefined
若是,则注册并初始化用户数据
若否则,直接使用globalData.userInfo的数据,进行下一步操作
3.判断globalData.userInfo.detectAllow
若为T,直接请求API
若为F,则询问并支付
(返回看思路二)
皮肤检测事件逻辑
皮肤分析结果页展示出来后,remaining_times–
否则不更新remaining_times
10.云函数的使用
在 project.config.json 文件中添加以下字段
"cloudfunctionRoot": "cloudbase/"
新建云函数目录cloudfunctions
cloudfunctions下新建node.js云函数getopenid
index.js中编写云函数
const cloud = require('wx-server-sdk')
cloud.init()
exports.main = async (event, context) => {
const wxContext = cloud.getWXContext()
return {
openid: wxContext.OPENID,
}
}
config.json中配置触发器(可选)
{
"permissions": {
"openapi": [
]
},
"triggers": [
{
"name": "tomylove",
"type": "timer",
"config": "0 0 2 1 * * *"
}
]
}
最后右键保存部署到云环境
其它内容**详见微信官方云开发文档
11.Home页
使用模板template
实现多个页面引入topBar
模板页
/template/topBar.wxml
<template name="topBar">
<view style="display: flex; flex-direction:column; align-items: center; font-size: smaller; padding:10rpx; position: relative; width:100%" class='wux-energized--bg wux-light'>
<view style='position: absolute;top:0rpx;left:0rpx;display: flex;align-items: center;height:100%; width:35%; overflow: scroll;'>
<wux-avatar wx:if="{{avatarUrl}}" bind:tap='{{openSettings}}' src='{{avatarUrl}}' style="margin: 10rpx;"></wux-avatar>
<wux-icon wx:else="{{avatarUrl}}" bindtap="{{initAccount}}" style="margin: 10rpx;" type="ios-contact" size="40" color="#f0f0f0" />
{{nickName}}
</view>
<view><wux-avatar style="margin: 10rpx 10rpx;" size='large' src='https://636c-cloud1-6g4fn1e1fc689993-1309475441.tcb.qcloud.la/photos/%E7%99%BD%E8%BE%B9%E5%9C%86%E8%A7%92logo.png?sign=e773d7f0b3b6337c6b572ce0a80dd8ba&t=1651971170'></wux-avatar>Magic Mirror</view>
</view>
</template>
引用页
以home页为例
home.wxml
<import src="/template/topBar" />
<template is="topBar" data="{{...userInfo}}" ></template>
传入数据和绑定事件
home.js
data: {
userInfo:{
avatarUrl:'',
nickName:'',
openSettings:'openPerson',
initAccount:'initAccount'
}
},
page({
openPerson:()=>{
wx.showModal({
title:'hello',
})
},
initAccount:()=>{
wx.showToast({
title: 'hello',
})
}
})
魔镜用户默认头像base64 icon:
data:image/svg+xml;base64,PHN2ZyB0PSIxNjM2NjEzNDY0Mjg4IiBjbGFzcz0iaWNvbiIgdmlld0JveD0iMCAwIDEwMjQgMTAyNCIgdmVyc2lvbj0iMS4xIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHAtaWQ9IjI0OTcwIiB3aWR0aD0iMjAwIiBoZWlnaHQ9IjIwMCI+PHBhdGggZD0iTTQyOS4zIDU0NC4xYy00MS4zLTI0LjEtNjUuNy01NS4xLTg0LjItMTA3bC0xLjQtMy41Yy0zLjUtNi45LTEwLjUtMTEuMS0xOC41LTExLjEtMi4yIDAuMS00LjIgMC41LTYuMSAxLjEtOS4xIDMuMi0xNC42IDEyLjYtMTMgMjIuN2wxLjMgNC40YzIxLjIgNTkuNyA1MS4xIDk3LjUgOTkuOSAxMjYuNCAwLjEgMCA3LjIgNC44IDExIDQuOGwyLjggMC4xYzEuOS0wLjEgMy45LTAuNCA1LjgtMS4xIDEwLjQtMy43IDE1LjgtMTUuMiAxMi4xLTI1LjctMS42LTUuMS01LjMtOS4yLTkuNy0xMS4xek0zMTcuOSA0MTIuOWM1LjQtMS44IDkuOC01LjUgMTIuNS0xMC43IDIuNi01LjEgMy0xMC45IDEuMy0xNi40LTIuOS04LjktMTEuMS0xNC44LTIwLjQtMTQuOC0yLjIgMC00LjQgMC40LTYuNyAxLjEtMTEuMiAzLjctMTcuNCAxNS44LTEzLjcgMjcgMi45IDguOSAxMS4xIDE0LjggMjAuNCAxNC44IDIuMiAwLjEgNC40LTAuMyA2LjYtMXoiIGZpbGw9IiNGRjY0NzYiIHAtaWQ9IjI0OTcxIiBkYXRhLXNwbS1hbmNob3ItaWQ9ImEzMTN4Ljc3ODEwNjkuMC5pMzgiIGNsYXNzPSJzZWxlY3RlZCI+PC9wYXRoPjxwYXRoIGQ9Ik02ODIuOCA2NjkuM2M5My45LTU3LjcgMTU2LjgtMTYwLjcgMTU2LjgtMjc4LjMgMC0xODAuNC0xNDcuNi0zMjcuMi0zMjguOS0zMjcuMlMxODEuOCAyMTAuNiAxODEuOCAzOTFjMCAxMTcuNiA2Mi45IDIyMC42IDE1Ni44IDI3OC4zbC0xMzQgMTcwLjFjLTkuMiAxNi41LTEwLjYgMzUtMTAuNiA0NC42IDAgNTAuOCAyNiA3NS43IDY2LjIgNzUuN2g1MDEuMWM0MC4yIDAgNjYuMi0yNC45IDY2LjItNzUuNyAwLTE3LjgtMy4yLTMxLjQtMTAuNC00NC4zTDY4Mi44IDY2OS4zek0yNDIuNyAzOTFjMC0xNDYuOCAxMjAuMi0yNjYuMyAyNjgtMjY2LjNzMjY4IDExOS40IDI2OCAyNjYuMy0xMjAuMiAyNjYuMy0yNjggMjY2LjMtMjY4LTExOS41LTI2OC0yNjYuM3ogbTQ5NC4xIDUwNi4xSDI4NC43Yy0xMy41IDAtMjIuNS0xMi4xLTIyLjUtMjQuMiAwLTQuOCAwLTcuMyAyLjMtMTIuMWwxMjcuNC0xNjUuMWMzNi45IDE0LjMgNzYuOSAyMi41IDExOC45IDIyLjVzODEuOS04LjEgMTE4LjktMjIuNUw3NTcgODYwLjhjMi4yIDIuNCAyLjIgNy4zIDIuMiAxMi4xIDAuMSAxMi4xLTguOSAyNC4yLTIyLjQgMjQuMnoiIGZpbGw9IiNmZmZmZmYiIHAtaWQ9IjI0OTcyIj48L3BhdGg+PHBhdGggZD0iTTYyOS42IDY5NS43Yy0zNi45IDE0LjMtNzYuOSAyMi41LTExOC45IDIyLjUtNDEuOSAwLTgxLjktOC4xLTExOC45LTIyLjVMMjY0LjQgODYwLjhjLTIuMyA0LjgtMi4zIDcuMy0yLjMgMTIuMSAwIDEyLjEgOSAyNC4yIDIyLjUgMjQuMmg0NTIuMWMxMy41IDAgMjIuNS0xMi4xIDIyLjUtMjQuMiAwLTQuOCAwLTkuNy0yLjItMTIuMUw2MjkuNiA2OTUuN3oiIGZpbGw9IiNmZmZmZmYiIHAtaWQ9IjI0OTczIj48L3BhdGg+PC9zdmc+
使用fabButton浮动按钮
更改
/components/lib/fab-button
.wux-fab-button{position:absolute;z-index:1020}
app.json
"wux-fab-button": "./components/lib/fab-button/index"
wxml
<view style='height:100rpx;width:100%;position:fixed;bottom:135rpx;left:0'>
<view style="height:100%; width:100%; position:relative;">
<wux-fab-button
position="center"
theme="energized"
hideShadow='true'
scale='1.1'
backdrop='true'
direction="circle"
sAngle="110"
eAngle="250"
buttons="{{ buttons }}"
bind:change="onChange"
bind:click="onClick"
/>
</view>
</view>
js
data:
buttons:[
{
label: 'Camera',
icon:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJpb25pY29uIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+PHRpdGxlPkNhbWVyYTwvdGl0bGU+PHBhdGggZD0iTTM1MC41NCAxNDguNjhsLTI2LjYyLTQyLjA2QzMxOC4zMSAxMDAuMDggMzEwLjYyIDk2IDMwMiA5NmgtOTJjLTguNjIgMC0xNi4zMSA0LjA4LTIxLjkyIDEwLjYybC0yNi42MiA0Mi4wNkMxNTUuODUgMTU1LjIzIDE0OC42MiAxNjAgMTQwIDE2MEg4MGEzMiAzMiAwIDAwLTMyIDMydjE5MmEzMiAzMiAwIDAwMzIgMzJoMzUyYTMyIDMyIDAgMDAzMi0zMlYxOTJhMzIgMzIgMCAwMC0zMi0zMmgtNTljLTguNjUgMC0xNi44NS00Ljc3LTIyLjQ2LTExLjMyeiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIzMiIvPjxjaXJjbGUgY3g9IjI1NiIgY3k9IjI3MiIgcj0iODAiIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMzIiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjMyIiBkPSJNMTI0IDE1OHYtMjJoLTI0djIyIi8+PC9zdmc+',
hideShadow:true,
},
{
label: 'Image',
icon:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJpb25pY29uIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+PHRpdGxlPkltYWdlPC90aXRsZT48cmVjdCB4PSI0OCIgeT0iODAiIHdpZHRoPSI0MTYiIGhlaWdodD0iMzUyIiByeD0iNDgiIHJ5PSI0OCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMzIiLz48Y2lyY2xlIGN4PSIzMzYiIGN5PSIxNzYiIHI9IjMyIiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2Utd2lkdGg9IjMyIi8+PHBhdGggZD0iTTMwNCAzMzUuNzlsLTkwLjY2LTkwLjQ5YTMyIDMyIDAgMDAtNDMuODctMS4zTDQ4IDM1Mk0yMjQgNDMybDEyMy4zNC0xMjMuMzRhMzIgMzIgMCAwMTQzLjExLTJMNDY0IDM2OCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIzMiIvPjwvc3ZnPg==',
hideShadow:true
},
{
label: 'Create',
icon:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJpb25pY29uIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+PHRpdGxlPkJydXNoPC90aXRsZT48cGF0aCBkPSJNNDUyLjM3IDU5LjYzaDBhNDAuNDkgNDAuNDkgMCAwMC01Ny4yNiAwTDE4NCAyOTQuNzRjMjMuMDggNC43IDQ2LjEyIDI3LjI5IDQ5LjI2IDQ5LjI2bDIxOS4xMS0yMjcuMTFhNDAuNDkgNDAuNDkgMCAwMDAtNTcuMjZ6TTEzOCAzMzZjLTI5Ljg4IDAtNTQgMjQuNS01NCA1NC44NiAwIDIzLjk1LTIwLjg4IDM2LjU3LTM2IDM2LjU3QzY0LjU2IDQ0OS43NCA5Mi44MiA0NjQgMTIwIDQ2NGMzOS43OCAwIDcyLTMyLjczIDcyLTczLjE0IDAtMzAuMzYtMjQuMTItNTQuODYtNTQtNTQuODZ6IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjMyIi8+PC9zdmc+',
hideShadow:true
}
]
onClick事件
onClick(e) {
console.log('onClick', e.detail)
switch(e.detail.index) {
case 0:
this.camera()
break;
case 1:
this.image()
break;
case 2:
this.create()
break;
}
},
image:function(){
wx.showModal({
title:'image',
cancelColor: '#000',
})
},
camera:function(){
wx.showModal({
title:'camera',
cancelColor: '#000',
})
},
create:function(){
wx.showModal({
title:'create',
cancelColor: '#000',
})
}
maddleBar
maddleBar.wxml
<template name="middleBar_logo">
<view style="display: flex; flex-direction:column; align-items: center; font-size: smaller; padding:10rpx; position: relative; width:100%; font-size: larger; font-weight: bolder;" class='wux-royal '>
<view><wux-avatar style="margin: 10rpx 10rpx;" size='large' src='https://636c-cloud1-6g4fn1e1fc689993-1309475441.tcb.qcloud.la/photos/%E7%99%BD%E8%BE%B9%E5%9C%86%E8%A7%92logo.png?sign=e773d7f0b3b6337c6b572ce0a80dd8ba&t=1651971170'></wux-avatar>Magic Mirror</view>
</view>
</template>
person.wxml
<import src="/template/middleBar" />
卡片1
app.json
"wux-card": "./components/lib/card/index",
"wux-wing-blank": "./components/lib/wing-blank/index",
"wux-grids": "./components/lib/grids/index",
"wux-grid": "./components/lib/grid/index"
引入页.js
avatar:'/images/logo/logo.png',
nickName:'孤勇者',
free_times:0,
remain_times:0,
引入页.wxml
<wux-wing-blank size="default">
<wux-card
title="{{nickName}}"
extra="我的账户"
thumb="{{avatar}}"
thumb-style="border-radius: 50%;"
>
<view slot="body">
<wux-grids col="2">
<wux-grid thumb="" label="剩余免费{{free_times}}次" />
<wux-grid thumb="" label="剩余付费{{remain_times}}次" />
</wux-grids>
</view>
<view slot="footer">
</view>
</wux-card>
</wux-wing-blank>
卡片2
<wux-wing-blank size="default">
<wux-card extra="">
<view slot="body">
<wux-popup-select value="{{ value2 }}" options="{{ options2 }}" multiple data-index="2" bind:confirm="onConfirm" bind:valueChange="onValueChange">
<wux-cell thumb="{{inspectOptions}}" title="测肤指标" label='肤色、光滑度、痣、皱纹、黑眼圈···' is-link extra="自定义"></wux-cell>
</wux-popup-select>
<wux-cell thumb="{{analyzeHistory}}" is-link extra="测肤记录"></wux-cell>
</view>
</wux-card>
</wux-wing-blank>
.js
data
inspectOptions:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJpb25pY29uIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+PHRpdGxlPk9wdGlvbnM8L3RpdGxlPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMzIiIGQ9Ik0zNjggMTI4aDgwTTY0IDEyOGgyNDBNMzY4IDM4NGg4ME02NCAzODRoMjQwTTIwOCAyNTZoMjQwTTY0IDI1Nmg4MCIvPjxjaXJjbGUgY3g9IjMzNiIgY3k9IjEyOCIgcj0iMzIiIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMzIiLz48Y2lyY2xlIGN4PSIxNzYiIGN5PSIyNTYiIHI9IjMyIiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjMyIi8+PGNpcmNsZSBjeD0iMzM2IiBjeT0iMzg0IiByPSIzMiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIzMiIvPjwvc3ZnPg==',
analyzeHistory:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJpb25pY29uIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+PHRpdGxlPkFuYWx5dGljczwvdGl0bGU+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIzMiIgZD0iTTM0NCAyODBsODgtODhNMjMyIDIxNmw2NCA2NE04MCAzMjBsMTA0LTEwNCIvPjxjaXJjbGUgY3g9IjQ1NiIgY3k9IjE2OCIgcj0iMjQiIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMzIiLz48Y2lyY2xlIGN4PSIzMjAiIGN5PSIzMDQiIHI9IjI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjMyIi8+PGNpcmNsZSBjeD0iMjA4IiBjeT0iMTkyIiByPSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIzMiIvPjxjaXJjbGUgY3g9IjU2IiBjeT0iMzQ0IiByPSIyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIzMiIvPjwvc3ZnPg==',
value2:['001','002','003','004','005','006'],
options2: [
{
title: '肤色',
value: '001',
},
{
title: ' 光滑度',
value: '002',
},
{
title: '痘、斑、痣',
value: '003',
},
{
title: '皱纹',
value: '004',
},
{
title: '黑眼圈、眼袋',
value: '005',
},
{
title: '毛孔、黑头',
value: '006',
}
],
app.json
"wux-popup-select": "./components/lib/popup-select/index",
"wux-cell-group": "./components/lib/cell-group/index",
"wux-cell": "./components/lib/cell/index"
卡片3
xml
<wux-wing-blank size="default">
<wux-card title="" extra="分享社区" thumb="">
<view slot="body">
<wux-cell thumb="{{articleShare}}" is-link extra="我的贴子"></wux-cell>
<wux-cell thumb="{{articleEnshrine}}" is-link extra="我的收藏"></wux-cell>
</view>
</wux-card>
</wux-wing-blank>
data
articleShare:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJpb25pY29uIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+PHRpdGxlPkRvY3VtZW50IFRleHQ8L3RpdGxlPjxwYXRoIGQ9Ik00MTYgMjIxLjI1VjQxNmE0OCA0OCAwIDAxLTQ4IDQ4SDE0NGE0OCA0OCAwIDAxLTQ4LTQ4Vjk2YTQ4IDQ4IDAgMDE0OC00OGg5OC43NWEzMiAzMiAwIDAxMjIuNjIgOS4zN2wxNDEuMjYgMTQxLjI2YTMyIDMyIDAgMDE5LjM3IDIyLjYyeiIgZmlsbD0ibm9uZSIgc3Ryb2tlPSJjdXJyZW50Q29sb3IiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMzIiLz48cGF0aCBkPSJNMjU2IDU2djEyMGEzMiAzMiAwIDAwMzIgMzJoMTIwTTE3NiAyODhoMTYwTTE3NiAzNjhoMTYwIiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjMyIi8+PC9zdmc+',
articleEnshrine:'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJpb25pY29uIiB2aWV3Qm94PSIwIDAgNTEyIDUxMiI+PHRpdGxlPlN0YXI8L3RpdGxlPjxwYXRoIGQ9Ik00ODAgMjA4SDMwOEwyNTYgNDhsLTUyIDE2MEgzMmwxNDAgOTYtNTQgMTYwIDEzOC0xMDAgMTM4IDEwMC01NC0xNjB6IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIzMiIvPjwvc3ZnPg==',
limit查询加载首页推荐列表
wx:for 循环来加载推荐列表
xml
<wux-wing-blank>
<block wx:for="{{articleList}}" wx:for-item="items">
<view style="height:310rpx; margin-top: 10rpx; margin-bottom: 15rpx; border-radius: 12rpx; overflow: hidden; box-shadow: 0rpx 0rpx 30rpx rgb(216, 216, 216);" bindtap="navToArticle" data-articleid='{{index}}'>
<view style="height: 75%;" class="wux-light--bg">
<view style="width: 40%;height:100%; ;float:left;display:flex;align-items: center;justify-content: center;">
<image style="width:85%;height:82%; ; border-radius: 15rpx" mode="aspectFill" src="{{items.layoutPicture?items.layoutPicture:'/images/logo/logo.png'}}"></image>
</view>
<view style="width: 60%;height:100%; ;float:right;">
<view style="height:50%; ; font-weight: bolder; margin-left: 10rpx; margin-right: 10rpx; display:flex; align-items: center;">
<view style="margin-top:10rpx;" class="wux-ellipsis--l2">{{items.title?items.title:"标题加载中"}}</view>
</view>
<view style="height:50%; ; margin-left: 10rpx; margin-right: 10rpx; display:flex; align-items: center; color:rgb(128, 132, 143)">
<view style="margin-bottom: 10rpx;" class="wux-ellipsis--l2">{{items.subTitle?items.subTitle:"简介加载中"}}</view>
</view>
</view>
</view>
<view style="height: 25%; background-color: rgb(255, 240, 243);" class="wux-energized--border" >
<view style="width: 40%;height:100%; float:left">
<view style="width: 30%;height:100%; ;float:left;display:flex;align-items: center;justify-content: center;">
<wux-avatar src="{{items.userAvatar?items.userAvatar:'/images/logo/logo.png'}}"></wux-avatar>
</view>
<view style="width: 70%;height:100%; ;float:left; display: flex; align-items: center; font-weight: bolder;" class="wux-royal"><view class="wux-ellipsis">{{items.nickName?items.nickName:""}}</view></view>
</view>
<view style="width: 60%;height:100%; ; float:right; font-size: smaller; color:rgb(128, 132, 143) ">
<view style="width: 34%;height:100%; ;float:left; display:flex;align-items: center; justify-content: center;">
<wux-icon type="md-thumbs-up" size="30" class="wux-royal wux-margin-right--5" />
<text>{{items.like?items.like:0}}</text>
</view>
<view style="width: 33%;height:100%; ;float:left; display:flex;align-items: center; justify-content: center;">
<wux-icon type="md-text" size="30" class="wux-royal wux-margin-right--5 wux-margin-top--5" />
<text>{{items.discuss?items.discuss:0}}</text>
</view>
<view style="width: 33%;height:100%; ;float:left; display:flex;align-items: center; justify-content: center;">
<wux-icon type="md-share-alt" size="30" class="wux-royal wux-margin-right--5" />
<text>{{items.share?items.share:0}}</text>
</view>
</view>
</view>
</view>
</block>
</wux-wing-blank>
util.js
const queryDBbyLimt=async function(collectionName,skipNum,limitNum,condition){
return (await db.collection(collectionName)
.where(
condition
)
.orderBy('like', 'desc')
.skip(skipNum)
.limit(limitNum)
.get()
).data
}
home.js
const util=require('../../utils/util')
let app=getApp();
let begin=0;
const step=4;
data
articleList:[
]
onload()
let aList=await util.queryDBbyLimt('Articles',begin,step,{visibility:true});
this.setData({articleList:aList})
getApp().globalData.showArticleList=aList;
console.log(this.data.articleList)
点击跳转事件
navToArticle(e){
var url='/pages/...';
var articleid=e.currentTarget.dataset['articleid']
console.log(`${url}?id=${articleid}`)
}
打开新页面展示文章
navToArticle(e){
var pageUrl='/pages/article/readArticle';
var articleid=e.currentTarget.dataset['articleid']
wx.navigateTo({
url: `${pageUrl}?id=${articleid}`,
})
}
下拉触底加载更多
onReachBottom()
onReachBottom:async function () {
wx.showToast({
icon:'loading',
title: 'loading',
})
begin+=step;
var addList=await util.queryDBbyLimt('Articles',begin,step,{visibility:true});
console.log(addList)
wx.hideToast({})
if(addList==false){
wx.showToast({
icon:'none',
title: '暂无更多内容',
duration:1500
})
}else{
getApp().globalData.showArticleList=getApp().globalData.showArticleList.concat(addList)
this.setData({
articleList:this.data.articleList.concat(addList)
})
}
},
浏览文章页
xml
<view class="container" style="height:{{editorHeight}}px;">
<editor
id="editor"
class="ql-container"
placeholder="{{'正在加载...'}}"
bindready="onEditorReady"
read-only="{{true}}"
>
</editor>
</view>
js
onLoad(options) {
let app=getApp();
this.setData({article:app.globalData.showArticleList[options.id]})
const { windowHeight, platform } = wx.getSystemInfoSync()
let editorHeight = windowHeight
this.setData({ editorHeight })
},
onEditorReady() {
const that = this
let app=getApp()
wx.createSelectorQuery().select('#editor').context(function (res) {
that.editorCtx = res.context
let article=that.data.article;
if(article){
var title=article.title;
that.editorCtx.setContents({
delta:article.content,
fail:res=>{
console.log(res)
}
})
}
}).exec()
},
wxss
@import "/components/editor/assets/iconfont.wxss";
@import "/components/common/lib/weui.wxss";
.container {
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.ql-container {
box-sizing: border-box;
width: 100%;
height: 100%;
font-size: 16px;
line-height: 1.5;
overflow: auto;
padding: 10px 10px 20px 10px;
border: 1px solid #ECECEC;
}
半屏窗
template xml
<template name="middleBar_logo2">
<view style="display: flex; flex-direction:column; align-items: center; font-size: smaller; position: relative; width:100%; font-weight: bolder;" class='wux-light '>
<view><wux-avatar style="margin: 5rpx 5rpx;" size='large' src='https://636c-cloud1-6g4fn1e1fc689993-1309475441.tcb.qcloud.la/photos/%E7%99%BD%E8%BE%B9%E5%9C%86%E8%A7%92logo.png?sign=e773d7f0b3b6337c6b572ce0a80dd8ba&t=1651971170'></wux-avatar>Magic Mirror</view>
</view>
</template>
wxss
.recordBtn{
border-radius: 18rpx;
margin-top: 18rpx;
width: 48%;
font-size: 28rpx;
padding-top: 18rpx;
padding-bottom: 18rpx;
background-color: #fff;
text-align: center;
font-weight: bolder;
}
.settingBtn{
width: 100%;
border-radius: 18rpx;
margin-top: 18rpx;
padding-top: 15rpx;
padding-bottom: 15rpx;
background-color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
color:rgb(155, 155, 155);
}
app.json
"useExtendedLib": {
"weui": true
},
usingcomponent
"mp-half-screen-dialog": "weui-miniprogram/half-screen-dialog/half-screen-dialog"
12.Shop购物页
json
"wux-row": "./components/lib/row/index",
"wux-col": "./components/lib/col/index"
xml
<wux-row>
<wux-col span="6" wx:for="{{goodsList}}" wx:for-key="{{item._id}}">
<view style="height:350rpx;border-radius: 15rpx;overflow: hidden;margin: 15rpx 15rpx 15rpx 15rpx;box-shadow: 0 0 20rpx rgb(216, 209, 255);">
<view style="height:280rpx; ;bottom: 0rpx; background-image: url('{{item.imgurl}}'); background-repeat: no-repeat;background-size:contain;background-position: center">
</view>
<view style="height:70rpx; background-color: white; bottom: 0rpx; color: gray;">
<view style="width: 34%;height:100%; ;float:left; display:flex;align-items: center; justify-content: center;">
<wux-icon type="md-eye" size="22" class="wux-calm wux-margin-right--5" />
<text>{{item.watching}}</text>
</view>
<view style="width: 33%;height:100%; ;float:left; display:flex;align-items: center; justify-content: center;">
<wux-icon type="md-text" size="22" class="wux-calm wux-margin-right--5" />
<text>{{item.discussion}}</text>
</view>
<view style="width: 33%;height:100%; ;float:left; display:flex;align-items: center; justify-content: center;">
<wux-icon type="md-star" size="22" class="wux-calm wux-margin-right--5" />
<text>{{item.stars}}</text>
</view>
</view>
</view>
</wux-col>
</wux-row>
js
data
goodsList:[
{
_id:0,
imgurl:'http://cdn.skyvow.cn/logo.png',
navurl:'',
goodsInfo:'',
watching:0,
discussion:0,
stars:0
},
{
_id:1,
imgurl:'http://cdn.skyvow.cn/logo.png',
navurl:'',
goodsInfo:'',
watching:0,
discussion:0,
stars:0
},
{
_id:2,
imgurl:'http://cdn.skyvow.cn/logo.png',
navurl:'',
goodsInfo:'',
watching:0,
discussion:0,
stars:0
},
{
_id:3,
imgurl:'http://cdn.skyvow.cn/logo.png',
navurl:'',
goodsInfo:'',
watching:0,
discussion:0,
stars:0
},
]
onload()
onLoad: async function (options) {
let app=getApp();
const db=wx.cloud.database()
let goods=await db.collection('Goods').where({}).limit(8).get()
goods=goods.data
console.log(goods)
app.globalData.goods=goods
this.setData({
pageHeight:app.globalData.windowHeight,
['userInfo.avatarUrl']:app.globalData.userInfo.avatarUrl,
['userInfo.nickName']:app.globalData.userInfo.nickName,
['userInfo.bgc']:app.globalData.presentTheme.lightBgColor,
presentTheme:app.globalData.presentTheme,
themeName:app.globalData.presentTheme.deepColor.match(/wux-(\S*)/)[1],
goodsList:goods,
})
var j=0;
var tabs1=[];
var contents=[];
for(var i=0;i<goods.length;i++){
contents.push(goods[i].product_name)
if((i+1)%3==0){
tabs1[j]={
key:"tab"+(j+1),
title:"推荐"+(j+1),
content:contents
}
console.log(contents)
contents=[]
j++
}
}
if(i>=1){
if(contents!=false){
tabs1.push({
key:"tab"+(j+1),
title:"推荐"+(j+1),
content:contents}
)
}
this.setData({
tabs1:tabs1,
tabsHeight:tabs1.length*87<520?tabs1.length*87:520
})
}
},
13.富文本编辑及提交存储
程序流程逻辑
具体实现
wxss
@import "../common/lib/weui.wxss";
@import "./assets/iconfont.wxss";
.container {
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.ql-container {
box-sizing: border-box;
width: 100%;
height: 100%;
font-size: 16px;
line-height: 1.5;
overflow: auto;
padding: 10px 10px 20px 10px;
border: 1px solid #ECECEC;
}
.ql-active {
color: #7044ffc4;
}
.iconfont {
display: inline-block;
width: 30px;
height: 30px;
cursor: pointer;
font-size: 20px;
}
.toolbar {
box-sizing: border-box;
padding: 0 10px;
height: 50px;
width: 100%;
position: fixed;
left: 0;
right: 100%;
bottom: 0;
display: flex;
align-items: center;
justify-content: space-between;
border: 1px solid #ECECEC;
border-left: none;
border-right: none;
}
wxml
<view style="height:{{editorHeight}}px;">
<editor id="editor" class="ql-container" placeholder="{{placeholder}}" bindstatuschange="onStatusChange" bindready="onEditorReady">
</editor>
<wux-wing-blank size="default">
<wux-button block outline type="royal" bindtap="submit">提交</wux-button>
</wux-wing-blank>
<wux-backdrop id="wux-backdrop" />
</view>
<view class="toolbar" catchtouchend="format" hidden="{{keyboardHeight > 0 ? false : true}}" show-img-toolbar="{{true}}" style="bottom: {{isIOS ? keyboardHeight : 0}}px">
<i class="iconfont " bindtap="indentClick"> ? </i>
<i class="iconfont icon-charutupian" catchtouchend="insertImage"></i>
<i class="iconfont icon-format-header-2 {{formats.header === 2 ? 'ql-active' : ''}}" data-name="header" data-value="{{2}}"></i>
<i class="iconfont icon-format-header-3 {{formats.header === 3 ? 'ql-active' : ''}}" data-name="header" data-value="{{3}}"></i>
<i class="iconfont icon-zitijiacu {{formats.bold ? 'ql-active' : ''}}" data-name="bold"></i>
<i class="iconfont icon-zitixieti {{formats.italic ? 'ql-active' : ''}}" data-name="italic"></i>
<i class="iconfont icon-zitixiahuaxian {{formats.underline ? 'ql-active' : ''}}" data-name="underline"></i>
<i class="iconfont icon-juzhongduiqi {{formats.align === 'center' ? 'ql-active' : ''}}" data-name="align" data-value="center"></i>
<i class="iconfont icon-zuoyouduiqi {{formats.align === 'justify' ? 'ql-active' : ''}}" data-name="align" data-value="justify"></i>
<i class="iconfont icon-youduiqi {{formats.align === 'right' ? 'ql-active' : ''}}" data-name="align" data-value="right"></i>
<i class="iconfont icon-youxupailie {{formats.list === 'ordered' ? 'ql-active' : ''}}" data-name="list" data-value="ordered"></i>
<i class="iconfont icon-wuxupailie {{formats.list === 'bullet' ? 'ql-active' : ''}}" data-name="list" data-value="bullet"></i>
<i class="iconfont icon-undo {{formats.redo === 'undo' ? 'ql-active' : ''}}" data-name='undo' bindtap="undo"></i>
</view>
js
const util=require('../../utils/util')
import { $wuxBackdrop } from '../../components/lib/index'
Page({
data: {
locks:0,
show:false,
formats: {},
readOnly: false,
placeholder: '分享你的护肤小妙招吧!',
editorHeight: 300,
keyboardHeight: 0,
isIOS: false,
tempimageid: 1,
imageid: 1,
image:[
],
result:[]
},
readOnlyChange() {
this.setData({
readOnly: !this.data.readOnly
})
},
onLoad() {
const platform = wx.getSystemInfoSync().platform
this.$wuxBackdrop = $wuxBackdrop()
const isIOS = platform === 'ios'
this.setData({ isIOS})
const that = this
this.updatePosition(0)
let keyboardHeight = 0
wx.onKeyboardHeightChange(res => {
if (res.height === keyboardHeight) return
const duration = res.height > 0 ? res.duration * 1000 : 0
keyboardHeight = res.height
setTimeout(() => {
wx.pageScrollTo({
scrollTop: 0,
success() {
that.updatePosition(keyboardHeight)
that.editorCtx.scrollIntoView()
}
})
}, duration)
})
},
updatePosition(keyboardHeight) {
const toolbarHeight = 50
const { windowHeight, platform } = wx.getSystemInfoSync()
let editorHeight = keyboardHeight > 0 ? (windowHeight - keyboardHeight - toolbarHeight-60) : (windowHeight-80)
this.setData({ editorHeight, keyboardHeight })
},
calNavigationBarAndStatusBar() {
const systemInfo = wx.getSystemInfoSync()
const { statusBarHeight, platform } = systemInfo
const isIOS = platform === 'ios'
const navigationBarHeight = isIOS ? 44 : 48
return statusBarHeight + navigationBarHeight
},
onEditorReady() {
const that = this
let app=getApp()
wx.createSelectorQuery().select('#editor').context(function (res) {
that.editorCtx = res.context
let article=getApp().globalData.article;
if(article){
that.editorCtx.setContents({
delta:article.delta,
fail:res=>{
console.log(res)
}
})
}
}).exec()
},
blur() {
this.editorCtx.blur()
},
undo(){
this.editorCtx.undo()
}
,
format(e) {
let { name, value } = e.target.dataset
if (!name) return
this.editorCtx.format(name, value)
},
onStatusChange(e) {
const formats = e.detail
this.setData({ formats })
},
insertDivider() {
this.editorCtx.insertDivider({
success: function () {
console.log('insert divider success')
}
})
},
clear() {
let that=this;
this.editorCtx.clear({
success: function (res) {
console.log("clear success")
}
})
},
removeFormat() {
this.editorCtx.removeFormat()
},
insertDate() {
const date = new Date()
const formatDate = `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()}`
this.editorCtx.insertText({
text: formatDate
})
},
insertImage() {
const that = this
var max=1
wx.chooseImage({
count: max,
success: function (res) {
let tmpId=that.data.tempimageid
var i = 0
that.editorCtx.insertImage({
src: res.tempFilePaths[i],
data: {
id: tmpId,
},
width: '100%',
success: function () {
that.data.image.push({id:tmpId,url:res.tempFilePaths[i]})
that.data.tempimageid=tmpId+1;
console.log(tmpId+" "+res.tempFilePaths[i]);
}
})
}
})
}
,
submit: function(){
let app=getApp()
let that=this;
var time= util.formatTime(new Date());
var visible=true;
let title='';
let subTitle='';
that.editorCtx.getContents({
success:async function(res){
if(!res.text.replace(/\s+/g, '').length ==0 ){
let images=that.data.image;
console.log(images.length+"张临时图片")
var length = res.delta.ops.length;
wx.showLoading({
title: '正在上传',
})
for (var i = 0; i < length; i++) {
if(res.delta.ops[i].attributes){
let imageId=that.data.imageid;
var _id =parseInt(res.delta.ops[i].attributes['data-custom'].trim().slice(3));
for (var j = 0; j < images.length; j++){
if (_id === images[j].id){
res.delta.ops[i].insert.image = await util.uploadFile('image',images[j].url,'articlePictures/'+ (new Date().getTime())+'.png',{
id: imageId
});
that.data.result.push(res.delta.ops[i].insert.image);
console.log(_id+" "+that.data.result[imageId-1])
that.data.imageid=imageId+1;
break;
}
}
}
}
console.log(that.data.result.length+"张实际图片")
var strify = JSON.stringify(res.delta);
console.log(strify);
utils.addDB('Articles',{
'title':title,
'subTitle':subTitle,
'images': that.data.result,
'content':res.delta,
'time':time,
'like':0,
'discuss':0,
'collect':0,
'visibility': visible
});
wx.hideLoading({
success: (res) => {
console.log('文章发布成功')
},
})
var arrParse = JSON.parse(strify);
console.log(arrParse);
} else {
wx.showModal({
showCancel: false,
title: '提示',
content: '内容不能为空或仅包含图片'
})
return;
}
}
});
},
onUnload: function () {
let that=this;
that.editorCtx.getContents({
success: res=>{
let content=res;
if (!res.text.replace(/\s+/g, '').length ==0) {
wx.showModal({
title: '要放弃编辑的内容吗?',
confirmColor:'#f00',
success (res) {
if (res.confirm) {
console.log('用户点击确认');
that.clear();
} else if (res.cancel) {
console.log('用户点击取消');
getApp().globalData.article=content
console.log(content)
}
}
})
}
}
});
},
indentClick(){
this.editorCtx.insertText({
text: "??"
})
},
retain() {
this.$wuxBackdrop.retain()
this.setData({
locks: this.$wuxBackdrop.backdropHolds,
show:true
})
},
release() {
this.$wuxBackdrop.release()
this.setData({
locks: this.$wuxBackdrop.backdropHolds,
show:false
})
}
})
以上是对本人前段时间开发魔镜微信小程序过程中所写的代码和笔记,上传到CSDN方便日后复习回顾。
|