vue3+TypeScript+Element Plus技术
文章目录
技术概述
该技术用于web前端技术开发,也是现阶段较为流行的前端开发语言。学习该技术的原因是因为我在项目中担任前端开发的角色,需要在项目中使用,而且该技术相对比较好上门和入手。该技术的难点在于vue3还不够成熟,网上的资料还是相对较少,使找资料的效率会较低。
技术详述
以结对实战的为例,我是如下使用该技术
组件抽象
对项目的前端原型中的相同的部分抽象为一个个组件,使其便于复用,通过组件来进行页面的搭建
页面视图表现
将一个个页面抽象为一个个视图,通过组件组合成一个个视图
路由部署
对不同的页面,使用不同的路由访问,路由间传参使用props传
import { createRouter, createWebHistory } from 'vue-router'
import HomeView from '../views/HomeView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/',
name: 'app',
component: HomeView
},
{
path: '/home',
name: 'home',
component: HomeView
},
{
path: '/more',
name: 'more',
component: () => import('../views/MoreView.vue')
},
{
path: '/athletes',
name: 'athletes',
component: () => import('../views/AthletesView.vue')
},
{
path: '/results',
name: 'results',
component: () => import('../views/ResultsView.vue')
},
{
path: '/schedules',
name: 'schedules',
component: () => import('../views/SchedulesView.vue')
},
{
path: '/medal_table',
name: 'medal_table',
component: () => import('../views/MedalTableView.vue')
},
{
path: '/details/:name/:type',
name: 'details',
component: () => import('../views/DetailsView.vue'),
props: true // 通过props传递参数
}
]
})
export default router
App程序入口
在App.vue中使用router-view来显示各个页面
<script setup>
</script>
<template>
<!-- <router-link to="/">10</router-link><br>
<router-link to="/more">22</router-link> -->
<router-view></router-view>
</template>
<style scoped>
</style>
程序运行入口
在main.js中部署后端的访问地址,路由的引入以及前端页面开始的入口
import { createApp, provide } from 'vue'
import App from './App.vue'
import router from './router'
import { library } from '@fortawesome/fontawesome-svg-core';
import { fas } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import ElementPlus from 'element-plus'
library.add(fas);
const backendRouter = {
// 在这里定义全局变量
appName: 'My App',
url:'http://8.140.241.231:14000' /* 'http://localhost:14000' */
};
const app = createApp(App)
app.provide('backendRouter', backendRouter)
app.use(router).use(ElementPlus, { size: 'small', zIndex: 3000 })
.component('font-awesome-icon', FontAwesomeIcon).mount('#app')
访问后端接口
用axios和async进行异步访问后端
watch(activeNames, () => {
/* console.log(activeNames.value) */
if(activeNames.value !== "" ){
/* console.log(activeNames.value) */
// 发送GET请求
axios.get(backendRouter.url +'/rank/detail?eventName=' + activeNames.value )
.then(response => {
// response.data 就是上面的JSON数组Message: {{ msg }}
datas.value = response.data.data;
types.value = types.value.filter((t) => t === '')
datas.value.forEach(element => {
types.value.push(element.phaseName);
});
phaseName.value = types.value[types.value.length - 1];
datas.value.forEach(element => {
if(element.phaseName === phaseName.value){
detials.value = element.eventRanks;
}
});
})
}
})
技术使用中遇到的问题和解决过程
element Plus的css样式不好修改
可以按F12进入开发者模式进行寻找和调整
可以输入表情的富文本框
由于网上的这些富文本框要么是vue2语法,要么就是用其他的语言,我学习对照了这些程序,用自己的代码进行描述
表情框选择组件
<template>
<div class="emoji-panel-container">
<div class="emoji-title" v-show="emojihistory.length !== 0">
<span>最近使用</span>
<span class="delete-history-btn" @click="removeHistory">删除历史</span>
</div>
<div class="emoji-history-container">
<img v-for="i in emojihistory" :src="'/emoji/' + i"
:key="i" class="emoji-item" @click="appendEmoji(i)">
</div>
<div class="emoji-title">
全部表情
</div>
<div class="emoji-all-container">
<img class="emoji-item" v-for="i in emoji" :src="'/emoji/' + i"
:key="i" @click="appendEmoji(i)">
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue';
const emojihistory = ref<string[]>([]);
const emit = defineEmits(['emojiChick']);
const emoji = ref([
"0161251e1e8547b2a3af25089edfac36.png",...//就是你所有的表情包文件,由于表情包不多直接放在public中
]);
// 往文本框中输入表情包图片地址
function appendEmoji(i: string){
let targetUrl = "/emoji/" + i;
emit("emojiChick", targetUrl); // 请确保父组件中有对应的事件监听器
saveHistory(i);
restoreHistory();
};
// 移除历史记录
function removeHistory(){
window.localStorage.removeItem("emoji-history");
restoreHistory();
};
// 保存历史记录
function restoreHistory(){
let record = window.localStorage.getItem("emoji-history");
emojihistory.value = record ? record.split(",") : [];
};
// 保存表情包为历史记录
function saveHistory(emojiIndex: string){
let record = window.localStorage.getItem("emoji-history");
let targetSaveStr = emojiIndex;
let count = 1;
if (record) {
let splitArrays = record.split(",");
for (let i = 0; i < splitArrays.length; i++) {
let item = splitArrays[i];
if (emojiIndex.toString() !== item) {
targetSaveStr += ",";
targetSaveStr += item;
count++;
if (count > 5) {
break;
}
}
}
}
window.localStorage.setItem("emoji-history", targetSaveStr);
};
onMounted(() => {
restoreHistory();
});
</script>
<style>
/*均可自己设计*/
.emoji-panel-container{
width: 25vw;
border: 0.1vh solid;
border-radius: 0.5vh;
box-shadow: 0.5vh 0.5vh 1vh rgba(29, 29, 29, 0.5); /* 水平偏移量 垂直偏移量 模糊半径 阴影颜色 */
background-color: white;
}
.delete-history-btn{
color: #0084FF;
cursor: pointer;
}
.emoji-title{
display: flex;
justify-content: space-between;
}
.emoji-title {
padding: 1vh;
}
/*滚动条效果*/
.emoji-panel-container::-webkit-scrollbar{
width: 1vw;
}
.emoji-panel-container::-webkit-scrollbar-track{
background-color: #F9FAFB;
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
}
.emoji-panel-container::-webkit-scrollbar-thumb {
background-color: #E5E6EB;
-webkit-border-radius: 2em;
-moz-border-radius: 2em;
border-radius: 2em;
}
.emoji-panel-container {
overflow-y: scroll;
overflow-x: hidden;
height: 20vh;
}
.emoji-item:hover{
transform: scale(1.3);
transition: transform .3s;
}
.emoji-item{
width: 5vh;
height: 5vh;
transition: all .3s linear;
padding: 1vh;
cursor: pointer;
}
</style>
富文本框组件
<template>
<div class="rich-text-input">
<!--用div的contenteditable属性将该标签设置为可编辑框-->
<div ref="inputBox" class="input-box" :placeholder="hitContent" contenteditable="true" spellcheck="false"></div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted, watch} from 'vue';
const props = defineProps({
content: {
type: String,
required: true
}
});
const emit = defineEmits(['update:content']);
const hitContent = '请输入内容';
const inputBox = ref<HTMLElement | null>(null);
// 更新文本
function updateContent(value: string){
if (inputBox.value && value !== inputBox.value.innerHTML) {
inputBox.value.innerHTML = value;
}
};
// 获取输入框焦点
const requestFocus = () => {
if (inputBox.value) {
inputBox.value.focus();
}
};
defineExpose({
requestFocus
})
onMounted(() => {
updateContent(props.content);
if (inputBox.value) {
inputBox.value.addEventListener("input", () => {
if(inputBox.value != null){
//console.log(inputBox.value.innerHTML);
emit('update:content', inputBox.value.innerHTML);
}
});
}
});
// 当文本框内容发生变化(插入表情)时,获取输入框焦点
watch(() => props.content, (newValue: string) => {
updateContent(newValue);
if (inputBox.value) {
//console.log(inputBox.value.innerHTML);
inputBox.value.focus();
}
});
</script>
<style>
.rich-text-input{
width: 100%;
height: 40vh;
}
.input-box:focus{
border: #0084FF solid 0.2vh;
}
.input-box:empty:before {
content: attr(placeholder);
position: flex;
color: lightgray;
background-color: transparent;
}
.input-box {
font-size: 17px;
width: 96%;
border-radius: 0.5vh;
height: 40vh;
overflow-y: auto;
display: block;
outline: none;
border: #EBEDF0 solid 0.2vh;
padding: 1vh;
margin: 1vh;
}
</style>
组合一体
将富文本框和表情选择框组合一起,相互联系
<template>
<div class="container">
<div class="parent">
<div class="input-box-container">
<RichInput ref="richTextInput" v-model:content="Content" />
<div class="emoji-container" v-if="isEmojiShow">
<Emoji @emojiChick="onEmojiClick" />
</div>
<div class="input-action-part">
<span class="emoji-trigger" ref="emojiTrigger" @click="controlEmojiPanelVisibility">
<!--一个笑脸标志-->
<svg t="1715419910457" class="emoji-icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="14031"
data-spm-anchor-id="a313x.search_index.0.i26.73883a81a68hSX" width="200" height="200">
<path
d="M536.953154 464.455014m-448.014129 0a448.014129 448.014129 0 1 0 896.028258 0 448.014129 448.014129 0 1 0-896.028258 0Z"
fill="#FFE585" p-id="14032"></path>
<path
d="M547.508202 24.443486c-4.200646 0-8.421844 0.078094-12.593718 0.193181 223.698798 6.374953 403.064748 189.699047 403.064748 414.939177 0 229.268148-185.8601 415.132358-415.132358 415.132358-229.255817 0-415.132358-185.86421-415.132358-415.132358 0-36.104184 4.619889-71.143822 13.284236-104.5517-13.958312 43.305292-21.504678 89.483629-21.504679 137.433471 0 247.427106 200.599354 448.014129 448.014129 448.014129 247.431216 0 448.014129-200.587023 448.014129-448.014129 0-247.431216-200.582913-448.014129-448.014129-448.014129z"
fill="#FF9900" opacity=".24" p-id="14033"></path>
<path
d="M437.757071 1024h197.290626a16.440885 16.440885 0 0 0 0-32.881771h-197.290626a16.440885 16.440885 0 0 0 0 32.881771z"
fill="#ffffff" opacity=".29" p-id="14034" data-spm-anchor-id="a313x.search_index.0.i22.73883a81a68hSX"
class=""></path>
<path
d="M708.480912 1024h57.543099a16.440885 16.440885 0 0 0 0-32.881771h-57.543099a16.440885 16.440885 0 0 0 0 32.881771zM359.112096 991.118229h-45.212435a16.440885 16.440885 0 0 0 0 32.881771h45.212435a16.440885 16.440885 0 0 0 0-32.881771z"
fill="#ffffff" opacity=".17" p-id="14035" data-spm-anchor-id="a313x.search_index.0.i24.73883a81a68hSX"
class=""></path>
<path
d="M125.931017 487.061232c0-234.948474 190.459438-425.407911 425.407912-425.407912 229.851799 0 417.092934 182.300648 425.107865 410.187762 0.18085-5.182989 0.300046-10.378309 0.300046-15.60651 0-247.427106-200.599354-448.014129-448.014129-448.014129-247.431216 0-448.014129 200.587023-448.014129 448.014129 0 205.954972 138.983025 379.435085 328.308042 431.819856-164.955514-58.558324-283.095607-215.955141-283.095607-400.993196z"
fill="#FFEFB5" p-id="14036"></path>
<path
d="M244.531455 452.979276c-4.151324 144.252329 45.985157 270.925241 166.768122 304.933213 120.778855 34.007972 274.945038-35.121842 346.688951-160.331515L244.531455 452.979276z"
fill="#C7A17B" p-id="14037"></path>
<path
d="M228.09468 452.506601c-4.841841 168.264242 61.982138 288.35258 178.749417 321.234351 129.956979 36.593301 290.465234-37.189283 365.406899-167.984748a16.449106 16.449106 0 0 0-9.815208-23.999582l-513.448853-144.601698a16.436775 16.436775 0 0 0-20.892255 15.351677z m515.62727 136.903253c-66.66368 116.348036-213.789054 184.836655-327.962783 152.678283-146.019724-41.114544-156.89537-215.544119-154.790937-288.636185a16.453216 16.453216 0 0 1-20.892255 15.351677l513.457073 144.605808a16.444996 16.444996 0 0 1-9.811098-23.999583z"
fill="#6E6E96" p-id="14038"></path>
<path
d="M313.065286 97.954796C381.340173 55.385233 460.182439 32.881771 541.063375 32.881771c115.275268 0 223.649475 44.891838 305.163386 126.405748 81.51802 81.51391 126.409858 189.892227 126.409858 305.167495 0 237.969486-193.607867 431.573243-431.573244 431.573244-237.969486 0-431.573243-193.603757-431.573243-431.573244 0-51.965529 9.132912-102.767865 27.139792-151.005422a16.440885 16.440885 0 0 0-30.80611-11.49629C86.44001 353.877729 76.608361 408.551894 76.608361 464.455014c0 256.107893 208.347121 464.455014 464.455014 464.455015 256.103783 0 464.455014-208.347121 464.455015-464.455015 0-124.058811-48.315652-240.694563-136.044217-328.419017C781.753828 48.311542 665.122187 0 541.063375 0 454.025328 0 369.165697 24.225645 295.666719 70.054613a16.440885 16.440885 0 0 0 17.398567 27.900183z"
fill="#6E6E96" p-id="14039"></path>
<path
d="M451.727714 273.078997m-36.991992 0a36.991992 36.991992 0 1 0 73.983984 0 36.991992 36.991992 0 1 0-73.983984 0Z"
fill="#6E6E96" p-id="14040"></path>
<path
d="M711.016919 341.049728m-36.991993 0a36.991992 36.991992 0 1 0 73.983985 0 36.991992 36.991992 0 1 0-73.983985 0Z"
fill="#6E6E96" p-id="14041"></path>
<path
d="M219.37279 277.008369c9.342533-28.442732 38.393578-63.326181 64.682553-77.646192l0.098646-0.061653c26.420503-14.274799 26.420503-37.579754 0-51.821671l-0.098646-0.086315C257.762257 133.13829 227.190431 101.0128 216.117495 76.039095c-11.052385-25.014807-29.174351-25.014807-40.255509 0-11.035944 25.006587-41.636542 57.115636-68.052935 71.349333l-0.123306 0.098645c-26.342409 14.254248-26.342409 37.542762 0 51.829892l0.123306 0.049322c26.416393 14.258358 55.393453 49.19524 64.662003 77.637972l6.658558 20.123644c9.301431 28.467393 24.32018 28.467393 33.679154 0l6.564024-20.119534z"
fill="#CFD3FF" p-id="14042"></path>
<path
d="M234.991631 282.137925c8.101246-24.648998 34.16827-55.944223 56.930676-68.33654l0.300046-0.164409 0.291826-0.17674-0.542549 0.304157c18.080864-9.765886 28.446842-24.492809 28.438621-40.403476-0.00411-15.902446-10.374199-30.608819-28.450952-40.358264l3.058005 2.129095-1.393365-1.224846a48.891083 48.891083 0 0 0-1.730403-0.974123c-23.235081-12.593718-51.048949-41.702306-60.744962-63.560463-10.970181-24.821627-26.724659-28.533157-35.138282-28.537267s-24.172212 3.699199-35.183495 28.541377c-9.65902 21.882819-37.50577 50.970855-60.818946 63.535802l-1.331712 0.719289-1.183743 0.953571 2.371597-1.561884c-18.023321 9.753555-28.360527 24.455817-28.368747 40.341823-0.00822 15.894226 10.328986 30.617039 28.352307 40.399366l0.92891 0.501447 0.982342 0.37814-1.755064-0.817934c22.750075 12.277231 48.784217 43.539575 56.848472 68.266667l6.679109 20.193517c8.799984 26.938391 22.47469 32.618717 32.400875 32.626938s23.625552-5.647444 32.507741-32.655709l6.551693-20.119534z m-37.793485 9.860421c-2.947029 8.956172-5.478925 11.278447-5.507697 11.298999 1.878371-1.66464 6.666779-1.656419 8.54104 0.00822-0.028772-0.020551-2.548337-2.338716-5.466594-11.278447l-6.67911-20.185297c-10.604371-32.520071-42.446256-70.73691-72.471423-86.939403l-0.908359-0.493226-0.965902-0.37403 1.783836 0.834375c-7.706665-4.180095-11.155141-8.779433-11.155141-11.479849 0-2.696305 3.444366-7.279202 11.1387-11.438746l1.323491-0.715178 1.171413-0.945351-2.396259 1.574215c29.692239-16.001092 62.76308-50.781785 75.291035-79.187525 3.34983-7.562807 6.198214-9.145243 6.226986-9.157573a3.312838 3.312838 0 0 1-2.268843 0c0.028772 0.012331 2.868935 1.594766 6.218765 9.169903 12.585498 28.372858 45.590575 63.13711 75.143067 79.158754l-3.021012-2.112654 1.409806 1.237177c0.094535 0.086315 1.750954 0.978233 1.750954 0.978232 7.718996 4.159544 11.175692 8.738331 11.175692 11.426416 0 2.696305-3.456696 7.283312-11.188023 11.459297l-0.324707 0.172629-0.320598 0.18907 0.497337-0.279495c-29.963514 16.317579-61.780737 54.509756-72.442651 86.951733l-6.555803 20.127754z"
fill="#6E6E96" p-id="14043"></path>
<path
d="M260.791491 594.53119s251.730508-113.355795 386.081313 118.867602c0 0 111.115724-89.820668 111.115724-115.809597L244.531455 452.979276s-29.030494 96.269605 16.260036 141.551914z"
fill="#6E6E96" opacity=".25" p-id="14044"></path>
</svg>
</span>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted, watch } from 'vue';
import Emoji from './Emoji.vue'
import RichInput from './RichInputBox.vue'
const props = defineProps({
inputContent: {
type: String,
required: true
}
});
const emit = defineEmits(['update:inputContent']);
const isEmojiShow = ref(false);
const Content = ref('');
const richTextInput = ref<InstanceType<typeof RichInput> | null>(null);
const emojiTrigger = ref<HTMLElement | null>(null);
// 当点击表情时,将表情加入输入框
function onEmojiClick(emojiUrl: string) {
if (richTextInput.value != null) {
//console.log(emojiUrl);
const inputBox = richTextInput.value;
inputBox?.requestFocus();
const imgTag = `<img src="${emojiUrl}" class="emoji">`;
document.execCommand("insertHTML", false, imgTag);
}
};
// 模拟发布按键指令
function doPost() {
console.log(props.inputContent);
}
// 控制表情面板的显示和隐藏
function controlEmojiPanelVisibility() {
console.log(isEmojiShow)
isEmojiShow.value = !isEmojiShow.value;
}
onMounted(() => {
if (emojiTrigger.value) {
emojiTrigger.value.addEventListener("mousedown", (event) => {
event.preventDefault();
});
}
Content.value = props.inputContent;
});
// 监听content的属性编号
watch(Content, ()=> {
emit('update:inputContent', Content.value);
});
</script>
<style>
.container {
background-color: white;
width: 100%;
min-height: 50vh;
}
.input-box-container {
padding: 5px;
margin-bottom: 20px;
position: relative;
}
.emoji-trigger {
cursor: pointer;
color: #0084FF;
}
.emoji-icon {
width: 2vw;
height: 2vw;
transition: transform 0.5s ease;
}
.emoji-icon:hover {
transform: scale(1.3);
}
.emoji-container {
position: absolute;
top: 20vh;
left: 2vw;
}
.content-post-btn:hover {
opacity: .8;
}
.content-post-btn {
background: #0084FF;
color: white;
border-radius: 4px;
margin-right: 10px;
cursor: pointer;
}
.input-action-part {
display: flex;
justify-content: space-between;
align-items: center;
padding: 5px 20px;
margin-top: 10px;
margin-bottom: 10px;
}
.input-action-part {
display: flex;
justify-content: space-between;
}
.parent {
width: 100%;
height: 40vh;
background: #fff;
}
</style>
资源管理器的面包屑组件
因为element Plus的面包屑组件只能和路由绑定,故而无法使用,然而每次要寻找路径时都需要遍历数结构,时间消耗巨大,十分缓慢,故而我采取以空间换时间,将所有的路径直接存储,然后通过id寻找节点,时间复杂度直接变为O(1).
const defaultProps = {// 树结构
id: 'id',
children: 'children',
label: 'name',
}
function setbread(date: any[], idPath: object[]) {
date.forEach((item: any) => {// data为一个树结构
//console.log(item);
idPath.push({ id: item.id, name: item.name, children: item.children });
//console.log([...idPath]);
//console.log(idPath.length);
paths.value.push({ id: item.id, name: item.name, path: [...idPath] });
//console.log(paths.value);
//console.log(item.children)
if (item.children !== null) {
setbread(item.children, [...idPath]);
}
idPath.pop();
})
}
总结
Vue 3携手TypeScript和Element Plus,为前端开发带来了高效与健康新生态。Vue 3通过Composition API增强代码组织灵活性,性能优化及强化TypeScript支持,提升了应用的开发效率与维护性。TypeScript注入静态类型检查,减少运行时错误,IDE智能提示让开发更流畅。Element Plus,作为成熟的UI组件库,提供丰富预设样式组件,支持按需加载与国际化,无缝融合Vue 3新特性,加速界面搭建。三者结合,不仅极大提升开发速度与代码质量,还保障了项目的可维护性和未来的扩展性,为开发者营造了现代化、高效且愉悦的开发体验。
参考文献、参考博客
vue官方文档:https://cn.vuejs.org/guide/introduction.html
Element Plus组件官方文档:https://element-plus.org/zh-CN/component/overview.html
axios官方文档:https://www.axios-http.cn/docs/req_config