Tips:一些记录,一些笔记

2024/06/09
SUNDAY
Henry David Thoreau「In the end, we will remember not the words of our enemies, but the silence of our friends」
亨利·大卫·梭罗「最终,我们不会记住敌人的话,而会记住朋友的沉默」

Master.Jonathan的「欧洲行」
01
Ant.design Vue「表格组件」官方样例
官方「表格组件」的文档地址:
https://www.antdv.com/components/table-cn
其中有一个示例是这样的:

可以看到,它实现了「排序与过滤」的相关效果。
但是它实现的方式仅仅是通过在列定义的时候,添加相关声明即可:

而我之前实现同样的功能,则复杂得多:
那么,官方是如何实现的呢?有没有什么地方是我们可以借鉴的?以提高我们的编程技巧与能力?
下面的章节,会先对官方代码进行一步步的解析,然后根据我们解析出来的结论,重新对前面实现的较为复杂的「排序」效果的代码进行优化。
在开始前,首先看看官方的完整代码。
官方的完整代码如下:
<template><a-table:columns="columns":row-key="record => record.login.uuid":data-source="dataSource":pagination="pagination":loading="loading"@change="handleTableChange"><template #bodyCell="{ column, text }"><template v-if="column.dataIndex === 'name'">{{ text.first }} {{ text.last }}</template></template></a-table></template><script lang="ts" setup>import { computed } from 'vue';import type { TableProps } from 'ant-design-vue';import { usePagination } from 'vue-request';import axios from 'axios';const columns = [{title: 'Name',dataIndex: 'name',sorter: true,width: '20%',},{title: 'Gender',dataIndex: 'gender',filters: [{ text: 'Male', value: 'male' },{ text: 'Female', value: 'female' },],width: '20%',},{title: 'Email',dataIndex: 'email',},];type APIParams = {results: number;page?: number;sortField?: string;sortOrder?: number;[key: string]: any;};type APIResult = {results: {gender: 'female' | 'male';name: string;email: string;}[];};const queryData = (params: APIParams) => {return axios.get<APIResult>('https://randomuser.me/api?noinfo', { params });};const {data: dataSource,run,loading,current,pageSize,} = usePagination(queryData, {formatResult: res => res.data.results,pagination: {currentKey: 'page',pageSizeKey: 'results',},});const pagination = computed(() => ({total: 200,current: current.value,pageSize: pageSize.value,}));const handleTableChange: TableProps['onChange'] = (pag: { pageSize: number; current: number },filters: any,sorter: any,) => {run({results: pag.pageSize,page: pag?.current,sortField: sorter.field,sortOrder: sorter.order,...filters,});};</script>
02
对官方代码的解析:总览
官方代码主要分为两个部分:
template
script
一、「template」页面渲染的布局:
<template><a-table:columns="columns":row-key="record => record.login.uuid":data-source="dataSource":pagination="pagination":loading="loading"@change="handleTableChange"><template #bodyCell="{ column, text }"><template v-if="column.dataIndex === 'name'">{{ text.first }} {{ text.last }}</template></template></a-table></template>
前端页面很简单,只有一个表格组件「a-table」
表格组件包裹的「template #bodyCell」可以先不管,它是对表格中的某些单元格进行「风格化定义」的代码。
二、「script」页面渲染的逻辑:
<script lang="ts" setup>import { computed } from 'vue';import type { TableProps } from 'ant-design-vue';import { usePagination } from 'vue-request';import axios from 'axios';const columns = [{title: 'Name',dataIndex: 'name',sorter: true,width: '20%',},{title: 'Gender',dataIndex: 'gender',filters: [{ text: 'Male', value: 'male' },{ text: 'Female', value: 'female' },],width: '20%',},{title: 'Email',dataIndex: 'email',},];type APIParams = {results: number;page?: number;sortField?: string;sortOrder?: number;[key: string]: any;};type APIResult = {results: {gender: 'female' | 'male';name: string;email: string;}[];};const queryData = (params: APIParams) => {return axios.get<APIResult>('https://randomuser.me/api?noinfo', { params });};const {data: dataSource,run,loading,current,pageSize,} = usePagination(queryData, {formatResult: res => res.data.results,pagination: {currentKey: 'page',pageSizeKey: 'results',},});const pagination = computed(() => ({total: 200,current: current.value,pageSize: pageSize.value,}));const handleTableChange: TableProps['onChange'] = (pag: { pageSize: number; current: number },filters: any,sorter: any,) => {run({results: pag.pageSize,page: pag?.current,sortField: sorter.field,sortOrder: sorter.order,...filters,});};</script>
03
「template」页面渲染的布局
代码:

