您的位置:首页 > 房产 > 建筑 > 凡科教育_长沙网站制作公司有哪些_国际新闻直播_重庆seo教程博客

凡科教育_长沙网站制作公司有哪些_国际新闻直播_重庆seo教程博客

2024/12/24 3:48:36 来源:https://blog.csdn.net/qq_43206280/article/details/144427926  浏览:    关键词:凡科教育_长沙网站制作公司有哪些_国际新闻直播_重庆seo教程博客
凡科教育_长沙网站制作公司有哪些_国际新闻直播_重庆seo教程博客

一、使用 Vite 创建 Vue 3 + TypeScript 项目

PS E:\web\cursor-project\web> npm create vite@latest yf-blog -- --template vue-ts> npx
> create-vite yf-blog --template vue-tsScaffolding project in E:\web\cursor-project\web\yf-blog...Done. Now run:cd yf-blognpm installnpm run devPS E:\web\cursor-project\web> cd yf-blog
PS E:\web\cursor-project\web\yf-blog> npm installadded 47 packages in 7s5 packages are looking for fundingrun `npm fund` for details
PS E:\web\cursor-project\web\yf-blog> npm run dev> yf-blog@0.0.0 dev
> viteVITE v6.0.3  ready in 594 ms➜  Local:   http://localhost:5173/➜  Network: use --host to expose➜  press h + enter to show help

在这里插入图片描述

二、安装生产必要依赖

PS E:\web\cursor-project\web\yf-blog> npm install vue-router@4 pinia element-plus @element-plus/icons-vue axios marked highlight.jsadded 16 packages in 4s9 packages are looking for fundingrun `npm fund` for details

三、安装开发依赖

PS E:\web\cursor-project\web\yf-blog> npm install -D sass sass-loader mockjs @types/mockjs vite-plugin-mock cross-env unplugin-auto-import unplugin-vue-componentsadded 87 packages in 8s26 packages are looking for fundingrun `npm fund` for details

四、配置别名

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// https://vite.dev/config/
export default defineConfig({resolve: {alias: {'@': path.resolve(__dirname, './src')}},plugins: [vue()],
})

在src下的vite-env.d.ts文件增加模块定义,否则别名引用会报错找不到模块

npm i @types/node --D
declare module "*.vue" {import type { DefineComponent } from "vue";const component: DefineComponent<typeof DefineComponent>;export default component;
}

在tsconfig.app.json添加

{"compilerOptions": {"paths": {"@": ["./src"]}},"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
}

五、依赖的功能和使用方法

1. vue-router@4

功能: Vue.js的官方路由管理器
安装: npm install vue-router@4

使用步骤:

1、新建src/router/index.ts

import { createRouter, createWebHistory } from 'vue-router'const router = createRouter({history: createWebHistory(),routes: [{path: '/',name: 'Home',component: () => import('@/views/Home.vue')},{path: '/article/:id',name: 'Article',component: () => import('@/views/Article.vue')}]
})export default router

2、main.ts引入router

import router from '@/router/index'
const app = createApp(App)
app.use(router)
app.mount('#app')

3、在组件中使用:

<script setup lang="ts">
const router = useRouter()
const route = useRoute()// 编程式导航
const goToArticle = (id: number) => {router.push(`/article/${id}`)
}// 获取路由参数
const articleId = route.params.id
</script>

2. element-plus

功能: 基于 Vue 3的UI组件库
安装: npm install element-plus

使用步骤:

1、main.ts引入 element-plus

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from '@/router/index'
import ElementPlus from 'element-plus'// 样式导入
import 'element-plus/dist/index.css'
// 创建应用实例
const app = createApp(App)app.use(router)
app.use(ElementPlus, {size: 'default',zIndex: 3000
})
app.mount('#app')

2、路由layout组件化处理

在这里插入图片描述

3、新建layout模块

在这里插入图片描述

3.1、src/layout/index.vue内容如下

<template><el-container class="common-layout"><el-aside class="aside"><Aside/></el-aside><el-container><el-header class="header"><Header/></el-header><el-main><router-view></router-view></el-main><el-footer class="footer">Footer</el-footer></el-container></el-container>
</template><script setup lang="ts">
import Aside from './components/aside.vue'
import Header from './components/header.vue'</script><style lang="scss" scoped>
.common-layout{width: 100%;height: 100vh;.aside{height: 100vh;width: 200px;background-color: #ccc;}.header{height: 50px;background-color: #c9c1c1;border-bottom: 1px solid #c9c6c6;}.footer{height: 50px;background-color: #c9c1c1;}
}</style>

3.2、aside.vue、header.vue、home.vue内容相似如下

<template><div class="home"><span>侧边/头部/博客首页</span></div>
</template><script setup lang="ts"></script><style lang="scss" scoped></style> 

最终页面效果如下

在这里插入图片描述

3. element-plus/icons-vue

功能: Element Plus的图标库
安装: npm install @element-plus/icons-vue

使用步骤:

1、main.ts引入 element-plus/icons-vue

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from '@/router/index'
import ElementPlus from 'element-plus'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'// 样式导入
import 'element-plus/dist/index.css'
// 创建应用实例
const app = createApp(App)
// 注册 Element Plus 图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {app.component(key, component)
}
app.use(router)
app.use(ElementPlus, {size: 'default',zIndex: 3000
})
app.mount('#app')

