소스 검색

initial commit

zzf 2 년 전
커밋
a3ab22c5eb
43개의 변경된 파일3737개의 추가작업 그리고 0개의 파일을 삭제
  1. 5 0
      .gitignore
  2. 36 0
      README.md
  3. 43 0
      index.html
  4. 1284 0
      package-lock.json
  5. 32 0
      package.json
  6. 31 0
      postcss.config.js
  7. BIN
      public/favicon.ico
  8. 5 0
      public/static/img/loading.svg
  9. BIN
      public/static/img/logo.png
  10. 13 0
      src/App.vue
  11. 40 0
      src/api/common.js
  12. 10 0
      src/api/learn-more.js
  13. 0 0
      src/api/promotion.js
  14. 17 0
      src/api/student.js
  15. 10 0
      src/api/wholesale.js
  16. BIN
      src/assets/countdown-banner.jpg
  17. 111 0
      src/assets/css/common.scss
  18. BIN
      src/assets/img/coupon/coupon-banner.jpg
  19. BIN
      src/assets/img/coupon/coupon-qrcode.jpg
  20. BIN
      src/assets/img/coupon/coupon-red.png
  21. BIN
      src/assets/img/coupon/coupon-violet.png
  22. BIN
      src/assets/img/promo_hair_bundles_sale/202204191600.jpg
  23. BIN
      src/assets/img/promo_hair_bundles_sale/202204191602.jpg
  24. BIN
      src/assets/img/promo_hair_bundles_sale/202204191603.jpg
  25. BIN
      src/assets/img/promo_hair_bundles_sale/202204201012-1.jpg
  26. BIN
      src/assets/img/promo_hair_bundles_sale/202204201012-2.jpg
  27. BIN
      src/assets/img/promo_hair_bundles_sale/202204261006-1.jpg
  28. BIN
      src/assets/img/promo_hair_bundles_sale/202204261006-2.jpg
  29. BIN
      src/assets/img/promo_hair_bundles_sale/202204261006-3.jpg
  30. BIN
      src/assets/img/promo_hair_bundles_sale/202204261006-4.jpg
  31. BIN
      src/assets/logo.png
  32. 29 0
      src/main.js
  33. 14 0
      src/router/router.js
  34. 30 0
      src/router/routes.js
  35. 215 0
      src/util/countdown.js
  36. 55 0
      src/util/lib.js
  37. 129 0
      src/util/rafflerotating.js
  38. 112 0
      src/util/request.js
  39. 587 0
      src/view/Home/Home.vue
  40. 747 0
      src/view/Home/home.css
  41. 23 0
      src/view/Test/Test.vue
  42. 67 0
      src/view/Test/test.scss
  43. 92 0
      vite.config.js

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+node_modules
+.DS_Store
+dist
+dist-ssr
+*.local

+ 36 - 0
README.md

@@ -0,0 +1,36 @@
+# Vue 3 + Vite
+
+This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
+
+## Recommended IDE Setup
+
+- [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
+
+
+
+
+https://github.com/evrone/postcss-px-to-viewport/issues/72
+
+import { useRouter } from 'vue-router'; 必须在setup中使用 https://next.router.vuejs.org/zh/api/index.html#uselink
+
+> 本来想用 postcss-px-to-viewport,但是vant nutui采用的设计稿都是375宽度,只好换回postcss-pxtorem
+
+
+/****
+ * WestKiss JS 交互
+///跳转商品详情
+goto_pushGoodsDetailsC。
+[@"entity_id"]
+
+///跳转商品分类列表
+goto_pushProductListVC 
+  [@"name"]  	[@"type"]
+
+///跳转登录
+present_LoginC
+
+///分享相关内容
+share_click
+ @"image”,[@"title”]、[@"shareurl"]
+ Hi, I’m using points to win $500 CASH, come and try your luck today!
+ */

+ 43 - 0
index.html

@@ -0,0 +1,43 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" href="/favicon.ico" />
+    <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no,minimal-ui" />
+    <title>Lucky Wheel</title>
+    <style>
+      .init-loading{
+        position: fixed;
+        top:50%;
+        left:50%;
+        transform: translate3d(-50%,-50%,0);
+        width: 50%;
+        height: auto;
+      }
+      .init-loading .init-loading-svg{
+        display:block;
+        width: 50px;
+        height: 50px;
+        margin:auto;
+      }
+    </style>
+    <script>
+      if(window.location.href.indexOf('_ly') < 0) {
+        var originUrl = window.location.origin;
+        var path = window.location.pathname;
+        var hash = window.location.hash;
+        window.location.href = originUrl + path + '?_ly=' + Date.now() + hash;
+      }
+        
+    </script>
+  </head>
+  <body>
+    <div id="app">
+      <div class="init-loading">
+        <img class="init-loading-svg" src="/static/img/loading.svg" />
+      </div>
+    </div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+
+</html>

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 1284 - 0
package-lock.json


+ 32 - 0
package.json

@@ -0,0 +1,32 @@
+{
+  "name": "vue3_demo",
+  "version": "0.0.0",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "serve": "vite preview"
+  },
+  "browserslist": [
+    "last 4 versions",
+    "Android >= 5.0"
+  ],
+  "dependencies": {
+    "amfe-flexible": "^2.2.1",
+    "axios": "^0.24.0",
+    "dsbridge": "^3.1.4",
+    "qs": "^6.10.2",
+    "vant": "^3.3.6",
+    "vue": "^3.2.16",
+    "vue-router": "^4.0.12",
+    "vue-wechat-title": "^2.0.7"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^1.9.3",
+    "autoprefixer": "^10.4.0",
+    "postcss": "^8.4.4",
+    "postcss-pxtorem": "^6.0.0",
+    "sass": "^1.44.0",
+    "vite": "^2.6.4",
+    "vite-plugin-style-import": "^1.4.0"
+  }
+}

+ 31 - 0
postcss.config.js

@@ -0,0 +1,31 @@
+
+
+// module.exports = {
+//     plugins: {
+//         autoprefixer: {},
+//         // https://github.com/evrone/postcss-px-to-viewport/blob/HEAD/README_CN.md
+//         // https://www.shangmayuan.com/a/455cef3107c848f9a6190e82.html
+//         'postcss-px-to-viewport': {
+//             unitToConvert: 'px', // 需要转换的单位,默认为"px"
+//             viewportWidth: 750, // 设计稿的宽度
+//             unitPrecision: 4,// 单位转换后保留的精度
+//             viewportUnit: 'vw', //(String) 希望使用的视口单位
+//             fontViewportUnit: 'vw', //(String) 字体使用的视口单位
+//         }
+//     }    
+    
+//   }
+// postcss.config.js
+module.exports = {
+    plugins: {
+        autoprefixer: {},
+        // postcss-pxtorem 插件的版本需要 >= 5.0.0
+        'postcss-pxtorem': {
+            rootValue({ file }) {
+                return 37.5;
+                //return file.indexOf('vant') !== -1 ? 37.5 : 75;
+            },
+            propList: ['*'],
+        },
+    },
+  };

BIN
public/favicon.ico


+ 5 - 0
public/static/img/loading.svg

@@ -0,0 +1,5 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="50" height="50" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
+    <circle cx="50" cy="50" fill="none" stroke="#fe3e94" stroke-width="10" r="35" stroke-dasharray="164.93361431346415 56.97787143782138">
+        <animateTransform attributeName="transform" type="rotate" repeatCount="indefinite" dur="1s" values="0 50 50;360 50 50" keyTimes="0;1"></animateTransform>
+    </circle>
+</svg>

BIN
public/static/img/logo.png


+ 13 - 0
src/App.vue

