vue3 中实现父子组件v-model双向绑定 总结

我们经常会封装一下组件 使用props emit 来进行 父子组件之间的 绑定 其实这种方式我们可以解决开发中的大部分问题 但是有更好的方式 v-model 语法糖。

我直接上大家能够看懂 并且能够使用的部分 我拿我的 抽屉组件来看

需求

我现在有个作品信息 很多地方都需要使用 并且 组件内容是以一样的 显示的信息 等 可能只有按钮不一样 我们可以吧按钮写成插槽

现在我直接上组件代码

复制代码
<template>
  <div class="book-detail-component">
    <!-- 作品信息 -->
    <el-drawer
      v-model="workDetailFlag"
      title="作品信息"
      size="60%"
      direction="rtl"
      :before-close="handleWorkDetailFlagClose"
    >
      <el-tabs v-model="activeName" @tab-click="handleClick">
        <el-tab-pane label="作品信息" name="first">
          <div class="book-container">
            <div class="data-set">
              <div class="title">基础信息</div>
              <!-- <div class="set">
                <el-button type="primary" round>签约</el-button>
              </div> -->
              <!-- 使用插槽,父组件可以在这里插入自定义内容 -->
              <slot name="action-buttons">
                <!-- 默认内容 -->
                <el-button type="primary" round>签约</el-button>
              </slot>
            </div>
            <div class="data-info">
              <div class="item">
                <div class="label">书籍封面</div>
                <div class="value">
                  <img
                    src="../../../assets/book.jpg"
                    style="width: 120px; border-radius: 6px"
                    alt=""
                  />
                </div>
              </div>
              <div class="item">
                <div class="label">书籍名称</div>
                <div class="value">测试环境2</div>
              </div>
              <div class="item">
                <div class="label">目标读者</div>
                <div class="value">女频</div>
              </div>
              <div class="item">
                <div class="label">标签</div>
                <div class="value">女频</div>
              </div>
              <div class="item">
                <div class="label">标签</div>
                <div class="value">阿勒、阿明</div>
              </div>
              <div class="item">
                <div class="label">作品简介</div>
                <div class="value">
                  这个是测试数据数据的健身房好的减肥还是得减肥的精神发挥大数据恢复时间的恢复
                </div>
              </div>
            </div>
            <div class="data-set">
              <div class="title">其他信息</div>
              <div class="set"></div>
            </div>
            <div class="other-info">
              <div class="row">
                <div class="item">
                  <div class="label">书号</div>
                  <div class="value">2025101510151508547</div>
                </div>
                <div class="item">
                  <div class="label">签约状态</div>
                  <div class="value">未签约</div>
                </div>
              </div>
              <div class="row">
                <div class="item">
                  <div class="label">创建时间</div>
                  <div class="value">2025-10-15 10:15:15</div>
                </div>
                <div class="item">
                  <div class="label">更新状态</div>
                  <div class="value">连载状态</div>
                </div>
              </div>
              <div class="row">
                <div class="item">
                  <div class="label">上架起始章节</div>
                  <div class="value">无</div>
                </div>
              </div>
            </div>

            <div class="info-card">
              <div class="card-header">
                <h3>章节信息</h3>
              </div>
              <div class="card-body">
                <el-table :data="chapterList" style="width: 100%" class="chapter-table">
                  <el-table-column prop="name" label="章节名称/ID" min-width="180">
                    <template #default="{ row }">
                      <div class="chapter-name">
                        <span class="name">{{ row.name }}</span>
                        <span class="id">{{ row.id }}</span>
                      </div>
                    </template>
                  </el-table-column>
                  <el-table-column prop="updateTime" label="更新时间" width="150" />
                  <el-table-column prop="payStatus" label="付费状态" width="80">
                    <template #default="{ row }">
                      <el-tag :type="row.payStatus === '付费' ? 'danger' : 'info'" size="small">
                        {{ row.payStatus }}
                      </el-tag>
                    </template>
                  </el-table-column>
                  <el-table-column prop="auditStatus" label="审核状态" width="80">
                    <template #default="{ row }">
                      <el-tag type="success" size="small">{{ row.auditStatus }}</el-tag>
                    </template>
                  </el-table-column>
                  <el-table-column prop="clickCount" label="点击人数" width="80" align="center">
                    <template #default="{ row }">
                      <span class="click-count">{{ row.clickCount }}</span>
                    </template>
                  </el-table-column>
                  <el-table-column label="操作" width="120" fixed="right">
                    <!-- <template #default="{ row }">
                      <el-button type="primary" link size="small">编辑</el-button>
                      <el-button type="primary" link size="small">查看</el-button>
                    </template> -->
                  </el-table-column>
                </el-table>
                <div class="pagination">
                  <div></div>
                  <el-pagination background layout="prev, pager, next" :total="1000" />
                </div>
              </div>
            </div>
          </div>
        </el-tab-pane>
      </el-tabs>
    </el-drawer>
  </div>
