芋道框架审批流如何实现(Cloud+Vue3)

本文涉及的产品
性能测试 PTS,5000VUM额度
可观测可视化 Grafana 版,10个用户账号 1个月
可观测监控 Prometheus 版,每月50GB免费额度
简介: 芋道框架审批流如何实现(Cloud+Vue3)

 审批流的其实就是提交之后发送给你的上级然后让他进行批阅,再进行反馈。实际上的就是发送、接收、改变状态、再发送的一个过程。

芋道框架帮我们集成了这个功能,下面就让我来带大家做一个简易版的审批流。

首先:数据库准备一定要有这四个字段 image.gif 编辑

后端:

实体类:

DO类:

/**
     * 导入批次号
     */
    private String importBatch;
    /**
     * 录入批次号
     */
    private String subBatchNo;
    /**
     * 状态
     */
    private Long status;
    private String processInstanceId;

image.gif

SaveReqVO类:

@Schema(description = "录入批次号", example = "9103")
    private String subBatchNo;
    @Schema(description = "导入批次号", example = "9103")
    private String importBatch;
    @Schema(description = "状态", example = "9103")
    private Long status;
    private String processInstanceId;

image.gif

RespVO类:

private String subBatchNo;
    private Long status;
    private String processInstanceId;

image.gif

提交审核的方法

Service:

 

void examine(List<TbQyyrYrzyjpljlbRespVO> examineReqVO);

image.gif

实现类:

public static final String PROCESS_KEY = "carbon-yr-jcdjpl";
    public static final String prefix = "yr-b-";
    @Resource
    private BpmUtil bpmUtil;
   @Override
    public void examine(List<TbQyyrYrzyjpljlbRespVO> examineReqVO) {
        // 生成提交批次号
        String submitBatchNo = NumberGenerate.generateNumber(prefix);
        // 发送到审批流程
        String processInstanceId = bpmUtil.submitToBpm(submitBatchNo, PROCESS_KEY);
        for(TbQyyrYrzyjpljlbRespVO tbQyyrYrzyjpljlbRespVO : examineReqVO){
            TbQyyrYrzyjpljlbDO updateObj = BeanUtils.toBean(tbQyyrYrzyjpljlbRespVO, TbQyyrYrzyjpljlbDO.class);
            // 每条数据保存提交批次号
            updateObj.setSubBatchNo(submitBatchNo);
            // 每天数据保存审批流 processInstanceId
            updateObj.setProcessInstanceId(processInstanceId);
            updateObj.setStatus(2L);
            tbQyyrYrzyjpljlbMapper.updateById(updateObj);
        }
    }

image.gif

记住这里的PROCESS_KEY和prefix,后面有用,这是唯一的标识。

Controller:

@PostMapping("/examine")
    @Operation(summary = "线缆原材料生产数据采集审核")
    public CommonResult<Boolean> examine(@RequestBody List<TbQyyrYrzyjpljlbRespVO>  examineReqVO ) {
        tbQyyrYrzyjpljlbService.examine(examineReqVO);
        return success(true);
    }

image.gif

generateNumber方法:

package com.todod.carbon.framework.common.util.common;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Random;
public class NumberGenerate {
    public static String generateNumber(String prefix) {
        // 使用当前时间作为随机数生成器的种子
        Random random = new Random(System.currentTimeMillis());
        // 生成一个0到9999之间的随机数
        int randomNumber = random.nextInt(10000);
        String batchNo = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"))+randomNumber;
        return prefix + batchNo;
    }
}

image.gif

该方法用于生成提交的批次号。

提交方法写完后我们回到页面的工作流程去做一个审批流

image.gif 编辑

image.gif 编辑

这里的流程标识需要和我们定义的PROCESS_KEY是一样的。

image.gif 编辑

按照这个流程,创建之后我们先去修改流程补全信息

image.gif 编辑

这里的表单查看地址对应了一个只能查看的页面用于审批的时候能够看到审批的信息

点击设计流程

image.gif 编辑

按照这样配置,画一个流程审批图

点击保存模型后发布

image.gif 编辑

接下来我们写一个改变状态的方法:

service:

void setStatus(String subNo,Integer status);

image.gif

实现类:

@Override
    public void setStatus(String id, Integer bpmStatus) {
        List<TbQyyrYrzyjpljlbDO> yrzyjpljlbDOS = tbQyyrYrzyjpljlbMapper.selectBpmList(id);
        if (yrzyjpljlbDOS != null && yrzyjpljlbDOS.size()>0){
            yrzyjpljlbDOS.forEach(item->{
                item.setStatus(Long.parseLong(bpmStatus.toString()));
                tbQyyrYrzyjpljlbMapper.updateById(item);
            });
        }
        if (bpmStatus == 3){
            //执行kettle
//            kettleUtils.runKTR(kettleFileName);
        }
    }

image.gif

selectBpmList方法:


default List<TbQyyrYrzyjpljlbDO> selectBpmList (String processInstanceId){
        return selectList(new LambdaQueryWrapperX<TbQyyrYrzyjpljlbDO>()
                .eq(TbQyyrYrzyjpljlbDO::getProcessInstanceId,processInstanceId)
        );
    }

image.gif

这个方法会根据返回的审批id去查找符合条件的数据集合,也就是同一时间审批的那一批数据。

改变状态的方法就写好了,接下来我们去Bpm模块写一个监听方法。

image.gif 编辑

在这里写一个监听类,层级是这样的,大家不要写错了。

package com.todod.carbon.module.bpm.service.oa.listener;
import com.todod.carbon.module.bpm.event.BpmProcessInstanceStatusEvent;
import com.todod.carbon.module.bpm.event.BpmProcessInstanceStatusEventListener;
import com.todod.carbon.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Component;
@Component
public class YrZyJplListener extends BpmProcessInstanceStatusEventListener {
    public static final String PROCESS_KEY = "carbon-yr-jcdjpl";
    @Resource
    private AdminUserApi adminUserApi;
    @Override
    protected String getProcessDefinitionKey() {
        return PROCESS_KEY;
    }
    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        String id = event.getId();
        adminUserApi.setCommonStatus(id,event.getStatus(),"yr-b");
    }
}

image.gif

这里依旧要保证PROCESS_KEY和prefix一致。

这是我自己写的一个改变状态的rpc,大家可以自己写一个,我相信并不难,我放这大家可以参考。

@GetMapping(PREFIX + "/bpmCommonStatus")
    @Operation(summary = "工作流状态")
    @Parameters({
            @Parameter(name = "id", description = "订单id", example = "1", required = true),
            @Parameter(name = "bpmStatus", description = "工作流状态", example = "3", required = true),
            @Parameter(name = "type", description = "对应的工作表", example = "3", required = true)
    })
    void setCommonStatus(@RequestParam("id")String id, @RequestParam("status")Integer status, @RequestParam("type")String type);

image.gif

@Override
    public void setStatus(Long id, Integer bpmStatus) {
        switch (bpmStatus) {
            case 2://通过
                break;
            case 3://未通过
                break;
            case 4://撤回、取消 审批
                break;
        }
    }
    @Override
    public void setCommonStatus(String id, Integer bpmStatus, String type) {
        switch (bpmStatus) {
            case 2://通过
                bpmStatus = 3;
                break;
            case 3://未通过
                bpmStatus = 4;
                break;
            case 4://撤回、取消 审批
                bpmStatus = 1;
                break;
        }
        switch (type){
            //产品信息
            case "xl-a":
                tbXlcpCpxxService.setStatus(id,bpmStatus);
            //原材料生产
            case "xl-b":
                tbXlcpYclscjdService.setStatus(id,bpmStatus);
            //原材料运输
            case "xl-c":
                tbXlcpYclysjdService.setStatus(id,bpmStatus);
            //产品生产
            case "xl-d":
                tbXlcpCpscjdService.setStatus(id,bpmStatus);
            //产品运输
            case "xl-e":
                tbXlcpCpysjdService.setStatus(id,bpmStatus);
            //产品运输
            case "xl-f":
                tbXlcpCpsyjdService.setStatus(id,bpmStatus);
            //产品回收
            case "xl-g":
                tbXlcpCphsjdService.setStatus(id,bpmStatus);
            //楼宇电表
            case "ly-a":
                lyntglAmmeterInfoService.setStatus(id,bpmStatus);
            //楼宇水表
            case "ly-b":
                lyntglWaterinfoService.setStatus(id,bpmStatus);
           //楼宇天然气
            case "ly-c":
                lyntglTrqInfoService.setStatus(id,bpmStatus);
           //楼宇市政热力
            case "ly-d":
                lyntglSzrlInfoService.setStatus(id,bpmStatus);
           //楼宇可再生能源
            case "ly-e":
                lyntglKzsnyInfoService.setStatus(id,bpmStatus);
           //工业园区
            case "gyyq-a":
                tbGyyqGyyqcjService.setStatus(id,bpmStatus);
           //余热-监测点
            case "yr-a":
                tbQyyrJcdbService.setStatus(id,bpmStatus);
            default:
                break;
        }
    }

