drew 5 роки тому
коміт
40de1505ed
100 змінених файлів з 2959 додано та 0 видалено
  1. 9 0
      .editorconfig
  2. 1 0
      .env.development
  3. 1 0
      .env.production
  4. 3 0
      .eslintignore
  5. 22 0
      .eslintrc.js
  6. 22 0
      .gitignore
  7. 19 0
      .prettierrc.js
  8. 21 0
      README.md
  9. 85 0
      megalo.config.js
  10. 47 0
      package.json
  11. 73 0
      project.config.json
  12. 7 0
      sitemap.json
  13. 74 0
      src/App.vue
  14. 175 0
      src/authUtil.js
  15. 28 0
      src/components/HelloWorld.vue
  16. 112 0
      src/main.js
  17. BIN
      src/native/imgs/icon_dot.png
  18. BIN
      src/native/imgs/icon_location.png
  19. BIN
      src/native/imgs/icon_picker.png
  20. BIN
      src/native/imgs/tab_car.png
  21. BIN
      src/native/imgs/tab_car_pre.png
  22. BIN
      src/native/imgs/tab_home.png
  23. BIN
      src/native/imgs/tab_home_pre.png
  24. BIN
      src/native/imgs/tab_msg.png
  25. BIN
      src/native/imgs/tab_msg_pre.png
  26. BIN
      src/native/imgs/tab_my.png
  27. BIN
      src/native/imgs/tab_my_pre.png
  28. 11 0
      src/pages/car.vue
  29. 353 0
      src/pages/home.vue
  30. 71 0
      src/pages/my.vue
  31. 11 0
      src/pages/tuxiang.vue
  32. 137 0
      src/plugins/http.js
  33. BIN
      src/static/imgs/home_tool_ico_1.png
  34. BIN
      src/static/imgs/home_tool_ico_2.png
  35. BIN
      src/static/imgs/home_tool_ico_3.png
  36. BIN
      src/static/imgs/home_tool_ico_4.png
  37. BIN
      src/static/imgs/icon_into.png
  38. BIN
      src/static/imgs/tap_car_nor.png
  39. BIN
      src/static/imgs/tap_car_sel.png
  40. BIN
      src/static/imgs/tap_home_nor.png
  41. BIN
      src/static/imgs/tap_home_sel.png
  42. BIN
      src/static/imgs/tap_me_nor.png
  43. BIN
      src/static/imgs/tap_me_sel.png
  44. BIN
      src/static/imgs/tap_tour_nor.png
  45. BIN
      src/static/imgs/tap_tour_sel.png
  46. BIN
      src/static/imgs/user.png
  47. 36 0
      src/store/index.js
  48. 21 0
      src/styles/common.less
  49. 1 0
      src/vant/action-sheet/index.d.ts
  50. 62 0
      src/vant/action-sheet/index.js
  51. 8 0
      src/vant/action-sheet/index.json
  52. 67 0
      src/vant/action-sheet/index.wxml
  53. 0 0
      src/vant/action-sheet/index.wxss
  54. 1 0
      src/vant/area/index.d.ts
  55. 229 0
      src/vant/area/index.js
  56. 6 0
      src/vant/area/index.json
  57. 18 0
      src/vant/area/index.wxml
  58. 1 0
      src/vant/area/index.wxss
  59. 1 0
      src/vant/button/index.d.ts
  60. 79 0
      src/vant/button/index.js
  61. 7 0
      src/vant/button/index.json
  62. 68 0
      src/vant/button/index.wxml
  63. 0 0
      src/vant/button/index.wxss
  64. 57 0
      src/vant/calendar/calendar.wxml
  65. 1 0
      src/vant/calendar/components/header/index.d.ts
  66. 16 0
      src/vant/calendar/components/header/index.js
  67. 3 0
      src/vant/calendar/components/header/index.json
  68. 16 0
      src/vant/calendar/components/header/index.wxml
  69. 1 0
      src/vant/calendar/components/header/index.wxss
  70. 1 0
      src/vant/calendar/components/month/index.d.ts
  71. 157 0
      src/vant/calendar/components/month/index.js
  72. 3 0
      src/vant/calendar/components/month/index.json
  73. 39 0
      src/vant/calendar/components/month/index.wxml
  74. 67 0
      src/vant/calendar/components/month/index.wxs
  75. 0 0
      src/vant/calendar/components/month/index.wxss
  76. 1 0
      src/vant/calendar/index.d.ts
  77. 290 0
      src/vant/calendar/index.js
  78. 10 0
      src/vant/calendar/index.json
  79. 31 0
      src/vant/calendar/index.wxml
  80. 37 0
      src/vant/calendar/index.wxs
  81. 1 0
      src/vant/calendar/index.wxss
  82. 17 0
      src/vant/calendar/utils.d.ts
  83. 78 0
      src/vant/calendar/utils.js
  84. 25 0
      src/vant/calendar/utils.wxs
  85. 1 0
      src/vant/card/index.d.ts
  86. 49 0
      src/vant/card/index.js
  87. 6 0
      src/vant/card/index.json
  88. 50 0
      src/vant/card/index.wxml
  89. 0 0
      src/vant/card/index.wxss
  90. 1 0
      src/vant/cell-group/index.d.ts
  91. 10 0
      src/vant/cell-group/index.js
  92. 3 0
      src/vant/cell-group/index.json
  93. 9 0
      src/vant/cell-group/index.wxml
  94. 1 0
      src/vant/cell-group/index.wxss
  95. 1 0
      src/vant/cell/index.d.ts
  96. 38 0
      src/vant/cell/index.js
  97. 6 0
      src/vant/cell/index.json
  98. 45 0
      src/vant/cell/index.wxml
  99. 0 0
      src/vant/cell/index.wxss
  100. 1 0
      src/vant/checkbox-group/index.d.ts

+ 9 - 0
.editorconfig

@@ -0,0 +1,9 @@
+root = true
+
+[*]
+charset = utf-8
+indent_style = space
+indent_size = 4
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true

+ 1 - 0
.env.development

@@ -0,0 +1 @@
+VUE_APP_BASE_URL=https://shoptest.jetour.com.cn

+ 1 - 0
.env.production

@@ -0,0 +1 @@
+VUE_APP_BASE_URL=https://shoptest.jetour.com.cn

+ 3 - 0
.eslintignore

@@ -0,0 +1,3 @@
+node_modules
+dist-*
+src/native

+ 22 - 0
.eslintrc.js

@@ -0,0 +1,22 @@
+module.exports = {
+    root: true,
+    env: {
+        node: true
+    },
+    extends: ['@megalo/standard', '@vue/prettier'],
+    rules: {
+        'no-console': 'off',
+        'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
+        'no-unused-vars': 'off',
+        'no-empty': ['error', { allowEmptyCatch: true }],
+        'prettier/prettier': ['error']
+    },
+    // parserOptions: {
+    //     parser: 'babel-eslint'
+    // },
+    globals: {
+        Chart: true,
+        AMap: true,
+        FontAwesome: true
+    }
+};

+ 22 - 0
.gitignore

@@ -0,0 +1,22 @@
+node_modules
+dist*
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw*
+
+.DS_Store

+ 19 - 0
.prettierrc.js

@@ -0,0 +1,19 @@
+module.exports = {
+    printWidth: 120,
+    singleQuote: true,
+    tabWidth: 4,
+    overrides: [
+        {
+            files: '*.html',
+            options: {
+                parser: 'html'
+            }
+        },
+        {
+            files: '*.vue',
+            options: {
+                parser: 'vue'
+            }
+        }
+    ]
+};

+ 21 - 0
README.md

@@ -0,0 +1,21 @@
+# quzu_mr
+
+## 运行
+
+微信小程序:
+
+```bash
+npm run dev:wechat
+```
+
+支付宝小程序:
+
+```bash
+npm run dev:alipay
+```
+
+百度智能小程序:
+
+```bash
+npm run dev:swan
+```

+ 85 - 0
megalo.config.js

@@ -0,0 +1,85 @@
+const path = require('path');
+const fs = require('fs');
+module.exports = {
+    // 构件生产模式时是否生成source map(仅在process.env.NODE_ENV === 'production' 时该选项生效)
+    productionSourceMap: false,
+
+    // 开启eslint格式化代码
+    lintOnSave: true,
+
+    configureWebpack: config => {
+        // 你可以在这里粗放的修改webpack的配置并返回
+        return config;
+    },
+    chainWebpack: chainConfig => {
+        chainConfig.plugin('copy-webpack-plugin').tap(args => {
+            args[0].push({
+                context: path.resolve('src'),
+                from: path.resolve('project.config.json'),
+                to: path.resolve(`dist-${process.env.PLATFORM}/project.config.json`)
+            });
+            args[0].push({
+                context: path.resolve('src'),
+                from: path.resolve('sitemap.json'),
+                to: path.resolve(`dist-${process.env.PLATFORM}/sitemap.json`)
+            });
+            args[0].push({
+                context: path.resolve('src'),
+                from: path.resolve('src/vant'),
+                to: path.resolve(`dist-${process.env.PLATFORM}/vant`)
+            });
+            // let str = /usingComponents:[\W](\{[\w\W]+?\})/
+            //     .exec(fs.readFileSync('src/main.js').toString())[1]
+            //     .replace(/[\s]/g, '')
+            //     .replace(/'/g, '"');
+            // console.log(str);
+            // let components = JSON.parse(str);
+            // Object.keys(components).forEach(key => {
+            //     let from = path.resolve('src', components[key].replace(/^\//, ''), '../');
+            //     let to = path.resolve(`dist-${process.env.PLATFORM}`, components[key].replace(/^\//, ''), '../');
+            //     args[0].push({
+            //         context: path.resolve('src'),
+            //         from,
+            //         to
+            //     });
+            //     console.log(from, to);
+            // });
+            return args;
+        });
+        chainConfig.module
+            .rule('less')
+            .test(/\.less$/)
+            .use('style-resources-loader')
+            .loader('style-resources-loader')
+            .options({
+                patterns: [path.resolve(__dirname, './src/styles/common.less')]
+            })
+            .end();
+    },
+    // 原生小程序组件存放目录,默认为src/native
+    // 如果你有多个平台的原生组件,你应当在此目录下再新建几个子文件夹,我们约定,子文件夹名和平台的名字一致:
+    // 微信小程序组件则命名为 'wechat',支付宝为'alipay', 百度为 'swan'
+    // 如果只有一个平台,则无需再新建子文件夹
+    nativeDir: '/src/native',
+
+    css: {
+        loaderOptions: {
+            css: {
+                // https://github.com/webpack-contrib/css-loader#options
+            },
+            less: {
+                // https://github.com/webpack-contrib/less-loader
+            },
+            sass: {
+                // https://github.com/webpack-contrib/sass-loader
+            },
+            stylus: {
+                // https://github.com/shama/stylus-loader
+            },
+            // https://github.com/megalojs/megalo-px2rpx-loader
+            px2rpx: {
+                rpxUnit: 0.5
+            }
+        }
+    }
+};

+ 47 - 0
package.json

@@ -0,0 +1,47 @@
+{
+  "name": "quzu_mr",
+  "version": "1.0.0",
+  "private": true,
+  "scripts": {
+    "build:wechat": "megalo-cli-service build",
+    "build:alipay": "megalo-cli-service build --platform alipay",
+    "build:swan": "megalo-cli-service build --platform swan",
+    "build:toutiao": "megalo-cli-service build --platform toutiao",
+    "dev:alipay": "megalo-cli-service serve --platform alipay",
+    "dev:swan": "megalo-cli-service serve --platform swan",
+    "dev:wechat": "megalo-cli-service serve",
+    "dev:toutiao": "megalo-cli-service serve --platform toutiao",
+    "lint": "megalo-cli-service lint"
+  },
+  "license": "ISC",
+  "babel": {
+    "presets": [
+      "@megalo/app"
+    ]
+  },
+  "devDependencies": {
+    "@megalo/babel-preset-app": "latest",
+    "@megalo/cli-plugin-eslint": "latest",
+    "@megalo/cli-service": "latest",
+    "@megalo/eslint-config-standard": "latest",
+    "@megalo/target": "latest",
+    "@megalo/template-compiler": "latest",
+    "@vue/eslint-config-prettier": "^5.0.0",
+    "babel-eslint": "^10.1.0",
+    "eslint": "^5.15.3",
+    "eslint-plugin-prettier": "^3.1.1",
+    "eslint-plugin-vue": "^5.0.0",
+    "less": "^3.8.1",
+    "less-loader": "^4.1.0"
+  },
+  "dependencies": {
+    "@megalo/api": "latest",
+    "@megalo/vhtml-plugin": "latest",
+    "@vant/weapp": "^1.4.4",
+    "date-fns": "^2.15.0",
+    "megalo": "latest",
+    "octoparse": "^0.4.2",
+    "style-resources-loader": "^1.3.3",
+    "vuex": "^3.1.0"
+  }
+}

