diff --git a/README.md b/README.md new file mode 100644 index 0000000..6d393c5 --- /dev/null +++ b/README.md @@ -0,0 +1,109 @@ +## 中传小动物图鉴 + +### 主要需求 + +1. 显示小动物部分资料(所有人) +2. 支持每只动物的图片上传(所有人) +3. 小动物资料的修改/增添/删除(管理员) +4. 普通用户上传图片的审核(管理员) +5. 领养途径(所有人) + +### 前端设计 + +(暂时参考北大喵咪图鉴) + +#### animals + +初始页面 + +列表形式,显示名字和头像和状态(状态包括在校,毕业,喵星/汪星) + +底部:「小动物们」「相册」「搜索」 + +#### gallery + +相册 + +平铺形式,显示小动物名字,作者名字,点赞数,按审核完成时间排序 + +底部:「小动物们」「相册」「搜索」 + +#### search + +搜索页面 + +(可以任何信息搜索,包括名字,性格,种类) + +底部:「小动物们」「相册」「搜索」 + +#### searchDetail + +搜索结果 + +列表形式,显示名字和头像和状态,没有结果显示「未找到」 + +顶部:返回键 + +#### animalDetail + +小动物资料 + +界面同北大 + +顶部:返回键 + +底部:「查看相册」「我想上传」「我想领养」 + +#### animalGallery + +个人(不是)相册 + +平铺形式,显示小动物名字,作者名字,点赞数 + +顶部:返回键 + +#### uploading + +上传页面 + +填写图片和作者名字 + +顶部:返回键 + +底部:确定上传 + +#### adopt + +领养页面 + +领养表单下载地址(百度云链接啥的)和领养联系人 + +顶部:返回键 + +### 数据库设计 + +#### 表1 animals + +用于小动物资料和列表 + +小动物编号(主键),名字name,头像图片地址icon,状态status,出生时间birth,性别sex,物种type +健康状况health,绝育情况及时间opr,第一次目击时间time,外貌look,性格personality,关系relation,领养建议adopt-adv + +#### 表2 photos + +用于相册 + +图片编号(主键),作者upper,小动物名字name,点赞数likes,图片地址pic + +#### 表3 wait + +待审核照片 + +图片编号(主键),作者upper,小动物名字name,图片地址pic + +### 未完成功能 + +- 图片裁剪 + +- 点赞 + diff --git a/app.js b/app.js new file mode 100644 index 0000000..782ac6c --- /dev/null +++ b/app.js @@ -0,0 +1,5 @@ +wx.cloud.init({ + + traceUser: true, + +}) \ No newline at end of file diff --git a/app.json b/app.json new file mode 100644 index 0000000..89d9bb8 --- /dev/null +++ b/app.json @@ -0,0 +1,43 @@ +{ + "pages": [ + "pages/animals/animals", + "pages/gallery/gallery", + "pages/search/search", + "pages/animalDetail/animalDetail", + "pages/animalGallery/animalGallery", + "pages/adopt/adopt", + "pages/uploading/uploading", + "pages/selectPic/selectPic", + "pages/searchDetail/searchDetail" + ], + "window": { + "navigationBarBackgroundColor": "#ebebeb", + "backgroundTextStyle": "light", + "navigationBarTitleText": "CUC小动物图鉴", + "navigationBarTextStyle": "black" + }, + "tabBar": { + "color": "#686868", + "selectedColor": "#47a86c", + "backgroundColor": "#ffffff", + "borderStyle": "white", + "list": [ + { + "pagePath": "pages/animals/animals", + "iconPath": "images/animals_icon.png", + "selectedIconPath": "images/animals_icon_active.png" + }, + { + "pagePath": "pages/gallery/gallery", + "iconPath": "images/gallery_icon.png", + "selectedIconPath": "images/gallery_icon_active.png" + }, + { + "pagePath": "pages/search/search", + "iconPath": "images/search_icon.png", + "selectedIconPath": "images/search_icon_active.png" + } + ] + }, + "sitemapLocation": "sitemap.json" +} \ No newline at end of file diff --git a/app.wxss b/app.wxss new file mode 100644 index 0000000..4e83c9a --- /dev/null +++ b/app.wxss @@ -0,0 +1,4 @@ +.animal-name { + font-size: 40rpx; + color: #333; +} \ No newline at end of file diff --git a/cloudfunctions/callback/config.json b/cloudfunctions/callback/config.json new file mode 100644 index 0000000..43aa5fc --- /dev/null +++ b/cloudfunctions/callback/config.json @@ -0,0 +1,7 @@ +{ + "permissions": { + "openapi": [ + "customerServiceMessage.send" + ] + } +} \ No newline at end of file diff --git a/cloudfunctions/callback/index.js b/cloudfunctions/callback/index.js new file mode 100644 index 0000000..c80f887 --- /dev/null +++ b/cloudfunctions/callback/index.js @@ -0,0 +1,26 @@ +const cloud = require('wx-server-sdk') + +cloud.init({ + // API 调用都保持和云函数当前所在环境一致 + env: cloud.DYNAMIC_CURRENT_ENV +}) + +// 云函数入口函数 +exports.main = async (event, context) => { + + console.log(event) + + const { OPENID } = cloud.getWXContext() + + const result = await cloud.openapi.customerServiceMessage.send({ + touser: OPENID, + msgtype: 'text', + text: { + content: '收到:' + event.Content, + } + }) + + console.log(result) + + return result +} diff --git a/cloudfunctions/callback/package.json b/cloudfunctions/callback/package.json new file mode 100644 index 0000000..4e1b843 --- /dev/null +++ b/cloudfunctions/callback/package.json @@ -0,0 +1,14 @@ +{ + "name": "callback", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "wx-server-sdk": "~1.8.3" + } +} \ No newline at end of file diff --git a/cloudfunctions/echo/config.json b/cloudfunctions/echo/config.json new file mode 100644 index 0000000..16348ce --- /dev/null +++ b/cloudfunctions/echo/config.json @@ -0,0 +1,5 @@ +{ + "permissions": { + "openapi": [] + } +} diff --git a/cloudfunctions/echo/index.js b/cloudfunctions/echo/index.js new file mode 100644 index 0000000..4f83878 --- /dev/null +++ b/cloudfunctions/echo/index.js @@ -0,0 +1,8 @@ +const cloud = require('wx-server-sdk') + +exports.main = async (event, context) => { + // event.userInfo 是已废弃的保留字段,在此不做展示 + // 获取 OPENID 等微信上下文请使用 cloud.getWXContext() + delete event.userInfo + return event +} diff --git a/cloudfunctions/echo/package.json b/cloudfunctions/echo/package.json new file mode 100644 index 0000000..4979e86 --- /dev/null +++ b/cloudfunctions/echo/package.json @@ -0,0 +1,14 @@ +{ + "name": "echo", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "wx-server-sdk": "~1.8.3" + } +} \ No newline at end of file diff --git a/cloudfunctions/login/config.json b/cloudfunctions/login/config.json new file mode 100644 index 0000000..16348ce --- /dev/null +++ b/cloudfunctions/login/config.json @@ -0,0 +1,5 @@ +{ + "permissions": { + "openapi": [] + } +} diff --git a/cloudfunctions/login/index.js b/cloudfunctions/login/index.js new file mode 100644 index 0000000..3c524f3 --- /dev/null +++ b/cloudfunctions/login/index.js @@ -0,0 +1,36 @@ +// 云函数模板 +// 部署:在 cloud-functions/login 文件夹右击选择 “上传并部署” + +const cloud = require('wx-server-sdk') + +// 初始化 cloud +cloud.init({ + // API 调用都保持和云函数当前所在环境一致 + env: cloud.DYNAMIC_CURRENT_ENV +}) + +/** + * 这个示例将经自动鉴权过的小程序用户 openid 返回给小程序端 + * + * event 参数包含小程序端调用传入的 data + * + */ +exports.main = (event, context) => { + console.log(event) + console.log(context) + + // 可执行其他自定义逻辑 + // console.log 的内容可以在云开发云函数调用日志查看 + + // 获取 WX Context (微信调用上下文),包括 OPENID、APPID、及 UNIONID(需满足 UNIONID 获取条件)等信息 + const wxContext = cloud.getWXContext() + + return { + event, + openid: wxContext.OPENID, + appid: wxContext.APPID, + unionid: wxContext.UNIONID, + env: wxContext.ENV, + } +} + diff --git a/cloudfunctions/login/package.json b/cloudfunctions/login/package.json new file mode 100644 index 0000000..ffce01a --- /dev/null +++ b/cloudfunctions/login/package.json @@ -0,0 +1,14 @@ +{ + "name": "login", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "wx-server-sdk": "~1.8.3" + } +} diff --git a/cloudfunctions/openapi/config.json b/cloudfunctions/openapi/config.json new file mode 100644 index 0000000..0074569 --- /dev/null +++ b/cloudfunctions/openapi/config.json @@ -0,0 +1,15 @@ +{ + "permissions": { + "openapi": [ + "wxacode.get", + "subscribeMessage.send", + "subscribeMessage.addTemplate", + "templateMessage.send", + "templateMessage.addTemplate", + "templateMessage.deleteTemplate", + "templateMessage.getTemplateList", + "templateMessage.getTemplateLibraryById", + "templateMessage.getTemplateLibraryList" + ] + } +} \ No newline at end of file diff --git a/cloudfunctions/openapi/index.js b/cloudfunctions/openapi/index.js new file mode 100644 index 0000000..1ae21a4 --- /dev/null +++ b/cloudfunctions/openapi/index.js @@ -0,0 +1,87 @@ +// 云函数入口文件 +const cloud = require('wx-server-sdk') + +cloud.init() + +// 云函数入口函数 +exports.main = async (event, context) => { + console.log(event) + switch (event.action) { + case 'requestSubscribeMessage': { + return requestSubscribeMessage(event) + } + case 'sendSubscribeMessage': { + return sendSubscribeMessage(event) + } + case 'getWXACode': { + return getWXACode(event) + } + case 'getOpenData': { + return getOpenData(event) + } + default: { + return + } + } +} + +async function requestSubscribeMessage(event) { + // 此处为模板 ID,开发者需要到小程序管理后台 - 订阅消息 - 公共模板库中添加模板, + // 然后在我的模板中找到对应模板的 ID,填入此处 + return '请到管理后台申请模板 ID 然后在此替换' // 如 'N_J6F05_bjhqd6zh2h1LHJ9TAv9IpkCiAJEpSw0PrmQ' +} + +async function sendSubscribeMessage(event) { + const { OPENID } = cloud.getWXContext() + + const { templateId } = event + + const sendResult = await cloud.openapi.subscribeMessage.send({ + touser: OPENID, + templateId, + miniprogram_state: 'developer', + page: 'pages/openapi/openapi', + // 此处字段应修改为所申请模板所要求的字段 + data: { + thing1: { + value: '咖啡', + }, + time3: { + value: '2020-01-01 00:00', + }, + } + }) + + return sendResult +} + +async function getWXACode(event) { + + // 此处将获取永久有效的小程序码,并将其保存在云文件存储中,最后返回云文件 ID 给前端使用 + + const wxacodeResult = await cloud.openapi.wxacode.get({ + path: 'pages/openapi/openapi', + }) + + const fileExtensionMatches = wxacodeResult.contentType.match(/\/([^\/]+)/) + const fileExtension = (fileExtensionMatches && fileExtensionMatches[1]) || 'jpg' + + const uploadResult = await cloud.uploadFile({ + // 云文件路径,此处为演示采用一个固定名称 + cloudPath: `wxacode_default_openapi_page.${fileExtension}`, + // 要上传的文件内容可直接传入图片 Buffer + fileContent: wxacodeResult.buffer, + }) + + if (!uploadResult.fileID) { + throw new Error(`upload failed with empty fileID and storage server status code ${uploadResult.statusCode}`) + } + + return uploadResult.fileID +} + +async function getOpenData(event) { + return cloud.getOpenData({ + list: event.openData.list, + }) +} diff --git a/cloudfunctions/openapi/package.json b/cloudfunctions/openapi/package.json new file mode 100644 index 0000000..96b3f19 --- /dev/null +++ b/cloudfunctions/openapi/package.json @@ -0,0 +1,14 @@ +{ + "name": "openapi", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC", + "dependencies": { + "wx-server-sdk": "~1.8.3" + } +} diff --git a/component/animalList/animalList.wxml b/component/animalList/animalList.wxml new file mode 100644 index 0000000..0de4ff9 --- /dev/null +++ b/component/animalList/animalList.wxml @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/component/animalList/animalList.wxss b/component/animalList/animalList.wxss new file mode 100644 index 0000000..5d7f81d --- /dev/null +++ b/component/animalList/animalList.wxss @@ -0,0 +1,81 @@ +page{ + background-color: #ebebeb; +} + +.animalListView { + /*width: 100%; + bottom: 0; + top: 500rpx; + position: absolute;*/ + box-sizing: border-box; + width: 100%; + padding: 15rpx 20rpx; + display: flex; + flex-wrap: wrap; + flex-direction: row; + justify-content: space-between; + /*box-shadow: 0 0 40rpx #f4f4f4 inset;*/ + background-color: #ebebeb; +} + +.animal { + width: 100%; + margin-bottom: 23rpx; + border-radius: 15rpx; + background-color: #fff; + /*border: 1px solid #e4e4e4;*/ + box-shadow: 3rpx 3rpx 10rpx rgba(129, 129, 129, 0.274); + overflow: hidden; + animation: fadeIn 1s; + height: 150rpx; + position: relative; + + /*width: 100%; + height: 150rpx; + position: relative; + border-bottom: 1px solid #f0f0f0;*/ +} + +.iconImageView { + width: 150rpx; + height: 150rpx; + top: 20rpx; + left: 40rpx; + position: absolute; +} +.iconImage { + width: 108rpx; + height: 108rpx; + border-radius: 100rpx; +} + +.animalNameView { + top: 50rpx; + left: 195rpx; + right: 130rpx; + height: 50rpx; + position: absolute; +} +.animalNameText { + color: rgb(32, 27, 27); + font-size: 32rpx; + white-space: nowrap; + text-overflow:ellipsis; +} + +.animalStatusView { + top: 65rpx; + right: 10rpx; + height: 50rpx; + width: 100rpx; + position: absolute; +} +.animalStatusText { + font-size: 26rpx; + padding: 4rpx 6rpx; + margin-right: 10rpx; + border-top: 1 1 1 1px solid rgb(252, 202, 37); + border-radius: 10rpx; + background-color: rgb(255, 218, 124); + color: rgb(175, 157, 57); +} \ No newline at end of file diff --git a/component/image-cropper/.DS_Store b/component/image-cropper/.DS_Store new file mode 100644 index 0000000..5008ddf Binary files /dev/null and b/component/image-cropper/.DS_Store differ diff --git a/component/image-cropper/.github/ISSUE_TEMPLATE/bug_report.md b/component/image-cropper/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..b707306 --- /dev/null +++ b/component/image-cropper/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,20 @@ +--- +name: 提交bug +about: 按照这个模板写 +title: '' +labels: '' +assignees: '' + +--- + +对应工具或者iOS或者Andriod的版本号 + +微信版本号 + +代码截图 + +重现步骤 + +期待的行为 + +实际的行为 diff --git a/component/image-cropper/.github/ISSUE_TEMPLATE/encourage.jpg b/component/image-cropper/.github/ISSUE_TEMPLATE/encourage.jpg new file mode 100644 index 0000000..09866a3 Binary files /dev/null and b/component/image-cropper/.github/ISSUE_TEMPLATE/encourage.jpg differ diff --git a/component/image-cropper/LICENSE b/component/image-cropper/LICENSE new file mode 100644 index 0000000..a5ec9a0 --- /dev/null +++ b/component/image-cropper/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 wx-plugin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/component/image-cropper/image-cropper.js b/component/image-cropper/image-cropper.js new file mode 100644 index 0000000..6957179 --- /dev/null +++ b/component/image-cropper/image-cropper.js @@ -0,0 +1,1120 @@ +Component({ + properties: { + /** + * 图片路径 + */ + 'imgSrc': { + type: String + }, + /** + * 裁剪框高度 + */ + '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(); + }, + 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, + }) + }, + /** + * 加载(更换)图片 + */ + 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() { + } + } +}) diff --git a/component/image-cropper/image-cropper.json b/component/image-cropper/image-cropper.json new file mode 100644 index 0000000..32640e0 --- /dev/null +++ b/component/image-cropper/image-cropper.json @@ -0,0 +1,3 @@ +{ + "component": true +} \ No newline at end of file diff --git a/component/image-cropper/image-cropper.wxml b/component/image-cropper/image-cropper.wxml new file mode 100644 index 0000000..12079bb --- /dev/null +++ b/component/image-cropper/image-cropper.wxml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/component/image-cropper/image-cropper.wxss b/component/image-cropper/image-cropper.wxss new file mode 100644 index 0000000..b5ef0c0 --- /dev/null +++ b/component/image-cropper/image-cropper.wxss @@ -0,0 +1,123 @@ +.image-cropper{ + background:rgba(14, 13, 13,.8); + position: fixed; + top:0; + left:0; + width:100vw; + height:100vh; + z-index: 1; +} +.main{ + position: absolute; + width:100vw; + height:100vh; + overflow: hidden; +} +.content{ + z-index: 9; + position: absolute; + width:100vw; + height:100vh; + display: flex; + flex-direction:column; + pointer-events:none; +} +.bg_black{ + background: rgba(0, 0, 0, 0.8)!important; +} +.bg_gray{ + background: rgba(0, 0, 0, 0.45); + transition-duration: .35s; +} +.content>.content_top{ + pointer-events:none; +} +.content>.content_middle{ + display: flex; + height: 200px; + width:100%; +} +.content_middle_middle{ + width:200px; + box-sizing:border-box; + position: relative; + transition-duration: .3s; +} +.content_middle_right{ + flex: auto; +} +.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-canvas{ + position: fixed; + background: white; + width:150px; + height:150px; + z-index: 10; + top:-200%; + pointer-events:none; +} +.border{ + background: white; + pointer-events:auto; + position:absolute; +} +.border-top-left{ + left:-2.5px; + top:-2.5px; + height:2.5px; + width:33rpx; +} +.border-top-right{ + right:-2.5px; + top:-2.5px; + height:2.5px; + width:33rpx; +} +.border-right-top{ + top:-1px; + width:2.5px; + height:30rpx; + right:-2.5px; +} +.border-right-bottom{ + width:2.5px; + height:30rpx; + right:-2.5px; + bottom:-1px; +} +.border-bottom-left{ + height:2.5px; + width:33rpx; + bottom:-2.5px; + left:-2.5px; +} +.border-bottom-right{ + height:2.5px; + width:33rpx; + bottom:-2.5px; + right:-2.5px; +} +.border-left-top{ + top:-1px; + width:2.5px; + height:30rpx; + left:-2.5px; +} +.border-left-bottom{ + width:2.5px; + height:30rpx; + left:-2.5px; + bottom:-1px; +} \ No newline at end of file diff --git a/component/photoList/photoList.wxml b/component/photoList/photoList.wxml new file mode 100644 index 0000000..6848a53 --- /dev/null +++ b/component/photoList/photoList.wxml @@ -0,0 +1,24 @@ + \ No newline at end of file diff --git a/component/photoList/photoList.wxss b/component/photoList/photoList.wxss new file mode 100644 index 0000000..a5b64ef --- /dev/null +++ b/component/photoList/photoList.wxss @@ -0,0 +1,66 @@ +page{ + background-color: #ebebeb; + } + +.photo { + box-sizing: border-box; + width: 750rpx; + padding: 15rpx 40rpx; + display: flex; + flex-wrap: wrap; + flex-direction: row; + justify-content: space-between; + /*box-shadow: 0 0 40rpx #f4f4f4 inset;*/ + background-color: #ebebeb; +} + +.photo-item { + width: 100%; + margin-bottom: 28rpx; + border-radius: 20rpx; + background-color: #fff; + /*border: 1px solid #e4e4e4;*/ + box-shadow: 5rpx 5rpx 15rpx rgba(129, 129, 129, 0.425); + overflow: hidden; + animation: fadeIn 1s; +} + +.photo-cover, .photo-cover-img { + width: 675rpx; +} + +.photo-cover { + position: relative; + overflow: hidden; +} + +.file-intro { + padding: 20rpx; +} + +.photo-title { + font-size: 35rpx; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + +.photo-upper { + width: 100%; + margin-top: 10rpx; + display: flex; + justify-content: flex-start; +} + +.photo-upper-item { + font-size: 24rpx; + color: #666; +} + +.loading-tip { + width: 100%; + height: 80rpx; + line-height: 80rpx; + text-align: center; + color: #ccc; +} \ No newline at end of file diff --git a/images/.DS_Store b/images/.DS_Store new file mode 100644 index 0000000..cc32493 Binary files /dev/null and b/images/.DS_Store differ diff --git a/images/animals_icon.png b/images/animals_icon.png new file mode 100644 index 0000000..8f2857d Binary files /dev/null and b/images/animals_icon.png differ diff --git a/images/animals_icon_active.png b/images/animals_icon_active.png new file mode 100644 index 0000000..1d6c60f Binary files /dev/null and b/images/animals_icon_active.png differ diff --git a/images/arrow.png b/images/arrow.png new file mode 100644 index 0000000..982d1cb Binary files /dev/null and b/images/arrow.png differ diff --git a/images/gallery_icon.png b/images/gallery_icon.png new file mode 100644 index 0000000..9a0bafc Binary files /dev/null and b/images/gallery_icon.png differ diff --git a/images/gallery_icon_active.png b/images/gallery_icon_active.png new file mode 100644 index 0000000..3df1be7 Binary files /dev/null and b/images/gallery_icon_active.png differ diff --git a/images/search_icon.png b/images/search_icon.png new file mode 100644 index 0000000..a510ae3 Binary files /dev/null and b/images/search_icon.png differ diff --git a/images/search_icon_active.png b/images/search_icon_active.png new file mode 100644 index 0000000..518a9bd Binary files /dev/null and b/images/search_icon_active.png differ diff --git a/pages/.DS_Store b/pages/.DS_Store new file mode 100644 index 0000000..ced4dfd Binary files /dev/null and b/pages/.DS_Store differ diff --git a/pages/adopt/adopt.js b/pages/adopt/adopt.js new file mode 100644 index 0000000..fc70c6c --- /dev/null +++ b/pages/adopt/adopt.js @@ -0,0 +1,81 @@ +// pages/adopt/adopt.js +Page({ + + /** + * 页面的初始数据 + */ + data: { + animalData:{ + id: 0, + name: "小白", + alias:[ ], + avatarId: 123, + } + }, + + /** + * 生命周期函数--监听页面加载 + */ + onLoad: function (options) { + console.log(options) + if(!(options.name && options.id)){ + wx.showToast({ + title: '参数错误', + icon: "none", + duration: 2000, + + }) + }else{ + this.setData({name: options.name, id: options.id}) + } + }, + + /** + * 生命周期函数--监听页面初次渲染完成 + */ + onReady: function () { + + }, + + /** + * 生命周期函数--监听页面显示 + */ + onShow: function () { + + }, + + /** + * 生命周期函数--监听页面隐藏 + */ + onHide: function () { + + }, + + /** + * 生命周期函数--监听页面卸载 + */ + onUnload: function () { + + }, + + /** + * 页面相关事件处理函数--监听用户下拉动作 + */ + onPullDownRefresh: function () { + + }, + + /** + * 页面上拉触底事件的处理函数 + */ + onReachBottom: function () { + + }, + + /** + * 用户点击右上角分享 + */ + onShareAppMessage: function () { + + } +}) \ No newline at end of file diff --git a/pages/adopt/adopt.json b/pages/adopt/adopt.json new file mode 100644 index 0000000..8835af0 --- /dev/null +++ b/pages/adopt/adopt.json @@ -0,0 +1,3 @@ +{ + "usingComponents": {} +} \ No newline at end of file diff --git a/pages/adopt/adopt.wxml b/pages/adopt/adopt.wxml new file mode 100644 index 0000000..e415b7d --- /dev/null +++ b/pages/adopt/adopt.wxml @@ -0,0 +1,26 @@ + + + + + + + + 领养{{animalData.name}} + + 领养表格下载: 下载链接 + + TEL:13000000000 + + + + \ No newline at end of file diff --git a/pages/adopt/adopt.wxss b/pages/adopt/adopt.wxss new file mode 100644 index 0000000..487e65c --- /dev/null +++ b/pages/adopt/adopt.wxss @@ -0,0 +1,25 @@ +/* pages/adopt/adopt.wxss */ +.header{ + padding: 25rpx +} +.container{ + padding: 0 25rpx; +} +.animal-name{ + text-align: center; + display: inline-block; + width: 100%; + font-size: 1.4em; + font-weight: bold; + padding: 20rpx 0; +} + +.animal-avatar{ + width: 100%; +} +.adopt-info{ + padding: 0 35rpx; +} +.form-item{ + margin: 15rpx 0; +} \ No newline at end of file diff --git a/pages/animalDetail/animalDetail.js b/pages/animalDetail/animalDetail.js new file mode 100644 index 0000000..831b500 --- /dev/null +++ b/pages/animalDetail/animalDetail.js @@ -0,0 +1,155 @@ +// pages/animalDetail/animalDetail.js + +const db = wx.cloud.database({}); +const cont = db.collection('animals'); + +Page({ + + /** + * 页面的初始数据 + */ + data: { + animalData:{} + }, + + /*animalState(state){ + console.log("WTF") + return this.states[state]; + }, + + toAdoptPage(){ + wx.navigateTo({url: `/pages/adopt/adopt?id=${this.data.animalData.id}&name=${this.data.animalData.name}`}) + },*/ + + clockIn: function(e) { + /*我想能在每次打卡时获取打卡地点,也有助于我们跟踪小动物 + wx.getLocation({success: (res)=>{ + save(res) + }}) + */ + var data = e.currentTarget.dataset; + console.log(e.currentTarget.dataset.name) + wx.navigateTo({ + url: "../uploading/uploading?name=" + data.name + }) + }, + + viewGallery: function(e) { + var data = e.currentTarget.dataset; + console.log(e.currentTarget.dataset.name) + wx.navigateTo({ + url: "../animalGallery/animalGallery?name=" + data.name + }) + }, + + askSave(evt){ + let pic = evt.currentTarget.dataset.pic; + wx.showModal({ + title: "保存图片到相册", + success: ({confirm})=>{ + if (confirm) { + wx.cloud.downloadFile({ + fileID: pic, + success: res => { + console.log('下载成功', res) + this.saveImage(res.tempFilePath) + }, + fail: res => { + console.log('下载失败', res) + } + }) + } + } + }) + }, + saveImage(imgUrl){ + wx.saveImageToPhotosAlbum({ + filePath:imgUrl, + success(res) { + wx.showToast({ + title: '保存成功', + icon: 'success', + duration: 2000, + }) + }, + fail(res) { + console.log('保存失败', res) + } + }) + }, + +/* + setAnimalData(id){ + this.setData({ + animalData: wx.cloud.callFunction({ + name: "animalData", + data: { + id: id + } + }) + }) + }, +*/ + /** + * 生命周期函数--监听页面加载 + */ + onLoad: function (options) { + var id=options.id + db.collection('animals').doc(id).get({ + success:res =>{ + this.setData({ + animalData:res.data + }) + } + }) + }, + + /** + * 生命周期函数--监听页面初次渲染完成 + */ + onReady: function () { + + }, + + /** + * 生命周期函数--监听页面显示 + */ + onShow: function () { + + }, + + /** + * 生命周期函数--监听页面隐藏 + */ + onHide: function () { + + }, + + /** + * 生命周期函数--监听页面卸载 + */ + onUnload: function () { + + }, + + /** + * 页面相关事件处理函数--监听用户下拉动作 + */ + onPullDownRefresh: function () { + + }, + + /** + * 页面上拉触底事件的处理函数 + */ + onReachBottom: function () { + + }, + + /** + * 用户点击右上角分享 + */ + onShareAppMessage: function () { + + } +}) \ No newline at end of file diff --git a/pages/animalDetail/animalDetail.json b/pages/animalDetail/animalDetail.json new file mode 100644 index 0000000..8835af0 --- /dev/null +++ b/pages/animalDetail/animalDetail.json @@ -0,0 +1,3 @@ +{ + "usingComponents": {} +} \ No newline at end of file diff --git a/pages/animalDetail/animalDetail.wxml b/pages/animalDetail/animalDetail.wxml new file mode 100644 index 0000000..3b48829 --- /dev/null +++ b/pages/animalDetail/animalDetail.wxml @@ -0,0 +1,65 @@ + + + + {{animalData.name}} + + + + + + + + + + + + 出生时间: + {{animalData.birth}} + + + 性别: + {{animalData.sex}} + + + 状态: + {{animalData.status}} + + + 外貌: + {{animalData.look}} + + + 性格: + + {{animalData.personality}} + + + + 健康状况: + {{animalData.health}} + + + 关系: + {{animalData.relation}} + + + 绝育情况及时间: + {{animalData.opr}} + + + 第一次目击时间: + {{animalData.time}} + + + 领养建议: + {{animalData.adopt_adv}} + + + + + + + + + + diff --git a/pages/animalDetail/animalDetail.wxss b/pages/animalDetail/animalDetail.wxss new file mode 100644 index 0000000..1892e59 --- /dev/null +++ b/pages/animalDetail/animalDetail.wxss @@ -0,0 +1,101 @@ +/* pages/animalDetail/animalDetail.wxss */ + +.top-name{ + position: fixed; + top: 0; + left: 0; + display: flex; + width: 100%; + height: 110rpx; + background-color: white; + box-shadow: 0 0 10rpx #f4f4f4 inset; + box-shadow: 0 0 20rpx #b9b9b9; + /*border: 1px solid rgb(233, 233, 233);*/ +} +.animal-name{ + margin-top: 25rpx; + margin-left: 35rpx; + color: rgb(32, 27, 27); + font-size: 45rpx; +} + +.container{ + padding: 0 rpx; + margin-top: 110rpx; + margin-bottom: 125rpx; +} + +.animal-icon{ + width: 100%; + height: 0; + padding-bottom: 103%; +} +.animal-avatar{ + width: 100%; + height: 750rpx; +} + +.detail-info{ + margin-left: 35rpx; + margin-top: 20rpx; +} + +.detail-item{ + margin: 10rpx 0; +} +.detail-item-key{ + /*font-weight: bold;*/ + font-size: 37rpx; + color: rgb(77, 77, 77); +} +.detail-item-value{ + font-size: 37rpx; + font-weight: lighter; + color: rgb(82, 82, 82); +} + +.data-modified-time{ + width: 100%; + font-weight: lighter; + padding: 0 15rpx; + text-align: right; +} + +.detail-buttons{ + position: fixed; + bottom: 0; + left: 0; + padding-bottom: 0rpx; + display: flex; + width: 100%; +} +.detail-button{ + width: 50%; + height: 90rpx; + background-color: white; + color: rgb(82, 82, 82); + border: solid 1px rgb(240, 240, 240); + border-radius: 0rpx; + font-size: 35rpx; + /*font-weight: lighter;*/ + line-height: 80rpx; + /*float: left;*/ +} + +/*.detail-button-2{ + width: 50%; + height: 90rpx; + background-color: white; + color: rgb(82, 82, 82); + border: solid 1px rgb(240, 240, 240); + border-radius: 0rpx; + font-size: 35rpx; + /*font-weight: lighter; + line-height: 80rpx; + float: right; +}*/ + +.detail-button::after{ + border:0; + border-radius: 0; +} \ No newline at end of file diff --git a/pages/animalDetail/utils.wxs b/pages/animalDetail/utils.wxs new file mode 100644 index 0000000..3544ea0 --- /dev/null +++ b/pages/animalDetail/utils.wxs @@ -0,0 +1,9 @@ + +function formatDate(stamp){ + var date = getDate(stamp); + return date.getFullYear() + "-" + date.getMonth() + "-" + date.getDate() ; +} + +module.exports = { + formatDate: formatDate, +} \ No newline at end of file diff --git a/pages/animalGallery/animalGallery.js b/pages/animalGallery/animalGallery.js new file mode 100644 index 0000000..45fbf8c --- /dev/null +++ b/pages/animalGallery/animalGallery.js @@ -0,0 +1,169 @@ +// pages/animalGallery/animalGallery.js + +const db = wx.cloud.database({}); +const cont = db.collection('photos'); + +Page({ + + /** + * 页面的初始数据 + */ + data: { + photo_cnt:0, + photo_now:0, + photos:[], + name:"" + }, + + likePhoto({currentTarget}){ + let {idx, liked} = currentTarget.dataset; + if(liked) return; + let photos = this.data.photos; + photos[idx].like += 1; + photos[idx].liked = true; + this.setData({ + photos: photos + }) + }, + + /** + * 生命周期函数--监听页面加载 + */ + onLoad: function (options) { + var name=options.name + this.data.name=name + const _ = db.command + db.collection('photos').where({name:name}).count({ + success:res =>{ + //console.log("wtf:"+res.total) + this.setData({ + photo_cnt: res.total + }) + //console.log(this.data.photo_cnt) + } + }) + db.collection('photos').where({name:name} + ).get({ + success:res =>{ + this.setData({ + photos:res.data, + photo_now:20 + }) + } + }) + }, + + askSave(evt){ + let pic = evt.currentTarget.dataset.pic; + wx.showModal({ + title: "保存图片到相册", + success: ({confirm})=>{ + if (confirm) { + wx.cloud.downloadFile({ + fileID: pic, + success: res => { + console.log('下载成功', res) + this.saveImage(res.tempFilePath) + }, + fail: res => { + console.log('下载失败', res) + } + }) + } + } + }) + }, + saveImage(imgUrl){ + wx.saveImageToPhotosAlbum({ + filePath:imgUrl, + success(res) { + wx.showToast({ + title: '保存成功', + icon: 'success', + duration: 2000, + }) + }, + fail(res) { + console.log('保存失败', res) + } + }) + }, + + + /** + * 生命周期函数--监听页面初次渲染完成 + */ + onReady: function () { + + }, + + /** + * 生命周期函数--监听页面显示 + */ + onShow: function () { + + }, + + /** + * 生命周期函数--监听页面隐藏 + */ + onHide: function () { + + }, + + /** + * 生命周期函数--监听页面卸载 + */ + onUnload: function () { + + }, + + /** + * 页面相关事件处理函数--监听用户下拉动作 + */ + onPullDownRefresh: function () { + + }, + + /** + * 页面上拉触底事件的处理函数 + */ + onReachBottom: function () { + var name=this.data.name + if (this.data.photo_cnt <= this.data.photo_now) { + //console.log(this.data.photo_cnt+"<="+this.data.photo_now) + wx.showToast({ + icon: 'none', + title: '没有更多~' + }) + } + else{ + wx.showLoading({ + title: '加载中...', + duration: 1000 + }), + + db.collection('photos').where({name:name}).skip(this.data.photo_now).get() + .then(res => { + //console.log(res); + let newdata = this.data.photos.concat(res.data); + //console.log("newdata:"+newdata); + let adddata = this.data.photo_now; + adddata = adddata + 20; + this.setData({ + photo_now: adddata, + photos: newdata + }) + //console.log(this.data.photo_now,this.data.photos) + }) + } + + }, + + /** + * 用户点击右上角分享 + */ + onShareAppMessage: function () { + + } +}) \ No newline at end of file diff --git a/pages/animalGallery/animalGallery.json b/pages/animalGallery/animalGallery.json new file mode 100644 index 0000000..8835af0 --- /dev/null +++ b/pages/animalGallery/animalGallery.json @@ -0,0 +1,3 @@ +{ + "usingComponents": {} +} \ No newline at end of file diff --git a/pages/animalGallery/animalGallery.wxml b/pages/animalGallery/animalGallery.wxml new file mode 100644 index 0000000..c880487 --- /dev/null +++ b/pages/animalGallery/animalGallery.wxml @@ -0,0 +1,3 @@ + + +