前言
无。
环境搭建
首先安装Vue3.0
找到官方文档下面的安装方法,使用create命令创建一个新的Vue项目:
安装了3.11.1版本的vue,先安装vite构建工具:
代码完成后再通过build命令可以将vue项目构建为发布的前端代码:
然后就会在项目根目录下生成一个新的dist目录,里面包含了js、css、html和其他静态文件等前端文件。
ElementUI
ElementUI是一个好用的组件库,方便我这种前端菜鸟抄来构建一个看得过去的前端界面,找到官方文档下面的安装方法:
| 1
 | npm install element-plus
 | 
通过ElementUI标签,就可以方便地在前端页面引入各种组件,比如按钮,修改一下App.vue:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 
 | <script setup>
 </script>
 
 <template>
 <div id="app">
 <el-button>{{ message }}</el-button>
 </div>
 </template>
 
 <script>
 export default {
 data() {
 return {
 message: "Hello ElementUI!"
 }
 }
 }
 </script>
 
 | 
然后再修改一下main.js:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 
 | import './assets/main.css'
 import { createApp } from 'vue'
 import 'element-plus/dist/index.css'
 import ElementPlus from 'element-plus'
 import App from './App.vue'
 
 const app = createApp(App);
 app.use(ElementPlus);
 app.mount("#app");
 
 | 
就能看到按钮了。
其他依赖
| 1
 | npm install axios moment qs
 | 
Vue基础语法
模板语法
两个大括号可以将数据当作纯文本填入模板:
| 1
 | <el-button>{{ message }}</el-button>
 | 
需要将数据当作HTML,即动态渲染页面时可以使用v-html:
| 1
 | <span v-html="rawHtml"></span>
 | 
不怎么用得到,动态渲染还是比较危险的行为,什么表达式注入、模板注入、XSS都是动态渲染导致的。
双大括号填充数据不能用在HTML标签的属性里面,需要使用v-bind,或者缩写:
| 12
 
 | <div v-bind:id="dynamicId"></div><div :id="dynamicId"></div>
 
 | 
也可以通过对象一次性绑定多个属性:
| 12
 3
 4
 5
 
 | const objectOfAttrs = {id: 'container',
 class: 'wrapper',
 style: 'background-color:green'
 }
 
 | 
除了单个变量,还可以填充一个表达式,包括字符串拼接和函数调用:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | {{ number + 1 }}
 {{ ok ? 'YES' : 'NO' }}
 
 {{ message.split('').reverse().join('') }}
 
 <div :id="`list-${id}`"></div>
 
 <time :title="toTitleDate(date)" :datetime="date">
 {{ formatDate(date) }}
 </time>
 
 | 
除此之外,其他v-语法也各有用处,比如v-if控制标签存在与否,v-for遍历数组并生成多个HTML代码块,具体可以看官方指令集文档。
计算属性
一种特殊的属性,在每次访问它时会先经过代码块运算得到结果,比如:
| 1
 | <el-progress type="dashboard" :percentage="gotScorePercentage" :format="gotScore" :color="colors"></el-progress>
 | 
gotScorePercentage就是一个计算属性,在export default的computed中定义,访问时会先进行计算:
| 12
 3
 4
 5
 
 | computed: {gotScorePercentage: function() {
 return 100 * (this.myScore / this.fullScore);
 }
 },
 
 | 
条件渲染
先设置一个数据:
| 12
 3
 4
 5
 
 | data() {return {
 ok: 1
 }
 },
 
 | 
条件渲染即v-if指令:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | <div v-if="ok === 3">A
 </div>
 <div v-else-if="ok === 2">
 B
 </div>
 <div v-else-if="ok === 1">
 C
 </div>
 <div v-else>
 Not A/B/C
 </div>
 
 | 
和v-show指令:
| 1
 | <h1 v-show="ok === 1">Hello!</h1>
 | 
区别在于v-show的实现方式是切换标签的display属性,所以无论条件如何,标签都会被渲染。
而v-if是惰性的,只有条件符合时才会被渲染。
列表渲染
先设置一串数据:
| 12
 3
 4
 5
 6
 7
 8
 
 | data() {return {
 items: [
 {msg: "Twings"},
 {msg: "Aluvion"}
 ]
 }
 }
 
 | 
v-for指令可以遍历数组,分割成index和数据项并生成多个HTML块:
| 12
 3
 
 | <li v-for="(item, index) in items">{{ index }}: {{ item.msg }}
 </li>
 
 | 
