Heim  >  Artikel  >  Web-Frontend  >  So verwenden Sie Vue3+ts zur Entwicklung von ProTable

So verwenden Sie Vue3+ts zur Entwicklung von ProTable

PHPz
PHPznach vorne
2023-05-12 21:10:121531Durchsuche

Front-End-Implementierung

Erfolgseffekt

So verwenden Sie Vue3+ts zur Entwicklung von ProTable

Technologie-Stack

vue3 + Typescript + Element-Plus

Nutzung

<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 &#39;vue&#39;
import ProTable from &#39;./components/ProTable/index.vue&#39;
import { ColumnProps, RequestUrl } from &#39;./components/ProTable/types&#39;
import { projectConfig, projectConfigBatchDelete } from &#39;./service/api&#39;
const activeName = ref(&#39;user&#39;)
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: &#39;ProTable&#39;,
    name: &#39;user&#39;,
    columns: [
      {
        key: &#39;userName&#39;,
        title: &#39;用户名&#39;,
        searchType: &#39;el-input&#39;
      },
      {
        key: &#39;password&#39;,
        title: &#39;密码&#39;,
        searchType: &#39;el-input&#39;
      },
      {
        key: &#39;email&#39;,
        title: &#39;邮箱&#39;,
        searchType: &#39;el-input&#39;
      },
      {
        key: &#39;phone&#39;,
        title: &#39;手机号&#39;,
        searchType: &#39;el-input&#39;
      },
      {
        key: &#39;role&#39;,
        title: &#39;角色&#39;,
        searchType: &#39;z-select&#39;,
        attrs: {
          options: [
            {
              label: &#39;管理员&#39;,
              value: &#39;admin&#39;
            },
            {
              label: &#39;普通用户&#39;,
              value: &#39;user&#39;
            }
          ]
        }
      },
      {
        key: &#39;status&#39;,
        title: &#39;状态&#39;,
        searchType: &#39;z-select&#39;,
        attrs: {
          options: [
            {
              label: &#39;启用&#39;,
              value: 1
            },
            {
              label: &#39;禁用&#39;,
              value: 0
            }
          ]
        },
        columnType: &#39;status&#39;
      },
      {
        key: &#39;hasUseArray&#39;,
        title: &#39;是否使用数组参数?&#39;,
        search: false,
        searchType: &#39;useExpandField&#39;,
        show: false,
        add: false
      },
      {
        key: &#39;arrayParams&#39;,
        title: &#39;数组参数&#39;,
        searchType: &#39;z-array&#39;,
        search: false,
        width: 120,
        add: false,
        show: false
      },
      {
        key: &#39;hasUseArray&#39;,
        title: &#39;是否使用JSON参数?&#39;,
        search: false,
        searchType: &#39;useExpandField&#39;,
        show: false,
        add: false
      },
      {
        key: &#39;jsonParams&#39;,
        title: &#39;JSON参数&#39;,
        searchType: &#39;z-json&#39;,
        search: false,
        width: 120,
        add: false,
        show: false
      },
      {
        key: &#39;createdAt&#39;,
        title: &#39;创建时间&#39;,
        width: 180,
        searchType: &#39;el-date-picker&#39;,
        add: false
      },
      {
        key: &#39;updatedAt&#39;,
        title: &#39;更新时间&#39;,
        width: 180,
        searchType: &#39;el-date-picker&#39;,
        add: false
      },
      {
        key: &#39;action&#39;,
        title: &#39;操作&#39;,
        search: false,
        add: false,
        width: 150
      }
    ]
  }
])
</script>
<style lang="less">
</style>

ProTable-Designidee

Die Seite ist in 5 Bereiche unterteilt,

  • Einzelner Suchbereich

  • Tabellenfunktionsschaltflächenbereich

  • Bedienbereich in der oberen rechten Ecke der Tabelle

  • Tabellenthemenbereich

  • Tabellenpaginierungsbereich