@@ -0,0 +1,13 @@
+<script setup>
+// This starter template is using Vue 3 <script setup> SFCs
+// Check out https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup
+</script>
+
+<template>
+  <router-view v-wechat-title="$route.meta.title"></router-view>
+</template>
+
+<style lang="scss">
+@import "@/assets/css/common.scss";
+
+</style>

+ 40 - 0
src/api/common.js

@@ -0,0 +1,40 @@
+import axiosInstance from "@/util/request";
+
+function getCategoryProduct(data,config) {
+    let url = '/app-api/product/getCategoryProduct';
+    return axiosInstance.post(url,data,config);
+}
+// 获取奖品
+function getRaffle() {
+    let url = '/api/raffle/getRaffle';
+    return axiosInstance.post(url);
+}
+// 获取中奖纪录
+function getRaffleList(data) {
+    let url = '/api/raffle/getRaffleList';
+    return axiosInstance.post(url,data);
+}
+// 获取用户的积分和抽奖次数
+function getPointsAndCount(data) {
+    let url = '/api/raffle/getPoints';
+    return axiosInstance.post(url,data);
+}
+// 抽奖
+function raffleAction(data) {
+    let url = '/api/raffle/raffle';
+    return axiosInstance.post(url,data);
+}
+// 分享增加抽奖次数
+function shareAddRaffleCount(data) {
+    let url = '/api/raffle/shareAdd';
+    return axiosInstance.post(url,data);
+}
+
+export {
+    getCategoryProduct,
+    getRaffle,
+    getRaffleList,
+    getPointsAndCount,
+    raffleAction,
+    shareAddRaffleCount
+};

+ 10 - 0
src/api/learn-more.js

@@ -0,0 +1,10 @@
+import axiosInstance from "@/util/request";
+
+function getLearnMoreDetail(data,config) {
+    let url = '/index/getLearn';
+    return axiosInstance.post(url,data,config);
+}
+
+export {
+    getLearnMoreDetail,
+};

+ 0 - 0
src/api/promotion.js


+ 17 - 0
src/api/student.js

@@ -0,0 +1,17 @@
+import axiosInstance from "@/util/request";
+
+function getAllStudent(data,config) {
+    let url = '/student/all';
+    let queryStr = '';
+    if(data) {
+        for(let key in data) {
+            queryStr = queryStr ? (queryStr + '&' + key + '=' + encodeURIComponent(data[key])) : (key + '=' + encodeURIComponent(data[key]));
+        }
+        url = url + '?' + queryStr;
+    }
+    return axiosInstance.get(url,config);
+}
+
+export {
+    getAllStudent,
+};

+ 10 - 0
src/api/wholesale.js

@@ -0,0 +1,10 @@
+import axiosInstance from "@/util/request";
+
+function submitWholesale(data,config) {
+    let url = '/index/savemessage';
+    return axiosInstance.post(url,data,config);
+}
+
+export {
+    submitWholesale
+};

BIN
src/assets/countdown-banner.jpg


+ 111 - 0
src/assets/css/common.scss

@@ -0,0 +1,111 @@
+*{
+    margin:0;
+    padding:0;
+}
+button{
+    background: none;
+    border:none;
+}
+button:focus,
+select:focus,
+input:focus{
+    outline: none;
+}
+html,body{
+    width:100%;
+    height:100%;
+}
+#app {
+    width:100%;
+    height:100%;
+    font-family: -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol",sans-serif;
+}
+.icon{
+    width: 1em;
+    height: 1em;
+    vertical-align: -0.15em;
+    fill: currentColor;
+    overflow: hidden;
+}
+/**自定义滚动条样式 */
+.scrollbar-style{
+    /**work on FireFox https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Scrollbars */
+    scrollbar-color: #999999 transparent; /* 第一个值是滑块颜色,第二个值是轨道背景颜色 */
+    scrollbar-width: thin;
+}
+/**work on Chrome, Edge, Safari 
+::-webkit-scrollbar— 整个滚动条.
+::-webkit-scrollbar-button — 滚动条上的按钮 (上下箭头).
+::-webkit-scrollbar-thumb — 滚动条上的滚动滑块.
+::-webkit-scrollbar-track — 滚动条轨道.
+::-webkit-scrollbar-track-piece — 滚动条没有滑块的轨道部分.
+::-webkit-scrollbar-corner — 当同时有垂直滚动条和水平滚动条时交汇的部分.
+::-webkit-resizer — 某些元素的corner部分的部分样式(例:textarea的可拖动按钮).
+* https://segmentfault.com/a/1190000020991323
+*/
+.scrollbar-style::-webkit-scrollbar{
+    width: 8px; /* 垂直滚动条宽度 */
+    height: 8px; /* 水平滚动条高度 */
+}
+.scrollbar-style::-webkit-scrollbar-track{
+    background-color: transparent;
+}
+.scrollbar-style::-webkit-scrollbar-thumb{
+    background: #999999;
+    border-radius: 25px;
+}
+
+.scrollbar-style::-webkit-scrollbar-corner{
+    background-color: transparent;
+}
+
+.page{
+    box-sizing: border-box;
+    width: 100%;
+    padding: 30px 20px;
+    color:#000000;
+}
+.page-h1{
+    font-size: 36px;
+    font-weight: bold;
+    padding: 20px 0;
+}
+.page-h2{
+    font-size: 32px;
+    font-weight: bold;
+    padding: 20px 0;
+}
+.page-h3{
+    font-size: 28px;
+    font-weight: bold;
+}
+.page-p{
+    font-size: 24px;
+    line-height: 1.5em;
+}
+.page-p.x-large{
+    font-size: 32px;
+    line-height: 1.5em;
+}
+.page-p.large{
+    font-size: 30px;
+    line-height: 1.5em;
+}
+.page-p.medium{
+    font-size: 28px;
+    line-height: 1.5em;
+}
+.page-p img{
+    width: 100%;
+    height: auto;
+}
+
+.text-overflow-2{
+    overflow:hidden;
+    text-overflow:ellipsis;
+    display:-webkit-box;
+    -webkit-line-clamp:2;
+    -webkit-box-orient:vertical;
+    font-size: 24px;
+    line-height: 1.5em;
+}

BIN
src/assets/img/coupon/coupon-banner.jpg


BIN
src/assets/img/coupon/coupon-qrcode.jpg


BIN
src/assets/img/coupon/coupon-red.png


BIN
src/assets/img/coupon/coupon-violet.png


BIN
src/assets/img/promo_hair_bundles_sale/202204191600.jpg


BIN
src/assets/img/promo_hair_bundles_sale/202204191602.jpg


BIN
src/assets/img/promo_hair_bundles_sale/202204191603.jpg


BIN
src/assets/img/promo_hair_bundles_sale/202204201012-1.jpg


BIN
src/assets/img/promo_hair_bundles_sale/202204201012-2.jpg


BIN
src/assets/img/promo_hair_bundles_sale/202204261006-1.jpg


BIN
src/assets/img/promo_hair_bundles_sale/202204261006-2.jpg


BIN
src/assets/img/promo_hair_bundles_sale/202204261006-3.jpg


BIN
src/assets/img/promo_hair_bundles_sale/202204261006-4.jpg


BIN
src/assets/logo.png


+ 29 - 0
src/main.js

@@ -0,0 +1,29 @@
+import * as Vue from 'vue';
+import 'amfe-flexible';
+import VueWechatTitle from 'vue-wechat-title';
+import { router } from '@/router/router.js';
+
+import App from '@/App.vue';
+import bridge from 'dsbridge';
+
+// 创建并挂载根实例
+const app = Vue.createApp(App);
+
+
+var userToken = bridge.call('gettoken');
+var userToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsYXQiOjE2ODU3NTI3NzQsIm5iZiI6MTY4NTc1Mjc3NCwiZXhwIjoxNjg2MzU3NTc0LCJ1aWQiOiI4MTAzNCIsIndlYnNpdGUiOiJ3ZXN0a2lzcyJ9.K_zn4eGsxB1hEJ9KIdEjRvOGXDP1m-v6n_Nwkp13az4";
+
+
+console.log('入口文件获取userToken ----- ', userToken);
+// app.provide('userToken', userToken);
+if(userToken) {
+    window.sessionStorage.setItem('usertoken', userToken);
+}
+
+//确保 _use_ 路由实例使
+//整个应用支持路由。
+app.use(router);
+app.use(VueWechatTitle);
+app.mount('#app');
+
+

