【全栈实战】基于 Vue3 + Wot Design Uni 动手封装组件

server/2024/12/24 3:03:35/

😊你好,我是小航,一个正在变秃、变强的文艺倾年。

😊好久没有更新有关前端实战教程了,本文主要讲解【全栈实战】基于 Vue3 + Wot Design Uni 动手封装组件!

😊这个教程你将会学到技术正确的选型、Vue3基础语法实践、父子组件传参、移动端组件封装、组件源码阅读技巧、小程序适配技巧等,一起卷起来吧!

目录

  • 一、前言
    • Wot Design Uni
      • 技术选型
      • UI库选型
        • 开源热度对比
        • 多端支持情况
        • 组件数量
        • ts 支持情况
        • 发展趋势
    • Tyme
      • 语言支持情况
      • 活跃程度
      • 发展趋势
      • 使用教程
  • 二、封装
    • 思路
    • 父组件
    • 子组件
      • 初始化
      • 样式搭建
      • 工具封装
      • 常量定义
      • 逻辑设计
      • 解决微信小程序 Tab 动画异常
    • 效果展示
  • 三、完整代码
    • 代码
    • 下一步建议

一、前言

由于本人近期需要一个支持输入农历、公历的移动端组件,仅需要输入年、月、日、时,输入界面要求可以同时选择四列,且自由切换农历、公历。网上没找到现成的组件,只好自己封装一个了。下面先介绍一下自己的技术选型,本人开发采用的语言是Vue3 + Typescript

Wot Design Uni

在这里插入图片描述

技术选型

Uniapp的上手难度很低,而且Uniapp的语法风格与Vue保持高度一致,可以直接复用已有的Vue项目中的代码和经验。因此我这里使用的开发框架是Uniapp。

UI库选型

对于UI库的选型,我们常常从开源热度、多端支持情况、组件数量、ts支持情况、发展趋势来选择。

开源热度对比

截止到 2024-05-30 发表文章时的数据:

UI 框架uv-uiuview-pluswot-uiTuniaoUI
github stars568362492192
gitee stars55512635-
github forks1.1k15818820
gitee forks75430-

实到这里就一决高下了,github star 数uv-ui(568) > wot-ui(492) > uview-plus(362) > TuniaoUI(192),其中 uv-uiwot-ui 拔得头筹。

在这里插入图片描述

多端支持情况
UI 框架uv-uiuview-pluswot-uiTuniaoUI
h5
app(ios)
app(android)
微信小程序
支付宝小程序
QQ 小程序
百度小程序
头条小程序
组件数量
UI 框架uv-uiuview-pluswot-uiTuniaoUI
总数67677155
基础组件81185
表单组件16172014
数据组件134184
反馈组件810168
布局组件79-8
导航组件8895
其他组件78-5
内容组件---6

组件数:wot(71) > uv-ui(67) = uview-plus(67) > TuniaoUI(55)

ts 支持情况

查看 4 个组件库的源码,可以了解到:

  • uv-uiuView-plus 都是 js 写的,并非 ts,可以通过 ttou/uv-typings 提供类型支持。
  • wotTuniaoUI 都是 ts 写的,编码体验会好很多。

小知识:代码里如何辨别一个库是否有 ts 支持,写代码的时候按 ctrl + i (Mac 里 cmd + i),如果有提示就是有,啥都没有就是没有。

举个例子,编写 <xx-button type="" ...,在 type="" 双引号里面按 ctrl + i,看提示就知道了。

  • wot 有提示
    在这里插入图片描述
  • uv-ui 无提示
    在这里插入图片描述
发展趋势

wot-uiuv-ui皇城PK

在这里插入图片描述
目前 wot-ui 还是比不过 uv-ui 的,但是 wot-ui 有反超的势头。

这里我选择了wot-ui。wot-ui 全称 wot-design-uni,是 wot-design 的 uniapp 版本,文档地址:https://wot-design-uni.netlify.app/

Tyme

Tyme是一个非常强大的日历工具库,可以看作 Lunar 的升级版,拥有更优的设计和扩展性,支持公历和农历、星座、干支、生肖、节气、法定假日等。这有助与我们进行农历和公历之间的转化。