根据文档,使用v-for时最好给它提供一个唯一的key便于渲染。
事件处理
JavaScript常用的on事件,在Vue里可以用v-on指令或者@click的简写方式监听事件,先定义一个method:
| 12
 3
 4
 5
 
 | methods: {clickButton(event) {
 console.log(event)
 }
 },
 
 | 
然后再将它配置到click事件里:
| 1
 | <el-button @click="clickButton">{{ message }}</el-button>
 | 
这样点击按钮就能触发事件了。
表单输入绑定
Web页面中常用的表单传值,为了JavaScript异步传值需要将表单输入绑定到某个变量上面:
| 12
 
 | <input v-model="message" placeholder="edit me" /><el-button @click="clickButton">{{ message }}</el-button>
 
 | 
将输入绑定到了message变量上面,这样按钮中的消息也会跟着输入框改变。
此外,textarea、checkbox、radio和select等输入类型也是类似的绑定方式,使用multiple属性可以将多个值绑定到同一个select变量上。
生命周期钩子
如created等方法,方便初始化环境。
ElementUI Plus
发现一个非常奇怪的问题,我最上层的app div只能显示非常小的一块地区,标签栏都被压缩了。
后面发现问题出在默认引入的main.css里面,删掉body和app定义的display样式就恢复正常了:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | @media (min-width: 1024px) {body {
 
 place-items: center;
 }
 
 #app {
 
 grid-template-columns: 1fr 1fr;
 padding: 0 2rem;
 }
 }
 
 
 | 
抽屉式导航栏
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | <el-switch v-model="isCollapse" size="large" style="margin-left: 24px; margin-bottom: 20px;"/><el-menu defaultActive="Reading" class="el-menu-vertical-demo" @select="handleTag" :collapse="!isCollapse">
 <el-menu-item index="Reading">
 <el-icon><Reading /></el-icon>
 <span style="width: 120px;">查看</span>
 </el-menu-item>
 <el-menu-item index="Plus">
 <el-icon><Plus /></el-icon>
 <span>新增</span>
 </el-menu-item>
 <el-menu-item index="Edit">
 <el-icon><Edit /></el-icon>
 <span>修改</span>
 </el-menu-item>
 <el-menu-item index="Delete">
 <el-icon><Delete /></el-icon>
 <span>删除</span>
 </el-menu-item>
 <el-menu-item index="Search">
 <el-icon><Search /></el-icon>
 <el-input v-if="isCollapse" v-model="search" style="width: 120px;" placeholder="输入搜索" clearable/>
 </el-menu-item>
 </el-menu>
 
 | 
环形进度条
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 
 | <div style="text-align: center;"><el-progress type="dashboard" :percentage="lunchTime" :color="colors"></el-progress>
 <br/>
 <span>上午工作还有: {{ leftTimeLunch() }}</span>
 </div>
 <div style="text-align: center; margin-top: 100px;">
 <el-progress type="dashboard" :percentage="AfterNoonTime" :color="colors"></el-progress>
 <br/>
 <span>下午工作还有: {{ leftAfterNoon() }}</span>
 </div>
 <div style="text-align: center; margin-top: 100px;">
 <el-progress type="dashboard" :percentage="workFinished" :color="colors"></el-progress>
 <br/>
 <span>距离下班还有: {{ leftTime() }}</span>
 </div>
 
 | 
