The premise

Recently, the entire ecology of Vue has been fully transferred to the vuE3 version, and the related ecology of VUE3 has also started to migrate gradually. The vue3 version of element, our most commonly used backend UI library, also went live in stable release on February 7th, which means that vue3 is ripe for full use.

My younger brother also tried the development of VUe3.0 in the company, but due to the immature ecology, the development was indeed bumpy, so I was not interested in it.

Later, I briefly learned some TypeScript and Vite related knowledge, but never experienced it myself, so I decided to document the process of building a back-end project management template.

The background template built this time is just a simple version, and various functions will continue to be improved in the future

The warehouse address

Initialize the project

This is where the project is initialized

Create a project using Vite2

/ / NPM created
npm init vite@latest
// Select the VUE TS version
npx: 6Installation successful, time3.765✔ Seconds Project Name:... Sam-vue-admin-ts ✔ ✔ Select a framework from vue ✔ A variant// Install dependencies
cd sam-vue-admin-ts
npm i

// Start running locally
npm run dev

// The default package.json
{
  "name": "sam-vue-admin-ts"."version": "0.0.0"."scripts": {
    "dev": "vite"."build": "vue-tsc --noEmit && vite build"."serve": "vite preview"
  },
  "dependencies": {
    "vue": "^ 3.2.31"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^ 1.9.3." "."typescript": "^ 4.4.3." "."vite": "^ 2.8.1"."vue-tsc": "^ 0.31.3"}}Copy the code
// initialize tsconfig.json
{
  "compilerOptions": {
    "target": "esnext"."useDefineForClassFields": true."module": "esnext"."moduleResolution": "node"."strict": true."jsx": "preserve"."sourceMap": true."resolveJsonModule": true."esModuleInterop": true."lib": ["esnext"."dom"]},"include": ["src/**/*.ts"."src/**/*.d.ts"."src/**/*.tsx"."src/**/*.vue"]}Copy the code

Vite Configures the directory alias

The configuration file vite.config.ts is displayed

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
/ / the import form
import { resolve } from "path";

// https://vitejs.dev/config/
export default defineConfig({
  resolve: {
    alias: {
      "@": resolve(__dirname, "./src"),}},plugins: [
    vue() 
  ],
});
Copy the code

Here ts will prompt

The name "__dirname" could not be found. ts(2304)
any
// Let's install node related types
npm install @types/node --save-dev
// Then configure tsconfig.json
{
  "compilerOptions": {
    "target": "esnext"."useDefineForClassFields": true."module": "esnext"."moduleResolution": "node"."strict": true."jsx": "preserve"."sourceMap": true."resolveJsonModule": true."esModuleInterop": true."lib": ["esnext"."dom"].// Configure the path here
    "baseUrl": "."."paths": {
      "@ / *": [
        "src/*"],}},"include": ["src/**/*.ts"."src/**/*.d.ts"."src/**/*.tsx"."src/**/*.vue"]}Copy the code

Installing the CSS Preprocessor (SASS)

npm install -D sass
Copy the code

Modify the style tag of the.vue file and add lang=” SCSS “helloworld.vue

<style lang="scss" scoped>
a {
  color: #42b983;
}

label {
  margin: 0 0.5 em;
  font-weight: bold;
}

code {
  background-color: #eee;
  padding: 2px 4px;
  border-radius: 4px;
  color: # 304455;
}
</style>
Copy the code

The project installs Element-Plus, which is automatically introduced on demand using unplugin-vue-Components

npm install element-plus –save

npm install unplugin-vue-components -D

The unplugin-vue-Components plug-in can also import its own components, but I didn’t configure that here,

If you are interested, you can read this big man’s article

Especially recommended unplugin-vue-components

In the vite.config.ts configuration, unplugin-vue-Components provides many well-known UI libraries that resolvers can use directly

I’m just using ElementPlusResolver here

import Components from "unplugin-vue-components/vite";
import ViteComponents, {
  //AntDesignVueResolver,
  ElementPlusResolver,
  //VantResolver,
} from 'unplugin-vue-components/resolvers'


Components({
  resolvers: [
    //AntDesignVueResolver(),
    ElementPlusResolver(),
    //VantResolver(),]})Copy the code

Let’s modify helloWorld.vue to see what happens

<el-button type="button" @click="count++">count is: {{ count }}</el-button>
Copy the code

You can see that even styles are automatically introduced

Automatically introduce vUe-related hooks using unplugin-auto-import/vite

npm i -D unplugin-auto-import

Supports automatic import of VUE, VUe-Router, VUE-i18N, @vueuse/head, @vueuse/ Core, etc

If you are interested, you can read this big man’s article

Juejin. Cn/post / 701244…

Modify vite configurations

plugins: [
    vue(),
    Components({
      resolvers: [ElementPlusResolver()],
    }),
    AutoImport({
      imports: ['vue'.'vue-router'].dts: 'src/auto-imports.d.ts'})].Copy the code

Modify the HelloWorld. Vue

I’m going to comment out the previous introduction and see what happens

<script setup lang="ts">
// import { ref } from 'vue'

defineProps<{ msg: string }>()

const count = ref(0)
</script>
Copy the code

Some dependencies that the installation will use

Global state Management

Pinia (You can also use Vex4, Pinia is easier to use)

npm install vuex@next –save

npm install pinia@next

Reference big guy:

Especially recommended unplugin-vue-components

routing

vue-router4

npm install vue-router@next

HTTP request correlation

axios

npm install axios –save

@types/qs

npm install @types/qs -D

qs

npm install qs –save

Complete the project catalog

We continue to refine the contents of the SRC folder

App.vue
components.d.ts
http
plugins
views
assets
types
constants
layout
router
auto-imports.d.ts
env.d.ts
libs
store
components
hooks
main.ts
Copy the code

Layout container components and routing configurations

Layout container

Create baseLayout, baseView and Components folders respectively under SRC/Layout

Components/BaseHeader vue create head module

<script setup lang="ts"> import useUserStore from '@/store/modules/user.module'; import { ElMessage } from 'element-plus'; const UserStore = useUserStore(); const router = useRouter(); const state = reactive({ circleUrl: 'https://cube.elemecdn.com/3/7c/3ea6beec64369c2642b92c6726f1epng.png' }); const userName = computed(() => UserStore.userInfo.userName); const clickHandler_logout = () => { UserStore.logout(); Elmessage. success(' Logged out successfully '); router.push('/Login'); }; const { circleUrl } = toRefs(state); </script> <template> <div class="baseHeader"> <div class="baseHeader_right"> <el-dropdown> <div class="baseHeader-memu"> "El - avatar: size =" 40: "SRC =" circleUrl "> < / el - avatar > < div class =" baseHeader - name "> {{userName | | 'operations'}} < / div > < / div > <template #dropdown> <el-dropdown-menu> <el-dropdown-item @click="clickHandler_logout"> </el-dropdown-menu> </template> </el-dropdown> </div> </div> </template> <style lang="scss"> .baseHeader { height: 100%; width: 100%; background-color: #fff; border-bottom: 1px solid #f1f1f1; Box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); &_right { height: 100%; float: right; .baseHeader-memu { height: 60px; display: flex; justify-content: center; align-items: center; .baseHeader-name { padding: 0 10px; } } } } </style>Copy the code

Components/Baseaside. vue creates a menu sidebar module

<script setup lang="ts">
</script>

<template>
	<div class="baseAside">
		<div class="baseAside-title">vite-admin</div>
		<BaseSiderMenu />
	</div>
</template>

<style lang="scss">
.baseAside {
	height: 100vh;
	width: 100%;
	background-color: #545c64;
	.baseAside-title {
		height: 60px;
		width: 100%;
		color: #fff;
		font-size: large;
		line-height: 60px;
		text-align: center;
	}
}
</style>

Copy the code

Create an index.vue file as a baseLayout container component

<script setup lang="ts"> import BaseHeader from ".. /components/BaseHeader.vue"; import BaseAside from ".. /components/BaseAside.vue"; </script> <template> <el-container> <el-aside width="200px"> <BaseAside /> </el-aside> <el-container> <el-header> <BaseHeader /> </el-header> <el-main> <router-view /> </el-main> </el-container> </el-container> </template> <style></style>Copy the code

Layout /baseView creates an index.vue file as a content container component

<script setup lang="ts"></script>

<template>
	<div class="baseView">
		<router-view v-slot="{ Component, route }">
			<transition name="fade-transform" mode="out-in">
				<component :is="Component" :key="route.fullPath" />
			</transition>
		</router-view>
	</div>
</template>

<style>
</style>

Copy the code

The basic layout module is complete

The routing configuration

SRC /router creates modules folders, as well as index.ts and route.ts files

SRC /types folder creates the index.ts file,

Modules: Stores routes related to submodules

Index. ts: responsible for route initialization, route hooks, and route mounting

Route. ts: Defines routing tables

Types /index.ts: Responsible for defining the types of the project

Modules creates several submodule routes first

modules/home.module.ts

import BaseView from '@/layout/baseView/index.vue';

export default {
	path: 'home'.component: BaseView,
	meta: {
		title: 'home'
	},
	children: [{path: ' '.redirect: '/home/index'
		},
		{
			path: 'index'.component: () = > import('@/views/home/index.vue'),
			meta: {
				title: 'home'}}, {path: 'edit'.component: () = > import('@/views/home/edit.vue'),
			meta: {
				title: 'edit'}}, {path: 'list'.component: () = > import('@/views/home/list.vue'),
			meta: {
				title: 'list'}}};Copy the code

modules/other.module.ts

import BaseView from '@/layout/baseView/index.vue';

export default {
	path: 'other'.component: BaseView,
	meta: {
		title: 'other'
	},
	children: [{path: ' '.redirect: '/other/index'
		},
		{
			path: 'index'.component: () = > import('@/views/other/index.vue'),
			meta: {
				title: 'other'}}, {path: 'edit'.component: () = > import('@/views/other/edit.vue'),
			meta: {
				title: 'edit'}}, {path: 'list'.component: () = > import('@/views/other/list.vue'),
			meta: {
				title: 'list'}}};Copy the code

SRC /types/index.ts Adds route related types

import { RouteRecordRaw } from 'vue-router';

/ / routing meta
export typeTRouteMeta = { title? :string; // Page titleicon? :string; // Side menu bar iconactiveMenu? :string; slideHidden? :boolean; // Whether the side menu bar needs to be hiddenbreadcrumb? :boolean; // Whether the current route should be displayed in the breadcrumbs of the pageauth? :string | number; / / permission
};

// Route object
export typeTRouteRecordRaw = { meta? : TRouteMeta; } & RouteRecordRaw;export type TMenuRaw = {
    label:string | undefined.path:string, children? :TMenuRaw[] |undefined
}


Copy the code

The router/route. Ts routing

const BaseLayout = () = > import('@/layout/baseLayout/index.vue');
const Login = () = > import('@/views/login/index.vue');
import home from './modules/home.module';
import other from './modules/other.module';
import { TRouteRecordRaw, TMenuRaw } from '@/types/index';

const mainRoute = [home, other];

export const baseRoute = [
	{
		path: '/login'.name: 'login'.component: Login
	},
	{
		path: '/'.component: BaseLayout,
		redirect: '/home'.children: [...mainRoute]
	},
	{
		path: '/error/:status'.name: 'error'.component: () = > import('@/views/error/index.vue')},/* Error route */
	{
		path: '/:catchAll(.*)'.component: () = > import('@/views/error/index.vue')}];// Convert to menu
const transform_menu = (route: TRouteRecordRaw[]) = > {
	return route
		.map(item= > {
			let menuItem: TMenuRaw = {
				path: '/' + item.path,
				label: item.meta? .title }; item.children && (menuItem.children = transform_menu(item.children));return menuItem;
		})
		.filter(item= >item.path ! = ='/');
};

export const menuRoute: TMenuRaw[] = transform_menu(mainRoute);

Copy the code

router/index.ts

import type { App } from 'vue';
import { createRouter, createWebHashHistory } from 'vue-router';
import { baseRoute } from './route';
import useUserStore from '@/store/modules/user.module';

const routerHistory = createWebHashHistory();
// createWebHashHistory Hash route
CreateWebHistory History Route
// createMemoryHistory With the cache history route

const router = createRouter({
	history: routerHistory,
	routes: baseRoute
});

let UserStore:any = null

router.beforeEach((to, form, next) = > {
	!UserStore&&(UserStore = useUserStore())
	if (to.name == 'login') {
		// If you are logged in, you will not jump to the login page
		if (UserStore.loginInfo.isLogin) {
			if (to.name == 'login') next('/');
			else next();
		} else{ next(); }}else {
		// You cannot open any page except the login page
		if (UserStore.loginInfo.isLogin) {
			next();
		} else {
			next('/login'); }}});export function setupRouter(app: App<Element>) :void {
	app.use(router);
}
export default router;

Copy the code

The routing table automatically generates menus

src/components/BaseSiderMenu/index.vue

Create the test sidebar menu

<script setup lang="ts">
import { menuRoute } from '@/router/route';
const router = useRouter();
const route = useRoute();
const menuList = reactive(menuRoute);
const activeMenu = computed(() => {
	const { path } = route;
	return path;
});

const clickHandler_menu = (key: string, keyPath: string[]) => {
	console.log(key, keyPath);
	router.push({ path: key });
};
</script>

<template>
	<el-menu active-text-color="#ffd04b" :default-active="activeMenu" background-color="#545c64" text-color="#fff" @select="clickHandler_menu" class="el-menu-vertical-demo">
		<el-sub-menu v-for="menu in menuList" :key="menu.path" :index="menu.path">
			<template #title>
				<span>{{ menu.label }}</span>
			</template>
			<el-menu-item v-for="item in menu.children" :key="item.path" :index="menu.path + item.path">{{ item.label }}</el-menu-item>
		</el-sub-menu>
	</el-menu>
</template>

<style>
.el-menu-vertical-demo {
	border-right: 0;
	/* width: 100%; */
}
</style>

Copy the code

Create login pages and error pages

Create login, error folder under view

Login and Registration page

login/index.vue

<script setup lang="ts"> import { ElForm, ElMessage } from 'element-plus'; import loginApi from '@/http/api/loginApi'; import useUserStore from '@/store/modules/user.module'; const UserStore = useUserStore(); const router = useRouter(); const isLoginType = ref(true); const clickHandler_Type = (type: boolean, formEl: InstanceType<typeof ElForm> | undefined) => { isLoginType.value = type; resetForm(formEl); }; const resetForm = (formEl: InstanceType<typeof ElForm> | undefined) => { if (! formEl) return; formEl.resetFields(); }; Const loginObject = reactive({name: '', password: ''}); const loginFormRef = ref<InstanceType<typeof ElForm>>(); const loginRules = reactive({ name: [ { required: true, message: 'Please input name', trigger: 'blur' } ], password: [ { required: true, message: 'Please input password', trigger: 'blur' } ] }); const post_login = async () => { let params = { phone: loginObject.name, password: loginObject.password }; // let [results, message, code]: any = await loginApi.login(params); const results = { createdTime: '111', id: '111', phone: '111', userName: '111', token:'mock-token' }; Const message = 'Login succeeded '; if (results) { ElMessage.success(message); const { token } = results; UserStore.login(results, { isLogin: true, token }); router.push('/'); }}; const clickHandler_login = (formEl: InstanceType<typeof ElForm> | undefined) => { if (! formEl) return; formEl.validate(valid => { if (valid) { post_login(); } else { return false; }}); }; // register const registerObject = reactive({name: '', password: ''}); const registerFormRef = ref<InstanceType<typeof ElForm>>(); const registerRules = reactive({ name: [ { required: true, message: 'Please input name', trigger: 'blur' } ], password: [ { required: true, message: 'Please input password', trigger: 'blur' } ] }); const clickHandler_register = (formEl: InstanceType<typeof ElForm> | undefined) => { if (! formEl) return; formEl.validate(valid => { if (valid) { } else { return false; }}); }; </script> <template> <div class="login"> <div class="login-box"> <h1>vite-admin</h1> <! <el-form status-icon ref="loginFormRef" label-position="top" :model="loginObject" hide-required-asterisk :rules="loginRules" V-if ="isLoginType"> <el-form-item> <h2 class="form-title"> User login </h2> </el-form-item> <el-form-item Label =" user name" prop="name"> <el-input V-model ="loginObject.name"></el-input> </el-form-item> <el-form-item label=" password" prop="password"> <el-input type="password" v-model="loginObject.password"></el-input> </el-form-item> <el-form-item> <el-button class="button-sub" type="primary" @click="clickHandler_login(loginFormRef)"> </el-button> </el-form-item> <el-form-item> <el-button class="button-sub" @click="clickHandler_Type(false, LoginFormRef)" type="text"> user registration </el-button> </el-form> </el-form> <! <el-form status-icon ref="registerFormRef" label-position="top" :model="registerObject" hide-required-asterisk  :rules="registerRules" v-if="! IsLoginType "> <el-form-item> <h2 class="form-title"> User registration </h2> </el-form-item> <el-form-item label=" user name" prop="name"> <el-input V-model ="registerObject.name"></el-input> </el-form-item> <el-form-item label=" password" prop="password"> < EL-input type="password" v-model="registerObject.password"></el-input> </el-form-item> <el-form-item> <el-button Class ="button-sub" type="primary" @click="clickHandler_register(loginFormRef)" </el-button> </el-form-item> <el-form-item> <el-button @click="clickHandler_Type(true, RegisterFormRef)" type="text"> </el-button> </el-form-item> </el-form> </div> </div> </template> scoped> .login { background: rgb(217, 236, 255); height: 100vh; width: 100vw; .login-box { width: 400px; height: 500px; padding: 20px; background-color: #fff; border-radius: 16px; position: absolute; top: 0; left: 0; right: 0; bottom: 0; margin: auto; text-align: center; Box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1); .form-title { width: 100%; text-align: center; } .button-sub { width: 100%; margin-top: 20px; } } } </style>Copy the code

Error page

error/index.vue


<script setup lang="ts">
const router = useRouter()
const clickHandler_back = () => {
    router.go(-1)
}
</script>

<template>
	<div class="error-page">
        <el-result
        icon="error"
        title="找不到该路由"
        sub-title=""
      >
        <template #extra>
          <el-button @click="clickHandler_back" type="primary">返回上一页</el-button>
        </template>
      </el-result>
    </div>
</template>

<style lang="scss" scoped>
.error-page{
    width: 100%;
    display: flex;
    height: 500px;
    justify-content: center;
    align-items: center;
}
</style>

Copy the code

Simulation pages for other routes

All put in github copy, you can also simulate

Global State Management (PINIA)

In this project, we used Pinia for global state management, which is a lightweight library recommended by The University

Create a global mount port

SRC /store/index.ts creates an entry and exports a setupStore method

import { createPinia } from 'pinia'; // Global management
import type { App } from 'vue';

export function setupStore(app: App<Element>) :void {
	const pinia = createPinia();
	app.use(pinia);
}
Copy the code

Creating a User module

src/store/modules/user.module.ts

import { ref, reactive, computed } from 'vue';
import { defineStore } from 'pinia';
import storage from '@/libs/storage';


// Expose a use method to the outside, which exports the state we defined
interface UserInfoRaw {
	createdTime: string;
	id: number | string;
	phone: string;
	userName: string;
}
interface loginInfoRaw {
	isLogin: boolean;
	token: string;
}
const useUserStore = defineStore('user'.function () {
	const userInfo: UserInfoRaw | null = reactive<UserInfoRaw>(
		storage.get('userInfo') | | {createdTime: ' '.id: ' '.phone: ' '.userName: ' '});const loginInfo: loginInfoRaw = reactive<loginInfoRaw>({
		isLogin: false || storage.get('isLogin'),
		token: ' ' || storage.get('token')});// Set the login status
	const setLogin = (data: loginInfoRaw) = > {
		loginInfo.isLogin = data.isLogin;
		loginInfo.token = data.token;
		storage.set('isLogin'.true);
		storage.set('token', data.token);
	};
	const resetLogin = () = > {
		loginInfo.isLogin = false;
		loginInfo.token = ' ';
	};
	// Set user status
	const setUserInfo = (data: UserInfoRaw) = > {
		userInfo.createdTime = data.createdTime;
		userInfo.id = data.id;
		userInfo.phone = data.phone;
		userInfo.userName = data.userName;
		storage.set('userInfo', data);
	};
	// Clear the user status
	const resetUserInfo = () = > {
		userInfo.createdTime = ' ';
		userInfo.id = ' ';
		userInfo.phone = ' ';
		userInfo.userName = ' ';
	};
	const login = (user: UserInfoRaw, data: loginInfoRaw) = > {
		setLogin(data);
		setUserInfo(user);
	};
	const logout = () = > {
		resetLogin();
		resetUserInfo();
		storage.clear();
	};
	return {
		userInfo,
		setUserInfo,
		resetUserInfo,
		loginInfo,
		setLogin,
		resetLogin,
		login,
		logout
	};
});

export default useUserStore;

Copy the code

Use Store in the login page

Extract only part of the code for presentation

import useUserStore from '@/store/modules/user.module'; / / import
const UserStore = useUserStore(); / / initialization
UserStore.login(results, { isLogin: true, token }); / / use
Copy the code

Network Request Encapsulation (AXIOS)

The axiOS library is still used for network requests

Create request.ts file and API folder in HTTP folder

API folder creates a loginapi.ts file to write requests related to the login module

Encapsulates the request

I initialized an instance of AXIos, simply encapsulated post, GET, and postJSON requests, added global loading Settings, and processed the data in response to the request according to the status code

import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios';
import qs from 'qs';
import { Path, TIME_OUT, HTTP_REQUEST_CODE, HTTP_REQUEST_STA } from '@/constants/http';
import { ElMessage } from 'element-plus';
import router from '@/router';
import { showLoading, hideLoading } from '@/components/baseFullScreenLoading';
import useUserStore from '@/store/modules/user.module';
let UserStore: any = null;

// Configure a new axiOS instance
const instance: AxiosInstance = axios.create({
	baseURL: Path.HTTP_URL,
	timeout: TIME_OUT
});
// Add request interceptor
instance.interceptors.request.use(
	(config: AxiosRequestConfig) = > {
		!UserStore && (UserStore = useUserStore());
		const token = UserStore.loginInfo.token;
		if (token && config.headers) {
			config.headers['token'] = `${UserStore.loginInfo.token}`;
		}
		return config;
	},
	(error: AxiosError) = > {
		return Promise.reject(error); });// Add a response interceptor
instance.interceptors.response.use(
	(response: AxiosResponse) = > {
		hideLoading();
		switch (response.status) {
			case HTTP_REQUEST_STA.LOGIN_ERROR:
				return [null];
			case HTTP_REQUEST_STA.SUCCESS:
				return handle_response(response);
			default:
				return [null]; }},(error: AxiosError) = > {
		hideLoading();
		ElMessage.error('Request exception, try again later! ');
		return Promise.reject(error); });export default {
	/ * * *@param {String} Url Interface address *@param {Object} The data parameter *@param {Boolean} IsLoading Whether to display loading */
	post(url: string, data = {}, isLoading: boolean = true) {
		if (isLoading) {
			showLoading();
		}
		return instance({
			url,
			method: 'post'.data: qs.stringify(data),
			headers: {
				'Content-Type': 'application/x-www-form-urlencoded'}}); },/ * * *@param {String} Url Interface address *@param {Object} The data parameter *@param {Boolean} IsLoading Whether to display loading */
	postJSON(url: string, data: any, isLoading = true) {
		if (isLoading) {
			showLoading();
		}
		return instance({
			url,
			method: 'post'.data: data,
			headers: {
				'Content-Type': 'application/json'}}); },/ * * *@param {String} Url Interface address *@param {Object} The data parameter *@param {Boolean} IsLoading Whether to display loading */
	get(url: string, data: object, isLoading = true) {
		if (isLoading) {
			showLoading();
		}
		return instance({
			url,
			method: 'get'.params: data }); }};// Process the data back from the response
function handle_response(response: any) {
	switch (response.data.code) {
		case HTTP_REQUEST_CODE.SUCCESS:
			return [response.data.results ? response.data.results : true, response.data.message, response.data.code];
		caseHTTP_REQUEST_CODE.LOGIN_OUT: ! UserStore && (UserStore = useUserStore()); UserStore.logout(); ElMessage.error(response.data.message); router.push('Login');
			return [null, response.data.message, response.data.code];
		default: ElMessage.error(response.data.message); }}Copy the code

Encapsulation global loading

Create loading file. Here we use el-loading

src/components/baseFullScreenLoading/index.ts

import { ElLoading } from "element-plus";

let loadingInstance:any;
const showLoading = () = > {
  loadingInstance = ElLoading.service({
    fullscreen: true.text: "loading...".background: "Rgba (0,0,0,0.6)"}); };const hideLoading = () = > {
  loadingInstance && loadingInstance.close();
};

export { showLoading, hideLoading };

Copy the code

Create request module

Here we create a login module


import request from '.. /request'
const prefix = '/question-Server'
const login = {
	// Login interface
	login: (params: any) = > {
		return request.postJSON(`${prefix}/user/login`, params)
	},
}


export default {
	...login
}
Copy the code

Use in pages

import loginApi from '@/http/api/loginApi';
let [results, message, code]: any = await loginApi.login(params);
Copy the code

Style of management

We manage global styles in the styles folder under SRC/Assets

Global style definition

This part of SCSS code has been placed on Github and is common to both VUe2 and VUE3 projects

ElementPlus global style reset and global API style introduction

The main reference here is to the elementPlus website

Overwriting SCSS variables (a bit of a hassle to be honest)

Create a SRC/assets/styles/element. SCSS


$--header: 
  (
    'padding': 0 0.'height': 60px,);@forward "element-plus/theme-chalk/src/common/var.scss" with
  (
    $header:$--header
  );


Copy the code

Modify the vite. Config. Ts

css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@use "@/assets/styles/element.scss" as *; `,}}},Copy the code

When we call el-message using API, we will find that the corresponding style is not loaded. This is because the unplugin-vue-components are introduced when loading, so the component style like elloading and Elmessage will not be loaded when it is used for the first time. Therefore, it is recommended to introduce the corresponding SCSS globally on the official website

src/main.ts

import { createApp } from "vue";
import App from "./App.vue";
import router, { setupRouter } from "./router"; / / routing
import { setupStore } from "./store"

import "element-plus/theme-chalk/src/message.scss"
import "element-plus/theme-chalk/src/loading.scss"
import './assets/styles/index.scss'


const app = createApp(App);
setupStore(app)
setupRouter(app); // Import routes

router.isReady().then(() = > {
  app.mount("#app");
});

Copy the code

Environment Variable Configuration

In previous webpack development processes, we will set up. Env file for development, testing, production environment configuration

Env files are also used in Vite

Configure the env environment variable

We create it in the project root directory

.env.development

.env.production

.env.staging

This represents three patterns

Then take.env.development as an example

VITE_MODE_NAME=development
VITE_BASE_URL= 'http://xxxx'
VITE_APP_TITLE=vite-admin-ts-dev
Copy the code

Modify SRC /env.d.ts to provide TS support

/// <reference types="vite/client" />

declare module '*.vue' {
  import { DefineComponent } from 'vue'
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
  const component: DefineComponent<{}, {}, any>
  export default component
}

interface ImportMetaEnv {
  VITE_MODE_NAME: string.VITE_BASE_URL: string.VITE_APP_TITLE: string
}

Copy the code

Then modify package.json

"scripts": {
    "dev": "vite --host --mode development"."build": "vue-tsc --noEmit && vite build"."serve": "vite preview --host"
  },
Copy the code

Use environment variables in your code

The following is an example of configuring access to server addresses in different environments

src/constants/http.ts

/ * * *@description Path constant */
export const Path = {
	HTTP_URL: import.meta.env.VITE_BASE_URL as string
};

Copy the code

This allows you to switch environment variables during development and packaging depending on which scripts command you run

summary

The development of a background template personally think that is a rough HHH, like a lot of features are added, some of the ts usage is very chaotic, some architecture design or not well, this I am going to is on the late then slowly according to the demand continue to improve, to put a minimum run version, each subsequent perfect record a module is a short article. There has always been a pitfall in using elementPlus because elementPlus is constantly being updated and some of its auto-on-demand packages are being updated at the same time, resulting in constant changes. Hope that if the big guys have modified the opinion, you can give the younger brother to mention HHH.