开源地址:https://github.com/6tail/tyme4ts

语言支持情况

看着就很棒,支持了市面了主流的语言。
在这里插入图片描述

活跃程度

近两周刚更新源代码,说明出了问题能解决。我们在采用别人封装好的库时候,一定要多看看仓库活跃程度,如果这个仓库都好久没有更新维护了,不建议使用。

在这里插入图片描述

发展趋势

没毛病,就它了!
在这里插入图片描述

使用教程

// install
npm init -y
npm i typescript -D
npm i ts-node -D
npm i tyme4ts// test.ts
import {SolarDay} from 'tyme4ts';const solar: SolarDay = SolarDay.fromYmd(1986, 5, 29);// 1986年5月29日
console.log(solar.toString());// 农历丙寅年四月廿一
console.log(solar.getLunarDay().toString());// run
ts-node test.ts

二、封装

思路

在这里插入图片描述
示例数据:

// 公历2024年12月22日13时
birthdayValue: [2024, 12, 22, 13, 1]

用到的组件:

Input、Picker、Popup

父组件

<birthdayPickerv-model="state.userInfo.birthdayValue"prop="birthdayValue":rules="[{required: false,message: '请输入生辰',},]"></birthdayPicker>// 定义变量
const state = reactive({// 用户信息数据userInfo: {birthdayValue: null,},
})

子组件

初始化

Tips:便于调试,推荐onload加入debugger,可以直接看到小程序源代码是否刷新,小程序经常不刷新代码,经常误认为自己的编码问题,也是比较恼火。

onLoad(() => {debugger
})

定义组件名称

defineOptions({name: 'birthdayPicker',
})

定义接收父组件传值的接口

type bithdayPickerProps = {modelValue: number[] // 生日值disabled?: boolean // 是否禁用prop: string // 表单域 model 字段名,在使用表单校验功能的情况下,该属性是必填的rules: FormItemRule[] // 表单校验规则
}
const props = defineProps<bithdayPickerProps>()
const emit = defineEmits(['update:modelValue'])

定义用到的状态变量

const state = reactive({displayColumns: [],popupShow: false,birthdayArr: ['农历', '公历'],birthday: [],birthdayIndex: 0,pickerViewLoaded: false,
})// 计算Picker展示值
const displayBirthday = computed(() => {const birthday = state.birthday// 避免空数组调用join函数if (isNullBirthdayArray(birthday, 4)) return null// 公历2024年12月22日13时return (state.birthdayArr[state.birthdayIndex] +birthday[0] +'年' +findValueInCoulums(birthday[1], state.displayColumns[1]) +findValueInCoulums(birthday[2], state.displayColumns[2]) +birthday[3] +'时')
})

在这里插入图片描述

这里有个常见的面试考点:

如何在 ref() 与 reactive() 之间做正确选择?
(1)ref() 和 reactive() 用于跟踪其参数的更改。当使用它们初始化变量时,是向 Vue 提供信息:“嘿,每次这些变量发生更改时,请重新构建或重新运行依赖于它们的所有内容”。
(2)ref() 函数可以接受原始类型(最常见的是布尔值、字符串和数字)以及对象作为参数,而 reactive() 函数只能接受对象作为参数。
(3)ref() 有一个 .value 属性,你必须使用 .value 属性获取内容,但是使用 reactive() 的话可以直接访问。
(4)使用 ref() 函数可以替换整个对象实例,但是在使用 reactive() 函数时就不行。
(5)如果你喜欢组件内部状态仅有一个变量,那么 reactive() 就是适合你的正确工具。本人更喜欢使用reactive,看着比较舒服。
在这里插入图片描述

样式搭建

编写组件样式:

