├── miniprogram ├── pages │ ├── add │ │ ├── add.json │ │ ├── add.wxss │ │ ├── add.wxml │ │ └── add.js │ ├── map │ │ ├── map.json │ │ ├── map.wxml │ │ ├── map.wxss │ │ └── map.js │ ├── test │ │ ├── test.json │ │ ├── test.wxss │ │ ├── test.wxml │ │ └── test.js │ ├── index │ │ ├── index.json │ │ ├── index.js │ │ ├── index.wxss │ │ └── index.wxml │ ├── info │ │ ├── info.json │ │ ├── info.wxml │ │ ├── info.wxss │ │ └── info.js │ ├── allList │ │ ├── allList.json │ │ ├── allList.wxml │ │ ├── allList.wxss │ │ └── allList.js │ ├── search │ │ ├── search.json │ │ ├── search.wxml │ │ ├── search.wxss │ │ └── search.js │ ├── article │ │ ├── article.json │ │ ├── article.wxss │ │ ├── article.wxml │ │ └── article.js │ ├── myList │ │ ├── myList.json │ │ ├── myList.js │ │ ├── myList.wxml │ │ └── myList.wxss │ └── share │ │ ├── share.json │ │ ├── share.wxss │ │ ├── share.wxml │ │ ├── share.js │ │ └── art.js ├── colorui │ ├── components │ │ ├── cu-custom.wxss │ │ ├── cu-custom.json │ │ ├── cu-custom.wxml │ │ └── cu-custom.js │ └── animation.wxss ├── painter │ ├── painter.json │ ├── painter.wxml │ ├── lib │ │ ├── util.js │ │ ├── downloader.js │ │ ├── pen.js │ │ └── qrcode.js │ └── painter.js ├── components │ ├── dialogModal │ │ ├── index.json │ │ ├── index.wxml │ │ ├── index.js │ │ └── index.wxss │ └── shareBox │ │ ├── index.json │ │ ├── index.wxml │ │ ├── index.wxss │ │ └── index.js ├── images │ ├── nav │ │ ├── add.png │ │ ├── list.png │ │ └── article.png │ ├── home │ │ ├── error.png │ │ ├── design.png │ │ ├── occupy.png │ │ ├── center-marker.png │ │ └── getLocation.png │ ├── intro │ │ ├── nav2.png │ │ └── tips3.png │ ├── share │ │ └── art.png │ └── marker │ │ ├── design.png │ │ ├── error.png │ │ └── occupy.png ├── sitemap.json ├── app.wxss ├── config.js ├── app.js └── app.json ├── cloudfunctions ├── getStore │ ├── config.json │ ├── package.json │ └── index.js ├── getUserOpenId │ ├── index.js │ └── package.json └── checkUserAuth │ ├── package.json │ └── index.js ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── package.json ├── CHANGELOG ├── .all-contributorsrc ├── project.private.config.json ├── .gitignore ├── project.config.json ├── README.md └── LICENSE /miniprogram/pages/add/add.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } -------------------------------------------------------------------------------- /miniprogram/pages/map/map.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } -------------------------------------------------------------------------------- /miniprogram/pages/test/test.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } -------------------------------------------------------------------------------- /miniprogram/pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {} 3 | } -------------------------------------------------------------------------------- /miniprogram/colorui/components/cu-custom.wxss: -------------------------------------------------------------------------------- 1 | /* colorui/components/cu-custom.wxss */ -------------------------------------------------------------------------------- /miniprogram/painter/painter.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /miniprogram/painter/painter.wxml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /cloudfunctions/getStore/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "permissions": { 3 | "openapi": [ 4 | ] 5 | } 6 | } -------------------------------------------------------------------------------- /miniprogram/colorui/components/cu-custom.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /miniprogram/components/dialogModal/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /miniprogram/pages/info/info.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {}, 3 | "navigationBarTitleText": "问题详情" 4 | } -------------------------------------------------------------------------------- /miniprogram/pages/test/test.wxss: -------------------------------------------------------------------------------- 1 | @import "../../colorui/main.wxss"; 2 | @import "../../colorui/icon.wxss"; -------------------------------------------------------------------------------- /miniprogram/pages/allList/allList.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {}, 3 | "navigationBarTitleText": "全部美食" 4 | } -------------------------------------------------------------------------------- /miniprogram/pages/search/search.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {}, 3 | "navigationBarTitleText": "搜索" 4 | } -------------------------------------------------------------------------------- /miniprogram/pages/article/article.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {}, 3 | "navigationBarTitleText": "盲道科普" 4 | } 5 | -------------------------------------------------------------------------------- /miniprogram/pages/myList/myList.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": {}, 3 | "navigationBarTitleText": "筑路日志" 4 | } 5 | -------------------------------------------------------------------------------- /miniprogram/pages/share/share.json: -------------------------------------------------------------------------------- 1 | { 2 | "usingComponents": { 3 | "share-box": "/components/shareBox/index" 4 | } 5 | } -------------------------------------------------------------------------------- /miniprogram/images/nav/add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volcano-Yang/map-marker-miniprogram/HEAD/miniprogram/images/nav/add.png -------------------------------------------------------------------------------- /miniprogram/images/nav/list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volcano-Yang/map-marker-miniprogram/HEAD/miniprogram/images/nav/list.png -------------------------------------------------------------------------------- /miniprogram/images/home/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volcano-Yang/map-marker-miniprogram/HEAD/miniprogram/images/home/error.png -------------------------------------------------------------------------------- /miniprogram/images/intro/nav2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volcano-Yang/map-marker-miniprogram/HEAD/miniprogram/images/intro/nav2.png -------------------------------------------------------------------------------- /miniprogram/images/share/art.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volcano-Yang/map-marker-miniprogram/HEAD/miniprogram/images/share/art.png -------------------------------------------------------------------------------- /miniprogram/images/home/design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volcano-Yang/map-marker-miniprogram/HEAD/miniprogram/images/home/design.png -------------------------------------------------------------------------------- /miniprogram/images/home/occupy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volcano-Yang/map-marker-miniprogram/HEAD/miniprogram/images/home/occupy.png -------------------------------------------------------------------------------- /miniprogram/images/intro/tips3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volcano-Yang/map-marker-miniprogram/HEAD/miniprogram/images/intro/tips3.png -------------------------------------------------------------------------------- /miniprogram/images/marker/design.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volcano-Yang/map-marker-miniprogram/HEAD/miniprogram/images/marker/design.png -------------------------------------------------------------------------------- /miniprogram/images/marker/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volcano-Yang/map-marker-miniprogram/HEAD/miniprogram/images/marker/error.png -------------------------------------------------------------------------------- /miniprogram/images/marker/occupy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volcano-Yang/map-marker-miniprogram/HEAD/miniprogram/images/marker/occupy.png -------------------------------------------------------------------------------- /miniprogram/images/nav/article.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volcano-Yang/map-marker-miniprogram/HEAD/miniprogram/images/nav/article.png -------------------------------------------------------------------------------- /miniprogram/images/home/center-marker.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volcano-Yang/map-marker-miniprogram/HEAD/miniprogram/images/home/center-marker.png -------------------------------------------------------------------------------- /miniprogram/images/home/getLocation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Volcano-Yang/map-marker-miniprogram/HEAD/miniprogram/images/home/getLocation.png -------------------------------------------------------------------------------- /miniprogram/pages/article/article.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | background-color: #c8e2f9; 3 | } 4 | 5 | image { 6 | width: 100%; 7 | margin: 0; 8 | padding: 0; 9 | } -------------------------------------------------------------------------------- /miniprogram/components/shareBox/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": { 4 | "painter": "/painter/painter", 5 | "dialog-modal": "/components/dialogModal/index" 6 | } 7 | } -------------------------------------------------------------------------------- /miniprogram/sitemap.json: -------------------------------------------------------------------------------- 1 | { 2 | "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html", 3 | "rules": [{ 4 | "action": "allow", 5 | "page": "*" 6 | }] 7 | } -------------------------------------------------------------------------------- /miniprogram/app.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', Helvetica, 3 | Segoe UI, Arial, Roboto, 'PingFang SC', 'miui', 'Hiragino Sans GB', 'Microsoft Yahei', 4 | sans-serif; 5 | } -------------------------------------------------------------------------------- /cloudfunctions/getUserOpenId/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init() 5 | 6 | // 云函数入口函数 7 | exports.main = async (event, context) => { 8 | const wxContext = cloud.getWXContext() 9 | 10 | return { 11 | openid: wxContext.OPENID 12 | } 13 | } -------------------------------------------------------------------------------- /miniprogram/config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * envID: 云开发环境id 3 | * mapSubKey: 腾讯地图key 4 | */ 5 | module.exports = { 6 | "appName": "友好盲道地图", 7 | "envID": "", 8 | "mapSubKey": "", 9 | "center_longitude": 113.942304, 10 | "center_latitude": 22.529544, 11 | "dynamic_title": true, 12 | "show_admin": false, 13 | "default_scale": 16 14 | } -------------------------------------------------------------------------------- /cloudfunctions/getStore/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "getStore", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "~2.4.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /cloudfunctions/checkUserAuth/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "checkUserAuth", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "latest" 13 | } 14 | } -------------------------------------------------------------------------------- /cloudfunctions/getUserOpenId/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "getUserOpenId", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "wx-server-sdk": "latest" 13 | } 14 | } -------------------------------------------------------------------------------- /miniprogram/pages/allList/allList.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{item.problemLabel}} 6 | 7 | {{item.address}} 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /cloudfunctions/checkUserAuth/index.js: -------------------------------------------------------------------------------- 1 | // 云函数入口文件 2 | const cloud = require('wx-server-sdk') 3 | 4 | cloud.init() 5 | 6 | // 云函数入口函数 7 | exports.main = async (event, context) => { 8 | const wxContext = cloud.getWXContext() 9 | const administrator = process.env.ADMIN.split('|'); 10 | 11 | if (administrator.indexOf(wxContext.OPENID) == -1){ 12 | return { 13 | data:{ 14 | is_administrator:false 15 | } 16 | } 17 | }else{ 18 | return { 19 | data: { 20 | is_administrator: true 21 | } 22 | } 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /miniprogram/components/dialogModal/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{title}} 5 | {{content}} 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /miniprogram/pages/article/article.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /miniprogram/app.js: -------------------------------------------------------------------------------- 1 | const config = require('config.js'); 2 | App({ 3 | onLaunch: function (options) { 4 | /** 5 | * 初始化云开发 6 | */ 7 | if (!wx.cloud) { 8 | console.error('请使用 2.2.3 或以上的基础库以使用云能力') 9 | } else { 10 | wx.cloud.init({ 11 | traceUser: true, 12 | env: config.envID 13 | }) 14 | } 15 | /** 16 | * 获取屏幕高度 17 | */ 18 | let { windowHeight } = wx.getSystemInfoSync(); 19 | let showAdmin = wx.getStorageSync('showAdmin'); 20 | if (showAdmin == ""){ 21 | showAdmin = false; 22 | } 23 | this.globalData = { windowHeight, is_administrator: false, showAdmin: showAdmin} 24 | } 25 | }) 26 | -------------------------------------------------------------------------------- /miniprogram/components/shareBox/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /miniprogram/pages/allList/allList.wxss: -------------------------------------------------------------------------------- 1 | .card{ 2 | border-bottom: 1rpx solid #cecece; 3 | display: flex; 4 | flex-direction: column; 5 | justify-content: space-around; 6 | padding:10rpx; 7 | } 8 | .card_title{ 9 | font-size: 50rpx; 10 | font-weight: 700; 11 | } 12 | .card_infobar{ 13 | display: flex; 14 | flex-direction: row; 15 | justify-content: space-between; 16 | } 17 | .card_address{ 18 | font-size: 30rpx; 19 | font-weight: 300; 20 | width: 600rpx; 21 | overflow: hidden; 22 | white-space:nowrap; 23 | } 24 | .card_thumbsup{ 25 | font-size: 30rpx; 26 | font-weight: 700; 27 | 28 | } 29 | .search_input{ 30 | background-color: #eee; 31 | margin:5px; 32 | width: 730rpx; 33 | height: 80rpx; 34 | } 35 | .input-placeholder{ 36 | text-align: center; 37 | } -------------------------------------------------------------------------------- /miniprogram/pages/test/test.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 图片上传 4 | 5 | 6 | {{images.length}}/2 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "map-miniprogram", 3 | "version": "1.0.0", 4 | "description": "![banner](https://postimg.aliavv.com/mweb/2019/01/28/%E7%A8%BF%E5%AE%9A%E8%AE%BE%E8%AE%A1%E5%AF%BC%E5%87%BA-20190128-154753.png)", 5 | "main": "index.js", 6 | "dependencies": { 7 | "@vant/weapp": "^1.6.8" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/Volcano-Yang/map-miniprogram.git" 16 | }, 17 | "author": "volcanoyang", 18 | "license": "ISC", 19 | "bugs": { 20 | "url": "https://github.com/Volcano-Yang/map-miniprogram/issues" 21 | }, 22 | "homepage": "https://github.com/Volcano-Yang/map-miniprogram#readme" 23 | } 24 | -------------------------------------------------------------------------------- /cloudfunctions/getStore/index.js: -------------------------------------------------------------------------------- 1 | const cloud = require('wx-server-sdk') 2 | cloud.init({ 3 | traceUser: true, 4 | env: "map-4g0ciu1x80002ab0" 5 | }) 6 | const db = cloud.database() 7 | const MAX_LIMIT = 100 8 | exports.main = async (event, context) => { 9 | // 先取出集合记录总数 10 | const countResult = await db.collection('store').count() 11 | const total = countResult.total 12 | // 计算需分几次取 13 | const batchTimes = Math.ceil(total / 100) 14 | // 承载所有读操作的 promise 的数组 15 | const tasks = [] 16 | for (let i = 0; i < batchTimes; i++) { 17 | const promise = db.collection('store').skip(i * MAX_LIMIT).limit(MAX_LIMIT).get() 18 | tasks.push(promise) 19 | } 20 | // 等待所有 21 | return (await Promise.all(tasks)).reduce((acc, cur) => { 22 | return { 23 | data: acc.data.concat(cur.data), 24 | errMsg: acc.errMsg, 25 | } 26 | }) 27 | } -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | == 2019.01.31 alpha-0131 == 2 | 1. A 添加了搜索的功能 3 | 2. A 添加了关键词的中英文逗号分割的功能 4 | 3. F 修复了 Cover View 导致的 搜索按钮消失的问题 5 | 4. A 添加了没有图片时默认隐藏 Swiper 的功能 6 | 5. A 在全部内容的列表里加入了搜索框,用于跳转到搜索页面 7 | 6. A 添加了搜索的自动 focus 8 | 7. A 添加了 default Scale ,调整默认的地图缩放 9 | == 2019.01.30 alpha-0130 == 10 | 1. F 修复了由于 cover-view 丢失样式导致的按钮丢失的问题 11 | 2. A 添加了地图界面的「添加我到小程序」的提醒 12 | 3. F 修复了由于没有添加授权导致无法获取用户地址的问题 13 | 4. A 添加了默认隐藏管理入口,需要通过长按「查看全部」来唤起管理入口 https://github.com/CloudKits/miniprogram-foodmap/wiki/How-to-enable-Admin-Button 14 | == 2019.01.29 alpha-0129 == 15 | 1. U 分享时如果可以获取到云文件的路径,就使用云文件路径 #6 @bestony 16 | 2. U 使用环境变量设置管理员身份 #8 @bestony docs: https://github.com/CloudKits/miniprogram-foodmap/wiki/Administrator 17 | 3. F 修复了添加新店铺数据后,地图页面不更新的问题。 #10 @bestony 18 | 4. A 添加了详情页面的复制路径的按钮 #7 @bestony 19 | 5. A 添加了店铺的导航功能 20 | 6. A 添加了在手机上删除商店的功能 21 | 7. A 添加了用户身份获取的功能 22 | 8. A 添加了地理位置获取时的权限核查 23 | == 2019.01.28 alpha == 24 | 1. 发布 alpha 版本 25 | -------------------------------------------------------------------------------- /miniprogram/pages/search/search.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | {{item.title}} 8 | 9 | {{item.address}} 10 | 11 | 12 | 13 | 14 | 15 | 请点击上方搜索框输入要搜索的关键字 16 | 17 | 18 | 19 | 没有找到想吃的美食?不妨去看看其他美食? 20 | 查看全部 21 | 22 | -------------------------------------------------------------------------------- /miniprogram/colorui/components/cu-custom.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.all-contributorsrc: -------------------------------------------------------------------------------- 1 | { 2 | "files": [ 3 | "README.md" 4 | ], 5 | "imageSize": 100, 6 | "commit": false, 7 | "contributors": [ 8 | { 9 | "login": "Volcano-Yang", 10 | "name": "杨灿就是杨火山", 11 | "avatar_url": "https://avatars.githubusercontent.com/u/43328103?v=4", 12 | "profile": "http://volcanoblog.cn", 13 | "contributions": [ 14 | "infra", 15 | "design", 16 | "code" 17 | ] 18 | }, 19 | { 20 | "login": "bestony", 21 | "name": "白宦成", 22 | "avatar_url": "https://avatars.githubusercontent.com/u/13283837?v=4", 23 | "profile": "https://www.ixiqin.com/", 24 | "contributions": [ 25 | "ideas", 26 | "code" 27 | ] 28 | } 29 | ], 30 | "contributorsPerLine": 7, 31 | "projectName": "map-marker-miniprogram", 32 | "projectOwner": "Volcano-Yang", 33 | "repoType": "github", 34 | "repoHost": "https://github.com", 35 | "skipCi": true 36 | } 37 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /miniprogram/pages/add/add.wxss: -------------------------------------------------------------------------------- 1 | @import "../../colorui/main.wxss"; 2 | @import "../../colorui/icon.wxss"; 3 | page { 4 | background-color: #ffffff; 5 | } 6 | 7 | .form { 8 | padding: 20px; 9 | } 10 | 11 | .form-item { 12 | margin-bottom: 18px; 13 | } 14 | 15 | .item-title { 16 | padding: 20rpx 20rpx 20rpx 0; 17 | font-size: 18px; 18 | font-weight: normal; 19 | display: flex; 20 | flex-direction: row; 21 | } 22 | 23 | .item-desc { 24 | font-size: 14px; 25 | margin-bottom: 6px; 26 | color: #adadad; 27 | } 28 | 29 | .loactionGroup { 30 | display: flex; 31 | flex-direction: row; 32 | } 33 | 34 | .loactionGroup input { 35 | width: 80%; 36 | height: 40rpx; 37 | margin-left: 5px; 38 | border: none; 39 | } 40 | 41 | textarea { 42 | border: 1px solid #dededd; 43 | padding: 20rpx; 44 | width: 90%; 45 | } 46 | 47 | .submit { 48 | margin-top: 40rpx; 49 | width: 100%; 50 | background-color: #007ae7; 51 | color: #ffffff; 52 | } 53 | 54 | .is-ness:before { 55 | content: '* '; 56 | color: red; 57 | } -------------------------------------------------------------------------------- /miniprogram/pages/index/index.js: -------------------------------------------------------------------------------- 1 | // miniprogram/pages/index/index.js 2 | Page({ 3 | /** 4 | * 页面的初始数据 5 | */ 6 | data: { 7 | isNotShowIntroduction: true, 8 | }, 9 | 10 | onCloseIntroduction() { 11 | console.log("关闭新手引导"); 12 | try { 13 | wx.setStorageSync("isNotShowIntroduction", true); 14 | } catch (e) {} 15 | wx.navigateTo({ 16 | url: "../map/map", 17 | }); 18 | }, 19 | 20 | /** 21 | * 生命周期函数--监听页面加载 22 | */ 23 | onLoad: function (options) { 24 | // try { 25 | // const res = wx.getStorageSync('isNotShowIntroduction'); 26 | // console.log("isNotShowIntroduction缓存内容", res); 27 | // if (res) { 28 | // console.log("不需要展示引导"); 29 | // wx.navigateTo({ 30 | // url: "../map/map", 31 | // }); 32 | // } else { 33 | // console.log("需要展示引导"); 34 | // this.setData({ isNotShowIntroduction: false }); 35 | // } 36 | // } catch (e) { 37 | // console.log("缓存不支持", e); 38 | // } 39 | }, 40 | }); 41 | -------------------------------------------------------------------------------- /miniprogram/colorui/components/cu-custom.js: -------------------------------------------------------------------------------- 1 | const app = getApp(); 2 | Component({ 3 | /** 4 | * 组件的一些选项 5 | */ 6 | options: { 7 | addGlobalClass: true, 8 | multipleSlots: true 9 | }, 10 | /** 11 | * 组件的对外属性 12 | */ 13 | properties: { 14 | bgColor: { 15 | type: String, 16 | default: '' 17 | }, 18 | isCustom: { 19 | type: [Boolean, String], 20 | default: false 21 | }, 22 | isBack: { 23 | type: [Boolean, String], 24 | default: false 25 | }, 26 | bgImage: { 27 | type: String, 28 | default: '' 29 | }, 30 | }, 31 | /** 32 | * 组件的初始数据 33 | */ 34 | data: { 35 | StatusBar: app.globalData.StatusBar, 36 | CustomBar: app.globalData.CustomBar, 37 | Custom: app.globalData.Custom 38 | }, 39 | /** 40 | * 组件的方法列表 41 | */ 42 | methods: { 43 | BackPage() { 44 | wx.navigateBack({ 45 | delta: 1 46 | }); 47 | }, 48 | toHome(){ 49 | wx.reLaunch({ 50 | url: '/pages/index/index', 51 | }) 52 | } 53 | } 54 | }) -------------------------------------------------------------------------------- /miniprogram/components/dialogModal/index.js: -------------------------------------------------------------------------------- 1 | var app = getApp() 2 | Component({ 3 | data: { 4 | 5 | }, 6 | properties: { 7 | isShow: { 8 | type: Boolean, 9 | value: false 10 | }, 11 | title: { 12 | type: String, 13 | value: '提示' 14 | }, 15 | content: { 16 | type: String, 17 | value: '' 18 | }, 19 | cancelText: { 20 | type: String, 21 | value: '取消' 22 | }, 23 | confirmText: { 24 | type: String, 25 | value: '确定' 26 | }, 27 | isNeedAuth: { 28 | type: Boolean, 29 | value: false 30 | }, 31 | cancelType: { 32 | type: String, 33 | value: '' 34 | }, 35 | confirmType: { 36 | type: String, 37 | value: '' 38 | } 39 | }, 40 | methods: { 41 | preventTouchMove() { }, 42 | cancel() { 43 | this.setData({ 44 | isShow: false 45 | }) 46 | this.triggerEvent('cancel') 47 | }, 48 | confirm() { 49 | this.setData({ 50 | isShow: false 51 | }) 52 | this.triggerEvent('confirm') 53 | } 54 | } 55 | }) 56 | -------------------------------------------------------------------------------- /miniprogram/pages/allList/allList.js: -------------------------------------------------------------------------------- 1 | const app = getApp(); 2 | const db = wx.cloud.database() 3 | const store = db.collection('store'); 4 | Page({ 5 | 6 | /** 7 | * 页面的初始数据 8 | */ 9 | data: { 10 | numbers: 0, 11 | stores: [] 12 | }, 13 | 14 | /** 15 | * 生命周期函数--监听页面加载 16 | */ 17 | onLoad: function(options) { 18 | this.loadData(); 19 | }, 20 | loadData: function() { 21 | store.skip(this.data.numbers).get().then(res => { 22 | /** 23 | * 如果没有数据,就提示没有商户了,并返回。 24 | */ 25 | if (res.data.length == 0) { 26 | wx.showToast({ 27 | title: '没有别的店铺了!', 28 | icon: 'none' 29 | }); 30 | return; 31 | } 32 | this.setData({ 33 | stores: this.data.stores.concat(res.data), 34 | numbers: this.data.numbers + res.data.length 35 | }); 36 | }) 37 | }, 38 | /** 39 | * 页面上拉触底事件的处理函数 40 | */ 41 | onReachBottom: function() { 42 | this.loadData(); 43 | }, 44 | navigateToSearch:function(e){ 45 | wx.redirectTo({ 46 | url: '../search/search', 47 | }) 48 | } 49 | }) -------------------------------------------------------------------------------- /miniprogram/pages/search/search.wxss: -------------------------------------------------------------------------------- 1 | .search_box{ 2 | background-color: #eee; 3 | display: flex; 4 | padding:5rpx; 5 | } 6 | .search_title{ 7 | text-align: center; 8 | font-size: 80rpx; 9 | } 10 | .search_input{ 11 | background-color: #fff; 12 | width: 100%; 13 | height: 80rpx; 14 | } 15 | .input-placeholder{ 16 | text-align: center; 17 | } 18 | .not_found{ 19 | display: flex; 20 | width: 100%; 21 | height: 750rpx; 22 | justify-content: center; 23 | line-height: 750rpx; 24 | } 25 | .card{ 26 | border-bottom: 1rpx solid #cecece; 27 | display: flex; 28 | flex-direction: column; 29 | justify-content: space-around; 30 | padding:10rpx; 31 | } 32 | .card_title{ 33 | font-size: 50rpx; 34 | font-weight: 700; 35 | } 36 | .card_infobar{ 37 | display: flex; 38 | flex-direction: row; 39 | justify-content: space-between; 40 | } 41 | .card_address{ 42 | font-size: 30rpx; 43 | font-weight: 300; 44 | width:750rpx; 45 | overflow: hidden; 46 | white-space:nowrap; 47 | } 48 | .no_result_button{ 49 | display:flex; 50 | flex-direction:column; 51 | justify-content:center; 52 | height:700rpx; 53 | width:750rpx; 54 | align-items:center; 55 | font-size: 30rpx; 56 | } 57 | .view_all{ 58 | margin-top: 40rpx; 59 | } -------------------------------------------------------------------------------- /miniprogram/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages": [ 3 | "pages/map/map", 4 | "pages/index/index", 5 | "pages/allList/allList", 6 | "pages/info/info", 7 | "pages/add/add", 8 | "pages/search/search", 9 | "pages/share/share", 10 | "pages/myList/myList", 11 | "pages/article/article", 12 | "pages/test/test" 13 | ], 14 | "window": { 15 | "backgroundColor": "#F6F6F6", 16 | "backgroundTextStyle": "light", 17 | "navigationBarBackgroundColor": "#007ae7", 18 | "navigationBarTitleText": "友好盲道地图", 19 | "navigationBarTextStyle": "white" 20 | }, 21 | "permission": { 22 | "scope.userLocation": { 23 | "desc": "你的位置信息将用于确定当前地图中心点" 24 | } 25 | }, 26 | "sitemapLocation": "sitemap.json", 27 | "usingComponents": { 28 | "van-panel": "@vant/weapp/panel/index", 29 | "van-field": "@vant/weapp/field/index", 30 | "van-icon": "@vant/weapp/icon/index", 31 | "van-button": "@vant/weapp/button/index", 32 | "van-radio": "@vant/weapp/radio/index", 33 | "van-radio-group": "@vant/weapp/radio-group/index", 34 | "van-row": "@vant/weapp/row/index", 35 | "van-col": "@vant/weapp/col/index", 36 | "van-uploader": "@vant/weapp/uploader/index", 37 | "van-divider": "@vant/weapp/divider/index", 38 | "van-popup": "@vant/weapp/popup/index" 39 | } 40 | } -------------------------------------------------------------------------------- /miniprogram/pages/info/info.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | {{store.problemLabel}} 12 | 13 | 14 | 15 | 上传者: 16 | {{store.userName || "系统管理员"}} 17 | 18 | 19 | 20 | 21 | {{store.address}} 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /miniprogram/pages/search/search.js: -------------------------------------------------------------------------------- 1 | const app = getApp(); 2 | const db = wx.cloud.database() 3 | const store = db.collection('store'); 4 | Page({ 5 | 6 | /** 7 | * 页面的初始数据 8 | */ 9 | data: { 10 | numbers: 0, 11 | stores: [], 12 | focus:false, 13 | searched:false 14 | }, 15 | 16 | /** 17 | * 生命周期函数--监听页面加载 18 | */ 19 | onLoad: function (options) { 20 | this.setData({ 21 | focus:true 22 | }) 23 | }, 24 | 25 | /** 26 | * 生命周期函数--监听页面初次渲染完成 27 | */ 28 | onReady: function () { 29 | 30 | }, 31 | onReachBottom: function () { 32 | this.loadData(); 33 | }, 34 | loadData:function(keywords){ 35 | store.skip(this.data.numbers).where({ 36 | title: db.RegExp({ 37 | regexp: this.data.keywords, 38 | options: 'i', 39 | }) 40 | }).get().then(res => { 41 | /** 42 | * 如果没有数据,就提示没有商户了,并返回。 43 | */ 44 | if (res.data.length == 0) { 45 | this.setData({ 46 | searched:true 47 | }) 48 | } 49 | this.setData({ 50 | stores: this.data.stores.concat(res.data), 51 | numbers: this.data.numbers + res.data.length 52 | }); 53 | }) 54 | }, 55 | search:function(e){ 56 | this.setData({ 57 | keywords: e.detail.value 58 | },res => { 59 | this.loadData(); 60 | }) 61 | } 62 | }) -------------------------------------------------------------------------------- /miniprogram/components/dialogModal/index.wxss: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | height: 100%; 4 | } 5 | 6 | .back-model { 7 | width: 100%; 8 | height: 100%; 9 | position: fixed; 10 | z-index: 999; 11 | background-color: rgba(0, 0, 0, 0.6); 12 | top: 0; 13 | } 14 | 15 | .conent-model { 16 | position: fixed; 17 | left: 50%; 18 | top: 50%; 19 | width: 622rpx; 20 | margin-left: -311rpx; 21 | margin-top: -200rpx; 22 | z-index: 999; 23 | background: #fff; 24 | border-radius: 8rpx; 25 | padding-top: 32rpx; 26 | } 27 | 28 | .title { 29 | display: block; 30 | text-align: center; 31 | font-size: 36rpx; 32 | color: #3c3c3c; 33 | } 34 | 35 | .content { 36 | display: block; 37 | text-align: center; 38 | font-size: 30rpx; 39 | padding: 32rpx; 40 | color: #999; 41 | } 42 | 43 | .quickBtn { 44 | width: 100%; 45 | height: 96rpx; 46 | border-top: 2rpx solid #EEE; 47 | line-height: 96rpx; 48 | } 49 | 50 | .cancel-btn { 51 | width: 50%; 52 | display: inline-block; 53 | color: #3c3c3c; 54 | font-size: 32rpx; 55 | text-align: center; 56 | height: 96rpx; 57 | line-height: 96rpx; 58 | border-right: 1rpx solid #EEE; 59 | } 60 | 61 | .confirm-btn { 62 | width: 50%; 63 | display: inline-block; 64 | color: #00cc88; 65 | font-size: 32rpx; 66 | height: 96rpx; 67 | line-height: 96rpx; 68 | text-align: center; 69 | border-left: 1rpx solid #EEE; 70 | } 71 | -------------------------------------------------------------------------------- /miniprogram/pages/article/article.js: -------------------------------------------------------------------------------- 1 | // pages/article/article.js 2 | Page({ 3 | /** 4 | * 页面的初始数据 5 | */ 6 | data: {}, 7 | 8 | /** 9 | * 生命周期函数--监听页面加载 10 | */ 11 | onLoad: function (options) {}, 12 | 13 | /** 14 | * 生命周期函数--监听页面初次渲染完成 15 | */ 16 | onReady: function () {}, 17 | 18 | /** 19 | * 生命周期函数--监听页面显示 20 | */ 21 | onShow: function () {}, 22 | 23 | /** 24 | * 生命周期函数--监听页面隐藏 25 | */ 26 | onHide: function () {}, 27 | 28 | /** 29 | * 生命周期函数--监听页面卸载 30 | */ 31 | onUnload: function () {}, 32 | 33 | /** 34 | * 页面相关事件处理函数--监听用户下拉动作 35 | */ 36 | onPullDownRefresh: function () {}, 37 | 38 | /** 39 | * 页面上拉触底事件的处理函数 40 | */ 41 | onReachBottom: function () {}, 42 | 43 | /** 44 | * 用户点击右上角分享 45 | */ 46 | onShareAppMessage: function () { 47 | return { 48 | title: "我在友好盲道地图上标记了一处盲道问题,你也快来加入我们吧", 49 | path: "/pages/map/map", 50 | imageUrl: 51 | "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/share/share-pre.jpg?sign=d0236b01a9f4f1255d06109ef4a3fa91&t=1618313697", 52 | }; 53 | }, 54 | /** 55 | * 用户分享到朋友圈 56 | */ 57 | onShareTimeline: function () { 58 | return { 59 | title: "我在友好盲道地图上标记了一处盲道问题,你也快来加入我们吧", 60 | path: "/pages/map/map", 61 | imageUrl: 62 | "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/share/share-pre.jpg?sign=d0236b01a9f4f1255d06109ef4a3fa91&t=1618313697", 63 | }; 64 | }, 65 | }); 66 | -------------------------------------------------------------------------------- /miniprogram/components/shareBox/index.wxss: -------------------------------------------------------------------------------- 1 | .share-wrap { 2 | width: 100%; 3 | } 4 | 5 | .share-back { 6 | width: 100%; 7 | height: 100%; 8 | background: rgba(0, 0, 0, 0.6); 9 | position: fixed; 10 | top: 0; 11 | left: 0; 12 | right: 0; 13 | bottom: 0; 14 | z-index: 888; 15 | } 16 | 17 | .share-container { 18 | width: 100%; 19 | height: 100vh; 20 | color: #3C3C3C; 21 | position: fixed; 22 | bottom: 0; 23 | left: 0; 24 | right: 0; 25 | z-index: 999; 26 | } 27 | 28 | .close { 29 | width: 30rpx; 30 | height: 30rpx; 31 | overflow: hidden; 32 | position: absolute; 33 | right: 64rpx; 34 | top: 64rpx; 35 | } 36 | 37 | .close::after { 38 | transform: rotate(-45deg); 39 | } 40 | 41 | .close::before { 42 | transform: rotate(45deg); 43 | } 44 | 45 | .close::before, 46 | .close::after { 47 | content: ''; 48 | position: absolute; 49 | height: 3rpx; 50 | width: 100%; 51 | top: 50%; 52 | left: 0; 53 | margin-top: -2rpx; 54 | background: #9C9C9C; 55 | } 56 | 57 | .share-image { 58 | width: 80%; 59 | margin: 110rpx auto 20rpx; 60 | display: block; 61 | border-radius: 16rpx; 62 | box-shadow: 0px 4rpx 8px 0px rgba(0, 0, 0, 0.1); 63 | } 64 | 65 | .share-tips { 66 | width: 100%; 67 | text-align: center; 68 | color: #3C3C3C; 69 | font-size: 28rpx; 70 | margin: 32rpx 0; 71 | } 72 | 73 | .save-btn { 74 | color: #fff; 75 | font-size: 36rpx; 76 | width: 336rpx; 77 | height: 80rpx; 78 | margin: 0rpx auto 94rpx; 79 | background-color: #007ae8; 80 | display: flex; 81 | justify-content: center; 82 | align-items: center; 83 | border-radius: 40rpx; 84 | } -------------------------------------------------------------------------------- /miniprogram/pages/share/share.wxss: -------------------------------------------------------------------------------- 1 | page { 2 | width: 750rpx; 3 | background-color: #69aff1; 4 | background-image: url("https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/share/share-page2.png?sign=8abad4226cef1ca7f2174c9d89af6b93&t=1617611501"); 5 | background-size: 100%; 6 | background-repeat: no-repeat; 7 | } 8 | 9 | .userAvatar { 10 | position: absolute; 11 | top: 72rpx; 12 | left: 296rpx; 13 | width: 150rpx; 14 | height: 150rpx; 15 | border-radius: 50%; 16 | overflow: hidden; 17 | border: 1px solid #619bcd; 18 | } 19 | 20 | .content { 21 | width: 500rpx; 22 | height: 650rpx; 23 | position: absolute; 24 | position: absolute; 25 | left: 50%; 26 | top: 240rpx; 27 | /* 宽度的一半 */ 28 | margin-left: -250rpx; 29 | display: flex; 30 | flex-direction: column; 31 | justify-content: center; 32 | align-items: center; 33 | } 34 | 35 | .userName { 36 | font-size: 42rpx; 37 | margin-bottom: 20rpx; 38 | } 39 | 40 | .user-content { 41 | font-size: 30rpx; 42 | line-height: 40rpx; 43 | text-align: center; 44 | margin-bottom: 10rpx; 45 | } 46 | 47 | .art { 48 | width: 400rpx; 49 | height: 300rpx; 50 | border: 14rpx #fff solid; 51 | box-shadow:0px 1px 5px #9C9C9C; 52 | margin: 20rpx; 53 | } 54 | 55 | .share-wechat { 56 | background-size: 110rpx 110rpx; 57 | position: absolute; 58 | top: 1000rpx; 59 | left: 164rpx; 60 | width: 110rpx; 61 | height: 110rpx; 62 | border-radius: 50%; 63 | } 64 | 65 | .create-image { 66 | background-size: 110rpx 110rpx; 67 | position: absolute; 68 | top: 1000rpx; 69 | right: 164rpx; 70 | width: 110rpx; 71 | height: 110rpx; 72 | border-radius: 50%; 73 | } -------------------------------------------------------------------------------- /miniprogram/pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | .page { 2 | width: 100vw; 3 | height: 100vh; 4 | display: flex; 5 | flex-direction: column; 6 | justify-content: center; 7 | align-items: center; 8 | background-color: #454545; 9 | } 10 | 11 | .map { 12 | width: 100vw; 13 | height: 100vh; 14 | } 15 | 16 | /* page { 17 | background-color: #454545; 18 | } */ 19 | .top-image { 20 | width: 100vw; 21 | height: 100vh; 22 | } 23 | 24 | .tips-image { 25 | position: absolute; 26 | width: 100vw; 27 | height: 100vh; 28 | bottom: 0; 29 | background-color: rgba(0, 0, 0, 0.8); 30 | } 31 | 32 | .tips { 33 | position: absolute; 34 | width: 750rpx; 35 | height: 926rpx; 36 | bottom: 200rpx; 37 | } 38 | 39 | .nav { 40 | position: absolute; 41 | width: 750rpx; 42 | height: 140rpx; 43 | bottom: 20rpx; 44 | } 45 | 46 | 47 | 48 | /* 问题列表 */ 49 | .numberList { 50 | position: absolute; 51 | right: 30rpx; 52 | top: 20rpx; 53 | text-align: center; 54 | background-color: #fff; 55 | box-shadow: 3px 3px 2px #bbbcbc; 56 | } 57 | 58 | .numberList-item { 59 | display: flex; 60 | flex-direction: row; 61 | background-color: #fff; 62 | justify-content: center; 63 | align-items: center; 64 | padding: 10px 5px 10px 4px; 65 | } 66 | 67 | .numberList-item-line { 68 | height: 1px; 69 | width: 80%; 70 | margin: 0px auto; 71 | background-color: #ededed; 72 | } 73 | 74 | .numberList-item-image { 75 | width: 30px; 76 | margin-right: 5px; 77 | } 78 | 79 | .numberList-item-text { 80 | font-size: 14px; 81 | } 82 | 83 | 84 | .getLocation { 85 | position: absolute; 86 | left: 12px; 87 | bottom: 120px; 88 | text-align: center; 89 | width: 20px; 90 | background-color: #fff; 91 | padding: 10px; 92 | box-shadow: 1px 2px 3px #999999; 93 | } -------------------------------------------------------------------------------- /miniprogram/pages/info/info.wxss: -------------------------------------------------------------------------------- 1 | @import "../../colorui/main.wxss"; 2 | @import "../../colorui/icon.wxss"; 3 | 4 | page { 5 | width: 750rpx; 6 | background-color: #fff; 7 | } 8 | 9 | .slide-image { 10 | width: 750rpx; 11 | } 12 | 13 | swiper { 14 | height: 424rpx; 15 | background-color: #eee; 16 | } 17 | 18 | .content { 19 | 20 | margin: 0px auto; 21 | display: flex; 22 | flex-direction: column; 23 | justify-content: center; 24 | align-items: center; 25 | } 26 | 27 | .problem { 28 | font-size: 60rpx; 29 | font-weight: 350; 30 | margin-top: 80rpx; 31 | margin-bottom: 70rpx; 32 | color: #0077e5; 33 | } 34 | 35 | .user { 36 | width: 700rpx; 37 | margin-bottom: 30rpx; 38 | /* font-weight: 200; */ 39 | text-align: center; 40 | } 41 | 42 | .address { 43 | width: 600rpx; 44 | margin-bottom: 40rpx; 45 | /* font-weight: 200; */ 46 | text-align: center; 47 | } 48 | 49 | 50 | 51 | 52 | 53 | .copy_path { 54 | position: absolute; 55 | right: 100rpx; 56 | bottom: 50rpx; 57 | color: #7795f8; 58 | background-color: rgb(255, 255, 255); 59 | box-shadow: 0 4px 6px rgba(50, 50, 93, .11), 0 1px 3px rgba(0, 0, 0, .08); 60 | } 61 | 62 | .delete_item { 63 | position: absolute; 64 | left: 100rpx; 65 | bottom: 50rpx; 66 | color: #ff0000; 67 | background-color: rgb(255, 255, 255); 68 | box-shadow: 0 4px 6px rgba(50, 50, 93, .11), 0 1px 3px rgba(0, 0, 0, .08); 69 | } 70 | 71 | 72 | .navigateBox { 73 | display: flex; 74 | flex-direction: row; 75 | justify-content: center; 76 | margin: 20rpx; 77 | } 78 | 79 | .navigate { 80 | margin: 20rpx 0rpx; 81 | width: 80%; 82 | height: 80rpx; 83 | color: #fff; 84 | text-align: center; 85 | line-height: 80rpx; 86 | font-weight: 200; 87 | background-color: #007ce6; 88 | } -------------------------------------------------------------------------------- /miniprogram/pages/share/share.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {{drawData.context1}}{{drawData.numberId}}{{drawData.context2}} 10 | 11 | 12 | {{drawData.context3}} 13 | 14 | 15 | {{drawData.artTitle}} 16 | 17 | {{drawData.artContext1}} 18 | {{drawData.artContext2}} 19 | 20 | 21 | 22 | 23 | 25 | 26 | 29 | 32 | 33 | -------------------------------------------------------------------------------- /miniprogram/painter/lib/util.js: -------------------------------------------------------------------------------- 1 | 2 | function isValidUrl(url) { 3 | return /(ht|f)tp(s?):\/\/([^ \\/]*\.)+[^ \\/]*(:[0-9]+)?\/?/.test(url); 4 | } 5 | 6 | /** 7 | * 深度对比两个对象是否一致 8 | * from: https://github.com/epoberezkin/fast-deep-equal 9 | * @param {Object} a 对象a 10 | * @param {Object} b 对象b 11 | * @return {Boolean} 是否相同 12 | */ 13 | /* eslint-disable */ 14 | function equal(a, b) { 15 | if (a === b) return true; 16 | 17 | if (a && b && typeof a == 'object' && typeof b == 'object') { 18 | var arrA = Array.isArray(a) 19 | , arrB = Array.isArray(b) 20 | , i 21 | , length 22 | , key; 23 | 24 | if (arrA && arrB) { 25 | length = a.length; 26 | if (length != b.length) return false; 27 | for (i = length; i-- !== 0;) 28 | if (!equal(a[i], b[i])) return false; 29 | return true; 30 | } 31 | 32 | if (arrA != arrB) return false; 33 | 34 | var dateA = a instanceof Date 35 | , dateB = b instanceof Date; 36 | if (dateA != dateB) return false; 37 | if (dateA && dateB) return a.getTime() == b.getTime(); 38 | 39 | var regexpA = a instanceof RegExp 40 | , regexpB = b instanceof RegExp; 41 | if (regexpA != regexpB) return false; 42 | if (regexpA && regexpB) return a.toString() == b.toString(); 43 | 44 | var keys = Object.keys(a); 45 | length = keys.length; 46 | 47 | if (length !== Object.keys(b).length) 48 | return false; 49 | 50 | for (i = length; i-- !== 0;) 51 | if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; 52 | 53 | for (i = length; i-- !== 0;) { 54 | key = keys[i]; 55 | if (!equal(a[key], b[key])) return false; 56 | } 57 | 58 | return true; 59 | } 60 | 61 | return a!==a && b!==b; 62 | } 63 | 64 | module.exports = { 65 | isValidUrl, 66 | equal 67 | }; 68 | 69 | -------------------------------------------------------------------------------- /miniprogram/pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 3 9 | 设计 10 | 11 | 12 | 13 | 14 | 15 | 16 | 4 17 | 占用 18 | 19 | 20 | 21 | 22 | 23 | 24 | 5 25 | 破损 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /miniprogram/pages/test/test.js: -------------------------------------------------------------------------------- 1 | // miniprogram/pages/test/test.js 2 | Page({ 3 | data: { 4 | images: [], 5 | }, 6 | onLoad() {}, 7 | uploadImage: function (e) { 8 | wx.chooseImage({ 9 | count: 2, 10 | sizeType: ['original', 'compressed'], 11 | sourceType: ['album', 'camera'], 12 | success: res => { 13 | 14 | wx.showLoading({ title: '上传中' }) 15 | let tempFilePaths = res.tempFilePaths 16 | let items = []; 17 | for (const tempFilePath of tempFilePaths) { 18 | items.push({ 19 | src: tempFilePath 20 | }) 21 | } 22 | const uploadTask = items.map(item => this.uploadPhoto(item.src)) 23 | 24 | Promise.all(uploadTask).then(result => { 25 | 26 | let urls = this.data.images; 27 | for (const file of result) { 28 | urls.push(file.fileID); 29 | } 30 | this.setData({ 31 | images: urls 32 | }, res => { 33 | wx.hideLoading(); 34 | wx.showToast({ title: '上传图片成功', icon: 'success' }) 35 | }) 36 | }).catch(() => { 37 | wx.hideLoading() 38 | wx.showToast({ title: '上传图片错误', icon: 'error' }) 39 | }) 40 | 41 | this.setData({ tempPhoto: items }) 42 | } 43 | }) 44 | }, 45 | uploadPhoto(filePath) { 46 | return wx.cloud.uploadFile({ 47 | cloudPath: `${Date.now()}-${Math.floor(Math.random(0, 1) * 10000000)}.png`, 48 | filePath 49 | }) 50 | }, 51 | ViewImage(e) { 52 | wx.previewImage({ 53 | urls: this.data.images, 54 | current: e.currentTarget.dataset.url 55 | }); 56 | }, 57 | DelImg(e) { 58 | wx.showModal({ 59 | title: '确定要删除这个图片吗?', 60 | cancelText: '保留', 61 | confirmText: '删除', 62 | success: res => { 63 | if (res.confirm) { 64 | this.data.images.splice(e.currentTarget.dataset.index, 1); 65 | this.setData({ 66 | images: this.data.images 67 | }) 68 | } 69 | } 70 | }) 71 | }, 72 | }) -------------------------------------------------------------------------------- /miniprogram/pages/myList/myList.js: -------------------------------------------------------------------------------- 1 | const db = wx.cloud.database(); 2 | const store = db.collection("store"); 3 | 4 | Page({ 5 | /** 6 | * 页面的初始数据 7 | */ 8 | data: { 9 | isEmpty: true, 10 | store: [], 11 | }, 12 | 13 | /** 14 | * 生命周期函数--监听页面加载 15 | */ 16 | onLoad: function (options) { 17 | const openId = wx.getStorageSync("openId"); 18 | store 19 | .where({ 20 | _openid: openId, 21 | }) 22 | .get() 23 | .then((res) => { 24 | const store = res.data.map((item, index) => { 25 | if (item.createTime) { 26 | item.date = `${item.createTime.getFullYear()}年${ 27 | item.createTime.getMonth() + 1 28 | }月${item.createTime.getDate()}日`; 29 | } 30 | item.id = index; 31 | return item; 32 | }); 33 | console.log(res.data); 34 | // 处理数据日期 和 添加id 35 | this.setData({ 36 | store:store.reverse(), 37 | }, 38 | (res) => { 39 | console.log("设置数据成功"); 40 | if (this.data.store.length > 0) { 41 | this.setData({ 42 | isEmpty: false, 43 | }); 44 | } 45 | } 46 | ); 47 | }); 48 | }, 49 | 50 | /* 51 | * 生命周期函数--监听页面初次渲染完成 52 | */ 53 | onReady: function () {}, 54 | 55 | /** 56 | * 用户点击右上角分享 57 | */ 58 | onShareAppMessage: function () { 59 | return { 60 | title: "我在友好盲道地图上标记了一处盲道问题,你也快来加入我们吧", 61 | path: "/pages/map/map", 62 | imageUrl: "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/share/share-pre.jpg?sign=d0236b01a9f4f1255d06109ef4a3fa91&t=1618313697", 63 | }; 64 | }, 65 | /** 66 | * 用户分享到朋友圈 67 | */ 68 | onShareTimeline: function () { 69 | return { 70 | title: "我在友好盲道地图上标记了一处盲道问题,你也快来加入我们吧", 71 | path: "/pages/map/map", 72 | imageUrl: "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/share/share-pre.jpg?sign=d0236b01a9f4f1255d06109ef4a3fa91&t=1618313697", 73 | }; 74 | }, 75 | 76 | tapImage: function (e) { 77 | wx.previewImage({ 78 | urls: [e.currentTarget.dataset.url], 79 | current: e.currentTarget.dataset.url, 80 | }); 81 | }, 82 | }); -------------------------------------------------------------------------------- /miniprogram/pages/myList/myList.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 你还没有日志记录哦 13 | 快去上传吧~ 14 | 15 | 16 | 17 | 18 | 19 | {{item.date || "没有记录时间"}} 20 | 21 | 22 | 23 | 24 | {{item.address}} 25 | 26 | 你第{{item.id+1}}次上传了{{item.problemLabel}}问题,帮助了友好地图的修筑。 27 | 28 | 30 | 32 | 33 | 34 | 35 | 40 | 41 | 42 | 43 | 已展示全部数据 44 | 45 | -------------------------------------------------------------------------------- /project.private.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "setting": {}, 3 | "condition": { 4 | "plugin": { 5 | "list": [] 6 | }, 7 | "game": { 8 | "list": [] 9 | }, 10 | "gamePlugin": { 11 | "list": [] 12 | }, 13 | "miniprogram": { 14 | "list": [ 15 | { 16 | "name": "引导页", 17 | "pathName": "pages/index/index", 18 | "query": "", 19 | "scene": null 20 | }, 21 | { 22 | "name": "地图页", 23 | "pathName": "pages/map/map", 24 | "query": "", 25 | "scene": null 26 | }, 27 | { 28 | "name": "新增markers", 29 | "pathName": "pages/add/add", 30 | "query": "", 31 | "scene": null 32 | }, 33 | { 34 | "name": "上传成功分享页", 35 | "pathName": "pages/share/share", 36 | "query": "", 37 | "scene": null 38 | }, 39 | { 40 | "name": "我的上传日志", 41 | "pathName": "pages/myList/myList", 42 | "query": "", 43 | "scene": null 44 | }, 45 | { 46 | "name": "盲道科普", 47 | "pathName": "pages/article/article", 48 | "query": "", 49 | "scene": null 50 | }, 51 | { 52 | "name": "全部数据", 53 | "pathName": "pages/allList/allList", 54 | "query": "id=28ee4e3e605329880b56996217a0c745", 55 | "scene": null 56 | }, 57 | { 58 | "name": "文章宣传页", 59 | "pathName": "pages/article/article", 60 | "query": "", 61 | "scene": null 62 | }, 63 | { 64 | "name": "查看全部markers", 65 | "pathName": "pages/allList/allList", 66 | "query": "", 67 | "scene": null 68 | }, 69 | { 70 | "name": "markers详情页", 71 | "pathName": "pages/info/info", 72 | "query": "id=28ee4e3e605329880b56996217a0c745", 73 | "scene": null 74 | }, 75 | { 76 | "id": -1, 77 | "name": "搜索页面", 78 | "pathName": "pages/search/search", 79 | "query": "", 80 | "scene": null 81 | }, 82 | { 83 | "name": "pages/test/test", 84 | "pathName": "pages/test/test", 85 | "query": "", 86 | "scene": null 87 | } 88 | ] 89 | } 90 | } 91 | } -------------------------------------------------------------------------------- /miniprogram/pages/add/add.wxml: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 当前定位 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 盲道问题 13 | 14 | 15 | 16 | 17 | 18 | 盲道占用 19 | 20 | 21 | 盲道破损 22 | 23 | 24 | 盲道设计 25 | 26 | 27 | 28 | 29 | 30 | 图片上传 31 | 请最多上传两张完整且清晰的盲道问题图片 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 文字描述 48 | 49 | 50 | 51 |
52 |
-------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.gitignore.io/api/node,macos 3 | # Edit at https://www.gitignore.io/?templates=node,macos 4 | 5 | ### macOS ### 6 | # General 7 | *.DS_Store 8 | .AppleDouble 9 | .LSOverride 10 | 11 | # Icon must end with two \r 12 | Icon 13 | 14 | # Thumbnails 15 | ._* 16 | 17 | # Files that might appear in the root of a volume 18 | .DocumentRevisions-V100 19 | .fseventsd 20 | .Spotlight-V100 21 | .TemporaryItems 22 | .Trashes 23 | .VolumeIcon.icns 24 | .com.apple.timemachine.donotpresent 25 | 26 | # Directories potentially created on remote AFP share 27 | .AppleDB 28 | .AppleDesktop 29 | Network Trash Folder 30 | Temporary Items 31 | .apdisk 32 | 33 | ### Node ### 34 | # Logs 35 | logs 36 | *.log 37 | npm-debug.log* 38 | yarn-debug.log* 39 | yarn-error.log* 40 | 41 | # Runtime data 42 | pids 43 | *.pid 44 | *.seed 45 | *.pid.lock 46 | 47 | # Directory for instrumented libs generated by jscoverage/JSCover 48 | lib-cov 49 | 50 | # Coverage directory used by tools like istanbul 51 | coverage 52 | 53 | # nyc test coverage 54 | .nyc_output 55 | 56 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 57 | .grunt 58 | 59 | # Bower dependency directory (https://bower.io/) 60 | bower_components 61 | 62 | # node-waf configuration 63 | .lock-wscript 64 | 65 | # Compiled binary addons (https://nodejs.org/api/addons.html) 66 | build/Release 67 | 68 | # Dependency directories 69 | node_modules 70 | miniprogram/miniprogram_npm 71 | package-lock.json 72 | 73 | # TypeScript v1 declaration files 74 | typings/ 75 | 76 | # Optional npm cache directory 77 | .npm 78 | 79 | # Optional eslint cache 80 | .eslintcache 81 | 82 | # Optional REPL history 83 | .node_repl_history 84 | 85 | # Output of 'npm pack' 86 | *.tgz 87 | 88 | # Yarn Integrity file 89 | .yarn-integrity 90 | 91 | # dotenv environment variables file 92 | .env 93 | .env.test 94 | 95 | # parcel-bundler cache (https://parceljs.org/) 96 | .cache 97 | 98 | # next.js build output 99 | .next 100 | 101 | # nuxt.js build output 102 | .nuxt 103 | 104 | # vuepress build output 105 | .vuepress/dist 106 | 107 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # End of https://www.gitignore.io/api/node,macos 117 | 118 | /miniprogram/config.js 119 | 120 | 121 | -------------------------------------------------------------------------------- /miniprogram/pages/myList/myList.wxss: -------------------------------------------------------------------------------- 1 | /* miniprogram/pages/myList/myList.wxss */ 2 | page { 3 | overflow: scroll; 4 | width: 750rpx; 5 | } 6 | 7 | .userInfo { 8 | background-color: #ffffff; 9 | width: 100%; 10 | height: 150px; 11 | display: flex; 12 | flex-direction: column; 13 | justify-content: center; 14 | align-items: center; 15 | } 16 | 17 | .userName { 18 | margin-top: 5px; 19 | font-size: 18px; 20 | } 21 | 22 | .userAvatar { 23 | width: 80px; 24 | height: 80px; 25 | border-radius: 50%; 26 | overflow: hidden; 27 | border: 3px solid #fff; 28 | box-shadow: 0px 1px 5px #c9c6c6; 29 | } 30 | 31 | .divider { 32 | border: 3px solid #f7f6fc; 33 | } 34 | 35 | 36 | .isEmpty { 37 | background-color: #ffffff; 38 | width: 100%; 39 | height: 150px; 40 | display: flex; 41 | flex-direction: column; 42 | justify-content: center; 43 | align-items: center; 44 | color: #c9c9c9; 45 | font-size: 14px; 46 | padding-top: 60px; 47 | } 48 | 49 | .isEmpty-image { 50 | width: 150px; 51 | height: 150px; 52 | } 53 | 54 | 55 | /* 日志卡片 */ 56 | 57 | .item { 58 | margin-right: 16px; 59 | } 60 | 61 | .item-top { 62 | display: flex; 63 | flex-direction: row; 64 | align-items: center; 65 | padding: 10px 20px 30rpx 20px; 66 | } 67 | 68 | .item-icon { 69 | width: 8px; 70 | height: 8px; 71 | background-color: #007ae7; 72 | border-radius: 50%; 73 | overflow: hidden; 74 | margin-right: 15px; 75 | } 76 | 77 | .item-title { 78 | font-size: 18px; 79 | } 80 | 81 | .item-bottom { 82 | border-left: 1px solid #dedede; 83 | margin-left: 24px; 84 | padding-left: 15px; 85 | padding-bottom: 6px; 86 | } 87 | 88 | .item-address { 89 | display: flex; 90 | flex-direction: row; 91 | justify-content: flex-start; 92 | align-items: center; 93 | line-height: 14px; 94 | font-size: 12px; 95 | color: #4d4d4d; 96 | margin-bottom: 30rpx; 97 | } 98 | 99 | .item-content { 100 | line-height: 16px; 101 | font-size: 14px; 102 | margin-bottom: 30rpx; 103 | } 104 | 105 | .item-swiper { 106 | margin: 5px 0px 0px 0px; 107 | padding-bottom: 5px; 108 | width: 300px; 109 | height: 200px; 110 | } 111 | 112 | /* .item-images { 113 | width: 250px; 114 | height: 200px; 115 | } */ 116 | 117 | .imgList { 118 | display: flex; 119 | flex-direction: row; 120 | justify-content: start; 121 | align-items: center; 122 | } 123 | 124 | .img { 125 | width: 271rpx; 126 | height: 271rpx; 127 | margin: 0px 5px 0px; 128 | } -------------------------------------------------------------------------------- /miniprogram/colorui/animation.wxss: -------------------------------------------------------------------------------- 1 | /* 2 | Animation 微动画 3 | 基于ColorUI组建库的动画模块 by 文晓港 2019年3月26日19:52:28 4 | */ 5 | 6 | /* css 滤镜 控制黑白底色gif的 */ 7 | .gif-black{ 8 | mix-blend-mode: screen; 9 | } 10 | .gif-white{ 11 | mix-blend-mode: multiply; 12 | } 13 | 14 | 15 | /* Animation css */ 16 | [class*=animation-] { 17 | animation-duration: .5s; 18 | animation-timing-function: ease-out; 19 | animation-fill-mode: both 20 | } 21 | 22 | .animation-fade { 23 | animation-name: fade; 24 | animation-duration: .8s; 25 | animation-timing-function: linear 26 | } 27 | 28 | .animation-scale-up { 29 | animation-name: scale-up 30 | } 31 | 32 | .animation-scale-down { 33 | animation-name: scale-down 34 | } 35 | 36 | .animation-slide-top { 37 | animation-name: slide-top 38 | } 39 | 40 | .animation-slide-bottom { 41 | animation-name: slide-bottom 42 | } 43 | 44 | .animation-slide-left { 45 | animation-name: slide-left 46 | } 47 | 48 | .animation-slide-right { 49 | animation-name: slide-right 50 | } 51 | 52 | .animation-shake { 53 | animation-name: shake 54 | } 55 | 56 | .animation-reverse { 57 | animation-direction: reverse 58 | } 59 | 60 | @keyframes fade { 61 | 0% { 62 | opacity: 0 63 | } 64 | 65 | 100% { 66 | opacity: 1 67 | } 68 | } 69 | 70 | @keyframes scale-up { 71 | 0% { 72 | opacity: 0; 73 | transform: scale(.2) 74 | } 75 | 76 | 100% { 77 | opacity: 1; 78 | transform: scale(1) 79 | } 80 | } 81 | 82 | @keyframes scale-down { 83 | 0% { 84 | opacity: 0; 85 | transform: scale(1.8) 86 | } 87 | 88 | 100% { 89 | opacity: 1; 90 | transform: scale(1) 91 | } 92 | } 93 | 94 | @keyframes slide-top { 95 | 0% { 96 | opacity: 0; 97 | transform: translateY(-100%) 98 | } 99 | 100 | 100% { 101 | opacity: 1; 102 | transform: translateY(0) 103 | } 104 | } 105 | 106 | @keyframes slide-bottom { 107 | 0% { 108 | opacity: 0; 109 | transform: translateY(100%) 110 | } 111 | 112 | 100% { 113 | opacity: 1; 114 | transform: translateY(0) 115 | } 116 | } 117 | 118 | @keyframes shake { 119 | 120 | 0%, 121 | 100% { 122 | transform: translateX(0) 123 | } 124 | 125 | 10% { 126 | transform: translateX(-9px) 127 | } 128 | 129 | 20% { 130 | transform: translateX(8px) 131 | } 132 | 133 | 30% { 134 | transform: translateX(-7px) 135 | } 136 | 137 | 40% { 138 | transform: translateX(6px) 139 | } 140 | 141 | 50% { 142 | transform: translateX(-5px) 143 | } 144 | 145 | 60% { 146 | transform: translateX(4px) 147 | } 148 | 149 | 70% { 150 | transform: translateX(-3px) 151 | } 152 | 153 | 80% { 154 | transform: translateX(2px) 155 | } 156 | 157 | 90% { 158 | transform: translateX(-1px) 159 | } 160 | } 161 | 162 | @keyframes slide-left { 163 | 0% { 164 | opacity: 0; 165 | transform: translateX(-100%) 166 | } 167 | 168 | 100% { 169 | opacity: 1; 170 | transform: translateX(0) 171 | } 172 | } 173 | 174 | @keyframes slide-right { 175 | 0% { 176 | opacity: 0; 177 | transform: translateX(100%) 178 | } 179 | 180 | 100% { 181 | opacity: 1; 182 | transform: translateX(0) 183 | } 184 | } -------------------------------------------------------------------------------- /project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "miniprogramRoot": "miniprogram/", 3 | "cloudfunctionRoot": "cloudfunctions/", 4 | "setting": { 5 | "urlCheck": true, 6 | "es6": true, 7 | "enhance": true, 8 | "postcss": true, 9 | "preloadBackgroundData": false, 10 | "minified": true, 11 | "newFeature": true, 12 | "coverView": true, 13 | "nodeModules": true, 14 | "autoAudits": false, 15 | "showShadowRootInWxmlPanel": true, 16 | "scopeDataCheck": false, 17 | "uglifyFileName": false, 18 | "checkInvalidKey": true, 19 | "checkSiteMap": true, 20 | "uploadWithSourceMap": true, 21 | "compileHotReLoad": false, 22 | "useMultiFrameRuntime": true, 23 | "useApiHook": true, 24 | "useApiHostProcess": true, 25 | "babelSetting": { 26 | "ignore": [], 27 | "disablePlugins": [], 28 | "outputPath": "" 29 | }, 30 | "enableEngineNative": false, 31 | "bundle": false, 32 | "useIsolateContext": true, 33 | "useCompilerModule": true, 34 | "userConfirmedUseCompilerModuleSwitch": false, 35 | "userConfirmedBundleSwitch": false, 36 | "packNpmManually": true, 37 | "packNpmRelationList": [ 38 | { 39 | "packageJsonPath": "./package.json", 40 | "miniprogramNpmDistDir": "./miniprogram/" 41 | } 42 | ], 43 | "minifyWXSS": true 44 | }, 45 | "appid": "wx9e77ae37861c73ec", 46 | "projectname": "meishitujian", 47 | "libVersion": "2.15.0", 48 | "cloudfunctionTemplateRoot": "cloudfunctionTemplate", 49 | "condition": { 50 | "search": { 51 | "list": [] 52 | }, 53 | "conversation": { 54 | "list": [] 55 | }, 56 | "plugin": { 57 | "list": [] 58 | }, 59 | "game": { 60 | "list": [] 61 | }, 62 | "miniprogram": { 63 | "list": [ 64 | { 65 | "name": "引导页", 66 | "pathName": "pages/index/index", 67 | "query": "", 68 | "scene": null 69 | }, 70 | { 71 | "name": "地图页", 72 | "pathName": "pages/map/map", 73 | "query": "", 74 | "scene": null 75 | }, 76 | { 77 | "name": "新增markers", 78 | "pathName": "pages/add/add", 79 | "query": "", 80 | "scene": null 81 | }, 82 | { 83 | "name": "上传成功分享页", 84 | "pathName": "pages/share/share", 85 | "query": "", 86 | "scene": null 87 | }, 88 | { 89 | "name": "我的上传日志", 90 | "pathName": "pages/myList/myList", 91 | "query": "", 92 | "scene": null 93 | }, 94 | { 95 | "name": "盲道科普", 96 | "pathName": "pages/article/article", 97 | "query": "", 98 | "scene": null 99 | }, 100 | { 101 | "name": "全部数据", 102 | "pathName": "pages/allList/allList", 103 | "query": "id=28ee4e3e605329880b56996217a0c745", 104 | "scene": null 105 | }, 106 | { 107 | "name": "文章宣传页", 108 | "pathName": "pages/article/article", 109 | "query": "", 110 | "scene": null 111 | }, 112 | { 113 | "name": "查看全部markers", 114 | "pathName": "pages/allList/allList", 115 | "query": "", 116 | "scene": null 117 | }, 118 | { 119 | "name": "markers详情页", 120 | "pathName": "pages/info/info", 121 | "query": "id=28ee4e3e605329880b56996217a0c745", 122 | "scene": null 123 | }, 124 | { 125 | "id": -1, 126 | "name": "搜索页面", 127 | "pathName": "pages/search/search", 128 | "query": "", 129 | "scene": null 130 | } 131 | ] 132 | } 133 | } 134 | } -------------------------------------------------------------------------------- /miniprogram/pages/map/map.wxml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 7 | 8 | 9 | 添加到【我的小程序】更快找到我 10 | 11 | 12 | 13 | 14 | 15 | 16 | {{designProblemNumber}} 17 | 设计 18 | 19 | 20 | 21 | 22 | 23 | 24 | {{occurpyProblemNumber}} 25 | 占用 26 | 27 | 28 | 29 | 30 | 31 | 32 | {{errorProblemNumber}} 33 | 破损 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 盲道科普 47 | 48 | 49 | 50 | 51 | 上传 52 | 53 | 54 | 55 | 56 | 筑路日志 57 | 58 | 59 | 60 | 61 | 62 | 63 | 上传 64 | 65 | 66 | 70 | 81 | -------------------------------------------------------------------------------- /miniprogram/pages/share/share.js: -------------------------------------------------------------------------------- 1 | const app = getApp(); 2 | const db = wx.cloud.database(); 3 | const store = db.collection("store"); 4 | const userInfo = db.collection("userInfo"); 5 | const artList = require("./art") 6 | 7 | Page({ 8 | data: { 9 | nickName: "", 10 | avatarUrl: "", 11 | isCanDraw: false, 12 | userId: 26, 13 | drawData: {}, 14 | }, 15 | onLoad() { 16 | this.setData({ 17 | nickName: wx.getStorageSync("nickName") || "", 18 | avatarUrl: wx.getStorageSync("avatarUrl") || "", 19 | }); 20 | 21 | this.handleDrawData(); 22 | }, 23 | 24 | handleDrawData: async function () { 25 | //查询分享次数 26 | const openId = wx.getStorageSync("openId"); 27 | const shareTimeRes = await store 28 | .where({ 29 | _openid: openId, 30 | }) 31 | .count(); 32 | console.log("查询分享次数", shareTimeRes); 33 | const shareTime = shareTimeRes.total; 34 | 35 | // 用户首次分享和非首次分享文案不同 36 | if (shareTime === 0 || shareTime === 1) { 37 | // 查询用户编号 38 | const userIdRes = await userInfo.where({ 39 | _openid: openId 40 | }).get(); 41 | console.log("该用户的编号", userIdRes.data); 42 | const userId = userIdRes.data[0].id; 43 | this.setData({ 44 | drawData: { 45 | context1: "已成为第", 46 | numberId: userId, 47 | context2: "位上传盲道问题的筑路", 48 | context3: "者,并获得了视障人士分享的视界", 49 | artImage: artList[userId % 30].artUrl, 50 | artTitle: artList[userId % 30].artName, 51 | artContext1: `摄影师:${artList[userId % 30].photographer} |`, 52 | artContext2: artList[userId % 30].other, 53 | }, 54 | }); 55 | } else { 56 | this.setData({ 57 | drawData: { 58 | context1: "已经第", 59 | numberId: shareTime, 60 | context2: "次上传盲道问题,并", 61 | context3: "获得了视障人士分享的视界", 62 | artImage: artList[shareTime % 30].artUrl, 63 | artTitle: artList[shareTime % 30].artName, 64 | artContext1: `摄影师:${artList[shareTime % 30].photographer} |`, 65 | artContext2: artList[shareTime % 30].other, 66 | }, 67 | }); 68 | } 69 | }, 70 | createShareImage: function () { 71 | this.setData({ 72 | isCanDraw: !this.data.isCanDraw, 73 | }); 74 | }, 75 | getUserInfo(e) { 76 | const nickName = wx.getStorageSync('nickName') 77 | const avatarUrl = wx.getStorageSync('avatarUrl') 78 | if (!nickName && !avatarUrl) { 79 | // 推荐使用wx.getUserProfile获取用户信息,开发者每次通过该接口获取用户个人信息均需用户确认 80 | // 开发者妥善保管用户快速填写的头像昵称,避免重复弹窗 81 | wx.getUserProfile({ 82 | desc: '用于生成海报', // 声明获取用户个人信息后的用途,后续会展示在弹窗中,请谨慎填写 83 | success: (res) => { 84 | this.setData({ 85 | nickName: res.userInfo.nickName, 86 | avatarUrl: res.userInfo.avatarUrl, 87 | }) 88 | wx.setStorageSync("avatarUrl", res.userInfo.avatarUrl); 89 | wx.setStorageSync("nickName", res.userInfo.nickName); 90 | this.createShareImage() 91 | } 92 | }) 93 | } else { 94 | this.createShareImage() 95 | } 96 | }, 97 | /** 98 | * 用户点击右上角分享 99 | */ 100 | onShareAppMessage: function () { 101 | return { 102 | title: "我在友好盲道地图上标记了一处盲道问题,你也快来加入我们吧", 103 | path: "/pages/map/map", 104 | imageUrl: "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/share/share-pre.jpg?sign=d0236b01a9f4f1255d06109ef4a3fa91&t=1618313697", 105 | }; 106 | }, 107 | /** 108 | * 用户分享到朋友圈 109 | */ 110 | onShareTimeline: function () { 111 | return { 112 | title: "我在友好盲道地图上标记了一处盲道问题,你也快来加入我们吧", 113 | path: "/pages/map/map", 114 | imageUrl: "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/share/share-pre.jpg?sign=d0236b01a9f4f1255d06109ef4a3fa91&t=1618313697", 115 | }; 116 | }, 117 | /** 118 | * 修改返回上一页的路径 119 | */ 120 | onUnload: function () { 121 | wx.reLaunch({ 122 | url: '../map/map' 123 | }) 124 | }, 125 | }); -------------------------------------------------------------------------------- /miniprogram/pages/map/map.wxss: -------------------------------------------------------------------------------- 1 | map { 2 | width: 750rpx; 3 | } 4 | 5 | /* 顶部添加我的小程序提示 */ 6 | .add_me { 7 | font-size: 30rpx; 8 | color: #fff; 9 | background-color: rgb(62, 207, 142); 10 | width: 450rpx; 11 | padding: 20rpx; 12 | position: absolute; 13 | right: 10rpx; 14 | top: 20rpx; 15 | text-align: center; 16 | } 17 | 18 | .nav { 19 | position: absolute; 20 | width: 300px; 21 | left: calc(50% - 150px); 22 | bottom: 40rpx; 23 | background-color: rgb(255, 255, 255); 24 | border-radius: 50rpx; 25 | box-shadow: 3px 3px 2px #bbbcbc; 26 | } 27 | 28 | /* 底部导航栏 */ 29 | .nav-tabbar { 30 | display: flex; 31 | flex-direction: row; 32 | justify-content: center; 33 | align-items: center; 34 | } 35 | 36 | .nav-tabbar-image { 37 | width: 20px; 38 | height: 20px; 39 | margin-bottom: 3px; 40 | } 41 | 42 | .postion-add { 43 | position: absolute; 44 | left: calc(50% - 35px); 45 | bottom: 15px; 46 | width: 70px; 47 | height: 70px; 48 | font-size: 25rpx; 49 | color: #fff; 50 | background-color: #0079e7; 51 | border-radius: 50%; 52 | overflow: hidden; 53 | display: flex; 54 | flex-direction: column; 55 | justify-content: center; 56 | align-items: center; 57 | } 58 | 59 | 60 | /* .nav-tabbar-left{ 61 | position: absolute; 62 | left: calc(50% - 140px); 63 | bottom: 0px; 64 | } 65 | 66 | .nav-tabbar-center{ 67 | position: absolute; 68 | left: calc(50% - 30px); 69 | bottom: 0px; 70 | } 71 | 72 | .nav-tabbar-right{ 73 | position: absolute; 74 | left: calc(50% + 30px); 75 | bottom: 0px; 76 | } */ 77 | 78 | .nav-button { 79 | height: 100rpx; 80 | width: 220rpx; 81 | font-size: 25rpx; 82 | text-align: center; 83 | /* color: #7795f8; */ 84 | background-color: rgb(255, 255, 255); 85 | /* line-height: 100rpx; */ 86 | display: flex; 87 | flex-direction: column; 88 | justify-content: center; 89 | align-items: center; 90 | } 91 | 92 | .add { 93 | width: 60px; 94 | height: 60px; 95 | font-size: 25rpx; 96 | color: #fff; 97 | background-color: #fff; 98 | border-radius: 50%; 99 | overflow: hidden; 100 | display: flex; 101 | flex-direction: column; 102 | justify-content: center; 103 | align-items: center; 104 | } 105 | 106 | 107 | /* 中心标记点 */ 108 | .center { 109 | width: 32px; 110 | height: 40px; 111 | margin: 0 auto; 112 | position: relative; 113 | top: calc(50% - 40px); 114 | } 115 | 116 | /* 问题列表 */ 117 | .numberList { 118 | position: absolute; 119 | right: 30rpx; 120 | top: 20rpx; 121 | text-align: center; 122 | background-color: #fff; 123 | box-shadow: 3px 3px 2px #bbbcbc; 124 | } 125 | 126 | .numberList-item { 127 | display: flex; 128 | flex-direction: row; 129 | background-color: #fff; 130 | justify-content: center; 131 | align-items: center; 132 | padding: 10px 5px 10px 4px; 133 | } 134 | 135 | .numberList-item-line { 136 | height: 1px; 137 | width: 80%; 138 | margin: 0px auto; 139 | background-color: #ededed; 140 | } 141 | 142 | .numberList-item-image { 143 | width: 30px; 144 | margin-right: 5px; 145 | } 146 | 147 | .numberList-item-text { 148 | font-size: 14px; 149 | } 150 | 151 | .getLocation { 152 | position: absolute; 153 | left: 12px; 154 | bottom: 120px; 155 | text-align: center; 156 | width: 20px; 157 | background-color: #fff; 158 | padding: 10px; 159 | box-shadow: 1px 2px 3px #999999; 160 | } 161 | 162 | /* 自定义气泡 */ 163 | 164 | .customCallout { 165 | background-color: rgba(277, 277, 277, 0.9); 166 | padding: 10rpx; 167 | border-radius: 10rpx; 168 | z-index: 10000; 169 | display: flex; 170 | flex-direction: column; 171 | justify-content: center; 172 | align-items: center; 173 | } 174 | 175 | .customCallout-image { 176 | width: 180rpx; 177 | height: 180rpx; 178 | } 179 | 180 | .customCallout-text { 181 | width: 180rpx; 182 | font-size: 20rpx; 183 | margin-top: 6rpx; 184 | text-overflow: ellipsis; 185 | white-space: nowrap; 186 | overflow: hidden; 187 | } 188 | 189 | .trangle { 190 | position: absolute; 191 | bottom: -14px; 192 | width: 0; 193 | height: 0; 194 | border-left: 15px solid transparent; 195 | border-right: 15px solid transparent; 196 | border-top: 15px solid rgba(277, 277, 277, 0.9); 197 | } -------------------------------------------------------------------------------- /miniprogram/pages/info/info.js: -------------------------------------------------------------------------------- 1 | const app = getApp(); 2 | const db = wx.cloud.database(); 3 | const store = db.collection("store"); 4 | const config = require("../../config.js"); 5 | Page({ 6 | /** 7 | * 页面的初始数据 8 | */ 9 | data: {}, 10 | 11 | /** 12 | * 生命周期函数--监听页面加载 13 | */ 14 | onLoad: function (options) { 15 | wx.showLoading({ 16 | title: "加载中...", 17 | }); 18 | 19 | // 获取url页面参数 20 | console.log("id", options.id); 21 | 22 | store 23 | .doc(options.id) 24 | .get() 25 | .then((res) => { 26 | this.setData( 27 | { 28 | store: res.data, 29 | is_administrator: app.globalData.is_administrator, 30 | }, 31 | (res) => { 32 | wx.hideLoading(); 33 | } 34 | ); 35 | }); 36 | }, 37 | 38 | tapImage: function (e) { 39 | wx.previewImage({ 40 | urls: this.data.store.images, 41 | current: e.currentTarget.dataset.url, 42 | }); 43 | }, 44 | copyPath: function (e) { 45 | let path = this.route + "?id=" + this.data.store._id; 46 | wx.setClipboardData({ 47 | data: path, 48 | success: (res) => { 49 | wx.showToast({ 50 | title: "路径复制成功", 51 | icon: "success", 52 | }); 53 | }, 54 | }); 55 | }, 56 | /** 57 | * 用户点击右上角分享 58 | */ 59 | // onShareAppMessage: function () { 60 | // let path = "/pages/info/info?id=" + this.data.store._id; 61 | // let image = "/images/share.jpg"; 62 | // if (this.data.store.images[0]) { 63 | // wx.cloud.getTempFileURL({ 64 | // fileList: [this.data.store.images[0]], 65 | // success: (res) => { 66 | // return { 67 | // title: "我在" + config.appName + "上发现了好吃的,你也看看吧!", 68 | // path: path, 69 | // imageUrl: res.fileList[0].tempFileURL, 70 | // }; 71 | // }, 72 | // fail: (error) => { 73 | // console.error("出现Bug了", error); 74 | // }, 75 | // }); 76 | // } else { 77 | // return { 78 | // title: "我在" + config.appName + "上发现了好吃的,你也看看吧!", 79 | // path: path, 80 | // imageUrl: image, 81 | // }; 82 | // } 83 | // }, 84 | /** 85 | * 用户点击右上角分享 86 | */ 87 | onShareAppMessage: function () { 88 | return { 89 | title: "我在友好盲道地图上标记了一处盲道问题,你也快来加入我们吧", 90 | path: "/pages/map/map", 91 | imageUrl: 92 | "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/share/share-pre.jpg?sign=d0236b01a9f4f1255d06109ef4a3fa91&t=1618313697", 93 | }; 94 | }, 95 | /** 96 | * 用户分享到朋友圈 97 | */ 98 | onShareTimeline: function () { 99 | return { 100 | title: "我在友好盲道地图上标记了一处盲道问题,你也快来加入我们吧", 101 | path: "/pages/map/map", 102 | imageUrl: 103 | "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/share/share-pre.jpg?sign=d0236b01a9f4f1255d06109ef4a3fa91&t=1618313697", 104 | }; 105 | }, 106 | callContact: function (event) { 107 | wx.makePhoneCall({ 108 | phoneNumber: this.data.store.contact, 109 | }); 110 | }, 111 | navigate: function (e) { 112 | wx.openLocation({ 113 | latitude: this.data.store.latitude, 114 | longitude: this.data.store.longitude, 115 | name: this.data.store.title, 116 | address: this.data.store.address, 117 | }); 118 | }, 119 | deleteItem: function (e) { 120 | wx.showModal({ 121 | title: "删除确认", 122 | content: "您真的要删除" + this.data.store.title + "么?", 123 | success: (res) => { 124 | if (res.confirm) { 125 | store 126 | .doc(this.data.store._id) 127 | .remove() 128 | .then((res) => { 129 | wx.showToast({ 130 | title: "删除成功", 131 | icon: "success", 132 | success: (res) => { 133 | wx.navigateBack({ 134 | delta: 2, 135 | }); 136 | }, 137 | }); 138 | }) 139 | .catch((error) => { 140 | wx.showToast({ 141 | title: "删除失败!请添加微信 ixiqin_com 排查问题", 142 | }); 143 | }); 144 | } else if (res.cancel) { 145 | console.log("用户点击取消"); 146 | } 147 | }, 148 | }); 149 | }, 150 | }); 151 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 地图标记小程序模板 2 | 3 | [![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#contributors-) 4 | 5 | 6 | 地图标记小程序(map-markers-miniprogram)是一个借助腾讯地图map api给用户展示周边资源和允许用户自主上传添加标记的小程序模板。可以用它改造成新生校园导航、美食周边、AED地图、好房资源地图等地图小程序。希望我的代码对你有所帮助~ 7 | 8 | ## 一、现有功能: 9 | 10 | 1. **基于地图和位置的查找周边标记**:小程序首页为地图, 可以查看地图中的标记资源或问题 11 | 2. **完整的搜索支持和列表功能**:用户除了可以在地图中滑动或缩放寻找标记,还可以直接查看标记列表或搜索标记点 12 | 3. **点击地图标记自动跳转详情页,查看更多信息,还可以导航前往标记点** 13 | 4. **支持用户自主上传标记**:支持让用户选择新增标记经纬度,自动填写地址,填写描述,选择分类,添加图片之后,新增标记点 14 | 5. **支持定义多种类型的标记,不同类型标记在地图中显示不同图标,并且支持统计不同类型标记的数量** 15 | 6. **丰富的分享功能**:支持分享给微信好友,分享到朋友圈,生成标记成功激励海报分享等多种分享功能 16 | 7. **展示用户的个人上传日志** 17 | 8. **支持小程序端管理标记点**:支持自动识别管理员,管理可以直接在小程序上对标记进行删除隐藏 18 | 9. **支持第一次进入小程序的用户查看功能引导页** 19 | 10. **添加到我的小程序提醒下**:在首页加入了「添加到我的小程序」的提醒,提醒用户收藏小程序。 20 | 21 | ## 二、特性 22 | 23 | - 基于微信小程序地图实现 24 | - 使用[小程序·云开发](https://developers.weixin.qq.com/miniprogram/dev/wxcloud/basis/getting-started.html),无需编写后端代码 25 | - 全移动端交互,数据管理无需再开电脑 26 | - 独立配置项目,方便自行部署 27 | 28 | ## 三、功能截图 29 | 30 | | 首页 | 上传页 | 上传成功页 | 31 | | ---- | ----| ----| 32 | |![](http://qny.volcanoblog.cn/markdown/20210327222847.png) |![](http://qny.volcanoblog.cn/markdown/20210327222910-20210327222917352.png)| ![](http://qny.volcanoblog.cn/markdown/20210327221526.png) | 33 | | 生成海报 | 用户上传日志 | 分享到微信 | 34 | | ![](http://qny.volcanoblog.cn/markdown/20210327221709.png) |![](http://qny.volcanoblog.cn/markdown/20210327222936-20210327222945968.png)|![](http://qny.volcanoblog.cn/markdown/20210327221657.png)| 35 | 36 | ## 四、Demo 小程序 37 | 38 | > 如果你希望自己的小程序展示在这里,欢迎pr或在issue中提出 39 | 40 | | 友好盲道小程序 | 深圳美食图鉴 | **千岛湖民宿地图**| 41 | | ---- | ----| ----| 42 | | ![](http://qny.volcanoblog.cn/markdown/gh_a140ade9386a_258-20210327220337422.jpg) |![小程序码](http://qny.volcanoblog.cn/markdown/gh_ab61838fb8b2_258.jpg)| ![](https://postimg.aliavv.com/201810/pw1cy.jpg)| 43 | | **jooyi的美食地图** | | | 44 | | ![](http://qny.volcanoblog.cn/markdown/mw4c9.jpg) ||| 45 | 46 | ## 开始使用 47 | 1. 请查看 [安装教程](https://github.com/CloudKits/miniprogram-foodmap/wiki/Install) 48 | 2. 请查看 [配置文件说明](https://github.com/CloudKits/miniprogram-foodmap/wiki/Settings) 49 | 3. 请查看 [依赖服务说明](https://github.com/CloudKits/miniprogram-foodmap/wiki/Service) 50 | 4. 请使用[vantui npm方式安装](https://vant-contrib.gitee.io/vant-weapp/#/quickstart) 51 | 5. 生成海报使用:[painter](https://github.com/Kujiale-Mobile/Painter) 52 | 53 | 54 | ## 注意事项: 55 | 56 | 1. 基础库要>2.15.0才会在地图显示中心点标记 57 | 2. marker对象中的label不能是一个字符串 应该是一个对象 不然会在高版本基础库上真机不显示marker 58 | 59 | ## 致谢 60 | 61 | > 在[miniprogram-foodmap](https://github.com/CloudKits/miniprogram-foodmap)的基础上进行开发,感谢优秀的成哥。 62 | 63 | ## FAQ 64 | 65 | 常见问题请参看 [FAQ](https://github.com/CloudKits/miniprogram-foodmap/wiki/FAQ) 66 | 67 | ## 贡献说明 68 | 69 | 欢迎您为项目提交 Pull Request;如果您在使用中有任何问题或有新的特性需求,请前往[ISSUE](https://github.com/CloudKits/miniprogram-foodmap/issues)提交新的 issue。 70 | 71 | ## 寻求帮助 72 | 73 | 您可以在 [支持社区](https://www.xieit.com/forum-51-1.html) 内发帖寻求帮助 74 | 75 | ## LICENSE 76 | 77 | 本项目基于 Apache License 2.0 开放源代码授权 78 | 79 | ## Contributors ✨ 80 | 81 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)): 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 |

