IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> JavaScript知识库 -> 使用 Vue3 + ts 开发一个ProTable -> 正文阅读

[JavaScript知识库]使用 Vue3 + ts 开发一个ProTable

前台实现

实现效果

?

技术栈

vue3 + typescript + element-plus

使用方法

<template>
??<el-tabs?type="border-card"?v-model="activeName">
????<el-tab-pane
????:label="item.label"
????v-for="(item,?index)?in?templateConfig"
????:key="index"?:name="item.name"
????lazy
????>
????<!--所有的?slot内容都在表格内部处理好,?columnsType进行区分-->
????<pro-table
??????:columns="item.columns"
??????:type="item.name"
??????:request-url="requestUrl"
????>
????</pro-table>
????</el-tab-pane>
??</el-tabs>
</template>

<script?lang="ts"?setup>
import?{?ref?}?from?'vue'
import?ProTable?from?'./components/ProTable/index.vue'
import?{?ColumnProps,?RequestUrl?}?from?'./components/ProTable/types'
import?{?projectConfig,?projectConfigBatchDelete?}?from?'./service/api'
const?activeName?=?ref('user')

interface?TemplateConfig?{
??name:?string
??label:?string
??columns:?ColumnProps[],
}

const?requestUrl:?RequestUrl?=?{
??create:?projectConfig,
??list:?projectConfig,
??update:?projectConfig,
??destroy:?projectConfig,
??batchDelete:?projectConfigBatchDelete
}

const?templateConfig?=?ref<TemplateConfig[]>([
??{
????label:?'ProTable',
????name:?'user',
????columns:?[
??????{
????????key:?'userName',
????????title:?'用户名',
????????searchType:?'el-input'
??????},
??????{
????????key:?'password',
????????title:?'密码',
????????searchType:?'el-input'
??????},
??????{
????????key:?'email',
????????title:?'邮箱',
????????searchType:?'el-input'
??????},
??????{
????????key:?'phone',
????????title:?'手机号',
????????searchType:?'el-input'
??????},
??????{
????????key:?'role',
????????title:?'角色',
????????searchType:?'z-select',
????????attrs:?{
??????????options:?[
????????????{
??????????????label:?'管理员',
??????????????value:?'admin'
????????????},
????????????{
??????????????label:?'普通用户',
??????????????value:?'user'
????????????}
??????????]
????????}
??????},
??????{
????????key:?'status',
????????title:?'状态',
????????searchType:?'z-select',
????????attrs:?{
??????????options:?[
????????????{
??????????????label:?'启用',
??????????????value:?1
????????????},
????????????{
??????????????label:?'禁用',
??????????????value:?0
????????????}
??????????]
????????},
????????columnType:?'status'
??????},
??????{
????????key:?'hasUseArray',
????????title:?'是否使用数组参数?',
????????search:?false,
????????searchType:?'useExpandField',
????????show:?false,
????????add:?false
??????},
??????{
????????key:?'arrayParams',
????????title:?'数组参数',
????????searchType:?'z-array',
????????search:?false,
????????width:?120,
????????add:?false,
????????show:?false
??????},
??????{
????????key:?'hasUseArray',
????????title:?'是否使用JSON参数?',
????????search:?false,
????????searchType:?'useExpandField',
????????show:?false,
????????add:?false
??????},
??????{
????????key:?'jsonParams',
????????title:?'JSON参数',
????????searchType:?'z-json',
????????search:?false,
????????width:?120,
????????add:?false,
????????show:?false
??????},
??????{
????????key:?'createdAt',
????????title:?'创建时间',
????????width:?180,
????????searchType:?'el-date-picker',
????????add:?false
??????},
??????{
????????key:?'updatedAt',
????????title:?'更新时间',
????????width:?180,
????????searchType:?'el-date-picker',
????????add:?false
??????},
??????{
????????key:?'action',
????????title:?'操作',
????????search:?false,
????????add:?false,
????????width:?150
??????}
????]
??}
])
</script>
<style?lang="less">

</style>

复制代码

ProTable 设计思路

页面整体分为5个区域,

  1. 表单搜索区域

  2. 表格功能按钮区域

  3. 表格右上角操作区域

  4. 表格主题区域

  5. 表格分页区域

要考虑的问题?

  1. 那些区域是要支持传入slot的?

  2. 表格原有的slot是否要交给用户来传递,还是在内部进行封装?如colum是状态的时候需要映射成tag,是数组类型的时候映射成表格,是json的时候需要点击查看详情?假设每个表格都要处理的的话就太麻烦,我们希望通过一个字段来控制它。

  3. column的某一列是否需要复制的功能?

  4. 列字段需要编辑的功能?