<template><wd-popupv-model="state.popupShow"position="bottom"custom-style="border-radius:32rpx;height: 300px;":close-on-click-modal="false"@close="closeBirthdayPicker"closable@after-enter="handleOpened"><wd-tabs v-model="state.birthdayIndex" animated swipeable ref="tabsRef" @change="onChangeTab"><wd-tab v-for="(item, index) in state.birthdayArr" :key="index" :title="item"><wd-picker-view:columns="state.displayColumns"v-model="state.birthday":column-change="onChangeColumn"ref="birthdayPicker"/></wd-tab></wd-tabs></wd-popup><div @click="openBirthdayPicker"><wd-input:prop="props.prop":rules="props.rules"type="text"label="生辰"v-model="displayBirthday"placeholder="请选择"readonlyrequiredplaceholder-style="color:#bfbfbf"><template #suffix><!--箭头--><wd-icon name="arrow-right" color="rgba(0, 0, 0, 0.25)" size="18px" /></template></wd-input></div>
</template>

在这里插入图片描述
基础的打开、关闭

// 打开生日选择器
const openBirthdayPicker = () => {if (props.disabled) returnstate.popupShow = trueconst value = props.modelValueif (isNullBirthdayArray(value, 5)) {state.birthday = [1940, 1, 1, 1]state.birthdayIndex = 0return}state.birthday = [value[0], value[1], value[2], value[3]]state.birthdayIndex = value[4]
}// 关闭生日选择器
const closeBirthdayPicker = () => {emit('update:modelValue', [...state.birthday, state.birthdayIndex])state.popupShow = false
}

工具封装

由于我这里会经常对bithday数组进行判空,判断依据有:是否有空值、长度是否满足,是否是数组等,这里我们封装成一个函数

const isNullBirthdayArray = (birthday: number[], validLength: number = birthday.length) => {return (!birthday ||(isArray(birthday) &&(!birthday.length ||birthday.length !== validLength ||birthday.every((item) => item === null))))
}

Picker值展示时需要根据birthday的值进行与当前columns映射

const findValueInCoulums = (value: number, columns: any[]) => {let res = ''columns.forEach((item) => {if (item.value === value) res = item.label})return res
}

常量定义

// 定义农历月份和日期的汉字表示
const chineseMonths = ['一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月',
]
const chineseDays = ['初一','初二','初三','初四','初五','初六','初七','初八','初九','初十','十一','十二','十三','十四','十五','十六','十七','十八','十九','二十','廿一','廿二','廿三','廿四','廿五','廿六','廿七','廿八','廿九','三十',
]

根据常量来更新展示列信息:

const yearColumns = Array.from({ length: new Date().getFullYear() - 1940 + 1 }, (_, i) => {return { label: `${1940 + i}`, value: 1940 + i }
})
const hourColumns = Array.from({ length: 24 }, (_, i) => ({label: `${i + 1}`,value: i + 1,
}))
const initDays = (len: number, type: 'lunar' | 'gregorian') =>Array.from({ length: len }, (_, i) => {return {label: type === 'lunar' ? `${chineseDays[i]}` : `${i + 1}`,value: i + 1,}})
const updateColumns = (birthday: number[]) => {let monthColumns = []let dayColumns = []const [_year, _month, _day, _hour, birthdayIndex] = birthday// 1.农历列if (birthdayIndex === 0) {const lunarYear: LunarYear = LunarYear.fromYear(_year)monthColumns = lunarYear.getMonths().map((month) => ({label: month.isLeap()? `${chineseMonths[month.getMonth() - 1]}`: chineseMonths[month.getMonth() - 1],value: month.getMonthWithLeap(),}))const lunarMonth: LunarMonth = LunarMonth.fromYm(_year, _month)const dayCount: number = lunarMonth.getDayCount()dayColumns = initDays(dayCount, 'lunar')}// 2.公历列if (birthdayIndex === 1) {monthColumns = Array.from({ length: 12 }, (_, i) => {return { label: `${chineseMonths[i]}`, value: i + 1 }})const solarMonth: SolarMonth = SolarMonth.fromYm(_year, _month)const dayCount: number = solarMonth.getDayCount()dayColumns = initDays(dayCount, 'gregorian')}state.displayColumns = [yearColumns, monthColumns, dayColumns, hourColumns]
}

逻辑设计

初始化列

