使用vuedraggable实现拖拽式操作实战

news2024/11/18 23:35:26

背景

先说下需求,为了提升开发效率和降低开发成本和技术难度,采用低代码方式,通过配置来生成代码,算是代码生成器的升级版解决方案。

对于前端页面,某个业务实体,如系统参数,常见的菜单对应一个列表页面,顶部为页面级功能按钮,如新增、删除、导出等,中间为查询区域,可以放几个常用的查询条件,最下面则是查询结果,以表格形式展现行列数据。此外,为了方便用户操作,往往查询结果表格的行记录,最后一列放一些针对于该行数据的快捷按钮,如删除、编辑等,如下图所示

在这里插入图片描述

注:上图中将页面级功能按钮放到了查询区域与查询结果之间。

要实现上面页面的配置,需要进行抽象,划分出四个可配置的区域,即页面按钮、查询条件、查询列表、行按钮,效果图如下:

在这里插入图片描述

技术方案上,通过配置,结合模板技术,实现列表视图页面前端代码的自动化生成。

上面这个配置功能,一方面,涉及到元素的排序,如按钮的次序、查询条件的次序、查询结果中列的次序;另一方面,涉及到列表间元素的移动,如将实体属性添加到查询列表或查询结果中。如采用传统模式,需要选中某个元素,点击左移、右移等按钮,既不直观,操作也繁琐。而采用拖拽式操作,所见即所得,则用户体验大幅提升。

Element UI 提供了大部分UI控件,但对于拖拽,确实是个短板,于是就需要额外的控件来补充了,即今天的主角vuedraggable。

简介

Draggable为基于Sortable.js的vue组件,用以实现拖拽功能。

特性

  • 支持触摸设备
  • 支持拖拽和选择文本
  • 支持智能滚动
  • 支持不同列表之间的拖拽
  • 不以jQuery为基础
  • 和视图模型同步刷新
  • 和vue2的过渡动画兼容
  • 支持撤销操作
  • 当需要完全控制时,可以抛出所有变化
  • 可以和现有的UI组件兼容

官方地址:https://github.com/SortableJS/Vue.Draggable
中文版文档:https://www.itxst.com/vue-draggable/tutorial.html

安装

npm install vuedraggable -S

引用

import Draggable from ‘vuedraggable’

实战

接下来我会从实战角度介绍如何实现我们的功能,用到的关键属性和方法,需要注意的事项,坑点以及解决方案。

拖动排序

以页面级功能按钮为例,实现效果如下:
在这里插入图片描述

前端源码如下:

<template>
  <el-row :gutter="20" type="flex">
    <el-col :span="24">
      <el-card class="box-card">
        <div slot="header" class="clearfix">
          <span>页面按钮配置</span>
          <span style="float:right">
            <el-button style=" padding: 1px 0" icon="el-icon-plus" @click="add" />
            <el-button style=" padding: 1px 0" icon="el-icon-delete" @click="clear" />
          </span>
        </div>
        <draggable v-model="buttonList" :group="group" @sort="updateSort" @update="update">

          <el-row v-for="(item) in buttonList" :key="item.code">
            <el-col>
              <el-tag closable @close="remove(item.id)" @click="modify(item.id)"> {{ item.name }}</el-tag>

            </el-col>
          </el-row>

        </draggable>

        <detail ref="detail" :custom-parent-id="this.$route.query.id" :button-type="$constant.BUTTON_TYPE_PAGE" @ok="handleDetailSave" />
      </el-card>
    </el-col>

  </el-row>

</template>

