内容摘要:

  • 需求分析
  • 定义 interface
  • 定义 json 文件
  • 定义列表控件的 props
  • 基于 el-table 封装,实现依赖 json 渲染
  • 实现内置功能:选择行(单选、多选),格式化、锁定等。
  • 使用 slot 实现自定义扩展
  • 做个工具维护 json 文件(下篇介绍)

管理后台里面,列表是一个常用的功能,UI库提供了列表组件和分页组件实现功能。虽然功能强大,也很灵活,只是还不能称为低代码,不过没关系,我们可以写点代码让UI库变为摸鱼神器!

本篇介绍列表的设计思路和封装方式。

需求分析

如果基于原生HTML来实现显示数据列表的功能的话,那么需考虑如何创建 table,如何设置css等。

如果直接使用UI库的话,那么可以简单很多,只需要设置各种属性,然后绑定数据即可。

以 el-table 为例:

  <el-table
:data="tableData"
border
stripe
style="width: 100%"
>
<el-table-column prop="date" label="Date" width="180" />
<el-table-column prop="name" label="Name" width="180" />
<el-table-column prop="address" label="Address" />
</el-table>

设置好属性、记录集合,然后设置列(el-table-column)即可。

这样一个列表就搞定了,再加上 el-pagination 分页组件,编写一些代码即可实现分页的功能。

如果只是一个列表的话,这种方式没啥问题,但是管理后台项目,往往需要n个列表,而每个列表都大同小异,如果要一个一个手撸出来,那就有点麻烦了。

那么如何解决呢?我们可以参考低代码,基于 el-talbe 封装一个列表控件,

实现依赖 json 动态渲染列表,同时支持自定义扩展。

定义 interface

最近开始学习 Typescript,发现了一个现象,如果可以先定义好类型,那么代码就可以更清晰的展现出来。

另外 Vue3 的最新文档,也采用了通过 interface 来介绍API功能的方式,所以我们也可以借鉴一下。

依据 el-table 的属性,定义列表控件属性的 interface。

Vue3 的 props 有一套约束方式,这个似乎和TS的方式有点冲突,没想出了更好的方法(option API 和 script setup两种定义props的方式,都有不足 ),所以只好做两个 interface,一个用于定义组件的 props ,一个用于取值。

  • IGridPropsComp:定义组件的 props
/**
* 列表控件的属性的描述,基于el-table
*/
export interface IGridPropsComp {
/**
* 模块ID,number | string
*/
moduleId: IPropsValidation,
/**
* 主键字段的名称 String,对应 row-key
*/
idName: IPropsValidation,
/**
* table的高度, Number
*/
height: IPropsValidation,
/**
* 列(字段)显示的顺序 Array<number|string>
*/
colOrder: IPropsValidation,
/**
* 斑马纹,Boolean
*/
stripe: IPropsValidation,
/**
* 纵向边框,Boolean
*/
border: IPropsValidation,
/**
* 列的宽度是否自撑开,Boolean
*/
fit: IPropsValidation,
/**
* 要高亮当前行,Boolean
*/
highlightCurrentRow: IPropsValidation,
/**
* 锁定的列数 Number,设置到 el-table-column 的 fixed
*/
fixedIndex: IPropsValidation,
/**
* table的列的 IGridItem
* * id: number | string,
* * colName: string, 字段名称
* * label: string, 列的标签、标题
* * width: number, 列的宽度
* * align: EAlign, 内容对齐方式
* * headerAlign: EAlign 列标题对齐方式
*/
itemMeta: IPropsValidation, //
/**
* 记录选择的行:IGridSelection
* * dataId: '', 单选ID number 、string
* * row: {}, 单选的数据对象 {}
* * dataIds: [], 多选ID []
* * rows: [] 多选的数据对象 []
*/
selection: IPropsValidation, /**
* 绑定的数据 Array, 对应 data
*/
dataList: IPropsValidation // 其他扩展属性
[propName: string]: IPropsValidation }
  • moduleId:模块ID,一个模块菜单只能有一个列表,菜单可以嵌套。
  • itemMeta:列的属性集合,记录列表的列的属性。
  • selection:记录列表的单选、多选的 row。
  • dataList:显示的数据,对应 el-table 的 data
  • 其他:对应 el-table 的属性