杨灿就是杨火山

🚇 🎨 💻

白宦成

🤔 💻
92 | 93 | 94 | 95 | 96 | 97 | 98 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome! -------------------------------------------------------------------------------- /miniprogram/pages/add/add.js: -------------------------------------------------------------------------------- 1 | const app = getApp(); 2 | const db = wx.cloud.database(); 3 | const store = db.collection("store"); 4 | const userInfo = db.collection("userInfo"); 5 | const _ = db.command; 6 | 7 | Page({ 8 | /** 9 | * 页面的初始数据 10 | */ 11 | data: { 12 | problemLabel: "", 13 | images: [], 14 | content: "", 15 | iconPath: "", 16 | titleColor: { 17 | address: "black", 18 | problemLabel: "black", 19 | images: "black", 20 | } 21 | }, 22 | 23 | /** 24 | * 生命周期函数--监听页面加载 25 | */ 26 | onLoad: function (options) {}, 27 | 28 | chooseLocation: function (event) { 29 | wx.getSetting({ 30 | success: (res) => { 31 | if (!res.authSetting["scope.userLocation"]) { 32 | wx.authorize({ 33 | scope: "scope.userLocation", 34 | success: (res) => { 35 | wx.chooseLocation({ 36 | success: (res) => { 37 | this.setData({ 38 | // address: `${res.address} ${res.name}`, 39 | address: `${res.name}`, 40 | latitude: res.latitude, 41 | longitude: res.longitude, 42 | }); 43 | }, 44 | }); 45 | }, 46 | }); 47 | } else { 48 | wx.chooseLocation({ 49 | success: (res) => { 50 | this.setData({ 51 | // address: `${res.address} ${res.name}`, 52 | address: `${res.name}`, 53 | latitude: res.latitude, 54 | longitude: res.longitude, 55 | }); 56 | }, 57 | }); 58 | } 59 | }, 60 | }); 61 | }, 62 | 63 | createUserInfo: async function () { 64 | const openId = wx.getStorageSync("openId"); 65 | const res = await userInfo.where({ 66 | _openid: openId 67 | }).count(); 68 | // 不存在就创建用户并设置用编号 69 | if (!res.total) { 70 | const res2 = await userInfo.where({ 71 | _openid: _.exists(true) 72 | }).count(); 73 | console.log("当前总用户数", res2.total); 74 | userInfo 75 | .add({ 76 | data: { 77 | id: res2.total + 1, 78 | nickName: wx.getStorageSync("nickName") || "", 79 | avatarUrl: wx.getStorageSync("avatarUrl") || "", 80 | openId, 81 | }, 82 | }) 83 | .then((res) => { 84 | console.log("创建用户成功", res); 85 | }); 86 | } 87 | }, 88 | 89 | createItem: function (event) { 90 | this.setData({ 91 | titleColor: { 92 | address: (!this.data.address || !this.data.longitude) ? "red" : "black", 93 | problemLabel: !this.data.problemLabel ? "red" : "black", 94 | images: !this.data.images.length ? "red" : "black", 95 | } 96 | }) 97 | if (!this.data.address || !this.data.longitude || !this.data.problemLabel || !this.data.images.length) { 98 | wx.showToast({ 99 | title: "缺少必填项", 100 | icon: "error", 101 | }); 102 | } else { 103 | wx.showLoading({ 104 | title: "上传数据中...", 105 | }); 106 | this.createUserInfo(); 107 | store 108 | .add({ 109 | data: { 110 | createTime: new Date(), 111 | address: this.data.address, 112 | longitude: this.data.longitude, 113 | latitude: this.data.latitude, 114 | problemLabel: this.data.problemLabel, 115 | iconPath: this.data.iconPath, 116 | images: this.data.images, 117 | content: event.detail.value.content, 118 | userName: wx.getStorageSync('nickName') 119 | }, 120 | }) 121 | .then((res) => { 122 | wx.hideLoading(); 123 | wx.showToast({ 124 | title: "创建成功!", 125 | icon: "success", 126 | success: (res) => { 127 | wx.navigateTo({ 128 | url: "../share/share", 129 | }); 130 | }, 131 | }); 132 | }) 133 | .catch((error) => { 134 | console.error(error); 135 | }); 136 | } 137 | }, 138 | 139 | uploadImage: function (e) { 140 | wx.chooseImage({ 141 | count: 2, 142 | sizeType: ['original', 'compressed'], 143 | sourceType: ['album', 'camera'], 144 | success: res => { 145 | 146 | wx.showLoading({ 147 | title: '上传中' 148 | }) 149 | let tempFilePaths = res.tempFilePaths 150 | let items = []; 151 | for (const tempFilePath of tempFilePaths) { 152 | items.push({ 153 | src: tempFilePath 154 | }) 155 | } 156 | const uploadTask = items.map(item => this.uploadPhoto(item.src)) 157 | 158 | Promise.all(uploadTask).then(result => { 159 | 160 | let urls = this.data.images; 161 | for (const file of result) { 162 | urls.push(file.fileID); 163 | } 164 | this.setData({ 165 | images: urls 166 | }, res => { 167 | wx.hideLoading(); 168 | wx.showToast({ 169 | title: '上传图片成功', 170 | icon: 'success' 171 | }) 172 | }) 173 | }).catch(() => { 174 | wx.hideLoading() 175 | wx.showToast({ 176 | title: '上传图片错误', 177 | icon: 'error' 178 | }) 179 | }) 180 | 181 | this.setData({ 182 | tempPhoto: items 183 | }) 184 | } 185 | }) 186 | }, 187 | uploadPhoto(filePath) { 188 | return wx.cloud.uploadFile({ 189 | cloudPath: `${Date.now()}-${Math.floor(Math.random(0, 1) * 10000000)}.png`, 190 | filePath 191 | }) 192 | }, 193 | ViewImage(e) { 194 | wx.previewImage({ 195 | urls: this.data.images, 196 | current: e.currentTarget.dataset.url 197 | }); 198 | }, 199 | DelImg(e) { 200 | wx.showModal({ 201 | title: '确定要删除这个图片吗?', 202 | cancelText: '保留', 203 | confirmText: '删除', 204 | success: res => { 205 | if (res.confirm) { 206 | this.data.images.splice(e.currentTarget.dataset.index, 1); 207 | this.setData({ 208 | images: this.data.images 209 | }) 210 | } 211 | } 212 | }) 213 | }, 214 | 215 | onChangeRadio(event) { 216 | const problemLabel = event.detail; 217 | let iconPath = ""; 218 | switch (problemLabel) { 219 | case "盲道占用": 220 | iconPath = "../../images/marker/occupy.png"; 221 | break; 222 | 223 | case "盲道设计": 224 | iconPath = "../../images/marker/design.png"; 225 | break; 226 | 227 | default: 228 | iconPath = "../../images/marker/error.png"; 229 | break; 230 | } 231 | this.setData({ 232 | problemLabel, 233 | iconPath, 234 | }); 235 | }, 236 | goToArticle: function () { 237 | wx.navigateTo({ 238 | url: '../article/article', 239 | }) 240 | } 241 | }); -------------------------------------------------------------------------------- /miniprogram/painter/lib/downloader.js: -------------------------------------------------------------------------------- 1 | /** 2 | * LRU 文件存储,使用该 downloader 可以让下载的文件存储在本地,下次进入小程序后可以直接使用 3 | * 详细设计文档可查看 https://juejin.im/post/5b42d3ede51d4519277b6ce3 4 | */ 5 | const util = require('./util'); 6 | 7 | const SAVED_FILES_KEY = 'savedFiles'; 8 | const KEY_TOTAL_SIZE = 'totalSize'; 9 | const KEY_PATH = 'path'; 10 | const KEY_TIME = 'time'; 11 | const KEY_SIZE = 'size'; 12 | 13 | // 可存储总共为 6M,目前小程序可允许的最大本地存储为 10M 14 | let MAX_SPACE_IN_B = 6 * 1024 * 1024; 15 | let savedFiles = {}; 16 | 17 | export default class Dowloader { 18 | constructor() { 19 | // app 如果设置了最大存储空间,则使用 app 中的 20 | if (getApp().PAINTER_MAX_LRU_SPACE) { 21 | MAX_SPACE_IN_B = getApp().PAINTER_MAX_LRU_SPACE; 22 | } 23 | wx.getStorage({ 24 | key: SAVED_FILES_KEY, 25 | success: function (res) { 26 | if (res.data) { 27 | savedFiles = res.data; 28 | } 29 | }, 30 | }); 31 | } 32 | 33 | /** 34 | * 下载文件,会用 lru 方式来缓存文件到本地 35 | * @param {String} url 文件的 url 36 | */ 37 | download(url) { 38 | return new Promise((resolve, reject) => { 39 | if (!(url && util.isValidUrl(url))) { 40 | resolve(url); 41 | return; 42 | } 43 | const file = getFile(url); 44 | 45 | if (file) { 46 | // 检查文件是否正常,不正常需要重新下载 47 | wx.getSavedFileInfo({ 48 | filePath: file[KEY_PATH], 49 | success: (res) => { 50 | resolve(file[KEY_PATH]); 51 | }, 52 | fail: (error) => { 53 | console.error(`the file is broken, redownload it, ${JSON.stringify(error)}`); 54 | downloadFile(url).then((path) => { 55 | resolve(path); 56 | }, () => { 57 | reject(); 58 | }); 59 | }, 60 | }); 61 | } else { 62 | downloadFile(url).then((path) => { 63 | resolve(path); 64 | }, () => { 65 | reject(); 66 | }); 67 | } 68 | }); 69 | } 70 | } 71 | 72 | function downloadFile(url) { 73 | return new Promise((resolve, reject) => { 74 | wx.downloadFile({ 75 | url: url, 76 | success: function (res) { 77 | if (res.statusCode !== 200) { 78 | console.error(`downloadFile ${url} failed res.statusCode is not 200`); 79 | reject(); 80 | return; 81 | } 82 | const { tempFilePath } = res; 83 | wx.getFileInfo({ 84 | filePath: tempFilePath, 85 | success: (tmpRes) => { 86 | const newFileSize = tmpRes.size; 87 | doLru(newFileSize).then(() => { 88 | saveFile(url, newFileSize, tempFilePath).then((filePath) => { 89 | resolve(filePath); 90 | }); 91 | }, () => { 92 | resolve(tempFilePath); 93 | }); 94 | }, 95 | fail: (error) => { 96 | // 文件大小信息获取失败,则此文件也不要进行存储 97 | console.error(`getFileInfo ${res.tempFilePath} failed, ${JSON.stringify(error)}`); 98 | resolve(res.tempFilePath); 99 | }, 100 | }); 101 | }, 102 | fail: function (error) { 103 | console.error(`downloadFile failed, ${JSON.stringify(error)} `); 104 | reject(); 105 | }, 106 | }); 107 | }); 108 | } 109 | 110 | function saveFile(key, newFileSize, tempFilePath) { 111 | return new Promise((resolve, reject) => { 112 | wx.saveFile({ 113 | tempFilePath: tempFilePath, 114 | success: (fileRes) => { 115 | const totalSize = savedFiles[KEY_TOTAL_SIZE] ? savedFiles[KEY_TOTAL_SIZE] : 0; 116 | savedFiles[key] = {}; 117 | savedFiles[key][KEY_PATH] = fileRes.savedFilePath; 118 | savedFiles[key][KEY_TIME] = new Date().getTime(); 119 | savedFiles[key][KEY_SIZE] = newFileSize; 120 | savedFiles['totalSize'] = newFileSize + totalSize; 121 | wx.setStorage({ 122 | key: SAVED_FILES_KEY, 123 | data: savedFiles, 124 | }); 125 | resolve(fileRes.savedFilePath); 126 | }, 127 | fail: (error) => { 128 | console.error(`saveFile ${key} failed, then we delete all files, ${JSON.stringify(error)}`); 129 | // 由于 saveFile 成功后,res.tempFilePath 处的文件会被移除,所以在存储未成功时,我们还是继续使用临时文件 130 | resolve(tempFilePath); 131 | // 如果出现错误,就直接情况本地的所有文件,因为你不知道是不是因为哪次lru的某个文件未删除成功 132 | reset(); 133 | }, 134 | }); 135 | }); 136 | } 137 | 138 | /** 139 | * 清空所有下载相关内容 140 | */ 141 | function reset() { 142 | wx.removeStorage({ 143 | key: SAVED_FILES_KEY, 144 | success: () => { 145 | wx.getSavedFileList({ 146 | success: (listRes) => { 147 | removeFiles(listRes.fileList); 148 | }, 149 | fail: (getError) => { 150 | console.error(`getSavedFileList failed, ${JSON.stringify(getError)}`); 151 | }, 152 | }); 153 | }, 154 | }); 155 | } 156 | 157 | function doLru(size) { 158 | return new Promise((resolve, reject) => { 159 | let totalSize = savedFiles[KEY_TOTAL_SIZE] ? savedFiles[KEY_TOTAL_SIZE] : 0; 160 | 161 | if (size + totalSize <= MAX_SPACE_IN_B) { 162 | resolve(); 163 | return; 164 | } 165 | // 如果加上新文件后大小超过最大限制,则进行 lru 166 | const pathsShouldDelete = []; 167 | // 按照最后一次的访问时间,从小到大排序 168 | const allFiles = JSON.parse(JSON.stringify(savedFiles)); 169 | delete allFiles[KEY_TOTAL_SIZE]; 170 | const sortedKeys = Object.keys(allFiles).sort((a, b) => { 171 | return allFiles[a][KEY_TIME] - allFiles[b][KEY_TIME]; 172 | }); 173 | 174 | for (const sortedKey of sortedKeys) { 175 | totalSize -= savedFiles[sortedKey].size; 176 | pathsShouldDelete.push(savedFiles[sortedKey][KEY_PATH]); 177 | delete savedFiles[sortedKey]; 178 | if (totalSize + size < MAX_SPACE_IN_B) { 179 | break; 180 | } 181 | } 182 | 183 | savedFiles['totalSize'] = totalSize; 184 | 185 | wx.setStorage({ 186 | key: SAVED_FILES_KEY, 187 | data: savedFiles, 188 | success: () => { 189 | // 保证 storage 中不会存在不存在的文件数据 190 | if (pathsShouldDelete.length > 0) { 191 | removeFiles(pathsShouldDelete); 192 | } 193 | resolve(); 194 | }, 195 | fail: (error) => { 196 | console.error(`doLru setStorage failed, ${JSON.stringify(error)}`); 197 | reject(); 198 | }, 199 | }); 200 | }); 201 | } 202 | 203 | function removeFiles(pathsShouldDelete) { 204 | for (const pathDel of pathsShouldDelete) { 205 | let delPath = pathDel; 206 | if (typeof pathDel === 'object') { 207 | delPath = pathDel.filePath; 208 | } 209 | wx.removeSavedFile({ 210 | filePath: delPath, 211 | fail: (error) => { 212 | console.error(`removeSavedFile ${pathDel} failed, ${JSON.stringify(error)}`); 213 | }, 214 | }); 215 | } 216 | } 217 | 218 | function getFile(key) { 219 | if (!savedFiles[key]) { 220 | return; 221 | } 222 | savedFiles[key]['time'] = new Date().getTime(); 223 | wx.setStorage({ 224 | key: SAVED_FILES_KEY, 225 | data: savedFiles, 226 | }); 227 | return savedFiles[key]; 228 | } 229 | -------------------------------------------------------------------------------- /miniprogram/painter/painter.js: -------------------------------------------------------------------------------- 1 | import Pen from './lib/pen'; 2 | import Downloader from './lib/downloader'; 3 | 4 | const util = require('./lib/util'); 5 | 6 | const downloader = new Downloader(); 7 | 8 | // 最大尝试的绘制次数 9 | const MAX_PAINT_COUNT = 5; 10 | Component({ 11 | canvasWidthInPx: 0, 12 | canvasHeightInPx: 0, 13 | paintCount: 0, 14 | /** 15 | * 组件的属性列表 16 | */ 17 | properties: { 18 | customStyle: { 19 | type: String, 20 | }, 21 | palette: { 22 | type: Object, 23 | observer: function (newVal, oldVal) { 24 | if (this.isNeedRefresh(newVal, oldVal)) { 25 | this.paintCount = 0; 26 | this.startPaint(); 27 | } 28 | }, 29 | }, 30 | // 启用脏检查,默认 false 31 | dirty: { 32 | type: Boolean, 33 | value: false, 34 | }, 35 | }, 36 | 37 | data: { 38 | picURL: '', 39 | showCanvas: true, 40 | painterStyle: '', 41 | }, 42 | 43 | attached() { 44 | setStringPrototype(); 45 | }, 46 | 47 | methods: { 48 | /** 49 | * 判断一个 object 是否为 空 50 | * @param {object} object 51 | */ 52 | isEmpty(object) { 53 | for (const i in object) { 54 | return false; 55 | } 56 | return true; 57 | }, 58 | 59 | isNeedRefresh(newVal, oldVal) { 60 | if (!newVal || this.isEmpty(newVal) || (this.data.dirty && util.equal(newVal, oldVal))) { 61 | return false; 62 | } 63 | return true; 64 | }, 65 | 66 | startPaint() { 67 | if (this.isEmpty(this.properties.palette)) { 68 | return; 69 | } 70 | 71 | if (!(getApp().systemInfo && getApp().systemInfo.screenWidth)) { 72 | try { 73 | getApp().systemInfo = wx.getSystemInfoSync(); 74 | } catch (e) { 75 | const error = `Painter get system info failed, ${JSON.stringify(e)}`; 76 | that.triggerEvent('imgErr', { error: error }); 77 | console.log(error); 78 | return; 79 | } 80 | } 81 | screenK = getApp().systemInfo.screenWidth / 750; 82 | 83 | this.downloadImages().then((palette) => { 84 | const { width, height } = palette; 85 | this.canvasWidthInPx = width.toPx(); 86 | this.canvasHeightInPx = height.toPx(); 87 | if (!width || !height) { 88 | console.log(`You should set width and height correctly for painter, width: ${width}, height: ${height}`); 89 | return; 90 | } 91 | this.setData({ 92 | painterStyle: `width:${width};height:${height};`, 93 | }); 94 | const ctx = wx.createCanvasContext('k-canvas', this); 95 | const pen = new Pen(ctx, palette); 96 | pen.paint(() => { 97 | this.saveImgToLocal(); 98 | }); 99 | }); 100 | }, 101 | 102 | downloadImages() { 103 | return new Promise((resolve, reject) => { 104 | let preCount = 0; 105 | let completeCount = 0; 106 | const paletteCopy = JSON.parse(JSON.stringify(this.properties.palette)); 107 | if (paletteCopy.background) { 108 | preCount++; 109 | downloader.download(paletteCopy.background).then((path) => { 110 | paletteCopy.background = path; 111 | completeCount++; 112 | if (preCount === completeCount) { 113 | resolve(paletteCopy); 114 | } 115 | }, () => { 116 | completeCount++; 117 | if (preCount === completeCount) { 118 | resolve(paletteCopy); 119 | } 120 | }); 121 | } 122 | if (paletteCopy.views) { 123 | let that = this 124 | for (const view of paletteCopy.views) { 125 | if (view && view.type === 'image' && view.url) { 126 | preCount++; 127 | /* eslint-disable no-loop-func */ 128 | downloader.download(view.url).then((path) => { 129 | view.url = path; 130 | wx.getImageInfo({ 131 | src: view.url, 132 | success: (res) => { 133 | // 获得一下图片信息,供后续裁减使用 134 | view.sWidth = res.width; 135 | view.sHeight = res.height; 136 | }, 137 | fail: (error) => { 138 | console.log(`imgDownloadErr failed, ${JSON.stringify(error)}`); 139 | that.triggerEvent('imgDownloadErr', { error: error }); 140 | }, 141 | complete: () => { 142 | completeCount++; 143 | if (preCount === completeCount) { 144 | resolve(paletteCopy); 145 | } 146 | }, 147 | }); 148 | }, () => { 149 | completeCount++; 150 | if (preCount === completeCount) { 151 | resolve(paletteCopy); 152 | } 153 | }); 154 | } 155 | } 156 | } 157 | if (preCount === 0) { 158 | resolve(paletteCopy); 159 | } 160 | }); 161 | }, 162 | 163 | saveImgToLocal() { 164 | const that = this; 165 | setTimeout(() => { 166 | wx.canvasToTempFilePath({ 167 | canvasId: 'k-canvas', 168 | success: function (res) { 169 | that.getImageInfo(res.tempFilePath); 170 | }, 171 | fail: function (error) { 172 | console.log(`canvasToTempFilePath failed, ${JSON.stringify(error)}`); 173 | that.triggerEvent('imgErr', { error: error }); 174 | }, 175 | }, this); 176 | }, 300); 177 | }, 178 | 179 | getImageInfo(filePath) { 180 | const that = this; 181 | wx.getImageInfo({ 182 | src: filePath, 183 | success: (infoRes) => { 184 | if (that.paintCount > MAX_PAINT_COUNT) { 185 | const error = `The result is always fault, even we tried ${MAX_PAINT_COUNT} times`; 186 | console.log(error); 187 | that.triggerEvent('imgErr', { error: error }); 188 | return; 189 | } 190 | // 比例相符时才证明绘制成功,否则进行强制重绘制 191 | if (Math.abs((infoRes.width * that.canvasHeightInPx - that.canvasWidthInPx * infoRes.height) / (infoRes.height * that.canvasHeightInPx)) < 0.01) { 192 | that.triggerEvent('imgOK', { path: filePath }); 193 | } else { 194 | that.startPaint(); 195 | } 196 | that.paintCount++; 197 | }, 198 | fail: (error) => { 199 | console.log(`getImageInfo failed, ${JSON.stringify(error)}`); 200 | that.triggerEvent('imgErr', { error: error }); 201 | }, 202 | }); 203 | }, 204 | }, 205 | }); 206 | 207 | let screenK = 0.5; 208 | 209 | function setStringPrototype() { 210 | /* eslint-disable no-extend-native */ 211 | /** 212 | * 是否支持负数 213 | * @param {Boolean} minus 是否支持负数 214 | */ 215 | String.prototype.toPx = function toPx(minus) { 216 | let reg; 217 | if (minus) { 218 | reg = /^-?[0-9]+([.]{1}[0-9]+){0,1}(rpx|px)$/g; 219 | } else { 220 | reg = /^[0-9]+([.]{1}[0-9]+){0,1}(rpx|px)$/g; 221 | } 222 | const results = reg.exec(this); 223 | if (!this || !results) { 224 | console.log(`The size: ${this} is illegal`); 225 | return 0; 226 | } 227 | const unit = results[2]; 228 | const value = parseFloat(this); 229 | 230 | let res = 0; 231 | if (unit === 'rpx') { 232 | res = Math.round(value * screenK); 233 | } else if (unit === 'px') { 234 | res = value; 235 | } 236 | return res; 237 | }; 238 | } 239 | -------------------------------------------------------------------------------- /miniprogram/pages/map/map.js: -------------------------------------------------------------------------------- 1 | const app = getApp(); 2 | const config = require("../../config.js"); 3 | const db = wx.cloud.database(); 4 | const store = db.collection("store"); 5 | 6 | Page({ 7 | /** 8 | * 页面的初始数据 9 | */ 10 | data: { 11 | stores: {}, 12 | occurpyProblemNumber: 2, 13 | errorProblemNumber: 1, 14 | designProblemNumber: 3, 15 | longitude: config.center_longitude, 16 | latitude: config.center_latitude, 17 | windowHeight: 600, 18 | mapSubKey: config.mapSubKey, 19 | hideMe: true, 20 | showAdmin: false, 21 | windowHeight: app.globalData.windowHeight, 22 | defaultScale: config.default_scale, 23 | }, 24 | 25 | /** 26 | * 生命周期函数--监听页面加载 27 | */ 28 | onLoad: function (options) { 29 | this.goToIntro() 30 | let showAdmin = config.show_admin ? true : false; 31 | if (app.globalData.showAdmin) { 32 | showAdmin = true; 33 | } 34 | this.setData({ 35 | showAdmin: showAdmin, 36 | }); 37 | this.getOpenID(); 38 | this.getCenterLocation(); 39 | this.getMarkerData(); 40 | this.data.mapCtx = wx.createMapContext("map"); 41 | wx.showToast({ 42 | title: "双指缩放可以调整地图可视区域", 43 | icon: "none", 44 | }); 45 | }, 46 | 47 | /** 48 | * 生命周期函数--监听页面的显示 49 | */ 50 | 51 | onShow: function () { 52 | this.getMarkerData(); 53 | }, 54 | 55 | goToIntro: function () { 56 | try { 57 | const res = wx.getStorageSync('isNotShowIntroduction'); 58 | console.log("isNotShowIntroduction缓存内容", res); 59 | if (res) { 60 | console.log("不需要展示引导"); 61 | } else { 62 | console.log("需要展示引导"); 63 | wx.navigateTo({ 64 | url: "../index/index", 65 | }); 66 | } 67 | } catch (e) { 68 | console.log("缓存不支持", e); 69 | } 70 | }, 71 | 72 | getMarkerData: function () { 73 | wx.showLoading({ 74 | title: "数据加载中...", 75 | }); 76 | 77 | wx.cloud 78 | .callFunction({ 79 | name: "getStore", 80 | }) 81 | .then((res) => { 82 | console.log("云函数获取store", res.result) 83 | if (res.result.errMsg = "collection.get:ok") { 84 | let data = res.result.data; 85 | /** 86 | * 处理 occurpyProblemNumber,errorProblemNumber,designProblemNumber 87 | */ 88 | let occurpyProblemNumber = 0; 89 | let errorProblemNumber = 0; 90 | let designProblemNumber = 0; 91 | res.result.data.forEach((item) => { 92 | if (item.problemLabel === "盲道破损") { 93 | errorProblemNumber++; 94 | } else if (item.problemLabel === "盲道占用") { 95 | occurpyProblemNumber++; 96 | } else if (item.problemLabel === "盲道设计") { 97 | designProblemNumber++; 98 | } 99 | }); 100 | 101 | this.setData({ 102 | occurpyProblemNumber, 103 | errorProblemNumber, 104 | designProblemNumber, 105 | }); 106 | 107 | /*** 108 | * 处理marker 109 | * 将 _id 给 id ,确保 marker 事件的正确触发 110 | */ 111 | data.map((item, index) => { 112 | item.id = index; 113 | item.width = 20; 114 | item.height = 25; 115 | item.title = item.problemLabel; 116 | // item.customCallout = { 117 | // anchorX: 0, 118 | // anchorY: -20, 119 | // display: "BYCLICK" 120 | // } 121 | }); 122 | this.setData({ 123 | stores: data, 124 | }, 125 | () => { 126 | wx.hideLoading(); 127 | } 128 | ); 129 | } else { 130 | () => { 131 | wx.showToast({ 132 | title: '获取数据失败', 133 | }) 134 | wx.hideLoading(); 135 | } 136 | } 137 | }); 138 | }, 139 | 140 | getUserInfo: function (e) { 141 | if (e.detail.userInfo) { 142 | wx.cloud 143 | .callFunction({ 144 | name: "checkUserAuth", 145 | }) 146 | .then((res) => { 147 | if (res.result.data.is_administrator) { 148 | app.globalData.is_administrator = true; 149 | wx.showModal({ 150 | title: "管理员登陆成功", 151 | content: "管理员您好,是否要进入新增界面?", 152 | success: (res) => { 153 | if (res.cancel == false && res.confirm == true) { 154 | wx.navigateTo({ 155 | url: "../add/add", 156 | }); 157 | } else { 158 | wx.showToast({ 159 | title: "您可以点击下方查看全部按钮管理已有数据", 160 | icon: "none", 161 | }); 162 | } 163 | }, 164 | }); 165 | } else { 166 | wx.showToast({ 167 | title: "您不是管理员,无法进入管理入口!", 168 | icon: "none", 169 | }); 170 | } 171 | }); 172 | } else { 173 | // 处理未授权的场景 174 | wx.showModal({ 175 | title: "授权失败", 176 | content: "您尚未授权获取您的用户信息,是否开启授权界面?", 177 | success: (res) => { 178 | if (res.confirm) { 179 | wx.openSetting({}); 180 | } 181 | }, 182 | }); 183 | } 184 | }, 185 | 186 | /** 187 | * 获取用户经纬度 188 | */ 189 | getCenterLocation: function () { 190 | wx.getLocation({ 191 | type: "gcj02", 192 | success: (res) => { 193 | this.setData({ 194 | longitude: res.longitude, 195 | latitude: res.latitude, 196 | }); 197 | console.log( 198 | "当前中心点的位置:", 199 | this.data.longitude, 200 | this.data.latitude 201 | ); 202 | }, 203 | fail: (err) => { 204 | wx.showToast({ 205 | title: "请确认你的手机GPS定位已经打开", 206 | icon: "fail", 207 | }); 208 | console.log("err", err); 209 | }, 210 | }); 211 | }, 212 | getOpenID: function (event) { 213 | wx.cloud 214 | .callFunction({ 215 | name: "getUserOpenId", 216 | }) 217 | .then((res) => { 218 | try { 219 | wx.setStorageSync("openId", res.result.openid); 220 | } catch (e) { 221 | console.log("openID储存成功", res); 222 | } 223 | }); 224 | }, 225 | hideMe: function (res) { 226 | this.setData({ 227 | hideMe: true, 228 | }); 229 | }, 230 | showAdmin: function (res) { 231 | wx.setStorage({ 232 | key: "showAdmin", 233 | data: !this.data.showAdmin, 234 | }); 235 | this.setData({ 236 | showAdmin: !this.data.showAdmin, 237 | }); 238 | }, 239 | search: function () { 240 | wx.navigateTo({ 241 | url: "../search/search", 242 | }); 243 | }, 244 | /** 245 | * 一些页面跳转 246 | */ 247 | onMarkerTap: function (event) { 248 | console.log("marker点击", event); 249 | const index = event.detail.markerId; 250 | const _id = this.data.stores[index]._id; 251 | wx.navigateTo({ 252 | url: "../info/info?id=" + _id, 253 | }); 254 | }, 255 | viewAll: function () { 256 | wx.navigateTo({ 257 | url: "../list/list", 258 | }); 259 | }, 260 | 261 | viewMyList: function () { 262 | wx.navigateTo({ 263 | url: "../myList/myList", 264 | }); 265 | }, 266 | 267 | addMarker: async function () { 268 | const nickName = wx.getStorageSync('nickName') 269 | if (!nickName) { 270 | wx.getUserProfile({ 271 | desc: '用于记录上传者信息', 272 | success: (res) => { 273 | this.setData({ 274 | nickName: res.userInfo.nickName, 275 | avatarUrl: res.userInfo.avatarUrl, 276 | }) 277 | wx.setStorageSync("avatarUrl", res.userInfo.avatarUrl); 278 | wx.setStorageSync("nickName", res.userInfo.nickName); 279 | wx.navigateTo({ 280 | url: "../add/add", 281 | }); 282 | } 283 | }) 284 | } else { 285 | wx.navigateTo({ 286 | url: "../add/add", 287 | }); 288 | } 289 | }, 290 | 291 | goArticle: function () { 292 | wx.navigateTo({ 293 | url: "../article/article", 294 | }); 295 | }, 296 | 297 | /** 298 | * 用户点击右上角分享 299 | */ 300 | onShareAppMessage: function () { 301 | return { 302 | title: "我在友好盲道地图上标记了一处盲道问题,你也快来加入我们吧", 303 | path: "/pages/map/map", 304 | imageUrl: "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/share/share-pre.jpg?sign=d0236b01a9f4f1255d06109ef4a3fa91&t=1618313697", 305 | }; 306 | }, 307 | /** 308 | * 用户分享到朋友圈 309 | */ 310 | onShareTimeline: function () { 311 | return { 312 | title: "我在友好盲道地图上标记了一处盲道问题,你也快来加入我们吧", 313 | path: "/pages/map/map", 314 | imageUrl: "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/share/share-pre.jpg?sign=d0236b01a9f4f1255d06109ef4a3fa91&t=1618313697", 315 | }; 316 | }, 317 | }); -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /miniprogram/painter/lib/pen.js: -------------------------------------------------------------------------------- 1 | const QR = require('./qrcode.js'); 2 | 3 | export default class Painter { 4 | constructor(ctx, data) { 5 | this.ctx = ctx; 6 | this.data = data; 7 | } 8 | 9 | paint(callback) { 10 | this.style = { 11 | width: this.data.width.toPx(), 12 | height: this.data.height.toPx(), 13 | }; 14 | this._background(); 15 | for (const view of this.data.views) { 16 | this._drawAbsolute(view); 17 | } 18 | this.ctx.draw(false, () => { 19 | callback(); 20 | }); 21 | } 22 | 23 | _background() { 24 | this.ctx.save(); 25 | const { 26 | width, 27 | height, 28 | } = this.style; 29 | const bg = this.data.background; 30 | this.ctx.translate(width / 2, height / 2); 31 | 32 | this._doClip(this.data.borderRadius, width, height); 33 | if (!bg) { 34 | // 如果未设置背景,则默认使用白色 35 | this.ctx.fillStyle = '#fff'; 36 | this.ctx.fillRect(-(width / 2), -(height / 2), width, height); 37 | } else if (bg.startsWith('#') || bg.startsWith('rgba') || bg.toLowerCase() === 'transparent') { 38 | // 背景填充颜色 39 | this.ctx.fillStyle = bg; 40 | this.ctx.fillRect(-(width / 2), -(height / 2), width, height); 41 | } else { 42 | // 背景填充图片 43 | this.ctx.drawImage(bg, -(width / 2), -(height / 2), width, height); 44 | } 45 | this.ctx.restore(); 46 | } 47 | 48 | _drawAbsolute(view) { 49 | // 证明 css 为数组形式,需要合并 50 | if (view.css && view.css.length) { 51 | /* eslint-disable no-param-reassign */ 52 | view.css = Object.assign(...view.css); 53 | } 54 | switch (view.type) { 55 | case 'image': 56 | this._drawAbsImage(view); 57 | break; 58 | case 'text': 59 | this._fillAbsText(view); 60 | break; 61 | case 'rect': 62 | this._drawAbsRect(view); 63 | break; 64 | case 'qrcode': 65 | this._drawQRCode(view); 66 | break; 67 | default: 68 | break; 69 | } 70 | } 71 | 72 | /** 73 | * 根据 borderRadius 进行裁减 74 | */ 75 | _doClip(borderRadius, width, height) { 76 | if (borderRadius && width && height) { 77 | const r = Math.min(borderRadius.toPx(), width / 2, height / 2); 78 | // 防止在某些机型上周边有黑框现象,此处如果直接设置 setFillStyle 为透明,在 Android 机型上会导致被裁减的图片也变为透明, iOS 和 IDE 上不会 79 | // setGlobalAlpha 在 1.9.90 起支持,低版本下无效,但把 setFillStyle 设为了 white,相对默认的 black 要好点 80 | this.ctx.globalAlpha = 0; 81 | this.ctx.fillStyle = 'white'; 82 | this.ctx.beginPath(); 83 | this.ctx.arc(-width / 2 + r, -height / 2 + r, r, 1 * Math.PI, 1.5 * Math.PI); 84 | this.ctx.lineTo(width / 2 - r, -height / 2); 85 | this.ctx.arc(width / 2 - r, -height / 2 + r, r, 1.5 * Math.PI, 2 * Math.PI); 86 | this.ctx.lineTo(width / 2, height / 2 - r); 87 | this.ctx.arc(width / 2 - r, height / 2 - r, r, 0, 0.5 * Math.PI); 88 | this.ctx.lineTo(-width / 2 + r, height / 2); 89 | this.ctx.arc(-width / 2 + r, height / 2 - r, r, 0.5 * Math.PI, 1 * Math.PI); 90 | this.ctx.closePath(); 91 | this.ctx.fill(); 92 | // 在 ios 的 6.6.6 版本上 clip 有 bug,禁掉此类型上的 clip,也就意味着,在此版本微信的 ios 设备下无法使用 border 属性 93 | if (!(getApp().systemInfo && 94 | getApp().systemInfo.version <= '6.6.6' && 95 | getApp().systemInfo.platform === 'ios')) { 96 | this.ctx.clip(); 97 | } 98 | this.ctx.globalAlpha = 1; 99 | } 100 | } 101 | 102 | /** 103 | * 画边框 104 | */ 105 | _doBorder(view, width, height) { 106 | if (!view.css) { 107 | return; 108 | } 109 | const { 110 | borderRadius, 111 | borderWidth, 112 | borderColor, 113 | } = view.css; 114 | if (!borderWidth) { 115 | return; 116 | } 117 | this.ctx.save(); 118 | this._preProcess(view, true); 119 | let r; 120 | if (borderRadius) { 121 | r = Math.min(borderRadius.toPx(), width / 2, height / 2); 122 | } else { 123 | r = 0; 124 | } 125 | const lineWidth = borderWidth.toPx(); 126 | this.ctx.lineWidth = lineWidth; 127 | this.ctx.strokeStyle = borderColor || 'black'; 128 | this.ctx.beginPath(); 129 | this.ctx.arc(-width / 2 + r, -height / 2 + r, r + lineWidth / 2, 1 * Math.PI, 1.5 * Math.PI); 130 | this.ctx.lineTo(width / 2 - r, -height / 2 - lineWidth / 2); 131 | this.ctx.arc(width / 2 - r, -height / 2 + r, r + lineWidth / 2, 1.5 * Math.PI, 2 * Math.PI); 132 | this.ctx.lineTo(width / 2 + lineWidth / 2, height / 2 - r); 133 | this.ctx.arc(width / 2 - r, height / 2 - r, r + lineWidth / 2, 0, 0.5 * Math.PI); 134 | this.ctx.lineTo(-width / 2 + r, height / 2 + lineWidth / 2); 135 | this.ctx.arc(-width / 2 + r, height / 2 - r, r + lineWidth / 2, 0.5 * Math.PI, 1 * Math.PI); 136 | this.ctx.closePath(); 137 | this.ctx.stroke(); 138 | this.ctx.restore(); 139 | } 140 | 141 | _preProcess(view, notClip) { 142 | let width; 143 | let height; 144 | let extra; 145 | switch (view.type) { 146 | case 'text': { 147 | const fontWeight = view.css.fontWeight === 'bold' ? 'bold' : 'normal'; 148 | view.css.fontSize = view.css.fontSize ? view.css.fontSize : '20rpx'; 149 | this.ctx.font = `normal ${fontWeight} ${view.css.fontSize.toPx()}px sans-serif`; 150 | // this.ctx.setFontSize(view.css.fontSize.toPx()); 151 | const textLength = this.ctx.measureText(view.text).width; 152 | width = view.css.width ? view.css.width.toPx() : textLength; 153 | // 计算行数 154 | const calLines = Math.ceil(textLength / width); 155 | const lines = view.css.maxLines < calLines ? view.css.maxLines : calLines; 156 | const lineHeight = view.css.lineHeight ? view.css.lineHeight.toPx() : view.css.fontSize.toPx(); 157 | height = lineHeight * lines; 158 | extra = { lines: lines, lineHeight: lineHeight }; 159 | break; 160 | } 161 | case 'image': { 162 | // image 如果未设置长宽,则使用图片本身的长宽 163 | const ratio = getApp().systemInfo.pixelRatio ? getApp().systemInfo.pixelRatio : 2; 164 | width = view.css && view.css.width ? view.css.width.toPx() : Math.round(view.sWidth / ratio); 165 | height = view.css && view.css.height ? view.css.height.toPx() : Math.round(view.sHeight / ratio); 166 | break; 167 | } 168 | default: { 169 | if (!(view.css.width && view.css.height)) { 170 | console.error('You should set width and height'); 171 | return; 172 | } 173 | width = view.css.width.toPx(); 174 | height = view.css.height.toPx(); 175 | } 176 | } 177 | const x = view.css && view.css.right ? this.style.width - view.css.right.toPx(true) : (view.css && view.css.left ? view.css.left.toPx(true) : 0); 178 | 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); 179 | 180 | const angle = view.css && view.css.rotate ? this._getAngle(view.css.rotate) : 0; 181 | // 当设置了 right 时,默认 align 用 right,反之用 left 182 | const align = view.css && view.css.align ? view.css.align : (view.css && view.css.right ? 'right' : 'left'); 183 | switch (align) { 184 | case 'center': 185 | this.ctx.translate(x, y + height / 2); 186 | break; 187 | case 'right': 188 | this.ctx.translate(x - width / 2, y + height / 2); 189 | break; 190 | default: 191 | this.ctx.translate(x + width / 2, y + height / 2); 192 | break; 193 | } 194 | this.ctx.rotate(angle); 195 | if (!notClip && view.css && view.css.borderRadius) { 196 | this._doClip(view.css.borderRadius, width, height); 197 | } 198 | 199 | return { 200 | width: width, 201 | height: height, 202 | x: x, 203 | y: y, 204 | extra: extra, 205 | }; 206 | } 207 | 208 | _drawQRCode(view) { 209 | this.ctx.save(); 210 | const { 211 | width, 212 | height, 213 | } = this._preProcess(view); 214 | QR.api.draw(view.content, this.ctx, -width / 2, -height / 2, width, height, view.css.background, view.css.color); 215 | this.ctx.restore(); 216 | this._doBorder(view, width, height); 217 | } 218 | 219 | _drawAbsImage(view) { 220 | if (!view.url) { 221 | return; 222 | } 223 | this.ctx.save(); 224 | const { 225 | width, 226 | height, 227 | } = this._preProcess(view); 228 | // 获得缩放到图片大小级别的裁减框 229 | let rWidth; 230 | let rHeight; 231 | let startX = 0; 232 | let startY = 0; 233 | if (width > height) { 234 | rHeight = Math.round((view.sWidth / width) * height); 235 | rWidth = view.sWidth; 236 | } else { 237 | rWidth = Math.round((view.sHeight / height) * width); 238 | rHeight = view.sHeight; 239 | } 240 | if (view.sWidth > rWidth) { 241 | startX = Math.round((view.sWidth - rWidth) / 2); 242 | } 243 | if (view.sHeight > rHeight) { 244 | startY = Math.round((view.sHeight - rHeight) / 2); 245 | } 246 | if (view.css && view.css.mode === 'scaleToFill') { 247 | this.ctx.drawImage(view.url, -(width / 2), -(height / 2), width, height); 248 | } else { 249 | this.ctx.drawImage(view.url, startX, startY, rWidth, rHeight, -(width / 2), -(height / 2), width, height); 250 | } 251 | this.ctx.restore(); 252 | this._doBorder(view, width, height); 253 | } 254 | 255 | _fillAbsText(view) { 256 | if (!view.text) { 257 | return; 258 | } 259 | this.ctx.save(); 260 | const { 261 | width, 262 | height, 263 | extra, 264 | } = this._preProcess(view); 265 | this.ctx.fillStyle = view.css.color || 'black'; 266 | const { lines, lineHeight } = extra; 267 | const preLineLength = Math.round(view.text.length / lines); 268 | let start = 0; 269 | let alreadyCount = 0; 270 | for (let i = 0; i < lines; ++i) { 271 | alreadyCount = preLineLength; 272 | let text = view.text.substr(start, alreadyCount); 273 | let measuredWith = this.ctx.measureText(text).width; 274 | // 如果测量大小小于width一个字符的大小,则进行补齐,如果测量大小超出 width,则进行减除 275 | // 如果已经到文本末尾,也不要进行该循环 276 | while ((start + alreadyCount <= view.text.length) && (width - measuredWith > view.css.fontSize.toPx() || measuredWith > width)) { 277 | if (measuredWith < width) { 278 | text = view.text.substr(start, ++alreadyCount); 279 | } else { 280 | if (text.length <= 1) { 281 | // 如果只有一个字符时,直接跳出循环 282 | break; 283 | } 284 | text = view.text.substr(start, --alreadyCount); 285 | } 286 | measuredWith = this.ctx.measureText(text).width; 287 | } 288 | start += text.length; 289 | // 如果是最后一行了,发现还有未绘制完的内容,则加... 290 | if (i === lines - 1 && start < view.text.length) { 291 | while (this.ctx.measureText(`${text}...`).width > width) { 292 | if (text.length <= 1) { 293 | // 如果只有一个字符时,直接跳出循环 294 | break; 295 | } 296 | text = text.substring(0, text.length - 1); 297 | } 298 | text += '...'; 299 | measuredWith = this.ctx.measureText(text).width; 300 | } 301 | this.ctx.setTextAlign(view.css.align ? view.css.align : 'left'); 302 | let x; 303 | switch (view.css.align) { 304 | case 'center': 305 | x = 0; 306 | break; 307 | case 'right': 308 | x = (width / 2); 309 | break; 310 | default: 311 | x = -(width / 2); 312 | break; 313 | } 314 | const y = -(height / 2) + (i === 0 ? view.css.fontSize.toPx() : (view.css.fontSize.toPx() + i * lineHeight)); 315 | if (view.css.textStyle === 'stroke') { 316 | this.ctx.strokeText(text, x, y, measuredWith); 317 | } else { 318 | this.ctx.fillText(text, x, y, measuredWith); 319 | } 320 | const fontSize = view.css.fontSize.toPx(); 321 | if (view.css.textDecoration) { 322 | this.ctx.beginPath(); 323 | if (/\bunderline\b/.test(view.css.textDecoration)) { 324 | this.ctx.moveTo(x, y); 325 | this.ctx.lineTo(x + measuredWith, y); 326 | } 327 | if (/\boverline\b/.test(view.css.textDecoration)) { 328 | this.ctx.moveTo(x, y - fontSize); 329 | this.ctx.lineTo(x + measuredWith, y - fontSize); 330 | } 331 | if (/\bline-through\b/.test(view.css.textDecoration)) { 332 | this.ctx.moveTo(x, y - fontSize / 3); 333 | this.ctx.lineTo(x + measuredWith, y - fontSize / 3); 334 | } 335 | this.ctx.closePath(); 336 | this.ctx.strokeStyle = view.css.color; 337 | this.ctx.stroke(); 338 | } 339 | } 340 | 341 | this.ctx.restore(); 342 | this._doBorder(view, width, height); 343 | } 344 | 345 | _drawAbsRect(view) { 346 | this.ctx.save(); 347 | const { 348 | width, 349 | height, 350 | } = this._preProcess(view); 351 | this.ctx.fillStyle = view.css.color; 352 | this.ctx.fillRect(-(width / 2), -(height / 2), width, height); 353 | this.ctx.restore(); 354 | this._doBorder(view, width, height); 355 | } 356 | 357 | _getAngle(angle) { 358 | return Number(angle) * Math.PI / 180; 359 | } 360 | } 361 | -------------------------------------------------------------------------------- /miniprogram/components/shareBox/index.js: -------------------------------------------------------------------------------- 1 | Component({ 2 | properties: { 3 | //属性值可以在组件使用时指定 4 | isCanDraw: { 5 | type: Boolean, 6 | value: false, 7 | observer(newVal, oldVal) { 8 | newVal && this.drawPic(); 9 | }, 10 | }, 11 | drawData: { 12 | type: Object, 13 | value: {}, 14 | }, 15 | }, 16 | data: { 17 | isModal: false, //是否显示拒绝保存图片后的弹窗 18 | imgDraw: {}, //绘制图片的大对象 19 | sharePath: "", //生成的分享图 20 | visible: false, 21 | }, 22 | methods: { 23 | handlePhotoSaved() { 24 | this.savePhoto(this.data.sharePath); 25 | }, 26 | handleClose() { 27 | this.setData({ 28 | visible: false, 29 | }); 30 | }, 31 | drawPic() { 32 | console.log("drawData", this.data.drawData); 33 | if (this.data.sharePath) { 34 | //如果已经绘制过了本地保存有图片不需要重新绘制 35 | this.setData({ 36 | visible: true, 37 | }); 38 | this.triggerEvent("initData"); 39 | return; 40 | } 41 | wx.showLoading({ 42 | title: "生成中", 43 | }); 44 | if (this.data.drawData.context1 === "已经第") { 45 | let numberId = ""; 46 | if (this.data.drawData.numberId <= 10) { 47 | numberId = " " + this.data.drawData.numberId 48 | } else { 49 | numberId = "" + this.data.drawData.numberId 50 | } 51 | console.log("numberId", numberId) 52 | // 下次这个画布宽度最好就按iphone6的750rpx定 53 | this.setData({ 54 | imgDraw: { 55 | width: "1210rpx", 56 | height: "1830rpx", 57 | background: "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/poster/poster.png?sign=8e64452badeadc8e80770a1b6bcf6a67&t=1616160878", 58 | views: [ 59 | // 图画 60 | { 61 | type: "image", 62 | url: this.data.drawData.artImage, 63 | css: { 64 | top: "680rpx", 65 | left: "260rpx", 66 | width: "674rpx", 67 | height: "500rpx", 68 | borderWidth: "20rpx", 69 | borderColor: "#ffffff", 70 | shadow: "10rpx 10rpx 5rpx #64afee", 71 | }, 72 | }, 73 | 74 | // 头像 75 | { 76 | type: "image", 77 | url: wx.getStorageSync("avatarUrl") || 78 | "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/share/default-avatar.png?sign=70bf172250d552f97216258faa41785c&t=1616315144", 79 | css: { 80 | top: "80rpx", 81 | left: "480rpx", 82 | width: "254rpx", 83 | height: "254rpx", 84 | borderWidth: "3rpx", 85 | borderColor: "#0063a9", 86 | borderRadius: "127rpx", 87 | }, 88 | }, 89 | { 90 | type: "text", 91 | text: wx.getStorageSync("nickName") || "微信昵称获取失败", 92 | css: { 93 | top: "360rpx", 94 | left: "600rpx", 95 | fontSize: "68rpx", 96 | align: "center", 97 | color: "black", 98 | fontWeight: "bolder", 99 | }, 100 | }, 101 | { 102 | type: "text", 103 | text: this.data.drawData.context1, 104 | css: { 105 | top: "500rpx", 106 | left: "290rpx", 107 | fontSize: "48rpx", 108 | align: "left", 109 | color: "black", 110 | fontWeight: "normal", 111 | }, 112 | }, 113 | { 114 | type: "text", 115 | text: numberId, 116 | css: { 117 | top: "500rpx", 118 | left: "440rpx", 119 | fontSize: "48rpx", 120 | align: "left", 121 | color: "#006de4", 122 | fontWeight: "normal", 123 | }, 124 | }, 125 | { 126 | type: "text", 127 | text: this.data.drawData.context2, 128 | css: { 129 | top: "500rpx", 130 | left: "500rpx", 131 | fontSize: "48rpx", 132 | align: "left", 133 | color: "black", 134 | fontWeight: "normal", 135 | }, 136 | }, 137 | { 138 | type: "text", 139 | text: this.data.drawData.context3, 140 | css: { 141 | top: "570rpx", 142 | left: "605rpx", 143 | fontSize: "48rpx", 144 | align: "center", 145 | color: "black", 146 | fontWeight: "normal", 147 | }, 148 | }, 149 | 150 | // 二维码 151 | { 152 | type: "image", 153 | url: "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/share/qr-code2.jpg?sign=e9272e95a564b5b62bd327f02c7be7a1&t=1618475799", 154 | css: { 155 | bottom: "80rpx", 156 | left: "180rpx", 157 | width: "220rpx", 158 | height: "220rpx", 159 | borderRadius: "110rpx", 160 | }, 161 | }, 162 | 163 | { 164 | type: "text", 165 | text: this.data.drawData.artTitle, 166 | css: { 167 | bottom: "540rpx", 168 | left: "605rpx", 169 | fontSize: "50rpx", 170 | align: "center", 171 | color: "black", 172 | fontWeight: "normal", 173 | }, 174 | }, 175 | 176 | { 177 | type: "text", 178 | text: this.data.drawData.artContext1, 179 | css: { 180 | bottom: "470rpx", 181 | left: "340rpx", 182 | fontSize: "50rpx", 183 | align: "left", 184 | color: "black", 185 | fontWeight: "normal", 186 | }, 187 | }, 188 | 189 | { 190 | type: "text", 191 | text: this.data.drawData.artContext2, 192 | css: { 193 | bottom: "470rpx", 194 | left: "730rpx", 195 | fontSize: "50rpx", 196 | align: "left", 197 | color: "#006de4", 198 | fontWeight: "normal", 199 | }, 200 | }, 201 | ], 202 | }, 203 | }); 204 | } else { 205 | // let numberId = ""; 206 | // if (this.data.drawData.numberId <= 10) { 207 | // numberId = " " + this.data.drawData.numberId 208 | // } else { 209 | // numberId = "" + this.data.drawData.numberId 210 | // } 211 | // console.log("numberId", numberId) 212 | this.setData({ 213 | imgDraw: { 214 | width: "1210rpx", 215 | height: "1830rpx", 216 | background: "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/poster/poster.png?sign=8e64452badeadc8e80770a1b6bcf6a67&t=1616160878", 217 | views: [ 218 | // 图画 219 | { 220 | type: "image", 221 | url: this.data.drawData.artImage, 222 | css: { 223 | top: "680rpx", 224 | left: "260rpx", 225 | width: "674rpx", 226 | height: "500rpx", 227 | borderWidth: "20rpx", 228 | borderColor: "#ffffff", 229 | shadow: "10rpx 10rpx 5rpx #64afee", 230 | }, 231 | }, 232 | 233 | // 头像 234 | { 235 | type: "image", 236 | url: wx.getStorageSync("avatarUrl") || 237 | "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/share/default-avatar.png?sign=70bf172250d552f97216258faa41785c&t=1616315144", 238 | css: { 239 | top: "80rpx", 240 | left: "480rpx", 241 | width: "254rpx", 242 | height: "254rpx", 243 | borderWidth: "3rpx", 244 | borderColor: "#0063a9", 245 | borderRadius: "127rpx", 246 | }, 247 | }, 248 | { 249 | type: "text", 250 | text: wx.getStorageSync("nickName") || "微信昵称获取失败", 251 | css: { 252 | top: "360rpx", 253 | left: "600rpx", 254 | fontSize: "68rpx", 255 | align: "center", 256 | color: "black", 257 | fontWeight: "bolder", 258 | }, 259 | }, 260 | { 261 | type: "text", 262 | text: this.data.drawData.context1, 263 | css: { 264 | top: "500rpx", 265 | left: "245rpx", 266 | fontSize: "48rpx", 267 | align: "left", 268 | color: "black", 269 | fontWeight: "normal", 270 | }, 271 | }, 272 | { 273 | type: "text", 274 | text: "" + this.data.drawData.numberId, 275 | css: { 276 | top: "500rpx", 277 | left: "470rpx", 278 | fontSize: "48rpx", 279 | align: "center", 280 | color: "#006de4", 281 | fontWeight: "normal", 282 | }, 283 | }, 284 | { 285 | type: "text", 286 | text: this.data.drawData.context2, 287 | css: { 288 | top: "500rpx", 289 | left: "500rpx", 290 | fontSize: "48rpx", 291 | align: "left", 292 | color: "black", 293 | fontWeight: "normal", 294 | }, 295 | }, 296 | { 297 | type: "text", 298 | text: this.data.drawData.context3, 299 | css: { 300 | top: "570rpx", 301 | left: "605rpx", 302 | fontSize: "48rpx", 303 | align: "center", 304 | color: "black", 305 | fontWeight: "normal", 306 | }, 307 | }, 308 | 309 | // 二维码 310 | { 311 | type: "image", 312 | url: "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/share/qr-code2.jpg?sign=e9272e95a564b5b62bd327f02c7be7a1&t=1618475799", 313 | css: { 314 | bottom: "80rpx", 315 | left: "180rpx", 316 | width: "220rpx", 317 | height: "220rpx", 318 | borderRadius: "110rpx", 319 | }, 320 | }, 321 | 322 | { 323 | type: "text", 324 | text: this.data.drawData.artTitle, 325 | css: { 326 | bottom: "540rpx", 327 | left: "605rpx", 328 | fontSize: "50rpx", 329 | align: "center", 330 | color: "black", 331 | fontWeight: "normal", 332 | }, 333 | }, 334 | 335 | { 336 | type: "text", 337 | text: this.data.drawData.artContext1, 338 | css: { 339 | bottom: "470rpx", 340 | left: "340rpx", 341 | fontSize: "50rpx", 342 | align: "left", 343 | color: "black", 344 | fontWeight: "normal", 345 | }, 346 | }, 347 | 348 | { 349 | type: "text", 350 | text: this.data.drawData.artContext2, 351 | css: { 352 | bottom: "470rpx", 353 | left: "730rpx", 354 | fontSize: "50rpx", 355 | align: "left", 356 | color: "#006de4", 357 | fontWeight: "normal", 358 | }, 359 | }, 360 | ], 361 | }, 362 | }); 363 | } 364 | }, 365 | onImgErr(e) { 366 | wx.hideLoading(); 367 | wx.showToast({ 368 | title: "生成分享图失败,请刷新页面重试", 369 | }); 370 | }, 371 | onImgOK(e) { 372 | wx.hideLoading(); 373 | this.setData({ 374 | sharePath: e.detail.path, 375 | visible: true, 376 | }); 377 | //通知外部绘制完成,重置isCanDraw为false 378 | this.triggerEvent("initData"); 379 | }, 380 | preventDefault() {}, 381 | // 保存图片 382 | savePhoto(path) { 383 | wx.showLoading({ 384 | title: "正在保存...", 385 | mask: true, 386 | }); 387 | this.setData({ 388 | isDrawImage: false, 389 | }); 390 | wx.saveImageToPhotosAlbum({ 391 | filePath: path, 392 | success: (res) => { 393 | wx.showToast({ 394 | title: "图片已保存到相册,快去朋友圈分享吧", 395 | icon: "none", 396 | }); 397 | setTimeout(() => { 398 | this.setData({ 399 | visible: false, 400 | }); 401 | }, 300); 402 | }, 403 | fail: (res) => { 404 | wx.getSetting({ 405 | success: (res) => { 406 | let authSetting = res.authSetting; 407 | if (!authSetting["scope.writePhotosAlbum"]) { 408 | this.setData({ 409 | isModal: true, 410 | }); 411 | } 412 | }, 413 | }); 414 | setTimeout(() => { 415 | wx.hideLoading(); 416 | this.setData({ 417 | visible: false, 418 | }); 419 | }, 300); 420 | }, 421 | }); 422 | }, 423 | }, 424 | }); -------------------------------------------------------------------------------- /miniprogram/pages/share/art.js: -------------------------------------------------------------------------------- 1 | const artList = [{ 2 | "id": 1, 3 | "fileName": "art1.jpg", 4 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art1.jpg", 5 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art1.jpg?sign=b663ce5fc3c51cb411ac6778562d2083&t=1618480172", 6 | "artName": "感知生活", 7 | "photographer": "石越 ", 8 | "other": "全盲" 9 | }, 10 | { 11 | "id": 2, 12 | "fileName": "art2.jpg", 13 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art2.jpg", 14 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art2.jpg?sign=3b7fe7f2f9824d6b52117f88504e9b92&t=1618480189", 15 | "artName": "合影", 16 | "photographer": "李彦双", 17 | "other": "低视力" 18 | }, 19 | { 20 | "id": 3, 21 | "fileName": "art3.jpg", 22 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art3.jpg", 23 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art3.jpg?sign=bfdf24dbb2b22b4b0cce9929b97feabd&t=1618480208", 24 | "artName": "那一刻的安静", 25 | "photographer": "刘惠 ", 26 | "other": "低视力" 27 | }, 28 | { 29 | "id": 4, 30 | "fileName": "art4.jpg", 31 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art4.jpg", 32 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art4.jpg?sign=064803d0ee28af97257f40deb1a21e03&t=1618480223", 33 | "artName": "曙光", 34 | "photographer": "刘惠 ", 35 | "other": "低视力" 36 | }, 37 | { 38 | "id": 5, 39 | "fileName": "art5.jpg", 40 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art5.jpg", 41 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art5.jpg?sign=754a9e9488223bde0d44a750dd6c49e6&t=1618480237", 42 | "artName": "有些光", 43 | "photographer": "傅高山", 44 | "other": "低视力" 45 | }, 46 | { 47 | "id": 6, 48 | "fileName": "art6.jpg", 49 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art6.jpg", 50 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art6.jpg?sign=93e2079d0d10fcd87f1f020e364839af&t=1618480266", 51 | "artName": "最可靠的朋友", 52 | "photographer": "石越 ", 53 | "other": "全盲" 54 | }, 55 | { 56 | "id": 8, 57 | "fileName": "art8.jpg", 58 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art8.jpg", 59 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art8.jpg?sign=0414cd57384c42888a43693d4dd8ed81&t=1618480293", 60 | "artName": "信仰", 61 | "photographer": "蔡聪 ", 62 | "other": "低视力" 63 | }, 64 | { 65 | "id": 9, 66 | "fileName": "art9.jpg", 67 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art9.jpg", 68 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art9.jpg?sign=da661bcbf53680a473402cae002087de&t=1618480323", 69 | "artName": "夜色的布达拉", 70 | "photographer": "蔡聪 ", 71 | "other": "低视力" 72 | }, 73 | { 74 | "id": 10, 75 | "fileName": "art10.jpg", 76 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art10.jpg", 77 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art10.jpg?sign=3bca021104cceb0db708b20482579b1e&t=1618480354", 78 | "artName": "车尾的感动", 79 | "photographer": "李彦双", 80 | "other": "低视力" 81 | }, 82 | { 83 | "id": 11, 84 | "fileName": "art11.jpg", 85 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art11.jpg", 86 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art11.jpg?sign=1370dd20b3482a235dd6a6d14ebca1d4&t=1618480367", 87 | "artName": "第一次签名", 88 | "photographer": "石越 ", 89 | "other": "全盲" 90 | }, 91 | { 92 | "id": 12, 93 | "fileName": "art12.jpg", 94 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art12.jpg", 95 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art12.jpg?sign=8467804b61a4c8a8931ed62a7469bdff&t=1618480377", 96 | "artName": "对抗", 97 | "photographer": "孙志远", 98 | "other": "低视力" 99 | }, 100 | { 101 | "id": 13, 102 | "fileName": "art13.jpg", 103 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art13.jpg", 104 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art13.jpg?sign=48d0b0eea6225fdc42a3ba441f8136e2&t=1618480391", 105 | "artName": "温暖的光", 106 | "photographer": "冯兰婷", 107 | "other": "全盲" 108 | }, 109 | { 110 | "id": 14, 111 | "fileName": "art14.jpg", 112 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art14.jpg", 113 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art14.jpg?sign=3517c6639133db234421215be0caada8&t=1618480418", 114 | "artName": "虔诚", 115 | "photographer": "傅高山", 116 | "other": "低视力" 117 | }, 118 | { 119 | "id": 15, 120 | "fileName": "art15.jpg", 121 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art15.jpg", 122 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art15.jpg?sign=39f85adfd8b67e38391d254bf189d3e4&t=1618480438", 123 | "artName": "呼吸", 124 | "photographer": "孙志远", 125 | "other": "低视力" 126 | }, 127 | { 128 | "id": 16, 129 | "fileName": "art16.jpg", 130 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art16.jpg", 131 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art16.jpg?sign=3868691214df0be99a5cd686d971b5ad&t=1618480458", 132 | "artName": "希望", 133 | "photographer": "刘惠 ", 134 | "other": "低视力" 135 | }, 136 | { 137 | "id": 17, 138 | "fileName": "art17.jpg", 139 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art17.jpg", 140 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art17.jpg?sign=43d20d4710694614ab190cb55e6e7276&t=1618480469", 141 | "artName": "起点 & 终点", 142 | "photographer": "蔡聪 ", 143 | "other": "低视力" 144 | }, 145 | { 146 | "id": 18, 147 | "fileName": "art18.jpg", 148 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art18.jpg", 149 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art18.jpg?sign=195312ea2c54ddab9b8deb18de9ba11f&t=1618480478", 150 | "artName": "十几步", 151 | "photographer": "徐橙 ", 152 | "other": "全盲" 153 | }, 154 | { 155 | "id": 19, 156 | "fileName": "art19.jpg", 157 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art19.jpg", 158 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art19.jpg?sign=ea604b4a68e9917f86950a60cf1a0516&t=1618480489", 159 | "artName": "双重世界", 160 | "photographer": "孙志远", 161 | "other": "低视力" 162 | }, 163 | { 164 | "id": 20, 165 | "fileName": "art20.jpg", 166 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art20.jpg", 167 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art20.jpg?sign=7687ea253b9cbf74c579363f92471714&t=1618480499", 168 | "artName": "照亮世界的花", 169 | "photographer": "下培 ", 170 | "other": "低视力" 171 | }, 172 | { 173 | "id": 21, 174 | "fileName": "art21.jpg", 175 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art21.jpg", 176 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art21.jpg?sign=40fad2b14410389882bfd1a75ef158db&t=1618480509", 177 | "artName": "小腿的情人", 178 | "photographer": "梁奕轩", 179 | "other": "全盲" 180 | }, 181 | { 182 | "id": 22, 183 | "fileName": "art22.jpg", 184 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art22.jpg", 185 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art22.jpg?sign=3233bbc491841a764a285a98e18edf1e&t=1618480518", 186 | "artName": "盲路", 187 | "photographer": "杨青风", 188 | "other": "全盲" 189 | }, 190 | { 191 | "id": 23, 192 | "fileName": "art23.jpg", 193 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art23.jpg", 194 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art23.jpg?sign=c7e5dfcee30dcbf3e7dc922701add1dd&t=1617899119", 195 | "artName": "儿子真棒 敢玩滑梯", 196 | "photographer": "黎彩英", 197 | "other": "全盲" 198 | }, 199 | { 200 | "id": 24, 201 | "fileName": "art24.jpg", 202 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art24.jpg", 203 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art24.jpg?sign=e8ecf0671bb9f9bbb82a7ae1abcbdec0&t=1617899130", 204 | "artName": "繁忙的都市", 205 | "photographer": "李金古", 206 | "other": "全盲" 207 | }, 208 | { 209 | "id": 25, 210 | "fileName": "art25.jpg", 211 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art25.jpg", 212 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art25.jpg?sign=d66b74206a9d246fab3969f01fa8fb60&t=1617899143", 213 | "artName": "海珠桥", 214 | "photographer": "罗耀辉", 215 | "other": "全盲" 216 | }, 217 | { 218 | "id": 26, 219 | "fileName": "art26.jpg", 220 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art26.jpg", 221 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art26.jpg?sign=03d515abdf9f0c0dfb3a1aedd33f61ee&t=1617899153", 222 | "artName": "花城广场的一棵树", 223 | "photographer": "巢馨颖", 224 | "other": "全盲" 225 | }, 226 | { 227 | "id": 27, 228 | "fileName": "art27.jpg", 229 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art27.jpg", 230 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art27.jpg?sign=27f4306839ec94b36a4672d2034d8485&t=1617899163", 231 | "artName": "快乐的摄影人", 232 | "photographer": "傅丹妮", 233 | "other": "全盲" 234 | }, 235 | { 236 | "id": 28, 237 | "fileName": "art28.jpg", 238 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art28.jpg", 239 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art28.jpg?sign=06a1377fb0d29d374f2d3cd689ed43d3&t=1617899172", 240 | "artName": "了不起的盲人朋友们-办公者周彤", 241 | "photographer": "蔡聪 ", 242 | "other": "低视力" 243 | }, 244 | { 245 | "id": 29, 246 | "fileName": "art29.jpg", 247 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art29.jpg", 248 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art29.jpg?sign=a3f5b8e1d2579e71e903a31999d293be&t=1617899180", 249 | "artName": "了不起的盲人朋友们-钢琴调音师杨京燕", 250 | "photographer": "蔡聪 ", 251 | "other": "低视力" 252 | }, 253 | { 254 | "id": 30, 255 | "fileName": "art30.jpg", 256 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art30.jpg", 257 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art30.jpg?sign=32cb3e2cc2d0035307553e34fd1564a8&t=1617899192", 258 | "artName": "了不起的盲人朋友们-化妆师肖佳", 259 | "photographer": "蔡聪 ", 260 | "other": "低视力" 261 | }, 262 | { 263 | "id": 31, 264 | "fileName": "art31.jpg", 265 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art31.jpg", 266 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art31.jpg?sign=4e18acd78d753fbaa1ac5d374339ffad&t=1617899201", 267 | "artName": "了不起的盲人朋友们-环球旅行家曹晟康", 268 | "photographer": "蔡聪 ", 269 | "other": "低视力" 270 | }, 271 | { 272 | "id": 32, 273 | "fileName": "art32.jpg", 274 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art32.jpg", 275 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art32.jpg?sign=fe618c6084c80e5132f0c36298892faf&t=1617899211", 276 | "artName": "了不起的盲人朋友们-马拉松跑者何亚军", 277 | "photographer": "蔡聪 ", 278 | "other": "低视力" 279 | }, 280 | { 281 | "id": 33, 282 | "fileName": "art33.jpg", 283 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art33.jpg", 284 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art33.jpg?sign=3c42b485fd126079e30a69ae5aa2a5b0&t=1617899223", 285 | "artName": "了不起的盲人朋友们-盲人培训者王志华", 286 | "photographer": "蔡聪 ", 287 | "other": "低视力" 288 | }, 289 | { 290 | "id": 34, 291 | "fileName": "art34.jpg", 292 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art34.jpg", 293 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art34.jpg?sign=f660a26cc3872afdde7ddf60d22e6cac&t=1617899232", 294 | "artName": "了不起的盲人朋友们-盲人培训者杨青风", 295 | "photographer": "蔡聪 ", 296 | "other": "低视力" 297 | }, 298 | { 299 | "id": 35, 300 | "fileName": "art35.jpg", 301 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art35.jpg", 302 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art35.jpg?sign=0cc2213481445585deb9fe6ef1942e34&t=1617899249", 303 | "artName": "裂痕", 304 | "photographer": "蔡聪 ", 305 | "other": "低视力" 306 | }, 307 | { 308 | "id": 36, 309 | "fileName": "art36.jpg", 310 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art36.jpg", 311 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art36.jpg?sign=5bc060b45262d30ef304a630bce481ef&t=1617899261", 312 | "artName": "石头", 313 | "photographer": "韩青君", 314 | "other": "全盲" 315 | }, 316 | { 317 | "id": 37, 318 | "fileName": "art37.jpg", 319 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art37.jpg", 320 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art37.jpg?sign=b9679700f895aea3ad38a69fa512c672&t=1617899271", 321 | "artName": "通往梦境的话筒", 322 | "photographer": "范子伟", 323 | "other": "全盲" 324 | }, 325 | { 326 | "id": 38, 327 | "fileName": "art38.jpg", 328 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art38.jpg", 329 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art38.jpg?sign=b191bdf0c97aa2845ad9f7e7c7bb42d7&t=1617899281", 330 | "artName": "无题", 331 | "photographer": "傅高山", 332 | "other": "低视力" 333 | }, 334 | { 335 | "id": 39, 336 | "fileName": "art39.jpg", 337 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art39.jpg", 338 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art39.jpg?sign=54a7e5365506b3ef0317523c9286054b&t=1617899294", 339 | "artName": "无题", 340 | "photographer": "李彦双", 341 | "other": "低视力" 342 | }, 343 | { 344 | "id": 40, 345 | "fileName": "art40.jpg", 346 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art40.jpg", 347 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art40.jpg?sign=67b5d44c3897b8c0701e48d50f2b9e71&t=1617899304", 348 | "artName": "相遇", 349 | "photographer": "李宁 ", 350 | "other": "全盲" 351 | }, 352 | { 353 | "id": 41, 354 | "fileName": "art41.jpg", 355 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art41.jpg", 356 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art41.jpg?sign=6840116b874a5ffe962c5721ca37c544&t=1617899317", 357 | "artName": "夜色下的布达拉", 358 | "photographer": "边巴顿珠", 359 | "other": "全盲" 360 | }, 361 | { 362 | "id": 42, 363 | "fileName": "art42.jpg", 364 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art42.jpg", 365 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art42.jpg?sign=5ca729183cabf771021256cb6261df93&t=1617899327", 366 | "artName": "一样的布达拉", 367 | "photographer": "蔡聪 ", 368 | "other": "低视力" 369 | }, 370 | { 371 | "id": 43, 372 | "fileName": "art43.jpg", 373 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art43.jpg", 374 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art43.jpg?sign=44383c70e555280f04dd58bc4c8b2fb2&t=1617899690", 375 | "artName": "可见的时光", 376 | "photographer": "朱学元", 377 | "other": "低视力" 378 | }, 379 | { 380 | "id": 44, 381 | "fileName": "art44.jpg", 382 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art44.jpg", 383 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art44.jpg?sign=8450edb7d4faca9ba84b7db1c14980ff&t=1617899727", 384 | "artName": "展翅高飞的海鸥", 385 | "photographer": "孙禀贺", 386 | "other": "全盲" 387 | }, 388 | { 389 | "id": 45, 390 | "fileName": "art45.jpg", 391 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art45.jpg", 392 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art45.jpg?sign=c63c6216fe0929841c38c3be9d79c528&t=1617899737", 393 | "artName": "选择", 394 | "photographer": "金玲 ", 395 | "other": "低视力" 396 | }, 397 | { 398 | "id": 46, 399 | "fileName": "art46.jpg", 400 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art46.jpg", 401 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art46.jpg?sign=aa520d69c8b40fab1af387a6de6fec17&t=1617899747", 402 | "artName": "相望", 403 | "photographer": "高放 ", 404 | "other": "低视力" 405 | }, 406 | { 407 | "id": 47, 408 | "fileName": "art47.jpg", 409 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art47.jpg", 410 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art47.jpg?sign=c21b14125e28cb22e31ce35bf8f4a799&t=1617899757", 411 | "artName": "无题", 412 | "photographer": "李琦 ", 413 | "other": "全盲" 414 | }, 415 | { 416 | "id": 48, 417 | "fileName": "art48.jpg", 418 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art48.jpg", 419 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art48.jpg?sign=d6ab179e723d972cb9e14329ffb932c4&t=1617899768", 420 | "artName": "我要绽放", 421 | "photographer": "傅高山", 422 | "other": "低视力" 423 | }, 424 | { 425 | "id": 49, 426 | "fileName": "art49.jpg", 427 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art49.jpg", 428 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art49.jpg?sign=5277055087b9423f97829b0af65ffce4&t=1617899792", 429 | "artName": "惬意的源头", 430 | "photographer": "傅高山", 431 | "other": "低视力" 432 | }, 433 | { 434 | "id": 50, 435 | "fileName": "art50.jpg", 436 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art50.jpg", 437 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art50.jpg?sign=7c1d7fff400afa2d8cc3b5c0c2cef68a&t=1617899803", 438 | "artName": "魔镜", 439 | "photographer": "薛民辉", 440 | "other": "全盲" 441 | }, 442 | { 443 | "id": 51, 444 | "fileName": "art51.jpg", 445 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art51.jpg", 446 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art51.jpg?sign=4102224e780366d8dd18e9643b37efc1&t=1617899813", 447 | "artName": "两栖", 448 | "photographer": "韩瑶 ", 449 | "other": "低视力" 450 | }, 451 | { 452 | "id": 52, 453 | "fileName": "art52.jpg", 454 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art52.jpg", 455 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art52.jpg?sign=fa8263964346d30cc9be226ce11bcff7&t=1617899825", 456 | "artName": "背朝大海", 457 | "photographer": "李娜 ", 458 | "other": "全盲" 459 | }, 460 | { 461 | "id": 53, 462 | "fileName": "art53.jpg", 463 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art53.jpg", 464 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art53.jpg?sign=bad11ecd3f5b3333c98f5998448366c4&t=1617899835", 465 | "artName": "采花大盗", 466 | "photographer": "李娜 ", 467 | "other": "全盲" 468 | }, 469 | { 470 | "id": 54, 471 | "fileName": "art54.jpg", 472 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art54.jpg", 473 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art54.jpg?sign=0195309b0e1e9b5647f4a6232493d80d&t=1617899848", 474 | "artName": "光", 475 | "photographer": "李娜 ", 476 | "other": "全盲" 477 | }, 478 | { 479 | "id": 55, 480 | "fileName": "art55.jpg", 481 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art55.jpg", 482 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art55.jpg?sign=7bbc0b975aa5c9b5924b75ed4593eb88&t=1617899858", 483 | "artName": "经纬", 484 | "photographer": "李娜 ", 485 | "other": "全盲" 486 | }, 487 | { 488 | "id": 56, 489 | "fileName": "art56.jpg", 490 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art56.jpg", 491 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art56.jpg?sign=f262126cdad4fd10a67579dcad58b046&t=1617899871", 492 | "artName": "看与被看", 493 | "photographer": "傅高山", 494 | "other": "低视力" 495 | }, 496 | { 497 | "id": 57, 498 | "fileName": "art57.jpg", 499 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art57.jpg", 500 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art57.jpg?sign=0dd15124829017a3dd08dd73507c7f11&t=1617899881", 501 | "artName": "枯叶", 502 | "photographer": "李娜 ", 503 | "other": "全盲" 504 | }, 505 | { 506 | "id": 58, 507 | "fileName": "art58.jpg", 508 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art58.jpg", 509 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art58.jpg?sign=2a433bdbb1b3683c2bde01e0d64f843f&t=1617899893", 510 | "artName": "劳作", 511 | "photographer": "李娜 ", 512 | "other": "全盲" 513 | }, 514 | { 515 | "id": 59, 516 | "fileName": "art59.jpg", 517 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art59.jpg", 518 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art59.jpg?sign=0a0dd464c7fa89b06ab2e7a1c806f820&t=1617899902", 519 | "artName": "铅笔", 520 | "photographer": "李娜 ", 521 | "other": "全盲" 522 | }, 523 | { 524 | "id": 60, 525 | "fileName": "art60.jpg", 526 | "artFileId": "cloud://map-4g0ciu1x80002ab0.6d61-map-4g0ciu1x80002ab0-1305236624/artwork/art60.jpg", 527 | "artUrl": "https://6d61-map-4g0ciu1x80002ab0-1305236624.tcb.qcloud.la/artwork/art60.jpg?sign=b4b7c93b805cbe1b5b3e7bec72d1f898&t=1617899914", 528 | "artName": "我的全部", 529 | "photographer": "孙志远", 530 | "other": "低视力" 531 | } 532 | ] 533 | module.exports = artList; -------------------------------------------------------------------------------- /miniprogram/painter/lib/qrcode.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | !(function () { 3 | 4 | // alignment pattern 5 | var adelta = [ 6 | 0, 11, 15, 19, 23, 27, 31, 7 | 16, 18, 20, 22, 24, 26, 28, 20, 22, 24, 24, 26, 28, 28, 22, 24, 24, 8 | 26, 26, 28, 28, 24, 24, 26, 26, 26, 28, 28, 24, 26, 26, 26, 28, 28 9 | ]; 10 | 11 | // version block 12 | var vpat = [ 13 | 0xc94, 0x5bc, 0xa99, 0x4d3, 0xbf6, 0x762, 0x847, 0x60d, 14 | 0x928, 0xb78, 0x45d, 0xa17, 0x532, 0x9a6, 0x683, 0x8c9, 15 | 0x7ec, 0xec4, 0x1e1, 0xfab, 0x08e, 0xc1a, 0x33f, 0xd75, 16 | 0x250, 0x9d5, 0x6f0, 0x8ba, 0x79f, 0xb0b, 0x42e, 0xa64, 17 | 0x541, 0xc69 18 | ]; 19 | 20 | // final format bits with mask: level << 3 | mask 21 | var fmtword = [ 22 | 0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976, //L 23 | 0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0, //M 24 | 0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed, //Q 25 | 0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b //H 26 | ]; 27 | 28 | // 4 per version: number of blocks 1,2; data width; ecc width 29 | var eccblocks = [ 30 | 1, 0, 19, 7, 1, 0, 16, 10, 1, 0, 13, 13, 1, 0, 9, 17, 31 | 1, 0, 34, 10, 1, 0, 28, 16, 1, 0, 22, 22, 1, 0, 16, 28, 32 | 1, 0, 55, 15, 1, 0, 44, 26, 2, 0, 17, 18, 2, 0, 13, 22, 33 | 1, 0, 80, 20, 2, 0, 32, 18, 2, 0, 24, 26, 4, 0, 9, 16, 34 | 1, 0, 108, 26, 2, 0, 43, 24, 2, 2, 15, 18, 2, 2, 11, 22, 35 | 2, 0, 68, 18, 4, 0, 27, 16, 4, 0, 19, 24, 4, 0, 15, 28, 36 | 2, 0, 78, 20, 4, 0, 31, 18, 2, 4, 14, 18, 4, 1, 13, 26, 37 | 2, 0, 97, 24, 2, 2, 38, 22, 4, 2, 18, 22, 4, 2, 14, 26, 38 | 2, 0, 116, 30, 3, 2, 36, 22, 4, 4, 16, 20, 4, 4, 12, 24, 39 | 2, 2, 68, 18, 4, 1, 43, 26, 6, 2, 19, 24, 6, 2, 15, 28, 40 | 4, 0, 81, 20, 1, 4, 50, 30, 4, 4, 22, 28, 3, 8, 12, 24, 41 | 2, 2, 92, 24, 6, 2, 36, 22, 4, 6, 20, 26, 7, 4, 14, 28, 42 | 4, 0, 107, 26, 8, 1, 37, 22, 8, 4, 20, 24, 12, 4, 11, 22, 43 | 3, 1, 115, 30, 4, 5, 40, 24, 11, 5, 16, 20, 11, 5, 12, 24, 44 | 5, 1, 87, 22, 5, 5, 41, 24, 5, 7, 24, 30, 11, 7, 12, 24, 45 | 5, 1, 98, 24, 7, 3, 45, 28, 15, 2, 19, 24, 3, 13, 15, 30, 46 | 1, 5, 107, 28, 10, 1, 46, 28, 1, 15, 22, 28, 2, 17, 14, 28, 47 | 5, 1, 120, 30, 9, 4, 43, 26, 17, 1, 22, 28, 2, 19, 14, 28, 48 | 3, 4, 113, 28, 3, 11, 44, 26, 17, 4, 21, 26, 9, 16, 13, 26, 49 | 3, 5, 107, 28, 3, 13, 41, 26, 15, 5, 24, 30, 15, 10, 15, 28, 50 | 4, 4, 116, 28, 17, 0, 42, 26, 17, 6, 22, 28, 19, 6, 16, 30, 51 | 2, 7, 111, 28, 17, 0, 46, 28, 7, 16, 24, 30, 34, 0, 13, 24, 52 | 4, 5, 121, 30, 4, 14, 47, 28, 11, 14, 24, 30, 16, 14, 15, 30, 53 | 6, 4, 117, 30, 6, 14, 45, 28, 11, 16, 24, 30, 30, 2, 16, 30, 54 | 8, 4, 106, 26, 8, 13, 47, 28, 7, 22, 24, 30, 22, 13, 15, 30, 55 | 10, 2, 114, 28, 19, 4, 46, 28, 28, 6, 22, 28, 33, 4, 16, 30, 56 | 8, 4, 122, 30, 22, 3, 45, 28, 8, 26, 23, 30, 12, 28, 15, 30, 57 | 3, 10, 117, 30, 3, 23, 45, 28, 4, 31, 24, 30, 11, 31, 15, 30, 58 | 7, 7, 116, 30, 21, 7, 45, 28, 1, 37, 23, 30, 19, 26, 15, 30, 59 | 5, 10, 115, 30, 19, 10, 47, 28, 15, 25, 24, 30, 23, 25, 15, 30, 60 | 13, 3, 115, 30, 2, 29, 46, 28, 42, 1, 24, 30, 23, 28, 15, 30, 61 | 17, 0, 115, 30, 10, 23, 46, 28, 10, 35, 24, 30, 19, 35, 15, 30, 62 | 17, 1, 115, 30, 14, 21, 46, 28, 29, 19, 24, 30, 11, 46, 15, 30, 63 | 13, 6, 115, 30, 14, 23, 46, 28, 44, 7, 24, 30, 59, 1, 16, 30, 64 | 12, 7, 121, 30, 12, 26, 47, 28, 39, 14, 24, 30, 22, 41, 15, 30, 65 | 6, 14, 121, 30, 6, 34, 47, 28, 46, 10, 24, 30, 2, 64, 15, 30, 66 | 17, 4, 122, 30, 29, 14, 46, 28, 49, 10, 24, 30, 24, 46, 15, 30, 67 | 4, 18, 122, 30, 13, 32, 46, 28, 48, 14, 24, 30, 42, 32, 15, 30, 68 | 20, 4, 117, 30, 40, 7, 47, 28, 43, 22, 24, 30, 10, 67, 15, 30, 69 | 19, 6, 118, 30, 18, 31, 47, 28, 34, 34, 24, 30, 20, 61, 15, 30 70 | ]; 71 | 72 | // Galois field log table 73 | var glog = [ 74 | 0xff, 0x00, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b, 75 | 0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71, 76 | 0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45, 77 | 0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6, 78 | 0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88, 79 | 0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40, 80 | 0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d, 81 | 0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57, 82 | 0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18, 83 | 0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e, 84 | 0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61, 85 | 0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2, 86 | 0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6, 87 | 0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a, 88 | 0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7, 89 | 0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf 90 | ]; 91 | 92 | // Galios field exponent table 93 | var gexp = [ 94 | 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26, 95 | 0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0, 96 | 0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23, 97 | 0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1, 98 | 0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0, 99 | 0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2, 100 | 0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce, 101 | 0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc, 102 | 0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54, 103 | 0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73, 104 | 0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff, 105 | 0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41, 106 | 0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6, 107 | 0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09, 108 | 0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16, 109 | 0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x00 110 | ]; 111 | 112 | // Working buffers: 113 | // data input and ecc append, image working buffer, fixed part of image, run lengths for badness 114 | var strinbuf = [], eccbuf = [], qrframe = [], framask = [], rlens = []; 115 | // Control values - width is based on version, last 4 are from table. 116 | var version, width, neccblk1, neccblk2, datablkw, eccblkwid; 117 | var ecclevel = 2; 118 | // set bit to indicate cell in qrframe is immutable. symmetric around diagonal 119 | function setmask(x, y) { 120 | var bt; 121 | if (x > y) { 122 | bt = x; 123 | x = y; 124 | y = bt; 125 | } 126 | // y*y = 1+3+5... 127 | bt = y; 128 | bt *= y; 129 | bt += y; 130 | bt >>= 1; 131 | bt += x; 132 | framask[bt] = 1; 133 | } 134 | 135 | // enter alignment pattern - black to qrframe, white to mask (later black frame merged to mask) 136 | function putalign(x, y) { 137 | var j; 138 | 139 | qrframe[x + width * y] = 1; 140 | for (j = -2; j < 2; j++) { 141 | qrframe[(x + j) + width * (y - 2)] = 1; 142 | qrframe[(x - 2) + width * (y + j + 1)] = 1; 143 | qrframe[(x + 2) + width * (y + j)] = 1; 144 | qrframe[(x + j + 1) + width * (y + 2)] = 1; 145 | } 146 | for (j = 0; j < 2; j++) { 147 | setmask(x - 1, y + j); 148 | setmask(x + 1, y - j); 149 | setmask(x - j, y - 1); 150 | setmask(x + j, y + 1); 151 | } 152 | } 153 | 154 | //======================================================================== 155 | // Reed Solomon error correction 156 | // exponentiation mod N 157 | function modnn(x) { 158 | while (x >= 255) { 159 | x -= 255; 160 | x = (x >> 8) + (x & 255); 161 | } 162 | return x; 163 | } 164 | 165 | var genpoly = []; 166 | 167 | // Calculate and append ECC data to data block. Block is in strinbuf, indexes to buffers given. 168 | function appendrs(data, dlen, ecbuf, eclen) { 169 | var i, j, fb; 170 | 171 | for (i = 0; i < eclen; i++) 172 | strinbuf[ecbuf + i] = 0; 173 | for (i = 0; i < dlen; i++) { 174 | fb = glog[strinbuf[data + i] ^ strinbuf[ecbuf]]; 175 | if (fb != 255) /* fb term is non-zero */ 176 | for (j = 1; j < eclen; j++) 177 | strinbuf[ecbuf + j - 1] = strinbuf[ecbuf + j] ^ gexp[modnn(fb + genpoly[eclen - j])]; 178 | else 179 | for (j = ecbuf; j < ecbuf + eclen; j++) 180 | strinbuf[j] = strinbuf[j + 1]; 181 | strinbuf[ecbuf + eclen - 1] = fb == 255 ? 0 : gexp[modnn(fb + genpoly[0])]; 182 | } 183 | } 184 | 185 | //======================================================================== 186 | // Frame data insert following the path rules 187 | 188 | // check mask - since symmetrical use half. 189 | function ismasked(x, y) { 190 | var bt; 191 | if (x > y) { 192 | bt = x; 193 | x = y; 194 | y = bt; 195 | } 196 | bt = y; 197 | bt += y * y; 198 | bt >>= 1; 199 | bt += x; 200 | return framask[bt]; 201 | } 202 | 203 | //======================================================================== 204 | // Apply the selected mask out of the 8. 205 | function applymask(m) { 206 | var x, y, r3x, r3y; 207 | 208 | switch (m) { 209 | case 0: 210 | for (y = 0; y < width; y++) 211 | for (x = 0; x < width; x++) 212 | if (!((x + y) & 1) && !ismasked(x, y)) 213 | qrframe[x + y * width] ^= 1; 214 | break; 215 | case 1: 216 | for (y = 0; y < width; y++) 217 | for (x = 0; x < width; x++) 218 | if (!(y & 1) && !ismasked(x, y)) 219 | qrframe[x + y * width] ^= 1; 220 | break; 221 | case 2: 222 | for (y = 0; y < width; y++) 223 | for (r3x = 0, x = 0; x < width; x++ , r3x++) { 224 | if (r3x == 3) 225 | r3x = 0; 226 | if (!r3x && !ismasked(x, y)) 227 | qrframe[x + y * width] ^= 1; 228 | } 229 | break; 230 | case 3: 231 | for (r3y = 0, y = 0; y < width; y++ , r3y++) { 232 | if (r3y == 3) 233 | r3y = 0; 234 | for (r3x = r3y, x = 0; x < width; x++ , r3x++) { 235 | if (r3x == 3) 236 | r3x = 0; 237 | if (!r3x && !ismasked(x, y)) 238 | qrframe[x + y * width] ^= 1; 239 | } 240 | } 241 | break; 242 | case 4: 243 | for (y = 0; y < width; y++) 244 | for (r3x = 0, r3y = ((y >> 1) & 1), x = 0; x < width; x++ , r3x++) { 245 | if (r3x == 3) { 246 | r3x = 0; 247 | r3y = !r3y; 248 | } 249 | if (!r3y && !ismasked(x, y)) 250 | qrframe[x + y * width] ^= 1; 251 | } 252 | break; 253 | case 5: 254 | for (r3y = 0, y = 0; y < width; y++ , r3y++) { 255 | if (r3y == 3) 256 | r3y = 0; 257 | for (r3x = 0, x = 0; x < width; x++ , r3x++) { 258 | if (r3x == 3) 259 | r3x = 0; 260 | if (!((x & y & 1) + !(!r3x | !r3y)) && !ismasked(x, y)) 261 | qrframe[x + y * width] ^= 1; 262 | } 263 | } 264 | break; 265 | case 6: 266 | for (r3y = 0, y = 0; y < width; y++ , r3y++) { 267 | if (r3y == 3) 268 | r3y = 0; 269 | for (r3x = 0, x = 0; x < width; x++ , r3x++) { 270 | if (r3x == 3) 271 | r3x = 0; 272 | if (!(((x & y & 1) + (r3x && (r3x == r3y))) & 1) && !ismasked(x, y)) 273 | qrframe[x + y * width] ^= 1; 274 | } 275 | } 276 | break; 277 | case 7: 278 | for (r3y = 0, y = 0; y < width; y++ , r3y++) { 279 | if (r3y == 3) 280 | r3y = 0; 281 | for (r3x = 0, x = 0; x < width; x++ , r3x++) { 282 | if (r3x == 3) 283 | r3x = 0; 284 | if (!(((r3x && (r3x == r3y)) + ((x + y) & 1)) & 1) && !ismasked(x, y)) 285 | qrframe[x + y * width] ^= 1; 286 | } 287 | } 288 | break; 289 | } 290 | return; 291 | } 292 | 293 | // Badness coefficients. 294 | var N1 = 3, N2 = 3, N3 = 40, N4 = 10; 295 | 296 | // Using the table of the length of each run, calculate the amount of bad image 297 | // - long runs or those that look like finders; called twice, once each for X and Y 298 | function badruns(length) { 299 | var i; 300 | var runsbad = 0; 301 | for (i = 0; i <= length; i++) 302 | if (rlens[i] >= 5) 303 | runsbad += N1 + rlens[i] - 5; 304 | // BwBBBwB as in finder 305 | for (i = 3; i < length - 1; i += 2) 306 | if (rlens[i - 2] == rlens[i + 2] 307 | && rlens[i + 2] == rlens[i - 1] 308 | && rlens[i - 1] == rlens[i + 1] 309 | && rlens[i - 1] * 3 == rlens[i] 310 | // white around the black pattern? Not part of spec 311 | && (rlens[i - 3] == 0 // beginning 312 | || i + 3 > length // end 313 | || rlens[i - 3] * 3 >= rlens[i] * 4 || rlens[i + 3] * 3 >= rlens[i] * 4) 314 | ) 315 | runsbad += N3; 316 | return runsbad; 317 | } 318 | 319 | // Calculate how bad the masked image is - blocks, imbalance, runs, or finders. 320 | function badcheck() { 321 | var x, y, h, b, b1; 322 | var thisbad = 0; 323 | var bw = 0; 324 | 325 | // blocks of same color. 326 | for (y = 0; y < width - 1; y++) 327 | for (x = 0; x < width - 1; x++) 328 | if ((qrframe[x + width * y] && qrframe[(x + 1) + width * y] 329 | && qrframe[x + width * (y + 1)] && qrframe[(x + 1) + width * (y + 1)]) // all black 330 | || !(qrframe[x + width * y] || qrframe[(x + 1) + width * y] 331 | || qrframe[x + width * (y + 1)] || qrframe[(x + 1) + width * (y + 1)])) // all white 332 | thisbad += N2; 333 | 334 | // X runs 335 | for (y = 0; y < width; y++) { 336 | rlens[0] = 0; 337 | for (h = b = x = 0; x < width; x++) { 338 | if ((b1 = qrframe[x + width * y]) == b) 339 | rlens[h]++; 340 | else 341 | rlens[++h] = 1; 342 | b = b1; 343 | bw += b ? 1 : -1; 344 | } 345 | thisbad += badruns(h); 346 | } 347 | 348 | // black/white imbalance 349 | if (bw < 0) 350 | bw = -bw; 351 | 352 | var big = bw; 353 | var count = 0; 354 | big += big << 2; 355 | big <<= 1; 356 | while (big > width * width) 357 | big -= width * width, count++; 358 | thisbad += count * N4; 359 | 360 | // Y runs 361 | for (x = 0; x < width; x++) { 362 | rlens[0] = 0; 363 | for (h = b = y = 0; y < width; y++) { 364 | if ((b1 = qrframe[x + width * y]) == b) 365 | rlens[h]++; 366 | else 367 | rlens[++h] = 1; 368 | b = b1; 369 | } 370 | thisbad += badruns(h); 371 | } 372 | return thisbad; 373 | } 374 | 375 | function genframe(instring) { 376 | var x, y, k, t, v, i, j, m; 377 | 378 | // find the smallest version that fits the string 379 | t = instring.length; 380 | version = 0; 381 | do { 382 | version++; 383 | k = (ecclevel - 1) * 4 + (version - 1) * 16; 384 | neccblk1 = eccblocks[k++]; 385 | neccblk2 = eccblocks[k++]; 386 | datablkw = eccblocks[k++]; 387 | eccblkwid = eccblocks[k]; 388 | k = datablkw * (neccblk1 + neccblk2) + neccblk2 - 3 + (version <= 9); 389 | if (t <= k) 390 | break; 391 | } while (version < 40); 392 | 393 | // FIXME - insure that it fits insted of being truncated 394 | width = 17 + 4 * version; 395 | 396 | // allocate, clear and setup data structures 397 | v = datablkw + (datablkw + eccblkwid) * (neccblk1 + neccblk2) + neccblk2; 398 | for (t = 0; t < v; t++) 399 | eccbuf[t] = 0; 400 | strinbuf = instring.slice(0); 401 | 402 | for (t = 0; t < width * width; t++) 403 | qrframe[t] = 0; 404 | 405 | for (t = 0; t < (width * (width + 1) + 1) / 2; t++) 406 | framask[t] = 0; 407 | 408 | // insert finders - black to frame, white to mask 409 | for (t = 0; t < 3; t++) { 410 | k = 0; 411 | y = 0; 412 | if (t == 1) 413 | k = (width - 7); 414 | if (t == 2) 415 | y = (width - 7); 416 | qrframe[(y + 3) + width * (k + 3)] = 1; 417 | for (x = 0; x < 6; x++) { 418 | qrframe[(y + x) + width * k] = 1; 419 | qrframe[y + width * (k + x + 1)] = 1; 420 | qrframe[(y + 6) + width * (k + x)] = 1; 421 | qrframe[(y + x + 1) + width * (k + 6)] = 1; 422 | } 423 | for (x = 1; x < 5; x++) { 424 | setmask(y + x, k + 1); 425 | setmask(y + 1, k + x + 1); 426 | setmask(y + 5, k + x); 427 | setmask(y + x + 1, k + 5); 428 | } 429 | for (x = 2; x < 4; x++) { 430 | qrframe[(y + x) + width * (k + 2)] = 1; 431 | qrframe[(y + 2) + width * (k + x + 1)] = 1; 432 | qrframe[(y + 4) + width * (k + x)] = 1; 433 | qrframe[(y + x + 1) + width * (k + 4)] = 1; 434 | } 435 | } 436 | 437 | // alignment blocks 438 | if (version > 1) { 439 | t = adelta[version]; 440 | y = width - 7; 441 | for (; ;) { 442 | x = width - 7; 443 | while (x > t - 3) { 444 | putalign(x, y); 445 | if (x < t) 446 | break; 447 | x -= t; 448 | } 449 | if (y <= t + 9) 450 | break; 451 | y -= t; 452 | putalign(6, y); 453 | putalign(y, 6); 454 | } 455 | } 456 | 457 | // single black 458 | qrframe[8 + width * (width - 8)] = 1; 459 | 460 | // timing gap - mask only 461 | for (y = 0; y < 7; y++) { 462 | setmask(7, y); 463 | setmask(width - 8, y); 464 | setmask(7, y + width - 7); 465 | } 466 | for (x = 0; x < 8; x++) { 467 | setmask(x, 7); 468 | setmask(x + width - 8, 7); 469 | setmask(x, width - 8); 470 | } 471 | 472 | // reserve mask-format area 473 | for (x = 0; x < 9; x++) 474 | setmask(x, 8); 475 | for (x = 0; x < 8; x++) { 476 | setmask(x + width - 8, 8); 477 | setmask(8, x); 478 | } 479 | for (y = 0; y < 7; y++) 480 | setmask(8, y + width - 7); 481 | 482 | // timing row/col 483 | for (x = 0; x < width - 14; x++) 484 | if (x & 1) { 485 | setmask(8 + x, 6); 486 | setmask(6, 8 + x); 487 | } 488 | else { 489 | qrframe[(8 + x) + width * 6] = 1; 490 | qrframe[6 + width * (8 + x)] = 1; 491 | } 492 | 493 | // version block 494 | if (version > 6) { 495 | t = vpat[version - 7]; 496 | k = 17; 497 | for (x = 0; x < 6; x++) 498 | for (y = 0; y < 3; y++ , k--) 499 | if (1 & (k > 11 ? version >> (k - 12) : t >> k)) { 500 | qrframe[(5 - x) + width * (2 - y + width - 11)] = 1; 501 | qrframe[(2 - y + width - 11) + width * (5 - x)] = 1; 502 | } 503 | else { 504 | setmask(5 - x, 2 - y + width - 11); 505 | setmask(2 - y + width - 11, 5 - x); 506 | } 507 | } 508 | 509 | // sync mask bits - only set above for white spaces, so add in black bits 510 | for (y = 0; y < width; y++) 511 | for (x = 0; x <= y; x++) 512 | if (qrframe[x + width * y]) 513 | setmask(x, y); 514 | 515 | // convert string to bitstream 516 | // 8 bit data to QR-coded 8 bit data (numeric or alphanum, or kanji not supported) 517 | v = strinbuf.length; 518 | 519 | // string to array 520 | for (i = 0; i < v; i++) 521 | eccbuf[i] = strinbuf.charCodeAt(i); 522 | strinbuf = eccbuf.slice(0); 523 | 524 | // calculate max string length 525 | x = datablkw * (neccblk1 + neccblk2) + neccblk2; 526 | if (v >= x - 2) { 527 | v = x - 2; 528 | if (version > 9) 529 | v--; 530 | } 531 | 532 | // shift and repack to insert length prefix 533 | i = v; 534 | if (version > 9) { 535 | strinbuf[i + 2] = 0; 536 | strinbuf[i + 3] = 0; 537 | while (i--) { 538 | t = strinbuf[i]; 539 | strinbuf[i + 3] |= 255 & (t << 4); 540 | strinbuf[i + 2] = t >> 4; 541 | } 542 | strinbuf[2] |= 255 & (v << 4); 543 | strinbuf[1] = v >> 4; 544 | strinbuf[0] = 0x40 | (v >> 12); 545 | } 546 | else { 547 | strinbuf[i + 1] = 0; 548 | strinbuf[i + 2] = 0; 549 | while (i--) { 550 | t = strinbuf[i]; 551 | strinbuf[i + 2] |= 255 & (t << 4); 552 | strinbuf[i + 1] = t >> 4; 553 | } 554 | strinbuf[1] |= 255 & (v << 4); 555 | strinbuf[0] = 0x40 | (v >> 4); 556 | } 557 | // fill to end with pad pattern 558 | i = v + 3 - (version < 10); 559 | while (i < x) { 560 | strinbuf[i++] = 0xec; 561 | // buffer has room if (i == x) break; 562 | strinbuf[i++] = 0x11; 563 | } 564 | 565 | // calculate and append ECC 566 | 567 | // calculate generator polynomial 568 | genpoly[0] = 1; 569 | for (i = 0; i < eccblkwid; i++) { 570 | genpoly[i + 1] = 1; 571 | for (j = i; j > 0; j--) 572 | genpoly[j] = genpoly[j] 573 | ? genpoly[j - 1] ^ gexp[modnn(glog[genpoly[j]] + i)] : genpoly[j - 1]; 574 | genpoly[0] = gexp[modnn(glog[genpoly[0]] + i)]; 575 | } 576 | for (i = 0; i <= eccblkwid; i++) 577 | genpoly[i] = glog[genpoly[i]]; // use logs for genpoly[] to save calc step 578 | 579 | // append ecc to data buffer 580 | k = x; 581 | y = 0; 582 | for (i = 0; i < neccblk1; i++) { 583 | appendrs(y, datablkw, k, eccblkwid); 584 | y += datablkw; 585 | k += eccblkwid; 586 | } 587 | for (i = 0; i < neccblk2; i++) { 588 | appendrs(y, datablkw + 1, k, eccblkwid); 589 | y += datablkw + 1; 590 | k += eccblkwid; 591 | } 592 | // interleave blocks 593 | y = 0; 594 | for (i = 0; i < datablkw; i++) { 595 | for (j = 0; j < neccblk1; j++) 596 | eccbuf[y++] = strinbuf[i + j * datablkw]; 597 | for (j = 0; j < neccblk2; j++) 598 | eccbuf[y++] = strinbuf[(neccblk1 * datablkw) + i + (j * (datablkw + 1))]; 599 | } 600 | for (j = 0; j < neccblk2; j++) 601 | eccbuf[y++] = strinbuf[(neccblk1 * datablkw) + i + (j * (datablkw + 1))]; 602 | for (i = 0; i < eccblkwid; i++) 603 | for (j = 0; j < neccblk1 + neccblk2; j++) 604 | eccbuf[y++] = strinbuf[x + i + j * eccblkwid]; 605 | strinbuf = eccbuf; 606 | 607 | // pack bits into frame avoiding masked area. 608 | x = y = width - 1; 609 | k = v = 1; // up, minus 610 | /* inteleaved data and ecc codes */ 611 | m = (datablkw + eccblkwid) * (neccblk1 + neccblk2) + neccblk2; 612 | for (i = 0; i < m; i++) { 613 | t = strinbuf[i]; 614 | for (j = 0; j < 8; j++ , t <<= 1) { 615 | if (0x80 & t) 616 | qrframe[x + width * y] = 1; 617 | do { // find next fill position 618 | if (v) 619 | x--; 620 | else { 621 | x++; 622 | if (k) { 623 | if (y != 0) 624 | y--; 625 | else { 626 | x -= 2; 627 | k = !k; 628 | if (x == 6) { 629 | x--; 630 | y = 9; 631 | } 632 | } 633 | } 634 | else { 635 | if (y != width - 1) 636 | y++; 637 | else { 638 | x -= 2; 639 | k = !k; 640 | if (x == 6) { 641 | x--; 642 | y -= 8; 643 | } 644 | } 645 | } 646 | } 647 | v = !v; 648 | } while (ismasked(x, y)); 649 | } 650 | } 651 | 652 | // save pre-mask copy of frame 653 | strinbuf = qrframe.slice(0); 654 | t = 0; // best 655 | y = 30000; // demerit 656 | // for instead of while since in original arduino code 657 | // if an early mask was "good enough" it wouldn't try for a better one 658 | // since they get more complex and take longer. 659 | for (k = 0; k < 8; k++) { 660 | applymask(k); // returns black-white imbalance 661 | x = badcheck(); 662 | if (x < y) { // current mask better than previous best? 663 | y = x; 664 | t = k; 665 | } 666 | if (t == 7) 667 | break; // don't increment i to a void redoing mask 668 | qrframe = strinbuf.slice(0); // reset for next pass 669 | } 670 | if (t != k) // redo best mask - none good enough, last wasn't t 671 | applymask(t); 672 | 673 | // add in final mask/ecclevel bytes 674 | y = fmtword[t + ((ecclevel - 1) << 3)]; 675 | // low byte 676 | for (k = 0; k < 8; k++ , y >>= 1) 677 | if (y & 1) { 678 | qrframe[(width - 1 - k) + width * 8] = 1; 679 | if (k < 6) 680 | qrframe[8 + width * k] = 1; 681 | else 682 | qrframe[8 + width * (k + 1)] = 1; 683 | } 684 | // high byte 685 | for (k = 0; k < 7; k++ , y >>= 1) 686 | if (y & 1) { 687 | qrframe[8 + width * (width - 7 + k)] = 1; 688 | if (k) 689 | qrframe[(6 - k) + width * 8] = 1; 690 | else 691 | qrframe[7 + width * 8] = 1; 692 | } 693 | return qrframe; 694 | } 695 | 696 | 697 | 698 | 699 | var _canvas = null; 700 | 701 | var api = { 702 | 703 | get ecclevel() { 704 | return ecclevel; 705 | }, 706 | 707 | set ecclevel(val) { 708 | ecclevel = val; 709 | }, 710 | 711 | get size() { 712 | return _size; 713 | }, 714 | 715 | set size(val) { 716 | _size = val 717 | }, 718 | 719 | get canvas() { 720 | return _canvas; 721 | }, 722 | 723 | set canvas(el) { 724 | _canvas = el; 725 | }, 726 | 727 | getFrame: function (string) { 728 | return genframe(string); 729 | }, 730 | //这里的utf16to8(str)是对Text中的字符串进行转码,让其支持中文 731 | utf16to8: function (str) { 732 | var out, i, len, c; 733 | 734 | out = ""; 735 | len = str.length; 736 | for (i = 0; i < len; i++) { 737 | c = str.charCodeAt(i); 738 | if ((c >= 0x0001) && (c <= 0x007F)) { 739 | out += str.charAt(i); 740 | } else if (c > 0x07FF) { 741 | out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F)); 742 | out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F)); 743 | out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); 744 | } else { 745 | out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F)); 746 | out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F)); 747 | } 748 | } 749 | return out; 750 | }, 751 | /** 752 | * 新增$this参数,传入组件的this,兼容在组件中生成 753 | * @param bg 目前只能设置颜色值 754 | */ 755 | draw: function (str, ctx, startX, startY, cavW, cavH, bg, color, $this, ecc) { 756 | var that = this; 757 | ecclevel = ecc || ecclevel; 758 | if (!ctx) { 759 | console.warn('No canvas provided to draw QR code in!') 760 | return; 761 | } 762 | var size = Math.min(cavW, cavH); 763 | str = that.utf16to8(str);//增加中文显示 764 | 765 | var frame = that.getFrame(str); 766 | var px = size / width; 767 | if (bg) { 768 | ctx.setFillStyle(bg) 769 | ctx.fillRect(startX, startY, cavW, cavW); 770 | } 771 | ctx.setFillStyle(color || 'black'); 772 | for (var i = 0; i < width; i++) { 773 | for (var j = 0; j < width; j++) { 774 | if (frame[j * width + i]) { 775 | ctx.fillRect(startX + px * i, startY + px * j, px, px); 776 | } 777 | } 778 | } 779 | } 780 | } 781 | module.exports = { api } 782 | // exports.draw = api; 783 | 784 | })(); --------------------------------------------------------------------------------