实现的过程中有哪些细节?

  1. 表格的高度,把可表格可视区域的大小交给用户自己来控制,把批量操作按钮放在最下面(fixed定位)。这样用户可以在最大区域内看到表格的内容。

编码风格

  1. 组件上面属性如果超过三个,就换行

  2. eslint使用的是standard风格。

css 小知识

<div?class='box'>
??<div?class='z'></div>
</div>
复制代码
*{
??box-sizing:?border-box;
}
.box{
??display:?inline-block;
????vertical-align:?top;
}
.z{
??height:?32px;
??border:?1px?solid;
??width:?100px;
??display:?inline-block;
}
复制代码

如果把盒子变成了行内元素之后,若其内部还是行内元素,那么就会产生间隙,就会导致其高度与父元素高度不同。如下。

?

image.png

解决方法也很简单,则需要设置其子元素的vertical-align属性,或者设置font-size: 0,其根本原因是因为中间的文本元素也占位置。再或者不使用inline-block,换做inline-flex属性完全没有问题,因为在element-plus组件库中也大量的使用了这个属性,兼容性也很nice。

这个几个解决方法很早就知道了,就是关于vertical-algin这个,以及与line-height的关系还是有点模糊,一直也没去深究。

?

深入浅出vertical-align和line-height[1]?简单说 CSS的vertical-align[2]

还有联想到baseline这个东西在flex,align-items属性:交叉轴上的一个属性很像。

链接[3]

表格操作

  1. 添加数据之后,重新获取数据的时候pageIndex要重置为1,删除数据的时候也是一样。

  2. 编辑数据的时候,pageIndex不变,还是当前页码。

  3. 总结下来,就是当数据条数会发生改变的时候,都会重置pageIndex1。当用户操作不会影响数据总条数的时候,pageSize还维持当前不变。

总结

  1. 使用了一个库,可以监听dom元素大小的变化,resize-observer-polyfill[4]。

  2. 在 3.x 中,如果一个元素同时定义了?v-bind="object"?和一个相同的独立 attribute。开发者可以自己选择要保留哪一个。

<!--?模板?-->
<div?id="red"?v-bind="{?id:?'blue'?}"></div>
<!--?结果?-->
<div?id="blue"></div>

<!--?模板?-->
<div?v-bind="{?id:?'blue'?}"?id="red"></div>
<!--?结果?-->
<div?id="red"></div>
复制代码

文档地址\# v-bind 合并行为

参考文章

一个较新的JavaScript API——ResizeObserver 的使用

后期功能扩展

  1. 字段之间有关联关系情况的处理,暂时还没想好。

  2. 扩展一下slot

  3. 等等。。

迭代中....

?

后台实现

数据库 mysql

我这里使用的是?xampp安装的,我们来查看一下版本。这是什么版本?假的吧,真的到10了吗?先不管了,能用就行。

?

image.png

建表

CREATE?TABLE?`project_config`??(
??`id`?int?NOT?NULL?AUTO_INCREMENT?COMMENT?'主键',
??`type`?varchar(255)?CHARACTER?SET?utf8mb4?COLLATE?utf8mb4_general_ci?NOT?NULL?COMMENT?'配置类型',
??`value`?text?CHARACTER?SET?utf8mb4?COLLATE?utf8mb4_general_ci?NOT?NULL?COMMENT?'配置的json字符串',
??`created_at`?datetime?NOT?NULL,
??`updated_at`?datetime?NOT?NULL?DEFAULT?'0000-00-00?00:00:00'?ON?UPDATE?CURRENT_TIMESTAMP,
??PRIMARY?KEY?(`id`)?USING?BTREE
)?ENGINE?=?InnoDB?AUTO_INCREMENT?=?65?CHARACTER?SET?=?utf8mb4?COLLATE?=?utf8mb4_general_ci?ROW_FORMAT?=?COMPACT;
复制代码

新建项目

npm?init?egg?--type=simple
复制代码

项目目录大致如下所示,

?

image.png

RESTful 风格的 URL 定义

?

image.png

Sequelize

npm?install?--save?egg-sequelize?mysql2
复制代码
  • 在?config/plugin.js?中引入 egg-sequelize 插件, 这里我们引入了一个库egg-cors来帮我们实现cors

'use?strict';

/**?@type?Egg.EggPlugin?*/
exports.sequelize?=?{
??enable:?true,
??package:?'egg-sequelize',
};