IGridPropsComp 的作用是,约束列表控件需要设置哪些属性,属性的具体类型,就无法在这里约束了。

  • IPropsValidation (不知道vue内部有没有这样的 interface)
/**
* vue 的 props 的验证的类型约束
*/
export interface IPropsValidation {
/**
* 属性的类型,比较灵活,可以是 String、Number 等,也可以是数组、class等
*/
type: Array<any> | any,
/**
* 是否必须传递属性
*/
required?: boolean,
/**
* 自定义类型校验函数(箭头函数),value:属性值
*/
validator?: (value: any) => boolean,
/**
* 默认值,可以是值,也可以是函数(箭头函数)
*/
default?: any
}

取 props 用的 interface

IGridPropsComp 无法约束属性的具体类型,所以只好再做一个 interface。

  • IGridProps
/**
* 列表控件的属性的类型,基于el-table
*/
export interface IGridProps {
/**
* 模块ID,number | string
*/
moduleId: number | string,
/**
* 主键字段的名称 String,对应 row-key
*/
idName: String,
/**
* table的高度, Number
*/
height: number,
/**
* 列(字段)显示的顺序 Array<number|string>
*/
colOrder: Array<number|string>,
/**
* 斑马纹,Boolean
*/
stripe: boolean,
/**
* 纵向边框,Boolean
*/
border: boolean,
/**
* 列的宽度是否自撑开,Boolean
*/
fit: boolean,
/**
* 要高亮当前行,Boolean
*/
highlightCurrentRow: boolean,
/**
* 锁定的列数 Number,设置到 el-table-column 的 fixed
*/
fixedIndex: number,
/**
* table的列的 Object< IGridItem >
* * id: number | string,
* * colName: string, 字段名称
* * label: string, 列的标签、标题
* * width: number, 列的宽度
* * align: EAlign, 内容对齐方式
* * headerAlign: EAlign 列标题对齐方式
*/
itemMeta: {
[key:string | number]: IGridItem
}, //
/**
* 选择行的情况:IGridSelection
* * dataId: '', 单选ID number 、string
* * row: {}, 单选的数据对象 {}
* * dataIds: [], 多选ID []
* * rows: [] 多选的数据对象 []
*/
selection: IGridSelection, /**
* 绑定的数据 Array, 对应 data
*/
dataList: Array<any> // 其他扩展属性
[propName: string]: any
}

对比一下就会发现,属性的类型不一样。因为定义 props 需要使用一套特定的对象格式,而使用 props 的时候需要的是属性自己的类型。

理想情况下,应该可以在 script setup 里面,引入外部文件 定义的 interface ,然后设置给组件的 props,但是到目前为止还不支持,只能在( script setup方式的)组件内部定义 props。希望早日支持,支持了就不会这么纠结和痛苦了。

依据 el-table-column 定义列属性的 interface。

  • IGridItem:列表里面列的属性
/**
* 列的属性,基于 el-table-column
*/
export interface IGridItem {
/**
* 字段ID、列ID
*/
id: number | string,
/**
* 字段名称
*/
colName: string,
/**
* 列的标签、标题
*/
label: string,
/**
* 列的宽度
*/
width: number,
/**
* 内容对齐方式 EAlign
*/
align: EAlign,
/**
* 列标题对齐方式
*/
headerAlign: EAlign, // 其他扩展属性
[propName: string]: any
}

还是需要扩展属性的,因为这里只是列出来目前需要的属性,el-table-column 的其他属性、方法还有很多,而且以后也可能会新增。

这个属性不是直接设置给组件的 props,所以不用定义两套了。

对齐方式的枚举

枚举可以理解为常量,定义之后可以避免低级错误,避免手滑。

  • EAlign
export const enum EAlign {
left = 'left',
center = 'center',
right = 'right'
}

选择记录的 interface。

列表可以单选也可以多选,el-table 在默认情况下似乎是二选一,觉得有点不方便,为啥不能都要?

  • 单选:鼠标单一任意一行就是单选;(清空其他已选项)
  • 多选:单击第一列的(多个)复选框,就是多选;

这样用户就可以愉快的想单选就单选,想多选就多选了。

  • IGridSelection
/**
* 列表里选择的数据
*/
export interface IGridSelection {
/**
* 单选ID number 、string
*/
dataId: number | string,
/**
* 单选的数据对象 {}
*/
row: any,
/**
* 多选ID []
*/
dataIds: Array<number | string>,
/**
* 多选的数据对象 []
*/
rows: Array<any>
}