// 1.初始化
onMounted(() => {handleShowValueUpdate(props.modelValue)
})// 2.监听父组件的值变化
watch(() => props.modelValue,(newValue) => {handleShowValueUpdate(newValue)},
)// 3.值变更时更新显示内容
const handleShowValueUpdate = (value: number[]) => {// 0.校验if (isNullBirthdayArray(value, 5)) {// 更新为初始值,状态值保持不变updateColumns([1940, 1, 1, 1, 0])return}// 1.获取选中项state.birthday = [value[0], value[1], value[2], value[3]]state.birthdayIndex = value[4]// 2.更新列updateColumns(value)
}// 4.当农历、公历切换时:
const onChangeTab = (index) => {state.pickerViewLoaded = trueconst [_year, _month, _day, _hour] = state.birthday// 1.农历转公历if (index.name === 1) {console.log('农历转公历前', index.name, state.birthday)const lunarHour = LunarHour.fromYmdHms(_year, _month, _day, _hour, 0, 0)const solarTime: SolarTime = lunarHour.getSolarTime()state.birthday = [solarTime.getYear(),solarTime.getMonth(),solarTime.getDay(),solarTime.getHour(),]console.log('农历转公历后', index.name, state.birthday)}// 2.公历转农历if (index.name === 0) {console.log('公历转农历前', index.name, state.birthday)const solarTime = SolarTime.fromYmdHms(_year, _month, _day, _hour, 0, 0)const lunarHour = solarTime.getLunarHour()state.birthday = [lunarHour.getYear(),lunarHour.getMonth(),lunarHour.getDay(),lunarHour.getHour(),]console.log('公历转农历后', index.name, state.birthday)}// 3.更新Columns列,注意此时state.birthdayIndex还未更新updateColumns([...state.birthday, index.name])
}// 5.更新列
const updateColumns = (birthday: number[]) => {let monthColumns = []let dayColumns = []const [_year, _month, _day, _hour, birthdayIndex] = birthday// 1.农历列if (birthdayIndex === 0) {const lunarYear: LunarYear = LunarYear.fromYear(_year)monthColumns = lunarYear.getMonths().map((month) => ({label: month.isLeap()? `${chineseMonths[month.getMonth() - 1]}`: chineseMonths[month.getMonth() - 1],value: month.getMonthWithLeap(),}))const lunarMonth: LunarMonth = LunarMonth.fromYm(_year, _month)const dayCount: number = lunarMonth.getDayCount()dayColumns = initDays(dayCount, 'lunar')}// 2.公历列if (birthdayIndex === 1) {monthColumns = Array.from({ length: 12 }, (_, i) => {return { label: `${chineseMonths[i]}`, value: i + 1 }})const solarMonth: SolarMonth = SolarMonth.fromYm(_year, _month)const dayCount: number = solarMonth.getDayCount()dayColumns = initDays(dayCount, 'gregorian')}state.displayColumns = [yearColumns, monthColumns, dayColumns, hourColumns]
}

当用户滑动列后进行更新picker

const onChangeColumn = (pickerView, value, columnIndex, resolve) => {// debugger// 1.更新birthday值const item = value[columnIndex]state.birthday[columnIndex] = Number(item.value)// 2.滑动列后重置后面的列for (let i = columnIndex + 1; i < 4; i++) {state.birthday[i] = 1}// 3.更新Columns列updateColumns([...state.birthday, state.birthdayIndex])// 4.更新pickerViewif (columnIndex === 0) {pickerView.setColumnData(1, state.displayColumns[1])pickerView.setColumnData(2, state.displayColumns[2])pickerView.setColumnData(3, state.displayColumns[3])} else if (columnIndex === 1) {pickerView.setColumnData(2, state.displayColumns[2])pickerView.setColumnData(3, state.displayColumns[3])} else if (columnIndex === 2) {pickerView.setColumnData(3, state.displayColumns[3])}// 5.更新pickerView组件resolve()
}

这里我进行了后列重置,避免出现后面的日期不存在于当月,造成列Index错误。

在这里插入图片描述

解决微信小程序 Tab 动画异常

// 解决微信小程序Tabs表现异常
const tabsRef = ref<TabsInstance>()
function handleOpened() {tabsRef.value?.updateLineStyle(false)
}

在这里插入图片描述

效果展示

选择农历后:

在这里插入图片描述
农历切换到公里后:

在这里插入图片描述
测试闰月:

在这里插入图片描述
测试校验规则

在这里插入图片描述

三、完整代码

代码

<template><wd-popupv-model="state.popupShow"position="bottom"custom-style="border-radius:32rpx;height: 300px;":close-on-click-modal="false"@close="closeBirthdayPicker"closable@after-enter="handleOpened"><wd-tabs v-model="state.birthdayIndex" animated swipeable ref="tabsRef" @change="onChangeTab"><wd-tab v-for="(item, index) in state.birthdayArr" :key="index" :title="item"><wd-picker-view:columns="state.displayColumns"v-model="state.birthday":column-change="onChangeColumn"ref="birthdayPicker"/></wd-tab></wd-tabs></wd-popup><div @click="openBirthdayPicker"><wd-input:prop="props.prop":rules="props.rules"type="text"label="生辰"v-model="displayBirthday"placeholder="请选择"readonlyrequiredplaceholder-style="color:#bfbfbf"><template #suffix><!--箭头--><wd-icon name="arrow-right" color="rgba(0, 0, 0, 0.25)" size="18px" /></template></wd-input></div>
</template><script lang="ts" setup>
import { LunarYear, LunarMonth, LunarHour, SolarTime, SolarMonth } from 'tyme4ts'
import { isArray } from 'wot-design-uni/components/common/util'
import { reactive, computed, watch, ref, defineProps, defineEmits, defineOptions } from 'vue'
import { FormItemRule } from 'wot-design-uni/components/wd-form/types'defineOptions({name: 'birthdayPicker',
})onLoad(() => {debugger
})// 解决微信小程序Tabs表现异常
const tabsRef = ref<TabsInstance>()
function handleOpened() {tabsRef.value?.updateLineStyle(false)
}// 定义农历月份和日期的汉字表示
const chineseMonths = ['一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月',
]
const chineseDays = ['初一','初二','初三','初四','初五','初六','初七','初八','初九','初十','十一','十二','十三','十四','十五','十六','十七','十八','十九','二十','廿一','廿二','廿三','廿四','廿五','廿六','廿七','廿八','廿九','三十',
]// 1.状态管理
const state = reactive({displayColumns: [],popupShow: false,birthdayArr: ['农历', '公历'],birthday: [],birthdayIndex: 0,pickerViewLoaded: false,
})// 2.定义组件接收值
type bithdayPickerProps = {modelValue: number[] // 生日值disabled?: boolean // 是否禁用prop: string // 表单域 model 字段名,在使用表单校验功能的情况下,该属性是必填的rules: FormItemRule[] // 表单校验规则
}
const props = defineProps<bithdayPickerProps>()
const emit = defineEmits(['update:modelValue'])// 3.计算Picker展示值
const findValueInCoulums = (value: number, columns: any[]) => {let res = ''columns.forEach((item) => {if (item.value === value) res = item.label})return res
}
const displayBirthday = computed(() => {const birthday = state.birthday// 避免空数组调用join函数if (isNullBirthdayArray(birthday, 4)) return null// 公历2024年12月22日13时return (state.birthdayArr[state.birthdayIndex] +birthday[0] +'年' +findValueInCoulums(birthday[1], state.displayColumns[1]) +findValueInCoulums(birthday[2], state.displayColumns[2]) +birthday[3] +'时')
})
const isNullBirthdayArray = (birthday: number[], validLength: number = birthday.length) => {return (!birthday ||(isArray(birthday) &&(!birthday.length ||birthday.length !== validLength ||birthday.every((item) => item === null))))
}
// 4.初始化
onMounted(() => {handleShowValueUpdate(props.modelValue)
})// 5.监听父组件的值变化
watch(() => props.modelValue,(newValue) => {handleShowValueUpdate(newValue)},
)// 6.值变更时更新显示内容
const handleShowValueUpdate = (value: number[]) => {// 0.校验if (isNullBirthdayArray(value, 5)) {// 更新为初始值,状态值保持不变updateColumns([1940, 1, 1, 1, 0])return}// 1.获取选中项state.birthday = [value[0], value[1], value[2], value[3]]state.birthdayIndex = value[4]// 2.更新列updateColumns(value)
}// 7.更新Columns:年份列、月份列、天数列、小时列
const yearColumns = Array.from({ length: new Date().getFullYear() - 1940 + 1 }, (_, i) => {return { label: `${1940 + i}`, value: 1940 + i }
})
const hourColumns = Array.from({ length: 24 }, (_, i) => ({label: `${i + 1}`,value: i + 1,
}))
const initDays = (len: number, type: 'lunar' | 'gregorian') =>Array.from({ length: len }, (_, i) => {return {label: type === 'lunar' ? `${chineseDays[i]}` : `${i + 1}`,value: i + 1,}})const updateColumns = (birthday: number[]) => {let monthColumns = []let dayColumns = []const [_year, _month, _day, _hour, birthdayIndex] = birthday// 1.农历列if (birthdayIndex === 0) {const lunarYear: LunarYear = LunarYear.fromYear(_year)monthColumns = lunarYear.getMonths().map((month) => ({label: month.isLeap()? `${chineseMonths[month.getMonth() - 1]}`: chineseMonths[month.getMonth() - 1],value: month.getMonthWithLeap(),}))const lunarMonth: LunarMonth = LunarMonth.fromYm(_year, _month)const dayCount: number = lunarMonth.getDayCount()dayColumns = initDays(dayCount, 'lunar')}// 2.公历列if (birthdayIndex === 1) {monthColumns = Array.from({ length: 12 }, (_, i) => {return { label: `${chineseMonths[i]}`, value: i + 1 }})const solarMonth: SolarMonth = SolarMonth.fromYm(_year, _month)const dayCount: number = solarMonth.getDayCount()dayColumns = initDays(dayCount, 'gregorian')}state.displayColumns = [yearColumns, monthColumns, dayColumns, hourColumns]
}// 8.当用户滑动列后触发
const onChangeColumn = (pickerView, value, columnIndex, resolve) => {// debugger// 1.更新birthday值const item = value[columnIndex]state.birthday[columnIndex] = Number(item.value)// 2.滑动列后重置后面的列for (let i = columnIndex + 1; i < 4; i++) {state.birthday[i] = 1}// 3.更新Columns列updateColumns([...state.birthday, state.birthdayIndex])// 4.更新pickerViewif (columnIndex === 0) {pickerView.setColumnData(1, state.displayColumns[1])pickerView.setColumnData(2, state.displayColumns[2])pickerView.setColumnData(3, state.displayColumns[3])} else if (columnIndex === 1) {pickerView.setColumnData(2, state.displayColumns[2])pickerView.setColumnData(3, state.displayColumns[3])} else if (columnIndex === 2) {pickerView.setColumnData(3, state.displayColumns[3])}// 5.更新pickerView组件resolve()
}// 9.当农历、公历切换时:
const onChangeTab = (index) => {state.pickerViewLoaded = trueconst [_year, _month, _day, _hour] = state.birthday// 1.农历转公历if (index.name === 1) {console.log('农历转公历前', index.name, state.birthday)const lunarHour = LunarHour.fromYmdHms(_year, _month, _day, _hour, 0, 0)const solarTime: SolarTime = lunarHour.getSolarTime()state.birthday = [solarTime.getYear(),solarTime.getMonth(),solarTime.getDay(),solarTime.getHour(),]console.log('农历转公历后', index.name, state.birthday)}// 2.公历转农历if (index.name === 0) {console.log('公历转农历前', index.name, state.birthday)const solarTime = SolarTime.fromYmdHms(_year, _month, _day, _hour, 0, 0)const lunarHour = solarTime.getLunarHour()state.birthday = [lunarHour.getYear(),lunarHour.getMonth(),lunarHour.getDay(),lunarHour.getHour(),]console.log('公历转农历后', index.name, state.birthday)}// 3.更新Columns列,注意此时state.birthdayIndex还未更新updateColumns([...state.birthday, index.name])
}// 打开生日选择器
const openBirthdayPicker = () => {if (props.disabled) returnstate.popupShow = trueconst value = props.modelValueif (isNullBirthdayArray(value, 5)) {state.birthday = [1940, 1, 1, 1]state.birthdayIndex = 0return}state.birthday = [value[0], value[1], value[2], value[3]]state.birthdayIndex = value[4]
}// 关闭生日选择器
const closeBirthdayPicker = () => {emit('update:modelValue', [...state.birthday, state.birthdayIndex])state.popupShow = false
}
</script><style lang="scss" scoped></style>

下一步建议

近期本人时间有限,暂不做改进,大佬们可以拿去做完善。

  1. 缺少 取消和确认 的操作。
  2. 组件不够通用,需要暴漏常用的函数和变量。
📌 [ 笔者 ]   文艺倾年
📃 [ 更新 ]   2024.12.22
❌ [ 勘误 ]   /* 暂无 */
📜 [ 声明 ]   由于作者水平有限,本文有错误和不准确之处在所难免,本人也很想知道这些错误,恳望读者批评指正!

在这里插入图片描述


http://www.ppmy.cn/server/152647.html

相关文章

项目搭建+删除(单/批)

一 : 删除没有单独的页面,在列表页面写 二 : 删除在列表的页面 1.删除(单/双)的按钮 ① : 在列表文档就绪函数的ajax里面,成功回调函数追加数据里写删除按钮 注意点 : 删除/修改/回显都是根据id来的,记得传id ② : 批删给批删按钮,定义批删的方法 one : 示例(单删) : //循环追…

POD 存储、PV、PVC

目录 容器如何持久化存储&#xff1f; PV和PVC 为什么不能直接在 Pod 或容器中存储数据&#xff1f; 什么是 PV和 PVC&#xff1f; 可以使用本地磁盘空间创建PV吗&#xff1f; 如何让客户端通过ftp上传到远端服务器的POD里面&#xff1f; 另一个POD想访问ftp的POD里面的…

spring mvc | servlet :serviceImpl无法自动装配 UserMapper

纯注解SSM整合 解决办法&#xff1a; 在MybatisConfig添加 Configuration MapperScan("mapper")

网络安全怎么学习

当我们谈论网络安全时&#xff0c;我们正在讨论的是保护我们的在线空间&#xff0c;这是我们所有人的共享责任。网络安全涉及保护我们的信息&#xff0c;防止被未经授权的人访问、披露、破坏或修改。 一、网络安全的基本概念 网络安全是一种保护&#xff1a;它涉及保护我们的设…

在 Ubuntu 上安装 Muduo 网络库的详细指南

在 Ubuntu 上安装 Muduo 网络库的详细指南 首先一份好的安装教程是非常重要的 C muduo网络库知识分享01 - Linux平台下muduo网络库源码编译安装-CSDN博客 像这篇文章就和shit一样&#xff0c;安装到2%一定会卡住&#xff0c;如果你不幸用了这个那真是遭老罪了 环境&#xf…

RK3588 , mpp硬编码yuv, 保存MP4视频文件.

RK3588 , mpp硬编码yuv, 保存MP4视频文件. ⚡️ 传送 ➡️ Ubuntu x64 架构, 交叉编译aarch64 FFmpeg mppRK3588, FFmpeg 拉流 RTSP, mpp 硬解码转RGBRk3588 FFmpeg 拉流 RTSP, 硬解码转RGBRK3588 , mpp硬编码yuv, 保存MP4视频文件.

34 Opencv 自定义角点检测

文章目录 cornerEigenValsAndVecscornerMinEigenVal示例 cornerEigenValsAndVecs void cornerEigenValsAndVecs(InputArray src, --单通道输入8位或浮点图像OutputArray dst, --输出图像&#xff0c;同源图像或CV_32FC(6)int blockSize, --邻域大小值int ape…

【前端面试】list转树、拍平, 指标,

这个题目涉及的是将一组具有父子关系的扁平数据转换为树形结构&#xff0c;通常称为“树形结构的构建”问题。类似的题目包括&#xff1a; 1. 组织架构转换 给定一个公司的员工列表&#xff0c;每个员工有 id 和 managerId&#xff0c;其中 managerId 表示该员工的上级。任务…