exports.cors?=?{
??enable:?true,
??package:?'egg-cors',
};
复制代码
  • 在?config/config.default.js?中编写 sequelize 配置

/*?eslint?valid-jsdoc:?"off"?*/

'use?strict';


/**
?*?@param?{Egg.EggAppInfo}?appInfo?app?info
?*/
module.exports?=?appInfo?=>?{
??/**
???*?built-in?config
???*?@type?{Egg.EggAppConfig}
???**/
??const?config?=?exports?=?{};

??//?use?for?cookie?sign?key,?should?change?to?your?own?and?keep?security
??config.keys?=?appInfo.name?+?'_1655529530112_7627';

??//?add?your?middleware?config?here
??config.middleware?=?[];

??config.security?=?{
????csrf:?{
??????enable:?false,
??????ignoreJSON:?true,
????},
??};

??config.cors?=?{
????origin:?'*',
????allowMethods:?'GET,HEAD,PUT,POST,DELETE,PATCH',
??};


??//?add?your?user?config?here
??const?userConfig?=?{
????//?myAppName:?'egg',
??};

??//?sequelize

??const?sequelize?=?{
????dialect:?'mysql',
????host:?'127.0.0.1',
????port:?3306,
????username:?'root',
????password:?'123456',
????database:?'test_database',
????timezone:?'+08:00',
????dialectOptions:?{
??????dateStrings:?true,
??????typeCast:?true,
????},
????define:?{
??????freezeTableName:?true,?//?模型名强制和表明一致
??????underscored:?true,?//?字段以下划线(_)来分割(默认是驼峰命名风格)
????},
??};

??return?{
????...config,
????...userConfig,
????sequelize,
??};
};

复制代码

1、时间格式化

类型需要采用:Sequelize.DATE

初始化Sequelize的时候传入dialectOptions参数,及timezone

timezone:?'+08:00',??//?改为标准时区
dialectOptions:?{
??dateStrings:?true,
??typeCast:?true,
},
复制代码

下面就开始编写

controller

对这块需要安装lodash,懂的都懂。

controller/ProjectConfig.js

'use?strict';

const?{?success?}?=?require('../utils/res');

const?{?omit,?pick?}?=?require('lodash');


const?Controller?=?require('egg').Controller;

class?ProjectConfigController?extends?Controller?{
??async?index()?{
????const?{?ctx?}?=?this;
????const?{?pageSize,?pageIndex?}?=?ctx.query;
????const?{?Op,?fn,?col,?where,?literal?}?=?this.app.Sequelize;
????//?固定的查询参数
????const?stableQuery?=?pick(ctx.query,?[?'type',?'createdAt',?'updatedAt'?]);
????const?stableQueryArgs?=?Object.keys(stableQuery)
??????.filter(key?=>?Boolean(stableQuery[key]))
??????.map(key?=>?{
????????return?{
??????????[key]:?stableQuery[key],
????????};
??????});

????const?whereCondition?=?omit(ctx.query,?[?'pageIndex',?'pageSize',?'type',?'createdAt',?'updatedAt'?]);
????//?需要模糊查询的参数
????const?whereArgs?=?Object.keys(whereCondition)
??????.filter(key?=>?Boolean(whereCondition[key]))
??????.map(key?=>?{
????????return?where(fn('json_extract',?col('value'),?literal(`\'$.${key}\'`)),?{
??????????[Op.like]:?`%${whereCondition[key]}%`,
????????});
??????});

????const?query?=?{
??????where:?{
????????[Op.and]:?[
??????????...stableQueryArgs,
??????????...whereArgs,
????????],
??????},
??????order:?[
????????[?'createdAt',?'DESC'?],
??????],
??????limit:?Number(pageSize),?//?每页显示数量
??????offset:?(pageIndex?-?1)?*?pageSize,?//?当前页数
????};
????const?data?=?await?ctx.model.ProjectConfig.findAndCountAll(query);
????ctx.body?=?success(data);
??}

??async?create()?{
????const?{?ctx?}?=?this;
????const?{?type,?value?}?=?ctx.request.body;
????const?data?=?await?ctx.model.ProjectConfig.create({?type,?value?});
????ctx.body?=?success(data);
??}

??async?update()?{
????const?{?ctx?}?=?this;
????const?{?type,?value?}?=?ctx.request.body;
????const?{?id?}?=?ctx.params;
????const?data?=?await?ctx.model.ProjectConfig.update({?type,?value?},?{?where:?{?id?}?});
????ctx.body?=?success(data);
??}

??async?destroy()?{
????const?{?ctx?}?=?this;
????const?{?id?}?=?ctx.params;
????console.log(id);
????const?data?=?await?ctx.model.ProjectConfig.destroy({?where:?{?id?}?});
????ctx.body?=?success(data);
??}

??async?batchDestroy()?{
????const?{?ctx?}?=?this;
????const?{?ids?}?=?ctx.request.body;
????console.log(ids);
????const?{?Op?}?=?this.app.Sequelize;
????const?data?=?await?ctx.model.ProjectConfig.destroy({
??????where:?{
????????id:?{
??????????[Op.in]:?ids,
????????},
??????},
????});
????ctx.body?=?success(data);
??}
}