+ 73 - 0
project.config.json

@@ -0,0 +1,73 @@
+{
+    "description": "项目配置文件",
+    "packOptions": {
+        "ignore": []
+    },
+    "setting": {
+        "urlCheck": false,
+        "es6": true,
+        "enhance": false,
+        "postcss": true,
+        "preloadBackgroundData": false,
+        "minified": true,
+        "newFeature": true,
+        "coverView": true,
+        "autoAudits": false,
+        "showShadowRootInWxmlPanel": true,
+        "scopeDataCheck": false,
+        "checkInvalidKey": true,
+        "checkSiteMap": true,
+        "uploadWithSourceMap": true,
+        "babelSetting": {
+            "ignore": [],
+            "disablePlugins": [],
+            "outputPath": ""
+        }
+    },
+    "compileType": "miniprogram",
+    "libVersion": "2.11.0",
+    "appid": "wx31f7e5e3081d6927",
+    "projectname": "%E5%8E%BB%E7%A7%9FMR",
+    "cloudfunctionTemplateRoot": "",
+    "watchOptions": {
+        "ignore": []
+    },
+    "debugOptions": {
+        "hidedInDevtools": []
+    },
+    "scripts": {},
+    "simulatorType": "wechat",
+    "simulatorPluginLibVersion": {},
+    "condition": {
+        "search": {
+            "current": -1,
+            "list": []
+        },
+        "conversation": {
+            "current": -1,
+            "list": []
+        },
+        "plugin": {
+            "current": -1,
+            "list": []
+        },
+        "game": {
+            "list": []
+        },
+        "gamePlugin": {
+            "current": -1,
+            "list": []
+        },
+        "miniprogram": {
+            "current": -1,
+            "list": [
+                {
+                    "id": -1,
+                    "name": "我的",
+                    "pathName": "pages/my",
+                    "scene": null
+                }
+            ]
+        }
+    }
+}

+ 7 - 0
sitemap.json

@@ -0,0 +1,7 @@
+{
+    "desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
+    "rules": [{
+    "action": "allow",
+    "page": "*"
+    }]
+  }

+ 74 - 0
src/App.vue

@@ -0,0 +1,74 @@
+<script>
+export default {
+    onLaunch(options) {
+        wx.login({
+            success: res => {
+                new Promise((resolve, reject) => {
+                    wx.request({
+                        method: 'get',
+                        url: this.$http.parseUrl('/applets/getlogininfo'),
+                        data: { code: res.code },
+                        dataType: 'json',
+                        header: {
+                            Accept: 'application/json',
+                            Authorization: 'Bearer '
+                        },
+                        success: res => {
+                            if (res && res.statusCode === 200) {
+                                resolve(res.data);
+                            } else {
+                                reject(res);
+                            }
+                        },
+                        fail: err => {
+                            reject(err);
+                        }
+                    });
+                })
+                    .then(res => {
+                        this.$http.setToken(res.data.token);
+                        this.$store.commit('setLoginInfo', res.data);
+                    })
+                    .catch(e => {
+                        wx.reLaunch({
+                            url: '/pages/home'
+                        });
+                    });
+
+                // this.$http.post('/auth/maLogin', { code: res.code }).then(res => {
+                //     this.$http.setToken(res.token);
+                //     this.$store.commit('setSessionKey', res.sessionKey);
+                //     this.$http.get('/user/my').then(res => {
+                //         this.$store.commit('setUserInfo', res);
+                //     });
+                // });
+            },
+            fail() {}
+        });
+
+        const sysInfo = wx.getSystemInfoSync();
+        console.log(sysInfo);
+        const safeAreaTop = sysInfo.safeArea.top;
+        const safeAreaBottom = sysInfo.screenHeight - sysInfo.safeArea.bottom;
+        this.$store.commit('setSafeAreaTop', safeAreaTop);
+        this.$store.commit('setSafeAreaBottom', safeAreaBottom);
+    },
+    onShow(options) {
+        // Do something when show.
+        console.log('App onShow');
+    },
+    onHide() {
+        // Do something when hide.
+        console.log('App onHide');
+    },
+    onError(msg) {
+        console.log('App onError');
+        console.log(msg);
+    },
+    globalData() {
+        return {
+            a: 'I am global data'
+        };
+    }
+};
+</script>

+ 175 - 0
src/authUtil.js

@@ -0,0 +1,175 @@
+/* eslint-disable */
+const formatTime = date => {
+  const year = date.getFullYear()
+  const month = date.getMonth() + 1
+  const day = date.getDate()
+  const hour = date.getHours()
+  const minute = date.getMinutes()
+  const second = date.getSeconds()
+
+  return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':')
+}
+
+const formatNumber = n => {
+  n = n.toString()
+  return n[1] ? n : '0' + n
+}
+
+module.exports = {
+  formatTime: formatTime,
+  initProduct: initProduct,
+  uploadImage: uploadImage,
+  uploadBase64Image: uploadBase64Image,
+  livedetect: livedetect,
+  idcardOCR: idcardOCR,
+  realNameAuth: realNameAuth,
+  compare: compare
+}
+
+function initProduct(membershipKey, callback) {
+  wx.request({
+    url: 'https://www.chinadatapay.com/MyAccount/checkMembership', // 初始化产品
+    dataType: 'json',
+    method: 'POST',
+    data: {
+      membershipKey: membershipKey
+    },
+    header: {
+      'content-type': 'application/x-www-form-urlencoded'
+    },
+    success(res) {
+      //执行回调
+      return typeof callback == "function" && callback(res.data);
+    }
+  })
+}
+function uploadImage(appkey, tempFilePath, callback) {
+  wx.uploadFile({
+    url: 'https://file.chinadatapay.com/img/upload', // 图片上传
+    filePath: tempFilePath,
+    name: 'file',
+    dataType: 'json',
+    formData: {
+      appkey: appkey
+    },
+    success(res) {
+      const data = JSON.parse(res.data);
+      //执行回调
+      return typeof callback == "function" && callback(data);
+    }
+  })
+}
+function uploadBase64Image(appkey, base64Image, callback) {
+  wx.request({
+    url: 'https://file.chinadatapay.com/img/uploadByBase64',
+    dataType: 'json',
+    method: 'POST',
+    data: {
+      appkey: appkey,
+      base64Image: base64Image
+    },
+    header: {
+      'content-type': 'application/x-www-form-urlencoded'
+    },
+    success(res) {
+      const data = res.data;
+      //执行回调
+      return typeof callback == "function" && callback(data);
+    }
+  })
+}
+function livedetect(membershipKey, key, tempFilePath, apiKey, apiSecret, callback) {
+  this.uploadImage(key, tempFilePath, function (result) {
+    var imageId = result.data;
+    wx.request({
+      url: 'https://api.chinadatapay.com/trade/user/2104', // 活体检测
+      dataType: 'json',
+      method: 'POST',
+      data: {
+        imageId: imageId,
+        key: key,
+        apiSecret: apiSecret,
+        return_image: true,
+        apiKey: apiKey,
+        membershipkey: membershipKey
+      },
+      header: {
+        'content-type': 'application/x-www-form-urlencoded'
+      },
+      success(res) {
+        const data = res.data
+        //执行回调
+        return typeof callback == "function" && callback(data);
+      }
+    })
+  });
+}
+function compare(membershipKey, key, base64Image, name, idcard, callback) {
+  this.uploadBase64Image(key, base64Image, function (result) {
+    var imageId = result.data;
+    wx.request({
+      url: 'https://api.chinadatapay.com/communication/personal/2061', // 人像比对
+      dataType: 'json',
+      method: 'POST',
+      data: {
+        imageId: imageId,
+        key: key,
+        name: name,
+        idcard: idcard,
+        membershipkey: membershipKey
+      },
+      header: {
+        'content-type': 'application/x-www-form-urlencoded'
+      },
+      success(res) {
+        const data = res.data
+        //执行回调
+        return typeof callback == "function" && callback(data);
+      }
+    })
+  });
+}
+function realNameAuth(membershipKey, key, name, idcard, callback) {
+  wx.request({
+    url: 'https://api.chinadatapay.com/communication/personal/1882', // 实名认证
+    dataType: 'json',
+    method: 'POST',
+    data: {
+      key: key,
+      name: name,
+      idcard: idcard,
+      membershipkey: membershipKey
+    },
+    header: {
+      'content-type': 'application/x-www-form-urlencoded'
+    },
+    success(res) {
+      const data = res.data
+      //执行回调
+      return typeof callback == "function" && callback(data);
+    }
+  })
+}
+function idcardOCR(membershipKey, key, tempFilePath, callback) {
+  this.uploadImage(key, tempFilePath, function (result) {
+    var imageId = result.data;
+    wx.request({
+      url: 'https://api.chinadatapay.com/trade/user/1985', // 身份证OCR识别
+      dataType: 'json',
+      method: 'POST',
+      data: {
+        imageId: imageId,
+        key: key,
+        membershipkey: membershipKey
+      },
+      header: {
+        'content-type': 'application/x-www-form-urlencoded'
+      },
+      success(res) {
+        const data = res.data;
+        //执行回调
+        return typeof callback == "function" && callback(data);
+      }
+    })
+  })
+}

+ 28 - 0
src/components/HelloWorld.vue

@@ -0,0 +1,28 @@
+<template>
+    <div class="hello">
+        <h1 :style="'color:' + color">{{ msg }}</h1>
+    </div>
+</template>
+
+<script>
+export default {
+    name: 'HelloWorld',
+    props: {
+        color: String,
+        msg: {
+            default: 'Welcome to Megalo'
+        }
+    },
+    data() {
+        return {};
+    }
+};
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+.hello {
+    font-size: 16px;
+    text-align: center;
+}
+</style>

+ 112 - 0
src/main.js

@@ -0,0 +1,112 @@
+import App from './App';
+import Vue from 'vue';
+import VHtmlPlugin from '@megalo/vhtml-plugin';
+import Vuex from 'vuex';
+import http from './plugins/http';
+import Dialog from './vant/dialog/dialog';
+Vue.prototype.$colors = {
+    prim: '#149A9A'
+};
+Vue.use(VHtmlPlugin);
+Vue.use(Vuex);
+Vue.use(http);
+Vue.prototype.$dialog = Dialog;
+
+const store = require('./store').default;
+Vue.prototype.$store = store;
+Vue.mixin({
+    methods: {
+        checkLogin() {
+            if (this.$store.state.userInfo && this.$store.state.userInfo.login) {
+                return true;
+            }
+            this.showLoginDialog = true;
+            return false;
+        },
+        share() {
+            return {
+                title: '捷途JETOUR',
+                path: 'pages/home',
+                imageUrl: 'https://mr-yimishijie.oss-cn-hangzhou.aliyuncs.com/share.png'
+            };
+        }
+    }
+});
+
+const app = new Vue(App);
+
+app.$mount();
+
+export default {
+    config: {
+        // pages 的首个页面会被编译成首页
+        pages: ['pages/home', 'pages/car', 'pages/tuxiang', 'pages/my'],
+        tabBar: {
+            color: '#6F6E77',
+            selectedColor: '#149A9A',
+            backgroundColor: '#ffffff',
+            list: [
+                {
+                    pagePath: 'pages/home',
+                    text: '首页',
+                    iconPath: 'native/imgs/tab_home.png',
+                    selectedIconPath: 'native/imgs/tab_home_pre.png'
+                },
+                {
+                    pagePath: 'pages/car',
+                    text: '购车',
+                    iconPath: 'native/imgs/tab_car.png',
+                    selectedIconPath: 'native/imgs/tab_car_pre.png'
+                },
+                {
+                    pagePath: 'pages/tuxiang',
+                    text: '途享',
+                    iconPath: 'native/imgs/tab_msg.png',
+                    selectedIconPath: 'native/imgs/tab_msg_pre.png'
+                },
+                {
+                    pagePath: 'pages/my',
+                    text: '我的',
+                    iconPath: 'native/imgs/tab_my.png',
+                    selectedIconPath: 'native/imgs/tab_my_pre.png'
+                }
+            ]
+        },
+        window: {
+            backgroundTextStyle: 'light',
+            navigationBarBackgroundColor: '#fff',
+            navigationBarTitleText: '捷途JETOUR',
+            navigationBarTextStyle: 'black'
+        },
+        usingComponents: {
+            'van-button': '/vant/button/index',
+            'van-tabs': '/vant/tabs/index',
+            'van-tab': '/vant/tab/index',
+            'van-notice-bar': '/vant/notice-bar/index',
+            'van-icon': '/vant/icon/index',
+            'van-grid-item ': '/vant/grid-item/index',
+            'van-dialog': '/vant/dialog/index',
+            'van-search': '/vant/search/index',
+            'van-empty': '/vant/empty/index',
+            'van-sticky': '/vant/sticky/index',
+            'van-loading': '/vant/loading/index',
+            'van-action-sheet': '/vant/action-sheet/index',
+            'van-picker': '/vant/picker/index',
+            'van-cell-group': '/vant/cell-group/index',
+            'van-cell': '/vant/cell/index',
+            'van-field': '/vant/field/index',
+            'van-uploader': '/vant/uploader/index',
+            'van-checkbox': '/vant/checkbox/index',
+            'van-panel': '/vant/panel/index',
+            'van-overlay': '/vant/overlay/index',
+            'van-stepper': '/vant/stepper/index',
+            'van-steps': '/vant/steps/index',
+            'van-rate': '/vant/rate/index'
+        },
+        permission: {
+            'scope.userLocation': {
+                desc: '需要获取你的位置来查询附近景区'
+            }
+        }
+    }
+};