+ 14 - 0
src/router/router.js

@@ -0,0 +1,14 @@
+import * as VueRouter from 'vue-router';
+import { routes } from './routes.js';
+
+// 3. 创建路由实例并传递 `routes` 配置
+// 你可以在这里输入更多的配置,但我们在这里
+// 暂时保持简单
+const router = VueRouter.createRouter({
+  // 4. 内部提供了 history 模式的实现。为了简单起见,我们在这里使用 hash 模式。
+  history: VueRouter.createWebHashHistory(),
+  routes, // `routes: routes` 的缩写
+});
+
+
+export { router };

+ 30 - 0
src/router/routes.js

@@ -0,0 +1,30 @@
+// 1. 定义路由组件.
+// 也可以从其他文件导入
+import Home from '@/view/Home/Home.vue';
+import Test from '@/view/Test/Test.vue';
+// const Home = { template: '<div>Home</div>' }
+// const About = { template: '<div>About</div>' }
+
+// 2. 定义一些路由
+// 每个路由都需要映射到一个组件。
+// 我们后面再讨论嵌套路由。
+const routes = [
+    { 
+        name: 'Home', 
+        path: '/', 
+        component: Home,
+        meta: {
+            title: 'Lucky Wheel'
+        }
+    },
+    { 
+        name: 'Test', 
+        path: '/test', 
+        component: Test,
+        meta: {
+            title: 'Test'
+        }
+    },
+]
+
+export { routes };

+ 215 - 0
src/util/countdown.js

@@ -0,0 +1,215 @@
+/**
+ * @file 倒计时
+ * @createDate 2022-04-12 16:21
+ * @TODO 使用setTimeout模拟setInterval
+ */
+/**
+ * ------------夏令时--------
+ * |--北京时间--|--美国洛杉矶时间--|
+ * |---13:00---|---22:00---|
+ * |---14:00---|---23:00---|
+ * |---15:00---|---00:00---|
+ * |---16:00---|---01:00---|
+ * |---17:00---|---02:00---|
+ * |---18:00---|---03:00---|
+ * |---19:00---|---04:00---|
+ * |---20:00---|---05:00---|
+ * |---21:00---|---06:00---|
+ * |---22:00---|---07:00---|
+ * |---23:00---|---08:00---|
+ * |---24:00---|---09:00---|
+ * |---01:00---|---10:00---|
+ * |---02:00---|---11:00---|
+ * |---03:00---|---12:00---|
+ * |---04:00---|---13:00---|
+ * |---05:00---|---14:00---|
+ * |---06:00---|---15:00---|
+ * |---07:00---|---16:00---|
+ * |---08:00---|---17:00---|
+ * |---09:00---|---18:00---|
+ * 2022-04-11 09:00:00  -- 2022-04-10 18:00:00 getTime 得到的数是相同的
+ * 2022-04-12 09:00:00  -- 2022-04-11 18:00:00
+ * 
+ */
+const LYCountDown = {
+    noop: function() {},
+    GTM_TIME_ZONE: 8, // 北京时间,东八区
+    addZero: function(i) {
+        return i < 10 ? "0" + i : i + "";
+    },
+    // 将当地时间的时针读数转为北京时间的时针读数
+    getBeijingHours: function (oriDate) {
+        var beijingHours = oriDate.getHours() + oriDate.getTimezoneOffset() / 60 + this.GTM_TIME_ZONE;
+        if(beijingHours === 24) {
+            beijingHours = 0;
+        }
+        if(beijingHours > 24) {
+            beijingHours = beijingHours - 24;
+        }
+        return beijingHours;
+    },
+    renderCountDownText: function (endTime,hasDays) {
+        var nowDate = new Date().getTime();
+        var newDate = endTime;
+        var totalSeconds = (newDate - nowDate) / 1000;
+        var days, hours, mins, seconds;
+        if (totalSeconds > 0) {
+            if(hasDays) {
+                days = Math.floor(totalSeconds / 3600 / 24);
+                hours = Math.floor(totalSeconds / 3600) % 24;
+            } else {
+                days = 0;
+                hours = Math.floor(totalSeconds / 3600);
+            }
+
+            mins = Math.floor(totalSeconds / 60) % 60;
+            seconds = Math.floor(totalSeconds) % 60;
+
+        }
+        return {
+            totalSeconds: totalSeconds,
+            days: days,
+            hours: hours,
+            minutes: mins,
+            seconds: seconds
+        };
+    },
+};
+/**
+ * 
+ * @param {Object} options 实例化时的配置项
+ * - deadlineTime {Number} 倒计时结束的时间 时间戳 单位ms
+ * - computeDays {Boolean} 是否计算天数,true表示计算天数,即多于一天的显示天数;false不计算天数,即多于一天的不计算天数,全部归到小时里。
+ * - endCallback {Function} 倒计时结束时的回调函数
+ */
+LYCountDown.DeadlineCountdown = function(options) {
+
+    this.deadlineTime = options.deadlineTime || new Date().getTime();
+    this.endCallback = options.endCallback || LYCountDown.noop;
+    this.countdownCalback = options.countdownCalback || LYCountDown.noop;
+    this.computeDays = !!options.computeDays;
+    this.init();
+};
+LYCountDown.DeadlineCountdown.prototype = {
+    constructor: LYCountDown.DeadlineCountdown,
+    init:function() {
+        return this.startDeadlineTimeCountdown(this.deadlineTime,this.endCallback);
+    },
+    /**
+     * 
+     * @param {Number} timeStr 倒计时结束时间的时间戳 
+     * @param {Function} endFunc 倒计时结束后的回调函数
+     * @returns setInterval函数的id
+     */
+    startDeadlineTimeCountdown: function (timeStr,endFunc) {
+        var that = this;
+        var timer = setInterval(function() {
+            var res = LYCountDown.renderCountDownText(timeStr,that.computeDays)
+            if(res.totalSeconds <= 0) {
+                clearInterval(timer);
+                endFunc && endFunc();
+            } else {
+                that.countdownCalback(res.countDownRes);
+            }
+        },1000);
+        return timer;
+    },
+
+};
+
+
+/**
+ * 
+ * @param {Object} options 实例化时的配置项
+ * - startTime {Number} 倒计时初始开始时间 时间戳 单位ms
+ * - countSeconds {Number} 需要倒计时的秒数  单位s
+ * - countNum {Number} 倒计时循环次数 0表示无限循环
+ * - computeDays {Boolean} 是否计算天数,true表示计算天数,即多于一天的显示天数;false不计算天数,即多于一天的不计算天数,全部归到小时里。
+ * - endCallback {Function} 倒计时结束时的回调函数
+ */
+LYCountDown.CycleCountDown = function(options) {
+    this.startTime = options.startTime;
+    this.countSeconds = options.countSeconds;
+    this.countNum = options.countNum;
+    this.endCallback = options.endCallback || LYCountDown.noop;
+    this.countdownCalback = options.countdownCalback || LYCountDown.noop;
+    this.computeDays = !!options.computeDays;
+
+    this.init();
+};
+LYCountDown.CycleCountDown.prototype = {
+    constructor: LYCountDown.CycleCountDown,
+    init:function() {
+        return this.startCycleCountDown({
+            startTime: this.startTime,
+            countSeconds: this.countSeconds,
+            countNum: this.countNum,
+        },this.endCallback);
+    },
+    
+    cycleCountDown: function (countdownOptions,hasDays) {
+        var res = {
+            status: -1
+        };
+        var originalTime = countdownOptions.startTime;
+        var countSeconds = countdownOptions.countSeconds;
+        var countNum = countdownOptions.countNum;
+        var nowTime = new Date().getTime();
+        var endTime = 0;
+        if( nowTime >= originalTime ) {
+            var divideTime = countSeconds * 1000; // ms
+            var differTime = nowTime - originalTime;
+            
+            var quotient = Math.floor(differTime / divideTime); // 商
+            var s = differTime % divideTime; // 余数
+
+            if((countNum && quotient < countNum) || countNum === 0) {
+                if(s) {
+                    endTime = nowTime + divideTime - s;
+                } else { // 整除
+                    if((differTime / divideTime) < 1) {
+                        endTime = originalTime + divideTime;
+                    } else {
+                        endTime = nowTime + divideTime;
+                    }
+                }
+            }
+            if(endTime) {
+                res.countDownRes = LYCountDown.renderCountDownText(endTime,hasDays);
+                res.status = 1;
+            } else{
+                res.status = -1;
+            }
+        } else {
+            res.status = 0;
+        }
+   
+        return res;
+    },
+    startCycleCountDown: function (countdownOptions,endCallback) {
+        var that = this;
+        var timer = setInterval(function() {
+            var res = that.cycleCountDown(countdownOptions,that.computeDays);
+            // console.log('res -- ', res);
+            if(res.status === -1) {
+                clearInterval(timer);
+                endCallback();
+            } else {
+                
+                that.countdownCalback(res.countDownRes);
+                
+            }
+        },1000);
+        return timer;
+    }
+    
+};
+
+// export default {LYCountDown};
+export {LYCountDown}
+
+/**
+ * export const a = 0; ==> export {a}; import 时使用 import {a} from 'js';
+ * 
+ * export default a; 相当于 default = a, 输出的是一个匿名的对象,import时不可以进行解构赋值
+ */

