Primroses 3 年 前
コミット
e977dbb3e4

+ 1 - 0
.env.development

@@ -0,0 +1 @@
+VUE_APP_BASE_URL = "http://test-shoes-api.hiyd.com"

+ 1 - 0
.env.production

@@ -0,0 +1 @@
+VUE_APP_BASE_URL = "http://test-shoes-api.hiyd.com"

+ 10 - 9
.eslintrc.js

@@ -1,31 +1,32 @@
 module.exports = {
   root: true,
   env: {
-    node: true
+    node: true,
   },
   extends: [
     "plugin:vue/vue3-essential",
     "eslint:recommended",
     "@vue/typescript/recommended",
     "@vue/prettier",
-    "@vue/prettier/@typescript-eslint"
+    "@vue/prettier/@typescript-eslint",
   ],
   parserOptions: {
-    ecmaVersion: 2020
+    ecmaVersion: 2020,
   },
   rules: {
     "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
-    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
+    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
+    "@typescript-eslint/camelcase": "off",
   },
   overrides: [
     {
       files: [
         "**/__tests__/*.{j,t}s?(x)",
-        "**/tests/unit/**/*.spec.{j,t}s?(x)"
+        "**/tests/unit/**/*.spec.{j,t}s?(x)",
       ],
       env: {
-        jest: true
-      }
-    }
-  ]
+        jest: true,
+      },
+    },
+  ],
 };

+ 1 - 0
package.json

@@ -9,6 +9,7 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
+    "axios": "^0.21.0",
     "core-js": "^3.6.5",
     "vue": "^3.0.0",
     "vue-router": "^4.0.0-0",

+ 1 - 1
postcss.config.js

@@ -6,7 +6,7 @@ module.exports = {
       //viewportHeight: 667, //视口的高度,对应的是设计稿的高度(也可以不配置)
       unitPrecision: 5, //指定‘px’转换为视口单位值的小数位数(很多时候无法整除)
       viewportUnit: "vw", //指定需要转换成的视口单位,建议使用vw
-      selectorBlankList: ["ignore"], //指定不需要转换的类
+      selectorBlackList: [".ignore-"], //指定不需要转换的类
       minPixelValue: 1, //小于或等于‘1px’不转换为视口单位
       mediaQuery: false, //允许在媒体查询中转换为‘px’
       // exclude: [/Tabbar/], //不需要转化的组件文件名正则,必须是正则表达式

BIN
public/favicon.ico


+ 0 - 1
public/index.html

@@ -4,7 +4,6 @@
     <meta charset="utf-8">
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
-    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
     <title><%= htmlWebpackPlugin.options.title %></title>
   </head>
   <body>

+ 0 - 4
src/App.vue

@@ -11,8 +11,4 @@ export default {
 </script>
 <style lang="scss">
 @import "~@/styles/reset.css";
-#app {
-  width: 100%;
-  height: 100%;
-}
 </style>

BIN
src/assets/sport-share-link/achievement-bg.png


BIN
src/assets/sport-share-link/qr-image.png


BIN
src/assets/sport-share-link/share_img_riding.png


BIN
src/assets/sport-share-link/share_img_run.png


BIN
src/assets/sport-share-link/share_img_walk.png


+ 8 - 0
src/services/config.ts

@@ -0,0 +1,8 @@
+import axios from "axios";
+
+const BaseUrl =
+  process.env.NODE_ENV === "production" ? process.env.VUE_APP_BASE_URL : "api/";
+
+export function get(url: string) {
+  return axios.get(BaseUrl + url);
+}

+ 4 - 0
src/services/index.ts

@@ -0,0 +1,4 @@
+//
+import { get } from "./config";
+
+export const getShare = (h: string) => get("/share/show?h=" + h);

+ 3 - 2
src/styles/reset.css

@@ -19,7 +19,8 @@ button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}
 mark{background-color:transparent}
 a,ins,s,u,del{text-decoration:none}
 sup,sub{vertical-align:baseline}