其实我觉得只记录ID即可,不过既然 el-talble 提供的 row,那么还是都记录下来吧。

定义 json 文件

接口定义好之后,我们可以依据 interface 编写 json 文件:

{
"moduleId": 142,
"height": 400,
"idName": "ID",
"colOrder": [
90, 100, 101
],
"stripe": true,
"border": true,
"fit": true,
"highlightCurrentRow": true,
"highlight-current-row": true,
"itemMeta": {
"90": {
"id": 90,
"colName": "kind",
"label": "分类",
"width": 140,
"title": "分类",
"align": "center",
"header-align": "center"
},
"100": {
"id": 100,
"colName": "area",
"label": "多行文本",
"width": 140,
"title": "多行文本",
"align": "center",
"header-align": "center"
},
"101": {
"id": 101,
"colName": "text",
"label": "文本",
"width": 140,
"title": "文本",
"align": "center",
"header-align": "center"
}
}
}
  • 为什么直接设置 json 文件而不是 js 对象呢?

    因为对象会比较长,如果是代码形式的话,那还不如直接使用UI库组件来的方便呢。

  • 你可能又会问了,既然直接用 json文件,为啥还要设计 interface 呢?

    当然是为了明确各种类型,interface 可以当做文档使用,另外封装UI库的组件的时候,也可以用到这些 interface。使用列表控件的时候也可以使用这些 interface。

其实json文件不用手动编写,而是通过工具来编写和维护。

定义列表控件的 props

封装组件之前需要先定义一下组件需要的 props:

  • props-grid.ts
import type { PropType } from 'vue'

import type {
IGridProps,
IGridItem,
IGridSelection
} from '../types/50-grid' /**
* 表单控件需要的属性propsForm
*/
export const gridProps: IGridProps = {
/**
* 模块ID,number | string
*/
moduleId: {
type: Number,
required: true
},
/**
* 主键字段的名称
*/
idName: {
type: String,
default: 'ID'
},
/**
* 字段显示的顺序
*/
colOrder: {
type: Array as PropType<Array<number | string>>,
default: () => []
},
/**
* 锁定的列数
*/
fixedIndex: {
type: Number,
default: 0
},
/**
* table的列的 meta
*/
itemMeta: {
type: Object as PropType<{
[key:string | number]: IGridItem
}>
},
/**
* 选择的情况 IGridSelection
*/
selection: {
type: Object as PropType<IGridSelection>,
default: () => {
return {
dataId: '', // 单选ID number 、string
row: {}, // 单选的数据对象 {}
dataIds: [], // 多选ID []
rows: [] // 多选的数据对象 []
}
}
},
/**
* 绑定的数据
*/
dataList: {
type: Array as PropType<Array<any>>,
default: () => []
},
其他略。。。
}

按照 Option API 的方式设置 props 的定义,这样便于共用属性的定义(好吧似乎也没有需要共用的地方,不过我还是喜欢把 props 的定义写在一个单独的文件里)。

封装列表控件

定义好 json 、props之后,我们基于 el-table 封装列表控件:

  • template 模板
  <el-table
ref="gridRef"
v-bind="$attrs"
:data="dataList"
:height="height"
:stripe="stripe"
:border="border"
:fit="fit"
:highlight-current-row="highlightCurrentRow"
:row-key="idName"
@selection-change="selectionChange"
@current-change="currentChange"
>
<!--多选框,实现多选功能-->
<el-table-column
type="selection"
width="55"
align="center"
header-align="center"
@click="clickCheck"
>
</el-table-column>
<!--依据 json 渲染的字段列表-->
<el-table-column
v-for="(id, index) in colOrder"
:key="'grid_list_' + index + '_' + id"
v-bind="itemMeta[id]"
:column-key="'col_' + id"
:fixed="index < fixedIndex"
:prop="itemMeta[id].colName"
>
</el-table-column>
</el-table>

设置 type="selection"列,实现多选的功能。

使用 v-for 的方式,遍历出动态列。

设置 :fixed="index < fixedIndex",实现锁定左面列的功能。

  • js 代码
  import { defineComponent, ref } from 'vue'