2、在Home.vue组件中应用

<template><div class="home"><h1>博客首页</h1><el-icon :size="50"><House /></el-icon></div>
</template><script setup lang="ts"></script><style lang="scss" scoped></style> 

在这里插入图片描述

4. unplugin-auto-import

功能: 自动导入API插件
安装: npm install -D unplugin-auto-import

使用步骤:

1、在vite.config.ts文件中添加配置

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import AutoImport from 'unplugin-auto-import/vite'
// https://vite.dev/config/
export default defineConfig({resolve: {alias: {'@': path.resolve(__dirname, './src')}},plugins: [vue(),AutoImport({imports:["vue","vue-router"],dts:'src/auto-import.d.ts',  // 路径下自动生成文件夹存放全局指令 eslintrc: {enabled: true,  // 1、改为true用于生成eslint配置。2、生成后改回false,避免重复生成消耗} })],
})

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
这里ref正常使用,并没有报错,说明已经自动导入完成

5. unplugin-auto-import

功能: 在Vue文件中自动引入组件
安装: npm install -D unplugin-vue-components

使用步骤:

1、在vite.config.ts文件中添加配置

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vite.dev/config/
export default defineConfig({resolve: {alias: {'@': path.resolve(__dirname, './src')}},plugins: [vue(),AutoImport({imports:["vue","vue-router"],dts:'src/auto-import.d.ts',  // 路径下自动生成文件夹存放全局指令 eslintrc: {enabled: false,  // 1、改为true用于生成eslint配置。2、生成后改回false,避免重复生成消耗},resolvers: [ElementPlusResolver()] }),Components({dirs: ['src/components'], // 配置需要默认导入的自定义组件文件夹,该文件夹下的所有组件都会自动 importresolvers: [ElementPlusResolver()],}),],
})

1、在home.vue组件中使用

<template><div class="home"><h1>{{ title }}</h1><!-- <el-icon :size="50"><House /></el-icon> --><el-button type="primary">点击</el-button></div>
</template><script setup lang="ts">const title = ref('首页')
</script><style lang="scss" scoped></style> 

在这里插入图片描述
这里直接使用,样式效果都正常显示,证明引入成功。

6. Pinia

功能: Vue 3的状态管理库
安装: npm install pinia pinia-plugin-persistedstate

使用步骤:

1、创建src/stores/index.ts

import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)export default pinia

2、main.ts引入 Pinia

import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import router from '@/router/index'
import ElementPlus from 'element-plus'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
import store from './stores/index'
// 样式导入
import 'element-plus/dist/index.css'
// 创建应用实例
const app = createApp(App)
// 注册 Element Plus 图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {app.component(key, component)
}
app.use(router)
app.use(ElementPlus, {size: 'default',zIndex: 3000
})
app.use(store)
app.mount('#app')

3、创建src/stores/article.ts

import { defineStore } from 'pinia'
interface Article {
id: number
title: string
content: string
}
export const useArticleStore = defineStore('article', {state: () => ({articles: [] as Article[],currentArticle:null as Article | null}),actions: {fetchArticles() {this.articles = [{id: 1,title: '文章1',content: '文章内容1'}]}},getters: {getArticleById: (state) => (id: number) => {return state.articles.find(article => article.id === id)}},persist: true,//持久化存储
})

4、在组件中应用

<template><div class="home"><h1>{{ title }}</h1><!-- <el-icon :size="50"><House /></el-icon> --><!-- <el-button type="primary">点击</el-button> --><div v-for="article in articles" :key="article.id"><h2>{{ article.title }}</h2><p>{{ article.content }}</p></div></div>
</template><script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useArticleStore } from '@/stores/article'
const articleStore = useArticleStore()
const { articles } = storeToRefs(articleStore)
const title = ref('首页')
onMounted(() => {articleStore.fetchArticles()
})
</script><style lang="scss" scoped></style> 

在这里插入图片描述

7. axios

功能: 基于Promise的HTTP客户端
安装: npm install axios

使用步骤:

开发前提需要配置代理

server: {host: '0.0.0.0',port: port,open: true,proxy: {[VITE_APP_BASE_API]: {target: VITE_SERVE,changeOrigin: true,rewrite: path => path.replace(RegExp(`^${VITE_APP_BASE_API}`), '')}},disableHostCheck: true
},

1、创建src/utils/request.ts

import axios, { AxiosRequestConfig } from 'axios'
import { ElNotification , ElMessageBox, ElMessage, ElLoading, LoadingParentElement } from 'element-plus'
import { getToken } from '@/utils/auth'
import { tansParams, blobValidate } from '@/utils/mis'
import cache from '@/plugins/cache'
import { saveAs } from 'file-saver'
import useUserStore from '@/stores/modules/user'
import { ComponentOptionsBase, ComponentProvideOptions } from 'vue'var downloadLoadingInstance: { close: any; setText?: (text: string) => void; removeElLoadingChild?: () => void; handleAfterLeave?: () => void; vm?: globalThis.ComponentPublicInstance<{}, {}, {}, {}, {}, {}, {}, {}, false, ComponentOptionsBase<any, any, any, any, any, any, any, any, any, {}, {}, string, {}, {}, {}, string, ComponentProvideOptions>, {}, {}, "", {}, any>; $el?: HTMLElement; originalPosition?: globalThis.Ref<string, string>; originalOverflow?: globalThis.Ref<string, string>; visible?: globalThis.Ref<boolean, boolean>; parent?: globalThis.Ref<LoadingParentElement, LoadingParentElement>; background?: globalThis.Ref<string, string>; svg?: globalThis.Ref<string, string>; svgViewBox?: globalThis.Ref<string, string>; spinner?: globalThis.Ref<string | boolean, string | boolean>; text?: globalThis.Ref<string, string>; fullscreen?: globalThis.Ref<boolean, boolean>; lock?: globalThis.Ref<boolean, boolean>; customClass?: globalThis.Ref<string, string>; target?: globalThis.Ref<HTMLElement, HTMLElement>; beforeClose?: globalThis.Ref<(() => boolean) | undefined, (() => boolean) | undefined> | undefined; closed?: globalThis.Ref<(() => void) | undefined, (() => void) | undefined> | undefined };
let errorCode = {'401': '登录状态已过期,您可以继续留在该页面,或者重新登录','403': '当前操作没有权限','404': '请求的资源不存在','500': '服务器错误','601': '请求参数错误','default': '请求失败,请稍后再试'
}
// 是否显示重新登录
export let isRelogin = { show: false };axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8'
// 创建axios实例
const service = axios.create({// axios中请求配置有baseURL选项,表示请求URL公共部分baseURL: import.meta.env.VITE_APP_BASE_API,// 超时timeout: 60000
})// request拦截器
service.interceptors.request.use(config => {// 是否需要设置 tokenconst isToken = (config.headers || {}).isToken === false// 是否需要防止数据重复提交const isRepeatSubmit = (config.headers || {}).repeatSubmit === falseif (getToken() && !isToken) {config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改}if(config.headers){config.headers['Content-Type'] = config.headers['Content-Type']}// get请求映射params参数if (config.method === 'get' && config.params) {let url = config.url + '?' + tansParams(config.params);url = url.slice(0, -1);config.params = {};config.url = url;}if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put'|| config.method === 'delete')) {const requestObj = {url: config.url,data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,time: new Date().getTime()}const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小const limitSize = 5 * 1024 * 1024; // 限制存放数据5Mif (requestSize >= limitSize) {console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。')return config;}const sessionObj = cache.session.getJSON('sessionObj')if (sessionObj === undefined || sessionObj === null || sessionObj === '') {cache.session.setJSON('sessionObj', requestObj as any)} else {const s_url = sessionObj.url;                // 请求地址const s_data = sessionObj.data;              // 请求数据const s_time = sessionObj.time;              // 请求时间const interval = 100;                       // 间隔时间(ms),小于此时间视为重复提交if (s_data === requestObj.data && s_url === requestObj.url&& requestObj.time - s_time < interval) {const message = '数据正在处理,请勿重复提交';console.warn(`[${s_url}]: ` + message)return Promise.reject(new Error(message))} else {cache.session.setJSON('sessionObj', requestObj as any)}}}return config
}, error => {console.log(error)Promise.reject(error)
})// 响应拦截器
service.interceptors.response.use(res => {// 未设置状态码则默认成功状态const code = res.data.code || 200;// 获取错误信息const msg = errorCode[code as keyof typeof errorCode] || res.data.msg || errorCode['default']// 二进制数据则直接返回if (res.request.responseType ===  'blob' || res.request.responseType ===  'arraybuffer') {return res.data}if (code === 401) {if (!isRelogin.show) {isRelogin.show = true;ElMessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {isRelogin.show = false;useUserStore().logOut().then(() => {location.href = '/index';})}).catch(() => {isRelogin.show = false;});}return Promise.reject('无效的会话,或者会话已过期,请重新登录。')} else if (code === 500) {ElMessage({ message: msg, type: 'error' })return Promise.reject(new Error(msg))} else if (code === 601) {ElMessage({ message: msg, type: 'warning' })return Promise.reject(new Error(msg))} else if (code !== 200&& code !== 400) {ElNotification.error({ title: msg })return Promise.reject('error')} else {return  Promise.resolve(res.data)} },error => {console.log('err' + error)let { message } = error;if (message == "Network Error") {message = "后端接口连接异常";} else if (message.includes("timeout")) {message = "系统接口请求超时";} else if (message.includes("Request failed with status code")) {message = "系统接口" + message.substr(message.length - 3) + "异常";}ElMessage({ message: message, type: 'error', duration: 5 * 1000 })return Promise.reject(error)}
)// 通用下载方法
export function download(url: string, params: any, filename: string | undefined, config: AxiosRequestConfig<any> | undefined) {downloadLoadingInstance = ElLoading.service({ text: "正在下载数据,请稍候", background: "rgba(0, 0, 0, 0.7)", })return service.post(url, params, {transformRequest: [(params) => { return tansParams(params) }],headers: { 'Content-Type': 'application/x-www-form-urlencoded' },responseType: 'blob',...config}).then(async (data:any) => {const isBlob = blobValidate(data);if (isBlob) {const blob = new Blob([data])saveAs(blob, filename)} else {const resText = await data.text();const rspObj = JSON.parse(resText);const errMsg = errorCode[rspObj.code as keyof typeof errorCode] || rspObj.msg || errorCode['default']ElMessage.error(errMsg);}downloadLoadingInstance.close();}).catch((r) => {console.error(r)ElMessage.error('下载文件出现错误,请联系管理员!')downloadLoadingInstance.close();})
}export default service

2、创建src/utils/auth.ts

import Cookies from 'js-cookie'const TokenKey = 'Admin-Token'export function getToken() {return Cookies.get(TokenKey)
}export function setToken(token:any) {return Cookies.set(TokenKey, token,{ expires: 1 })
}export function removeToken() {return Cookies.remove(TokenKey)
}

3、创建src/utils/mis.ts

/*** 通用js方法封装处理* Copyright (c) 2019 ruoyi*/// 日期格式化// 日期格式化
// 日期格式化
export function parseTime(time: string | number | Date, pattern: string) {if (arguments.length === 0 || !time) {return null}const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'let dateif (typeof time === 'object') {date = time} else {if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {time = parseInt(time)} else if (typeof time === 'string') {time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), '');}if ((typeof time === 'number') && (time.toString().length === 10)) {time = time * 1000}date = new Date(time)}const formatObj = {y: date.getFullYear(),m: date.getMonth() + 1,d: date.getDate(),h: date.getHours(),i: date.getMinutes(),s: date.getSeconds(),a: date.getDay()}const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result: string, key: string) => {let value = key in formatObj ? formatObj[key as keyof typeof formatObj] : 0;// Note: getDay() returns 0 on Sundayif (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }if (result.length > 0 && value < 10) {return '0' + value.toString(); // 将 value 转换为字符串并返回}return value.toString(); // 确保返回值是字符串});return time_str}// 添加日期范围export function addDateRange(params: any, dateRange: any[], propName: string) {let search = params;search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {};dateRange = Array.isArray(dateRange) ? dateRange : [];if (typeof (propName) === 'undefined') {search.params['beginTime'] = dateRange[0];search.params['endTime'] = dateRange[1];} else {search.params['begin' + propName] = dateRange[0];search.params['end' + propName] = dateRange[1];}return search;}// 回显数据字典export function selectDictLabel(datas: { [x: string]: { label: any, value: any } }, value: string | undefined) {if (value === undefined) {return "";}var actions = [];Object.keys(datas).some((key) => {if (datas[key].value == ('' + value)) {actions.push(datas[key].label);return true;}})if (actions.length === 0) {actions.push(value);}return actions.join('');}// 回显数据字典(字符串数组)export function selectDictLabels(datas: { [key: string]: { label: any, value: any } }, value: string | undefined, separator: undefined) {if (value === undefined || value.length ===0) {return "";}if (Array.isArray(value)) {value = value.join(",");}var actions: string[] = [];var currentSeparator = undefined === separator ? "," : separator;var temp = value.split(currentSeparator);Object.keys(value.split(currentSeparator)).forEach((val) => {var match = false;Object.keys(datas).some((key) => {if (datas[key].value == ('' + temp[parseInt(val)])) {actions.push(datas[key].label + currentSeparator);match = true;}})if (!match) {actions.push(temp[parseInt(val)] + currentSeparator);}})return actions.join('').substring(0, actions.join('').length - 1);}// 字符串格式化(%s )export function sprintf(str: string) {var args = arguments, flag = true, i = 1;str = str.replace(/%s/g, function () {var arg = args[i++];if (typeof arg === 'undefined') {flag = false;return '';}return arg;});return flag ? str : '';}// 转换字符串,undefined,null等转化为""export function parseStrEmpty(str: string) {if (!str || str == "undefined" || str == "null") {return "";}return str;}// 数据合并export function mergeRecursive(source: { [x: string]: any }, target: { [x: string]: any }) {for (var p in target) {try {if (target[p].constructor == Object) {source[p] = mergeRecursive(source[p], target[p]);} else {source[p] = target[p];}} catch (e) {source[p] = target[p];}}return source;};/*** 构造树型结构数据* @param {*} data 数据源* @param {*} id id字段 默认 'id'* @param {*} parentId 父节点字段 默认 'parentId'* @param {*} children 孩子节点字段 默认 'children'*/export function handleTree(data: any, id: any, parentId: any, children: any) {let config = {id: id || 'id',parentId: parentId || 'parentId',childrenList: children || 'children'};const childrenListMap: { [key: string]: any[] } = {};const nodeIds: { [key: string]: any } = {};var tree = [];for (let d of data) {let parentId = d[config.parentId];if (childrenListMap[parentId] == null) {childrenListMap[parentId] = [];}nodeIds[d[config.id]] = d;childrenListMap[parentId].push(d);}for (let d of data) {let parentId = d[config.parentId];if (nodeIds[parentId] == null) {tree.push(d);}}for (let t of tree) {adaptToChildrenList(t);}function adaptToChildrenList(o: { [x: string]: any }) {if (childrenListMap[o[config.id]] !== null) {o[config.childrenList] = childrenListMap[o[config.id]];}if (o[config.childrenList]) {for (let c of o[config.childrenList]) {adaptToChildrenList(c);}}}return tree;}/*** 参数处理* @param {*} params  参数*/export function tansParams(params: { [x: string]: any }) {let result = ''for (const propName of Object.keys(params)) {const value = params[propName];var part = encodeURIComponent(propName) + "=";if (value !== null && value !== "" && typeof (value) !== "undefined") {if (typeof value === 'object') {for (const key of Object.keys(value)) {if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') {let params = propName + '[' + key + ']';var subPart = encodeURIComponent(params) + "=";result += subPart + encodeURIComponent(value[key]) + "&";}}} else {result += part + encodeURIComponent(value) + "&";}}}return result}// 返回项目路径export function getNormalPath(p: string) {if (p.length === 0 || !p || p == 'undefined') {return p};let res = p.replace('//', '/')if (res[res.length - 1] === '/') {return res.slice(0, res.length - 1)}return res;}// 验证是否为blob格式export function blobValidate(data: { type: string }) {return data.type !== 'application/json'}

