<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          我用 Vue3+Ts+Vite2 寫了一個美女小黃站

          共 37377字,需瀏覽 75分鐘

           ·

          2021-04-06 15:16

          首先使用以下命令創(chuàng)建項(xiàng)目

          yarn create @vitejs/app vue3-ts-vite2 --template vue-ts

          vite.config.js

          import { defineConfig } from 'vite'
          import vue from '@vitejs/plugin-vue'
          import path from 'path'
          // https://vitejs.dev/config/
          export default defineConfig({
            plugins: [vue()],
            base'./'// 打包路徑
            resolve: { // 解析
              alias: { // 重命名路勁
                '@': path.resolve(__dirname, './src')
              }
            },
            server: {
              port4000// 服務(wù)端口
              opentrue// 是否自動打開瀏覽器
              host'localhost'// 主機(jī)名字
              proxy: { // 代理
                '/api': {
                  target'/api',
                  changeOrigintrue,
                  wsfalse,
                  rewritepath => path.replace(/^\/api/'')
                }
              },
              corstrue
            }
          })

          tsconfig.json

          {
            "compilerOptions": {
              "target""esnext",
              "module""esnext",
              "moduleResolution""node",
              "strict"true,
              "jsx""preserve",
              "sourceMap"true,
              "resolveJsonModule"true,
              "esModuleInterop"true,
              "lib": ["esnext""dom"],
              "types": ["vite/client"]
            },
            "include": ["src/**/*.ts""src/**/*.d.ts""src/**/*.tsx""src/**/*.vue"]
          }

          postcss.config.js

          module.exports = {
              "plugins": {
                  "postcss-pxtorem": {
                      rootValue37.5,
                      // Vant 官方根字體大小是 37.5
                      propList: ['*'],
                      selectorBlackList: ['.norem']
                      // 過濾掉.norem-開頭的class,不進(jìn)行rem轉(zhuǎn)換
                  }
              }
          }

          package.json

          {
            "name""vue3-vite2-ts",
            "version""0.0.0",
            "scripts": {
              "dev""vite",
              "build""vue-tsc --noEmit && vite build",
              "serve""vite preview"
            },
            "dependencies": {
              "@types/node""^14.14.37",
              "axios""^0.21.1",
              "jsonwebtoken""^8.5.1",
              "mockjs""^1.1.0",
              "vant""^3.0.11",
              "vue""^3.0.10",
              "vue-router""4",
              "vuex""^4.0.0"
            },
            "devDependencies": {
              "@vitejs/plugin-vue""^1.1.5",
              "@vue/compiler-sfc""^3.0.5",
              "postcss-pxtorem""^6.0.0",
              "sass""^1.32.8",
              "typescript""^4.1.3",
              "vite""^2.1.3",
              "vue-tsc""^0.0.8"
            }
          }

          index.html

          <!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.0, user-scalable=no" />

            <title>Vite App</title>
          </head>

          <body>
            <div id="app"></div>
            <script type="module" src="/src/main.ts"></script>
          </body>

          </html>

          src/api/request.ts

          import axios from 'axios'

          const service = axios.create({ // 創(chuàng)建服務(wù)
              baseURL'/api/',
              timeout5000
          })

          service.interceptors.request.use(config => { // 請求攔截處理
              const token = window.localStorage.getItem("accessToken")
              if (token) {
                  config.headers.common.Authorization = token
              }
              return config
          }, error => {
              return Promise.reject(error)
          })

          service.interceptors.response.use(response => { // 響應(yīng)攔截處理
              const res = response.data
              if (response.status !== 200) {
                  return Promise.reject(new Error(res.message || 'Error'))
              } else {
                  return res
              }
          }, error => {
              return Promise.reject(error)
          })

          export default service

          src/api/index.ts

          import type { AxiosPromise } from 'axios'
          import request from './request'

          // 獲取首頁banner新聞數(shù)據(jù)
          export const getBannerList = (): AxiosPromise => {
              return request({
                  url'/bannerList'
              })
          }

          //  獲取首頁newsList數(shù)據(jù)
          export const getNewsList = (): AxiosPromise => {
              return request({
                  url'/newsList'
              })
          }

          //獲取newsDetail數(shù)據(jù)
          export const getNewsDetail = (id: any): AxiosPromise => {
              return request.post('/detailList', {
                  id
              })
          }

          // 登錄驗(yàn)證
          export const toLogin = (data: Object): AxiosPromise => {
              return request.post('/login', data)
          }

          export default {
              getBannerList,
              getNewsList,
              getNewsDetail,
              toLogin
          }

          src/api/mock.ts

          import Mock from 'mockjs'
          interface Data {
              id?: string | number,
              title?: string,
              images?: string | Array<string>,
              author?: string,
              token?: string
          }

          const bannerData: Array<Data> = [
              {
                  "id""1",
                  "images""./2021-02-27/1.jpg",
                  "title""翹屁美女"
              },
              {
                  "id""2",
                  "images""./2021-02-27/2.jpg",
                  "title""青春美女"
              },
              {
                  "id""3",
                  "title""翹屁美女",
                  "images""./2021-02-27/3.jpg",
              },
              {
                  "id""4",
                  "title""性感美女",
                  "images""./2021-02-27/4.jpg",
              },
              {
                  "id""5",
                  "title""哈哈美女",
                  "images""./2021-02-27/5.jpg",
              },
              {
                  "id""6",
                  "title""性感美女",
                  "images""./2021-02-27/6.jpg",
              },
              {
                  "id""7",
                  "title""愛笑美女",
                  "images""./2021-02-27/7.jpg",
              },
              {
                  "id""8",
                  "title""哈哈美女",
                  "images""./2021-02-27/8.jpg",
              },
              {
                  "id""9",
                  "title""愛笑美女",
                  "images""./2021-02-27/9.jpg",
              },
              {
                  "id""10",
                  "title""愛笑美女",
                  "images""./2021-02-27/10.jpg",
              },
              {
                  "id""11",
                  "title""愛笑美女",
                  "images""./2021-02-27/11.jpg",
              },
              {
                  "id""12",
                  "title""愛笑美女",
                  "images""./2021-02-27/12.jpg",
              },
              {
                  "id""13",
                  "title""愛笑美女",
                  "images""./2021-02-27/13.jpg",
              },
              {
                  "id""14",
                  "title""愛笑美女",
                  "images""./2021-02-27/14.jpg",
              },
              {
                  "id""15",
                  "title""愛笑美女",
                  "images""./2021-02-27/15.jpg",
              },
          ]

          const newsData: Array<Data> = [
              {
                  "id""1",
                  "images": ["../assets/logo.png"],
                  "title""金發(fā)碧眼為什么很少在白人以外的種族出現(xiàn)?",
                  "author""作者 / biokiwi"
              },
              {
                  "id""2",
                  "title""《哈利波特》原著中與電影中人物有哪些差別?",
                  "author""作者 / kalinnn",
                  "images": ["../assets/logo.png"]
              },
              {
                  "id""3",
                  "title""有哪些適合情侶兩個人一起玩的桌游?",
                  "author""作者 / 北邙",
                  "images": ["../assets/logo.png"]
              }
          ]

          const loginData: Data = {
              "token""eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInVzZXJfaWQiOjEsImlhdCI6MTU5NDI2MjQ5NSwiZXhwIjoxNTk0MzQ4ODk1fQ.1MJ_MAFgpBjOjpggj69Xz8F_evBcMAenRK_7a8fdVrc"
          }

          Mock.mock('/api/bannerList''get', {
              "data": bannerData
          })

          Mock.mock('/api/newsList''get', {
              "data": newsData
          })


          Mock.mock('/api/login''post', {
              "data": loginData
          })

          src/api/token.ts

          import jwt from 'jsonwebtoken'
          const jwtScrect: string = "accessToken"
          const genToken = (username: string, password: string): string => {
              const token: string = jwt.sign({ username, password }, jwtScrect, { expiresIn'24h' })

              return token
          }

          export default {
              genToken
          }

          src/components/Banner/index.vue

          <template>
            <div class="swipe-content">
              <van-swipe class="my-swipe" :autoplay="3000" indicator-color="white" lazy-render>
                <van-swipe-item v-for="item in bannerData" :key="item.id">
                  <img :src="item.images" class="img" :alt="item.title" @click="toDetail(item)" />
                </van-swipe-item>
              </van-swipe>
            </div>
          </template>
          <script>
          import { useRouter } from "vue-router";
          export default {
            name"Banner",
            props: {
              bannerData: {
                typeArray
              }
            },
            setup(props) {
              const router = useRouter();
              const toDetail = item => {
                router.push({
                  name"Detail",
                  params: {
                    id: item.id,
                    itemJSON.stringify(item)
                  }
                });
              };

              return {
                toDetail
              };
            }
          };
          </script>
          <style lang="scss" scoped>
          .img {
            width: 100%;
            height: 100%;
          }
          .my-swipe {
            width: 100%;
          }
          .my-swipe .van-swipe-item {
            width: 100%;
            //   color: #fff;
            font-size: 1rem;
            //   line-height: 6rem;
            text-align: center;
            background-color: #fff;
          }
          </style>

          src/router/index.ts

          import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'

          const routes: Array<RouteRecordRaw> = [
              {
                  path'/',
                  name'Home',
                  meta: {
                      title"首頁",
                      keepAlivetrue,
                      requireAuthtrue
                  },
                  component() => import("../views/Home/index.vue")
              },
              {
                  path'/select',
                  name'Select',
                  meta: {
                      title"選項(xiàng)",
                      keepAlivetrue,
                      requireAuthtrue
                  },
                  component() => import("../views/Select/index.vue")
              },
              {
                  path'/detail/:id/:item',
                  name'Detail',
                  meta: {
                      title"選項(xiàng)",
                      keepAlivetrue,
                      requireAuthtrue
                  },
                  component() => import("../views/Detail/index.vue")
              },
              {
                  path'/login',
                  name'Login',
                  meta: {
                      title"登錄",
                      keepAlivetrue
                  },
                  component() => import("../views/Login/index.vue")
              }
          ]

          const router = createRouter({
              history: createWebHashHistory(),
              routes
          })

          router.beforeEach((to, from, next) => {
              if (to.meta.requireAuth) {  // 判斷該路由是否需要登錄權(quán)限
                  if (window.localStorage.getItem('accessToken')) {  // 通過vuex state獲取當(dāng)前的token是否存在
                      next();
                  }
                  else {
                      next({
                          path'/login',
                          query: { redirect: to.fullPath }  // 將跳轉(zhuǎn)的路由path作為參數(shù),登錄成功后跳轉(zhuǎn)到該路由
                      })
                      window.localStorage.clear()
                  }
              }
              else {
                  next();
              }
          })

          export default router

          src/store/index.ts

          import { createStore } from 'vuex'
          export default createStore({
              state: {
                  listData: { 110 },
                  num10
              },
              mutations: {
                  setData(state, value) {
                      state.listData = value
                  },
                  addNum(state) {
                      state.num = state.num + 10
                  }
              },
              actions: {
                  setData(context, value) {
                      context.commit('setData', value)
                  }
              },
              modules: {}
          })

          src/utils/rem.ts

          import { stringifyQuery } from "vue-router"

          const baseSize: number = 37.5

          function setRem({
              const scale: number = document.documentElement.clientWidth / 375
              document.documentElement.style.fontSize = baseSize * Math.min(scale, 2) + 'px'
          }

          setRem()

          window.onresize = function ({
              setRem()
          }

          src/views/Detail/index.vue

          <template>
            <div class="detail-wrap">
              <p class="title">{{item.title}}</p>
              <div class="img" v-for="(img, index) in item.imgs" :key="index">
                <img :src="img" class="img" />
              </div>
            </div>

          </template>

          <script lang="ts">
          import { useRoute } from "vue-router";
          import { reactive, toRefs, computed } from "vue";
          export default {
            name: "Detail",
            setup(props) {
              interface Data {
                id?: string | Array<string>;
                item?: Object;
              }
              let data: Data = {
                id: "",
                item: Object
              };
              const state = reactive(data);

              const route = useRoute();
              const item = computed(() => route.params.item).value;
              state.id = computed(() => route.params.id).value;
              state.item = JSON.parse(item);

              return {
                ...toRefs(state)
              };
            }
          };
          </
          script>

          <style lang="scss" scoped>
          .detail-wrap {
            width: 100%;
            .title {
              position: fixed;
              top: 0;
              left: 0;
              width: 100%;
              color: #cccc;
              font-size: 20px;
              margin-block-start: 0;
              margin-block-end: 0;
              background-color: #2c323c;
            }
            .img {
              width: 100%;
              height: 100%;
            }
          }
          </style>


          src/views/Select/index.vue

          <template>
            <div class="select-wrap">
              <van-radio-group
                v-model="state.checked"
                direction="horizontal"
                :icon-size="30"
                @change="change"
              >

                <van-radio name="1"></van-radio>
                <van-radio name="2"></van-radio>
              </van-radio-group>
            </div>

          </template>

          <script lang="ts" setup="props">
          import { ref, getCurrentInstance } from "vue";
          import { useRouter } from "vue-router";
          const router = useRouter();
          const state = ref({
            checked: ""
          });
          const { ctx } = getCurrentInstance();

          const change = (e: string) => {
            const num = parseInt(e);
            switch (num) {
              case 1:
                console.log("男");
                ctx.$toast({
                  type: "text",
                  message: "暫無帥哥"
                });
                break;
              case 2:
                console.log("女");
                setTimeout(() => {
                  router.push("/
          ");
                }, 1000);
                break;
              default:
            }
          };
          </script>

          <style lang="
          scss" scoped>
          .select-wrap {
            width: 100%;
            height: 100%;
            background-color: #ebfff0;
            display: flex;
            justify-content: center;
            flex-direction: row;
            align-items: center;
            ::v-deep .van-radio__label {
              font-size: 30px;
            }
          }
          </style>

          src/views/Home/index.vue

          <template>
            <div class="home">
              <p class="text">美女圖片預(yù)覽</p>
              <banner :bannerData="state.bannerData"></banner>
              <van-loading color="#1989fa" v-if="state.loading" />
            </div>
          </template>
          <script lang="ts" setup="props">
          import { reactive, onMounted, getCurrentInstance } from "vue";
          import { useRouter } from "vue-router";
          import { useStore } from "vuex";
          import { getBannerList } from "../../api/index";

          const router = useRouter();
          const store = useStore();

          // 獲取上下文對象
          const { ctx } = getCurrentInstance();

          // 定義響應(yīng)式數(shù)據(jù)
          const state = reactive({
            color"#ccc",
            bannerData: [],
            loadingfalse
          });

          const initFN = () => {
            ctx.$toast({
              type"fail",
              message"請求失敗"
            });
            state.loading = false;
          };

          // DOM 加載完成后更新數(shù)據(jù)
          onMounted(() => {
            state.loading = true;
            getBannerList()
              .then(
                (res: any) => {
                  state.loading = false;
                  state.bannerData = res.data;
                  ctx.$toast({
                    type"success",
                    message"請求成功"
                  });
                  console.log(res);
                },
                error => {
                  initFN();
                }
              )
              .catch(error => {
                initFN();
              });
          });
          </script>
          <style lang="scss" scoped>
          .home {
            height100%;
          }
          .text {
            colorv-bind("state.color");
            font-size20px;
            margin-block-start0;
            margin-block-end0;
            background-color#2c323c;
          }
          .login {
            width100%;
            height100px;
            line-height100px;
            background-color: pink;
          }
          </style>

          src/views/Login/index.vue

          <template>
            <div class="login">
              <div class="passw-name-box">
                <div class="name">
                  <i class="name-img"></i>
                  <input v-model="state.userName" type="text" placeholder="請輸入用戶名" />
                </div>
                <div class="name">
                  <i class="passw-img"></i>
                  <input v-model="state.passWord" type="password" placeholder="請輸入密碼" />
                </div>
                <div>
                  <van-button type="primary" @click="login">登錄</van-button>
                </div>
              </div>
            </div>
          </template>
          <script lang="ts" setup="props">
          import { reactive, getCurrentInstance } from "vue";
          import { useRouter } from "vue-router";
          import { toLogin } from "../../api/index";
          const router = useRouter();

          const { ctx } = getCurrentInstance();

          const state = reactive({
            userName"",
            passWord""
          });

          const initError = () => {
            ctx.$toast({
              type"fail",
              message"登錄失敗"
            });
            window.localStorage.clear();
            router.push("/login");
          };

          const login = async () => {
            if (!state.userName) {
              ctx.$toast({
                type"text",
                message"請輸入用戶名"
              });
              return;
            }

            if (!state.passWord) {
              ctx.$toast({
                type"text",
                message"請輸入密碼"
              });
              return;
            }
            toLogin({ userName: state.userName, passWord: state.passWord })
              .then(
                res => {
                  ctx.$toast({
                    type"success",
                    message"登錄成功"
                  });

                  // 設(shè)置 token
                  window.localStorage.setItem("accessToken", res.data.token);
                  console.log("res===>", res);
                  router.push("/select");
                },
                error => {
                  initError();
                }
              )
              .catch(error => {
                initError();
              });
          };
          </script>

          <style lang="scss" scoped>
          .login {
            display: flex;
            flex-direction: column;
            justify-content: center;
            height: 100%;
            background: url("./login/bgimg.jpg") no-repeat;
            background-size: 100% 100%;
            background-position: 100% 100%;
            .passw-name-box {
              display: flex;
              flex-direction: column;
              justify-content: center;
              .name {
                display: flex;
                justify-content: center;
                padding: 5px 10px;
                input {
                  width: 250px;
                  height: 32px;
                  outline: none;
                  border: none;
                  font-size: 16px;
                  margin-left: 10px;
                }
                .name-img {
                  display: block;
                  width: 32px;
                  height: 32px;
                  background: url("./login/sno.png") no-repeat;
                  background-size: 100% 100%;
                }
                .passw-img {
                  display: block;
                  width: 32px;
                  height: 32px;
                  background: url("./login/pasw.png") no-repeat;
                  background-size: 100% 100%;
                }
              }
            }
          }
          </style>

          src/App.vue

          <template>
            <div style="height: 100%;">
              <router-view></router-view>
            </div>
          </template>

          <script lang="ts">
          import { defineComponent } from "vue";

          export default defineComponent({
            name"App"
          });
          </script>

          <style>
          #app {
            font-family: Avenir, Helvetica, Arial, sans-serif;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
            text-align: center;
            color#2c3e50;
            /* width: 100%; */
            height100%;
          }
          html,
          body {
            height100%;
            /* width: 100%; */
            overflow-x: hidden;
          }
          </style>

          src/main.ts

          import { createApp } from 'vue'
          import App from './App.vue'
          import router from './router/index'
          import store from './store/index'
          import 'vant/lib/index.css'
          import vant from 'vant'

          import { Toast } from 'vant'
          import "./utils/rem"
          import './api/mock'
          import Banner from "./components/Banner/index.vue"
          const app = createApp(App)
          app.component('banner', Banner).use(router).use(store).use(vant).mount('#app')
          app.config.globalProperties = {
              "$toast": Toast,
          }

          src/shims-vue.d.ts

          declare module '*.vue' { // 定義 .vue 文件模塊
            import { DefineComponent } from 'vue'
            const component: DefineComponent<{}, {}, any>
            export default component
          }

          declare module 'mockjs' // 定義 mockjs 模塊
          declare module 'jsonwebtoken' // 定義 jsonwebtoken 模塊



          瀏覽 424
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91一区二区三区综合网 | 无码性按摩九九九国产AV | 亚洲国产精品成人综合色在线婷婷 | 亚洲精品欧美精品 | 免费看澡逼视频 |