+ 55 - 0
src/util/lib.js

@@ -0,0 +1,55 @@
+import { router } from '@/router/router.js';
+import qs from 'qs';
+
+// 跳转app页面 的函数
+function toApp(item) {
+    console.log(item.type);
+    let url = '';
+    if(item.type ==="product") {
+        url = '/pages/product/product?productId=' + item.productId
+    } else if(item.type ==="category") {
+        url = '/pages/product/list?' + 'categoryId=' + item.categoryId + '&categoryName=' + encodeURIComponent(item.name)
+    } else if(item.type === 0 || item.type === 'web') { // 有问题,无法跳转
+        url = '/pages/webview/webview?url=' + encodeURIComponent(item.url)
+    } else if(item.type === 'router') {
+        router.push(item.path);
+        return;
+    }
+    console.log('toapp url == ', url);
+    uni.navigateTo({
+        url: url
+    });
+}
+// 获取链接中的参数 返回对象 qs location.hash, location.search
+function getUrlParam() {
+    let searchUrl = location.search;
+    let hashUrl = location.hash;
+    let searchQueryStr = '';
+    let hashQueryStr = '';
+    let res = {};
+    if(searchUrl) {
+        searchQueryStr = searchUrl.split('?')[1];
+    }
+    if(hashUrl) {
+        hashQueryStr = hashUrl.split('?')[1];
+    }
+    if(searchQueryStr) {
+        res = Object.assign({},res,qs.parse(searchQueryStr));
+    }
+    if(hashQueryStr) {
+        res = Object.assign({},res,qs.parse(hashQueryStr));
+    }
+
+    return res;
+}
+
+//动态引入图片
+function getAssetsImg(url) {
+    console.log(import.meta.url);
+    return new URL(`../assets/img/${url}`, import.meta.url).href;
+}
+export {
+    toApp,
+    getUrlParam,
+    getAssetsImg
+}

+ 129 - 0
src/util/rafflerotating.js

@@ -0,0 +1,129 @@
+function RaffleRotate(option) {
+    this.prizeNums = option.prizeNums; // 奖项个数
+    this.prizeDeg = 360 / option.prizeNums; // 每个奖项所占的角度
+    this.activePrize = 0; // 抽中的奖项序号 0角度的奖项对应最后一个序号: 比如6个奖项,0角度时的奖项序号是6
+    this.rotateDom = null;
+    if(option.rotateDom) {
+        this.rotateDom = option.rotateDom;
+    }
+    this.endCallback = option.endCallback || function() {}; // 转动结束时的回调函数
+
+    this.preTimestamp = 0; //前一次requestAnimationFrame调用的时刻
+    this.originalStartTimes = 0; // 转动动画最开始的时间点
+    this.easeOutSatrtDeg = 0; // 开始减速时rotateDom转动的角度
+    this.easeOutTimer = 0; // 减速转动开始的时刻
+    this.easeOutEndDeg = 0; // 减速转动结束时rotateDom应该停留的角度
+
+    this.deg = 0; // rotateDom的实时角度
+    this.maxSpeed = 20; // 转动的最大速度
+    this.isRotating = false; // 是否正在旋转中
+
+    this.forceStop = false;
+}
+RaffleRotate.prototype = {
+    constructor: RaffleRotate,
+    raffleRotaing: function() {
+        var self = this;
+        function rotating(timestamp) {
+            if(self.forceStop) {
+                self.forceStop = false;
+                return;
+            }
+            if (self.preTimestamp === undefined) {
+                self.preTimestamp = timestamp; // 初始化
+            }
+            var totalElapsed = timestamp - self.originalStartTimes; // 从动画开始到此刻的时间间隔
+            var fps = timestamp - self.preTimestamp; // 两次requestAnimationFrame调用的时间间隔 屏幕刷新帧率
+            // 从0速度开始加速直到maxSpeed
+            self.deg = self.deg + self.easeIn(totalElapsed,0,self.maxSpeed,2000);
+            self.deg = self.deg % 360; // 防止角度值过大
+            // console.log(deg);
+            if(self.rotateDom) {
+                self.rotateDom.style.transform = 'rotate(' + self.deg + 'deg)';
+            }
+            self.preTimestamp = timestamp;
+
+            if(self.activePrize > 0) {
+                self.easeOutTimer = timestamp;// Date.now();
+
+                // 开始减速时rotateDom转动的角度
+                self.easeOutSatrtDeg = self.deg;
+                let i = 0;
+                while(++i) {
+                    // 结合开始减速时所处的位置和结束时所处的位置计算旋转总路程
+                    const endDeg = 360 * i - (self.activePrize * self.prizeDeg) - self.easeOutSatrtDeg;
+
+                    // 计算刚开始第一帧旋转的角度,也就是初始速度
+                    const curSpeed = self.easeOut(fps, self.easeOutSatrtDeg, endDeg, 2000) - self.easeOutSatrtDeg;
+
+                    // 当初始速度与当前旋转最大速度相等,即可获取总共需要旋转的角度
+                    if (curSpeed >= self.maxSpeed) {
+                        self.easeOutEndDeg = endDeg;
+                        break;
+                    }
+                }
+
+                // 开始减速
+                return self.slowDown();
+            }
+            window.requestAnimationFrame(rotating);
+        }
+        window.requestAnimationFrame(function(timestamp) {
+            self.originalStartTimes = timestamp;
+            rotating(timestamp);
+        });
+    },
+    slowDown: function () {
+        var self = this;
+        window.requestAnimationFrame(function(timestamp) {
+            // 开始减速时刻到此时刻的时间间隔
+            const timeInterval = timestamp - self.easeOutTimer;
+
+            // 减速完成
+            if (timeInterval >= 2000) {
+                self.isRotating = false;
+                self.endCallback({
+                    index: self.activePrize,
+                    deg: self.deg
+                });
+                self.resetInit();
+                return;
+            }
+
+            // 缓出减速
+            self.deg = self.easeOut(timeInterval, self.easeOutSatrtDeg, self.easeOutEndDeg, 2000) % 360;
+            if(self.rotateDom) {
+                self.rotateDom.style.transform = 'rotate('+ self.deg+ 'deg)';
+            }
+            self.slowDown();
+        });
+    },
+    forceStopRotating: function() {
+        this.forceStop = true;
+    },
+    setActivePrizeIndex: function(index) {
+        this.activePrize = index;
+    },
+    resetInit: function() {
+        this.forceStop = false;
+        this.activePrize = 0;
+    },
+    // t:缓动开始时间 ms
+    // b:缓动开始位置 deg
+    // c:缓动移动的距离 deg
+    // d:缓动持续的时间 ms
+    // 结果值为当前位置值 deg
+    // 缓入函数
+    easeIn: function (t, b, c, d) {
+        if (t >= d) t = d;
+        // 除法赋值(/=)运算符将变量除以右操作数的值,并将结果赋值给该变量。
+        return c * (t /= d) * t + b;
+    },
+    // 缓出函数
+    easeOut: function  (t, b, c, d) {
+        if (t >= d) t = d;
+        return -c * (t /= d) * (t - 2) + b;
+    }
+};
+
+export { RaffleRotate }

