@ -0,0 +1,6 @@ |
|||
let server = require('./server/index') |
|||
module.exports = { |
|||
// ...require('././cacheServer/index'),
|
|||
...server, |
|||
// ...require('././utils/index'),
|
|||
} |
@ -0,0 +1,165 @@ |
|||
let request = require('../../utils/request') |
|||
module.exports = { |
|||
// /**
|
|||
// *
|
|||
// * @param {jsCode,userInfo} data
|
|||
// */
|
|||
// login(data){
|
|||
// return request.xhr({
|
|||
// url: `api/v1/login`,
|
|||
// method: `post`,
|
|||
// data
|
|||
// })
|
|||
// },
|
|||
// getUser(){
|
|||
// return request.xhr({
|
|||
// url: `api/user`
|
|||
// })
|
|||
// }
|
|||
// 获取设备分类
|
|||
getEquipmentCategory() { |
|||
return request.xhr({ |
|||
url: `api/v1/dict/pid/1`, |
|||
}) |
|||
}, |
|||
// 获取设备品牌
|
|||
getEquipmentBrand() { |
|||
return request.xhr({ |
|||
url: `api/v1/dict/pid/2`, |
|||
}) |
|||
}, |
|||
// 获取年限
|
|||
getyearsList() { |
|||
return request.xhr({ |
|||
url: `api/v1/dict/8/years`, |
|||
}) |
|||
}, |
|||
// 获取臂长
|
|||
getboom_list() { |
|||
return request.xhr({ |
|||
url: `api/v1/dict/pid/3`, |
|||
}) |
|||
}, |
|||
// 获取底盘
|
|||
getchassis_list() { |
|||
return request.xhr({ |
|||
url: `api/v1/dict/pid/4`, |
|||
}) |
|||
}, |
|||
// 获取装载方量
|
|||
getvolume_list() { |
|||
return request.xhr({ |
|||
url: `api/v1/dict/pid/5`, |
|||
}) |
|||
}, |
|||
// 获取最大传输量
|
|||
getmaxtransmission_list() { |
|||
return request.xhr({ |
|||
url: `api/v1/dict/pid/6`, |
|||
}) |
|||
}, |
|||
// 分页查询设备列表
|
|||
/** |
|||
* |
|||
* @param {key,categoryId,brandId,jobId,currentPage,pageSize} data |
|||
*/ |
|||
getEquipmentSearch(data) { |
|||
return request.xhr({ |
|||
url: `api/v1/equipment/search`, |
|||
method: `get`, |
|||
data |
|||
}) |
|||
}, |
|||
// 根据id获取设备详情
|
|||
getEquipmentById(id) { |
|||
return request.xhr({ |
|||
url: `api/v1/equipment/${id}`, |
|||
method: `get` |
|||
}) |
|||
}, |
|||
/** |
|||
* 收藏 |
|||
*/ |
|||
addCollection(id) { |
|||
return request.xhr({ |
|||
url: `api/v1/collection/add/${id}`, |
|||
method: 'post', |
|||
//data
|
|||
}) |
|||
}, |
|||
/** |
|||
* 取消收藏 |
|||
*/ |
|||
cancelCollection(id) { |
|||
return request.xhr({ |
|||
url: `api/v1/collection/cancel/${id}`, |
|||
method: 'PUT', |
|||
}) |
|||
}, |
|||
// 获取电话
|
|||
getPhone(id) { |
|||
return request.xhr({ |
|||
url: `api/v1/equipment/phone/${id}`, |
|||
method: `get` |
|||
|
|||
// data
|
|||
}) |
|||
}, |
|||
/** |
|||
* 刷新 |
|||
*/ |
|||
refreshMyJobRec(id) { |
|||
return request.xhr({ |
|||
url: `api/v1/equipment/refresh/${id}`, |
|||
method: 'PUT' |
|||
}) |
|||
}, |
|||
/** |
|||
* 置顶 |
|||
*/ |
|||
upthrustTop(id) { |
|||
return request.xhr({ |
|||
url: `api/v1/equipment/top/${id}`, |
|||
method: 'PUT' |
|||
}) |
|||
}, |
|||
// -----------------------------
|
|||
// 根据code获取品牌
|
|||
getgetbrand_list(data) { |
|||
return request.xhr({ |
|||
url: `api/v1/dict/2/${data}`, |
|||
method: `get` |
|||
}) |
|||
}, |
|||
// 添加设备信息
|
|||
addequipment(data) { |
|||
return request.xhr({ |
|||
url: `api/v1/equipment`, |
|||
method: 'post', |
|||
data |
|||
}) |
|||
}, |
|||
// 编辑设备信息
|
|||
eidtequipment(data){ |
|||
return request.xhr({ |
|||
url: `api/v1/equipment`, |
|||
method: 'put', |
|||
data |
|||
}) |
|||
}, |
|||
// 卖家主页,根据id查用户信息列表
|
|||
getEquipmentListbyid(data){ |
|||
return request.xhr({ |
|||
url: `api/v1/equipment/userid`, |
|||
method: 'get', |
|||
data |
|||
}) |
|||
}, |
|||
// 获取最近一次发布时候的联系人和电话
|
|||
get_newshowinfo(){ |
|||
return request.xhr({ |
|||
url: `api/v1/equipment/history`, |
|||
method: 'get' |
|||
}) |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
var login = require('./login'); |
|||
var home = require('./home'); |
|||
var my = require('./my'); |
|||
module.exports = { |
|||
...login, |
|||
...home, |
|||
...my, |
|||
} |
@ -0,0 +1,19 @@ |
|||
let request = require('../../utils/request') |
|||
module.exports = { |
|||
/** |
|||
* |
|||
* @param {jsCode,userInfo} data |
|||
*/ |
|||
login(data){ |
|||
return request.xhr({ |
|||
url: `api/v1/login`, |
|||
method: `post`, |
|||
data |
|||
}) |
|||
}, |
|||
getUser(){ |
|||
return request.xhr({ |
|||
url: `api/v1/userinfo` |
|||
}) |
|||
} |
|||
} |
@ -0,0 +1,152 @@ |
|||
let request = require('../../utils/request') |
|||
let baseUrl = getApp().globalData.cacheServerUrl |
|||
module.exports = { |
|||
// 查询浏览记录
|
|||
getBrowseSearch(data) { |
|||
return request.xhr({ |
|||
url: `api/v1/browse/search`, |
|||
method: `get`, |
|||
data |
|||
}) |
|||
}, |
|||
// 查询收藏记录
|
|||
getCollectionSearch(data) { |
|||
return request.xhr({ |
|||
url: `api/v1/collection/search`, |
|||
method: `get`, |
|||
data |
|||
}) |
|||
}, |
|||
/** |
|||
* 获取充值列表 |
|||
*/ |
|||
getCloudRechargeList() { |
|||
return request.xhr({ |
|||
method: `get`, |
|||
// url: `api/product/page`
|
|||
url: `api/v1/recharge` |
|||
}) |
|||
}, |
|||
/** |
|||
* 修改用户信息 |
|||
* @param {*} data |
|||
*/ |
|||
userUpdate(data) { |
|||
return request.xhr({ |
|||
url: 'api/v1/user', |
|||
method: 'PUT', |
|||
data |
|||
}) |
|||
}, |
|||
|
|||
/** |
|||
* 微信充值 |
|||
*/ |
|||
wxpay(id) { |
|||
return request.xhr({ |
|||
url: `api/v1/wxpay/${id}` |
|||
}) |
|||
}, |
|||
/** |
|||
* 获取云币列表 |
|||
*/ |
|||
getCurrencyList(data) { |
|||
return request.xhr({ |
|||
url: `api/v1/currency/search`, |
|||
data |
|||
}) |
|||
}, |
|||
/** |
|||
* 获取我的云币 |
|||
*/ |
|||
getMyCurrency() { |
|||
return request.xhr({ |
|||
url: `api/v1/currency`, |
|||
}) |
|||
}, |
|||
// 获取识别身份证token
|
|||
getAuthToken() { |
|||
return request.xhr({ |
|||
url: `api/v1/auth/baidu/accesstoken` |
|||
}) |
|||
}, |
|||
//实名认证
|
|||
autonym(data) { |
|||
return request.xhr(data) |
|||
}, |
|||
|
|||
/** |
|||
* 获取当前账号实名认证信息 |
|||
*/ |
|||
getCertification(){ |
|||
return request.xhr({ |
|||
url: `api/v1/certification` |
|||
}) |
|||
}, |
|||
/** |
|||
* 获取消息列表 |
|||
*/ |
|||
getMessageList(data) { |
|||
// console.log(data)
|
|||
return request.xhr({ |
|||
url: `api/v1/message/search`, |
|||
method:'get', |
|||
data |
|||
}) |
|||
}, |
|||
/** |
|||
* 未读消息记录 |
|||
*/ |
|||
getUnreadMessageList(data) { |
|||
// console.log(data)
|
|||
return request.xhr({ |
|||
url: `api/v1/message/unread`, |
|||
method:'get', |
|||
data |
|||
}) |
|||
}, |
|||
setmessage(){ |
|||
return request.xhr({ |
|||
url: `api/v1/message`, |
|||
method:'put' |
|||
}) |
|||
}, |
|||
// 我的发布
|
|||
getmyequipment(data){ |
|||
return request.xhr({ |
|||
url: `api/v1/my/equipment`, |
|||
method:'get', |
|||
data |
|||
}) |
|||
}, |
|||
// 更改设备状态
|
|||
setEquipmentState(data){ |
|||
return request.xhr({ |
|||
url: `api/v1/equipment/state/${data.value}/${data.id}`, |
|||
method:'put' |
|||
}) |
|||
}, |
|||
// y优惠卡
|
|||
// 获取优惠卡
|
|||
getCardintro(){ |
|||
return request.xhr({ |
|||
url: `api/v1/cardintro`, |
|||
method:'get', |
|||
}) |
|||
}, |
|||
getRecharge(){//充值列表
|
|||
return request.xhr({ |
|||
url: `api/v1/recharge`, |
|||
method:'get', |
|||
}) |
|||
}, |
|||
// 支付接口
|
|||
wxpay(data){//充值列表
|
|||
return request.xhr({ |
|||
url: `api/v1/wxpay/${data.id}/${data.type}`, |
|||
method:'get', |
|||
}) |
|||
} |
|||
|
|||
|
|||
} |
@ -0,0 +1,79 @@ |
|||
// app.js
|
|||
App({ |
|||
onLaunch() { |
|||
// 展示本地存储能力
|
|||
// const logs = wx.getStorageSync('logs') || []
|
|||
// logs.unshift(Date.now())
|
|||
// wx.setStorageSync('logs', logs)
|
|||
|
|||
// 登录
|
|||
wx.login({ |
|||
success: res => { |
|||
// 发送 res.code 到后台换取 openId, sessionKey, unionId
|
|||
} |
|||
}) |
|||
}, |
|||
|
|||
globalData: { |
|||
userInfo: null, |
|||
// serverUrl:'http://81.71.138.239:8001/', //测试服务器
|
|||
serverUrl:'https://znyunchecloudcar.cn/', //正式服务器
|
|||
// serverUrl:"http://localhost:8001/",//本地
|
|||
imageServerUrl: 'https://zhongnengyunche.com/recruitment/wx/static/images/', //统一图片地址
|
|||
cacheServerUrl:'' |
|||
}, |
|||
onShow(){ |
|||
// 获取首页轮播
|
|||
wx.request({ |
|||
url: this.globalData.serverUrl+'api/v1/banners', |
|||
success: res => { |
|||
wx.setStorage({ |
|||
key: 'banner', |
|||
data: res.data.data |
|||
}) |
|||
}, |
|||
fail: e => { |
|||
} |
|||
}) |
|||
// 获取首页邀请人
|
|||
wx.request({ |
|||
url: this.globalData.serverUrl+'api/v1/register/newusers', |
|||
success: res => { |
|||
wx.setStorage({ |
|||
key: 'sentiment', |
|||
data: res.data.data |
|||
}) |
|||
}, |
|||
fail: e => { |
|||
} |
|||
}) |
|||
}, |
|||
global_share(title) { |
|||
let userInfo = wx.getStorageSync('userInfo'); |
|||
return { |
|||
title: title || '上云车二手', |
|||
path: `pages/index/index?shareType=newusers&userId=${ userInfo.id }`, |
|||
// imageUrl: this.globalData.imageServerUrl+'common/share_image_new.png'
|
|||
imageUrl:'/images/common/share_image_news2.png' |
|||
} |
|||
}, |
|||
share_add_integral(shareType, userId) { |
|||
|
|||
let data = { |
|||
userId, |
|||
shareType |
|||
} |
|||
if(userId){ |
|||
console.log(22222) |
|||
const http = require('./utils/request') |
|||
http.xhr({ |
|||
url: `api/v1/share/${data.shareType}/${data.userId}`, |
|||
//data
|
|||
method:'Get' |
|||
}).then(res => { |
|||
console.log(res) |
|||
console.log('点好友链接进入') |
|||
}) |
|||
} |
|||
}, |
|||
}) |
@ -0,0 +1,31 @@ |
|||
{ |
|||
"pages": [ |
|||
"pages/index/index", |
|||
"pages/new_show/new_show", |
|||
"pages/detail/detail", |
|||
"pages/seller_home/seller_home", |
|||
"pages/mine/mine", |
|||
"pages/my_release/my_release", |
|||
"pages/my_obtain/my_obtain", |
|||
"pages/my_record/my_record", |
|||
"pages/my_collection/my_collection", |
|||
"pages/my_notice/my_notice", |
|||
"pages/my_realname/my_realname", |
|||
"pages/my_realname/protocol/protocol", |
|||
"pages/my_notice/my_notice_item/my_notice_item", |
|||
"pages/my_obtain/rule/rule", |
|||
"pages/publishFinish/publishFinish", |
|||
"pages/login/login", |
|||
"pages/my_feedback/my_feedback", |
|||
"pages/my_release/my_release_detail/my_release_detail" |
|||
], |
|||
"window": { |
|||
"backgroundTextStyle": "light", |
|||
"navigationBarTitleText": "云车二手", |
|||
"navigationBarBackgroundColor": "#3476FE", |
|||
"navigationBarTextStyle": "white" |
|||
}, |
|||
"style": "v2", |
|||
"sitemapLocation": "sitemap.json", |
|||
"lazyCodeLoading": "requiredComponents" |
|||
} |
@ -0,0 +1,68 @@ |
|||
/**app.wxss**/ |
|||
.container { |
|||
height: 100%; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
padding: 200rpx 0; |
|||
box-sizing: border-box; |
|||
} |
|||
pages { |
|||
position: relative; |
|||
z-index: 9999998; |
|||
background: #fff; |
|||
} |
|||
.wx-switch-input{ |
|||
width:80rpx !important; |
|||
height:44rpx !important |
|||
} |
|||
.wx-switch-input::before{ |
|||
width:80rpx !important; |
|||
height: 44rpx !important; |
|||
} |
|||
.wx-switch-input::after{ |
|||
width: 42rpx !important; |
|||
height: 44rpx !important; |
|||
margin-top: -3rpx; |
|||
} |
|||
|
|||
.red{ |
|||
color: #FF0000; |
|||
} |
|||
view ,text{ |
|||
/* 苹方-简 常规体 */ |
|||
font-family: PingFangSC-Regular, sans-serif; |
|||
} |
|||
|
|||
.very_samll { |
|||
/* 苹方-简 极细体 */ |
|||
font-family: PingFangSC-Ultralight, sans-serif; |
|||
} |
|||
|
|||
.samll { |
|||
/* 苹方-简 细体 */ |
|||
font-family: PingFangSC-Light, sans-serif; |
|||
} |
|||
|
|||
/* //苹方-简 纤细体 |
|||
font-family: PingFangSC-Thin, sans-serif; |
|||
*/ |
|||
.dark { |
|||
/* 苹方-简 中黑体 */ |
|||
font-family: PingFangSC-Medium, sans-serif; |
|||
} |
|||
|
|||
.dark_thick { |
|||
/* 苹方-简 中粗体 */ |
|||
font-family: PingFangSC-Semibold, sans-serif; |
|||
} |
|||
view{ |
|||
word-break:break-all; |
|||
} |
|||
/* 隐藏滚动条 */ |
|||
::-webkit-scrollbar { |
|||
width: 0; |
|||
height: 0; |
|||
color: transparent; |
|||
} |
@ -0,0 +1,453 @@ |
|||
// components/buycar/buycar.js
|
|||
const axios = require('../../api/index') |
|||
let http = require('../../utils/request') |
|||
|
|||
let loading = false |
|||
Component({ |
|||
/** |
|||
* 组件的属性列表 |
|||
*/ |
|||
properties: { |
|||
|
|||
}, |
|||
|
|||
/** |
|||
* 组件的初始数据 |
|||
*/ |
|||
data: { |
|||
// 轮播
|
|||
banner_img: [], |
|||
// 人气数据
|
|||
sentiment_list: [], |
|||
// 获取列表
|
|||
key: '', |
|||
currentPage: 1, |
|||
pageSize:5, |
|||
list_total: 0, |
|||
// 筛选
|
|||
condition_list: [{ |
|||
name: '分类', |
|||
id: 0 |
|||
}, { |
|||
name: '品牌', |
|||
id: 0 |
|||
}, |
|||
{ |
|||
name: '年限', |
|||
id: 0 |
|||
}, |
|||
], |
|||
is_scrool_top: false, |
|||
autoLoad: false, |
|||
// 筛选的第几个
|
|||
condition_idx: null, |
|||
// 分类列表
|
|||
classification_list: [], |
|||
classification_idx: 0, |
|||
classification_id: 0, |
|||
// 品牌列表
|
|||
brand_code:'',//品牌
|
|||
brand_list: [], |
|||
brand_idx: 0, |
|||
brand_id: 0, |
|||
// 年限列表
|
|||
years_list: [], |
|||
time_idx: 0, |
|||
time_id: 0, |
|||
// 商品列表
|
|||
showlist: [], |
|||
have_data: true, |
|||
triggered: false, |
|||
refresh: true, |
|||
|
|||
}, |
|||
lifetimes: { |
|||
created() { |
|||
this.getEquipmentCategory(); |
|||
this.getEquipmentBrand(); |
|||
this.getyearsList(); |
|||
this.get_list(); |
|||
this.getSentiment(); |
|||
this.getBanner(); |
|||
}, |
|||
attached: function () { |
|||
// this.getSysdata()
|
|||
}, |
|||
detached: function () { |
|||
// 在组件实例被从页面节点树移除时执行
|
|||
}, |
|||
}, |
|||
/** |
|||
* 组件的方法列表 |
|||
*/ |
|||
methods: { |
|||
// 点击轮播
|
|||
click_banner(e){ |
|||
console.log(e.currentTarget.dataset.data) |
|||
var data=(e.currentTarget.dataset.data) |
|||
if(data.type==1){ |
|||
console.log('电话') |
|||
console.log(data.phone) |
|||
wx.makePhoneCall({ |
|||
phoneNumber: data.phone, |
|||
}) |
|||
}else if(data.type==2){ |
|||
console.log('链接') |
|||
let token = wx.getStorageSync('token') |
|||
if (!token) { |
|||
wx.navigateTo({ |
|||
url: '/pages/login/login?isred=1', |
|||
}) |
|||
return |
|||
} |
|||
wx.navigateTo({ |
|||
url:data.pageUrl, |
|||
}) |
|||
}else{ |
|||
return |
|||
} |
|||
}, |
|||
getSentiment() { |
|||
var sentiment_list = wx.getStorageSync('sentiment') |
|||
console.log(sentiment_list) |
|||
if (!sentiment_list) { |
|||
http.xhr({ |
|||
url: 'api/v1/register/newusers' |
|||
}).then(res => { |
|||
console.log(res) |
|||
var sentiment_list = res.data |
|||
this.setData({ |
|||
sentiment_list |
|||
}) |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
} else { |
|||
setTimeout(() => { |
|||
this.setData({ |
|||
sentiment_list |
|||
}) |
|||
}, 0) |
|||
} |
|||
}, |
|||
// 获取轮播
|
|||
getBanner() { |
|||
var banner_img = wx.getStorageSync('banner') |
|||
if (!banner_img) { |
|||
http.xhr({ |
|||
url: 'api/v1/banners' |
|||
}).then(res => { |
|||
var banner_img = res.data |
|||
this.setData({ |
|||
banner_img |
|||
}) |
|||
}) |
|||
} else { |
|||
setTimeout(() => { |
|||
this.setData({ |
|||
banner_img |
|||
}) |
|||
}, 0) |
|||
} |
|||
}, |
|||
// 获取分类列表
|
|||
getEquipmentCategory() { |
|||
var that = this |
|||
axios.getEquipmentCategory().then(res => { |
|||
console.log(res) |
|||
var list = res.data |
|||
list.unshift({ |
|||
name: '全部分类', |
|||
id: 0 |
|||
}) |
|||
wx.setStorage({ |
|||
key: 'classification', |
|||
data: res.data |
|||
}) |
|||
that.setData({ |
|||
classification_list: res.data, |
|||
}) |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
}, |
|||
// 获取品牌列表
|
|||
getEquipmentBrand() { |
|||
var that = this |
|||
axios.getEquipmentBrand().then(res => { |
|||
console.log(res) |
|||
var list = res.data |
|||
list.unshift({ |
|||
name: '全部品牌', |
|||
id: 0 |
|||
}) |
|||
wx.setStorage({ |
|||
key: 'brand', |
|||
data: res.data |
|||
}) |
|||
that.setData({ |
|||
brand_list: res.data, |
|||
}) |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
// 根据分类查询品牌
|
|||
// if(this.data.brand_code==''){
|
|||
// axios.getEquipmentBrand().then(res => {
|
|||
// console.log(res)
|
|||
// var list = res.data
|
|||
// list.unshift({
|
|||
// name: '全部品牌',
|
|||
// id: 0
|
|||
// })
|
|||
// wx.setStorage({
|
|||
// key: 'brand',
|
|||
// data: res.data
|
|||
// })
|
|||
// that.setData({
|
|||
// brand_list: res.data,
|
|||
// })
|
|||
// }).catch(err => {
|
|||
// console.log(err)
|
|||
// })
|
|||
// }else{
|
|||
// axios.getgetbrand_list(this.data.brand_code).then(res => {
|
|||
// console.log(res)
|
|||
// var list = res.data
|
|||
// list.unshift({
|
|||
// name: '全部品牌',
|
|||
// id: 0
|
|||
// })
|
|||
// // wx.setStorage({
|
|||
// // key: 'brand',
|
|||
// // data: res.data
|
|||
// // })
|
|||
// that.setData({
|
|||
// brand_list: res.data,
|
|||
// })
|
|||
// }).catch(err => {
|
|||
// console.log(err)
|
|||
// })
|
|||
// }
|
|||
}, |
|||
// 获取品牌列表
|
|||
getyearsList() { |
|||
var that = this |
|||
axios.getyearsList().then(res => { |
|||
console.log(res) |
|||
var list = res.data |
|||
list.unshift({ |
|||
name: '全部年限', |
|||
id: 0 |
|||
}) |
|||
wx.setStorage({ |
|||
key: 'years', |
|||
data: res.data |
|||
}) |
|||
that.setData({ |
|||
years_list: res.data, |
|||
}) |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
}, |
|||
// 获取设备列表click_banner
|
|||
get_list() { |
|||
var data = { |
|||
key: this.data.key, |
|||
categoryId: this.data.classification_id, |
|||
brandId: this.data.brand_id, |
|||
yearId: this.data.time_id, |
|||
currentPage: this.data.currentPage, |
|||
pageSize: this.data.pageSize |
|||
} |
|||
axios.getEquipmentSearch(data).then(res => { |
|||
console.log(res) |
|||
var list_total = res.data.total |
|||
if(this.data.currentPage==1){ |
|||
var list=res.data.list |
|||
}else{ |
|||
var list = [...this.data.showlist, ...res.data.list] |
|||
} |
|||
this.setData({ |
|||
showlist: list, |
|||
list_total, |
|||
refresh: false, |
|||
triggered: false |
|||
}) |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
// getEquipmentSearch
|
|||
}, |
|||
// 重置
|
|||
reset() { |
|||
var condition_list = [{ |
|||
name: '分类', |
|||
id: 0 |
|||
}, { |
|||
name: '品牌', |
|||
id: 0 |
|||
}, |
|||
{ |
|||
name: '年限', |
|||
id: 0 |
|||
}, |
|||
] |
|||
this.setData({ |
|||
classification_idx: 0, |
|||
classification_id: 0, |
|||
time_idx: 0, |
|||
time_id: 0, |
|||
brand_idx: 0, |
|||
brand_id: 0, |
|||
condition_list, |
|||
key: '', |
|||
currentPage: 1, |
|||
pageSize: 5, |
|||
}, () => { |
|||
this.get_list() |
|||
}) |
|||
}, |
|||
// 上拉刷新
|
|||
onScrolltolower() { |
|||
let { |
|||
list_total, |
|||
showlist |
|||
} = this.data; |
|||
if (list_total == showlist.length) return |
|||
this.setData({ |
|||
currentPage: this.data.currentPage + 1, |
|||
}, () => { |
|||
this.get_list() |
|||
}) |
|||
}, |
|||
// 下拉加载
|
|||
onRefresh() { |
|||
this.setData({ |
|||
condition_idx: null, |
|||
|
|||
currentPage: 1, |
|||
refresh: true, |
|||
showlist: [] |
|||
}, () => { |
|||
this.get_list() |
|||
}) |
|||
// this.get_list()
|
|||
// setTimeout(()=>{
|
|||
// this.setData({
|
|||
// triggered:false
|
|||
// })
|
|||
// },1000)
|
|||
}, |
|||
// 点击蒙版 隐藏
|
|||
menus_btn() { |
|||
this.setData({ |
|||
condition_idx: null |
|||
}) |
|||
}, |
|||
// 点击筛选
|
|||
condition_btn(e) { |
|||
let condition_idx = e.currentTarget.dataset.idx; |
|||
if (condition_idx == this.data.condition_idx) { |
|||
this.setData({ |
|||
autoLoad: true, |
|||
condition_idx: null, |
|||
}) |
|||
} else { |
|||
this.setData({ |
|||
condition_idx: e.currentTarget.dataset.idx, |
|||
autoLoad: true, |
|||
}) |
|||
} |
|||
}, |
|||
// 分类项点击
|
|||
classification_btn(e) { |
|||
console.log(e) |
|||
let idx = e.currentTarget.dataset.idx; |
|||
let id = e.currentTarget.dataset.id; |
|||
let { condition_idx, condition_list } = this.data |
|||
condition_list[condition_idx].name = this.data.classification_list[idx].name |
|||
let brand_code=e.currentTarget.dataset.code||'' |
|||
this.setData({ |
|||
brand_code, |
|||
classification_idx: idx, |
|||
classification_id: id, |
|||
autoLoad: false, |
|||
condition_idx: null, |
|||
condition_list, |
|||
currentPage: 1, |
|||
refresh: true, |
|||
// showlist:[]
|
|||
},()=>{ |
|||
this.get_list() |
|||
this.getEquipmentBrand() |
|||
}); |
|||
}, |
|||
// 品牌项点击
|
|||
brand_btn(e) { |
|||
let idx = e.currentTarget.dataset.idx; |
|||
let id = e.currentTarget.dataset.id; |
|||
let { condition_idx, condition_list } = this.data |
|||
condition_list[condition_idx].name = this.data.brand_list[idx].name |
|||
this.setData({ |
|||
brand_idx: idx, |
|||
brand_id: id, |
|||
autoLoad: false, |
|||
condition_idx: null, |
|||
condition_list, |
|||
// showlist:[]
|
|||
currentPage: 1, |
|||
refresh: true, |
|||
},()=>{ |
|||
this.get_list() |
|||
}); |
|||
}, |
|||
// 年限项点击
|
|||
time_btn(e) { |
|||
let idx = e.currentTarget.dataset.idx; |
|||
let id = e.currentTarget.dataset.id; |
|||
let { condition_idx, condition_list } = this.data |
|||
condition_list[condition_idx].name = this.data.years_list[idx].name |
|||
this.setData({ |
|||
time_idx: idx, |
|||
time_id: id, |
|||
autoLoad: false, |
|||
condition_idx: null, |
|||
condition_list, |
|||
// showlist:[]
|
|||
currentPage: 1, |
|||
refresh: true, |
|||
|
|||
},()=>{ |
|||
this.get_list() |
|||
}); |
|||
}, |
|||
// 滑动触发判断筛选
|
|||
nobindscroll(e) { |
|||
this.setData({ |
|||
condition_idx: null |
|||
}) |
|||
if (e.detail.scrollTop >= 220) { |
|||
this.setData({ |
|||
is_scrool_top: true |
|||
}) |
|||
} |
|||
if (e.detail.scrollTop < 220) { |
|||
this.setData({ |
|||
is_scrool_top: false |
|||
}) |
|||
} |
|||
if (this.data.condition_idx == null) { |
|||
return |
|||
} |
|||
// this.setData({
|
|||
// condition_idx: null
|
|||
// })
|
|||
}, |
|||
bindshare(e) { |
|||
// console.log(e)
|
|||
this.triggerEvent('bindshare', { item: e.detail.item }) |
|||
} |
|||
} |
|||
}) |
@ -0,0 +1,7 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
"no_data": "/components/noData/noData", |
|||
"buycar_card": "/components/buycar_card/buycar_card" |
|||
} |
|||
} |
@ -0,0 +1,127 @@ |
|||
|
|||
<scroll-view style="background: #f2f1f6;height: calc(100vh - 88rpx);" scroll-y bindscroll="nobindscroll" refresher-enabled refresher-default-style="black" refresher-background="#fff" refresher-triggered="{{triggered}}" bindrefresherrefresh="onRefresh" bindscrolltolower='onScrolltolower'> |
|||
<!-- 轮播 --> |
|||
<view class="banner"> |
|||
<swiper indicator-dots="{{false}}" autoplay="true" indicator-dots indicator-active-color="#3476fe" interval="3000" duration="500" style='height:248rpx;width:100%;border-radius: 20rpx;' circular="true"> |
|||
<block wx:for="{{banner_img}}" wx:key="picUrl"> |
|||
<swiper-item> |
|||
<view style="width: 100%;height:248rpx;; overflow: hidden; overflow: hidden;" data-data="{{item}}" bindtap="click_banner"> |
|||
<image style="width: 100%;height:248rpx; border-radius: 20rpx;" src="{{item.picUrl}}" mode="aspectFill"> |
|||
</image> |
|||
</view> |
|||
</swiper-item> |
|||
</block> |
|||
</swiper> |
|||
</view> |
|||
<!-- 注册通知 --> |
|||
<view class="sentiment"> |
|||
<view class="sentiment_ico"> |
|||
<image src="/images/index/laba.png" class="sreach_icon" mode="widthFix"> |
|||
</image> |
|||
</view> |
|||
<view class="sentiment_text"> |
|||
<swiper style='height:88rpx;width:100%;border-radius: 20rpx;line-height: 88rpx;' vertical="true" autoplay="true" circular="true" interval="5000"> |
|||
<block wx:for="{{sentiment_list}}" wx:key="item"> |
|||
<swiper-item> |
|||
<text style="margin-right: 15rpx;font-size: 28rpx;">恭喜</text><text style="font-size: 28rpx;">{{item}}</text><text style="margin-left: 15rpx; font-size: 28rpx;">成功注册会员</text> |
|||
</swiper-item> |
|||
</block> |
|||
</swiper> |
|||
</view> |
|||
<!-- <view class="sentiment_img" bindtap="toranking"> |
|||
<image src="{{imageServerUrl}}index/sentiment.png"></image> |
|||
</view> --> |
|||
</view> |
|||
<!-- 筛选 --> |
|||
<view class="condition" wx:if="{{!is_scrool_top}}"> |
|||
<view class="condition_list" wx:for="{{condition_list}}" wx:key='index' data-idx="{{index}}" bindtap="condition_btn"> |
|||
<text style="color:{{ condition_idx == index ? '#3476FE' : '' }}">{{item.name|| item}}</text> |
|||
<image wx:if='{{condition_idx == index}}' src="/images/index/shrink.png"></image> |
|||
<image wx:else src="/images/index/unfold.png"></image> |
|||
</view> |
|||
<!-- 下拉框的封装 --> |
|||
<view class="condition_menus" bindtap="menus_btn" wx:if='{{ condition_idx!== null }}' style="height:calc(100vh - {{ (118 + 50 )}}rpx)"> |
|||
<!-- 分类 --> |
|||
<block wx:if='{{ condition_idx == 0 }}'> |
|||
<scroll-view scroll-y scroll-into-view='a{{ autoLoad ? classification_idx : ""}}'> |
|||
<view class="industry "> |
|||
<text wx:for='{{ classification_list }}' wx:key='index' id='a{{index}}' class="{{ classification_idx == index ? 'active_industry' : '' }}" data-idx="{{index}}" data-id="{{item.id}}" catchtap="classification_btn" data-code="{{item.code}}">{{item.name}}</text> |
|||
</view> |
|||
</scroll-view> |
|||
</block> |
|||
<!-- 品牌 --> |
|||
<block wx:if='{{ condition_idx == 1 }}'> |
|||
<scroll-view scroll-y scroll-into-view='a{{ autoLoad ? brand_idx : ""}}'> |
|||
<view class="industry "> |
|||
<text wx:for='{{ brand_list }}' wx:key='index' id='a{{index}}' class="{{ brand_idx == index ? 'active_industry' : '' }}" data-idx="{{index}}" data-id="{{item.id}}" catchtap="brand_btn">{{item.name}}</text> |
|||
</view> |
|||
</scroll-view> |
|||
</block> |
|||
<!-- 年限 --> |
|||
<block wx:if='{{ condition_idx == 2 }}'> |
|||
<scroll-view scroll-y scroll-into-view='a{{ autoLoad ? time_idx : ""}}'> |
|||
<view class="industry "> |
|||
<text wx:for='{{years_list }}' wx:key='index' id='a{{index}}' class="{{time_idx == index ? 'active_industry' : '' }}" data-idx="{{index}}" data-id="{{item.id}}" catchtap="time_btn">{{item.name}}</text> |
|||
</view> |
|||
</scroll-view> |
|||
</block> |
|||
</view> |
|||
</view> |
|||
<view class="condition" wx:if="{{is_scrool_top}}"></view> |
|||
<!-- 列表 --> |
|||
<view class="showlist"> |
|||
<block wx:if='{{showlist.length != 0}}'> |
|||
<block wx:for="{{showlist}}" wx:key='id'> |
|||
<block> |
|||
<buycar_card id="recruitment" buycar_card_data='{{item}}' bindbindshare="bindshare" /> |
|||
</block> |
|||
</block> |
|||
</block> |
|||
<block wx:if="{{ showlist.length == 0 && !refresh }}"> |
|||
<!-- 未选择筛选条件无数据 --> |
|||
<no_data bindreset='reset' prompt_txt='很抱歉,暂无匹配的信息~' have_data='{{ have_data }}' /> |
|||
<!-- 已选择筛选条件无数据 --> |
|||
</block> |
|||
<view class="bottom_tip" wx:if="{{list_total == showlist.length && list_total != 0 && list_total >= 3 }}"> |
|||
<text> 没有更多内容啦 ~ </text> |
|||
</view> |
|||
</view> |
|||
|
|||
|
|||
</scroll-view> |
|||
<!-- 筛选2 --> |
|||
|
|||
<view class="condition is_scrool_top" wx:if="{{is_scrool_top}}" style="border-radius: 0;"> |
|||
<view class="condition_list" wx:for="{{condition_list}}" wx:key='index' data-idx="{{index}}" bindtap="condition_btn"> |
|||
<text style="color:{{ condition_idx == index ? '#3476FE' : '' }}">{{item.name|| item}}</text> |
|||
<image wx:if='{{condition_idx == index}}' src="/images/index/shrink.png"></image> |
|||
<image wx:else src="/images/index/unfold.png"></image> |
|||
</view> |
|||
<!-- 下拉框的封装 --> |
|||
<view class="condition_menus" bindtap="menus_btn" wx:if='{{ condition_idx!== null }}' style="height:calc(100vh - {{ (118 + 50 )}}rpx)"> |
|||
<!-- 分类 --> |
|||
<block wx:if='{{ condition_idx == 0 }}'> |
|||
<scroll-view scroll-y scroll-into-view='a{{ autoLoad ? classification_idx : ""}}'> |
|||
<view class="industry "> |
|||
<text wx:for='{{ classification_list }}' wx:key='index' id='a{{index}}' class="{{ classification_idx == index ? 'active_industry' : '' }}" data-idx="{{index}}" data-id="{{item.id}}" catchtap="classification_btn">{{item.name}}</text> |
|||
</view> |
|||
</scroll-view> |
|||
</block> |
|||
<!-- 品牌 --> |
|||
<block wx:if='{{ condition_idx == 1 }}'> |
|||
<scroll-view scroll-y scroll-into-view='a{{ autoLoad ? brand_idx : ""}}'> |
|||
<view class="industry "> |
|||
<text wx:for='{{ brand_list }}' wx:key='index' id='a{{index}}' class="{{ brand_idx == index ? 'active_industry' : '' }}" data-idx="{{index}}" data-id="{{item.id}}" catchtap="brand_btn">{{item.name}}</text> |
|||
</view> |
|||
</scroll-view> |
|||
</block> |
|||
<!-- 年限 --> |
|||
<block wx:if='{{ condition_idx == 2 }}'> |
|||
<scroll-view scroll-y scroll-into-view='a{{ autoLoad ? time_idx : ""}}'> |
|||
<view class="industry "> |
|||
<text wx:for='{{years_list }}' wx:key='index' id='a{{index}}' class="{{time_idx == index ? 'active_industry' : '' }}" data-idx="{{index}}" data-id="{{item.id}}" catchtap="time_btn">{{item.name}}</text> |
|||
</view> |
|||
</scroll-view> |
|||
</block> |
|||
</view> |
|||
</view> |
@ -0,0 +1,149 @@ |
|||
/* components/buycar/buycar.wxss */ |
|||
.banner{ |
|||
box-sizing: border-box; |
|||
padding: 15rpx; |
|||
border-radius: 20rpx; |
|||
overflow: hidden; |
|||
} |
|||
.sentiment{ |
|||
width: 750rpx; |
|||
height: 68rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
/* justify-content: space-between; */ |
|||
background: white; |
|||
overflow: hidden; |
|||
} |
|||
.sentiment_ico{ |
|||
margin-left: 20rpx; |
|||
display: flex; |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
} |
|||
.sentiment_ico image{ |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
} |
|||
.sentiment_text{ |
|||
margin-left: 20rpx; |
|||
flex: 1; |
|||
color: #333; |
|||
font-size: 24rpx; |
|||
} |
|||
.sentiment_img{ |
|||
width: 135rpx; |
|||
height: 50rpx; |
|||
border-left: 2rpx solid #f1f1f1; |
|||
padding-left: 10rpx; |
|||
margin-right: 10rpx; |
|||
} |
|||
.sentiment_img image{ |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
.condition{ |
|||
box-sizing: border-box; |
|||
/* overflow: hidden; */ |
|||
margin-top: 12rpx; |
|||
width: 750rpx; |
|||
height: 88rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
font-size: 28rpx; |
|||
z-index: 99; |
|||
position: relative; |
|||
left: 0; |
|||
} |
|||
.condition .condition_list{ |
|||
flex: 1; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
height: 100%; |
|||
background: white; |
|||
} |
|||
|
|||
.condition .condition_list text{ |
|||
max-width: 220rpx; |
|||
white-space: nowrap; |
|||
overflow: hidden; |
|||
text-overflow: ellipsis; |
|||
} |
|||
|
|||
.condition .condition_list image{ |
|||
width: 22rpx; |
|||
height: 14rpx; |
|||
margin-left: 8rpx; |
|||
} |
|||
|
|||
.condition .condition_menus{ |
|||
width: 100vw; |
|||
height: 846rpx; |
|||
position: absolute; |
|||
top: 88rpx; |
|||
z-index: 99; |
|||
background: rgba(51, 51, 51, 0.5); |
|||
display: flex; |
|||
z-index: 99; |
|||
} |
|||
|
|||
.condition .condition_menus scroll-view{ |
|||
flex: 1; |
|||
width: 720rpx; |
|||
height: 540rpx; |
|||
box-sizing: border-box; |
|||
padding: 0 20rpx; |
|||
background: white; |
|||
padding-top: 0; |
|||
box-sizing: border-box; |
|||
} |
|||
.condition>.condition_menus .industry{ |
|||
width: 100%; |
|||
height: auto; |
|||
display: flex; |
|||
flex-wrap: wrap; |
|||
align-items: center; |
|||
justify-content: center; |
|||
background: white; |
|||
padding-bottom: 20rpx; |
|||
box-sizing: border-box; |
|||
padding: 0; |
|||
} |
|||
.is_scrool_top{ |
|||
border-radius: 0rpx; |
|||
border-bottom: 15rpx solid #f8f8f8; |
|||
position: fixed; |
|||
top: 0rpx; |
|||
margin-top: 0; |
|||
width: 750rpx; |
|||
height: 88rpx; |
|||
z-index: 99; |
|||
} |
|||
.bottom_tip{ |
|||
width: 100%; |
|||
height: 100rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
color: #999999; |
|||
font-size: 26rpx; |
|||
} |
|||
.condition_menus .industry text{ |
|||
width: 33.3%; |
|||
overflow: hidden; |
|||
color: #333; |
|||
text-align: center; |
|||
height: 88rpx; |
|||
line-height: 88rpx; |
|||
} |
|||
.condition_menus .industry .active_industry{ |
|||
color: #3476FE; |
|||
background: white; |
|||
/* border-left: 8rpx solid #3476FE; */ |
|||
} |
|||
.showlist{ |
|||
width: 100%; |
|||
background: transparent; |
|||
} |
|||
|
@ -0,0 +1,44 @@ |
|||
Component({ |
|||
/** |
|||
* 组件的属性列表 |
|||
*/ |
|||
properties: { |
|||
buycar_card_data:{ |
|||
type: Object, |
|||
value:{}, |
|||
}, |
|||
is_my:{ |
|||
type:String, |
|||
value:'0', |
|||
}, |
|||
}, |
|||
|
|||
/** |
|||
* 组件的初始数据 |
|||
*/ |
|||
data: { |
|||
|
|||
}, |
|||
/** |
|||
* 组件的方法列表 |
|||
*/ |
|||
methods: { |
|||
bindshare(){ |
|||
this.triggerEvent('bindshare',{item:this.data.buycar_card_data}) |
|||
}, |
|||
go_detail(){ |
|||
if(this.data.buycar_card_data.state==0||this.data.buycar_card_data.state==10){ |
|||
wx.showToast({ |
|||
title: '该信息已下架', |
|||
icon:'none' |
|||
}) |
|||
return |
|||
} |
|||
// console.log(this.data.buycar_card_data)
|
|||
wx.navigateTo({ |
|||
url:'/pages/detail/detail?id='+this.data.buycar_card_data.id, |
|||
}) |
|||
}, |
|||
|
|||
} |
|||
}) |
@ -0,0 +1,5 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
} |
|||
} |
@ -0,0 +1,37 @@ |
|||
<view class="line"></view> |
|||
<view class="card" bindtap="go_detail"> |
|||
<!-- --> |
|||
<image class="clinch_deal" src="/images/publish/clinch_deal.png" mode="widthFix" wx:if="{{buycar_card_data.state==30}}"></image> |
|||
<view class="head" wx:if="{{is_my=='0'}}"> |
|||
<view class="head_img"> |
|||
<image src="{{buycar_card_data.avatarUrl}}"></image> |
|||
</view> |
|||
<view class="head_name">{{buycar_card_data.userName}}</view> |
|||
<view class="head_card"> <text>车主</text> </view> |
|||
<view class="head_card2" wx:if="{{buycar_card_data.isTop}}"> <text>置顶</text> </view> |
|||
<view class="head_details">查看详情</view> |
|||
</view> |
|||
<view class="body"> |
|||
<view class="body_1"> |
|||
<view class="body_title">{{buycar_card_data.title}}</view> |
|||
<view class="body_price">{{buycar_card_data.sellingPrice}} 万</view> |
|||
</view> |
|||
<view class="body_desc">{{buycar_card_data.introduction}}</view> |
|||
<view class="body_imgs"> |
|||
<image src="{{item.pictureLink}}" wx:for="{{buycar_card_data.equipmentPictures}}" wx:key="equipmentId" mode="aspectFill"></image> |
|||
|
|||
</view> |
|||
<view class="body_2"> |
|||
<view class="body_address"> |
|||
<image src="/images/index/dw.png"></image> |
|||
{{buycar_card_data.automobileLocation}} |
|||
</view> |
|||
<view wx:if="{{is_my=='my_record'}}">{{buycar_card_data.createdTime}}</view> |
|||
<view wx:if="{{is_my=='my_collection'}}"><image src="/images/common/cellection.png" style="width: 40rpx;height: 40rpx;"></image></view> |
|||
<view class="body_time" catchtap="bindshare" wx:if="{{is_my==0}}"> |
|||
<image class="body_time_img" src="/images/index/share.png"></image> |
|||
<view class="body_time_text">转发</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
@ -0,0 +1,143 @@ |
|||
.line { |
|||
width: 100%; |
|||
height: 12rpx; |
|||
background: transparent; |
|||
} |
|||
.card{ |
|||
width: 750rpx; |
|||
padding: 10rpx 20rpx; |
|||
box-sizing: border-box; |
|||
background: white; |
|||
/* box-shadow: 0 10rpx 40rpx rgba(203, 203, 202, 0.76); */ |
|||
position: relative; |
|||
font-size: 28rpx; |
|||
} |
|||
.clinch_deal{ |
|||
position: absolute; |
|||
width: 326rpx; |
|||
height:268rpx; |
|||
top: 50%; |
|||
left: 50%; |
|||
transform: translateX(-50%) translateY(-50%); |
|||
} |
|||
.head{ |
|||
display: flex; |
|||
align-items: center; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
.head_img{ |
|||
width: 58rpx; |
|||
height: 58rpx; |
|||
border-radius: 5555rpx; |
|||
overflow: hidden; |
|||
margin-right: 20rpx; |
|||
} |
|||
.head_img image{ |
|||
width: 58rpx; |
|||
height: 58rpx; |
|||
} |
|||
.head_name{ |
|||
margin-right: 20rpx; |
|||
max-width: 300rpx; |
|||
text-overflow: ellipsis; |
|||
overflow: hidden; |
|||
white-space: nowrap; |
|||
color: #333; |
|||
} |
|||
.head_card{ |
|||
margin-right: 20rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
.head_card2{ |
|||
/* margin-right: 20rpx; */ |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
.head_card2 text{ |
|||
border-radius: 8rpx; |
|||
background: red; |
|||
padding: 4rpx 16rpx; |
|||
font-size: 24rpx; |
|||
color: white; |
|||
} |
|||
.head_card text{ |
|||
border-radius: 8rpx; |
|||
background: #e99800; |
|||
padding: 4rpx 16rpx; |
|||
font-size: 24rpx; |
|||
color: white; |
|||
} |
|||
.head_details{ |
|||
text-align: right; |
|||
flex: 1; |
|||
color: #e99208; |
|||
font-size: 28rpx; |
|||
} |
|||
.body{ |
|||
color: #333; |
|||
} |
|||
.body_1{ |
|||
display: flex; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
.body_title{ |
|||
flex: 1; |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
overflow: hidden; |
|||
margin-right: 10rpx; |
|||
font-size: 32rpx; |
|||
/* font-weight: bold; */ |
|||
} |
|||
.body_price{ |
|||
color: red; |
|||
font-size: 32rpx; |
|||
|
|||
} |
|||
.body_desc{ |
|||
color: #858585; |
|||
font-size: 28rpx; |
|||
margin-bottom: 10rpx; |
|||
|
|||
} |
|||
.body_imgs{ |
|||
width: 100%; |
|||
} |
|||
.body_imgs image{ |
|||
width: 224rpx; |
|||
height: 224rpx; |
|||
margin:6rpx; |
|||
} |
|||
.body_2{ |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
margin-top: 10rpx; |
|||
font-size: 28rpx; |
|||
} |
|||
.body_address{ |
|||
color: #8c8c8c; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
.body_address image{ |
|||
width: 28rpx; |
|||
height: 28rpx; |
|||
|
|||
} |
|||
.body_time{ |
|||
width:120rpx; |
|||
height: 50rpx; |
|||
color: #616161; |
|||
display: flex; |
|||
align-items: center; |
|||
} |
|||
.body_time_img{ |
|||
width: 50rpx; |
|||
height: 50rpx; |
|||
} |
|||
.body_time_text{ |
|||
color: #979797; |
|||
} |
@ -0,0 +1,99 @@ |
|||
// components/list/list.js
|
|||
Component({ |
|||
/** |
|||
* 组件的属性列表 |
|||
*/ |
|||
properties: { |
|||
list:{ |
|||
type: Array, |
|||
value: [] |
|||
}, |
|||
triggered:{ // false 将设置下拉列表复位 // 正在下拉变成true
|
|||
type: Boolean, |
|||
value: false |
|||
}, |
|||
refresh: { //下拉刷新
|
|||
type: Boolean, |
|||
value: false |
|||
}, |
|||
load: { |
|||
type: Boolean, |
|||
value: false |
|||
}, |
|||
list_total: { |
|||
type: Number, |
|||
value:0 |
|||
}, |
|||
page_size:{ |
|||
type: Number, |
|||
value:0 |
|||
}, |
|||
}, |
|||
|
|||
/** |
|||
* 组件的初始数据 |
|||
*/ |
|||
data: { |
|||
imageServerUrl: getApp().globalData.imageServerUrl, |
|||
top: 0, |
|||
no_have: false |
|||
}, |
|||
|
|||
/** |
|||
* 组件的方法列表 |
|||
*/ |
|||
methods: { |
|||
// 滑动开始事件
|
|||
nobinddragstart(){ |
|||
this.triggerEvent('nobinddragstart') |
|||
}, |
|||
// 滑动结束事件
|
|||
nobinddragend(){ |
|||
this.triggerEvent('nobinddragend') |
|||
}, |
|||
onPulling(e) { |
|||
this.triggerEvent('onPulling',e) |
|||
}, |
|||
nobindscroll(e){ |
|||
this.triggerEvent('nobindscroll',e) |
|||
}, |
|||
onRefresh(e) { |
|||
let { refresh } = this.data; |
|||
if(refresh) return |
|||
this.setData({ |
|||
triggered: true, |
|||
}) |
|||
this.triggerEvent('onRefresh',e) |
|||
}, |
|||
|
|||
onRestore(e) { |
|||
this.setData({ |
|||
triggered: false, |
|||
top: 0 |
|||
}) |
|||
this.triggerEvent('onRestore',e) |
|||
}, |
|||
|
|||
onAbort(e) { |
|||
this.setData({ |
|||
triggered: false |
|||
}) |
|||
console.log('被打断') |
|||
this.setData({ |
|||
refresh: false, |
|||
load: false, |
|||
triggered: false |
|||
}) |
|||
this.triggerEvent('onAbort',e) |
|||
}, |
|||
onScrolltolower(e){ |
|||
let { load } = this.data; |
|||
this.setData({ |
|||
no_have: true |
|||
}) |
|||
if(load) return |
|||
if(this.data.list_total == this.data.list.length) return |
|||
this.triggerEvent('onScrolltolower',e) |
|||
} |
|||
} |
|||
}) |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
"no_data": "/components/noData/noData" |
|||
} |
|||
} |
@ -0,0 +1,43 @@ |
|||
<!--components/list/list.wxml--> |
|||
<scroll-view |
|||
scroll-y='{{!refresh && !triggered && list_total > 0}}' |
|||
scroll-with-animation |
|||
refresher-enabled |
|||
refresher-default-style="black" |
|||
refresher-background="#fff" |
|||
refresher-triggered="{{triggered}}" |
|||
bindrefresherpulling="onPulling" |
|||
bindrefresherrefresh="onRefresh" |
|||
bindrefresherrestore="onRestore" |
|||
bindrefresherabort="onAbort" |
|||
bindscrolltolower='onScrolltolower' |
|||
bindscroll="nobindscroll" |
|||
binddragstart="nobinddragstart" |
|||
binddragend="nobinddragend" |
|||
> |
|||
<!-- 下拉刷新 --> |
|||
<!-- <block wx:if='{{ triggered || refresh }}'> |
|||
<view class="loading"> |
|||
<image src="{{imageServerUrl}}common/loading.gif"></image> |
|||
<text>加载中···</text> |
|||
</view> |
|||
</block> --> |
|||
<!-- 数据列表 --> |
|||
<!-- <block wx:if='{{!triggered && !refresh}}'> --> |
|||
<block> |
|||
<slot></slot> |
|||
</block> |
|||
<!-- load 上拉加载 --> |
|||
<block wx:if="{{ load && list.length < list_total}}"> |
|||
<view class="loading"> |
|||
<image src="{{imageServerUrl}}common/loading.gif"></image> |
|||
<text>努力加载中···</text> |
|||
</view> |
|||
</block> |
|||
<!-- <view class="bottom_tip" wx:if='{{ list_total == list.length && list_total != 0 && list_total >= 3 && no_have}}'> |
|||
<text> 没有更多内容啦 ~ </text> |
|||
</view> --> |
|||
|
|||
</scroll-view> |
|||
|
|||
<view class="mask" wx:if="{{ refresh || load && !triggered}}"></view> |
@ -0,0 +1,57 @@ |
|||
/* components/list/list.wxss */ |
|||
::-webkit-scrollbar{ |
|||
width: 0; |
|||
} |
|||
.list{ |
|||
width: 100%; |
|||
height:92vh; |
|||
/* padding: 20rpx 0; */ |
|||
background: transparent; |
|||
box-sizing: border-box; |
|||
overflow:auto; |
|||
-webkit-overflow-scrolling: touch; |
|||
} |
|||
scroll-view{ |
|||
width: 100%; |
|||
height:92vh; |
|||
background: #f2f1f6; |
|||
-webkit-overflow-scrolling: touch; |
|||
box-sizing: border-box; |
|||
margin: 0; |
|||
} |
|||
|
|||
.bottom_tip{ |
|||
width: 100%; |
|||
height: 100rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
color: #999999; |
|||
font-size: 26rpx; |
|||
} |
|||
|
|||
.loading{ |
|||
width: 100%; |
|||
height: 100rpx; |
|||
font-size: 22rpx; |
|||
color: #999999; |
|||
|
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.loading image{ |
|||
width: 24rpx; |
|||
height: 24rpx; |
|||
margin-right: 8rpx; |
|||
} |
|||
|
|||
.mask{ |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
width: 100vw; |
|||
height: 100vh; |
|||
z-index: 100; |
|||
} |
@ -0,0 +1,81 @@ |
|||
// components/login/login.js
|
|||
const app = getApp(); |
|||
const axios = require('../../api/index') |
|||
Component({ |
|||
/** |
|||
* 组件的属性列表 |
|||
*/ |
|||
properties: { |
|||
is_show: { |
|||
type: Boolean, |
|||
value: false |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 组件的初始数据 |
|||
*/ |
|||
data: { |
|||
// user_info: null
|
|||
imageServerUrl: getApp().globalData.imageServerUrl, |
|||
}, |
|||
|
|||
/** |
|||
* 组件的方法列表 |
|||
*/ |
|||
methods: { |
|||
close() { |
|||
this.setData({ |
|||
is_show: false |
|||
}) |
|||
this.triggerEvent('close') |
|||
}, |
|||
prevent() {}, |
|||
//登录
|
|||
getUserProfile() { |
|||
wx.getUserProfile({ |
|||
desc: '记录用户信息', |
|||
success: res => { |
|||
let { |
|||
errMsg, |
|||
userInfo |
|||
} = res; |
|||
userInfo.id = '0'; |
|||
if (errMsg == "getUserProfile:ok") { |
|||
wx.login({ |
|||
success: e => { |
|||
let code = e.code; |
|||
wx.showLoading({ |
|||
title: '正在登录...', |
|||
}); |
|||
let data = { |
|||
jsCode: code, |
|||
wxUnifyUserAddInput: userInfo |
|||
} |
|||
axios.login(data).then(res => { |
|||
wx.setStorageSync('token', res.data); |
|||
wx.setStorageSync('loginUserInfo', userInfo) |
|||
this.get_user_info() |
|||
wx.hideLoading() |
|||
}).catch(err => { |
|||
wx.hideLoading() |
|||
}) |
|||
} |
|||
}) |
|||
} |
|||
} |
|||
}) |
|||
}, |
|||
get_user_info() { |
|||
axios.getUser().then(res => { |
|||
console.log(res) |
|||
app.globalData.userInfo = res.data |
|||
wx.setStorageSync('userInfo', res.data) |
|||
let shareType = wx.getStorageSync('shareType'), |
|||
userId = wx.getStorageSync('userId') |
|||
if (shareType && userId) app.share_add_integral(shareType, userId) |
|||
this.close() |
|||
}) |
|||
}, |
|||
} |
|||
}) |
@ -0,0 +1,4 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": {} |
|||
} |
@ -0,0 +1,15 @@ |
|||
<!--components/login/login.wxml--> |
|||
<!-- 首次进入小程序弹窗 --> |
|||
|
|||
<view class="login_maks" wx:if='{{ is_show }}' bindtap="close"> |
|||
<view class="login_box" catchtap="prevent"> |
|||
<view class="logo"> |
|||
<image src="{{imageServerUrl}}common/login_logo_icon.png"></image> |
|||
</view> |
|||
<text>云车招聘欢迎您</text> |
|||
<text class="tip dark">祝您早日找到工作|人才</text> |
|||
<view class="bottom"> |
|||
<button size="mini" open-type="getUserInfo" bindtap="getUserProfile">确定</button> |
|||
</view> |
|||
</view> |
|||
</view> |
@ -0,0 +1,62 @@ |
|||
/* components/login/login.wxss */ |
|||
/* 首次登录弹窗 */ |
|||
.login_maks{ |
|||
width: 100vw; |
|||
height: 100vh ; |
|||
|
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
z-index: 999999; |
|||
|
|||
background: rgba(0, 0, 0, 0.5); |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.login_box{ |
|||
width: 500rpx; |
|||
height: 482rpx; |
|||
background: white; |
|||
border-radius: 18rpx; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
|
|||
font-size: 32rpx; |
|||
color: #666666; |
|||
} |
|||
|
|||
.login_box .logo image{ |
|||
width: 136rpx; |
|||
height: 136rpx; |
|||
margin-top: 50rpx; |
|||
} |
|||
|
|||
.login_box .tip{ |
|||
margin: 20rpx 0 40rpx 0; |
|||
color: #0052EC; |
|||
} |
|||
|
|||
.login_box .bottom{ |
|||
flex-shrink: 0; |
|||
width: 100%; |
|||
height: 114rpx; |
|||
border-top:2rpx solid #E6E6E6; |
|||
|
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.login_box .bottom button{ |
|||
width: 264rpx; |
|||
height: 66rpx; |
|||
line-height: 66rpx; |
|||
background: #0052EC; |
|||
color: white; |
|||
border-radius: 30rpx; |
|||
font-size: 30rpx; |
|||
} |
@ -0,0 +1,75 @@ |
|||
// components/me/me.js
|
|||
const axios = require('../../api/index') |
|||
|
|||
Component({ |
|||
/** |
|||
* 组件的属性列表 |
|||
*/ |
|||
properties: { |
|||
}, |
|||
pageLifetimes: { |
|||
show() { |
|||
// console.log(1)
|
|||
this.getUnreadMessageList() |
|||
var userinfo = wx.getStorageSync('userInfo') |
|||
setTimeout(()=>{ |
|||
this.setData({ |
|||
userinfo |
|||
}) |
|||
console.log(this.data.userinfo) |
|||
console.log(this.data.userinfo.userName) |
|||
},0) |
|||
} |
|||
}, |
|||
lifetimes: { |
|||
created() { |
|||
this.getUnreadMessageList() |
|||
var userinfo = wx.getStorageSync('userInfo') |
|||
setTimeout(()=>{ |
|||
this.setData({ |
|||
userinfo |
|||
}) |
|||
// console.log(this.data.userinfo)
|
|||
// console.log(this.data.userinfo.userName)
|
|||
},0) |
|||
}, |
|||
attached: function () { |
|||
// this.getSysdata()
|
|||
}, |
|||
detached: function () { |
|||
// 在组件实例被从页面节点树移除时执行
|
|||
}, |
|||
}, |
|||
/** |
|||
* 组件的初始数据 |
|||
*/ |
|||
data: { |
|||
userinfo:{}, |
|||
messageCount:'' |
|||
}, |
|||
|
|||
|
|||
/** |
|||
* 组件的方法列表 |
|||
*/ |
|||
methods: { |
|||
// 消息通知
|
|||
getUnreadMessageList(){ |
|||
axios.getUnreadMessageList().then(res => { |
|||
// auditFailCount: "0"审核失败
|
|||
// messageCount: "0"消息提醒
|
|||
let messageCount = res.data.messageCount |
|||
this.setData({ |
|||
messageCount |
|||
}) |
|||
}) |
|||
}, |
|||
// 路由跳转
|
|||
to_path(e) { |
|||
// console.log(e.currentTarget.dataset.path)
|
|||
wx.navigateTo({ |
|||
url: e.currentTarget.dataset.path, |
|||
}) |
|||
}, |
|||
} |
|||
}) |
@ -0,0 +1,4 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": {} |
|||
} |
@ -0,0 +1,56 @@ |
|||
<scroll-view scroll-y="true" class="box"> |
|||
<view class="top"> |
|||
<view class="top_img"> |
|||
<view style="width: 140rpx;height: 140rpx;border-radius: 9999rpx;"> |
|||
<image class="head" src="{{userinfo.avatarUrl}}" mode="aspectFill"></image> |
|||
</view> |
|||
<image wx:if="{{userinfo.cardType==1001}}" class="vip" src="/images/me/v1.png" mode="widthFix"></image> |
|||
<image wx:if="{{userinfo.cardType==1002}}" class="vip" src="/images/me/v2.png" mode="widthFix"></image> |
|||
<image wx:if="{{userinfo.cardType==1003}}" class="vip" src="/images/me/v3.png" mode="widthFix"></image> |
|||
</view> |
|||
<view class="top_info"> |
|||
<view class="top_name">{{userinfo.userName}}</view> |
|||
<view class="top_phone"> |
|||
{{userinfo.phone}} <text wx:if="{{userinfo.isRealAuthentication}}">已 实 名</text> |
|||
</view> |
|||
</view> |
|||
<view class="top_card" bindtap="to_path" data-path="/pages/mine/mine"> |
|||
<view class="top_card_text">修改资料</view> |
|||
<image src="/images/me/uploadUserName.png"></image> |
|||
</view> |
|||
</view> |
|||
<view class="bottom"> |
|||
<view class="bottom_item" bindtap="to_path" data-path="/pages/my_release/my_release"> |
|||
<image src="/images/me/release.png"></image> |
|||
<text>我的发布</text> |
|||
</view> |
|||
<view class="bottom_item" bindtap="to_path" data-path="/pages/my_obtain/my_obtain"> |
|||
<image src="/images/me/obtain.png"></image> |
|||
<text>获取云币</text> |
|||
</view> |
|||
<view class="xian"></view> |
|||
<view class="bottom_item" bindtap="to_path" data-path="/pages/my_record/my_record"> |
|||
<image src="/images/me/record.png"></image> |
|||
<text>浏览记录</text> |
|||
</view> |
|||
<view class="bottom_item" bindtap="to_path" data-path="/pages/my_collection/my_collection"> |
|||
<image src="/images/me/collection.png"></image> |
|||
<text>我的收藏</text> |
|||
</view> |
|||
<view class="xian"></view> |
|||
<view class="bottom_item" bindtap="to_path" data-path="/pages/my_notice/my_notice"> |
|||
<image src="/images/me/notice.png"></image> |
|||
<text>消息通知</text> |
|||
<view style="color: red;font-size: 24rpx;position: absolute;right: 30rpx;" wx:if="{{messageCount>0}}">{{messageCount}}条消息通知</view> |
|||
</view> |
|||
<view class="bottom_item" bindtap="to_path" data-path="/pages/my_realname/my_realname"> |
|||
<image src="/images/me/realname.png"></image> |
|||
<text>实名认证</text> |
|||
</view> |
|||
<view class="bottom_item" bindtap="to_path" data-path="/pages/my_feedback/my_feedback"> |
|||
<image src="/images/me/feedback.png"></image> |
|||
<text>意见反馈</text> |
|||
</view> |
|||
<view class="xian"></view> |
|||
</view> |
|||
</scroll-view> |
@ -0,0 +1,90 @@ |
|||
.box { |
|||
background: #f2f1f6; |
|||
height: calc(100vh - 88rpx); |
|||
} |
|||
|
|||
.top { |
|||
padding: 40rpx 0rpx 40rpx 30rpx; |
|||
background: #3476fe; |
|||
display: flex; |
|||
align-items: center; |
|||
color: white; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.top_img { |
|||
position: relative; |
|||
margin-right: 30rpx; |
|||
} |
|||
|
|||
.head { |
|||
width: 140rpx; |
|||
height: 140rpx; |
|||
border-radius: 9999rpx; |
|||
} |
|||
|
|||
.vip { |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
position: absolute; |
|||
bottom: -10rpx; |
|||
right: -10rpx; |
|||
} |
|||
|
|||
.top_info { |
|||
flex: 1; |
|||
} |
|||
|
|||
.top_name { |
|||
margin-bottom: 10rpx; |
|||
} |
|||
|
|||
.top_phone {} |
|||
|
|||
.top_phone text { |
|||
font-size: 24rpx; |
|||
background: #ef432b; |
|||
border-radius: 6rpx; |
|||
padding: 6rpx 8rpx; |
|||
margin-right: 6rpx; |
|||
} |
|||
|
|||
.top_card { |
|||
width: 200rpx; |
|||
display: flex; |
|||
} |
|||
|
|||
.top_card_text {} |
|||
|
|||
.top_card image { |
|||
width: 40rpx; |
|||
height: 40rpx; |
|||
} |
|||
|
|||
.bottom { |
|||
} |
|||
|
|||
.bottom_item { |
|||
background: white; |
|||
padding: 24rpx 30rpx; |
|||
align-items: center; |
|||
display: flex; |
|||
box-sizing: border-box; |
|||
border-bottom: solid 2rpx #f2f2f2; |
|||
/* font-weight: 550; */ |
|||
} |
|||
|
|||
.bottom_item image { |
|||
width: 50rpx; |
|||
height: 50rpx; |
|||
margin-right: 30rpx; |
|||
} |
|||
|
|||
.bottom_item text { |
|||
letter-spacing:4rpx |
|||
} |
|||
|
|||
.xian { |
|||
background: #efefef; |
|||
height: 20rpx; |
|||
} |
@ -0,0 +1,45 @@ |
|||
// components/modal/modal.js
|
|||
Component({ |
|||
/** |
|||
* 组件的属性列表 |
|||
*/ |
|||
properties: { |
|||
confirm_txt:{ |
|||
type: String, |
|||
value: '确定' |
|||
}, |
|||
show_cancel_btn: { |
|||
type: Boolean, |
|||
value: true |
|||
}, |
|||
cencel_txt: { |
|||
type: String, |
|||
value: '取消' |
|||
}, |
|||
|
|||
}, |
|||
|
|||
/** |
|||
* 组件的初始数据 |
|||
*/ |
|||
data: { |
|||
imageServerUrl: getApp().globalData.imageServerUrl, |
|||
}, |
|||
|
|||
/** |
|||
* 组件的方法列表 |
|||
*/ |
|||
methods: { |
|||
no_bubble() { |
|||
|
|||
}, |
|||
// 确定
|
|||
confirm_fn() { |
|||
this.triggerEvent('confirm_fn') |
|||
}, |
|||
// 取消
|
|||
cancel_fn() { |
|||
this.triggerEvent('cancel_fn') |
|||
}, |
|||
} |
|||
}) |
@ -0,0 +1,4 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": {} |
|||
} |
@ -0,0 +1,16 @@ |
|||
<!--components/modal/modal.wxml--> |
|||
<view class="mask" bindtap="cancel_fn"> |
|||
<view class="container" catchtap="no_bubble"> |
|||
<view> |
|||
<slot></slot> |
|||
</view> |
|||
<view class="btn"> |
|||
<view bindtap="cancel_fn" wx:if="{{ show_cancel_btn }}"> |
|||
<text>{{cencel_txt}}</text> |
|||
</view> |
|||
<view bindtap="confirm_fn" class="confirm_fn"> |
|||
<text>{{ confirm_txt || '确定' }}</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
@ -0,0 +1,68 @@ |
|||
/* components/modal/modal.wxss */ |
|||
.mask { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
background: rgba(0, 0, 0, 0.5); |
|||
|
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
|
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
z-index: 100; |
|||
} |
|||
|
|||
.container { |
|||
width: 560rpx; |
|||
height: 290rpx; |
|||
background: white; |
|||
border-radius: 16rpx; |
|||
|
|||
display: flex; |
|||
align-items: center; |
|||
justify-items: center; |
|||
flex-direction: column; |
|||
color: #666666; |
|||
font-size: 32rpx; |
|||
} |
|||
|
|||
.container>view:nth-of-type(1) { |
|||
flex: 2; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
text-align: center; |
|||
} |
|||
|
|||
.container>view:nth-of-type(2) { |
|||
flex: 1; |
|||
width: 100%; |
|||
|
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
text-align: center; |
|||
border-top: 2rpx solid #E5E5E5; |
|||
} |
|||
|
|||
.btn view+view{ |
|||
border-left: 2rpx solid #E5E5E5; |
|||
} |
|||
|
|||
.btn view{ |
|||
flex: 1; |
|||
height: 100%; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.btn view { |
|||
color: #666666; |
|||
} |
|||
|
|||
.btn .confirm_fn { |
|||
color: #3476FE; |
|||
} |
@ -0,0 +1,66 @@ |
|||
Component({ |
|||
observers: { |
|||
'equipment_idx'(arr){ |
|||
var idx =arr |
|||
this.setData({ |
|||
idx |
|||
}) |
|||
} |
|||
}, |
|||
/** |
|||
* 组件的属性列表 |
|||
*/ |
|||
properties: { |
|||
listData: { |
|||
type: Array, |
|||
value: [], |
|||
}, |
|||
isShowPicker:{ |
|||
type: Boolean, |
|||
value: false, |
|||
}, |
|||
equipment_idx:{ |
|||
type:Number, |
|||
value:0 |
|||
}, |
|||
|
|||
}, |
|||
|
|||
/** |
|||
* 组件的初始数据 |
|||
*/ |
|||
data: { |
|||
imageServerUrl: getApp().globalData.imageServerUrl, |
|||
idx: 0, |
|||
}, |
|||
|
|||
/** |
|||
* 组件的方法列表 |
|||
*/ |
|||
lifetimes: { |
|||
ready :function(){ |
|||
var idx =this.data.equipment_idx |
|||
this.setData({ |
|||
idx |
|||
}) |
|||
}, |
|||
}, |
|||
methods: { |
|||
btn_fn(){ |
|||
}, |
|||
cancel(e){ |
|||
this.triggerEvent('cancel',false) |
|||
}, |
|||
bindChange(e){ |
|||
this.setData({ |
|||
idx:e.currentTarget.dataset.index |
|||
}) |
|||
}, |
|||
confirm(e){ |
|||
this.setData({ |
|||
isShowPicker:false |
|||
}) |
|||
this.triggerEvent('confirm',{idx:this.data.idx}) |
|||
} |
|||
} |
|||
}) |
@ -0,0 +1,4 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": {} |
|||
} |
@ -0,0 +1,13 @@ |
|||
<view class="region_picker" wx:if='{{isShowPicker}}' bindtap="cancel"> |
|||
<view class="box" catchtap="btn_fn"> |
|||
<view class="header"> |
|||
<text catchtap="cancel">取消</text> |
|||
<text catchtap="confirm">确定</text> |
|||
</view> |
|||
<scroll-view class="footer" catchtap="btn_fn" scroll-y scroll-into-view="{{'a'+equipment_idx}}"> |
|||
<view wx:for="{{listData}}" class="{{idx==index?'item is_item':'item'}}" id="{{'a'+index}}" bindtap="bindChange" data-index="{{index}}" wx:key="id"> |
|||
{{item.name}} |
|||
</view> |
|||
</scroll-view> |
|||
</view> |
|||
</view> |
@ -0,0 +1,55 @@ |
|||
.region_picker { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
background: rgba(0, 0, 0, 0.5); |
|||
z-index: 999; |
|||
} |
|||
|
|||
.box { |
|||
width: 100%; |
|||
height: 50vh; |
|||
position: absolute; |
|||
bottom: 0; |
|||
left: 0; |
|||
box-sizing: border-box; |
|||
background: white; |
|||
} |
|||
|
|||
.header { |
|||
width: 100%; |
|||
height: 90rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
.header text { |
|||
width: 100rpx; |
|||
margin-right: 20rpx; |
|||
height: 100%; |
|||
line-height: 90rpx; |
|||
text-align: center; |
|||
} |
|||
|
|||
.header text:nth-of-type(2) { |
|||
color: #3476FE; |
|||
} |
|||
.footer { |
|||
height:calc(50vh - 90rpx); |
|||
/* height:200rpx; */ |
|||
width: 100%; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.footer .item { |
|||
height:100rpx; |
|||
line-height: 100rpx; |
|||
text-align: center; |
|||
} |
|||
.is_item{ |
|||
background-color: #6094fe; |
|||
color: white; |
|||
} |
@ -0,0 +1,13 @@ |
|||
function _typeof(obj) { |
|||
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase(); |
|||
} |
|||
function isString(obj) { //是否字符串
|
|||
return _typeof(obj) === 'string' |
|||
} |
|||
function isPlainObject(obj) { |
|||
return _typeof(obj) === 'object'; |
|||
} |
|||
module.exports = { |
|||
isString, |
|||
isPlainObject |
|||
} |
@ -0,0 +1,72 @@ |
|||
Component({ |
|||
observers: { |
|||
'defaultPickData'(arr){ |
|||
var idx =arr |
|||
this.setData({ |
|||
idx |
|||
}) |
|||
} |
|||
}, |
|||
/** |
|||
* 组件的属性列表 |
|||
*/ |
|||
properties: { |
|||
listData: { |
|||
type: Array, |
|||
value: [], |
|||
}, |
|||
isShowPicker:{ |
|||
type: Boolean, |
|||
value: false, |
|||
}, |
|||
defaultPickData:{ |
|||
type:Number, |
|||
value:0 |
|||
}, |
|||
key:{ |
|||
type:String, |
|||
value:'' |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 组件的初始数据 |
|||
*/ |
|||
data: { |
|||
imageServerUrl: getApp().globalData.imageServerUrl, |
|||
idx: 0, |
|||
}, |
|||
|
|||
/** |
|||
* 组件的方法列表 |
|||
*/ |
|||
lifetimes: { |
|||
ready :function(){ |
|||
setTimeout(() => { |
|||
var idx =this.data.defaultPickData |
|||
this.setData({ |
|||
idx |
|||
}) |
|||
},0) |
|||
}, |
|||
}, |
|||
methods: { |
|||
btn_fn(){ |
|||
}, |
|||
cancel(e){ |
|||
console.log(e) |
|||
this.triggerEvent('cancel',false) |
|||
}, |
|||
to_ok(e){ |
|||
this.setData({ |
|||
idx:e.currentTarget.dataset.index |
|||
}) |
|||
}, |
|||
confirm(e){ |
|||
this.setData({ |
|||
isShowPicker:false |
|||
}) |
|||
this.triggerEvent('confirm',this.data.idx) |
|||
} |
|||
} |
|||
}) |
@ -0,0 +1,4 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": {} |
|||
} |
@ -0,0 +1,15 @@ |
|||
<view class="region_picker" wx:if='{{isShowPicker}}' bindtap="cancel"> |
|||
<view class="box" catchtap="btn_fn"> |
|||
<view class="header"> |
|||
<text catchtap="cancel">取消</text> |
|||
<text catchtap="confirm">确定</text> |
|||
</view> |
|||
<view class="footer" catchtap="btn_fn"> |
|||
<scroll-view class="scroll_box" scroll-y scroll-into-view="{{'a'+defaultPickData}}"> |
|||
<view class="{{idx==index? 'scroll_box_item is_ok' : 'scroll_box_item'}}" wx:for="{{listData}}" wx:key='id' style="line-height: 50px" id="{{'a'+index}}" bindtap="to_ok" data-index="{{index}}"> |
|||
{{item.name || item[key]}} |
|||
</view> |
|||
</scroll-view> |
|||
</view> |
|||
</view> |
|||
</view> |
@ -0,0 +1,89 @@ |
|||
.region_picker { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
background: rgba(0, 0, 0, 0.5); |
|||
z-index: 999; |
|||
} |
|||
|
|||
.box { |
|||
width: 100%; |
|||
height: 50vh; |
|||
position: absolute; |
|||
bottom: 0; |
|||
left: 0; |
|||
box-sizing: border-box; |
|||
background: white; |
|||
} |
|||
.scroll_box{ |
|||
height:calc(50vh - 90rpx); |
|||
|
|||
} |
|||
.scroll_box_item{ |
|||
display: inline-block; |
|||
width: 50%; |
|||
} |
|||
.header { |
|||
width: 100%; |
|||
height: 90rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
.header text { |
|||
width: 100rpx; |
|||
margin-right: 20rpx; |
|||
height: 100%; |
|||
line-height: 90rpx; |
|||
text-align: center; |
|||
} |
|||
|
|||
.header text:nth-of-type(2) { |
|||
color: #3476FE; |
|||
} |
|||
|
|||
.middle_tag { |
|||
width: 100%; |
|||
height: 100rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
box-sizing: border-box; |
|||
padding: 0 30rpx; |
|||
} |
|||
|
|||
.middle_tag view { |
|||
padding: 5rpx 30rpx; |
|||
border-radius: 12rpx; |
|||
color: #666666; |
|||
position: relative; |
|||
margin-right: 56rpx; |
|||
border: 1px solid #E6E6E6; |
|||
border-radius: 12rpx; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.middle_tag view image { |
|||
width: 38rpx; |
|||
height: 38rpx; |
|||
position: absolute; |
|||
top: -19rpx; |
|||
right: -19rpx; |
|||
} |
|||
|
|||
.footer { |
|||
width: 100%; |
|||
height: 470rpx; |
|||
display: flex; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.footer view { |
|||
text-align: center; |
|||
} |
|||
.is_ok{ |
|||
background-color: #6094fe; |
|||
color: white; |
|||
} |
@ -0,0 +1,13 @@ |
|||
function _typeof(obj) { |
|||
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase(); |
|||
} |
|||
function isString(obj) { //是否字符串
|
|||
return _typeof(obj) === 'string' |
|||
} |
|||
function isPlainObject(obj) { |
|||
return _typeof(obj) === 'object'; |
|||
} |
|||
module.exports = { |
|||
isString, |
|||
isPlainObject |
|||
} |
@ -0,0 +1,34 @@ |
|||
// components/noData/noData.js
|
|||
Component({ |
|||
/** |
|||
* 组件的属性列表 |
|||
*/ |
|||
properties: { |
|||
have_data: { |
|||
type:Boolean, |
|||
value: false, |
|||
observer:(newVal,oldVal) => { |
|||
} |
|||
}, |
|||
prompt_txt:{ |
|||
type: String, |
|||
value:'很抱歉,暂无此内容~' |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 组件的初始数据 |
|||
*/ |
|||
data: { |
|||
imageServerUrl: getApp().globalData.imageServerUrl, |
|||
}, |
|||
|
|||
/** |
|||
* 组件的方法列表 |
|||
*/ |
|||
methods: { |
|||
reset(){ |
|||
this.triggerEvent('reset') |
|||
} |
|||
} |
|||
}) |
@ -0,0 +1,4 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": {} |
|||
} |
@ -0,0 +1,6 @@ |
|||
<!--components/noData/noData.wxml--> |
|||
<view class="mask"> |
|||
<image src="/images/common/nothing.png"></image> |
|||
<text>{{prompt_txt}}</text> |
|||
<view bindtap="reset" class="reset" wx:if='{{ have_data}}'>重置</view> |
|||
</view> |
@ -0,0 +1,30 @@ |
|||
/* components/noData/noData.wxss */ |
|||
.mask { |
|||
width: 100%; |
|||
height: 70%; |
|||
font-size: 28rpx; |
|||
color: #999999; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
margin-top: 60rpx; |
|||
} |
|||
|
|||
.mask image { |
|||
width: 312rpx; |
|||
height: 164rpx; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.reset { |
|||
padding: 12rpx 64rpx; |
|||
line-height: 40rpx; |
|||
text-align: center; |
|||
border-radius: 44rpx; |
|||
background: #3476FE; |
|||
font-size: 30rpx; |
|||
color: #fff; |
|||
margin-top: 20rpx; |
|||
|
|||
} |
@ -0,0 +1,251 @@ |
|||
/** |
|||
* LRU 文件存储,使用该 downloader 可以让下载的文件存储在本地,下次进入小程序后可以直接使用 |
|||
* 详细设计文档可查看 https://juejin.im/post/5b42d3ede51d4519277b6ce3
|
|||
*/ |
|||
const util = require('./util'); |
|||
|
|||
const SAVED_FILES_KEY = 'savedFiles'; |
|||
const KEY_TOTAL_SIZE = 'totalSize'; |
|||
const KEY_PATH = 'path'; |
|||
const KEY_TIME = 'time'; |
|||
const KEY_SIZE = 'size'; |
|||
|
|||
// 可存储总共为 6M,目前小程序可允许的最大本地存储为 10M
|
|||
let MAX_SPACE_IN_B = 6 * 1024 * 1024; |
|||
let savedFiles = {}; |
|||
|
|||
export default class Dowloader { |
|||
constructor() { |
|||
// app 如果设置了最大存储空间,则使用 app 中的
|
|||
if (getApp().PAINTER_MAX_LRU_SPACE) { |
|||
MAX_SPACE_IN_B = getApp().PAINTER_MAX_LRU_SPACE; |
|||
} |
|||
wx.getStorage({ |
|||
key: SAVED_FILES_KEY, |
|||
success: function (res) { |
|||
if (res.data) { |
|||
savedFiles = res.data; |
|||
} |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 下载文件,会用 lru 方式来缓存文件到本地 |
|||
* @param {String} url 文件的 url |
|||
*/ |
|||
download(url, lru) { |
|||
return new Promise((resolve, reject) => { |
|||
if (!(url && util.isValidUrl(url))) { |
|||
resolve(url); |
|||
return; |
|||
} |
|||
if (!lru) { |
|||
// 无 lru 情况下直接判断 临时文件是否存在,不存在重新下载
|
|||
wx.getFileInfo({ |
|||
filePath: url, |
|||
success: () => { |
|||
resolve(url); |
|||
}, |
|||
fail: () => { |
|||
downloadFile(url, lru).then((path) => { |
|||
resolve(path); |
|||
}, () => { |
|||
reject(); |
|||
}); |
|||
}, |
|||
}) |
|||
return |
|||
} |
|||
|
|||
const file = getFile(url); |
|||
|
|||
if (file) { |
|||
// 检查文件是否正常,不正常需要重新下载
|
|||
wx.getSavedFileInfo({ |
|||
filePath: file[KEY_PATH], |
|||
success: (res) => { |
|||
resolve(file[KEY_PATH]); |
|||
}, |
|||
fail: (error) => { |
|||
console.error(`the file is broken, redownload it, ${JSON.stringify(error)}`); |
|||
downloadFile(url, lru).then((path) => { |
|||
resolve(path); |
|||
}, () => { |
|||
reject(); |
|||
}); |
|||
}, |
|||
}); |
|||
} else { |
|||
downloadFile(url, lru).then((path) => { |
|||
resolve(path); |
|||
}, () => { |
|||
reject(); |
|||
}); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
|
|||
function downloadFile(url, lru) { |
|||
return new Promise((resolve, reject) => { |
|||
wx.downloadFile({ |
|||
url: url, |
|||
success: function (res) { |
|||
if (res.statusCode !== 200) { |
|||
console.error(`downloadFile ${url} failed res.statusCode is not 200`); |
|||
reject(); |
|||
return; |
|||
} |
|||
const { |
|||
tempFilePath |
|||
} = res; |
|||
wx.getFileInfo({ |
|||
filePath: tempFilePath, |
|||
success: (tmpRes) => { |
|||
const newFileSize = tmpRes.size; |
|||
lru ? doLru(newFileSize).then(() => { |
|||
saveFile(url, newFileSize, tempFilePath).then((filePath) => { |
|||
resolve(filePath); |
|||
}); |
|||
}, () => { |
|||
resolve(tempFilePath); |
|||
}) : resolve(tempFilePath); |
|||
}, |
|||
fail: (error) => { |
|||
// 文件大小信息获取失败,则此文件也不要进行存储
|
|||
console.error(`getFileInfo ${res.tempFilePath} failed, ${JSON.stringify(error)}`); |
|||
resolve(res.tempFilePath); |
|||
}, |
|||
}); |
|||
}, |
|||
fail: function (error) { |
|||
console.error(`downloadFile failed, ${JSON.stringify(error)} `); |
|||
reject(); |
|||
}, |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function saveFile(key, newFileSize, tempFilePath) { |
|||
return new Promise((resolve, reject) => { |
|||
wx.saveFile({ |
|||
tempFilePath: tempFilePath, |
|||
success: (fileRes) => { |
|||
const totalSize = savedFiles[KEY_TOTAL_SIZE] ? savedFiles[KEY_TOTAL_SIZE] : 0; |
|||
savedFiles[key] = {}; |
|||
savedFiles[key][KEY_PATH] = fileRes.savedFilePath; |
|||
savedFiles[key][KEY_TIME] = new Date().getTime(); |
|||
savedFiles[key][KEY_SIZE] = newFileSize; |
|||
savedFiles['totalSize'] = newFileSize + totalSize; |
|||
wx.setStorage({ |
|||
key: SAVED_FILES_KEY, |
|||
data: savedFiles, |
|||
}); |
|||
resolve(fileRes.savedFilePath); |
|||
}, |
|||
fail: (error) => { |
|||
console.error(`saveFile ${key} failed, then we delete all files, ${JSON.stringify(error)}`); |
|||
// 由于 saveFile 成功后,res.tempFilePath 处的文件会被移除,所以在存储未成功时,我们还是继续使用临时文件
|
|||
resolve(tempFilePath); |
|||
// 如果出现错误,就直接情况本地的所有文件,因为你不知道是不是因为哪次lru的某个文件未删除成功
|
|||
reset(); |
|||
}, |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 清空所有下载相关内容 |
|||
*/ |
|||
function reset() { |
|||
wx.removeStorage({ |
|||
key: SAVED_FILES_KEY, |
|||
success: () => { |
|||
wx.getSavedFileList({ |
|||
success: (listRes) => { |
|||
removeFiles(listRes.fileList); |
|||
}, |
|||
fail: (getError) => { |
|||
console.error(`getSavedFileList failed, ${JSON.stringify(getError)}`); |
|||
}, |
|||
}); |
|||
}, |
|||
}); |
|||
} |
|||
|
|||
function doLru(size) { |
|||
if (size > MAX_SPACE_IN_B) { |
|||
return Promise.reject() |
|||
} |
|||
return new Promise((resolve, reject) => { |
|||
let totalSize = savedFiles[KEY_TOTAL_SIZE] ? savedFiles[KEY_TOTAL_SIZE] : 0; |
|||
|
|||
if (size + totalSize <= MAX_SPACE_IN_B) { |
|||
resolve(); |
|||
return; |
|||
} |
|||
// 如果加上新文件后大小超过最大限制,则进行 lru
|
|||
const pathsShouldDelete = []; |
|||
// 按照最后一次的访问时间,从小到大排序
|
|||
const allFiles = JSON.parse(JSON.stringify(savedFiles)); |
|||
delete allFiles[KEY_TOTAL_SIZE]; |
|||
const sortedKeys = Object.keys(allFiles).sort((a, b) => { |
|||
return allFiles[a][KEY_TIME] - allFiles[b][KEY_TIME]; |
|||
}); |
|||
|
|||
for (const sortedKey of sortedKeys) { |
|||
totalSize -= savedFiles[sortedKey].size; |
|||
pathsShouldDelete.push(savedFiles[sortedKey][KEY_PATH]); |
|||
delete savedFiles[sortedKey]; |
|||
if (totalSize + size < MAX_SPACE_IN_B) { |
|||
break; |
|||
} |
|||
} |
|||
|
|||
savedFiles['totalSize'] = totalSize; |
|||
|
|||
wx.setStorage({ |
|||
key: SAVED_FILES_KEY, |
|||
data: savedFiles, |
|||
success: () => { |
|||
// 保证 storage 中不会存在不存在的文件数据
|
|||
if (pathsShouldDelete.length > 0) { |
|||
removeFiles(pathsShouldDelete); |
|||
} |
|||
resolve(); |
|||
}, |
|||
fail: (error) => { |
|||
console.error(`doLru setStorage failed, ${JSON.stringify(error)}`); |
|||
reject(); |
|||
}, |
|||
}); |
|||
}); |
|||
} |
|||
|
|||
function removeFiles(pathsShouldDelete) { |
|||
for (const pathDel of pathsShouldDelete) { |
|||
let delPath = pathDel; |
|||
if (typeof pathDel === 'object') { |
|||
delPath = pathDel.filePath; |
|||
} |
|||
wx.removeSavedFile({ |
|||
filePath: delPath, |
|||
fail: (error) => { |
|||
console.error(`removeSavedFile ${pathDel} failed, ${JSON.stringify(error)}`); |
|||
}, |
|||
}); |
|||
} |
|||
} |
|||
|
|||
function getFile(key) { |
|||
if (!savedFiles[key]) { |
|||
return; |
|||
} |
|||
savedFiles[key]['time'] = new Date().getTime(); |
|||
wx.setStorage({ |
|||
key: SAVED_FILES_KEY, |
|||
data: savedFiles, |
|||
}); |
|||
return savedFiles[key]; |
|||
} |
@ -0,0 +1,102 @@ |
|||
/* eslint-disable */ |
|||
// 当ctx传入当前文件,const grd = ctx.createCircularGradient() 和
|
|||
// const grd = this.ctx.createLinearGradient() 无效,因此只能分开处理
|
|||
// 先分析,在外部创建grd,再传入使用就可以
|
|||
|
|||
!(function () { |
|||
|
|||
var api = { |
|||
isGradient: function(bg) { |
|||
if (bg && (bg.startsWith('linear') || bg.startsWith('radial'))) { |
|||
return true; |
|||
} |
|||
return false; |
|||
}, |
|||
|
|||
doGradient: function(bg, width, height, ctx) { |
|||
if (bg.startsWith('linear')) { |
|||
linearEffect(width, height, bg, ctx); |
|||
} else if (bg.startsWith('radial')) { |
|||
radialEffect(width, height, bg, ctx); |
|||
} |
|||
}, |
|||
} |
|||
|
|||
function analizeGrad(string) { |
|||
const colorPercents = string.substring(0, string.length - 1).split("%,"); |
|||
const colors = []; |
|||
const percents = []; |
|||
for (let colorPercent of colorPercents) { |
|||
colors.push(colorPercent.substring(0, colorPercent.lastIndexOf(" ")).trim()); |
|||
percents.push(colorPercent.substring(colorPercent.lastIndexOf(" "), colorPercent.length) / 100); |
|||
} |
|||
return {colors: colors, percents: percents}; |
|||
} |
|||
|
|||
function radialEffect(width, height, bg, ctx) { |
|||
const colorPer = analizeGrad(bg.match(/radial-gradient\((.+)\)/)[1]); |
|||
const grd = ctx.createRadialGradient(0, 0, 0, 0, 0, width < height ? height / 2 : width / 2); |
|||
for (let i = 0; i < colorPer.colors.length; i++) { |
|||
grd.addColorStop(colorPer.percents[i], colorPer.colors[i]); |
|||
} |
|||
ctx.fillStyle = grd; |
|||
//ctx.fillRect(-(width / 2), -(height / 2), width, height);
|
|||
} |
|||
|
|||
function analizeLinear(bg, width, height) { |
|||
const direction = bg.match(/([-]?\d{1,3})deg/); |
|||
const dir = direction && direction[1] ? parseFloat(direction[1]) : 0; |
|||
let coordinate; |
|||
switch (dir) { |
|||
case 0: coordinate = [0, -height / 2, 0, height / 2]; break; |
|||
case 90: coordinate = [width / 2, 0, -width / 2, 0]; break; |
|||
case -90: coordinate = [-width / 2, 0, width / 2, 0]; break; |
|||
case 180: coordinate = [0, height / 2, 0, -height / 2]; break; |
|||
case -180: coordinate = [0, -height / 2, 0, height / 2]; break; |
|||
default: |
|||
let x1 = 0; |
|||
let y1 = 0; |
|||
let x2 = 0; |
|||
let y2 = 0; |
|||
if (direction[1] > 0 && direction[1] < 90) { |
|||
x1 = (width / 2) - ((width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (90 - direction[1]) * Math.PI * 2 / 360) / 2; |
|||
y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1; |
|||
x2 = -x1; |
|||
y1 = -y2; |
|||
} else if (direction[1] > -180 && direction[1] < -90) { |
|||
x1 = -(width / 2) + ((width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (90 - direction[1]) * Math.PI * 2 / 360) / 2; |
|||
y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1; |
|||
x2 = -x1; |
|||
y1 = -y2; |
|||
} else if (direction[1] > 90 && direction[1] < 180) { |
|||
x1 = (width / 2) + (-(width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (90 - direction[1]) * Math.PI * 2 / 360) / 2; |
|||
y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1; |
|||
x2 = -x1; |
|||
y1 = -y2; |
|||
} else { |
|||
x1 = -(width / 2) - (-(width / 2) * Math.tan((90 - direction[1]) * Math.PI * 2 / 360) - height / 2) * Math.sin(2 * (90 - direction[1]) * Math.PI * 2 / 360) / 2; |
|||
y2 = Math.tan((90 - direction[1]) * Math.PI * 2 / 360) * x1; |
|||
x2 = -x1; |
|||
y1 = -y2; |
|||
} |
|||
coordinate = [x1, y1, x2, y2]; |
|||
break; |
|||
} |
|||
return coordinate; |
|||
} |
|||
|
|||
function linearEffect(width, height, bg, ctx) { |
|||
const param = analizeLinear(bg, width, height); |
|||
const grd = ctx.createLinearGradient(param[0], param[1], param[2], param[3]); |
|||
const content = bg.match(/linear-gradient\((.+)\)/)[1]; |
|||
const colorPer = analizeGrad(content.substring(content.indexOf(',') + 1)); |
|||
for (let i = 0; i < colorPer.colors.length; i++) { |
|||
grd.addColorStop(colorPer.percents[i], colorPer.colors[i]); |
|||
} |
|||
ctx.fillStyle = grd |
|||
//ctx.fillRect(-(width / 2), -(height / 2), width, height);
|
|||
} |
|||
|
|||
module.exports = { api } |
|||
|
|||
})(); |
@ -0,0 +1,750 @@ |
|||
const QR = require('./qrcode.js'); |
|||
const GD = require('./gradient.js'); |
|||
|
|||
export default class Painter { |
|||
constructor(ctx, data) { |
|||
this.ctx = ctx; |
|||
this.data = data; |
|||
this.globalWidth = {}; |
|||
this.globalHeight = {}; |
|||
} |
|||
|
|||
isMoving = false |
|||
movingCache = {} |
|||
paint(callback, isMoving, movingCache) { |
|||
this.style = { |
|||
width: this.data.width.toPx(), |
|||
height: this.data.height.toPx(), |
|||
}; |
|||
if (isMoving) { |
|||
this.isMoving = true |
|||
this.movingCache = movingCache |
|||
} |
|||
this._background(); |
|||
for (const view of this.data.views) { |
|||
this._drawAbsolute(view); |
|||
} |
|||
this.ctx.draw(false, () => { |
|||
callback && callback(this.callbackInfo); |
|||
}); |
|||
} |
|||
|
|||
_background() { |
|||
this.ctx.save(); |
|||
const { |
|||
width, |
|||
height, |
|||
} = this.style; |
|||
const bg = this.data.background; |
|||
this.ctx.translate(width / 2, height / 2); |
|||
|
|||
this._doClip(this.data.borderRadius, width, height); |
|||
if (!bg) { |
|||
// 如果未设置背景,则默认使用透明色
|
|||
this.ctx.fillStyle = 'transparent'; |
|||
this.ctx.fillRect(-(width / 2), -(height / 2), width, height); |
|||
} else if (bg.startsWith('#') || bg.startsWith('rgba') || bg.toLowerCase() === 'transparent') { |
|||
// 背景填充颜色
|
|||
this.ctx.fillStyle = bg; |
|||
this.ctx.fillRect(-(width / 2), -(height / 2), width, height); |
|||
} else if (GD.api.isGradient(bg)) { |
|||
GD.api.doGradient(bg, width, height, this.ctx); |
|||
this.ctx.fillRect(-(width / 2), -(height / 2), width, height); |
|||
} else { |
|||
// 背景填充图片
|
|||
this.ctx.drawImage(bg, -(width / 2), -(height / 2), width, height); |
|||
} |
|||
this.ctx.restore(); |
|||
} |
|||
|
|||
_drawAbsolute(view) { |
|||
if (!(view && view.type)) { |
|||
// 过滤无效 view
|
|||
return |
|||
} |
|||
// 证明 css 为数组形式,需要合并
|
|||
if (view.css && view.css.length) { |
|||
/* eslint-disable no-param-reassign */ |
|||
view.css = Object.assign(...view.css); |
|||
} |
|||
switch (view.type) { |
|||
case 'image': |
|||
this._drawAbsImage(view); |
|||
break; |
|||
case 'text': |
|||
this._fillAbsText(view); |
|||
break; |
|||
case 'rect': |
|||
this._drawAbsRect(view); |
|||
break; |
|||
case 'qrcode': |
|||
this._drawQRCode(view); |
|||
break; |
|||
default: |
|||
break; |
|||
} |
|||
} |
|||
|
|||
_border({ |
|||
borderRadius = 0, |
|||
width, |
|||
height, |
|||
borderWidth = 0, |
|||
borderStyle = 'solid' |
|||
}) { |
|||
let r1 = 0, |
|||
r2 = 0, |
|||
r3 = 0, |
|||
r4 = 0 |
|||
const minSize = Math.min(width, height); |
|||
if (borderRadius) { |
|||
const border = borderRadius.split(/\s+/) |
|||
if (border.length === 4) { |
|||
r1 = Math.min(border[0].toPx(false, minSize), width / 2, height / 2); |
|||
r2 = Math.min(border[1].toPx(false, minSize), width / 2, height / 2); |
|||
r3 = Math.min(border[2].toPx(false, minSize), width / 2, height / 2); |
|||
r4 = Math.min(border[3].toPx(false, minSize), width / 2, height / 2); |
|||
} else { |
|||
r1 = r2 = r3 = r4 = Math.min(borderRadius && borderRadius.toPx(false, minSize), width / 2, height / 2); |
|||
} |
|||
} |
|||
const lineWidth = borderWidth && borderWidth.toPx(false, minSize); |
|||
this.ctx.lineWidth = lineWidth; |
|||
if (borderStyle === 'dashed') { |
|||
this.ctx.setLineDash([lineWidth * 4 / 3, lineWidth * 4 / 3]); |
|||
// this.ctx.lineDashOffset = 2 * lineWidth
|
|||
} else if (borderStyle === 'dotted') { |
|||
this.ctx.setLineDash([lineWidth, lineWidth]); |
|||
} |
|||
const notSolid = borderStyle !== 'solid' |
|||
this.ctx.beginPath(); |
|||
|
|||
notSolid && r1 === 0 && this.ctx.moveTo(-width / 2 - lineWidth, -height / 2 - lineWidth / 2) // 顶边虚线规避重叠规则
|
|||
r1 !== 0 && this.ctx.arc(-width / 2 + r1, -height / 2 + r1, r1 + lineWidth / 2, 1 * Math.PI, 1.5 * Math.PI); //左上角圆弧
|
|||
this.ctx.lineTo(r2 === 0 ? notSolid ? width / 2 : width / 2 + lineWidth / 2 : width / 2 - r2, -height / 2 - lineWidth / 2); // 顶边线
|
|||
|
|||
notSolid && r2 === 0 && this.ctx.moveTo(width / 2 + lineWidth / 2, -height / 2 - lineWidth) // 右边虚线规避重叠规则
|
|||
r2 !== 0 && this.ctx.arc(width / 2 - r2, -height / 2 + r2, r2 + lineWidth / 2, 1.5 * Math.PI, 2 * Math.PI); // 右上角圆弧
|
|||
this.ctx.lineTo(width / 2 + lineWidth / 2, r3 === 0 ? notSolid ? height / 2 : height / 2 + lineWidth / 2 : height / 2 - r3); // 右边线
|
|||
|
|||
notSolid && r3 === 0 && this.ctx.moveTo(width / 2 + lineWidth, height / 2 + lineWidth / 2) // 底边虚线规避重叠规则
|
|||
r3 !== 0 && this.ctx.arc(width / 2 - r3, height / 2 - r3, r3 + lineWidth / 2, 0, 0.5 * Math.PI); // 右下角圆弧
|
|||
this.ctx.lineTo(r4 === 0 ? notSolid ? -width / 2 : -width / 2 - lineWidth / 2 : -width / 2 + r4, height / 2 + lineWidth / 2); // 底边线
|
|||
|
|||
notSolid && r4 === 0 && this.ctx.moveTo(-width / 2 - lineWidth / 2, height / 2 + lineWidth) // 左边虚线规避重叠规则
|
|||
r4 !== 0 && this.ctx.arc(-width / 2 + r4, height / 2 - r4, r4 + lineWidth / 2, 0.5 * Math.PI, 1 * Math.PI); // 左下角圆弧
|
|||
this.ctx.lineTo(-width / 2 - lineWidth / 2, r1 === 0 ? notSolid ? -height / 2 : -height / 2 - lineWidth / 2 : -height / 2 + r1); // 左边线
|
|||
notSolid && r1 === 0 && this.ctx.moveTo(-width / 2 - lineWidth, -height / 2 - lineWidth / 2) // 顶边虚线规避重叠规则
|
|||
|
|||
if (!notSolid) { |
|||
this.ctx.closePath(); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 根据 borderRadius 进行裁减 |
|||
*/ |
|||
_doClip(borderRadius, width, height, borderStyle) { |
|||
if (borderRadius && width && height) { |
|||
// 防止在某些机型上周边有黑框现象,此处如果直接设置 fillStyle 为透明,在 Android 机型上会导致被裁减的图片也变为透明, iOS 和 IDE 上不会
|
|||
// globalAlpha 在 1.9.90 起支持,低版本下无效,但把 fillStyle 设为了 white,相对默认的 black 要好点
|
|||
this.ctx.globalAlpha = 0; |
|||
this.ctx.fillStyle = 'white'; |
|||
this._border({ |
|||
borderRadius, |
|||
width, |
|||
height, |
|||
borderStyle |
|||
}) |
|||
this.ctx.fill(); |
|||
// 在 ios 的 6.6.6 版本上 clip 有 bug,禁掉此类型上的 clip,也就意味着,在此版本微信的 ios 设备下无法使用 border 属性
|
|||
if (!(getApp().systemInfo && |
|||
getApp().systemInfo.version <= '6.6.6' && |
|||
getApp().systemInfo.platform === 'ios')) { |
|||
this.ctx.clip(); |
|||
} |
|||
this.ctx.globalAlpha = 1; |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* 画边框 |
|||
*/ |
|||
_doBorder(view, width, height) { |
|||
if (!view.css) { |
|||
return; |
|||
} |
|||
const { |
|||
borderRadius, |
|||
borderWidth, |
|||
borderColor, |
|||
borderStyle |
|||
} = view.css; |
|||
if (!borderWidth) { |
|||
return; |
|||
} |
|||
this.ctx.save(); |
|||
this._preProcess(view, true); |
|||
this.ctx.strokeStyle = (borderColor || 'black'); |
|||
this._border({ |
|||
borderRadius, |
|||
width, |
|||
height, |
|||
borderWidth, |
|||
borderStyle |
|||
}) |
|||
this.ctx.stroke(); |
|||
this.ctx.restore(); |
|||
} |
|||
|
|||
_preProcess(view, notClip) { |
|||
let width = 0; |
|||
let height; |
|||
let extra; |
|||
const paddings = this._doPaddings(view); |
|||
switch (view.type) { |
|||
case 'text': { |
|||
const textArray = String(view.text).split('\n'); |
|||
// 处理多个连续的'\n'
|
|||
for (let i = 0; i < textArray.length; ++i) { |
|||
if (textArray[i] === '') { |
|||
textArray[i] = ' '; |
|||
} |
|||
} |
|||
const fontWeight = view.css.fontWeight || '400'; |
|||
const textStyle = view.css.textStyle || 'normal'; |
|||
if (!view.css.fontSize) { |
|||
view.css.fontSize = '20rpx'; |
|||
} |
|||
this.ctx.font = `${textStyle} ${fontWeight} ${view.css.fontSize.toPx()}px "${view.css.fontFamily || 'sans-serif'}"`; |
|||
// 计算行数
|
|||
let lines = 0; |
|||
const linesArray = []; |
|||
for (let i = 0; i < textArray.length; ++i) { |
|||
const textLength = this.ctx.measureText(textArray[i]).width; |
|||
const minWidth = view.css.fontSize.toPx() + paddings[1] + paddings[3]; |
|||
let partWidth = view.css.width ? view.css.width.toPx(false, this.style.width) - paddings[1] - paddings[3] : textLength; |
|||
if (partWidth < minWidth) { |
|||
partWidth = minWidth; |
|||
} |
|||
const calLines = Math.ceil(textLength / partWidth); |
|||
// 取最长的作为 width
|
|||
width = partWidth > width ? partWidth : width; |
|||
lines += calLines; |
|||
linesArray[i] = calLines; |
|||
} |
|||
lines = view.css.maxLines < lines ? view.css.maxLines : lines; |
|||
const lineHeight = view.css.lineHeight ? view.css.lineHeight.toPx() : view.css.fontSize.toPx(); |
|||
height = lineHeight * lines; |
|||
extra = { |
|||
lines: lines, |
|||
lineHeight: lineHeight, |
|||
textArray: textArray, |
|||
linesArray: linesArray, |
|||
}; |
|||
break; |
|||
} |
|||
case 'image': { |
|||
// image的长宽设置成auto的逻辑处理
|
|||
const ratio = getApp().systemInfo.pixelRatio ? getApp().systemInfo.pixelRatio : 2; |
|||
// 有css却未设置width或height,则默认为auto
|
|||
if (view.css) { |
|||
if (!view.css.width) { |
|||
view.css.width = 'auto'; |
|||
} |
|||
if (!view.css.height) { |
|||
view.css.height = 'auto'; |
|||
} |
|||
} |
|||
if (!view.css || (view.css.width === 'auto' && view.css.height === 'auto')) { |
|||
width = Math.round(view.sWidth / ratio); |
|||
height = Math.round(view.sHeight / ratio); |
|||
} else if (view.css.width === 'auto') { |
|||
height = view.css.height.toPx(false, this.style.height); |
|||
width = view.sWidth / view.sHeight * height; |
|||
} else if (view.css.height === 'auto') { |
|||
width = view.css.width.toPx(false, this.style.width); |
|||
height = view.sHeight / view.sWidth * width; |
|||
} else { |
|||
width = view.css.width.toPx(false, this.style.width); |
|||
height = view.css.height.toPx(false, this.style.height); |
|||
} |
|||
break; |
|||
} |
|||
default: |
|||
if (!(view.css.width && view.css.height)) { |
|||
console.error('You should set width and height'); |
|||
return; |
|||
} |
|||
width = view.css.width.toPx(false, this.style.width); |
|||
height = view.css.height.toPx(false, this.style.height); |
|||
break; |
|||
} |
|||
let x; |
|||
if (view.css && view.css.right) { |
|||
if (typeof view.css.right === 'string') { |
|||
x = this.style.width - view.css.right.toPx(true, this.style.width); |
|||
} else { |
|||
// 可以用数组方式,把文字长度计算进去
|
|||
// [right, 文字id, 乘数(默认 1)]
|
|||
const rights = view.css.right; |
|||
x = this.style.width - rights[0].toPx(true, this.style.width) - this.globalWidth[rights[1]] * (rights[2] || 1); |
|||
} |
|||
} else if (view.css && view.css.left) { |
|||
if (typeof view.css.left === 'string') { |
|||
x = view.css.left.toPx(true, this.style.width); |
|||
} else { |
|||
const lefts = view.css.left; |
|||
x = lefts[0].toPx(true, this.style.width) + this.globalWidth[lefts[1]] * (lefts[2] || 1); |
|||
} |
|||
} else { |
|||
x = 0; |
|||
} |
|||
//const y = view.css && view.css.bottom ? this.style.height - height - view.css.bottom.toPx(true) : (view.css && view.css.top ? view.css.top.toPx(true) : 0);
|
|||
let y; |
|||
if (view.css && view.css.bottom) { |
|||
y = this.style.height - height - view.css.bottom.toPx(true, this.style.height); |
|||
} else { |
|||
if (view.css && view.css.top) { |
|||
if (typeof view.css.top === 'string') { |
|||
y = view.css.top.toPx(true, this.style.height); |
|||
} else { |
|||
const tops = view.css.top; |
|||
y = tops[0].toPx(true, this.style.height) + this.globalHeight[tops[1]] * (tops[2] || 1); |
|||
} |
|||
} else { |
|||
y = 0 |
|||
} |
|||
} |
|||
|
|||
const angle = view.css && view.css.rotate ? this._getAngle(view.css.rotate) : 0; |
|||
// 当设置了 right 时,默认 align 用 right,反之用 left
|
|||
const align = view.css && view.css.align ? view.css.align : (view.css && view.css.right ? 'right' : 'left'); |
|||
const verticalAlign = view.css && view.css.verticalAlign ? view.css.verticalAlign : 'top'; |
|||
// 记录绘制时的画布
|
|||
let xa = 0; |
|||
switch (align) { |
|||
case 'center': |
|||
xa = x; |
|||
break; |
|||
case 'right': |
|||
xa = x - width / 2; |
|||
break; |
|||
default: |
|||
xa = x + width / 2; |
|||
break; |
|||
} |
|||
let ya = 0; |
|||
switch (verticalAlign) { |
|||
case 'center': |
|||
ya = y; |
|||
break; |
|||
case 'bottom': |
|||
ya = y - height / 2; |
|||
break; |
|||
default: |
|||
ya = y + height / 2; |
|||
break; |
|||
} |
|||
this.ctx.translate(xa, ya); |
|||
// 记录该 view 的有效点击区域
|
|||
// TODO ,旋转和裁剪的判断
|
|||
// 记录在真实画布上的左侧
|
|||
let left = x |
|||
if (align === 'center') { |
|||
left = x - width / 2 |
|||
} else if (align === 'right') { |
|||
left = x - width |
|||
} |
|||
var top = y; |
|||
if (verticalAlign === 'center') { |
|||
top = y - height / 2; |
|||
} else if (verticalAlign === 'bottom') { |
|||
top = y - height |
|||
} |
|||
if (view.rect) { |
|||
view.rect.left = left; |
|||
view.rect.top = top; |
|||
view.rect.right = left + width; |
|||
view.rect.bottom = top + height; |
|||
view.rect.x = view.css && view.css.right ? x - width : x; |
|||
view.rect.y = y; |
|||
} else { |
|||
view.rect = { |
|||
left: left, |
|||
top: top, |
|||
right: left + width, |
|||
bottom: top + height, |
|||
x: view.css && view.css.right ? x - width : x, |
|||
y: y |
|||
}; |
|||
} |
|||
|
|||
view.rect.left = view.rect.left - paddings[3]; |
|||
view.rect.top = view.rect.top - paddings[0]; |
|||
view.rect.right = view.rect.right + paddings[1]; |
|||
view.rect.bottom = view.rect.bottom + paddings[2]; |
|||
if (view.type === 'text') { |
|||
view.rect.minWidth = view.css.fontSize.toPx() + paddings[1] + paddings[3]; |
|||
} |
|||
|
|||
this.ctx.rotate(angle); |
|||
if (!notClip && view.css && view.css.borderRadius && view.type !== 'rect') { |
|||
this._doClip(view.css.borderRadius, width, height, view.css.borderStyle); |
|||
} |
|||
this._doShadow(view); |
|||
if (view.id) { |
|||
this.globalWidth[view.id] = width; |
|||
this.globalHeight[view.id] = height; |
|||
} |
|||
return { |
|||
width: width, |
|||
height: height, |
|||
x: x, |
|||
y: y, |
|||
extra: extra, |
|||
}; |
|||
} |
|||
|
|||
_doPaddings(view) { |
|||
const { |
|||
padding, |
|||
} = view.css ? view.css : {}; |
|||
let pd = [0, 0, 0, 0]; |
|||
if (padding) { |
|||
const pdg = padding.split(/\s+/); |
|||
if (pdg.length === 1) { |
|||
const x = pdg[0].toPx(); |
|||
pd = [x, x, x, x]; |
|||
} |
|||
if (pdg.length === 2) { |
|||
const x = pdg[0].toPx(); |
|||
const y = pdg[1].toPx(); |
|||
pd = [x, y, x, y]; |
|||
} |
|||
if (pdg.length === 3) { |
|||
const x = pdg[0].toPx(); |
|||
const y = pdg[1].toPx(); |
|||
const z = pdg[2].toPx(); |
|||
pd = [x, y, z, y]; |
|||
} |
|||
if (pdg.length === 4) { |
|||
const x = pdg[0].toPx(); |
|||
const y = pdg[1].toPx(); |
|||
const z = pdg[2].toPx(); |
|||
const a = pdg[3].toPx(); |
|||
pd = [x, y, z, a]; |
|||
} |
|||
} |
|||
return pd; |
|||
} |
|||
|
|||
// 画文字的背景图片
|
|||
_doBackground(view) { |
|||
this.ctx.save(); |
|||
const { |
|||
width: rawWidth, |
|||
height: rawHeight, |
|||
} = this._preProcess(view, true); |
|||
|
|||
const { |
|||
background, |
|||
} = view.css; |
|||
let pd = this._doPaddings(view); |
|||
const width = rawWidth + pd[1] + pd[3]; |
|||
const height = rawHeight + pd[0] + pd[2]; |
|||
|
|||
this._doClip(view.css.borderRadius, width, height, view.css.borderStyle) |
|||
if (GD.api.isGradient(background)) { |
|||
GD.api.doGradient(background, width, height, this.ctx); |
|||
} else { |
|||
this.ctx.fillStyle = background; |
|||
} |
|||
this.ctx.fillRect(-(width / 2), -(height / 2), width, height); |
|||
|
|||
this.ctx.restore(); |
|||
} |
|||
|
|||
_drawQRCode(view) { |
|||
this.ctx.save(); |
|||
const { |
|||
width, |
|||
height, |
|||
} = this._preProcess(view); |
|||
QR.api.draw(view.content, this.ctx, -width / 2, -height / 2, width, height, view.css.background, view.css.color); |
|||
this.ctx.restore(); |
|||
this._doBorder(view, width, height); |
|||
} |
|||
|
|||
_drawAbsImage(view) { |
|||
if (!view.url) { |
|||
return; |
|||
} |
|||
this.ctx.save(); |
|||
const { |
|||
width, |
|||
height, |
|||
} = this._preProcess(view); |
|||
// 获得缩放到图片大小级别的裁减框
|
|||
let rWidth = view.sWidth; |
|||
let rHeight = view.sHeight; |
|||
let startX = 0; |
|||
let startY = 0; |
|||
// 绘画区域比例
|
|||
const cp = width / height; |
|||
// 原图比例
|
|||
const op = view.sWidth / view.sHeight; |
|||
if (cp >= op) { |
|||
rHeight = rWidth / cp; |
|||
startY = Math.round((view.sHeight - rHeight) / 2); |
|||
} else { |
|||
rWidth = rHeight * cp; |
|||
startX = Math.round((view.sWidth - rWidth) / 2); |
|||
} |
|||
if (view.css && view.css.mode === 'scaleToFill') { |
|||
this.ctx.drawImage(view.url, -(width / 2), -(height / 2), width, height); |
|||
} else { |
|||
this.ctx.drawImage(view.url, startX, startY, rWidth, rHeight, -(width / 2), -(height / 2), width, height); |
|||
view.rect.startX = startX / view.sWidth; |
|||
view.rect.startY = startY / view.sHeight; |
|||
view.rect.endX = (startX + rWidth) / view.sWidth; |
|||
view.rect.endY = (startY + rHeight) / view.sHeight; |
|||
} |
|||
this.ctx.restore(); |
|||
this._doBorder(view, width, height); |
|||
} |
|||
|
|||
callbackInfo = {} |
|||
_fillAbsText(view) { |
|||
if (!view.text) { |
|||
return; |
|||
} |
|||
if (view.css.background) { |
|||
// 生成背景
|
|||
this._doBackground(view); |
|||
} |
|||
this.ctx.save(); |
|||
const { |
|||
width, |
|||
height, |
|||
extra, |
|||
} = this._preProcess(view, view.css.background && view.css.borderRadius); |
|||
this.ctx.fillStyle = (view.css.color || 'black'); |
|||
if (this.isMoving && JSON.stringify(this.movingCache) !== JSON.stringify({})) { |
|||
this.globalWidth[view.id] = this.movingCache.globalWidth |
|||
this.ctx.textAlign = view.css.textAlign ? view.css.textAlign : 'left'; |
|||
for (const i of this.movingCache.lineArray) { |
|||
const { |
|||
measuredWith, |
|||
text, |
|||
x, |
|||
y, |
|||
textDecoration |
|||
} = i |
|||
if (view.css.textStyle === 'stroke') { |
|||
this.ctx.strokeText(text, x, y, measuredWith); |
|||
} else { |
|||
this.ctx.fillText(text, x, y, measuredWith); |
|||
} |
|||
if (textDecoration) { |
|||
const fontSize = view.css.fontSize.toPx(); |
|||
this.ctx.lineWidth = fontSize / 13; |
|||
this.ctx.beginPath(); |
|||
this.ctx.moveTo(...textDecoration.moveTo); |
|||
this.ctx.lineTo(...textDecoration.lineTo); |
|||
this.ctx.closePath(); |
|||
this.ctx.strokeStyle = view.css.color; |
|||
this.ctx.stroke(); |
|||
} |
|||
} |
|||
} else { |
|||
const { |
|||
lines, |
|||
lineHeight, |
|||
textArray, |
|||
linesArray, |
|||
} = extra; |
|||
// 如果设置了id,则保留 text 的长度
|
|||
if (view.id) { |
|||
let textWidth = 0; |
|||
for (let i = 0; i < textArray.length; ++i) { |
|||
const _w = this.ctx.measureText(textArray[i]).width |
|||
textWidth = _w > textWidth ? _w : textWidth; |
|||
} |
|||
this.globalWidth[view.id] = width ? (textWidth < width ? textWidth : width) : textWidth; |
|||
if (!this.isMoving) { |
|||
Object.assign(this.callbackInfo, { |
|||
globalWidth: this.globalWidth[view.id] |
|||
}) |
|||
} |
|||
} |
|||
let lineIndex = 0; |
|||
for (let j = 0; j < textArray.length; ++j) { |
|||
const preLineLength = Math.ceil(textArray[j].length / linesArray[j]); |
|||
let start = 0; |
|||
let alreadyCount = 0; |
|||
|
|||
for (let i = 0; i < linesArray[j]; ++i) { |
|||
// 绘制行数大于最大行数,则直接跳出循环
|
|||
if (lineIndex >= lines) { |
|||
break; |
|||
} |
|||
alreadyCount = preLineLength; |
|||
let text = textArray[j].substr(start, alreadyCount); |
|||
let measuredWith = this.ctx.measureText(text).width; |
|||
// 如果测量大小小于width一个字符的大小,则进行补齐,如果测量大小超出 width,则进行减除
|
|||
// 如果已经到文本末尾,也不要进行该循环
|
|||
while ((start + alreadyCount <= textArray[j].length) && (width - measuredWith > view.css.fontSize.toPx() || measuredWith - width > view.css.fontSize.toPx())) { |
|||
if (measuredWith < width) { |
|||
text = textArray[j].substr(start, ++alreadyCount); |
|||
} else { |
|||
if (text.length <= 1) { |
|||
// 如果只有一个字符时,直接跳出循环
|
|||
break; |
|||
} |
|||
text = textArray[j].substr(start, --alreadyCount); |
|||
// break;
|
|||
} |
|||
measuredWith = this.ctx.measureText(text).width; |
|||
} |
|||
start += text.length |
|||
// 如果是最后一行了,发现还有未绘制完的内容,则加...
|
|||
if (lineIndex === lines - 1 && (j < textArray.length - 1 || start < textArray[j].length)) { |
|||
while (this.ctx.measureText(`${text}...`).width > width) { |
|||
if (text.length <= 1) { |
|||
// 如果只有一个字符时,直接跳出循环
|
|||
break; |
|||
} |
|||
text = text.substring(0, text.length - 1); |
|||
} |
|||
text += '...'; |
|||
measuredWith = this.ctx.measureText(text).width; |
|||
} |
|||
this.ctx.textAlign = view.css.textAlign ? view.css.textAlign : 'left'; |
|||
let x; |
|||
let lineX; |
|||
switch (view.css.textAlign) { |
|||
case 'center': |
|||
x = 0; |
|||
lineX = x - measuredWith / 2; |
|||
break; |
|||
case 'right': |
|||
x = (width / 2); |
|||
lineX = x - measuredWith; |
|||
break; |
|||
default: |
|||
x = -(width / 2); |
|||
lineX = x; |
|||
break; |
|||
} |
|||
const y = -(height / 2) + (lineIndex === 0 ? view.css.fontSize.toPx() : (view.css.fontSize.toPx() + lineIndex * lineHeight)); |
|||
lineIndex++; |
|||
if (view.css.textStyle === 'stroke') { |
|||
this.ctx.strokeText(text, x, y, measuredWith); |
|||
} else { |
|||
this.ctx.fillText(text, x, y, measuredWith); |
|||
} |
|||
const fontSize = view.css.fontSize.toPx(); |
|||
let textDecoration; |
|||
if (view.css.textDecoration) { |
|||
this.ctx.lineWidth = fontSize / 13; |
|||
this.ctx.beginPath(); |
|||
if (/\bunderline\b/.test(view.css.textDecoration)) { |
|||
this.ctx.moveTo(lineX, y); |
|||
this.ctx.lineTo(lineX + measuredWith, y); |
|||
textDecoration = { |
|||
moveTo: [lineX, y], |
|||
lineTo: [lineX + measuredWith, y] |
|||
} |
|||
} |
|||
if (/\boverline\b/.test(view.css.textDecoration)) { |
|||
this.ctx.moveTo(lineX, y - fontSize); |
|||
this.ctx.lineTo(lineX + measuredWith, y - fontSize); |
|||
textDecoration = { |
|||
moveTo: [lineX, y - fontSize], |
|||
lineTo: [lineX + measuredWith, y - fontSize] |
|||
} |
|||
} |
|||
if (/\bline-through\b/.test(view.css.textDecoration)) { |
|||
this.ctx.moveTo(lineX, y - fontSize / 3); |
|||
this.ctx.lineTo(lineX + measuredWith, y - fontSize / 3); |
|||
textDecoration = { |
|||
moveTo: [lineX, y - fontSize / 3], |
|||
lineTo: [lineX + measuredWith, y - fontSize / 3] |
|||
} |
|||
} |
|||
this.ctx.closePath(); |
|||
this.ctx.strokeStyle = view.css.color; |
|||
this.ctx.stroke(); |
|||
} |
|||
if (!this.isMoving) { |
|||
this.callbackInfo.lineArray ? this.callbackInfo.lineArray.push({ |
|||
text, |
|||
x, |
|||
y, |
|||
measuredWith, |
|||
textDecoration |
|||
}) : this.callbackInfo.lineArray = [{ |
|||
text, |
|||
x, |
|||
y, |
|||
measuredWith, |
|||
textDecoration |
|||
}] |
|||
} |
|||
} |
|||
} |
|||
} |
|||
this.ctx.restore(); |
|||
this._doBorder(view, width, height); |
|||
} |
|||
|
|||
_drawAbsRect(view) { |
|||
this.ctx.save(); |
|||
const { |
|||
width, |
|||
height, |
|||
} = this._preProcess(view); |
|||
if (GD.api.isGradient(view.css.color)) { |
|||
GD.api.doGradient(view.css.color, width, height, this.ctx); |
|||
} else { |
|||
this.ctx.fillStyle = view.css.color; |
|||
} |
|||
const { |
|||
borderRadius, |
|||
borderStyle, |
|||
borderWidth |
|||
} = view.css |
|||
this._border({ |
|||
borderRadius, |
|||
width, |
|||
height, |
|||
borderWidth, |
|||
borderStyle |
|||
}) |
|||
this.ctx.fill(); |
|||
this.ctx.restore(); |
|||
this._doBorder(view, width, height); |
|||
} |
|||
|
|||
// shadow 支持 (x, y, blur, color), 不支持 spread
|
|||
// shadow:0px 0px 10px rgba(0,0,0,0.1);
|
|||
_doShadow(view) { |
|||
if (!view.css || !view.css.shadow) { |
|||
return; |
|||
} |
|||
const box = view.css.shadow.replace(/,\s+/g, ',').split(/\s+/); |
|||
if (box.length > 4) { |
|||
console.error('shadow don\'t spread option'); |
|||
return; |
|||
} |
|||
this.ctx.shadowOffsetX = parseInt(box[0], 10); |
|||
this.ctx.shadowOffsetY = parseInt(box[1], 10); |
|||
this.ctx.shadowBlur = parseInt(box[2], 10); |
|||
this.ctx.shadowColor = box[3]; |
|||
} |
|||
|
|||
_getAngle(angle) { |
|||
return Number(angle) * Math.PI / 180; |
|||
} |
|||
} |
@ -0,0 +1,784 @@ |
|||
/* eslint-disable */ |
|||
!(function () { |
|||
|
|||
// alignment pattern
|
|||
var adelta = [ |
|||
0, 11, 15, 19, 23, 27, 31, |
|||
16, 18, 20, 22, 24, 26, 28, 20, 22, 24, 24, 26, 28, 28, 22, 24, 24, |
|||
26, 26, 28, 28, 24, 24, 26, 26, 26, 28, 28, 24, 26, 26, 26, 28, 28 |
|||
]; |
|||
|
|||
// version block
|
|||
var vpat = [ |
|||
0xc94, 0x5bc, 0xa99, 0x4d3, 0xbf6, 0x762, 0x847, 0x60d, |
|||
0x928, 0xb78, 0x45d, 0xa17, 0x532, 0x9a6, 0x683, 0x8c9, |
|||
0x7ec, 0xec4, 0x1e1, 0xfab, 0x08e, 0xc1a, 0x33f, 0xd75, |
|||
0x250, 0x9d5, 0x6f0, 0x8ba, 0x79f, 0xb0b, 0x42e, 0xa64, |
|||
0x541, 0xc69 |
|||
]; |
|||
|
|||
// final format bits with mask: level << 3 | mask
|
|||
var fmtword = [ |
|||
0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976, //L
|
|||
0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0, //M
|
|||
0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed, //Q
|
|||
0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b //H
|
|||
]; |
|||
|
|||
// 4 per version: number of blocks 1,2; data width; ecc width
|
|||
var eccblocks = [ |
|||
1, 0, 19, 7, 1, 0, 16, 10, 1, 0, 13, 13, 1, 0, 9, 17, |
|||
1, 0, 34, 10, 1, 0, 28, 16, 1, 0, 22, 22, 1, 0, 16, 28, |
|||
1, 0, 55, 15, 1, 0, 44, 26, 2, 0, 17, 18, 2, 0, 13, 22, |
|||
1, 0, 80, 20, 2, 0, 32, 18, 2, 0, 24, 26, 4, 0, 9, 16, |
|||
1, 0, 108, 26, 2, 0, 43, 24, 2, 2, 15, 18, 2, 2, 11, 22, |
|||
2, 0, 68, 18, 4, 0, 27, 16, 4, 0, 19, 24, 4, 0, 15, 28, |
|||
2, 0, 78, 20, 4, 0, 31, 18, 2, 4, 14, 18, 4, 1, 13, 26, |
|||
2, 0, 97, 24, 2, 2, 38, 22, 4, 2, 18, 22, 4, 2, 14, 26, |
|||
2, 0, 116, 30, 3, 2, 36, 22, 4, 4, 16, 20, 4, 4, 12, 24, |
|||
2, 2, 68, 18, 4, 1, 43, 26, 6, 2, 19, 24, 6, 2, 15, 28, |
|||
4, 0, 81, 20, 1, 4, 50, 30, 4, 4, 22, 28, 3, 8, 12, 24, |
|||
2, 2, 92, 24, 6, 2, 36, 22, 4, 6, 20, 26, 7, 4, 14, 28, |
|||
4, 0, 107, 26, 8, 1, 37, 22, 8, 4, 20, 24, 12, 4, 11, 22, |
|||
3, 1, 115, 30, 4, 5, 40, 24, 11, 5, 16, 20, 11, 5, 12, 24, |
|||
5, 1, 87, 22, 5, 5, 41, 24, 5, 7, 24, 30, 11, 7, 12, 24, |
|||
5, 1, 98, 24, 7, 3, 45, 28, 15, 2, 19, 24, 3, 13, 15, 30, |
|||
1, 5, 107, 28, 10, 1, 46, 28, 1, 15, 22, 28, 2, 17, 14, 28, |
|||
5, 1, 120, 30, 9, 4, 43, 26, 17, 1, 22, 28, 2, 19, 14, 28, |
|||
3, 4, 113, 28, 3, 11, 44, 26, 17, 4, 21, 26, 9, 16, 13, 26, |
|||
3, 5, 107, 28, 3, 13, 41, 26, 15, 5, 24, 30, 15, 10, 15, 28, |
|||
4, 4, 116, 28, 17, 0, 42, 26, 17, 6, 22, 28, 19, 6, 16, 30, |
|||
2, 7, 111, 28, 17, 0, 46, 28, 7, 16, 24, 30, 34, 0, 13, 24, |
|||
4, 5, 121, 30, 4, 14, 47, 28, 11, 14, 24, 30, 16, 14, 15, 30, |
|||
6, 4, 117, 30, 6, 14, 45, 28, 11, 16, 24, 30, 30, 2, 16, 30, |
|||
8, 4, 106, 26, 8, 13, 47, 28, 7, 22, 24, 30, 22, 13, 15, 30, |
|||
10, 2, 114, 28, 19, 4, 46, 28, 28, 6, 22, 28, 33, 4, 16, 30, |
|||
8, 4, 122, 30, 22, 3, 45, 28, 8, 26, 23, 30, 12, 28, 15, 30, |
|||
3, 10, 117, 30, 3, 23, 45, 28, 4, 31, 24, 30, 11, 31, 15, 30, |
|||
7, 7, 116, 30, 21, 7, 45, 28, 1, 37, 23, 30, 19, 26, 15, 30, |
|||
5, 10, 115, 30, 19, 10, 47, 28, 15, 25, 24, 30, 23, 25, 15, 30, |
|||
13, 3, 115, 30, 2, 29, 46, 28, 42, 1, 24, 30, 23, 28, 15, 30, |
|||
17, 0, 115, 30, 10, 23, 46, 28, 10, 35, 24, 30, 19, 35, 15, 30, |
|||
17, 1, 115, 30, 14, 21, 46, 28, 29, 19, 24, 30, 11, 46, 15, 30, |
|||
13, 6, 115, 30, 14, 23, 46, 28, 44, 7, 24, 30, 59, 1, 16, 30, |
|||
12, 7, 121, 30, 12, 26, 47, 28, 39, 14, 24, 30, 22, 41, 15, 30, |
|||
6, 14, 121, 30, 6, 34, 47, 28, 46, 10, 24, 30, 2, 64, 15, 30, |
|||
17, 4, 122, 30, 29, 14, 46, 28, 49, 10, 24, 30, 24, 46, 15, 30, |
|||
4, 18, 122, 30, 13, 32, 46, 28, 48, 14, 24, 30, 42, 32, 15, 30, |
|||
20, 4, 117, 30, 40, 7, 47, 28, 43, 22, 24, 30, 10, 67, 15, 30, |
|||
19, 6, 118, 30, 18, 31, 47, 28, 34, 34, 24, 30, 20, 61, 15, 30 |
|||
]; |
|||
|
|||
// Galois field log table
|
|||
var glog = [ |
|||
0xff, 0x00, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, |
|||
0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71, |
|||
0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, |
|||
0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6, |
|||
0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88, |
|||
0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, |
|||
0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, |
|||
0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, |
|||
0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, |
|||
0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, |
|||
0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, |
|||
0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, |
|||
0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6, |
|||
0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, |
|||
0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, |
|||
0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf |
|||
]; |
|||
|
|||
// Galios field exponent table
|
|||
var gexp = [ |
|||
0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, |
|||
0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, |
|||
0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, |
|||
0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, |
|||
0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, |
|||
0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, |
|||
0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, |
|||
0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, |
|||
0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, |
|||
0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, |
|||
0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, |
|||
0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, |
|||
0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, |
|||
0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09, |
|||
0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16, |
|||
0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x00 |
|||
]; |
|||
|
|||
// Working buffers:
|
|||
// data input and ecc append, image working buffer, fixed part of image, run lengths for badness
|
|||
var strinbuf = [], eccbuf = [], qrframe = [], framask = [], rlens = []; |
|||
// Control values - width is based on version, last 4 are from table.
|
|||
var version, width, neccblk1, neccblk2, datablkw, eccblkwid; |
|||
var ecclevel = 2; |
|||
// set bit to indicate cell in qrframe is immutable. symmetric around diagonal
|
|||
function setmask(x, y) { |
|||
var bt; |
|||
if (x > y) { |
|||
bt = x; |
|||
x = y; |
|||
y = bt; |
|||
} |
|||
// y*y = 1+3+5...
|
|||
bt = y; |
|||
bt *= y; |
|||
bt += y; |
|||
bt >>= 1; |
|||
bt += x; |
|||
framask[bt] = 1; |
|||
} |
|||
|
|||
// enter alignment pattern - black to qrframe, white to mask (later black frame merged to mask)
|
|||
function putalign(x, y) { |
|||
var j; |
|||
|
|||
qrframe[x + width * y] = 1; |
|||
for (j = -2; j < 2; j++) { |
|||
qrframe[(x + j) + width * (y - 2)] = 1; |
|||
qrframe[(x - 2) + width * (y + j + 1)] = 1; |
|||
qrframe[(x + 2) + width * (y + j)] = 1; |
|||
qrframe[(x + j + 1) + width * (y + 2)] = 1; |
|||
} |
|||
for (j = 0; j < 2; j++) { |
|||
setmask(x - 1, y + j); |
|||
setmask(x + 1, y - j); |
|||
setmask(x - j, y - 1); |
|||
setmask(x + j, y + 1); |
|||
} |
|||
} |
|||
|
|||
//========================================================================
|
|||
// Reed Solomon error correction
|
|||
// exponentiation mod N
|
|||
function modnn(x) { |
|||
while (x >= 255) { |
|||
x -= 255; |
|||
x = (x >> 8) + (x & 255); |
|||
} |
|||
return x; |
|||
} |
|||
|
|||
var genpoly = []; |
|||
|
|||
// Calculate and append ECC data to data block. Block is in strinbuf, indexes to buffers given.
|
|||
function appendrs(data, dlen, ecbuf, eclen) { |
|||
var i, j, fb; |
|||
|
|||
for (i = 0; i < eclen; i++) |
|||
strinbuf[ecbuf + i] = 0; |
|||
for (i = 0; i < dlen; i++) { |
|||
fb = glog[strinbuf[data + i] ^ strinbuf[ecbuf]]; |
|||
if (fb != 255) /* fb term is non-zero */ |
|||
for (j = 1; j < eclen; j++) |
|||
strinbuf[ecbuf + j - 1] = strinbuf[ecbuf + j] ^ gexp[modnn(fb + genpoly[eclen - j])]; |
|||
else |
|||
for (j = ecbuf; j < ecbuf + eclen; j++) |
|||
strinbuf[j] = strinbuf[j + 1]; |
|||
strinbuf[ecbuf + eclen - 1] = fb == 255 ? 0 : gexp[modnn(fb + genpoly[0])]; |
|||
} |
|||
} |
|||
|
|||
//========================================================================
|
|||
// Frame data insert following the path rules
|
|||
|
|||
// check mask - since symmetrical use half.
|
|||
function ismasked(x, y) { |
|||
var bt; |
|||
if (x > y) { |
|||
bt = x; |
|||
x = y; |
|||
y = bt; |
|||
} |
|||
bt = y; |
|||
bt += y * y; |
|||
bt >>= 1; |
|||
bt += x; |
|||
return framask[bt]; |
|||
} |
|||
|
|||
//========================================================================
|
|||
// Apply the selected mask out of the 8.
|
|||
function applymask(m) { |
|||
var x, y, r3x, r3y; |
|||
|
|||
switch (m) { |
|||
case 0: |
|||
for (y = 0; y < width; y++) |
|||
for (x = 0; x < width; x++) |
|||
if (!((x + y) & 1) && !ismasked(x, y)) |
|||
qrframe[x + y * width] ^= 1; |
|||
break; |
|||
case 1: |
|||
for (y = 0; y < width; y++) |
|||
for (x = 0; x < width; x++) |
|||
if (!(y & 1) && !ismasked(x, y)) |
|||
qrframe[x + y * width] ^= 1; |
|||
break; |
|||
case 2: |
|||
for (y = 0; y < width; y++) |
|||
for (r3x = 0, x = 0; x < width; x++ , r3x++) { |
|||
if (r3x == 3) |
|||
r3x = 0; |
|||
if (!r3x && !ismasked(x, y)) |
|||
qrframe[x + y * width] ^= 1; |
|||
} |
|||
break; |
|||
case 3: |
|||
for (r3y = 0, y = 0; y < width; y++ , r3y++) { |
|||
if (r3y == 3) |
|||
r3y = 0; |
|||
for (r3x = r3y, x = 0; x < width; x++ , r3x++) { |
|||
if (r3x == 3) |
|||
r3x = 0; |
|||
if (!r3x && !ismasked(x, y)) |
|||
qrframe[x + y * width] ^= 1; |
|||
} |
|||
} |
|||
break; |
|||
case 4: |
|||
for (y = 0; y < width; y++) |
|||
for (r3x = 0, r3y = ((y >> 1) & 1), x = 0; x < width; x++ , r3x++) { |
|||
if (r3x == 3) { |
|||
r3x = 0; |
|||
r3y = !r3y; |
|||
} |
|||
if (!r3y && !ismasked(x, y)) |
|||
qrframe[x + y * width] ^= 1; |
|||
} |
|||
break; |
|||
case 5: |
|||
for (r3y = 0, y = 0; y < width; y++ , r3y++) { |
|||
if (r3y == 3) |
|||
r3y = 0; |
|||
for (r3x = 0, x = 0; x < width; x++ , r3x++) { |
|||
if (r3x == 3) |
|||
r3x = 0; |
|||
if (!((x & y & 1) + !(!r3x | !r3y)) && !ismasked(x, y)) |
|||
qrframe[x + y * width] ^= 1; |
|||
} |
|||
} |
|||
break; |
|||
case 6: |
|||
for (r3y = 0, y = 0; y < width; y++ , r3y++) { |
|||
if (r3y == 3) |
|||
r3y = 0; |
|||
for (r3x = 0, x = 0; x < width; x++ , r3x++) { |
|||
if (r3x == 3) |
|||
r3x = 0; |
|||
if (!(((x & y & 1) + (r3x && (r3x == r3y))) & 1) && !ismasked(x, y)) |
|||
qrframe[x + y * width] ^= 1; |
|||
} |
|||
} |
|||
break; |
|||
case 7: |
|||
for (r3y = 0, y = 0; y < width; y++ , r3y++) { |
|||
if (r3y == 3) |
|||
r3y = 0; |
|||
for (r3x = 0, x = 0; x < width; x++ , r3x++) { |
|||
if (r3x == 3) |
|||
r3x = 0; |
|||
if (!(((r3x && (r3x == r3y)) + ((x + y) & 1)) & 1) && !ismasked(x, y)) |
|||
qrframe[x + y * width] ^= 1; |
|||
} |
|||
} |
|||
break; |
|||
} |
|||
return; |
|||
} |
|||
|
|||
// Badness coefficients.
|
|||
var N1 = 3, N2 = 3, N3 = 40, N4 = 10; |
|||
|
|||
// Using the table of the length of each run, calculate the amount of bad image
|
|||
// - long runs or those that look like finders; called twice, once each for X and Y
|
|||
function badruns(length) { |
|||
var i; |
|||
var runsbad = 0; |
|||
for (i = 0; i <= length; i++) |
|||
if (rlens[i] >= 5) |
|||
runsbad += N1 + rlens[i] - 5; |
|||
// BwBBBwB as in finder
|
|||
for (i = 3; i < length - 1; i += 2) |
|||
if (rlens[i - 2] == rlens[i + 2] |
|||
&& rlens[i + 2] == rlens[i - 1] |
|||
&& rlens[i - 1] == rlens[i + 1] |
|||
&& rlens[i - 1] * 3 == rlens[i] |
|||
// white around the black pattern? Not part of spec
|
|||
&& (rlens[i - 3] == 0 // beginning
|
|||
|| i + 3 > length // end
|
|||
|| rlens[i - 3] * 3 >= rlens[i] * 4 || rlens[i + 3] * 3 >= rlens[i] * 4) |
|||
) |
|||
runsbad += N3; |
|||
return runsbad; |
|||
} |
|||
|
|||
// Calculate how bad the masked image is - blocks, imbalance, runs, or finders.
|
|||
function badcheck() { |
|||
var x, y, h, b, b1; |
|||
var thisbad = 0; |
|||
var bw = 0; |
|||
|
|||
// blocks of same color.
|
|||
for (y = 0; y < width - 1; y++) |
|||
for (x = 0; x < width - 1; x++) |
|||
if ((qrframe[x + width * y] && qrframe[(x + 1) + width * y] |
|||
&& qrframe[x + width * (y + 1)] && qrframe[(x + 1) + width * (y + 1)]) // all black
|
|||
|| !(qrframe[x + width * y] || qrframe[(x + 1) + width * y] |
|||
|| qrframe[x + width * (y + 1)] || qrframe[(x + 1) + width * (y + 1)])) // all white
|
|||
thisbad += N2; |
|||
|
|||
// X runs
|
|||
for (y = 0; y < width; y++) { |
|||
rlens[0] = 0; |
|||
for (h = b = x = 0; x < width; x++) { |
|||
if ((b1 = qrframe[x + width * y]) == b) |
|||
rlens[h]++; |
|||
else |
|||
rlens[++h] = 1; |
|||
b = b1; |
|||
bw += b ? 1 : -1; |
|||
} |
|||
thisbad += badruns(h); |
|||
} |
|||
|
|||
// black/white imbalance
|
|||
if (bw < 0) |
|||
bw = -bw; |
|||
|
|||
var big = bw; |
|||
var count = 0; |
|||
big += big << 2; |
|||
big <<= 1; |
|||
while (big > width * width) |
|||
big -= width * width, count++; |
|||
thisbad += count * N4; |
|||
|
|||
// Y runs
|
|||
for (x = 0; x < width; x++) { |
|||
rlens[0] = 0; |
|||
for (h = b = y = 0; y < width; y++) { |
|||
if ((b1 = qrframe[x + width * y]) == b) |
|||
rlens[h]++; |
|||
else |
|||
rlens[++h] = 1; |
|||
b = b1; |
|||
} |
|||
thisbad += badruns(h); |
|||
} |
|||
return thisbad; |
|||
} |
|||
|
|||
function genframe(instring) { |
|||
var x, y, k, t, v, i, j, m; |
|||
|
|||
// find the smallest version that fits the string
|
|||
t = instring.length; |
|||
version = 0; |
|||
do { |
|||
version++; |
|||
k = (ecclevel - 1) * 4 + (version - 1) * 16; |
|||
neccblk1 = eccblocks[k++]; |
|||
neccblk2 = eccblocks[k++]; |
|||
datablkw = eccblocks[k++]; |
|||
eccblkwid = eccblocks[k]; |
|||
k = datablkw * (neccblk1 + neccblk2) + neccblk2 - 3 + (version <= 9); |
|||
if (t <= k) |
|||
break; |
|||
} while (version < 40); |
|||
|
|||
// FIXME - insure that it fits insted of being truncated
|
|||
width = 17 + 4 * version; |
|||
|
|||
// allocate, clear and setup data structures
|
|||
v = datablkw + (datablkw + eccblkwid) * (neccblk1 + neccblk2) + neccblk2; |
|||
for (t = 0; t < v; t++) |
|||
eccbuf[t] = 0; |
|||
strinbuf = instring.slice(0); |
|||
|
|||
for (t = 0; t < width * width; t++) |
|||
qrframe[t] = 0; |
|||
|
|||
for (t = 0; t < (width * (width + 1) + 1) / 2; t++) |
|||
framask[t] = 0; |
|||
|
|||
// insert finders - black to frame, white to mask
|
|||
for (t = 0; t < 3; t++) { |
|||
k = 0; |
|||
y = 0; |
|||
if (t == 1) |
|||
k = (width - 7); |
|||
if (t == 2) |
|||
y = (width - 7); |
|||
qrframe[(y + 3) + width * (k + 3)] = 1; |
|||
for (x = 0; x < 6; x++) { |
|||
qrframe[(y + x) + width * k] = 1; |
|||
qrframe[y + width * (k + x + 1)] = 1; |
|||
qrframe[(y + 6) + width * (k + x)] = 1; |
|||
qrframe[(y + x + 1) + width * (k + 6)] = 1; |
|||
} |
|||
for (x = 1; x < 5; x++) { |
|||
setmask(y + x, k + 1); |
|||
setmask(y + 1, k + x + 1); |
|||
setmask(y + 5, k + x); |
|||
setmask(y + x + 1, k + 5); |
|||
} |
|||
for (x = 2; x < 4; x++) { |
|||
qrframe[(y + x) + width * (k + 2)] = 1; |
|||
qrframe[(y + 2) + width * (k + x + 1)] = 1; |
|||
qrframe[(y + 4) + width * (k + x)] = 1; |
|||
qrframe[(y + x + 1) + width * (k + 4)] = 1; |
|||
} |
|||
} |
|||
|
|||
// alignment blocks
|
|||
if (version > 1) { |
|||
t = adelta[version]; |
|||
y = width - 7; |
|||
for (; ;) { |
|||
x = width - 7; |
|||
while (x > t - 3) { |
|||
putalign(x, y); |
|||
if (x < t) |
|||
break; |
|||
x -= t; |
|||
} |
|||
if (y <= t + 9) |
|||
break; |
|||
y -= t; |
|||
putalign(6, y); |
|||
putalign(y, 6); |
|||
} |
|||
} |
|||
|
|||
// single black
|
|||
qrframe[8 + width * (width - 8)] = 1; |
|||
|
|||
// timing gap - mask only
|
|||
for (y = 0; y < 7; y++) { |
|||
setmask(7, y); |
|||
setmask(width - 8, y); |
|||
setmask(7, y + width - 7); |
|||
} |
|||
for (x = 0; x < 8; x++) { |
|||
setmask(x, 7); |
|||
setmask(x + width - 8, 7); |
|||
setmask(x, width - 8); |
|||
} |
|||
|
|||
// reserve mask-format area
|
|||
for (x = 0; x < 9; x++) |
|||
setmask(x, 8); |
|||
for (x = 0; x < 8; x++) { |
|||
setmask(x + width - 8, 8); |
|||
setmask(8, x); |
|||
} |
|||
for (y = 0; y < 7; y++) |
|||
setmask(8, y + width - 7); |
|||
|
|||
// timing row/col
|
|||
for (x = 0; x < width - 14; x++) |
|||
if (x & 1) { |
|||
setmask(8 + x, 6); |
|||
setmask(6, 8 + x); |
|||
} |
|||
else { |
|||
qrframe[(8 + x) + width * 6] = 1; |
|||
qrframe[6 + width * (8 + x)] = 1; |
|||
} |
|||
|
|||
// version block
|
|||
if (version > 6) { |
|||
t = vpat[version - 7]; |
|||
k = 17; |
|||
for (x = 0; x < 6; x++) |
|||
for (y = 0; y < 3; y++ , k--) |
|||
if (1 & (k > 11 ? version >> (k - 12) : t >> k)) { |
|||
qrframe[(5 - x) + width * (2 - y + width - 11)] = 1; |
|||
qrframe[(2 - y + width - 11) + width * (5 - x)] = 1; |
|||
} |
|||
else { |
|||
setmask(5 - x, 2 - y + width - 11); |
|||
setmask(2 - y + width - 11, 5 - x); |
|||
} |
|||
} |
|||
|
|||
// sync mask bits - only set above for white spaces, so add in black bits
|
|||
for (y = 0; y < width; y++) |
|||
for (x = 0; x <= y; x++) |
|||
if (qrframe[x + width * y]) |
|||
setmask(x, y); |
|||
|
|||
// convert string to bitstream
|
|||
// 8 bit data to QR-coded 8 bit data (numeric or alphanum, or kanji not supported)
|
|||
v = strinbuf.length; |
|||
|
|||
// string to array
|
|||
for (i = 0; i < v; i++) |
|||
eccbuf[i] = strinbuf.charCodeAt(i); |
|||
strinbuf = eccbuf.slice(0); |
|||
|
|||
// calculate max string length
|
|||
x = datablkw * (neccblk1 + neccblk2) + neccblk2; |
|||
if (v >= x - 2) { |
|||
v = x - 2; |
|||
if (version > 9) |
|||
v--; |
|||
} |
|||
|
|||
// shift and repack to insert length prefix
|
|||
i = v; |
|||
if (version > 9) { |
|||
strinbuf[i + 2] = 0; |
|||
strinbuf[i + 3] = 0; |
|||
while (i--) { |
|||
t = strinbuf[i]; |
|||
strinbuf[i + 3] |= 255 & (t << 4); |
|||
strinbuf[i + 2] = t >> 4; |
|||
} |
|||
strinbuf[2] |= 255 & (v << 4); |
|||
strinbuf[1] = v >> 4; |
|||
strinbuf[0] = 0x40 | (v >> 12); |
|||
} |
|||
else { |
|||
strinbuf[i + 1] = 0; |
|||
strinbuf[i + 2] = 0; |
|||
while (i--) { |
|||
t = strinbuf[i]; |
|||
strinbuf[i + 2] |= 255 & (t << 4); |
|||
strinbuf[i + 1] = t >> 4; |
|||
} |
|||
strinbuf[1] |= 255 & (v << 4); |
|||
strinbuf[0] = 0x40 | (v >> 4); |
|||
} |
|||
// fill to end with pad pattern
|
|||
i = v + 3 - (version < 10); |
|||
while (i < x) { |
|||
strinbuf[i++] = 0xec; |
|||
// buffer has room if (i == x) break;
|
|||
strinbuf[i++] = 0x11; |
|||
} |
|||
|
|||
// calculate and append ECC
|
|||
|
|||
// calculate generator polynomial
|
|||
genpoly[0] = 1; |
|||
for (i = 0; i < eccblkwid; i++) { |
|||
genpoly[i + 1] = 1; |
|||
for (j = i; j > 0; j--) |
|||
genpoly[j] = genpoly[j] |
|||
? genpoly[j - 1] ^ gexp[modnn(glog[genpoly[j]] + i)] : genpoly[j - 1]; |
|||
genpoly[0] = gexp[modnn(glog[genpoly[0]] + i)]; |
|||
} |
|||
for (i = 0; i <= eccblkwid; i++) |
|||
genpoly[i] = glog[genpoly[i]]; // use logs for genpoly[] to save calc step
|
|||
|
|||
// append ecc to data buffer
|
|||
k = x; |
|||
y = 0; |
|||
for (i = 0; i < neccblk1; i++) { |
|||
appendrs(y, datablkw, k, eccblkwid); |
|||
y += datablkw; |
|||
k += eccblkwid; |
|||
} |
|||
for (i = 0; i < neccblk2; i++) { |
|||
appendrs(y, datablkw + 1, k, eccblkwid); |
|||
y += datablkw + 1; |
|||
k += eccblkwid; |
|||
} |
|||
// interleave blocks
|
|||
y = 0; |
|||
for (i = 0; i < datablkw; i++) { |
|||
for (j = 0; j < neccblk1; j++) |
|||
eccbuf[y++] = strinbuf[i + j * datablkw]; |
|||
for (j = 0; j < neccblk2; j++) |
|||
eccbuf[y++] = strinbuf[(neccblk1 * datablkw) + i + (j * (datablkw + 1))]; |
|||
} |
|||
for (j = 0; j < neccblk2; j++) |
|||
eccbuf[y++] = strinbuf[(neccblk1 * datablkw) + i + (j * (datablkw + 1))]; |
|||
for (i = 0; i < eccblkwid; i++) |
|||
for (j = 0; j < neccblk1 + neccblk2; j++) |
|||
eccbuf[y++] = strinbuf[x + i + j * eccblkwid]; |
|||
strinbuf = eccbuf; |
|||
|
|||
// pack bits into frame avoiding masked area.
|
|||
x = y = width - 1; |
|||
k = v = 1; // up, minus
|
|||
/* inteleaved data and ecc codes */ |
|||
m = (datablkw + eccblkwid) * (neccblk1 + neccblk2) + neccblk2; |
|||
for (i = 0; i < m; i++) { |
|||
t = strinbuf[i]; |
|||
for (j = 0; j < 8; j++ , t <<= 1) { |
|||
if (0x80 & t) |
|||
qrframe[x + width * y] = 1; |
|||
do { // find next fill position
|
|||
if (v) |
|||
x--; |
|||
else { |
|||
x++; |
|||
if (k) { |
|||
if (y != 0) |
|||
y--; |
|||
else { |
|||
x -= 2; |
|||
k = !k; |
|||
if (x == 6) { |
|||
x--; |
|||
y = 9; |
|||
} |
|||
} |
|||
} |
|||
else { |
|||
if (y != width - 1) |
|||
y++; |
|||
else { |
|||
x -= 2; |
|||
k = !k; |
|||
if (x == 6) { |
|||
x--; |
|||
y -= 8; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
v = !v; |
|||
} while (ismasked(x, y)); |
|||
} |
|||
} |
|||
|
|||
// save pre-mask copy of frame
|
|||
strinbuf = qrframe.slice(0); |
|||
t = 0; // best
|
|||
y = 30000; // demerit
|
|||
// for instead of while since in original arduino code
|
|||
// if an early mask was "good enough" it wouldn't try for a better one
|
|||
// since they get more complex and take longer.
|
|||
for (k = 0; k < 8; k++) { |
|||
applymask(k); // returns black-white imbalance
|
|||
x = badcheck(); |
|||
if (x < y) { // current mask better than previous best?
|
|||
y = x; |
|||
t = k; |
|||
} |
|||
if (t == 7) |
|||
break; // don't increment i to a void redoing mask
|
|||
qrframe = strinbuf.slice(0); // reset for next pass
|
|||
} |
|||
if (t != k) // redo best mask - none good enough, last wasn't t
|
|||
applymask(t); |
|||
|
|||
// add in final mask/ecclevel bytes
|
|||
y = fmtword[t + ((ecclevel - 1) << 3)]; |
|||
// low byte
|
|||
for (k = 0; k < 8; k++ , y >>= 1) |
|||
if (y & 1) { |
|||
qrframe[(width - 1 - k) + width * 8] = 1; |
|||
if (k < 6) |
|||
qrframe[8 + width * k] = 1; |
|||
else |
|||
qrframe[8 + width * (k + 1)] = 1; |
|||
} |
|||
// high byte
|
|||
for (k = 0; k < 7; k++ , y >>= 1) |
|||
if (y & 1) { |
|||
qrframe[8 + width * (width - 7 + k)] = 1; |
|||
if (k) |
|||
qrframe[(6 - k) + width * 8] = 1; |
|||
else |
|||
qrframe[7 + width * 8] = 1; |
|||
} |
|||
return qrframe; |
|||
} |
|||
|
|||
|
|||
|
|||
|
|||
var _canvas = null; |
|||
|
|||
var api = { |
|||
|
|||
get ecclevel() { |
|||
return ecclevel; |
|||
}, |
|||
|
|||
set ecclevel(val) { |
|||
ecclevel = val; |
|||
}, |
|||
|
|||
get size() { |
|||
return _size; |
|||
}, |
|||
|
|||
set size(val) { |
|||
_size = val |
|||
}, |
|||
|
|||
get canvas() { |
|||
return _canvas; |
|||
}, |
|||
|
|||
set canvas(el) { |
|||
_canvas = el; |
|||
}, |
|||
|
|||
getFrame: function (string) { |
|||
return genframe(string); |
|||
}, |
|||
//这里的utf16to8(str)是对Text中的字符串进行转码,让其支持中文
|
|||
utf16to8: function (str) { |
|||
var out, i, len, c; |
|||
|
|||
out = ""; |
|||
len = str.length; |
|||
for (i = 0; i < len; i++) { |
|||
c = str.charCodeAt(i); |
|||
if ((c >= 0x0001) && (c <= 0x007F)) { |
|||
out += str.charAt(i); |
|||
} else if (c > 0x07FF) { |
|||
out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F)); |
|||
out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F)); |
|||
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); |
|||
} else { |
|||
out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F)); |
|||
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); |
|||
} |
|||
} |
|||
return out; |
|||
}, |
|||
/** |
|||
* 新增$this参数,传入组件的this,兼容在组件中生成 |
|||
* @param bg 目前只能设置颜色值 |
|||
*/ |
|||
draw: function (str, ctx, startX, startY, cavW, cavH, bg, color, $this, ecc) { |
|||
var that = this; |
|||
ecclevel = ecc || ecclevel; |
|||
if (!ctx) { |
|||
console.warn('No canvas provided to draw QR code in!') |
|||
return; |
|||
} |
|||
var size = Math.min(cavW, cavH); |
|||
str = that.utf16to8(str);//增加中文显示
|
|||
|
|||
var frame = that.getFrame(str); |
|||
var px = size / width; |
|||
if (bg) { |
|||
ctx.fillStyle = bg; |
|||
ctx.fillRect(startX, startY, cavW, cavW); |
|||
} |
|||
ctx.fillStyle = color || 'black'; |
|||
for (var i = 0; i < width; i++) { |
|||
for (var j = 0; j < width; j++) { |
|||
if (frame[j * width + i]) { |
|||
ctx.fillRect(startX + px * i, startY + px * j, px, px); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
module.exports = { api } |
|||
// exports.draw = api;
|
|||
|
|||
})(); |
@ -0,0 +1,68 @@ |
|||
|
|||
function isValidUrl(url) { |
|||
return /(ht|f)tp(s?):\/\/([^ \\/]*\.)+[^ \\/]*(:[0-9]+)?\/?/.test(url); |
|||
} |
|||
|
|||
/** |
|||
* 深度对比两个对象是否一致 |
|||
* from: https://github.com/epoberezkin/fast-deep-equal
|
|||
* @param {Object} a 对象a |
|||
* @param {Object} b 对象b |
|||
* @return {Boolean} 是否相同 |
|||
*/ |
|||
/* eslint-disable */ |
|||
function equal(a, b) { |
|||
if (a === b) return true; |
|||
|
|||
if (a && b && typeof a == 'object' && typeof b == 'object') { |
|||
var arrA = Array.isArray(a) |
|||
, arrB = Array.isArray(b) |
|||
, i |
|||
, length |
|||
, key; |
|||
|
|||
if (arrA && arrB) { |
|||
length = a.length; |
|||
if (length != b.length) return false; |
|||
for (i = length; i-- !== 0;) |
|||
if (!equal(a[i], b[i])) return false; |
|||
return true; |
|||
} |
|||
|
|||
if (arrA != arrB) return false; |
|||
|
|||
var dateA = a instanceof Date |
|||
, dateB = b instanceof Date; |
|||
if (dateA != dateB) return false; |
|||
if (dateA && dateB) return a.getTime() == b.getTime(); |
|||
|
|||
var regexpA = a instanceof RegExp |
|||
, regexpB = b instanceof RegExp; |
|||
if (regexpA != regexpB) return false; |
|||
if (regexpA && regexpB) return a.toString() == b.toString(); |
|||
|
|||
var keys = Object.keys(a); |
|||
length = keys.length; |
|||
|
|||
if (length !== Object.keys(b).length) |
|||
return false; |
|||
|
|||
for (i = length; i-- !== 0;) |
|||
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; |
|||
|
|||
for (i = length; i-- !== 0;) { |
|||
key = keys[i]; |
|||
if (!equal(a[key], b[key])) return false; |
|||
} |
|||
|
|||
return true; |
|||
} |
|||
|
|||
return a!==a && b!==b; |
|||
} |
|||
|
|||
module.exports = { |
|||
isValidUrl, |
|||
equal |
|||
}; |
|||
|
@ -0,0 +1,611 @@ |
|||
// @ts-check
|
|||
export default class WxCanvas { |
|||
ctx; |
|||
type; |
|||
canvasId; |
|||
canvasNode; |
|||
stepList = []; |
|||
canvasPrototype = {}; |
|||
|
|||
constructor(type, ctx, canvasId, isNew, canvasNode) { |
|||
this.ctx = ctx; |
|||
this.canvasId = canvasId; |
|||
this.type = type; |
|||
if (isNew) { |
|||
this.canvasNode = canvasNode || {}; |
|||
} |
|||
} |
|||
|
|||
set width(w) { |
|||
if (this.canvasNode) this.canvasNode.width = w; |
|||
} |
|||
|
|||
get width() { |
|||
if (this.canvasNode) return this.canvasNode.width; |
|||
return 0; |
|||
} |
|||
|
|||
set height(h) { |
|||
if (this.canvasNode) this.canvasNode.height = h; |
|||
} |
|||
|
|||
get height() { |
|||
if (this.canvasNode) return this.canvasNode.height; |
|||
return 0; |
|||
} |
|||
|
|||
set lineWidth(args) { |
|||
this.canvasPrototype.lineWidth = args; |
|||
this.stepList.push({ |
|||
action: "lineWidth", |
|||
args, |
|||
actionType: "set", |
|||
}); |
|||
} |
|||
|
|||
get lineWidth() { |
|||
return this.canvasPrototype.lineWidth; |
|||
} |
|||
|
|||
set lineCap(args) { |
|||
this.canvasPrototype.lineCap = args; |
|||
this.stepList.push({ |
|||
action: "lineCap", |
|||
args, |
|||
actionType: "set", |
|||
}); |
|||
} |
|||
|
|||
get lineCap() { |
|||
return this.canvasPrototype.lineCap; |
|||
} |
|||
|
|||
set lineJoin(args) { |
|||
this.canvasPrototype.lineJoin = args; |
|||
this.stepList.push({ |
|||
action: "lineJoin", |
|||
args, |
|||
actionType: "set", |
|||
}); |
|||
} |
|||
|
|||
get lineJoin() { |
|||
return this.canvasPrototype.lineJoin; |
|||
} |
|||
|
|||
set miterLimit(args) { |
|||
this.canvasPrototype.miterLimit = args; |
|||
this.stepList.push({ |
|||
action: "miterLimit", |
|||
args, |
|||
actionType: "set", |
|||
}); |
|||
} |
|||
|
|||
get miterLimit() { |
|||
return this.canvasPrototype.miterLimit; |
|||
} |
|||
|
|||
set lineDashOffset(args) { |
|||
this.canvasPrototype.lineDashOffset = args; |
|||
this.stepList.push({ |
|||
action: "lineDashOffset", |
|||
args, |
|||
actionType: "set", |
|||
}); |
|||
} |
|||
|
|||
get lineDashOffset() { |
|||
return this.canvasPrototype.lineDashOffset; |
|||
} |
|||
|
|||
set font(args) { |
|||
this.canvasPrototype.font = args; |
|||
this.ctx.font = args; |
|||
this.stepList.push({ |
|||
action: "font", |
|||
args, |
|||
actionType: "set", |
|||
}); |
|||
} |
|||
|
|||
get font() { |
|||
return this.canvasPrototype.font; |
|||
} |
|||
|
|||
set textAlign(args) { |
|||
this.canvasPrototype.textAlign = args; |
|||
this.stepList.push({ |
|||
action: "textAlign", |
|||
args, |
|||
actionType: "set", |
|||
}); |
|||
} |
|||
|
|||
get textAlign() { |
|||
return this.canvasPrototype.textAlign; |
|||
} |
|||
|
|||
set textBaseline(args) { |
|||
this.canvasPrototype.textBaseline = args; |
|||
this.stepList.push({ |
|||
action: "textBaseline", |
|||
args, |
|||
actionType: "set", |
|||
}); |
|||
} |
|||
|
|||
get textBaseline() { |
|||
return this.canvasPrototype.textBaseline; |
|||
} |
|||
|
|||
set fillStyle(args) { |
|||
this.canvasPrototype.fillStyle = args; |
|||
this.stepList.push({ |
|||
action: "fillStyle", |
|||
args, |
|||
actionType: "set", |
|||
}); |
|||
} |
|||
|
|||
get fillStyle() { |
|||
return this.canvasPrototype.fillStyle; |
|||
} |
|||
|
|||
set strokeStyle(args) { |
|||
this.canvasPrototype.strokeStyle = args; |
|||
this.stepList.push({ |
|||
action: "strokeStyle", |
|||
args, |
|||
actionType: "set", |
|||
}); |
|||
} |
|||
|
|||
get strokeStyle() { |
|||
return this.canvasPrototype.strokeStyle; |
|||
} |
|||
|
|||
set globalAlpha(args) { |
|||
this.canvasPrototype.globalAlpha = args; |
|||
this.stepList.push({ |
|||
action: "globalAlpha", |
|||
args, |
|||
actionType: "set", |
|||
}); |
|||
} |
|||
|
|||
get globalAlpha() { |
|||
return this.canvasPrototype.globalAlpha; |
|||
} |
|||
|
|||
set globalCompositeOperation(args) { |
|||
this.canvasPrototype.globalCompositeOperation = args; |
|||
this.stepList.push({ |
|||
action: "globalCompositeOperation", |
|||
args, |
|||
actionType: "set", |
|||
}); |
|||
} |
|||
|
|||
get globalCompositeOperation() { |
|||
return this.canvasPrototype.globalCompositeOperation; |
|||
} |
|||
|
|||
set shadowColor(args) { |
|||
this.canvasPrototype.shadowColor = args; |
|||
this.stepList.push({ |
|||
action: "shadowColor", |
|||
args, |
|||
actionType: "set", |
|||
}); |
|||
} |
|||
|
|||
get shadowColor() { |
|||
return this.canvasPrototype.shadowColor; |
|||
} |
|||
|
|||
set shadowOffsetX(args) { |
|||
this.canvasPrototype.shadowOffsetX = args; |
|||
this.stepList.push({ |
|||
action: "shadowOffsetX", |
|||
args, |
|||
actionType: "set", |
|||
}); |
|||
} |
|||
|
|||
get shadowOffsetX() { |
|||
return this.canvasPrototype.shadowOffsetX; |
|||
} |
|||
|
|||
set shadowOffsetY(args) { |
|||
this.canvasPrototype.shadowOffsetY = args; |
|||
this.stepList.push({ |
|||
action: "shadowOffsetY", |
|||
args, |
|||
actionType: "set", |
|||
}); |
|||
} |
|||
|
|||
get shadowOffsetY() { |
|||
return this.canvasPrototype.shadowOffsetY; |
|||
} |
|||
|
|||
set shadowBlur(args) { |
|||
this.canvasPrototype.shadowBlur = args; |
|||
this.stepList.push({ |
|||
action: "shadowBlur", |
|||
args, |
|||
actionType: "set", |
|||
}); |
|||
} |
|||
|
|||
get shadowBlur() { |
|||
return this.canvasPrototype.shadowBlur; |
|||
} |
|||
|
|||
save() { |
|||
this.stepList.push({ |
|||
action: "save", |
|||
args: null, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
restore() { |
|||
this.stepList.push({ |
|||
action: "restore", |
|||
args: null, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
setLineDash(...args) { |
|||
this.canvasPrototype.lineDash = args; |
|||
this.stepList.push({ |
|||
action: "setLineDash", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
moveTo(...args) { |
|||
this.stepList.push({ |
|||
action: "moveTo", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
closePath() { |
|||
this.stepList.push({ |
|||
action: "closePath", |
|||
args: null, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
lineTo(...args) { |
|||
this.stepList.push({ |
|||
action: "lineTo", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
quadraticCurveTo(...args) { |
|||
this.stepList.push({ |
|||
action: "quadraticCurveTo", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
bezierCurveTo(...args) { |
|||
this.stepList.push({ |
|||
action: "bezierCurveTo", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
arcTo(...args) { |
|||
this.stepList.push({ |
|||
action: "arcTo", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
arc(...args) { |
|||
this.stepList.push({ |
|||
action: "arc", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
rect(...args) { |
|||
this.stepList.push({ |
|||
action: "rect", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
scale(...args) { |
|||
this.stepList.push({ |
|||
action: "scale", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
rotate(...args) { |
|||
this.stepList.push({ |
|||
action: "rotate", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
translate(...args) { |
|||
this.stepList.push({ |
|||
action: "translate", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
transform(...args) { |
|||
this.stepList.push({ |
|||
action: "transform", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
setTransform(...args) { |
|||
this.stepList.push({ |
|||
action: "setTransform", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
clearRect(...args) { |
|||
this.stepList.push({ |
|||
action: "clearRect", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
fillRect(...args) { |
|||
this.stepList.push({ |
|||
action: "fillRect", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
strokeRect(...args) { |
|||
this.stepList.push({ |
|||
action: "strokeRect", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
fillText(...args) { |
|||
this.stepList.push({ |
|||
action: "fillText", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
strokeText(...args) { |
|||
this.stepList.push({ |
|||
action: "strokeText", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
beginPath() { |
|||
this.stepList.push({ |
|||
action: "beginPath", |
|||
args: null, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
fill() { |
|||
this.stepList.push({ |
|||
action: "fill", |
|||
args: null, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
stroke() { |
|||
this.stepList.push({ |
|||
action: "stroke", |
|||
args: null, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
drawFocusIfNeeded(...args) { |
|||
this.stepList.push({ |
|||
action: "drawFocusIfNeeded", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
clip() { |
|||
this.stepList.push({ |
|||
action: "clip", |
|||
args: null, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
isPointInPath(...args) { |
|||
this.stepList.push({ |
|||
action: "isPointInPath", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
drawImage(...args) { |
|||
this.stepList.push({ |
|||
action: "drawImage", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
addHitRegion(...args) { |
|||
this.stepList.push({ |
|||
action: "addHitRegion", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
removeHitRegion(...args) { |
|||
this.stepList.push({ |
|||
action: "removeHitRegion", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
clearHitRegions(...args) { |
|||
this.stepList.push({ |
|||
action: "clearHitRegions", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
putImageData(...args) { |
|||
this.stepList.push({ |
|||
action: "putImageData", |
|||
args, |
|||
actionType: "func", |
|||
}); |
|||
} |
|||
|
|||
getLineDash() { |
|||
return this.canvasPrototype.lineDash; |
|||
} |
|||
|
|||
createLinearGradient(...args) { |
|||
return this.ctx.createLinearGradient(...args); |
|||
} |
|||
|
|||
createRadialGradient(...args) { |
|||
if (this.type === "2d") { |
|||
return this.ctx.createRadialGradient(...args); |
|||
} else { |
|||
return this.ctx.createCircularGradient(...args.slice(3, 6)); |
|||
} |
|||
} |
|||
|
|||
createPattern(...args) { |
|||
return this.ctx.createPattern(...args); |
|||
} |
|||
|
|||
measureText(...args) { |
|||
return this.ctx.measureText(...args); |
|||
} |
|||
|
|||
createImageData(...args) { |
|||
return this.ctx.createImageData(...args); |
|||
} |
|||
|
|||
getImageData(...args) { |
|||
return this.ctx.getImageData(...args); |
|||
} |
|||
|
|||
async draw(reserve, func) { |
|||
const realstepList = this.stepList.slice(); |
|||
this.stepList.length = 0; |
|||
if (this.type === "mina") { |
|||
if (realstepList.length > 0) { |
|||
for (const step of realstepList) { |
|||
this.implementMinaStep(step); |
|||
} |
|||
this.ctx.draw(reserve, func); |
|||
realstepList.length = 0; |
|||
} |
|||
} else if (this.type === "2d") { |
|||
if (!reserve) { |
|||
this.ctx.clearRect(0, 0, this.canvasNode.width, this.canvasNode.height); |
|||
} |
|||
if (realstepList.length > 0) { |
|||
for (const step of realstepList) { |
|||
await this.implement2DStep(step); |
|||
} |
|||
realstepList.length = 0; |
|||
} |
|||
if (func) { |
|||
func(); |
|||
} |
|||
} |
|||
realstepList.length = 0; |
|||
} |
|||
|
|||
implementMinaStep(step) { |
|||
switch (step.action) { |
|||
case "textAlign": { |
|||
this.ctx.setTextAlign(step.args); |
|||
break; |
|||
} |
|||
case "textBaseline": { |
|||
this.ctx.setTextBaseline(step.args); |
|||
break; |
|||
} |
|||
default: { |
|||
if (step.actionType === "set") { |
|||
this.ctx[step.action] = step.args; |
|||
} else if (step.actionType === "func") { |
|||
if (step.args) { |
|||
this.ctx[step.action](...step.args); |
|||
} else { |
|||
this.ctx[step.action](); |
|||
} |
|||
} |
|||
break; |
|||
} |
|||
} |
|||
} |
|||
|
|||
implement2DStep(step) { |
|||
return new Promise((resolve) => { |
|||
if (step.action === "drawImage") { |
|||
const img = this.canvasNode.createImage(); |
|||
img.src = step.args[0]; |
|||
img.onload = () => { |
|||
this.ctx.drawImage(img, ...step.args.slice(1)); |
|||
resolve(); |
|||
}; |
|||
} else { |
|||
if (step.actionType === "set") { |
|||
this.ctx[step.action] = step.args; |
|||
} else if (step.actionType === "func") { |
|||
if (step.args) { |
|||
this.ctx[step.action](...step.args); |
|||
} else { |
|||
this.ctx[step.action](); |
|||
} |
|||
} |
|||
resolve(); |
|||
} |
|||
}); |
|||
} |
|||
} |
@ -0,0 +1,888 @@ |
|||
import Pen from './lib/pen'; |
|||
import Downloader from './lib/downloader'; |
|||
import WxCanvas from './lib/wx-canvas'; |
|||
|
|||
const util = require('./lib/util'); |
|||
|
|||
const downloader = new Downloader(); |
|||
|
|||
// 最大尝试的绘制次数
|
|||
const MAX_PAINT_COUNT = 5; |
|||
const ACTION_DEFAULT_SIZE = 24; |
|||
const ACTION_OFFSET = '2rpx'; |
|||
Component({ |
|||
canvasWidthInPx: 0, |
|||
canvasHeightInPx: 0, |
|||
canvasNode: null, |
|||
paintCount: 0, |
|||
currentPalette: {}, |
|||
movingCache: {}, |
|||
outterDisabled: false, |
|||
isDisabled: false, |
|||
needClear: false, |
|||
/** |
|||
* 组件的属性列表 |
|||
*/ |
|||
properties: { |
|||
use2D: { |
|||
type: Boolean, |
|||
}, |
|||
customStyle: { |
|||
type: String, |
|||
}, |
|||
// 运行自定义选择框和删除缩放按钮
|
|||
customActionStyle: { |
|||
type: Object, |
|||
}, |
|||
palette: { |
|||
type: Object, |
|||
observer: function (newVal, oldVal) { |
|||
if (this.isNeedRefresh(newVal, oldVal)) { |
|||
this.paintCount = 0; |
|||
this.startPaint(); |
|||
} |
|||
}, |
|||
}, |
|||
dancePalette: { |
|||
type: Object, |
|||
observer: function (newVal, oldVal) { |
|||
if (!this.isEmpty(newVal) && !this.properties.use2D) { |
|||
this.initDancePalette(newVal); |
|||
} |
|||
}, |
|||
}, |
|||
// 缩放比,会在传入的 palette 中统一乘以该缩放比
|
|||
scaleRatio: { |
|||
type: Number, |
|||
value: 1 |
|||
}, |
|||
widthPixels: { |
|||
type: Number, |
|||
value: 0 |
|||
}, |
|||
// 启用脏检查,默认 false
|
|||
dirty: { |
|||
type: Boolean, |
|||
value: false, |
|||
}, |
|||
LRU: { |
|||
type: Boolean, |
|||
value: false, |
|||
}, |
|||
action: { |
|||
type: Object, |
|||
observer: function (newVal, oldVal) { |
|||
if (newVal && !this.isEmpty(newVal) && !this.properties.use2D) { |
|||
this.doAction(newVal, (callbackInfo) => { |
|||
this.movingCache = callbackInfo |
|||
}, false, true) |
|||
} |
|||
}, |
|||
}, |
|||
disableAction: { |
|||
type: Boolean, |
|||
observer: function (isDisabled) { |
|||
this.outterDisabled = isDisabled |
|||
this.isDisabled = isDisabled |
|||
} |
|||
}, |
|||
clearActionBox: { |
|||
type: Boolean, |
|||
observer: function (needClear) { |
|||
if (needClear && !this.needClear) { |
|||
if (this.frontContext) { |
|||
setTimeout(() => { |
|||
this.frontContext.draw(); |
|||
}, 100); |
|||
this.touchedView = {}; |
|||
this.prevFindedIndex = this.findedIndex |
|||
this.findedIndex = -1; |
|||
} |
|||
} |
|||
this.needClear = needClear |
|||
} |
|||
}, |
|||
}, |
|||
|
|||
data: { |
|||
imageServerUrl: getApp().globalData.imageServerUrl, |
|||
picURL: '', |
|||
showCanvas: true, |
|||
painterStyle: '', |
|||
}, |
|||
|
|||
methods: { |
|||
|
|||
/** |
|||
* 判断一个 object 是否为 空 |
|||
* @param {object} object |
|||
*/ |
|||
isEmpty(object) { |
|||
for (const i in object) { |
|||
return false; |
|||
} |
|||
return true; |
|||
}, |
|||
|
|||
isNeedRefresh(newVal, oldVal) { |
|||
if (!newVal || this.isEmpty(newVal) || (this.data.dirty && util.equal(newVal, oldVal))) { |
|||
return false; |
|||
} |
|||
return true; |
|||
}, |
|||
|
|||
getBox(rect, type) { |
|||
const boxArea = { |
|||
type: 'rect', |
|||
css: { |
|||
height: `${rect.bottom - rect.top}px`, |
|||
width: `${rect.right - rect.left}px`, |
|||
left: `${rect.left}px`, |
|||
top: `${rect.top}px`, |
|||
borderWidth: '4rpx', |
|||
borderColor: '#1A7AF8', |
|||
color: 'transparent' |
|||
} |
|||
} |
|||
if (type === 'text') { |
|||
boxArea.css = Object.assign({}, boxArea.css, { |
|||
borderStyle: 'dashed' |
|||
}) |
|||
} |
|||
if (this.properties.customActionStyle && this.properties.customActionStyle.border) { |
|||
boxArea.css = Object.assign({}, boxArea.css, this.properties.customActionStyle.border) |
|||
} |
|||
Object.assign(boxArea, { |
|||
id: 'box' |
|||
}) |
|||
return boxArea |
|||
}, |
|||
|
|||
getScaleIcon(rect, type) { |
|||
let scaleArea = {} |
|||
const { |
|||
customActionStyle |
|||
} = this.properties |
|||
if (customActionStyle && customActionStyle.scale) { |
|||
scaleArea = { |
|||
type: 'image', |
|||
url: type === 'text' ? customActionStyle.scale.textIcon : customActionStyle.scale.imageIcon, |
|||
css: { |
|||
height: `${2 * ACTION_DEFAULT_SIZE}rpx`, |
|||
width: `${2 * ACTION_DEFAULT_SIZE}rpx`, |
|||
borderRadius: `${ACTION_DEFAULT_SIZE}rpx`, |
|||
} |
|||
} |
|||
} else { |
|||
scaleArea = { |
|||
type: 'rect', |
|||
css: { |
|||
height: `${2 * ACTION_DEFAULT_SIZE}rpx`, |
|||
width: `${2 * ACTION_DEFAULT_SIZE}rpx`, |
|||
borderRadius: `${ACTION_DEFAULT_SIZE}rpx`, |
|||
color: '#0000ff', |
|||
} |
|||
} |
|||
} |
|||
scaleArea.css = Object.assign({}, scaleArea.css, { |
|||
align: 'center', |
|||
left: `${rect.right + ACTION_OFFSET.toPx()}px`, |
|||
top: type === 'text' ? `${rect.top - ACTION_OFFSET.toPx() - scaleArea.css.height.toPx() / 2}px` : `${rect.bottom - ACTION_OFFSET.toPx() - scaleArea.css.height.toPx() / 2}px` |
|||
}) |
|||
Object.assign(scaleArea, { |
|||
id: 'scale' |
|||
}) |
|||
return scaleArea |
|||
}, |
|||
|
|||
getDeleteIcon(rect) { |
|||
let deleteArea = {} |
|||
const { |
|||
customActionStyle |
|||
} = this.properties |
|||
if (customActionStyle && customActionStyle.scale) { |
|||
deleteArea = { |
|||
type: 'image', |
|||
url: customActionStyle.delete.icon, |
|||
css: { |
|||
height: `${2 * ACTION_DEFAULT_SIZE}rpx`, |
|||
width: `${2 * ACTION_DEFAULT_SIZE}rpx`, |
|||
borderRadius: `${ACTION_DEFAULT_SIZE}rpx`, |
|||
} |
|||
} |
|||
} else { |
|||
deleteArea = { |
|||
type: 'rect', |
|||
css: { |
|||
height: `${2 * ACTION_DEFAULT_SIZE}rpx`, |
|||
width: `${2 * ACTION_DEFAULT_SIZE}rpx`, |
|||
borderRadius: `${ACTION_DEFAULT_SIZE}rpx`, |
|||
color: '#0000ff', |
|||
} |
|||
} |
|||
} |
|||
deleteArea.css = Object.assign({}, deleteArea.css, { |
|||
align: 'center', |
|||
left: `${rect.left - ACTION_OFFSET.toPx()}px`, |
|||
top: `${rect.top - ACTION_OFFSET.toPx() - deleteArea.css.height.toPx() / 2}px` |
|||
}) |
|||
Object.assign(deleteArea, { |
|||
id: 'delete' |
|||
}) |
|||
return deleteArea |
|||
}, |
|||
|
|||
doAction(action, callback, isMoving, overwrite) { |
|||
if (this.properties.use2D) { |
|||
return; |
|||
} |
|||
let newVal = null |
|||
if (action) { |
|||
newVal = action.view |
|||
} |
|||
if (newVal && newVal.id && this.touchedView.id !== newVal.id) { |
|||
// 带 id 的动作给撤回时使用,不带 id,表示对当前选中对象进行操作
|
|||
const { |
|||
views |
|||
} = this.currentPalette; |
|||
for (let i = 0; i < views.length; i++) { |
|||
if (views[i].id === newVal.id) { |
|||
// 跨层回撤,需要重新构建三层关系
|
|||
this.touchedView = views[i]; |
|||
this.findedIndex = i; |
|||
this.sliceLayers(); |
|||
break |
|||
} |
|||
} |
|||
} |
|||
|
|||
const doView = this.touchedView |
|||
|
|||
if (!doView || this.isEmpty(doView)) { |
|||
return |
|||
} |
|||
if (newVal && newVal.css) { |
|||
if (overwrite) { |
|||
doView.css = newVal.css |
|||
} else if (Array.isArray(doView.css) && Array.isArray(newVal.css)) { |
|||
doView.css = Object.assign({}, ...doView.css, ...newVal.css) |
|||
} else if (Array.isArray(doView.css)) { |
|||
doView.css = Object.assign({}, ...doView.css, newVal.css) |
|||
} else if (Array.isArray(newVal.css)) { |
|||
doView.css = Object.assign({}, doView.css, ...newVal.css) |
|||
} else { |
|||
doView.css = Object.assign({}, doView.css, newVal.css) |
|||
} |
|||
} |
|||
if (newVal && newVal.rect) { |
|||
doView.rect = newVal.rect; |
|||
} |
|||
if (newVal && newVal.url && doView.url && newVal.url !== doView.url) { |
|||
downloader.download(newVal.url, this.properties.LRU).then((path) => { |
|||
if (newVal.url.startsWith('https')) { |
|||
doView.originUrl = newVal.url |
|||
} |
|||
doView.url = path; |
|||
wx.getImageInfo({ |
|||
src: path, |
|||
success: (res) => { |
|||
doView.sHeight = res.height |
|||
doView.sWidth = res.width |
|||
this.reDraw(doView, callback, isMoving) |
|||
}, |
|||
fail: () => { |
|||
this.reDraw(doView, callback, isMoving) |
|||
} |
|||
}) |
|||
}).catch((error) => { |
|||
// 未下载成功,直接绘制
|
|||
console.error(error) |
|||
this.reDraw(doView, callback, isMoving) |
|||
}) |
|||
} else { |
|||
(newVal && newVal.text && doView.text && newVal.text !== doView.text) && (doView.text = newVal.text); |
|||
(newVal && newVal.content && doView.content && newVal.content !== doView.content) && (doView.content = newVal.content); |
|||
this.reDraw(doView, callback, isMoving) |
|||
} |
|||
}, |
|||
|
|||
reDraw(doView, callback, isMoving) { |
|||
const draw = { |
|||
width: this.currentPalette.width, |
|||
height: this.currentPalette.height, |
|||
views: this.isEmpty(doView) ? [] : [doView] |
|||
} |
|||
const pen = new Pen(this.globalContext, draw); |
|||
|
|||
if (isMoving && doView.type === 'text') { |
|||
pen.paint((callbackInfo) => { |
|||
callback && callback(callbackInfo); |
|||
this.triggerEvent('viewUpdate', { |
|||
view: this.touchedView |
|||
}); |
|||
}, true, this.movingCache); |
|||
} else { |
|||
// 某些机型(华为 P20)非移动和缩放场景下,只绘制一遍会偶然性图片绘制失败
|
|||
// if (!isMoving && !this.isScale) {
|
|||
// pen.paint()
|
|||
// }
|
|||
pen.paint((callbackInfo) => { |
|||
callback && callback(callbackInfo); |
|||
this.triggerEvent('viewUpdate', { |
|||
view: this.touchedView |
|||
}); |
|||
}) |
|||
} |
|||
|
|||
const { |
|||
rect, |
|||
css, |
|||
type |
|||
} = doView |
|||
|
|||
this.block = { |
|||
width: this.currentPalette.width, |
|||
height: this.currentPalette.height, |
|||
views: this.isEmpty(doView) ? [] : [this.getBox(rect, doView.type)] |
|||
} |
|||
if (css && css.scalable) { |
|||
this.block.views.push(this.getScaleIcon(rect, type)) |
|||
} |
|||
if (css && css.deletable) { |
|||
this.block.views.push(this.getDeleteIcon(rect)) |
|||
} |
|||
const topBlock = new Pen(this.frontContext, this.block) |
|||
topBlock.paint(); |
|||
}, |
|||
|
|||
isInView(x, y, rect) { |
|||
return (x > rect.left && |
|||
y > rect.top && |
|||
x < rect.right && |
|||
y < rect.bottom |
|||
) |
|||
}, |
|||
|
|||
isInDelete(x, y) { |
|||
for (const view of this.block.views) { |
|||
if (view.id === 'delete') { |
|||
return (x > view.rect.left && |
|||
y > view.rect.top && |
|||
x < view.rect.right && |
|||
y < view.rect.bottom) |
|||
} |
|||
} |
|||
return false |
|||
}, |
|||
|
|||
isInScale(x, y) { |
|||
for (const view of this.block.views) { |
|||
if (view.id === 'scale') { |
|||
return (x > view.rect.left && |
|||
y > view.rect.top && |
|||
x < view.rect.right && |
|||
y < view.rect.bottom) |
|||
} |
|||
} |
|||
return false |
|||
}, |
|||
|
|||
touchedView: {}, |
|||
findedIndex: -1, |
|||
onClick() { |
|||
const x = this.startX |
|||
const y = this.startY |
|||
const totalLayerCount = this.currentPalette.views.length |
|||
let canBeTouched = [] |
|||
let isDelete = false |
|||
let deleteIndex = -1 |
|||
for (let i = totalLayerCount - 1; i >= 0; i--) { |
|||
const view = this.currentPalette.views[i] |
|||
const { |
|||
rect |
|||
} = view |
|||
if (this.touchedView && this.touchedView.id && this.touchedView.id === view.id && this.isInDelete(x, y, rect)) { |
|||
canBeTouched.length = 0 |
|||
deleteIndex = i |
|||
isDelete = true |
|||
break |
|||
} |
|||
if (this.isInView(x, y, rect)) { |
|||
canBeTouched.push({ |
|||
view, |
|||
index: i |
|||
}) |
|||
} |
|||
} |
|||
this.touchedView = {} |
|||
if (canBeTouched.length === 0) { |
|||
this.findedIndex = -1 |
|||
} else { |
|||
let i = 0 |
|||
const touchAble = canBeTouched.filter(item => Boolean(item.view.id)) |
|||
if (touchAble.length === 0) { |
|||
this.findedIndex = canBeTouched[0].index |
|||
} else { |
|||
for (i = 0; i < touchAble.length; i++) { |
|||
if (this.findedIndex === touchAble[i].index) { |
|||
i++ |
|||
break |
|||
} |
|||
} |
|||
if (i === touchAble.length) { |
|||
i = 0 |
|||
} |
|||
this.touchedView = touchAble[i].view |
|||
this.findedIndex = touchAble[i].index |
|||
this.triggerEvent('viewClicked', { |
|||
view: this.touchedView |
|||
}) |
|||
} |
|||
} |
|||
if (this.findedIndex < 0 || (this.touchedView && !this.touchedView.id)) { |
|||
// 证明点击了背景 或无法移动的view
|
|||
this.frontContext.draw(); |
|||
if (isDelete) { |
|||
this.triggerEvent('touchEnd', { |
|||
view: this.currentPalette.views[deleteIndex], |
|||
index: deleteIndex, |
|||
type: 'delete' |
|||
}) |
|||
this.doAction() |
|||
} else if (this.findedIndex < 0) { |
|||
this.triggerEvent('viewClicked', {}) |
|||
} |
|||
this.findedIndex = -1 |
|||
this.prevFindedIndex = -1 |
|||
} else if (this.touchedView && this.touchedView.id) { |
|||
this.sliceLayers(); |
|||
} |
|||
}, |
|||
|
|||
sliceLayers() { |
|||
const bottomLayers = this.currentPalette.views.slice(0, this.findedIndex) |
|||
const topLayers = this.currentPalette.views.slice(this.findedIndex + 1) |
|||
const bottomDraw = { |
|||
width: this.currentPalette.width, |
|||
height: this.currentPalette.height, |
|||
background: this.currentPalette.background, |
|||
views: bottomLayers |
|||
} |
|||
const topDraw = { |
|||
width: this.currentPalette.width, |
|||
height: this.currentPalette.height, |
|||
views: topLayers |
|||
} |
|||
if (this.prevFindedIndex < this.findedIndex) { |
|||
new Pen(this.bottomContext, bottomDraw).paint(); |
|||
this.doAction(null, (callbackInfo) => { |
|||
this.movingCache = callbackInfo |
|||
}) |
|||
new Pen(this.topContext, topDraw).paint(); |
|||
} else { |
|||
new Pen(this.topContext, topDraw).paint(); |
|||
this.doAction(null, (callbackInfo) => { |
|||
this.movingCache = callbackInfo |
|||
}) |
|||
new Pen(this.bottomContext, bottomDraw).paint(); |
|||
} |
|||
this.prevFindedIndex = this.findedIndex |
|||
}, |
|||
|
|||
startX: 0, |
|||
startY: 0, |
|||
startH: 0, |
|||
startW: 0, |
|||
isScale: false, |
|||
startTimeStamp: 0, |
|||
onTouchStart(event) { |
|||
if (this.isDisabled) { |
|||
return |
|||
} |
|||
const { |
|||
x, |
|||
y |
|||
} = event.touches[0] |
|||
this.startX = x |
|||
this.startY = y |
|||
this.startTimeStamp = new Date().getTime() |
|||
if (this.touchedView && !this.isEmpty(this.touchedView)) { |
|||
const { |
|||
rect |
|||
} = this.touchedView |
|||
if (this.isInScale(x, y, rect)) { |
|||
this.isScale = true |
|||
this.movingCache = {} |
|||
this.startH = rect.bottom - rect.top |
|||
this.startW = rect.right - rect.left |
|||
} else { |
|||
this.isScale = false |
|||
} |
|||
} else { |
|||
this.isScale = false |
|||
} |
|||
}, |
|||
|
|||
onTouchEnd(e) { |
|||
if (this.isDisabled) { |
|||
return |
|||
} |
|||
const current = new Date().getTime() |
|||
if ((current - this.startTimeStamp) <= 500 && !this.hasMove) { |
|||
!this.isScale && this.onClick(e) |
|||
} else if (this.touchedView && !this.isEmpty(this.touchedView)) { |
|||
this.triggerEvent('touchEnd', { |
|||
view: this.touchedView, |
|||
}) |
|||
} |
|||
this.hasMove = false |
|||
}, |
|||
|
|||
onTouchCancel(e) { |
|||
if (this.isDisabled) { |
|||
return |
|||
} |
|||
this.onTouchEnd(e) |
|||
}, |
|||
|
|||
hasMove: false, |
|||
onTouchMove(event) { |
|||
if (this.isDisabled) { |
|||
return |
|||
} |
|||
this.hasMove = true |
|||
if (!this.touchedView || (this.touchedView && !this.touchedView.id)) { |
|||
return |
|||
} |
|||
const { |
|||
x, |
|||
y |
|||
} = event.touches[0] |
|||
const offsetX = x - this.startX |
|||
const offsetY = y - this.startY |
|||
const { |
|||
rect, |
|||
type |
|||
} = this.touchedView |
|||
let css = {} |
|||
if (this.isScale) { |
|||
const newW = this.startW + offsetX > 1 ? this.startW + offsetX : 1 |
|||
if (this.touchedView.css && this.touchedView.css.minWidth) { |
|||
if (newW < this.touchedView.css.minWidth.toPx()) { |
|||
return |
|||
} |
|||
} |
|||
if (this.touchedView.rect && this.touchedView.rect.minWidth) { |
|||
if (newW < this.touchedView.rect.minWidth) { |
|||
return |
|||
} |
|||
} |
|||
const newH = this.startH + offsetY > 1 ? this.startH + offsetY : 1 |
|||
css = { |
|||
width: `${newW}px`, |
|||
} |
|||
if (type !== 'text') { |
|||
if (type === 'image') { |
|||
css.height = `${(newW) * this.startH / this.startW }px` |
|||
} else { |
|||
css.height = `${newH}px` |
|||
} |
|||
} |
|||
} else { |
|||
this.startX = x |
|||
this.startY = y |
|||
css = { |
|||
left: `${rect.x + offsetX}px`, |
|||
top: `${rect.y + offsetY}px`, |
|||
right: undefined, |
|||
bottom: undefined |
|||
} |
|||
} |
|||
this.doAction({ |
|||
view: { |
|||
css |
|||
} |
|||
}, (callbackInfo) => { |
|||
if (this.isScale) { |
|||
this.movingCache = callbackInfo |
|||
} |
|||
}, !this.isScale) |
|||
}, |
|||
|
|||
initScreenK() { |
|||
if (!(getApp() && getApp().systemInfo && getApp().systemInfo.screenWidth)) { |
|||
try { |
|||
getApp().systemInfo = wx.getSystemInfoSync(); |
|||
} catch (e) { |
|||
console.error(`Painter get system info failed, ${JSON.stringify(e)}`); |
|||
return; |
|||
} |
|||
} |
|||
this.screenK = 0.5; |
|||
if (getApp() && getApp().systemInfo && getApp().systemInfo.screenWidth) { |
|||
this.screenK = getApp().systemInfo.screenWidth / 750; |
|||
} |
|||
setStringPrototype(this.screenK, this.properties.scaleRatio); |
|||
}, |
|||
|
|||
initDancePalette() { |
|||
if (this.properties.use2D) { |
|||
return; |
|||
} |
|||
this.isDisabled = true; |
|||
this.initScreenK(); |
|||
this.downloadImages(this.properties.dancePalette).then(async (palette) => { |
|||
this.currentPalette = palette |
|||
const { |
|||
width, |
|||
height |
|||
} = palette; |
|||
|
|||
if (!width || !height) { |
|||
console.error(`You should set width and height correctly for painter, width: ${width}, height: ${height}`); |
|||
return; |
|||
} |
|||
this.setData({ |
|||
painterStyle: `width:${width.toPx()}px;height:${height.toPx()}px;`, |
|||
}); |
|||
this.frontContext || (this.frontContext = await this.getCanvasContext(this.properties.use2D, 'front')); |
|||
this.bottomContext || (this.bottomContext = await this.getCanvasContext(this.properties.use2D, 'bottom')); |
|||
this.topContext || (this.topContext = await this.getCanvasContext(this.properties.use2D, 'top')); |
|||
this.globalContext || (this.globalContext = await this.getCanvasContext(this.properties.use2D, 'k-canvas')); |
|||
new Pen(this.bottomContext, palette, this.properties.use2D).paint(() => { |
|||
this.isDisabled = false; |
|||
this.isDisabled = this.outterDisabled; |
|||
this.triggerEvent('didShow'); |
|||
}); |
|||
this.globalContext.draw(); |
|||
this.frontContext.draw(); |
|||
this.topContext.draw(); |
|||
}); |
|||
this.touchedView = {}; |
|||
}, |
|||
|
|||
startPaint() { |
|||
this.initScreenK(); |
|||
|
|||
this.downloadImages(this.properties.palette).then(async (palette) => { |
|||
const { |
|||
width, |
|||
height |
|||
} = palette; |
|||
|
|||
if (!width || !height) { |
|||
console.error(`You should set width and height correctly for painter, width: ${width}, height: ${height}`); |
|||
return; |
|||
} |
|||
|
|||
let needScale = false; |
|||
// 生成图片时,根据设置的像素值重新绘制
|
|||
if (width.toPx() !== this.canvasWidthInPx) { |
|||
this.canvasWidthInPx = width.toPx(); |
|||
needScale = this.properties.use2D; |
|||
} |
|||
if (this.properties.widthPixels) { |
|||
setStringPrototype(this.screenK, this.properties.widthPixels / this.canvasWidthInPx) |
|||
this.canvasWidthInPx = this.properties.widthPixels |
|||
} |
|||
|
|||
if (this.canvasHeightInPx !== height.toPx()) { |
|||
this.canvasHeightInPx = height.toPx(); |
|||
needScale = needScale || this.properties.use2D; |
|||
} |
|||
this.setData({ |
|||
photoStyle: `width:${this.canvasWidthInPx}px;height:${this.canvasHeightInPx}px;`, |
|||
}); |
|||
if (!this.photoContext) { |
|||
this.photoContext = await this.getCanvasContext(this.properties.use2D, 'photo'); |
|||
} |
|||
if (needScale) { |
|||
const scale = getApp().systemInfo.pixelRatio; |
|||
this.photoContext.width = this.canvasWidthInPx * scale; |
|||
this.photoContext.height = this.canvasHeightInPx * scale; |
|||
this.photoContext.scale(scale, scale); |
|||
} |
|||
new Pen(this.photoContext, palette).paint(() => { |
|||
this.saveImgToLocal(); |
|||
}); |
|||
setStringPrototype(this.screenK, this.properties.scaleRatio); |
|||
}); |
|||
}, |
|||
|
|||
downloadImages(palette) { |
|||
return new Promise((resolve, reject) => { |
|||
let preCount = 0; |
|||
let completeCount = 0; |
|||
const paletteCopy = JSON.parse(JSON.stringify(palette)); |
|||
if (paletteCopy.background) { |
|||
preCount++; |
|||
downloader.download(paletteCopy.background, this.properties.LRU).then((path) => { |
|||
paletteCopy.background = path; |
|||
completeCount++; |
|||
if (preCount === completeCount) { |
|||
resolve(paletteCopy); |
|||
} |
|||
}, () => { |
|||
completeCount++; |
|||
if (preCount === completeCount) { |
|||
resolve(paletteCopy); |
|||
} |
|||
}); |
|||
} |
|||
if (paletteCopy.views) { |
|||
for (const view of paletteCopy.views) { |
|||
if (view && view.type === 'image' && view.url) { |
|||
preCount++; |
|||
/* eslint-disable no-loop-func */ |
|||
downloader.download(view.url, this.properties.LRU).then((path) => { |
|||
view.originUrl = view.url; |
|||
view.url = path; |
|||
wx.getImageInfo({ |
|||
src: path, |
|||
success: (res) => { |
|||
// 获得一下图片信息,供后续裁减使用
|
|||
view.sWidth = res.width; |
|||
view.sHeight = res.height; |
|||
}, |
|||
fail: (error) => { |
|||
// 如果图片坏了,则直接置空,防止坑爹的 canvas 画崩溃了
|
|||
console.warn(`getImageInfo ${view.originUrl} failed, ${JSON.stringify(error)}`); |
|||
console.warn(JSON.stringify(error)); |
|||
view.url = ""; |
|||
}, |
|||
complete: () => { |
|||
completeCount++; |
|||
if (preCount === completeCount) { |
|||
resolve(paletteCopy); |
|||
} |
|||
}, |
|||
}); |
|||
}, () => { |
|||
completeCount++; |
|||
if (preCount === completeCount) { |
|||
resolve(paletteCopy); |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
if (preCount === 0) { |
|||
resolve(paletteCopy); |
|||
} |
|||
}); |
|||
}, |
|||
|
|||
saveImgToLocal() { |
|||
const that = this; |
|||
setTimeout(() => { |
|||
wx.canvasToTempFilePath({ |
|||
canvasId: 'photo', |
|||
canvas: that.properties.use2D ? that.canvasNode : null, |
|||
destWidth: that.canvasWidthInPx * getApp().systemInfo.pixelRatio, |
|||
destHeight: that.canvasHeightInPx * getApp().systemInfo.pixelRatio, |
|||
success: function (res) { |
|||
that.getImageInfo(res.tempFilePath); |
|||
}, |
|||
fail: function (error) { |
|||
console.error(`canvasToTempFilePath failed, ${JSON.stringify(error)}`); |
|||
that.triggerEvent('imgErr', { |
|||
error: error |
|||
}); |
|||
}, |
|||
}, this); |
|||
}, 300); |
|||
}, |
|||
|
|||
|
|||
getCanvasContext(use2D, id) { |
|||
const that = this; |
|||
return new Promise(resolve => { |
|||
if (use2D) { |
|||
const query = wx.createSelectorQuery().in(that); |
|||
const selectId = `#${id}`; |
|||
query.select(selectId) |
|||
.fields({ node: true, size: true }) |
|||
.exec((res) => { |
|||
that.canvasNode = res[0].node; |
|||
const ctx = that.canvasNode.getContext('2d'); |
|||
const wxCanvas = new WxCanvas('2d', ctx, id, true, that.canvasNode); |
|||
resolve(wxCanvas); |
|||
}); |
|||
} else { |
|||
const temp = wx.createCanvasContext(id, that); |
|||
resolve(new WxCanvas('mina', temp, id, true)); |
|||
} |
|||
}) |
|||
}, |
|||
|
|||
getImageInfo(filePath) { |
|||
const that = this; |
|||
wx.getImageInfo({ |
|||
src: filePath, |
|||
success: (infoRes) => { |
|||
if (that.paintCount > MAX_PAINT_COUNT) { |
|||
const error = `The result is always fault, even we tried ${MAX_PAINT_COUNT} times`; |
|||
console.error(error); |
|||
that.triggerEvent('imgErr', { |
|||
error: error |
|||
}); |
|||
return; |
|||
} |
|||
// 比例相符时才证明绘制成功,否则进行强制重绘制
|
|||
if (Math.abs((infoRes.width * that.canvasHeightInPx - that.canvasWidthInPx * infoRes.height) / (infoRes.height * that.canvasHeightInPx)) < 0.01) { |
|||
that.triggerEvent('imgOK', { |
|||
path: filePath |
|||
}); |
|||
} else { |
|||
that.startPaint(); |
|||
} |
|||
that.paintCount++; |
|||
}, |
|||
fail: (error) => { |
|||
console.error(`getImageInfo failed, ${JSON.stringify(error)}`); |
|||
that.triggerEvent('imgErr', { |
|||
error: error |
|||
}); |
|||
}, |
|||
}); |
|||
}, |
|||
}, |
|||
}); |
|||
|
|||
|
|||
function setStringPrototype(screenK, scale) { |
|||
/* eslint-disable no-extend-native */ |
|||
/** |
|||
* 是否支持负数 |
|||
* @param {Boolean} minus 是否支持负数 |
|||
* @param {Number} baseSize 当设置了 % 号时,设置的基准值 |
|||
*/ |
|||
String.prototype.toPx = function toPx(minus, baseSize) { |
|||
if (this === '0') { |
|||
return 0 |
|||
} |
|||
let reg; |
|||
if (minus) { |
|||
reg = /^-?[0-9]+([.]{1}[0-9]+){0,1}(rpx|px|%)$/g; |
|||
} else { |
|||
reg = /^[0-9]+([.]{1}[0-9]+){0,1}(rpx|px|%)$/g; |
|||
} |
|||
const results = reg.exec(this); |
|||
if (!this || !results) { |
|||
console.error(`The size: ${this} is illegal`); |
|||
return 0; |
|||
} |
|||
const unit = results[2]; |
|||
const value = parseFloat(this); |
|||
|
|||
let res = 0; |
|||
if (unit === 'rpx') { |
|||
res = Math.round(value * (screenK || 0.5) * (scale || 1)); |
|||
} else if (unit === 'px') { |
|||
res = Math.round(value * (scale || 1)); |
|||
} else if (unit === '%') { |
|||
res = Math.round(value * baseSize / 100); |
|||
} |
|||
return res; |
|||
}; |
|||
} |
@ -0,0 +1,4 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": {} |
|||
} |
@ -0,0 +1,25 @@ |
|||
<view style='position: relative;{{customStyle}};{{painterStyle}}'> |
|||
<block wx:if="{{!use2D}}"> |
|||
<canvas canvas-id="photo" style="{{photoStyle}};position: absolute; left: -9999px; top: -9999rpx;" /> |
|||
<canvas canvas-id="bottom" style="{{painterStyle}};position: absolute;" /> |
|||
<canvas canvas-id="k-canvas" style="{{painterStyle}};position: absolute;" /> |
|||
<canvas canvas-id="top" style="{{painterStyle}};position: absolute;" /> |
|||
<canvas canvas-id="front" style="{{painterStyle}};position: absolute;" bindtouchstart="onTouchStart" |
|||
bindtouchmove="onTouchMove" bindtouchend="onTouchEnd" bindtouchcancel="onTouchCancel" disable-scroll="{{true}}" /> |
|||
</block> |
|||
<block wx:if="{{use2D}}"> |
|||
<canvas type="2d" id="photo" style="{{photoStyle}};" /> |
|||
<!-- <canvas type="2d" id="bottom" style="{{painterStyle}};position: absolute;" /> |
|||
<canvas type="2d" id="k-canvas" style="{{painterStyle}};position: absolute;" /> |
|||
<canvas type="2d" id="top" style="{{painterStyle}};position: absolute;" /> |
|||
<canvas |
|||
type="2d" |
|||
id="front" |
|||
style="{{painterStyle}};position: absolute;" |
|||
bindtouchstart="onTouchStart" |
|||
bindtouchmove="onTouchMove" |
|||
bindtouchend="onTouchEnd" |
|||
bindtouchcancel="onTouchCancel" |
|||
disable-scroll="{{true}}" /> --> |
|||
</block> |
|||
</view> |
@ -0,0 +1,66 @@ |
|||
Component({ |
|||
/** |
|||
* 组件的属性列表 |
|||
*/ |
|||
properties: { |
|||
listData: { |
|||
type: Array, |
|||
value: [], |
|||
}, |
|||
isShowPicker:{ |
|||
type: Boolean, |
|||
value: false, |
|||
}, |
|||
defaultPickData:{ |
|||
type:Array, |
|||
value:[] |
|||
}, |
|||
key:{ |
|||
type:String, |
|||
value:'' |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 组件的初始数据 |
|||
*/ |
|||
data: { |
|||
imageServerUrl: getApp().globalData.imageServerUrl, |
|||
idx: 0, |
|||
}, |
|||
|
|||
/** |
|||
* 组件的方法列表 |
|||
*/ |
|||
lifetimes: { |
|||
ready :function(){ |
|||
setTimeout(() => { |
|||
var idx =this.data.defaultPickData[0] |
|||
this.setData({ |
|||
idx |
|||
}) |
|||
},200) |
|||
}, |
|||
}, |
|||
methods: { |
|||
btn_fn(){ |
|||
}, |
|||
cancel(e){ |
|||
console.log(e) |
|||
this.triggerEvent('cancel',false) |
|||
}, |
|||
bindChange(e){ |
|||
console.log(this.data.defaultPickData[0]) |
|||
console.log(e) |
|||
this.setData({ |
|||
idx:e.detail.value[0] |
|||
}) |
|||
}, |
|||
confirm(e){ |
|||
this.setData({ |
|||
isShowPicker:false |
|||
}) |
|||
this.triggerEvent('confirm',this.data.idx) |
|||
} |
|||
} |
|||
}) |
@ -0,0 +1,4 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": {} |
|||
} |
@ -0,0 +1,17 @@ |
|||
<view class="region_picker" wx:if='{{isShowPicker}}' bindtap="cancel"> |
|||
<view class="box" catchtap="btn_fn"> |
|||
<view class="header"> |
|||
<text catchtap="cancel">取消</text> |
|||
<text catchtap="confirm">确定</text> |
|||
</view> |
|||
<view class="footer" catchtap="btn_fn"> |
|||
<picker-view indicator-style="height: 50px;" range-key='{{ key }}' style="width: 100%; height: 300px;" |
|||
value="{{defaultPickData}}" bindchange="bindChange"> |
|||
<picker-view-column> |
|||
<view wx:for="{{listData}}" wx:key='index' style="line-height: 50px">{{item.name || item[key]}} |
|||
</view> |
|||
</picker-view-column> |
|||
</picker-view> |
|||
</view> |
|||
</view> |
|||
</view> |
@ -0,0 +1,80 @@ |
|||
.region_picker { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
background: rgba(0, 0, 0, 0.5); |
|||
z-index: 999; |
|||
} |
|||
|
|||
.box { |
|||
width: 100%; |
|||
height: 660rpx; |
|||
position: absolute; |
|||
bottom: 0; |
|||
left: 0; |
|||
box-sizing: border-box; |
|||
background: white; |
|||
} |
|||
|
|||
.header { |
|||
width: 100%; |
|||
height: 90rpx; |
|||
|
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
.header text { |
|||
width: 100rpx; |
|||
margin-right: 20rpx; |
|||
height: 100%; |
|||
line-height: 90rpx; |
|||
text-align: center; |
|||
} |
|||
|
|||
.header text:nth-of-type(2) { |
|||
color: #3476FE; |
|||
} |
|||
|
|||
.middle_tag { |
|||
width: 100%; |
|||
height: 100rpx; |
|||
|
|||
display: flex; |
|||
align-items: center; |
|||
box-sizing: border-box; |
|||
padding: 0 30rpx; |
|||
} |
|||
|
|||
.middle_tag view { |
|||
padding: 10rpx 30rpx; |
|||
border-radius: 12rpx; |
|||
color: #666666; |
|||
position: relative; |
|||
margin-right: 56rpx; |
|||
border: 1px solid #E6E6E6; |
|||
border-radius: 12rpx; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.middle_tag view image { |
|||
width: 38rpx; |
|||
height: 38rpx; |
|||
position: absolute; |
|||
top: -19rpx; |
|||
right: -19rpx; |
|||
} |
|||
|
|||
.footer { |
|||
width: 100%; |
|||
height: 470rpx; |
|||
display: flex; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.footer view { |
|||
text-align: center; |
|||
} |
@ -0,0 +1,13 @@ |
|||
function _typeof(obj) { |
|||
return Object.prototype.toString.call(obj).slice(8, -1).toLowerCase(); |
|||
} |
|||
function isString(obj) { //是否字符串
|
|||
return _typeof(obj) === 'string' |
|||
} |
|||
function isPlainObject(obj) { |
|||
return _typeof(obj) === 'object'; |
|||
} |
|||
module.exports = { |
|||
isString, |
|||
isPlainObject |
|||
} |
@ -0,0 +1,246 @@ |
|||
// const regionList = require('../../utils/region')
|
|||
let flag = false; |
|||
Component({ |
|||
/** |
|||
* 组件的属性列表 |
|||
*/ |
|||
observers: { |
|||
'list'(arr){ //默认选中
|
|||
// console.log(arr)
|
|||
// console.log('113123')
|
|||
|
|||
if(arr.length> 1){ |
|||
let { multiple, select_list } = this.data |
|||
if(multiple){ //开启多选
|
|||
console.log(select_list) |
|||
if(select_list.length != 0 )return |
|||
select_list = []; |
|||
arr.forEach(item => { |
|||
if(item.select){ |
|||
let seleArr = item.childers.filter(v => { |
|||
if(v.select){ |
|||
if(v.name == '全部' || v.name == '全国'){ |
|||
v.cityName = item.name |
|||
}else{ |
|||
v.cityName = v.name |
|||
} |
|||
} |
|||
return v.select |
|||
}) |
|||
select_list.push(...seleArr) |
|||
} |
|||
}) |
|||
this.setData({ |
|||
select_list |
|||
}) |
|||
}else{ |
|||
let province_idx = arr.findIndex(item => item.select); |
|||
province_idx = province_idx != -1 ? province_idx : 0; |
|||
let city_idx = arr[province_idx].childers.findIndex(item => item.select) |
|||
city_idx = city_idx != -1 ? city_idx : 0; |
|||
this.setData({ |
|||
province_idx, |
|||
city_idx, |
|||
default_val: [province_idx,city_idx] |
|||
}) |
|||
} |
|||
} |
|||
} |
|||
}, |
|||
properties: { |
|||
list: { |
|||
type: Array, |
|||
value: [], |
|||
}, |
|||
is_show: { |
|||
type: Boolean, |
|||
value: false, |
|||
}, |
|||
multiple: { //单选或者多选
|
|||
type: Boolean, |
|||
value: true |
|||
} |
|||
}, |
|||
/** |
|||
* 组件的初始数据 |
|||
*/ |
|||
data: { |
|||
imageServerUrl: getApp().globalData.imageServerUrl, |
|||
select_list: [], |
|||
province_idx: 0, |
|||
city_idx: null, |
|||
default_val: [0,0] |
|||
}, |
|||
|
|||
/** |
|||
* 组件的方法列表 |
|||
*/ |
|||
methods: { |
|||
// 单选触发事件
|
|||
pickerChange(e){ |
|||
let province_idx = e.detail.value[0],city_idx = e.detail.value[1]; |
|||
this.setData({ |
|||
province_idx, |
|||
city_idx |
|||
}) |
|||
}, |
|||
btn_fn() {}, |
|||
//选择省份
|
|||
province_fn(e) { |
|||
let province_idx = e.currentTarget.dataset.idx, |
|||
{ |
|||
list |
|||
} = this.data; |
|||
this.setData({ |
|||
province_idx, |
|||
city_idx: null, |
|||
list |
|||
}) |
|||
}, |
|||
// 选择城市
|
|||
city_fn(e) { |
|||
flag = true |
|||
let city_idx = e.currentTarget.dataset.idx, |
|||
{ |
|||
select_list, |
|||
list, |
|||
province_idx, |
|||
num |
|||
} = this.data, |
|||
province_list = list[province_idx].childers, |
|||
city_data = province_list[city_idx]; |
|||
if (city_data.select) { //选中反选
|
|||
city_data.select = !city_data.select; |
|||
select_list = select_list.filter(item => { |
|||
return item.select |
|||
}) |
|||
list.forEach(item => { |
|||
if (item.select) { |
|||
let selectIdx = select_list.findIndex(v => v.parentid == item.regionid) |
|||
if (selectIdx == -1) { |
|||
item.select = false |
|||
} |
|||
} |
|||
}) |
|||
} else { //选择未选中的
|
|||
if (city_data.name == '全部' || city_data.name == '全国') { //选中全部
|
|||
select_list = select_list.filter(item => { |
|||
if (item.parentid == city_data.parentid) { |
|||
item.select = false |
|||
} |
|||
return item.parentid != city_data.parentid |
|||
}) |
|||
city_data.cityName = list[province_idx].name |
|||
} else { |
|||
select_list = select_list.filter(item => { |
|||
if (item.parentid == city_data.parentid && item.name == '全部') { |
|||
item.select = false |
|||
} |
|||
return !(item.parentid == city_data.parentid && item.name == '全部') |
|||
}) |
|||
city_data.cityName = city_data.name |
|||
} |
|||
if (city_data.parentid == 1) { // 选中中国
|
|||
//初始化地区列表数据
|
|||
list = list.map(item => { |
|||
if (item.select) { |
|||
item.childers = item.childers.map(v => { |
|||
v.select = false |
|||
return v |
|||
}) |
|||
item.select = false |
|||
} |
|||
return item |
|||
}) |
|||
|
|||
city_data.select = true |
|||
list[province_idx].select = true |
|||
select_list = [city_data] |
|||
this.setData({ |
|||
city_idx, |
|||
select_list, |
|||
list, |
|||
}) |
|||
return |
|||
} |
|||
let selectData = select_list.find(item => item.parentid == 1) |
|||
if (selectData && selectData.parentid == 1) { //已经选中了中国
|
|||
select_list[0].select = false |
|||
select_list = select_list.filter(item => item.parentid != 1) |
|||
let selectIndex = list.findIndex(item => item.regionid == 1) |
|||
list[selectIndex].select = false |
|||
} |
|||
if (select_list.length >= 3) { //多选时
|
|||
console.log('最多只能选', num, '个') |
|||
return |
|||
} |
|||
|
|||
city_data.select = true |
|||
list[province_idx].select = true |
|||
select_list.push(city_data) |
|||
} |
|||
this.setData({ |
|||
city_idx, |
|||
select_list, |
|||
list, |
|||
}) |
|||
}, |
|||
del_city_fn(e) { |
|||
flag = true |
|||
let idx = e.currentTarget.dataset.idx, |
|||
{ |
|||
select_list, |
|||
list |
|||
} = this.data; |
|||
select_list[idx].select = false |
|||
select_list.splice(idx, 1) |
|||
list.forEach(item => { |
|||
if (item.select) { |
|||
let selectIdx = select_list.findIndex(v => v.parentid == item.regionid) |
|||
if (selectIdx == -1) { |
|||
item.select = false |
|||
} |
|||
} |
|||
}) |
|||
this.setData({ |
|||
select_list, |
|||
list |
|||
}) |
|||
}, |
|||
cancel() { |
|||
flag = false |
|||
this.triggerEvent('cancel', false) |
|||
}, |
|||
confirm() { |
|||
|
|||
let { |
|||
select_list, |
|||
multiple, |
|||
province_idx, |
|||
city_idx, |
|||
list |
|||
} = this.data; |
|||
if(!multiple){ |
|||
city_idx = city_idx || 0 |
|||
select_list = [ |
|||
list[province_idx], |
|||
list[province_idx].childers[city_idx] |
|||
] |
|||
} |
|||
// console.log(list)
|
|||
if(select_list.length==0){ |
|||
wx.showToast({ |
|||
title: '请选择地区', |
|||
icon:'none' |
|||
}) |
|||
return |
|||
} |
|||
this.setData({ |
|||
is_show: false |
|||
}) |
|||
this.triggerEvent('confirm', { |
|||
list: select_list |
|||
}) |
|||
} |
|||
} |
|||
}) |
@ -0,0 +1,4 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": {} |
|||
} |
@ -0,0 +1,52 @@ |
|||
<view class="region_picker" wx:if='{{is_show}}' bindtap="cancel"> |
|||
<!-- multiple 开启多选 招聘 --> |
|||
<view wx:if="{{ multiple }}" class="box" style="height: {{select_list.length >= 1 ? '900rpx' : '780rpx' }}" |
|||
catchtap="btn_fn"> |
|||
<view class="header"> |
|||
<text>意向地区(最多选3个)</text> |
|||
</view> |
|||
<view class="middle_tag" catchtap="btn_fn" wx:if='{{ select_list.length >= 1}}'> |
|||
<view wx:for="{{select_list}}" wx:key='index' data-idx="{{index}}" bindtap="del_city_fn"> |
|||
<text>{{item.cityName}}</text> |
|||
<image src="{{imageServerUrl}}publish/cancel.png"></image> |
|||
</view> |
|||
</view> |
|||
<view class="handle"> |
|||
<text catchtap="cancel">取消</text> |
|||
<text catchtap="confirm">确定</text> |
|||
</view> |
|||
<view class="footer" catchtap="btn_fn"> |
|||
<scroll-view scroll-y enable-flex > |
|||
<view wx:for="{{ list }}" wx:key="regionid" data-idx="{{index}}" catchtap="province_fn" |
|||
class="{{ index == province_idx ? 'province_active': '' }}"> |
|||
<text class="{{ item.select ? 'active_dot' : '' }}">{{item.name}}</text> |
|||
</view> |
|||
</scroll-view> |
|||
<scroll-view scroll-y enable-flex > |
|||
<view wx:for="{{ list[province_idx].childers }}" catchtap="city_fn" data-idx="{{index}}" wx:key='regionid' |
|||
class="{{ item.select ? 'city_active' : ''}}"> |
|||
<text>{{item.name}}</text> |
|||
</view> |
|||
</scroll-view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- multiple -> false 单选 招聘--> |
|||
<view class="odd_box" catchtap="btn_fn" wx:if="{{ !multiple }}"> |
|||
<view class="odd_box_header"> |
|||
<text catchtap="cancel">取消</text> |
|||
<text catchtap="confirm">确定</text> |
|||
</view> |
|||
<view class="odd_container"> |
|||
<picker-view style="width:100%; height: 100%" value="{{default_val}}" indicator-class='odd_indicator_class' bindchange='pickerChange'> |
|||
<picker-view-column > |
|||
<view wx:for="{{list}}" wx:key='index'>{{item.name}}</view> |
|||
</picker-view-column> |
|||
<picker-view-column> |
|||
<view wx:for="{{list[province_idx].childers}}" wx:key='index'>{{item.name}}</view> |
|||
</picker-view-column> |
|||
</picker-view> |
|||
</view> |
|||
</view> |
|||
|
|||
</view> |
@ -0,0 +1,222 @@ |
|||
.region_picker { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
background: rgba(0, 0, 0, 0.5); |
|||
z-index: 999; |
|||
} |
|||
|
|||
.box { |
|||
width: 100%; |
|||
position: absolute; |
|||
border-radius: 10px 10px 0px 0px; |
|||
height: 50vh; |
|||
bottom: 0; |
|||
left: 0; |
|||
box-sizing: border-box; |
|||
background: white; |
|||
} |
|||
|
|||
.header { |
|||
width: 100%; |
|||
height: 90rpx; |
|||
|
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
font-weight: bold; |
|||
font-size: 28rpx; |
|||
color: #333333; |
|||
} |
|||
|
|||
|
|||
.middle_tag { |
|||
width: 100%; |
|||
height: 120rpx; |
|||
|
|||
display: flex; |
|||
align-items: center; |
|||
box-sizing: border-box; |
|||
padding: 0 30rpx; |
|||
} |
|||
|
|||
.middle_tag view { |
|||
width: calc((100%/3) - 42rpx); |
|||
padding: 10rpx 0; |
|||
color: #666666; |
|||
position: relative; |
|||
border: 1px solid #E6E6E6; |
|||
border-radius: 12rpx; |
|||
font-size: 28rpx; |
|||
text-align: center; |
|||
margin-left: 26rpx; |
|||
} |
|||
|
|||
.middle_tag view image { |
|||
width: 38rpx; |
|||
height: 38rpx; |
|||
position: absolute; |
|||
top: -19rpx; |
|||
right: -19rpx; |
|||
} |
|||
|
|||
.handle{ |
|||
width: 100%; |
|||
height: 94rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
box-sizing: border-box; |
|||
font-size: 28rpx; |
|||
|
|||
border-top: 2rpx solid #E6E6E6; |
|||
} |
|||
|
|||
|
|||
.handle text{ |
|||
padding: 10rpx 30rpx; |
|||
} |
|||
|
|||
.handle text:nth-child(1){ |
|||
color: #666666; |
|||
} |
|||
|
|||
.handle text:nth-child(2){ |
|||
color: #1B64F8; |
|||
} |
|||
|
|||
.footer { |
|||
width: 100%; |
|||
height:calc(50vh - 90rpx); |
|||
display: flex; |
|||
border-top: 2rpx solid #E6E6E6; |
|||
position: absolute; |
|||
bottom: 0; |
|||
left: 0; |
|||
} |
|||
|
|||
.footer scroll-view { |
|||
flex: 1; |
|||
height: 100%; |
|||
} |
|||
|
|||
.footer scroll-view text{ |
|||
position: relative; |
|||
} |
|||
/* |
|||
.footer scroll-view:nth-of-type(1) { |
|||
|
|||
} */ |
|||
|
|||
.footer scroll-view:nth-of-type(2){ |
|||
background: #FBFBFB; |
|||
flex: 1; |
|||
|
|||
display: flex; |
|||
align-items: center; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.footer scroll-view:nth-of-type(2) view { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
.footer scroll-view:nth-of-type(3) view { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
.footer scroll-view view{ |
|||
flex-shrink: 0; |
|||
width: 100%; |
|||
height: 100rpx; |
|||
line-height: 100rpx; |
|||
border-left: solid 4rpx transparent; |
|||
box-sizing: border-box; |
|||
padding: 0 30rpx; |
|||
} |
|||
|
|||
.footer scroll-view image{ |
|||
width: 26rpx; |
|||
height: 18rpx; |
|||
} |
|||
|
|||
.footer scroll-view .province_active{ |
|||
color: #3476FE; |
|||
border-left: solid 4rpx #3476FE; |
|||
} |
|||
|
|||
.footer scroll-view .city_active{ |
|||
color: #3476FE; |
|||
} |
|||
|
|||
.active_dot::after{ |
|||
content: ''; |
|||
display: inline-block; |
|||
width: 12rpx; |
|||
height: 12rpx; |
|||
border-radius: 50%; |
|||
background: #F63315; |
|||
position: absolute; |
|||
top: -4rpx; |
|||
right: -16rpx; |
|||
} |
|||
|
|||
.odd_box{ |
|||
width: 100%; |
|||
height: 618rpx; |
|||
position: absolute; |
|||
border-radius: 10px 10px 0px 0px; |
|||
bottom: 0; |
|||
left: 0; |
|||
box-sizing: border-box; |
|||
background: white; |
|||
} |
|||
|
|||
.odd_box_header{ |
|||
width: 100%; |
|||
height: 94rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
box-sizing: border-box; |
|||
font-size: 28rpx; |
|||
|
|||
} |
|||
|
|||
|
|||
.odd_box_header text{ |
|||
padding: 10rpx 30rpx; |
|||
} |
|||
|
|||
.odd_box_header text:nth-child(1){ |
|||
color: #666666; |
|||
} |
|||
|
|||
.odd_box_header text:nth-child(2){ |
|||
color: #1B64F8; |
|||
} |
|||
|
|||
.odd_container{ |
|||
width: 100%; |
|||
height: 500rpx; |
|||
box-sizing: border-box; |
|||
padding: 0 28rpx; |
|||
} |
|||
|
|||
.odd_container view{ |
|||
height: 100rpx; |
|||
line-height: 100rpx; |
|||
text-align: center; |
|||
} |
|||
|
|||
.odd_indicator_class{ |
|||
height: 86rpx; |
|||
border-top: 2rpx solid #E6E6E6; |
|||
border-bottom: 2rpx solid #E6E6E6; |
|||
} |
@ -0,0 +1,72 @@ |
|||
const axios = require('../../api/index') |
|||
|
|||
Component({ |
|||
/** |
|||
* 组件的属性列表 |
|||
*/ |
|||
properties: { |
|||
buycar_card_data: { |
|||
type: Object, |
|||
value: {}, |
|||
} |
|||
}, |
|||
|
|||
/** |
|||
* 组件的初始数据 |
|||
*/ |
|||
data: { |
|||
|
|||
}, |
|||
/** |
|||
* 组件的方法列表 |
|||
*/ |
|||
methods: { |
|||
// 刷新
|
|||
refresh() { |
|||
this.triggerEvent('refresh', {item:this.data.buycar_card_data}) |
|||
}, |
|||
// 置顶
|
|||
totop() { |
|||
this.triggerEvent('totop', {item:this.data.buycar_card_data}) |
|||
}, |
|||
// 正在招人
|
|||
change_status(e) { |
|||
console.log(1111) |
|||
var data = { |
|||
id: this.data.buycar_card_data.id, |
|||
value:e.detail.value |
|||
} |
|||
this.triggerEvent('change_status', data) |
|||
|
|||
}, |
|||
// 修改
|
|||
go_edit() { |
|||
console.log('修改') |
|||
console.log(this.data.buycar_card_data.id) |
|||
axios.getEquipmentById(this.data.buycar_card_data.id).then(res => { |
|||
console.log(res) |
|||
var data = res.data |
|||
var url = `/pages/new_show/new_show?data=${escape(JSON.stringify(data))}` |
|||
wx.navigateTo({ |
|||
url, |
|||
}) |
|||
}).catch(err => { |
|||
console.log(err) |
|||
}) |
|||
}, |
|||
|
|||
go_detail() { |
|||
if(this.data.buycar_card_data.state==20||this.data.buycar_card_data.state==30){ |
|||
wx.navigateTo({ |
|||
url:'/pages/detail/detail?id='+this.data.buycar_card_data.id, |
|||
}) |
|||
}else{ |
|||
wx.navigateTo({ |
|||
url:'/pages/my_release/my_release_detail/my_release_detail?id='+this.data.buycar_card_data.id, |
|||
}) |
|||
} |
|||
|
|||
}, |
|||
|
|||
} |
|||
}) |
@ -0,0 +1,5 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
} |
|||
} |
@ -0,0 +1,62 @@ |
|||
<view class="line"></view> |
|||
<view class="card" bindtap="go_detail"> |
|||
<view class="body"> |
|||
<view class="body_1"> |
|||
<view class="body_title">{{buycar_card_data.title}}</view> |
|||
<view class="body_price">{{buycar_card_data.sellingPrice}} 万</view> |
|||
</view> |
|||
<view class="body_desc">{{buycar_card_data.introduction}}</view> |
|||
<view class="body_imgs"> |
|||
<image wx:for="{{buycar_card_data.equipmentPictures}}" src="{{item.pictureLink}}" wx:key="equipmentId" mode="aspectFill"></image> |
|||
</view> |
|||
<view class="body_2" wx:if='{{ buycar_card_data.state != 0&&buycar_card_data.state != 10 }}'> |
|||
<view class="body_address" > |
|||
<image src="/images/index/dw.png"></image> |
|||
{{buycar_card_data.automobileLocation}} |
|||
</view> |
|||
<view class="body_time">{{buycar_card_data.refreshDate}}</view> |
|||
</view> |
|||
<!-- 正在出售中 --> |
|||
<view class="footer" wx:if='{{ buycar_card_data.state == 20 }}'> |
|||
<view class="public" catchtap="prevent"> |
|||
<text wx:if="{{buycar_card_data.state==20}}">出售中</text> |
|||
<switch color="#3878F9" checked="{{buycar_card_data.state==20}}" bindchange="change_status"></switch> |
|||
</view> |
|||
<view> |
|||
<text class="top" catchtap="refresh">一键刷新</text> |
|||
<text class="top" wx:if="{{ !detail.isTop }}" catchtap="totop">我要置顶</text> |
|||
<text class="top" wx:if="{{ detail.isTop }}">已置顶</text> |
|||
<text class="share" catchtap="go_edit">修改信息</text> |
|||
</view> |
|||
</view> |
|||
<!-- 已成交 --> |
|||
<view class="footer" wx:if='{{ buycar_card_data.state == 30 }}'> |
|||
<view class="public" catchtap="prevent"> |
|||
<text>已成交</text> |
|||
<switch color="#3878F9" checked="{{buycar_card_data.state==20}}" bindchange="change_status"></switch> |
|||
</view> |
|||
<text class="change_status" catchtap="go_edit">修改信息</text> |
|||
<!-- <view catchtap="change_status" data-status="1" class="change_status">热招中</view> --> |
|||
</view> |
|||
<!-- 审核中 --> |
|||
<view class="footer" wx:if='{{ buycar_card_data.state == 10 }}'> |
|||
<view > |
|||
<text class="audit">{{buycar_card_data.refreshDate}}</text> |
|||
<view style="margin-left: 70rpx;">审核中</view> |
|||
</view> |
|||
<view> |
|||
<text class="share" catchtap="go_edit">修改信息</text> |
|||
</view> |
|||
</view> |
|||
<!-- 审核失败 --> |
|||
<view class="footer_sb" wx:if='{{ buycar_card_data.state == 0 }}'>失败原因 :<text style="color:red;">{{buycar_card_data.note}}</text></view> |
|||
<view class="footer" wx:if='{{ buycar_card_data.state == 0 }}'> |
|||
<view> |
|||
<text class="audit">{{ buycar_card_data.auditTime }}</text> |
|||
</view> |
|||
<view> |
|||
<text class="share" catchtap="go_edit">修改信息</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
@ -0,0 +1,156 @@ |
|||
.line { |
|||
width: 100%; |
|||
height: 10rpx; |
|||
background: transparent; |
|||
} |
|||
.card{ |
|||
width: 750rpx; |
|||
padding: 20rpx ; |
|||
box-sizing: border-box; |
|||
background: white; |
|||
/* box-shadow: 0 10rpx 40rpx rgba(203, 203, 202, 0.76); */ |
|||
position: relative; |
|||
font-size: 28rpx; |
|||
} |
|||
.body{ |
|||
color: #333; |
|||
} |
|||
.body_1{ |
|||
display: flex; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
.body_title{ |
|||
flex: 1; |
|||
white-space: nowrap; |
|||
text-overflow: ellipsis; |
|||
overflow: hidden; |
|||
margin-right: 10rpx; |
|||
/* font-weight: bold; */ |
|||
font-size: 32rpx; |
|||
color: #333; |
|||
} |
|||
.body_price{ |
|||
color: red; |
|||
font-size: 32rpx; |
|||
} |
|||
.body_desc{ |
|||
color: #858585; |
|||
font-size: 28rpx; |
|||
margin-bottom: 10rpx; |
|||
font-size: 28rpx; |
|||
|
|||
} |
|||
.body_imgs{ |
|||
width: 100%; |
|||
} |
|||
.body_imgs image{ |
|||
width: 224rpx; |
|||
height: 224rpx; |
|||
margin:6rpx ; |
|||
} |
|||
.body_2{ |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
margin: 14rpx 0; |
|||
font-size: 24rpx; |
|||
} |
|||
.body_address{ |
|||
color: #8c8c8c; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
} |
|||
.body_address image{ |
|||
width: 24rpx; |
|||
height: 24rpx; |
|||
|
|||
} |
|||
.body_time{ |
|||
color: #616161; |
|||
} |
|||
|
|||
.footer { |
|||
display: flex; |
|||
margin-top: 0rpx; |
|||
box-sizing: border-box; |
|||
height: 60rpx; |
|||
justify-content: space-between; |
|||
} |
|||
|
|||
.footer view { |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: flex-start; |
|||
font-size: 28rpx; |
|||
color: #3476FE; |
|||
} |
|||
|
|||
.footer view image { |
|||
width: 32rpx; |
|||
height: 32rpx; |
|||
} |
|||
|
|||
.footer view:nth-of-type(2) { |
|||
font-size: 26rpx; |
|||
justify-content: space-around; |
|||
} |
|||
|
|||
.footer view:nth-of-type(2) text { |
|||
display: inline-block; |
|||
|
|||
padding: 4rpx 12rpx; |
|||
border-radius: 10rpx; |
|||
margin: 0 6rpx; |
|||
} |
|||
|
|||
.top { |
|||
color: #353535; |
|||
border: 2rpx solid #ACACAC; |
|||
} |
|||
|
|||
|
|||
.share { |
|||
color: #353535; |
|||
border: 2rpx solid #ACACAC; |
|||
|
|||
} |
|||
|
|||
/* 审核中 */ |
|||
.audit { |
|||
color: #999999; |
|||
} |
|||
|
|||
.footer .audit_btn { |
|||
width: 164rpx; |
|||
height: 48rpx; |
|||
border: 2rpx solid #999999; |
|||
border-radius: 34rpx; |
|||
text-align: center; |
|||
line-height: 48rpx; |
|||
font-size: 26rpx; |
|||
color: #666666; |
|||
} |
|||
|
|||
.footer .audit_btn { |
|||
padding: 0 !important; |
|||
} |
|||
|
|||
/* 已招满 */ |
|||
|
|||
.footer .public text{ |
|||
color: #333333; |
|||
font-size: 26rpx; |
|||
margin-right: 10rpx; |
|||
} |
|||
|
|||
.footer .change_status { |
|||
font-size: 26rpx; |
|||
display: inline-block; |
|||
color: #353535; |
|||
border: 2rpx solid #ACACAC; |
|||
padding: 4rpx 12rpx; |
|||
border-radius: 10rpx; |
|||
margin: 0 6rpx; |
|||
height: 40rpx; |
|||
} |
@ -0,0 +1,314 @@ |
|||
// components/share/share.js
|
|||
let http = require('../../utils/request') |
|||
|
|||
Component({ |
|||
/** |
|||
* 组件的属性列表 |
|||
*/ |
|||
properties: { |
|||
share_detail: { |
|||
type: Object, |
|||
value: { |
|||
} |
|||
}, |
|||
share_type: { |
|||
type: String, |
|||
value: 'apply' |
|||
}, |
|||
is_show_share_mask: { |
|||
type: Boolean, |
|||
value: false |
|||
}, |
|||
}, |
|||
|
|||
/** |
|||
* 组件的初始数据 |
|||
*/ |
|||
data: { |
|||
imageServerUrl: getApp().globalData.imageServerUrl, |
|||
shareList: [{ |
|||
imageUrl: getApp().globalData.imageServerUrl + 'publish/we_chat.png', |
|||
name: '微信好友', |
|||
type: 'wx_chat' |
|||
}, { |
|||
imageUrl: getApp().globalData.imageServerUrl + 'publish/we_chat_ring.png', |
|||
name: '微信群', |
|||
type: 'wx_chat_friends' |
|||
}, { |
|||
imageUrl: getApp().globalData.imageServerUrl + 'publish/pic.png', |
|||
name: '分享海报', |
|||
type: 'created_pic' |
|||
},], |
|||
}, |
|||
|
|||
/** |
|||
* 组件的方法列表 |
|||
*/ |
|||
methods: { |
|||
// 显示转发窗口
|
|||
show_share_mask_fn() { |
|||
this.setData({ |
|||
is_show_share_mask: true, |
|||
}) |
|||
}, |
|||
// 隐藏转发窗口
|
|||
hide_share_mask_fn() { |
|||
this.triggerEvent('hide_share_mask_fn', false) |
|||
this.setData({ |
|||
is_show_share_mask: false |
|||
}) |
|||
}, |
|||
share_fn(e) { |
|||
let type = e.currentTarget.dataset.type; |
|||
if (type == "created_pic") { |
|||
this.create_image() |
|||
} |
|||
}, |
|||
// 生成图片
|
|||
create_image() { |
|||
wx.showLoading({ |
|||
title: '正在生成图片', |
|||
}) |
|||
let { |
|||
share_detail |
|||
} = this.data; |
|||
console.log(share_detail.id) |
|||
http.xhr({ |
|||
url: 'api/v1/auth/weixin/qrcode/'+share_detail.id |
|||
}).then(res => { |
|||
console.log(res) |
|||
var base64 = JSON.parse(res.data).buffer; |
|||
base64 = 'data:image/jpeg;base64,' + base64 |
|||
this.triggerEvent('hide_share_mask_fn', false) |
|||
// 处理二维码
|
|||
var imgPath = wx.env.USER_DATA_PATH + '/fx' + new Date().getTime() + '.png'; |
|||
var imageData = base64.replace(/^data:image\/\w+;base64,/, ""); |
|||
var fs = wx.getFileSystemManager(); |
|||
fs.writeFileSync(imgPath, imageData, "base64"); |
|||
console.log(imgPath) |
|||
// 详情长度
|
|||
var islog=share_detail.introduction.length>12?2:1 |
|||
let paintPallette = { |
|||
width: `${375}px`, |
|||
height: `${553}px`, |
|||
background: '#fff', |
|||
borderRadius: '8px', |
|||
views: [ |
|||
{ |
|||
type: 'image', |
|||
// url: this.data.imageServerUrl + 'publish/qr_code.jpg',
|
|||
url:'/images/publish/yces.png', |
|||
css: { |
|||
top: `${0}px`, |
|||
left: `${68}px`, |
|||
width: '240px', |
|||
height: '58px', |
|||
} |
|||
}, |
|||
{ |
|||
type: 'image', |
|||
// url: this.data.imageServerUrl + 'publish/qr_code.jpg',
|
|||
url:share_detail.equipmentPictures[0].pictureLink, |
|||
mode:"aspectFill", |
|||
css: { |
|||
top: `${58}px`, |
|||
left: `${0}px`, |
|||
width: '375px', |
|||
height: '300px', |
|||
} |
|||
}, |
|||
{ |
|||
type: 'text', |
|||
text: `${share_detail.title}`, |
|||
// text: `${'大师傅大师傅士大夫士大夫士大夫胜多负少放沙发沙发沙发双方都是士大夫石帆胜丰士大夫是反三得房贷首付士大夫大师傅是'}`,
|
|||
|
|||
css: { |
|||
top: `${374}px`, |
|||
left: '20px', |
|||
fontWeight:'bold', |
|||
width: `258px`, |
|||
lineHeight: '28px', |
|||
color: '#333', |
|||
fontSize: '19px', |
|||
maxLines: 1, |
|||
}, |
|||
}, { |
|||
type: 'text', |
|||
text: `${share_detail.introduction}`, |
|||
// text: `${'大师傅大师傅士大夫士大夫士大夫胜多负少放沙发沙发沙发双方都是士大夫石帆胜丰士大夫是反三得房贷首付士大夫大师傅是'}`,
|
|||
css: { |
|||
top: `${410}px`, |
|||
left: '20px', |
|||
width: `200px`, |
|||
lineHeight: '24px', |
|||
color: '#353535', |
|||
fontSize: '15px', |
|||
maxLines: 2, |
|||
}, |
|||
}, |
|||
{ |
|||
type: 'text', |
|||
text: `${share_detail.automobileLocation} | ${share_detail.appearanceDate}年`, |
|||
css: { |
|||
top: `${islog==1?445:465}px`, |
|||
left: '20px', |
|||
width: `240px`, |
|||
color: '#6d6d6d', |
|||
fontSize: '14px', |
|||
maxLines: 2, |
|||
}, |
|||
}, |
|||
{ |
|||
type: 'text', |
|||
text: '售价 :', |
|||
css: { |
|||
top: `${islog==1?470:490}px`, |
|||
left: '20px', |
|||
width: `240px`, |
|||
lineHeight: '28px', |
|||
color: '#6d6d6d', |
|||
fontSize: '20px', |
|||
maxLines: 2, |
|||
}, |
|||
}, |
|||
{ |
|||
type: 'text', |
|||
text: `${share_detail.sellingPrice}万`, |
|||
css: { |
|||
top: `${islog==1?470:490}px`, |
|||
left: '90px', |
|||
width: `200px`, |
|||
fontWeight:'bold', |
|||
lineHeight: '28px', |
|||
color: '#bd0008', |
|||
fontSize: '20px', |
|||
maxLines: 2, |
|||
}, |
|||
}, |
|||
{ |
|||
type: 'text', |
|||
text: `云车二手 · 直买直卖平台`, |
|||
css: { |
|||
bottom: `${12}px`, |
|||
left: '0', |
|||
width: `375px`, |
|||
color: '#3476fe', |
|||
fontSize: '18px', |
|||
maxLines: 1, |
|||
textAlign:'center' |
|||
}, |
|||
}, |
|||
{ |
|||
type: 'text', |
|||
text: `长按识别二维码`, |
|||
css: { |
|||
top: `480px`, |
|||
left: `${268}px`, |
|||
width: `240px`, |
|||
color: '#6d6d6d', |
|||
fontSize: '14px', |
|||
maxLines: 2, |
|||
}, |
|||
}, |
|||
{ |
|||
type: 'image', |
|||
// url: this.data.imageServerUrl + 'publish/qr_code.jpg',
|
|||
url: `${imgPath}`, |
|||
css: { |
|||
top: `${382}px`, |
|||
left: `${270}px`, |
|||
width: '91px', |
|||
height: '93px', |
|||
} |
|||
}] |
|||
}; |
|||
this.setData({ |
|||
paintPallette |
|||
}) |
|||
}) |
|||
.catch(err => { |
|||
console.log(err) |
|||
}) |
|||
|
|||
}, |
|||
image_mask_fn() { |
|||
this.setData({ |
|||
imagePath: '', |
|||
paintPallette: '' |
|||
}) |
|||
}, |
|||
onImgOK(e) { |
|||
let imagePath = e.detail.path; |
|||
this.setData({ |
|||
imagePath: imagePath |
|||
}) |
|||
}, |
|||
image_load() { |
|||
this.setData({ |
|||
is_show_share_mask: false |
|||
}) |
|||
wx.hideLoading() |
|||
}, |
|||
save_image() { |
|||
let url = this.data.imagePath; |
|||
wx.getSetting({ |
|||
success: (res) => { |
|||
if (!res.authSetting['scope.writePhotosAlbum']) { |
|||
wx.authorize({ |
|||
scope: 'scope.writePhotosAlbum', |
|||
success: () => { |
|||
// 同意授权
|
|||
this.save_img_fn(url); |
|||
}, |
|||
fail: (res) => { |
|||
wx.showModal({ |
|||
content: '暂无权限保存本地,点击确认前往设置页面开启权限', |
|||
success: (res) => { |
|||
if (res.confirm) { |
|||
wx.openSetting({ |
|||
withSubscriptions: true, |
|||
}) |
|||
} |
|||
} |
|||
}) |
|||
} |
|||
}) |
|||
} else { |
|||
// 已经授权了
|
|||
this.save_img_fn(url); |
|||
} |
|||
}, |
|||
fail: (res) => { |
|||
wx.showToast({ |
|||
title: '您还未授权,请授权', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
}) |
|||
}, |
|||
save_img_fn(url) { |
|||
wx.getImageInfo({ |
|||
src: url, |
|||
success: (res) => { |
|||
let path = res.path; |
|||
wx.saveImageToPhotosAlbum({ |
|||
filePath: path, |
|||
success: (res) => { |
|||
wx.showToast({ |
|||
title: '保存成功', |
|||
icon: 'none' |
|||
}) |
|||
}, |
|||
fail: (res) => { |
|||
wx.showToast({ |
|||
title: '保存失败', |
|||
icon: 'none' |
|||
}) |
|||
} |
|||
}) |
|||
}, |
|||
fail: (res) => { } |
|||
}) |
|||
}, |
|||
} |
|||
}) |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"component": true, |
|||
"usingComponents": { |
|||
"painter": "/components/painter/painter" |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
<!--components/share/share.wxml--> |
|||
<block wx:if='{{ is_show_share_mask }}'> |
|||
<!-- <block wx:if='{{ true }}'> --> |
|||
<!-- 分享弹窗 --> |
|||
<view class="mask" bindtap="hide_share_mask_fn"> |
|||
<view class="box" catchtap="no_bubble"> |
|||
<view class="header"> |
|||
<text>立即分享给好友</text> |
|||
</view> |
|||
<view class="middle"> |
|||
<button wx:for="{{shareList}}" open-type="{{ index != 2 ? 'share' : '' }}" data-type="{{item.type}}" |
|||
bindtap="share_fn" wx:key='index'> |
|||
<image src="{{item.imageUrl}}"></image> |
|||
<text>{{item.name}}</text> |
|||
</button> |
|||
</view> |
|||
<view class="footer" catchtap="hide_share_mask_fn"> |
|||
<text>取消</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</block> |
|||
<block wx:if='{{ paintPallette }}'> |
|||
<painter palette="{{paintPallette}}" bind:imgOK="onImgOK" bind:touchEnd="touchEnd" /> |
|||
</block> |
|||
<view class="image_mask" mode='heightFix' wx:if='{{imagePath}}' catchtap="image_mask_fn"> |
|||
<image bindload='image_load' catchtap="no_bubble" catchlongpress="save_image" src="{{imagePath}}" /> |
|||
<text>长按上方图片保存</text> |
|||
</view> |
@ -0,0 +1,115 @@ |
|||
/* components/share/share.wxss */ |
|||
.mask { |
|||
width: 100vw; |
|||
height: 100vh; |
|||
background: rgba(0, 0, 0, 0.5); |
|||
z-index: 999; |
|||
|
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
} |
|||
|
|||
.mask .box{ |
|||
|
|||
position: absolute; |
|||
bottom: 0rpx; |
|||
left: 0; |
|||
|
|||
width: 100vw; |
|||
height: auto; |
|||
padding-bottom: 10rpx; |
|||
background: white; |
|||
border-radius: 16rpx 16rpx 0 0; |
|||
} |
|||
|
|||
.mask .box .header{ |
|||
width: 100%; |
|||
box-sizing: border-box; |
|||
padding: 36rpx 0 0; |
|||
text-align: center; |
|||
} |
|||
|
|||
.mask .box .middle{ |
|||
width: 100%; |
|||
height: 180rpx; |
|||
box-sizing: border-box; |
|||
flex-wrap: wrap; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.mask .box .middle button{ |
|||
width: 160rpx; |
|||
height: 180rpx; |
|||
background: transparent; |
|||
margin: 0; |
|||
padding: 0; |
|||
display: inline-block; |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
color: #333333; |
|||
font-size: 0; |
|||
line-height: 20rpx; |
|||
} |
|||
|
|||
.mask .box .middle button::after{ |
|||
border: 0; |
|||
} |
|||
|
|||
.mask .box .middle button text{ |
|||
margin-top: 20rpx; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.mask .box .middle image{ |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
} |
|||
|
|||
.mask .box .footer{ |
|||
width: 100%; |
|||
height: 98rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: center; |
|||
color: #666666; |
|||
font-size: 28rpx; |
|||
border-top: 16rpx solid #F7F8FA; |
|||
font-size: 32rpx; |
|||
} |
|||
|
|||
.image_mask{ |
|||
width: 100vw; |
|||
height: 100vh; |
|||
position: fixed; |
|||
top: 0; |
|||
left: 0; |
|||
z-index: 999; |
|||
|
|||
background: rgba(0, 0, 0, 0.5); |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
} |
|||
|
|||
.image_mask image{ |
|||
width: 608rpx; |
|||
height: 864rpx; |
|||
} |
|||
|
|||
.image_mask text{ |
|||
width: 608rpx; |
|||
height: 76rpx; |
|||
background: #3476FE; |
|||
color: white; |
|||
font-size: 28rpx; |
|||
border-radius: 8rpx; |
|||
line-height: 76rpx; |
|||
text-align: center; |
|||
margin-top: 50rpx; |
|||
} |
After Width: | Height: | Size: 755 B |
After Width: | Height: | Size: 814 B |
After Width: | Height: | Size: 392 B |
After Width: | Height: | Size: 613 B |
After Width: | Height: | Size: 773 B |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 24 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 696 B |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 761 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 440 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 243 KiB |
After Width: | Height: | Size: 246 KiB |
After Width: | Height: | Size: 6.4 KiB |
After Width: | Height: | Size: 1002 B |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 306 B |
After Width: | Height: | Size: 282 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 584 B |
After Width: | Height: | Size: 1.8 KiB |