image.gif

前端:

大家可以那前端做个参考,我就把完整代码放到这里了。

<template>
  <ContentWrap>
    <!-- 搜索工作栏 -->
    <el-form
      class="-mb-15px"
      :model="queryParams"
      ref="queryFormRef"
      :inline="true"
      label-width="130px"
    >
      <el-form-item label="企业名称" prop="ttbQyyrQyname">
        <el-input
          v-model="queryParams.ttbQyyrQyname"
          placeholder="请输入企业名称"
          clearable
          @keyup.enter="handleQuery"
          class="!w-240px"
        />
      </el-form-item>
      <el-form-item label="余热标识" prop="ttbQyyrYrbs">
        <el-input
          v-model="queryParams.ttbQyyrYrbs"
          placeholder="请输入余热标识"
          clearable
          @keyup.enter="handleQuery"
          class="!w-240px"
        />
      </el-form-item>
      <el-form-item label="余热资源类型" prop="ttbQyyrYrzylx">
        <el-input
          v-model="queryParams.ttbQyyrYrzylx"
          placeholder="请输入余热资源类型"
          clearable
          @keyup.enter="handleQuery"
          class="!w-240px"
        />
      </el-form-item>
      <el-form-item label="填报日期" prop="ttbTbDate">
        <el-date-picker
          v-model="queryParams.ttbTbDate"
          value-format="YYYY-MM-DD HH:mm:ss"
          type="daterange"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
          class="!w-240px"
        />
      </el-form-item>
      <el-form-item label="创建时间" prop="createTime">
        <el-date-picker
          v-model="queryParams.createTime"
          value-format="YYYY-MM-DD HH:mm:ss"
          type="daterange"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
          class="!w-240px"
        />
      </el-form-item>
      <el-form-item>
        <el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
        <el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
        <el-button
          type="primary"
          plain
          @click="openForm('create')"
          v-hasPermi="['system:tb-qyyr-yrzyjpljlb:create']"
        >
          <Icon icon="ep:plus" class="mr-5px" /> 新增
        </el-button>
        <el-button
          type="warning"
          plain
          @click="handleImport"
        >
          <Icon icon="ep:upload" /> 导入
        </el-button>
        <el-button
          type="success"
          plain
          @click="handleExport"
          :loading="exportLoading"
          v-hasPermi="['system:tb-qyyr-yrzyjpljlb:export']"
        >
          <Icon icon="ep:download" class="mr-5px" /> 导出
        </el-button>
        <el-button  type="primary" plain @click="examine">
          提交审核
        </el-button>
      </el-form-item>
    </el-form>
  </ContentWrap>
  <!-- 列表 -->
  <ContentWrap>
    <el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
      <el-table-column
        label="序号"
        type="index"
        header-align="center"
        align="center"
        width="60px"
        fixed
      />
      <el-table-column type="selection" :selectable="selectable" width="55" />
      <el-table-column label="企业名称" align="center" prop="ttbQyyrQyname" />
      <el-table-column label="余热标识" align="center" prop="ttbQyyrYrbs" />
      <el-table-column label="余热资源类型" align="center" prop="ttbQyyrYrzylx" width="120px"/>
      <el-table-column label="余热资源等级" align="center" prop="ttbQyyrYrzydj" width="120px"/>
      <el-table-column label="可回收余热资源量" align="center" prop="ttbQyyrKhsyrzyl" width="140px"/>
      <el-table-column label="余热资源回收率" align="center" prop="ttbQyyrYrzyhsl" width="140px"/>
      <el-table-column label="余热资源减排量" align="center" prop="ttbQyyrYrzyjpl" width="140px"/>
      <el-table-column label="余热资源回收总量" align="center" prop="ttbQyyrYrzyhszl" width="140px"/>
      <el-table-column label="备注" align="center" prop="ttbQyyrBz" />
      <el-table-column
        label="填报日期"
        align="center"
        prop="createTime"
        :formatter="dateFormatter2"
        width="180px"
      />
      <el-table-column
        label="创建时间"
        align="center"
        prop="createTime"
        :formatter="dateFormatter"
        width="180px"
      />
      <el-table-column label="操作" align="center" width="200px">
        <template #default="scope">
          <el-tag type="primary" v-if = "scope.row.status ==1" >草稿  </el-tag>
          <el-tag type="success" v-if = "scope.row.status ==3" >审批通过</el-tag>
          <el-tag type="danger" v-if = "scope.row.status ==4" >审批驳回</el-tag>
          <el-tag type="warning" v-if = "scope.row.status ==2" >审批中</el-tag>
          <el-button
            link
            type="primary"
            @click="openForm('update', scope.row.id)"
            v-if = "scope.row.status ==1 || scope.row.status ==4"
          >
            编辑
          </el-button>
          <el-button
            link
            type="danger"
            @click="handleDelete(scope.row.id)"
            v-if = "scope.row.status ==1 || scope.row.status ==4"
          >
            删除
          </el-button>
        </template>
      </el-table-column>
    </el-table>
    <!-- 分页 -->
    <Pagination
      :total="total"
      v-model:page="queryParams.pageNo"
      v-model:limit="queryParams.pageSize"
      @pagination="getList"
    />
  </ContentWrap>
  <!-- 表单弹窗:添加/修改 -->
  <TbQyyrYrzyjpljlbForm ref="formRef" @success="getList" />
   <!-- 对象导入对话框 -->
   <ImportForm ref="importFormRef" @success="getList" />
  <Examine ref="examineFormRef" @success="getList" />
