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

本文涉及的产品
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
应用实时监控服务-应用监控,每月50GB免费额度
容器服务 Serverless 版 ACK Serverless,317元额度 多规格
简介: 芋道框架审批流如何实现(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,谢谢大家浏览。


目录
相关文章
|
18天前
|
存储 JavaScript 前端开发
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
【10月更文挑战第21天】 vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
vue3的脚手架模板你真的了解吗?里面有很多值得我们学习的地方!
|
15天前
|
JavaScript 前端开发 开发者
Vue 3中的Proxy
【10月更文挑战第23天】Vue 3中的`Proxy`为响应式系统带来了更强大、更灵活的功能,解决了Vue 2中响应式系统的一些局限性,同时在性能方面也有一定的提升,为开发者提供了更好的开发体验和性能保障。
34 7
|
15天前
|
JavaScript 前端开发 开发者
前端框架对比:Vue.js与Angular的优劣分析与选择建议
【10月更文挑战第27天】在前端开发领域,Vue.js和Angular是两个备受瞩目的框架。本文对比了两者的优劣,Vue.js以轻量级和易上手著称,适合快速开发小型到中型项目;Angular则由Google支持,功能全面,适合大型企业级应用。选择时需考虑项目需求、团队熟悉度和长期维护等因素。
21 1
|
15天前
|
JavaScript 数据管理 Java
在 Vue 3 中使用 Proxy 实现数据双向绑定的性能如何?
【10月更文挑战第23天】Vue 3中使用Proxy实现数据双向绑定在多个方面都带来了性能的提升,从更高效的响应式追踪、更好的初始化性能、对数组操作的优化到更优的内存管理等,使得Vue 3在处理复杂的应用场景和大量数据时能够更加高效和稳定地运行。
36 1
|
15天前
|
JavaScript 开发者
在 Vue 3 中使用 Proxy 实现数据的双向绑定
【10月更文挑战第23天】Vue 3利用 `Proxy` 实现了数据的双向绑定,无论是使用内置的指令如 `v-model`,还是通过自定义事件或自定义指令,都能够方便地实现数据与视图之间的双向交互,满足不同场景下的开发需求。
36 1
|
16天前
|
JavaScript 前端开发 API
前端框架对比:Vue.js与Angular的优劣分析与选择建议
【10月更文挑战第26天】前端技术的飞速发展让开发者在构建用户界面时有了更多选择。本文对比了Vue.js和Angular两大框架,介绍了它们的特点和优劣,并给出了在实际项目中如何选择的建议。Vue.js轻量级、易上手,适合小型项目;Angular结构化、功能强大,适合大型项目。
15 1
|
17天前
|
前端开发 JavaScript
简记 Vue3(一)—— setup、ref、reactive、toRefs、toRef
简记 Vue3(一)—— setup、ref、reactive、toRefs、toRef
|
18天前
Vue3 项目的 setup 函数
【10月更文挑战第23天】setup` 函数是 Vue3 中非常重要的一个概念,掌握它的使用方法对于开发高效、灵活的 Vue3 组件至关重要。通过不断的实践和探索,你将能够更好地利用 `setup` 函数来构建优秀的 Vue3 项目。
|
21天前
|
JavaScript Java API
vue3知识点:setup
vue3知识点:setup
27 5
|
21天前
|
资源调度 JavaScript 前端开发
vue3第一章基础:创建Vue3.0工程,包括使用vue-cli 创建、使用 vite 创建
vue3第一章基础:创建Vue3.0工程,包括使用vue-cli 创建、使用 vite 创建
21 5