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