【摸鱼神器】UI库秒变LowCode工具——列表篇(一)设计与实现
内容摘要:
- 需求分析
- 定义 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工具——列表篇(一)设计与实现的更多相关文章
- 【摸鱼神器】UI库秒变LowCode工具——列表篇(二)维护json的小工具
上一篇介绍了一下如何实现一个可以依赖 json 渲染的列表控件,既然需要 json 文件,那么要如何维护这个 json 文件就成了重点,如果没有好的维护方案的话,那么还不如直接用UI库. 所以需要我们 ...
- 【摸鱼神器】UI库秒变低代码工具——表单篇(二)子控件
上一篇介绍了表单控件,这一篇介绍一下表单里面的各种子控件的封装方式. 主要内容 需求分析 子控件的分类 子控件属性的分类 定义 interface. 定义子控件的的 props. 定义 json 文件 ...
- 【摸鱼神器】UI库秒变低代码工具——表单篇(一)设计
前面说了列表的低代码化的方法,本篇介绍一下表单的低代码化. 内容摘要 需求分析. 定义 interface. 定义表单控件的 props. 定义 json 文件. 基于 el-form 封装,实现依赖 ...
- Thief-Book 上班摸鱼神器
Thief-Book 上班摸鱼神器 介绍 Thief-Book 是一款真正的摸鱼神器,可以更加隐秘性大胆的看小说. 隐蔽性 自定义透明背景,随意调整大小,完美融入各种软件界面 快捷性 三个快捷键,实现 ...
- vscode插件(摸鱼神器-小霸王游戏机
vscode插件(摸鱼神器-小霸王游戏机 步骤 vscode扩展搜索小霸王,点击下载即可. 使用 默认有一个demo小游戏,即超级玛丽. 本地仓库 可以通过local菜单上的添加按钮添加本地nes r ...
- vue-cli3.0结合lib-flexible、px2rem实现移动端适配,完美解决第三方ui库样式变小问题
公司最近做的一个移动端项目从搭框架到前端开发由我独立完成,以前做移动端适配用的媒体查询,这次想用点别的适配方案,然后就采用了vue-cli3.0结合lib-flexible.px2rem实现移动端适配 ...
- 【转】让Chrome化身成为摸鱼神器,利用Chorme运行布卡漫画以及其他安卓APK应用教程
下周就是十一了,无论是学生党还是工作党,大家的大概都会有点心不在焉,为了让大家更好的心不在焉,更好的在十一前最后一周愉快的摸鱼,今天就写一个如何让Chrome(google浏览器)运行安卓APK应用的 ...
- 【摸鱼神器】UCode Cms管理系统 内置超好用的代码生成器 解决多表连接痛点
一.序言 UCode Cms管理系统是面向企业级应用软件开发的脚手架.当前版本1.3.4.快速体验: git clone https://gitee.com/decsa/demo-cms.git (一 ...
- 【摸鱼神器】基于SSM风格的Java源代码生成器 单表生成 一对一、一对多、多对多连接查询生成
一.序言 UCode Cms 是一款Maven版的Java源代码生成器,是快速构建项目的利器.代码生成器模块属于可拆卸模块,即按需引入.代码生成器生成SSM(Spring.SpringBoot.Myb ...
随机推荐
- 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 ...
- 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 解决办法 - 程序员大本营 ...
- Blazor组件自做九: 用20行代码实现文件上传,浏览目录功能 (3)
接上篇 Blazor组件自做九: 用20行代码实现文件上传,浏览目录功能 (2) 7. 使用配置文件指定监听地址 打开 appsettings.json 文件,加入一行 "UseUrls&q ...
- Conda 配置 Python 环境
目录 前言 一.Conda 是什么 二.如何获取 三.使用 Conda 命令配置多环境 1.创建新环境 2.激活新环境 3.配置新环境 4.退出新环境 5.检查所有环境 6.检查所有安装的包 7.删除 ...
- Z-blog csrf漏洞学习
Z-blog csrf 环境搭建 1. 首先我在本地搭了一个z-blog. 思路:csrf并不侧重于哪种功能点,只要检测不规范,就可能利用成功,所以我考虑了一下后台添加管理员的地方. 数据包构造 ...
- Python入门-迭代器和生成器
迭代演示 # 传统数据生成缺陷演示,编号操作未全部使用,会占用内存 #合适的做法,是需要的时候再生产,而不是全部生成好了再用 def generator(maxnum): print("[代 ...
- ethool的使用
ethtool命令 网络配置 ethtool命令用于获取以太网卡的配置信息,或者修改这些配置.这个命令比较复杂,功能特别多 语法 ethtool [ -a | -c | -g | -i | -d | ...
- 小程序已成为超级APP必选项,逐鹿私域“留量”
截止2021年底,中国移动互联网月活跃用规模达到11.74亿人,增速逐渐呈放缓趋势,用户渗透率接近天花板.客户的增长速度越趋于平缓,品牌在不同成长阶段也要适应增长节奏的变化,越来越多主流商家不得不利用 ...
- NodeJs学习日报day9——操作数据库
const mysql = require('mysql') const db = mysql.createPool({ // 数据库的ip地址 host: 'localhost', user: 'r ...
- 通过Nginx TCP反向代理实现Apache Doris负载均衡
概述 Nginx能够实现HTTP.HTTPS协议的负载均衡,也能够实现TCP协议的负载均衡.那么,问题来了,可不可以通过Nginx实现Apache Doris数据库的负载均衡呢?答案是:可以.接下来, ...