// 列表控件的属性
import { gridProps } from '../map' /**
* 普通列表控件
*/
export default defineComponent({
name: 'nf-elp-grid-list',
inheritAttrs: false,
props: {
...gridProps // 解构共用属性
},
setup (props, ctx) {
// 获取 el-table
const gridRef = ref(null) return {
gridRef
}
}
})

把 props 的定义放在单独的 ts文件 里面,组件内部的代码就可以简洁很多。

实现内置功能

可以按照自己的喜好,设置一些内部功能,比如单选/多选的功能,格式化的功能等。

  • 定义控制函数 controller.ts
import type { ElTable } from 'element-plus'

// 列表控件的属性
import type { IGridProps } from '../map' export interface IRow {
[key: string | number]: any
} /**
* 列表的单选和多选的事件
* @param props 列表组件的 props
* @param gridRef el-table 的 $ref
*/
export default function choiceManage<T extends IGridProps, V extends typeof ElTable>(props: T, gridRef: V) {
// 是否单选触发
let isCurrenting = false
// 是否多选触发
let isMoring = false // 单选
const currentChange = (row: IRow) => {
if (isMoring) return // 多选代码触发
if (!row) return // 清空 if (gridRef.value) {
isCurrenting = true
gridRef.value.clearSelection() // 清空多选
gridRef.value.toggleRowSelection(row) // 设置复选框
gridRef.value.setCurrentRow(row) // 设置单选
// 记录
props.selection.dataId = row[props.idName]
props.selection.dataIds = [ row[props.idName] ]
props.selection.row = row
props.selection.rows = [ row ] isCurrenting = false
}
} // 多选
const selectionChange = (rows: Array<IRow>) => {
if (isCurrenting) return
// 记录
if (typeof props.selection.dataIds === 'undefined') {
props.selection.dataIds = []
}
props.selection.dataIds.length = 0 // 清空
// 设置多选
rows.forEach((item: IRow) => {
if (typeof item !== 'undefined' && item !== null) {
props.selection.dataIds.push(item[props.idName])
}
})
props.selection.rows = rows
// 设置单选
switch (rows.length) {
case 0:
// 清掉单选
gridRef.value.setCurrentRow()
props.selection.dataId = ''
props.selection.row = {}
break
case 1:
isMoring = true
// 设置新单选
gridRef.value.setCurrentRow(rows[0])
isMoring = false
props.selection.row = rows[0]
props.selection.dataId = rows[0][props.idName]
break
default:
// 去掉单选
gridRef.value.setCurrentRow()
props.selection.row = rows[rows.length - 1]
props.selection.dataId = props.selection.row[props.idName]
}
} return {
currentChange, // 单选
selectionChange // 多选
}
}
  • 列表控件的 setup 里调用
setup (props, ctx) {
// 获取 el-table
const gridRef = ref<InstanceType<typeof ElTable>>() // 列表选项的事件
const {
currentChange, // 单选
selectionChange // 多选
} = choiceManage(props, gridRef) return {
selectionChange, // 多选
currentChange, // 单选
gridRef // table 的 dom
}
}

这里有一个“度”的问题:

  • el-table 完全通过 slot 的方式实现各种功能,这种方法的特点是:非常灵活,可以各种组合;缺点是比较繁琐。

    而我们需要寻找到一个适合的“折中点”,显然这个折中点很难统一,这也是过渡封装带来的问题。

  • 不能遇到新的需求,就增加内部功能,这样就陷入了《人月神话》里说的“焦油坑”,进去了就很难出来。

这也是低代码被诟病的因素。

支持扩展

那么如何找到这个折中点呢?可以按照 “开闭原则”,按照不同的需求,设置多个不同功能的列表控件,使用 slot 实现扩展功能。或者干脆改为直接使用 el-table 的方式。(要灵活,不要一刀切)

比如简单需求,不需要扩展功能的情况,设置一个基础列表控件:nf-grid。

需要扩展列的情况,设置一个可以扩展的列表控件:nf-grid-slot。

如果需要多表头、树形数据等需求,可以设置一个新的列表控件,不过需要先想想,是不是直接用 el-table 更方便。

要不要新增一个控件,不要惯性思维,而要多方面全局考虑。

这里介绍一下支持 slot 扩展的列表控件的封装方式:

  <el-table
