瀏覽代碼

新增 chain 页面

会飞的电脑 2 年之前
父節點
當前提交
0de043ac0c

+ 13 - 0
src/App.vue

@@ -12,6 +12,7 @@
         <v-tab key="history" @change="historyChange">History</v-tab>
         <v-tab key="pending" @change="pendingChange">Pending</v-tab>
         <v-tab key="hddress" @change="hddressChange">Hddress</v-tab>
+        <v-tab key="chain" @change="chainChange">Chain</v-tab>
       </v-tabs>
 
       <v-spacer></v-spacer>
@@ -71,6 +72,9 @@
         <v-tab-item key="hddress">
           <Hddress :chain="chain" ref="hddress"></Hddress>
         </v-tab-item>
+        <v-tab-item key="chain">
+          <Chain_Page :chain="chain" ref="chain"></Chain_Page>
+        </v-tab-item>
       </v-tabs-items>
     </v-main>
   </v-app>
@@ -82,6 +86,7 @@ import History from '@/components/History'
 import Pending from '@/components/Pending'
 import houliang from '@/components/hl/houliang'
 import Hddress from '@/components/Hddress'
+import Chain_Page from '@/components/Chain'
 
 import HttpKit from "@/plugins/kit/HttpKit";
 import Chain from '@/plugins/model/Chain.js'
@@ -94,6 +99,7 @@ export default {
     Pending,
     houliang,
     Hddress,
+    Chain_Page,
   },
   data: () => ({
     tab: 'history',
@@ -145,6 +151,13 @@ export default {
       }
       this.$refs.hddress.prePackQuery()
     },
+    chainChange() {
+      if (this.calcPwdMD5() !== this.pwdMD5 || this.$refs.chain === undefined) {
+        return
+      }
+      this.$refs.chain.prePackQuery()
+    },
+
   },
   async mounted() {
     const rst = await Chain.getAll(null)

+ 139 - 0
src/components/Chain.vue

@@ -0,0 +1,139 @@
+<template>
+  <v-card elevation="1">
+    <!-- 顶部组件 -->
+    <Top :query='query' :page='page' :table='table' :visible="editDialog.visible"></Top>
+
+    <!-- 中间表格组件 -->
+    <Table :query='query' :page='page' :table='table' :chainModel="chainModel" :explorer="chain.explorer"
+           :editDialog="editDialog" ref="table"></Table>
+  </v-card>
+</template>
+
+<script>
+import Top from '@/components/viewer/Chain/Top.vue'
+import Table from '@/components/viewer/Chain/Table.vue'
+import TxModel from '@/plugins/model/TxModel'
+import Chain from '@/plugins/model/Chain.js'
+
+
+export default {
+  name: 'Chain',
+  components: {Top, Table},
+  props: ['chain'],
+  data: () => ({
+    chainModel: undefined,
+    editDialog: {
+      item: {},
+      visible: false,
+      addOrUpdate: true,
+    },
+    query: {
+      // hddress: {
+      //   name: '',
+      //   hash: process.env.NODE_ENV === 'development' ? '0x1a97a8fe340933a8b050dd3d1c823116d31178a253a74ea5a42ea909b37901c4' : '',
+      // },
+      // tx: {
+      //   block: '',
+      //   from: '',
+      //   to: ''
+      // },
+      // transfer: {
+      //   from: '',
+      //   to: '',
+      //   token: ''
+      // },
+      // autoFlushTime: 0
+    },
+    page: {
+      name: 'Chain Page'
+    },
+    table: {
+      search: '',
+      loading: false,
+      groupBy: '',
+      groupDesc: true,
+      sortBy: [],
+      sortDesc: [],
+      pageSize: 500,
+      pageNum: 1,
+      pageLength: 1,
+      data: [],
+      headers: [
+        {text: 'Option', value: 'option', width: '5%'},
+        {text: 'Id', value: 'id', width: '10%'},
+        {text: 'Chain', value: 'chain'},
+        {text: 'TokenSymbol', value: 'tokenSymbol', width: '5%'},
+        {text: 'NetworkName', value: 'networkName',},
+        {text: 'Ws', value: 'ws', width: '5%'},
+        {text: 'V3ToolAddress', value: 'v3ToolAddress', width: '5%'},
+        {text: 'V2ToolAddress', value: 'v2ToolAddress', width: '5%'},
+        {text: 'Ipc', value: 'ipc', width: '5%'},
+        {text: "Http", value: 'http'},
+        {text: 'Explorer', value: 'explorer', width: '10%'},
+      ]
+    },
+  }),
+  methods: {
+    // 获取数据
+    async generateTableData() {
+      this.createAddressModel()
+
+      this.table.loading = true
+      this.table.data.length = 0
+
+      const rst = await Chain.getAll(null)
+
+      if (rst.state) {
+        this.$msgkit.normal(rst.msg)
+        this.table.data = rst.data
+      }
+
+      // const rst = await this.addressModel.find(this.table.pageNum, this.table.pageSize)
+      //
+      //
+      // if (rst.state) {
+      //   this.table.data = rst.data
+      //   this.$msgkit.success(rst.msg)
+      // } else {
+      //   this.$msgkit.error(rst.msg)
+      // }
+
+      this.table.loading = false
+    },
+    createAddressModel() {
+      if (!this.chainModel) this.chainModel = new Chain(this.chain.id, TxModel.MODULES.CHAIN)
+    },
+    //设置 新增按钮编辑
+    showVisible() {
+      this.editDialog.item = {id: "", chain: "", tokenSymbol: "", networkName: "", http: "", explorer: "", other: "{}"}
+      this.editDialog.visible = true
+      this.editDialog.addOrUpdate = true
+    },
+    async generatePageCount() {
+
+    },
+
+    async prePackQuery() {
+      // await this.$refs.table.remButClick()
+    },
+    async packQuery() {
+      await this.prePackQuery()//查询数据之前移除 多余的临时标签
+      await this.generateTableData()//拿到数据
+      // this.generatePageCount()
+      await this.afterPackQuery()//添加标签 ,添加点击按钮事件
+    },
+    async afterPackQuery() {
+    },
+
+  },
+  provide() {
+    return {
+      packQuery: this.packQuery,
+      showVisible: this.showVisible
+    }
+  },
+  async mounted() {
+    await this.packQuery()
+  }
+}
+</script>

+ 12 - 3
src/components/Hddress.vue

@@ -55,7 +55,7 @@ export default {
       sortDesc: [true, false],
       pageSize: process.env.NODE_ENV === 'development' ? 100 : 200,
       pageNum: 1,
-      pageLength:99,
+      pageLength: 99,
       data: [],
       headers: [
         {text: 'Option', value: 'option', width: '5%'},
@@ -80,9 +80,7 @@ export default {
 
 
       if (rst.state) {
-        console.log("data--:", rst)
         this.table.data = rst.data
-        console.log("data--_:", this.table.data)
         this.$msgkit.success(rst.msg)
       } else {
         this.$msgkit.error(rst.msg)
@@ -99,6 +97,16 @@ export default {
       this.editDialog.visible = true
       this.editDialog.addOrUpdate = true
     },
+    async generatePageCount() {
+      const rstCount = await this.addressModel.paginateCount()
+      if (rstCount.state && rstCount.data > 0) {
+        console.log("当前 分页数据,index:" + this.table.pageNum +
+            "\t 每页数据:" + this.table.pageSize +
+            "\t总数据量:" +rstCount.data +
+        "\t因有分页:"+(rstCount.data/this.table.pageSize))
+        // this.table.pageLength = rstCount.data / this.table.pageSize
+      }
+    },
 
     async prePackQuery() {
       // await this.$refs.table.remButClick()
@@ -106,6 +114,7 @@ export default {
     async packQuery() {
       await this.prePackQuery()//查询数据之前移除 多余的临时标签
       await this.generateTableData()//拿到数据
+      // this.generatePageCount()
       await this.afterPackQuery()//添加标签 ,添加点击按钮事件
     },
     async afterPackQuery() {

+ 310 - 0
src/components/viewer/Chain/Table.vue

@@ -0,0 +1,310 @@
+<template>
+  <div class="table-container">
+    <v-card elevation="0">
+      <!-- 表格上功能区 -->
+      <v-card-title>
+        <v-container id="dataTableHeader">
+          <v-row>
+            <v-col cols="4">
+              {{ page.name }}
+            </v-col>
+            <v-col cols="8">
+              <v-text-field hide-details single-line label="Local Search" append-icon="mdi-magnify"
+                            v-model="table.search"/>
+            </v-col>
+          </v-row>
+        </v-container>
+      </v-card-title>
+
+      <!-- 顶部分页 -->
+      <div class="mt-2">
+        <v-row>
+          <v-col cols="2"></v-col>
+          <v-col cols="2">
+            <v-text-field required type="number" label="page" v-model="tempPage" @change="inputPageNum"/>
+          </v-col>
+          <v-col cols="4">
+            <v-pagination color="teal" :disabled="table.loading" :length="table.pageLength" v-model="table.pageNum"
+                          @input="generateTableDataAgain"></v-pagination>
+          </v-col>
+        </v-row>
+      </div>
+
+      <!-- 表格主体 -->
+      <v-data-table calculate-widths multi-sort hide-default-footer
+                    :headers="table.headers" :items="table.data" :search="table.search" :loading="table.loading"
+                    :items-per-page="table.pageSize"
+                    :sort-by="table.sortBy" :sort-desc="table.sortDesc">
+
+        <!-- 操作区 -->
+        <template v-slot:item.option="{ item }">
+          <div>
+            <v-row>
+              <v-col cols="5">
+                <v-btn large icon elevation="0" color="primary" @click="editItem(item)">
+                  <v-icon>mdi-table-edit</v-icon>
+                </v-btn>
+              </v-col>
+            </v-row>
+          </div>
+        </template>
+        <template v-slot:item.networkName="{ item }">
+          <div>
+            {{ item.networkName }}
+          </div>
+        </template>
+        <template v-slot:item.ws="{ item }">
+          <div v-if="item.ws !== undefined && item.ws !== '' && item.ws !== null">
+            {{ item.ws }}
+          </div>
+          <div v-else>
+            [ws]
+          </div>
+        </template>
+
+
+        <template v-slot:item.v3ToolAddress="{ item }">
+          <div v-if="item.v3ToolAddress !== undefined && item.v3ToolAddress !== '' && item.v3ToolAddress !== null">
+            {{ item.v3ToolAddress }}
+          </div>
+          <div v-else>
+            [v3ToolAddress]
+          </div>
+        </template>
+        <template v-slot:item.v2ToolAddress="{ item }">
+          <div v-if="item.v2ToolAddress !== undefined && item.v2ToolAddress !== '' && item.v2ToolAddress !== null">
+            {{ item.v2ToolAddress }}
+          </div>
+          <div v-else>
+            [v2ToolAddress]
+          </div>
+        </template>
+        <template v-slot:item.tokenSymbol="{ item }">
+          <div v-if="item.tokenSymbol !== undefined && item.tokenSymbol !== '' && item.tokenSymbol !== null">
+            {{ item.tokenSymbol }}
+          </div>
+          <div v-else>
+            [tokenSymbol]
+          </div>
+        </template>
+
+        <template v-slot:item.ipc="{ item }">
+          <div v-if="item.ipc !== undefined && item.ipc !== '' && item.ipc !== null">
+            {{ item.ipc }}
+          </div>
+          <div v-else>
+            [ipc]
+          </div>
+        </template>
+        <template v-slot:item.id="{ item }">
+          <div v-if="item.id !== undefined && item.id !== '' && item.id !== null">
+            {{ item.id }}
+          </div>
+          <div v-else>
+            [id]
+          </div>
+        </template>
+        <template v-slot:item.http="{ item }">
+          <div v-if="item.http !== undefined && item.http !== '' && item.http !== null">
+            {{ item.http }}
+          </div>
+          <div v-else>
+            [http]
+          </div>
+        </template>
+        <template v-slot:item.explorer="{ item }">
+          <div v-if="item.explorer !== undefined && item.explorer !== '' && item.explorer !== null">
+            {{ item.explorer }}
+          </div>
+          <div v-else>
+            [explorer]
+          </div>
+        </template>
+        <template v-slot:item.chain="{ item }">
+          <div v-if="item.chain !== undefined && item.chain !== '' && item.chain !== null">
+            {{ item.chain }}
+          </div>
+          <div v-else>
+            [chain]
+          </div>
+        </template>
+      </v-data-table>
+
+      <!-- 底部分页 -->
+      <div class="mt-2">
+        <v-row>
+          <v-col cols="2"></v-col>
+          <v-col cols="2">
+            <v-text-field type="number" required label="page" v-model="tempPage" @change="inputPageNum"/>
+          </v-col>
+          <v-col cols="4">
+            <v-pagination :disabled="table.loading" :length="table.pageLength" v-model="table.pageNum"
+                          @input="generateTableDataAgain"></v-pagination>
+          </v-col>
+        </v-row>
+      </div>
+    </v-card>
+
+    <!-- 新增Address -->
+    <v-dialog v-model="editDialog.visible" max-width="1200">
+      <v-card elevation="0">
+        <v-card-title>{{ editDialog.addOrUpdate ? '新增' : '编辑' }} Address</v-card-title>
+        <v-card-text>
+          <v-row>
+            <v-col cols="12">
+              <v-text-field label="id" v-model="editDialog.item.id"></v-text-field>
+            </v-col>
+          </v-row>
+          <v-row>
+            <v-col cols="12">
+              <v-text-field label="chain" v-model="editDialog.item.chain"></v-text-field>
+            </v-col>
+          </v-row>
+          <v-row>
+            <v-col cols="12">
+              <v-text-field label="tokenSymbol" v-model="editDialog.item.tokenSymbol"></v-text-field>
+            </v-col>
+          </v-row>
+          <v-row>
+            <v-col cols="12">
+              <v-text-field label="networkName" v-model="editDialog.item.networkName"></v-text-field>
+            </v-col>
+          </v-row>
+          <v-row>
+            <v-col cols="12">
+              <v-text-field label="http" v-model="editDialog.item.http"></v-text-field>
+            </v-col>
+          </v-row>
+          <v-row>
+            <v-col cols="12">
+              <v-text-field label="explorer" v-model="editDialog.item.explorer"></v-text-field>
+            </v-col>
+          </v-row>
+          <v-row>
+            <v-col cols="12">
+              <v-text-field label="other" v-model="editDialog.item.other"></v-text-field>
+            </v-col>
+          </v-row>
+
+
+        </v-card-text>
+        <v-card-actions>
+          <v-spacer></v-spacer>
+          <v-btn text color="primary" @click="addOrUpdateChain">提交</v-btn>
+        </v-card-actions>
+      </v-card>
+    </v-dialog>
+  </div>
+</template>
+
+<script>
+import TradeInfo from '@/components/viewer/Chain/table/TradeInfoDetails'
+import BooleanViewer from '@/components/viewer/Chain/table/BooleanViewer'
+import HashKit from '@/plugins/kit/HashKit'
+import HttpKit from '@/plugins/kit/HttpKit'
+import TimeKit from '@/plugins/kit/TimeKit'
+
+import jquery from 'jquery'
+
+
+import Chain from '@/plugins/model/Chain.js'
+import AddressModel from "@/plugins/model/AddressModel";
+
+export default {
+  name: 'Table',
+  components: {BooleanViewer, TradeInfo},
+  props: ['query', 'page', 'table', 'chainModel', 'explorer', 'editDialog'],
+  inject: ['packQuery', 'showVisible'],
+  data: () => ({
+    hashKit: HashKit,
+    httpKit: HttpKit,
+    timeKit: TimeKit,
+    transferDetailsDialog: {
+      data: [],
+      visible: false
+    },
+    tempPage: 1,
+    dblclickRowUrl: "https://tools.blocksec.com/tx/eth/",
+  }),
+  methods: {
+    async addOrUpdateChain() {
+
+      const {id, chain, tokenSymbol, networkName, http, explorer, other} = this.editDialog.item
+      console.log("???", this.editDialog.item)
+      const updateOrAdd = {
+        id, chain, tokenSymbol, networkName, http, explorer,other
+      }
+
+      const rst1 = await this.chainModel.appendOrUpdateChain(updateOrAdd)
+      if (rst1.state) {
+        this.$msgkit.success((this.editDialog.addOrUpdate ? '添加' : '更新') + '成功')
+
+        this.editDialog.visible = false
+        await this.packQuery()
+      } else {
+        this.$msgkit.error(`rst1: ${rst1.msg}`)
+      }
+    },
+
+
+    async inputPageNum() {
+      this.table.pageNum = parseInt(this.tempPage)
+      await this.generateTableDataAgain()
+    },
+    async generateTableDataAgain() {
+      this.tempPage = this.table.pageNum
+      this.table.data = []
+
+      await this.packQuery()
+    },
+    formatTimeBySixBitTimestamp(sixBitTimestamp) {
+      let lastThreeBit = (sixBitTimestamp + '').slice(-3)
+      return this.timeKit.getTime(sixBitTimestamp) + lastThreeBit
+    },
+    editItem(item) {
+      this.editDialog.item = item
+      this.editDialog.visible = true
+      this.editDialog.addOrUpdate = false
+    },
+
+
+    remButClick() {
+      jquery(".v-row-group__header td.text-start").unbind('click')
+      console.log("移除点击事件:remButClick")
+    },
+  },
+  async mounted() {
+  }
+}
+</script>
+
+<style scoped>
+.table-container {
+  width: 100%;
+}
+
+#dataTableHeader {
+  max-width: none;
+}
+
+.tokenChip {
+  margin-top: 5px;
+  margin-left: 5px;
+  margin-bottom: 5px;
+}
+
+.tradeInfoBtn {
+  padding: 15px !important;
+  border: grey 2px solid;
+}
+
+.tradeInfoBtn:hover {
+  background: beige;
+}
+
+.memo-span {
+  width: 200px;
+  display: block;
+  padding: 10px;
+}
+</style>

+ 64 - 0
src/components/viewer/Chain/Top.vue

@@ -0,0 +1,64 @@
+<template>
+  <div class="top-container">
+    <v-row>
+      <!-- tx信息过滤 -->
+<!--      <v-col cols="18" md="2">-->
+<!--        <v-text-field required label="hddress.Hash" v-model="query.hddress.hash"/>-->
+<!--      </v-col>-->
+<!--      <v-col cols="18" md="2">-->
+<!--        <v-text-field required label="hddress.name" v-model="query.hddress.name"/>-->
+<!--      </v-col>-->
+
+      <v-col cols="18" md="2">
+        <v-btn outlined x-large tile color="primary" @click="generateTableDataAgain">
+          <v-icon left>mdi-cloud-search-outline</v-icon>
+          刷新
+        </v-btn >
+        <v-btn style='margin-left: 5px;' outlined x-large tile color="primary" @click="addClick()">
+          <v-icon left>mdi-cloud-search-outline</v-icon>
+          新增
+        </v-btn>
+      </v-col>
+
+    </v-row>
+
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'Top',
+  props: ['query', 'page', 'table', 'visible'],
+  inject: ['packQuery', 'showVisible'],
+  methods: {
+    async generateTableDataAgain() {
+      await this.packQuery()
+      console.log("---占位事件")
+    },
+    addClick() {
+      this.showVisible()
+    },
+  },
+  async mounted() {
+    let prevFlushTime = 0
+    setInterval(async function (top) {
+      if (top.query.autoFlushTime > 0) {
+        let now = parseInt(new Date().getTime() / 1000)
+
+        if (now - top.query.autoFlushTime > prevFlushTime) {
+          await top.generateTableDataAgain()
+
+          prevFlushTime = now
+        }
+      }
+    }, 1000, this)
+  }
+}
+</script>
+
+<style scoped>
+.top-container {
+  width: 95%;
+  margin: auto;
+}
+</style>

+ 15 - 0
src/components/viewer/Chain/table/BooleanViewer.vue

@@ -0,0 +1,15 @@
+<template>
+  <span v-if="value" class="teal--text">yes</span>
+  <span v-else class="pink--text">no</span>
+</template>
+
+<script>
+export default {
+  name: 'BooleanViewer',
+  props: ['value']
+}
+</script>
+
+<style scoped>
+
+</style>

+ 98 - 0
src/components/viewer/Chain/table/TradeInfoDetails.vue

@@ -0,0 +1,98 @@
+<template>
+  <div>
+    <v-container>
+      <div v-for="trade in item.transferList">
+        <v-row>
+          <!--token symbol-->
+          <!-- 没有名字的 -->
+          <v-chip v-if="!trade.tokenSymbol"
+                  label class="ma-2 tradeLabel" @click="httpKit.jumpToExplorer(trade.token, 'token')">
+            <div class="tokenName">{{ '**' + hashKit.headAndEnd2(trade.token) }}</div>
+            <div class="tokenAmount">{{ trade.amount }} </div>
+          </v-chip>
+          <!-- 有名字的和Ethereum/EthereumPow -->
+          <v-chip v-else
+                  label class="ma-2 tradeLabel" :color="hashKit.generateColorByHash(trade.token)"
+                  @click="httpKit.jumpToExplorer(trade.token, 'token')">
+            <div class="tokenName">{{ trade.tokenSymbol === '' ? '[no name]' : trade.tokenSymbol }}</div>
+            <div class="tokenAmount">{{ numKit.getSubFloat(trade.amount, 4) }}</div>
+          </v-chip>
+
+
+          <!--from-->
+          <div>
+            <v-chip v-if="trade.from === item.from"
+                    label class="ma-2" color="green lighten-3" @click="httpKit.jumpToExplorer(trade.from)">
+              {{ '[F] ' + hashKit.headAndEnd2(trade.from) }}
+            </v-chip>
+            <v-chip v-else-if="trade.from === item.to"
+                    label class="ma-2" color="indigo lighten-4" @click="httpKit.jumpToExplorer(trade.from)">
+              <div v-if="!trade.fromName">{{ '[T] ' + hashKit.headAndEnd2(trade.from) }}</div>
+              <div v-else>{{ '[T] ' + trade.fromName }}</div>
+            </v-chip>
+            <v-chip v-else
+                    label class="ma-2" :color="hashKit.generateColorByHash(trade.from)"
+                    @click="httpKit.jumpToExplorer(trade.from)">
+              <div v-if="!trade.fromName">{{ hashKit.headAndEnd2(trade.from) }}</div>
+              <div v-else>{{ trade.fromName }}</div>
+            </v-chip>
+          </div>
+          <v-icon>mdi-arrow-expand-right</v-icon>
+          <!--to-->
+          <div>
+            <v-chip v-if="trade.to === item.from"
+                    label class="ma-2" color="green lighten-3" @click="httpKit.jumpToExplorer(trade.to)">
+              {{ '[F] ' + hashKit.headAndEnd2(trade.to) }}
+            </v-chip>
+            <v-chip v-else-if="trade.to === item.to"
+                    label class="ma-2" color="indigo lighten-4" @click="httpKit.jumpToExplorer(trade.to)">
+              <div v-if="!trade.toName">{{ '[T] ' + hashKit.headAndEnd2(trade.to) }}</div>
+              <div v-else>{{'[T] ' +  trade.toName }}</div>
+            </v-chip>
+            <v-chip v-else
+                    label class="ma-2" :color="hashKit.generateColorByHash(trade.to)" @click="httpKit.jumpToExplorer(trade.to)">
+              <div v-if="!trade.toName">{{ hashKit.headAndEnd2(trade.to) }}</div>
+              <div v-else>{{ trade.toName }}</div>
+            </v-chip>
+          </div>
+        </v-row>
+        <v-row>
+          <v-divider></v-divider>
+        </v-row>
+      </div>
+    </v-container>
+  </div>
+</template>
+
+<script>
+import NumKit from '@/plugins/kit/NumKit'
+import HashKit from '@/plugins/kit/HashKit'
+import HttpKit from '@/plugins/kit/HttpKit'
+
+export default {
+  name: 'TradeInfo',
+  props: ['item'],
+  data: () => ({
+    numKit: NumKit,
+    hashKit: HashKit,
+    httpKit: HttpKit
+  })
+}
+</script>
+
+<style>
+.tradeLabel {
+  width: 250px;
+}
+div.tokenName {
+  text-align: left;
+  width: 100%;
+}
+div.tokenAmount {
+  text-align: right;
+  width: 100%;
+}
+span.v-chip__content {
+  width: 100%;
+}
+</style>

+ 1 - 5
src/components/viewer/hddress/Table.vue

@@ -164,7 +164,6 @@ export default {
   }),
   methods: {
     async addOrUpdateAddress() {
-      // this.createAddressModel()
 
       const {hash, name} = this.editDialog.item
       const updateOrAdd = {hash, name}
@@ -198,10 +197,7 @@ export default {
       this.editDialog.visible = true
       this.editDialog.addOrUpdate = false
     },
-    createAddressModel() {
-      if (!this.addressModel)
-        this.addressModel = new AddressModel(this.tx.chainId, AddressModel.MODULES.unknown)
-    },
+
 
     remButClick(){
       jquery(".v-row-group__header td.text-start").unbind('click')

+ 8 - 0
src/plugins/model/AddressModel.js

@@ -33,4 +33,12 @@ export default class AddressModel {
         return rst.data
     }
 
+    async paginateCount() {
+        const url = `/${this.module}/countByChainId`
+        const rst = await http.post(url, {
+            chainId: this.chainId,
+        })
+
+        return rst.data
+    }
 }

+ 18 - 0
src/plugins/model/Chain.js

@@ -13,5 +13,23 @@ export default class EthMev {
         return rst.data
     }
 
+    static MODULES = {
+        unknown: 'unknown'
+    }
+
+    constructor(chainId, module) {
+        if (!chainId || !module) throw "Must have [chainId, module]."
+
+        this.chainId = chainId
+        this.module = module
+    }
+
+
+    async appendOrUpdateChain(item) {
+        const url = `/chain/appendOrUpdate`
+        const rst = await http.post(url, item)
+
+        return rst.data
+    }
 
 }

+ 2 - 1
src/plugins/model/TxModel.js

@@ -4,7 +4,8 @@ export default class TxModel {
     static MODULES = {
         HISTORY: 'history',
         PENDING: 'pending',
-        ADDRESS: 'address'
+        ADDRESS: 'address',
+        CHAIN: 'chain'
     }
 
     constructor(chainId, module) {