【Vue #2】脚手架 指令
一、脚手架
脚手架:一个保证各项工作顺利开展的平台,方便我们 拿来就用,零配置
1. Vue 代码开发方式
相比直接 script 引入 vue 源码,有没有更好的方式编写vue代码呢?
① 传统开发模式:
- 基于html文件开发Vue,类似jQuery的使用
<script src="vue.js"></script>
- 优点:简单、上手快
- 缺点:功能单一、开发体验差
② 工厂化开发模式:
在 构建工具(Vite/Webpack )环境下开发Vue,这是最推荐的、也是企业采用的方式
- 优点:功能全面,开发体验好
- 缺点:目录结构复杂,理解难度提升
2. 准备工程化环境
① 安装工具 Nodejs
注意: 安装18.3或更高版本,Nodejs 官网:https://nodejs.org/en/
安装好之后,可以打开命令行,输入下面指令,进行测试如下:
> node -v
v22.13.1> npm -v
10.9.2
npm 换源 – 避免下载过慢,当前下载好的可以不用管
// 查看 npm 源
npm config get registry
// 默认是指向 https://registry.npmjs.org/,也就是官⽅源
// 为了提⾼npm下载速度, 可以给npm换源
// 国内源有很多,我这⾥⽤淘宝源吧。毕竟是⼤公司,会⽐较稳定
npm config set registry https://registry.npmmirror.com
// 再⼀次查看 npm 源
npm config get registry
② 安装 yarn 和 pnpm
yarn和 pnpm、还有 npm
三者的功能类似,都是包管理工具, 用来下载或删除模块包,性能上 yarn
和 pnpm
优于 npm
命令 | 装包 | 删包 |
---|---|---|
npm | npm i 包名 | npm un 包名 |
yarn | yarn add 包名 | yarn remove 包名 |
pnpm | pnpm i 包名 | pnpm un 包名 |
在命令行上进行安装,如下:
# windows系统
npm install yarn -g
npm install pnpm -g
___________________________________
# mac系统
sudo npm install yarn -g
sudo npm install pnpm -g# 检测是否安装成功, 如下
> yarn -v
1.22.22> pnpm -v
10.6.4
3. 创建 Vue 工厂化项目
创建步骤如下:
- 选定⼀个存放位置,比如选择桌面,根据自己情况,选择D盘或E盘等
- 执行命令
npm create vue@latest
,会安装并执行create-vue
, 它是Vue官方的项目脚手架工具 - 进⼊项目根目录:
cd
项目名称 - 安装 vue 等模块依赖:
npm i
- 启动项目:
npm run dev
,会开启⼀个本地服务器,然后在浏览器网址栏输入:http://localhost:5173
C:\Users\>cd desktop # 先切换到桌面C:\Users\Desktop>npm create vue@latest
Need to install the following packages:
create-vue@3.15.1
Ok to proceed? (y) y> npx
> create-vueT Vue.js - The Progressive JavaScript Framework
|
o 请输入项目名称:
| vue-engineering-way
|
o 请选择要包含的功能: (↑/↓ 切换,空格选择,a 全选,回车确认)
| Prettier(代码格式化)
安装选项如下:
启动服务如下:
\Desktop\vue-engineering-way> npm run dev> vue-engineering-way@0.0.0 dev
> viteVITE v6.2.2 ready in 1172 ms➜ Local: http://localhost:5173/➜ Network: use --host to expose➜ Vue DevTools: Open http://localhost:5173/__devtools__/ as a separate window➜ Vue DevTools: Press Alt(⌥)+Shift(⇧)+D in App to toggle the Vue DevTools➜ press h + enter to show help
最后呈现的界面效果如下,说明项目创建并启动成功了
补充 – 后继我们要打开这个界面,就需要先运行,然后输入 localhost:端口号(看自己设定的端口号是多少,我这里是 5173)
4. 认识工程化项目下目录和文件
用 vscode 打开查看,如下:
我们今后Vue代码写哪个目录下?
- 答: src 目录,src下的所有代码会被
vite
打包成css/js/img
, 然后交给index.html
,最终通过浏览器呈现在用户眼前
分析上面三个入口文件关系:
1、main.js、App.vue、index.html三个文件的作用?
- main.js - 项目打包的入口 - 创建应用
- App.vue - Vue代码的入口(根组件)
- index.html- 项目的入口网页
2、mian.js、App.vue、index.html 三者的关系是什么?
- App.vue(vue入口)=>main.js(项目打包入口)index.html(浏览器入口)
- main.js 是 Vue 代码通向网页代码的桥梁,非常关键
5. Vue 单文件
思考:代码写一起,会不会出现class类名、js变量名 重名冲突?Vue中如何避免呢?
vue单文件介绍
- vue推荐采用
.vue
的文件来开发项目 - 一个 vue 文件通常有3部分组成,
script(JS)+template(HTML)+ style(CSS)
- 一个 vue 文件是 独立的模块,作用域互不影响
- style 配合
scoped
属性,保证样式只针对当前 template 内的标签生效
作用:提供了独立的作用域,不用担心 JS 变量名、CSS 选择器名冲突
注意:.vue 文件浏览器无法识别,需要借助 vite打包成 js、css 等,最终交给 index.html
,通过浏览器呈现效果
6. 清理目录结构
- 删除assets文件夹
- 删除components文件夹
- 清除App.vue的内容
- 清除main.js的内容
补充内容
App.vue
<script setup></script>
<template>App根组件
</template>
<style></style>
main.js
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
7. set up简写 + 插值 + 响应式
实例1:完整写法
<script>export default{setup(){const msg = 'Hello World' // 声明数据return {msg} // 返回数据}}</script><template><h1>{{msg}}</h1>
</template>
实例2:简写
<script setup>const msg = 'Hello World'
</script><template><h1>{{msg}}</h1>
</template>
实例3:练习
<script setup>import { reactive, ref } from 'vue' // 通过导入的方法模块化// 字符串const msg = ref('Hello World') // 对象const obj = reactive({name: 'vue3',age: 18})// 函数function fn(){return 100}
</script><template><h1>{{msg}}</h1><p>{{obj.name}}, 今年{{ obj.age }}岁</p><p>函数返回值:{{ fn() }}</p>
</template>
二、指令
1. 基本概念
指令(Directives)是Vue提供的带有v-前缀的特殊标签属性,用来增强标签的能能力
- 作用:提高标签数据渲染的能力
vue3 中的指令按照不同的用途可以分为如下 6 大类:
- 内容渲染指令(v-html、v-text):作用类似于插值,把表达式的结果渲染到双标签
- 属性绑定指令(v-bind):把表达式的值与标签的属性 动态绑定
- 事件绑定指令(v-on):用来与标签进行事件绑定,处理用户交互
- 条件渲染指令(v-show、v-if、v-else、v-else-if):根据表达式的 true 或 false,决定标签是否展示
- 列表渲染指令(v-for):基于数组循环生成一套列表
- 双向绑定指令(v-model):数据 <–> 视图(数据与视图相互影响,双向奔赴)
2. 内容渲染指令
内容渲染指令用来辅助开发者渲染 DOM 元素的文本内容。常用的内容渲染指令有如下2个:
v-text
(类似innerText)- 使用语法:
<p v-text="表达式"></p>
,意思是将 表达式的 值渲染到 p标签中 - 类似
innerText
,使用该语法,会覆盖p标签原有内容
- 使用语法:
v-html
(类似innerHTML)- 使用语法:
<p v-html-"表达式"></p>
,意思是将 表达式的 值渲染到p标签中。 - 类似
innerHTML
,使用该语法,会覆盖p标签原有内容,并且能够将HTML标签的样式呈现出来。
- 使用语法:
代码如下:
<script setup>import { reactive, ref } from 'vue' // 通过导入的方法模块化const str = ref('<span style="color: red">Hello Vue</span>')
</script><template><p v-text="str"></p><!-- 上下两个达到的效果是一样的 --><p>{{str}}}</p> <p v-html="str"></p>
</template>
效果如下:
3. 属性绑定指令
作用:把表达式的结果 与标签的属性动态绑定 3
语法:
v-bind: 属性名="表达式" (可简写成 ::属性名="表达式")
基本用法
① 绑定单个属性
使用 v-bind
可以绑定单个属性,例如绑定图片的 src
属性:
<img v-bind:src="imgSrc" alt="">
<!-- 或者使用缩写 -->
<img :src="imgSrc" alt="">
② 绑定多个属性
如果需要绑定多个属性,可以使用对象语法,将多个属性和对应的值放在一个对象中,然后通过 v-bind
绑定这个对象
<img v-bind="{ src: imgSrc, title: imgTitle }" alt="">
③ 绑定 Class
通过 v-bind:class 可以动态绑定元素的 class 属性。例如,根据 isActive 的值动态切换 class:
<div v-bind:class="{ active: isActive }" class="test"></div>
④ 绑定 style
通过 v-bind:style
可以动态绑定元素的 style 属性。需要注意的是,CSS 样式名中的 - 需要转换为驼峰命名法,例如 font-size 需要转换为 fontSize
:
<div v-bind:style="{ background: bground, fontSize: fSize + 'px' }">
hello-vue
</div>
⑤ 传递多个 Props
在父组件向子组件传递多个参数时,可以使用 v-bind 的对象语法,将所有的 props 集中在一个对象中传递:
<child-component v-bind="props"></child-component>
代码样例如下:
<script setup>import { reactive, ref } from 'vue' // 通过导入的方法模块化const url = ref('https://www.baidu.com') const msg = ref('Hello Vue 3')const imgsrc = ref('https://haowallpaper.com/link/common/file/previewFileImg/16677062396530048')
</script><template><p><a v-bind:href="url">百度一下</a></p><!-- 简写 --><p><a :href="url">百度一下</a></p><div v-bind:title="msg">{{ msg }}</div><!-- 绑定多个元素 --><img v-bind="{ src: imgsrc, title: msg }" alt=""><div v-bind:style="{background: 'pink', color: 'red'}">I miss you</div></template>
4. 事件绑定指令
使用Vue时,如需为DOM注册事件,及其的简单,语法如下:
<button v-on:事件名="内联语句">按钮</button>
<button v-on:事件名="处理函数">按钮</button>
<button v-on:事件名="处理函数(实参)">按钮</button>
- 注意:
v-on
可以简写为 @
内联语句指的是直接在HTML标签上使用JavaScript代码的一种方式。在Vue中,可以通过v-on指令将内联语句与DOM事件关联起来,从而在触发事件时执行相应的 JavaScript
代码。
代码示例如下:
<script setup>import { reactive, ref } from 'vue' // 通过导入的方法模块化const cnt = ref(0)// 无参函数const increase = () =>{cnt.value++}// 有参函数const add = (n) =>{cnt.value += n}function increment() {cnt.value++}
</script><template><p>{{ cnt }}</p><!-- 内联/行内代码 --><button @click="cnt++">+1</button><!-- 处理函数 --><button @click="increase">+1</button><!-- 处理函数(实参) --><button @click="add(5)">+5</button><br><button @click="increment">Count is:{{cnt}}</button>
</template>
5. 条件渲染指令
v-show
- 作用:控制元素css 的 display属性来控制元素 显示或隐藏 的
- 语法:v-show=“布尔表达式”【表达式值为 true 显示,false 隐藏】
- 原理:切换
display:none
控制显示隐藏 - 场景:频繁切换显示隐藏的场景
v-if
- 作用:通过创建和插入元素 或移除 DOM 元素 控制元素显示隐藏(条件渲染)
- 语法:
v-if="布尔表达式"
【表达式值 true显示,false 隐藏】 - 原理:基于条件判断,创建 或 移除元素。
- 场景:要么显示,要么隐藏,不频繁切换的场景
v-else 和 v-else-if
- 作用:辅助v-if进行判断渲染
- 语法:
v-else
v-else-if="表达式"
- 需要紧接着v-if使用
代码示例1
<script setup>
import { ref } from 'vue'const awesome = ref(true)function toggle() {awesome.value = !awesome.value
}
</script><template><button @click="toggle">Toggle</button><h1 v-if="awesome">Vue is awesome!</h1><h1 v-else>Oh no 😢</h1>
</template>
- 通过按钮点击就可以切换 文字 显示
代码示例2
<script setup>import { ref } from 'vue'const vis = ref(true) // 是否可见const login = ref(true) // 是否登录const mark = ref(80)
</script><template><!-- v-show --><div class="red" v-show="vis"></div><!-- v-if --><div class="green" v-if="vis"></div><hr><!-- 双分⽀的条件渲染 --> <div v-if="login">xxx, 欢迎回来</div><div v-else>你好, 请登录</div><hr><!--多分⽀的条件渲染: 1. 90及其以上优秀 2. 70到90之间良好 3. 其他的差 --><div v-if="mark >= 90">优秀</div><div v-else-if="mark >= 70">良好</div><div v-else>差</div></template><style scoped>.red, .green{width: 200px;height: 200px;}.red{background-color: red;}.green{background-color: green;}
</style>
6. 列表渲染指令
v-for指令需要使用(item,index)in 目标结构 形式的特殊语法,其中:
item
:数组中的每一项index
:每一项的索引,不需要可以省略- 目标结构:被遍历的 数组/对象/数字
<script setup>import { ref } from 'vue'const nums = ref([11, 22, 33, 44])const goodsList = ref([{ id: 1, name: '篮球', price: 100 },{ id: 2, name: '足球', price: 200 },{ id: 3, name: '排球', price: 300 }])const obj = {id: 10001,name: 'bit',age: 18}
</script><template><div><!-- 遍历数字数组 --><ul><li v-for="(item, index) in nums">{{ item }} =>{{ index }}</li></ul><div class="goods-list"><div class="goods-item" v-for="item in goodsList"><!-- 遍历对象数组 --><p>id = {{ item.id }}</p><p>name = {{ item.name }}</p><p>price = {{ item.price }}</p></div><ul><!-- 遍历对象 --> <li v-for="(value, key, index) in obj">{{ value }} => {{ key }} => {{ index }}</li></ul><!-- 遍历数字 --><ul><li v-for="(item, index) in 5">{{ item }} => {{ index }}</li></ul></div></div>
</template><style lang="scss"></style>
v-for 中的 key
语法 :key="唯一值"
- 作用:给列表项添加的唯一标识,便于Vue进行列表项的正确排序复用,因为Vue 的默认行为会尝试原地修改元
素(就地复用)
代码如下:
<script setup>import { ref } from 'vue'const bookList = ref([{ id: 1, name: '《红楼梦》', author: '曹雪芹' },{ id: 2, name: '《西游记》', author: '吴承恩' },{ id: 3, name: '《三国演义》', author: '罗贯中' },{ id: 4, name: '《水浒传》', author: '施耐庵' }])// 删除function onDel(i){// i: 当前点击下标// 删除前先确认if(window.confirm('确定删除吗?')){// 调用 splice方法删除bookList.value.splice(i, 1)}}
</script><template><h3>书架管理</h3><!-- 无 key --><ul><li v-for="(item, index) in bookList"><span>{{ item.name }}</span><span>{{ item.author }}</span><button @click="onDel(index)">删除</button></li></ul><!-- 有 key 且为 id --><ul><li v-for="(item, index) in bookList" :key="item.id"><span>{{ item.name }}</span><span>{{ item.author }}</span><button @click="onDel(index)">删除</button></li></ul>
</template><style>#app {width: 400px;margin: 100px auto;}ul {list-style: none;}ul li{display: flex;justify-content: space-around;padding: 10px 0;border-bottom: 1px solid #ccc;}
</style>
删除时结果如下:
- 右边闪烁越少,说明 vue 复用性更好,性能也更快
- 因此可以知道:最大限度的复用DOM、从而提⾼DOM的更新性能
注意:
- key 的类型只能是 数字 或 字符串
- key的值必须 唯一,不能重复
- 推荐用 id 作为 key(因为id唯一),不推荐用 index 作为 key( 会变化)
7. 双向绑定指令
所谓双向绑定就是:
- 数据改变 -> 视图变化
- 视图改变 -> 数据变化
作用:在 表单元素(input、select、radio、checkbox)上,实现数据双向绑定。从而可以快速 获取 或设置 表单元素的值
我们可以同时使用 v-bind
和 v-on
来在表单的输入元素上创建双向绑定:
<input :value="text" @input="onInput">
试着在文本框里输入——你会看到 <p>
里的文本也随着你的输入更新了【实时更新】
代码如下:
<script setup>
import { ref } from 'vue'const text = ref('')function onInput(e) {text.value = e.target.value
}
</script><template><input :value="text" @input="onInput" placeholder="Type here"><p>{{ text }}</p>
</template>
为了简化双向绑定,Vue 提供了一个 v-model
指令,它实际上是上述操作的语法糖:
<input v-model="text">
-
v-model
会将被绑定的值与<input>
的值自动同步,这样我们就不必再使用事件处理函数了。 -
v-model
不仅支持文本输入框,也支持诸如多选框、单选框、下拉框之类的输入类型
<script setup>
import { ref } from 'vue'const text = ref('')
</script><template><input v-model="text" placeholder="Type here"><p>{{ text }}</p>
</template>
案例:实现登录界面,需求如下:
- 点击登录按钮获取表单中的内容
- 点击重置按钮清空表单中的内容
<script setup>
import { reactive } from 'vue'// 表单对象
const loginForm = reactive({username: '',password: ''
})// 登录方法
const handleLogin = () => {// 获取表单数据(reactive对象会自动解包,直接使用即可)console.log('提交的表单数据:', {username: loginForm.username,password: loginForm.password})// 这里可以添加实际的登录逻辑,比如调用API
}// 重置方法
const handleReset = () => {// 重置表单字段loginForm.username = ''loginForm.password = ''
}
</script><template><div>账号: <input v-model="loginForm.username" type="text" /> <br/><br/>密码: <input v-model="loginForm.password" type="password" /> <br/><br/><!-- 添加点击事件处理 --><button type="button" @click="handleLogin">登录</button><button type="button" @click="handleReset">重置</button></div>
</template>
三、案例学习
1. 学习之旅
效果如下:
- 需求:默认展示数组中的第⼀张图片,点击上一页下一页来回切换数组中的图片
实现思路
- 数组存储图片路径 [‘url1,url2’url3’
- 准备下标index 去数组中取图片地址
- 通过v-bind给src绑定当前的图片地址
- 点击上一页下一页只需要修改下标的值即可
- 当展示第一张的时候,上一页按钮应该隐藏。展示最后一张的时候,下一页按钮应该隐藏
代码如下:
<script setup>import { ref } from 'vue'// 图片列表const imglist = ['https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-00.gif','https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-01.gif','https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-02.gif','https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-03.gif','https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-04.png','https://cxk-1305128831.cos.ap-beijing.myqcloud.com/11-05.png']const i = ref(0)
</script><template><div><button v-if="i >= 1" @click="i--">上一页</button><img :src="imglist[i]" alt="" /> <button v-if="i < imglist.length - 1" @click="i++">下一页</button></div>
</template><style scoped>#app {display: flex;width: 500px;height: 240px;}img {width: 240px;height: 240px;}#app div {flex: 1;display: flex;justify-content: center;align-items: center;}</style>
2. 可折叠面板
效果如下:
实现思路
- 搭建了HTML结构+CSS样式
- 准备一个响应式的布尔数据
- 通过 v-show 绑定布尔值控制盒子的显示或隐藏
- 给按钮绑定点击事件,每点击的时候让布尔值取反
- 布尔值控制按钮的名称
代码如下:
<script setup>import { ref } from 'vue'const visible = ref(true)
</script><template><!-- 面板区域 --><h3>可折叠面板</h3><div class="panel"><!-- 标题区域 --><div class="title"><h4>自由与爱情</h4><span class="btn" @click="visible = !visible"> 收起 </span></div><!-- 主体内容区域 --><div class="container" v-show="visible"><p>生命诚可贵,</p><p>爱情价更高。</p><p>若为自由故,</p><p>两者皆可抛。</p></div></div>
</template><style lang="scss"> body{background-color: #ccc;}#app{width: 400px;margin: 20px auto;padding: 1em 2em 2em;border: 4px solid green;border-radius: 1em;box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.5);background-color: #fff;}#app h3 {text-align: center;}.panel{.title {display: flex;justify-content: space-between;align-items: center;padding: 0 1em;border: 1px solid #ccc;}.title h4 {line-height: 2;margin: 0;}.container {border: 1px solid #ccc;padding: 0 1em;}.btn {/* ⿏标改成⼿的形状 */ cursor: pointer;}}
</style>
如果上面指明了 lang=“scss” 之后,项目运行失败
- 就需要安装 sass 模块,执行
npm i -D sass
- 然后安装完毕之后,再重新执行项目:npm run dev
3. 书架管理
效果如下:
需求:
- 根据左侧数据渲染出右侧列表(v-for)
- 点击删除按钮时应该把当前行从列表中删除(获取当前行的index,利用splice删除)
代码如下:
<script setup>import { ref } from 'vue'const bookList = ref([{ id: 1, name: '《红楼梦》', author: '曹雪芹' },{ id: 2, name: '《西游记》', author: '吴承恩' },{ id: 3, name: '《三国演义》', author: '罗贯中' },{ id: 4, name: '《水浒传》', author: '施耐庵' }])// 删除function onDel(i){// i: 当前点击下标// 删除前先确认if(window.confirm('确定删除吗?')){// 调用 splice方法删除bookList.value.splice(i, 1)}}
</script><template><h3>书架管理</h3><ul><li v-for="(item, index) in bookList"><span>{{ item.name }}</span><span>{{ item.author }}</span><button @click="onDel(index)">删除</button></li></ul>
</template><style>#app {width: 400px;margin: 100px auto;}ul {list-style: none;}ul li{display: flex;justify-content: space-around;padding: 10px 0;border-bottom: 1px solid #ccc;}
</style>
4. 个人记事本
在 src 目录下新建一个 styles目录,然后创建 index.css 文件,如下:
/** @format */html,
body {margin: 0;padding: 0;
}body {background: #fff;
}button {margin: 0;padding: 0;border: 0;background: none;font-size: 100%;vertical-align: baseline;font-family: inherit;font-weight: inherit;color: inherit;-webkit-appearance: none;appearance: none;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;
}body {font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;line-height: 1.4em;background: #f5f5f5;color: #4d4d4d;min-width: 230px;max-width: 550px;margin: 0 auto;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;font-weight: 300;
}:focus {outline: 0;
}.hidden {display: none;
}#app {background: #fff;margin: 180px 0 40px 0;padding: 15px;position: relative;box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2), 0 25px 50px 0 rgba(0, 0, 0, 0.1);
}#app .header input {border: 2px solid rgba(175, 47, 47, 0.8);border-radius: 10px;
}#app .add {position: absolute;right: 15px;top: 15px;height: 68px;width: 140px;text-align: center;background-color: rgba(175, 47, 47, 0.8);color: #fff;cursor: pointer;font-size: 18px;border-radius: 0 10px 10px 0;
}#app input::-webkit-input-placeholder {font-style: italic;font-weight: 300;color: #e6e6e6;
}#app input::-moz-placeholder {font-style: italic;font-weight: 300;color: #e6e6e6;
}#app input::input-placeholder {font-style: italic;font-weight: 300;color: gray;
}#app h1 {position: absolute;top: -120px;width: 100%;left: 50%;transform: translateX(-50%);font-size: 60px;font-weight: 100;text-align: center;color: rgba(175, 47, 47, 0.8);-webkit-text-rendering: optimizeLegibility;-moz-text-rendering: optimizeLegibility;text-rendering: optimizeLegibility;
}.new-todo,
.edit {position: relative;margin: 0;width: 100%;font-size: 24px;font-family: inherit;font-weight: inherit;line-height: 1.4em;border: 0;color: inherit;padding: 6px;box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);box-sizing: border-box;-webkit-font-smoothing: antialiased;-moz-osx-font-smoothing: grayscale;
}.new-todo {padding: 16px;border: none;background: rgba(0, 0, 0, 0.003);box-shadow: inset 0 -2px 1px rgba(0, 0, 0, 0.03);
}.main {position: relative;z-index: 2;
}.todo-list {margin: 0;padding: 0;list-style: none;overflow: hidden;
}.todo-list li {position: relative;font-size: 24px;height: 60px;box-sizing: border-box;border-bottom: 1px solid #e6e6e6;
}.todo-list li:last-child {border-bottom: none;
}.todo-list .view .index {position: absolute;color: gray;left: 10px;top: 20px;font-size: 22px;
}.todo-list li .toggle {text-align: center;width: 40px;/* auto, since non-WebKit browsers doesn't support input styling */height: auto;position: absolute;top: 0;bottom: 0;margin: auto 0;border: none;/* Mobile Safari */-webkit-appearance: none;appearance: none;
}.todo-list li .toggle {opacity: 0;
}.todo-list li .toggle+label {/*Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/*/background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23ededed%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');background-repeat: no-repeat;background-position: center left;
}.todo-list li .toggle:checked+label {background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23bddad5%22%20stroke-width%3D%223%22/%3E%3Cpath%20fill%3D%22%235dc2af%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22/%3E%3C/svg%3E');
}.todo-list li label {word-break: break-all;padding: 15px 15px 15px 60px;display: block;line-height: 1.2;transition: color 0.4s;
}.todo-list li.completed label {color: #d9d9d9;text-decoration: line-through;
}.todo-list li .destroy {display: none;position: absolute;top: 0;right: 10px;bottom: 0;width: 40px;height: 40px;margin: auto 0;font-size: 30px;color: #cc9a9a;margin-bottom: 11px;transition: color 0.2s ease-out;
}.todo-list li .destroy:hover {color: #af5b5e;
}.todo-list li .destroy:after {content: '×';
}.todo-list li:hover .destroy {display: block;
}.todo-list li .edit {display: none;
}.todo-list li.editing:last-child {margin-bottom: -1px;
}.footer {color: #777;padding: 10px 15px;height: 20px;text-align: center;border-top: 1px solid #e6e6e6;
}.footer:before {content: '';position: absolute;right: 0;bottom: 0;left: 0;height: 50px;overflow: hidden;box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2), 0 8px 0 -3px #f6f6f6,0 9px 1px -3px rgba(0, 0, 0, 0.2), 0 16px 0 -6px #f6f6f6,0 17px 2px -6px rgba(0, 0, 0, 0.2);
}.todo-count {float: left;text-align: left;
}.todo-count strong {font-weight: 300;
}.filters {margin: 0;padding: 0;list-style: none;position: absolute;right: 0;left: 0;
}.filters li {display: inline;
}.filters li a {color: inherit;margin: 3px;padding: 3px 7px;text-decoration: none;border: 1px solid transparent;border-radius: 3px;
}.filters li a:hover {border-color: rgba(175, 47, 47, 0.1);
}.filters li a.selected {border-color: rgba(175, 47, 47, 0.2);
}.clear-completed,
html .clear-completed:active {float: right;position: relative;line-height: 20px;text-decoration: none;cursor: pointer;
}.clear-completed:hover {text-decoration: underline;
}.info {margin: 50px auto 0;color: #bfbfbf;font-size: 15px;text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);text-align: center;
}.info p {line-height: 1;
}.info a {color: inherit;text-decoration: none;font-weight: 400;
}.info a:hover {text-decoration: underline;
}/*Hack to remove background from Mobile Safari.Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio: 0) {.toggle-all,.todo-list li .toggle {background: none;}.todo-list li .toggle {height: 40px;}
}@media (max-width: 430px) {.footer {height: 50px;}.filters {bottom: 10px;}
}
代码如下:
<script setup>
import './styles/index.css'
import { ref } from 'vue'// 任办任务列表
const todoList = ref([{ id: 123, name: '吃饭', finished: false },{ id: 985, name: '睡觉', finished: true },{ id: 666, name: '吃饭', finished: false }
])// 添加任务
const title = ref('')
// 添加
const onAdd = () => {// 去除 title 的首尾空格const name = title.value.trim()// 非空校验if (!name) return alert('名称不能为空')// 可以在 onAdd 中添加检查const exists = todoList.value.some(item => item.name === name)if (exists) return alert('任务已存在')// 给 todoList 数组的末尾添加一个新对象todoList.value.push({name, id: Date.now(), finished: false})// 清空输入框title.value = ''
}
// 删除
const onDel = (index) => {if (window.confirm('确定删除么?')) {todoList.value.splice(index, 1)}
}
// 清空
const onClear = () => {if (window.confirm('确定清空所有任务嘛?')) {todoList.value = []}
}
</script>
<template><section class="todoapp"><header class="header"><h1>个人记事本</h1><!-- onAdd 监听回车事件 --><input v-model="title" placeholder="请输入任务" class="new-todo" @keyup.enter="onAdd" /><button class="add" @click="onAdd">添加任务</button></header><section class="main"><ul class="todo-list"><li class="todo" v-for="(item, index) in todoList" :key="item.id"><div class="view"><span class="index">{{ index + 1 }}.</span><label>{{ item.name }}</label><button class="destroy" @click="onDel(index)"></button></div></li></ul></section><footer class="footer"><span class="todo-count">合计: <strong>{{ todoList.length }}</strong></span><button class="clear-completed" @click="onClear">清空任务</button></footer></section>
</template>
最终效果如下:
相关文章:
【Vue #2】脚手架 指令
一、脚手架 脚手架:一个保证各项工作顺利开展的平台,方便我们 拿来就用,零配置 1. Vue 代码开发方式 相比直接 script 引入 vue 源码,有没有更好的方式编写vue代码呢? ① 传统开发模式: 基于html文件开发Vue&…...
UE5 后坐力枪口上抬和恢复
文章目录 计算后坐力并让视角上抬后坐力回落 计算后坐力并让视角上抬 在玩家蓝图里,声明一个方法OnShootOnce,在武器每次射击时调用 1检测新的后坐力是否超过了最大后坐力,并选择一个小的 2 如何将角色模型设置为相机的子物体 3 最后记录一下当前的…...
Mysql B+树高度如何计算?
MySQL 的 InnoDB 存储引擎使用 B+树 作为索引结构,其高度增加会直接影响查询性能(每次高度增加意味着多一次磁盘 I/O)。以下是 B+树高度增加的 关键场景 和 优化建议: 1. B+树高度增加的触发条件 (1) 数据量持续增长 根本原因:B+树的层级由数据量(记录数)和每个节点的容…...
UE5 使用贴花创建弹孔
文章目录 使用射线检测击中点在击中点处创建贴花 使用射线检测击中点 和untiy一样,发射一条射线,在命中点处创建弹孔 在武器里定义射击检测方法 以下是对上边使用的方法的展开 GetShootStartPosition:获取射击起点 computeShootEndPosition:…...
程序持续内存泄漏问题定位参考
0 概括 本文用于记录 x86-Linux 应用程序发生持续性内存泄漏问题时的定位方法。主要介绍valgrind工具的应用。 1 原理 对于内存泄漏问题的定位,一种朴素的想法就是对内存申请点进行监控。对于一个内存申请调用点(例如c/c中的malloc函数)&a…...
Lumion 与 Enscape 怎么选?附川翔云电脑适配指南
建筑可视化领域,Lumion 和 Enscape 是两款主流实时渲染器,核心差异体现在操作逻辑、渲染特性及适用场景。结合川翔云电脑平台的硬件支持,可进一步优化使用体验。 一、核心差异:效率、操作与场景适配 1. 操作门槛与实时性 Lumio…...
WebShell详解:原理、分类、攻击与防御
目录 一、WebShell的定义与核心概念 二、WebShell的分类 三、WebShell的攻击原理与常见手法 1. 攻击原理 2. 常见攻击路径 四、WebShell的危害 五、防御与检测策略 六、总结 一、WebShell的定义与核心概念 WebShell是一种以ASP、PHP、JSP等网页脚本形式存在的恶…...
Ubuntu 24.04 中文输入法安装
搜狗输入法,在Ubuntu 24.04上使用失败,安装教程如下 https://shurufa.sogou.com/linux/guide 出现问题的情况,是这个帖子里描述的: https://forum.ubuntu.org.cn/viewtopic.php?t493893 后面通过google拼音输入法解决了&#x…...
数码视讯TR100系列/TR100-G1/TR100-G4/数码视讯F7-国科GK6323V100C芯片-刷机固件包
数码视讯TR100系列/数码视讯TR100-G1/数码视讯TR100-G4/数码视讯F7-国科GK6323V100C芯片-刷机固件包 刷机教程: 里面共有两种方法,一是TTL线刷烧录方法;二是卡刷固件包; 下面以数码视讯TR100-…...
Cloudflare教程:免费优化CDN加速配置,提升网站访问速度 | 域名访问缓存压缩视频图片媒体文件优化配置
1、启用 Tiered Cache 缓存开关:通过选择缓存拓扑,可以控制源服务器与 Cloudflare 数据中心的连接方式,以确保缓存命中率更高、源服务器连接数更少,并且 Internet 延迟更短。 2、增加浏览器缓存时间TTL:在此期间&#…...
24体育NBA足球直播M24模板自适应板源码
源码名称:体育直播赛事扁平自适应M24直播模板源码 开发环境:帝国cms7.5 空间支持:phpmysql 带软件采集,可以挂着自动采集发布,无需人工操作! 演示地址:https://www.52muban.com/shop/184022.h…...
dify+wan2.1搭建文生视频生成工具流
本文介绍在dify中使用阿里开源的Wan2.1 1.3B模型搭建文生视频工作流的方法。 使用的工具如下: 1、dify(官方:https://docs.dify.ai/zh-hans) 2、comfyui 一、comfyui安装 为了简单起见,本文介绍使用autodl完成comfyui的部署。在autodl创建实例,使用的镜像如下图: 大…...
C# js 判断table中tr否存在相同的值
html 中如: 实现:table数据表格中,点击删除按钮时,验证相同子订单号条数是否大于1,大于允许删除。保证数据表格中只有唯一的一条子订单号数据。 <table style"width: 100%; background-color: #fff;" ce…...
HTML5+CSS3小实例:纯CSS绘制七巧板
实例:纯CSS绘制七巧板 技术栈:HTML+CSS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale…...
什么是虚拟线程?与普通线程的区别
引言:线程的演进与挑战 在传统的并发编程中,线程是一种非常重要的概念。我们使用线程来实现任务的并发执行,从而提高程序的执行效率。普通线程(如 Thread 类)是一种重量级的线程,每个线程都对应着操作系统…...
Docker MySQL的主从同步 数据备份 数据同步 配置文件
创建主库 docker run \--namemysql_1 \-e MYSQL_ROOT_PASSWORD123456 \-p 3306:3306 \-v mysql_main_data:/var/lib/mysql \--restart unless-stopped \-d \mysql:8.0进入容器内部 docker exec -it mysql_1 bash查找配置文件 find / -name my.cnf复制出主机 docker cp mysql…...
PowerBI中的DATEDIFF函数
一、语法 DATEDIFF(开始日期,结束日期,日期时间类型),返回两个日期之间的时间差 二、示例 开始日期结束日期度量值计算结果2025/4/1 15:33:202025/4/1 15:33:30计算 DATEDIFF([开始日期],[结束日期],SECOND)102025/4/1 15:332025/4/1 15:3…...
C/C++共有的类型转换与c++特有的四种强制类型转换
前言 C 语言和 C 共有的类型转换: 自动类型转换(隐式类型转换): 编译器在某些情况下会自动进行的类型转换。强制类型转换(显示类型转换): 使用 (type)expression 或 type(expression) 语法进行…...
体验OceanBase的 并行导入功能
在数据库的日常使用中,会经常遇到以下场景: 数据复制:将一个或多个表中的数据复制到目标表中,可能是复制全部数据,也可能仅复制部分数据。数据合并:将数据从一个表转移到另一个表,或者将多…...
CSS的字体
在 CSS 中,字体(font)是网页设计中的一个重要部分,它控制了文本的外观和排版效果。通过设置不同的字体属性,我们可以使网页上的文字更具吸引力和可读性。以下是与字体相关的 CSS 属性及其用法: 1️⃣ font…...
开源模型应用落地-LangChain与MCP协议-集成GPT-4o构建下一代AI智能体的全栈实践(三)
一、前言 在人工智能技术快速迭代的今天,大型语言模型(LLM)如何高效集成外部工具与多模态能力,成为开发者面临的核心挑战。Anthropic推出的模型上下文协议(MCP)通过标准化工具接口,为AI应用提供了“即插即用”的生态基础,而LangChain凭借其模块化设计,正成为连接LLM…...
Qt 5.14.2入门(一)写个Hello Qt!程序
目录 参考链接:一、新建项目二、直接运行三、修改代码增加窗口内容1、Qt 显示一个 QLabel 标签控件窗口2、添加按键 参考链接: Qt5教程(一):Hello World 程序 Qt 编程指南 一、新建项目 1、新建一个项目(…...
FPGA_DDR(二)
在下板的时候遇到问题 1:在写一包数据后再读,再写再读 这时候读无法读出 查看时axi_arready没有拉高 原因 : 由于读地址后没有拉高rready,导致数据没有读出卡死现象。 解决结果...
思科交换机配置
以下是交换机配置的详细步骤指南,适用于Cisco交换机,其他品牌需调整命令: 1. 初始连接与基本配置 连接方式:使用Console线连接交换机,通过终端软件(如PuTTY)登录。波特率:9600&…...
单链表——C语言实现
目录 一.相关指针知识点 二.链表 1.为什么学了顺序表还要学链表 2.优点 三.实现 1.链表的打印 —— 理解链表结构 (2) 物理结构图 2.链表的尾插 —— 入门 错误写法:tail ! NULL 总结: 正确代码物理图解: (2) 尾插整体代码 (思考…...
PostgreSQL插件生态全景解析:赋能数据库的无限可能
PostgreSQL以其开放的扩展生态闻名于世,其插件机制如同瑞士军刀般灵活,能够在不修改核心代码的前提下实现功能无限延伸。本文将基于多年内核开发经验,深度剖析PostgreSQL插件生态体系,为架构师与开发者提供全景式技术选型参考。 一…...
minio提供nfs服务
minio提供nfs服务 挂载minio为本地目录开机自动挂载使用supervisor实现开机自动挂载服务单元实现开机自动挂载minio为本地目录---失败 调试 挂载minio为本地目录 使用 Minio 作为后端存储,并通过 NFS 为客户端提供访问,那么你需要一个中间层来将 Minio …...
QML中的信号与槽机制
QML 中的信号与槽机制是 Qt 框架的核心特性之一,它提供了一种对象间通信的强大方式。与 C 中的信号槽类似,但语法更加简洁。 基本概念 1. 信号 (Signal) 当某个特定事件发生时由对象自动发出的通知 例如:按钮被点击时发出的 clicked 信号 …...
使用 Ktor 构建现代 Android 应用的后端服务
使用 Ktor 构建现代 Android 应用的后端服务 前言 在移动互联网时代,Android 应用对后端服务的实时性与性能要求越来越高,而传统的后端框架在一些场景中存在复杂度高、扩展性不足等问题。Ktor 作为 JetBrains 推出的异步 Web 框架,充分利用…...
leetcode_454. 四数相加 II_java
454. 四数相加 IIhttps://leetcode.cn/problems/4sum-ii/ 1、题目 给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足: 0 < i, j, k, l < nnums1[i] nums2[j] nums3[k] …...
类名与协议名相同,开发中应该避免吗?
在 Objective-C 开发中,协议与实现类之间的命名关系非常重要。虽然语言允许协议名和类名相同,但从可读性和维护性等角度出发,这种做法并不推荐。本文通过一个典型示例展开分析,并提供更合理的命名建议。 一、示例 在某项目中&…...
环信鸿蒙版 UIKit 快速上手指南
环信鸿蒙版 UIKit 是专为 HarmonyOS 开发者设计的 IM UI 组件库,基于环信 IM SDK 开发,可帮助开发者快速集成即时通讯功能。 环信UIKit 的特点 ArkUI 声明式开发范式:采用高效简洁的声明式开发方式状态管理 V2:支持深度观测和精…...
编译freecad
git clone --recurse-submodules https://github.com/FreeCAD/FreeCAD.git freecad-source 手动安装 vscode 扩展安装cmake tool cmake ../ 缺什么装什么 Third Party Libraries - FreeCAD Documentation sudo apt install qt6-base-dev sudo apt install libyaml-cpp-dev …...
安卓Kotlin接入高德定位和地图SDK
前言:高德的定位sdk可以获取设备当前的详细信息,如经纬度,具体地址(省->街道)等, 本文主要使用的是定位sdk和地图sdk中的poi搜索功能(以当前位置半径多少米内的关键词搜索) 目录…...
JavaScript浅拷贝与深拷贝
目录 浅拷贝(Shallow Copy) 一、浅拷贝的定义 二、直接赋值 vs 浅拷贝 1. 直接赋值 2. 浅拷贝 三、数组的浅拷贝方法 1. slice() 2. concat() 3. 扩展运算符(...) 四、对象的浅拷贝方法 1. Object.assign() 2. 扩展运…...
智能生态之城-广东茂名
故事摘要 在中国广东茂名的未来社区,晨光中,垂直果园里发光的荔枝与智能无人机的早餐派送唤醒了城市的生活。在海底透明隧道的图书馆里,孩子们通过声控设备与虚拟生物互动。面对暴雨来临时,市民们积极参与到荔枝蜜饯制作和雨季造林…...
【Android】Android Activity 横屏设置详解及常见异常问题解决方法汇总
在 Android 开发中,我们经常需要控制 Activity 的屏幕方向,例如视频播放、游戏、VR/AR 应用等场景通常希望默认横屏显示。本文将讲解如何通过 Manifest 配置 和 Java/Kotlin 代码 设置横屏显示,并分析常见设置无效的原因与解决方法。 一、通过…...
Android 存储路径
一、内部存储路径(Internal Storage) stats.codeSize(内部代码大小) 路径:/data/app/com.example.test-{随机后缀}/base.apk 说明:APK 安装路径,包含应用代码…...
【12】数据结构之基于线性表的排序算法
目录标题 插入排序直接插入排序折半插入排序希尔排序 交换排序冒泡排序快速排序 归并排序时间复杂度对比最好情况平均情况最坏情况 空间复杂度对比 插入排序 基本思想:将一个元素插入到一个有序序列中,继而得到一个有序的元素个数加一的新序列. 直接插…...
解决RecyclerView在调用smoothScrollToPosition后最后一个item底部超出屏幕的问题
要解决RecyclerView在调用smoothScrollToPosition后最后一个item底部超出屏幕的问题,可以使用自定义的LinearSmoothScroller,使其底部对齐屏幕。步骤如下: 创建自定义的SmoothScroller类: 继承LinearSmoothScroller并重写getVerti…...
数字世界的免疫系统:恶意流量检测如何守护网络安全
在2023年全球网络安全威胁报告中,某跨国电商平台每秒拦截的恶意请求峰值达到217万次,这个数字背后是无数黑客精心设计的自动化攻击脚本。恶意流量如同数字世界的埃博拉病毒,正在以指数级速度进化,传统安全防线频频失守。这场没有硝烟的战争中,恶意流量检测技术已成为守护网…...
十分钟机器学习之--------------线性回归
线性回归(linear regression)是一种基于数学模型的算法,首先假设数据集与标签之间存在线性关系,然后简历线性模型求解参数。在实际生活中,线性回归算法因为其简单容易计算,在统计学经济学等领域都有广泛的应…...
常用 Excel VBA 技巧,简单好学易上手
在日常办公中,我们常常会遇到各种繁琐的数据处理任务,而 Excel VBA(Visual Basic for Applications)作为一款强大的自动化工具,能够帮助我们轻松应对这些挑战。本文将介绍一些常用且简单好学的 Excel VBA 技巧…...
第7篇:Linux程序访问控制FPGA端LEDR<五>
Q:如何设计.c程序代码实现FPGA端外设LEDR流水灯? A:在DE1-SoC开发板上实现的流水灯效果:一次只点亮一个红色LED,初始状态为向左移动直至点亮LEDR9,然后改变移动的方向为向右直至点亮LEDR0,以此…...
PyTorch 深度学习实战(35):图生成模型与分子设计
在上一篇文章中,我们探讨了强化学习在机器人控制中的应用。本文将深入介绍图生成模型及其在分子设计领域的应用,这是一个结合深度学习与化学的交叉领域。我们将使用PyTorch Geometric实现基于图神经网络的分子生成模型,并在ZINC250k数据集上进…...
免费送源码:Java+ssm+HTML 三分糖——甜品店网站设计与实现 计算机毕业设计原创定制
录 摘要 1 1 绪论 3 1.1 研究背景 3 1.2 研究意义 3 1.3论文结构与章节安排 3 2系统分析 4 2.1 可行性分析 4 2.2 系统流程分析 4 2.2.1 登录流程 4 2.2.2数据删除流程 5 2.3 系统功能分析 5 2.3.1功能性分析 6 2.3.2 非功能性分析 7 2.4 系统用例分析 7 2.5本章…...
JVM 调试与内存优化实战详解
🌟 JVM 调试与内存优化实战详解 🌟 前言一、JVM 内存模型概览二、常见内存问题与诊断思路三、核心调试工具与命令详解四、实战案例一:频繁 Full GC 深度排查与优化1. 🕵️ 问题现象2. 🔬 排查流程3. ✅ 优化方案 五、实…...
Linux上通过Docker部署Zabbix6.2监控平台
文章目录 前言Zabbix概述zabbix特性 一、Zabbix架构二、主节点部署容器2.1 部署Docker 三、主节点部署数据库3.1 创建存储卷3.2 查看存储卷3.3 删除存储卷3.4 运行容器MySQL8 四、主节点部署Zabbix4.1 安装zabbix-java-gateway4.2 运行zabbix-server4.3 安装zabbix-web 五、配置…...
Grok3 API 已经免费开放了,附上免费使用 Grok3 API的教程
Grok-3是什么 Grok-3 是 xAI(由 Elon Musk 创立的 AI 公司)开发的最新大语言模型(LLM),属于 Grok 系列模型的第三代,旨在与 OpenAI 的 GPT-4、Anthropic 的 Claude 3 和 Google 的 Gemini 1.5 等顶尖 AI 竞…...
2025年4月9日-华为暑期实习-第三题-300分
📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围OJ 03. 矩阵螺旋排序 问题描述 卢小姐是一家艺术展览馆的策展人,她正在为一个数字艺术展览设计一个特殊的展示方案。展览厅的墙面是一个 N N N \times N...