ref="gridDom"
v-bind="$attrs"
size="mini"
style="width: 100%"
:data="dataList"
:height="height"
:stripe="stripe"
:border="border"
:fit="fit"
:highlight-current-row="highlightCurrentRow"
:current-row-key="idName"
:row-key="idName"
@selection-change="selectionChange"
@current-change="currentChange"
>
<!--显示选择框-->
<el-table-column
type="selection"
width="55">
</el-table-column>
<!--显示字段列表-->
<template
v-for="(id, index) in colOrder"
:key="'grid_list_' + index + '_' + id"
>
<!--检查插槽里是否包含 字段名,作为判断依据-->
<!--不带插槽的列-->
<el-table-column
v-if="!(slotsKey.includes(itemMeta[id].colName))"
:fixed="index < fixedIndex"
v-bind="itemMeta[id]"
:prop="itemMeta[id].colName"
:min-width="50"
>
</el-table-column>
<!--带插槽的列-->
<el-table-column v-else
v-bind="itemMeta[id]"
>
<template #default="scope">
<!--读取外部插槽内容,并且传递 scope -->
<slot :name="itemMeta[id].colName" v-bind="scope"></slot>
</template>
</el-table-column>
</template>
</el-table>

模板部分,首先判断一下是否需要使用 slot,做一个分支。

需要使用 slot 的列,通过 <template #default="scope"> 设置 slot。

  • 代码部分
  import { defineComponent, ref } from 'vue'
// 表单控件的属性
import { gridProps } from '../map'
import choiceManage from './controller' export default defineComponent({
name: 'nf-elp-grid-slot',
inheritAttrs: false,
props: {
...gridProps
},
setup (props, ctx) {
// 记录插槽 的 名称
const slots = ctx.slots
const slotsKey = Object.keys(slots) // 列表选项的事件
const {
currentChange, // 单选
selectionChange // 多选
} = choiceManage(props, gridRef) return {
slotsKey,
selectionChange, // 多选
currentChange // 单选
}
}
})

一般列表的使用方法

封装之后,使用起来就很方便了,引入 json文件,设置属性即可。

  • template
  <nf-grid
v-bind="gridMeta"
:dataList="dataList"
:selection="selection"
size="small"
/>

是不是简单多了。

  • 代码部分
  import { defineComponent, reactive } from 'vue'
import { nfGrid, createDataList } from '../../../../lib-elp/main'
import _gridMeta from '../../grid/grid.json'
import _formMeta from '../../form/form.json' export default defineComponent({
name: 'nf-elp-grid-page',
components: { nfGrid },
setup(props) {
// 不需要动态改变的话,可以不使用 reactive。
const gridMeta = reactive(_gridMeta)
// 设置选择的行
const selection = reactive({
dataId: '', // 单选ID number 、string
row: {}, // 单选的数据对象 {}
dataIds: [], // 多选ID []
rows: [] // 多选的数据对象 []
})
// 设置记录集。
const dataList = reactive(_dataList) return {
dataList,
selection,
gridMeta
}
}
})

控件可以做成全局组件的形式。

  • 看看效果

扩展列表的使用方法

首先还是依据 json 渲染列表,然后根据需要设置插槽即可,设置插槽后会替换默认的列。

  • template
  可以使用 slot 自定义扩展列 <br>
<!--表单控件-->
<nf-grid
v-grid-drag="gridMeta"
v-bind="gridMeta"
:dataList="dataList"
:selection="selection"
size="small"
>
<!--普通字段,用字段名作为插槽的名称-->
<template #text="scope">
<div style="display: flex; align-items: center">
<el-icon><timer /></el-icon>
<span style="margin-left: 10px">扩展:{{ scope.row.text }}</span>
</div>
</template>
<!--普通字段-->
<template #week="scope">
<span style="margin-left: 10px">{{ scope.row.week.replace('-w','年 第') + '周' }}</span>
</template>
<!--操作按钮-->
<template #option="scope">
<el-button size="small" @click="handleEdit(scope.$index, scope.row)">修改</el-button>
<el-button
size="small"
type="danger"
@click="handleDelete(scope.$index, scope.row)">删除</el-button>
</template>
</nf-grid>

通过 slot 扩展列,可以按照 Table-column 的匿名插槽的方式进行设置。

列的先后顺序还是由 colOrder 控制,和插槽的先后顺序无关。

  • 代码部分
  import { defineComponent, reactive } from 'vue'