</template>

<script setup>
import { ref, reactive, computed } from 'vue'
const props = defineProps({
  visible: {
    type: Boolean,
    default: false
  }
})
const emit = defineEmits(['update:visible'])
const workDetailFlag = computed({
  get: () => props.visible,
  set: (value) => emit('update:visible', value)
})

const handleWorkDetailFlagClose = () => {
  workDetailFlag.value = false
  done()
}

// 章节列表数据
const chapterList = ref([
  {
    name: '第2章 PM测试第二次',
    id: '2013年',
    updateTime: '2025-09-29 11:12:01',
    payStatus: '付费',
    auditStatus: '已发布',
    clickCount: 0
  },
  {
    name: '第1章 PM测试',
    id: '2013年',
    updateTime: '2025-09-28 11:52:26',
    payStatus: '付费',
    auditStatus: '已发布',
    clickCount: 0
  }
])

const activeName = ref('first')
//切换tabs
const handleClick = (tab, event) => {
  console.log(tab, event)
}
</script>

<style lang="less" scoped>
.book-detail-component {
  .book-container {
    .data-set {
      display: flex;
      align-items: center;
      justify-content: space-between;
      margin-top: 40px;
      .title {
        color: #000;
        font-weight: 700;
      }
    }
    .data-info {
      margin-top: 20px;
      .item {
        display: flex;
        .label {
          width: 100px;
          color: #999;
        }
        .value {
          flex: 1;
          margin-left: 30px;
          color: #000;
        }
      }
      .item:nth-child(n + 2) {
        margin-top: 15px;
      }
    }
    .info-card {
      background: #fff;
      border-radius: 8px;
      box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
      margin-bottom: 20px;

      .card-header {
        padding: 16px 20px;
        border-bottom: 1px solid #e8e8e8;
        display: flex;
        justify-content: space-between;
        align-items: center;

        h3 {
          margin: 0;
          font-size: 16px;
          font-weight: 600;
          color: #303133;
        }
      }

      .card-body {
        padding: 20px;
        .pagination {
          margin-top: 20px;
          display: flex;
          align-items: center;
          justify-content: space-between;
        }
      }
    }
    .other-info {
      margin-top: 20px;
      .row {
        display: flex;
        align-items: center;
        margin-top: 15px;

        .item {
          display: flex;
          width: 500px;

          .label {
            width: 100px;
            color: #999;
          }
          .value {
            flex: 1;
            margin-left: 30px;
            color: #000;
          }
        }
        .item:nth-child(n + 2) {
          margin-left: 15px;
        }
      }
    }
  }
}
</style>

const props = defineProps({
  visible: {
    type: Boolean,
    default: false
  }
})
const emit = defineEmits(['update:visible'])
const workDetailFlag = computed({
  get: () => props.visible,
  set: (value) => emit('update:visible', value)
})

这里面主要需要看这个代码 组件接受了props 以及自定义时间

我们一般 会使用computed来写一下复杂的 get 和set 以后都可以按照我的来写 当然你如果不设置visible 这个名称 默认是modalvalue 设置的话 就是你设置的

这个事件是 updated:visible

插槽的话 就是 你需要哪里需呀父组件 传递自己的东西 使用默认插槽就好了

父组件使用

复制代码
    <BookDetailCom v-model:visible="bookDetailVisible"></BookDetailCom>

使用就很简单了

我们见过的 所有的组件库 vue3 版本的其实我感觉是这样封装的

相关推荐
星光一影3 小时前
快递比价寄件系统技术解析:基于PHP+Vue+小程序的高效聚合配送解决方案
vue.js·mysql·小程序·php
qq_338032923 小时前
Vue 3 的<script setup> 和 Vue 2 的 Options API的关系
前端·javascript·vue.js
擦拉嘿3 小时前
Days.js实时更新时间格式文案在切换全局语言之后的方案
vue.js·days.js·动态更新时间
lumi.3 小时前
Vue Router页面跳转指南:告别a标签,拥抱组件化无刷新跳转
前端·javascript·vue.js
yeyuningzi3 小时前
VUE 运行npm run dev命令提示error Missing script: “dev“
前端·vue.js·npm
Mintopia3 小时前
🧠 一文吃透 Next.js 中的 JWT vs Session:底层原理+幽默拆解指南
前端·javascript·全栈
葛小白13 小时前
Winform控件:Combobox
前端·ui·c#·combobox
政采云技术3 小时前
前端设计模式详解
前端·设计模式
前端开发爱好者3 小时前
字节出手!「Vue Native」真的要来了!
前端·javascript·vue.js