可以看到「表格组件」绑定了「5个属性」以及「1个事件」
5个属性:
| columns | 表格列的配置描述 |
| row-key | 表格行 key 的取值,可以是字符串或一个函数 |
| data-source | 数据数组 |
| pagination | 分页器,设为false时不展示,也不进行分页 |
| loading | 页面是否加载中 |
columns的数据类型应该是「array」
data-source的数据类型应该是「object[]」
loading的数据类型应该是「boolean」
pagination的数据类型应该是「object false」
row-key的数据类型应该是「string Function(record):string」
1个事件:
| change | 分页、排序、筛选变化时触发 |
change事件的回调函数:
Function(pagination,filters,sorter,{ action, currentDataSource })
04
「script」页面渲染的逻辑
这一块的代码可以分为几个区域:
导入包
表格的列描述(定义)
API类型的声明:请求参数的类型声明、反馈结果的类型声明
「远程数据」获取方法(通过AXIOS请求远端API接口)
「usePagination」分页器的实例化与拆包
分页器变量的赋值
「change」事件发生时的处理函数的定义
导入包:
import { computed } from 'vue';import type { TableProps } from 'ant-design-vue';import { usePagination } from 'vue-request';import axios from 'axios';
列描述 列定义:
const columns = [{title: 'Name',dataIndex: 'name',sorter: true,width: '20%',},{title: 'Gender',dataIndex: 'gender',filters: [{ text: 'Male', value: 'male' },{ text: 'Female', value: 'female' },],width: '20%',},{title: 'Email',dataIndex: 'email',},];
API类型定义:
type APIParams = {results: number;page?: number;sortField?: string;sortOrder?: number;[key: string]: any;};type APIResult = {results: {gender: 'female' | 'male';name: string;email: string;}[];};
获取数据:
const queryData = (params: APIParams) => {return axios.get<APIResult>('https://randomuser.me/api?noinfo', { params });};
分页器拆包初始化:
const {data: dataSource,run,loading,current,pageSize,} = usePagination(queryData, {formatResult: res => res.data.results,pagination: {currentKey: 'page',pageSizeKey: 'results',},});
分页器变量赋值:
const pagination = computed(() => ({total: 200,current: current.value,pageSize: pageSize.value,}));
「change」事件的处理函数:
const handleTableChange: TableProps['onChange'] = (pag: { pageSize: number; current: number },filters: any,sorter: any,) => {run({results: pag.pageSize,page: pag?.current,sortField: sorter.field,sortOrder: sorter.order,...filters,});};
05
官方样例的数据源
官方使用的数据源是一个开源的项目:
https://randomuser.me/api?noinfo
Github地址:
https://github.com/RandomAPI/Randomuser.me-Node
它可以随机生成虚假的用户数据。
来看看前面API的参数定义的部分:

APIParams定义的其实是访问这个随机生成用户的项目的时候,需要传给它的参数。
具体来看看:
一、results:
https://randomuser.me/api?results=3

可以看到,生成的结果集包含三条数据。
二、page

可以看到,多了一个属性「page」
06
仿照官方代码来实现我们自己的「随机用户信息」表格
官方文档中使用的是「vue-request」的「v1」的语法「formatResult」

在V2中,该语法已经被移除了:
https://www.attojs.com/guide/migration.html#%E8%BF%81%E7%A7%BB%E5%B8%AE%E5%8A%A9


来看看目前阶段的代码:
<template>{{data}}</template><script setup>import axios from 'axios'import {useRequest} from 'vue-request'// ================================= Version 1// let orders//// console.log(axios.defaults)//// axios// .get('/api/order/')// .then(// response => {// console.log(response.data.results)// }// )//// console.log(orders)// ================================= Version 2const getOrders = () => {let return_data = ''return axios.get('/api/order/')// return return_data}// vue-request 官方文档 https://www.attojs.com/api/#return-valuesconst {data} = useRequest(getOrders)console.log(data)</script><style scoped></style>
在这段代码中,可以看到最后通过「console.log(data)」将拿到的数据打印到了浏览器的控制台。
来看看这个时候的页面效果:

在前面,通过「formatResult」是为了对「useRequest」拿到的结果集进行格式化处理。
但在该语法被弃用后,用户需要在服务(Service)请求阶段自行处理
将代码按照下列方式书写:
<template>{{data}}</template><script setup>import axios from 'axios'import {useRequest} from 'vue-request'// ================================= Version 1// let orders//// console.log(axios.defaults)//// axios// .get('/api/order/')// .then(// response => {// console.log(response.data.results)// }// )//// console.log(orders)// ================================= Version 2const getOrders = () => {let return_data = ''return axios.get('/api/order/').then(response => {return response.data.results})// return return_data}// vue-request 官方文档 https://www.attojs.com/api/#return-valuesconst {data} = useRequest(getOrders)console.log(data)</script><style scoped></style>
这时候的页面效果:

可以看到,请求得到的结果集,就按照我们期望的方式,被格式化了。
07
「表格」改版后的完整代码
按照上面的思路,就可以对之前的代码做正式修改了。
一、旧代码
之前的代码实现,可以看这个笔记:
原代码如下:
<template><div id="container" class="text-white text-sm bg-primary-300 min-h-screen pb-4"><Header/><div class="flex items-center justify-center"><div class="w-full px-2" style="max-width:1440px;"><div id="main" class="bg-primary-300 p-6 text-black"><!-- 订单 --><div class="rounded bg-white mx-4 my-4 py-6 "><div class="px-6"><h1 class="text-lg font-semibold">我的订单</h1></div></div><div id="orders_all" class="rounded bg-white mx-4 my-4 py-6"><a-row class="flex items-center justify-center"><a-space><!-- 标签组 --><div><a-tag color="purple" @click="TagClick('All 「全部」')"><a href="/orders">All 「全部」</a></a-tag><a-tag color="cyan" @click="TagClick('Paying 「支付中」')"><a href="/orders/?pay_status=paying">Paying 「支付中」</a></a-tag><a-tag color="green" @click="TagClick('FINISH 「完结」')"><a href="/orders/?pay_status=trade_success">FINISH 「完结」</a></a-tag></div><!--输入框 --><div class="relative shrink"><form><input v-model.trim="order_sn" type="text" name="order_sn" class="outline-0h-9roundedborderborder-gray-600placeholder-gray-400w-64px-2py-1max-w-[180px]" placeholder="输入订单编号" ><div class="absolutetop-0right-0flexitems-centerh-full"><div class=" rounded text-xs text-gray-400 px-2 mr-2"><button v-on:click.prevent="searchOrders"><svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" ></svg></button></div></div></form></div></a-space></a-row><br/><a-table:columns="columns_orders":data-source="data_orders"borderedstripe><template #bodyCell="{ column, record }"><template v-if="column.key === 'pay_status'"><span><a-tag v-if="record.pay_status === 'PAYING'" color="orange">支付中</a-tag><a-tag v-if="record.pay_status === 'TRADE_SUCCESS'" color="green">支付成功</a-tag></span></template></template></a-table></div></div></div></div><Footer/></div></template><script setup>import Header from "@/components/layout/Header.vue";import Footer from "@/components/layout/Footer.vue";import showMessage from "@/utils/message.js";import {reactive,ref,onMounted,watch} from "vue";import axios from 'axios';// Routerimport {useRoute} from "vue-router";import router from "@/router/index.ts";// ================================const route = useRoute()// ================================// 数据:表单 - 全部// -- 原始数据let orders = ref("")// -- 处理后数据let orders_total = []// 数据列const columns_orders = [{title: '订单编号',dataIndex: 'order_sn',key: 'order_sn',},{title: '支付状态',dataIndex: 'pay_status',key: 'pay_status',},{title: '支付金额',dataIndex: 'order_mount',key: 'order_mount',},{title: '创建时间',dataIndex: 'created_at'},{title: '会员卡',dataIndex: "card_name",key: "card_name",},{title: '会员有效期(天)',dataIndex: 'duration',key: 'duration'},]// 数据源let data_orders = ref([])// ================================const order_sn = ref("")// 方法// 获取订单数据const get_orders = () => {let url = '/api/order/'// 获取 URL参数const page = Number(route.query.page)const pay_status = route.query.pay_statusconst order_sn = route.query.order_sn// 显示console.log(pay_status)const params = new URLSearchParams()if (page) {params.append('page', page)}if (pay_status) {params.append('pay_status', pay_status)}if (order_sn) {params.append('order_sn', order_sn)}url = url + "?" + params.toString()console.log("发送的请求地址是:" + url)axios.get(url).then(response => {console.log(response.data.results)orders.value = response.data.resultsfor (var order in orders.value) {// 当前循环的最终返回结果var order_current = {}// 当前循环的数据var order_data = orders.value[order]var order_card_data = order_data['card']// 从原始字典中去除嵌套的字典delete order_data.card// 将去除后的新字典与嵌套字典,整合成新字典order_current = Object.assign({},order_data,order_card_data)// 然后将当前循环的最终结果整合到循环外的统一变量中orders_total.push(order_current)// 显示console.log(orders_total)}// 最后,将新的结果集赋值给表格组件的数据源data_orders.value = orders_total})}const TagClick = (message) => {showMessage(message, 'info')}const searchOrders = () => {router.push({name: 'Orders',query: {order_sn: order_sn.value}})}// 生命周期函数onMounted(() => {// 获取订单数据get_orders()})watch(() => route.params,(newParams, oldParams) => {console.log('路由参数变化了:', newParams, oldParams);orders = ref("")orders_total = []data_orders = ref([])get_orders()})</script><style scoped></style>
二、新代码
代码:
<template><div id="container" class="text-white text-sm bg-primary-300 min-h-screen pb-4"><Header/><div class="flex items-center justify-center"><div class="w-full px-2" style="max-width:1440px;"><div id="main" class="bg-primary-300 p-6 text-black"><!-- 订单 --><div class="rounded bg-white mx-4 my-4 py-6 "><div class="px-6"><h1 class="text-lg font-semibold">我的订单</h1></div></div><div id="orders_all" class="rounded bg-white mx-4 my-4 py-6"><!-- 头部标签组 与 输入框 --><a-row class="flex items-center justify-center"><a-space><!-- 标签组 --><div><a-tag color="purple" @click="TagClick('all')"><a href="/orders">All 「全部」</a><!-- All 「全部」 --></a-tag><a-tag color="cyan" @click="TagClick('paying')"><a href="/orders/?pay_status=paying">Paying 「支付中」</a><!-- Paying 「支付中」 --></a-tag><a-tag color="green" @click="TagClick('finish')"><a href="/orders/?pay_status=trade_success">FINISH 「完结」</a><!-- FINISH 「完结」 --></a-tag></div><!--输入框 --><div class="relative shrink"><form><input v-model.trim="order_sn" type="text" name="order_sn" class="outline-0h-9roundedborderborder-gray-600placeholder-gray-400w-64px-2py-1max-w-[180px]" placeholder="输入订单编号" ><div class="absolutetop-0right-0flexitems-centerh-full"><div class=" rounded text-xs text-gray-400 px-2 mr-2"><button v-on:click.prevent="searchOrders"><svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2"><path stroke-linecap="round" stroke-linejoin="round" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" ></svg></button></div></div></form></div></a-space></a-row><!-- 空行 --><br/><!-- 表格组件 --><a-table:columns="columns":data-source="data"><template #bodyCell="{ column, record }"><template v-if="column.key === 'pay_status'"><span><a-tag v-if="record.pay_status === 'PAYING'" color="orange">支付中</a-tag><a-tag v-if="record.pay_status === 'TRADE_SUCCESS'" color="green">支付成功</a-tag></span></template></template></a-table></div></div></div></div><Footer/></div></template><script setup lang="ts">// ================================= 引入包// 项目:头与底部import Header from "@/components/layout/Header.vue"import Footer from "@/components/layout/Footer.vue"// 消息通知import showMessage from "@/utils/message.js";// 处理表格数据与渲染import axios from 'axios'import {useRequest} from 'vue-request'import {watch} from 'vue';// 路由或地址相关import {useRoute} from "vue-router";const route = useRoute()import router from "@/router/index.ts";import {ref} from "vue";// ================================= 获取数据const getOrders = () => {// 最终返回的变量let data_return = ''// 地址let url = '/api/order/'// 根据 URL 地址的状态进行动态设定const page:any = Number(route.query.page)const pay_status:any = route.query.pay_statusconst order_sn:any = route.query.order_snconst params = new URLSearchParams()if (page) {params.append('page', page)}if (pay_status) {params.append('pay_status', pay_status)}if (order_sn) {params.append('order_sn', order_sn)}url = url + '?' + params.toString()// AXIOS 请求API接口const orders = axios.get(url).then(response => {// 最终返回数据let return_data = []// 原始队列let origin_orders = response.data.resultsfor (var key in origin_orders) {let origin_orders_item = origin_orders[key]let origin_orders_item_card = origin_orders_item['card']// 处理原始字典delete origin_orders_item.cardreturn_data.push(Object.assign({},origin_orders_item,origin_orders_item_card,))// 显示// console.log("@@@@@@@@@@ 项目")// console.log(origin_orders_item)// console.log("--- 项目【Card】")// console.log(origin_orders_item_card)}return return_data})// data_return = handleNestedDict(orders)data_return = orders// 返回阶段return data_return}// ================================= 通过 vue-request 请求// vue-request 官方文档 https://www.attojs.com/api/#return-valuesconst {data,run,} = useRequest(getOrders)// ================================= 表格组件相关const columns = [{title: '订单编号',dataIndex: 'order_sn',key: 'order_sn',},{title: '支付状态',dataIndex: 'pay_status',key: 'pay_status',filters: [{text:"已支付", value:"TRADE_SUCCESS"},{text:"支付中", value:"PAYING"},],},{title: '支付金额',dataIndex: 'order_mount',key: 'order_mount',// 排序「数字」sorter: (a, b) => a.order_mount - b.order_mount,sortDirections: ['ascend','descend',],},{title: '创建时间',dataIndex: 'created_at'},{title: '会员卡',dataIndex: "card_name",key: "card_name",},{title: '会员有效期(天)',dataIndex: 'duration',key: 'duration'},]// ================================= 当表格数据变化时触发// const handleTableChange: TableProps['onChange'] = (// filters: any,// sorter: any,// ) => {// run({// sortField: sorter.field,// sortOrder: sorter.order,// ...filters,// })// }// ================================= 「标签组」相关function TagClick(message) {if (message === 'all') {showMessage(message, "info")run({pay_status: ''})}if (message === 'paying') {showMessage(message, "info")run({pay_status: 'paying'})}if (message === 'finish') {showMessage(message, "info")run({pay_status: 'trade_success'})}}// ================================= 检索框「搜索」相关// 检索框搜索方法const order_sn = ref('')function searchOrders() {router.push({name: "Orders",query: {order_sn: order_sn.value}})}// 当路由地址发生变化的时候,重新渲染表格数据watch(() => route.params, (newParams, oldParams) => {console.log("路由参数发生了变化")run({order_sn: order_sn.value})})// ================================= 输出 / 显示console.log("@@@@@@@@@@@@@@@@@@@@@@ FINISH")console.log(data)</script><style scoped></style>
效果:

END
温馨提示
如果你喜欢本文,请分享到朋友圈,想要获得更多信息,请关注我。