<script>
import Detail from './detail'
import Draggable from 'vuedraggable'
export default {
  components: { Detail, Draggable },
  data() {
    return {
      entityViewId: '',
      buttonList: [],
      group: {
        name: 'pageButton',
        pull: false,
        put: false
      }
    }
  },
  mounted() {
    this.init()
  },
  methods: {
    // 初始化
    init() {
      this.entityViewId = this.$route.query.id
      this.query()
    },
    // 新增
    add() {
      this.$refs.detail.add()
    },
    // 修改
    modify(id) {
      this.$refs.detail.modify(id)
    },
    // 移除
    remove(id) {
      this.$confirm('此操作将移除页面按钮, 是否继续?', '确认', {
        type: 'warning'
      }).then(() => {
        this.$api.entityconfig.entityViewButton.remove(id)
          .then(() => {
            this.query()
          })
      }).catch(() => {
        this.$message.info('已取消')
      })
    },
    // 清空
    clear(id) {
      this.$confirm('此操作将移除所有页面按钮,已配置信息丢失且不可恢复,是否继续?', '确认', {
        type: 'warning'
      }).then(() => {
        this.$api.entityconfig.entityViewButton.clear(id)
          .then(() => {
            this.query()
          })
      }).catch(() => {
        this.$message.info('已取消')
      })
    },

    // 加载按钮列表
    query() {
      this.$api.entityconfig.entityViewButton.listByViewAndType(this.entityViewId, this.$constant.BUTTON_TYPE_PAGE).then(res => {
        this.buttonList = res.data
      })
    },

    // 更新次序
    updateSort(evt) {
      evt.preventDefault()
      const sortedButtonList = this.buttonList.map(function (value, index) {
        return { 'index': index, 'code': value.code }
      })
      this.$api.entityconfig.entityViewButton.updateButtonSort(this.entityViewId, sortedButtonList)
    },
    handleDetailSave() {
      this.query()
    }
  }

}

</script>

<style>

</style>

这里组合了Element UI的多种控件,包括el-row、el-col、el-card、el-tag,与vuedraggable相关的核心代码如下:

 <draggable v-model="buttonList" :group="group" @update="updateSort">
    <el-row v-for="(item) in buttonList" :key="item.code">
      <el-col>
        <el-tag closable @close="remove(item.id)" @click="modify(item.id)"> {{ item.name }}</el-tag>
      </el-col>
    </el-row>
  </draggable>

关键属性,就是v-model,绑定了一个对象数组,也就是组件的数据源,这是vue的标准作法,没什么好说的。
至于group属性,如果单看这一个组件实际可以不用设置,但全局来看不设置会有坑点,后面再说,这里可以忽略。

组件跟排序相关的事件有两个,一个是update,一个是sort,我们应该用哪个呢?
从官方描述很难看出差异:update:拖拽变换位置时触发的事件;sort:位置变化时的事件。
自己测试了一下,在当前这个场景下,拖拽元素改变次序,两个事件都能触发。数据源数组新增或删除元素时,两个事件都不触发,在这个场景下,用哪个都行,这里选用了update事件。

  // 更新次序
    updateSort(evt) {
      evt.preventDefault()
      const sortedButtonList = this.buttonList.map(function (value, index) {
        return { 'index': index, 'code': value.code }
      })
      this.$api.entityconfig.entityViewButton.updateButtonSort(this.entityViewId, sortedButtonList)
    },

evt.preventDefault()的作用是防止某些浏览器,如firefox,将拖动视为下载。
vuedraggable干的是前端的排序的活,最终还是需要传到后端处理的。因此后面几行是将唯一性的数据编码和当前索引值,处理成对象数组,传到后端,由后端更新排序号,从而将次序调整持久化。

列表间拖动

实现效果图如下:
在这里插入图片描述

这实际上是三个vuedraggable组件的组合,最外围是下面这个样子:

<template>
  <el-row :gutter="20" type="flex">
    <el-col :span="5"> <page-button /></el-col>
    <el-col :span="5"> <property-list @getAllPropertyData="getAllPropertyData" @refreshQueryCondition="refreshQueryCondition" @refreshQueryResult="refreshQueryResult" /></el-col>
    <el-col :span="9">
      <el-row :gutter="20" type="flex">
        <el-col :span="24">  <query-area ref="queryCondition" :property-list-data="allPropertyList" /></el-col>
      </el-row>
      <el-row :gutter="20" type="flex">
        <el-col :span="24">  <result-area ref="queryResult" :property-list-data="allPropertyList" /></el-col>
      </el-row>
    </el-col>
    <el-col :span="5"> <row-button /></el-col>
  </el-row>

</template>

每个区域拆成了一个独立的vue页面,开始的时候还担心,这种跨页面列表间的元素移动,有可能不支持,实际测了下,没有问题,这点很好。

按照官方文档说明,如果要在列表间拖动,则需要设置group属性,group可以是一个单属性,也可以是一个对象,该对象的name属性需要相同。

//设置方式一,直接设置组名
group:'list'

//设置方式二,object,也可以通过自定义函数function实现复杂的逻辑
group:{
    name:'list',
    pull: true|false| 'clone'|array|function,//是否允许拖出当前组
    put:true|false|array|function,//是否允许拖入当前组
}

在我的设计中,实体属性列表是从实体配置中读取出来的,既可以添加到右上方的查询条件列表,又可以添加到右下方的查询结果列表,因此设置如下:

allPropertyGroup: {
    name: 'list',
    pull: 'clone',
    put: false
  }

name是组名,put为false是禁止通过拖拽方式添加元素。
pull属性值需要注意,在当前场景下,需要设置为clone,即拷贝,意思是将当前元素拷贝一份,放到目的地列表,如果设置为true,相当于移动,如果设置为false,相当于禁止拖出当前列表。

查询条件和查询结果本质上一样的,我这边以查询条件为例说明,如果接收从左侧实体属性列表拖动过来的元素。

<draggable v-model="queryConditionList" :group="group" style="height:200px" @add="addFromModelProperty" @update="updateSort">
          <el-row v-for="(item) in queryConditionList" :key="item.code">
            <el-col> <el-tag closable @close="remove(item.id)" @click="modify(item.id)">{{ item.name }}</el-tag>
            </el-col>
          </el-row>
        </draggable>

首先,是设置group属性,名字保持与左侧列表一致,都叫list,pull设置为false,禁止拖出当前列表,而put这时候要给true,即接受拖入。

group: {
        name: 'list',
        pull: false,
        put: true
      }

然后关键的事件是add,这是拖入后触发的事件,需要注意的是,这个事件,携带的evt参数虽然很庞大,但里面放的东西,是前端ui元素,而不是我们期望的数据。例如,我希望拿到实体属性的编码,通过这个编码,找到实体属性,然后拷贝其他相关数据,插入到查询列表的库表中去,从evt参数中,拿不到这些信息。
这时候,就通过曲线救国的方式来实现了。从evt参数中,可以拿到拖动元素的来源列表处的索引oldIndex,这样结合来源列表的绑定的数据对象,就能拿到我们期望的数据了。

// 新增
    addFromModelProperty(evt) {
      const code = this.propertyListData[evt.oldIndex].code
      this.$api.entityconfig.viewQueryCondition.addFromModelProperty(this.entityViewId, code)
        .then(() => this.sort())
    },

在这个场景下,就能测试出sort事件和update事件的差别来了,如果是从别的列表拖动元素过来,则只会触发sort事件,不会触发update。也就是说,update事件仅在列表内部拖动改变次序时才会触发,而sort在拖入元素时也会触发。

既然sort触发的时机更多,那我这边为什么没有使用sort而依然是使用update呢?
因为sort在我们这个场景下是有问题的,我们是左侧属性列表拖动实体属性到右侧的查询条件,逻辑处理是先根据编码,拷贝属性在查询条件中新建一行记录。而sort事件会在拖动对象一放下就触发,然后传入当前的属性列表和索引值,而这时候,数据插入动作尚未完成,新拖入的元素去更新排序号会报对象不存在的异常。
如何解决呢?其实也简单,我们不使用sort,一方面仍然使用update来处理列表内部拖动排序,另一方面在通过拖动新增元素的add事件中,通过promise函数,在数据插入处理成功后,再调用一次更新次序的后端操作。

// 新增
    addFromModelProperty(evt) {
      const code = this.propertyListData[evt.oldIndex].code
      this.$api.entityconfig.viewQueryCondition.addFromModelProperty(this.entityViewId, code)
        .then(() => this.sort())
        }
    },

    // 拖拽结束
    updateSort(evt) {
      evt.preventDefault()
      this.sort()
    },
    // 排序
    sort() {
      const sortedList = this.queryConditionList.map(function (value, index) {
        return { 'index': index, 'code': value.code }
      })
      this.$api.entityconfig.viewQueryCondition.updateSort(this.entityViewId, sortedList)
    },

附上查询条件区域的完整源码供参考

<template>

  <el-row :gutter="20">
    <el-col :span="24">
      <el-card class="box-card">
        <div slot="header" class="clearfix">
          <span>查询条件</span>
          <span style="float:right">
            <el-button style=" padding: 1px 0" icon="el-icon-plus" @click="add" />
            <el-button style=" padding: 1px 0" icon="el-icon-delete" @click="clear" />

          </span>
        </div>
        <draggable v-model="queryConditionList" :group="group" style="height:200px" @add="addFromModelProperty" @update="updateSort">
          <el-row v-for="(item) in queryConditionList" :key="item.code">
            <el-col> <el-tag closable @close="remove(item.id)" @click="modify(item.id)">{{ item.name }}</el-tag>
            </el-col>
          </el-row>
        </draggable>
      </el-card>
      <detail ref="detail" :custom-parent-id="this.$route.query.id" @ok="handleDetailSave" />
    </el-col>

  </el-row>

</template>

<script>
import Detail from './detail'
import Draggable from 'vuedraggable'

export default {
  components: { Detail, Draggable },
  props: {
    propertyListData: {
      type: Array,
      default: () => [],
      required: false

    }},
  data() {
    return {
      entityViewId: '',
      queryConditionList: [],
      currentConditionId: '',
      group: {
        name: 'list',
        pull: false,
        put: true
      }
    }
  },
  mounted() {
    this.init()
  },
  methods: {
    // 初始化
    init() {
      this.entityViewId = this.$route.query.id

      this.query()
    },
    // 新增
    add() {
      this.$refs.detail.add()
    },
    // 新增
    addFromModelProperty(evt) {
      const code = this.propertyListData[evt.oldIndex].code
      this.$api.entityconfig.viewQueryCondition.addFromModelProperty(this.entityViewId, code)
        .then(() => this.sort())
    },
    // 修改
    modify(id) {
      this.currentConditionId = id
      this.$refs.detail.modify(this.currentConditionId)
    },
    // 移除
    remove(queryConditionId) {
      this.$confirm('此操作将移除查询条件, 是否继续?', '确认', {
        type: 'warning'
      }).then(() => {
        this.$api.entityconfig.viewQueryCondition.remove(queryConditionId)
          .then(() => {
            this.query()
          })
      }).catch(() => {
        this.$message.info('已取消')
      })
    },

    // 加载列表
    query() {
      this.$api.entityconfig.viewQueryCondition.listByView(this.entityViewId).then(res => {
        this.queryConditionList = res.data
      })
    },
    // 拖拽结束
    updateSort(evt) {
      evt.preventDefault()
      this.sort()
    },
    // 排序
    sort() {
      const sortedList = this.queryConditionList.map(function (value, index) {
        return { 'index': index, 'code': value.code }
      })
      this.$api.entityconfig.viewQueryCondition.updateSort(this.entityViewId, sortedList)
    },
    // 清空
    clear() {
      this.$confirm('此操作将移除所有查询条件,已配置信息丢失且不可恢复,是否继续?', '确认', {
        type: 'warning'
      }).then(() => {
        this.$api.entityconfig.viewQueryCondition.clear(this.entityViewId)
          .then(() => {
            this.query()
          })
      }).catch(() => {
        this.$message.info('已取消')
      })
    },
    handleDetailSave() {
      this.query()
    }
  }

}

</script>

<style>
.el-row {
    margin-bottom: 10px;
  }

</style>

接下来,面临的一个问题,如何处理属性重复添加问题。
vuedraggable只要拖放,立马就能看到效果,例如,从左侧实体属性列表,拖放到右侧查询条件。但在这个场景下,实际上,需要判断下右侧属性列表是否已存在,如不存在,则允许添加,如存在,则不再添加。后端的验证处理是小case,就不在这里多说了,关键是前端该怎么处理。

看了半天官方文档,没找到合适的控制,又百度了半天,也没有满意的结果,自行摸索,终于发现move事件可以用。实际上,官方对于move的定位,是用来自定义控制那些元素可以拖拽或不允许拖拽并控制是否允许停靠的。我们这里就是希望控制是否允许停靠。

//move回调方法
onMove(e,originalEvent){ 
         console.log(e);
         console.log(originalEvent);
         //false表示阻止拖拽
         return true;
  },  

首先,一个坑点是,move虽然是事件,但不是我们常用的,用@move='move’来触发,而是使用了属性绑定的方式,用:move=‘move’,这点有点反常理,我就卡了一会,发现事件不触发,详细看文档才发现问题在这。

其次,在我们这个场景下,move属性,绑在左侧实体属性组件,还是右侧查询条件组件上,从是否允许停靠描述看,好像应该放到右侧,但实际测试发现,从左侧拖到右侧,根本就不触发,因此这个move,实际是针对源列表而言的。

再次,实际测试发现,存在触发多次问题,怀疑跟前端的事件冒泡机制有关,摸索了半天,也没找到只触发一次的办法。如有人填过这个坑,烦请评论中告知处理方式,先行谢过。

// 移动
    move(e) {
      // TODO 存在触发多次问题    
      const code = e.draggedContext.element.code
      const list = e.relatedContext.list
      const exist = list.some(item => { return item.code === code })
      // if (exist) {
      //   this.$message.info('已存在,请勿重复添加')
      // }


      return !exist
    }

这个事件比较不错的是,期望的数据都能从事件参数中拿到,比如e.draggedContext.element,可以拿到拖动元素绑定的数据对象, e.relatedContext.list可以拿到目标列表的数据,二者做个简单对比,就能得出是否属于重复添加,如是,方法返回false,即可终止拖动这个动作。

最后,再说一个意料之外的坑,按照官方文档描述,不同列表间拖动,需要设置group属性,并保证name属性一致,但是……在测试过程中,无意中发现,我居然能把页面级按钮,拖放到查询条件列表中,明明组名不同……后来,不得不给页面按钮的组件,打了个补丁,限制其元素拖出列表来解决这个问题。

group: {
        name: 'pageButton',
        pull: false,
        put: false
      }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/62986.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Memcached 未授权访问漏洞验证

Memcached 是一个高性能的分布式内存对象缓存系统&#xff0c;用于动态Web应用以减轻数据库负载。 该服务的端号为&#xff1a;11211 使用绿盟漏洞扫描系统发现某服务器存在Memcached 未授权访问漏洞&#xff0c;如下图所示。 验证方法一&#xff1a; 使用nmap 脚本script-…

ADSP-21489的开发详解:Norflash的编程和烧写

编者的话 Flash 编程与烧写&#xff0c;原本应该是开发的最后一步&#xff0c;当所有程序都做好了&#xff0c;在线编译运行正常&#xff0c;才会通过 Flash 编程&#xff0c;生成二进制的可执行文件 LDR&#xff0c;再通过 JTAG 仿真器将 LDR 文件烧写到 Flash 中&#xff0c…

初探 Vue3 新特性

在本讲中&#xff0c;我们只会概述性的来介绍一下 Vue3 中新增的一些主要内容&#xff0c;而这些主要知识点的详细的使用和功能以及作用&#xff0c;我们会在后面的课程里面逐渐的给大家去解锁。那么明确好了我们的学习目标之后呢&#xff1f; 我们来看一下 Vue3 之中到底新增…

Docker---Docker-compose 安装部署 zentao 禅道

Docker-compose 安装部署 zentao 禅道 目录Docker-compose 安装部署 zentao 禅道一、环境准备1.更换国内源2.拉取zentao的docker镜像3.关闭防火墙4.安装启动docker、docker-compose二、通过yml文件进行部署一、环境准备 1.更换国内源 CentOS7 Base源&#xff1a; wget -O /et…

技术男的春天:小姐姐求助暖男分析

时光荏苒&#xff0c;这个故事发生在4年前&#xff0c;那时候我头发可真厚&#xff0c;坐地铁也不用戴口罩。 小姐姐求助 友圈一位要做毕设的小姐姐在求助postman怎么用&#xff0c;我就帮她解答了一下。 我知道她并非计算机相关专业&#xff0c;所以很奇怪为什么要用postma…

web安全渗透

自己模拟的环境,要的私信 Web安全渗透 1、通过URL访问http://靶机IP/1,对该页面进行渗透测试,将完成后返回的结果内容作为flag值提交; 修改源码maxlength对应的数值,3+16=19输出的数值是两位数,然后修改完之输入

HTML+CSS+JS网页设计期末课程大作业—— 绿色化妆品HTML+CSS+JavaScript

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

Windows搭建web站点:为站点配置二级子域名 2-2

在上一篇文章《Windows搭建Web站点&#xff1a;免费内网穿透发布至公网 1/2》中&#xff0c;我们成功通过cpolar将内网web站点发布到公网可访问&#xff0c;但免费使用cpolar所生成的域名是随机变化的&#xff08;24小时后会变化&#xff09;&#xff0c;这只适合临时测试使用。…

Mysql SQL优化跟踪来看看是如何优化并决策使用哪个索引或者不适用索引

背景 使用索引字段进行筛选数据时&#xff0c;explain查询语句发现MySQL居然没有使用索引&#xff0c;产生疑问&#xff0c;因此决定调查清楚为什么会不用索引&#xff0c;而是走全表扫描 原因调查出的结果是当你要查询的数据量是连续又占整个表五分之一以上那就不会走索引了&a…

Java面试宝典.exe程序成功运行,经典 Java 万字笔记,查漏补缺,备战跳槽面试

