前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >【Vue #2】脚手架 & 指令

【Vue #2】脚手架 & 指令

作者头像
IsLand1314
发布于 2025-04-11 01:35:39
发布于 2025-04-11 01:35:39
10700
代码可运行
举报
文章被收录于专栏:学习之路学习之路
运行总次数:0
代码可运行

一、脚手架

脚手架:一个保证各项工作顺利开展的平台,方便我们 拿来就用,零配置

1. Vue 代码开发方式

相比直接 script 引入 vue 源码,有没有更好的方式编写vue代码呢?

传统开发模式

  1. 基于html文件开发Vue,类似jQuery的使用 <script src="vue.js"></script>
  2. 优点:简单、上手快
  3. 缺点:功能单一、开发体验差
image-20250319144701029
image-20250319144701029

工厂化开发模式:

在 构建工具(Vite/Webpack )环境下开发Vue,这是最推荐的、也是企业采用的方式

image-20250319144743072
image-20250319144743072
  • 优点:功能全面,开发体验好
  • 缺点:目录结构复杂,理解难度提升
2. 准备工程化环境

① 安装工具 Nodejs

注意: 安装18.3或更高版本,Nodejs 官网:https://nodejs.org/en/

安装好之后,可以打开命令行,输入下面指令,进行测试如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
> node -v
v22.13.1

> npm -v
10.9.2

npm 换源 – 避免下载过慢,当前下载好的可以不用管

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 查看 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 三者的功能类似,都是包管理工具, 用来下载或删除模块包,性能上 yarnpnpm 优于 npm

命令

装包

删包

npm

npm i 包名

npm un 包名

yarn

yarn add 包名

yarn remove 包名

pnpm

pnpm i 包名

pnpm un 包名

在命令行上进行安装,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
# 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 工厂化项目

创建步骤如下

  1. 选定⼀个存放位置,比如选择桌面,根据自己情况,选择D盘或E盘等
  2. 执行命令 npm create vue@latest ,会安装并执行 create-vue , 它是Vue官方的项目脚手架工具
  3. 进⼊项目根目录:cd 项目名称
  4. 安装 vue 等模块依赖:npm i
  5. 启动项目: npm run dev ,会开启⼀个本地服务器,然后在浏览器网址栏输入:http://localhost:5173
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
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-vue

T  Vue.js - The Progressive JavaScript Framework
|
o  请输入项目名称:
|  vue-engineering-way
|
o  请选择要包含的功能: (/↓ 切换,空格选择,a 全选,回车确认)
|  Prettier(代码格式化)

安装选项如下:

image-20250319151332863
image-20250319151332863

启动服务如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
\Desktop\vue-engineering-way> npm run dev

> vue-engineering-way@0.0.0 dev
> vite


  VITE 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

最后呈现的界面效果如下,说明项目创建并启动成功了

image-20250319151448949
image-20250319151448949

补充 – 后继我们要打开这个界面,就需要先运行,然后输入 localhost:端口号(看自己设定的端口号是多少,我这里是 5173)

4. 认识工程化项目下目录和文件

用 vscode 打开查看,如下:

image-20250319151740519
image-20250319151740519

我们今后Vue代码写哪个目录下?

  • 答: src 目录,src下的所有代码会被 vite 打包成 css/js/img , 然后交给 index.html ,最终通过浏览器呈现在用户眼前

分析上面三个入口文件关系:

image-20250319161150291
image-20250319161150291

1、main.js、App.vue、index.html三个文件的作用?

  1. main.js - 项目打包的入口 - 创建应用
  2. App.vue - Vue代码的入口(根组件)
  3. index.html- 项目的入口网页

2、mian.js、App.vue、index.html 三者的关系是什么?

  1. App.vue(vue入口)=>main.js(项目打包入口)index.html(浏览器入口)
  2. main.js 是 Vue 代码通向网页代码的桥梁,非常关键
5. Vue 单文件

思考:代码写一起,会不会出现class类名、js变量名 重名冲突?Vue中如何避免呢?

vue单文件介绍

  1. vue推荐采用 .vue 的文件来开发项目
  2. 一个 vue 文件通常有3部分组成,script(JS)+template(HTML)+ style(CSS)
  3. 一个 vue 文件是 独立的模块,作用域互不影响
  4. style 配合 scoped 属性,保证样式只针对当前 template 内的标签生效

作用:提供了独立的作用域,不用担心 JS 变量名、CSS 选择器名冲突

注意:.vue 文件浏览器无法识别,需要借助 vite打包成 js、css 等,最终交给 index.html,通过浏览器呈现效果

6. 清理目录结构
  1. 删除assets文件夹
  2. 删除components文件夹
  3. 清除App.vue的内容
  4. 清除main.js的内容

