commit b496603f7ca0002e266cedad1128098253a2e106 Author: zoujiandong <10130823232@qq.com> Date: Tue Jun 17 17:58:29 2025 +0800 111 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c11a8a6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +# Dependency directories +node_modules +*.log* +*.lock +.eslintrc.js +package-lock.json +.history +dist/ +h5/ +dist.zip +wxAppPatient.zip +miniprogram_npm diff --git a/README.md b/README.md new file mode 100644 index 0000000..541fdfb --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +人工肝病例登记系统-小程序 \ No newline at end of file diff --git a/api/api.js b/api/api.js new file mode 100644 index 0000000..d164d35 --- /dev/null +++ b/api/api.js @@ -0,0 +1,118 @@ +import {request} from '../utils/request.js' +function addCase(data){ //添加病例 + return request('/medicalRecord/add','POST',data,true) +}; +function caseDetail(id){ //病例详情 + return request('/medicalRecord/getDetail/'+id,'GET',{},true) +}; +function caseList(data){ //病例列表 + return request('/medicalRecord/getList','POST',data,true) +}; +function editCase(data){ //修改病例 + return request('/medicalRecord/update','POST',data,true) +}; +function getCaptcha(){ //获取图片验证码 + return request('/user/getCaptcha','GET') +}; + + +function getDeal(data){ //获取协议 + return request('/user/getDeal','GET',data) +}; +function getInfo(data){ //个人中心 + return request('/user/getInfo','GET',data) +}; +function getRubric(data){ //操作说明 + return request('/user/getRubric','GET',data) +}; +function sendSms(data){ //发送验证码 + return request('/user/sendSms','POST',data,true) +}; +function smsLogin(data){ //短信登录 + return request('/user/smsLogin','POST',data,true) +}; +function smsRegister(data){ //短信注册 + return request('/user/smsRegister','POST',data,true) +}; +function addBank(data){ //添加医生签名 + return request('/user/addBank','POST',data) +}; +function getSign(data){ //获取医生签名 + return request('/user/getSign','GET',data) +}; +function getOssSign(type){ //获取上传文件 Policy + return request('/file/getOSSPolicy/'+type,'GET') +}; +function getArea(data){//获取省市区 + return request('/user/areaList','GET',data) +} +function getHospital(id){//医院列表 + return request('/user/hospitalList/'+id,'GET') +} +function getOfficeList(data){//科室列表 + return request('/user/officeList','GET',data) +} +function getPosition(){//职称列表 + return request('/user/positionList','GET',{}) +} +function uploadImg(data){//上传图片 + return request('/user/uoloadImg','POST',data) +} +function phoneLogin(appid,data){//获取用户绑定手机号信息 + return request(`/wx/user/${appid}/phone`,'GET',data,true) +} +function logout(){ + return request(`/user/logOut`,'GET') +} +function needInfo(mobile){//获取用户绑定手机号信息 + return request(`/user/getDetail/${mobile}`,'GET',{},true) +} +function modifyInfo(data){//修改HCP资料 + return request(`/user/modify`,'POST',data,true) +} +function pwdLogin(data){ //密码登录 + return request('/user/login','POST',data,true) +}; +function getPrivacy(){//获取用户绑定手机号信息 + return request(`/user/getPrivacy`,'GET',{},true) +} +function getProjectStatus(){//获取项目项目状态 + return request(`/user/getProjectStatus`,'GET',{},true) +} + +// function getCaseNum(){//获取项目病例总数 +// return request(`/user/getCaseNum`,'GET',{},true) +// } +// function getCaseSwitch(){//项目病例开关 +// return request(`/user/getCaseSwitch`,'GET',{},true) +// } + + +module.exports={ + addCase, + caseDetail, + caseList, + editCase, + getCaptcha, + getDeal, + getInfo, + getRubric, + sendSms, + smsLogin, + smsRegister, + addBank, + getSign, + getOssSign, + getArea, + getHospital, + getOfficeList, + getPosition, + uploadImg, + phoneLogin, + logout, + needInfo, + modifyInfo, + pwdLogin, + getPrivacy, + getProjectStatus + } diff --git a/api/auth.js b/api/auth.js new file mode 100644 index 0000000..d8e53e8 --- /dev/null +++ b/api/auth.js @@ -0,0 +1,22 @@ +function auth(){ //鉴权 + return new Promise((resolve,reject)=>{ + wx.login({ + success(res){ + if(res.errMsg=="login:ok"){ + resolve(res.code) + }else{ + wx.showToast({ + title:res.errMsg, + icon:'error' + }) + } + }, + fail(err){ + reject(err) + } + }) + }) + } + module.exports = { + auth + } \ No newline at end of file diff --git a/app.js b/app.js new file mode 100644 index 0000000..f50758d --- /dev/null +++ b/app.js @@ -0,0 +1,12 @@ +// app.js +import { hostConfig} from "./utils/config" +import router from './utils/router.js' +App({ + onLaunch: function () { + }, + method: router, + hostConfig: hostConfig, + globalData: { + height: 0 + } +}); diff --git a/app.json b/app.json new file mode 100644 index 0000000..2f9094c --- /dev/null +++ b/app.json @@ -0,0 +1,49 @@ +{ + "pages": [ + "pages/index/index", + "pages/personCenter/personCenter" + ], + "subpackages": [ + { + "root": "case", + "pages": [ + "pages/mobileLogin/mobileLogin", + "pages/createCase/createCase", + "pages/register/register", + "pages/agreement/agreement", + "pages/signcanvas/signcanvas", + "pages/improveInfo/improveInfo", + "pages/pwdLogin/pwdLogin", + "pages/privacy/privacy", + "pages/bankCard/bankCard", + "pages/paintCanvas/paintCanvas" + ] + } + ], + "tabBar": { + "custom": true, + "color": "#000000", + "selectedColor": "#3881F7", + "backgroundColor": "#fff", + "list": [ + { + "pagePath": "pages/index/index", + "text": "病例列表" + }, + { + "pagePath": "pages/personCenter/personCenter", + "text": "个人中心" + } + ] + }, + "window": { + "backgroundColor": "#F6F6F6", + "backgroundTextStyle": "light", + "navigationBarBackgroundColor": "#F6F6F6", + "navigationBarTitleText": "人工肝病例登记系统", + "navigationBarTextStyle": "black" + }, + " __usePrivacyCheck__": true, + "sitemapLocation": "sitemap.json", + "lazyCodeLoading": "requiredComponents" +} \ No newline at end of file diff --git a/app.wxss b/app.wxss new file mode 100644 index 0000000..85ebc31 --- /dev/null +++ b/app.wxss @@ -0,0 +1,35 @@ + +page{ + width: 100%; + height:100vh; + display: flex; + flex-direction: column; + + font-family: PingFang SC; +} +.container { + display: flex; + flex-direction: column; + align-items: center; + box-sizing: border-box; +} + +button { + background: initial; +} + +button:focus{ + outline: 0; +} + +button::after{ + border: none; +} +.nonedata{ + min-height:350rpx; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + diff --git a/case/pages/agreement/agreement.js b/case/pages/agreement/agreement.js new file mode 100644 index 0000000..16f22f6 --- /dev/null +++ b/case/pages/agreement/agreement.js @@ -0,0 +1,154 @@ +// case/pages/agreement/agreement.js +import {getDeal,getRubric,getSign} from "../../../api/api" +import { throttle } from "../../../utils/util" +const dayjs = require("../../../utils/dayjs"); +const app = getApp() +Page({ + + /** + * 页面的初始数据 + */ + data: { + node:'', + type:'', + year:'', + month:'', + day:'', + signImg:'', + navName:'项目协议' + }, + handleGetDeal(name,mobile,bankName,bankCardNo,idCardNo){ + getDeal().then(res=>{ + let deal=res.replace(/\$\{name\}/g,name).replace(/\$\{mobile\}/g,mobile).replace(/\$\{bankName\}/g,bankName).replace(/\$\{bankCardNo\}/g,bankCardNo).replace(/\$\{idCardNo\}/g,idCardNo); + this.setData({ + node:deal + }) + }) + }, + handleFetRubric(){ + getRubric().then(res=>{ + this.setData({ + node:res + }) + }) + }, + goSign:throttle(function(){ + let tokenStr='' + const { envVersion } = wx.getAccountInfoSync().miniProgram; + if(envVersion=="develop" || envVersion=="trial"){ + tokenStr="DEV_CASE_TOKEN" + }else{ + tokenStr="PROD_CASE_TOKEN" + } + let token = wx.getStorageSync(tokenStr); + if(token){ + app.method.navigateTo({ + url: '/case/pages/bankCard/bankCard', + complete:function(err){ + console.log(err) + } + }) + }else{ + app.method.navigateTo({ + url: '/case/pages/mobileLogin/mobileLogin?redirectUrl='+encodeURIComponent('case/pages/agreement/agreement'), + complete:function(err){ + console.log(err) + } + }) + } + + }), + handleGetSign(){ + getSign().then(res=>{ + if(res && res.signImg){ + let {signImg,createTime,bankCardNo,bankName,idCardNo,mobile,name}=res; + this.handleGetDeal(name,mobile,bankName,bankCardNo,idCardNo); + this.setData({ + signImg:signImg, + year:dayjs(createTime).format("YYYY"), + month:dayjs(createTime).format("M"), + day:dayjs(createTime).format("D"), + }) + }else{ + this.handleGetDeal('','','','',''); + } + }).catch(error=>{ + + if(error.code==30007){ + app.method.navigateTo({ + url: '/case/pages/mobileLogin/mobileLogin?redirectUrl='+encodeURIComponent('case/pages/agreement/agreement'), + complete:function(err){ + console.log(err) + } + }) + + } + }) + }, + /** + * 生命周期函数--监听页面加载 + */ + onLoad(options) { + if(options.type=="description"){ + this.handleFetRubric(); + this.setData({ + type:'description', + navName:'操作说明' + }) + }else{ + + this.handleGetSign(); + } + + + }, + + /** + * 生命周期函数--监听页面初次渲染完成 + */ + onReady() { + + }, + + /** + * 生命周期函数--监听页面显示 + */ + onShow() { + + }, + + /** + * 生命周期函数--监听页面隐藏 + */ + onHide() { + + }, + + /** + * 生命周期函数--监听页面卸载 + */ + onUnload() { + + }, + + /** + * 页面相关事件处理函数--监听用户下拉动作 + */ + onPullDownRefresh() { + + }, + + /** + * 页面上拉触底事件的处理函数 + */ + onReachBottom() { + + }, + + /** + * 用户点击右上角分享 + */ + // onShareAppMessage() { + + // } +}) \ No newline at end of file diff --git a/case/pages/agreement/agreement.json b/case/pages/agreement/agreement.json new file mode 100644 index 0000000..0c7e114 --- /dev/null +++ b/case/pages/agreement/agreement.json @@ -0,0 +1,6 @@ +{ + "usingComponents": { + "navBar":"../../../components/navBar/navBar" + }, + "navigationStyle":"custom" +} \ No newline at end of file diff --git a/case/pages/agreement/agreement.wxml b/case/pages/agreement/agreement.wxml new file mode 100644 index 0000000..042bb76 --- /dev/null +++ b/case/pages/agreement/agreement.wxml @@ -0,0 +1,33 @@ + + + + + + + + + + 甲方:北京欣欣相照健康科技有限公司 + 日期:{{year}}年{{month}}月{{day}}日 + + + + 乙方:北京杏苑爱医科技发展有限公司 + 日期:{{year}}年{{month}}月{{day}}日 + + + + 丙方: + 日期:{{year}}年{{month}}月{{day}}日 + + + + + + + + 同意签署 + + \ No newline at end of file diff --git a/case/pages/agreement/agreement.wxss b/case/pages/agreement/agreement.wxss new file mode 100644 index 0000000..b2ea1f5 --- /dev/null +++ b/case/pages/agreement/agreement.wxss @@ -0,0 +1,92 @@ +/* case/pages/agreement/agreement.wxss */ +.page{ + background: #F7F9F9; + flex:1; + display: flex; + position: relative; + flex-direction: column; + margin-top: 172rpx; +} +.con{ + flex:1; + padding:10rpx 32rpx 0; + margin-bottom: 20rpx; +} +.con.active{ + margin-bottom: 150rpx; +} +.btnbox{ + z-index:999; + padding:0 32rpx; + background-color: #fff; + position: fixed; + box-sizing: border-box; + bottom:0; + height: 130rpx; + width:100%; +} +.btnbox .btn{ + margin-top: 15rpx; + width:100%; + display: flex; + align-items: center; + justify-content: center; + font-size: 32rpx; + font-weight: 500; + color: #FFFFFF; + height: 88rpx; + background: linear-gradient(90deg, #377FF7 0%, #51AAFF 100%); + border-radius: 8rpx; +} +.imgbox{ + margin:30rpx 32rpx 60rpx; + display: flex; + flex-direction: column; + align-items: stretch; + justify-content: space-between; +} +.company{ + z-index:99; + top:-55rpx; + position: absolute; + width:220rpx; + right:206rpx; + height:220rpx; +} +.signimg{ + top:-40rpx; + left:80rpx; + position: absolute; + width:160rpx; + height:120rpx; +} +.signbox{ + display: flex; + align-items: center; + justify-content: flex-start; +} +.jiafang{ + position: relative; + flex:1; + font-size:28rpx; + margin-bottom:50rpx; +} +.yifang{ + font-size:small; + flex:1; + font-size:28rpx; + display: flex; + justify-content: space-between; + align-items: flex-start; + flex-direction: column; + position: relative; +} +.signdate{ + margin-top: 40rpx; +} +.company2{ + top:-90rpx; + right:280rpx; + width:280rpx; + height:300rpx; +} \ No newline at end of file diff --git a/case/pages/bankCard/bankCard.js b/case/pages/bankCard/bankCard.js new file mode 100644 index 0000000..ce96e09 --- /dev/null +++ b/case/pages/bankCard/bankCard.js @@ -0,0 +1,322 @@ +// case/pages/improveInfo/improveInfo.js +// case/pages/register/register.js +import {throttle} from "../../../utils/util" +import {hostConfig} from "../../../utils/config" +import {getArea,addBank} from "../../../api/api" +const host=hostConfig().host; +const app=getApp(); +Page({ + /** + * 页面的初始数据 + */ + data: { + showSuccess:false, + img_host:app.hostConfig().imghost, + showArea:false, + cityName:'', + areaColumns:[ + { + values: [1,2], + className: 'column1', + }, + { + values: [3,4], + className: 'column2', + defaultIndex: 0 + }, + { + values: [3,4], + className: 'column3', + defaultIndex: 0 + }, + ], + showArea:false, + bankCardNo:'', + bankName:'', + cityId:'', + countyId:'', + provId:'', + name:'', + idCardNo:'', + signImg:'', + }, + opeArea(){ + this.setData({ + showArea:true + }) + }, + openOffice(){ + this.setData({ + showOffice:true, + }) + }, + openPosition(){ + this.setData({ + showPosition:true, + }) + }, + onChange(e){ + const {value} = e.detail; + + const {id}=e.currentTarget.dataset; + // console.log(value,id) + this.setData({ + [id]: value + }); + }, + onChangeArea(event){ + const { picker, value, index } = event.detail; + const provinceId=value[0].id; + const cityId=value[1].id; + if(index==0){ + this.handleGetArea(provinceId,2); + }else if(index==1){ + this.handleGetArea(cityId,3) + } + }, + confirmArea(event){ + const {value} = event.detail; + console.log(value); + let provId=value[0].id; + let cityId=value[1].id; + let countyId=value[2]?value[2].id:value[1].id; + let cityName=''; + for (let i = 0; i { + wx.showToast({ + title: '绑定成功', + icon:'none', + duration:2000, + success:function(){ + let timer=setTimeout(()=>{ + wx.switchTab({ + url: '/pages/index/index', + }) + clearTimeout(timer) + },1000) + } + }) + }) + }), + luhnCheck(cardNumber) { + var sum = 0; + var shouldDouble = false; + var digit; + + // 去除任何非数字字符 + cardNumber = cardNumber.replace(/\D/g, ''); + + // 从右向左遍历数字 + for (var i = cardNumber.length - 1; i >= 0; i--) { + digit = parseInt(cardNumber.charAt(i), 10); + + if (shouldDouble) { + if ((digit *= 2) > 9) digit -= 9; + } + + sum += digit; + shouldDouble = !shouldDouble; + } + + // 如果校验和能被10整除,则卡号有效 + return (sum % 10) === 0; + }, + goSign:throttle(function(){ + app.method.navigateTo({ + url:'/case/pages/signcanvas/signcanvas' + }) + }), + handleGetArea(id,type){ + getArea({ + parent:id + }).then(res=>{ + if(type==1){ + let obj='areaColumns[0].values'; + this.setData({ + [obj]:res + }) + this.handleGetArea(res[0].id,2) + }else if(type==2){ + let obj='areaColumns[1].values'; + this.setData({ + [obj]:res + }) + this.handleGetArea(res[0].id,3) + }else{ + let obj='areaColumns[2].values'; + this.setData({ + [obj]:res + }) + + } + + }).catch(error=>{ + console.log(error) + }) + }, + handleGetOffice(){ + getOfficeList({}).then(res=>{ + this.setData({ + officeColumns:res + }) + }).catch(error=>{ + console.log(error) + }) + }, + handleGetPosition(){ + getPosition().then(res=>{ + this.setData({ + positionColumns:res + }) + this.handleNeedInfo(); + }) + }, + + + + + /** + * 生命周期函数--监听页面加载 + */ + onLoad(options) { + this.handleGetArea('',1); + }, + + /** + * 生命周期函数--监听页面初次渲染完成 + */ + onReady() { + + }, + + /** + * 生命周期函数--监听页面显示 + */ + onShow() { + + }, + + /** + * 生命周期函数--监听页面隐藏 + */ + onHide() { + + }, + + /** + * 生命周期函数--监听页面卸载 + */ + onUnload() { + + }, + + /** + * 页面相关事件处理函数--监听用户下拉动作 + */ + onPullDownRefresh() { + + }, + + /** + * 页面上拉触底事件的处理函数 + */ + onReachBottom() { + + }, + + /** + * 用户点击右上角分享 + */ + // onShareAppMessage() { + + // } +}) \ No newline at end of file diff --git a/case/pages/bankCard/bankCard.json b/case/pages/bankCard/bankCard.json new file mode 100644 index 0000000..49976f1 --- /dev/null +++ b/case/pages/bankCard/bankCard.json @@ -0,0 +1,16 @@ +{ + "usingComponents": { + "navBar":"../../../components/navBar/navBar", + "dialog":"../../../components/dialog/dialog", + "van-popup": "@vant/weapp/popup/index", + "van-icon": "@vant/weapp/icon/index", + "van-picker": "@vant/weapp/picker/index", + "van-field": "@vant/weapp/field/index", + "van-cell": "@vant/weapp/cell/index", + "van-button": "@vant/weapp/button/index", + "van-image": "@vant/weapp/image/index", + "van-uploader": "@vant/weapp/uploader/index", + "van-cell-group": "@vant/weapp/cell-group/index" + }, + "navigationStyle":"custom" +} \ No newline at end of file diff --git a/case/pages/bankCard/bankCard.wxml b/case/pages/bankCard/bankCard.wxml new file mode 100644 index 0000000..8f5dcc4 --- /dev/null +++ b/case/pages/bankCard/bankCard.wxml @@ -0,0 +1,51 @@ + + + + + 请确定开户人姓名与真实姓名一致 + + + + + + + + + + + + + + + + + + + + 签名 + + + 添加签名 + + + + + + + + + + 提交 + + + + + \ No newline at end of file diff --git a/case/pages/bankCard/bankCard.wxss b/case/pages/bankCard/bankCard.wxss new file mode 100644 index 0000000..1902212 --- /dev/null +++ b/case/pages/bankCard/bankCard.wxss @@ -0,0 +1,143 @@ +/* case/pages/bankCard/bankCard.wxss *//* case/pages/improveInfo/improveInfo.wxss */ +/* case/pages/register/register.wxss */ +page{ + overflow: hidden; +} +.page{ + height:calc(100vh - 172rpx); + margin-top: 172rpx; + display: flex; + flex-direction: column; + background-color: #F4F4F4; +} +.content{ + flex:1; + overflow: scroll; + -webkit-overflow-scrolling: touch; +} +.tip{ + height:80rpx; + display: flex; + font-size: 28rpx; + align-items: center; + color:#fff; + padding:0 32rpx; + background: #3881F7; + border-bottom:1px solid #ccc; +} +.next{ + margin: 40rpx 20rpx!important; +} +.prev{ + margin:-20rpx 20rpx 0!important ; +} +.van-field__label{ + white-space: nowrap; +} +.custom-class .van-cell:last-child{ + background-color: red; +} +.van-cell__title{ + white-space: nowrap; + display: flex; + color: #646566; + align-items: center; +} +.van-image{ + margin-top: 10rpx; +} +.van-cell{ + display: flex; + align-items: center; +} +.van-field__label,.van-cell__title{ + position: relative; +} +.signname .name{ + width: auto; + position: relative; +} +.van-field__label::after,.cert .van-cell__title::after{ + content: "*"; + color:red; + position: absolute; + top:12rpx; + right:-14rpx; +} +.signname .name::after{ + content: "*"; + color:red; + position: absolute; + top:4rpx; + right:-14rpx; +} +.cert .van-cell__title::after{ + top:4rpx; +} +.myclass .van-field__label::after{ + content: ""; +} +.upload{ + opacity:0; + position: absolute; + z-index:99; +} +.cert .van-cell{ + overflow: hidden; +} +.van-button{ + border-radius: 10rpx!important; +} +.imgCode{ + width:200rpx; + height:75.6rpx; +} +.custom-class{ + min-height:43.5px !important; +} +.van-field__label{ + color:#333!important; + padding:12rpx 0; + font-size: 15px!important; +} +.van-cell__title{ + color:#333!important; + font-size: 15px!important; +} +.buttonbox{ + position: absolute; + z-index:99; + display: flex; + width:600rpx; + left:50%; + transform: translateX(-50%); + justify-content: space-between; + bottom:50rpx; + } + .van-button--primary{ + background: linear-gradient(90deg, #377FF7 0%, #51AAFF 100%)!important; + border-color:#377FF7!important + } + .signbox{ + border: 0.5px solid #ebedf0; + border-left: none; + border-right: none; + padding:10px 16px; + background-color: #fff; + } + .uploadbox{ + margin-top: 20rpx; + width:300rpx; + height:300rpx; + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + font-size: 28rpx; + color:#666; + border-radius: 10rpx; + background-color: #f7f8fa; + } + .uploadbox .name{ + margin-top: 10rpx; + } \ No newline at end of file diff --git a/case/pages/createCase/createCase.js b/case/pages/createCase/createCase.js new file mode 100644 index 0000000..e7b1552 --- /dev/null +++ b/case/pages/createCase/createCase.js @@ -0,0 +1,1237 @@ +// case/pages/createCase/createCase.js +import {addCase,caseDetail,editCase,getOssSign} from "../../../api/api" +const dayjs = require("../../../utils/dayjs"); +import {throttle} from "../../../utils/util" +import { FileUtil } from '../../../utils/fileutil' +const app=getApp(); +Page({ + /** + * 页面的初始数据 + */ + data: { + active:0, + wordLength:0, + showSaveBtn:true, + delIndex:'', + navName:'创建病例', + showDraft:false, + showUseDraft:false, + isUseDraft:false, + medicalRecordId:'', + dealIndex:0, + showTime:false, + showType:false, + img_host:app.hostConfig().imghost, + showTip:false, + showDel:false, + showAttention:false, + fileList_basic:[], + fileList_abstract:[], + fileList_check:[], + dpmas_list:[{fileList:[],treatTime:''}], + disable_ziliao:false, + disable_record:false, + disable_check:false, + columns:[ + { text: '四次及以上疗程化', value: 1 }, + { text: '早前期(INR≤1.5)', value: 2 } + ], + typeName:'', + case:{ + name:'', + uid:'', + sex:null, + age:'', + admissionTime:'', + caseType:'', + baseImg:'', + abstractStr:'', + abstractImg:'', + dpmas:[{ + 'dpmasImg':'', + 'treatTime':'' + }], + headTime:'', + afterTime:'', + headTb:'', + afterTb:'', + headAlt:'', + afterAlt:'', + headAlb:'', + afterAlb:'', + headAst:'', + afterAst:'', + headInr:'', + afterInr:'', + headDb:'', + afterDb:'', + headIb:'', + afterIb:'', + headPta:'', + afterPta:'', + headIl6:'', + afterIl6:'', + headTnf:'', + afterTnf:'', + checkImg:'' + }, + time_type:'date', + time_str:"hospitalTime", + time_title:'选择入院时间', + minHeight:{minHeight: 100}, + currentDate: new Date().getTime(), + maxDate:new Date().getTime(),//new Date().getTime(), + minDate:new Date('2023-01-01').getTime(), + formatter(type, value) { + if (type === 'year') { + return `${value}年`; + } + if (type === 'month') { + return `${value}月`; + }; + if(type === 'day'){ + return `${value}日`; + } + if(type === 'hour'){ + return `${value}时`; + } + if(type === 'minute'){ + return `${value}分`; + } + return value; + }, + }, + save:throttle(function(){ + this.data.medicalRecordId?this.handleEditCase():this.handleAddCase(); + }), + saveBasic(){ + if(this.validateBasic(true)){ + this.setData({ + active:1 + }) + + } + + !this.data.medicalRecordId && this.saveDraft(); + }, + saveAbstract(){ + if(this.validateZiliao(true)){ + this.setData({ + active:2 + }) + } + !this.data.medicalRecordId && this.saveDraft(); + }, + saveRecord(){ + if(this.validateRecord(true)){ + this.setData({ + active:3 + }) + } + !this.data.medicalRecordId && this.saveDraft(); + }, + goBack:throttle(function(){ + let {medicalRecordId}=this.data; + if(!medicalRecordId && this.isHasValue()){ + this.setData({ + showDraft:true + }) + }else{ + wx.navigateBack() + } + + }), + isHasValue(){ + let {name,uid,sex,age,admissionTime,caseType,abstractStr,headTime,afterTime,headTb,afterTb,headAlt,afterAlt,headAlb,afterAlb,headAst,afterAst,headInr,afterInr,headDb,afterDb,headIb,afterIb,headPta,afterPta,headIl6,afterIl6,headTnf,afterTnf}=this.data.case; + let {fileList_basic,fileList_abstract,fileList_check,dpmas_list}=this.data; + let hasTime=dpmas_list.some(item=>{ return item.fileList.length>0}); + + let hasImg=dpmas_list.some(item=>{ return item.treatTime!=''}); + + if(name || uid || sex || age || admissionTime || caseType || abstractStr || headTime || afterTime || headTb || afterTb || headAlt || afterAlt || headAlb || afterAlb || headAst || afterAst || headInr || afterInr || headDb || afterDb || headIb || afterIb || headPta || afterPta || headIl6 || afterIl6 || headTnf || afterTnf || fileList_basic.length>0 || fileList_abstract.length>0 || fileList_check.length>0 || hasTime || hasImg){ + return true + }else{ + return false + } + }, + handleAddCase(){ + let {fileList_basic,fileList_abstract,fileList_check,dpmas_list}=this.data; + let baseImg=''; + let abstractImg=''; + let checkImg=''; + if(this.validateBasic(true) && this.validateZiliao(true) && this.validateRecord(true) && this.validateCheck(true)){ + fileList_basic.forEach(item=>{ + if(baseImg){ + baseImg+="," +item.url + }else{ + baseImg+=item.url; + } + }) + fileList_abstract.forEach(item=>{ + if(abstractImg){ + abstractImg+="," +item.url + }else{ + abstractImg+=item.url; + } + }) + fileList_check.forEach(item=>{ + if(checkImg){ + checkImg+="," +item.url + }else{ + checkImg+=item.url; + } + }); + dpmas_list.forEach((item,index)=>{ + let imgStr=''; + let itemArr=item.fileList; + itemArr.forEach((cell)=>{ + if(imgStr){ + imgStr+="," +cell.url + }else{ + imgStr+=cell.url; + } + }) + let cur='case.dpmas['+index+'].dpmasImg' + this.setData({ + [cur]:imgStr + }) + }) + this.setData({ + 'case.baseImg':baseImg, + 'case.abstractImg':abstractImg, + 'case.checkImg':checkImg + }) + let caseObj=this.data.case; + let {admissionTime,headTime,afterTime}=this.data.case; + Object.assign(caseObj,{ + admissionTime:dayjs(admissionTime).format('YYYY-MM-DD HH:mm:ss'), + headTime:dayjs(headTime).format('YYYY-MM-DD HH:mm:ss'), + afterTime:dayjs(afterTime).format('YYYY-MM-DD HH:mm:ss') + }) + + addCase({...caseObj}).then(res=>{ + wx.showToast({ + title: '病例创建成功', + icon:'none' + }) + // if(this.data.isUseDraft){ + wx.setStorageSync('caseDraft','') + // } + wx.navigateBack(); + }) + } + + }, + handleEditCase(){ + let {fileList_basic,fileList_abstract,fileList_check,dpmas_list,medicalRecordId}=this.data; + let baseImg=''; + let abstractImg=''; + let checkImg=''; + if(this.validateBasic(true) && this.validateZiliao(true) && this.validateRecord(true) && this.validateCheck(true)){ + fileList_basic.forEach(item=>{ + if(baseImg){ + baseImg+="," +item.url + }else{ + baseImg+=item.url; + } + }) + fileList_abstract.forEach(item=>{ + if(abstractImg){ + abstractImg+="," +item.url + }else{ + abstractImg+=item.url; + } + }) + fileList_check.forEach(item=>{ + if(checkImg){ + checkImg+="," +item.url + }else{ + checkImg+=item.url; + } + }); + dpmas_list.forEach((item,index)=>{ + let imgStr=''; + let itemArr=item.fileList; + itemArr.forEach((cell)=>{ + if(imgStr){ + imgStr+="," +cell.url + }else{ + imgStr+=cell.url; + } + }) + let cur='case.dpmas['+index+'].dpmasImg' + this.setData({ + [cur]:imgStr + }) + }) + this.setData({ + 'case.baseImg':baseImg, + 'case.abstractImg':abstractImg, + 'case.checkImg':checkImg + }) + let {admissionTime,headTime,afterTime}=this.data.case; + this.setData({ + 'case.id':medicalRecordId + }) + let caseObj=this.data.case; + + + Object.assign(caseObj,{ + admissionTime:dayjs(admissionTime).format('YYYY-MM-DD HH:mm:ss'), + headTime:dayjs(headTime).format('YYYY-MM-DD HH:mm:ss'), + afterTime:dayjs(afterTime).format('YYYY-MM-DD HH:mm:ss') + }) + editCase({...caseObj}).then(res=>{ + wx.showToast({ + title: '病例修改成功', + icon:'none' + }) + wx.navigateBack(); + }) + } + + }, + initAllvalue(res){ + let caseObj=this.data.case; + for (const key in caseObj) { + if(key=="admissionTime"){ + this.setData({ + ['case.'+key]:res[key]?dayjs(res[key]).format('YYYY-MM-DD'):'' + }) + }else if(key=='caseType'){ + let {columns}=this.data; + for (let i = 0; i < columns.length; i++) { + if(columns[i].value==res[key]){ + this.setData({ + ['case.'+key]:res[key], + 'typeName': columns[i].text + }) + break; + } + + } + }else if(key=='baseImg'){ + if(res[key]){ + let imgList=res[key].split(','); + this.setData({ + ['case.'+key]:res[key], + fileList_basic:imgList.map(item=>{ + return {url:item} + }) + }) + } + + }else if(key=='abstractImg'){ + if(res[key]){ + let imgList=res[key].split(','); + this.setData({ + ['case.'+key]:res[key], + fileList_abstract:imgList.map(item=>{ + return {url:item} + }) + }) + } + }else if(key=='checkImg'){ + if(res[key]){ + let imgList=res[key].split(','); + console.log(imgList) + this.setData({ + ['case.'+key]:res[key], + fileList_check:imgList.map(item=>{ + return {url:item} + }) + }) + } + + }else if(key=='dpmas'){ + let arr=res[key]; + this.setData({ + ['case.'+key]:arr + }) + arr.forEach((item,index)=>{ + if(item.dpmasImg){ + let imgList=item.dpmasImg.split(','); + + let objFile="dpmas_list["+index+"].fileList"; + let objTime="dpmas_list["+index+"].treatTime" + this.setData({ + [objFile]:imgList.map(item1=>{ + return {url:item1} + }), + [objTime]:item.treatTime?dayjs(item.treatTime).format('YYYY-MM-DD HH'):'' + }) + } + + }) + + }else if(key=='headTime' || key=='afterTime'){ + this.setData({ + ['case.'+key]:res[key]?dayjs(res[key]).format('YYYY-MM-DD HH'):'' + }) + }else if(key=='abstractStr'){ + this.setData({ + wordLength:res[key].length, + ['case.'+key]:res[key] + }) + + }else{ + this.setData({ + ['case.'+key]:res[key] + }) + } + + } + }, + handleCaseDetail(){ + let { medicalRecordId}=this.data; + caseDetail(medicalRecordId).then(res=>{ + this.initAllvalue(res) + }) + }, + onBeforeChange(event){ + let {index,callback}=event.detail; + if(index==0){ + this.setData({ + active:0 + }) + callback(true); + }else if(index==1){ + if(this.validateBasic()){ + callback(true); + this.setData({ + active:1 + }) + }else{ + this.setData({ + showAttention:true + }) + callback(false) + } + }else if(index==2){ + if(this.validateBasic(false) && this.validateZiliao(false)){ + callback(true); + this.setData({ + active:2 + }) + }else{ + this.setData({ + showAttention:true + }) + callback(false) + } + }else if(index==3){ + if(this.validateBasic(false) && this.validateZiliao(false) && this.validateRecord(false)){ + callback(true); + this.setData({ + active:3 + }) + }else{ + this.setData({ + showAttention:true + }) + callback(false) + } + } + }, + confirmDelRecord(event){ + let {index}=event.currentTarget.dataset; + this.setData({ + showDel:true, + delIndex:index + }) + }, + delRecord(){ + let {dpmas}=this.data.case; + let {dpmas_list}=this.data; + let index=this.data.delIndex; + dpmas.splice(index,1); + dpmas_list.splice(index,1); + this.setData({ + 'case.dpmas':dpmas, + dpmas_list:dpmas_list + }) + + }, + addRecord(){ + this.setData({ + 'case.dpmas':this.data.case.dpmas.concat([{ + 'dpmasImg':'', + 'treatTime':'' + }]), + 'dpmas_list':this.data.dpmas_list.concat({fileList:[]}) + + }) + }, + onClickDisabled(event){ + let {index}=event.detail; + if(index==1){ + this.validateBasic(false); + }else if(index==2){ + this.validateBasic(); + this.validateZiliao(); + }else if(index==3){ + this.validateBasic(); + this.validateZiliao(); + this.validateRecord(); + } + + }, + validateBasic(flag){ + let {name,uid,sex,age,admissionTime,caseType}=this.data.case; + let {fileList_basic}=this.data; + if(!name){ + flag && wx.showToast({ + title: '患者姓名不能为空', + icon:"none" + }) + return false + } + if(!uid){ + flag && wx.showToast({ + title: '患者ID号不能为空', + icon:"none" + }) + return false + } + if(!sex){ + console.log(flag) + flag && wx.showToast({ + title: '性别不能为空', + icon:"none" + }) + return false + } + if(!age){ + flag && wx.showToast({ + title: '年龄不能为空', + icon:"none" + }) + return false + } + if(!admissionTime){ + flag && wx.showToast({ + title: '入院时间不能为空', + icon:"none" + }) + return false + } + if(!caseType){ + flag && wx.showToast({ + title: '病例类型不能为空', + icon:"none" + }) + return false + } + if(fileList_basic.length==0){ + flag && wx.showToast({ + title: '请上传病案照片', + icon:"none" + }) + return false + } + return true + }, + validateZiliao(flag){ + let {abstractStr}=this.data.case; + let {fileList_abstract}=this.data; + if( !abstractStr && fileList_abstract.length==0){ + flag && wx.showToast({ + title: '病历摘要和病历图片至少要提交一项', + icon:"none" + }) + return false + } + return true + }, + validateRecord(flag){ + let {dpmas,admissionTime,caseType}=this.data.case; + let {dpmas_list}=this.data; + + if(caseType==1 && dpmas.length<4){ + flag && wx.showToast({ + title: 'DPMAS治疗不少于4次', + icon:'none' + }) + return false; + }; + if(caseType==2 && dpmas.length<1){ + flag && wx.showToast({ + title: 'DPMAS治疗不少于1次', + icon:'none' + }) + return false; + } + for (let i = 0; i < dpmas.length; i++) { + if(!dpmas[i].treatTime){ + flag && wx.showToast({ + title: '第'+(i+1)+'次治疗时间不能为空', + icon:'none' + }) + return false; + } + }; + let time1=dayjs(admissionTime).format('YYYY-MM-DD'); + let time2=dayjs(dpmas[0].treatTime).format('YYYY-MM-DD'); + + if(dayjs(time1).diff(dayjs(time2))>0){ + flag && wx.showToast({ + title: '患者基本信息入院时间应该早于第一次治疗时间', + icon:'none' + }) + return false; + } + for (let i = 0; i < dpmas.length-1; i++) { + if(dayjs(dpmas[i].treatTime).diff(dayjs(dpmas[i+1].treatTime))>0){ + flag && wx.showToast({ + title: '第'+(i+1)+'次治疗时间应该早于第'+(i+2)+'次治疗时间', + icon:'none' + }) + return false; + } + + } + for (let index = 0; index =0 && dayjs(headTime_new).diff(dayjs(firstTime))<=0)){ + flag && wx.showToast({ + title: '治疗前检测时间应该在入院时间与第一次治疗时间之间', + icon:"none" + }) + return false + } + if(dpmas.length<2){ + if(!(dayjs(afterTime_new).diff(dayjs(firstTime))>=0)){ + flag && wx.showToast({ + title: '治疗后检测时间应该在第一次之后', + icon:"none" + }) + return false + } + + }else{ + let secondTime=dayjs(dpmas[1].treatTime).format('YYYY-MM-DD') + if(!(dayjs(afterTime_new).diff(dayjs(firstTime))>=0 && dayjs(afterTime_new).diff(dayjs(secondTime))<=0)){ + flag && wx.showToast({ + title: '治疗后检测时间应该在第一次与第二次治疗时间之间', + icon:"none" + }) + return false + }; + } + + + }; + if(caseType==1){ + if(!(dayjs(headTime_new).diff(dayjs(admissionTime_new))>=0 && dayjs(headTime_new).diff(dayjs(firstTime))<=0)){ + flag && wx.showToast({ + title: '治疗前检测时间应该在入院时间与第一次治疗时间之间', + icon:"none" + }) + return false + } + if(!(dayjs(afterTime_new).diff(dayjs(lastTime)>=0))){ + flag && wx.showToast({ + title: '治疗后检测时间应该最后一次治疗时间之后', + icon:"none" + }) + return false + } + } + if(!headTb || !afterTb){ + flag && wx.showToast({ + title: '总胆红素未填写完整', + icon:"none" + }) + return false + }; + if(!headAlt || !afterAlt){ + flag && wx.showToast({ + title: '谷丙转氨酶未填写完整', + icon:"none" + }) + return false + }; + if(!headInr || !afterInr){ + flag && wx.showToast({ + title: '国际标准化比值未填写完整', + icon:"none" + }) + return false + } + // if(!headDb || !afterDb){ + // flag && wx.showToast({ + // title: '直接胆红素未填写完整', + // icon:"none" + // }) + // return false + // } + // if(!headIb || !afterIb){ + // flag && wx.showToast({ + // title: '间接胆红素未填写完整', + // icon:"none" + // }) + // return false + // } + // if(!headPta || !afterPta){ + // flag && wx.showToast({ + // title: '凝血酶原活动度未填写完整', + // icon:"none" + // }) + // return false + // } + // if(!headIl6 || !afterIl6){ + // flag && wx.showToast({ + // title: '白介素6未填写完整', + // icon:"none" + // }) + // return false + // } + // if(!headTnf || !afterTnf){ + // wx.showToast({ + // title: '肿瘤坏死因子', + // icon:"none" + // }) + // return false + // } + if(fileList_check.length==0){ + flag && wx.showToast({ + title: '请上传报告单', + icon:"none" + }) + return false + } + return true; + }, + onChangeAbstract(event){ + let {value}=event.detail + this.setData({ + wordLength:value.length>500?500:value.length, + 'case.abstractStr':value.length>500?value.substring(0,500):value + }) + }, + openType(){ + if(!this.data.showSaveBtn)return; + this.setData({ + showType:true, + }) + }, + onCancelType(){ + this.setData({ + showType:false, + }) + }, + onConfirmType(event){ + let {value,text}=event.detail.value; + this.setData({ + showType:false, + 'typeName':text, + 'case.caseType':value + }) + }, + saveDraft(){ + let {fileList_basic,fileList_abstract,fileList_check,dpmas_list}=this.data; + let baseImg=''; + let abstractImg=''; + let checkImg=''; + fileList_basic.forEach(item=>{ + if(baseImg){ + baseImg+="," +item.url + }else{ + baseImg+=item.url; + } + }) + fileList_abstract.forEach(item=>{ + if(abstractImg){ + abstractImg+="," +item.url + }else{ + abstractImg+=item.url; + } + }) + fileList_check.forEach(item=>{ + if(checkImg){ + checkImg+="," +item.url + }else{ + checkImg+=item.url; + } + }); + dpmas_list.forEach((item,index)=>{ + let imgStr=''; + let itemArr=item.fileList; + itemArr.forEach((cell)=>{ + if(imgStr){ + imgStr+="," +cell.url + }else{ + imgStr+=cell.url; + } + }) + let cur='case.dpmas['+index+'].dpmasImg' + this.setData({ + [cur]:imgStr + }) + }) + this.setData({ + 'case.baseImg':baseImg, + 'case.abstractImg':abstractImg, + 'case.checkImg':checkImg + }) + let caseObj=this.data.case; + let {admissionTime,headTime,afterTime}=this.data.case; + Object.assign(caseObj,{ + admissionTime:admissionTime?dayjs(admissionTime).format('YYYY-MM-DD HH:mm:ss'):'', + headTime:headTime?dayjs(headTime).format('YYYY-MM-DD HH:mm:ss'):'', + afterTime:afterTime?dayjs(afterTime).format('YYYY-MM-DD HH:mm:ss'):'' + }) + wx.setStorageSync("draftTime",dayjs(new Date().getTime()).format('YYYY-MM-DD')); + wx.setStorageSync('caseDraft',JSON.stringify({...caseObj})) + + }, + onConfirmUseDraft(){ + let caseDraft=wx.getStorageSync('caseDraft'); + if(caseDraft){ + this.initAllvalue(JSON.parse(caseDraft)) + } + this.setData({ + showUseDraft:false, + isUseDraft:true + }) + + }, + onCancelUseDraft(){ + this.setData({ + showUseDraft:false + }); + + }, + onConfirmDraft(){ + this.saveDraft() + this.setData({ + showDraft:false + }); + + wx.navigateBack() + }, + onCancelDraft(){ + this.setData({ + showDraft:false + }) + wx.navigateBack() + }, + onConfirmAttention(){ + this.setData({ + showAttention:false + }) + }, + onConfirmTip(){ + this.setData({ + showTip:false + }) + }, + onConfirmDel(){ + this.setData({ + showDel:false + }) + this.delRecord(); + }, + onCancelDel(){ + this.setData({ + showDel:false + }) + }, + cancelDate(){ + this.setData({ + showTime:false, + }); + }, + onInput(event) { + this.setData({ + currentDate: event.detail, + }); + }, + confirmDate(event) { + let {time_str,dealIndex}=this.data; + let key=''; + let time=''; + if(time_str=="hospitalTime"){ + key='admissionTime' + time=dayjs(event.detail).format('YYYY-MM-DD') + }else if(time_str=="dealTime"){ + key="dealTime" + let year=dayjs(event.detail).format('YYYY') + if(year!=2024){ + this.setData({ + showTip:true, + showTime:false + }) + return false + } + time=dayjs(event.detail).format('YYYY-MM-DD HH:mm'); + + }else if(time_str=="headTime"){ + key="headTime" + time=dayjs(event.detail).format('YYYY-MM-DD HH') + }else if(time_str=="afterTime"){ + key="afterTime"; + time=dayjs(event.detail).format('YYYY-MM-DD HH') + } + if(key=="dealTime"){ + const obj = "case.dpmas[" + dealIndex + "].treatTime" + const oterobj="dpmas_list[" + dealIndex + "].treatTime" + this.setData({ + showTime:false, + currentDate: event.detail, + [oterobj]:dayjs(time).format('YYYY-MM-DD HH'), + [obj]:time+ ":00" + }) + console.log(this.data.case.dpmas) + }else{ + this.setData({ + currentDate: event.detail, + showTime:false, + ['case.'+key]: time, + }); + } + + + }, + handleIpt(e){ + let key = e.target.dataset.id; + let type=e.target.dataset.type; + if(key=='name'){ + let value=e.detail.value; + let newVal=value.toUpperCase(); + this.setData({ + ['case.'+key]:newVal + }) + }else{ + let iptValue=''; + let tempValue=e.detail.value; + if( type=='number'){ + console.log(this.countDecimals(tempValue)); + iptValue=this.countDecimals(tempValue)>3?Number(tempValue).toFixed(3):tempValue + }else{ + iptValue=tempValue + } + this.setData({ + ['case.'+key]:key=="age"?Number(e.detail.value):iptValue + }) + } + + + }, + countDecimals(num) { + // 将数值转换为字符串 + const str = String(num); + // 匹配小数点及其后面的数字 + const match = str.match(/\.(\d+)/); + // 返回小数位数 + return match ? match[1].length : 0; + }, + openTime(){ + if(!this.data.showSaveBtn)return; + const caseInfo=this.data.case; + this.setData({ + showTime:true, + currentDate:caseInfo.admissionTime?new Date(caseInfo.admissionTime).getTime():new Date().getTime(), + time_type:'date', + time_str:"hospitalTime", + time_title:'选择入院时间', + }) + }, + openDealTime(event){ + if(!this.data.showSaveBtn)return; + const caseInfo=this.data.case; + let {index}=event.currentTarget.dataset; + console.log( caseInfo.dpmas ) + console.log(caseInfo.dpmas[index].treatTime) + this.setData({ + showTime:true, + currentDate:caseInfo.dpmas[index].treatTime?new Date(dayjs(caseInfo.dpmas[index].treatTime).format("YYYY-MM-DD HH:mm:ss")).getTime():new Date().getTime(), + dealIndex:index, + time_type:'datetime', + time_str:"dealTime", + time_title:'选择治疗时间', + }) + }, + openHeadTime(){ + if(!this.data.showSaveBtn)return; + const caseInfo=this.data.case; + this.setData({ + showTime:true, + currentDate:caseInfo.headTime?new Date(dayjs(caseInfo.headTime).format("YYYY-MM-DD HH:mm:ss")).getTime():new Date().getTime(), + time_type:'datetime', + time_str:"headTime", + time_title:'选择治疗前检测时间', + }) + }, + openAfterTime(){ + if(!this.data.showSaveBtn)return; + const caseInfo=this.data.case; + this.setData({ + showTime:true, + currentDate:caseInfo.afterTime?new Date(dayjs(caseInfo.afterTime).format("YYYY-MM-DD HH:mm:ss")).getTime():new Date().getTime(), + time_type:'datetime', + time_str:"afterTime", + time_title:'选择治疗后检测时间', + }) + }, + onChange(event) { + this.setData({ + 'case.sex':Number(event.detail), + }); + }, + + deleteImg(event){ + const { fileList_basic,fileList_abstract,fileList_check,dpmas_list} = this.data; + const {name,index} = event.detail; + + if(name=='basic'){ + fileList_basic.splice(index,1); + this.setData({ + fileList_basic:fileList_basic + }) + }else if(name=="abstract"){ + fileList_abstract.splice(index,1); + this.setData({ + fileList_abstract:fileList_abstract + }) + }else if(name=="check"){ + fileList_check.splice(index,1); + this.setData({ + fileList_check:fileList_check + }) + }else if(name.indexOf("record")!=-1){ + let recordIndex=Number(name.split("record")[1]); + dpmas_list[recordIndex].fileList.splice(index,1); + let obj="dpmas_list["+ recordIndex +"].fileList" + this.setData({ + [obj]:dpmas_list[recordIndex].fileList + }) + } + }, + uploadImg(file,name){ + let THIS = this; + return new Promise((resolve, reject) => { + getOssSign(1).then(resdata=>{ + let { accessid, dir,policy,signature,host} = resdata; + let imgUrl = file; + //let index = imgUrl.lastIndexOf("/"); + let filename = FileUtil.UUID()+'.'+imgUrl.match(/\.(\w+)$/)[1]; + + //imgUrl.substring(index + 1, imgUrl.length); + + wx.uploadFile({ + url: host, // 仅为示例,非真实的接口地址 + filePath: file, + name: 'file', + formData: { + OSSAccessKeyId: accessid, + policy, + key: dir + filename, + signature + }, + success(res) { + + if (res.statusCode === 204) { + resolve(true) + let url = host + '/' + dir + filename; + // 上传完成需要更新 fileList + console.log('11111111111111'); + console.log(url); + if(name=='basic'){ + const { fileList_basic} = THIS.data; + fileList_basic.push({ url: url }); + THIS.setData({ fileList_basic }); + }else if(name=='abstract'){ + const { fileList_abstract} = THIS.data; + fileList_abstract.push({ url: url }); + THIS.setData({ fileList_abstract }); + }else if(name=='check'){ + const { fileList_check} = THIS.data; + fileList_check.push({ url: url }); + THIS.setData({ fileList_check}); + }else if(name.indexOf("record")!=-1){ + let recordIndex=Number(name.split("record")[1]); + let obj="dpmas_list[" +recordIndex +"].fileList" + THIS.setData({ + [obj]:THIS.data.dpmas_list[recordIndex].fileList.concat([{ url: url }]) + }) + } + + }else{ + reject(false) + } + }, + fail: err => { + console.log(err); + } + }); + }) + + + }) + + }, + afterRead(event) { + + console.log( event.detail) + const { file,name} = event.detail; + console.log(file) + wx.showLoading({ + title: '图片上传中', + mask: true + }) + var promiseFun = []; + for (let i = 0; i < file.length; i++) { + var cur_file = file[i].url; + promiseFun.push( + this.uploadImg(cur_file,name) + ) + }; + Promise.all(promiseFun).then((res) => { + wx.showToast({ + title: '图片上传成功', + icon: "none" + }) + console.log(res.length+"张上传成功"); + wx.hideLoading(); + + }).catch(error=>{ + wx.showToast({ + title: '图片上传失败', + icon: "none" + }) + console.log(error) + wx.hideLoading(); + }); + + // 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式 + // wx.uploadFile({ + // url: 'https://example.weixin.qq.com/upload', // 仅为示例,非真实的接口地址 + // filePath: file.url, + // name: 'file', + // formData: { user: 'test' }, + // success(res) { + // // 上传完成需要更新 fileList + // const { fileList = [] } = this.data; + // fileList.push({ ...file, url: res.data }); + // this.setData({ fileList }); + // }, + // }); + }, + /** + * 生命周期函数--监听页面加载 + */ + onLoad(options) { + if(options.medicalRecordId){ + if(options.status==0 || options.status==1){ + this.setData({ + medicalRecordId:options.medicalRecordId, + navName:'查看病例', + showSaveBtn:false, + }) + }else{ + this.setData({ + showSaveBtn:true, + medicalRecordId:options.medicalRecordId, + navName:'病例详情' + }) + } + this.handleCaseDetail(); + }else{ + let caseDraft=wx.getStorageSync('caseDraft'); + this.setData({ + showSaveBtn:true, + }) + if(caseDraft){ + this.setData({ + showUseDraft:true + }) + } + } + }, + + /** + * 生命周期函数--监听页面初次渲染完成 + */ + onReady() { + + }, + + /** + * 生命周期函数--监听页面显示 + */ + onShow() { + + }, + + /** + * 生命周期函数--监听页面隐藏 + */ + onHide() { + + }, + + /** + * 生命周期函数--监听页面卸载 + */ + onUnload() { + + }, + + /** + * 页面相关事件处理函数--监听用户下拉动作 + */ + onPullDownRefresh() { + + }, + + /** + * 页面上拉触底事件的处理函数 + */ + onReachBottom() { + + }, + + /** + * 用户点击右上角分享 + */ + // onShareAppMessage() { + + // } +}) \ No newline at end of file diff --git a/case/pages/createCase/createCase.json b/case/pages/createCase/createCase.json new file mode 100644 index 0000000..1ad8a33 --- /dev/null +++ b/case/pages/createCase/createCase.json @@ -0,0 +1,17 @@ +{ + "usingComponents": { + "navBar":"../../../components/navBar/navBar", + "van-tab": "@vant/weapp/tab/index", + "van-tabs": "@vant/weapp/tabs/index", + "van-picker": "@vant/weapp/picker/index", + "van-icon": "@vant/weapp/icon/index", + "van-uploader": "@vant/weapp/uploader/index", + "van-popup": "@vant/weapp/popup/index", + "dialog":"../../../components/dialog/dialog", + "van-datetime-picker": "@vant/weapp/datetime-picker/index", + "van-radio": "@vant/weapp/radio/index", + "van-field": "@vant/weapp/field/index", + "van-radio-group": "@vant/weapp/radio-group/index" + }, + "navigationStyle":"custom" +} \ No newline at end of file diff --git a/case/pages/createCase/createCase.wxml b/case/pages/createCase/createCase.wxml new file mode 100644 index 0000000..fbf5b83 --- /dev/null +++ b/case/pages/createCase/createCase.wxml @@ -0,0 +1,454 @@ + + + + + {{navName}} + + + + + + + + + + 患者姓名(首字母大写)* + + + + + + + + 患者ID号* + + + + + + + + 性别* + + + + 男 + 女 + + + + + + 年龄* + + + + + + + + 入院时间* + + + + + + + + + 病例类型* + + + + + + + + + 病案照片(可上传1-6张)* + + + + + + + 注意:“病案首页”需包含患者住院号或姓名 + + 下一步 + + + + + + + + + + + 病历摘要 + + + {{wordLength}}/500 + + + + + + 病历摘要图片(可上传1-6张) + + + + + + + + 下一步 + + 注意:病历摘要,文字填写或者上传图片,二选一 + + + + + + + + + + 第{{index+1}}次治疗 + + + + + + 治疗时间* + + + + h + + + + + + 治疗凭证,如医嘱、收费明细、处方单(可上传1-3张)* + + + + + + + + + 增加记录 + + + + + 注意:“DPMAS治疗凭证”照片需包含患者住院号或姓名 + 1.4次及以上疗程化病例,每次治疗对应一张凭证照片; + 2.早前期(INR≤1.5)的病例,上传第一次DPMAS治疗前最新的“INR检验报告单” + 3.早前期(INR≤1.5)的病例,DPMAS治疗时间不能晚于INR出报告时间24小时。 + + + + 下一步 + + + + + + + (早前期为首次治疗前后、4次及以上为疗程化治疗前后) + + + 治疗前检测时间* + + + + h + + + + + + 治疗后检测时间* + + + + h + + + + + + 总胆红素(TB-umol/L)* + + + 治疗前 + + + + + + 治疗后 + + + + + + + + + + 谷丙转氨酶(ALT-U/L)* + + + 治疗前 + + + + + + 治疗后 + + + + + + + + 谷草转氨酶(AST-U/L)* + + + 治疗前 + + + + + + 治疗后 + + + + + + + + 国际标准化比值(INR)* + + + 治疗前 + + + + + + 治疗后 + + + + + + + + 白蛋白(ALB-g/L) + + + 治疗前 + + + + + + 治疗后 + + + + + + + + 凝血酶原活动度(PTA-%) + + + 治疗前 + + + + + + 治疗后 + + + + + + + + + + + 白介素6(IL-6-ng/L) + + + 治疗前 + + + + + + 治疗后 + + + + + + + + 肿瘤坏死因子α(TNF-α-μg/L) + + + 治疗前 + + + + + + 治疗后 + + + + + + + + 检查报告单(可上传1-6张)* + + 至少包含国际标准化比值 (INR)治疗前结果报告 + + + + + + 提交 + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/case/pages/createCase/createCase.wxss b/case/pages/createCase/createCase.wxss new file mode 100644 index 0000000..e5dc9f0 --- /dev/null +++ b/case/pages/createCase/createCase.wxss @@ -0,0 +1,351 @@ +/* case/pages/createCase/createCase.wxss */ +page{ + overflow: hidden; +} +.page{ + overflow: hidden; + background: #F7F9F9; + flex:1; + display: flex; + flex-direction: column; + margin-top: 172rpx; +} +.van-tab__pane{ + flex:1; + } + .tabbox{ + flex:1; + width:100%; + height:100%; + } + + .van-tabs{ + width:100%; + } + +.van-tabs__line{ + width: 40rpx!important; + height: 8rpx!important; + background: linear-gradient(90deg, #377FF7 0%, #51AAFF 100%); +} +.van-tabs__wrap{ + padding-bottom: 10rpx; + border-bottom: 1rpx solid #E3E4E5; +} +.basic{ + position: relative; + height:calc( 100vh - 172rpx - 100rpx); + overflow-y: scroll; + margin-bottom: 120rpx; + -webkit-overflow-scrolling: touch; +} +.basic.active{ + margin-bottom:0rpx; +} +.basic .row{ + margin:0 32rpx; + padding:32rpx 0; + display: flex; + border-bottom: 1rpx solid rgba(0,0,0,0.1); + justify-content: space-between; +} +.basic .row:last-child{ + border-bottom: none; +} +.basic .row .ipt{ + text-align: right; +} +.placeholder{ + font-size: 32rpx; + font-weight: 400; + color: rgba(0,0,0,0.25); +} +.basic .row .left{ + display: flex; + align-items: center; + white-space: nowrap; +} +.basic .row .right{ + display: flex; + align-items: center; +} +.basic .row .left .red{ + margin-top: 8rpx; + margin-left: 6rpx; + color: #FF4D4F; +} +.righticon{ + margin-top: 4rpx; +} +.uploadbox{ +margin-top: 24rpx; +} +.basic .tip{ + line-height: 44rpx; + margin:0 32rpx; + padding:24rpx 0; + font-size: 28rpx; + font-weight: 400; + color: rgba(0,0,0,0.45); +} +.recordcon{ + background-color: #fff; + padding-bottom: 28rpx; +} +.van-cell { + padding:10px 0 !important; +} +.van-field__word-limit{ + color: rgba(0,0,0,0.25)!important; +} + +.record .title{ + background: #F7F9F9; + padding:0 32rpx; + height: 92rpx; + font-size: 32rpx; + font-weight: 500; +color: rgba(0,0,0,0.85); + display: flex; + align-items: center; + justify-content: space-between; +} +.titlename{ + display: flex; + align-items: center; +} +.record .bar{ + margin-right: 12rpx; + width: 8rpx; +height: 32rpx; +background: linear-gradient(90deg, #377FF7 0%, #51AAFF 100%); +border-radius: 4rpx; +} +.basic .record .row{ + border-bottom: 1rpx solid rgba(0,0,0,0.1); +} +.row .time{ + display: flex; + align-items: center; + font-size: 32rpx; + font-weight: 400; + color: rgba(0,0,0,0.85); +} +.addrecord{ + width:100%; + + + display:flex; + align-items: center; + font-size: 32rpx; + text-align: center; + color: #3881F7; + justify-content: center; +} +.recordText{ + margin-left: 10rpx; +} +.savebtn{ + display: flex; + justify-content: center; + align-items: center; + font-size: 32rpx; +font-weight: 500; +color: #FFFFFF; +height: 88rpx; +background: linear-gradient(90deg, #377FF7 0%, #51AAFF 100%); +border-radius: 8rpx; +} +.bottom{ + left:32rpx; + right:32rpx; + position: fixed; + bottom:20rpx; +} +.record .left{ + color: rgba(0,0,0,0.65); +} +.message{ + height: 72rpx; + display: flex; + align-items: center; + justify-content: center; + background: #3881F7; + font-size: 28rpx; +padding:0 32rpx; +font-weight: 400; +color: #FFFFFF; +} +.rederror{ + font-size: 28rpx; + font-weight: 400; + color: #FF4D4F; +} +/* .van-uploader__wrapper,.van-uploader__preview-image,.van-uploader__preview{ + border-radius: 10rpx; + overflow: hidden; +} */ +.basiccon,.ziliao{ + background-color: #fff; +} +.ziliao{ + display: block!important; +} +.ziliao.notshowArea{ + display: none!important; + } +.van-tabs__nav{ + width:800rpx; +} +.van-tab{ + padding:0 30rpx!important; + flex:none!important; + font-size:30rpx!important +} +/* .van-ellipsis{ + overflow: auto; +} */ +.van-tabs__scroll{ + width:750rpx; + overflow-x: scroll; +} +.van-tab--active{ + font-weight: 500; + color: rgba(0,0,0,0.85) +} +.table{ + margin:24rpx 32rpx 0; + overflow: hidden; + border-radius: 10rpx; + border: 1rpx solid rgba(0,0,0,0.15) +} +.table .row{ + border-bottom-style: dashed; + margin:0 24rpx; +} +.t_title{ + display: flex; + align-items: center; + + padding:0 24rpx; + height: 80rpx; + font-size: 32rpx; +font-weight: 500; +color: #3881F7; + background: #EBF3FF; +} +.table .red{ + margin-top: 8rpx; + margin-left: 6rpx; + color: #FF4D4F; +} +.btnbox{ + position: fixed; + bottom:0; + z-index:9; + height: 120rpx; + left:32rpx; + right:32rpx; +} +.btnbox .btn{ + width:100%; + display: flex; + align-items: center; + justify-content: center; + font-size: 32rpx; + font-weight: 500; + color: #FFFFFF; + height: 88rpx; + background: linear-gradient(90deg, #377FF7 0%, #51AAFF 100%); + border-radius: 8rpx; +} +.van-tab--disabled{ + color:#646566!important; +} +/* components/navBar.wxss */ +.ui-navigatorbar { + position: fixed; + z-index:99; + top: 0; + width: 750rpx; + height: 172rpx; + background: #F2F2F2; + backdrop-filter: blur(20px); + border-bottom: none; + } + + .ui-navigatorbar-back { + position: absolute; + padding-left:40rpx; + padding-right:40rpx; + + left: 0rpx; + font-size: 40rpx; + bottom: 20rpx; + } + + .ui-title { + position: absolute; + width: 350rpx; + height: 88rpx; + line-height: 56rpx; + font-size: 36rpx; + white-space: nowrap; + color: #000000; + bottom: 0; + left: 200rpx; + display: flex; + justify-content: center; + align-items: center; + }.van-uploader__preview-delete, .van-uploader__preview-delete:after{ + + width: 18px!important; + height: 18px!important; + } + .van-uploader__preview-delete-icon{ + font-size:18px!important; + width: 18px; + height: 18px; + + } + .van-picker-column__item--selected,.van-picker__confirm{ + color: #3881F7!important; + } + .textArea{ + margin-top: 20rpx; + width:100%; + max-height: 500rpx!important; + font-size: 15px; + min-height:50rpx!important; + padding-bottom: 30px; + display: block!important; + } + + .word{ + white-space: pre-wrap; + line-height:48rpx; + position: absolute; + bottom:20rpx; + color: rgba(0,0,0,0.25); + right:10rpx; + } + .van-picker__cancel, .van-picker__confirm{ + font-size: 15px!important; + } + .van-picker__column:nth-child(5){ + display: none; + } + .recordtime{ + font-weight: bold; + } + .van-radio__icon--disabled{ + background-color: #fff!important; + } + .van-radio__icon--disabled.van-radio__icon--checked{ + color:#fff!important; + background-color: #1989fa!important; + border-color:#1989fa ; + } + .van-radio__label--disabled{ + color:#000!important; + } diff --git a/case/pages/improveInfo/improveInfo.js b/case/pages/improveInfo/improveInfo.js new file mode 100644 index 0000000..1bdebbb --- /dev/null +++ b/case/pages/improveInfo/improveInfo.js @@ -0,0 +1,520 @@ +// case/pages/improveInfo/improveInfo.js +// case/pages/register/register.js +import {throttle} from "../../../utils/util" +import {hostConfig} from "../../../utils/config" +import {getArea,getHospital,getOfficeList,getPosition,needInfo,modifyInfo} from "../../../api/api" +import {base64src} from "../../../utils/base64ToImg" +const host=hostConfig().host; +const app=getApp(); +Page({ + /** + * 页面的初始数据 + */ + data: { + showSuccess:false, + img_host:app.hostConfig().imghost, + showArea:false, + areaColumns:[ + { + values: [1,2], + className: 'column1', + }, + { + values: [3,4], + className: 'column2', + defaultIndex: 0 + }, + ], + hospitalColumns:[], + officeColumns:[], + positionColumns:[], + showHospital:false, + showOffice:false, + showPosition:false, + fileList:[], + disabled:false, + county_id:'', + hospital_name:'', + hospital_uuid:'', + mobile:'', + name:'', + uuid:'', + office_name:'', + office_uuid:'', + password:'', + position_uuid:'', + certificate:'', + certificate_img:'', + codeActive:true, + msg:'获取验证码', + captchaUuid:'', + captchaCode:'', + loginDevice:5, + imgCode:'', + sms:'', + timer:null, + time:59, + smsType:1, + showCrop:false, + width: 250, //宽度 + height: 250, //高度 + max_width: 300, + max_height: 300, + disable_rotate: true, //是否禁用旋转 + disable_ratio: false, //锁定比例 + limit_move: true, //是否限制移动 + }, + closeCrop(){ + this.setData({ + showCrop:false + }) + }, + submit() { + this.setData({ + showCrop:false + }) + this.cropper.getImg((obj) => { + this.uploadImg(obj) + }); + }, + cropperload(e) { + console.log('cropper加载完成'); + }, + initCrop(options){ + + this.cropper = this.selectComponent("#image-cropper"); + + + + if(options.imgSrc){ + this.setData({ + src: options.imgSrc + }); + } + + // if(!options.imgSrc){ + // this.cropper.upload(); //上传图片 + // } + }, + loadimage(e) { + this.cropper.imgReset(); + }, + onConfirmSuccess(){ + this.setData({ + showSuccess:false + }) + app.method.navigateTo({ + url:"/case/pages/mobileLogin/mobileLogin" + }) + }, + opeArea(){ + this.setData({ + showArea:true + }) + }, + openOffice(){ + this.setData({ + showOffice:true, + }) + }, + openPosition(){ + this.setData({ + showPosition:true, + }) + }, + onChange(e){ + const {value} = e.detail; + + const {id}=e.currentTarget.dataset; + // console.log(value,id) + this.setData({ + [id]: value + }); + }, + onChangeArea(event){ + const { picker, value, index } = event.detail; + const provinceId=value[0].id; + this.handleGetArea(provinceId) + }, + confirmArea(event){ + const {value} = event.detail; + const countyId=value[1].id; + this.setData({ + county_id:countyId, + showArea:false + }); + this.handleGetHospital(countyId) + }, + cancelArea(){ + this.setData({ + showArea:false + }) + }, + confirmHospital(event){ + let {value}=event.detail; + this.setData({ + showHospital:false, + hospital_uuid:value.uuid, + hospital_name:value.name + }) + }, + cancelHospital(){ + this.setData({ + showHospital:false + }) + }, + confirmOffice(event){ + let {value}=event.detail; + + this.setData({ + office_name:value.officeName, + office_uuid:value.officeUuid, + showOffice:false + }) + }, + cancelOffice(){ + this.setData({ + showOffice:false + }) + }, + confirmPosition(event){ + let {value}=event.detail; + this.setData({ + position_uuid:value.uuid, + position_name:value.name, + showPosition:false + }) + }, + cancelPosition(){ + this.setData({ + showPosition:false + }) + }, + + + handleNeedInfo(){ + let {mobile,positionColumns}=this.data; + needInfo(mobile).then(res=>{ + let {certificate,certificate_img,county_id,hospital_name,hospital_uuid,name,office_name,office_uuid,position_uuid,uuid}=res; + let arr= positionColumns.filter((item)=>item.uuid==position_uuid); + console.log(arr); + this.setData({ + certificate, + county_id, + certificate_img, + hospital_name, + hospital_uuid, + name, + position_name:arr.length>0?arr[0].name:'', + office_name, + office_uuid, + position_uuid, + uuid + }) + }) + }, + handleModifyInfo:throttle(function(){ + let {certificate,certificate_img,county_id,hospital_name,hospital_uuid,name,office_name,office_uuid,position_uuid,uuid}=this.data; + if(!(/^[\u4E00-\u9FA5·]{2,20}$/.test(name))){ + wx.showToast({ + title: `请输入您的真实姓名`, + icon: 'none', + }); + return false; + }; + if(!hospital_uuid){ + wx.showToast({ + title: `请选择医院`, + icon: 'none', + }); + return false; + } + if(!office_uuid){ + wx.showToast({ + title: `请选择科室`, + icon: 'none', + }); + return false; + } + if(!position_uuid){ + wx.showToast({ + title: `请选择职称`, + icon: 'none', + }); + return false; + } + if(!certificate_img){ + wx.showToast({ + title: `请上传执业医师资格证或工作胸牌`, + icon: 'none', + }); + return false; + } + modifyInfo({ + certificate, + certificate_img, + county_id, + hospital_name, + hospital_uuid, + name, + office_name, + office_uuid, + position_uuid, + uuid + }).then(res=>{ + this.setData({ + showSuccess:true, + }) + }) + }), + handleGetArea(id){ + getArea({ + parent:id + }).then(res=>{ + if(id){ + let obj='areaColumns[1].values'; + this.setData({ + [obj]:res + }) + }else{ + let obj='areaColumns[0].values'; + this.setData({ + [obj]:res + }) + this.handleGetArea(res[0].id) + } + + }).catch(error=>{ + console.log(error) + }) + }, + handleGetHospital(countyId){ + getHospital(countyId).then(res=>{ + this.setData({ + showHospital:true, + hospitalColumns:res.concat({uuid:1,name:'其他医院'}) + }) + }) + }, + handleGetOffice(){ + getOfficeList({}).then(res=>{ + this.setData({ + officeColumns:res + }) + }).catch(error=>{ + console.log(error) + }) + }, + handleGetPosition(){ + getPosition().then(res=>{ + this.setData({ + positionColumns:res + }) + this.handleNeedInfo(); + }) + }, + handleSmsRegister:throttle(function(){ + let {name,password,hospital_uuid,hospital_name,office_name,office_uuid,position_uuid,certificate,certificate_img,county_id}=this.data; + if(!(/^[\u4E00-\u9FA5·]{2,20}$/.test(name))){ + wx.showToast({ + title: `请输入您的真实姓名`, + icon: 'none', + }); + return false; + } + smsRegister({ + + name, + hospital_uuid, + hospital_name, + office_name, + office_uuid, + county_id, + position_uuid, + certificate, + certificate_img + }).then(res=>{ + this.setData({ + showSuccess:true, + }) + }) + }), + handleThrottle:throttle(function(){ + this.getCode() + }), + handleGetCaptcha(){ + getCaptcha().then(res=>{ + base64src(res.captchaBase64Image,(img)=>{ + this.setData({ + imgCode:img, + captchaUuid:res.captchaUuid + }) + }) + }) + }, + getCode(){ + if(this.data.disabled)return; + let {mobile,captchaCode,captchaUuid,loginDevice,smsType}=this.data; + if(!/^1[3456789]\d{9}$/.test(mobile)){ + wx.showToast({ + title: `请输入有效的手机号码!`, + icon: 'none', + }); + return false; + }; + if(!captchaCode){ + wx.showToast({ + title: `图形验证码不能为空`, + icon: 'none', + }); + return false; + } + sendSms({ + mobile, + captchaCode, + captchaUuid, + smsType, + loginDevice + }).then((res)=>{ + let timer=setInterval(() => { + if (this.data.time == 0) { + clearInterval(this.data.timer); + this.setData({ + time:59, + msg:'重新获取验证码', + disabled:false, + codeActive:true, + }) + } else { + let msg= this.data.time + " s 后重新发送"; + this.setData({ + disabled:true, + msg:msg, + codeActive:false, + }) + let time=this.data.time--; + this.setData({ + msg:time + " s 后重新发送" + }) + } + }, 1000); + + this.setData({ + timer:timer + }) + }).catch(()=>{ + this.handleGetCaptcha(); + }); + + }, + uploadImg(file){ + let THIS=this; + wx.showLoading({ + title: '图片上传中', + mask: true + }) + + // 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式 + wx.uploadFile({ + url: host+'/user/uoloadImg', // 仅为示例,非真实的接口地址 + filePath: file.url, + name: 'file', + formData: file, + success(res) { + let result=JSON.parse(res.data); + + if(result.code==200){ + wx.hideLoading(); + THIS.setData({ + certificate_img:result.data + }); + }else{ + wx.hideLoading(); + wx.showToast({ + title:result.msg, + icon:'none' + }) + } + + }, + complete(res){ + console.log(res) + } + }); + }, + afterRead(event) { + const { + file + } = event.detail; + this.setData({ + showCrop:true, + src:file.url + }) + + }, + /** + * 生命周期函数--监听页面加载 + */ + onLoad(options) { + if(options.mobile){ + this.setData({ + mobile:options.mobile + }) + } + //this.handleGetCaptcha(); + this.handleGetArea(''); + this.handleGetOffice(); + this.handleGetPosition(); + this.initCrop(options); + + }, + + /** + * 生命周期函数--监听页面初次渲染完成 + */ + onReady() { + + }, + + /** + * 生命周期函数--监听页面显示 + */ + onShow() { + + }, + + /** + * 生命周期函数--监听页面隐藏 + */ + onHide() { + + }, + + /** + * 生命周期函数--监听页面卸载 + */ + onUnload() { + + }, + + /** + * 页面相关事件处理函数--监听用户下拉动作 + */ + onPullDownRefresh() { + + }, + + /** + * 页面上拉触底事件的处理函数 + */ + onReachBottom() { + + }, + + /** + * 用户点击右上角分享 + */ + // onShareAppMessage() { + + // } +}) \ No newline at end of file diff --git a/case/pages/improveInfo/improveInfo.json b/case/pages/improveInfo/improveInfo.json new file mode 100644 index 0000000..2ab1965 --- /dev/null +++ b/case/pages/improveInfo/improveInfo.json @@ -0,0 +1,17 @@ +{ + "usingComponents": { + "van-overlay": "@vant/weapp/overlay/index", + "image-cropper": "../../../components/image-cropper/image-cropper", + "dialog":"../../../components/dialog/dialog", + "van-popup": "@vant/weapp/popup/index", + "van-picker": "@vant/weapp/picker/index", + "van-field": "@vant/weapp/field/index", + "van-cell": "@vant/weapp/cell/index", + "van-button": "@vant/weapp/button/index", + "van-image": "@vant/weapp/image/index", + "van-uploader": "@vant/weapp/uploader/index", + "van-cell-group": "@vant/weapp/cell-group/index" + + }, + "navigationBarTitleText": "完善信息" +} \ No newline at end of file diff --git a/case/pages/improveInfo/improveInfo.wxml b/case/pages/improveInfo/improveInfo.wxml new file mode 100644 index 0000000..70cb1c4 --- /dev/null +++ b/case/pages/improveInfo/improveInfo.wxml @@ -0,0 +1,69 @@ + + + + + + + + + + + + + + + + + + 提交 + + + + + + + + + + + + + + + + + + + + + 返回 + 确定 + + \ No newline at end of file diff --git a/case/pages/improveInfo/improveInfo.wxss b/case/pages/improveInfo/improveInfo.wxss new file mode 100644 index 0000000..b4e1759 --- /dev/null +++ b/case/pages/improveInfo/improveInfo.wxss @@ -0,0 +1,84 @@ +/* case/pages/improveInfo/improveInfo.wxss */ +/* case/pages/register/register.wxss */ +.next{ + margin: 40rpx 20rpx!important; +} +.prev{ + margin:-20rpx 20rpx 0!important ; +} +.van-field__label{ + white-space: nowrap; +} +.custom-class .van-cell:last-child{ + background-color: red; +} +.van-cell__title{ + white-space: nowrap; + display: flex; + color: #646566; + align-items: center; +} +.van-image{ + margin-top: 10rpx; +} +.van-cell{ + display: flex; + align-items: center; +} +.van-field__label,.van-cell__title{ + position: relative; +} +.van-field__label::after,.cert .van-cell__title::after{ + content: "*"; + color:red; + position: absolute; + top:12rpx; + right:-14rpx; +} +.cert .van-cell__title::after{ + top:4rpx; +} +.myclass .van-field__label::after{ + content: ""; +} +.upload{ + opacity:0; + position: absolute; + z-index:99; +} +.cert .van-cell{ + overflow: hidden; +} +.van-button{ + border-radius: 10rpx!important; +} +.imgCode{ + width:200rpx; + height:75.6rpx; +} +.custom-class{ + min-height:43.5px !important; +} +.van-field__label{ + color:#333!important; + padding:12rpx 0; + font-size: 15px!important; +} +.van-cell__title{ + color:#333!important; + font-size: 15px!important; +} +.buttonbox{ + position: absolute; + z-index:99; + display: flex; + width:600rpx; + left:50%; + transform: translateX(-50%); + justify-content: space-between; + bottom:50rpx; + } + .van-button--primary{ + background: linear-gradient(90deg, #377FF7 0%, #51AAFF 100%)!important; + border-color:#377FF7!important + } \ No newline at end of file diff --git a/case/pages/login/login.js b/case/pages/login/login.js new file mode 100644 index 0000000..eca929e --- /dev/null +++ b/case/pages/login/login.js @@ -0,0 +1,249 @@ + +const app = getApp() +import {phoneLogin} from "../../../api/api" +import {auth} from "../../../api/auth.js" +import {throttle} from "../../../utils/util" +let urlHost=app.hostConfig().agreehost; +Page({ + /** + * 页面的初始数据 + */ + data: { + message:'', + check: false, + mobile:'', + showSuccess:false, + beforeClose(action){ + return new Promise((resolve) => { + if (action === 'confirm') { + resolve(true); + } else { + // 拦截取消操作 + resolve(false); + } + }) +}, + showEntryTip:false, + showEntryTip_second:false, + redirecUrl:'', + img_host:'https://oss.prod.applets.igandanyiyuan.com/applet/case/static' + + }, + onConfirmSuccess(){ + let {mobile}=this.data; + this.setData({ + showSuccess:false + }) + app.method.navigateTo({ + url:'/case/pages/improveInfo/improveInfo?mobile='+mobile + }) + }, + onCancelSuccess(){ + this.setData({ + showSuccess:false + }) + }, + handleAgree(){ + if(!this.data.check){ + wx.showToast({ + title: '请同意《肝胆相照用户服务协议》!', + icon:'none' + }) + return false + }; + }, + + onConfirmEntry(){ + this.setData({ + showEntryTip:false + }) + //wx.setStorageSync('hasEntry', true); + }, + onCloseEntry(){ + this.setData({ + showEntryTip:true, + showEntryTip_second:true + }) + }, + onConfirmEntry_second(){ + this.setData({ + showEntryTip_second:false, + }); + //wx.setStorageSync('hasEntry', true); + + }, + onCloseEntry_second(){ + wx.exitMiniProgram({success: (res) => {}}) + this.setData({ + showEntryTip_second:false, + showEntryTip:false, + }); + + }, + goRegister:throttle(function(){ + app.method.navigateTo({ + url:'/case/pages/register/register' + }) + }), + getPhoneNumber:throttle(function(e) { + console.log(e.detail) + if (e.detail.errMsg == 'getPhoneNumber:ok'){ + auth().then(res => { + phoneLogin('wx415cbcf96f4a3b27',{ + code:e.detail.code + }).then((data)=>{ + const { envVersion } = wx.getAccountInfoSync().miniProgram; + let token='' + if(envVersion=="develop" || envVersion=="trial"){ + token="DEV_CASE_TOKEN" + }else{ + token="PROD_CASE_TOKEN" + } + wx.setStorageSync(token, data.token); + let url=this.data.redirectUrl?this.data.redirectUrl:'/pages/index/index'; + + if(url.indexOf('login')!=-1 || url.indexOf('mobileLogin')!=-1){ + wx.reLaunch({ + url:'/pages/index/index' + }) + }else{ + wx.reLaunch({ + url:url + }) + } + }).catch(error=>{ + if(error.code==10007){ + this.setData({ + showSuccess:true, + message:error.msg, + mobile:error.data + }) + } + }) + }); + }else if(e.detail.errMsg == 'getPhoneNumber:fail user deny'){ + console.log('您拒绝手机号授权') + }else{ + wx.showToast({ + title: '手机号授权失败', + icon:'none' + }) + } + }), + goMobile(){ + let url='/case/pages/mobileLogin/mobileLogin'; + let redirectUrl=this.data.redirectUrl?this.data.redirectUrl:'/pages/index/index'; + app.method.navigateTo({ + url: url+"?redirectUrl="+encodeURIComponent(redirectUrl) + }) + }, + goAgreement:throttle(function(event){ + let id=event.currentTarget.dataset.id; + app.method.navigateTo({ + url:"/case/pages/linkPage/linkPage?url="+encodeURIComponent(urlHost+'/basic/file/agreement.htm?id='+id) + }) + }), + checkboxChange(event) { + this.setData({ + check: event.detail + }) + + }, + getRedirect(urlParams){ + + let url=decodeURIComponent(urlParams); + let index=url.indexOf("redirectUrl="); + let cur_url='' + if(index!=-1){ + cur_url=url.substring(index+12,index.length); + // console.log(cur_url) + this.getRedirect(cur_url); + }else{ + cur_url=url; + } + return decodeURIComponent(cur_url); + + }, + /** + * 生命周期函数--监听页面加载 + */ + onLoad(options) { + this.setData({ + beforeClose: this.data.beforeClose.bind(this), + }) + if(options.redirectUrl){ + //console.log(decodeURIComponent(options.redirectUrl)) + this.setData({ + redirectUrl:"/"+decodeURIComponent(options.redirectUrl) + }) + } + }, + + /** + * 生命周期函数--监听页面初次渲染完成 + */ + onReady() { + + }, + + /** + * 生命周期函数--监听页面显示 + */ + onShow() { + this.setData({ + img_host:app.hostConfig().imghost + }); + wx.getPrivacySetting({ + success: res => { + console.log(res) // 返回结果为: res = { needAuthorization: true/false, privacyContractName: '《xxx隐私保护指引》' } + if (res.needAuthorization) { + // 需要弹出隐私协议 + this.setData({ + showEntryTip:true + }) + } else { + this.setData({ + showEntryTip:false + }) + // 用户已经同意过隐私协议,所以不需要再弹出隐私协议,也能调用已声明过的隐私 + } + }, + fail: () => {}, + complete: () => {} + }) + + }, + + /** + * 生命周期函数--监听页面隐藏 + */ + onHide() { + + }, + + /** + * 生命周期函数--监听页面卸载 + */ + onUnload() { + + }, + + /** + * 页面相关事件处理函数--监听用户下拉动作 + */ + onPullDownRefresh() { + + }, + + /** + * 页面上拉触底事件的处理函数 + */ + onReachBottom() { + + }, + + /** + * 用户点击右上角分享 + */ + +}) \ No newline at end of file diff --git a/case/pages/login/login.json b/case/pages/login/login.json new file mode 100644 index 0000000..070d30e --- /dev/null +++ b/case/pages/login/login.json @@ -0,0 +1,10 @@ +{ + "usingComponents": { + "dialog":"../../../components/dialog/dialog", + "van-overlay": "@vant/weapp/overlay/index", + "van-checkbox": "@vant/weapp/checkbox/index", + "van-checkbox-group": "@vant/weapp/checkbox-group/index", + "van-dialog": "@vant/weapp/dialog/index" + }, + "navigationBarTitleText": "登录" +} \ No newline at end of file diff --git a/case/pages/login/login.wxml b/case/pages/login/login.wxml new file mode 100644 index 0000000..3f3203e --- /dev/null +++ b/case/pages/login/login.wxml @@ -0,0 +1,70 @@ + + + 注册 + + + 你好!欢迎登录人工肝病例登记系统 + + + 手机号快捷登录 + 手机号快捷登录 + + + + + 我已阅读并同意协议《肝胆相照用户注册及服务协议》、《隐私协议》、《风险告知书》 + + + 输入手机号登录 + + + + + + 温馨提示 + + 亲爱的用户,感谢您信任并使用人工肝病例登录系统!我们依据最新法律法规的要求,制定了《隐私协议》。请您仔细阅《隐私协议》,并确认了解我们对您的个人信息处理原则。 + 如您同意《隐私协议》,请点击“同意”开始使用我们的产品和服务。 + + + 不同意 + 同意并继续 + + + + + + + + + 温馨提示 + + 很抱歉,如您不同意《隐私协议》,可能无法继续正常使用我们的服务,请您先同意哦~ + + + 不同意 + 好的 + + + + + + + \ No newline at end of file diff --git a/case/pages/login/login.wxss b/case/pages/login/login.wxss new file mode 100644 index 0000000..3cbb467 --- /dev/null +++ b/case/pages/login/login.wxss @@ -0,0 +1,108 @@ +/* pages/login/login.wxss */ +.contain { + padding: 0 30rpx 0rpx; + height: 100%; + overflow: hidden; + position: relative; +} +.logobox{display: flex;flex-direction: column;margin-top: 122rpx;} +.logobox image{width: 275rpx;height:56rpx} +.logobox .desc{font-size: 40rpx;margin-top: 42rpx;} +.btnbox_shouquan{width:100%;margin-top: 280rpx;} +.btnbox_shouquan button{display:flex;width:100%;font-size:36rpx;height: 94rpx;background: #09BB07;border-radius: 47rpx;color: #FFFFFF;align-items: center;justify-content: center;font-weight: normal;} +.checkbox{display: flex;margin-top: 40rpx;font-size: 28rpx;align-items: flex-start;height:40rpx;} +.checkbox view{ + /* width: 40rpx;height:40rpx; + margin-right:14rpx;border-radius: 50%; */ + display: flex;align-items: center; + position: relative; +} +.checkbox .check{width: 40rpx;height:40rpx;opacity: 0;} +.checkbox image{ + left:5rpx; + top:4rpx; + position: absolute; + width: 40rpx; + height: 40rpx; + z-index:1; +} +.navigator{color: #4384FE;white-space:pre-wrap;word-break: break-all;background-color: none;font-size: 28rpx;} +.footer{ + height: 60rpx; + line-height: 60rpx; + width:600rpx; + position: absolute; + bottom:60rpx; + text-align: center; + left:50%; + transform: translateX(-50%); + font-size: 30rpx; + font-weight: 400; + color: #333333} + .entrymsg{ + -webkit-overflow-scrolling: touch; + font-size: 28rpx; + line-height: 40rpx; + max-height: 60vh; + overflow-y: auto; + padding: 48rpx; + text-align: left; + } + .navigator{ + color:#3CC7C0 + } + .wrapper { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + + } + .privacyBox { + width:90%; + background-color: #fff; + border-radius: 30rpx; + } + .privacyBox .title{ + margin: 0; + text-align: center; + font-weight: 500; + line-height:24px; + padding-top:24px; + text-align: center; + font-size:16px; + } + .privacyBox .btnbox{ + display: flex; + align-items: center; + border-top:1rpx solid #efefef; + width:100%; + font-size: 17px; + } + .privacyBox .btnbox .cancel{ + flex:1; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + } + .privacyBox .btnbox .confirm{ + margin: 0; + height: 50px; + padding: 0; + color: #3CC7C0; + flex:1; + font-size: 17px; + border: none!important; + border-left:1rpx solid #efefef!important; + display: flex; + background-color: none!important; + justify-content: center; + align-items: center; + } + .zhuce{ + position: absolute; + color: rgba(0,0,0,0.65); + top:20rpx; + right:32rpx; + } diff --git a/case/pages/mobileLogin/mobileLogin.js b/case/pages/mobileLogin/mobileLogin.js new file mode 100644 index 0000000..3c3cfa3 --- /dev/null +++ b/case/pages/mobileLogin/mobileLogin.js @@ -0,0 +1,404 @@ +// pages/mobileLogin/mobileLogin.js +import { smsLogin, sendSms,getCaptcha,phoneLogin} from "../../../api/api" +const app = getApp(); +import {auth} from "../../../api/auth.js" +import {throttle} from "../../../utils/util" +import {base64src} from "../../../utils/base64ToImg" +let urlHost=app.hostConfig().agreehost; +Page({ + /** + * 页面的初始数据 + */ + data: { + showSuccess:false, + showEntryTip:false, + showEntryTip_second:false, + show: false, + check:false, + sms:'', + message:'', + disabled:true, + isActive:false, + codeActive:true, + imgCode:'', + mobile:'', + redirecUrl:'', + timer:null, + time:59, + msg:'获取验证码', + captchaUuid:'', + captchaCode:'', + loginDevice:5, + smsType:2, + img_host:app.hostConfig().imghost + }, + onConfirmEntry(){ + this.setData({ + showEntryTip:false + }) + //wx.setStorageSync('hasEntry', true); + }, + onCloseEntry(){ + this.setData({ + showEntryTip:true, + showEntryTip_second:true + }) + }, + onConfirmEntry_second(){ + this.setData({ + showEntryTip_second:false, + }); + + }, + onCloseEntry_second(){ + wx.exitMiniProgram({success: (res) => {}}) + this.setData({ + showEntryTip_second:false, + showEntryTip:false, + }); + + }, + onConfirmSuccess(){ + this.setData({ + showSuccess:false + }) + }, + onCancelSuccess(){ + this.setData({ + showSuccess:false + }) + }, + checkboxChange(e) { + this.setData({ + check:e.detail + }) + + }, + getPhoneNumber:throttle(function(e) { + if (e.detail.errMsg == 'getPhoneNumber:ok'){ + auth().then(res => { + phoneLogin('wx415cbcf96f4a3b27',{ + code:e.detail.code + }).then((data)=>{ + const { envVersion } = wx.getAccountInfoSync().miniProgram; + let token='' + if(envVersion=="develop" || envVersion=="trial"){ + token="DEV_CASE_TOKEN" + }else{ + token="PROD_CASE_TOKEN" + } + wx.setStorageSync(token, data.token); + let url=this.data.redirectUrl?this.data.redirectUrl:'/pages/index/index'; + + if(url.indexOf('login')!=-1 || url.indexOf('mobileLogin')!=-1){ + wx.reLaunch({ + url:'/pages/index/index' + }) + }else{ + wx.reLaunch({ + url:url + }) + } + }).catch(error=>{ + if(error.code==10007){ + console.log(error.msg) + this.setData({ + showSuccess:true, + message:error.msg, + mobile:error.data + }) + } + }) + }); + }else if(e.detail.errMsg == 'getPhoneNumber:fail user deny'){ + console.log('您拒绝手机号授权') + }else{ + wx.showToast({ + title: '手机号授权失败', + icon:'none' + }) + } + }), + onConfirmSuccess(){ + let {mobile}=this.data; + this.setData({ + showSuccess:false + }) + app.method.navigateTo({ + url:'/case/pages/improveInfo/improveInfo?mobile='+mobile + }) +}, +onCancelSuccess(){ + this.setData({ + showSuccess:false + }) + }, + handleAgree(){ + if(!this.data.check){ + wx.showToast({ + title: '请同意《用户服务协议》!', + icon:'none' + }) + return false + }; + }, + getUserInfo(event) { + this.handlelogin(); + }, + onClose() { + this.setData({ show: false }); + }, + handleThrottle:throttle(function(){ + this.getCode() + }), + handleGetCaptcha(){ + getCaptcha().then(res=>{ + base64src(res.captchaBase64Image,(img)=>{ + this.setData({ + imgCode:img, + captchaUuid:res.captchaUuid + }) + }) + }) + }, + goPwdLogin:throttle(function(){ + app.method.navigateTo({ + url:'/case/pages/pwdLogin/pwdLogin' + }) +}), + goRegister:throttle(function(){ + app.method.navigateTo({ + url:'/case/pages/register/register' + }) +}), + getCode(){ + let {mobile,captchaCode,captchaUuid,loginDevice,smsType}=this.data; + + if(!/^1[3456789]\d{9}$/.test(mobile)){ + wx.showToast({ + title: `请输入有效的手机号码!`, + icon: 'none', + }); + return false; + }; + if(!captchaCode){ + wx.showToast({ + title: `图形验证码不能为空`, + icon: 'none', + }); + return false; + } + sendSms({ + mobile, + smsType, + captchaCode, + captchaUuid, + loginDevice + }).then((res)=>{ + let timer=setInterval(() => { + if (this.data.time == 0) { + clearInterval(this.data.timer); + this.setData({ + time:59, + msg:'重新获取验证码', + isActive:false, + codeActive:true, + }) + } else { + let msg= this.data.time + " s 后重新发送"; + this.setData({ + isActive:true, + msg:msg, + codeActive:false, + }) + let time=this.data.time--; + this.setData({ + msg:time + " s 后重新发送" + }) + } + }, 1000); + + this.setData({ + timer:timer + }) + + }).catch(()=>{ + this.handleGetCaptcha(); + }); + + }, + goLogin(){ + let THIS=this; + if(!this.data.check){ + wx.showToast({ + title: '请同意《肝胆相照用户服务协议》!', + icon:'none' + }) + return false + }; + if(!/^1[3456789]\d{9}$/.test(this.data.mobile)){ + wx.showToast({ + title: `请输入有效的手机号码!`, + icon: 'none', + }); + return false; + }; + if(!this.data.sms){ + wx.showToast({ + title: `请输入验证码!`, + icon: 'none', + }); + return false; + } + + + wx.getSetting({ + success(res) { + // 判断它是否为true + if (res.authSetting["scope.userInfo"]) { + THIS.handlelogin(); + } else { + THIS.setData({ + show:true + }) + } + } + }); + }, + + handlelogin:throttle(function(){ + let {sms,mobile }=this.data; + smsLogin({ + sms:sms, + mobile + }).then((data)=>{ + const { envVersion } = wx.getAccountInfoSync().miniProgram; + let token='' + if(envVersion=="develop" || envVersion=="trial"){ + token="DEV_CASE_TOKEN" + }else{ + token="PROD_CASE_TOKEN" + } + wx.setStorageSync(token, data.token); + let url=this.data.redirectUrl?this.data.redirectUrl:'/pages/index/index'; + if(url.indexOf('login')!=-1 || url.indexOf('mobileLogin')!=-1 ){ + wx.reLaunch({ + url:'/pages/index/index' + }) + + }else{ + wx.reLaunch({ + url:url + }) + } + }).catch(error=>{ + if(error.code==10007){ + this.setData({ + showSuccess:true, + message:error.msg, + mobile:error.data + }) + }else{ + this.handleGetCaptcha(); + } + }) + }), + goAgreement:throttle(function(event){ + app.method.navigateTo({ + url:"/case/pages/privacy/privacy" + }) + }), + inputChange(e){ + this.setData({ + [e.target.dataset.id]: e.detail.value + }); + if(e.target.dataset.id=="sms"){ + if(e.detail.value.trim()){ + this.setData({ + disabled: false + }); + }else{ + this.setData({ + disabled: true + }); + } + + } + }, + /** + * 生命周期函数--监听页面加载 + */ + onLoad(options) { + this.handleGetCaptcha(); + if(options.redirectUrl){ + this.setData({ + redirectUrl:"/"+decodeURIComponent(options.redirectUrl) + }) + } + }, + + /** + * 生命周期函数--监听页面初次渲染完成 + */ + onReady() { + + }, + + /** + * 生命周期函数--监听页面显示 + */ + onShow() { + wx.getPrivacySetting({ + success: res => { + console.log(res) // 返回结果为: res = { needAuthorization: true/false, privacyContractName: '《xxx隐私保护指引》' } + if (res.needAuthorization) { + // 需要弹出隐私协议 + this.setData({ + showEntryTip:true + }) + } else { + this.setData({ + showEntryTip:false + }) + // 用户已经同意过隐私协议,所以不需要再弹出隐私协议,也能调用已声明过的隐私 + } + }, + fail: () => {}, + complete: () => {} + }) + }, + + /** + * 生命周期函数--监听页面隐藏 + */ + onHide() { + + }, + + /** + * 生命周期函数--监听页面卸载 + */ + onUnload() { + clearInterval(this.data.timer); + + }, + + /** + * 页面相关事件处理函数--监听用户下拉动作 + */ + onPullDownRefresh() { + + }, + + /** + * 页面上拉触底事件的处理函数 + */ + onReachBottom() { + + }, + + /** + * 用户点击右上角分享 + */ + +}) \ No newline at end of file diff --git a/case/pages/mobileLogin/mobileLogin.json b/case/pages/mobileLogin/mobileLogin.json new file mode 100644 index 0000000..520354b --- /dev/null +++ b/case/pages/mobileLogin/mobileLogin.json @@ -0,0 +1,13 @@ +{ + "usingComponents": { + "navBar":"../../../components/navBar/navBar", + "dialog":"../../../components/dialog/dialog", + "van-overlay": "@vant/weapp/overlay/index", + "van-dialog": "@vant/weapp/dialog/index", + "van-checkbox": "@vant/weapp/checkbox/index", + "van-checkbox-group": "@vant/weapp/checkbox-group/index", + "nav":"../../../components/navBar/navBar" + }, + "navigationStyle":"custom", + "navigationBarTitleText": "登录" +} \ No newline at end of file diff --git a/case/pages/mobileLogin/mobileLogin.wxml b/case/pages/mobileLogin/mobileLogin.wxml new file mode 100644 index 0000000..63a3511 --- /dev/null +++ b/case/pages/mobileLogin/mobileLogin.wxml @@ -0,0 +1,80 @@ + + + + + + + 欢迎登录 + 人工肝病例登记系统 + + + + + + + + + + + + + {{msg}} + + + + 登录 + + + + 密码登录 + 注册 + + + - 手机号快捷登录 - + + 手机号快捷登录 + + + + + 我已阅读并同意《隐私协议》 + + + + + + + + 温馨提示 + + 亲爱的用户,感谢您信任并使用人工肝病例登录系统!我们依据最新法律法规的要求,制定了《隐私协议》。请您仔细阅《隐私协议》,并确认了解我们对您的个人信息处理原则。 + 如您同意《隐私协议》,请点击“同意”开始使用我们的产品和服务。 + + + 不同意 + 同意并继续 + + + + + + + + + 温馨提示 + + 很抱歉,如您不同意《隐私协议》,可能无法继续正常使用我们的服务,请您先同意哦~ + + + 不同意 + 好的 + + + + \ No newline at end of file diff --git a/case/pages/mobileLogin/mobileLogin.wxss b/case/pages/mobileLogin/mobileLogin.wxss new file mode 100644 index 0000000..517427e --- /dev/null +++ b/case/pages/mobileLogin/mobileLogin.wxss @@ -0,0 +1,304 @@ +/* pages/mobileLogin/mobileLogin.wxss */ +.loginbox { + display: flex; + flex-direction: column; + align-items: center; + margin-top: 80rpx; +} +.contain { + padding: 0 30rpx 0rpx; + height: 100%; + overflow: hidden; + position: relative; +} +.logo { + width: 160rpx; + height: 196rpx +} + + +.iptcell { + width: 100%; + height: 112rpx; + align-items: center; + white-space: nowrap; + display: flex; + justify-content: space-between; + border-bottom: 1rpx solid rgba(0, 0, 0, 0.1); +} + +.iptbox { + margin-top: 68rpx; +} + +.iptcell input { + flex: 1; +} + +.iptcell text { + color: rgba(0, 0, 0, 0.9); + font-size: 17px; +} + +.iptcell .code { + width: 400rpx; + margin-left: 0; +} + +.iptcell button { + outline: none; + white-space: nowrap; + padding: 0 10rpx; + height: 60rpx; + display: flex; + width: 220rpx; + align-items: center; + justify-content: center; + color: #3881F7; + background-color: transparent; + font-size: 26rpx; + + border: none; +} + +.iptcell button[disabled] { + width: 280rpx !important; +} + +/* .iptcell button.active { + color: #3CC7C0 !important; + border: 1px solid #3CC7C0 !important; +} */ + +.btnbox { + + margin: 40rpx 47rpx 0; +} + +.btnbox button { + border: none; + outline: none; + display: flex; + width: 100%; + font-size: 36rpx; + height: 94rpx; + background: #3CC7C0; + border-radius: 47rpx; + color: #FFFFFF; + align-items: center; + justify-content: center; + font-weight: normal; +} +.btnbox button[disabled]{ + color: #FFFFFF; + background: #8addd9; +} +.checkbox { + display: flex; + align-items: flex-start; + margin-top: 40rpx; + font-size: 28rpx; +} + +.checkbox view { + display: flex; + align-items: center; + position: relative; +} + +.checkbox .check { + width: 40rpx; + height: 40rpx; + opacity: 0; +} + +.checkbox image { + left: 5rpx; + top: 4rpx; + position: absolute; + width: 40rpx; + height: 40rpx; + z-index: -1; +} + +.navigator { + color: #4384FE; + white-space: pre-wrap; + word-break: break-all; + background-color: none; + font-size: 28rpx; +} +.imgCode{ + width:200rpx; + height:75.6rpx; +} +page{ + background-image: linear-gradient( #cdf0ff,#fff 40%); +} +.myclass{ + background-color: transparent!important; +} +.page{ + position: relative; + flex:1; + display: flex; + position: relative; + flex-direction: column; + margin-top: 172rpx; +} +.logobox{display: flex;flex-direction: column;margin: 40rpx 47rpx 0;position: relative;} +.logobox .desc{ +position: relative; +z-index: 1; + margin-top: 22rpx; + font-size: 48rpx; +font-weight: 500; +color: rgba(0,0,0,0.85); +} +.logobg{ + left:90rpx; + top:76rpx; + z-index:0; +position: absolute; + width:228rpx; + height:22rpx; +} +.iptbox{ + margin:70rpx 47rpx 0; +} +.buttonbox{ + margin:64rpx 47rpx 0; +} +.btn{ + display: flex; + justify-content: center; + align-items: center; + height: 88rpx; + background: linear-gradient(90deg, #377FF7 0%, #51AAFF 100%); + border-radius: 44rpx; + font-size: 32rpx; +font-weight: 500; +color: #FFFFFF; +} +.iptcell { + width: 100%; + height: 112rpx; + align-items: center; + white-space: nowrap; + display: flex; + justify-content: space-between; + border-bottom: 1rpx solid rgba(0, 0, 0, 0.1); +} +.type{ + margin:24rpx 47rpx 0; + display: flex; + justify-content: space-between; + align-items: center; +} +.mobileLogin,.zhuce{ + font-size: 28rpx; + font-family: PingFangSC, PingFang SC; + font-weight: 400; + color: rgba(0,0,0,0.65); +} +.wechatbox{ + flex:1; + position: relative; + margin:44rpx 47rpx 100rpx; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} +.chatmsg{ + font-size: 28rpx; + +font-weight: 400; +color: rgba(0,0,0,0.45); +} +.wecaht{ + margin-top: 42rpx; + width:88rpx; + height:88rpx; +} + +.agree{ + position: absolute; + bottom:40rpx; + margin:54rpx 47rpx 0; + display: flex; + font-size: 28rpx; +font-weight: 400; +display: flex; +align-items: center; +color: rgba(0,0,0,0.65); +} +.link{ + color:#3881F7 +} +.mobileAuth{ + top:50%; + left:50%; + margin-top: -10rpx; + transform: translate(-50%); + opacity: 0; + position: absolute; +} +.wrapper { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + + } + .privacyBox { + width:90%; + background-color: #fff; + border-radius: 30rpx; + } + .privacyBox .title{ + margin: 0; + text-align: center; + font-weight: 500; + line-height:24px; + padding-top:24px; + text-align: center; + font-size:16px; + } + .privacyBox .btnbox_xieyi{ + display: flex; + align-items: center; + border-top:1rpx solid #efefef; + width:100%; + font-size: 17px; + } + .privacyBox .btnbox_xieyi .cancel{ + flex:1; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + } + .privacyBox .btnbox_xieyi .confirm{ + margin: 0; + height: 50px; + padding: 0; + color: #3881F7; + flex:1; + font-size: 17px; + border: none!important; + border-left:1rpx solid #efefef!important; + display: flex; + background-color: none!important; + justify-content: center; + align-items: center; + } + .entrymsg{ + -webkit-overflow-scrolling: touch; + font-size: 28rpx; + line-height: 40rpx; + max-height: 60vh; + overflow-y: auto; + padding: 48rpx; + text-align: left; + } \ No newline at end of file diff --git a/case/pages/paintCanvas/paintCanvas.js b/case/pages/paintCanvas/paintCanvas.js new file mode 100644 index 0000000..5fca74d --- /dev/null +++ b/case/pages/paintCanvas/paintCanvas.js @@ -0,0 +1,222 @@ +import utils from "../../utils/utils"; +// painting-2.js +Page({ + + /** + * 页面的初始数据 + */ + data: { + hasChoosedImg: false, + canvasWidth: 0, + canvasHeight: 0, // canvas的完整高度 + canvasHeightLen: 0, // canvas的临时高度(用在操作栏影响画布高度时) + windowHeight: 0, // 屏幕高度 + prevPosition: [0, 0], // 手指触摸的所在位置 + background: '', // 背景图片,即导入的图片 + + btnInfo: [ + { + type: 'width', + background: 'url("http://ov8a2tdri.bkt.clouddn.com/wx-app/icon-1.png"); background-size: 30px 30px;' + }, + { + type: 'color', + background: 'url("http://ov8a2tdri.bkt.clouddn.com/wx-app/icon-2.png") white no-repeat; background-size: 24px 24px;background-position: 3px 3px;' + }, + { + type: 'clear', + background: 'url("http://img0.imgtn.bdimg.com/it/u=1358545290,3102156418&fm=26&gp=0.jpg") white no-repeat; background-size: 20px 20px;background-position: 5px 5px;' + }, + { + type: 'save', + background: 'url("http://ov8a2tdri.bkt.clouddn.com/wx-app/icon-6.png") white no-repeat; background-size: 20px 20px;background-position: 5px 5px;' + } + ], + width: false, // 是否开启宽度调整栏 + color: false, // 是否开启颜色调整栏 + r: 33, + g: 33, + b: 33, + w: 10, + clear: false, // 是否开启清空栏 + eraser: false, // 是否开启橡皮擦 + saving: false, // 是否在保存状态 + scope: false, // 是否有保存图片的权限 + }, + + /** + * 生命周期函数--监听页面加载 + */ + onLoad: function (options) { + let that = this; + // 获取设备信息,canvas高度用 + wx.getSystemInfo({ + success: function(res) { + console.log(res); + that.setData({ + canvasWidth: res.windowWidth, + canvasHeight: res.windowHeight - 50, + windowHeight: res.windowHeight + }) + }, + }) + // 选照片 + this.chooseImg(); + // 检查权限,保存时提示弹窗用 + wx.getSetting({ + success(res) { + if (res.authSetting['scope.writePhotosAlbum']) { + that.setData({ + scope: true, + }) + } + } + }) + }, + + tapBtn: function (e) { + utils.tapBtn(e, this, 2); + }, + + addImg: function (e) { + this.chooseImg(); + }, + + chooseImg() { + let that = this; + wx.chooseImage({ + success: function (res) { + that.setData({ + hasChoosedImg: true, + }) + wx.getImageInfo({ + src: res.tempFilePaths[0], + success: function (res) { + // 获取图片信息,主要为宽高,选择合适的自适应方式(将最大边完整显示) + let [height, width] = [that.data.canvasWidth / res.width * res.height, that.data.canvasWidth]; + if (height > that.data.windowHeight - 50) { + height = that.data.windowHeight - 50; + width = height / res.height * res.width; + } + that.setData({ + canvasHeight: height, + canvasWidth: width, + background: res.path + }); + } + }) + }, + fail: function (res) { + console.log(res); + } + }) + }, + + touchStart: function (e) { + // 开始画图,隐藏所有的操作栏 + this.setData({ + prevPosition: [e.touches[0].x, e.touches[0].y], + width: false, + color: false, + canvasHeightLen: 0 + }) + }, + + touchMove: function (e) { + // 触摸移动,绘制中。。。 + let ctx = wx.createCanvasContext('myCanvas'); + + if (this.data.eraser) { + ctx.clearRect(e.touches[0].x, e.touches[0].y, 30, 30); + ctx.draw(true); + } else { + ctx.setStrokeStyle("rgb(" + this.data.r + ', ' + this.data.g + ', ' + this.data.b + ')'); + ctx.setLineWidth(this.data.w); + ctx.setLineCap('round'); + ctx.setLineJoin('round'); + ctx.moveTo(this.data.prevPosition[0], this.data.prevPosition[1]); + ctx.lineTo(e.touches[0].x, e.touches[0].y); + ctx.stroke(); + ctx.draw(true); + } + + this.setData({ + prevPosition: [e.touches[0].x, e.touches[0].y] + }) + }, + + clearCanvas: function () { + let ctx = wx.createCanvasContext('myCanvas'); + ctx.clearRect(0, 0, this.data.canvasWidth, this.data.canvasHeight); + ctx.draw(); + this.setData({ + clear: false, + canvasHeightLen: 0 + }) + }, + + chooseEraser: function () { + this.setData({ + eraser: !this.data.eraser, + clear: false, + canvasHeightLen: 0 + }) + }, + + changeColor: function (e) { + utils.changeColor(e, this); + }, + + changeWidth: function (e) { + utils.changeWidth(e, this, (this.data.canvasHeightLen + this.data.w - e.detail.value), 2); + }, + + /** + * 生命周期函数--监听页面初次渲染完成 + */ + onReady: function () { + + }, + + /** + * 生命周期函数--监听页面显示 + */ + onShow: function () { + + }, + + /** + * 生命周期函数--监听页面隐藏 + */ + onHide: function () { + + }, + + /** + * 生命周期函数--监听页面卸载 + */ + onUnload: function () { + + }, + + /** + * 页面相关事件处理函数--监听用户下拉动作 + */ + onPullDownRefresh: function () { + + }, + + /** + * 页面上拉触底事件的处理函数 + */ + onReachBottom: function () { + + }, + + /** + * 用户点击右上角分享 + */ + onShareAppMessage: function () { + + } +}) \ No newline at end of file diff --git a/case/pages/paintCanvas/paintCanvas.json b/case/pages/paintCanvas/paintCanvas.json new file mode 100644 index 0000000..16a5549 --- /dev/null +++ b/case/pages/paintCanvas/paintCanvas.json @@ -0,0 +1,6 @@ +{ +"navigationBarTitleText": "涂鸦", + "navigationBarBackgroundColor": "#9cf", + "navigationBarTextStyle": "white", + "usingComponents": {} +} \ No newline at end of file diff --git a/case/pages/paintCanvas/paintCanvas.wxml b/case/pages/paintCanvas/paintCanvas.wxml new file mode 100644 index 0000000..74a3f25 --- /dev/null +++ b/case/pages/paintCanvas/paintCanvas.wxml @@ -0,0 +1,31 @@ + + +没有选择照片,点击重新选择 + + + + + + + + + + + + + + + + + + + 橡皮擦 + + + + 清空 + + diff --git a/case/pages/paintCanvas/paintCanvas.wxss b/case/pages/paintCanvas/paintCanvas.wxss new file mode 100644 index 0000000..fd865c8 --- /dev/null +++ b/case/pages/paintCanvas/paintCanvas.wxss @@ -0,0 +1,59 @@ +/* case/pages/paintCanvas/paintCanvas.wxss *//* painting-2.wxss */ +page { + background: rgba(153, 204, 255, 0.1); + } + + .failText { + margin-top: 200rpx; + text-align: center; + color: #888; + } + + .bottom { + width: 750rpx; + height: 100rpx; + position: absolute; + bottom: 0; + display: flex; + justify-content: space-around; + } + + .list-item { + width: 60rpx; + height: 60rpx; + margin: 20rpx 0; + border-radius: 50%; + } + + .choose-box { + width: 750rpx; + position: absolute; + bottom: 100rpx; + } + + .color-box { + width: 375rpx; + margin: 40rpx auto; + } + + slider { + margin: 40rpx 60rpx; + } + + .choose-box-flex { + display: flex; + justify-content: space-around; + width: 750rpx; + position: absolute; + bottom: 100rpx; + font-size: 32rpx; + color: #666; + text-align: center; + } + .choose-img { + width: 60rpx; + height: 60rpx; + margin: 20rpx; + border-radius: 50%; + background: white; + } \ No newline at end of file diff --git a/case/pages/privacy/privacy.js b/case/pages/privacy/privacy.js new file mode 100644 index 0000000..ab1f0d0 --- /dev/null +++ b/case/pages/privacy/privacy.js @@ -0,0 +1,72 @@ +// case/pages/privacy/privacy.js +import {getPrivacy} from "../../../api/api" +Page({ + + /** + * 页面的初始数据 + */ + data: { + navName:'隐私协议', + node:'' + }, + handleGetPrivacy(){ + getPrivacy().then(res=>{ + this.setData({ + node:res + }) + }) + }, + /** + * 生命周期函数--监听页面加载 + */ + onLoad(options) { + + }, + + /** + * 生命周期函数--监听页面初次渲染完成 + */ + onReady() { + + }, + + /** + * 生命周期函数--监听页面显示 + */ + onShow() { + this.handleGetPrivacy(); + }, + + /** + * 生命周期函数--监听页面隐藏 + */ + onHide() { + + }, + + /** + * 生命周期函数--监听页面卸载 + */ + onUnload() { + + }, + + /** + * 页面相关事件处理函数--监听用户下拉动作 + */ + onPullDownRefresh() { + + }, + + /** + * 页面上拉触底事件的处理函数 + */ + onReachBottom() { + + }, + + /** + * 用户点击右上角分享 + */ + +}) \ No newline at end of file diff --git a/case/pages/privacy/privacy.json b/case/pages/privacy/privacy.json new file mode 100644 index 0000000..0c7e114 --- /dev/null +++ b/case/pages/privacy/privacy.json @@ -0,0 +1,6 @@ +{ + "usingComponents": { + "navBar":"../../../components/navBar/navBar" + }, + "navigationStyle":"custom" +} \ No newline at end of file diff --git a/case/pages/privacy/privacy.wxml b/case/pages/privacy/privacy.wxml new file mode 100644 index 0000000..7b707c7 --- /dev/null +++ b/case/pages/privacy/privacy.wxml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/case/pages/privacy/privacy.wxss b/case/pages/privacy/privacy.wxss new file mode 100644 index 0000000..2489af9 --- /dev/null +++ b/case/pages/privacy/privacy.wxss @@ -0,0 +1,15 @@ +/* case/pages/privacy/privacy.wxss */ +/* case/pages/agreement/agreement.wxss */ +.page{ + background: #fff; + flex:1; + display: flex; + position: relative; + flex-direction: column; + margin-top: 172rpx; +} +.con{ + flex:1; + padding:10rpx 32rpx 0; + margin-bottom: 20rpx; +} \ No newline at end of file diff --git a/case/pages/pwdLogin/pwdLogin.js b/case/pages/pwdLogin/pwdLogin.js new file mode 100644 index 0000000..d66afa2 --- /dev/null +++ b/case/pages/pwdLogin/pwdLogin.js @@ -0,0 +1,255 @@ +// case/pages/pwdLogin/pwdLogin.js +const app = getApp() +import {phoneLogin,getCaptcha,pwdLogin} from "../../../api/api" +import {auth} from "../../../api/auth.js" +import {throttle} from "../../../utils/util" +import {base64src} from "../../../utils/base64ToImg" +let urlHost=app.hostConfig().agreehost; + +Page({ + + /** + * 页面的初始数据 + */ + data: { + checked:false, + mobile:'', + pwd:'', + loginDevice:5, + showSuccess:false, + redirecUrl:'', + imgCode:'', + captchaUuid:'', + captchaCode:'', + }, + inputChange(e){ + this.setData({ + [e.target.dataset.id]: e.detail.value + }); + }, + goAgreement:throttle(function(event){ + app.method.navigateTo({ + url:"/case/pages/privacy/privacy" + }) + }), + goRegister:throttle(function(){ + app.method.navigateTo({ + url:'/case/pages/register/register' + }) + }), + handleGetCaptcha(){ + getCaptcha().then(res=>{ + base64src(res.captchaBase64Image,(img)=>{ + this.setData({ + imgCode:img, + captchaUuid:res.captchaUuid + }) + }) + }) + }, + handlePwdLogin:throttle(function(){ + let {mobile,pwd,loginDevice,captchaUuid,captchaCode,checked}=this.data; + if(!checked){ + wx.showToast({ + title: '请同意《用户服务协议》!', + icon:'none' + }) + return false + }; + if(!captchaCode){ + wx.showToast({ + title: '请输入图形验证码', + icon:'none' + }) + return false + } + if(!pwd){ + wx.showToast({ + title: '请输入密码', + icon:'none' + }) + return false + } + pwdLogin({ + mobile, + pwd, + loginDevice, + captchaUuid, + captchaCode + + }).then(data=>{ + const { envVersion } = wx.getAccountInfoSync().miniProgram; + let token='' + if(envVersion=="develop" || envVersion=="trial"){ + token="DEV_CASE_TOKEN" + }else{ + token="PROD_CASE_TOKEN" + } + wx.setStorageSync(token, data.token); + let url=this.data.redirectUrl?this.data.redirectUrl:'/pages/index/index'; + + if(url.indexOf('login')!=-1 || url.indexOf('mobileLogin')!=-1){ + wx.reLaunch({ + url:'/pages/index/index' + }) + }else{ + wx.reLaunch({ + url:url + }) + } + }).catch(error=>{ + if(error.code==10007){ + this.setData({ + showSuccess:true, + message:error.msg, + mobile:error.data + }) + }else{ + this.handleGetCaptcha(); + } + }) + }), + getPhoneNumber:throttle(function(e) { + if (e.detail.errMsg == 'getPhoneNumber:ok'){ + auth().then(res => { + phoneLogin('wx415cbcf96f4a3b27',{ + code:e.detail.code + }).then((data)=>{ + const { envVersion } = wx.getAccountInfoSync().miniProgram; + let token='' + if(envVersion=="develop" || envVersion=="trial"){ + token="DEV_CASE_TOKEN" + }else{ + token="PROD_CASE_TOKEN" + } + wx.setStorageSync(token, data.token); + let url=this.data.redirectUrl?this.data.redirectUrl:'/pages/index/index'; + + if(url.indexOf('login')!=-1 || url.indexOf('mobileLogin')!=-1){ + wx.reLaunch({ + url:'/pages/index/index' + }) + }else{ + wx.reLaunch({ + url:url + }) + } + }).catch(error=>{ + if(error.code==10007){ + this.setData({ + showSuccess:true, + message:error.msg, + mobile:error.data + }) + } + }) + }); + }else if(e.detail.errMsg == 'getPhoneNumber:fail user deny'){ + console.log('您拒绝手机号授权') + }else{ + wx.showToast({ + title: '手机号授权失败', + icon:'none' + }) + } + }), + onConfirmSuccess(){ + let {mobile}=this.data; + this.setData({ + showSuccess:false + }) + app.method.navigateTo({ + url:'/case/pages/improveInfo/improveInfo?mobile='+mobile + }) + }, + onCancelSuccess(){ + this.setData({ + showSuccess:false + }) + }, + goMobile(){ + let url='/case/pages/mobileLogin/mobileLogin'; + let redirectUrl=this.data.redirectUrl?this.data.redirectUrl:'/pages/index/index'; + app.method.navigateTo({ + url: url+"?redirectUrl="+encodeURIComponent(redirectUrl) + }) + }, + + handleAgree(){ + if(!this.data.checked){ + wx.showToast({ + title: '请同意《用户服务协议》!', + icon:'none' + }) + return false + }; + }, + onChange(event) { + this.setData({ + checked: event.detail, + }) + }, + /** + * 生命周期函数--监听页面加载 + */ + onLoad(options) { + if(options.redirectUrl){ + if(options.redirectUrl){ + this.setData({ + redirectUrl:decodeURIComponent(options.redirectUrl) + }) + }; + //console.log(decodeURIComponent(options.redirectUrl)) + + } + }, + + /** + * 生命周期函数--监听页面初次渲染完成 + */ + onReady() { + + }, + + /** + * 生命周期函数--监听页面显示 + */ + onShow() { + this.handleGetCaptcha(); + }, + + /** + * 生命周期函数--监听页面隐藏 + */ + onHide() { + + }, + + /** + * 生命周期函数--监听页面卸载 + */ + onUnload() { + + }, + + /** + * 页面相关事件处理函数--监听用户下拉动作 + */ + onPullDownRefresh() { + + }, + + /** + * 页面上拉触底事件的处理函数 + */ + onReachBottom() { + + }, + + /** + * 用户点击右上角分享 + */ + // onShareAppMessage() { + + // } +}) \ No newline at end of file diff --git a/case/pages/pwdLogin/pwdLogin.json b/case/pages/pwdLogin/pwdLogin.json new file mode 100644 index 0000000..e9b3339 --- /dev/null +++ b/case/pages/pwdLogin/pwdLogin.json @@ -0,0 +1,10 @@ +{ + "usingComponents": { + "dialog":"../../../components/dialog/dialog", + "navBar":"../../../components/navBar/navBar", + "van-checkbox": "@vant/weapp/checkbox/index", + "van-checkbox-group": "@vant/weapp/checkbox-group/index" + }, + "navigationStyle":"custom", + "navigationBarTitleText": "登录" +} \ No newline at end of file diff --git a/case/pages/pwdLogin/pwdLogin.wxml b/case/pages/pwdLogin/pwdLogin.wxml new file mode 100644 index 0000000..fb3b681 --- /dev/null +++ b/case/pages/pwdLogin/pwdLogin.wxml @@ -0,0 +1,39 @@ + + + + + + 欢迎登录 + 人工肝病例登记系统 + + + + + + + + + + + + + + + 登录 + + + 手机验证码登录 + 注册 + + + - 手机号快捷登录 - + + 手机号快捷登录 + + + + 我已阅读并同意《隐私协议》 + + + + diff --git a/case/pages/pwdLogin/pwdLogin.wxss b/case/pages/pwdLogin/pwdLogin.wxss new file mode 100644 index 0000000..291c7de --- /dev/null +++ b/case/pages/pwdLogin/pwdLogin.wxss @@ -0,0 +1,120 @@ +/* case/pages/pwdLogin/pwdLogin.wxss */ +page{ + background-image: linear-gradient( #cdf0ff,#fff 40%); +} +.myclass{ + background-color: transparent!important; +} +.page{ + position: relative; + flex:1; + display: flex; + position: relative; + flex-direction: column; + margin-top: 172rpx; +} +.logobox{display: flex;flex-direction: column;margin:40rpx 47rpx 0;position: relative;} +.logobox .desc{ +position: relative; +z-index: 1; + margin-top: 22rpx; + font-size: 48rpx; +font-weight: 500; +color: rgba(0,0,0,0.85); +} +.logobg{ + left:90rpx; + top:76rpx; + z-index:0; +position: absolute; + width:228rpx; + height:22rpx; +} +.iptbox{ + margin:70rpx 47rpx 0; +} +.buttonbox{ + margin:64rpx 47rpx 0; +} +.btn{ + display: flex; + justify-content: center; + align-items: center; + height: 88rpx; + background: linear-gradient(90deg, #377FF7 0%, #51AAFF 100%); + border-radius: 44rpx; + font-size: 32rpx; +font-weight: 500; +color: #FFFFFF; +} +.iptcell { + width: 100%; + height: 112rpx; + align-items: center; + white-space: nowrap; + display: flex; + justify-content: space-between; + border-bottom: 1rpx solid rgba(0, 0, 0, 0.1); +} +.imgCode{ + width:200rpx; + height:75.6rpx; +} +.iptcell .code { + width: 400rpx; + margin-left: 0; + } +.type{ + margin:24rpx 47rpx 0; + display: flex; + justify-content: space-between; + align-items: center; +} +.mobileLogin,.zhuce{ + font-size: 28rpx; + font-family: PingFangSC, PingFang SC; + font-weight: 400; + color: rgba(0,0,0,0.65); +} +.wechatbox{ + flex:1; + position: relative; + margin:44rpx 47rpx 100rpx; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} +.chatmsg{ + font-size: 28rpx; + +font-weight: 400; +color: rgba(0,0,0,0.45); +} +.wecaht{ + margin-top: 42rpx; + width:88rpx; + height:88rpx; +} + +.agree{ + position: absolute; + bottom:40rpx; + margin:54rpx 47rpx 0; + display: flex; + align-items: center; + font-size: 28rpx; +font-weight: 400; +color: rgba(0,0,0,0.65); +} +.link{ + color:#3881F7 +} +.mobileAuth{ + top:50%; + left:50%; + margin-top: -10rpx; + transform: translate(-50%); + opacity: 0; + position: absolute; +} diff --git a/case/pages/register/register.js b/case/pages/register/register.js new file mode 100644 index 0000000..6e883c8 --- /dev/null +++ b/case/pages/register/register.js @@ -0,0 +1,533 @@ +// case/pages/register/register.js +import {throttle} from "../../../utils/util" +import {hostConfig} from "../../../utils/config" +import {getArea,getHospital,getOfficeList,getPosition,smsRegister,sendSms,getCaptcha} from "../../../api/api" +import {base64src} from "../../../utils/base64ToImg" +const host=hostConfig().host; +const app=getApp(); +Page({ + /** + * 页面的初始数据 + */ + data: { + showImprove:false, + showSuccess:false, + img_host:app.hostConfig().imghost, + showArea:false, + areaColumns:[ + { + values: [1,2], + className: 'column1', + }, + { + values: [3,4], + className: 'column2', + defaultIndex: 0 + }, + ], + hospitalColumns:[], + officeColumns:[], + positionColumns:[], + showHospital:false, + showOffice:false, + showPosition:false, + showNext:false, + fileList:[], + disabled:false, + county_id:'', + hospital_name:'', + hospital_uuid:'', + mobile:'', + name:'', + office_name:'', + office_uuid:'', + password:'', + position_uuid:'', + certificate:'', + certificate_img:'', + codeActive:true, + msg:'获取验证码', + captchaUuid:'', + captchaCode:'', + loginDevice:5, + imgCode:'', + sms:'', + timer:null, + time:59, + smsType:1, + showCrop:false, + width: 250, //宽度 + height: 250, //高度 + max_width: 300, + max_height: 300, + disable_rotate: true, //是否禁用旋转 + disable_ratio: false, //锁定比例 + limit_move: true, //是否限制移动 + }, + closeCrop(){ + this.setData({ + showCrop:false + }) + }, + submit() { + this.setData({ + showCrop:false + }) + this.cropper.getImg((obj) => { + this.uploadImg(obj) + }); + }, + cropperload(e) { + console.log('cropper加载完成'); + }, + initCrop(options){ + + this.cropper = this.selectComponent("#image-cropper"); + + + + if(options.imgSrc){ + this.setData({ + src: options.imgSrc + }); + } + + // if(!options.imgSrc){ + // this.cropper.upload(); //上传图片 + // } + }, + loadimage(e) { + this.cropper.imgReset(); + }, + onConfirmSuccess(){ + this.setData({ + showSuccess:false + }) + app.method.navigateTo({ + url:"/case/pages/mobileLogin/mobileLogin" + }) + }, + opeArea(){ + this.setData({ + showArea:true + }) + }, + openOffice(){ + this.setData({ + showOffice:true, + }) + }, + openPosition(){ + this.setData({ + showPosition:true, + }) + }, + onChange(e){ + const {value} = e.detail; + + const {id}=e.currentTarget.dataset; + // console.log(value,id) + this.setData({ + [id]: value + }); + }, + onChangeArea(event){ + const { picker, value, index } = event.detail; + const provinceId=value[0].id; + this.handleGetArea(provinceId) + }, + confirmArea(event){ + const {value} = event.detail; + const countyId=value[1].id; + this.setData({ + county_id:countyId, + showArea:false + }); + this.handleGetHospital(countyId) + }, + cancelArea(){ + this.setData({ + showArea:false + }) + }, + confirmHospital(event){ + let {value}=event.detail; + this.setData({ + showHospital:false, + hospital_uuid:value.uuid, + hospital_name:value.name + }) + }, + cancelHospital(){ + this.setData({ + showHospital:false + }) + }, + confirmOffice(event){ + let {value}=event.detail; + + this.setData({ + office_name:value.officeName, + office_uuid:value.officeUuid, + showOffice:false + }) + }, + cancelOffice(){ + this.setData({ + showOffice:false + }) + }, + confirmPosition(event){ + let {value}=event.detail; + this.setData({ + position_uuid:value.uuid, + position_name:value.name, + showPosition:false + }) + }, + cancelPosition(){ + this.setData({ + showPosition:false + }) + }, + goNext(){ + let {mobile,sms,password,captchaCode}=this.data; + if(!/^1[3456789]\d{9}$/.test(mobile)){ + wx.showToast({ + title: `请输入有效的手机号码!`, + icon: 'none', + }); + return false; + }; + if(!captchaCode){ + wx.showToast({ + title: `请输入图形验证码`, + icon: 'none', + }); + return false; + } + if(!sms){ + wx.showToast({ + title: `请输入短信验证码`, + icon: 'none', + }); + return false; + } + if(!(/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,16}$/.test(password))){ + wx.showToast({ + title: `请输入6-16位字母、数字组合密码!`, + icon: 'none', + }); + return false; + } + this.setData({ + showNext:true + }) + }, + showNext(){ + this.setData({ + showNext:false + }) + }, + handleGetArea(id){ + getArea({ + parent:id + }).then(res=>{ + if(id){ + let obj='areaColumns[1].values'; + this.setData({ + [obj]:res + }) + }else{ + let obj='areaColumns[0].values'; + this.setData({ + [obj]:res + }) + this.handleGetArea(res[0].id) + } + + }).catch(error=>{ + console.log(error) + }) + }, + handleGetHospital(countyId){ + getHospital(countyId).then(res=>{ + this.setData({ + showHospital:true, + hospitalColumns:res.concat({uuid:1,name:'其他医院'}) + }) + }) + }, + handleGetOffice(){ + getOfficeList({}).then(res=>{ + this.setData({ + officeColumns:res + }) + }).catch(error=>{ + console.log(error) + }) + }, + handleGetPosition(){ + getPosition().then(res=>{ + this.setData({ + positionColumns:res + }) + }) + }, + handleSmsRegister:throttle(function(){ + let {mobile,name,sms,password,captchaCode,hospital_uuid,hospital_name,office_name,office_uuid,position_uuid,certificate,certificate_img,county_id}=this.data; + if(!/^1[3456789]\d{9}$/.test(mobile)){ + wx.showToast({ + title: `请输入有效的手机号码!`, + icon: 'none', + }); + return false; + }; + if(!captchaCode){ + wx.showToast({ + title: `请输入图形验证码`, + icon: 'none', + }); + return false; + } + if(!sms){ + wx.showToast({ + title: `请输入短信验证码`, + icon: 'none', + }); + return false; + } + if(!(/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,16}$/.test(password))){ + wx.showToast({ + title: `请输入6-16位字母、数字组合密码!`, + icon: 'none', + }); + return false; + } + if(!(/^[\u4E00-\u9FA5·]{2,20}$/.test(name))){ + wx.showToast({ + title: `请输入您的真实姓名`, + icon: 'none', + }); + return false; + } + if(!hospital_uuid){ + wx.showToast({ + title: `请选择医院`, + icon: 'none', + }); + return false; + } + if(!office_uuid){ + wx.showToast({ + title: `请选择科室`, + icon: 'none', + }); + return false; + } + if(!position_uuid){ + wx.showToast({ + title: `请选择职称`, + icon: 'none', + }); + return false; + } + if(!certificate_img){ + wx.showToast({ + title: `请上传执业医师资格证或工作胸牌`, + icon: 'none', + }); + return false; + } + smsRegister({ + mobile, + name, + sms, + password, + hospital_uuid, + hospital_name, + office_name, + office_uuid, + county_id, + position_uuid, + certificate, + certificate_img + }).then(res=>{ + this.setData({ + showSuccess:true, + }) + + + }) + }), + handleThrottle:throttle(function(){ + this.getCode() + }), + handleGetCaptcha(){ + getCaptcha().then(res=>{ + base64src(res.captchaBase64Image,(img)=>{ + this.setData({ + imgCode:img, + captchaUuid:res.captchaUuid + }) + }) + }) + }, + getCode(){ + if(this.data.disabled)return; + let {mobile,captchaCode,captchaUuid,loginDevice,smsType}=this.data; + if(!/^1[3456789]\d{9}$/.test(mobile)){ + wx.showToast({ + title: `请输入有效的手机号码!`, + icon: 'none', + }); + return false; + }; + if(!captchaCode){ + wx.showToast({ + title: `图形验证码不能为空`, + icon: 'none', + }); + return false; + } + sendSms({ + mobile, + captchaCode, + captchaUuid, + smsType, + loginDevice + }).then((res)=>{ + let timer=setInterval(() => { + if (this.data.time == 0) { + clearInterval(this.data.timer); + this.setData({ + time:59, + msg:'重新获取验证码', + disabled:false, + codeActive:true, + }) + } else { + let msg= this.data.time + " s 后重新发送"; + this.setData({ + disabled:true, + msg:msg, + codeActive:false, + }) + let time=this.data.time--; + this.setData({ + msg:time + " s 后重新发送" + }) + } + }, 1000); + + this.setData({ + timer:timer + }) + }).catch(()=>{ + this.handleGetCaptcha(); + }); + + }, + uploadImg(file){ + let THIS=this; + wx.showLoading({ + title: '图片上传中', + mask: true + }) + + // 当设置 mutiple 为 true 时, file 为数组格式,否则为对象格式 + wx.uploadFile({ + url: host+'/user/uoloadImg', // 仅为示例,非真实的接口地址 + filePath: file.url, + name: 'file', + formData: file, + success(res) { + let result=JSON.parse(res.data); + wx.hideLoading(); + if(result.code==200){ + THIS.setData({ + certificate_img:result.data + }); + }else{ + wx.showToast({ + title:result.msg, + icon:'none' + }) + } + + }, + complete(res){ + console.log(res) + } + }); + }, + afterRead(event) { + const { + file + } = event.detail; + this.setData({ + showCrop:true, + src:file.url + }) + + }, + /** + * 生命周期函数--监听页面加载 + */ + onLoad(options) { + this.handleGetCaptcha(); + this.handleGetArea(''); + this.handleGetOffice(); + this.handleGetPosition(); + this.initCrop(options); + }, + + /** + * 生命周期函数--监听页面初次渲染完成 + */ + onReady() { + + }, + + /** + * 生命周期函数--监听页面显示 + */ + onShow() { + + }, + + /** + * 生命周期函数--监听页面隐藏 + */ + onHide() { + + }, + + /** + * 生命周期函数--监听页面卸载 + */ + onUnload() { + + }, + + /** + * 页面相关事件处理函数--监听用户下拉动作 + */ + onPullDownRefresh() { + + }, + + /** + * 页面上拉触底事件的处理函数 + */ + onReachBottom() { + + }, + + /** + * 用户点击右上角分享 + */ + // onShareAppMessage() { + + // } +}) \ No newline at end of file diff --git a/case/pages/register/register.json b/case/pages/register/register.json new file mode 100644 index 0000000..ca74397 --- /dev/null +++ b/case/pages/register/register.json @@ -0,0 +1,17 @@ +{ + "usingComponents": { + "van-overlay": "@vant/weapp/overlay/index", + "image-cropper": "../../../components/image-cropper/image-cropper", + "dialog":"../../../components/dialog/dialog", + "van-popup": "@vant/weapp/popup/index", + "van-picker": "@vant/weapp/picker/index", + "van-field": "@vant/weapp/field/index", + "van-cell": "@vant/weapp/cell/index", + "van-button": "@vant/weapp/button/index", + "van-image": "@vant/weapp/image/index", + "van-uploader": "@vant/weapp/uploader/index", + "van-cell-group": "@vant/weapp/cell-group/index" + + }, + "navigationBarTitleText": "注册" +} \ No newline at end of file diff --git a/case/pages/register/register.wxml b/case/pages/register/register.wxml new file mode 100644 index 0000000..52bb5aa --- /dev/null +++ b/case/pages/register/register.wxml @@ -0,0 +1,102 @@ + + + + + + + + + + + {{msg}} + + + + + 下一步 + + + + 若您有任何疑问或需要我们协助,请与您的小助手联系或直接微信联系igandan1000 + + + + + + + + + + + + + + + 提交 + + + 上一步 + + + + + + + + + + + + + + + + + + + + + 返回 + 确定 + + \ No newline at end of file diff --git a/case/pages/register/register.wxss b/case/pages/register/register.wxss new file mode 100644 index 0000000..881c263 --- /dev/null +++ b/case/pages/register/register.wxss @@ -0,0 +1,91 @@ +/* case/pages/register/register.wxss */ +.next{ + margin: 40rpx 20rpx!important; +} +.prev{ + margin:-20rpx 20rpx 0!important ; +} +.van-field__label{ + white-space: nowrap; +} +.custom-class .van-cell:last-child{ + background-color: red; +} +.van-cell__title{ + white-space: nowrap; + display: flex; + color: #646566; + align-items: center; +} +.van-image{ + margin-top: 10rpx; +} +.van-cell{ + display: flex; + align-items: center; +} +.van-field__label,.van-cell__title{ + position: relative; +} +.van-field__label::after,.cert .van-cell__title::after{ + content: "*"; + color:red; + position: absolute; + top:12rpx; + right:-14rpx; +} +.cert .van-cell__title::after{ + top:4rpx; +} +.myclass .van-field__label::after{ + content: ""; +} +.upload{ + opacity:0; + position: absolute; + z-index:99; +} +.cert .van-cell{ + overflow: hidden; +} +.van-button{ + border-radius: 10rpx!important; +} +.imgCode{ + width:200rpx; + height:75.6rpx; +} +.custom-class{ + min-height:43.5px !important; +} +.van-field__label{ + color:#333!important; + padding:12rpx 0; + font-size: 15px!important; +} +.van-cell__title{ + color:#333!important; + font-size: 15px!important; +} +.buttonbox{ + position: absolute; + z-index:99; + display: flex; + width:600rpx; + left:50%; + transform: translateX(-50%); + justify-content: space-between; + bottom:50rpx; + } + .van-button--primary{ + background: linear-gradient(90deg, #377FF7 0%, #51AAFF 100%)!important; + border-color:#377FF7!important + } + .tip{ + margin:0 32rpx; + font-size: 28rpx; + color:#999; + } + .red{ + color:red; + } \ No newline at end of file diff --git a/case/pages/signcanvas/signcanvas.js b/case/pages/signcanvas/signcanvas.js new file mode 100644 index 0000000..b43643d --- /dev/null +++ b/case/pages/signcanvas/signcanvas.js @@ -0,0 +1,169 @@ +import { getOssSign} from '../../../api/api' +import { FileUtil } from '../../../utils/fileutil' +const app = getApp() +Page({ + data: { + height: app.globalData.height, + ctx: null, + canvas: null, + txt_show: true, + template:{ + width: '676rpx', + height: '414rpx', + views: [ + {} + ], + }, + }, + onReady() { + const query = wx.createSelectorQuery() + query.select('#sign_panels') + .fields({ node: true, size: true }) + .exec((res) => { + const canvas = res[0].node + const ctx = canvas.getContext('2d') + ctx.lineWidth = 5; + // const dpr = wx.getSystemInfoSync().pixelRatio + + canvas.width = res[0].width + canvas.height = res[0].height + ctx.scale(1, 1) + console.log(ctx); + this.setData({ + ctx: ctx, + canvas: canvas + }); + + console.log("onReady onReady start") + console.log(ctx) + console.log(canvas) + console.log("onReady onReady end") + }) + }, + ontouchmove(e){ + console.log("ontouchmove ontouchmove ontouchmove"); + console.log(e); + this.data.ctx.lineWidth = 5; + this.data.ctx.lineCap="round"; + this.data.ctx.lineJoin ="round"; + this.data.ctx.lineTo(e.touches[0].x,e.touches[0].y); + this.data.ctx.stroke(); + }, + ontouchstart(e){ + console.log("ontouchstart ontouchstart ontouchstart") + this.setData({ + txt_show: false + }); + console.log(this.data.ctx); + this.data.ctx.beginPath(); + this.data.ctx.moveTo(e.touches[0].x,e.touches[0].y); + }, + toClear() { + this.setData({ + txt_show: true + }); + this.data.ctx.clearRect(0,0,this.data.canvas.width,this.data.canvas.height) + }, + rotate(){ + let ctx = this.data.ctx; + ctx.rotate(90); + }, + toSave(event) { + let _this = this; + let base64Img = _this.data.canvas.toDataURL(); + console.log("sign: ", base64Img); + wx.canvasToTempFilePath({ + canvasId: "sign_panels", + canvas: _this.data.canvas, + x:0, + y:0, + success(res) { + console.log(res.tempFilePath) + let filePath = res.tempFilePath; + console.log(_this.data.canvas) + console.log(_this.data.canvas.width) + console.log(_this.data.canvas.height) + let cs = (_this.data.canvas.height - _this.data.canvas.width)/2; + _this.setData({ + "template.width": _this.data.canvas.height+"rpx", + "template.height": _this.data.canvas.width+"rpx", + "template.views[0].css.width": _this.data.canvas.width+"rpx", + "template.views[0].css.left": cs+"rpx", + "template.views[0].css.top": "-"+cs+"rpx", + "template.views[0].url": filePath, + "template.views[0].type": "image", + "template.views[0].css.rotate": "-90", + }) + // _this.doUploadFile(event, 2, filePath); + } + }) + }, + onImgOK(e){ + console.log("onImgOK onImgOK onImgOK onImgOK"); + console.log(e.detail.path); + let txt_show = this.data.txt_show; + if(!txt_show){ + this.doUploadFile(e, 2, e.detail.path); + } + }, + doUploadFile(event, scene, filePath) { + console.log("index douploadFIle: ", event); + console.log("scene: ", scene); + let _this = this; + getOssSign(2).then(response => { + console.log(response); + console.log("filePath: ", filePath); + const filename = FileUtil.UUID()+".png"; + const host = response.host; + const signature = response.signature; + const ossAccessKeyId = response.accessid; + const policy = response.policy; + const key = response.dir+filename; + wx.uploadFile({ + url: host, // 开发者服务器的URL。 + filePath: filePath, + name: 'file', // 必须填file。 + formData: { + key, + policy, + OSSAccessKeyId: ossAccessKeyId, + signature, + }, + success: (res) => { + console.log("upload: ", res); + if (res.statusCode === 204) { + wx.showToast({title: '上传成功'}) + let url = host+"/"+key; + console.log(url); + let pages = getCurrentPages(); + let prevPage = pages[pages.length - 2] + prevPage.setData({ //setData给上个页面的data进行赋值 + signImg:url + }) + wx.navigateBack(); + //this.handleAddSign(url) + } + + }, + fail: err => { + console.log(err); + wx.showToast({ + title: '上传失败', + icon: "error" + }) + } + }); + }).catch(error => { + + if(error.code==30007){ + wx.showToast({ + title: '您未登录', + icon:'none' + }) + app.method.navigateTo({ + url: '/case/pages/mobileLogin/mobileLogin', + }) + } + }) + } +}) diff --git a/case/pages/signcanvas/signcanvas.json b/case/pages/signcanvas/signcanvas.json new file mode 100644 index 0000000..85d0a17 --- /dev/null +++ b/case/pages/signcanvas/signcanvas.json @@ -0,0 +1,10 @@ +{ + "component": true, + "usingComponents": { + "navBar":"../../../components/navBar/navBar", + "van-button": "@vant/weapp/button/index", + "painter":"../../../components/painter/painter" + }, + "disableScroll": true, + "navigationStyle":"custom" +} \ No newline at end of file diff --git a/case/pages/signcanvas/signcanvas.wxml b/case/pages/signcanvas/signcanvas.wxml new file mode 100644 index 0000000..fd709ad --- /dev/null +++ b/case/pages/signcanvas/signcanvas.wxml @@ -0,0 +1,11 @@ + + + + 签名面板 + + + 清空 + 确认 + + + \ No newline at end of file diff --git a/case/pages/signcanvas/signcanvas.wxss b/case/pages/signcanvas/signcanvas.wxss new file mode 100644 index 0000000..f2c4828 --- /dev/null +++ b/case/pages/signcanvas/signcanvas.wxss @@ -0,0 +1,23 @@ +.container{ + height: 100vh; + margin-top: 172rpx; + background: #FAFAFA; +} +.title{ + font-size: 40rpx; + width: 160rpx; + height: 50rpx; + position:absolute; + left: calc(50vw - 80rpx); + top: calc(50vh - 160rpx); + transform: rotate(90deg); +} +.btn_group{ + transform: rotate(90deg); + width: 500rpx; + position: absolute; + left: calc(-215rpx + 50rpx);/* 和按钮的高度有关 (width/2 - 按钮高度/2) */ + bottom: calc(215rpx + 100rpx);/* 和按钮的高度有关 (width/2 - 按钮高度/2) */ + display: flex; + justify-content: space-between; +} \ No newline at end of file diff --git a/case/utils/utils.js b/case/utils/utils.js new file mode 100644 index 0000000..ac624aa --- /dev/null +++ b/case/utils/utils.js @@ -0,0 +1,223 @@ +function formatTime(date) { + var year = date.getFullYear() + var month = date.getMonth() + 1 + var day = date.getDate() + + var hour = date.getHours() + var minute = date.getMinutes() + var second = date.getSeconds() + + + return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') + } + + function formatNumber(n) { + n = n.toString() + return n[1] ? n : '0' + n + } + + // 公用的修改颜色 + function changeColor(e, _this) { + let tempData = {}; + tempData[e.target.dataset.color] = e.detail.value; + _this.setData({ + ...tempData, + eraser: false, + }); + } + + // 公用的修改画笔宽度 + function changeWidth(e, _this, canvasHeight, pageType) { + let c = {}; + if (pageType === 1) { + c.canvasHeight = canvasHeight; + } else { + c.canvasHeightLen = canvasHeight; + } + _this.setData({ + w: e.detail.value, + eraser: false, + ...c, + }) + } + + // 点击按钮触发的事件 + function tapBtn(e, _this, pageType) { + let btnType = e.target.dataset.type; + + let c = {}; + + switch (btnType) { + // 画笔宽度 + case 'width': + if (pageType === 1) { + c.canvasHeight = (!_this.data.width) ? 130 + _this.data.w : 50 + } else if (pageType === 2) { + c.canvasHeightLen = (!_this.data.width) ? Math.min(_this.data.canvasHeight, _this.data.windowHeight - _this.data.w - 130) : 0; + } else if (pageType === 3) { + c.canvasHeight = 130; + } + _this.setData({ + width: !_this.data.width, + color: false, + clear: false, + ...c, + }); + return; + // 画笔颜色 + case 'color': + if (pageType === 1) { + c.canvasHeight = (!_this.data.color) ? 205 + _this.data.w : 50; + } else if (pageType === 2) { + c.canvasHeightLen = (!_this.data.color) ? Math.min(_this.data.canvasHeight, _this.data.windowHeight - _this.data.w - 205) : 0; + } + _this.setData({ + width: false, + color: !_this.data.color, + clear: false, + ...c, + }); + return; + // 清空按钮 + case 'clear': + if (pageType === 1) { + c.canvasHeight = (!_this.data.clear) ? 120 + _this.data.w : 50; + } else if (pageType === 2) { + c.canvasHeightLen = (!_this.data.clear) ? Math.min(_this.data.canvasHeight, _this.data.windowHeight - _this.data.w - 120) : 0; + } + _this.setData({ + width: false, + color: false, + clear: !_this.data.clear, + ...c, + }) + return; + // 保存 + case 'save': + saveImg(_this, pageType); + return; + default: + return; + } + } + + function saveImg(_this, pageType) { + let c = {}; + if (pageType === 1) { + c.canvasHeight = 50; + } else if (pageType === 2) { + c.canvasHeightLen = 0; + } + // 查看授权 + if (!_this.data.scope) { + wx.showModal({ + title: '需要授权', + content: '保存图片需要获取您的授权', + success: (res) => { + if (res.confirm) { + wx.openSetting({ + success: (res) => { + if (res.authSetting['scope.writePhotosAlbum']) { + _this.setData({ + scope: true, + }) + } + } + }); + } + } + }) + } + // 已经获得授权且不在保存中 + if (_this.data.scope && !_this.data.saving) { + wx.showLoading({ + title: '保存中', + mask: true, + }) + // 关闭所有的操作栏 + _this.setData({ + width: false, + color: false, + clear: false, + saving: true, + ...c, + }) + + if (pageType === 2) { + /* + * 对于涂鸦照片,一共分为四步: + * 1、将画的内容先保存出来 + * 2、然后再将照片先画在canvas上 + * 3、将画的内容覆盖的画在canvas上 + * 4、最终保存 + */ + wx.canvasToTempFilePath({ + canvasId: 'myCanvas', + success: function (res) { + // 把单纯用户画的内容存好了 + let src = res.tempFilePath; + let ctx = wx.createCanvasContext('myCanvas'); + // 照片 + ctx.drawImage(_this.data.background, 0, 0, _this.data.canvasWidth, _this.data.canvasHeight); + // 覆盖上画的内容 + ctx.drawImage(src, 0, 0, _this.data.canvasWidth, _this.data.canvasHeight); + ctx.draw(); + + _canvaseSaveToImg(_this); + } + }); + } else { + _canvaseSaveToImg(_this); + } + } + } + + function _canvaseSaveToImg(_this) { + // 调用微信canvas存为图片 + wx.canvasToTempFilePath({ + canvasId: 'myCanvas', + success: function (res) { + // 转图片成功,继续调用存储相册接口 + wx.saveImageToPhotosAlbum({ + filePath: res.tempFilePath, + // 存储成功 + success: function (r) { + wx.hideLoading(); + wx.showToast({ + title: '保存成功', + }) + _this.setData({ + saving: false, + }) + }, + // 失败弹窗 + fail: function (res) { + wx.hideLoading(); + wx.showToast({ + title: '保存失败', + icon: 'loading', + }) + _this.setData({ + saving: false, + }) + } + }) + }, + fail: function (res) { + // canvas转图片失败 + wx.hideLoading(); + wx.showToast({ + icon: 'loading', + title: '保存失败', + }) + } + }) + } + + module.exports = { + formatTime: formatTime, + changeColor: changeColor, + changeWidth: changeWidth, + tapBtn: tapBtn, + } + \ No newline at end of file diff --git a/components/dialog/dialog.js b/components/dialog/dialog.js new file mode 100644 index 0000000..4360c6b --- /dev/null +++ b/components/dialog/dialog.js @@ -0,0 +1,91 @@ +// components/dialog/dialog.js +Component({ + + /** + * 组件的属性列表 + */ + properties: { + showDialog:{ + type: Boolean, + value:false, + observer(newval) { + this.setData({ + showDialog: newval + }); + }, + }, + message:{ + type: String, + value: '22', + observer(newval) { + this.setData({ + message: newval, + }); + }, + }, + showTip:{ + type: Boolean, + value: false, + observer(newval) { + this.setData({ + showTip: newval + }); + }, + }, + showCancel:{ + type: Boolean, + value: true, + observer(newval) { + this.setData({ + showCancel: newval, + }); + }, + }, + title:{ + type: String, + value: '温馨提示', + observer(newval) { + this.setData({ + title: newval, + }); + }, + }, + cancelText:{ + type: String, + value: '取消', + observer(newval) { + this.setData({ + cancelText: newval, + }); + }, + }, + confirmText:{ + type: String, + value: '确定', + observer(newval) { + this.setData({ + confirmText: newval, + }); + }, + }, + }, + + /** + * 组件的初始数据 + */ + data: { + + }, + + /** + * 组件的方法列表 + */ + methods: { + onConfirm(){ + this.triggerEvent("confirm") + }, + onCancel(){ + this.triggerEvent("cancel") + } + } +}) \ No newline at end of file diff --git a/components/dialog/dialog.json b/components/dialog/dialog.json new file mode 100644 index 0000000..809b0ff --- /dev/null +++ b/components/dialog/dialog.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "van-overlay": "@vant/weapp/overlay/index" + } +} \ No newline at end of file diff --git a/components/dialog/dialog.wxml b/components/dialog/dialog.wxml new file mode 100644 index 0000000..6215ee3 --- /dev/null +++ b/components/dialog/dialog.wxml @@ -0,0 +1,22 @@ + + + + + + {{title}} + 请修改完善后重新提交 + + + {{message}} + + + {{message}} + + + + {{cancelText}} + {{confirmText}} + + + + diff --git a/components/dialog/dialog.wxss b/components/dialog/dialog.wxss new file mode 100644 index 0000000..9f7ade0 --- /dev/null +++ b/components/dialog/dialog.wxss @@ -0,0 +1,77 @@ +/* components/dialog/dialog.wxss */ +.wrapper { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + } + .dialogbox{ + box-sizing: border-box; + padding:48rpx; + display: flex; + flex-direction: column; + align-items: center; + width: 630rpx; + border-radius: 16rpx; + background-color: #fff; + } + .title{ + height: 54rpx; + font-size: 36rpx; + font-weight: 500; + color: rgba(0,0,0,0.85); + line-height: 54rpx + } + .content{ + padding:32rpx 0 48rpx; + font-size: 32rpx; + font-weight: 400; + text-align: center; + color: rgba(0,0,0,0.65); + line-height: 48rpx; + width:100%; + } + .msg{ + word-break: break-all; + word-wrap: break-word; + } + .footer{ + width:100%; + display: flex; + justify-content: center; + align-items: center; + } + .cancelBtn{ + flex:1; + margin-right: 24rpx; + display: flex; + justify-content: center; + align-items: center; + +height: 88rpx; +background: #FFFFFF; +border-radius: 50rpx; +border: 2rpx solid rgba(0,0,0,0.15); +font-size: 32rpx; +font-weight: 500; +color: rgba(0,0,0,0.85); + } + .okBtn{ + color:#fff; + flex:1; + display: flex; + justify-content: center; + align-items: center; + + height: 88rpx; + font-size: 32rpx; +font-weight: 500; +color: #FFFFFF; + background: linear-gradient(90deg, #377FF7 0%, #51AAFF 100%); + border-radius: 50rpx; + } + .tip{ + font-size: 32rpx; +font-weight: 400; +color: #FF9C00; + } \ No newline at end of file diff --git a/components/image-cropper/image-cropper.js b/components/image-cropper/image-cropper.js new file mode 100644 index 0000000..06a6383 --- /dev/null +++ b/components/image-cropper/image-cropper.js @@ -0,0 +1,1122 @@ +Component({ + properties: { + /** + * 图片路径 + */ + 'imgSrc': { + type: String, + value:'' + }, + + /** + * 裁剪框高度 + */ + 'height': { + type: Number, + value: 200 + }, + /** + * 裁剪框宽度 + */ + 'width': { + type: Number, + value: 200 + }, + /** + * 裁剪框最小尺寸 + */ + 'min_width': { + type: Number, + value: 100 + }, + 'min_height': { + type: Number, + value: 100 + }, + /** + * 裁剪框最大尺寸 + */ + 'max_width': { + type: Number, + value: 300 + }, + 'max_height': { + type: Number, + value: 300 + }, + /** + * 裁剪框禁止拖动 + */ + 'disable_width': { + type: Boolean, + value: false + }, + 'disable_height': { + type: Boolean, + value: false + }, + /** + * 锁定裁剪框比例 + */ + 'disable_ratio': { + type: Boolean, + value: false + }, + /** + * 生成的图片尺寸相对剪裁框的比例 + */ + 'export_scale': { + type: Number, + value: 3 + }, + /** + * 生成的图片质量0-1 + */ + 'quality': { + type: Number, + value: 1 + }, + 'cut_top': { + type: Number, + value: null + }, + 'cut_left': { + type: Number, + value: null + }, + /** + * canvas上边距(不设置默认不显示) + */ + 'canvas_top': { + type: Number, + value: null + }, + /** + * canvas左边距(不设置默认不显示) + */ + 'canvas_left': { + type: Number, + value: null + }, + /** + * 图片宽度 + */ + 'img_width': { + type: null, + value: null + }, + /** + * 图片高度 + */ + 'img_height': { + type: null, + value: null + }, + /** + * 图片缩放比 + */ + 'scale': { + type: Number, + value: 1 + }, + /** + * 图片旋转角度 + */ + 'angle': { + type: Number, + value: 0 + }, + /** + * 最小缩放比 + */ + 'min_scale': { + type: Number, + value: 0.5 + }, + /** + * 最大缩放比 + */ + 'max_scale': { + type: Number, + value: 2 + }, + /** + * 是否禁用旋转 + */ + 'disable_rotate': { + type: Boolean, + value: false + }, + /** + * 是否限制移动范围(剪裁框只能在图片内) + */ + 'limit_move': { + type: Boolean, + value: false + } + }, + data: { + el: 'image-cropper', //暂时无用 + info: wx.getSystemInfoSync(), + MOVE_THROTTLE: null, //触摸移动节流settimeout + MOVE_THROTTLE_FLAG: true, //节流标识 + INIT_IMGWIDTH: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸) + INIT_IMGHEIGHT: 0, //图片设置尺寸,此值不变(记录最初设定的尺寸) + TIME_BG: null, //背景变暗延时函数 + TIME_CUT_CENTER: null, + _touch_img_relative: [{ + x: 0, + y: 0 + }], //鼠标和图片中心的相对位置 + _flag_cut_touch: false, //是否是拖动裁剪框 + _hypotenuse_length: 0, //双指触摸时斜边长度 + _flag_img_endtouch: false, //是否结束触摸 + _flag_bright: true, //背景是否亮 + _canvas_overflow: true, //canvas缩略图是否在屏幕外面 + _canvas_width: 200, + _canvas_height: 200, + origin_x: 0.5, //图片旋转中心 + origin_y: 0.5, //图片旋转中心 + _cut_animation: false, //是否开启图片和裁剪框过渡 + _img_top: wx.getSystemInfoSync().windowHeight / 2, //图片上边距 + _img_left: wx.getSystemInfoSync().windowWidth / 2, //图片左边距 + watch: { + //监听截取框宽高变化 + width(value, that) { + if (value < that.data.min_width) { + that.setData({ + width: that.data.min_width + }); + } + that._computeCutSize(); + }, + height(value, that) { + if (value < that.data.min_height) { + that.setData({ + height: that.data.min_height + }); + } + that._computeCutSize(); + }, + angle(value, that) { + //停止居中裁剪框,继续修改图片位置 + that._moveStop(); + if (that.data.limit_move) { + if (that.data.angle % 90) { + that.setData({ + angle: Math.round(that.data.angle / 90) * 90 + }); + return; + } + } + }, + _cut_animation(value, that) { + //开启过渡300毫秒之后自动关闭 + clearTimeout(that.data._cut_animation_time); + if (value) { + that.data._cut_animation_time = setTimeout(() => { + that.setData({ + _cut_animation: false + }); + }, 300) + } + }, + limit_move(value, that) { + if (value) { + if (that.data.angle % 90) { + that.setData({ + angle: Math.round(that.data.angle / 90) * 90 + }); + } + that._imgMarginDetectionScale(); + !that.data._canvas_overflow && that._draw(); + } + }, + canvas_top(value, that) { + that._canvasDetectionPosition(); + }, + canvas_left(value, that) { + that._canvasDetectionPosition(); + }, + imgSrc(value, that) { + + // that.pushImg(value); + }, + cut_top(value, that) { + that._cutDetectionPosition(); + if (that.data.limit_move) { + !that.data._canvas_overflow && that._draw(); + } + }, + cut_left(value, that) { + that._cutDetectionPosition(); + if (that.data.limit_move) { + !that.data._canvas_overflow && that._draw(); + } + } + } + }, + attached() { + this.data.info = wx.getSystemInfoSync(); + //启用数据监听 + this._watcher(); + this.data.INIT_IMGWIDTH = this.data.img_width; + this.data.INIT_IMGHEIGHT = this.data.img_height; + this.setData({ + _canvas_height: this.data.height, + _canvas_width: this.data.width, + }); + this._initCanvas(); + this.data.imgSrc && (this.data.imgSrc = this.data.imgSrc); + //根据开发者设置的图片目标尺寸计算实际尺寸 + this._initImageSize(); + //设置裁剪框大小>设置图片尺寸>绘制canvas + this._computeCutSize(); + //检查裁剪框是否在范围内 + this._cutDetectionPosition(); + //检查canvas是否在范围内 + this._canvasDetectionPosition(); + //初始化完成 + this.triggerEvent('load', { + cropper: this + }); + }, + methods: { + /** + * 上传图片 + */ + upload() { + let that = this; + wx.chooseImage({ + count: 1, + sizeType: ['original', 'compressed'], + sourceType: ['album', 'camera'], + success(res) { + const tempFilePaths = res.tempFilePaths[0]; + that.pushImg(tempFilePaths); + // wx.showLoading({ + // title: '加载中...' + // }) + } + }) + }, + /** + * 返回图片信息 + */ + getImg(getCallback) { + this._draw(() => { + wx.canvasToTempFilePath({ + width: this.data.width * this.data.export_scale, + height: Math.round(this.data.height * this.data.export_scale), + destWidth: this.data.width * this.data.export_scale, + destHeight: Math.round(this.data.height) * this.data.export_scale, + fileType: 'png', + quality: this.data.quality, + canvasId: this.data.el, + success: (res) => { + getCallback({ + url: res.tempFilePath, + width: this.data.width * this.data.export_scale, + height: this.data.height * this.data.export_scale + }); + } + }, this) + }); + }, + /** + * 设置图片动画 + * { + * x:10,//图片在原有基础上向下移动10px + * y:10,//图片在原有基础上向右移动10px + * angle:10,//图片在原有基础上旋转10deg + * scale:0.5,//图片在原有基础上增加0.5倍 + * } + */ + setTransform(transform) { + if (!transform) return; + if (!this.data.disable_rotate) { + this.setData({ + angle: transform.angle ? this.data.angle + transform.angle : this.data.angle + }); + } + var scale = this.data.scale; + if (transform.scale) { + scale = this.data.scale + transform.scale; + scale = scale <= this.data.min_scale ? this.data.min_scale : scale; + scale = scale >= this.data.max_scale ? this.data.max_scale : scale; + } + this.data.scale = scale; + let cutX = this.data.cut_left; + let cutY = this.data.cut_top; + if (transform.cutX) { + this.setData({ + cut_left: cutX + transform.cutX + }); + this.data.watch.cut_left(null, this); + } + if (transform.cutY) { + this.setData({ + cut_top: cutY + transform.cutY + }); + this.data.watch.cut_top(null, this); + } + this.data._img_top = transform.y ? this.data._img_top + transform.y : this.data._img_top; + this.data._img_left = transform.x ? this.data._img_left + transform.x : this.data._img_left; + //图像边缘检测,防止截取到空白 + this._imgMarginDetectionScale(); + //停止居中裁剪框,继续修改图片位置 + this._moveDuring(); + this.setData({ + scale: this.data.scale, + _img_top: this.data._img_top, + _img_left: this.data._img_left + }); + !this.data._canvas_overflow && this._draw(); + //可以居中裁剪框了 + this._moveStop(); //结束操作 + }, + /** + * 设置剪裁框位置 + */ + setCutXY(x, y) { + this.setData({ + cut_top: y, + cut_left: x + }); + }, + /** + * 设置剪裁框尺寸 + */ + setCutSize(w, h) { + this.setData({ + width: w, + height: h + }); + this._computeCutSize(); + }, + /** + * 设置剪裁框和图片居中 + */ + setCutCenter() { + let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5; + let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5; + //顺序不能变 + this.setData({ + _img_top: this.data._img_top - this.data.cut_top + cut_top, + cut_top: cut_top, //截取的框上边距 + _img_left: this.data._img_left - this.data.cut_left + cut_left, + cut_left: cut_left, //截取的框左边距 + }); + }, + _setCutCenter() { + let cut_top = (this.data.info.windowHeight - this.data.height) * 0.5; + let cut_left = (this.data.info.windowWidth - this.data.width) * 0.5; + this.setData({ + cut_top: cut_top, //截取的框上边距 + cut_left: cut_left, //截取的框左边距 + }); + }, + /** + * 设置剪裁框宽度-即将废弃 + */ + setWidth(width) { + this.setData({ + width: width + }); + this._computeCutSize(); + }, + /** + * 设置剪裁框高度-即将废弃 + */ + setHeight(height) { + this.setData({ + height: height + }); + this._computeCutSize(); + }, + /** + * 是否锁定旋转 + */ + setDisableRotate(value) { + this.data.disable_rotate = value; + }, + /** + * 是否限制移动 + */ + setLimitMove(value) { + this.setData({ + _cut_animation: true, + limit_move: !!value + }); + }, + /** + * 初始化图片,包括位置、大小、旋转角度 + */ + imgReset() { + this.setData({ + scale: 1, + angle: 0, + _img_top: wx.getSystemInfoSync().windowHeight / 2, + _img_left: wx.getSystemInfoSync().windowWidth / 2, + }); + this.pushImg(); + }, + /** + * 加载(更换)图片 + */ + pushImg(src) { + + if (src) { + this.setData({ + imgSrc: src + }); + //发现是手动赋值直接返回,交给watch处理 + return; + } + + // getImageInfo接口传入 src: '' 会导致内存泄漏 + + if (!this.data.imgSrc) return; + wx.getImageInfo({ + src: this.data.imgSrc, + success: (res) => { + this.data.imageObject = res; + //图片非本地路径需要换成本地路径 + if (this.data.imgSrc.search(/tmp/) == -1) { + this.setData({ + imgSrc: res.path + }); + } + //计算最后图片尺寸 + this._imgComputeSize(); + if (this.data.limit_move) { + //限制移动,不留空白处理 + this._imgMarginDetectionScale(); + } + this._draw(); + }, + fail: (err) => { + this.setData({ + imgSrc: '' + }); + } + }); + }, + imageLoad(e) { + setTimeout(() => { + this.triggerEvent('imageload', this.data.imageObject); + + }, 1000) + }, + /** + * 设置图片放大缩小 + */ + setScale(scale) { + if (!scale) return; + this.setData({ + scale: scale + }); + !this.data._canvas_overflow && this._draw(); + }, + /** + * 设置图片旋转角度 + */ + setAngle(angle) { + if (!angle) return; + this.setData({ + _cut_animation: true, + angle: angle + }); + this._imgMarginDetectionScale(); + !this.data._canvas_overflow && this._draw(); + }, + _initCanvas() { + //初始化canvas + if (!this.data.ctx) { + this.data.ctx = wx.createCanvasContext("image-cropper", this); + } + }, + /** + * 根据开发者设置的图片目标尺寸计算实际尺寸 + */ + _initImageSize() { + //处理宽高特殊单位 %>px + if (this.data.INIT_IMGWIDTH && typeof this.data.INIT_IMGWIDTH == "string" && this.data.INIT_IMGWIDTH.indexOf("%") != -1) { + let width = this.data.INIT_IMGWIDTH.replace("%", ""); + this.data.INIT_IMGWIDTH = this.data.img_width = this.data.info.windowWidth / 100 * width; + } + if (this.data.INIT_IMGHEIGHT && typeof this.data.INIT_IMGHEIGHT == "string" && this.data.INIT_IMGHEIGHT.indexOf("%") != -1) { + let height = this.data.img_height.replace("%", ""); + this.data.INIT_IMGHEIGHT = this.data.img_height = this.data.info.windowHeight / 100 * height; + } + }, + /** + * 检测剪裁框位置是否在允许的范围内(屏幕内) + */ + _cutDetectionPosition() { + let _cutDetectionPositionTop = () => { + //检测上边距是否在范围内 + if (this.data.cut_top < 0) { + this.setData({ + cut_top: 0 + }); + } + if (this.data.cut_top > this.data.info.windowHeight - this.data.height) { + this.setData({ + cut_top: this.data.info.windowHeight - this.data.height + }); + } + }, + _cutDetectionPositionLeft = () => { + //检测左边距是否在范围内 + if (this.data.cut_left < 0) { + this.setData({ + cut_left: 0 + }); + } + if (this.data.cut_left > this.data.info.windowWidth - this.data.width) { + this.setData({ + cut_left: this.data.info.windowWidth - this.data.width + }); + } + }; + //裁剪框坐标处理(如果只写一个参数则另一个默认为0,都不写默认居中) + if (this.data.cut_top == null && this.data.cut_left == null) { + this._setCutCenter(); + } else if (this.data.cut_top != null && this.data.cut_left != null) { + _cutDetectionPositionTop(); + _cutDetectionPositionLeft(); + } else if (this.data.cut_top != null && this.data.cut_left == null) { + _cutDetectionPositionTop(); + this.setData({ + cut_left: (this.data.info.windowWidth - this.data.width) / 2 + }); + } else if (this.data.cut_top == null && this.data.cut_left != null) { + _cutDetectionPositionLeft(); + this.setData({ + cut_top: (this.data.info.windowHeight - this.data.height) / 2 + }); + } + }, + /** + * 检测canvas位置是否在允许的范围内(屏幕内)如果在屏幕外则不开启实时渲染 + * 如果只写一个参数则另一个默认为0,都不写默认超出屏幕外 + */ + _canvasDetectionPosition() { + if (this.data.canvas_top == null && this.data.canvas_left == null) { + this.data._canvas_overflow = false; + this.setData({ + canvas_top: -5000, + canvas_left: -5000 + }); + } else if (this.data.canvas_top != null && this.data.canvas_left != null) { + if (this.data.canvas_top < -this.data.height || this.data.canvas_top > this.data.info.windowHeight) { + this.data._canvas_overflow = true; + } else { + this.data._canvas_overflow = false; + } + } else if (this.data.canvas_top != null && this.data.canvas_left == null) { + this.setData({ + canvas_left: 0 + }); + } else if (this.data.canvas_top == null && this.data.canvas_left != null) { + this.setData({ + canvas_top: 0 + }); + if (this.data.canvas_left < -this.data.width || this.data.canvas_left > this.data.info.windowWidth) { + this.data._canvas_overflow = true; + } else { + this.data._canvas_overflow = false; + } + } + }, + /** + * 图片边缘检测-位置 + */ + _imgMarginDetectionPosition(scale) { + if (!this.data.limit_move) return; + let left = this.data._img_left; + let top = this.data._img_top; + var scale = scale || this.data.scale; + let img_width = this.data.img_width; + let img_height = this.data.img_height; + if (this.data.angle / 90 % 2) { + img_width = this.data.img_height; + img_height = this.data.img_width; + } + left = this.data.cut_left + img_width * scale / 2 >= left ? left : this.data.cut_left + img_width * scale / 2; + left = this.data.cut_left + this.data.width - img_width * scale / 2 <= left ? left : this.data.cut_left + this.data.width - img_width * scale / 2; + top = this.data.cut_top + img_height * scale / 2 >= top ? top : this.data.cut_top + img_height * scale / 2; + top = this.data.cut_top + this.data.height - img_height * scale / 2 <= top ? top : this.data.cut_top + this.data.height - img_height * scale / 2; + this.setData({ + _img_left: left, + _img_top: top, + scale: scale + }) + }, + /** + * 图片边缘检测-缩放 + */ + _imgMarginDetectionScale() { + if (!this.data.limit_move) return; + let scale = this.data.scale; + let img_width = this.data.img_width; + let img_height = this.data.img_height; + if (this.data.angle / 90 % 2) { + img_width = this.data.img_height; + img_height = this.data.img_width; + } + if (img_width * scale < this.data.width) { + scale = this.data.width / img_width; + } + if (img_height * scale < this.data.height) { + scale = Math.max(scale, this.data.height / img_height); + } + this._imgMarginDetectionPosition(scale); + }, + _setData(obj) { + let data = {}; + for (var key in obj) { + if (this.data[key] != obj[key]) { + data[key] = obj[key]; + } + } + this.setData(data); + return data; + }, + /** + * 计算图片尺寸 + */ + _imgComputeSize() { + let img_width = this.data.img_width, + img_height = this.data.img_height; + if (!this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) { + //默认按图片最小边 = 对应裁剪框尺寸 + img_width = this.data.imageObject.width; + img_height = this.data.imageObject.height; + if (img_width / img_height > this.data.width / this.data.height) { + img_height = this.data.height; + img_width = this.data.imageObject.width / this.data.imageObject.height * img_height; + } else { + img_width = this.data.width; + img_height = this.data.imageObject.height / this.data.imageObject.width * img_width; + } + } else if (this.data.INIT_IMGHEIGHT && !this.data.INIT_IMGWIDTH) { + img_width = this.data.imageObject.width / this.data.imageObject.height * this.data.INIT_IMGHEIGHT; + } else if (!this.data.INIT_IMGHEIGHT && this.data.INIT_IMGWIDTH) { + img_height = this.data.imageObject.height / this.data.imageObject.width * this.data.INIT_IMGWIDTH; + } + this.setData({ + img_width: img_width, + img_height: img_height + }); + }, + //改变截取框大小 + _computeCutSize() { + if (this.data.width > this.data.info.windowWidth) { + this.setData({ + width: this.data.info.windowWidth, + }); + } else if (this.data.width + this.data.cut_left > this.data.info.windowWidth) { + this.setData({ + cut_left: this.data.info.windowWidth - this.data.cut_left, + }); + }; + if (this.data.height > this.data.info.windowHeight) { + this.setData({ + height: this.data.info.windowHeight, + }); + } else if (this.data.height + this.data.cut_top > this.data.info.windowHeight) { + this.setData({ + cut_top: this.data.info.windowHeight - this.data.cut_top, + }); + }!this.data._canvas_overflow && this._draw(); + }, + //开始触摸 + _start(event) { + this.data._flag_img_endtouch = false; + if (event.touches.length == 1) { + //单指拖动 + this.data._touch_img_relative[0] = { + x: (event.touches[0].clientX - this.data._img_left), + y: (event.touches[0].clientY - this.data._img_top) + } + } else { + //双指放大 + let width = Math.abs(event.touches[0].clientX - event.touches[1].clientX); + let height = Math.abs(event.touches[0].clientY - event.touches[1].clientY); + this.data._touch_img_relative = [{ + x: (event.touches[0].clientX - this.data._img_left), + y: (event.touches[0].clientY - this.data._img_top) + }, { + x: (event.touches[1].clientX - this.data._img_left), + y: (event.touches[1].clientY - this.data._img_top) + }]; + this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); + }!this.data._canvas_overflow && this._draw(); + }, + _move_throttle() { + //安卓需要节流 + if (this.data.info.platform == 'android') { + clearTimeout(this.data.MOVE_THROTTLE); + this.data.MOVE_THROTTLE = setTimeout(() => { + this.data.MOVE_THROTTLE_FLAG = true; + }, 1000 / 40) + return this.data.MOVE_THROTTLE_FLAG; + } else { + this.data.MOVE_THROTTLE_FLAG = true; + } + }, + _move(event) { + if (this.data._flag_img_endtouch || !this.data.MOVE_THROTTLE_FLAG) return; + this.data.MOVE_THROTTLE_FLAG = false; + this._move_throttle(); + this._moveDuring(); + if (event.touches.length == 1) { + //单指拖动 + let left = (event.touches[0].clientX - this.data._touch_img_relative[0].x), + top = (event.touches[0].clientY - this.data._touch_img_relative[0].y); + //图像边缘检测,防止截取到空白 + this.data._img_left = left; + this.data._img_top = top; + this._imgMarginDetectionPosition(); + this.setData({ + _img_left: this.data._img_left, + _img_top: this.data._img_top + }); + } else { + //双指放大 + let width = (Math.abs(event.touches[0].clientX - event.touches[1].clientX)), + height = (Math.abs(event.touches[0].clientY - event.touches[1].clientY)), + hypotenuse = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)), + scale = this.data.scale * (hypotenuse / this.data._hypotenuse_length), + current_deg = 0; + scale = scale <= this.data.min_scale ? this.data.min_scale : scale; + scale = scale >= this.data.max_scale ? this.data.max_scale : scale; + //图像边缘检测,防止截取到空白 + this.data.scale = scale; + this._imgMarginDetectionScale(); + //双指旋转(如果没禁用旋转) + let _touch_img_relative = [{ + x: (event.touches[0].clientX - this.data._img_left), + y: (event.touches[0].clientY - this.data._img_top) + }, { + x: (event.touches[1].clientX - this.data._img_left), + y: (event.touches[1].clientY - this.data._img_top) + }]; + if (!this.data.disable_rotate) { + let first_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[0].y, _touch_img_relative[0].x); + let first_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[0].y, this.data._touch_img_relative[0].x); + let second_atan = 180 / Math.PI * Math.atan2(_touch_img_relative[1].y, _touch_img_relative[1].x); + let second_atan_old = 180 / Math.PI * Math.atan2(this.data._touch_img_relative[1].y, this.data._touch_img_relative[1].x); + //当前旋转的角度 + let first_deg = first_atan - first_atan_old, + second_deg = second_atan - second_atan_old; + if (first_deg != 0) { + current_deg = first_deg; + } else if (second_deg != 0) { + current_deg = second_deg; + } + } + this.data._touch_img_relative = _touch_img_relative; + this.data._hypotenuse_length = Math.sqrt(Math.pow(width, 2) + Math.pow(height, 2)); + //更新视图 + this.setData({ + angle: this.data.angle + current_deg, + scale: this.data.scale + }); + }!this.data._canvas_overflow && this._draw(); + }, + //结束操作 + _end(event) { + this.data._flag_img_endtouch = true; + this._moveStop(); + }, + //点击中间剪裁框处理 + _click(event) { + // if (!this.data.imgSrc) { + // //调起上传 + // this.upload(); + // return; + // } + this._draw(() => { + let x = event.detail ? event.detail.x : event.touches[0].clientX; + let y = event.detail ? event.detail.y : event.touches[0].clientY; + if ((x >= this.data.cut_left && x <= (this.data.cut_left + this.data.width)) && (y >= this.data.cut_top && y <= (this.data.cut_top + this.data.height))) { + //生成图片并回调 + wx.canvasToTempFilePath({ + width: this.data.width * this.data.export_scale, + height: Math.round(this.data.height * this.data.export_scale), + destWidth: this.data.width * this.data.export_scale, + destHeight: Math.round(this.data.height) * this.data.export_scale, + fileType: 'png', + quality: this.data.quality, + canvasId: this.data.el, + success: (res) => { + this.triggerEvent('tapcut', { + url: res.tempFilePath, + width: this.data.width * this.data.export_scale, + height: this.data.height * this.data.export_scale + }); + } + }, this) + } + }); + }, + //渲染 + _draw(callback) { + if (!this.data.imgSrc) return; + let draw = () => { + //图片实际大小 + let img_width = this.data.img_width * this.data.scale * this.data.export_scale; + let img_height = this.data.img_height * this.data.scale * this.data.export_scale; + //canvas和图片的相对距离 + var xpos = this.data._img_left - this.data.cut_left; + var ypos = this.data._img_top - this.data.cut_top; + //旋转画布 + this.data.ctx.translate(xpos * this.data.export_scale, ypos * this.data.export_scale); + this.data.ctx.rotate(this.data.angle * Math.PI / 180); + this.data.ctx.drawImage(this.data.imgSrc, -img_width / 2, -img_height / 2, img_width, img_height); + this.data.ctx.draw(false, () => { + callback && callback(); + }); + } + if (this.data.ctx.width != this.data.width || this.data.ctx.height != this.data.height) { + //优化拖动裁剪框,所以必须把宽高设置放在离用户触发渲染最近的地方 + this.setData({ + _canvas_height: this.data.height, + _canvas_width: this.data.width, + }, () => { + //延迟40毫秒防止点击过快出现拉伸或裁剪过多 + setTimeout(() => { + draw(); + }, 40); + }); + } else { + draw(); + } + }, + //裁剪框处理 + _cutTouchMove(e) { + if (this.data._flag_cut_touch && this.data.MOVE_THROTTLE_FLAG) { + if (this.data.disable_ratio && (this.data.disable_width || this.data.disable_height)) return; + //节流 + this.data.MOVE_THROTTLE_FLAG = false; + this._move_throttle(); + let width = this.data.width, + height = this.data.height, + cut_top = this.data.cut_top, + cut_left = this.data.cut_left, + size_correct = () => { + width = width <= this.data.max_width ? width >= this.data.min_width ? width : this.data.min_width : this.data.max_width; + height = height <= this.data.max_height ? height >= this.data.min_height ? height : this.data.min_height : this.data.max_height; + }, + size_inspect = () => { + if ((width > this.data.max_width || width < this.data.min_width || height > this.data.max_height || height < this.data.min_height) && this.data.disable_ratio) { + size_correct(); + return false; + } else { + size_correct(); + return true; + } + }; + height = this.data.CUT_START.height + ((this.data.CUT_START.corner > 1 && this.data.CUT_START.corner < 4 ? 1 : -1) * (this.data.CUT_START.y - e.touches[0].clientY)); + switch (this.data.CUT_START.corner) { + case 1: + width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX; + if (this.data.disable_ratio) { + height = width / (this.data.width / this.data.height) + } + if (!size_inspect()) return; + cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width); + break + case 2: + width = this.data.CUT_START.width + this.data.CUT_START.x - e.touches[0].clientX; + if (this.data.disable_ratio) { + height = width / (this.data.width / this.data.height) + } + if (!size_inspect()) return; + cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height) + cut_left = this.data.CUT_START.cut_left - (width - this.data.CUT_START.width) + break + case 3: + width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX; + if (this.data.disable_ratio) { + height = width / (this.data.width / this.data.height) + } + if (!size_inspect()) return; + cut_top = this.data.CUT_START.cut_top - (height - this.data.CUT_START.height); + break + case 4: + width = this.data.CUT_START.width - this.data.CUT_START.x + e.touches[0].clientX; + if (this.data.disable_ratio) { + height = width / (this.data.width / this.data.height) + } + if (!size_inspect()) return; + break + } + if (!this.data.disable_width && !this.data.disable_height) { + this.setData({ + width: width, + cut_left: cut_left, + height: height, + cut_top: cut_top, + }) + } else if (!this.data.disable_width) { + this.setData({ + width: width, + cut_left: cut_left + }) + } else if (!this.data.disable_height) { + this.setData({ + height: height, + cut_top: cut_top + }) + } + this._imgMarginDetectionScale(); + } + }, + _cutTouchStart(e) { + let currentX = e.touches[0].clientX; + let currentY = e.touches[0].clientY; + let cutbox_top4 = this.data.cut_top + this.data.height - 30; + let cutbox_bottom4 = this.data.cut_top + this.data.height + 20; + let cutbox_left4 = this.data.cut_left + this.data.width - 30; + let cutbox_right4 = this.data.cut_left + this.data.width + 30; + + let cutbox_top3 = this.data.cut_top - 30; + let cutbox_bottom3 = this.data.cut_top + 30; + let cutbox_left3 = this.data.cut_left + this.data.width - 30; + let cutbox_right3 = this.data.cut_left + this.data.width + 30; + + let cutbox_top2 = this.data.cut_top - 30; + let cutbox_bottom2 = this.data.cut_top + 30; + let cutbox_left2 = this.data.cut_left - 30; + let cutbox_right2 = this.data.cut_left + 30; + + let cutbox_top1 = this.data.cut_top + this.data.height - 30; + let cutbox_bottom1 = this.data.cut_top + this.data.height + 30; + let cutbox_left1 = this.data.cut_left - 30; + let cutbox_right1 = this.data.cut_left + 30; + if (currentX > cutbox_left4 && currentX < cutbox_right4 && currentY > cutbox_top4 && currentY < cutbox_bottom4) { + this._moveDuring(); + this.data._flag_cut_touch = true; + this.data._flag_img_endtouch = true; + this.data.CUT_START = { + width: this.data.width, + height: this.data.height, + x: currentX, + y: currentY, + corner: 4 + } + } else if (currentX > cutbox_left3 && currentX < cutbox_right3 && currentY > cutbox_top3 && currentY < cutbox_bottom3) { + this._moveDuring(); + this.data._flag_cut_touch = true; + this.data._flag_img_endtouch = true; + this.data.CUT_START = { + width: this.data.width, + height: this.data.height, + x: currentX, + y: currentY, + cut_top: this.data.cut_top, + cut_left: this.data.cut_left, + corner: 3 + } + } else if (currentX > cutbox_left2 && currentX < cutbox_right2 && currentY > cutbox_top2 && currentY < cutbox_bottom2) { + this._moveDuring(); + this.data._flag_cut_touch = true; + this.data._flag_img_endtouch = true; + this.data.CUT_START = { + width: this.data.width, + height: this.data.height, + cut_top: this.data.cut_top, + cut_left: this.data.cut_left, + x: currentX, + y: currentY, + corner: 2 + } + } else if (currentX > cutbox_left1 && currentX < cutbox_right1 && currentY > cutbox_top1 && currentY < cutbox_bottom1) { + this._moveDuring(); + this.data._flag_cut_touch = true; + this.data._flag_img_endtouch = true; + this.data.CUT_START = { + width: this.data.width, + height: this.data.height, + cut_top: this.data.cut_top, + cut_left: this.data.cut_left, + x: currentX, + y: currentY, + corner: 1 + } + } + }, + _cutTouchEnd(e) { + this._moveStop(); + this.data._flag_cut_touch = false; + }, + //停止移动时需要做的操作 + _moveStop() { + //清空之前的自动居中延迟函数并添加最新的 + clearTimeout(this.data.TIME_CUT_CENTER); + this.data.TIME_CUT_CENTER = setTimeout(() => { + //动画启动 + if (!this.data._cut_animation) { + this.setData({ + _cut_animation: true + }); + } + this.setCutCenter(); + }, 1000) + //清空之前的背景变化延迟函数并添加最新的 + clearTimeout(this.data.TIME_BG); + this.data.TIME_BG = setTimeout(() => { + if (this.data._flag_bright) { + this.setData({ + _flag_bright: false + }); + } + }, 2000) + }, + //移动中 + _moveDuring() { + //清空之前的自动居中延迟函数 + clearTimeout(this.data.TIME_CUT_CENTER); + //清空之前的背景变化延迟函数 + clearTimeout(this.data.TIME_BG); + //高亮背景 + if (!this.data._flag_bright) { + this.setData({ + _flag_bright: true + }); + } + }, + //监听器 + _watcher() { + Object.keys(this.data).forEach(v => { + this._observe(this.data, v, this.data.watch[v]); + }) + }, + _observe(obj, key, watchFun) { + var val = obj[key]; + Object.defineProperty(obj, key, { + configurable: true, + enumerable: true, + set: (value) => { + val = value; + watchFun && watchFun(val, this); + }, + get() { + if (val && '_img_top|img_left||width|height|min_width|max_width|min_height|max_height|export_scale|cut_top|cut_left|canvas_top|canvas_left|img_width|img_height|scale|angle|min_scale|max_scale'.indexOf(key) != -1) { + let ret = parseFloat(parseFloat(val).toFixed(3)); + if (typeof val == "string" && val.indexOf("%") != -1) { + ret += '%'; + } + return ret; + } + return val; + } + }) + }, + _preventTouchMove() {} + } +}) \ No newline at end of file diff --git a/components/image-cropper/image-cropper.json b/components/image-cropper/image-cropper.json new file mode 100644 index 0000000..d577ade --- /dev/null +++ b/components/image-cropper/image-cropper.json @@ -0,0 +1,3 @@ +{ + "component": true +} \ No newline at end of file diff --git a/components/image-cropper/image-cropper.wxml b/components/image-cropper/image-cropper.wxml new file mode 100644 index 0000000..a4a7526 --- /dev/null +++ b/components/image-cropper/image-cropper.wxml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/image-cropper/image-cropper.wxss b/components/image-cropper/image-cropper.wxss new file mode 100644 index 0000000..80eb895 --- /dev/null +++ b/components/image-cropper/image-cropper.wxss @@ -0,0 +1,143 @@ +.image-cropper { + background: rgba(14, 13, 13, .8); + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 1; +} + +.image-cropper .main { + position: absolute; + width: 100vw; + height: 100vh; + overflow: hidden; +} + +.image-cropper .content { + z-index: 9; + position: absolute; + width: 100vw; + height: 100vh; + display: flex; + flex-direction: column; + pointer-events: none; +} + +.image-cropper .bg_black { + background: rgba(0, 0, 0, 0.8) !important; +} + +.image-cropper .bg_gray { + background: rgba(0, 0, 0, 0.45); + transition-duration: .35s; +} + +.image-cropper .content>.content_top { + pointer-events: none; +} + +.image-cropper .content>.content_middle { + display: flex; + height: 200px; + width: 100%; +} + +.image-cropper .content_middle_middle { + width: 200px; + box-sizing: border-box; + position: relative; + transition-duration: .3s; +} + +.image-cropper .content_middle_right { + flex: auto; +} + +.image-cropper .content>.content_bottom { + flex: auto; +} + +.image-cropper .img { + z-index: 2; + top: 0; + left: 0; + position: absolute; + border: none; + width: 100%; + backface-visibility: hidden; + transform-origin: center; +} + +.image-cropper .image-cropper-canvas { + position: fixed; + background: white; + width: 150px; + height: 150px; + z-index: 10; + top: -200%; + pointer-events: none; +} + +.image-cropper .border { + background: white; + pointer-events: auto; + position: absolute; +} + +.image-cropper .border-top-left { + left: -2.5px; + top: -2.5px; + height: 2.5px; + width: 33rpx; +} + +.image-cropper .border-top-right { + right: -2.5px; + top: -2.5px; + height: 2.5px; + width: 33rpx; +} + +.image-cropper .border-right-top { + top: -1px; + width: 2.5px; + height: 30rpx; + right: -2.5px; +} + +.image-cropper .border-right-bottom { + width: 2.5px; + height: 30rpx; + right: -2.5px; + bottom: -1px; +} + +.image-cropper .border-bottom-left { + height: 2.5px; + width: 33rpx; + bottom: -2.5px; + left: -2.5px; +} + +.image-cropper .border-bottom-right { + height: 2.5px; + width: 33rpx; + bottom: -2.5px; + right: -2.5px; +} + +.image-cropper .border-left-top { + top: -1px; + width: 2.5px; + height: 30rpx; + left: -2.5px; +} + +.image-cropper .border-left-bottom { + width: 2.5px; + height: 30rpx; + left: -2.5px; + bottom: -1px; +} \ No newline at end of file diff --git a/components/nav/nav.js b/components/nav/nav.js new file mode 100644 index 0000000..8873c31 --- /dev/null +++ b/components/nav/nav.js @@ -0,0 +1,48 @@ + + +// components/nav/nav.js +const app=getApp(); +Component({ + /** + * 页面的初始数据 + */ + data: { + keyWord:'', + img_host:app.hostConfig().imghost + }, + pageLifetimes: { + // 组件所在页面的生命周期函数 + show() { + this.setData({ + img_host:app.hostConfig().imghost + }); + }, + hide: function () { }, + resize: function () { }, + }, + + methods: { + search(){ + this.triggerEvent('goSearch',this.data.keyWord) + }, + clearKeyWord(){ + this.setData({ + keyWord:'' + }) + }, + changeIpt(event){ + let {value} = event.detail; + console.log(value); + this.setData({ + keyWord:value + }) + + }, + goInquirtForm(){ + wx.navigateTo({ + url: '/case/pages/paintCanvas/paintCanvas', + }) + } + }, + +}) \ No newline at end of file diff --git a/components/nav/nav.json b/components/nav/nav.json new file mode 100644 index 0000000..7e37c03 --- /dev/null +++ b/components/nav/nav.json @@ -0,0 +1,4 @@ +{ + "component": true, + "usingComponents": {} +} \ No newline at end of file diff --git a/components/nav/nav.wxml b/components/nav/nav.wxml new file mode 100644 index 0000000..89cbd99 --- /dev/null +++ b/components/nav/nav.wxml @@ -0,0 +1,11 @@ + + + + + 人工肝病例登记系统 + + + 搜索 + + + diff --git a/components/nav/nav.wxss b/components/nav/nav.wxss new file mode 100644 index 0000000..ab06fad --- /dev/null +++ b/components/nav/nav.wxss @@ -0,0 +1,61 @@ +/* components/nav/nav.wxss */ +.barcontain { + position: fixed; + top: 0; + z-index: 9; + width: 100%; + overflow: hidden; + height: 290rpx; + } + + .barcontain .barcon { + margin: 106rpx 32rpx 0; + } + .barcon .scon { + width: 100%; + height: 80rpx; + background: #FBFBFB; + display: flex; + + box-shadow: 0rpx 12rpx 28rpx 0rpx rgba(0,0,0,0.03); + border-radius: 8rpx; + align-items: center; + margin-top: 55rpx; + } + + .barcontain .text { + font-size: 36rpx; + font-weight: 600; + } + + .barcon .ss { + width: 30rpx; + height: 30rpx; + margin-right: 25rpx; + } + + .barcontain .ipt { + margin-left: 30rpx; + flex: 1; + font-size: 32rpx; + height: 80rpx; + + } + .bg{ + position: absolute; + z-index:-1; + width:750rpx; + } + .btn{ + width: 128rpx; + height: 64rpx; + display: flex; + margin-right: 8rpx; + font-size: 32rpx; +font-weight: 500; +color: #FFFFFF; + align-items: center; + justify-content: center; + background: linear-gradient(90deg, #377FF7 0%, #51AAFF 100%); + border-radius: 8rpx; + } \ No newline at end of file diff --git a/components/navBar/navBar.js b/components/navBar/navBar.js new file mode 100644 index 0000000..5937544 --- /dev/null +++ b/components/navBar/navBar.js @@ -0,0 +1,44 @@ +// components/navBar.js + +Component({ + /** + * 组件的属性列表 + */ + externalClasses:['myclass'], + properties: { + navName: { + type: String, + value: "", + observer(newval) { + this.setData({ + name: newval, + }); + }, + }, + }, + + /** + * 组件的初始数据 + */ + data: { + name:'' + }, + + /** + * 组件的方法列表 + */ + methods: { + + goBack(){ + wx.navigateBack({ + delta: 1, + fail:function(){ + wx.reLaunch({ + url: '/pages/index/index', + }) + } + }) + }, + + } +}) diff --git a/components/navBar/navBar.json b/components/navBar/navBar.json new file mode 100644 index 0000000..12a4d8b --- /dev/null +++ b/components/navBar/navBar.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "van-icon": "@vant/weapp/icon/index" + } +} \ No newline at end of file diff --git a/components/navBar/navBar.wxml b/components/navBar/navBar.wxml new file mode 100644 index 0000000..6fb711e --- /dev/null +++ b/components/navBar/navBar.wxml @@ -0,0 +1,5 @@ + + + + {{name}} + diff --git a/components/navBar/navBar.wxss b/components/navBar/navBar.wxss new file mode 100644 index 0000000..162b2dc --- /dev/null +++ b/components/navBar/navBar.wxss @@ -0,0 +1,36 @@ +/* components/navBar.wxss */ +.ui-navigatorbar { + position: fixed; + z-index:99; + top: 0; + width: 750rpx; + height: 172rpx; + background: #FFFFFF; + backdrop-filter: blur(20px); + border-bottom: none; + } + + .ui-navigatorbar-back { + position: absolute; + padding-left:40rpx; + padding-right:40rpx; + + left: 0rpx; + font-size: 40rpx; + bottom: 20rpx; + } + + .ui-title { + position: absolute; + width: 350rpx; + height: 88rpx; + line-height: 56rpx; + font-size: 36rpx; + white-space: nowrap; + color: #000000; + bottom: 0; + left: 200rpx; + display: flex; + justify-content: center; + align-items: center; + } \ No newline at end of file diff --git a/components/painter/lib/calc.js b/components/painter/lib/calc.js new file mode 100644 index 0000000..e6788de --- /dev/null +++ b/components/painter/lib/calc.js @@ -0,0 +1,54 @@ +/* eslint-disable */ +// 四则运算 + +!(function () { + var calculate = function (s) { + s = s.trim(); + const stack = new Array(); + let preSign = '+'; + let numStr = ''; + const n = s.length; + for (let i = 0; i < n; ++i) { + if (s[i] === '.' || (!isNaN(Number(s[i])) && s[i] !== ' ')) { + numStr += s[i]; + } else if (s[i] === '(') { + let isClose = 1; + let j = i; + while (isClose > 0) { + j += 1; + if (s[j] === '(') isClose += 1; + if (s[j] === ')') isClose -= 1; + } + numStr = `${calculate(s.slice(i + 1, j))}`; + i = j; + } + if ((isNaN(Number(s[i])) && s[i] !== '.') || i === n - 1) { + let num = parseFloat(numStr); + switch (preSign) { + case '+': + stack.push(num); + break; + case '-': + stack.push(-num); + break; + case '*': + stack.push(stack.pop() * num); + break; + case '/': + stack.push(stack.pop() / num); + break; + default: + break; + } + preSign = s[i]; + numStr = ''; + } + } + let ans = 0; + while (stack.length) { + ans += stack.pop(); + } + return ans; + }; + module.exports = calculate; +})(); diff --git a/components/painter/lib/downloader.js b/components/painter/lib/downloader.js new file mode 100644 index 0000000..73a894e --- /dev/null +++ b/components/painter/lib/downloader.js @@ -0,0 +1,363 @@ +/** + * LRU 文件存储,使用该 downloader 可以让下载的文件存储在本地,下次进入小程序后可以直接使用 + * 详细设计文档可查看 https://juejin.im/post/5b42d3ede51d4519277b6ce3 + */ +const util = require('./util'); +const sha1 = require('./sha1'); + +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; + } + const fileName = getFileName(url); + if (!lru) { + // 无 lru 情况下直接判断 临时文件是否存在,不存在重新下载 + wx.getFileInfo({ + filePath: fileName, + success: () => { + resolve(url); + }, + fail: () => { + if (util.isOnlineUrl(url)) { + downloadFile(url, lru).then((path) => { + resolve(path); + }, () => { + reject(); + }); + } else if (util.isDataUrl(url)) { + transformBase64File(url, lru).then(path => { + resolve(path); + }, () => { + reject(); + }); + } + }, + }) + return + } + + const file = getFile(fileName); + + if (file) { + if (file[KEY_PATH].indexOf('//usr/') !== -1) { + wx.getFileInfo({ + filePath: file[KEY_PATH], + success() { + resolve(file[KEY_PATH]); + }, + fail(error) { + console.error(`base64 file broken, ${JSON.stringify(error)}`); + transformBase64File(url, lru).then(path => { + resolve(path); + }, () => { + reject(); + }); + } + }) + } else { + // 检查文件是否正常,不正常需要重新下载 + 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 { + if (util.isOnlineUrl(url)) { + downloadFile(url, lru).then((path) => { + resolve(path); + }, () => { + reject(); + }); + } else if (util.isDataUrl(url)) { + transformBase64File(url, lru).then(path => { + resolve(path); + }, () => { + reject(); + }); + } + } + }); + } +} + +function getFileName(url) { + if (util.isDataUrl(url)) { + const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(url) || []; + const fileName = `${sha1.hex_sha1(bodyData)}.${format}`; + return fileName; + } else { + return url; + } +} + +function transformBase64File(base64data, lru) { + return new Promise((resolve, reject) => { + const [, format, bodyData] = /data:image\/(\w+);base64,(.*)/.exec(base64data) || []; + if (!format) { + console.error('base parse failed'); + reject(); + return; + } + const fileName = `${sha1.hex_sha1(bodyData)}.${format}`; + const path = `${wx.env.USER_DATA_PATH}/${fileName}`; + const buffer = wx.base64ToArrayBuffer(bodyData.replace(/[\r\n]/g, "")); + wx.getFileSystemManager().writeFile({ + filePath: path, + data: buffer, + encoding: 'binary', + success() { + wx.getFileInfo({ + filePath: path, + success: (tmpRes) => { + const newFileSize = tmpRes.size; + lru ? doLru(newFileSize).then(() => { + saveFile(fileName, newFileSize, path, true).then((filePath) => { + resolve(filePath); + }); + }, () => { + resolve(path); + }) : resolve(path); + }, + fail: (error) => { + // 文件大小信息获取失败,则此文件也不要进行存储 + console.error(`getFileInfo ${path} failed, ${JSON.stringify(error)}`); + resolve(path); + }, + }); + }, + fail(err) { + console.log(err) + } + }) + }); +} + +function downloadFile(url, lru) { + return new Promise((resolve, reject) => { + const downloader = url.startsWith('cloud://')?wx.cloud.downloadFile:wx.downloadFile + downloader({ + url: url, + fileID: 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, isDataUrl = false) { + return new Promise((resolve, reject) => { + if (isDataUrl) { + const totalSize = savedFiles[KEY_TOTAL_SIZE] ? savedFiles[KEY_TOTAL_SIZE] : 0; + savedFiles[key] = {}; + savedFiles[key][KEY_PATH] = tempFilePath; + 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(tempFilePath); + return; + } + 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; + } + if (delPath.indexOf('//usr/') !== -1) { + wx.getFileSystemManager().unlink({ + filePath: delPath, + fail(error) { + console.error(`removeSavedFile ${pathDel} failed, ${JSON.stringify(error)}`); + } + }) + } else { + 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]; +} \ No newline at end of file diff --git a/components/painter/lib/gradient.js b/components/painter/lib/gradient.js new file mode 100644 index 0000000..13c7258 --- /dev/null +++ b/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 } + +})(); diff --git a/components/painter/lib/pen.js b/components/painter/lib/pen.js new file mode 100644 index 0000000..d0f099b --- /dev/null +++ b/components/painter/lib/pen.js @@ -0,0 +1,903 @@ +const QR = require('./qrcode.js'); +const GD = require('./gradient.js'); +require('./string-polyfill.js'); + +export const penCache = { + // 用于存储带 id 的 view 的 rect 信息 + viewRect: {}, + textLines: {}, +}; +export const clearPenCache = id => { + if (id) { + penCache.viewRect[id] = null; + penCache.textLines[id] = null; + } else { + penCache.viewRect = {}; + penCache.textLines = {}; + } +}; +export default class Painter { + constructor(ctx, data) { + this.ctx = ctx; + this.data = data; + } + + paint(callback) { + this.style = { + width: this.data.width.toPx(), + height: this.data.height.toPx(), + }; + + this._background(); + for (const view of this.data.views) { + this._drawAbsolute(view); + } + this.ctx.draw(false, () => { + callback && callback(); + }); + } + + _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 'inlineText': + this._fillAbsInlineText(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 'inlineText': { + { + // 计算行数 + let lines = 0; + // 文字总长度 + let textLength = 0; + // 行高 + let lineHeight = 0; + const textList = view.textList || []; + for (let i = 0; i < textList.length; i++) { + let subView = textList[i]; + const fontWeight = subView.css.fontWeight || '400'; + const textStyle = subView.css.textStyle || 'normal'; + if (!subView.css.fontSize) { + subView.css.fontSize = '20rpx'; + } + this.ctx.font = `${textStyle} ${fontWeight} ${subView.css.fontSize.toPx()}px "${subView.css.fontFamily || 'sans-serif'}"`; + textLength += this.ctx.measureText(subView.text).width; + let tempLineHeight = subView.css.lineHeight ? subView.css.lineHeight.toPx() : subView.css.fontSize.toPx(); + lineHeight = Math.max(lineHeight, tempLineHeight); + } + width = view.css.width ? view.css.width.toPx(false, this.style.width) - paddings[1] - paddings[3] : textLength;; + const calLines = Math.ceil(textLength / width); + + lines += calLines; + // lines = view.css.maxLines < lines ? view.css.maxLines : lines; + height = lineHeight * lines; + extra = { + lines: lines, + lineHeight: lineHeight, + // textArray: textArray, + // linesArray: linesArray, + }; + } + break; + } + 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) - + penCache.viewRect[rights[1]].width * (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) + penCache.viewRect[lefts[1]].width * (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) + penCache.viewRect[tops[1]].height * (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) { + penCache.viewRect[view.id] = { + width, + height, + left: view.rect.left, + top: view.rect.top, + right: view.rect.right, + bottom: view.rect.bottom, + }; + } + 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); + } + /** + * + * @param {*} view + * @description 一行内文字多样式的方法 + * + * 暂不支持配置 text-align,默认left + * 暂不支持配置 maxLines + */ + _fillAbsInlineText(view) { + if (!view.textList) { + return; + } + if (view.css.background) { + // 生成背景 + this._doBackground(view); + } + this.ctx.save(); + const { width, height, extra } = this._preProcess(view, view.css.background && view.css.borderRadius); + const { lines, lineHeight } = extra; + let staticX = -(width / 2); + let lineIndex = 0; // 第几行 + let x = staticX; // 开始x位置 + let leftWidth = width; // 当前行剩余多少宽度可以使用 + + let getStyle = css => { + const fontWeight = css.fontWeight || '400'; + const textStyle = css.textStyle || 'normal'; + if (!css.fontSize) { + css.fontSize = '20rpx'; + } + return `${textStyle} ${fontWeight} ${css.fontSize.toPx()}px "${css.fontFamily || 'sans-serif'}"`; + } + + // 遍历行内的文字数组 + for (let j = 0; j < view.textList.length; j++) { + const subView = view.textList[j]; + + // 某个文字开始位置 + let start = 0; + // 文字已使用的数量 + let alreadyCount = 0; + // 文字总长度 + let textLength = subView.text.length; + // 文字总宽度 + let textWidth = this.ctx.measureText(subView.text).width; + // 每个文字的平均宽度 + let preWidth = Math.ceil(textWidth / textLength); + + // 循环写文字 + while (alreadyCount < textLength) { + // alreadyCount - start + 1 -> 当前摘取出来的文字 + // 比较可用宽度,寻找最大可写文字长度 + while ((alreadyCount - start + 1) * preWidth < leftWidth && alreadyCount < textLength) { + alreadyCount++; + } + + // 取出文字 + let text = subView.text.substr(start, alreadyCount - start); + + const y = -(height / 2) + subView.css.fontSize.toPx() + lineIndex * lineHeight; + + // 设置文字样式 + this.ctx.font = getStyle(subView.css); + + this.ctx.fillStyle = subView.css.color || 'black'; + this.ctx.textAlign = 'left'; + + // 执行画布操作 + if (subView.css.textStyle === 'stroke') { + this.ctx.strokeText(text, x, y); + } else { + this.ctx.fillText(text, x, y); + } + + // 当次已使用宽度 + let currentUsedWidth = this.ctx.measureText(text).width; + + const fontSize = subView.css.fontSize.toPx(); + + // 画 textDecoration + let textDecoration; + if (subView.css.textDecoration) { + this.ctx.lineWidth = fontSize / 13; + this.ctx.beginPath(); + if (/\bunderline\b/.test(subView.css.textDecoration)) { + this.ctx.moveTo(x, y); + this.ctx.lineTo(x + currentUsedWidth, y); + textDecoration = { + moveTo: [x, y], + lineTo: [x + currentUsedWidth, y], + }; + } + if (/\boverline\b/.test(subView.css.textDecoration)) { + this.ctx.moveTo(x, y - fontSize); + this.ctx.lineTo(x + currentUsedWidth, y - fontSize); + textDecoration = { + moveTo: [x, y - fontSize], + lineTo: [x + currentUsedWidth, y - fontSize], + }; + } + if (/\bline-through\b/.test(subView.css.textDecoration)) { + this.ctx.moveTo(x, y - fontSize / 3); + this.ctx.lineTo(x + currentUsedWidth, y - fontSize / 3); + textDecoration = { + moveTo: [x, y - fontSize / 3], + lineTo: [x + currentUsedWidth, y - fontSize / 3], + }; + } + this.ctx.closePath(); + this.ctx.strokeStyle = subView.css.color; + this.ctx.stroke(); + } + + // 重置数据 + start = alreadyCount; + leftWidth -= currentUsedWidth; + x += currentUsedWidth; + // 如果剩余宽度 小于等于0 或者小于一个字的平均宽度,换行 + if (leftWidth <= 0 || leftWidth < preWidth) { + leftWidth = width; + x = staticX; + lineIndex++; + } + } + } + + this.ctx.restore(); + this._doBorder(view, width, height); + } + + _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 (view.id && penCache.textLines[view.id]) { + this.ctx.textAlign = view.css.textAlign ? view.css.textAlign : 'left'; + for (const i of penCache.textLines[view.id]) { + 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; + } + penCache.viewRect[view.id].width = width ? (textWidth < width ? textWidth : width) : textWidth; + } + 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 (view.id) { + penCache.textLines[view.id] + ? penCache.textLines[view.id].push({ + text, + x, + y, + measuredWith, + textDecoration, + }) + : (penCache.textLines[view.id] = [ + { + 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; + } +} diff --git a/components/painter/lib/qrcode.js b/components/painter/lib/qrcode.js new file mode 100644 index 0000000..adf1b64 --- /dev/null +++ b/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; + +})(); \ No newline at end of file diff --git a/components/painter/lib/sha1.js b/components/painter/lib/sha1.js new file mode 100644 index 0000000..e743ec1 --- /dev/null +++ b/components/painter/lib/sha1.js @@ -0,0 +1,97 @@ +var hexcase = 0; +var chrsz = 8; + +function hex_sha1(s) { + return binb2hex(core_sha1(str2binb(s), s.length * chrsz)); +} + +function core_sha1(x, len) { + x[len >> 5] |= 0x80 << (24 - (len % 32)); + x[(((len + 64) >> 9) << 4) + 15] = len; + + var w = Array(80); + var a = 1732584193; + var b = -271733879; + var c = -1732584194; + var d = 271733878; + var e = -1009589776; + + for (var i = 0; i < x.length; i += 16) { + var olda = a; + var oldb = b; + var oldc = c; + var oldd = d; + var olde = e; + + for (var j = 0; j < 80; j++) { + if (j < 16) w[j] = x[i + j]; + else w[j] = rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1); + var t = safe_add( + safe_add(rol(a, 5), sha1_ft(j, b, c, d)), + safe_add(safe_add(e, w[j]), sha1_kt(j)) + ); + e = d; + d = c; + c = rol(b, 30); + b = a; + a = t; + } + + a = safe_add(a, olda); + b = safe_add(b, oldb); + c = safe_add(c, oldc); + d = safe_add(d, oldd); + e = safe_add(e, olde); + } + return Array(a, b, c, d, e); +} + +function sha1_ft(t, b, c, d) { + if (t < 20) return (b & c) | (~b & d); + if (t < 40) return b ^ c ^ d; + if (t < 60) return (b & c) | (b & d) | (c & d); + return b ^ c ^ d; +} + +function sha1_kt(t) { + return t < 20 + ? 1518500249 + : t < 40 + ? 1859775393 + : t < 60 + ? -1894007588 + : -899497514; +} + +function safe_add(x, y) { + var lsw = (x & 0xffff) + (y & 0xffff); + var msw = (x >> 16) + (y >> 16) + (lsw >> 16); + return (msw << 16) | (lsw & 0xffff); +} + +function rol(num, cnt) { + return (num << cnt) | (num >>> (32 - cnt)); +} + +function str2binb(str) { + var bin = Array(); + var mask = (1 << chrsz) - 1; + for (var i = 0; i < str.length * chrsz; i += chrsz) + bin[i >> 5] |= (str.charCodeAt(i / chrsz) & mask) << (24 - (i % 32)); + return bin; +} + +function binb2hex(binarray) { + var hex_tab = hexcase ? "0123456789ABCDEF" : "0123456789abcdef"; + var str = ""; + for (var i = 0; i < binarray.length * 4; i++) { + str += + hex_tab.charAt((binarray[i >> 2] >> ((3 - (i % 4)) * 8 + 4)) & 0xf) + + hex_tab.charAt((binarray[i >> 2] >> ((3 - (i % 4)) * 8)) & 0xf); + } + return str; +} + +module.exports = { + hex_sha1, +} \ No newline at end of file diff --git a/components/painter/lib/string-polyfill.js b/components/painter/lib/string-polyfill.js new file mode 100644 index 0000000..1d13f53 --- /dev/null +++ b/components/painter/lib/string-polyfill.js @@ -0,0 +1,46 @@ +String.prototype.substr = function (start, length) { + if (start === undefined) { + return this.toString() + } + if (typeof start !== 'number' || (typeof length !== 'number' && length !== undefined) ) { + return '' + } + const strArr = [...this] + const _length = strArr.length + if (_length + start < 0) { + start = 0 + } + if (length === undefined || (start < 0 && start + length > 0)) { + return strArr.slice(start).join('') + } else { + return strArr.slice(start, start + length).join('') + } +} + + +String.prototype.substring = function (start, end) { + if (start === undefined) { + return this.toString() + } + if (typeof start !== 'number' || (typeof end !== 'number' && end !== undefined) ) { + return '' + } + if (!(start > 0)) { + start = 0 + } + if (!(end > 0) && end !== undefined) { + end = 0 + } + const strArr = [...this] + const _length = strArr.length + if (start > _length) { + start = _length + } + if (end > _length) { + end = _length + } + if (end < start) { + [start, end] = [end, start] + } + return strArr.slice(start, end).join('') +} \ No newline at end of file diff --git a/components/painter/lib/util.js b/components/painter/lib/util.js new file mode 100644 index 0000000..fb1476a --- /dev/null +++ b/components/painter/lib/util.js @@ -0,0 +1,78 @@ + +function isValidUrl(url) { + return isOnlineUrl(url) || isDataUrl(url); +} + +function isOnlineUrl(url) { + return /((ht|f)tp(s?)|cloud):\/\/([^ \\/]*\.)+[^ \\/]*(:[0-9]+)?\/?/.test(url) +} + +function isDataUrl(url) { + return /data:image\/(\w+);base64,(.*)/.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, + isOnlineUrl, + isDataUrl, + equal +}; + diff --git a/components/painter/lib/wx-canvas.js b/components/painter/lib/wx-canvas.js new file mode 100644 index 0000000..42c8474 --- /dev/null +++ b/components/painter/lib/wx-canvas.js @@ -0,0 +1,619 @@ +// @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; + // 经测试,在 2d 接口中如果不设置这个值,IOS 端有一定几率会出现图片显示不全的情况。 + 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; + // 经测试,在 2d 接口中如果不设置这个值,IOS 端有一定几率会出现图片显示不全的情况。 + 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); + } + realstepList.length = 0; + } + this.ctx.draw(reserve, func); + } 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(); + } + }); + } +} diff --git a/components/painter/painter.js b/components/painter/painter.js new file mode 100644 index 0000000..7c13838 --- /dev/null +++ b/components/painter/painter.js @@ -0,0 +1,876 @@ +import Pen, { penCache, clearPenCache } from './lib/pen'; +import Downloader from './lib/downloader'; +import WxCanvas from './lib/wx-canvas'; + +const util = require('./lib/util'); +const calc = require('./lib/calc'); + +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: {}, + 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; + clearPenCache(); + this.startPaint(); + } + }, + }, + dancePalette: { + type: Object, + observer: function (newVal, oldVal) { + if (!this.isEmpty(newVal) && !this.properties.use2D) { + clearPenCache(); + 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, null, 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: { + 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); + + 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(); + new Pen(this.topContext, topDraw).paint(); + } else { + new Pen(this.topContext, topDraw).paint(); + this.doAction(); + 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.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) { + clearPenCache(this.touchedView.id); + 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, + }, + }, + null, + !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(); + const { width, height } = this.properties.palette; + console.log("startPaint width: ",width) + 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) { + console.log("this ..............") + console.log("width.toPx(): ", width.toPx()) + console.log("this.canvasWidthInPx: ", this.canvasWidthInPx) + + this.canvasWidthInPx = width.toPx(); + needScale = this.properties.use2D; + } + + console.log("this.properties.widthPixels: ", this.properties.widthPixels) + if (this.properties.widthPixels) { + console.log("that ......") + 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;`, + }, + function () { + this.downloadImages(this.properties.palette).then(async palette => { + 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)}`); + view.url = ''; + }, + complete: () => { + completeCount++; + if (preCount === completeCount) { + resolve(paletteCopy); + } + }, + }); + }, + () => { + completeCount++; + if (preCount === completeCount) { + resolve(paletteCopy); + } + }, + ); + } + } + } + if (preCount === 0) { + resolve(paletteCopy); + } + }); + }, + + saveImgToLocal() { + const that = this; + const optionsOf2d = { + canvas: that.canvasNode, + } + const optionsOfOld = { + canvasId: 'photo', + destWidth: that.canvasWidthInPx, + destHeight: that.canvasHeightInPx, + } + setTimeout(() => { + wx.canvasToTempFilePath( + { + ...(that.properties.use2D ? optionsOf2d : optionsOfOld), + 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 */ + /** + * string 到对应的 px + * @param {Number} baseSize 当设置了 % 号时,设置的基准值 + */ + String.prototype.toPx = function toPx(_, baseSize) { + if (this === '0') { + return 0; + } + const REG = /-?[0-9]+(\.[0-9]+)?(rpx|px|%)/; + + const parsePx = origin => { + const results = new RegExp(REG).exec(origin); + if (!origin || !results) { + console.error(`The size: ${origin} is illegal`); + return 0; + } + const unit = results[2]; + const value = parseFloat(origin); + + 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; + }; + const formula = /^calc\((.+)\)$/.exec(this); + if (formula && formula[1]) { + // 进行 calc 计算 + const afterOne = formula[1].replace(/([^\s\(\+\-\*\/]+)\.(left|right|bottom|top|width|height)/g, word => { + const [id, attr] = word.split('.'); + return penCache.viewRect[id][attr]; + }); + const afterTwo = afterOne.replace(new RegExp(REG, 'g'), parsePx); + return calc(afterTwo); + } else { + return parsePx(this); + } + }; +} diff --git a/components/painter/painter.json b/components/painter/painter.json new file mode 100644 index 0000000..e8cfaaf --- /dev/null +++ b/components/painter/painter.json @@ -0,0 +1,4 @@ +{ + "component": true, + "usingComponents": {} +} \ No newline at end of file diff --git a/components/painter/painter.wxml b/components/painter/painter.wxml new file mode 100644 index 0000000..77ca5a3 --- /dev/null +++ b/components/painter/painter.wxml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + diff --git a/components/painter/painter.wxss b/components/painter/painter.wxss new file mode 100644 index 0000000..ec31fe3 --- /dev/null +++ b/components/painter/painter.wxss @@ -0,0 +1 @@ +/* commpents/painter/painter.wxss */ \ No newline at end of file diff --git a/components/tabBar/tabBar.js b/components/tabBar/tabBar.js new file mode 100644 index 0000000..42bd372 --- /dev/null +++ b/components/tabBar/tabBar.js @@ -0,0 +1,24 @@ +// components/tabBar/tabBar.js +Component({ + + /** + * 组件的属性列表 + */ + properties: { + + }, + + /** + * 组件的初始数据 + */ + data: { + + }, + + /** + * 组件的方法列表 + */ + methods: { + + } +}) \ No newline at end of file diff --git a/components/tabBar/tabBar.json b/components/tabBar/tabBar.json new file mode 100644 index 0000000..53b25ac --- /dev/null +++ b/components/tabBar/tabBar.json @@ -0,0 +1,7 @@ +{ + "component": true, + "usingComponents": { + "van-tabbar": "@vant/weapp/tabbar/index", + "van-tabbar-item": "@vant/weapp/tabbar-item/index" + } +} \ No newline at end of file diff --git a/components/tabBar/tabBar.wxml b/components/tabBar/tabBar.wxml new file mode 100644 index 0000000..6e0bc34 --- /dev/null +++ b/components/tabBar/tabBar.wxml @@ -0,0 +1,2 @@ + +components/tabBar/tabBar.wxml \ No newline at end of file diff --git a/components/tabBar/tabBar.wxss b/components/tabBar/tabBar.wxss new file mode 100644 index 0000000..cf8ed07 --- /dev/null +++ b/components/tabBar/tabBar.wxss @@ -0,0 +1 @@ +/* components/tabBar/tabBar.wxss */ \ No newline at end of file diff --git a/custom-tab-bar/index.js b/custom-tab-bar/index.js new file mode 100644 index 0000000..ee8d1a9 --- /dev/null +++ b/custom-tab-bar/index.js @@ -0,0 +1,166 @@ +// components/tabBar/tabBar.js +const app=getApp(); +import {throttle} from "../utils/util" +import {storeBindingsBehavior} from "mobx-miniprogram-bindings" +import {store} from '../store/store.js' +import {getSign,getProjectStatus} from "../api/api" +Component({ + behaviors:[storeBindingsBehavior], + storeBindings:{ + store, + fields:{ + active:"active" + }, + actions:{ + updateActive:"updateActive" + } + }, + /** + * 组件的属性列表 + */ + properties: { + + }, + lifetimes: { + // 生命周期函数,可以为函数,或一个在methods段中定义的方法名 + attached: function () { + var info=wx.getSystemInfoSync() + if(info.platform == 'ios'){ + this.setData({ + isIos:true + }) + } + }, + moved: function () { }, + detached: function () { }, + }, + /** + * 组件的初始数据 + */ + data: { + isIos:false, + showAgree:false, + //active:'list', + img_host:app.hostConfig().imghost, + tabList:[{ + id:'list', + name:'病例列表', + normal:app.hostConfig().imghost+'/list.png', + active:app.hostConfig().imghost+'/list_on.png' + }, + { + id:'center', + name:'个人中心', + normal:app.hostConfig().imghost+'/center.png', + active:app.hostConfig().imghost+'/center_on.png' + } + ], + showDraft:false + }, + + /** + * 组件的方法列表 + */ + methods: { + handleGetProjectStatus(){ + getProjectStatus().then(res=>{ + this.handleGetSign() + }).catch((error)=>{ + if(error.code==30007){ + wx.showToast({ + title: '您未登录', + icon:'none', + duration:4000 + }) + app.method.navigateTo({ + url: '/case/pages/mobileLogin/mobileLogin', + }) + } + }) + }, + onConfirmDraft(){ + this.setData({ + showDraft:false + }) + app.method.navigateTo({ + url: '/case/pages/createCase/createCase', + }) + }, + onCancelDraft(){ + this.setData({ + showDraft:false + }) + }, + handleGetSign(){ + getSign().then(res=>{ + if(res && res.signImg){ + let caseDraft=wx.getStorageSync('caseDraft'); + if(caseDraft){ + this.setData({ + showDraft:true + }) + }else{ + app.method.navigateTo({ + url: '/case/pages/createCase/createCase', + }) + } + + }else{ + this.setData({ + showAgree:true + }) + } + }).catch(error=>{ + if(error.code==30007){ + wx.showToast({ + title: '您未登录', + icon:'none', + duration:4000 + }) + app.method.navigateTo({ + url: '/case/pages/mobileLogin/mobileLogin', + }) + } + }) + }, + onConfirmAgree(){ + this.setData({ + showAgree:false + }); + app.method.navigateTo({ + url:'/case/pages/agreement/agreement' + }) + }, + onCancelAgree(){ + this.setData({ + showAgree:false + }) + }, + goCreate:throttle(function(){ + this.handleGetProjectStatus() + }), + onChange(event) { + this.updateActive(event.detail) + // this.setData({ + // active:event.detail + // }); + + //console.log(event.detail ) + let url=event.detail=='list'?'/pages/index/index':'/pages/personCenter/personCenter' + console.log(url); + let THIS=this; + wx.switchTab({ + url: url, + success:function(){ + // THIS.setData({ + // active:app.globalData.tabBar_active + // }) + // console.log(app.globalData.tabBar_active); + } + }) + + + + }, + } +}) \ No newline at end of file diff --git a/custom-tab-bar/index.json b/custom-tab-bar/index.json new file mode 100644 index 0000000..c1c862b --- /dev/null +++ b/custom-tab-bar/index.json @@ -0,0 +1,8 @@ +{ + "component": true, + "usingComponents": { + "dialog":"../components/dialog/dialog", + "van-tabbar": "@vant/weapp/tabbar/index", + "van-tabbar-item": "@vant/weapp/tabbar-item/index" + } +} \ No newline at end of file diff --git a/custom-tab-bar/index.wxml b/custom-tab-bar/index.wxml new file mode 100644 index 0000000..260024f --- /dev/null +++ b/custom-tab-bar/index.wxml @@ -0,0 +1,25 @@ + + + + + + + {{item.name}} + + + + + + + + \ No newline at end of file diff --git a/custom-tab-bar/index.wxss b/custom-tab-bar/index.wxss new file mode 100644 index 0000000..7ce8ea6 --- /dev/null +++ b/custom-tab-bar/index.wxss @@ -0,0 +1,20 @@ +/* components/tabBar/tabBar.wxss */ +.tab{ + position: relative; +} +.create{ + position: fixed; + left:50%; + width: 153rpx; + padding:0px; + box-shadow: 0 -0.5px 0.5px rgba(0, 0, 0, 0.1); + background-color: #fff; + height: 153rpx; + transform:translateX(-50%) ; + bottom:-3rpx; + border-radius: 50%; + z-index:9999; +} +.create.isIos{ + bottom:63rpx; +} \ No newline at end of file diff --git a/filters/filter.wxs b/filters/filter.wxs new file mode 100644 index 0000000..8d50f38 --- /dev/null +++ b/filters/filter.wxs @@ -0,0 +1,53 @@ + +function transforDay(time, type) { + if (time == null || type == '') { + return '' + } + if (arguments.length === 0) { + return null + } + var time_cur=0 + if(typeof time=="string"){ + var reg = getRegExp("-", "g"); + var timeS=time.replace(reg, '/'); + time_cur = getDate(timeS).getTime(); + }else{ + time_cur=time*1000 + } + var date = getDate(time_cur);//在wxs中不能使用new Date()来处理日期 + console.log("date", date); + var y = date.getFullYear(); + var m = addZero(date.getMonth() + 1); + var d = addZero(date.getDate()); + var h =addZero(date.getHours()); + var i = addZero(date.getMinutes()); + var s =addZero(date.getSeconds()); + var a = addZero(date.getDay()); + var time_str = ""; + if (type == 'month') { + time_str = y + '-' + m; + }else if(type=='day'){ + time_str = m + '-' + d; + } else if(type=='HM'){ + time_str =h + ':' + s; + }else if (type == 'date') { + time_str = y + '-' + m + '-' + d; + } else if(type == 'dateminute'){ + time_str = y + '-' + m + '-' + d + ' ' + h + ':' + i + }else if (type == 'datetime') { + time_str = y + '-' + m + '-' + d + ' ' + h + ':' + i + ':' + s; + } else if (type == 'onlyMonth') { + time_str = m; + } else if (type == 'onlyYear') { + time_str = y; + } + return time_str +}; +function addZero(n) { + n = n.toString() + return n[1] ? n : '0' + n +}; + +module.exports = { + transforDay: transforDay +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..cb043db --- /dev/null +++ b/package.json @@ -0,0 +1,7 @@ +{ + "dependencies": { + "@vant/weapp": "^1.10.12", + "mobx-miniprogram": "^4.13.2", + "mobx-miniprogram-bindings": "^2.1.5" + } +} diff --git a/pages/index/index.js b/pages/index/index.js new file mode 100644 index 0000000..b623e3a --- /dev/null +++ b/pages/index/index.js @@ -0,0 +1,426 @@ +// index.js + const app = getApp() + import {caseList,getSign,getProjectStatus} from "../../api/api" +import {throttle} from "../../utils/util" +import {createStoreBindings} from "mobx-miniprogram-bindings" +import {store} from '../../store/store.js' +Page({ + data: { + showDelDraft:false, + editId:'', + showDraft:true, + reason:'', + status_title:'病例状态', + casetype_title:'病例类型', + draftTime:'', + caseDraft:{}, + hasDraft:false, + showEntryTip:false, + showEntryTip_second:false, + statusList: [ + { text: '待提交', value: -1,select:false }, + { text: '待审核', value: 0,select:false }, + { text: '待修改', value: 2,select:false }, + { text: '已通过', value: 1,select:false }, + ], + typeList:[{ + text:'四次及以上疗程化', + value:1, + select:false, + },{ + text:'早前期(INR≤1.5)', + value:2, + select:false, + }], + select_status:false, + select_type:false, + lock:false, + showCheck:false, + showAgree:false, + isTriggered:false, + list:[], + showCancel:true, + caseType:'', + status:'', + sortItemList:[{ + column:0, + isAsc:true + }], + name:'', + pageNum:1, + pageSize:10, + img_host:app.hostConfig().imghost + }, + handleGetProjectStatus(){ + getProjectStatus().then(res=>{ + this.handleGetSign() + }).catch((error)=>{ + if(error.code==30007){ + wx.showToast({ + title: '您未登录', + icon:'none', + duration:4000 + }) + app.method.navigateTo({ + url: '/case/pages/mobileLogin/mobileLogin', + }) + } + }) +}, + onConfirmDelDraft(){ + wx.setStorageSync('caseDraft', ''); + this.setData({ + showDelDraft:false, + hasDraft:false, + showDraft:false + }); + }, + onCancelDelDraft(){ + this.setData({ + showDelDraft:false, + }) + }, + onCloseSwipe(event){ + const { position, instance } = event.detail; + switch (position) { + case 'left': + case 'cell': + instance.close(); + break; + case 'right': + this.setData({ + showDelDraft:true + }) + break; + } + }, + goDetail:throttle(function(event){ + let {id,status,reason}=event.currentTarget.dataset; + if(status==2){ + this.setData({ + reason, + editId:id, + showCheck:true + }) + }else{ + app.method.navigateTo({ + url: '/case/pages/createCase/createCase?medicalRecordId='+id+"&status="+status, + }) + } + + }), + goCreate:throttle(function(){ + this.handleGetProjectStatus(); + + // app.method.navigateTo({ + // url: '/case/pages/createCase/createCase', + // }) + + }), + goAgreement:throttle(function(event){ + app.method.navigateTo({ + url:"/case/pages/privacy/privacy" + }) + }), + handleGetSign(){ + getSign().then(res=>{ + if(res && res.signImg){ + app.method.navigateTo({ + url: '/case/pages/createCase/createCase', + }) + }else{ + this.setData({ + showAgree:true + }) + } + }).catch(error=>{ + + if(error.code==30007){ + wx.showToast({ + title: '您未登录', + icon:'none', + duration:4000 + }) + app.method.navigateTo({ + url: '/case/pages/mobileLogin/mobileLogin', + }) + } + }) + }, + handleCaseList(){ + let { caseType,pageNum,pageSize,searchCount,status,name}=this.data; + + caseList({ + caseType, + pageNum, + name, + pageSize, + searchCount, + status + }).then(res=>{ + if(status==='' && caseType===''){ + this.toggleDraft(); + }; + this.setData({ + isTriggered:false + }) + // if(pageNum==1){ + // this.setData({ + // isTriggered:false + // }) + // } + if(res.list.length==0){ + this.setData({ + lock:true + }); + return; + }; + this.setData({ + list:this.data.list.concat(res.list) + }) + + }).catch(error=>{ + this.setData({ + isTriggered:false + }) + }) + }, + onConfirm(){ + let id=this.data.editId; + this.setData({ + showCheck:false + }) + app.method.navigateTo({ + url: '/case/pages/createCase/createCase?medicalRecordId='+id+"&status=2", + }) + }, + onCancel(){ + this.setData({ + showCheck:false + }) + }, + onConfirmAgree(){ + this.setData({ + showAgree:false + }); + app.method.navigateTo({ + url:'/case/pages/agreement/agreement' + }) + }, + onCancelAgree(){ + this.setData({ + showAgree:false + }) + }, + goSearch(value){ + this.setData({ + lock:false, + pageNum:1, + name:value.detail, + list:[] + }); + this.handleCaseList() + }, + handleRefresher(){ + let {status}=this.data; + if(status!=-1){ + this.setData({ + lock:false, + pageNum:1, + list:[] + }); + this.handleCaseList() + }else{ + this.setData({ + isTriggered:false + }) + } + + }, + lower(e) { + + let {lock}=this.data; + let addPage=this.data.pageNum+1; + if(!lock){ + this.setData({ + pageNum:addPage + }); + this.handleCaseList(); + } + }, + chooseStatus(e){ + let {hasDraft,statusList,typeList}=this.data; + let {id,value,name,select,index}=e.currentTarget.dataset; + this.selectComponent('#'+id).toggle(false); + + if(id=="item1"){ + statusList.forEach((item,key)=>{ + let cur_obj='statusList['+key+'].select' + this.setData({ + [cur_obj]:false + }) + }) + let obj='statusList['+index+'].select'; + this.setData({ + status:!select?value:'', + status_title:!select?name:'病例状态', + [obj]:!select, + }) + if(value==-1 && this.data.statusList[0].select){ + this.setData({ + showDraft:hasDraft?true:false, + list:[] + }) + }else if(value==-1 && !this.data.statusList[0].select){ + + this.handleRefresher(); + }else{ + this.setData({ + showDraft:false + }) + this.handleRefresher(); + + } + let hasSelect=this.data.statusList.some(item=> item.select==true ); + if(hasSelect){ + this.setData({ + select_status:true + }) + }else{ + this.setData({ + select_status:false + }) + } + + }else{ + typeList.forEach((item,i)=>{ + let cur_obj='typeList['+i+'].select' + this.setData({ + [cur_obj]:false + }) + }) + let obj='typeList['+index+'].select'; + this.setData({ + caseType:!select?value:'', + [obj]:!select, + casetype_title:!select?name:'病例类型' + }) + if(this.data.status==-1 && this.data.statusList[0].select){ + + this.setData({ + showDraft:hasDraft?true:false, + list:[] + }) + }else if(this.data.status==-1 && !this.data.statusList[0].select){ + this.handleRefresher(); + }else{ + this.setData({ + showDraft:false + }) + this.handleRefresher(); + } + let hasSelect=this.data.typeList.some(item=> item.select==true ); + if(hasSelect){ + this.setData({ + select_type:true + }) + }else{ + this.setData({ + select_type:false + }) + } + console.log(this.data.typeList); + } + + + }, + onConfirmEntry(){ + this.setData({ + showEntryTip:false + }) + }, + onCloseEntry(){ + this.setData({ + showEntryTip:true, + showEntryTip_second:true + }) + }, + onConfirmEntry_second(){ + this.setData({ + showEntryTip_second:false, + }); + //wx.setStorageSync('hasEntry', true); + + }, + onCloseEntry_second(){ + wx.exitMiniProgram({success: (res) => {}}) + this.setData({ + showEntryTip_second:false, + showEntryTip:false, + }); + + }, + toggleDraft(){ + let caseDraft=wx.getStorageSync('caseDraft'); + let draftTime=wx.getStorageSync('draftTime') + if(caseDraft){ + this.setData({ + caseDraft:JSON.parse(caseDraft), + hasDraft:true, + showDraft:true, + draftTime:draftTime + }) + }else{ + this.setData({ + showDraft:false, + hasDraft:false + }) + } + }, + onLoad(){ + this.storeBindings = createStoreBindings(this, { + store, + fields: [,"active"], + actions: ["updateActive"], + }); + }, + onUnload() { + this.storeBindings.destroyStoreBindings(); +}, + onShow(){ + console.log("show") + this.updateActive('list'); + let nav=this.selectComponent('#nav'); + nav.clearKeyWord(); + this.setData({ + name:'', + select_status:false, + select_type:false, + status_title:'病例状态', + casetype_title:'病例类型', + status:'', + caseType:'' + }) + this.toggleDraft(); + this.handleRefresher(); + + wx.getPrivacySetting({ + success: res => { + console.log(res) // 返回结果为: res = { needAuthorization: true/false, privacyContractName: '《xxx隐私保护指引》' } + if (res.needAuthorization) { + // 需要弹出隐私协议 + this.setData({ + showEntryTip:true + }); + } else { + this.setData({ + showEntryTip:false + }) + // 用户已经同意过隐私协议,所以不需要再弹出隐私协议,也能调用已声明过的隐私 + } + }, + fail: () => {}, + complete: () => {} + }) + } +}); diff --git a/pages/index/index.json b/pages/index/index.json new file mode 100644 index 0000000..8376cec --- /dev/null +++ b/pages/index/index.json @@ -0,0 +1,13 @@ +{ + "usingComponents": { + "dialog":"../../components/dialog/dialog", + "van-image": "@vant/weapp/image/index", + "van-overlay": "@vant/weapp/overlay/index", + "van-swipe-cell": "@vant/weapp/swipe-cell/index", + "nav":"../../components/nav/nav", + "van-dropdown-menu": "@vant/weapp/dropdown-menu/index", + "van-dropdown-item": "@vant/weapp/dropdown-item/index" + }, + "disableScroll": true, + "navigationStyle":"custom" +} \ No newline at end of file diff --git a/pages/index/index.wxml b/pages/index/index.wxml new file mode 100644 index 0000000..c7c79c1 --- /dev/null +++ b/pages/index/index.wxml @@ -0,0 +1,102 @@ + + + + + + + + + + {{item.text}} + + + + + {{item.text}} + + + + + + + + + + + + + + + 草稿 + {{draftTime}} + + + 待提交 + + + 删除 + + + + + + + + + + {{item.userName}}({{item.uid}}) + {{filters.transforDay(item.createTime,'date')}} + + + {{item.status==0?'待审核':item.status==1?'已通过':item.status==2?'待修改':'未知'}} + + + + + + + + 暂无病例,点击创建病例 + + + + + + + + + + + 温馨提示 + + 亲爱的用户,感谢您信任并使用人工肝病例登录系统!我们依据最新法律法规的要求,制定了《隐私协议》。请您仔细阅《隐私协议》,并确认了解我们对您的个人信息处理原则。 + 如您同意《隐私协议》,请点击“同意”开始使用我们的产品和服务。 + + + 不同意 + 同意并继续 + + + + + + + + 温馨提示 + + 很抱歉,如您不同意《隐私协议》,可能无法继续正常使用我们的服务,请您先同意哦~ + + + 不同意 + 好的 + + + + \ No newline at end of file diff --git a/pages/index/index.wxss b/pages/index/index.wxss new file mode 100644 index 0000000..deb0d2b --- /dev/null +++ b/pages/index/index.wxss @@ -0,0 +1,208 @@ +page{ + background-image: linear-gradient( #cdf0ff,#fff 30%); +} +.page { + margin-top: 290rpx; + overflow: hidden; + flex: 1; + display: flex; + flex-direction: column; + +} +.viewcon{ + padding-bottom: 140rpx; +} +.banner { + margin: 24rpx 32rpx 0; + height: 264rpx; + background: #D8D8D8; + border-radius: 16rpx; +} +.banner .bannerImg{ + width:100%; +} +.row { + margin: 24rpx 0; + display: flex; + padding: 0 32rpx; + justify-content: space-between; +} + +.row .rowcell { + width: 160rpx; + display: flex; + align-items: center; + justify-content: center; + height: 72rpx; + color: rgba(0, 0, 0, 0.65); + font-size: 32rpx; + background: rgba(0, 0, 0, 0.06); + border-radius: 36rpx; +} +.type .rowcell{ + width:auto; + padding:0 24rpx; +} +.row .rowcell.active { + color: #3881F7; + background: #EBF3FF; +} + +.scrollwraper { + flex: 1; + overflow-y: scroll; + -webkit-overflow-scrolling: touch; + background: #f7f9f9; + padding-top: 30rpx; +} + +.viewcell { + display: flex; + justify-content: space-between; + align-items: center; + margin: 0 32rpx 16rpx; + padding: 24rpx 21rpx; + background-color: #fff; + box-shadow: 0rpx 12rpx 28rpx 0rpx rgba(0, 0, 0, 0.03); + border-radius: 16rpx; +} + +.viewcell .left { + display: flex; + align-items: center; +} + +.viewcell .left .name { + font-size: 32rpx; + font-weight: 400; + color: rgba(0, 0, 0, 0.85); +} + +.viewcell .left .date { + margin-top: 10rpx; + font-size: 28rpx; + font-weight: 400; + color: rgba(0, 0, 0, 0.25); +} + +.viewcell .left .info { + margin-left: 13rpx; +} + +.viewcell .btn { + width: 132rpx; + height: 56rpx; + display: flex; + justify-content: center; + align-items: center; + background: #FFFFFF; + border-radius: 8rpx; + font-size: 28rpx; + font-weight: 400; + color: rgba(0, 0, 0, 0.65); + border: 2rpx solid rgba(0, 0, 0, 0.15); +} + +.viewcell .btn.active { + color: #52C41A; + border: 2rpx solid #52C41A; +} +.viewcell .btn.red { + color: red; + border: 2rpx solid red; +} +.van-dropdown-menu { + margin:0 32rpx; + box-shadow: none!important; +} +.van-dropdown-menu__item{ + flex:none!important; + margin-right: 20rpx; +} +.nocase{ + width:300rpx; + height: 218rpx; +} +.nodatatip{ + margin-top: 16rpx; + font-size: 28rpx; +font-weight: 400; +color: rgba(0,0,0,0.45); +} +.link{ + color:#3881F7; +} +.droptitle{ + font-size: 32rpx!important; +} +.droptitle.active{ + + color:#3881F7!important; +} +.wrapper { + display: flex; + align-items: center; + justify-content: center; + height: 100%; + + } + .privacyBox { + width:90%; + background-color: #fff; + border-radius: 30rpx; + } + .privacyBox .title{ + margin: 0; + text-align: center; + font-weight: 500; + line-height:24px; + padding-top:24px; + text-align: center; + font-size:16px; + } + .privacyBox .btnbox{ + display: flex; + align-items: center; + border-top:1rpx solid #efefef; + width:100%; + font-size: 17px; + } + .privacyBox .btnbox .cancel{ + flex:1; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + } + .privacyBox .btnbox .confirm{ + margin: 0; + height: 50px; + padding: 0; + color: #3881F7; + flex:1; + font-size: 17px; + border: none!important; + border-left:1rpx solid #efefef!important; + display: flex; + background-color: none!important; + justify-content: center; + align-items: center; + } + .entrymsg{ + -webkit-overflow-scrolling: touch; + font-size: 28rpx; + line-height: 40rpx; + max-height: 60vh; + overflow-y: auto; + padding: 48rpx; + text-align: left; + } + .van-swipe-cell__right{ + width:65px; + display: flex; + font-size: 28rpx; + justify-content: center; + align-items: center; + color: #fff; + background-color: red; + } \ No newline at end of file diff --git a/pages/personCenter/personCenter.js b/pages/personCenter/personCenter.js new file mode 100644 index 0000000..df124d8 --- /dev/null +++ b/pages/personCenter/personCenter.js @@ -0,0 +1,149 @@ +// pages/personCenter/personCenter.js +import {getInfo,logout} from "../../api/api" +import {throttle} from "../../utils/util" +import {createStoreBindings} from "mobx-miniprogram-bindings" +import {store} from '../../store/store.js' +const app=getApp(); +Page({ + + /** + * 页面的初始数据 + */ + data: { + isLogin:false, + userInfo:{ + hospitalName:'', + name:'', + photo:'', + passNum:'', + waitNum:'', + refuseNum:'', + + }, + waitSubmitNum:0, + img_host:app.hostConfig().imghost + }, + goLogin:throttle(function(){ + app.method.navigateTo({ + url:'/case/pages/mobileLogin/mobileLogin' + }) + }), + goAgree:throttle(function(){ + app.method.navigateTo({ + url: '/case/pages/agreement/agreement', + }) + }), + goDescription:throttle(function(){ + app.method.navigateTo({ + url: '/case/pages/agreement/agreement?type=description', + }) + }), + handleLogout:throttle(function(){ + logout().then(res=>{ + // wx.clearStorageSync() + const { envVersion } = wx.getAccountInfoSync().miniProgram; + let token='' + if(envVersion=="develop" || envVersion=="trial"){ + token="DEV_CASE_TOKEN" + }else{ + token="PROD_CASE_TOKEN" + } + wx.setStorageSync(token,''); + wx.setStorageSync('draftTime',''); + wx.reLaunch({ + url:'/case/pages/mobileLogin/mobileLogin' + }) + }) + }), + handleGetInfo(){ + let {userInfo}=this.data; + getInfo().then(res=>{ + this.setData({ + isLogin:true + }); + for (const key in userInfo) { + this.setData({ + ['userInfo.'+key]:res[key] + }) + } + }).catch(error=>{ + if(error.code==30007){ + this.setData({ + isLogin:false + }) + } + }) + }, + /** + * 生命周期函数--监听页面加载 + */ + onLoad(options) { + + this.storeBindings = createStoreBindings(this, { + store, + fields: [,"active"], + actions: ["updateActive"], + }); + + }, + + /** + * 生命周期函数--监听页面初次渲染完成 + */ + onReady() { + + }, + + /** + * 生命周期函数--监听页面显示 + */ + onShow(){ + let caseDraft=wx.getStorageSync('caseDraft'); + if(caseDraft){ + this.setData({ + waitSubmitNum:1 + }) + }else{ + this.setData({ + waitSubmitNum:0 + }) + } + this.handleGetInfo(); + this.updateActive('center') + }, + + /** + * 生命周期函数--监听页面隐藏 + */ + onHide() { + + }, + + /** + * 生命周期函数--监听页面卸载 + */ + onUnload() { + this.storeBindings.destroyStoreBindings(); + }, + + /** + * 页面相关事件处理函数--监听用户下拉动作 + */ + onPullDownRefresh() { + + }, + + /** + * 页面上拉触底事件的处理函数 + */ + onReachBottom() { + + }, + + /** + * 用户点击右上角分享 + */ + // onShareAppMessage() { + + // } +}) \ No newline at end of file diff --git a/pages/personCenter/personCenter.json b/pages/personCenter/personCenter.json new file mode 100644 index 0000000..0744e74 --- /dev/null +++ b/pages/personCenter/personCenter.json @@ -0,0 +1,7 @@ +{ + "usingComponents": { + "van-image": "@vant/weapp/image/index", + "van-icon": "@vant/weapp/icon/index" + }, + "navigationStyle":"custom" +} \ No newline at end of file diff --git a/pages/personCenter/personCenter.wxml b/pages/personCenter/personCenter.wxml new file mode 100644 index 0000000..3f6be1c --- /dev/null +++ b/pages/personCenter/personCenter.wxml @@ -0,0 +1,58 @@ + + + 个人中心 + + + + + + {{userInfo.name}} + {{userInfo.hospitalName}} + + + + 点击登录 + - + + + + {{isLogin?userInfo.passNum:'-'}} + 已通过 + + + {{isLogin?userInfo.waitNum:'-'}} + 待审核 + + + {{isLogin?userInfo.refuseNum:'-'}} + 待修改 + + + {{isLogin?waitSubmitNum:'-'}} + 待提交 + + + + + + + + 项目协议 + + + + + + + + + 操作说明 + + + + + + 退出登录 + \ No newline at end of file diff --git a/pages/personCenter/personCenter.wxss b/pages/personCenter/personCenter.wxss new file mode 100644 index 0000000..45a60a9 --- /dev/null +++ b/pages/personCenter/personCenter.wxss @@ -0,0 +1,116 @@ +/* pages/personCenter/personCenter.wxss */ +page{ + background-image: linear-gradient( #cdf0ff,#F7F9F9 30%); +} +.page{ + margin-top: 180rpx; +} +.nav{ + position: fixed; + top:0; + height:290rpx; + width:750rpx; +} +.nav .title{ + font-size: 36rpx; + font-weight: 600; + color: #272B34; + margin: 106rpx 32rpx 0; +} +.headbox{ + margin:0 32rpx 24rpx; + display: flex; + flex-direction: column; + align-items: center; +} +.headbox .name{ + margin-top: 15rpx; +font-size: 36rpx; +line-height: 54rpx; +font-weight: 550; +color: rgba(0,0,0,0.85); +} +.hospital{ + line-height: 44rpx; + font-size: 28rpx; + font-weight: 400; + color: rgba(0,0,0,0.65); +} +.van-image--round{ + box-shadow: 0 5rpx 10rpx 0 #ccc; +} + +.databox{ + padding:24rpx 0 ; + margin:0 32rpx; + display: flex; + background: #FFFFFF; + box-shadow: 0rpx 12rpx 28rpx 0rpx rgba(0,0,0,0.03); + border-radius: 16rpx; + justify-content: space-around; +} +.datacell{ + display: flex; + flex-direction: column; + align-items: center; +} +.datacell .num{ + font-size: 40rpx; + font-weight: bold; + color: #333333; + line-height: 48rpx; +} +.datacell .num.active{ + color: #3881F7; +} +.datacell .name{ + font-size: 28rpx; + font-weight: 400; + color: rgba(0,0,0,0.45); + line-height: 44rpx; +} +.itembox{ + margin:24rpx 32rpx 0; + background: #FFFFFF; + box-shadow: 0rpx 12rpx 28rpx 0rpx rgba(0,0,0,0.03); + border-radius: 16rpx +} +.itembox .cell{ + padding:0 24rpx; +} +.cell .row{ + padding:32rpx 0; + border-bottom: 1rpx solid rgba(0,0,0,0.1); + align-items: center; + display: flex; + justify-content: space-between; +} +.cell:last-child .row{ + border-bottom:none +} +.cell .left{ + display: flex; + align-items: center; +} +.cell .item{ + width:40rpx; + height:40rpx; +} +.itembox .left .name{ + font-size: 32rpx; + margin-left: 12rpx; +font-weight: 400; +color: rgba(0,0,0,0.65); +} +.logout{ + margin:32rpx 32rpx 0; + height: 88rpx; + font-size: 32rpx; +font-weight: 400; +color: #FF4D4F; + background: #FFFFFF; + border-radius: 8rpx; + align-items: center; + display: flex; + justify-content: center; +} \ No newline at end of file diff --git a/project.config.json b/project.config.json new file mode 100644 index 0000000..ae5299c --- /dev/null +++ b/project.config.json @@ -0,0 +1,36 @@ +{ + "appid": "wx415cbcf96f4a3b27", + "compileType": "miniprogram", + "libVersion": "3.3.2", + "packOptions": { + "ignore": [], + "include": [] + }, + "setting": { + "coverView": true, + "es6": true, + "postcss": true, + "minified": true, + "enhance": true, + "showShadowRootInWxmlPanel": true, + "ignoreDevUnusedFiles": false, + "ignoreUploadUnusedFiles": false, + "packNpmRelationList": [ + { + "packageJsonPath": "./package.json", + "miniprogramNpmDistDir": "./" + } + ], + "babelSetting": { + "ignore": [], + "disablePlugins": [], + "outputPath": "" + }, + "packNpmManually": true + }, + "condition": {}, + "editorSetting": { + "tabIndent": "insertSpaces", + "tabSize": 4 + } +} \ No newline at end of file diff --git a/project.private.config.json b/project.private.config.json new file mode 100644 index 0000000..d01c240 --- /dev/null +++ b/project.private.config.json @@ -0,0 +1,9 @@ +{ + "description": "项目私有配置文件。此文件中的内容将覆盖 project.config.json 中的相同字段。项目的改动优先同步到此文件中。详见文档:https://developers.weixin.qq.com/miniprogram/dev/devtools/projectconfig.html", + "projectname": "miniprogram-caseSystem", + "setting": { + "compileHotReLoad": true, + "urlCheck": false + }, + "libVersion": "3.8.8" +} \ No newline at end of file diff --git a/static/banner.png b/static/banner.png new file mode 100644 index 0000000..dd10be7 Binary files /dev/null and b/static/banner.png differ diff --git a/static/caozuo.png b/static/caozuo.png new file mode 100644 index 0000000..c40a69a Binary files /dev/null and b/static/caozuo.png differ diff --git a/static/center.png b/static/center.png new file mode 100644 index 0000000..eef77ca Binary files /dev/null and b/static/center.png differ diff --git a/static/center_on.png b/static/center_on.png new file mode 100644 index 0000000..c2ead4b Binary files /dev/null and b/static/center_on.png differ diff --git a/static/create.png b/static/create.png new file mode 100644 index 0000000..23f2e2e Binary files /dev/null and b/static/create.png differ diff --git a/static/default_avatar.png b/static/default_avatar.png new file mode 100644 index 0000000..feb77c1 Binary files /dev/null and b/static/default_avatar.png differ diff --git a/static/gdxz.png b/static/gdxz.png new file mode 100644 index 0000000..f835602 Binary files /dev/null and b/static/gdxz.png differ diff --git a/static/list.png b/static/list.png new file mode 100644 index 0000000..8bad871 Binary files /dev/null and b/static/list.png differ diff --git a/static/list_on.png b/static/list_on.png new file mode 100644 index 0000000..dec3622 Binary files /dev/null and b/static/list_on.png differ diff --git a/static/man.png b/static/man.png new file mode 100644 index 0000000..862057d Binary files /dev/null and b/static/man.png differ diff --git a/static/navbg.png b/static/navbg.png new file mode 100644 index 0000000..254e512 Binary files /dev/null and b/static/navbg.png differ diff --git a/static/noCase.png b/static/noCase.png new file mode 100644 index 0000000..05df600 Binary files /dev/null and b/static/noCase.png differ diff --git a/static/title_bg.png b/static/title_bg.png new file mode 100644 index 0000000..a15d098 Binary files /dev/null and b/static/title_bg.png differ diff --git a/static/upload.png b/static/upload.png new file mode 100644 index 0000000..4360f99 Binary files /dev/null and b/static/upload.png differ diff --git a/static/wechat.png b/static/wechat.png new file mode 100644 index 0000000..edea9d7 Binary files /dev/null and b/static/wechat.png differ diff --git a/static/woman.png b/static/woman.png new file mode 100644 index 0000000..f28325e Binary files /dev/null and b/static/woman.png differ diff --git a/static/xiangmu.png b/static/xiangmu.png new file mode 100644 index 0000000..455f586 Binary files /dev/null and b/static/xiangmu.png differ diff --git a/static/xinlin.png b/static/xinlin.png new file mode 100644 index 0000000..13128f5 Binary files /dev/null and b/static/xinlin.png differ diff --git a/store/store.js b/store/store.js new file mode 100644 index 0000000..96da790 --- /dev/null +++ b/store/store.js @@ -0,0 +1,8 @@ +import { observable,action} from'mobx-miniprogram' +export const store = observable({ + active:'list', + updateActive:action(function(val){ + console.log(val) + this.active=val; + }) +}) \ No newline at end of file diff --git a/utils/base64ToImg.js b/utils/base64ToImg.js new file mode 100644 index 0000000..038fd16 --- /dev/null +++ b/utils/base64ToImg.js @@ -0,0 +1,13 @@ +const base64src = (base64data, fun) => { + const base64 = base64data; //base64格式图片 + const time = new Date().getTime(); + const imgPath = wx.env.USER_DATA_PATH + "/poster" + time + "share" + ".png"; + //如果图片字符串不含要清空的前缀,可以不执行下行代码. + const imageData = base64.replace(/^data:image\/\w+;base64,/, ""); + const file = wx.getFileSystemManager(); + file.writeFileSync(imgPath, imageData, "base64"); + fun(imgPath); + }; + + export { base64src }; + \ No newline at end of file diff --git a/utils/config.js b/utils/config.js new file mode 100644 index 0000000..6c5f44c --- /dev/null +++ b/utils/config.js @@ -0,0 +1,33 @@ + + function hostConfig(){ + const Hosts = { + host_dev:'https://dev-case.igandan.com/api', + img_dev: 'https://medical-case.oss-cn-beijing.aliyuncs.com/static', //开发环境 + + host_prod:'https://case.igandan.com/api', + img_prod: 'https://medical-case.oss-cn-beijing.aliyuncs.com/static', //线上环境 + + }; + const { envVersion } = wx.getAccountInfoSync().miniProgram; + let imghost=""; + let host=''; + switch (envVersion) { + case 'develop': //开发环境 + host = `${Hosts.host_dev}`; + imghost= `${Hosts.img_dev}`; + break; + case 'trial': //体验版环境 + host = `${Hosts.host_dev}`; + imghost=`${Hosts.img_dev}`; + break; + case 'release': + host =`${Hosts.host_prod}`; + imghost=`${Hosts.img_prod}`; + break; + }; + return { host, imghost}; + +} +module.exports={ + hostConfig +} \ No newline at end of file diff --git a/utils/dayjs.js b/utils/dayjs.js new file mode 100644 index 0000000..ae93b6c --- /dev/null +++ b/utils/dayjs.js @@ -0,0 +1,120 @@ +!(function (t, e) { + 'object' === typeof exports && 'undefined' !== typeof module ? module.exports = e() : 'function' === typeof define && define.amd ? define(e) : (t = 'undefined' !== typeof globalThis ? globalThis : t || self).dayjs = e(); +}(this, (() => { + 'use strict';const t = 1e3; const e = 6e4; const n = 36e5; const r = 'millisecond'; const i = 'second'; const s = 'minute'; const u = 'hour'; const a = 'day'; const o = 'week'; const f = 'month'; const h = 'quarter'; const c = 'year'; const d = 'date'; const $ = 'Invalid Date'; const l = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/; const y = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g; const M = { name: 'en', weekdays: 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), months: 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_') }; const m = function (t, e, n) { + const r = String(t);return !r || r.length >= e ? t : `${Array(e + 1 - r.length).join(n)}${t}`; + }; const g = { s: m, z(t) { + const e = -t.utcOffset(); const n = Math.abs(e); const r = Math.floor(n / 60); const i = n % 60;return `${(e <= 0 ? '+' : '-') + m(r, 2, '0')}:${m(i, 2, '0')}`; + }, m: function t(e, n) { + if (e.date() < n.date()) return -t(n, e);const r = 12 * (n.year() - e.year()) + (n.month() - e.month()); const i = e.clone().add(r, f); const s = n - i < 0; const u = e.clone().add(r + (s ? -1 : 1), f);return +(-(r + (n - i) / (s ? i - u : u - i)) || 0); + }, a(t) { + return t < 0 ? Math.ceil(t) || 0 : Math.floor(t); + }, p(t) { + return { M: f, y: c, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: h }[t] || String(t || '').toLowerCase() + .replace(/s$/, ''); + }, u(t) { + return void 0 === t; + } }; let D = 'en'; const v = {};v[D] = M;const p = function (t) { + return t instanceof _; + }; const S = function (t, e, n) { + let r;if (!t) return D;if ('string' === typeof t)v[t] && (r = t), e && (v[t] = e, r = t);else { + const i = t.name;v[i] = t, r = i; + } return !n && r && (D = r), r || !n && D; + }; const w = function (t, e) { + if (p(t)) return t.clone();const n = 'object' === typeof e ? e : {};return n.date = t, n.args = arguments, new _(n); + }; const O = g;O.l = S, O.i = p, O.w = function (t, e) { + return w(t, { locale: e.$L, utc: e.$u, x: e.$x, $offset: e.$offset }); + }; + var _ = (function () { + function M(t) { + this.$L = S(t.locale, null, !0), this.parse(t); + } const m = M.prototype;return m.parse = function (t) { + this.$d = (function (t) { + const e = t.date; const n = t.utc;if (null === e) return new Date(NaN);if (O.u(e)) return new Date;if (e instanceof Date) return new Date(e);if ('string' === typeof e && !/Z$/i.test(e)) { + const r = e.match(l);if (r) { + const i = r[2] - 1 || 0; const s = (r[7] || '0').substring(0, 3);return n ? new Date(Date.UTC(r[1], i, r[3] || 1, r[4] || 0, r[5] || 0, r[6] || 0, s)) : new Date(r[1], i, r[3] || 1, r[4] || 0, r[5] || 0, r[6] || 0, s); + } + } return new Date(e); + }(t)), this.$x = t.x || {}, this.init(); + }, m.init = function () { + const t = this.$d;this.$y = t.getFullYear(), this.$M = t.getMonth(), this.$D = t.getDate(), this.$W = t.getDay(), this.$H = t.getHours(), this.$m = t.getMinutes(), this.$s = t.getSeconds(), this.$ms = t.getMilliseconds(); + }, m.$utils = function () { + return O; + }, m.isValid = function () { + return !(this.$d.toString() === $); + }, m.isSame = function (t, e) { + const n = w(t);return this.startOf(e) <= n && n <= this.endOf(e); + }, m.isAfter = function (t, e) { + return w(t) < this.startOf(e); + }, m.isBefore = function (t, e) { + return this.endOf(e) < w(t); + }, m.$g = function (t, e, n) { + return O.u(t) ? this[e] : this.set(n, t); + }, m.unix = function () { + return Math.floor(this.valueOf() / 1e3); + }, m.valueOf = function () { + return this.$d.getTime(); + }, m.startOf = function (t, e) { + const n = this; const r = !!O.u(e) || e; const h = O.p(t); const $ = function (t, e) { + const i = O.w(n.$u ? Date.UTC(n.$y, e, t) : new Date(n.$y, e, t), n);return r ? i : i.endOf(a); + }; const l = function (t, e) { + return O.w(n.toDate()[t].apply(n.toDate('s'), (r ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e)), n); + }; const y = this.$W; const M = this.$M; const m = this.$D; const g = `set${this.$u ? 'UTC' : ''}`;switch (h) { + // eslint-disable-next-line no-var + case c:return r ? $(1, 0) : $(31, 11);case f:return r ? $(1, M) : $(0, M + 1);case o:var D = this.$locale().weekStart || 0; var v = (y < D ? y + 7 : y) - D;return $(r ? m - v : m + (6 - v), M);case a:case d:return l(`${g}Hours`, 0);case u:return l(`${g}Minutes`, 1);case s:return l(`${g}Seconds`, 2);case i:return l(`${g}Milliseconds`, 3);default:return this.clone(); + } + }, m.endOf = function (t) { + return this.startOf(t, !1); + }, m.$set = function (t, e) { + let n; const o = O.p(t); const h = `set${this.$u ? 'UTC' : ''}`; const $ = (n = {}, n[a] = `${h}Date`, n[d] = `${h}Date`, n[f] = `${h}Month`, n[c] = `${h}FullYear`, n[u] = `${h}Hours`, n[s] = `${h}Minutes`, n[i] = `${h}Seconds`, n[r] = `${h}Milliseconds`, n)[o]; const l = o === a ? this.$D + (e - this.$W) : e;if (o === f || o === c) { + const y = this.clone().set(d, 1);y.$d[$](l), y.init(), this.$d = y.set(d, Math.min(this.$D, y.daysInMonth())).$d; + } else $ && this.$d[$](l);return this.init(), this; + }, m.set = function (t, e) { + return this.clone().$set(t, e); + }, m.get = function (t) { + return this[O.p(t)](); + }, m.add = function (r, h) { + let d; const $ = this;r = Number(r);const l = O.p(h); const y = function (t) { + const e = w($);return O.w(e.date(e.date() + Math.round(t * r)), $); + };if (l === f) return this.set(f, this.$M + r);if (l === c) return this.set(c, this.$y + r);if (l === a) return y(1);if (l === o) return y(7);const M = (d = {}, d[s] = e, d[u] = n, d[i] = t, d)[l] || 1; const m = this.$d.getTime() + r * M;return O.w(m, this); + }, m.subtract = function (t, e) { + return this.add(-1 * t, e); + }, m.format = function (t) { + const e = this; const n = this.$locale();if (!this.isValid()) return n.invalidDate || $;const r = t || 'YYYY-MM-DDTHH:mm:ssZ'; const i = O.z(this); const s = this.$H; const u = this.$m; const a = this.$M; const o = n.weekdays; const f = n.months; const h = function (t, n, i, s) { + return t && (t[n] || t(e, r)) || i[n].substr(0, s); + }; const c = function (t) { + return O.s(s % 12 || 12, t, '0'); + }; const d = n.meridiem || function (t, _e, n) { + const r = t < 12 ? 'AM' : 'PM';return n ? r.toLowerCase() : r; + }; const l = { YY: String(this.$y).slice(-2), YYYY: this.$y, M: a + 1, MM: O.s(a + 1, 2, '0'), MMM: h(n.monthsShort, a, f, 3), MMMM: h(f, a), D: this.$D, DD: O.s(this.$D, 2, '0'), d: String(this.$W), dd: h(n.weekdaysMin, this.$W, o, 2), ddd: h(n.weekdaysShort, this.$W, o, 3), dddd: o[this.$W], H: String(s), HH: O.s(s, 2, '0'), h: c(1), hh: c(2), a: d(s, u, !0), A: d(s, u, !1), m: String(u), mm: O.s(u, 2, '0'), s: String(this.$s), ss: O.s(this.$s, 2, '0'), SSS: O.s(this.$ms, 3, '0'), Z: i };return r.replace(y, ((t, e) => e || l[t] || i.replace(':', ''))); + }, m.utcOffset = function () { + return 15 * -Math.round(this.$d.getTimezoneOffset() / 15); + }, m.diff = function (r, d, $) { + let l; const y = O.p(d); const M = w(r); const m = (M.utcOffset() - this.utcOffset()) * e; const g = this - M; let D = O.m(this, M);return D = (l = {}, l[c] = D / 12, l[f] = D, l[h] = D / 3, l[o] = (g - m) / 6048e5, l[a] = (g - m) / 864e5, l[u] = g / n, l[s] = g / e, l[i] = g / t, l)[y] || g, $ ? D : O.a(D); + }, m.daysInMonth = function () { + return this.endOf(f).$D; + }, m.$locale = function () { + return v[this.$L]; + }, m.locale = function (t, e) { + if (!t) return this.$L;const n = this.clone(); const r = S(t, e, !0);return r && (n.$L = r), n; + }, m.clone = function () { + return O.w(this.$d, this); + }, m.toDate = function () { + return new Date(this.valueOf()); + }, m.toJSON = function () { + return this.isValid() ? this.toISOString() : null; + }, m.toISOString = function () { + return this.$d.toISOString(); + }, m.toString = function () { + return this.$d.toUTCString(); + }, M; + }()); const b = _.prototype;return w.prototype = b, [['$ms', r], ['$s', i], ['$m', s], ['$H', u], ['$W', a], ['$M', f], ['$y', c], ['$D', d]].forEach(((t) => { + b[t[1]] = function (e) { + return this.$g(e, t[0], t[1]); + }; + })), w.extend = function (t, e) { + return t.$i || (t(e, _, w), t.$i = !0), w; + }, w.locale = S, w.isDayjs = p, w.unix = function (t) { + return w(1e3 * t); + }, w.en = v[D], w.Ls = v, w.p = {}, w; +}))); diff --git a/utils/fileutil.js b/utils/fileutil.js new file mode 100644 index 0000000..fce7810 --- /dev/null +++ b/utils/fileutil.js @@ -0,0 +1,91 @@ +const FileUtil = { + + //file 为微信file 单文件 + getFileName(file){ + // size: 132224 + // thumb: "http://tmp/0ftStiYfd18K517836423bbfde024c385140a666b344.png" + // type: "image" + // url: "http://tmp/0ftStiYfd18K517836423bbfde024c385140a666b344.png" + + if(file){ + const uuid = this.UUID().replace("/-/g",""); + if(file.url && (file.url.indexOf(".") > -1)){ + const fileType = file.url.split(".")[1]; + return uuid+"."+fileType; + }else{ + return; + } + } + return; + }, + + UUID () { + if (typeof crypto === 'object') { + if (typeof crypto.randomUUID === 'function') { + return crypto.randomUUID(); + } + if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') { + const callback = (c) => { + const num = Number(c); + return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(16); + }; + return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, callback); + } + } + let timestamp = new Date().getTime(); + let perforNow = (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0; + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => { + let random = Math.random() * 16; + if (timestamp > 0) { + random = (timestamp + random) % 16 | 0; + timestamp = Math.floor(timestamp / 16); + } else { + random = (perforNow + random) % 16 | 0; + perforNow = Math.floor(perforNow / 16); + } + return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16); + }); + }, + + canvasToFile(){ + let canvas = window.map3DControl.viewer.canvas; + let imgWidth = 1920; + let img = Canvas2Image.convertToImage( + canvas, + imgWidth, + (imgWidth * canvas.height) / canvas.width, + "png" + ); + let file_name = new Date().getTime() + ".png"; + let imgsrc = img.src.slice(22) + + let file = this.convertBase64UrlToImgFile( + imgsrc, + file_name, + "image/png" + ) + return file; + }, + + convertBase64UrlToImgFile(urlData, fileName, fileType) { + urlData = urlData.replace(/^data:image\/\w+;base64,/, ""); + let bytes = wx.base64ToArrayBuffer(urlData); + + // var bytes = wx.atob(urlData); //转换为byte + //处理异常,将ascii码小于0的转换为大于0 + var ab = new ArrayBuffer(bytes.length); + var ia = new Int8Array(ab); + var i; + for (i = 0; i < bytes.length; i++) { + ia[i] = bytes.charCodeAt(i); + } + //转换成文件,添加文件的type,name,lastModifiedDate属性 + var blob = new Blob([ab], { type: fileType }); + blob.lastModifiedDate = new Date(); + blob.name = fileName; + return blob; + } +} + + +export { FileUtil } \ No newline at end of file diff --git a/utils/formatParams.js b/utils/formatParams.js new file mode 100644 index 0000000..97aa8d1 --- /dev/null +++ b/utils/formatParams.js @@ -0,0 +1,26 @@ +import { + getCurrentPageUrl, + getCurrentPageParam +} from "./getUrl" +function formatUrl(){ + let redirectUrl=getCurrentPageUrl(); + let options=getCurrentPageParam(); + let params=""; + let url=''; + for (const key in options) { + if(params){ + params=params+'&'+key+'='+options[key]; + }else{ + params=params+key+'='+options[key]; + } + }; + if(params){ + url=redirectUrl+"?"+params + }else{ + url=redirectUrl + } + return encodeURIComponent(url); +} +module.exports={ + formatUrl +} \ No newline at end of file diff --git a/utils/getUrl.js b/utils/getUrl.js new file mode 100644 index 0000000..1969b83 --- /dev/null +++ b/utils/getUrl.js @@ -0,0 +1,23 @@ +/*获取当前页url*/ +const getCurrentPageUrl=()=>{ + try { + let pages = getCurrentPages() //获取加载的页面 + let currentPage = pages[pages.length-1] + let url = currentPage.route + //当前页面url + return url + } catch (error) { + + } +} +/*获取当前页参数*/ +const getCurrentPageParam=()=>{ + let pages = getCurrentPages() //获取加载的页面 + let currentPage = pages[pages.length-1] //获取当前页面的对象 + let options = currentPage.options //如果要获取url中所带的参数可以查看options + return options +} +module.exports = { + getCurrentPageUrl, + getCurrentPageParam +} \ No newline at end of file diff --git a/utils/request.js b/utils/request.js new file mode 100644 index 0000000..ba69ad8 --- /dev/null +++ b/utils/request.js @@ -0,0 +1,119 @@ + +import {formatUrl} from "../utils/formatParams" +import {getCurrentPageUrl} from "../utils/getUrl" +import {hostConfig} from "../utils/config" +let host=hostConfig().host; +let tokenStr='' +const { envVersion } = wx.getAccountInfoSync().miniProgram; +if(envVersion=="develop" || envVersion=="trial"){ + tokenStr="DEV_CASE_TOKEN" +}else{ + tokenStr="PROD_CASE_TOKEN" +} + +//loding 是否加loading弹窗 +function request(url, method, data, loding = false) { + + if (loding) { + wx.showLoading({ + title: '加载中', + mask: true + }) + } + + +// if (!token) { +// let freelist = ["/wx/user/", '/login/mobile_login','/code/phone','/patient/index',"/popup","/sign/im"];//接口白名单 +// let currentUrl=getCurrentPageUrl(); +// if (freelist.indexOf(url) == -1 && currentUrl!="pages/personCenter/personCenter") { +// let redirectUrl=formatUrl(); +// wx.reLaunch({ +// url: '/case/pages/login/login?redirectUrl='+redirectUrl +// }); + +// } + +// } +let token = wx.getStorageSync(tokenStr); +console.log(token) + let header = { + 'content-type': 'application/json', + 'x-access-token': token + // 'Authorization': 'Bearer ' + token + } + return new Promise((resolve, reject) => { + wx.request({ + url: host + url, + method: method, + data: data, + header: header, + success: function (res) { + console.log(res.data); + // var Authorization_token = res.header['x-access-token']; + // if (Authorization_token) { + // wx.setStorageSync(tokenStr, Authorization_token); //当token快过期时,服务器会返回新token,本地刷新 + // } + if (loding) { + wx.hideLoading() + wx.stopPullDownRefresh(); + } + if (Number(res.data.code) == 200 || Number(res.data.code) == 0) { + resolve(res.data.data); + + }else if (Number(res.data.code) == 401 ) { + let redirectUrl=formatUrl(); + + wx.reLaunch({ + url: '/case/pages/mobileLogin/mobileLogin?redirectUrl='+redirectUrl + }); + + }else if(Number(res.data.code) ==30007 || Number(res.data.code) ==10007){ + reject(res.data); + } else { + console.log(res.data) + reject(res.data); + wx.showToast({ + title: res.data.msg, + icon: 'none', + duration: 2000 + }) + } + + }, + fail: function (res) { + console.log(res) + // wx.showToast({ + // title: '网络错误,请稍后再试', + // icon: 'none', + // duration: 2000 + // }) + // reject(false) + } + }) + }) + +} + +function uploadFile(url, data) { + return new Promise((resolve, reject) => { + wx.uploadFile({ + url: host + url, + formData: data, + success(res) { + const result = res.data + resolve(result) + }, + // fail(res){ + // wx.showToast({ + // title: '网络错误,请稍后再试', + // icon: 'none', + // }) + // reject(false) + // } + }) + }) +} +module.exports = { + request, + uploadFile +} \ No newline at end of file diff --git a/utils/router.js b/utils/router.js new file mode 100644 index 0000000..01c1dca --- /dev/null +++ b/utils/router.js @@ -0,0 +1,28 @@ +// router.js +module.exports = { + navigateTo(object) { + if (getCurrentPages().length >= 10) { + wx.redirectTo(object) + } else { + wx.navigateTo(object) + } + }, + + // 其他跳转不处理 + navigateBack(object) { + wx.navigateBack(object) + }, + + switchTab(object) { + wx.switchTab(object) + }, + + redirectTo(object) { + wx.redirectTo(object) + }, + + reLaunch(object) { + wx.reLaunch(object) + }, + +} diff --git a/utils/util.js b/utils/util.js new file mode 100644 index 0000000..63875a9 --- /dev/null +++ b/utils/util.js @@ -0,0 +1,18 @@ + +const throttle=function(fn,wait=1000){ + var flag = true; + var timer = null; + return function(){ + if(flag) { + fn.apply(this,arguments); + flag = false; + timer = setTimeout(() => { + flag = true + },wait) + } + } +} + +module.exports = { + throttle +}