Welche Probleme sollten berücksichtigt werden?

  • Welche Bereiche sollten eingehende Slots unterstützen?

  • Soll der ursprüngliche Steckplatz des Formulars an den Benutzer übergeben werden oder intern gekapselt werden? Wenn beispielsweise colum ein Status ist, muss er einem tag zugeordnet werden, und wenn es sich um einen Typ array handelt, muss dies der Fall sein einer Tabelle zugeordnet, die json ist. Wann müssen Sie klicken, um Details anzuzeigen? Es wäre zu mühsam, jede Tabelle zu verarbeiten. Wir hoffen, sie über ein Feld steuern zu können. colum是状态的时候需要映射成tag,是数组类型的时候映射成表格,是json的时候需要点击查看详情?假设每个表格都要处理的的话就太麻烦,我们希望通过一个字段来控制它。

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

  • 列字段需要编辑的功能?

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

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

编码风格

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

  • eslint使用的是standard风格。

css 小知识

<div class=&#39;box&#39;>
  <div class=&#39;z&#39;></div>
</div>
*{
  box-sizing: border-box;
}
.box{
  display: inline-block;
    vertical-align: top;
}
.z{
  height: 32px;
  border: 1px solid;
  width: 100px;
  display: inline-block;
}

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

So verwenden Sie Vue3+ts zur Entwicklung von ProTable

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

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

So verwenden Sie Vue3+ts zur Entwicklung von ProTable

放两篇文章

CSS vertical-align 属性

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

flex布局语法教程详解

表格操作

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

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

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

小结

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

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

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

参考文章

JavaScript API——ResizeObserver 的使用

后期功能扩展

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

  • 扩展一下slot

  • 等等。。

迭代中....

So verwenden Sie Vue3+ts zur Entwicklung von ProTable

后台实现

数据库 mysql

我这里使用的是 xampp

So verwenden Sie Vue3+ts zur Entwicklung von ProTableMuss eine bestimmte Spalte von column kopiert werden?

Benötigen Sie die Möglichkeit, Spaltenfelder zu bearbeiten?

Was sind die Details im Umsetzungsprozess? So verwenden Sie Vue3+ts zur Entwicklung von ProTable