4、创建src/plugins/cache.ts

const sessionCache = {set (key: string | null, value: string | null) {if (!sessionStorage) {return}if (key != null && value != null) {sessionStorage.setItem(key, value)}},get (key: string | null) {if (!sessionStorage) {return null}if (key == null) {return null}return sessionStorage.getItem(key)},setJSON (key: any, jsonValue: null) {if (jsonValue != null) {this.set(key, JSON.stringify(jsonValue))}},getJSON (key: any) {const value = this.get(key)if (value != null) {return JSON.parse(value)}},remove (key: string) {sessionStorage.removeItem(key);}}const localCache = {set (key: string | null, value: string | null) {if (!localStorage) {return}if (key != null && value != null) {localStorage.setItem(key, value)}},get (key: string | null) {if (!localStorage) {return null}if (key == null) {return null}return localStorage.getItem(key)},setJSON (key: any, jsonValue: null) {if (jsonValue != null) {this.set(key, JSON.stringify(jsonValue))}},getJSON (key: any) {const value = this.get(key)if (value != null) {return JSON.parse(value)}},remove (key: string) {localStorage.removeItem(key);}}export default {/*** 会话级缓存*/session: sessionCache,/*** 本地缓存*/local: localCache}

5、创建src/plugins/cache.ts

const sessionCache = {set (key: string | null, value: string | null) {if (!sessionStorage) {return}if (key != null && value != null) {sessionStorage.setItem(key, value)}},get (key: string | null) {if (!sessionStorage) {return null}if (key == null) {return null}return sessionStorage.getItem(key)},setJSON (key: any, jsonValue: null) {if (jsonValue != null) {this.set(key, JSON.stringify(jsonValue))}},getJSON (key: any) {const value = this.get(key)if (value != null) {return JSON.parse(value)}},remove (key: string) {sessionStorage.removeItem(key);}}const localCache = {set (key: string | null, value: string | null) {if (!localStorage) {return}if (key != null && value != null) {localStorage.setItem(key, value)}},get (key: string | null) {if (!localStorage) {return null}if (key == null) {return null}return localStorage.getItem(key)},setJSON (key: any, jsonValue: null) {if (jsonValue != null) {this.set(key, JSON.stringify(jsonValue))}},getJSON (key: any) {const value = this.get(key)if (value != null) {return JSON.parse(value)}},remove (key: string) {localStorage.removeItem(key);}}export default {/*** 会话级缓存*/session: sessionCache,/*** 本地缓存*/local: localCache}