// 使用 图标
import { Timer } from '@element-plus/icons-vue'
import { nfGridSlot, createDataList } from '../../../../lib-elp/main'
import _gridMeta from '../../grid/grid.json'
import _formMeta from '../../form/form.json' import { EAlign } from '../../../../lib/types/enum'
import type { IGridSelection, IGridItem } from '../../../../lib/types/50-grid' export default defineComponent({
name: 'nf-elp-grid-slot-page',
components: {
Timer,
nfGrid: nfGridSlot
},
props: {
moduleID: { // 模块ID
type: [Number, String],
default: 1
}
},
setup(props) {
const gridMeta = reactive(_gridMeta) // 设置列的先后顺序和是否显示
gridMeta.colOrder = [90, 100, 101, 102, 105, 113, 115, 116, 120,121,150, 2000] // 设置一个操作按钮列
const optionCol: IGridItem = {
id: 2000,
colName: "option",
label: "操作",
width: 180,
fixed: EAlign.right,
align: EAlign.center, // 使用枚举
headerAlign: EAlign.center
} gridMeta.itemMeta['2000'] = optionCol // 设置操作列,也可以直接在json文件里设置。
const dataList = reactive(_dataList) const handleEdit = (index: number, row: any) => {
console.log(index, row)
}
const handleDelete = (index: number, row: any) => {
console.log(index, row)
} return {
handleEdit,
handleDelete,
dataList,
gridMeta
}
}
})

使用字段名称作为插槽的名称,可以把任意字段变成插槽的形式。

如果要添加操作按钮这类的列,可以给 itemMeta 添加对应的列属性。

  • 看看效果:

管理 json

其实,前面介绍的那些大家可能都会想到,也许早就实践过了,然后发现虽然看着挺好,但是其实没有解决根本问题!只是把 template 里的问题转移到 json 里面。

虽然不需要设置模板,但是需要设置 json,还不是一样,有啥本质区别吗?

其实不一样的,管理 json 的难度明显比管理模板要简单得多。

比如我们可以做一个维护 json 的小工具:

  • 首先从数据库文档生成基础的 json(毛坯房);
  • 然后使用可视化+拖拽的方式设置格子细节(精装修)。

这样就可以很方便的维护 json 了。具体实现方式,将在下一篇再介绍。

源码

https://gitee.com/naturefw-code/nf-rollup-ui-controller

https://gitee.com/naturefw-code/nf-rollup-ui-element-plus