+ 112 - 0
src/util/request.js

@@ -0,0 +1,112 @@
+import * as axios from 'axios';
+import { Toast } from 'vant';
+const axiosInstance = axios.create({
+    baseURL: '',// 'https://westkissapp.snjon.com/app-api',//'/app-api',//
+    timeout: 1000000,
+    headers: {
+        // 'Content-Type': 'application/json; charset=utf-8'
+        "Content-Type": "application/x-www-form-urlencoded"
+    }
+});
+const env = process.env.NODE_ENV;
+// console.log('process.env.NODE_ENV  --- qqwe',process.env.NODE_ENV );
+/**
+ * 响应的数据结构
+{
+  // `data` 由服务器提供的响应
+  data: {},
+
+  // `status` 来自服务器响应的 HTTP 状态码
+  status: 200,
+
+  // `statusText` 来自服务器响应的 HTTP 状态信息
+  statusText: 'OK',
+
+  // `headers` 服务器响应的头
+  headers: {},
+
+  // `config` 是为请求提供的配置信息
+  config: {}
+}
+ */
+
+// 添加请求拦截器
+axiosInstance.interceptors.request.use(function (config) {
+    if(config.url !== '/api/raffle/raffle') {
+        Toast.loading({
+            message: 'Loading...',
+            forbidClick: true,
+        });
+    }
+    
+    if(env === 'production') {
+        let apiTypeReg = /(\/app\-api\/)|(\/api\/)/ig;
+        let urlMap = {
+            '/api/':'https://actapi.snjon.com',
+            // 'https://actapi.longyihair.com',
+            '/app-api/': 'https://ios.alipearlhair.com',
+            // 'https://app.westkiss.com'
+            // '/api/': 'https://actapi.snjon.com',
+            // '/app-api/': 'https://westkissapp.snjon.com'
+        };
+        let urlType = config.url.match(apiTypeReg)[0];
+        if(urlType) {
+            config.url = urlMap[urlType] + config.url;
+        }
+    }
+    
+    if(config.method === 'post') {
+        
+        if(config.data && config.url.indexOf('/index/uploader') < 0) {
+            let requestData = config.data;
+            config.data = formdataPostParam(requestData);
+        }
+    } 
+    // 添加token
+    var userToken = window.sessionStorage.getItem('usertoken');
+    if(userToken) {
+        config.headers['token'] = userToken;
+    }
+    // console.log('request config --- ',config);
+    return config;
+}, function (error) {
+    Toast.clear();
+    // 对请求错误做些什么
+    return Promise.reject(error);
+});
+
+// 添加响应拦截器
+axiosInstance.interceptors.response.use(function (response) {
+    console.log('response == ',response);
+    Toast.clear();
+    if(response.status === 200) {
+        if(response.data.code === 200 || response.data.code === 1) {
+            return response.data;
+        } else {
+            if(response.config.url.indexOf('shareAdd') < 0) { 
+                Toast({
+                    message: response.data.msg,
+                    // position: 'top',
+                });
+            }
+            return Promise.reject(response.data);
+        }
+    }
+    return Promise.reject(response.data);
+}, function (error) {
+    Toast.clear();
+    // console.log(error,'kkkkkkkkkk');
+    // 对响应错误做点什么
+    return Promise.reject(error);
+});
+
+// 序列化post请求数据
+function formdataPostParam(data) {
+    let res = '';
+    for(let key in data) {
+        res = res ? res + '&' + encodeURIComponent(key) + '=' + encodeURIComponent(data[key]) : encodeURIComponent(key) + '=' + encodeURIComponent(data[key]);
+    }
+    // console.log('request-data: ',res);
+    return res;
+}
+export default axiosInstance;

파일 크기가 너무 크기때문에 변경 상태를 표시하지 않습니다.
+ 587 - 0
src/view/Home/Home.vue


+ 747 - 0
src/view/Home/home.css