-html {overflow-x: hidden;height: 100%;font-size: 50px;-webkit-tap-highlight-color: transparent;width: 100%;}
-body {font-family: Arial, "Microsoft Yahei", "Helvetica Neue", Helvetica, sans-serif;color: #333;line-height: 1;-webkit-text-size-adjust: none;width: 100%;height: 100%;}
+html {overflow-x: hidden;font-size: 50px;-webkit-tap-highlight-color: transparent;}
+body {font-family: 'PingFang', Microsoft Yahei, Hiragino Sans GB,
+  WenQuanYi Micro Hei, sans-serif;color: #333;line-height: 1;-webkit-text-size-adjust: none;}
 /* hr {height: .02rem;margin: .1rem 0;border: medium none;} */
 a {color: #25a4bb;text-decoration: none;}

+ 15 - 0
src/utils/index.ts

@@ -0,0 +1,15 @@
+/**
+ *
+ * @param {strimg} str
+ * @param {string} name
+ * @returns {string} result
+ */
+export function getUrlParameter(str: string, name: string): string {
+  // name = name.replace(/[\[]/, '\\[').replace(/[\]]/, '\\]');//如果name如果有方括号则加上转义符号\
+  const regex = new RegExp("[\\?&]" + name + "=([^&]*)");
+
+  const results = regex.exec(str);
+  return results === null
+    ? ""
+    : decodeURIComponent(results[1].replace(/\+/g, " ")); //字符串里的加号替换为空格,使用 decodeURIComponent() 对编码后的 URI 进行解码;
+}

+ 46 - 0
src/views/share.ts

@@ -0,0 +1,46 @@
+import { reactive, watchEffect, toRefs } from "vue";
+import { getShare } from "@/services/index";
+
+interface ShareState {
+  consume: number;
+  defeat: number;
+  duration: number;
+  period: string;
+  user: {};
+  hash?: string;
+  id?: number;
+  type?: string;
+  createdAt?: string;
+}
+
+export function initShare(hash: string) {
+  // 把自己用到的再填上去 没用到的可以 不填...
+  const state = reactive<ShareState>({
+    user: {},
+    consume: 0,
+    defeat: 0,
+    duration: 0,
+    period: "",
+    createdAt: ""
+  });
+
+  watchEffect(async function() {
+    const {
+      data: { user, consume, duration, period },
+      created_at
+    } = (await getShare(hash)).data.data;
+
+    state.user = user;
+    state.consume = consume;
+    state.duration = duration;
+    state.period = period;
+    state.createdAt = created_at.split(" ")[0];
+    return state;
+  });
+
+  // console.log(state.data);
+
+  return {
+    ...toRefs(state)
+  };
+}

+ 319 - 6
src/views/sport-share-link.vue

@@ -1,15 +1,328 @@
 <template>
-  <div class="share-page">share-sport-link</div>
+  <div :class="[currentBg, 'share-page ignore-share-page']">
+    <!-- header -->
+    <div class="share-head">
+      <div class="avatar-container">
+        <img :src="user.avatar" class="avatar" v-if="user.avatar" />
+        <div v-else class="avatar-empty"></div>
+      </div>
+      <div class="user-name">
+        {{ user.name }}
+      </div>
+      <div class="text-style">
+        今天使用智能鞋运动了
+      </div>
+      <div class="text-sport-container">
+        <div class="text-sport-time">{{ duration }}</div>
+        <div class="text-sport-title">分钟</div>
+      </div>
+      <div class="text-consume">累计消耗 {{ consume }} kal</div>
+      <div class="text-achievement">击败了全国 {{ defeat }}% 用户</div>
+      <div class="date">{{ createdAt }}</div>
+    </div>
+    <!-- content -->
+    <div class="share-content">
+      <div class="content-item">相当于持续{{ words[randomNumber] }}</div>
+      <div class="content-center">{{ kilometer }}</div>
+      <div class="content-item">公里</div>
+    </div>
+
+    <div class="share-bottom">
+      <div class="share-bottom-title">
+        <img src="@/assets/sport-share-link/achievement-bg.png" />
+      </div>
+      <div class="share-bottom-content">
+        <div
+          class="achievement-item achievement-item-margin"
+          v-for="item in user.achievements_show"
+          :key="item.id"
+        >
+          <img :src="item.logo" />
+          <div class="achievement-text">{{ item.name }}</div>
+        </div>
+      </div>
+    </div>
+
+    <div class="share-tips">
+      <div class="tips-left">
+        <div class="tips-logo"></div>
+        <div class="tips-title">运动智能鞋</div>
+        <div class="sub-tips">这是一段产品sloagn</div>
+      </div>
+      <div class="tips-qrcode-container">
+        <div class="qrcode-item qrcode-item-1">
+          <div class="qrcode-img">
+
+          </div>
+          <div class="qrcode-title">安卓下载</div>
+        </div>
+        <div class="qrcode-item">
+          <div class="qrcode-img"></div>
+          <div class="qrcode-title">IOS下载</div>
+        </div>
+      </div>
+    </div>
+  </div>
 </template>
 <script lang="ts">
-export default {};
+import { defineComponent, computed } from "vue";
+import { initShare } from "./share";
+import { getUrlParameter } from "@/utils/index";
+export default defineComponent({
+  setup() {
+    // 0 是步行 1是骑行 2是跑
+    const words = ["步行", "骑行", "跑步"];
+    const unit = [0.8214, 0.6142, 1.036];
+
+    const randomNumber = computed(() => parseInt(`${Math.random() * 3}`));
+
+    const currentBg = computed(() => {
+      return "bg-" + randomNumber.value;
+    });
+
+    const hash = getUrlParameter(window.location.href, "h");
+    const { user, consume, duration, defeat, period, createdAt } = initShare(
+      hash
+    );
+
+    const kilometer = computed(() =>
+      Math.floor(consume.value / 55 / unit[randomNumber.value])
+    );
+    return {
+      currentBg,
+      user,
+      consume,
+      duration,
+      defeat,
+      period,
+      createdAt,
+      words,
+      randomNumber,
+      kilometer,
+    };
+  },
+});
 </script>
 <style lang="scss" scoped>
-.share-page {
-  color: red;
-  background: url(~@/assets/sport-share-link/share_img_riding.png);
-  background-size: cover;
+.ignore-share-page {
+  background-size: 100% auto;
   width: 100%;
   height: 100%;
+  background-repeat: no-repeat;
+  background-color: #635248;
+  // align-items: ;
+}
+
+.share-page {
+  background-position: 0 -20px;
+}
+
+.bg-1 {
+  background-image: url(~@/assets/sport-share-link/share_img_riding.png);
+}
+.bg-2 {
+  background-image: url(~@/assets/sport-share-link/share_img_run.png);
+}
+.bg-0 {
+  background-image: url(~@/assets/sport-share-link/share_img_walk.png);
+}
+
+.share-head {
+  .avatar-container {
+    display: flex;
+    justify-content: center;
+    padding-top: 65px;
+    .avatar {
+      width: 80px;
+      height: 80px;
+      border-radius: 70px;
+      border: 4px solid black;
+    }
+  }
+
+  .user-name {
+    color: #333333;
+    text-align: center;
+    font-size: 18px;
+    font-weight: bold;
+    margin-top: 18px;
+  }
+
+  .text-style {
+    color: #333333;
+    text-align: center;
+    font-size: 14px;
+    margin-top: 16px;
+  }
+
+  .text-sport-container {
+    margin-top: 14px;
+    display: flex;
+    justify-content: center;
+    align-items: flex-end;
+  }
+
+  .text-sport-time {
+    font-size: 40px;
+    font-weight: bold;
+    color: #f52323;
+    text-align: center;
+    font-family: DIN;
+  }
+  .text-sport-title {
+    font-size: 17px;
+    color: #f52323;
+    line-height: 30px;
+    margin-left: 4px;
+  }
+  .text-consume {
+    font-size: 12px;
+    color: #333;
+    text-align: center;
+    margin-top: 12px;
+    padding-bottom: 5px;
+  }
+  .text-achievement {
+    width: 281px;
+    height: 29px;
+    line-height: 29px;
+    margin: 9px auto;
+    background: linear-gradient(
+      90deg,
+      rgba(255, 255, 255, 0) 0%,
+      #ffffff 53%,
+      rgba(255, 255, 255, 0) 100%
+    );
+    opacity: 1;
+    color: #f52323;
+    text-align: center;
+  }
+  .date {
+    font-size: 12px;
+    text-align: center;
+    color: #333;
+  }
+  .avatar-empty {
+    max-width: 80px;
+    max-height: 80px;
+    border-radius: 70px;
+    border: 4px solid black;
+    background-color: #fff;
+  }
+}
+
+.share-content {
+  display: flex;
+  justify-content: center;
+  color: #fff;
+  margin-top: 90px;
+  align-items: center;
+  .content-item {
+    font-size: 18px;
+  }
+  .content-center {
+    font-size: 32px;
+    padding: 0 5px;
+  }
+}
+
+.share-bottom {
+  margin-top: 290px;
+  .share-bottom-title {
+    margin: 0 auto;
+    // width: 80%;
+    width:254px;
+    img{
+      max-width: 100%;
+      max-height: 100%;
+    }
+    // text-align: center;
+    // color: #fff;
+    // background: url(~@/assets/sport-share-link/achievement-bg.png) no-repeat;
+    // background-size: cover;
+    height: 27px;
+    // background-position: 50% -50%;
+  }
+  .share-bottom-content {
+    margin: 25px 17px 0;
+    display: flex;
+    justify-content: start;
+  }
+  .achievement-item {
+    width: 70px;
+    text-align: center;
+    // margin-left: 15px;
+    img {
+      max-width: 70px;
+      max-height: 70px;
+    }
+    .achievement-text {
+      margin-top: 10px;
+      color: #d2c0b5;
+      font-size: 12px;
+    }
+  }
+  .achievement-item-margin {
+    margin-right: 20px;
+  }
+}
+
+.share-tips {
+  margin-top: 36px;
+  padding: 0 17px 12px;
+  // border-top: 1px dotted #fff;
+  display: flex;
+  justify-content: space-between;
+
+  .tips-left {
+    margin-top: 19px;
+  }
+  .tips-logo {
+    width: 50px;
+    height: 50px;
+    background: #ffc400;
+    border-radius: 5px;
+  }
+  .tips-title {
+    font-size: 16px;
+    color: #fff;
+    font-weight: 400;
+    margin-top: 8px;
+  }
+  .sub-tips {
+    font-size: 12px;
+    color: #fff;
+    margin-top: 7px;
+  }
+  .tips-qrcode-container {
+    display: flex;
+    justify-content: space-between;
+    // padding: 0 17px 19px;
+    margin-top: 18px;
+  }
+  .qrcode-item {
+    text-align: center;
+  }
+  .qrcode-item-1 {
+    margin-right: 20px;
+  }
+
+  .qrcode-title {
+    text-align: center;
+    margin-top: 8px;
+    color: #fff;
+    font-size: 12px;
+  }
+  .qrcode-img {
+    width: 75px;
+    height: 75px;
+    // background-color: #fff;
+    background-image: url(~@/assets/sport-share-link/qr-image.png);
+    background-size: cover;
+  }
+}
+
+.margin-top-18 {
+  margin-top: 18px;
 }
 </style>

+ 18 - 15
vue.config.js

@@ -1,18 +1,18 @@
 const path = require("path");
 
 function resolve(dir) {
-  return path.join(__dirname,dir);
+  return path.join(__dirname, dir);
 }
 
 /** @type {import('webpack').Configuration} */
 module.exports = {
-  publicPath: "./", // 默认为'/'
+  publicPath: process.env.NODE_ENV === 'production' ? './' : './',
 
   // 将构建好的文件输出到哪里,本司要求
-  outputDir: "dist/static",
+  // outputDir: "dist",
 
   // 放置生成的静态资源(js、css、img、fonts)的目录。
-  assetsDir: "static",
+  // assetsDir: "static",
 
   // 指定生成的 index.html 的输出路径
   indexPath: "index.html",
@@ -62,21 +62,24 @@ module.exports = {
 
   // 是否为 Babel 或 TypeScript 使用 thread-loader。该选项在系统的 CPU 有多于一个内核时自动启用,仅作用于生产构建。
   parallel: require("os").cpus().length > 1,
-
+  
+  pwa: {
+    iconPaths: {},
+  },
   devServer: {
     host: "0.0.0.0",
     port: 9090, // 端口号
     disableHostCheck: true,
     // 配置多个代理
-    // proxy: {
-    //   "/api": {
-    //     target: "https://www.mock.com",
-    //     ws: true, // 代理的WebSockets
-    //     changeOrigin: true, // 允许websockets跨域
-    //     pathRewrite: {
-    //       "^/api": "",
-    //     },
-    //   },
-    // },
+    proxy: {
+      "/api": {
+        target: process.env.VUE_APP_BASE_URL,
+        ws: true, // 代理的WebSockets
+        changeOrigin: true, // 允许websockets跨域
+        pathRewrite: {
+          "^/api": "",
+        },
+      },
+    },
   },
 };

+ 8 - 1
yarn.lock

@@ -2286,6 +2286,13 @@ aws4@^1.8.0:
   resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.11.0.tgz#d61f46d83b2519250e2784daf5b09479a8b41c59"
   integrity sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==
 
+axios@^0.21.0:
+  version "0.21.0"
+  resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.0.tgz#26df088803a2350dff2c27f96fef99fe49442aca"
+  integrity sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw==
+  dependencies:
+    follow-redirects "^1.10.0"
+
 babel-code-frame@^6.22.0, babel-code-frame@^6.26.0:
   version "6.26.0"
   resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b"
@@ -4766,7 +4773,7 @@ flush-write-stream@^1.0.0:
     inherits "^2.0.3"
     readable-stream "^2.3.6"
 
-follow-redirects@^1.0.0:
+follow-redirects@^1.0.0, follow-redirects@^1.10.0:
   version "1.13.0"
   resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db"
   integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==