jinxyang 5 lat temu
rodzic
commit
b4df47e523

+ 2 - 0
.browserslistrc

@@ -0,0 +1,2 @@
+last 2 versions
+> 1%

+ 18 - 0
.eslintrc.js

@@ -0,0 +1,18 @@
+
+module.exports = {
+  parser: 'vue-eslint-parser',
+  parserOptions: {
+    parser: 'babel-eslint',
+  },
+  extends: [
+    'standard',
+  ],
+  globals: {
+    Vue: 'readonly',
+    VueRouter: 'readonly',
+    Vuex: 'readonly',
+  },
+  rules: {
+    'comma-dangle': ['error', 'always-multiline'],
+  },
+}

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+dist
+node_modules

+ 13 - 0
babel.config.js

@@ -0,0 +1,13 @@
+
+module.exports = api => {
+  api.cache(true)
+
+  return {
+    presets: [
+      '@babel/preset-env',
+    ],
+    plugins: [
+      '@babel/plugin-transform-runtime',
+    ],
+  }
+}

Plik diff jest za duży
+ 10583 - 0
package-lock.json


+ 51 - 0
package.json

@@ -0,0 +1,51 @@
+{
+  "name": "trace-h5",
+  "version": "1.0.0",
+  "private": true,
+  "scripts": {
+    "start": "node scripts/server.js",
+    "build": "node scripts/build.js"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.7.4",
+    "@babel/plugin-transform-runtime": "^7.7.4",
+    "@babel/preset-env": "^7.7.4",
+    "@babel/runtime": "^7.7.4",
+    "autoprefixer": "^9.7.2",
+    "babel-eslint": "^10.0.3",
+    "babel-loader": "^8.0.6",
+    "chalk": "^3.0.0",
+    "connect-history-api-fallback": "^1.6.0",
+    "css-loader": "^3.2.0",
+    "eslint": "^6.7.1",
+    "eslint-config-standard": "^14.1.0",
+    "eslint-loader": "^3.0.2",
+    "eslint-plugin-import": "^2.18.2",
+    "eslint-plugin-node": "^10.0.0",
+    "eslint-plugin-promise": "^4.2.1",
+    "eslint-plugin-standard": "^4.0.1",
+    "express": "^4.17.1",
+    "fibers": "^4.0.2",
+    "file-loader": "^5.0.2",
+    "friendly-errors-webpack-plugin": "^1.7.0",
+    "html-webpack-plugin": "^3.2.0",
+    "ip": "^1.1.5",
+    "node-sass": "^4.13.0",
+    "postcss-loader": "^3.0.0",
+    "postcss-preset-env": "^6.7.0",
+    "sass": "^1.23.7",
+    "sass-loader": "^8.0.0",
+    "style-loader": "^1.0.0",
+    "vue-eslint-parser": "^7.0.0",
+    "vue-loader": "^15.7.2",
+    "vue-template-compiler": "^2.6.10",
+    "webpack": "^4.41.2",
+    "webpack-cli": "^3.3.10",
+    "webpack-dev-middleware": "^3.7.2",
+    "webpack-hot-middleware": "^2.25.0"
+  },
+  "dependencies": {
+    "axios": "^0.19.0",
+    "vant": "^2.2.15"
+  }
+}

+ 8 - 0
postcss.config.js

@@ -0,0 +1,8 @@
+
+module.exports = {
+  plugins: {
+    'postcss-preset-env': {
+      autoprefixer: {},
+    },
+  },
+}

+ 16 - 0
scripts/config.js

@@ -0,0 +1,16 @@
+
+const path = require('path')
+
+module.exports = {
+  port: 9528,
+  template: path.resolve(__dirname, '../src/index.html'),
+  dev: {
+    publicPath: '/',
+    env: {
+      NODE_ENV: '"dev"',
+      API_URL: {
+        default: '"http://192.168.23.111:9900"',
+      },
+    },
+  },
+}

+ 27 - 0
scripts/server.js