补充内容

App.vue

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<script setup></script>
<template>
 App根组件
</template>
<style></style>

main.js

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
7. set up简写 + 插值 + 响应式

实例1:完整写法

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<script>
  export default{
    setup(){
      const msg = 'Hello World' // 声明数据
      return {msg} // 返回数据
    }
  }
  
</script>

<template>
  <h1>{{msg}}</h1>
</template>

实例2:简写

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<script setup>
  const msg = 'Hello World' 
</script>

<template>
  <h1>{{msg}}</h1>
</template>

实例3:练习

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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标签的样式呈现出来。

代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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>

效果如下

image-20250402171127566
image-20250402171127566
3. 属性绑定指令

作用:把表达式的结果 与标签的属性动态绑定 3

语法:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
v-bind: 属性名="表达式" (可简写成 ::属性名="表达式"

基本用法

① 绑定单个属性

使用 v-bind 可以绑定单个属性,例如绑定图片的 src 属性:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<img v-bind:src="imgSrc" alt="">
<!-- 或者使用缩写 -->
<img :src="imgSrc" alt="">

② 绑定多个属性

如果需要绑定多个属性,可以使用对象语法,将多个属性和对应的值放在一个对象中,然后通过 v-bind 绑定这个对象

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<img v-bind="{ src: imgSrc, title: imgTitle }" alt="">

③ 绑定 Class

通过 v-bind:class 可以动态绑定元素的 class 属性。例如,根据 isActive 的值动态切换 class

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<div v-bind:class="{ active: isActive }" class="test"></div>

④ 绑定 style

通过 v-bind:style 可以动态绑定元素的 style 属性。需要注意的是,CSS 样式名中的 - 需要转换为驼峰命名法,例如 font-size 需要转换为 fontSize

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<div v-bind:style="{ background: bground, fontSize: fSize + 'px' }">
hello-vue
</div>

⑤ 传递多个 Props

在父组件向子组件传递多个参数时,可以使用 v-bind 的对象语法,将所有的 props 集中在一个对象中传递:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<child-component v-bind="props"></child-component>

代码样例如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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注册事件,及其的简单,语法如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<button v-on:事件名="内联语句">按钮</button>
<button v-on:事件名="处理函数">按钮</button>
<button v-on:事件名="处理函数(实参)">按钮</button>
  • 注意:v-on 可以简写为 @

内联语句指的是直接在HTML标签上使用JavaScript代码的一种方式。在Vue中,可以通过v-on指令将内联语句与DOM事件关联起来,从而在触发事件时执行相应的 JavaScript 代码。

代码示例如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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-elsev-else-if

  • 作用:辅助v-if进行判断渲染
  • 语法:v-else v-else-if="表达式"
  • 需要紧接着v-if使用

代码示例1

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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. 7090之间良好 
  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:每一项的索引,不需要可以省略
  • 目标结构:被遍历的 数组/对象/数字
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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 的默认行为会尝试原地修改元 素(就地复用)

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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>

删除时结果如下:

image-20250402220509145
image-20250402220509145
  • 右边闪烁越少,说明 vue 复用性更好,性能也更快
  • 因此可以知道:最大限度的复用DOM、从而提⾼DOM的更新性能

注意

  1. key 的类型只能是 数字 或 字符串
  2. key的值必须 唯一,不能重复
  3. 推荐用 id 作为 key(因为id唯一),不推荐用 index 作为 key( 会变化)
7. 双向绑定指令

所谓双向绑定就是:

  1. 数据改变 -> 视图变化
  2. 视图改变 -> 数据变化

作用:在 表单元素(input、select、radio、checkbox)上,实现数据双向绑定。从而可以快速 获取 或设置 表单元素的值

我们可以同时使用 v-bindv-on 来在表单的输入元素上创建双向绑定:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input :value="text" @input="onInput">

试着在文本框里输入——你会看到 <p> 里的文本也随着你的输入更新了【实时更新

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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 指令,它实际上是上述操作的语法糖:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<input v-model="text">
  • v-model 会将被绑定的值与 <input> 的值自动同步,这样我们就不必再使用事件处理函数了。
  • v-model 不仅支持文本输入框,也支持诸如多选框、单选框、下拉框之类的输入类型
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<script setup>
import { ref } from 'vue'

const text = ref('')
</script>

<template>
  <input v-model="text" placeholder="Type here">
  <p>{{ text }}</p>
</template>

案例:实现登录界面,需求如下:

  • 点击登录按钮获取表单中的内容
  • 点击重置按钮清空表单中的内容
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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. 学习之旅

效果如下:

image-20250402205718970
image-20250402205718970
  • 需求:默认展示数组中的第⼀张图片,点击上一页下一页来回切换数组中的图片

实现思路

  1. 数组存储图片路径 [‘url1,url2’url3’
  2. 准备下标index 去数组中取图片地址
  3. 通过v-bind给src绑定当前的图片地址
  4. 点击上一页下一页只需要修改下标的值即可
  5. 当展示第一张的时候,上一页按钮应该隐藏。展示最后一张的时候,下一页按钮应该隐藏

代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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. 可折叠面板

效果如下

image-20250402210659607
image-20250402210659607

实现思路

  1. 搭建了HTML结构+CSS样式
  2. 准备一个响应式的布尔数据
  3. 通过 v-show 绑定布尔值控制盒子的显示或隐藏
  4. 给按钮绑定点击事件,每点击的时候让布尔值取反
  5. 布尔值控制按钮的名称

代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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. 书架管理

效果如下

image-20250402220010061
image-20250402220010061

需求

  • 根据左侧数据渲染出右侧列表(v-for)
  • 点击删除按钮时应该把当前行从列表中删除(获取当前行的index,利用splice删除)

代码如下

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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 文件,如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/** @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=922433
    IE 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;
    }
}

代码如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
<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>

最终效果如下:

image-20250403152353647
image-20250403152353647
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-04-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
从进程组、会话、终端的概念深入理解守护进程
「守护进程」是 Linux 的一种长期运行的后台服务进程,也有人称它为「精灵进程」。我们常见的 httpd、named、sshd 等服务都是以守护进程 Daemon 方式运行的,通常服务名称以字母d结尾,也就是 Daemon 第一个字母。与普通进程相比它大概有如下特点:
用户3105362
2021/02/04
1.2K0
进程组、会话、控制终端概念,如何创建守护进程?
守护进程,也就是通常所说的Daemon进程,是Linux中的后台服务进程。周期性的执行某种任务或等待处理某些发生的事件。
睡魔的谎言
2020/11/25
1.6K0
linux系统编程之进程(五):终端、作业控制与守护进程
该文介绍了如何在Linux系统中通过fork函数创建守护进程,并给出了具体的示例代码。同时,文章还介绍了守护进程的一些常见用途,如保证程序在后台运行、处理控制台输入输出等。
s1mba
2018/01/03
2.8K0
linux系统编程之进程(五):终端、作业控制与守护进程
Linux 守护进程|应急响应
通常我们都是通过以上两种方式来获得一个shell,之后运行程序的,此时我需要纠正一个概念,我们通常都说获得一个shell,本质上来说,我们获取了一个session(会话,以下session都是会话)
意大利的猫
2021/03/18
4.1K0
Linux 守护进程|应急响应
Linux内核编程--进程组和守护进程
进程组:进程组是多个进程的集合, 接收同一个终端的各类信号信息。进程调用setpgid(pid, pgid)可以加入一个现有的进程组或者创建一个新的进程组。
Coder-ZZ
2022/05/09
3.2K0
Linux内核编程--进程组和守护进程
【在Linux世界中追寻伟大的One Piece】进程间关系与守护进程
其实每一个进程除了有一个进程ID(PID)之外,还属于一个进程组。进程组是一个或者多个进程的集合, 一个进程组可以包含多个进程。 每一个进程组也有一个唯一的进程组ID(PGID), 并且这个 PGID类似于进程ID, 同样是一个正整数, 可以存放在pid_t数据类型中。
枫叶丹
2024/09/24
1330
【在Linux世界中追寻伟大的One Piece】进程间关系与守护进程
什么是守护进程?
在了解守护进程之前,需要先知道什么是什么是终端?什么是作业?什么是进程组?什么是会话?
全栈程序员站长
2022/09/07
1.1K0
【Linux网络编程】:守护进程,前台进程,后台进程
●无控制终端:脱离控制终端,避免收到终端的干扰,它是和客户端进行交流的。和Xshell终端摆脱了联系。
用户11396661
2025/02/04
3480
【Linux网络编程】:守护进程,前台进程,后台进程
linux 后台运行进程:& , nohup
当我们在终端或控制台工作时,可能不希望由于运行一个作业而占住了屏幕,因为可能还有更重要的事情要做,比如阅读电子邮件。对于密集访问磁盘的进程,我们更希望它能够在每天的非负荷高峰时间段运行(例如凌晨)。为了使这些进程能够在后台运行,也就是说不在终端屏幕上运行,有几种选择方法可供使用。
DevOps在路上
2023/05/16
5.3K0
linux 后台运行进程:& , nohup
将 Web 应用丢给守护进程
最近老是要把 Web App/Service 部署在个人的服务器上进行测试,发现自己不怎么熟悉「前提:不上 docker ,逃~」,特写此文章来纪念下??(之前部署的 Web App/Service
Cloud-Cloudys
2020/07/07
1.6K0
将 Web 应用丢给守护进程
Linux - 请允许我静静地后台运行
枕边书
2018/01/04
1.8K0
Linux - 请允许我静静地后台运行
【计算机网络】日志与守护进程
一般使用cout进行打印,但是cout打印是不规范的 实际上 是采用日志进行打印的
lovevivi
2023/11/17
2210
【计算机网络】日志与守护进程
守护进程
在Linux中,session(会话)通常指的是与用户交互的一个环境,它是系统中与某个用户交互的一系列活动的集合。会话在Linux系统中有多种用途,下面是几种常见的会话类型及其相关概念:
ljw695
2025/01/03
2780
守护进程
守护进程「建议收藏」
在UNIX系统中, 用户通过终端登录系统后得到一个Shell进程, 这个终端成为Shell进程的控制终端(Controlling Terminal), 进程中, 控制终端是保存在PCB中的信息, 而fork会复制PCB中的信息, 因此由Shell进程启动的其它进程的控制终端也是这个终端. 默认情况下(没有重定向), 每个进程的标准输入, 标准输出和标准错误输出都指向控制终端, 进程从标准输入读也就是读用户的键盘输入, 进程往标准输出或标准错误输出写也就是输出到显示器上. 信号中还讲过, 在控制终端输入一些特殊的控制键可以给前台进程发信号, 例如Ctrl-C表示SIGINT,Ctrl-\表示SIGQUIT。
全栈程序员站长
2022/09/16
6340
Python实现守护进程
專 欄 ❈汤英康,Python程序员,负责设计和开发大数据监控平台的相关产品。 PyCon China2016 深圳 讲师。 博客:http://blog.tangyingkang.com/ ❈— Daemon场景 考虑如下场景:你编写了一个python服务程序,并且在命令行下启动,而你的命令行会话又被终端所控制,python服务成了终端程序的一个子进程。因此如果你关闭了终端,这个命令行程序也会随之关闭。 要使你的python服务不受终端影响而常驻系统,就需要将它变成守护进程。 守护
Python中文社区
2018/01/31
2K0
Python守护进程daemon实现
守护进程是系统中生存期较长的一种进程,常常在系统引导装入时启动,在系统关闭时终止,没有控制终端,在后台运行。守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断。 在这里,我们在Linux2.6内核的centos中,ps -ef |awk '{print $1"\t "$2"\t "$3"\t  "$8}'看到:PPID=0的进程有两个,分别是PID=1的/sbin/init进程和PID=2的[kthreadd]进程。
py3study
2020/01/07
7.9K0
PHP中的会话
2、当执行php xxx.php 时,默认系统会把当前的进程设置为会话首进程(使用strace查看),所以当前会话首进程不能使用posix_setsid 创建为会话首进程,只能使用子进程调用此函数
北溟有鱼QAQ
2021/06/08
1.4K0
nohup、&、setsid、fork和fg、bg究竟有啥区别?
在后台运行的进程不一定是守护进程!一个进程要成为守护进程,必须做到以下两点:
一见
2018/08/10
2.4K0
Linux守护进程
守护进程在 Linux 系统中极为重要,它们是许多服务器的核心组成部分,例如 Internet 服务器 inetd 和 Web 服务器 httpd。这些进程不仅负责提供网络服务,还执行各种系统任务,例如作业调度进程 crond。
不脱发的程序猿
2024/11/26
3920
Linux守护进程
Linux守护进程
进程组,也叫做作业。BSD于1980年前后向Unix中增加的一个新特性,代表一个或者多个进程的集合,每个进程都属于一个进程组。操作系统设计进程组的概念主要就是为了简化对多个进程的管理。
mindtechnist
2024/08/08
3670
Linux守护进程
相关推荐
从进程组、会话、终端的概念深入理解守护进程
更多 >
LV.0
下笔有神CTO
目录
  • 一、脚手架
    • 1. Vue 代码开发方式
    • 2. 准备工程化环境
    • 3. 创建 Vue 工厂化项目
    • 4. 认识工程化项目下目录和文件
    • 5. Vue 单文件
    • 6. 清理目录结构
    • 7. set up简写 + 插值 + 响应式
  • 二、指令
    • 1. 基本概念
    • 2. 内容渲染指令
    • 3. 属性绑定指令
    • 4. 事件绑定指令
    • 5. 条件渲染指令
    • 6. 列表渲染指令
      • v-for 中的 key
    • 7. 双向绑定指令
  • 三、案例学习
    • 1. 学习之旅
    • 2. 可折叠面板
    • 3. 书架管理
    • 4. 个人记事本
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档