Welcome to like, follow me, because this project was done in the company’s spare time, the article was first published on Github

Write a small yellow station using Vue3+Ts+Vite2

Interested, you can follow the small preparation, pictures to download it.

First create the project using the following command

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

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: '/'.// Package the path
  resolve: { / / parsing
    alias: { // Rename the path
      The '@': path.resolve(__dirname, './src')}},server: {
    port: 4000.// Service port
    open: true.// Whether to open the browser automatically
    host: 'localhost'.// Host name
    proxy: { / / agent
      '/api': {
        target: '/api'.changeOrigin: true.ws: false.rewrite: path= > path.replace(/^\/api/.' ')}},cors: true}})Copy the code

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"]}Copy the code

postcss.config.js

module.exports = {
    "plugins": {
        "postcss-pxtorem": {
            rootValue: 37.5.Vant's official root font size is 37.5
            propList: [The '*'].selectorBlackList: ['.norem']
            // filter out. Norem - classes that start with norem conversion}}}Copy the code

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"}}Copy the code

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>
Copy the code

src/api/request.ts

import axios from 'axios'

const service = axios.create({ // Create a service
    baseURL: '/api/'.timeout: 5000
})

service.interceptors.request.use(config= > { // Request interception processing
    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= > { // Response interception processing
    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
Copy the code

src/api/index.ts

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

// Get the banner news data from the home page
export const getBannerList = (): AxiosPromise= > {
    return request({
        url: '/bannerList'})}// Get the first newsList data
export const getNewsList = (): AxiosPromise= > {
    return request({
        url: '/newsList'})}// Get newsDetail data
export const getNewsDetail = (id: any): AxiosPromise= > {
    return request.post('/detailList', {
        id
    })
}

// Login authentication
export const toLogin = (data: Object) :AxiosPromise= > {
    return request.post('/login', data)
}

export default {
    getBannerList,
    getNewsList,
    getNewsDetail,
    toLogin
}
Copy the code

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": "Booty Girl."
    },
    {
        "id": "2"."images": "./2021-02-27/2.jpg"."title": "Teen Beauty"
    },
    {
        "id": "3"."title": "Booty Girl."."images": "./2021-02-27/3.jpg"}, {"id": "4"."title": "Hot girl"."images": "./2021-02-27/4.jpg"}, {"id": "5"."title": "Ha ha girl."."images": "./2021-02-27/5.jpg"}, {"id": "6"."title": "Hot girl"."images": "./2021-02-27/6.jpg"}, {"id": "Seven"."title": "Pretty Girl with a smile."."images": "./2021-02-27/7.jpg"}, {"id": "8"."title": "Ha ha girl."."images": "./2021-02-27/8.jpg"}, {"id": "9"."title": "Pretty Girl with a smile."."images": "./2021-02-27/9.jpg"}, {"id": "10"."title": "Pretty Girl with a smile."."images": "./2021-02-27/10.jpg"}, {"id": "11"."title": "Pretty Girl with a smile."."images": "./2021-02-27/11.jpg"}, {"id": "12"."title": "Pretty Girl with a smile."."images": "./2021-02-27/12.jpg"}, {"id": "13"."title": "Pretty Girl with a smile."."images": "./2021-02-27/13.jpg"}, {"id": "14"."title": "Pretty Girl with a smile."."images": "./2021-02-27/14.jpg"}, {"id": "15"."title": "Pretty Girl with a smile."."images": "./2021-02-27/15.jpg",},]const newsData: Array<Data> = [
    {
        "id": "1"."images": [".. /assets/logo.png"]."title": "Why are blondes rarely seen in races other than whites?"."author": Author/Biokiwi
    },
    {
        "id": "2"."title": "How are the characters in the Harry Potter books different from those in the film?"."author": "By Kalinnn"."images": [".. /assets/logo.png"] {},"id": "3"."title": "What are some good board games for couples to play together?."author": "Author/North Mangles"."images": [".. /assets/logo.png"]}]const loginData: Data = {
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX25hbWUiOiJhZG1pbiIsInVzZXJfaWQiOjEsImlhdCI6MTU5NDI2MjQ5NSwiZXhwIjoxNTk0Mz Q4ODk1fQ.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
})

Copy the code

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
}
Copy the code

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: {
      type: Array}},setup(props) {
    const router = useRouter();
    const toDetail = item= > {
      router.push({
        name: "Detail".params: {
          id: item.id,
          item: JSON.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>
Copy the code

src/router/index.ts

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

const routes: Array<RouteRecordRaw> = [
    {
        path: '/'.name: 'Home'.meta: {
            title: "Home page".keepAlive: true.requireAuth: true
        },
        component: () = > import(".. /views/Home/index.vue")}, {path: '/select'.name: 'Select'.meta: {
            title: "Options".keepAlive: true.requireAuth: true
        },
        component: () = > import(".. /views/Select/index.vue")}, {path: '/detail/:id/:item'.name: 'Detail'.meta: {
            title: "Options".keepAlive: true.requireAuth: true
        },
        component: () = > import(".. /views/Detail/index.vue")}, {path: '/login'.name: 'Login'.meta: {
            title: "Login".keepAlive: true
        },
        component: () = > import(".. /views/Login/index.vue")}]const router = createRouter({
    history: createWebHashHistory(),
    routes
})

router.beforeEach((to, from, next) = > {
    if (to.meta.requireAuth) {  // Determine whether the route requires login permission
        if (window.localStorage.getItem('accessToken')) {  // Check whether the current token exists through vuex state
            next();
        }
        else {
            next({
                path: '/login'.query: { redirect: to.fullPath }  // Use path as a parameter to switch to this route after successful login
            })
            window.localStorage.clear()
        }
    }
    else{ next(); }})export default router
Copy the code

src/store/index.ts

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

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()
}
Copy the code

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>

Copy the code

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">male</van-radio>
      <van-radio name="2">female</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("Male");
      ctx.$toast({
        type: "text".message: "No hot guy yet."
      });
      break;
    case 2:
      console.log("Female");
      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>
Copy the code

src/views/Home/index.vue

<template>
  <div class="home">
    <p class="text">Beauty preview</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();

// Get the context object
const { ctx } = getCurrentInstance();

// Define reactive data
const state = reactive({
  color: "#ccc".bannerData: [].loading: false
});

const initFN = () = > {
  ctx.$toast({
    type: "fail".message: "Request failed"
  });
  state.loading = false;
};

// Update data after DOM loading is complete
onMounted(() = > {
  state.loading = true;
  getBannerList()
    .then(
      (res: any) = > {
        state.loading = false;
        state.bannerData = res.data;
        ctx.$toast({
          type: "success".message: "Request successful"
        });
        console.log(res);
      },
      error= > {
        initFN();
      }
    )
    .catch(error= > {
      initFN();
    });
});
</script>
<style lang="scss" scoped>
.home {
  height: 100%;
}
.text {
  color: v-bind("state.color");
  font-size: 20px;
  margin-block-start: 0;
  margin-block-end: 0;
  background-color: #2c323c;
}
.login {
  width: 100%;
  height: 100px;
  line-height: 100px;
  background-color: pink;
}
</style>
Copy the code

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="Please enter user name" />
      </div>
      <div class="name">
        <i class="passw-img"></i>
        <input v-model="state.passWord" type="password" placeholder="Please enter your password" />
      </div>
      <div>
        <van-button type="primary" @click="login">The 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: "Login failed"
  });
  window.localStorage.clear();
  router.push("/login");
};

const login = async() = > {if(! state.userName) { ctx.$toast({type: "text".message: "Please enter user name"
    });
    return;
  }

  if(! state.passWord) { ctx.$toast({type: "text".message: "Please enter your password"
    });
    return;
  }
  toLogin({ userName: state.userName, passWord: state.passWord })
    .then(
      res= > {
        ctx.$toast({
          type: "success".message: "Login successful"
        });

        / / set the 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>
Copy the code

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%; * /
  height: 100%;
}
html.body {
  height: 100%;
  /* width: 100%; * /
  overflow-x: hidden;
}
</style>
Copy the code

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,
}
Copy the code

src/shims-vue.d.ts

declare module '*.vue' { // Define the.vue file module
  import { DefineComponent } from 'vue'
  const component: DefineComponent<{}, {}, any>
  export default component
}

declare module 'mockjs' // Define the mockJS module
declare module 'jsonwebtoken' // Define the jsonWebToken module
Copy the code