BIN
src/native/imgs/icon_dot.png


BIN
src/native/imgs/icon_location.png


BIN
src/native/imgs/icon_picker.png


BIN
src/native/imgs/tab_car.png


BIN
src/native/imgs/tab_car_pre.png


BIN
src/native/imgs/tab_home.png


BIN
src/native/imgs/tab_home_pre.png


BIN
src/native/imgs/tab_msg.png


BIN
src/native/imgs/tab_msg_pre.png


BIN
src/native/imgs/tab_my.png


BIN
src/native/imgs/tab_my_pre.png


+ 11 - 0
src/pages/car.vue

@@ -0,0 +1,11 @@
+<template>
+    <div></div>
+</template>
+<script>
+export default {
+    data() {
+        return {};
+    }
+};
+</script>
+<style lang="less"></style>

+ 353 - 0
src/pages/home.vue

@@ -0,0 +1,353 @@
+<config>
+{
+    "backgroundColor": "#ffffff"
+}
+</config>
+<template>
+    <div>
+        <swiper
+            class="swiper"
+            :autoplay="true"
+            :circular="true"
+            :indicator-dots="banners.length > 1"
+            indicator-color="rgba(255,255,255,0.3)"
+            indicator-active-color="#ffffff"
+        >
+            <swiper-item class="swiper-item" v-for="item in banners" :key="item.id">
+                <img class="swiper-item-img" :src="item.carouselImage" mode="aspectFill" />
+            </swiper-item>
+        </swiper>
+        <div class="menus">
+            <div class="menu-item">
+                <img class="icon" src="../static/imgs/home_tool_ico_1.png" />
+                <div class="name">电话咨询</div>
+            </div>
+            <div class="menu-item">
+                <img class="icon" src="../static/imgs/home_tool_ico_2.png" />
+                <div class="name">经销商查询</div>
+            </div>
+            <div class="menu-item">
+                <img class="icon" src="../static/imgs/home_tool_ico_3.png" />
+                <div class="name">预约试驾</div>
+            </div>
+            <div class="menu-item">
+                <img class="icon" src="../static/imgs/home_tool_ico_4.png" />
+                <div class="name">金融计算器</div>
+            </div>
+        </div>
+        <div v-if="event" class="event">
+            <div class="title">
+                活动推荐
+            </div>
+            <div class="event-body">
+                <img :src="event.thumbnail" mode="aspectFill" />
+                <div class="info">
+                    <div class="desc">{{ event.description }}</div>
+                    <div class="time-btn">
+                        <div class="time">{{ event.updateTime.split(' ')[0] }}</div>
+                        <div class="btn">立即参与</div>
+                    </div>
+                </div>
+            </div>
+        </div>
+        <div class="car-model" style="width:100%;">
+            <div class="title">
+                精品车型
+            </div>
+            <scroll-view
+                class="tabs"
+                scroll-x="true"
+                style="width:100%;"
+                id="scrollView"
+                :scroll-into-view="scrollTo"
+                scroll-with-animation="true"
+            >
+                <div
+                    v-for="(item, i) in carModels"
+                    :key="item.skuId"
+                    class="tab"
+                    :id="'id' + item.skuId"
+                    :class="{ active: tabIndex === i }"
+                    @click="chooseTab(i)"
+                >
+                    {{ item.name }}
+                </div>
+                <div style="width:15px;min-width:15px;display:inline-block;">&nbsp;</div>
+            </scroll-view>
+            <swiper class="car-swiper" next-margin="40px" @change="onCarSwiperChange" :current="tabIndex">
+                <swiper-item v-for="item in carModels" :key="item.skuId" class="car-swiper-item">
+                    <div class="container">
+                        <img :src="item.appImgurl" mode="aspectFill" class="cover" />
+                        <div class="info">
+                            <div class="col1">
+                                <div class="name">{{ item.name }}</div>
+                                <div class="desc">{{ item.desc }}</div>
+                            </div>
+                            <div class="col2">
+                                <div class="price">
+                                    指导价:<span style="font-size:14px">{{ item.price }}</span
+                                    >万起
+                                </div>
+                                <div class="btn">{{ item.button }}</div>
+                            </div>
+                        </div>
+                    </div>
+                </swiper-item>
+            </swiper>
+        </div>
+    </div>
+</template>
+<script>
+export default {
+    data() {
+        return {
+            carModels: [],
+            banners: [],
+            event: null,
+            tabIndex: 0,
+            scrollTo: ''
+        };
+    },
+    created() {
+        this.$http.get('/applets/mobile_querycarindexcarousellist').then(res => {
+            this.banners = res.data;
+        });
+        this.$http.get('/applets/carShowQueryAll').then(res => {
+            this.carModels = res.data.map(i => JSON.parse(i.columnValue));
+            console.log(this.carModels);
+        });
+        this.$http
+            .get('/applets/activeQueryId/5')
+            .then(res => {
+                if (res.data && res.data.columnValue) {
+                    console.log('eventId::', res);
+                    // eslint-disable-next-line prettier/prettier
+                    return this.$http.get(
+                        `https://www.jetour.com.cn/cmsapi/business/Article/get/${res.data.columnValue}`
+                    );
+                } else {
+                    return Promise.reject();
+                }
+            })
+            .then(res => {
+                console.log('event::', res);
+                this.event = res.data;
+            })
+            .catch(e => {
+                console.log(e);
+            });
+    },
+    methods: {
+        chooseTab(i) {
+            wx.pageScrollTo({
+                selector: this.carModels[i].skuId,
+                duration: 300
+            });
+            this.scrollTo = 'id' + this.carModels[i].skuId;
+            this.tabIndex = i;
+            console.log('id' + this.carModels[i].skuId);
+        },
+        onCarSwiperChange(e) {
+            console.log(e);
+            let i = e.detail.current;
+            this.scrollTo = 'id' + this.carModels[i].skuId;
+            this.tabIndex = i;
+        }
+    }
+};
+</script>
+<style lang="less">
+.swiper {
+    height: 225px;
+    width: 100%;
+    .swiper-item {
+        height: 225px;
+        width: 100%;
+        .swiper-item-img {
+            height: 225px;
+            width: 100%;
+        }
+    }
+}
+.menus {
+    .flex();
+    justify-content: space-around;
+    height: 96px;
+    background: white;
+    border-bottom: 10px solid @bg;
+    .menu-item {
+        .flex-col();
+        align-items: center;
+        .icon {
+            width: 45px;
+            height: 45px;
+            background: @prim;
+            border-radius: 50%;
+        }
+        .name {
+            font-size: 12px;
+            color: @text1;
+            margin-top: 8px;
+        }
+    }
+}
+
+.event {
+    .event-body {
+        margin: 0 20px;
+        .flex();
+        padding-bottom: 10px;
+        border-bottom: 1px solid @border1;
+        img {
+            width: 136px;
+            height: 93px;
+            border-radius: 12px;
+        }
+        .info {
+            height: 93px;
+            box-sizing: border-box;
+            flex-basis: 0;
+            flex-grow: 1;
+            margin-left: 15px;
+            .flex-col();
+            padding-top: 14px;
+            padding-bottom: 11px;
+            justify-content: space-between;
+            .desc {
+                line-height: 20px;
+                font-size: 13px;
+                color: @text1;
+            }
+            .time-btn {
+                .flex();
+                .time {
+                    color: @text3;
+                    font-size: 11px;
+                    flex-grow: 1;
+                }
+                .btn {
+                    width: 80px;
+                    height: 24px;
+                    border-radius: 12px;
+                    .flex();
+                    background: @prim;
+                    justify-content: center;
+                    font-size: 13px;
+                    font-weight: bold;
+                    color: white;
+                }
+            }
+        }
+    }
+}
+
+.car-model {
+    .tabs {
+        width: 100%;
+        padding-bottom: 15px;
+        overflow: hidden;
+        white-space: nowrap;
+        padding-left: 20px;
+        box-sizing: border-box;
+        .tab {
+            margin-left: 5px;
+            height: 22px;
+            line-height: 22px;
+            color: @text2;
+            font-size: 13px;
+            padding: 0 10px;
+            width: auto;
+            display: inline-block;
+            &.active {
+                color: white;
+                background: @prim;
+                border-radius: 4px;
+            }
+        }
+    }
+    .car-swiper {
+        height: 283px;
+        .car-swiper-item {
+            padding-left: 20px;
+            box-sizing: border-box;
+            .container {
+                height: 256px;
+                background: #ffffff;
+                box-shadow: 0px 2px 13px 0px rgba(0, 0, 0, 0.13);
+                border-radius: 10px;
+                overflow: hidden;
+                .flex-col();
+                .cover {
+                    width: 100%;
+                    height: 186px;
+                    border-radius: 6px 6px 0px 0px;
+                }
+                .info {
+                    height: 70px;
+                    flex-grow: 1;
+                    .flex();
+                    padding-left: 20px;
+                    padding-right: 10px;
+                    .col1 {
+                        .flex-col();
+                        justify-content: center;
+                        flex-grow: 1;
+                        .name {
+                            font-size: 14px;
+                            color: @text1;
+                            font-weight: bold;
+                            line-height: 14px;
+                        }
+                        .desc {
+                            margin-top: 12px;
+                            font-weight: 300;
+                            font-size: 12px;
+                            color: black;
+                            line-height: 12px;
+                        }
+                    }
+                    .col2 {
+                        .flex-col();
+                        align-items: center;
+                        .price {
+                            color: @text2;
+                            font-size: 10px;
+                            line-height: 10px;
+                        }
+                        .btn {
+                            margin-top: 6px;
+                            width: 110px;
+                            height: 26px;
+                            border-radius: 13px;
+                            color: white;
+                            font-size: 13px;
+                            font-weight: bold;
+                            background: @prim;
+                            .flex();
+                            justify-content: center;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+.title {
+    padding: 16px 20px 16px 28px;
+    position: relative;
+    font-size: 16px;
+    font-weight: bold;
+    &::before {
+        content: '';
+        position: absolute;
+        top: 0;
+        bottom: 0;
+        left: 20px;
+        width: 3px;
+        height: 20px;
+        background: @prim;
+        border-radius: 2px;
+        margin: auto;
+    }
+}
+</style>

+ 71 - 0
src/pages/my.vue

@@ -0,0 +1,71 @@
+<template>
+    <div>
+        <div class="top">
+            <img src="../static/imgs/user.png" class="avatar" />
+            <div class="name">点击登录</div>
+        </div>
+        <div class="cell">
+            <div class="title">COUPE订单</div>
+            <div class="desc"></div>
+        </div>
+        <div class="cell">
+            <div class="title">其他订单</div>
+            <div class="desc"></div>
+        </div>
+        <div class="cell">
+            <div class="title">收货地址管理</div>
+            <div class="desc"></div>
+        </div>
+        <div class="cell" @click="call">
+            <div class="title">联系我们</div>
+            <div class="desc">400-086-8888</div>
+        </div>
+    </div>
+</template>
+<script>
+export default {
+    data() {
+        return {};
+    },
+    methods: {
+        call() {
+            wx.makePhoneCall({
+                phoneNumber: '4000868888'
+            });
+        }
+    }
+};
+</script>
+<style lang="less">
+.top {
+    height: 190px;
+    margin: 0 24px;
+    .flex-col();
+    justify-content: center;
+    align-items: center;
+    border-bottom: 1px solid @border3;
+    .avatar {
+        width: 75px;
+        height: 75px;
+    }
+    .name {
+        font-size: 14px;
+        color: @text3;
+        margin-top: 27px;
+    }
+}
+.cell {
+    height: 58px;
+    .flex();
+    margin: 0 24px;
+    .title {
+        font-size: 14px;
+        flex-grow: 1;
+        color: @text1;
+    }
+    .desc {
+        font-size: 14px;
+        color: @text3;
+    }
+}
+</style>

+ 11 - 0
src/pages/tuxiang.vue

@@ -0,0 +1,11 @@
+<template>
+    <div></div>
+</template>
+<script>
+export default {
+    data() {
+        return {};
+    }
+};
+</script>
+<style lang="less"></style>

+ 137 - 0
src/plugins/http.js

@@ -0,0 +1,137 @@
+const baseUrl = process.env.VUE_APP_BASE_URL;
+function parseUrl(url) {
+    let _baseUrl = baseUrl;
+    if (url.startsWith('http')) {
+        return url;
+    }
+    if (!_baseUrl.endsWith('/')) {
+        _baseUrl += '/';
+    }
+    if (url.startsWith('/')) {
+        url = url.slice(1);
+    }
+    return _baseUrl + url;
+}
+const http = {
+    parseUrl: parseUrl,
+    setToken(token) {
+        wx.setStorageSync('token', token);
+        this.token = token;
+    },
+    clearToken() {
+        this.token = '';
+        wx.removeStorageSync('token');
+        console.log('clear token');
+    },
+    getToken() {
+        if (!this.token) {
+            try {
+                this.token = wx.getStorageSync('token');
+            } catch (e) {}
+        }
+        return this.token;
+    },
+    get(url, params, options) {
+        options = options || {};
+        return new Promise((resolve, reject) => {
+            wx.request({
+                method: 'GET',
+                url: parseUrl(url),
+                data: params,
+                dataType: 'json',
+                header: {
+                    Accept: 'application/json',
+                    Authorization: this.getToken() ? 'Bearer ' + this.getToken() : 'Bearer ',
+                    ...(options.header || {})
+                },
+                success(res) {
+                    if (res && res.statusCode === 200) {
+                        resolve(res.data);
+                    } else {
+                        reject(res.data || res);
+                    }
+                },
+                fail(err) {
+                    reject(err.data || err);
+                }
+            });
+        });
+    },
+    post(url, data, options) {
+        options = options || {};
+        return new Promise((resolve, reject) => {
+            wx.request({
+                method: 'post',
+                url: parseUrl(url),
+                data: data,
+                dataType: 'json',
+                header: {
+                    Accept: 'application/json',
+                    'content-type': 'application/x-www-form-urlencoded',
+                    Authorization: this.getToken() ? 'Bearer ' + this.getToken() : '',
+                    ...(options.header || {})
+                },
+                success(res) {
+                    if (res && res.statusCode === 200) {
+                        resolve(res.data);
+                    } else {
+                        reject(res.data || res);
+                    }
+                },
+                fail(err) {
+                    reject(err.data || err);
+                }
+            });
+        });
+    },
+    uploadFile(filePath, options) {
+        options = options || {};
+        return new Promise((resolve, reject) => {
+            wx.uploadFile({
+                url: baseUrl + '/upload/file',
+                filePath: filePath,
+                name: 'file',
+                header: {
+                    Accept: 'application/json',
+                    'content-type': 'application/x-www-form-urlencoded',
+                    Authorization: this.getToken() ? 'Bearer ' + this.getToken() : '',
+                    ...(options.header || {})
+                },
+                formData: options.formData || {},
+                success(res) {
+                    if (res && res.statusCode === 200) {
+                        try {
+                            resolve(JSON.parse(res.data));
+                        } catch (e) {
+                            reject(e);
+                        }
+                    } else {
+                        reject(res);
+                    }
+                },
+                fail(err) {
+                    reject(err);
+                }
+            });
+        });
+    }
+};
+export default {
+    http: http,
+    install(_Vue) {
+        _Vue.prototype.$baseUrl = baseUrl;
+        _Vue.prototype.$http = http;
+        _Vue.prototype.$request = options => {
+            options = options || {};
+            options.url = parseUrl(options.url);
+            return new Promise((resolve, reject) => {
+                options.success = res => {
+                    resolve(res);
+                };
+                options.success = err => {
+                    reject(err);
+                };
+            });
+        };
+    }
+};

BIN
src/static/imgs/home_tool_ico_1.png


BIN
src/static/imgs/home_tool_ico_2.png


BIN
src/static/imgs/home_tool_ico_3.png


BIN
src/static/imgs/home_tool_ico_4.png


BIN
src/static/imgs/icon_into.png


BIN
src/static/imgs/tap_car_nor.png


BIN
src/static/imgs/tap_car_sel.png


BIN
src/static/imgs/tap_home_nor.png


BIN
src/static/imgs/tap_home_sel.png


BIN
src/static/imgs/tap_me_nor.png


BIN
src/static/imgs/tap_me_sel.png


BIN
src/static/imgs/tap_tour_nor.png


BIN
src/static/imgs/tap_tour_sel.png


BIN
src/static/imgs/user.png


+ 36 - 0
src/store/index.js

@@ -0,0 +1,36 @@
+import Vuex from 'vuex';
+import http from '../plugins/http';
+export default new Vuex.Store({
+    state: {
+        sessionKey: '',
+        safeAreaTop: 0,
+        safeAreaBottom: 0,
+        userInfo: null,
+        loginInfo: null
+    },
+    mutations: {
+        setSafeAreaTop(state, val) {
+            state.safeAreaTop = val;
+        },
+        setSafeAreaBottom(state, val) {
+            state.safeAreaBottom = val;
+        },
+        setUserInfo(state, userInfo) {
+            state.userInfo = userInfo;
+        },
+        setSessionKey(state, sessionKey) {
+            state.sessionKey = sessionKey;
+        },
+        setLoginInfo(state, loginInfo) {
+            state.loginInfo = loginInfo;
+        }
+    },
+    actions: {
+        getUserInfo(context) {
+            http.http.get('/user/my').then(res => {
+                console.log(res);
+                context.commit('setUserInfo', res);
+            });
+        }
+    }
+});

+ 21 - 0
src/styles/common.less

@@ -0,0 +1,21 @@
+@prim: #149a9a;
+@success: #07c160;
+@danger: #ee0a24;
+@warn: #ff976a;
+@text1: #303133;
+@text2: #606266;
+@text3: #909399;
+@text4: #c0c4cc;
+@border1: #dcdfe6;
+@border2: #e4e7ed;
+@border3: #ebeef5;
+@border4: #f2f6fc;
+@bg: #f2f4f5;
+.flex() {
+    display: flex;
+    align-items: center;
+}
+.flex-col() {
+    display: flex;
+    flex-direction: column;
+}

+ 1 - 0
src/vant/action-sheet/index.d.ts

@@ -0,0 +1 @@
+export {};

+ 62 - 0
src/vant/action-sheet/index.js

@@ -0,0 +1,62 @@
+import { VantComponent } from '../common/component';
+import { button } from '../mixins/button';
+import { openType } from '../mixins/open-type';
+VantComponent({
+  mixins: [button, openType],
+  props: {
+    show: Boolean,
+    title: String,
+    cancelText: String,
+    description: String,
+    round: {
+      type: Boolean,
+      value: true,
+    },
+    zIndex: {
+      type: Number,
+      value: 100,
+    },
+    actions: {
+      type: Array,
+      value: [],
+    },
+    overlay: {
+      type: Boolean,
+      value: true,
+    },
+    closeOnClickOverlay: {
+      type: Boolean,
+      value: true,
+    },
+    closeOnClickAction: {
+      type: Boolean,
+      value: true,
+    },
+    safeAreaInsetBottom: {
+      type: Boolean,
+      value: true,
+    },
+  },
+  methods: {
+    onSelect(event) {
+      const { index } = event.currentTarget.dataset;
+      const item = this.data.actions[index];
+      if (item && !item.disabled && !item.loading) {
+        this.$emit('select', item);
+        if (this.data.closeOnClickAction) {
+          this.onClose();
+        }
+      }
+    },
+    onCancel() {
+      this.$emit('cancel');
+    },
+    onClose() {
+      this.$emit('close');
+    },
+    onClickOverlay() {
+      this.$emit('click-overlay');
+      this.onClose();
+    },
+  },
+});

+ 8 - 0
src/vant/action-sheet/index.json

@@ -0,0 +1,8 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-icon": "../icon/index",
+    "van-popup": "../popup/index",
+    "van-loading": "../loading/index"
+  }
+}

+ 67 - 0
src/vant/action-sheet/index.wxml

@@ -0,0 +1,67 @@
+<wxs src="../wxs/utils.wxs" module="utils" />
+
+<van-popup
+  show="{{ show }}"
+  position="bottom"
+  round="{{ round }}"
+  z-index="{{ zIndex }}"
+  overlay="{{ overlay }}"
+  custom-class="van-action-sheet"
+  safe-area-inset-bottom="{{ safeAreaInsetBottom }}"
+  close-on-click-overlay="{{ closeOnClickOverlay }}"
+  bind:close="onClickOverlay"
+>
+  <view wx:if="{{ title }}" class="van-hairline--bottom van-action-sheet__header">
+    {{ title }}
+    <van-icon
+      name="close"
+      custom-class="van-action-sheet__close"
+      bind:click="onClose"
+    />
+  </view>
+  <view wx:if="{{ description }}" class="van-action-sheet__description">
+    {{ description }}
+  </view>
+  <view wx:if="{{ actions && actions.length }}">
+    <!-- button外包一层view,防止actions动态变化,导致渲染时button被打散 -->
+    <button
+      wx:for="{{ actions }}"
+      wx:key="index"
+      open-type="{{ item.openType }}"
+      style="{{ item.color ? 'color: ' + item.color : '' }}"
+      class="{{ utils.bem('action-sheet__item', { disabled: item.disabled || item.loading }) }} van-hairline--top {{ item.className || '' }}"
+      hover-class="van-action-sheet__item--hover"
+      data-index="{{ index }}"
+      bind:tap="onSelect"
+      bindgetuserinfo="bindGetUserInfo"
+      bindcontact="bindContact"
+      bindgetphonenumber="bindGetPhoneNumber"
+      binderror="bindError"
+      bindlaunchapp="bindLaunchApp"
+      bindopensetting="bindOpenSetting"
+      lang="{{ lang }}"
+      session-from="{{ sessionFrom }}"
+      send-message-title="{{ sendMessageTitle }}"
+      send-message-path="{{ sendMessagePath }}"
+      send-message-img="{{ sendMessageImg }}"
+      show-message-card="{{ showMessageCard }}"
+      app-parameter="{{ appParameter }}"
+    >
+      <block wx:if="{{ !item.loading }}">
+        {{ item.name }}
+        <text wx:if="{{ item.subname }}" class="van-action-sheet__subname" >{{ item.subname }}</text>
+      </block>
+      <van-loading wx:else custom-class="van-action-sheet__loading" size="20px" />
+    </button>
+  </view>
+  <slot />
+  <view
+    wx:if="{{ cancelText }}"
+    class="van-action-sheet__cancel"
+    hover-class="van-action-sheet__cancel--hover"
+    hover-stay-time="70"
+    bind:tap="onCancel"
+  >
+    {{ cancelText }}
+  </view>
+</van-popup>

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/vant/action-sheet/index.wxss


+ 1 - 0
src/vant/area/index.d.ts

@@ -0,0 +1 @@
+export {};

+ 229 - 0
src/vant/area/index.js

@@ -0,0 +1,229 @@
+import { VantComponent } from '../common/component';
+import { pickerProps } from '../picker/shared';
+const COLUMNSPLACEHOLDERCODE = '000000';
+VantComponent({
+  classes: ['active-class', 'toolbar-class', 'column-class'],
+  props: Object.assign(Object.assign({}, pickerProps), {
+    value: {
+      type: String,
+      observer(value) {
+        this.code = value;
+        this.setValues();
+      },
+    },
+    areaList: {
+      type: Object,
+      value: {},
+      observer: 'setValues',
+    },
+    columnsNum: {
+      type: null,
+      value: 3,
+      observer(value) {
+        this.setData({
+          displayColumns: this.data.columns.slice(0, +value),
+        });
+      },
+    },
+    columnsPlaceholder: {
+      type: Array,
+      observer(val) {
+        this.setData({
+          typeToColumnsPlaceholder: {
+            province: val[0] || '',
+            city: val[1] || '',
+            county: val[2] || '',
+          },
+        });
+      },
+    },
+  }),
+  data: {
+    columns: [{ values: [] }, { values: [] }, { values: [] }],
+    displayColumns: [{ values: [] }, { values: [] }, { values: [] }],
+    typeToColumnsPlaceholder: {},
+  },
+  mounted() {
+    setTimeout(() => {
+      this.setValues();
+    }, 0);
+  },
+  methods: {
+    getPicker() {
+      if (this.picker == null) {
+        this.picker = this.selectComponent('.van-area__picker');
+      }
+      return this.picker;
+    },
+    onCancel(event) {
+      this.emit('cancel', event.detail);
+    },
+    onConfirm(event) {
+      const { index } = event.detail;
+      let { value } = event.detail;
+      value = this.parseOutputValues(value);
+      this.emit('confirm', { value, index });
+    },
+    emit(type, detail) {
+      detail.values = detail.value;
+      delete detail.value;
+      this.$emit(type, detail);
+    },
+    // parse output columns data
+    parseOutputValues(values) {
+      const { columnsPlaceholder } = this.data;
+      return values.map((value, index) => {
+        // save undefined value
+        if (!value) return value;
+        value = JSON.parse(JSON.stringify(value));
+        if (!value.code || value.name === columnsPlaceholder[index]) {
+          value.code = '';
+          value.name = '';
+        }
+        return value;
+      });
+    },
+    onChange(event) {
+      const { index, picker, value } = event.detail;
+      this.code = value[index].code;
+      this.setValues().then(() => {
+        this.$emit('change', {
+          picker,
+          values: this.parseOutputValues(picker.getValues()),
+          index,
+        });
+      });
+    },
+    getConfig(type) {
+      const { areaList } = this.data;
+      return (areaList && areaList[`${type}_list`]) || {};
+    },
+    getList(type, code) {
+      const { typeToColumnsPlaceholder } = this.data;
+      let result = [];
+      if (type !== 'province' && !code) {
+        return result;
+      }
+      const list = this.getConfig(type);
+      result = Object.keys(list).map((code) => ({
+        code,
+        name: list[code],
+      }));
+      if (code) {
+        // oversea code
+        if (code[0] === '9' && type === 'city') {
+          code = '9';
+        }
+        result = result.filter((item) => item.code.indexOf(code) === 0);
+      }
+      if (typeToColumnsPlaceholder[type] && result.length) {
+        // set columns placeholder
+        const codeFill =
+          type === 'province'
+            ? ''
+            : type === 'city'
+            ? COLUMNSPLACEHOLDERCODE.slice(2, 4)
+            : COLUMNSPLACEHOLDERCODE.slice(4, 6);
+        result.unshift({
+          code: `${code}${codeFill}`,
+          name: typeToColumnsPlaceholder[type],
+        });
+      }
+      return result;
+    },
+    getIndex(type, code) {
+      let compareNum = type === 'province' ? 2 : type === 'city' ? 4 : 6;
+      const list = this.getList(type, code.slice(0, compareNum - 2));
+      // oversea code
+      if (code[0] === '9' && type === 'province') {
+        compareNum = 1;
+      }
+      code = code.slice(0, compareNum);
+      for (let i = 0; i < list.length; i++) {
+        if (list[i].code.slice(0, compareNum) === code) {
+          return i;
+        }
+      }
+      return 0;
+    },
+    setValues() {
+      const county = this.getConfig('county');
+      let { code } = this;
+      if (!code) {
+        if (this.data.columnsPlaceholder.length) {
+          code = COLUMNSPLACEHOLDERCODE;
+        } else if (Object.keys(county)[0]) {
+          code = Object.keys(county)[0];
+        } else {
+          code = '';
+        }
+      }
+      const province = this.getList('province');
+      const city = this.getList('city', code.slice(0, 2));
+      const picker = this.getPicker();
+      if (!picker) {
+        return;
+      }
+      const stack = [];
+      const indexes = [];
+      const { columnsNum } = this.data;
+      if (columnsNum >= 1) {
+        stack.push(picker.setColumnValues(0, province, false));
+        indexes.push(this.getIndex('province', code));
+      }
+      if (columnsNum >= 2) {
+        stack.push(picker.setColumnValues(1, city, false));
+        indexes.push(this.getIndex('city', code));
+        if (city.length && code.slice(2, 4) === '00') {
+          [{ code }] = city;
+        }
+      }
+      if (columnsNum === 3) {
+        stack.push(
+          picker.setColumnValues(
+            2,
+            this.getList('county', code.slice(0, 4)),
+            false
+          )
+        );
+        indexes.push(this.getIndex('county', code));
+      }
+      return Promise.all(stack)
+        .catch(() => {})
+        .then(() => picker.setIndexes(indexes))
+        .catch(() => {});
+    },
+    getValues() {
+      const picker = this.getPicker();
+      return picker ? picker.getValues().filter((value) => !!value) : [];
+    },
+    getDetail() {
+      const values = this.getValues();
+      const area = {
+        code: '',
+        country: '',
+        province: '',
+        city: '',
+        county: '',
+      };
+      if (!values.length) {
+        return area;
+      }
+      const names = values.map((item) => item.name);
+      area.code = values[values.length - 1].code;
+      if (area.code[0] === '9') {
+        area.country = names[1] || '';
+        area.province = names[2] || '';
+      } else {
+        area.province = names[0] || '';
+        area.city = names[1] || '';
+        area.county = names[2] || '';
+      }
+      return area;
+    },
+    reset(code) {
+      this.code = code || '';
+      return this.setValues();
+    },
+  },
+});

+ 6 - 0
src/vant/area/index.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-picker": "../picker/index"
+  }
+}

+ 18 - 0
src/vant/area/index.wxml

@@ -0,0 +1,18 @@
+<van-picker
+  class="van-area__picker"
+  active-class="active-class"
+  toolbar-class="toolbar-class"
+  column-class="column-class"
+  show-toolbar
+  value-key="name"
+  title="{{ title }}"
+  loading="{{ loading }}"
+  columns="{{ displayColumns }}"
+  item-height="{{ itemHeight }}"
+  visible-item-count="{{ visibleItemCount }}"
+  cancel-button-text="{{ cancelButtonText }}"
+  confirm-button-text="{{ confirmButtonText }}"
+  bind:change="onChange"
+  bind:confirm="onConfirm"
+  bind:cancel="onCancel"
+/>

+ 1 - 0
src/vant/area/index.wxss

@@ -0,0 +1 @@
+@import '../common/index.wxss';

+ 1 - 0
src/vant/button/index.d.ts

@@ -0,0 +1 @@
+export {};

+ 79 - 0
src/vant/button/index.js

@@ -0,0 +1,79 @@
+import { VantComponent } from '../common/component';
+import { button } from '../mixins/button';
+import { openType } from '../mixins/open-type';
+import { canIUseFormFieldButton } from '../common/version';
+const mixins = [button, openType];
+if (canIUseFormFieldButton()) {
+  mixins.push('wx://form-field-button');
+}
+VantComponent({
+  mixins,
+  classes: ['hover-class', 'loading-class'],
+  data: {
+    baseStyle: '',
+  },
+  props: {
+    formType: String,
+    icon: String,
+    classPrefix: {
+      type: String,
+      value: 'van-icon',
+    },
+    plain: Boolean,
+    block: Boolean,
+    round: Boolean,
+    square: Boolean,
+    loading: Boolean,
+    hairline: Boolean,
+    disabled: Boolean,
+    loadingText: String,
+    customStyle: String,
+    loadingType: {
+      type: String,
+      value: 'circular',
+    },
+    type: {
+      type: String,
+      value: 'default',
+    },
+    dataset: null,
+    size: {
+      type: String,
+      value: 'normal',
+    },
+    loadingSize: {
+      type: String,
+      value: '20px',
+    },
+    color: {
+      type: String,
+      observer(color) {
+        let style = '';
+        if (color) {
+          style += `color: ${this.data.plain ? color : 'white'};`;
+          if (!this.data.plain) {
+            // Use background instead of backgroundColor to make linear-gradient work
+            style += `background: ${color};`;
+          }
+          // hide border when color is linear-gradient
+          if (color.indexOf('gradient') !== -1) {
+            style += 'border: 0;';
+          } else {
+            style += `border-color: ${color};`;
+          }
+        }
+        if (style !== this.data.baseStyle) {
+          this.setData({ baseStyle: style });
+        }
+      },
+    },
+  },
+  methods: {
+    onClick() {
+      if (!this.data.loading) {
+        this.$emit('click');
+      }
+    },
+    noop() {},
+  },
+});

+ 7 - 0
src/vant/button/index.json

@@ -0,0 +1,7 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-icon": "../icon/index",
+    "van-loading": "../loading/index"
+  }
+}

+ 68 - 0
src/vant/button/index.wxml

@@ -0,0 +1,68 @@
+<wxs src="../wxs/utils.wxs" module="utils" />
+
+<button
+  id="{{ id }}"
+  data-detail="{{ dataset }}"
+  class="custom-class {{ utils.bem('button', [type, size, { block, round, plain, square, loading, disabled, hairline, unclickable: disabled || loading }]) }} {{ hairline ? 'van-hairline--surround' : '' }}"
+  hover-class="van-button--active hover-class"
+  lang="{{ lang }}"
+  form-type="{{ formType }}"
+  style="{{ baseStyle }} {{ customStyle }}"
+  open-type="{{ disabled ? '' : openType }}"
+  business-id="{{ businessId }}"
+  session-from="{{ sessionFrom }}"
+  send-message-title="{{ sendMessageTitle }}"
+  send-message-path="{{ sendMessagePath }}"
+  send-message-img="{{ sendMessageImg }}"
+  show-message-card="{{ showMessageCard }}"
+  app-parameter="{{ appParameter }}"
+  aria-label="{{ ariaLabel }}"
+  bindtap="{{ !disabled ? 'onClick' : 'noop' }}"
+  bindgetuserinfo="bindGetUserInfo"
+  bindcontact="bindContact"
+  bindgetphonenumber="bindGetPhoneNumber"
+  binderror="bindError"
+  bindlaunchapp="bindLaunchApp"
+  bindopensetting="bindOpenSetting"
+>
+  <block wx:if="{{ loading }}">
+    <van-loading
+      custom-class="loading-class"
+      size="{{ loadingSize }}"
+      type="{{ loadingType }}"
+      color="{{ loadingColor(type,color,plain) }}"
+    />
+    <view wx:if="{{ loadingText }}" class="van-button__loading-text">
+      {{ loadingText }}
+    </view>
+  </block>
+  <block wx:else>
+    <van-icon
+      wx:if="{{ icon }}"
+      size="1.2em"
+      name="{{ icon }}"
+      class-prefix="{{ classPrefix }}"
+      class="van-button__icon"
+      custom-style="line-height: inherit;"
+    />
+    <view class="van-button__text">
+      <slot />
+    </view>
+  </block>
+</button>
+
+
+<wxs module="loadingColor">
+function get(type, color,plain) {
+  if(plain) {
+    return color ? color: '#c9c9c9';
+  }
+
+  if(type === 'default') {
+    return '#c9c9c9';
+  }
+  return 'white';
+}
+
+module.exports = get;
+</wxs>

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/vant/button/index.wxss


+ 57 - 0
src/vant/calendar/calendar.wxml

@@ -0,0 +1,57 @@
+<wxs src="./index.wxs" module="computed"></wxs>
+
+<template name="calendar">
+  <view class="van-calendar">
+    <header
+      title="{{ title }}"
+      showTitle="{{ showTitle }}"
+      subtitle="{{ subtitle }}"
+      showSubtitle="{{ showSubtitle }}"
+    >
+      <slot name="title" slot="title"></slot>
+    </header>
+
+    <scroll-view class="van-calendar__body" scroll-y scroll-into-view="{{ scrollIntoView }}">
+      <month
+        wx:for="{{ computed.getMonths(minDate, maxDate) }}"
+        wx:key="index"
+        id="month{{ index }}"
+        class="month"
+        data-date="{{ item }}"
+        date="{{ item }}"
+        type="{{ type }}"
+        color="{{ color }}"
+        minDate="{{ minDate }}"
+        maxDate="{{ maxDate }}"
+        showMark="{{ showMark }}"
+        formatter="{{ formatter }}"
+        rowHeight="{{ rowHeight }}"
+        currentDate="{{ currentDate }}"
+        showSubtitle="{{ showSubtitle }}"
+        allowSameDay="{{ allowSameDay }}"
+        showMonthTitle="{{ index !== 0 || !showSubtitle }}"
+        bind:click="onClickDay"
+      />
+    </scroll-view>
+
+    <view class="van-calendar__footer {{ safeAreaInsetBottom ? 'van-calendar__footer--safe-area-inset-bottom' : '' }}">
+      <slot name="footer"></slot>
+    </view>
+
+    <view class="van-calendar__footer {{ safeAreaInsetBottom ? 'van-calendar__footer--safe-area-inset-bottom' : '' }}">
+      <van-button
+        wx:if="{{ showConfirm }}"
+        round
+        block
+        type="danger"
+        color="{{ color }}"
+        custom-class="van-calendar__confirm"
+        disabled="{{ computed.getButtonDisabled(type, currentDate) }}"
+        nativeType="text"
+        bind:click="onConfirm"
+      >
+        {{ computed.getButtonDisabled(type, currentDate) ? confirmDisabledText : confirmText }}
+      </van-button>
+    </view>
+  </view>
+</template>

+ 1 - 0
src/vant/calendar/components/header/index.d.ts

@@ -0,0 +1 @@
+export {};

+ 16 - 0
src/vant/calendar/components/header/index.js

@@ -0,0 +1,16 @@
+import { VantComponent } from '../../../common/component';
+VantComponent({
+  props: {
+    title: {
+      type: String,
+      value: '日期选择',
+    },
+    subtitle: String,
+    showTitle: Boolean,
+    showSubtitle: Boolean,
+  },
+  data: {
+    weekdays: ['日', '一', '二', '三', '四', '五', '六'],
+  },
+  methods: {},
+});

+ 3 - 0
src/vant/calendar/components/header/index.json

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

+ 16 - 0
src/vant/calendar/components/header/index.wxml

@@ -0,0 +1,16 @@
+<view class="van-calendar__header">
+  <block wx:if="{{ showTitle }}">
+    <view class="van-calendar__header-title"><slot name="title"></slot></view>
+    <view class="van-calendar__header-title">{{ title }}</view>
+  </block>
+
+  <view wx:if="{{ showSubtitle }}" class="van-calendar__header-subtitle">
+    {{ subtitle }}
+  </view>
+
+  <view class="van-calendar__weekdays">
+    <view wx:for="{{ weekdays }}" wx:key="index" class="van-calendar__weekday">
+      {{ item }}
+    </view>
+  </view>
+</view>

+ 1 - 0
src/vant/calendar/components/header/index.wxss

@@ -0,0 +1 @@
+@import '../../../common/index.wxss';.van-calendar__header{-webkit-flex-shrink:0;flex-shrink:0;box-shadow:0 2px 10px rgba(125,126,128,.16);box-shadow:var(--calendar-header-box-shadow,0 2px 10px rgba(125,126,128,.16))}.van-calendar__header-subtitle,.van-calendar__header-title{text-align:center;height:44px;height:var(--calendar-header-title-height,44px);font-weight:500;font-weight:var(--font-weight-bold,500);line-height:44px;line-height:var(--calendar-header-title-height,44px)}.van-calendar__header-title+.van-calendar__header-title,.van-calendar__header-title:empty{display:none}.van-calendar__header-title:empty+.van-calendar__header-title{display:block!important}.van-calendar__weekdays{display:-webkit-flex;display:flex}.van-calendar__weekday{-webkit-flex:1;flex:1;text-align:center;font-size:12px;font-size:var(--calendar-weekdays-font-size,12px);line-height:30px;line-height:var(--calendar-weekdays-height,30px)}

+ 1 - 0
src/vant/calendar/components/month/index.d.ts

@@ -0,0 +1 @@
+export {};

+ 157 - 0
src/vant/calendar/components/month/index.js

@@ -0,0 +1,157 @@
+import { VantComponent } from '../../../common/component';
+import {
+  getMonthEndDay,
+  compareDay,
+  getPrevDay,
+  getNextDay,
+} from '../../utils';
+VantComponent({
+  props: {
+    date: {
+      type: null,
+      observer: 'setDays',
+    },
+    type: {
+      type: String,
+      observer: 'setDays',
+    },
+    color: String,
+    minDate: {
+      type: null,
+      observer: 'setDays',
+    },
+    maxDate: {
+      type: null,
+      observer: 'setDays',
+    },
+    showMark: Boolean,
+    rowHeight: [Number, String],
+    formatter: {
+      type: null,
+      observer: 'setDays',
+    },
+    currentDate: {
+      type: [null, Array],
+      observer: 'setDays',
+    },
+    allowSameDay: Boolean,
+    showSubtitle: Boolean,
+    showMonthTitle: Boolean,
+  },
+  data: {
+    visible: true,
+    days: [],
+  },
+  methods: {
+    onClick(event) {
+      const { index } = event.currentTarget.dataset;
+      const item = this.data.days[index];
+      if (item.type !== 'disabled') {
+        this.$emit('click', item);
+      }
+    },
+    setDays() {
+      const days = [];
+      const startDate = new Date(this.data.date);
+      const year = startDate.getFullYear();
+      const month = startDate.getMonth();
+      const totalDay = getMonthEndDay(
+        startDate.getFullYear(),
+        startDate.getMonth() + 1
+      );
+      for (let day = 1; day <= totalDay; day++) {
+        const date = new Date(year, month, day);
+        const type = this.getDayType(date);
+        let config = {
+          date,
+          type,
+          text: day,
+          bottomInfo: this.getBottomInfo(type),
+        };
+        if (this.data.formatter) {
+          config = this.data.formatter(config);
+        }
+        days.push(config);
+      }
+      this.setData({ days });
+    },
+    getMultipleDayType(day) {
+      const { currentDate } = this.data;
+      if (!Array.isArray(currentDate)) {
+        return '';
+      }
+      const isSelected = (date) =>
+        currentDate.some((item) => compareDay(item, date) === 0);
+      if (isSelected(day)) {
+        const prevDay = getPrevDay(day);
+        const nextDay = getNextDay(day);
+        const prevSelected = isSelected(prevDay);
+        const nextSelected = isSelected(nextDay);
+        if (prevSelected && nextSelected) {
+          return 'multiple-middle';
+        }
+        if (prevSelected) {
+          return 'end';
+        }
+        return nextSelected ? 'start' : 'multiple-selected';
+      }
+      return '';
+    },
+    getRangeDayType(day) {
+      const { currentDate, allowSameDay } = this.data;
+      if (!Array.isArray(currentDate)) {
+        return;
+      }
+      const [startDay, endDay] = currentDate;
+      if (!startDay) {
+        return;
+      }
+      const compareToStart = compareDay(day, startDay);
+      if (!endDay) {
+        return compareToStart === 0 ? 'start' : '';
+      }
+      const compareToEnd = compareDay(day, endDay);
+      if (compareToStart === 0 && compareToEnd === 0 && allowSameDay) {
+        return 'start-end';
+      }
+      if (compareToStart === 0) {
+        return 'start';
+      }
+      if (compareToEnd === 0) {
+        return 'end';
+      }
+      if (compareToStart > 0 && compareToEnd < 0) {
+        return 'middle';
+      }
+    },
+    getDayType(day) {
+      const { type, minDate, maxDate, currentDate } = this.data;
+      if (compareDay(day, minDate) < 0 || compareDay(day, maxDate) > 0) {
+        return 'disabled';
+      }
+      if (type === 'single') {
+        return compareDay(day, currentDate) === 0 ? 'selected' : '';
+      }
+      if (type === 'multiple') {
+        return this.getMultipleDayType(day);
+      }
+      /* istanbul ignore else */
+      if (type === 'range') {
+        return this.getRangeDayType(day);
+      }
+    },
+    getBottomInfo(type) {
+      if (this.data.type === 'range') {
+        if (type === 'start') {
+          return '开始';
+        }
+        if (type === 'end') {
+          return '结束';
+        }
+        if (type === 'start-end') {
+          return '开始/结束';
+        }
+      }
+    },
+  },
+});

+ 3 - 0
src/vant/calendar/components/month/index.json

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

+ 39 - 0
src/vant/calendar/components/month/index.wxml

@@ -0,0 +1,39 @@
+<wxs src="./index.wxs" module="computed"></wxs>
+<wxs src="../../../wxs/utils.wxs" module="utils" />
+
+<view class="van-calendar__month" style="{{ computed.getMonthStyle(visible, date, rowHeight) }}">
+  <view wx:if="{{ showMonthTitle }}" class="van-calendar__month-title">
+    {{ computed.formatMonthTitle(date) }}
+  </view>
+
+  <view wx:if="{{ visible }}" class="van-calendar__days">
+    <view wx:if="{{ showMark }}" class="van-calendar__month-mark">
+      {{ computed.getMark(date) }}
+    </view>
+
+    <view
+      wx:for="{{ days }}"
+      wx:key="index"
+      style="{{ computed.getDayStyle(item.type, index, date, rowHeight, color) }}"
+      class="{{ utils.bem('calendar__day', [item.type]) }} {{ item.className }}"
+      data-index="{{ index }}"
+      bindtap="onClick"
+    >
+      <view wx:if="{{ item.type === 'selected' }}" class="van-calendar__selected-day" style="background: {{ color }}">
+        <view wx:if="{{ item.topInfo }}" class="van-calendar__top-info">{{ item.topInfo }}</view>
+        {{ item.text }}
+        <view wx:if="{{ item.bottomInfo }}" class="van-calendar__bottom-info">
+          {{ item.bottomInfo }}
+        </view>
+      </view>
+
+      <view wx:else>
+        <view wx:if="{{ item.topInfo }}" class="van-calendar__top-info">{{ item.topInfo }}</view>
+        {{ item.text }}
+        <view wx:if="{{ item.bottomInfo }}" class="van-calendar__bottom-info">
+          {{ item.bottomInfo }}
+        </view>
+      </view>
+    </view>
+  </view>
+</view>

+ 67 - 0
src/vant/calendar/components/month/index.wxs

@@ -0,0 +1,67 @@
+/* eslint-disable */
+var utils = require('../../utils.wxs');
+
+function getMark(date) {
+  return getDate(date).getMonth() + 1;
+}
+
+var ROW_HEIGHT = 64;
+
+function getDayStyle(type, index, date, rowHeight, color) {
+  var style = [];
+  var offset = getDate(date).getDay();
+
+  if (index === 0) {
+    style.push(['margin-left', (100 * offset) / 7 + '%']);
+  }
+
+  if (rowHeight !== ROW_HEIGHT) {
+    style.push(['height', rowHeight + 'px']);
+  }
+
+  if (color) {
+    if (
+      type === 'start' ||
+      type === 'end' ||
+      type === 'multiple-selected' ||
+      type === 'multiple-middle'
+    ) {
+      style.push(['background', color]);
+    } else if (type === 'middle') {
+      style.push(['color', color]);
+    }
+  }
+
+  return style
+    .map(function(item) {
+      return item.join(':');
+    })
+    .join(';');
+}
+
+function formatMonthTitle(date) {
+  date = getDate(date);
+  return date.getFullYear() + '年' + (date.getMonth() + 1) + '月';
+}
+
+function getMonthStyle(visible, date, rowHeight) {
+  if (!visible) {
+    date = getDate(date);
+
+    var totalDay = utils.getMonthEndDay(
+      date.getFullYear(),
+      date.getMonth() + 1
+    );
+    var offset = getDate(date).getDay();
+    var padding = Math.ceil((totalDay + offset) / 7) * rowHeight;
+
+    return 'padding-bottom:' + padding + 'px';
+  }
+}
+
+module.exports = {
+  getMark: getMark,
+  getDayStyle: getDayStyle,
+  formatMonthTitle: formatMonthTitle,
+  getMonthStyle: getMonthStyle
+};

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/vant/calendar/components/month/index.wxss


+ 1 - 0
src/vant/calendar/index.d.ts

@@ -0,0 +1 @@
+export {};

+ 290 - 0
src/vant/calendar/index.js

@@ -0,0 +1,290 @@
+import { VantComponent } from '../common/component';
+import {
+  ROW_HEIGHT,
+  getNextDay,
+  compareDay,
+  copyDates,
+  calcDateNum,
+  formatMonthTitle,
+  compareMonth,
+  getMonths,
+  getDayByOffset,
+} from './utils';
+import Toast from '../toast/toast';
+VantComponent({
+  props: {
+    title: {
+      type: String,
+      value: '日期选择',
+    },
+    color: String,
+    show: {
+      type: Boolean,
+      observer(val) {
+        if (val) {
+          this.initRect();
+          this.scrollIntoView();
+        }
+      },
+    },
+    formatter: null,
+    confirmText: {
+      type: String,
+      value: '确定',
+    },
+    rangePrompt: String,
+    defaultDate: {
+      type: [Number, Array],
+      observer(val) {
+        this.setData({ currentDate: val });
+        this.scrollIntoView();
+      },
+    },
+    allowSameDay: Boolean,
+    confirmDisabledText: String,
+    type: {
+      type: String,
+      value: 'single',
+      observer: 'reset',
+    },
+    minDate: {
+      type: null,
+      value: Date.now(),
+    },
+    maxDate: {
+      type: null,
+      value: new Date(
+        new Date().getFullYear(),
+        new Date().getMonth() + 6,
+        new Date().getDate()
+      ).getTime(),
+    },
+    position: {
+      type: String,
+      value: 'bottom',
+    },
+    rowHeight: {
+      type: [Number, String],
+      value: ROW_HEIGHT,
+    },
+    round: {
+      type: Boolean,
+      value: true,
+    },
+    poppable: {
+      type: Boolean,
+      value: true,
+    },
+    showMark: {
+      type: Boolean,
+      value: true,
+    },
+    showTitle: {
+      type: Boolean,
+      value: true,
+    },
+    showConfirm: {
+      type: Boolean,
+      value: true,
+    },
+    showSubtitle: {
+      type: Boolean,
+      value: true,
+    },
+    safeAreaInsetBottom: {
+      type: Boolean,
+      value: true,
+    },
+    closeOnClickOverlay: {
+      type: Boolean,
+      value: true,
+    },
+    maxRange: {
+      type: [Number, String],
+      value: null,
+    },
+  },
+  data: {
+    subtitle: '',
+    currentDate: null,
+    scrollIntoView: '',
+  },
+  created() {
+    this.setData({
+      currentDate: this.getInitialDate(),
+    });
+  },
+  mounted() {
+    if (this.data.show || !this.data.poppable) {
+      this.initRect();
+      this.scrollIntoView();
+    }
+  },
+  methods: {
+    reset() {
+      this.setData({ currentDate: this.getInitialDate() });
+      this.scrollIntoView();
+    },
+    initRect() {
+      if (this.contentObserver != null) {
+        this.contentObserver.disconnect();
+      }
+      const contentObserver = this.createIntersectionObserver({
+        thresholds: [0, 0.1, 0.9, 1],
+        observeAll: true,
+      });
+      this.contentObserver = contentObserver;
+      contentObserver.relativeTo('.van-calendar__body');
+      contentObserver.observe('.month', (res) => {
+        if (res.boundingClientRect.top <= res.relativeRect.top) {
+          // @ts-ignore
+          this.setData({ subtitle: formatMonthTitle(res.dataset.date) });
+        }
+      });
+    },
+    getInitialDate() {
+      const { type, defaultDate, minDate } = this.data;
+      if (type === 'range') {
+        const [startDay, endDay] = defaultDate || [];
+        return [
+          startDay || minDate,
+          endDay || getNextDay(new Date(minDate)).getTime(),
+        ];
+      }
+      if (type === 'multiple') {
+        return defaultDate || [minDate];
+      }
+      return defaultDate || minDate;
+    },
+    scrollIntoView() {
+      setTimeout(() => {
+        const {
+          currentDate,
+          type,
+          show,
+          poppable,
+          minDate,
+          maxDate,
+        } = this.data;
+        const targetDate = type === 'single' ? currentDate : currentDate[0];
+        const displayed = show || !poppable;
+        if (!targetDate || !displayed) {
+          return;
+        }
+        const months = getMonths(minDate, maxDate);
+        months.some((month, index) => {
+          if (compareMonth(month, targetDate) === 0) {
+            this.setData({ scrollIntoView: `month${index}` });
+            return true;
+          }
+          return false;
+        });
+      }, 100);
+    },
+    onOpen() {
+      this.$emit('open');
+    },
+    onOpened() {
+      this.$emit('opened');
+    },
+    onClose() {
+      this.$emit('close');
+    },
+    onClosed() {
+      this.$emit('closed');
+    },
+    onClickDay(event) {
+      const { date } = event.detail;
+      const { type, currentDate, allowSameDay } = this.data;
+      if (type === 'range') {
+        const [startDay, endDay] = currentDate;
+        if (startDay && !endDay) {
+          const compareToStart = compareDay(date, startDay);
+          if (compareToStart === 1) {
+            this.select([startDay, date], true);
+          } else if (compareToStart === -1) {
+            this.select([date, null]);
+          } else if (allowSameDay) {
+            this.select([date, date]);
+          }
+        } else {
+          this.select([date, null]);
+        }
+      } else if (type === 'multiple') {
+        let selectedIndex;
+        const selected = currentDate.some((dateItem, index) => {
+          const equal = compareDay(dateItem, date) === 0;
+          if (equal) {
+            selectedIndex = index;
+          }
+          return equal;
+        });
+        if (selected) {
+          const cancelDate = currentDate.splice(selectedIndex, 1);
+          this.setData({ currentDate });
+          this.unselect(cancelDate);
+        } else {
+          this.select([...currentDate, date]);
+        }
+      } else {
+        this.select(date, true);
+      }
+    },
+    unselect(dateArray) {
+      const date = dateArray[0];
+      if (date) {
+        this.$emit('unselect', copyDates(date));
+      }
+    },
+    select(date, complete) {
+      if (complete && this.data.type === 'range') {
+        const valid = this.checkRange(date);
+        if (!valid) {
+          // auto selected to max range if showConfirm
+          if (this.data.showConfirm) {
+            this.emit([
+              date[0],
+              getDayByOffset(date[0], this.data.maxRange - 1),
+            ]);
+          } else {
+            this.emit(date);
+          }
+          return;
+        }
+      }
+      this.emit(date);
+      if (complete && !this.data.showConfirm) {
+        this.onConfirm();
+      }
+    },
+    emit(date) {
+      const getTime = (date) => (date instanceof Date ? date.getTime() : date);
+      this.setData({
+        currentDate: Array.isArray(date) ? date.map(getTime) : getTime(date),
+      });
+      this.$emit('select', copyDates(date));
+    },
+    checkRange(date) {
+      const { maxRange, rangePrompt } = this.data;
+      if (maxRange && calcDateNum(date) > maxRange) {
+        Toast({
+          context: this,
+          message: rangePrompt || `选择天数不能超过 ${maxRange} 天`,
+        });
+        return false;
+      }
+      return true;
+    },
+    onConfirm() {
+      if (
+        this.data.type === 'range' &&
+        !this.checkRange(this.data.currentDate)
+      ) {
+        return;
+      }
+      wx.nextTick(() => {
+        this.$emit('confirm', copyDates(this.data.currentDate));
+      });
+    },
+  },
+});

+ 10 - 0
src/vant/calendar/index.json

@@ -0,0 +1,10 @@
+{
+  "component": true,
+  "usingComponents": {
+    "header": "./components/header/index",
+    "month": "./components/month/index",
+    "van-button": "../button/index",
+    "van-popup": "../popup/index",
+    "van-toast": "../toast/index"
+  }
+}

+ 31 - 0
src/vant/calendar/index.wxml

@@ -0,0 +1,31 @@
+<wxs src="./index.wxs" module="computed" />
+
+<import src="./calendar.wxml" />
+
+<van-popup
+  wx:if="{{ poppable }}"
+  custom-class="van-calendar__popup--{{ position }}"
+  close-icon-class="van-calendar__close-icon"
+  show="{{ show }}"
+  round="{{ round }}"
+  position="{{ position }}"
+  closeable="{{ showTitle || showSubtitle }}"
+  close-on-click-overlay="{{ closeOnClickOverlay }}"
+  bind:enter="onOpen"
+  bind:close="onClose"
+  bind:after-enter="onOpened"
+  bind:after-leave="onClosed"
+>
+  <template
+    is="calendar"
+    data="{{ title, subtitle, showTitle, showSubtitle, minDate, maxDate, type, color, showMark, formatter, rowHeight, currentDate, safeAreaInsetBottom, showConfirm, confirmDisabledText, confirmText, scrollIntoView, allowSameDay }}"
+  />
+</van-popup>
+
+<template
+  wx:else
+  is="calendar"
+  data="{{ title, subtitle, showTitle, showSubtitle, minDate, maxDate, type, color, showMark, formatter, rowHeight, currentDate, safeAreaInsetBottom, showConfirm, confirmDisabledText, confirmText, scrollIntoView, allowSameDay }}"
+/>
+
+<van-toast id="van-toast" />

+ 37 - 0
src/vant/calendar/index.wxs

@@ -0,0 +1,37 @@
+/* eslint-disable */
+var utils = require('./utils.wxs');
+
+function getMonths(minDate, maxDate) {
+  var months = [];
+  var cursor = getDate(minDate);
+
+  cursor.setDate(1);
+
+  do {
+    months.push(cursor.getTime());
+    cursor.setMonth(cursor.getMonth() + 1);
+  } while (utils.compareMonth(cursor, getDate(maxDate)) !== 1);
+
+  return months;
+}
+
+function getButtonDisabled(type, currentDate) {
+  if (currentDate == null) {
+    return true;
+  }
+
+  if (type === 'range') {
+    return !currentDate[0] || !currentDate[1];
+  }
+
+  if (type === 'multiple') {
+    return !currentDate.length;
+  }
+
+  return !currentDate;
+}
+
+module.exports = {
+  getMonths: getMonths,
+  getButtonDisabled: getButtonDisabled
+};

+ 1 - 0
src/vant/calendar/index.wxss

@@ -0,0 +1 @@
+@import '../common/index.wxss';.van-calendar{display:-webkit-flex;display:flex;-webkit-flex-direction:column;flex-direction:column;height:100%;height:var(--calendar-height,100%);background-color:#fff;background-color:var(--calendar-background-color,#fff)}.van-calendar__close-icon{top:11px}.van-calendar__popup--bottom,.van-calendar__popup--top{height:80%;height:var(--calendar-popup-height,80%)}.van-calendar__popup--left,.van-calendar__popup--right{height:100%}.van-calendar__body{-webkit-flex:1;flex:1;overflow:auto;-webkit-overflow-scrolling:touch}.van-calendar__footer{-webkit-flex-shrink:0;flex-shrink:0;padding:0 16px;padding:0 var(--padding-md,16px)}.van-calendar__footer--safe-area-inset-bottom{padding-bottom:env(safe-area-inset-bottom)}.van-calendar__footer+.van-calendar__footer,.van-calendar__footer:empty{display:none}.van-calendar__footer:empty+.van-calendar__footer{display:block!important}.van-calendar__confirm{height:36px!important;height:var(--calendar-confirm-button-height,36px)!important;margin:7px 0!important;margin:var(--calendar-confirm-button-margin,7px 0)!important;line-height:34px!important;line-height:var(--calendar-confirm-button-line-height,34px)!important}

+ 17 - 0
src/vant/calendar/utils.d.ts

@@ -0,0 +1,17 @@
+export declare const ROW_HEIGHT = 64;
+export declare function formatMonthTitle(date: Date): string;
+export declare function compareMonth(
+  date1: Date | number,
+  date2: Date | number
+): 0 | 1 | -1;
+export declare function compareDay(
+  day1: Date | number,
+  day2: Date | number
+): 0 | 1 | -1;
+export declare function getDayByOffset(date: Date, offset: number): Date;
+export declare function getPrevDay(date: Date): Date;
+export declare function getNextDay(date: Date): Date;
+export declare function calcDateNum(date: [Date, Date]): number;
+export declare function copyDates(dates: Date | Date[]): Date | Date[];
+export declare function getMonthEndDay(year: number, month: number): number;
+export declare function getMonths(minDate: number, maxDate: number): any[];

+ 78 - 0
src/vant/calendar/utils.js

@@ -0,0 +1,78 @@
+export const ROW_HEIGHT = 64;
+export function formatMonthTitle(date) {
+  if (!(date instanceof Date)) {
+    date = new Date(date);
+  }
+  return `${date.getFullYear()}年${date.getMonth() + 1}月`;
+}
+export function compareMonth(date1, date2) {
+  if (!(date1 instanceof Date)) {
+    date1 = new Date(date1);
+  }
+  if (!(date2 instanceof Date)) {
+    date2 = new Date(date2);
+  }
+  const year1 = date1.getFullYear();
+  const year2 = date2.getFullYear();
+  const month1 = date1.getMonth();
+  const month2 = date2.getMonth();
+  if (year1 === year2) {
+    return month1 === month2 ? 0 : month1 > month2 ? 1 : -1;
+  }
+  return year1 > year2 ? 1 : -1;
+}
+export function compareDay(day1, day2) {
+  if (!(day1 instanceof Date)) {
+    day1 = new Date(day1);
+  }
+  if (!(day2 instanceof Date)) {
+    day2 = new Date(day2);
+  }
+  const compareMonthResult = compareMonth(day1, day2);
+  if (compareMonthResult === 0) {
+    const date1 = day1.getDate();
+    const date2 = day2.getDate();
+    return date1 === date2 ? 0 : date1 > date2 ? 1 : -1;
+  }
+  return compareMonthResult;
+}
+export function getDayByOffset(date, offset) {
+  date = new Date(date);
+  date.setDate(date.getDate() + offset);
+  return date;
+}
+export function getPrevDay(date) {
+  return getDayByOffset(date, -1);
+}
+export function getNextDay(date) {
+  return getDayByOffset(date, 1);
+}
+export function calcDateNum(date) {
+  const day1 = new Date(date[0]).getTime();
+  const day2 = new Date(date[1]).getTime();
+  return (day2 - day1) / (1000 * 60 * 60 * 24) + 1;
+}
+export function copyDates(dates) {
+  if (Array.isArray(dates)) {
+    return dates.map((date) => {
+      if (date === null) {
+        return date;
+      }
+      return new Date(date);
+    });
+  }
+  return new Date(dates);
+}
+export function getMonthEndDay(year, month) {
+  return 32 - new Date(year, month - 1, 32).getDate();
+}
+export function getMonths(minDate, maxDate) {
+  const months = [];
+  const cursor = new Date(minDate);
+  cursor.setDate(1);
+  do {
+    months.push(cursor.getTime());
+    cursor.setMonth(cursor.getMonth() + 1);
+  } while (compareMonth(cursor, maxDate) !== 1);
+  return months;
+}

+ 25 - 0
src/vant/calendar/utils.wxs

@@ -0,0 +1,25 @@
+/* eslint-disable */
+function getMonthEndDay(year, month) {
+  return 32 -  getDate(year, month - 1, 32).getDate();
+}
+
+function compareMonth(date1, date2) {
+  date1 = getDate(date1);
+  date2 = getDate(date2);
+
+  var year1 = date1.getFullYear();
+  var year2 = date2.getFullYear();
+  var month1 = date1.getMonth();
+  var month2 = date2.getMonth();
+
+  if (year1 === year2) {
+    return month1 === month2 ? 0 : month1 > month2 ? 1 : -1;
+  }
+
+  return year1 > year2 ? 1 : -1;
+}
+
+module.exports = {
+  getMonthEndDay: getMonthEndDay,
+  compareMonth: compareMonth
+};

+ 1 - 0
src/vant/card/index.d.ts

@@ -0,0 +1 @@
+export {};

+ 49 - 0
src/vant/card/index.js

@@ -0,0 +1,49 @@
+import { link } from '../mixins/link';
+import { VantComponent } from '../common/component';
+VantComponent({
+  classes: [
+    'num-class',
+    'desc-class',
+    'thumb-class',
+    'title-class',
+    'price-class',
+    'origin-price-class',
+  ],
+  mixins: [link],
+  props: {
+    tag: String,
+    num: String,
+    desc: String,
+    thumb: String,
+    title: String,
+    price: {
+      type: String,
+      observer: 'updatePrice',
+    },
+    centered: Boolean,
+    lazyLoad: Boolean,
+    thumbLink: String,
+    originPrice: String,
+    thumbMode: {
+      type: String,
+      value: 'aspectFit',
+    },
+    currency: {
+      type: String,
+      value: '¥',
+    },
+  },
+  methods: {
+    updatePrice() {
+      const { price } = this.data;
+      const priceArr = price.toString().split('.');
+      this.setData({
+        integerStr: priceArr[0],
+        decimalStr: priceArr[1] ? `.${priceArr[1]}` : '',
+      });
+    },
+    onClickThumb() {
+      this.jumpLink('thumbLink');
+    },
+  },
+});

+ 6 - 0
src/vant/card/index.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-tag": "../tag/index"
+  }
+}

