Kotlin+MVVM 构建todo App 应用

news2024/11/17 23:24:28

作者:易科

项目介绍

使用Kotlin+MVVM实现的todo app,功能界面参考微软的Todo软件(只实现了核心功能,部分功能未实现)。

功能模块介绍

  1. 项目模块:添加/删除项目,项目负责管理todo任务
  2. 任务模块:添加/删除任务,标记任务完成情况,标记任务为重要,标记为我的一天,设置提醒时间(发送前台通知),设置过期时间。
  3. 搜索模块:依据任务名称模糊搜索。

效果截图

技术栈

  • Kotlin
  • ViewModel + LiveData + Room + AlarmManager + WorkerManager
  • navigation + DiaLog + 前台通知

功能设计与实现

1. 项目模块设计实现

在项目模块中,分为固定模块和自定义模块。其中固定模块分为以下几个模块:

  • 我的一天:可以查看当天需要完成的任务列表;
  • 重要:可以查看标记为重要的任务列表;
  • 计划内:( 未实现
  • 已分配:(未实现
  • 任务:可以查看未完成的所有任务列表;

而自定义项目模块是提供给用户来将任务归类到项目的功能。

项目模块主要显示:icon + 项目名称 + 包含的任务列表数量

(没啥好说的,简单的recyclerView实现即可

2. 任务列表页面的动态更新

点击项目进入项目后可创建任务,任务是由Recyclerview生成的,由于想要在任务添加/删除时出现列表滑动的效果,所以任务的apater实现了ListAdapter。

class TasksAdapter(val viewModel: TasksViewModel)
    : ListAdapter<Task, TasksAdapter.ViewHolder>(DIFF_CALLBACK) {
        //...
    }

2.1 任务列表页的操作

另外在搜索页面上也会用到跟任务列表一样的UI,所以将任务列表的UI用fragment实现,方便复用。

2.1.1 任务列表Fragment化
  • TasksFragment.kt
class TasksFragment: BaseFragment() {
​
    override fun getResourceId() = R.layout.fragment_task_list
​
    lateinit var taskViewModel : TasksViewModel
    private var projectId = 0L
    private var projectName = ""
    private var projectSign : ProjectSign? = null
​
    private lateinit var adapter: TasksAdapter
    private lateinit var taskRecyclerView: RecyclerView
​
    private var previousList : List<Task>? = null
    private lateinit var baseActivity: BaseTaskActivity
​
    // 搜索参数
    var searchName = ""
    var isSearchPage = false
​
    override fun initView(rootView: View) {
        // 判断当前fragment的Activty是哪个,方便做特殊操作
        baseActivity = if (activity is TasksMainActivity) {
            activity as TasksMainActivity
        }else {
            isSearchPage = true
            activity as SearchMainActivity
        }
        taskViewModel = ViewModelProvider(baseActivity)[TasksViewModel::class.java]
​
        projectId = baseActivity.intent.getLongExtra(Constants.PROJECT_ID, 0L)
        projectName = baseActivity.intent.getStringExtra(Constants.PROJECT_NAME).toString()
        val serializable = baseActivity.intent.getSerializableExtra(Constants.PROJECT_SIGN)
        if (serializable != null) {
            projectSign = serializable as ProjectSign
        }
​
        Log.d(Constants.TASK_PAGE_TAG, "projectId = $projectId, projectName= $projectName")
        refreshList("onCreate")
​
        adapter = TasksAdapter(taskViewModel)
        taskRecyclerView = rootView.findViewById(R.id.task_recycle_view)
        taskRecyclerView.layoutManager = LinearLayoutManager(baseActivity)
        taskRecyclerView.adapter = adapter
​
        // 下拉刷新
        val swipeRefreshTask: SwipeRefreshLayout = rootView.findViewById(R.id.swipe_refresh_task)
        swipeRefreshTask.setOnRefreshListener {
            refreshList("refresh")
            swipeRefreshTask.isRefreshing = false   // 取消刷新状态
        }
        
        override fun initEvent(rootView: View) {
            initClickListener()
​
            initObserve()
        }
    }
2.1.2 任务item操作的封装

对任务的item操作有三个点击事件,分别是标记完成、点击item进入详情页编辑、标记为重要。故构建出TaskItem用来封装item的三个操作

class TaskItem(private val nameText: MaterialTextView?,
               private val checkTaskBtn : ImageButton,
               private val setTaskStartBtn: ImageButton,
               val task: Task) {
​
    var nameTextEdit: EditText? = null
    var curTaskName : String? = null
    
    fun initItem() {
        flushItemUI()
    }
​
    fun initClickListener(viewModel: TasksViewModel) {
        // 标记完成按钮
        checkTaskBtn.setOnClickListener {
            val upState = if (task.state == TaskState.DONE) {
                TaskState.DOING
            } else  {
                Log.d(Constants.TASK_PAGE_TAG,"播放动画")
                TaskState.DONE
            }
            task.state = upState
            viewModel.updateTask(task)
            flushItemUI()
            Log.d(Constants.TASK_PAGE_TAG,"update task state id= ${task.id} state for $upState")
        }
        // 标记重要按钮
        setTaskStartBtn.setOnClickListener {
            val isStart = !FlagHelper.containsFlag(task.flag, Task.IS_START)
            if (isStart) {
                task.flag = FlagHelper.addFlag(task.flag, Task.IS_START)
            }else {
                task.flag = FlagHelper.removeFlag(task.flag,Task.IS_START)
            }
            viewModel.setStart(task.id, task.flag)
            updateStartUI()
            Log.d(Constants.TASK_PAGE_TAG,"update task start id= ${task.id} isStart for $isStart")
        }
    }
​
​
    fun flushItemUI() {
        updateNameUI()
        updateStartUI()
    }
​
    fun updateNameUI() {
        /**
         * 从task中获取到名字 或者从输入框获取到名字
         */
        if (curTaskName == null) {
            curTaskName = task.name
        }else {
            curTaskName = if (nameText?.visibility == View.VISIBLE) {
                nameText.text.toString()
            } else {
                nameTextEdit?.text.toString()
            }
        }
​
        /**
         * checkTaskBtn
         */
        var resId = R.drawable.ic_select
        if (task.state == TaskState.DONE) {
            val spannableString = SpannableString(curTaskName)
            spannableString.setSpan(
                StrikethroughSpan(),
                0,
                spannableString.length,
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
            )
            if (nameText?.visibility == View.VISIBLE) {
                nameText.text = spannableString
            } else {
                nameTextEdit?.setText(spannableString) /** 划线的效果 **/
            }
            resId = R.drawable.ic_select_check
        }else {
            if (nameText?.visibility == View.VISIBLE) {
                nameText.text = curTaskName
            } else {
                nameTextEdit?.setText(curTaskName)
            }
        }
        checkTaskBtn.setImageResource(resId)
    }
​
    fun updateStartUI() {
        var startResId = R.drawable.ic_shoucang
        if (FlagHelper.containsFlag(task.flag, Task.IS_START)) {
            startResId = R.drawable.ic_shoucang_check
        }
        setTaskStartBtn.setImageResource(startResId)
    }
}

3. 任务详情页的编辑操作

3.1 任务的状态设计

任务详情页主要的操作包括:任务item的操作(标记完成、修改任务名、标记为重要),标记为我的一天,任务提醒,添加截止日期,重复(未实现),添加附件(未实现) 等。

任务item的操作同样封装在上面的 TaskItem中,直接调用即可,无需再实现

这里的有几个标记功能,标记为我的一天,标记为重要。因为不想新增一个字段来表示0或1的存储,这里将这两个属性为归为同一个字段flag,用int存储,用不同的位来表示对应字段的值,如:

  • 当字段值为 1 时,说明标记为重要的;(01)
  • 当字段值为 2 时,说明标记为我的一天;(10)
  • 当字段值为 3 时,说明标记为重要的且是我的一天;(11)
/**
 * Flag 常量
 */
companion object {
    /** 设为重要的 **/
    const val IS_START = 1
    /** 设为我的一天 **/
    const val IN_ONE_DAY = 2
}

其实就是位运算的一种,用二进制的位来表示不同状态下的真或假。判断也就比较简单了,通过与,或运算即可:

object FlagHelper {
​
    /**
     * 添加标识
     */
    fun addFlag(flag: Int, newFlag : Int) : Int {
        return flag.or(newFlag)
    }
​
    /**
     * 移除标识
     */
    fun removeFlag(flag: Int, newFlag: Int) : Int {
        return flag.and(newFlag.inv())
    }
​
    /**
     * 判断是否包含该标识
     */
    fun containsFlag(flag: Int, checkFlag: Int) : Boolean {
        return flag.and(checkFlag) == checkFlag
    }
}

接下来只要用 FlagHelper.containsFlag(task.flag, Task.IN_ONE_DAY) 来判断该任务是否该状态,添加/删除也是同理调用该帮助类即可。

3.2 提醒功能的设计

3.2.1 UI设计

提醒功能的UI是这样的,日期和时间有对应的DiaLog实现,也有Picker实现,那么只需要通过Button点击切换两个UI即可实现。

我这里采用DiaLogFragment实现的,通过自定义的DtPickerDiaLogFragment 来管理Button与两个时间Picker组件。遇到的难点在于在两个时间Picker组件选择好时间后,该怎么跟DiaLogFrament做通信呢。这里使用了EventBus来做DiaLogFrament和两个时间picker组件对应的fragment做通信。实现如下:

  • 日期选择器:DatePickerFragment.kt
class DatePickerFragment : BaseFragment() {
​
    private lateinit var dp : DatePicker
    lateinit var localDate : LocalDate
​
    override fun getResourceId() = R.layout.fragment_datepicker
​
    override fun initView(rootView: View) {
        dp = rootView.findViewById(R.id.datePicker)
    }
​
    override fun initEvent(rootView: View) {
        /**
         * The month that was set (0-11) for compatibility with java.util.Calendar.
         */
       dp.setOnDateChangedListener { view, year, monthOfYear, dayOfMonth ->
           localDate = LocalDate.of(year, monthOfYear + 1, dayOfMonth)
           EventBus.getDefault().post(DateTimeMessage(localDate))
           findNavController().navigate(R.id.switchTime)
       }
    }
​
​
}
  • 时间选择器:TimePickerFragment.kt
class TimePickerFragment : BaseFragment() {
    override fun getResourceId() = R.layout.fragment_timepicker
​
    private lateinit var tp : TimePicker
    private lateinit var localTime: LocalTime
​
    override fun initView(rootView: View) {
        tp = rootView.findViewById(R.id.timePicker)
    }
​
    override fun initEvent(rootView: View) {
        tp.setOnTimeChangedListener { view, hourOfDay, minute ->
            localTime = LocalTime.of(hourOfDay,minute)
            EventBus.getDefault().post(DateTimeMessage(localTime))
        }
    }
}
  • 日期和时间的选择器弹窗:DtPickerDiaLogFragment.kt
class DtPickerDiaLogFragment(private val dateTimeClick: DateTimeClickListener) : DialogFragment() {
​
    private var chooseDate: LocalDate? = null
    private var chooseTime: LocalTime? = null
    private var chooseDateTime : LocalDateTime? = null
​
​
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
​
        Log.d("DtPickerDiaLogFragment","onCreateView")
        val curView = inflater.inflate(R.layout.dialog_datetime_picker, null)
​
        val navHostFragment : FragmentContainerView = curView.findViewById(R.id.fragment_container_view)
        val switchCalendar : Button = curView.findViewById(R.id.switchCalendar)
        val switchTime : Button = curView.findViewById(R.id.switchTime)
        val cancelDialog : TextView = curView.findViewById(R.id.cancelDialog)
        val saveDateTime : TextView = curView.findViewById(R.id.saveDateTime)
​
        switchCalendar.setOnClickListener {
            switchCalendar.setTextColor(ContextCompat.getColor(MyToDoApplication.context, R.color.light_blue))
            switchTime.setTextColor(ContextCompat.getColor(MyToDoApplication.context, R.color.gray))
            navHostFragment.findNavController().navigate(R.id.switchCalendar)
        }
​
        switchTime.setOnClickListener {
            switchCalendar.setTextColor(ContextCompat.getColor(MyToDoApplication.context, R.color.gray))
            switchTime.setTextColor(ContextCompat.getColor(MyToDoApplication.context, R.color.light_blue))
            navHostFragment.findNavController().navigate(R.id.switchTime)
        }
​
        cancelDialog.setOnClickListener {
            dialog?.dismiss()
        }
​
        saveDateTime.setOnClickListener {
            chooseDateTime = if (chooseDate == null && chooseTime == null) {
                LocalDateTime.now()
            }else if (chooseDate == null) {
                LocalDateTime.of(LocalDate.now(), chooseTime)
            }else if (chooseTime == null) {
                LocalDateTime.of(chooseDate, LocalTime.now())
            } else {
                LocalDateTime.of(chooseDate, chooseTime)
            }
            Log.d("","选中的时间为:$chooseDateTime")
            dateTimeClick.onSaveDateTimeClick(chooseDateTime!!)
            dialog?.dismiss()
        }
​
        // 注册
        EventBus.getDefault().register(this)
        return curView
    }
​
    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        initWindow()
    }
​
    override fun onDestroy() {
        EventBus.getDefault().unregister(this)  // 注销
        super.onDestroy()
    }
​
    fun initWindow() {
        val window = dialog?.window
        window?.attributes?.width = 800 // 单位px
        window?.attributes?.height = 1450 // 单位px
        window?.attributes?.gravity = Gravity.CENTER    // 居中
    }
​
    fun getChooseTime() = chooseDateTime
​
​
    @Subscribe(threadMode = ThreadMode.MAIN)
    fun receiveDateTime(dateTimeMessage: DateTimeMessage) {
        if (dateTimeMessage.localDate != null) {
            chooseDate = dateTimeMessage.localDate!!
        }
        if (dateTimeMessage.localTime != null) {
            chooseTime = dateTimeMessage.localTime!!
        }
        Log.d("","接收到event消息,chooseDate=$chooseDate,chooseTime=$chooseTime")
    }
​
}
3.2.2 提醒功能设计

提醒功能是采用WorkerManager + AlarmManager实现的,实现流程如下:、

  1. 当选择好时间保存后就会提交一次性的后台任务;
  2. Worker后台接收到任务后,检查提醒时间,没过期的话检查当前任务是否已经存在闹钟,有的话则取消;
  3. 使用AlarmManager设置闹钟,保存当前任务和闹钟id的关系,方便下一次设置时取消该闹钟;
  4. 保存下一个闹钟的提醒id,防止PendingIntent 的requestCode重复导致任务提醒失败。

实现如下:

class RemindWorker(context: Context, params: WorkerParameters) : Worker(context, params)  {
​
    companion object {
        val Tag = "RemindWorker"
    }
​
    private lateinit var alarmManager: AlarmManager
​
    @RequiresApi(Build.VERSION_CODES.S)
    override fun doWork(): Result {
        val taskByte = inputData.getByteArray(Constants.TASK_BYTE)
        val task = taskByte?.toObject() as Task
        val projectName = inputData.getString(Constants.PROJECT_NAME)
        alarmManager = applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager
​
        Log.d(Tag,"需要提醒的任务为:task=$task, projectName=$projectName")
        if (LocalDateTime.now().isBefore(task.remindTime)) { // 未执行,发起广播
            alarmTask(task, projectName)
        }
​
        return Result.success()
    }
​
    private fun alarmTask(task: Task, projectName: String?) {
        val bundle = Bundle()
        bundle.putByteArray(Constants.TASK, task.toByteArray())
        bundle.putString(Constants.PROJECT_NAME, projectName)
        val intent = Intent(applicationContext, RemindAlarmReceiver::class.java).apply {
            putExtras(bundle)
        }
        val oldAlarmId = Repository.getInteger4Broad(task.id.toString())   // 找到旧的请求id,如果有值的话说明需要重设,取消旧闹钟
        var pi : PendingIntent
        if (oldAlarmId != 0 && LocalDateTime.now().isAfter(task.remindTime)) {
            // 取消闹钟,重设
            pi = PendingIntent.getBroadcast(applicationContext, oldAlarmId, intent, 0)
            alarmManager.cancel(pi)
        }
        var alarmId = Repository.getInteger4Alarm(Constants.ALARM_ID, 0)
        pi = PendingIntent.getBroadcast(applicationContext, alarmId, intent, 0)
        val triggerAtMillis = task.remindTime!!.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli()
        alarmManager.set(AlarmManager.RTC_WAKEUP, triggerAtMillis , pi)
​
        Repository.setInteger4Broad(task.id.toString(), alarmId)
        Repository.setInteger4Alarm(Constants.ALARM_ID, ++alarmId)
        Log.d(Tag,
            "闹钟设置成功;taskName=${task.name};remindTime=${task.remindTime};;now=${System.currentTimeMillis()}"
        )
    }
​
}

闹钟时间到了后,通过broadcast广播。所以还需要用Recevier去接收,接收到广播后。发起前台通知即实现了任务提醒功能。

class RemindAlarmReceiver: BroadcastReceiver() {
​
    private val channelId = "remind"
    private val channelName = "任务提醒"
​
    override fun onReceive(context: Context, intent: Intent) {
        Log.d("RemindAlarmReceiver", "请求收到了.")
        val taskByteArray = intent.getByteArrayExtra(Constants.TASK)
        val task = taskByteArray?.toObject() as Task
        val projectName = intent.getStringExtra(Constants.PROJECT_NAME)
        Log.d("RemindAlarmReceiver","接收到任务 task=$task,projectName=$projectName")
        val manager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
        // 创建渠道
        // Android8.0 以上才有下面的API
        val channel = NotificationChannel(channelId, channelName, NotificationManager.IMPORTANCE_DEFAULT)
        manager.createNotificationChannel(channel)
        
        val intent = Intent(context, EditTaskActivity::class.java).apply {
                putExtra(Constants.TASK, task)
                putExtra(Constants.PROJECT_NAME, projectName)
                setPackage("com.example.mytodo")
          }
           val alarmId = Repository.getAndSet4Alarm(Constants.ALARM_ID, 0)
           val pi = PendingIntent.getActivity(context, alarmId, intent, PendingIntent.FLAG_IMMUTABLE)
           val notification = NotificationCompat.Builder(context, channelId)   // 必须传入已经创建好的渠道ID
                .setContentTitle("提醒")
                .setContentText(task.name)
                .setSmallIcon(R.drawable.todo)
                .setColor(Color.BLUE)
                .setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL)
                .setContentIntent(pi)       // 设置内容点击的Intent
                .setAutoCancel(true)        // 点击后自动关闭
                .build()
​
           manager.notify(1, notification)
           Log.d("RemindAlarmReceiver", "通知发送成功;task=$task")
    }
}

搜索功能

搜索功能的UI跟任务列表的UI大体相似,只是多了一个搜索栏。前面将任务列表fragment化了,直接复用。

实现简单就不说了。

最后

这是本人在学完Android后搞得第一个练手项目,其中很多编码方式不一定规范,有些功能也未实现(如帐号管理,任务云同步,项目可移动,任务可重新分组,任务可细分步骤等功能)。

想说下Kotlin真的好用hh(比起Java),比如扩展函数的特性。在这个app开发中,有个功能是当编辑框出现的时候,要自动弹出输入法。这里直接用扩展函数把View扩展,EditText这个组件就能直接用了,真方便。

/**
 * 显示软键盘
 * postDelayed:避免界面还没绘制完毕就请求焦点导致不弹出键盘
 */
fun View.showSoftInput(flags: Int = InputMethodManager.SHOW_IMPLICIT) {
    postDelayed({
        requestFocus()
        val inManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
        inManager.showSoftInput(this, flags)
    },100)
}
​
/**
 * 隐藏软键盘
 */
fun View.hideSoftInputFromWindow(flags: Int = 0) {
    val inManager = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
    inManager.hideSoftInputFromWindow(this.windowToken, flags)
}
​
// 直接调用,看起来真优雅
editTaskName.showSoftInput()

Android 学习笔录

Android 性能优化篇:https://qr18.cn/FVlo89
Android 车载篇:https://qr18.cn/F05ZCM
Android 逆向安全学习笔记:https://qr18.cn/CQ5TcL
Android Framework底层原理篇:https://qr18.cn/AQpN4J
Android 音视频篇:https://qr18.cn/Ei3VPD
Jetpack全家桶篇(内含Compose):https://qr18.cn/A0gajp
Kotlin 篇:https://qr18.cn/CdjtAF
Gradle 篇:https://qr18.cn/DzrmMB
OkHttp 源码解析笔记:https://qr18.cn/Cw0pBD
Flutter 篇:https://qr18.cn/DIvKma
Android 八大知识体:https://qr18.cn/CyxarU
Android 核心笔记:https://qr21.cn/CaZQLo
Android 往年面试题锦:https://qr18.cn/CKV8OZ
2023年最新Android 面试题集:https://qr18.cn/CgxrRy
Android 车载开发岗位面试习题:https://qr18.cn/FTlyCJ
音视频面试题锦:https://qr18.cn/AcV6Ap

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

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

相关文章

执行上下文-通俗易懂版

(1) js引擎执行代码时候/前&#xff0c;在堆内存创建一个全局对象&#xff0c;该对象 所有的作用域&#xff08;scope&#xff09;都可以访问&#xff0c;里面会包含Date、Array、String、Number、setTimeout、setInterval等等&#xff0c;其中还有一个window属性指向自己 (2…

C++数组类的自实现,使其可以保存学生成绩,并进行降序排列

类的封装 #ifndef ARRAY_H #define ARRAY_Hclass DoubArray { private:int m_length;double* m_pointer;public:DoubArray(int len);DoubArray(const DoubArray& obj);int length();bool get(int index, double& value);bool set(int index, double value);void sort(…

尚硅谷大数据项目《在线教育之离线数仓》笔记007

视频地址&#xff1a;尚硅谷大数据项目《在线教育之离线数仓》_哔哩哔哩_bilibili 目录 第12章 报表数据导出 P112 01、创建数据表 02、修改datax的jar包 03、ads_traffic_stats_by_source.json文件 P113 P114 P115 P116 P117 P118 P119 P120 P121 P122【122_在…

Hadoop:HDFS--分布式文件存储系统

目录 HDFS的基础架构 VMware虚拟机部署HDFS集群 HDFS集群启停命令 HDFS Shell操作 hadoop 命令体系&#xff1a; 创建文件夹 -mkdir 查看目录内容 -ls 上传文件到hdfs -put 查看HDFS文件内容 -cat 下载HDFS文件 -get 复制HDFS文件 -cp 追加数据到HDFS文件中 -appendTo…

第 3 章 栈和队列(汉诺塔问题递归解法)

1. 背景说明 假设有 3 个分别命名为 X、Y 和 Z 的塔座&#xff0c;在塔座 X 上插有 n 个直径大小各不相同、依小到大编号为 1, 2&#xff0c;…&#xff0c;n 的圆盘。 现要求将 X 轴上的 n 个圆盘移至塔座 Z 上并仍按同样顺序叠排&#xff0c;圆盘移动时必须遵循下列规则&…

伦敦金的走势高低的规律

伦敦金市场是一个流动性很强的市场&#xff0c;其价格走势会在诸多因素的影响下&#xff0c;出现反复的上下波动&#xff0c;如果投资者能够在这些高低走势中找到一定的规律&#xff0c;在相对有利的时机入场和离场&#xff0c;就能够通过不断的交易&#xff0c;累积大量的财富…

浏览器渲染原理及流程

浏览器主要组成与浏览器线程 浏览器组件 浏览器大体上由以下几个组件组成&#xff0c;各个浏览器可能有一点不同。 界面控件 – 包括地址栏&#xff0c;前进后退&#xff0c;书签菜单等窗口上除了网页显示区域以外的部分浏览器引擎 – 查询与操作渲染引擎的接口渲染引擎 – …

记录vite下使用require报错和解决办法

前情提要 我们现在项目用的是vite4react18开发的项目、但是最近公司有个睿智的人让我把webpack中的bpmn组件迁移过来、结果就出现问题啦&#xff1a;因为webpack是commonjs规范、但是vite不是、好像是es吧、可想而知各种报错 废话不多说啦 直接上代码&#xff1a; 注释是之前c…

生成式AI爆发,安全问题如何解决?

在生成式AI浪潮下&#xff0c;如何为行业用户提供符合实际应用场景需求的生成式AI服务&#xff0c;是行业数字化转型的下一个重点。《亚马逊云科技AIGC加速企业创新指南》白皮书指出&#xff0c;AIGC在游戏、零售电商、金融、媒体娱乐、医疗健康等行业都有典型应用场景。作为 A…

冠达管理:股票退市整理期?

近些年来&#xff0c;随着我国股市的发展&#xff0c;股票市场的出资者逐渐增多。但在出资过程中&#xff0c;退市股票的问题也成为了备受重视的论题。那么&#xff0c;股票退市收拾期到底是什么&#xff1f;如何应对退市股票&#xff1f; 首要&#xff0c;什么是股票退市收拾…

设备管理系统有什么功能?它有什么用?

设备管理系统已成为现代化大规模研究所&#xff0c;信息化管理体系建设中最为关键的要素。随着工业设备的机械化、自动化、大型化、高速化以及复杂化等因素不断叠加&#xff0c;设备设施对于工业生产的作用和影响越来越大&#xff0c;其各项制度和流程也涉及面广、内容繁杂。  …

中企绕道突破封锁,防不胜防 | 百能云芯

韩国的财经媒体Business Korea最新报道指出&#xff0c;尽管美方在《通胀削减法案》&#xff08;IRA&#xff09;的补贴中排除了中国&#xff0c;但中国企业正通过多种方式积极应对美国在半导体和电动汽车电池领域的封锁&#xff0c;这包括建立合资企业、设立生产基地以及开展技…

STDF-Viewer 解析工具说明

一、简介 1. 概述 STDF&#xff08;Standard Test Data Format&#xff09;&#xff08;标准测试数据格式&#xff09;是半导体测试行业的最主要的数据格式&#xff0c;包含了summary信息和所有测试项的测试结果&#xff1b;是半导体行业芯片测试数据的存储规范。 在半导体行业…

城市排水监测方案(dtu终端配合工业路由器精准监测)

台风季节,暴雨容易导致城市内涝积水。为有效监测排水状况,预警和防控积水灾害,星创易联推出智慧排水监测解决方案。 解决方案采用星创易联DTU300作为水位数据采集终端,它可挂载在河道及排水井等地点,实时监测水位变化,一旦超过预警阈值,立即通过4G网络传输报警信息,实现对水位…

使用Kmeans进行图像聚类

Kmeans可以用于与发现聚类相关的其他任务 介绍 聚类是一种无监督机器学习技术。这意味着您的数据集没有标签&#xff0c;即与解释变量发现的模式关联的目标变量。 无监督学习是找到看似相似的模式并将它们放入同一个桶中的过程。 最常用的无监督学习算法之一是Kmeans&#xff…

冠达管理:紧盯必要性 追问合理性 再融资问询透露监管新动向

在“活泼资本市场&#xff0c;提振出资者决心”一系列办法落地之后&#xff0c;再融资市场整体已明确收紧&#xff0c;但审阅尺度、相关细则还有待进一步观察。有接受采访的投行人士指出&#xff0c;现在存量项目仍在持续推进&#xff0c;监管审阅要点已在问询环节有较为充沛的…

2.7 PE结构:重定位表详细解析

重定位表&#xff08;Relocation Table&#xff09;是Windows PE可执行文件中的一部分&#xff0c;主要记录了与地址相关的信息&#xff0c;它在程序加载和运行时被用来修改程序代码中的地址的值&#xff0c;因为程序在不同的内存地址中加载时&#xff0c;程序中使用到的地址也…

单月打造8个10w+,情感类视频号如何爆火?

上月&#xff0c;腾讯公布了2023年Q2财报&#xff0c;其中&#xff0c;较为亮眼的是微信视频号的广告收入。据财报显示&#xff0c;二季度视频号用户使用时长与去年同期相比几乎翻倍&#xff0c;广告收入超过30亿元。作为微信生态的核心组件&#xff0c;视频号的内容生态呈现出…

C/C++浮点数向零舍入 2019年9月电子学会青少年软件编程(C/C++)等级考试一级真题答案解析

目录 C/C浮点数向零舍入 一、题目要求 1、编程实现 2、输入输出 二、解题思路 1、案例分析 三、程序代码 四、程序说明 五、运行结果 六、考点分析 C/C浮点数向零舍入 2019年9月 C/C编程等级考试一级编程题 一、题目要求 1、编程实现 输入一个单精度浮点数&#…

09-JVM垃圾收集底层算法实现

上一篇&#xff1a;08-JVM垃圾收集器详解 1.三色标记 在并发标记的过程中&#xff0c;因为标记期间应用线程还在继续跑&#xff0c;对象间的引用可能发生变化&#xff0c;多标和漏标的情况就有可能发生。 这里我们引入“三色标记”来给大家解释下&#xff0c;把Gcroots可达性…