@@ -0,0 +1,747 @@
+.lucky-wheel-page{
+    box-sizing: border-box;
+    width: 100%;
+    padding-bottom: 1.0667rem;
+}
+.lucky-wheel-page-header{
+    position: relative;
+    width: 100%;
+    height: 1.8666rem;
+    text-align: center;
+    line-height: 1.8666rem;
+    background: #262626;
+    box-shadow: 0px .08rem .16rem 1px rgba(0,0,0,0.16);
+    font-size: .5333rem;
+    font-weight: 400;
+    color: #fff;
+}
+
+.lucky-wheel-page-header img{
+    width: .5333rem;
+    height: .5333rem;
+    position: absolute;
+    left: 0.8rem;
+    top: 0.64rem;
+}
+
+.lucky-wheel-page-header .icon-share{
+    flex: 0 0 auto;
+    width: .5333rem;
+    height: .5333rem;
+    margin-left: 9px;
+}
+
+.lucky-wheel-body{
+    box-sizing: border-box;
+    width: 100%;
+    background-image: url('https://cdn.alipearlhair.com/media/wysiwyg/zpage/1685757053.png ');
+    background-position: 0 0;
+    background-size: 100% auto;
+    background-repeat: no-repeat;
+    padding-top: .5333rem;
+}
+.lucky-wheel-top{
+    box-sizing: border-box;
+    width: 100%;
+}
+.lucky-wheel-top-title{
+    display: block;
+    width: 6.4rem;
+    height: 3.36rem;
+    margin: auto;
+    width: 95%;
+}
+.lucky-wheel-top-tips{
+    font-size: .4267rem;
+    font-weight: 400;
+    color: #fff;
+    margin-top: .32rem;
+    text-align: center;
+}
+.lucky-wheel-root{
+    position: relative;
+    box-sizing: border-box;
+    width: 8.48rem;
+    height: 8.48rem;
+    margin: .5333rem auto 0 auto;
+    background-image: url('https://cdn.alipearlhair.com/media/wysiwyg/zpage/1685757043.png');
+    background-position: 0 0;
+    background-size: 100% 100%;
+    box-shadow: 0 .5333rem .5333rem 0px rgba(0,0,0,0.45);
+    border-radius: 50%;
+}
+.lucky-wheel-box{
+    position: absolute;
+    top:0;
+    left: 0;
+    bottom:0;
+    right:0;
+    margin:auto;
+    /* transform: translate3d(-50%, -50%, 0); */
+    width: 7.6267rem;
+    height: 7.6267rem;
+    background-image: url('https://cdn.alipearlhair.com/media/wysiwyg/zpage/1685757044.png');
+    background-position: 0 0;
+    background-size: 100% 100%;
+    /* transition-property: transform;
+    transition-duration: 6s; */
+}
+.lucky-wheel-box[active="4"] .lucky-wheel-item.index-2{
+    right: 1.1733rem
+}
+.lucky-wheel-box[active="4"] .lucky-wheel-item.index-5{
+    left: .9867rem;
+}
+.lucky-wheel-box[active="5"] .lucky-wheel-item.index-1{
+    left: 3.0133rem;
+}
+.lucky-wheel-box[active="5"] .lucky-wheel-item.index-2{
+    right: 1.2533rem;
+}
+.lucky-wheel-box[active="1"] .lucky-wheel-item.index-2{
+  top: 1.8667rem;
+}
+.lucky-wheel-box[active="1"] .lucky-wheel-item.index-5{
+    top: 4.64rem;
+}
+.lucky-wheel-box[active="2"] .lucky-wheel-item.index-1{
+    left: 3.0933rem;
+    top: .5333rem;
+}
+.lucky-wheel-box[active="2"] .lucky-wheel-item.index-2{
+    right: 1.2267rem;
+}
+.lucky-wheel-box[active="3"] .lucky-wheel-item.index-1{
+    left: 3.2267rem;
+}
+.lucky-wheel-box[active="3"] .lucky-wheel-item.index-4{
+    left: 3.3867rem;
+}
+
+.lucky-wheel-item{
+    position: absolute;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    color: #fff;
+    transform-origin: center center;
+    line-height: 1;
+}
+.lucky-wheel-item-title{
+    font-size: .64rem;
+    font-weight: bold;
+}
+.lucky-wheel-item-label{
+    font-size: .32rem;
+    font-weight: 400;
+    margin-top: .16rem;
+}
+
+.lucky-wheel-item.index-1[data-title="Thanks"]{
+    transform: rotate(90deg) translateY(28px);
+}
+.lucky-wheel-item.index-2[data-title="Thanks"]{
+    transform: rotate(150deg);
+}
+.lucky-wheel-item.index-3[data-title="Thanks"]{
+    transform: rotate(-150deg) translateY(.2667rem);
+}
+.lucky-wheel-item.index-4[data-title="Thanks"]{
+    transform: rotate(-90deg) translateY(-0.2667rem);
+}
+.lucky-wheel-item.index-5[data-title="Thanks"]{
+    transform: rotate(-30deg) translateY(0.2667rem);
+}
+.lucky-wheel-item.index-6[data-title="Thanks"]{
+    transform: rotate(30deg) translateY(0.5333rem);
+}
+.lucky-wheel-item[data-title="Thanks"]{
+    transform: rotate(210deg);
+}
+.lucky-wheel-item[data-index="1"]{
+    top: .6667rem;
+    left: 3.3067rem;
+}
+.lucky-wheel-item[data-index="2"]{
+    top: 1.92rem;
+    right: .9867rem;
+}
+.lucky-wheel-item[data-index="3"]{
+    /* bottom: 1.8933rem; */
+    top: 4.4267rem;
+    right: .6933rem;
+}
+.lucky-wheel-item[data-index="4"]{
+    left: 3.4667rem;
+    top: 5.7067rem;
+}
+.lucky-wheel-item[data-index="5"]{
+    left: .6933rem;
+    /* bottom: 1.8933rem; */
+    top: 4.4267rem;
+}
+.lucky-wheel-item[data-index="6"]{
+    left: 1.0933rem;
+    top: 1.92rem;
+}
+/*
+.lucky-wheel-item[data-index="1"]{
+    top: .6667rem;
+    left: 3.3067rem;
+}
+.lucky-wheel-item[data-index="2"]{
+    top: 1.92rem;
+    right: .9867rem;
+}
+.lucky-wheel-item[data-index="3"]{
+    top: 4.4rem;
+    right: .6933rem;
+}
+.lucky-wheel-item[data-index="4"]{
+    left: 3.3067rem;
+    bottom: .6133rem;
+}
+.lucky-wheel-item[data-index="5"]{
+    left: .6667rem;
+    top: 4.4rem;
+}
+.lucky-wheel-item[data-index="6"]{
+    left: 1.0933rem;
+    top: 1.92rem;
+}
+*/
+.lucky-wheel-star{
+    display: block;
+    width: 2.2933rem;
+    height: 2.2933rem;
+    top: -0.16rem;
+    left: .4533rem;
+}
+.lucky-wheel-go{
+    position:absolute;
+    width: 2.5333rem;
+    height: 2.9067rem;
+    left: 50%;
+    top: 50%;
+    transform: translate3d(-50%, -50%, 0);
+}
+
+.lucky-wheel-start-box{
+    box-sizing: border-box;
+    width: 100%;
+    padding: 0 .3rem;
+    margin-top: .5333rem;
+}
+.lucky-wheel-start-btn{
+    box-sizing: border-box;
+    width: 100%;
+    height: 1.6rem;
+    color:#000;
+    border-radius: 4px;
+    font-size: .4267rem;
+    font-weight: bold;
+    line-height: 1.6rem;
+    text-align: center;
+}
+.lucky-wheel-chance{
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    font-size: 0.3733rem;
+    line-height: 0.402rem;
+    padding: 0 0.5333rem;
+}
+
+.lucky-wheel-chance-left div {
+    width: 0.8267rem;
+    height: 0.8267rem;
+    border-radius: 0.2133rem;
+    text-align: center;
+    line-height: 0.8267rem;
+    background: url("https://cdn.alipearlhair.com/media/wysiwyg/zpage/1685757046.png");
+    background-repeat: no-repeat;
+    background-size: cover;
+    color: #fff;
+    margin-bottom: 0.29rem;
+}
+
+.lucky-wheel-chance-right{
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-start;
+    align-items: flex-end;
+    padding-top: 0.25rem;
+    box-sizing: border-box;
+}
+
+.lucky-wheel-chance-right img {
+    height: 0.6133rem;
+    width: 0.6133rem;
+}
+
+.lucky-wheel-backcolor{
+background: linear-gradient(336deg, #FEDFA9 0%, #E0B17E 100%);
+}
+
+.lucky-wheel-list-section{
+    box-sizing: border-box;
+    width: 100%;
+    padding: 0 .8rem;
+    margin-top: .5333rem;
+}
+.lucky-wheel-list{
+    box-sizing: border-box;
+    width: 100%;
+    height: 2.6667rem;
+    background-image: url("https://cdn.alipearlhair.com/media/wysiwyg/zpage/1685757049.png");
+    background-repeat: no-repeat;
+    background-size: cover;
+    border-radius: 8px;
+    padding: .2133rem 0;
+    overflow-y: hidden;
+}
+.lucky-wheel-list-inner{
+    box-sizing: border-box;
+    width: 100%;
+}
+.lucky-wheel-list-inner.slidescroll{
+    animation-name: slidescroll;
+    animation-duration: 30s;
+    animation-iteration-count: infinite;
+    animation-timing-function: linear;
+}
+@keyframes slidescroll {
+    0%{
+        transform: translateY(0%);
+    }
+    50%{
+        transform: translateY(-50% + 1.24005rem);
+    }
+    100%{
+        transform: translateY(calc(-100% + 2.4801rem));
+    }
+}
+.lucky-wheel-list-item{
+    box-sizing: border-box;
+    width: 100%;
+    padding: 0.4rem .5333rem 0 .5333rem;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    font-size: .32rem;
+    font-weight: 400;
+    color: #fff;
+}
+.lucky-wheel-list-item:first-child{
+    margin-top: 0;
+}
+
+.lucky-wheel-rules-section{
+    box-sizing: border-box;
+    position: fixed;
+    width: 100%;
+    height: 100%;
+    top: 0;
+    background: rgba(0, 0, 0, 0.3);
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+}
+.lucky-wheel-rules{
+    box-sizing: border-box;
+    width: 83%;
+    padding: .5333rem;
+    background: #FFD1B9;
+    padding-top: 0.8rem;
+}
+.lucky-wheel-rules-title{
+    font-weight: 400;
+    margin: 0.8rem 0 0.53333rem;
+    font-size: .3733rem;
+    font-weight: bold;
+}
+
+.lucky-wheel-rules-close {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+
+.lucky-wheel-rules-close img{
+    width: 0.64rem;
+    height: 0.64rem;
+}
+.lucky-wheel-rules-content{
+    height: 6.8rem;
+    overflow: auto;
+}
+.lucky-wheel-rules p{
+    font-size: .3733rem;
+    font-weight: 400;
+    line-height: .5333rem;
+}
+.lucky-wheel-picked-section{
+    box-sizing: border-box;
+    width: 100%;
+    margin-top: .5333rem;
+}
+.lucky-wheel-picked{
+    box-sizing: border-box;
+    width: 100%;
+    padding: .5333rem 0rem;
+    border-radius: 4px;
+}
+.lucky-wheel-picked-title{
+    text-align: center;
+    font-size: .7467rem;
+    font-weight: 700;
+    color: #000;
+}
+.lucky-wheel-picked-list{
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+    justify-content: space-between;
+    box-sizing: border-box;
+    width: 100%;
+}
+.lucky-wheel-picked-item{
+    width: 48%;
+    margin-top: .4rem;
+}
+.lucky-wheel-picked-item-info{
+    text-align: center;
+    width: 100%;
+    padding: 0.2667rem;
+    padding-top: 0.5333rem;
+    position: relative;
+    background: #fff5f0;
+    box-sizing: border-box;
+}
+.lucky-wheel-picked-viewmore{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 4.2667rem;
+    height: .9333rem;
+    margin: .5333rem auto 0 auto;
+    border: 1px solid #000;
+    font-size: 0.48rem;
+    font-weight: 400;
+    color: #fff;
+    background-color: #000;
+}
+.lucky-wheel-modal{
+    box-sizing: border-box;
+    width: 8.2667rem;
+    background-color: #fff;
+    padding: .8rem .5333rem;
+}
+.lucky-wheel-modal-close{
+    font-size: .64rem;
+    text-align: center;
+}
+.lucky-wheel-modal-notice{
+    text-align: center;
+    margin-top: .8rem;
+    font-size: .3733rem;
+    font-weight: 900;
+}
+.lucky-wheel-modal-des{
+    box-sizing: border-box;
+    width: 100%;
+    margin-top: .8rem;
+}
+.lucky-wheel-modal-des p{
+    text-align: center;
+    font-size: .3733rem;
+    font-weight: 400;
+    line-height: .64rem;
+}
+.lucky-wheel-modal-btn{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 4.2667rem;
+    height: .9333rem;
+    margin: .8rem auto 0 auto;
+    background-color: #000;
+    color: #fff;
+    font-size: .3733rem;
+    font-weight: 500;
+}
+.lucky-wheel-modal-coupon{
+    position: relative;
+    box-sizing: border-box;
+    width: 6.7467rem;
+    margin: .8rem auto 0 auto;
+    padding-left: .8533rem;
+}
+.lucky-wheel-modal-coupon-left{
+    position: absolute;
+    height: 2.4267rem;
+    width: 1.12rem;
+    background: #FF0827;
+    border-radius: .32rem 0 0 .32rem;
+    top:0;
+    left:0;
+    font-size: .5333rem;
+    color: #b2051b;
+    font-weight: 500;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+}
+.lucky-wheel-modal-coupon-left span{
+    transform: translateX(-5%) rotate(270deg);
+}
+.lucky-wheel-modal-coupon-right{
+    position: relative;
+    box-sizing: border-box;
+    width: 5.8933rem;
+    height: 2.4267rem;
+    border-radius: .32rem 0 0 .32rem;
+    background-color: #FFDEE2;
+    z-index: 1;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+}
+.lucky-wheel-modal-cinfo{
+    flex: 0 0 auto;
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    width: 4.6933rem;
+    height: 100%;
+    border-right: 1px dashed #999;
+}
+.lucky-wheel-modal-cinfo-title{
+    font-size: .8533rem;
+    font-weight: bold;
+    text-align: center;
+}
+.lucky-wheel-modal-cinfo-tips{
+    font-size: .32rem;
+    color: #0b0b0b;
+    text-align: center;
+    font-weight: 400;
+    margin-top: .1867rem;
+}
+.lucky-wheel-modal-ccode{
+    flex: 0 0 auto;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 1.2rem;
+    height: 100%;
+}
+.lucky-wheel-modal-ccode span{
+    display: block;
+    font-size: .32rem;
+    font-weight: bold;
+    transform: rotate(270deg);
+    white-space: nowrap;
+}
+.lucky-wheel-modal-coupon-right .dot{
+    position: absolute;
+    width: .2667rem;
+    height: .2667rem;
+    border-radius: 50%;
+    background-color: #fff;
+}
+.lucky-wheel-modal-coupon-right .dot.top{
+    top:0;
+    left: 4.6933rem;
+    transform: translate3d(-50%, -50%, 0);
+}
+.lucky-wheel-modal-coupon-right .dot.bottom{
+    bottom:0;
+    left: 4.6933rem;
+    transform: translate3d(-50%, 50%, 0);
+}
+.lucky-wheel-modal-points{
+    text-align: center;
+    font-size: 0;
+    margin-top: .8rem;
+}
+.lucky-wheel-modal-points .icon{
+    font-size: 1.6rem;
+}
+.lucky-wheel-modal-points span{
+    font-size: 1.0667rem;
+    font-weight: bold;
+    margin-left: .2667rem;
+}
+
+
+
+.lucky-wheel-toast{
+    box-sizing: border-box;
+    width: 4.8rem;
+    padding: .8rem .5333rem;
+    color: #fff;
+}
+.lucky-wheel-toast-close{
+    display: flex;
+    justify-content: center;
+    text-align: center;
+    font-size: .64rem;
+    line-height: 1;
+}
+.lucky-wheel-toast-close .icon{
+    display:none;
+}
+.lucky-wheel-toast[type="error"] .lucky-wheel-toast-close .icon-error{
+    display: block;
+}
+.lucky-wheel-toast[type="success"] .lucky-wheel-toast-close .icon-success{
+    display: block;
+}
+.lucky-wheel-toast-content{
+    margin-top: .2667rem;
+    text-align: center;
+    font-size: .3733rem;
+    font-weight: 400;
+    line-height: .64rem;
+}
+
+
+.productcard-item{
+    display: block;
+    box-sizing: border-box;
+    width: 100%;
+    text-decoration: none;
+  }
+  .productcard-item-img{
+    position: relative;
+    display: block;
+    width: 100%;
+  }
+  .productcard-item-img img{
+    display:block;
+    width: 100%;
+    height: auto;
+  }
+  .productcard-item-img .productcard-item-img-cornor{
+    position: absolute;
+    width: 4.7467rem;
+    height: auto;
+    top:0;
+    right:0;
+  }
+  .productcard-item-percent{
+    font-size: 0.4267rem;
+    position: absolute;
+    top: -6%;
+    left: 50%;
+    transform: translate(-50%, 0);
+    width: 4rem;
+    height: 0.5333rem;
+    line-height: .64rem;
+    text-transform: uppercase;
+    background-color: #ffd1b9;
+  }
+  .productcard-item-category{
+    font-size: 0.3733rem;
+  }
+  .productcard-item-name{
+    font-size: 0.32rem;
+    overflow: hidden;
+    -o-text-overflow: ellipsis;
+    text-overflow: ellipsis;
+    display: -webkit-box;
+    -webkit-line-clamp: 1;
+    word-break: break-all;
+    -webkit-box-orient: vertical;
+    text-transform: uppercase;
+    margin: 0.2667rem 0;
+  }
+  .productcard-item-price{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    margin: 0.2667rem 0;
+  }
+  .productcard-item-nowprice{
+    font-size: 0.4267rem;
+    padding-right: 0.1333rem;
+  }
+  .productcard-item-lineprice{
+    font-size: 0.32rem;
+    text-decoration: line-through;
+  }
+  .productcard-item-num{
+    font-size: 0.32rem;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    text-transform: uppercase;
+    font-family: "Rokkitt-Medium";
+  }
+  .productcard-item-num .icon{
+    width: .5333rem;
+    height: .5333rem;
+    margin-right: .08rem;
+  }
+  .productcard-item-shopnow.none{
+    display:none;
+  }
+  .productcard-item-shopnow{
+    text-align: center;
+    margin-top: .2667rem;
+    font-size:0;
+  }
+  .productcard-item-shopnow-a{
+    font-size: .3733rem;
+    color:#000000;
+    line-height: 1em;
+    text-decoration: underline;
+  }
+  .productcard-item-shopnow-a .icon{
+    font-size: .48rem;
+  }.productcard-item{
+    display: block;
+    box-sizing: border-box;
+    width: 100%;
+    text-decoration: none;
+  }
+  .productcard-item-img{
+    position: relative;
+    display: block;
+    width: 100%;
+  }
+  .productcard-item-img img{
+    display:block;
+    width: 100%;
+    height: auto;
+  }
+  .productcard-item-img .productcard-item-img-cornor{
+    position: absolute;
+    width: 4.7467rem;
+    height: auto;
+    top:0;
+    right:0;
+  }
+  .productcard-item-shopnow.none{
+    display:none;
+  }
+  .productcard-item-shopnow{
+    text-align: center;
+    margin-top: .2667rem;
+    font-size:0;
+  }
+  .productcard-item-shopnow-a{
+    font-size: .3733rem;
+    color:#000000;
+    line-height: 1em;
+    text-decoration: underline;
+  }
+  .productcard-item-shopnow-a .icon{
+    font-size: .48rem;
+  }
+
+

+ 23 - 0
src/view/Test/Test.vue

@@ -0,0 +1,23 @@
+<script setup>
+/**
+ * 在 setup 中你应该避免使用 this,因为它不会找到组件实例。
+ * setup 的调用发生在 data property、computed property 或 methods 被解析之前,
+ * 所以它们无法在 setup 中被获取。
+ */
+import { ref, onMounted, watch, inject } from 'vue';
+import { useRouter } from 'vue-router';
+const rotating = ref(true);
+</script>
+
+<template>
+ 
+<div class="lucky-wheel-root-inner rotating">
+    dfgdfgf
+</div>
+
+   
+</template>
+
+<style scoped lang="scss">
+@import "./test.scss";
+</style>

+ 67 - 0
src/view/Test/test.scss

@@ -0,0 +1,67 @@
+.lucky-wheel-root-inner{
+    position: relative;
+    box-sizing: border-box;
+    width: 8.48rem;
+    height: 8.48rem;
+    margin: .16rem auto 0 auto;
+    background-image: url('https://cdn.westkiss.com/media/wysiwyg/wap/202303211430.png');
+    background-position: 0 0;
+    background-size: 100% 100%;
+    box-shadow: 0 .5333rem .5333rem 0px rgba(158,106,112,0.5);
+    border-radius: 50%;
+    transform: rotate(0deg);
+}
+.rotating{
+    // animation-name: rotaing;
+    animation-timing-function: ease-in-out;
+    // animation-duration: 8s;
+    animation-name: rotating;
+    animation-duration: 4s;
+    // animation-iteration-count: infinite;
+    // animation-direction: alternate ;
+    animation-timing-function: ease-in;
+ 
+    animation-delay: 0s;
+}
+@keyframes rotating {
+    0%{
+        transform: rotate(0deg);
+        // width: 0px;
+    }
+    50%{
+        transform: rotate(1800deg);
+        // width: 200px;
+        background-color: pink;
+    }
+    100%{
+        transform: rotate(3600deg);
+        // width: 400px;
+    }
+
+}
+
+
+#div3{
+    width: 100px;
+    height: 100px;
+    background-color: pink;
+    margin-bottom: 10px;
+    animation-name: test3;
+    animation-duration: 4s;
+    animation-iteration-count: infinite;
+    animation-direction: alternate ;
+    animation-timing-function: ease-in;
+    background-color: yellow;
+    animation-delay: 0s;
+
+}
+@keyframes test3{
+   0%{
+       width: 100px;
+   }
+   50%{width: 400px;background-color:red;}
+   100%{
+       width: 700px;
+        animation-timing-function:linear;
+   }
+}

+ 92 - 0
vite.config.js

@@ -0,0 +1,92 @@
+import { resolve } from 'path';
+import { defineConfig } from 'vite';
+import vue from '@vitejs/plugin-vue';
+import styleImport, { VantResolve } from 'vite-plugin-style-import';
+
+function pathResolve(dir) {
+  return resolve(__dirname, dir);
+}
+
+// https://vitejs.dev/config/
+export default defineConfig({
+    base: './',
+    build: {
+      outDir: 'dist/luckywheel'
+    },
+    resolve:{
+        alias:[
+            {
+                find: '@',
+                replacement: pathResolve('src'),
+            }
+        ],
+    },
+    plugins: [
+      vue(),
+      styleImport({
+        // vant
+        libs: [
+          {
+            libraryName: 'vant',
+            esModule: true,
+            // https://github.com/anncwb/vite-plugin-style-import/issues/52
+            resolveStyle: (name) => `../es/${name}/style/index`,
+            // resolveStyle: (name) => `../node_modules/vant/es/${name}/style/index`, // 如果报找不到vant可以改用上面的路径
+          },
+        ],
+        // nutui
+
+
+        // libs: [
+        //   {
+        //     libraryName: '@nutui/nutui',
+        //     libraryNameChangeCase: 'pascalCase',
+        //     resolveStyle: (name) => {
+        //       return `@nutui/nutui/dist/packages/${name}/index.scss`
+        //     }
+        //   }
+        // ]
+
+
+        
+      }),
+    ],
+    css: {
+      preprocessorOptions: {
+        scss: {
+          charset: false,
+          // 配置 nutui 全局 scss 变量
+          // additionalData: `@import "@nutui/nutui/dist/styles/variables.scss";`
+        }
+      }
+    },
+    server: {
+        proxy: {
+          // 字符串简写写法
+        //   '/api': 'http://www.myblog.com/api',
+          // 选项写法
+          '/app-api': {
+            target: "https://ios.alipearlhair.com",//'http://www.myblog.com',
+            changeOrigin: true, // 设置成false报错
+            // rewrite: (path) => {
+            //     console.log(path,'ooooooooooooo');
+            //     path.replace(/^\/api/, '')
+            // }
+          },
+          '/api': {
+            target: "http://actapi.snjon.com",//'http://www.myblog.com',
+            changeOrigin: true, // 设置成false报错
+            // rewrite: (path) => {
+            //     console.log(path,'ooooooooooooo');
+            //     path.replace(/^\/api/, '')
+            // }
+          }
+          // 正则表达式写法
+        //   '^/fallback/.*': {
+        //     target: 'http://jsonplaceholder.typicode.com',
+        //     changeOrigin: true,
+        //     rewrite: (path) => path.replace(/^\/fallback/, '')
+        //   }
+        }
+    }
+})