现在 java 的面试真的卷到家了&#xff0c;面试前很有必要针对性的多刷题&#xff0c;大部分童鞋实战能力强&#xff0c;理论不行&#xff0c;面试前不做准备很吃亏。这里整理了很多常考面试题&#xff0c;希望对你有帮助。 耗时一个月&#xff0c;我把牛客网上最火的 Java 面…

ZMQ之共享键值缓存(克隆模式)

发布-订阅模式和无线电广播有些类似&#xff0c;在你收听之前发送的消息你将无从得知&#xff0c;收到消息的多少又会取决于你的接收能力。让人吃惊的是&#xff0c;对于那些追求完美的工程师来说&#xff0c;这种机器恰恰符合他们的需求&#xff0c;且广为传播&#xff0c;成为…

导出 txt文件 处理思路和实现流程

1.先看导出目标文件需要的字段都存不存在&#xff0c; 存在继续处理&#xff0c;不存在就添加。 例如&#xff0c;我这里需要在若依的用户表在添加一个银行账户数据&#xff0c; //银行卡号 private String accountNumber; public String getAccountNumber() { r…

Java程序员3个月从月薪6k涨到15k,你知道我是怎么过来的吗?

(一). 基础 1. Java 基本功 Java 入门&#xff08;基础概念与常识&#xff09; Java 语法 基本数据类型 方法&#xff08;函数&#xff09; 2. Java 面向对象 类和对象 面向对象三大特征 修饰符 接口和抽象类 其它重要知识点 3. Java 核心技术 集合 异常 多线程 文…

Python——协程(Coroutine),异步IO

目录 生成器(Generator) yield表达式的使用 生产者和消费者模型 ​编辑 yield from表达式 协程(Coroutine) asyncio.coroutine async/await 总结 由于GIL的存在&#xff0c;导致Python多线程性能甚至比单线程更糟。 于是出现了协程&#xff08;Coroutine&#xff09;这…

Arduino UNO通过PCF8574串行IIC接口驱动LCD1602/LCD2004液晶屏

LCD1602/2004液晶屏简介 LCD1602液晶显示器是广泛使用的一种字符型液晶显示模块。可以显示2行每行16个字符&#xff0c;总共32个字符。字符型液晶显示模块是一种专门用于显示字母、数字和符号等的点阵式LCD&#xff0c;常用161&#xff0c;162&#xff0c;202和402等的模块。不…

Go编程项目实战教程

Go编程项目实战教程 在这个面向初学者的 Go 编程语言课程中&#xff0c;您将通过构建 11 个项目来提高您的 Go 编程技能 课程英文名&#xff1a;Learn Go Programming by Building 11 Projects – Full Course 此视频教程共21.0小时&#xff0c;中英双语字幕&#xff0c;画质…

数据之道读书笔记-07打造“数字孪生”的数据全量感知能力

数据之道读书笔记-07打造“数字孪生”的数据全量感知能力 在信息化时代构建的IT系统&#xff0c;基本上是功能化、烟囱化、封闭式的&#xff0c;只能给企业内部经过培训的专业人员使用&#xff0c;所有的决策数据和我们信任的IT系统基本都是靠人来录入数据。但是&#xff0c;人…

Android移动开发基础——实训项目:个人财务软件

目录 步骤 1. 项目计划 需求分析 程序流程图 2. 实现功能模块 2.1 登录模块 前提 软件&#xff1a;Android Studio开发工具、JDK1.8以上版本 目标&#xff1a;编写个人财务软件 步骤 &#xff08;1&#xff09;根据设计题目要求的指标&#xff0c;通过查阅有关资料…

某学生宿舍楼设计

目 录 1.建筑设计部分 1 1.1工程概况 1 1.2设计依据 2 1.3标高及建筑细部作法 2 1.4平面设计 2 1.4.1建筑方案设计 2 1.4.2建筑做法 4 1.4.3 建筑设计成果 6 2.结构设计部分 7 2.1结构平面设计 7 2.2构件截面尺寸的初步确定 8 2.3荷载统计 10 2.3.1楼屋面及卫生间恒活计算 10 2…

热加载技术:修改Python代码并实时查看结果 ⛵

&#x1f4a1; 作者&#xff1a;韩信子ShowMeAI &#x1f4d8; Python3◉技能提升系列&#xff1a;https://www.showmeai.tech/tutorials/56 &#x1f4d8; 本文地址&#xff1a;https://www.showmeai.tech/article-detail/406 &#x1f4e2; 声明&#xff1a;版权所有&#xf…