├── 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 |
4 |
5 |
6 | 保存到相册
7 |
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": "",
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 |
--------------------------------------------------------------------------------
/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 |
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 |
--------------------------------------------------------------------------------
/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 | [](#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 | | ||  |
33 | | 生成海报 | 用户上传日志 | 分享到微信 |
34 | |  |||
35 |
36 | ## 四、Demo 小程序
37 |
38 | > 如果你希望自己的小程序展示在这里,欢迎pr或在issue中提出
39 |
40 | | 友好盲道小程序 | 深圳美食图鉴 | **千岛湖民宿地图**|
41 | | ---- | ----| ----|
42 | |  || |
43 | | **jooyi的美食地图** | | |
44 | |  |||
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 |
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 | })();
--------------------------------------------------------------------------------