module.exports?=?ProjectConfigController;

复制代码

模糊查询

SELECT?json_extract(字段名,'$.json结构')?FROM?表名;
复制代码

sequelize高级查询

Post.findAll({
??where:?sequelize.where(sequelize.fn('char_length',?sequelize.col('content')),?7)
});
//?SELECT?...?FROM?"posts"?AS?"post"?WHERE?char_length("content")?=?7
复制代码

中文文档[7],英文看的吃力,看中文的也无妨,不寒碜。^_^

model

model/project_config.js

'use?strict';


module.exports?=?app?=>?{
??const?{?STRING,?INTEGER,?TEXT,?DATE?}?=?app.Sequelize;

??const?ProjectConfig?=?app.model.define('project_config',?{
????id:?{?type:?INTEGER,?primaryKey:?true,?autoIncrement:?true?},
????type:?{?type:?STRING?},
????value:?{
??????type:?TEXT,
??????get()?{
????????return?this.getDataValue('value')???JSON.parse(this.getDataValue('value'))?:?null;
??????},
??????set(value)?{
????????this.setDataValue('value',?JSON.stringify(value));
??????},
????},
????createdAt:?{?type:?DATE?},
????updatedAt:?{?type:?DATE?},
??});

??return?ProjectConfig;
};

复制代码

router.js

'use?strict';

/**
?*?@param?{Egg.Application}?app?-?egg?application
?*/
module.exports?=?app?=>?{
??const?{?router,?controller?}?=?app;
??router.get('/api/projectConfig',?controller.projectConfig.index);
??router.post('/api/projectConfig',?controller.projectConfig.create);
??router.put('/api/projectConfig/:id',?controller.projectConfig.update);
??router.delete('/api/projectConfig/:id',?controller.projectConfig.destroy);
??router.post('/api/projectConfig/batchDelete',?controller.projectConfig.batchDestroy);
};

复制代码

API 文档 Apifox

先快速测试一把,然后去对前端代码。

?

image.png

ts用到的一些只是

  1. 在类型别名(type alias)的声明中可以使用?`keyof`、`typeof`、`in`?等关键字来进行一些强大的类型操作
interface?A?{
??x:?number;
??y:?string;
}
//?拿到?A?类型的?key?字面量枚举类型,相当于?type?B?=?'x'?|?'y'
type?B?=?keyof?A;

const?json?=?{?foo:?1,?bar:?'hi'?};
//?根据 ts 的类型推论生成一个类型。此时 C 的类型为?{ foo: number; bar: string;?}
type?C?=?typeof?json;

//?根据已有类型生成相关新类型,此处将?A?类型的所有成员变成了可选项,相当于?type?D?=?{?x?:?number;?y?:?string;?};
type?D?=?{
??[T?in?keyof?A]?:?A[T];
};

复制代码

在比如用一个联合类型来约束对象的key,用interface我就没实现,貌似.

export?type?FormItemType?=?'el-input'?|?'z-select'?|?'el-date-picker'

//?目前发现?interface?的key?只能是三种?string?number?symbol???keyof?any
type?IPlaceholderMapping?=?{
??[key?in?FormItemType]:?string
}

export?const?placeholderMapping:?IPlaceholderMapping?=?{
??'el-input':?'请输入',
??'z-select':?'请选择',
??'el-date-picker':?'请选择日期'
}
复制代码

?ProTable前端代码地址:https://github.com/xiuxiuyifan/ProTable

  JavaScript知识库 最新文章
ES6的相关知识点
react 函数式组件 & react其他一些总结
Vue基础超详细
前端JS也可以连点成线(Vue中运用 AntVG6)
Vue事件处理的基本使用
Vue后台项目的记录 (一)
前后端分离vue跨域,devServer配置proxy代理
TypeScript
初识vuex
vue项目安装包指令收集
上一篇文章      下一篇文章      查看所有文章
加:2022-08-06 10:34:49  更:2022-08-06 10:36:12 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/11 12:45:32-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码