</template>
<script setup lang="ts">
import { dateFormatter,dateFormatter2 } from '@/utils/formatTime'
import download from '@/utils/download'
import { TbQyyrYrzyjpljlbApi, TbQyyrYrzyjpljlbVO } from '@/api/system/tbqyyryrzyjpljlb'
import TbQyyrYrzyjpljlbForm from './TbQyyrYrzyjpljlbForm.vue'
import TbQyyrJcdbForm from './TbQyyrJcdbForm.vue'
import { getIntDictOptions, DICT_TYPE, getDictLabel } from '@/utils/dict'
import Examine from './examine.vue'
import ImportForm from './ImportForm.vue'
/** 余热资源减排量记录 列表 */
defineOptions({ name: 'TbQyyrYrzyjpljlb' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
const loading = ref(true) // 列表的加载中
const list = ref<TbQyyrYrzyjpljlbVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const queryParams = reactive({
  pageNo: 1,
  pageSize: 10,
  ttbQyyrQyname: undefined,
  ttbQyyrYrbs: undefined,
  ttbQyyrYrzylx: undefined,
  ttbQyyrYrzydj: undefined,
  ttbQyyrKhsyrzyl: undefined,
  ttbQyyrYrzyhsl: undefined,
  ttbQyyrYrzyjpl: undefined,
  ttbQyyrYrzyhszl: undefined,
  ttbQyyrBz: undefined,
  ttbTbDate: [],
  createTime: [],
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
/** 查询列表 */
const getList = async () => {
  loading.value = true
  try {
    const data = await TbQyyrYrzyjpljlbApi.getTbQyyrYrzyjpljlbPage(queryParams)
    list.value = data.list
    total.value = data.total
  } finally {
    loading.value = false
  }
}
/** 搜索按钮操作 */
const handleQuery = () => {
  queryParams.pageNo = 1
  getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
  queryFormRef.value.resetFields()
  handleQuery()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
  formRef.value.open(type, id)
}
/** 删除按钮操作 */
const handleDelete = async (id: number) => {
  try {
    // 删除的二次确认
    await message.delConfirm()
    // 发起删除
    await TbQyyrYrzyjpljlbApi.deleteTbQyyrYrzyjpljlb(id)
    message.success(t('common.delSuccess'))
    // 刷新列表
    await getList()
  } catch {}
}
/** 导出按钮操作 */
const handleExport = async () => {
  try {
    // 导出的二次确认
    await message.exportConfirm()
    // 发起导出
    exportLoading.value = true
    const data = await TbQyyrYrzyjpljlbApi.exportTbQyyrYrzyjpljlb(queryParams)
    download.excel(data, '余热资源减排量记录.xls')
  } catch {
  } finally {
    exportLoading.value = false
  }
}
const importFormRef = ref()
const handleImport = () => {
  importFormRef.value.open()
}
const selectList = ref<TbQyyrYrzyjpljlbVO[]>([])
const handleSelectionChange = (val: TbQyyrYrzyjpljlbVO[]) => {
  selectList.value = val
}
const examineFormRef = ref()
const examine =() => {
    if(selectList.value.length > 0)
    {
      examineFormRef.value.open(selectList.value)
    }else{
      message.warning('请选择审核的数据')
    }
}
const selectable = (row: TbQyyrYrzyjpljlbVO) => ![2,3].includes(row.status)
/** 初始化 **/
onMounted(() => {
  getList()
})
</script>

image.gif

examine提交页:

<template>
  <Dialog title="提交审核" v-model="dialogVisible" width="60%">
    <el-table :data="selectList2"  >
      <el-table-column
        label="序号"
        type="index"
        header-align="center"
        align="center"
        width="60px"
        fixed
      />
      <el-table-column label="企业名称" align="center" prop="ttbQyyrQyname" />
      <el-table-column label="余热标识" align="center" prop="ttbQyyrYrbs" />
      <el-table-column label="余热资源类型" align="center" prop="ttbQyyrYrzylx" width="120px"/>
      <el-table-column label="余热资源等级" align="center" prop="ttbQyyrYrzydj" width="120px"/>
      <el-table-column label="可回收余热资源量" align="center" prop="ttbQyyrKhsyrzyl" width="140px"/>
      <el-table-column label="余热资源回收率" align="center" prop="ttbQyyrYrzyhsl" width="140px"/>
      <el-table-column label="余热资源减排量" align="center" prop="ttbQyyrYrzyjpl" width="140px"/>
      <el-table-column label="余热资源回收总量" align="center" prop="ttbQyyrYrzyhszl" width="140px"/>
      <el-table-column label="备注" align="center" prop="ttbQyyrBz" />
      <el-table-column
        label="填报日期"
        align="center"
        prop="createTime"
        :formatter="dateFormatter2"
        width="180px"
      />
      <el-table-column
        label="创建时间"
        align="center"
        prop="createTime"
        :formatter="dateFormatter"
        width="180px"
      />
      <el-table-column fixed="right" label="操作" align="center" width="120">
            <template #default="scope">
              <el-button
                @click.prevent="deleteRow(scope.$index, selectList2)"
                type="text"
                size="small"
              >
                删除
              </el-button>
            </template>
          </el-table-column>
  </el-table>
  <template #footer>
      <el-button @click="examine" type="primary" >提交审核</el-button>
      <el-button @click="closeDialong">取 消</el-button>
    </template> 
    </Dialog>
</template>
<script lang="ts" setup>
import { dateFormatter,dateFormatter2 } from '@/utils/formatTime'
import { getIntDictOptions, DICT_TYPE, getDictLabel } from '@/utils/dict'
import { TbQyyrYrzyjpljlbApi, TbQyyrYrzyjpljlbVO } from '@/api/system/tbqyyryrzyjpljlb'
const { t } = useI18n() // 国际化
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const selectList2 = ref<TbQyyrYrzyjpljlbVO[]>([])
/** 打开弹窗 */
const open = async ( selectList: TbQyyrYrzyjpljlbVO[]) => {
  dialogVisible.value = true
  selectList2.value = []
  selectList2.value = selectList
}
const deleteRow = (index, rows) => {
  rows.splice(index, 1)
}
const examine = async () => {
    if(selectList2.value.length > 0)
    {
      await message.confirm( t('你确定要提交审核吗?'))
        await TbQyyrYrzyjpljlbApi.examine(selectList2.value)
        message.success('已提交审核,静等相关人员处理')
        emit('success')
        dialogVisible.value = false
    }else{
      message.warning('请选择审核的数据')
    }
}
const closeDialong = () => {
  dialogVisible.value = false
  emit('success')
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
</script>

image.gif

现在让我们试一下能不能审核成功

image.gif 编辑

选中后提交审核

image.gif 编辑

提交审核成功,状态变为已审核

image.gif 编辑

同时在待办任务里面能看见这一条提交审核记录

image.gif 编辑

点击审批

image.gif 编辑

通过之后,状态变为已通过

image.gif 编辑

这就大功告成了,如果有什么问题,可以和我交流,我的微信是:sxy0802000,谢谢大家浏览。


目录
相关文章
|
15天前
|
缓存 JavaScript UED
Vue3中v-model在处理自定义组件双向数据绑定时有哪些注意事项?
在使用`v-model`处理自定义组件双向数据绑定时,要仔细考虑各种因素,确保数据的准确传递和更新,同时提供良好的用户体验和代码可维护性。通过合理的设计和注意事项的遵循,能够更好地发挥`v-model`的优势,实现高效的双向数据绑定效果。
118 64
|
15天前
|
JavaScript 前端开发 API
Vue 3 中 v-model 与 Vue 2 中 v-model 的区别是什么?
总的来说,Vue 3 中的 `v-model` 在灵活性、与组合式 API 的结合、对自定义组件的支持等方面都有了明显的提升和改进,使其更适应现代前端开发的需求和趋势。但需要注意的是,在迁移过程中可能需要对一些代码进行调整和适配。
|
15天前
|
前端开发 JavaScript 测试技术
Vue3中v-model在处理自定义组件双向数据绑定时,如何避免循环引用?
Web 组件化是一种有效的开发方法,可以提高项目的质量、效率和可维护性。在实际项目中,要结合项目的具体情况,合理应用 Web 组件化的理念和技术,实现项目的成功实施和交付。通过不断地探索和实践,将 Web 组件化的优势充分发挥出来,为前端开发领域的发展做出贡献。
26 8
|
14天前
|
存储 JavaScript 数据管理
除了provide/inject,Vue3中还有哪些方式可以避免v-model的循环引用?
需要注意的是,在实际开发中,应根据具体的项目需求和组件结构来选择合适的方式来避免`v-model`的循环引用。同时,要综合考虑代码的可读性、可维护性和性能等因素,以确保系统的稳定和高效运行。
19 1
|
14天前
|
JavaScript
Vue3中使用provide/inject来避免v-model的循环引用
`provide`和`inject`是 Vue 3 中非常有用的特性,在处理一些复杂的组件间通信问题时,可以提供一种灵活的解决方案。通过合理使用它们,可以帮助我们更好地避免`v-model`的循环引用问题,提高代码的质量和可维护性。
27 1
|
15天前
|
JavaScript
在 Vue 3 中,如何使用 v-model 来处理自定义组件的双向数据绑定?
需要注意的是,在实际开发中,根据具体的业务需求和组件设计,可能需要对上述步骤进行适当的调整和优化,以确保双向数据绑定的正确性和稳定性。同时,深入理解 Vue 3 的响应式机制和组件通信原理,将有助于更好地运用 `v-model` 实现自定义组件的双向数据绑定。
|
18天前
|
缓存 监控 JavaScript
Vue.js 框架下的性能优化策略与实践
Vue.js 框架下的性能优化策略与实践
|
23天前
|
JavaScript 索引
Vue 3.x 版本中双向数据绑定的底层实现有哪些变化
从Vue 2.x的`Object.defineProperty`到Vue 3.x的`Proxy`,实现了更高效的数据劫持与响应式处理。`Proxy`不仅能够代理整个对象,动态响应属性的增删,还优化了嵌套对象的处理和依赖追踪,减少了不必要的视图更新,提升了性能。同时,Vue 3.x对数组的响应式处理也更加灵活,简化了开发流程。
|
1月前
|
JavaScript 前端开发 开发者
Vue 3中的Proxy
【10月更文挑战第23天】Vue 3中的`Proxy`为响应式系统带来了更强大、更灵活的功能,解决了Vue 2中响应式系统的一些局限性,同时在性能方面也有一定的提升,为开发者提供了更好的开发体验和性能保障。
66 7
|
1月前
|
JavaScript 前端开发 开发者
前端框架对比:Vue.js与Angular的优劣分析与选择建议
【10月更文挑战第27天】在前端开发领域,Vue.js和Angular是两个备受瞩目的框架。本文对比了两者的优劣,Vue.js以轻量级和易上手著称,适合快速开发小型到中型项目;Angular则由Google支持,功能全面,适合大型企业级应用。选择时需考虑项目需求、团队熟悉度和长期维护等因素。
41 1