JS代码:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 
 | data() {nowDate: dayjs(),
 restDate: dayjs().endOf("week").subtract(20, 'day').subtract(20, 'hour').subtract(20, 'minute'),
 morningDate: dayjs().startOf("day").add(20, 'hour').add(20, 'minute'),
 AfterNoonDate: dayjs().startOf("day").add(20, 'hour').add(20, 'minute'),
 finishDate: dayjs().startOf('day').add(20, 'hour').add(20, 'minute'),
 lunchDate: dayjs().startOf('day').add(20, 'hour').add(20, 'minute'),
 },
 methods: {
 leftTime() {
 if (this.nowDate > this.finishDate) {
 return 0;
 }
 return this.nowDate < this.morningDate ? this.morningDate.to(this.finishDate, true) : this.nowDate.to(this.finishDate, true)
 },
 leftTimeLunch() {
 if (this.nowDate > this.lunchDate) {
 return 0;
 }
 return this.nowDate < this.morningDate ? this.morningDate.to(this.lunchDate, true): this.nowDate.to(this.lunchDate, true)
 },
 leftAfterNoon() {
 if (this.nowDate > this.finishDate) {
 return 0;
 }
 return this.nowDate < this.AfterNoonDate ? this.AfterNoonDate.to(this.finishDate, true) : this.nowDate.to(this.finishDate, true)
 },
 },
 computed: {
 workFinished: function() {
 var diff = this.finishDate.diff(this.nowDate)
 if (diff.valueOf() < 0) {
 return 0;
 }
 var percentage = Math.ceil(100 * diff.valueOf() / (3600000 * 8)) > 100 ? 100 : Math.ceil(100 * diff.valueOf() / (3600000 * 8))
 return 100 - percentage;
 },
 lunchTime: function() {
 var diff = this.lunchDate.diff(this.nowDate)
 if (diff.valueOf() < 0) {
 return 0;
 }
 var percentage = Math.ceil(100 * diff.valueOf() / (3600000 * 3 + 60000 * 10)) > 100 ? 100 : Math.ceil(100 * diff.valueOf() / (3600000 * 3 + 60000 * 10))
 return 100 - percentage
 },
 AfterNoonTime: function() {
 var diff = this.finishDate.diff(this.nowDate)
 if (diff.valueOf() < 0) {
 return 0;
 }
 var percentage = Math.ceil(100 * diff.valueOf() / (3600000 * 2.5 + 60000 * 10)) > 100 ? 100 : Math.ceil(100 * diff.valueOf() / (3600000 * 2.5 + 60000 * 10))
 return 100 - percentage
 },
 },
 
 | 
标签式导航
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 
 | <el-menu style="display:flex;" :default-active="defaultactiveIndex" class="el-menu-demo" mode="horizontal" @select="handleSelect" textColor="#A0CFFF" activeTextColor="#409EFF"><el-menu-item index="index" style="margin-left: 5%">主页</el-menu-item>
 <el-menu-item index="note" v-bind:disabled="!isLogin" @click="note_info();">备忘录</el-menu-item>
 <el-menu-item index="todo" v-bind:disabled="!isLogin" @click="todo_info();">待办</el-menu-item>
 <el-menu-item index="file" v-bind:disabled="!isLogin" @click="file_info();">附件</el-menu-item>
 
 <el-dropdown style="align-self:center; position:fixed; right: 22%">
 <span class="el-dropdown-link">
 <el-button type="success">{{ loginForm.name != '' ? loginForm.name : '未登录'}}</el-button>
 </span>
 
 <template #dropdown>
 <el-dropdown-menu>
 <div v-if="isLogin">
 <el-dropdown-item @click="logout()">
 退出
 </el-dropdown-item>
 </div>
 <div v-if="!isLogin">
 <el-dropdown-item @click="dialogFormVisible = true">
 登录
 </el-dropdown-item>
 </div>
 </el-dropdown-menu>
 </template>
 </el-dropdown>
 </el-menu>
 
 | 
日历
| 12
 3
 
 | <div v-if="activeIndex == 'index'" style="width: 95%; margin: 0 auto;"><el-calendar v-model="dateValue" />
 </div>
 
 | 
倒计时
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
 | <el-countdown format="DD [天] HH:mm:ss" :value="restDate"><template #title>
 <div style="display: inline-flex; align-items: center">
 <el-icon style="margin-right: 4px" :size="12">
 <Calendar />
 </el-icon>
 距离周末还有
 </div>
 </template>
 </el-countdown>
 <div class="countdown-footer">{{ nowDate.format('YYYY-MM-DD') }}</div>
 
 | 
dialog隐藏表单
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
 | <el-dialog title="登录" v-model="dialogFormVisible"><el-form v-model="loginForm">
 <el-form-item label="Username:" :label-width="formLabelWidth" required>
 <el-input placeholder="请输入用户" v-model="loginForm.name" maxlength="20" clearable></el-input>
 </el-form-item>
 <el-form-item label="Password:" :label-width="formLabelWidth" required>
 <el-input placeholder="请输入密码" v-model="loginForm.pwd" maxlength="20" clearable show-password></el-input>
 </el-form-item>
 </el-form>
 <div slot="footer" class="dialog-footer">
 <el-button @click="logout()">取 消</el-button>
 <el-button type="primary" @click="login()" v-bind:disabled="loginForm.name == '' || loginForm.pwd == ''">登 录</el-button>
 </div>
 </el-dialog>
 
 | 
Vue3.0官方文档
ElementUI Plus官方文档