Browse Source

添加项目文件。

master
faker 2 years ago
parent
commit
e4ad6639be
  1. 6
      api/index.js
  2. 165
      api/server/home.js
  3. 8
      api/server/index.js
  4. 19
      api/server/login.js
  5. 152
      api/server/my.js
  6. 79
      app.js
  7. 31
      app.json
  8. 68
      app.wxss
  9. 453
      components/buycar/buycar.js
  10. 7
      components/buycar/buycar.json
  11. 127
      components/buycar/buycar.wxml
  12. 149
      components/buycar/buycar.wxss
  13. 44
      components/buycar_card/buycar_card.js
  14. 5
      components/buycar_card/buycar_card.json
  15. 37
      components/buycar_card/buycar_card.wxml
  16. 143
      components/buycar_card/buycar_card.wxss
  17. 99
      components/list/list.js
  18. 6
      components/list/list.json
  19. 43
      components/list/list.wxml
  20. 57
      components/list/list.wxss
  21. 81
      components/login/login.js
  22. 4
      components/login/login.json
  23. 15
      components/login/login.wxml
  24. 62
      components/login/login.wxss
  25. 75
      components/me/me.js
  26. 4
      components/me/me.json
  27. 56
      components/me/me.wxml
  28. 90
      components/me/me.wxss
  29. 45
      components/modal/modal.js
  30. 4
      components/modal/modal.json
  31. 16
      components/modal/modal.wxml
  32. 68
      components/modal/modal.wxss
  33. 66
      components/new_picker/new_picker.js
  34. 4
      components/new_picker/new_picker.json
  35. 13
      components/new_picker/new_picker.wxml
  36. 55
      components/new_picker/new_picker.wxss
  37. 13
      components/new_picker/tool.js
  38. 72
      components/new_picker2/new_picker2.js
  39. 4
      components/new_picker2/new_picker2.json
  40. 15
      components/new_picker2/new_picker2.wxml
  41. 89
      components/new_picker2/new_picker2.wxss
  42. 13
      components/new_picker2/tool.js
  43. 34
      components/noData/noData.js
  44. 4
      components/noData/noData.json
  45. 6
      components/noData/noData.wxml
  46. 30
      components/noData/noData.wxss
  47. 251
      components/painter/lib/downloader.js
  48. 102
      components/painter/lib/gradient.js
  49. 750
      components/painter/lib/pen.js
  50. 784
      components/painter/lib/qrcode.js
  51. 68
      components/painter/lib/util.js
  52. 611
      components/painter/lib/wx-canvas.js
  53. 888
      components/painter/painter.js
  54. 4
      components/painter/painter.json
  55. 25
      components/painter/painter.wxml
  56. 66
      components/picker/picker.js
  57. 4
      components/picker/picker.json
  58. 17
      components/picker/picker.wxml
  59. 80
      components/picker/picker.wxss
  60. 13
      components/picker/tool.js
  61. 246
      components/region_picker/region_picker.js
  62. 4
      components/region_picker/region_picker.json
  63. 52
      components/region_picker/region_picker.wxml
  64. 222
      components/region_picker/region_picker.wxss
  65. 72
      components/release_card/release_card.js
  66. 5
      components/release_card/release_card.json
  67. 62
      components/release_card/release_card.wxml
  68. 156
      components/release_card/release_card.wxss
  69. 314
      components/share/share.js
  70. 6
      components/share/share.json
  71. 29
      components/share/share.wxml
  72. 115
      components/share/share.wxss
  73. BIN
      images/common/cellection.png
  74. BIN
      images/common/collect.png
  75. BIN
      images/common/edit.png
  76. BIN
      images/common/empty.png
  77. BIN
      images/common/fenxiang.png
  78. BIN
      images/common/loge.png
  79. BIN
      images/common/logo.jpg
  80. BIN
      images/common/nothing.png
  81. BIN
      images/common/phone_newicon.png
  82. BIN
      images/common/red_tis.png
  83. BIN
      images/common/refresh.png
  84. BIN
      images/common/report.png
  85. BIN
      images/common/service_icon.png
  86. BIN
      images/common/share_image_news2.png
  87. BIN
      images/common/upimg.png
  88. BIN
      images/index/banner.png
  89. BIN
      images/index/banner2.png
  90. BIN
      images/index/dw.png
  91. BIN
      images/index/laba.png
  92. BIN
      images/index/share.png
  93. BIN
      images/index/shrink.png
  94. BIN
      images/index/unfold.png
  95. BIN
      images/me/attestation.png
  96. BIN
      images/me/audit.png
  97. BIN
      images/me/audit_now.png
  98. BIN
      images/me/camera.png
  99. BIN
      images/me/check.png
  100. BIN
      images/me/cloud_1.png

6
api/index.js

@ -0,0 +1,6 @@
let server = require('./server/index')
module.exports = {
// ...require('././cacheServer/index'),
...server,
// ...require('././utils/index'),
}

165
api/server/home.js

@ -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'
})
}
}

8
api/server/index.js

@ -0,0 +1,8 @@
var login = require('./login');
var home = require('./home');
var my = require('./my');
module.exports = {
...login,
...home,
...my,
}

19
api/server/login.js