@@ -0,0 +1,27 @@
+
+const chalk = require('chalk')
+const express = require('express')
+const webpack = require('webpack')
+const history = require('connect-history-api-fallback')
+const webpackDevMiddleware = require('webpack-dev-middleware')
+const webpackHotMiddleware = require('webpack-hot-middleware')
+const config = require('./config')
+const webpackConfig = require('./webpack/dev')
+
+const app = express()
+const compiler = webpack(webpackConfig)
+
+app.use(history())
+app.use(webpackDevMiddleware(compiler, {
+  logLevel: 'silent',
+  publicPath: webpackConfig.output.publicPath,
+}))
+
+app.use(webpackHotMiddleware(compiler, {
+  log: false,
+  path: '/__what',
+  heartbeat: 5000,
+}))
+
+console.log(chalk.cyan('Starting the development server...\n'))
+app.listen(config.port)

+ 109 - 0
scripts/webpack/dev.js

@@ -0,0 +1,109 @@
+
+const path = require('path')
+const ip = require('ip')
+const webpack = require('webpack')
+const HtmlWebpackPlugin = require('html-webpack-plugin')
+const FriendlyErrorsWebpackPlugin = require('friendly-errors-webpack-plugin')
+const config = require('../config')
+const clientOptions = '?path=/__what&timeout=5000&reload=true&quiet=true'
+const VueLoaderPlugin = require('vue-loader/lib/plugin')
+
+module.exports = {
+  mode: 'development',
+  entry: [
+    `webpack-hot-middleware/client${clientOptions}`,
+    path.resolve(__dirname, '../../src/index.js'),
+  ],
+  output: {
+    filename: 'bundle.js',
+    publicPath: config.dev.publicPath,
+  },
+  module: {
+    rules: [
+      {
+        test: /\.css$/i,
+        use: [
+          'style-loader',
+          {
+            loader: 'css-loader',
+            options: {
+              importLoaders: 1,
+            },
+          },
+          'postcss-loader',
+        ],
+      },
+      {
+        test: /\.s[ac]ss$/i,
+        use: [
+          'style-loader',
+          {
+            loader: 'css-loader',
+            options: {
+              importLoaders: 2,
+            },
+          },
+          'postcss-loader',
+          'sass-loader',
+        ],
+      },
+      {
+        test: /\.(png|jpe?g|gif|svg)$/i,
+        loader: 'file-loader',
+      },
+      {
+        test: /\.(ttf|otf|woff2?|eot)$/i,
+        loader: 'file-loader',
+      },
+      {
+        test: /\.(mp4|mp3)$/i,
+        loader: 'file-loader',
+      },
+      {
+        test: /\.js$/,
+        enforce: 'pre',
+        exclude: /node_modules/,
+        loader: 'eslint-loader',
+      },
+      {
+        test: /\.js$/,
+        exclude: /node_modules/,
+        loader: 'babel-loader',
+      },
+      {
+        test: /\.vue$/,
+        loader: 'vue-loader',
+      },
+    ],
+  },
+  externals: {
+    vue: 'Vue',
+  },
+  resolve: {
+    extensions: [
+      '.js',
+      '.vue',
+    ],
+    alias: {
+      '@': path.resolve(__dirname, '../../src'),
+    },
+  },
+  devtool: 'inline-source-map',
+  plugins: [
+    new HtmlWebpackPlugin({
+      template: config.template,
+    }),
+    new VueLoaderPlugin(),
+    new webpack.DefinePlugin({
+      'process.env': config.dev.env,
+    }),
+    new webpack.HotModuleReplacementPlugin(),
+    new FriendlyErrorsWebpackPlugin({
+      compilationSuccessInfo: {
+        messages: [
+          `http://localhost:${config.port} or http://${ip.address()}:${config.port}`,
+        ],
+      },
+    }),
+  ],
+}

BIN
src/assets/images/buy_bg.png


BIN
src/assets/images/home_bg.png


BIN
src/assets/images/home_egg.png


BIN
src/assets/images/home_main.png


BIN
src/assets/images/home_report.png


BIN
src/assets/images/icons.png


BIN
src/assets/images/labels.png


BIN
src/assets/images/report_main.png


BIN
src/assets/images/titles.png


+ 43 - 0
src/assets/styles/main.scss