🎜🎜Für die Höhe der Tabelle lassen Sie den Benutzer die Größe des sichtbaren Bereichs der Tabelle steuern und platzieren die Schaltfläche für den Stapelbetrieb unten (feste Positionierung). Auf diese Weise kann der Benutzer den Inhalt der Tabelle im größten Bereich sehen. 🎜🎜🎜🎜Codierungsstil🎜🎜🎜🎜Wenn die Komponente mehr als drei Attribute enthält, packen Sie sie in neue Zeilen ein. 🎜🎜🎜🎜eslint verwendet den Standard-Stil. 🎜🎜🎜🎜css-Tipps🎜
CREATE TABLE `project_config`  (
  `id` int NOT NULL AUTO_INCREMENT COMMENT &#39;主键&#39;,
  `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT &#39;配置类型&#39;,
  `value` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT &#39;配置的json字符串&#39;,
  `created_at` datetime NOT NULL,
  `updated_at` datetime NOT NULL DEFAULT &#39;0000-00-00 00:00:00&#39; 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
🎜Wenn die Box in ein inline-Element umgewandelt wird und es sich darin noch um ein Inline-Element handelt, entsteht eine Lücke, die dazu führt, dass ihre Höhe von der abweicht Höhe des übergeordneten Elements. wie folgt. 🎜🎜So verwenden Sie Vue3+ts, um ProTable zu entwickeln🎜🎜Die Lösung ist auch sehr gut. Einfach, Sie müssen das Attribut vertical-align seiner untergeordneten Elemente festlegen oder font-size: 0 festlegen. Der Hauptgrund ist, dass der mittlere Text Element besetzt auch die Position. Alternativ können Sie anstelle von inline-block das Attribut inline-flex verwenden. Dies ist überhaupt kein Problem, da es auch eine große Anzahl davon im element-plus-Komponentenbibliothek ist ebenfalls sehr gut. 🎜🎜Ich kenne diese Lösungen schon seit langem, aber die Beziehung zwischen vertical-algin und line-height ist immer noch etwas vage und ich habe mich nie damit befasst . . 🎜🎜So verwenden Sie Vue3+ts, um ProTable zu entwickeln🎜🎜Setzen Sie zwei Artikel 🎜🎜CSS Vertical-Align-Attribut🎜🎜Ich denke auch an baseline in flex, align-items-Attribut: Ein Attribut auf der Querachse ist sehr ähnlich. 🎜🎜Detaillierte Erläuterung des Flex-Layout-Syntax-Tutorials🎜🎜Tabellenoperation🎜🎜🎜🎜Nach dem Hinzufügen von Daten sollte pageIndex beim erneuten Abrufen von Daten auf 1 zurückgesetzt werden, und das Gleiche gilt beim Löschen von Daten. 🎜🎜🎜🎜Beim Bearbeiten von Daten ändert sich pageIndex nicht, es ist immer noch die aktuelle Seitenzahl. 🎜🎜🎜🎜Zusammenfassend lässt sich sagen, dass pageIndex auf 1 zurückgesetzt wird, wenn sich die Anzahl der Datenelemente ändert. Wenn Benutzeroperationen keinen Einfluss auf die Gesamtzahl der Daten haben, bleibt pageSize unverändert. 🎜🎜🎜🎜Zusammenfassung🎜🎜🎜🎜Mit einer Bibliothek, die Änderungen in der Größe von Dom-Elementen überwachen kann, resize-observer-polyfill. 🎜🎜🎜🎜In 3.x, wenn ein Element sowohl v-bind="object" als auch ein identisches unabhängiges Attribut definiert. Entwickler können wählen, welches sie behalten möchten. 🎜🎜🎜🎜Dokumentadresse # V-Bind-Zusammenführungsverhalten🎜🎜Referenzartikel🎜🎜JavaScript-API——Verwendung von ResizeObserver🎜🎜Spätere Funktionserweiterung🎜🎜🎜🎜Wie man mit der Korrelation zwischen Feldern umgeht, darüber habe ich noch nicht nachgedacht noch . 🎜🎜🎜🎜slot erweitern 🎜🎜🎜🎜 und so weiter. . 🎜🎜🎜🎜Iterieren....🎜🎜Wie man Vue3+ts verwendet, um „ProTable entwickeln“ />🎜🎜Backend-Implementierung🎜🎜Datenbank MySQL🎜🎜Ich verwende die von <code>xampp</code> installierte Version. Schauen wir uns die Version an. Welche Version ist das? Es ist eine Fälschung, sind es wirklich 10? Machen Sie sich darüber im Moment keine Sorgen, solange es funktioniert. 🎜🎜🎜🎜🎜Erstellen Sie eine Tabelle🎜<pre class=npm install --save egg-sequelize mysql2🎜Neues Projekt🎜
&#39;use strict&#39;;
/** @type Egg.EggPlugin */
exports.sequelize = {
  enable: true,
  package: &#39;egg-sequelize&#39;,
};
exports.cors = {
  enable: true,
  package: &#39;egg-cors&#39;,
};
🎜Das Projektverzeichnis lautet ungefähr wie folgt:🎜🎜🎜🎜

RESTful 风格的 URL 定义

npm install --save egg-sequelize mysql2

  • 在 config/plugin.js 中引入 egg-sequelize 插件, 这里我们引入了一个库egg-cors来帮我们实现cors

&#39;use strict&#39;;
/** @type Egg.EggPlugin */
exports.sequelize = {
  enable: true,
  package: &#39;egg-sequelize&#39;,
};
exports.cors = {
  enable: true,
  package: &#39;egg-cors&#39;,
};
  • 在 config/config.default.js 中编写 sequelize 配置

/* eslint valid-jsdoc: "off" */
&#39;use strict&#39;;
/**
 * @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 + &#39;_1655529530112_7627&#39;;
  // add your middleware config here
  config.middleware = [];
  config.security = {
    csrf: {
      enable: false,
      ignoreJSON: true,
    },
  };
  config.cors = {
    origin: &#39;*&#39;,
    allowMethods: &#39;GET,HEAD,PUT,POST,DELETE,PATCH&#39;,
  };
  // add your user config here
  const userConfig = {
    // myAppName: &#39;egg&#39;,
  };
  // sequelize
  const sequelize = {
    dialect: &#39;mysql&#39;,
    host: &#39;127.0.0.1&#39;,
    port: 3306,
    username: &#39;root&#39;,
    password: &#39;123456&#39;,
    database: &#39;test_database&#39;,
    timezone: &#39;+08:00&#39;,
    dialectOptions: {
      dateStrings: true,
      typeCast: true,
    },
    define: {
      freezeTableName: true, // 模型名强制和表明一致
      underscored: true, // 字段以下划线(_)来分割(默认是驼峰命名风格)
    },
  };
  return {
    ...config,
    ...userConfig,
    sequelize,
  };
};

1、时间格式化

类型需要采用:Sequelize.DATE

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

timezone: &#39;+08:00&#39;,  // 改为标准时区
dialectOptions: {
  dateStrings: true,
  typeCast: true,
},

下面就开始编写

controller

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

controller/ProjectConfig.js

&#39;use strict&#39;;
const { success } = require(&#39;../utils/res&#39;);
const { omit, pick } = require(&#39;lodash&#39;);
const Controller = require(&#39;egg&#39;).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, [ &#39;type&#39;, &#39;createdAt&#39;, &#39;updatedAt&#39; ]);
    const stableQueryArgs = Object.keys(stableQuery)
      .filter(key => Boolean(stableQuery[key]))
      .map(key => {
        return {
          [key]: stableQuery[key],
        };
      });
    const whereCondition = omit(ctx.query, [ &#39;pageIndex&#39;, &#39;pageSize&#39;, &#39;type&#39;, &#39;createdAt&#39;, &#39;updatedAt&#39; ]);
    // 需要模糊查询的参数
    const whereArgs = Object.keys(whereCondition)
      .filter(key => Boolean(whereCondition[key]))
      .map(key => {
        return where(fn(&#39;json_extract&#39;, col(&#39;value&#39;), literal(`\&#39;$.${key}\&#39;`)), {
          [Op.like]: `%${whereCondition[key]}%`,
        });
      });
    const query = {
      where: {
        [Op.and]: [
          ...stableQueryArgs,
          ...whereArgs,
        ],
      },
      order: [
        [ &#39;createdAt&#39;, &#39;DESC&#39; ],
      ],
      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(字段名,&#39;$.json结构&#39;) FROM 表名;

sequelize高级查询

Post.findAll({
  where: sequelize.where(sequelize.fn(&#39;char_length&#39;, sequelize.col(&#39;content&#39;)), 7)
});
// SELECT ... FROM "posts" AS "post" WHERE char_length("content") = 7

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

model

model/project_config.js

&#39;use strict&#39;;
module.exports = app => {
  const { STRING, INTEGER, TEXT, DATE } = app.Sequelize;
  const ProjectConfig = app.model.define(&#39;project_config&#39;, {
    id: { type: INTEGER, primaryKey: true, autoIncrement: true },
    type: { type: STRING },
    value: {
      type: TEXT,
      get() {
        return this.getDataValue(&#39;value&#39;) ? JSON.parse(this.getDataValue(&#39;value&#39;)) : null;
      },
      set(value) {
        this.setDataValue(&#39;value&#39;, JSON.stringify(value));
      },
    },
    createdAt: { type: DATE },
    updatedAt: { type: DATE },
  });
  return ProjectConfig;
};

router.js

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

API 文档 Apifox

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

So verwenden Sie Vue3+ts zur Entwicklung von ProTable

ts用到的一些

  • 在类型别名(type alias)的声明中可以使用 keyoftypeofin 等关键字来进行一些强大的类型操作

interface A {
  x: number;
  y: string;
}
// 拿到 A 类型的 key 字面量枚举类型,相当于 type B = &#39;x&#39; | &#39;y&#39;
type B = keyof A;
const json = { foo: 1, bar: &#39;hi&#39; };
// 根据 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 = &#39;el-input&#39; | &#39;z-select&#39; | &#39;el-date-picker&#39;
// 目前发现 interface 的key 只能是三种 string number symbol   keyof any
type IPlaceholderMapping = {
  [key in FormItemType]: string
}
export const placeholderMapping: IPlaceholderMapping = {
  &#39;el-input&#39;: &#39;请输入&#39;,
  &#39;z-select&#39;: &#39;请选择&#39;,
  &#39;el-date-picker&#39;: &#39;请选择日期&#39;
}

Das obige ist der detaillierte Inhalt vonSo verwenden Sie Vue3+ts zur Entwicklung von ProTable. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen