Vue 实现高级穿梭框 Transfer 封装

devtools/2024/9/23 1:43:09/

文章目录

  • 01 基础信息
    • 1.1. 技术栈
    • 1.2. 组件设计
      • a. 竖版设计稿
      • b. 横版设计稿
  • 02 技术方案
    • (1)初定义数据
    • (2)注意事项
    • (3)逻辑草图
  • 03 代码示例
    • 3.1. 组件使用
    • 3.2. 组件源码
      • ./TransferPlus/index.vue
      • ./TransferPlus/TransferTable.vue

01 基础信息

1.1. 技术栈

Element-UIVue2lodash

1.2. 组件设计

需求描述:

  1. 【待选择列表】 接收业务的表格数据,支持选择多项并将其添加到【已添加列表】 (勾选或删除操作,两边的列表是同步的);
  2. 【已添加列表】支持本地分页和本地简易搜索功能(已添加的列表数据需要实时同步给业务);

a. 竖版设计稿

穿梭框竖版设计稿

b. 横版设计稿

穿梭框横版设计稿

02 技术方案

(1)初定义数据

javascript">// 【待选择列表】外部传输源数据
// 【已添加列表】组件内部控制数据的分页、搜索和展示const props = {sourceList: [], // 源数据columnList: [], // 表格列配置(注:字段类型均为字符串)searchList: [], // 【已添加列表】搜索项(注:与表头对应)refreshTableData: (param)=>{}, // 回调函数total: 0, // 用于控制分页器
}const state = {targetList: [], // 目标数据searchList: [], // 【已添加列表】搜索项
}

(2)注意事项

  1. 【待选择列表】翻页选择时需要记录并回显已选择的行
  2. 【已添加列表】删除后需要继续留在当前页,即要判断删除的是否是最后一页中只有一条的数据
  3. 【待选择列表】更改选择后,【已添加列表】的筛选项或是状态项是否重置?或是维持不变?

(3)逻辑草图

逻辑草图

03 代码示例

3.1. 组件使用

外部可通过 ref 调用的方法:

  1. clearSelection():清空所有选择项;
  2. setPaginationParam({pageNum,pageSize},isFetch):设置源表格分页器参数,若 isFetch 为 true 则会自动调用 fetchSourceListisFetch 默认为 false );
  3. initializeComponent(isFetch):初始化组件,若 isFetch 为 true 则初始化后自动请求源表格数据( isFetch 默认为 false );
  4. this.$refs['transferPlus'].selectList:若要初始化 selectList 可以使用 ref 设置(记得外面包裹 this.$nextTick);

注意事项:

  1. 使用插槽自定义表格列时,是同时应用到两个列表中的;
  2. 组件会通过 selectionChange 事件告知您选择的列表结果;
  3. 特别地,组件一开始不会默认请求源表格数据,所以您需要在使用前自行调用 fetchSourceList 获取 sourceList 等来渲染组件的数据,组件只会在内部的分页状态等有更改的情况下自动调用 fetchSourceList 为您刷新渲染数据;
  4. usePaginationtrue,则组件自动为您控制分页器,但您必须设置好变量(sourceTotal)和源表格数据请求方法(fetchSourceList),并且为了防止您初始请求的分页参数和组件内部定义的默认初始分页参数不同,您可以设置 initSourcePageNuminitSourcePageSize 来同步内外初始化参数;
javascript"><template>  <TransferPlus ref="transferPlusRef":sourceList="sourceList" :tableColumnList="tableColumnList" usePagination tableHeight="240" :sourceTotal="sourceTotal" :tableLoading="tableLoading" @fetchSourceList="fetchSourceList" ><!-- "table_"后拼接的是你定义该列的prop --><template #table_tag="{ row, rowIndex }">{{ rowIndex + 1 }}{{row.tag}}</template><!-- 自定义源表格的搜索区域 --><template #source_search><el-input placeholder="请输入课程名称" v-model="queryInfo.title" class="search-input" clearable><el-button slot="append" icon="el-icon-search" @click="searchSourceList"></el-button></el-input></template></TransferPlus>
</template><script>import TransferPlus from '@/components/TransferPlus'export default {components: { TransferPlus },data() {sourceList: [],    tableColumnList: [{ label: '课程id', prop: 'id' },{ label: '课程名称', prop: 'title' },{ label: '课程类型', prop: 'tag' },],tableLoading: false,sourceTotal: 0,queryInfo: {pageNum: 1,pageSize: 10,title: '',tag: '',},}method:{async fetchSourceList (params={pageNum,pageSize}) {this.tableLoading = trueconst { pageNum, pageSize } = this.queryInfothis.queryInfo = {...this.queryInfo,pageNum: params?.pageNum || pageNum,pageSize: params?.pageSize || pageSize,}const res = await getList(this.queryInfo)this.sourceList = res.data.list || []this.sourceTotal = res.data.total || 0this.tableLoading = false},searchSourceList() {// 每次查询时只需要重置穿梭框的页码到 1,并配置自动调用搜索函数this.$refs['transferPlusRef'].setPaginationParam({ pageNum: 1 }, true)},}}</script><style scoped>.search-input {margin-bottom: 12px;width: 100%;height: 32px;}</style>

实现效果图:
实现效果图

3.2. 组件源码

./TransferPlus/index.vue

javascript"><!--  
组件使用方式如下:<TransferPlus :sourceList="sourceList" :tableColumnList="tableColumnList" usePagination tableHeight="240" :sourceTotal="sourceTotal" :tableLoading="tableLoading" @fetchSourceList="fetchSourceList" ><template #table_你定义该列的prop="{ columnProps }">{{ columnProps.$index + 1 }}{{columnProps.row.xxx}}</template></TransferPlus>method:{async fetchSourceList (params={pageNum,pageSize}) {this.tableLoading = trueconst res = await getList({ ...this.queryInfo, ...params })this.sourceList = res.data.listthis.sourceTotal = res.data.totalthis.tableLoading = false}}
外部可通过 ref 调用的方法:
1. clearSelection():清空所有选择项;
2. setPaginationParam({pageNum,pageSize},isFetch):设置源表格分页器参数,若 isFetch 为 true 则会自动调用 fetchSourceList( isFetch 默认为 false );
3. initializeComponent(isFetch):初始化组件,若 isFetch 为 true 则初始化后自动请求源表格数据( isFetch 默认为 false );
4. this.$refs['transferPlusRef'].selectList:若要初始化 selectList 可以使用 ref 设置(记得外面包裹 this.$nextTick);
注意事项:
1. 使用插槽自定义表格列时,是同时应用到两个列表中的;
2. 组件会通过 selectionChange 事件告知您选择的列表结果;
3. 特别地,组件一开始不会默认请求源表格数据,所以您需要在使用前自行调用 fetchSourceList 获取 sourceList 等来渲染组件的数据,组件只会在内部的分页状态等有更改的情况下自动调用 fetchSourceList 为您刷新渲染数据;
4. 若 usePagination 为 true,则组件自动为您控制分页器,但您必须设置好变量(sourceTotal)和源表格数据请求方法(fetchSourceList),并且为了防止您初始请求的分页参数和组件内部定义的默认初始分页参数不同,您可以设置 initSourcePageNum 和 initSourcePageSize 来同步内外初始化参数;-->
<template><div :class="direction === 'horizontal' ? 'transfer-horizontal' : ''"><!-- 【待选择列表】 --><div :class="['list-wrapping', { horizontal: direction === 'horizontal' }]"><div class="wrapping-header"><span>待选择列表</span><span>{{ selectLength }}/{{ sourceTotal || sourceList.length }}</span></div><div class="wrapping-content"><!-- 自定义搜索 --><slot name="source_search" /><TransferTableref="sourceTransferTableRef"v-model="selectList":tableList="sourceList":tableColumnList="tableColumnList":tableHeight="tableHeight":total="sourceTotal":initPageNum="initSourcePageNum":initPageSize="initSourcePageSize":usePagination="usePagination":tableLoading="tableLoading":uniqueKey="uniqueKey":selectable="selectable":pagerCount="pagerCount"@fetchTableList="handleFetchTableList"><!-- 使用穿梭表格的自定义列插槽 --><template v-for="(item, index) in tableColumnList" :slot="`inner_table_${item.prop}`" slot-scope="slotData"><span :key="index"><!-- 设置新的插槽提供给消费端自定义列 --><slot :name="`table_${item.prop}`" :columnProps="slotData.columnProps" :row="slotData.columnProps.row" :rowIndex="slotData.columnProps.$index">{{ slotData.columnProps.row[item.prop] || '-' }}</slot></span></template></TransferTable></div></div><!-- 【已添加列表】 --><div :class="['list-wrapping', { horizontal: direction === 'horizontal' }]"><div class="wrapping-header"><span>已添加列表</span><span>{{ selectLength }}</span></div><div class="wrapping-content"><template v-if="selectLength"><el-input placeholder="请输入内容" v-model="searchStr" class="search-input" clearable><el-select slot="prepend" v-model="searchKey" placeholder="请选择" class="search-select" @change="handleSearchKeyChange" value-key="prop"><el-option v-for="item in targetSearchList" :key="item.prop" :label="item.label" :value="item.prop"></el-option></el-select><el-button slot="append" icon="el-icon-search" @click="handleSearchStrChange"></el-button></el-input><TransferTableref="targetTransferTableRef":tableList="targetList":tableColumnList="tableColumnList":tableHeight="tableHeight"tableType="target":uniqueKey="uniqueKey":total="targetTotal":usePagination="usePagination":pagerCount="pagerCount"@removeSelectRow="handleRemoveSelectRow"@fetchTableList="getTargetTableList"><!-- 使用穿梭表格的自定义列插槽 --><template v-for="(item, index) in tableColumnList" :slot="`inner_table_${item.prop}`" slot-scope="slotData"><span :key="index"><!-- 设置新的插槽提供给消费端自定义列 --><slot :name="`table_${item.prop}`" :columnProps="slotData.columnProps" :row="slotData.columnProps.row" :rowIndex="slotData.columnProps.$index">{{ slotData.columnProps.row[item.prop] || '-' }}</slot></span></template></TransferTable></template><div class="empty-box" v-else><el-image class="empty-image" :src="require('@/assets/empty_images/data_empty.png')" /></div></div></div></div>
</template><script>import TransferTable from './TransferTable.vue'import { throttle, differenceBy, filter, isNil, noop } from 'lodash'export default {components: { TransferTable },props: {// 源数据sourceList: {type: Array,default: () => [],},// 表格列配置列表tableColumnList: {type: Array,default: () => [], // {label,prop,align}[]},// 表格数据是否加载中tableLoading: {type: Boolean,default: false,},// 表格高度tableHeight: {type: String | Number,default: 240,},// 【已添加列表】搜索项(注:与表格列配置对应,且仅能搜索字段类型为 String)searchList: {type: Array,default: () => [], // {label,prop}[]},// 源表格总数据的条数sourceTotal: {type: Number,default: 0,},// 源表格初始 pageNum(用于同步消费端初始化请求时的分页参数,进而帮助控制分页器)initSourcePageNum: {type: Number,default: 1,},// 源表格初始 pageSize(用于同步消费端初始化请求时的分页参数,进而帮助控制分页器)initSourcePageSize: {type: Number,default: 10,},// 使用分页器usePagination: {type: Boolean,default: false,},// 唯一标识符(便于定位到某条数据进行添加和移除操作)uniqueKey: {type: String,default: 'id',},// 穿梭框展示方式direction: {type: String,default: 'vertical', // horizontal 左右布局, vertical 上下布局},selectable: {type: Function,default: noop(),},// 页码按钮的数量,当总页数超过该值时会折叠(element规定:大于等于 5 且小于等于 21 的奇数)pagerCount: {type: Number,default: 7,},},data() {return {selectList: [], // 已选择的列表targetList: [], // 已添加列表的回显数据searchKey: '',searchStr: '',targetPageNum: 1,targetPageSize: 10,targetTotal: 10,}},computed: {targetSearchList() {return this.searchList.length ? this.searchList : this.tableColumnList},selectLength() {return this.selectList?.length || 0},},watch: {selectList(newVal) {this.getTargetTableList()this.$emit('selectionChange', newVal)},},mounted() {this.searchKey = this.targetSearchList[0].propthis.targetPageNum = 1this.targetPageSize = 10},methods: {handleFetchTableList(params) {this.$emit('fetchSourceList', params)},handleRemoveSelectRow(rowItem) {this.selectList = differenceBy(this.selectList, [rowItem], this.uniqueKey)},handleSearchStrChange() {// 每次查询时只需要重置穿梭框的页码到 1,并配置自动调用搜索函数this.$refs['targetTransferTableRef'].setPaginationParam({ pageNum: 1 }, true)},handleSearchKeyChange() {// 更新搜索 Key 之后,需要清空搜索字符串this.searchStr = ''this.$refs['targetTransferTableRef'].setPaginationParam({ pageNum: 1 }, true)},getTargetTableList(params = null) {const targetTableList = filter(this.selectList, (item) => {if (this.searchStr) {const itemValueToString = isNil(item[this.searchKey]) ? '' : JSON.stringify(item[this.searchKey])return itemValueToString.includes(this.searchStr)} else {return true}})this.targetTotal = targetTableList.lengthif (params) {this.targetPageNum = params.pageNumthis.targetPageSize = params.pageSize}// 前端分页const startIndex = (this.targetPageNum - 1) * this.targetPageSizeconst endIndex = this.targetPageNum * this.targetPageSizethis.targetList = targetTableList.slice(startIndex, endIndex)},clearSelection() {// 清空所有选择项(用于消费端设置的 ref 调用)this.selectList = []this.targetPageNum = 1this.targetPageSize = 10this.searchKey = this.targetSearchList[0].prop},setPaginationParam({ pageNum, pageSize }, isFetch) {// 设置源表格分页器参数(用于消费端设置的 ref 调用)//  若 isFetch 为 true,则自动调用消费端传进来的回调搜索方法this.$refs['sourceTransferTableRef'].setPaginationParam({ pageNum, pageSize }, isFetch)},initializeComponent(isFetch) {// 初始化组件(用于消费端设置的 ref 调用)//  若 isFetch 为 true,则自动调用消费端传进来的回调搜索方法this.clearSelection()this.setPaginationParam({ pageNum: this.initSourcePageNum || 1, pageSize: this.initSourcePageSize || 10 }, isFetch)},},}
</script><style lang="scss" scoped>.transfer-horizontal {display: flex;}.list-wrapping {margin-bottom: 12px;border-radius: 2px;border: 1px solid #d9d9d9;background: #fff;overflow: hidden;}.horizontal {flex: 1;margin-right: 20px;margin-bottom: 0px;&:last-child {margin: 0px;}}.wrapping-header {width: 100%;padding: 10px 20px;height: 40px;display: flex;justify-content: space-between;align-items: center;border-bottom: 1px solid #d9d9d9;background: #f5f5f5;color: #333;font-size: 14px;line-height: 20px;}.wrapping-content {padding: 12px;width: 100%;}.search-input {margin-bottom: 12px;max-width: 500px;height: 32px;}.search-select {width: 120px;}.empty-box {display: flex;align-items: center;justify-content: center;width: 100%;height: 100%;margin: 40px 0px;}.empty-image {width: 150px;height: 150px;}:deep(.search-input .el-input-group__prepend) {background-color: #fff;}:deep(.el-select .el-input .el-select__caret) {color: #3564ff;}
</style>

./TransferPlus/TransferTable.vue

javascript"><template><div><!-- 表格区域 --><el-tableref="transferTable"v-loading="tableLoading":border="true":data="tableList"size="mini":stripe="true":height="tableHeight || 'auto'":row-class-name="getTableRowClassName":header-cell-style="{background: '#F1F1F1',}"@select="handleSelect"@select-all="handleSelectAll"><el-table-column type="index" align="center"></el-table-column><el-table-column type="selection" width="50" v-if="tableType === 'source'" :selectable="selectable"></el-table-column><el-table-column v-for="(item, index) in tableColumnList" :key="item.prop || index" :label="item.label" :prop="item.prop" :align="item.align || 'left'" :width="item.width || 'auto'" show-overflow-tooltip><template #default="columnProps"><slot :name="`inner_table_${item.prop}`" :columnProps="columnProps"><span>{{ columnProps.row[item.prop] }}</span></slot></template></el-table-column><el-table-column fixed="right" label="操作" width="70" align="center" v-if="tableType === 'target'"><template slot-scope="scope"><el-button @click="handleRemoveRowItem(scope.row, scope.$index)" type="text" icon="el-icon-delete" size="medium"></el-button></template></el-table-column></el-table><!-- 分页器区域 --><div v-if="usePagination" class="pagination-box"><!-- 实现两侧分布的分页器布局:使用两个分页器组件 + 不同 layout 组成 --><el-pagination background :current-page="pageNum" :layout="layoutLeft" :page-size="pageSize" :pager-count="pagerCount" :total="total" @current-change="handleCurrentChange" @size-change="handleSizeChange" /><el-pagination background :current-page="pageNum" :layout="layoutRight" :page-size="pageSize" :pager-count="pagerCount" :total="total" @current-change="handleCurrentChange" @size-change="handleSizeChange" /></div></div>
</template><script>import { differenceBy, uniqBy, noop } from 'lodash'export default {props: {// 已勾选的数组value: {type: Array,default: () => [],require: true,},// 表格数据tableList: {type: Array,default: () => [],},// 表格列配置列表tableColumnList: {type: Array,default: () => [], // {label,prop,align}[]},// 表格数据是否加载中tableLoading: {type: Boolean,default: false,},// 表格高度tableHeight: {type: String | Number,default: 240,},// 表格数据类型tableType: {type: String,default: 'source', // source 源列表,target 目标列表},// 【已添加列表】搜索项(注:与表格列配置对应,且仅能字段类型为 String)searchList: {type: Array,default: () => [], // {label,prop,align}[]},// 分页后表格总数据的条数total: {type: Number,default: 0,},// 初始 pageNuminitPageNum: {type: Number,default: 1,},// 初始 pageSizeinitPageSize: {type: Number,default: 10,},// 使用分页器usePagination: {type: Boolean,default: false,},// 唯一标识符(便于定位到某条数据进行添加和移除操作)uniqueKey: {type: String,default: 'id',},// Function 的返回值用来决定这一行的 CheckBox 是否可以勾选selectable: {type: Function,default: noop(),},// 页码按钮的数量,当总页数超过该值时会折叠(element规定:大于等于 5 且小于等于 21 的奇数)pagerCount: {type: Number,default: 7,},},data() {return {layoutLeft: 'total',layoutRight: 'sizes, prev, pager, next',pageNum: 1,pageSize: 10,preSelectList: [], // 上一次选择的数据(点击分页器就清空)stashSelectList: [], // 暂存数据,便于点击页码后,还能保存前一页的数据isNeedToggle: true, // 是否需要勾选该页已选择项(用于换页后的回显选择项)isTableChangeData: false, // 是否是当前表格造成选择项的变化(用于同步【待选择列表】的勾选项)}},computed: {currentPageSelectList() {const currentSelectList = []this.stashSelectList?.forEach((item) => {const currentRow = this.tableList?.find((row) => row[this.uniqueKey] === item[this.uniqueKey])if (currentRow) {currentSelectList.push(currentRow)}})return currentSelectList},},watch: {value(newVal) {this.stashSelectList = newVal || []// 只有在其他地方修改了选择表格数据后,才刷新覆盖勾选项(当前表格修改选择项是双向绑定的,所以不需要刷新覆盖勾选项),实现精准回显和两表格的联动if (!this.isTableChangeData) {this.handleToggleSelection()}// 当暂存的选择列表为空时,需要同步更新 preSelect 为空数组,以便下次选择时进行判断是增加选择项还是减少选择项if (!this.stashSelectList.length) {this.preSelectList = []}this.isTableChangeData = false},tableList() {if (this.isNeedToggle) {this.preSelectList = this.currentPageSelectListthis.handleToggleSelection()this.isNeedToggle = false}},},mounted() {this.pageNum = this.initPageNum || 1this.pageSize = this.initPageSize || 110// 解决右侧固定操作栏错位问题this.$nextTick(() => {this.$refs.transferTable.doLayout()})this.$emit('selectionChange', [])},methods: {getTableRowClassName({ rowIndex }) {if (rowIndex % 2 == 0) {return ''} else {return 'stripe-row'}},fetchTableList(pageNum = 1) {if (this.usePagination) {// 若不是页码更改触发,则默认将 pageNum 重置为 1this.pageNum = pageNumconst params = {pageNum: this.pageNum,pageSize: this.pageSize,}this.$emit('fetchTableList', params)} else {this.$emit('fetchTableList')}},setPaginationParam({ pageNum, pageSize }, isFetch = false) {// 设置分页器参数(用于消费端设置的 ref 调用)this.pageNum = pageNum || this.pageNumthis.pageSize = pageSize || this.pageSizethis.isNeedToggle = trueif (isFetch) {this.fetchTableList()}},handleSizeChange(val) {this.pageSize = valthis.isNeedToggle = truethis.fetchTableList()},handleCurrentChange(val) {this.isNeedToggle = truethis.fetchTableList(val)},handleStashSelectList(isAdd = true, list = []) {if (isAdd) {// 暂存数组中增加,并兜底去重this.stashSelectList = uniqBy([...this.stashSelectList, ...list], this.uniqueKey)} else {// 暂存数组中移除this.stashSelectList = differenceBy(this.stashSelectList, list, this.uniqueKey)}this.isTableChangeData = truethis.$emit('input', this.stashSelectList)this.$emit('selectionChange', this.stashSelectList)},handleSelect(selectList, row) {// 判断是否是增加选择项const isAddRow = this.preSelectList.length < selectList.lengththis.handleStashSelectList(isAddRow, [row])// 更新当前页记录的上次数据this.preSelectList = [...selectList]},handleSelectAll(selectList) {// 判断是否是全选(需要考虑两个数组长度相等的情况)const isAddAll = this.preSelectList.length <= selectList.length// 更新当前页记录的上次数据this.handleStashSelectList(isAddAll, isAddAll ? selectList : this.preSelectList)this.preSelectList = [...selectList]},handleRemoveRowItem(rowItem, rowIndex) {const remainderPage = this.total % this.pageSize ? 1 : 0const pageNumTotal = parseInt(this.total / this.pageSize) + remainderPageconst isLastPageOnlyOne = rowIndex === 0 && this.pageNum === pageNumTotal// 判断删除的是否是最后一页中只有一条的数据if (isLastPageOnlyOne && this.pageNum > 1) {// 若是,则 pageNum 需要往前调整一页,因为删除后最后一页不存在this.handleCurrentChange(this.pageNum - 1)}this.$emit('removeSelectRow', rowItem)},handleToggleSelection() {this.$nextTick(() => {// 先清除所有勾选状态this.$refs.transferTable.clearSelection()if (this.currentPageSelectList.length) {// 再依次勾选当前页存在的行this.currentPageSelectList.forEach((item) => {this.$refs.transferTable.toggleRowSelection(item, true)})}})},},}
</script><style scoped>/* 表格斑马自定义颜色 */:deep(.el-table__row.stripe-row) {background: #f9f9f9;}/* 表格操作栏按钮取消间距 */:deep(.el-button) {padding: 0px;}/* 表格操作栏按钮固定大小 */:deep(.el-icon-delete::before) {font-size: 14px !important;}.pagination-box {display: flex;justify-content: space-between;}
</style>

http://www.ppmy.cn/devtools/115737.html

相关文章

98-策略模式的理解

‌策略模式是一种软件设计模式&#xff0c;它定义了一系列算法&#xff0c;并将每个算法封装起来&#xff0c;使它们可以相互替换。这种模式允许算法的变化不会影响使用算法的客户端&#xff0c;通过将使用算法的责任和算法的实现分割开来&#xff0c;并委派给不同的对象对这些…

Go缓存系统

1.缓存 缓存&#xff08;Caching&#xff09;&#xff0c;用于提高数据访问速度和系统性能。它通过在快速的存储介质中保存数据的副本&#xff0c;使得数据可以被更快地检索&#xff0c;而不是每次都从较慢的原始数据源&#xff08;如数据库或磁盘&#xff09;中获取。缓存通常…

数据结构-树(基础,分类,遍历)

数据结构-树 1.什么是树&#xff1f; 在计算机科学中&#xff0c;树是一种常用的非线性数据结构&#xff0c;用于表示具有层次关系的数据。与线性数据结构&#xff08;如数组和链表&#xff09;不同&#xff0c;树结构以节点&#xff08;Nodes&#xff09;和边&#xff08;Ed…

搭建 PHP

快速搭建 PHP 环境指南 PHP 是一种广泛用于 Web 开发的后端脚本语言&#xff0c;因其灵活性和易用性而受到开发者的青睐。无论是开发个人项目还是企业级应用&#xff0c;PHP 环境的搭建都是一个不可忽视的基础步骤。本指南将带您快速学习如何在不同平台上搭建 PHP 环境&#x…

CentOS入门宝典:从零到一构建你的Linux服务器帝国

目录 引言 一、CentOS简介与版本选择 1.1 CentOS是什么&#xff1f; 1.2 版本选择 二、安装CentOS 2.1 准备安装介质 2.2 安装过程 三、基础配置与优化 3.1 更新系统 3.2 配置防火墙 3.3 配置SELinux 3.4 系统监控与日志 四、网络配置与管理 4.1 配置静态IP 4.…

Kotlin-Flow学习笔记

Channel 和 Flow 都是数据流&#xff0c;Channel 是“热”的&#xff0c;Flow 则是“冷”的。这里的冷&#xff0c;代表着 Flow 不仅是“冷淡”的&#xff0c;而且还是“懒惰”的。 Flow 从 API 的角度分类&#xff0c;主要分为&#xff1a;构造器、中间操作符、终止操作符。今…

深入了解 Maven 和 Redis

在现代软件开发中&#xff0c;工具的选择对于项目的成功至关重要。Maven 和 Redis 是两个在不同领域发挥着重要作用的工具&#xff0c;本文将对它们进行详细介绍。 一、Maven&#xff1a;强大的项目管理工具 &#xff08;一&#xff09;什么是 Maven&#xff1f; Maven 是一个基…

ARM驱动学习之 IOremap实现GPIO 读

ARM驱动学习之 IOremap实现GPIO 读 前面介绍了虚拟地址和物理地址。 读写GPIO&#xff0c;控制GPIO的寄存器都是使用系统做好的虚拟地址 本期介绍如何自己实现物理地址到虚拟地址的转化 iounmap和ioremap函数可以实现物理地址到虚拟地址的转化1.根据原理图找核心板对应的寄存器…