【摸鱼神器】UI库秒变LowCode工具——列表篇(一)设计与实现的更多相关文章

  1. 【摸鱼神器】UI库秒变LowCode工具——列表篇(二)维护json的小工具

    上一篇介绍了一下如何实现一个可以依赖 json 渲染的列表控件,既然需要 json 文件,那么要如何维护这个 json 文件就成了重点,如果没有好的维护方案的话,那么还不如直接用UI库. 所以需要我们 ...

  2. 【摸鱼神器】UI库秒变低代码工具——表单篇(二)子控件

    上一篇介绍了表单控件,这一篇介绍一下表单里面的各种子控件的封装方式. 主要内容 需求分析 子控件的分类 子控件属性的分类 定义 interface. 定义子控件的的 props. 定义 json 文件 ...

  3. 【摸鱼神器】UI库秒变低代码工具——表单篇(一)设计

    前面说了列表的低代码化的方法,本篇介绍一下表单的低代码化. 内容摘要 需求分析. 定义 interface. 定义表单控件的 props. 定义 json 文件. 基于 el-form 封装,实现依赖 ...

  4. Thief-Book 上班摸鱼神器

    Thief-Book 上班摸鱼神器 介绍 Thief-Book 是一款真正的摸鱼神器,可以更加隐秘性大胆的看小说. 隐蔽性 自定义透明背景,随意调整大小,完美融入各种软件界面 快捷性 三个快捷键,实现 ...

  5. vscode插件(摸鱼神器-小霸王游戏机

    vscode插件(摸鱼神器-小霸王游戏机 步骤 vscode扩展搜索小霸王,点击下载即可. 使用 默认有一个demo小游戏,即超级玛丽. 本地仓库 可以通过local菜单上的添加按钮添加本地nes r ...

  6. vue-cli3.0结合lib-flexible、px2rem实现移动端适配,完美解决第三方ui库样式变小问题

    公司最近做的一个移动端项目从搭框架到前端开发由我独立完成,以前做移动端适配用的媒体查询,这次想用点别的适配方案,然后就采用了vue-cli3.0结合lib-flexible.px2rem实现移动端适配 ...

  7. 【转】让Chrome化身成为摸鱼神器,利用Chorme运行布卡漫画以及其他安卓APK应用教程

    下周就是十一了,无论是学生党还是工作党,大家的大概都会有点心不在焉,为了让大家更好的心不在焉,更好的在十一前最后一周愉快的摸鱼,今天就写一个如何让Chrome(google浏览器)运行安卓APK应用的 ...

  8. 【摸鱼神器】UCode Cms管理系统 内置超好用的代码生成器 解决多表连接痛点

    一.序言 UCode Cms管理系统是面向企业级应用软件开发的脚手架.当前版本1.3.4.快速体验: git clone https://gitee.com/decsa/demo-cms.git (一 ...

  9. 【摸鱼神器】基于SSM风格的Java源代码生成器 单表生成 一对一、一对多、多对多连接查询生成

    一.序言 UCode Cms 是一款Maven版的Java源代码生成器,是快速构建项目的利器.代码生成器模块属于可拆卸模块,即按需引入.代码生成器生成SSM(Spring.SpringBoot.Myb ...

随机推荐

  1. JDBC/Mybatis连接数据库报错:The server time zone value 'Öйú±ê׼ʱ¼ä' is unrecognized or represents more than one time zone.

    造成这个的原因是maven导入MyBatis的时候会自动导入最新版本的8.0.11,然后8.0.11采用了新驱动,之前版本会报错. 当我们使用高版本的MySQL驱动时可以在获取数据库的连接getCon ...

  2. Unknown host mirrors.opencas.cn You may need to adjust the proxy settings in Gradle 报错及解决办法

    亲测Unknown host mirrors.opencas.cn You may need to adjust the proxy settings in Gradle 解决办法 - 程序员大本营 ...

  3. Blazor组件自做九: 用20行代码实现文件上传,浏览目录功能 (3)

    接上篇 Blazor组件自做九: 用20行代码实现文件上传,浏览目录功能 (2) 7. 使用配置文件指定监听地址 打开 appsettings.json 文件,加入一行 "UseUrls&q ...

  4. Conda 配置 Python 环境

    目录 前言 一.Conda 是什么 二.如何获取 三.使用 Conda 命令配置多环境 1.创建新环境 2.激活新环境 3.配置新环境 4.退出新环境 5.检查所有环境 6.检查所有安装的包 7.删除 ...

  5. Z-blog csrf漏洞学习

    Z-blog csrf 环境搭建 1. 首先我在本地搭了一个z-blog. ​ 思路:csrf并不侧重于哪种功能点,只要检测不规范,就可能利用成功,所以我考虑了一下后台添加管理员的地方. 数据包构造 ...

  6. Python入门-迭代器和生成器

    迭代演示 # 传统数据生成缺陷演示,编号操作未全部使用,会占用内存 #合适的做法,是需要的时候再生产,而不是全部生成好了再用 def generator(maxnum): print("[代 ...

  7. ethool的使用

    ethtool命令 网络配置 ethtool命令用于获取以太网卡的配置信息,或者修改这些配置.这个命令比较复杂,功能特别多 语法 ethtool [ -a | -c | -g | -i | -d | ...

  8. 小程序已成为超级APP必选项,逐鹿私域“留量”

    截止2021年底,中国移动互联网月活跃用规模达到11.74亿人,增速逐渐呈放缓趋势,用户渗透率接近天花板.客户的增长速度越趋于平缓,品牌在不同成长阶段也要适应增长节奏的变化,越来越多主流商家不得不利用 ...

  9. NodeJs学习日报day9——操作数据库

    const mysql = require('mysql') const db = mysql.createPool({ // 数据库的ip地址 host: 'localhost', user: 'r ...

  10. 通过Nginx TCP反向代理实现Apache Doris负载均衡

    概述 Nginx能够实现HTTP.HTTPS协议的负载均衡,也能够实现TCP协议的负载均衡.那么,问题来了,可不可以通过Nginx实现Apache Doris数据库的负载均衡呢?答案是:可以.接下来, ...