+ 50 - 0
src/vant/card/index.wxml

@@ -0,0 +1,50 @@
+<wxs src="../wxs/utils.wxs" module="utils" />
+
+<view class="custom-class van-card">
+  <view class="{{ utils.bem('card__header', { center: centered }) }}">
+    <view class="van-card__thumb" bind:tap="onClickThumb">
+      <image
+        wx:if="{{ thumb }}"
+        src="{{ thumb }}"
+        mode="{{ thumbMode }}"
+        lazy-load="{{ lazyLoad }}"
+        class="van-card__img thumb-class"
+      />
+      <slot name="thumb" />
+      <van-tag
+        wx:if="{{ tag }}"
+        mark
+        type="danger"
+        custom-class="van-card__tag"
+      >
+        {{ tag }}
+      </van-tag>
+    </view>
+
+    <view class="van-card__content {{ utils.bem('card__content', { center: centered }) }}">
+      <view>
+        <view wx:if="{{ title }}" class="van-card__title title-class">{{ title }}</view>
+        <slot wx:else name="title" />
+
+        <view wx:if="{{ desc }}" class="van-card__desc desc-class">{{ desc }}</view>
+        <slot wx:else name="desc" />
+
+        <slot name="tags" />
+      </view>
+
+      <view class="van-card__bottom">
+        <slot name="price-top" />
+        <view wx:if="{{ price || price === 0 }}" class="van-card__price price-class">{{ currency }}<text class="van-card__price-integer">{{ integerStr }}</text><text class="van-card__price-decimal">{{ decimalStr }}</text></view>
+        <slot wx:else name="price" />
+        <view wx:if="{{ originPrice || originPrice === 0 }}" class="van-card__origin-price origin-price-class">{{ currency }} {{ originPrice }}</view>
+        <view wx:if="{{ num }}" class="van-card__num num-class">x {{ num }}</view>
+        <slot wx:else  name="num" />
+        <slot name="bottom" />
+      </view>
+    </view>
+  </view>
+
+  <view class="van-card__footer">
+    <slot name="footer" />
+  </view>
+</view>

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/vant/card/index.wxss


