Vue боевые навыки Element Table вторичная упаковка

Vue.js
Vue боевые навыки Element Table вторичная упаковка

предисловие

Поскольку в проекте управления фоном рефакторинга много страниц таблиц, взять каштан

k-table-demo.png

Эта форма выглядит довольно хорошо, но когда она написана, она заставляет людей называть ХХ, использовать больше мозгов, меньше выпадения волос, меньше обходных путей и больше обуви.

Написав один, я почувствовал себя слишком хлопотным, поэтому написал быстро, используяVue+Element TableПереупакован набор столовых компонентов

Вы можете создать страницу формы за 3 минуты, вы это заслужили

думать

Прочитав таблицу, мы просто делим их на функциональные зоны.

  • Первый участник, который войдет, находится в верхнем ряду области функции поиска, аккуратной и немного серьезной.

  • Вторая запись — слегка кокетливая область табличных функций.

  • Тогда запись является областью содержимого универсальной всеобъемлющей формы игрока.

  • Последняя запись — это область компонента пейджинга с длинными ветвями и длинными очередями.

Эй, давай подумаем, где самое сложное?

Ниже я использую картинку, чтобы отметить высоты, которые нам нужно взять в следующий раз.

В общем, содержимое таблицы самое неуправляемое, в ней может быть несколько вариантов, серийные номера, картинки, статус, время, столбцы операций...

k-table.png

упражняться

Напишем слой поиска как компонентfilterPane.vue

Разделить таблицу на компонентtablePane.vue

компонент формыtablePane.vueВключая ленту, область содержимого таблицы, разбиение на страницы

filterPane.vue

ясная цель

Слой поиска обычно включает селекторы даты, поля ввода, раскрывающиеся селекторы выбора и т. Д. + Функция поиска, функция сброса.

Сопоставление структуры входящих данных

// 搜索栏组件
 filterData:{
   timeSelect:true,    //是否显示日期控件
   elinput:[          
     {
       name:'姓名',    //提示语
       key:'userName',  //字段名
       width:100        //宽度
     }
   ],
   elselect:[
     {
       name:'部门',
       key:'department',
       width:100
       option:[{
         key:1,
         value:'技术部'
       }]
     }
   ]
 }

timeSelect

  • ТипыBoolean

показывать ли средство выбора времени

elinput

  • ТипыArray

опция поля ввода, внутри дочернего объекта

nameдля поля вводаplaceholder

keyэто имя поля

elselect

  • ТипыArray

выберите опцию раскрывающегося списка внутри дочернего объекта

nameдля поля вводаplaceholder

keyэто имя поля

optionвыпадающие параметры для выбора

начать упаковку

<template>
  <div>
    <div class="filter-container">
      <el-date-picker
        v-if="filterData.timeSelect"
        v-model="dateRange"
        style="width: 300px"
        type="daterange"
        start-placeholder="开始日期"
        end-placeholder="结束日期"
        :default-time="['', '']"
        :picker-options="pickerOptions"
        class="filter-item"
      />
      <template v-if="filterData.elinput">
        <el-input
          v-for="(item,index) in filterData.elinput"
          :key="index"
          v-model="listQuery[item.key]"
          :placeholder="item.name"
          :style="{'width':item.width?item.width+'px':'200px'}"
          class="filter-item"
        />
      </template>
      <template v-if="filterData.elselect">
        <el-select
          v-for="(item,index) in filterData.elselect"
          :key="index"
          v-model="listQuery[item.key]"
          :placeholder="item.name"
          clearable
          :style="{'width':item.width?item.width+'px':'90px'}"
          class="filter-item"
        >
          <el-option
            v-for="i in item.option"
            :key="i.key"
            :label="i.value"
            :value="i.key"
          />
        </el-select>
      </template>
      <div class="btn">
        <el-button class="filter-item" type="primary" @click="handleSearch">
          搜索
        </el-button>
        <el-button class="filter-item" type="warning" @click="handleRest">
          重置
        </el-button>
      </div>
    </div>
  </div>
</template>
<script>
// 搜索栏组件
// filterData:{
//   timeSelect:true,
//   elinput:[
//     {
//       name:'姓名',
//       key:'userName'
//     }
//   ],
//   elselect:[
//     {
//       name:'部门',
//       key:'department'
//       option:[{
//         key:1,
//         value:'技术部'
//       }]
//     }
//   ]
// }
export default {
  props: {
    // eslint-disable-next-line vue/require-default-prop
    filterData: {
      type: Object
    }
  },
  data() {
    return {
      pickerOptions: {
        disabledDate(time) {
          return time.getTime() > Date.now()
        }
      },
      dateRange: ['', ''],
      listQuery: {}
    }
  },
  watch: {
    'filterData'(val) {
      console.log(val)
      if (val.elinput.length > 0) {
        val.elinput.map(item => {
          this.listQuery[item.key] = ''
        })
      }
      if (val.elselect.length > 0) {
        val.elinput.map(item => {
          this.listQuery[item.key] = ''
        })
      }
    },
    //缓存进页面想清空可用
    'filterData.rest': {
      handler: function(val) {
        if (val) {
          this.handleRest()
        }
      },
      deep: true
    }
  },
  methods: {
    handleSearch() {
      console.log('搜索成功', this.listQuery)
      const data = this.$global.deepClone(this.listQuery)
      if (this.dateRange && this.dateRange[0] !== '') {
        const startTime = this.$moment(this.dateRange[0]).format('YYYY-MM-DD') + ' 00:00:00'
        const endTime = this.$moment(this.dateRange[1]).format('YYYY-MM-DD') + ' 23:59:59'
        data.beginDate = startTime
        data.endDate = endTime
      }
      Object.keys(data).forEach(function(key) {
        if (data[key] === '') {
          delete data[key]
        }
      })
      this.$emit('filterMsg', data)
    },
    handleRest() {
      const data = this.$global.deepClone(this.listQuery)
      Object.keys(data).forEach(function(key) {
        data[key] = ''
      })
      this.listQuery = data
      this.dateRange = ['', '']
      console.log('重置成功', this.listQuery)
    }
  }
}
</script>

<style  scoped lang='scss'>
.filter-item{
  margin-left: 10px;
  display: inline-block;
}
.filter-container .filter-item:nth-of-type(1){
  margin-left: 0px;
}
.btn{
  display: inline-block;
  margin-left: 10px;
}
</style>

tablePane.vue

ясная цель

Реализовать строки табличных функций, реализовать базовые табличные функции и реализовать функции подкачки.

Сопоставление структуры входящих данных

  dataSource: {
          tool:[
            {
              name: '新增用户', //按钮名称
              key: 1,  // 唯一标识符
              permission: 2010106, // 权限点
              type: '',  // 使用element自带按钮类型
              bgColor: '#67c23a', // 自定义背景色
              handleClick: this.handleAdd //自定义事件
            },
          ]
         data: [], // 表格数据
         cols: [], // 表格的列数据
         isSelection: false, // 表格有多选时设置
         selectable: function(val) {//禁用部分行多选
          if (val.isVideoStatus === 1) {
            return false
          } else {
            return true
          }
        },
         handleSelectionChange:(val)=>{} //点击行选中多选返回选中数组
         isOperation: true, // 表格有操作列时设置
         isIndex: true, // 列表序号
         loading: true, // loading
         pageData: {
          total: 0, // 总条数
          pageSize: 10, // 每页数量
          pageNum: 1 // 页码
         }
         operation: {
           // 表格有操作列时设置
           label: '操作', // 列名
           width: '350', // 根据实际情况给宽度
           data: [
             {
               label: '冻结', // 操作名称
               permission:'' //权限点
               type: 'info', //按钮类型
               handleRow: function(){} // 自定义事件
             },
           ]
         }
       },

tool

  • ТипыArray
  • По умолчанию[ ]

Настройка панели инструментов таблицы

 dataSource: {
         tool:[
           {
             name: '新增用户', //按钮名称
             key: 1,  // 唯一标识符
             permission: 2010106, // 权限点
             type: '',  // 使用element自带按钮类型
             bgColor: '#67c23a', // 自定义背景色
             handleClick: this.handleAdd //自定义事件
           },
         ]
  }

cols

  • ТипыArray
  • По умолчанию[ ]

заголовок конфигурации

 dataSource: {
         cols:[
            {
               label: '标题',                       //列名
               prop: 'belongUserId',                //字段名称
               width: 100                           //列宽度
            },
            {
               label: '副标题(季)',
               prop: 'subtitle',
               isCodeTableFormatter: function(val) {//过滤器
                 if (val.subtitle === 0) {
                   return '无'
                 } else {
                   return val.subtitle
                 }
               },
               width: 100
            },
             {
               label: '创建时间',
               prop: 'createTime',
               isCodeTableFormatter: function(val) {//时间过滤器
                 return timeFormat(val.createTime)
               },
               width: 150
             }
         ]
  }

pageData

  • ТипыObject
  • По умолчанию{ }

Пейджинг конфигурации

 dataSource: {
        pageData: {
         total: 0, // 总条数
         pageSize: 10, // 每页数量
         pageNum: 1, // 页码
         pageSize:[5,10,15,20]// 每页数量
        }
 }

operation

  • ТипыObject
  • По умолчанию{ }

Настройка столбцов действий

dataSource: {
       operation: {
         // 表格有操作列时设置
         label: '操作', // 列名
         width: '350', // 根据实际情况给宽度
         data: [
           {
             label: '修改', // 操作名称
             permission:'1001' //权限点
             type: 'info', //按钮类型icon为图表类型
             handleRow: function(){} // 自定义事件
           },
           {
             label: '修改', // 操作名称
             permission:'1001' //权限点
             type: 'icon', //按钮类型icon为图表类型
             icon:'el-icon-plus'
             handleRow: function(){} // 自定义事件
           }
         ]
       }
}

Подробное объяснение элемента конфигурации tablePane.vue Cols

  • обычный столбец
cols:[
   {
       label: '标题',
       prop: 'title',
       width: 200
    }
]
  • Обычное изменение цвета шрифта столбца
cols:[
  {
    label: '状态',
    prop: 'status',
    isTemplate: function(val) {
      if (val === 1) {
        return '禁言中'
      } else {
        return '已解禁'
      }
    },
    isTemplateClass: function(val) {
      if (val === 1) {
        return 'color-red'
      } else {
        return 'color-green'
      }
    }
  }
]
  • фильтрующая колонка с фильтром
cols:[
   {
      label: '推送时间',
      prop: 'pushTime',
      isCodeTableFormatter: function(val) {
        return timeFormat(val.pushTime)
      }
    },
    {
      label: '状态',
      prop: 'status',
      isCodeTableFormatter: function(val) {
        if(val.status===1){
          return '成功'
        }else{
          return '失败'
        }
      }
    }
]
  • колонна с иконками
cols:[
  {
     label: '目标类型',
     prop: 'targetType',
     isIcon: true,
     filter: function(val) {
       if (val === 4) {
         return '特定用户'
       } else if (val === 3) {
         return '新注册用户'
       } else if (val === 2) {
         return '标签用户'
       } else if (val === 1) {
         return '全部用户'
       }
     },
     icon: function(val) {
       if (val === 4) {
         return 'el-icon-mobile'
       } else {
         return false
       }
     },
     handlerClick: this.handlerClick
   }
]

начать упаковку

<template>
 <div>
   <div v-if="dataSource.tool" class="tool">
     <el-button
       v-for="(item) in dataSource.tool"
       :key="item.key"
       v-permission="item.permission"
       class="filter-item"
       :style="{'background':item.bgColor,borderColor:item.bgColor}"
       :type="item.type || 'primary'"
       @click="item.handleClick(item.name,$event)"
     >
       {{ item.name }}
     </el-button>
   </div>
   <el-table
     ref="table"
     v-loading="dataSource.loading"
     style="width: 100%;"
     :class="{ 'no-data': !dataSource.data || !dataSource.data.length }"
     :data="dataSource.data"
     @row-click="getRowData"
     @selection-change="dataSource.handleSelectionChange"
   >
     <!-- 是否有多选 -->
     <el-table-column
       v-if="dataSource.isSelection"
       :selectable="dataSource.selectable"
       type="selection"
       :width="dataSource.selectionWidth || 50"
       align="center"
     />
     <!-- 是否需要序号 -->
     <el-table-column
       v-if="dataSource.isIndex"
       type="index"
       label="序号"
       width="55"
       align="center"
     />

     <template v-for="item in dataSource.cols">
       <!-- 表格的列展示 特殊情况处理 比如要输入框 显示图片 -->
       <el-table-column
         v-if="item.isTemplate"
         :key="item.prop"
         v-bind="item"
       >
         <template slot-scope="scope">
           <!-- 比如要输入框 显示图片等等 自己定义 -->
           <slot :name="item.prop" :scope="scope" />
         </template>
       </el-table-column>
      <!-- 需要特殊颜色显示字体-->
       <el-table-column
         v-if="item.isSpecial"
         :key="item.prop"
         v-bind="item"
         align="center"
       >
         <template slot-scope="scope">
           <span :class="item.isSpecialClass(scope.row[scope.column.property])">{{ item.isSpecial(scope.row[scope.column.property]) }}</span>
         </template>
       </el-table-column>
       <!-- 需要带图标的某列,带回调事件-->
       <el-table-column
         v-if="item.isIcon"
         :key="item.prop"
         v-bind="item"
         align="center"
       >
         <template slot-scope="scope">
           <span>
             <span>{{ item.filter(scope.row[scope.column.property]) }}</span>
             <i v-if="item.icon" :class="[item.icon(scope.row[scope.column.property]),'icon-normal']" @click="item.handlerClick(scope.row)" />
           </span>
           <!-- 比如要输入框 显示图片等等 自己定义 -->
           <slot :name="item.prop" :scope="scope" />
         </template>
       </el-table-column>
       <!-- 图片带tooltip -->
       <el-table-column
         v-if="item.isImagePopover"
         :key="item.prop"
         v-bind="item"
         align="center"
       >
         <template slot-scope="scope">
           <el-popover
             placement="right"
             title=""
             trigger="hover"
           >
             <img class="image-popover" :src="scope.row[scope.column.property]+'?x-oss-process=image/quality,q_60'" alt="">
             <img slot="reference" class="reference-img" :src="scope.row[scope.column.property]+'?x-oss-process=image/quality,q_10'" alt="">
           </el-popover>
         </template>
       </el-table-column>
       <!-- 大部分适用 -->
       <el-table-column
         v-if="!item.isImagePopover && !item.isTemplate && !item.isSpecial&&!item.isIcon"
         :key="item.prop"
         v-bind="item.isCodeTableFormatter ? Object.assign({ formatter: item.isCodeTableFormatter }, item) : item"
         align="center"
         show-overflow-tooltip
       />
     </template>
     <!-- 是否有操作列 -->
     <!-- 没有数据时候不固定列 -->
     <el-table-column
       v-if="dataSource.isOperation"
       :show-overflow-tooltip="dataSource.operation.overflowTooltip"
       v-bind="dataSource.data && dataSource.data.length ? { fixed: 'right' } : null"
       style="margin-right:20px"
       class-name="handle-td"
       label-class-name="tc"
       :width="dataSource.operation.width"
       :label="dataSource.operation.label"
       align="center"
     >
       <!-- UI统一一排放3个,4个以上出现更多 -->
       <template slot-scope="scope">
         <!-- 三个一排的情况,去掉隐藏的按钮后的长度 -->
         <template v-if="dataSource.operation.data.length > 0">
           <div class="btn">
             <div v-for="(item) in dataSource.operation.data" :key="item.label">
               <template v-if="item.type!=='icon'">
                 <el-button
                   v-permission="item.permission"
                   v-bind="item"
                   :type="item.type?item.type:''"
                   size="mini"
                   @click.native.prevent="item.handleRow(scope.$index, scope.row, item.label)"
                 >
                   {{ item.label }}
                 </el-button>
               </template>
               <template v-else>
                 <i :class="[icon,item.icon]" v-bind="item" @click="item.handleRow(scope.$index, scope.row, item.label)" />
               </template>
             </div>
           </div>
         </template>
       </template>
     </el-table-column>
   </el-table>
   <div class="page">
     <el-pagination
       v-if="dataSource.pageData.total>0"
       :current-page="dataSource.pageData.pageNum"
       :page-sizes="dataSource.pageData.pageSizes?dataSource.pageData.pageSizes:[5,10,15,20]"
       :page-size="dataSource.pageData.pageSize"
       layout="total, sizes, prev, pager, next, jumper"
       :total="dataSource.pageData.total"
       @size-change="handleSizeChange"
       @current-change="handleCurrentChange"
     />
   </div>
 </div>
</template>

<script>
//  dataSource: {
//          tool:[
//            {
//              name: '新增用户', //按钮名称
//              key: 1,  // 唯一标识符
//              permission: 2010106, // 权限点
//              type: '',  // 使用element自带按钮类型
//              bgColor: '#67c23a', // 自定义背景色
//              handleClick: this.handleAdd //自定义事件
//            },
//          ]
//         data: [], // 表格数据
//         cols: [], // 表格的列数据
//         handleSelectionChange:(val)=>{} //点击行选中多选返回选中数组
//         isSelection: false, // 表格有多选时设置
//         isOperation: true, // 表格有操作列时设置
//         isIndex: true, // 列表序号
//         loading: true, // loading
//         pageData: {
//          total: 0, // 总条数
//          pageSize: 10, // 每页数量
//          pageNum: 1, // 页码
//          pageSize:[5,10,15,20]// 每页数量
//         }
//         operation: {
//           // 表格有操作列时设置
//           label: '操作', // 列名
//           width: '350', // 根据实际情况给宽度
//           data: [
//             {
//               label: '冻结', // 操作名称
//               permission:'' //权限点
//               type: 'info', //按钮类型
//               handleRow: function(){} // 自定义事件
//             },
//           ]
//         }
//       },
export default {
 // 接收父组件传递过来的值
 props: {
   //  表格数据和表格部分属性的对象
   // eslint-disable-next-line vue/require-default-prop
   dataSource: {
     type: Object
   }
 },
 data() {
   return {

   }
 },
 watch: {
   'dataSource.cols': { // 监听表格列变化
     deep: true,
     handler() {
       // 解决表格列变动的抖动问题
       this.$nextTick(this.$refs.table.doLayout)
     }
   }
 },
 methods: {
   handleAdd(name) {
     console.log(name)
     this.$emit('toolMsg', name)
   },
   handleRow(index, row, lable) {
     console.log(index, row, lable)
   },
   handleSizeChange(val) {
     this.$emit('changeSize', val)
     console.log(`每页 ${val} 条`)
   },
   handleCurrentChange(val) {
     this.$emit('changeNum', val)
     console.log(`当前页: ${val}`)
   },
   // 点击行即可选中
   getRowData(row) {
     this.$refs.table.toggleRowSelection(row)
   }
 }
}
</script>
<style lang="scss" scoped>
.page{
 margin-top: 20px;
}
.btn{
 display: flex;
 justify-content: center;
}
.btn div{
 margin-left: 5px;
}
.reference-img{
 width: 40px;
 height: 40px;
 background-size:100% 100%;
 border-radius: 4px;
}
.image-popover{
 width: 200px;
 height: 200px;
 background-size:100% 100%;
}
.icon {
 width: 25px;
 font-size: 20px;
 font-weight: bold;
}
</style>

настоящий бой

Чтобы настроить страницу, давайте сначала посмотрим, проще и организованнее ли настроить изображение.

1.png

2.png

<template>
  <div class="app-container">
    <filter-pane :filter-data="filterData" @filterMsg="filterMsg" />
    <table-pane
      :data-source="dataSource"
      @changeSize="changeSize"
      @changeNum="changeNum"
    />
    <add :dialog-add="dialogAdd" @childMsg="childMsg" />
  </div>
</template>
<script>
import filterPane from '@/components/Table/filterPane'
import tablePane from '@/components/Table/tablePane'
import add from './components/add'
import { getVersionList, delVersion } from '@/api/user'
import { timeFormat } from '@/filters/index'
export default {
  name: 'Suggestion',
  components: { filterPane, tablePane, add },
  data() {
    return {
      // 搜索栏配置
      filterData: {
        timeSelect: false,
        elselect: [
          {
            name: '状态',
            width: 120,
            key: 'platform',
            option: [
              {
                key: '全部',
                value: '全部'
              },
              {
                key: 1,
                value: 'IOS'
              },
              {
                key: 2,
                value: '安卓'
              }
            ]
          }
        ]
      },
      // 表格配置
      dataSource: {
        tool: [{
          name: '新增版本',
          key: 1,
          permission: 2010701,
          handleClick: this.handleAdd
        }],
        data: [], // 表格数据
        cols: [
          {
            label: '发布时间',
            prop: 'appIssueTime',
            isCodeTableFormatter: function(val) {
              return timeFormat(val.appIssueTime)
            }
          },
          {
            label: 'APP名称',
            prop: 'appName'
          },
          {
            label: 'APP版本',
            prop: 'appVersion'
          },
          {
            label: '平台',
            prop: 'appPlatform',
            isCodeTableFormatter: function(val) {
              if (val.appPlatform === 1) {
                return 'IOS'
              } else {
                return 'Android'
              }
            }
          },
          {
            label: '是否自动更新',
            prop: 'appAutoUpdate',
            isCodeTableFormatter: function(val) {
              if (val.appAutoUpdate === 1) {
                return '是'
              } else {
                return '否'
              }
            }
          },
          {
            label: '更新描述',
            prop: 'appDesc',
            width: 300
          },
          {
            label: '下载地址',
            prop: 'downloadAddr'
          },
          {
            label: '发布人',
            prop: 'userName'
          }
        ], // 表格的列数据
        handleSelectionChange: this.handleSelectionChange,
        isSelection: false, // 表格有多选时设置
        isOperation: true, // 表格有操作列时设置
        isIndex: true, // 列表序号
        loading: true, // loading
        pageData: {
          total: 0, // 总条数
          pageSize: 10, // 每页数量
          pageNum: 1 // 页码
        },
        operation: {
          // 表格有操作列时设置
          label: '操作', // 列名
          width: '100', // 根据实际情况给宽度
          data: [
            {
              label: '删除', // 操作名称
              type: 'danger',
              permission: '2010702', // 后期这个操作的权限,用来控制权限
              handleRow: this.handleRow
            }
          ]
        }
      },
      dialogAdd: false,
      msg: {},
      selected: []
    }
  },
  created() {
    this.getList()
  },
  methods: {
    // 获取列表数据
    getList() {
      const data = {
        pageSize: this.dataSource.pageData.pageSize,
        pageNum: this.dataSource.pageData.pageNum
      }
      if (this.msg) {
        if (this.msg.platform === 'IOS') {
          data.platform = 1
        } else if (this.msg.platform === '安卓') {
          data.platform = 2
        }
      }
      this.dataSource.loading = true
      getVersionList(data).then(res => {
        this.dataSource.loading = false
        if (res.succeed) {
          if (res.data.total > 0) {
            this.dataSource.pageData.total = res.data.total
            this.dataSource.data = res.data.data
          } else {
            this.dataSource.data = []
            this.dataSource.pageData.total = 0
          }
        }
      })
    },
    // 搜索层事件
    filterMsg(msg) {
      this.msg = msg
      if (Object.keys(msg).length > 0) {
        this.getList(msg)
      } else {
        this.getList()
      }
    },
    // 子组件通信
    childMsg(msg) {
      if (msg.dialogAdd === false) {
        this.dialogAdd = false
      } else if (msg.refreshList) {
        this.getList()
      }
    },
    // 改变每页数量
    changeSize(size) {
      this.dataSource.pageData.pageSize = size
      this.getList()
    },
    // 改变页码
    changeNum(pageNum) {
      this.dataSource.pageData.pageNum = pageNum
      this.getList()
    },
    // 多选事件
    handleSelectionChange(val) {
      this.selected = val
    },
    // 表格上方工具栏回调
    handleAdd(index, row) {
      this.dialogAdd = true
    },
    // 表格操作列回调
    handleRow(index, row, lable) {
      if (lable === '删除') {
        this.$confirm('确认删除该版本?', '温馨提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning'
        }).then(() => {
          delVersion({ versionId: row.id }).then(res => {
            if (res.succeed) {
              this.$message.success('删除成功')
              this.getList()
            }
          })
        }).catch(() => {
        })
      }
    }
  }
}
</script>

<style  scoped lang='scss'>

</style>

конец

filterPane.vue,tablePane.vueЗавершено, некоторые специальные страницы нужно скопировать только на текущую специальную страницу.componentsизменить его в

Все в порядке, он все еще находится в процессе постоянного улучшения. Если у вас есть какие-либо вопросы, вы можете задать их для дальнейшей оптимизации.

Полный исходный файл находится по адресуgitHub, могут быть загружены и использованы напрямую, и будут постоянно обновляться в будущем.

я дал имяk-tableкkначать значит быстро

k-tableЕсли это вам поможет, пожалуйста, зажгите свою звездочку ⭐⭐⭐ о~ (сумасшедший намек)

Супер простой API с практическими примерами, которые научат вас его использовать!

напиши в конце

якрутой город а, фронтенд, любит технологии и любит жизнь.

Я очень счастлив встретить тебя.

Если вы хотите узнать больше, пожалуйста, нажмите здесь, с нетерпением жду вашего маленького ⭐⭐

  • Если в статье есть ошибки, исправьте их в комментариях, если статья вам поможет, ставьте лайк и подписывайтесь 😊

  • Эта статья была впервые опубликована на Наггетс, перепечатка без разрешения запрещена 💌