@ -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`
})
}
}

152
api/server/my.js

@ -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',
})
}
}

79
app.js

@ -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('点好友链接进入')
})
}
},
})

31
app.json

@ -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"
}

68
app.wxss

@ -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;
}

453
components/buycar/buycar.js

@ -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 })
}
}
})

7
components/buycar/buycar.json

@ -0,0 +1,7 @@
{
"component": true,
"usingComponents": {
"no_data": "/components/noData/noData",
"buycar_card": "/components/buycar_card/buycar_card"
}
}

127
components/buycar/buycar.wxml

@ -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>

149
components/buycar/buycar.wxss

@ -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;
}

44
components/buycar_card/buycar_card.js

@ -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,
})
},
}
})

5
components/buycar_card/buycar_card.json

@ -0,0 +1,5 @@
{
"component": true,
"usingComponents": {
}
}

37
components/buycar_card/buycar_card.wxml

@ -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>

143
components/buycar_card/buycar_card.wxss

@ -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;
}

99
components/list/list.js

@ -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)
}
}
})

6
components/list/list.json

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"no_data": "/components/noData/noData"
}
}

43
components/list/list.wxml

@ -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>

57
components/list/list.wxss

@ -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;
}

81
components/login/login.js

@ -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()
})
},
}
})

4
components/login/login.json

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

15
components/login/login.wxml

@ -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>

62
components/login/login.wxss

@ -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;
}

75
components/me/me.js

@ -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,
})
},
}
})

4
components/me/me.json

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

56
components/me/me.wxml

@ -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>

90
components/me/me.wxss

@ -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;
}

45
components/modal/modal.js

@ -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')
},
}
})

4
components/modal/modal.json

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

16
components/modal/modal.wxml

@ -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>

68
components/modal/modal.wxss

@ -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;
}

66
components/new_picker/new_picker.js

@ -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})
}
}
})

4
components/new_picker/new_picker.json

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

13
components/new_picker/new_picker.wxml

@ -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>

55
components/new_picker/new_picker.wxss

@ -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;
}

13
components/new_picker/tool.js

@ -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
}

72
components/new_picker2/new_picker2.js

@ -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)
}
}
})

4
components/new_picker2/new_picker2.json

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

15
components/new_picker2/new_picker2.wxml

@ -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>

89
components/new_picker2/new_picker2.wxss

@ -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;
}

13
components/new_picker2/tool.js

@ -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
}

34
components/noData/noData.js

@ -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')
}
}
})

4
components/noData/noData.json

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

6
components/noData/noData.wxml

@ -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>

30
components/noData/noData.wxss

@ -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;
}

251
components/painter/lib/downloader.js

@ -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];
}

102
components/painter/lib/gradient.js

@ -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 }
})();

750
components/painter/lib/pen.js

@ -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;
}
}

784
components/painter/lib/qrcode.js

@ -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;
})();

68
components/painter/lib/util.js

@ -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
};

611
components/painter/lib/wx-canvas.js

@ -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();
}
});
}
}

888
components/painter/painter.js

@ -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;
};
}

4
components/painter/painter.json

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

25
components/painter/painter.wxml

@ -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>

66
components/picker/picker.js

@ -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)
}
}
})

4
components/picker/picker.json

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

17
components/picker/picker.wxml

@ -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>

80
components/picker/picker.wxss

@ -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;
}

13
components/picker/tool.js

@ -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
}

246
components/region_picker/region_picker.js

@ -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
})
}
}
})

4
components/region_picker/region_picker.json

@ -0,0 +1,4 @@
{
"component": true,
"usingComponents": {}
}

52
components/region_picker/region_picker.wxml

@ -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>

222
components/region_picker/region_picker.wxss

@ -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;
}

72
components/release_card/release_card.js

@ -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,
})
}
},
}
})

5
components/release_card/release_card.json

@ -0,0 +1,5 @@
{
"component": true,
"usingComponents": {
}
}

62
components/release_card/release_card.wxml

@ -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>

156
components/release_card/release_card.wxss

@ -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;
}

314
components/share/share.js

@ -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) => { }
})
},
}
})

6
components/share/share.json

@ -0,0 +1,6 @@
{
"component": true,
"usingComponents": {
"painter": "/components/painter/painter"
}
}

29
components/share/share.wxml

@ -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>

115
components/share/share.wxss

@ -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;
}

BIN
images/common/cellection.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

BIN
images/common/collect.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 814 B

BIN
images/common/edit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 B

BIN
images/common/empty.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

BIN
images/common/fenxiang.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

BIN
images/common/loge.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
images/common/logo.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
images/common/nothing.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
images/common/phone_newicon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 696 B

BIN
images/common/red_tis.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

BIN
images/common/refresh.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 761 B

BIN
images/common/report.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
images/common/service_icon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
images/common/share_image_news2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 440 KiB

BIN
images/common/upimg.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
images/index/banner.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 243 KiB

BIN
images/index/banner2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

BIN
images/index/dw.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
images/index/laba.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1002 B

BIN
images/index/share.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

BIN
images/index/shrink.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

BIN
images/index/unfold.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 B

BIN
images/me/attestation.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
images/me/audit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
images/me/audit_now.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
images/me/camera.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
images/me/check.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 584 B

BIN
images/me/cloud_1.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save