+ 1 - 0
src/vant/cell-group/index.d.ts

@@ -0,0 +1 @@
+export {};

+ 10 - 0
src/vant/cell-group/index.js

@@ -0,0 +1,10 @@
+import { VantComponent } from '../common/component';
+VantComponent({
+  props: {
+    title: String,
+    border: {
+      type: Boolean,
+      value: true,
+    },
+  },
+});

+ 3 - 0
src/vant/cell-group/index.json

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

+ 9 - 0
src/vant/cell-group/index.wxml

@@ -0,0 +1,9 @@
+<view
+  wx:if="{{ title }}"
+  class="van-cell-group__title"
+>
+  {{ title }}
+</view>
+<view class="custom-class van-cell-group {{ border ? 'van-hairline--top-bottom' : '' }}">
+  <slot />
+</view>

+ 1 - 0
src/vant/cell-group/index.wxss

@@ -0,0 +1 @@
+@import '../common/index.wxss';.van-cell-group__title{padding:16px 16px 8px;padding:var(--cell-group-title-padding,16px 16px 8px);font-size:14px;font-size:var(--cell-group-title-font-size,14px);line-height:16px;line-height:var(--cell-group-title-line-height,16px);color:#969799;color:var(--cell-group-title-color,#969799)}

+ 1 - 0
src/vant/cell/index.d.ts

@@ -0,0 +1 @@
+export {};

+ 38 - 0
src/vant/cell/index.js

@@ -0,0 +1,38 @@
+import { link } from '../mixins/link';
+import { VantComponent } from '../common/component';
+VantComponent({
+  classes: [
+    'title-class',
+    'label-class',
+    'value-class',
+    'right-icon-class',
+    'hover-class',
+  ],
+  mixins: [link],
+  props: {
+    title: null,
+    value: null,
+    icon: String,
+    size: String,
+    label: String,
+    center: Boolean,
+    isLink: Boolean,
+    required: Boolean,
+    clickable: Boolean,
+    titleWidth: String,
+    customStyle: String,
+    arrowDirection: String,
+    useLabelSlot: Boolean,
+    border: {
+      type: Boolean,
+      value: true,
+    },
+    titleStyle: String,
+  },
+  methods: {
+    onClick(event) {
+      this.$emit('click', event.detail);
+      this.jumpLink();
+    },
+  },
+});

+ 6 - 0
src/vant/cell/index.json

@@ -0,0 +1,6 @@
+{
+  "component": true,
+  "usingComponents": {
+    "van-icon": "../icon/index"
+  }
+}

+ 45 - 0
src/vant/cell/index.wxml

@@ -0,0 +1,45 @@
+<wxs src="../wxs/utils.wxs" module="utils" />
+
+<view
+  class="custom-class {{ utils.bem('cell', [size, { center, required, borderless: !border, clickable: isLink || clickable }]) }}"
+  hover-class="van-cell--hover hover-class"
+  hover-stay-time="70"
+  style="{{ customStyle }}"
+  bind:tap="onClick"
+>
+  <van-icon
+    wx:if="{{ icon }}"
+    name="{{ icon }}"
+    class="van-cell__left-icon-wrap"
+    custom-class="van-cell__left-icon"
+  />
+  <slot wx:else name="icon" />
+
+  <view
+    style="{{ (titleWidth ? 'max-width:' + titleWidth + ';min-width:' + titleWidth + ';' : '') + titleStyle }}"
+    class="van-cell__title title-class"
+  >
+    <block wx:if="{{ title }}">{{ title }}</block>
+    <slot wx:else name="title" />
+
+    <view wx:if="{{ label || useLabelSlot }}" class="van-cell__label label-class">
+      <slot wx:if="{{ useLabelSlot }}" name="label" />
+      <block wx:elif="{{ label }}">{{ label }}</block>
+    </view>
+  </view>
+
+  <view class="van-cell__value value-class">
+    <block wx:if="{{ value || value === 0 }}">{{ value }}</block>
+    <slot wx:else />
+  </view>
+
+  <van-icon
+    wx:if="{{ isLink }}"
+    name="{{ arrowDirection ? 'arrow' + '-' + arrowDirection : 'arrow' }}"
+    class="van-cell__right-icon-wrap right-icon-class"
+    custom-class="van-cell__right-icon"
+  />
+  <slot wx:else name="right-icon" />
+
+  <slot name="extra" />
+</view>

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
src/vant/cell/index.wxss


+ 1 - 0
src/vant/checkbox-group/index.d.ts

@@ -0,0 +1 @@
+export {};

Деякі файли не було показано, через те що забагато файлів було змінено