@@ -0,0 +1,43 @@
+
+*,
+*::before,
+*::after {
+  box-sizing: border-box;
+}
+
+html {
+  font-size: calc(100vw / 7.5);
+}
+
+body {
+  min-height: 100vh;
+  margin: 0 auto;
+  background: #fff;
+  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
+            "Helvetica Neue", Arial, "Noto Sans", sans-serif,
+            "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol",
+            "Noto Color Emoji";
+  -webkit-user-select: none;
+  -webkit-tap-highlight-color: transparent;
+  -webkit-touch-callout: none;
+}
+
+@media screen and (min-width: 600px) {
+  html {
+    font-size: calc(600px / 7.5);
+  }
+
+  body {
+    width: 600px; 
+  }
+}
+
+p {
+  margin: 0;
+}
+
+ul {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+}

+ 217 - 0
src/components/App.vue

@@ -0,0 +1,217 @@
+<template>
+  <div class="app">
+    <div class="app__bg" />
+    <main class="app__content">
+      <router-view :trace="trace" @go="go" @open="open" @preview="preview" />
+    </main>
+    <nav class="app__nav" v-if="trace.patch_num">
+      <ul class="menu">
+        <li
+          class="menu__item"
+          :class="[
+            `menu__item--${name}`,
+            currentMenu === name ? 'menu__item--active' : '',
+          ]"
+          v-for="({ name, title }, index) in menus"
+          :key="index"
+          @click="go(name)">
+          <span class="icon" :class="[`icon--${name}`]" />{{title}}
+        </li>
+      </ul>
+    </nav>
+  </div>
+</template>
+
+<script>
+import ImagePreview from 'vant/lib/image-preview'
+
+export default {
+  name: 'app',
+  data () {
+    return {
+      menus: [
+        {
+          name: 'home',
+          title: '溯源',
+        },
+        {
+          name: 'report',
+          title: '检测',
+        },
+        {
+          name: 'buy',
+          title: '复购',
+        },
+      ],
+      currentMenu: 'home',
+    }
+  },
+  computed: {
+    ...Vuex.mapState(['trace']),
+  },
+  watch: {
+    $route ({ name }) {
+      this.currentMenu = name
+    },
+  },
+  methods: {
+    go (name) {
+      this.$router.push({ name })
+    },
+    open (link) {
+      window.location.href = link
+    },
+    preview: ImagePreview,
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+.app__bg {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100vh;
+  height: 100vh;
+  background: #fff;
+}
+
+.app__content {
+  display: flex;
+  flex-direction: column;
+  position: relative;
+  z-index: 10;
+  min-height: 100vh;
+  background: #fff;
+  padding-bottom: calc(1rem + env(safe-area-inset-bottom));
+}
+
+.app__nav {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  z-index: 500;
+  width: 100%;
+  height: calc(1rem + env(safe-area-inset-bottom));
+  background: #fff;
+  padding-bottom: env(safe-area-inset-bottom);
+}
+
+.menu {
+  display: flex;
+  align-items: center;
+  height: 100%;
+  border-top: .02rem solid rgba(#000, .1);
+}
+
+.menu__item {
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+  align-items: center;
+  flex: 0 0 calc(100% / 3);
+  color: #999;
+  font-size: .2rem;
+  line-height: .34rem;
+  transition: all .1s;
+
+  &--active {
+    color: #333;
+    font-weight: bold;
+
+    .icon {
+      &--home {
+        background-position: -1.6rem 0;
+      }
+
+      &--report {
+        background-position: -.8rem 0;
+      }
+
+      &--buy {
+        background-position: 0 0;
+      }
+    }
+  }
+}
+
+.icon {
+  display: block;
+  width: .4rem;
+  height: .4rem;
+  background: url('~@/assets/images/icons.png') no-repeat;
+  background-size: auto .4rem;
+
+  &--home {
+    background-position: -2rem 0;
+  }
+
+  &--report {
+    background-position: -1.2rem 0;
+  }
+
+  &--buy {
+    background-position: -.4rem 0;
+  }
+}
+</style>
+
+<style lang="scss">
+.common__title {
+  width: 100%;
+  height: .7rem;
+  background: url('~@/assets/images/titles.png') no-repeat;
+  background-size: 3.7rem auto;
+
+  &--1 {
+    background-position: center 0;
+  }
+
+  &--2 {
+    background-position: center -.7rem;
+  }
+
+  &--3 {
+    background-position: center -1.4rem;
+  }
+
+  &--4 {
+    background-position: center -2.1rem;
+  }
+
+  &--5 {
+    background-position: center -2.8rem;
+  }
+
+  &--6 {
+    background-position: center -3.5rem;
+  }
+}
+
+.common__button {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  width: 2.6rem;
+  height: .66rem;
+  margin: 0 auto;
+  overflow: hidden;
+  background: #f1ca84;
+  border-radius: .66rem;
+  color: #333;
+  font-size: .3rem;
+  line-height: .5rem;
+
+  &--large {
+    width: 3.2rem;
+    height: .8rem;
+  }
+
+  span {
+    position: relative;
+    margin-left: .2rem;
+    font-size: .75em;
+    transform: scale(1, 2);
+  }
+}
+</style>

+ 72 - 0
src/components/Buy.vue

@@ -0,0 +1,72 @@
+<template>
+  <div class="buy">
+    <video
+      ref="video"
+      class="video"
+      src="~@/assets/medias/video.mp4"
+      preload="auto"
+      playsinline="true"
+      webkit-playsinline="true"
+      x5-playsinline="true"
+      x5-video-player-type="h5"
+      x5-video-player-fullscreen="true"
+      x-webkit-airplay="true" />
+    <div class="common__title common__title--6" />
+    <div class="content">
+      <div>
+        <p>耗资百万 自建实验室</p>
+        <p>走过7868723公里</p>
+      </div>
+      <div>
+        <p>用脚步丈量大地</p>
+        <p>只为那些安全健康的食材</p>
+      </div>
+    </div>
+    <div
+      class="common__button common__button--large"
+      @click="$listeners.open(`https://eosflare.io/tx/${trace.trans_id}`)">
+      点击复购<span>&gt;&gt;</span>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  props: ['trace'],
+}
+</script>
+
+<style lang="scss" scoped>
+.buy {
+  display: flex;
+  flex-direction: column;
+  flex: 1;
+  padding: .7rem .4rem .6rem;
+  overflow: hidden;
+  background: url('~@/assets/images/buy_bg.png') no-repeat center bottom;
+  background-size: 100% auto;
+
+  .common__title {
+    margin-top: .7rem;
+  }
+}
+
+.video {
+  display: block;
+  width: 100%;
+  height: 3.6rem;
+  background: #d1d1d1;
+}
+
+.content {
+  padding: .5rem 0 1rem;
+  color: #666;
+  font-size: .32rem;
+  line-height: .6rem;
+  text-align: center;
+
+  div + div {
+    margin-top: .3rem;
+  }
+}
+</style>

Plik diff jest za duży
+ 314 - 0
src/components/Home.vue


+ 50 - 0
src/components/Report.vue

@@ -0,0 +1,50 @@
+<template>
+  <div class="report">
+    <div class="focus">
+      <span />
+    </div>
+    <div class="common__title common__title--5" />
+    <div class="content" v-html="trace.report" />
+  </div>
+</template>
+
+<script>
+export default {
+  props: ['trace'],
+}
+</script>
+
+<style lang="scss" scoped>
+.focus {
+  padding: .4rem;
+  padding-bottom: .6rem;
+
+  span {
+    display: block;
+    width: 100%;
+    height: 4.2rem;
+    background: url('~@/assets/images/report_main.png');
+    background-size: cover;
+    border-radius: .08rem;
+    box-shadow: 0 1px .06rem rgba(#000, .1);
+  }
+}
+
+.content {
+  padding: .4rem;
+  color: #666;
+  font-size: .3rem;
+  line-height: .5rem;
+  white-space: pre-wrap;
+
+  /deep/ p + p {
+    margin-top: .3rem;
+  }
+
+  /deep/ img {
+    display: block;
+    width: 100%;
+    border-radius: .08rem;
+  }
+}
+</style>

+ 14 - 0
src/index.html

@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8" />
+    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, shrink-to-fit=no, viewport-fit=cover" />
+    <title>彩虹溯源</title>
+  </head>
+  <body ontouchstart="">
+    <div id="root"></div>
+    <script src="//cdn.staticfile.org/vue/2.6.9/vue.min.js" crossorigin></script>
+    <script src="//cdn.staticfile.org/vue-router/3.1.3/vue-router.min.js" crossorigin></script>
+    <script src="//cdn.staticfile.org/vuex/3.1.1/vuex.min.js" crossorigin></script>
+  </body>
+</html>

+ 15 - 0
src/index.js

@@ -0,0 +1,15 @@
+
+import router from './router'
+import store from './store'
+import 'vant/lib/image-preview/style'
+import 'vant/lib/toast/style'
+
+import App from './components/App'
+import './assets/styles/main.scss'
+import './plugins'
+
+new Vue({
+  router,
+  store,
+  render: h => h(App),
+}).$mount('#root')

+ 4 - 0
src/plugins/index.js

@@ -0,0 +1,4 @@
+
+import request from './request'
+
+Vue.use(request)

+ 54 - 0
src/plugins/request.js

@@ -0,0 +1,54 @@
+
+import axios from 'axios'
+import Toast from 'vant/lib/toast'
+
+function install (v) {
+  const request = axios.create({
+    baseURL: process.env.API_URL.default,
+    timeout: 60000,
+  })
+
+  request.interceptors.request.use(
+    config => {
+      Toast.loading({
+        duration: 0,
+        forbidClick: true,
+        loadingType: 'spinner',
+        message: '加载中...',
+      })
+      return config
+    },
+    error => {
+      return Promise.reject(error)
+    },
+  )
+
+  request.interceptors.response.use(
+    response => {
+      const { data } = response
+      Toast.clear()
+      if (data.code) {
+        Toast.fail(data.message)
+        return Promise.resolve({ code: data.code })
+      }
+      return { code: 0, data: data.data }
+    },
+    error => {
+      Toast.clear()
+      // console.log(JSON.parse(JSON.stringify(error)))
+      const errorData = { code: 1, message: '网络超时,请重试' }
+      if (error.response) {
+        errorData.code = error.response.status
+        errorData.message = error.response.data.message
+      }
+      Toast.fail(errorData.message)
+      return Promise.resolve(errorData)
+    },
+  )
+  v.prototype.$request = request
+  v.request = request
+}
+
+export default {
+  install,
+}

+ 34 - 0
src/router.js

@@ -0,0 +1,34 @@
+
+import store from './store'
+
+const routes = [
+  {
+    name: 'home',
+    path: '/:patchNum',
+    component: () => import(/* webpackChunkName: 'home' */ '@/components/Home'),
+  },
+  {
+    name: 'report',
+    path: '/:patchNum/report',
+    component: () => import(/* webpackChunkName: 'report' */ '@/components/Report'),
+  },
+  {
+    name: 'buy',
+    path: '/:patchNum/buy',
+    component: () => import(/* webpackChunkName: 'buy' */ '@/components/Buy'),
+  },
+]
+
+const router = new VueRouter({
+  mode: 'history',
+  routes,
+})
+
+router.beforeEach(async (to, from, next) => {
+  if (!store.state.trace.patch_num) {
+    await store.dispatch('getTrace', to.params.patchNum)
+  }
+  next()
+})
+
+export default router

+ 21 - 0
src/store.js

@@ -0,0 +1,21 @@
+
+const store = {
+  strict: process.env.NODE_ENV === 'dev',
+  state: {
+    trace: {},
+  },
+  mutations: {
+    UPDATE_TRACE (state, payload) {
+      state.trace = payload
+    },
+  },
+  actions: {
+    async getTrace ({ commit }, patchNum) {
+      const { code, data } = await Vue.request.get(`/egg?patch_num=${patchNum}`)
+      if (code) return false
+      commit('UPDATE_TRACE', data)
+    },
+  },
+}
+
+export default new Vuex.Store(store)