6、创建src/stores/modules/user.ts

import { defineStore } from 'pinia'
import { login, logout, getInfo} from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
import defAva from '@/assets/vue.svg'const useUserStore = defineStore('user',{state: () => ({token: getToken(),id: '',name: '',avatar: '',roles: [] as string[],permissions: [],customList: [],customer: {}}),actions: {// 登录login(userInfo: { username: string }) {userInfo.username = userInfo.username.trim()return new Promise<void>((resolve, reject) => {login(userInfo).then((res: any) => {console.log(res,'res111')if (res.code === 200) { // 修改这里setToken(res.data.access_token)this.token = res.data.access_tokenresolve(res)} else {reject(res.msg) // 修改这里}}).catch((error: any) => {reject(error)})})},// 获取用户信息getInfo() {return new Promise((resolve, reject) => {getInfo().then((res: any) => {const user = res.data.user // 修改这里const avatar = (user.avatar == "" || user.avatar == null) ? defAva : user.avatar;if (res.data.roles && res.data.roles.length > 0) { // 修改这里this.roles = res.data.rolesthis.permissions = res.data.permissions} else {this.roles = ['ROLE_DEFAULT']}this.customer = res.data.customer // 修改这里this.id = user.userIdthis.name = user.userNamethis.avatar = avatarresolve(res)}).catch((error: any) => {reject(error)})})},// 退出系统logOut() {return new Promise<void>((resolve, reject) => {logout().then(() => {this.token = ''this.roles = []this.permissions = []removeToken()resolve()}).catch((error: any) => {reject(error)})})}}})export default useUserStore

7、在home.vue中测试应用

<template><div class="home"><h1>{{ title }}</h1><!-- <el-icon :size="50"><House /></el-icon> --><!-- <el-button type="primary">点击</el-button> --><div v-for="article in articles" :key="article.id"><h2>{{ article.title }}</h2><p>{{ article.content }}</p></div></div>
</template><script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useArticleStore } from '@/stores/article'
import useUserStore from '@/stores/modules/user'
const userStore = useUserStore()
const articleStore = useArticleStore()
const { articles } = storeToRefs(articleStore)
const title = ref('首页')onMounted(() => {articleStore.fetchArticles()const params = {username: "admin",password: "******",}userStore.login(params).then((res) => {console.log('res',res)}).catch((err) => {console.log('err',err)});
})
</script><style lang="scss" scoped></style> 

在这里插入图片描述
正常登录成功

8. mockjs & vite-plugin-mock

功能: 模拟接口数据
安装: npm install -D mockjs @types/mockjs vite-plugin-mock

1、在vite.config.ts引入配置mock

import { viteMockServe } from 'vite-plugin-mock'
export default defineConfig(({ mode }) => {plugins: [viteMockServe({mockPath: './mock/',//设置模拟数据的存储文件夹//@ts-ignoresupportTs: true,//是否读取ts文件模块logger:true,//是否在控制台显示请求日志localEnabled: true,//设置是否启用本地mock文件prodEnabled:false//设置打包是否启用mock功能})]
})

2、在根目录新建mock/index.ts

import { MockMethod } from 'vite-plugin-mock'
import Mock from 'mockjs'export default [{url: '/api/login',method: 'post',response: () => {return {code: 200,data: Mock.mock({access_token: "eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VyX2tleSI6Ij...",customerName: "管理员账户",expires_in: 28800,}),msg:'登录成功'}}}
] as MockMethod[]

在这里插入图片描述
成功返回预设的返回值对象。

9. sass & sass-loader

功能: CSS预处理器
安装: npm install -D sass sass-loader

使用步骤:

1、新建src/assets/styles/index.scss

body {--el-color-primary: #4F6EF7;height: 100%;margin: 0;-moz-osx-font-smoothing: grayscale;-webkit-font-smoothing: antialiased;text-rendering: optimizeLegibility;font-family: SourceHanSansSC-Regular, SourceHanSansSC,Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
}
.el-select__wrapper{height: 2.25rem;
}
label {font-weight: 700;
}html {height: 100%;box-sizing: border-box;
}#app {height: 100%;
}*,
*:before,
*:after {box-sizing: inherit;
}.no-padding {padding: 0px !important;
}.padding-content {padding: 4px 0;
}a:focus,
a:active {outline: none;
}a,
a:focus,
a:hover {cursor: pointer;color: inherit;text-decoration: none;
}
ul,li{list-style: none;
}
div:focus {outline: none;
}.fr {float: right;
}.fl {float: left;
}.pr-5 {padding-right: 5px;
}.pl-5 {padding-left: 5px;
}.block {display: block;
}.pointer {cursor: pointer;
}.inlineBlock {display: block;
}.clearfix {&:after {visibility: hidden;display: block;font-size: 0;content: " ";clear: both;height: 0;}
}

2、新建src/assets/styles/variables.module.scss

// base color
$blue: #324157;
$light-blue: #3A71A8;
$red: #C03639;
$pink: #E65D6E;
$green: #30B08F;
$tiffany: #4AB7BD;
$yellow: #FEC171;
$panGreen: #30B08F;// 默认菜单主题风格
$base-menu-color: #bfcbd9;
$base-menu-color-active: #f4f4f5;
$base-menu-background: #304156;
$base-logo-title-color: #ffffff;$base-menu-light-color: #606787;
$base-menu-light-background: #F2F6FB;
$base-logo-light-title-color: #fff;$base-sub-menu-background: #1f2d3d;
$base-sub-menu-hover: #001528;// 自定义暗色菜单风格
/**
$base-menu-color:hsla(0,0%,100%,.65);
$base-menu-color-active:#fff;
$base-menu-background:#001529;
$base-logo-title-color: #ffffff;$base-menu-light-color:rgba(0,0,0,.70);
$base-menu-light-background:#ffffff;
$base-logo-light-title-color: #001529;$base-sub-menu-background:#000c17;
$base-sub-menu-hover:#001528;
*/$--color-primary: #4F6EF7;
$--color-success: #67C23A;
$--color-warning: #E6A23C;
$--color-danger: #F56C6C;
$--color-info: #909399;$base-sidebar-width: 240px;// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {menuColor: $base-menu-color;menuLightColor: $base-menu-light-color;menuColorActive: $base-menu-color-active;menuBackground: $base-menu-background;menuLightBackground: $base-menu-light-background;subMenuBackground: $base-sub-menu-background;subMenuHover: $base-sub-menu-hover;sideBarWidth: $base-sidebar-width;logoTitleColor: $base-logo-title-color;logoLightTitleColor: $base-logo-light-title-color;primaryColor: $--color-primary;successColor: $--color-success;dangerColor: $--color-danger;infoColor: $--color-info;warningColor: $--color-warning;
}

3、新建src/assets/styles/mixins.scss

@mixin clearfix {&:after {content: "";display: table;clear: both;}}@mixin scrollBar {&::-webkit-scrollbar-track-piece {background: #d3dce6;}&::-webkit-scrollbar {width: 6px;}&::-webkit-scrollbar-thumb {background: #99a9bf;border-radius: 20px;}}@mixin relative {position: relative;width: 100%;height: 100%;}@mixin pct($pct) {width: #{$pct};position: relative;margin: 0 auto;}@mixin triangle($width, $height, $color, $direction) {$width: $width/2;$color-border-style: $height solid $color;$transparent-border-style: $width solid transparent;height: 0;width: 0;@if $direction==up {border-bottom: $color-border-style;border-left: $transparent-border-style;border-right: $transparent-border-style;}@else if $direction==right {border-left: $color-border-style;border-top: $transparent-border-style;border-bottom: $transparent-border-style;}@else if $direction==down {border-top: $color-border-style;border-left: $transparent-border-style;border-right: $transparent-border-style;}@else if $direction==left {border-right: $color-border-style;border-top: $transparent-border-style;border-bottom: $transparent-border-style;}}

4、在home组件中使用

<template><div class="home"><h1>{{ title }}</h1><!-- <el-icon :size="50"><House /></el-icon> --><!-- <el-button type="primary">点击</el-button> --><div v-for="article in articles" :key="article.id"><h2>{{ article.title }}</h2><p>{{ article.content }}</p></div></div>
</template><script setup lang="ts">
import { storeToRefs } from 'pinia'
import { useArticleStore } from '@/stores/article'
import useUserStore from '@/stores/modules/user'
const userStore = useUserStore()
const articleStore = useArticleStore()
const { articles } = storeToRefs(articleStore)
const title = ref('首页')onMounted(() => {articleStore.fetchArticles()const params = {username: "admin",password: "******",}userStore.login(params).then((res) => {console.log('res',res)}).catch((err) => {console.log('err',err)});
})
</script><style lang="scss" scoped>
@import '@/assets/styles/mixins.scss';
.container {@include relative;
}
</style> 

在这里插入图片描述
这里可以看出来样式已经生效赋值。

10. cross-env

功能: 跨平台设置环境变量
安装: npm install -D cross-env

使用步骤:
{"scripts": {"dev": "cross-env NODE_ENV=development vite","build:test": "cross-env NODE_ENV=test vue-tsc && vite build","build:prod": "cross-env NODE_ENV=production vue-tsc && vite build"}
}

11. 配置环境变量

使用步骤:

根目录新建.env.development和.env.production

.env.development内容如下:

# 页面标题
VITE_APP_TITLE = '博客'
# 开发环境配置
VITE_APP_ENV = 'development'
# 小麒物联/开发环境
VITE_APP_BASE_API = '/api'
# 是否启用代理
VITE_HTTP_PROXY = true
# 端口
VITE_PORT = 80# 本地环境接口地址
VITE_SERVE = 'http://******'

.env.production内容如下:

# 页面标题
VITE_APP_TITLE = '博客'
# 开发环境配置
VITE_APP_ENV = 'production'
# 小麒物联/开发环境
VITE_APP_BASE_API = '/api'
# 是否启用代理
VITE_HTTP_PROXY = false
# 端口
VITE_PORT = 80# 本地环境接口地址
VITE_SERVE = 'http://******'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip

12. 配置打包文件结构

build: {rollupOptions: {output: {chunkFileNames: 'static/js/[name]-[hash].js',entryFileNames: 'static/js/[name]-[hash].js',assetFileNames: 'static/[ext]/[name]-[hash][extname]',manualChunks(id) {if (id.includes('element-plus')) {return 'element-plus';}}}}
}

完整版vite.config.ts文件

import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import { viteMockServe } from 'vite-plugin-mock'
// https://vite.dev/config/
export default defineConfig(({ mode }) => {const env = loadEnv(mode, process.cwd())const { VITE_APP_ENV, VITE_APP_BASE_API, VITE_SERVE, VITE_PORT } = envconst port = VITE_PORT ? parseInt(VITE_PORT, 10) : 8080return {base: VITE_APP_ENV === 'production' ? '/' : './',resolve: {alias: {'@': path.resolve(__dirname, './src')}},plugins: [vue(),AutoImport({imports: ["vue", "vue-router"],dts: 'src/auto-import.d.ts',  // 路径下自动生成文件夹存放全局指令 eslintrc: {enabled: false,  // 1、改为true用于生成eslint配置。2、生成后改回false,避免重复生成消耗},resolvers: [ElementPlusResolver()]}),Components({dirs: ['src/components'], // 配置需要默认导入的自定义组件文件夹,该文件夹下的所有组件都会自动 importresolvers: [ElementPlusResolver()],}),viteMockServe({mockPath: './mock/',//设置模拟数据的存储文件夹//@ts-ignoresupportTs: true,//是否读取ts文件模块logger:true,//是否在控制台显示请求日志localEnabled: true,//设置是否启用本地mock文件prodEnabled:false//设置打包是否启用mock功能})],// vite 相关配置server: {host: '0.0.0.0',port: port,open: true,proxy: {[VITE_APP_BASE_API]: {target: VITE_SERVE,changeOrigin: true,rewrite: path => path.replace(RegExp(`^${VITE_APP_BASE_API}`), '')}},disableHostCheck: true},css: {postcss: {plugins: [{postcssPlugin: 'internal:charset-removal',AtRule: {charset: (atRule) => {if (atRule.name === 'charset') {atRule.remove();}}}}]}},build: {rollupOptions: {output: {chunkFileNames: 'static/js/[name]-[hash].js',entryFileNames: 'static/js/[name]-[hash].js',assetFileNames: 'static/[ext]/[name]-[hash][extname]',manualChunks(id) {if (id.includes('element-plus')) {return 'element-plus';}}}}}}
})

完整版package.json文件

{"name": "yf-blog","author": "nch","private": true,"version": "0.0.0","type": "module","scripts": {"dev": "vite","build": "vue-tsc -b && vite build","preview": "vite preview"},"repository": {"type": "git","url": "https://gitee.com/niech_project/***.git"},"dependencies": {"@element-plus/icons-vue": "^2.3.1","axios": "^1.7.9","element-plus": "^2.9.0","file-saver": "^2.0.5","highlight.js": "^11.10.0","js-cookie": "^3.0.5","marked": "^15.0.3","pinia": "^2.3.0","pinia-plugin-persistedstate": "^4.1.3","vue": "^3.5.13","vue-router": "^4.5.0"},"devDependencies": {"@types/file-saver": "^2.0.7","@types/js-cookie": "^3.0.6","@types/mockjs": "^1.0.10","@types/node": "^22.10.2","@vitejs/plugin-vue": "^5.2.1","cross-env": "^7.0.3","mockjs": "^1.1.0","sass": "^1.83.0","sass-loader": "^16.0.4","typescript": "~5.6.2","unplugin-auto-import": "^0.18.6","unplugin-vue-components": "^0.27.5","vite": "^6.0.1","vite-plugin-mock": "^3.0.2","vue-tsc": "^2.1.10"}
}

至此,前端项目基本架构完成。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com